Unify SQLite balance_assertions with Beancount Balance directives #27
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The dual-system problem
Libra currently has two parallel balance-assertion systems that don't know about each other:
balance_assertionstable (migrations.py:166): records assertions with a date, account, expected balance (sats + fiat), tolerance, and status. Checked bytasks.py:74(daily reconciliation watchdog), reported viaviews_api.py:3126+(reconciliation endpoints).Balancedirective (beancount/core/data.py:177-203, confirmed against refs/repos/libra/beancount): "asserts that the declared account should have a known number of units of a particular currency at the beginning of its date … these assertions act as checkpoints." Validated bybean-checkat parse time. Failures show up in Fava's UI as red flags.These are two reconciliation mechanisms that can independently pass or fail. A Libra
balance_assertionfailure surfaces in the daily reconciliation report; a BeancountBalancedirective failure surfaces in Fava and inbean-checkoutput. There is no code path that knows about both, so a failure in one is invisible to the other. "All green" in the reconciliation watchdog can coexist with a red BeancountBalancefailure that nobody on the Libra side sees.Options
A. Unify upward (write Libra assertions as Beancount Balance directives)
When
POST /api/v1/assertions/balanceis called, post the assertion to Fava as aBalancedirective (just another entry type inadd_entries). Drop the SQLitebalance_assertionstable over time; existing rows become a one-time migration.Pros:
Cons:
Balancedirectives are simpler than Libra's current model: they assert a balance at a date for one currency at a time. Libra's table carries multi-currency expected balance (sats + fiat) and a status workflow (pending/passed/failed). Multi-currency becomes multipleBalancedirectives at the same date.created_by,tolerance, audit history) unless mapped to Beancount meta keys. Probably fine — Beancount meta is flexible.B. Keep both, unify the failure surface
Keep the SQLite table for Libra's workflow needs, but have the daily reconciliation watchdog also call
bean-check(or equivalent) and surface any BeancountBalancefailures in the same report.Pros:
Cons:
C. Document and accept
The reviewer's framing: this is a "make sure the failure modes compose" concern, not architectural. Add a check in the daily reconciliation that also reports Beancount-side
Balancedirective failures, document that the two systems are intentionally parallel.Recommendation
(A) — unify upward — is the right call long-term, particularly if the audit triplet (#26) lands, because by then the journal is the canonical record and putting assertions outside it is the same drift hazard the journal-mirror cleanup already removed for transactions. But (A) has UI consequences (the "create assertion" flow now writes to Fava, the assertion list reads from Fava entries instead of SQLite) and isn't a pure refactor.
Sequence-wise: independent of #24/#25/#26. Can land at any time, but most naturally goes after #25 lands so the assertion creation goes through the same git-backed commit path as everything else.
Scope
Defer the option choice to when this issue is picked up. Whichever option is taken, the deliverable includes:
tasks.py:scheduled_daily_reconciliation) that surfaces failures from both systems if both are kept, or that's the single source of truth if unified.Dependencies