Workflow
- Map the domain: list financial objects in scope, trace where data enters, and document balance source of truth, lifecycle states, identifiers, rounding rules, and audit records.
- State the invariants before editing (e.g. "posted ledger entries are immutable"). Inspect current behavior first; ask the user only when code/tests don't reveal the rule.
- Test the invariants through public services/APIs/commands/jobs: normal posting, boundary amounts, currency mismatch, invalid state transitions, reversal/correction, retried operations.
- Implement defensively: typed value objects/schemas/DB constraints, checks near the write boundary, DB transactions for multi-record writes, domain errors naming the invariant.
Core Invariants
- Money uses integer minor units, fixed-precision decimal, or an existing money type — never floats.
- Currency is explicit at every boundary and in storage.
- Rounding rules are named and centralized: mode, precision, timing, jurisdiction.
- Ledger records are append-only unless corrections are explicitly allowed.
- Debits and credits balance for every posted journal, transfer, or settlement batch.
- Account/transaction states form an explicit state machine with valid transitions only.
- Reversals, refunds, chargebacks, cancellations, and corrections leave an audit trail.
- External identifiers are preserved for reconciliation and dispute investigation.
- Time-sensitive rules use controlled clocks and explicit effective dates.
Guardrails
- Do not silently change rounding, precision, account state, or balance semantics.
- Do not add derived balance fields without a rebuild/reconcile plan.
- Do not fix historical data in code unless the user asks for a migration/repair script.
- Financial correctness outranks a small reduction in code complexity.