The Double-Entry Ledger
Money is never overwritten. It only moves — and every move is permanent.
The incident
A wallet balance stored as one integer column. Two concurrent spends read the same balance, both passed the check, both wrote. The user spent the same money twice, the books didn't balance, and there was no history to reconstruct what happened — because history was never kept.
Why the obvious approach fails
balance = balance - amount looks like arithmetic but is actually three operations — read, compute, write — with a race between them. Locks patch the race but not the deeper flaw: a mutable balance has no memory. You can't audit it, can't reconcile it, can't answer a regulator's 'why is this number what it is,' and every bug that touches it destroys information permanently. Banking solved this five hundred years ago: don't store the balance, store the movements. The balance is a derived fact.
The standard
- Every movement of value is two entries — a debit and a credit — that must sum to zero; the invariant is enforced at the transaction level.
- Ledger rows are append-only. No UPDATE, no DELETE — corrections are compensating transactions, so the audit trail is complete by construction.
- Balances are derived from entry history (with cached snapshots for performance), never stored as independently mutable state.
- Every entry carries a correlation ID tying it to the business event (order, refund, payout) that caused it.
- Reconciliation against payment-provider records runs on a schedule, and discrepancies alert — they don't wait for month-end.
How Cortex validates it
At plan time
Any blueprint touching stored value must model it as ledger entries; a mutable balance column in the schema fails plan review.
At validation
assert: ledger_tables.grants exclude UPDATE, DELETEassert: sum(transaction.entries) == 0 for all transactionsassert: derived_balance == sum(entry_history)Assertion syntax is illustrative.
At review
The approver sees the concurrent-spend test and the zero-sum verification across the seeded transaction set.