Skip to content

Posting rules DSL

The execution layer for PAY-007, DT-012, and OPS-007. Converts policy intent into reviewable, version-controlled configuration rather than scattered code paths.

Design goals

  • Readable by finance, product, risk, and engineering.
  • Version-controlled and promotable through environments.
  • Deterministic: the same rule package and the same input event always produce the same posting outcome.
  • Explainable: every decision returns human-readable reason codes, machine-readable codes, and affected balance states.
  • Safe to retry: rule execution is idempotent and never duplicates a financial effect.

Core objects

Object Meaning Minimum fields
event Business event received for processing event_type, event_id, idempotency_key, effective_at, customer/account refs, amount, currency
rule_package Versioned collection of posting rules active for a domain or product package_id, version, effective_from, compatibility
rule One executable mapping from event + conditions → outcomes rule_code, priority, predicates, posting_template, reason_codes
posting_template Balanced ledger legs to create when rule fires legs, ledger_accounts, balance_effect, settlement_refs
leg One debit or credit line created by a posting template account_ref, side, amount_expr, currency_expr, tags
decision Execution result returned to caller and logged decision, reason_codes, posting_set_id, publication_refs
publication_contract Downstream event shape for consumers schema_version, topic, mandatory fields, latency_objective

Example rule package

package: ledger-core
version: 1.0.0
effective_from: 2026-04-01T00:00:00Z

rules:
  - rule_code: CARD_AUTH_HOLD
    event_type: card.auth.approved
    idempotency_scope: event_id
    predicates:
      - account.state in [ACTIVE]
      - amount > 0
      - available_balance >= amount
    posting_template:
      posting_set_type: pending_hold
      legs:
        - account_ref: customer.available_holds
          side: debit
          amount_expr: event.amount
          currency_expr: event.currency
        - account_ref: bank.pending_settlement
          side: credit
          amount_expr: event.amount
          currency_expr: event.currency
    decision:
      status: approved
      reason_codes:
        - code: AUTH_HOLD_CREATED
          human_text: "Card authorisation hold created"
    publish:
      event_name: ledger.pending_hold.created
      schema_version: 1

  - rule_code: MONTHLY_ACCOUNT_FEE
    event_type: account.fee.monthly
    idempotency_scope: event_id
    predicates:
      - account.state in [ACTIVE, RESTRICTED]
      - product.fee_plan == STANDARD
    posting_template:
      posting_set_type: posted_fee
      legs:
        - account_ref: customer.liability
          side: debit
          amount_expr: event.amount
          currency_expr: account.currency
        - account_ref: bank.fee_income
          side: credit
          amount_expr: event.amount
          currency_expr: account.currency
    decision:
      status: posted
      reason_codes:
        - code: FEE_MONTHLY_CHARGED
          human_text: "Monthly account fee charged"
    on_failure:
      suspense_account: bank.fee_suspense
      alert_code: FEE_POSTING_FAILURE

Execution flow

  1. Receive event and validate event schema.
  2. Resolve rule package by domain, product, jurisdiction, and effective date.
  3. Enforce idempotency before rule evaluation.
  4. Evaluate predicates; if none match, return a controlled rejection with reason codes.
  5. Build posting template and validate that total debits equal total credits.
  6. Execute posting set atomically.
  7. Update derived balances and account state side effects.
  8. Write immutable transaction-log records.
  9. Publish downstream events using the governed publication contract.
  10. Return decision object with reason codes, posting-set identifier, and any human-review flags.

Required decision payload

Field Requirement
decision_status approved | posted | rejected | reversed | routed_to_suspense
reason_codes At least one machine-readable code and one human-readable text
posting_set_id Stable immutable identifier for the generated posting set
affected_balances Current / available / pending deltas where relevant
rule_code and package_version Required for audit, support, and rollback
publication_refs IDs of downstream events emitted

Mandatory guardrails

  • No rule may create an unbalanced posting set.
  • No rule may write directly to a historical posting.
  • Every reversal must link to the original posting set.
  • No schema-breaking publication change may be deployed without a version increment.
  • Every rule promotion must include simulation evidence against historical event samples.
  • Any rule failure after event acceptance must result in explicit rejection, compensating action, or suspense routing.

Suggested initial rule packs

Pack Coverage
Deposit and withdrawal posting pack Inbound credits, outbound debits, account-to-account
Card authorisation, presentment, reversal, and expiry pack Full card hold lifecycle
Account fee and fee waiver pack Monthly/annual fees, fee waivers, fee reversal
Interest accrual and capitalisation pack Daily accrual, capitalisation events
FX conversion and cross-border wallet pack Cross-border debits, matched currency legs
Account closure and dormancy pack Zero-balance closure, dormancy state transitions

Governance

Rule packages are version-controlled artefacts owned by SD01 (Core Banking). Promotions through environments follow the standard deployment pipeline. Rule changes that affect posted balance types or published event shapes require ADR review before promotion.

Related policies: PAY-007 · DT-012 · OPS-007

Related architecture: Data contracts and decision publication · ADR-036