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

33 lines
3.5 KiB
Markdown

# 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.