# ADR 0003: Financial Ledger Invariants and Posting Rulebook

- Status: Accepted
- Date: 2026-03-10
- Owners: Financial Domain Team, Architecture Group
- Related:
  - `docs/adr/0001-app-boundaries.md`
  - `docs/adr/0002-eventing-and-outbox.md`
  - `docs/non-functional-slo.md`
  - `docs/domain-map.md`

## Context
The wallet platform must maintain strict financial correctness under retries, failures, and concurrency. Financial integrity cannot rely on eventual consistency alone.

A formal invariant rulebook is required before implementing `wallet_ledger`, `wallet_accounts`, and `wallet_transfers` to prevent ambiguous posting behavior.

## Decision
Adopt a strict double-entry ledger model with immutable postings and deterministic command processing.

Core principles:
1. Every financial posting is balanced: total debits equals total credits.
2. Ledger entries are immutable; corrections use compensating entries.
3. Posting commands are idempotent by business reference.
4. Available balance and ledger balance are derived from ledger entries, not cache truth.
5. No cross-app direct writes to ledger-owned tables.

## Account and Entry Model
Minimum account classes:
- `asset`
- `liability`
- `revenue`
- `expense`
- `equity` (reserved for future expansions)

Required ledger entry fields:
- `entry_id`
- `journal_id`
- `account_id`
- `direction` (`debit` or `credit`)
- `amount`
- `currency`
- `reference_id` (idempotency/business reference)
- `posted_at`
- `correlation_id`
- `metadata`

Journal constraints:
- A journal must contain at least two entries.
- Journal currency must be single-currency unless explicit FX flow is modeled.
- Sum(debits) == Sum(credits) for each journal.

## Invariant Set (Non-Negotiable)
1. Balance invariant:
- For each journal: debit total equals credit total exactly.

2. Immutability invariant:
- Posted entries cannot be updated or deleted.

3. Idempotency invariant:
- Replaying the same posting command with the same `reference_id` must return the same business outcome and not create duplicate journals.

4. Currency invariant:
- No mixed-currency postings in a single journal unless FX transaction type is explicitly used.

5. Freeze invariant:
- If an account is frozen for debits, debit postings are rejected by policy.

6. Authorization-posting invariant:
- Transfer completion requires successful debit authorization before final posting.

7. Reversal invariant:
- Reversal is represented by compensating journal entries and linked to original `journal_id`.

8. Precision invariant:
- Amount precision must follow currency scale policy; no floating-point arithmetic.

## Posting Workflow Rules
1. Validate command and idempotency key.
2. Validate policy gates (account status, limits, risk decision result).
3. Construct journal entries deterministically.
4. Write journal and entries transactionally.
5. Update account-derived snapshots in same transaction when applicable.
6. Append outbox event in same transaction.
7. Return canonical posting result.

## Concurrency and Isolation Rules
- Use DB transaction isolation and locking strategy to avoid write skew on critical balances.
- Prefer deterministic account ordering for multi-account postings to avoid deadlocks.
- Retry transient serialization conflicts with bounded retries and jitter.

## Error Semantics
- Validation errors: deterministic business rejection (4xx-equivalent domain error).
- Invariant violations: critical fault, incident escalation path.
- External dependency errors: never partially post financial journal; fail or compensate via explicit workflow.

## Reconciliation and Audit Requirements
- Every posting must be traceable via `correlation_id`.
- Daily reconciliation compares:
  - ledger aggregates
  - account snapshots
  - transfer lifecycle totals
- Mismatches above SLO thresholds trigger incident workflows.

## Test and Verification Requirements
Mandatory test layers:
1. Unit tests for posting policies and account state transitions.
2. Property tests for debit/credit balance invariants across randomized journals.
3. Concurrency tests for duplicate submission and lock contention.
4. Replay tests confirming idempotent results under retries/timeouts.
5. Migration tests ensuring immutability constraints remain intact.

## Consequences
Positive:
- Strong financial correctness guarantees.
- Clear implementation contract for `wallet_ledger` and `wallet_transfers`.
- Better audit and regulatory confidence.

Trade-offs:
- Higher implementation and test complexity.
- More rigid workflow constraints during rapid feature changes.

## Acceptance Criteria
1. Ledger posting engine enforces all invariants in this ADR.
2. Property tests prove debit-credit balance across randomized scenarios.
3. Duplicate command replay does not create additional journals.
4. Reversal flows use compensating entries only.
5. Invariant and idempotency metrics are visible in dashboards and mapped to SLO-C1/C2.
