Files
finance/docs/adr/0002-journal-and-budget-reserves.md

3.5 KiB

ADR 0002: Journal of operations/postings and budget reserves

  • Status: Accepted
  • Date: 2026-09-11
  • Scope: ledger model, balance derivation, budget plan/funding separation

Context

The product needs trustworthy account balances including credit-card debt, zero-based monthly budgeting where the plan is separate from actual funded money, carry-over savings envelopes, and permanent per-card credit reserves. History must remain auditable and deterministic: closed budget facts must never silently change.

Decision

  • The ledger is a journal of operations (expense | income | transfer | refund | adjustment; confirmed | voided) and postings (one posting for external income/expense/refund; two equal and opposite postings for an internal transfer; exactly one signed posting for a balance adjustment). Amounts are integer kopecks; no floats.
  • Balances are derived, never mutated directly: opening position plus confirmed, non-voided postings. Credit debt follows one documented sign convention.
  • This is not event sourcing and not full double-entry against the external world: the journal is just enough structure to make balances reconstructable and voiding explicit.
  • Budgeting splits plan from funding: plan_assignments express intent, funding_movements move actually received money from the general pool. Monthly categories fold their remainder into the pool at close; savings envelopes carry balances forward; each credit card has a permanent reserve.
  • Every financial space has exactly one visible system savings account paired with one system savings_envelope category, joined by system_key. The pair is created idempotently (space creation, migration, domain commands), is protected from rename/archive/delete, and is identified only by system_key — same-named user entities are never captured. Transfers into the envelope are carved out of the general pool as its backing; transfers back out return to the pool.
  • A balance adjustment is a journal operation with a single signed posting for the delta (the operation amount stays positive; direction lives in the posting). It moves assets and the envelope balance but never income, expenses, cash flow or the budget pool, and every adjustment stores old/new/delta plus a mandatory reason in the audit log. Direct changes to stored balances remain forbidden.
  • Expenses can be converted into savings transfers and converted back; the conversion marks the operation and stores the previous category so reversion restores type, category and postings exactly. Closed periods protect conversions, reversions and adjustments with the same reopen-before-change rule as other mutations.
  • Period closure writes an immutable snapshot. Changing or voiding an operation in a closed month requires an explicit cascade reopen of all subsequent periods; late refunds after closure credit the current pool and never rewrite the closed fact.

Consequences

  • All balance-affecting mutations must go through journal/budget services; direct updates are excluded by convention and tests.
  • Analytics for closed periods read from closure snapshots, not live aggregates.
  • Plan/funding separation and closure snapshots shape the write model permanently (hard to reverse once history exists).

Alternatives rejected

  • Event sourcing — complexity without a matching need at MVP scale.
  • Mutable stored balances without a journal — loses auditability and voiding semantics.
  • Folding envelopes/reserves into the pool — destroys carried-forward user intent.