Block 1.B — posting pipeline (architecture view)
For per-feature technical detail see posting engine and Shariah rules. For staff-facing flow see the business finance doc.
What Block 1.B closes
After Phase 0 the pipeline was one straight line: trigger → GL write. After Block 1.A there were subledgers, but no enforced contract that they got written alongside the GL. Block 1.B introduces the orchestrator that makes the contract real:
Everything inside the engine is wrapped in one Sequelize transaction. If subledger writes fail, the GL write rolls back too. That contract — which Block 1.A's subledger models were designed around — is now actually enforced.
Components
The handler-registry pattern
Handlers register themselves with the engine. Today only ManualPostingHandler is registered (in the engine's constructor). Future Blocks will register their handlers in the same way:
// Block 1.C
class NavPublishHandler implements PostingHandler<NavAdjustmentPosting> { ... }
// → registerHandler in NAV module's constructor
// Block 1.D
class EquityTradeHandler implements PostingHandler<EquityTradePosting> { ... }
class SubscriptionHandler implements PostingHandler<SubscriptionPosting> { ... }
// → register on startup
Adding a new event type means:
- Add a variant to the
PostingRequestunion. - Build a
PostingHandlerimplementingbuildLines()(and optionallypostSubledgers()). - Register the handler in the relevant module's startup wiring.
No engine changes.
Invariants Block 1.B introduces
- One transaction for GL + subledger. Both halves of a posting commit together or roll back together. The contract Block 1.A subledgers depended on now holds end-to-end.
- Shariah-gated by default. Every posting routed through the engine is evaluated. Block 1.D's trade handlers will use this to reject haram securities at the point of trade, not after.
- Validation is structured. Failures come back as a
ValidationExceptionwith afailures: [{code, message}]array — UIs can map errors to specific fields instead of free-text parsing. - Handlers are pure. No DB calls inside
buildLines. Side effects only insidepostSubledgers, which receives the open transaction. - Rule config in DB, behavior in code. Shariah thresholds and haram-sector lists live in JSONB
config. The code-level rule logic is fixed and Shariah-audit-reviewable.
What still bypasses the engine (intentional)
investments.service.tsMudarabah subscription posting still callsjournalEntriesService.createdirectly. Migration to the engine happens in Block 1.D (Task 1.D.1) behind a feature flag.chart-of-accounts/funds/ non-financial admin paths don't post journals, so they don't touch the engine.- The hash-chain verifier and the reconciliation engine are read paths — they consume the GL but don't produce postings.
How Block 1.B unblocks Block 1.D
| Block 1.D task | Depends on |
|---|---|
| 1.D.1 Subscription refactor | PostingEngine.post + ValidationEngine.registerRules('subscription', ...) + SubscriptionHandler implementing postSubledgers to write client + fund subledger rows |
| 1.D.2 Redemption lifecycle | Same as above for redemption variant |
| 1.D.3 Equity trade handler | BLOCK_HARAM_SECTOR Shariah rule (already active) + a per-module validation rule that asserts the security is screened |
| 1.D.4 Sukuk trade handler | Same shape as 1.D.3 |
Each of those handlers slots into the existing engine — Block 1.D adds no new orchestration code.
Failure modes explicitly handled
- Validation fails →
ValidationExceptionwith fullfailures[], no GL/subledger writes, tx rolls back. - Shariah rejects →
ForbiddenExceptionwithviolations[], no writes. - Handler not registered →
BadRequestExceptionclearly stating the type. (Indicates a wiring bug, not a runtime data issue.) - Handler
postSubledgersthrows → entire transaction rolls back; the GL journal that was created in step 5 is not committed. - DB conflict mid-transaction → Sequelize aborts, engine surfaces the underlying error, nothing partial.