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:

  1. Add a variant to the PostingRequest union.
  2. Build a PostingHandler implementing buildLines() (and optionally postSubledgers()).
  3. Register the handler in the relevant module's startup wiring.

No engine changes.

Invariants Block 1.B introduces

  1. 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.
  2. 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.
  3. Validation is structured. Failures come back as a ValidationException with a failures: [{code, message}] array — UIs can map errors to specific fields instead of free-text parsing.
  4. Handlers are pure. No DB calls inside buildLines. Side effects only inside postSubledgers, which receives the open transaction.
  5. 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.ts Mudarabah subscription posting still calls journalEntriesService.create directly. 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 taskDepends on
1.D.1 Subscription refactorPostingEngine.post + ValidationEngine.registerRules('subscription', ...) + SubscriptionHandler implementing postSubledgers to write client + fund subledger rows
1.D.2 Redemption lifecycleSame as above for redemption variant
1.D.3 Equity trade handlerBLOCK_HARAM_SECTOR Shariah rule (already active) + a per-module validation rule that asserts the security is screened
1.D.4 Sukuk trade handlerSame 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 failsValidationException with full failures[], no GL/subledger writes, tx rolls back.
  • Shariah rejectsForbiddenException with violations[], no writes.
  • Handler not registeredBadRequestException clearly stating the type. (Indicates a wiring bug, not a runtime data issue.)
  • Handler postSubledgers throws → 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.