Reconciliation

A balance is a SUM, never a stored number

Once you have a ledger, a walker's balance is never something you store and update β€” it's whatever ledger_entries.where(account: "walker_7").sum(:amount_cents) comes out to, computed fresh, every time. A cached balance_cents column on the Walker model is tempting for speed, but it's a second source of truth that can silently drift from the entries: a bug, a missed callback, a manual update_column in a Rails console β€” any of those corrupt a mutable counter forever, with no way to tell it happened.

The ledger itself can't drift, because it's append-only: nothing ever updates or deletes a LedgerEntry (append-only isn't a technical restriction here, it's a discipline β€” treat every row as permanent). If a payout needs correcting, you post a NEW pair of entries reversing it, you don't edit the old ones. The full history stays honest, forever.

A reconciliation job is the safety net that watches the ledger stay honest: periodically sum every debit and every credit across the WHOLE ledger and confirm they cancel out to zero (every transfer is balanced, so the grand total always should be). If they ever don't, something posted an unbalanced entry β€” a bug got through β€” and the job should flag it loudly rather than let it slide. Wire this up as a scheduled Solid Queue job (the same recurring-job pattern you'd use for any periodic task) so it runs on its own, say nightly, without anyone remembering to trigger it.