Orion → Vesper, Atlas, Katja — FLAG-030 commit halted¶
When: 2026-04-17 From: Orion Subject: FLAG-030 as specified will double-count the 2 pre-engine capital_events on first live restart. Halting commit pending decision.
TL;DR¶
Green light acknowledged. Code + 34 tests green on the branch. I did NOT commit. Live DB inspection (/tmp/live_post_s32.db, taken post-S32) proves the "apply ALL capital events unconditionally" rule will add +39.27 XRP and +85.00 RLUSD that are already baked into the current ledger via live.starting_balance_*. That's ~137 RLUSD of phantom inventory on next restart.
Same class of issue as the ledger_seq boundary pushback Atlas accepted before the S32 run — but for starting_balance instead of the ledger sequence.
The numbers¶
From /tmp/live_post_s32.db:
capital_events (2 rows, both at 2026-04-13T00:18:19 UTC — pre-engine) | id | asset | event_type | amount | price_rlusd | source_note | |---|---|---|---|---|---| | f9588349… | XRP | deposit | 39.27 | 1.3314 | stage1 opening baseline | | 192fdaae… | RLUSD | deposit | 85.00 | — | stage1 opening baseline |
engine_state
- live.starting_balance_xrp = 40.472253
- live.starting_balance_rlusd = 85.56902376775649
- live.balances_seeded = 1
First inventory_ledger row (id=1, created_at 2026-04-13T01:00:30 — ~42 min AFTER the capital_events) - XRP row: change +2.9314914, new_balance 43.40374441887616 → implied prior balance = 43.40374441887616 − 2.9314914 = 40.472253 ✅ matches starting_balance_xrp - RLUSD row: change −3.9, new_balance 81.66902376775649 → implied prior balance = 81.66902376775649 − (−3.9) = 85.56902376775649 ✅ matches starting_balance_rlusd
Last inventory_ledger row (id=1504, 2026-04-17T16:29:29) - XRP new_balance: 41.01792720880708 - RLUSD new_balance: 86.22001773779789
Conclusion from the data: live.starting_balance_* was snapshotted from the on-chain wallet at engine startup at 01:00:30 UTC on Apr 13. That wallet already reflected the two deposit capital_events posted 42 minutes earlier. Since every ledger row is computed from that seed, the current ledger.new_balance already contains those deposits. The overlay would re-add them.
What rebuild() would do on next live restart (as currently implemented)¶
xrp_fills_only = get_current_balance(XRP) = 41.01792720880708 # contains deposit
xrp_overlay = get_capital_delta_total("XRP") = +39.27
self._xrp_balance = fills_only + overlay = 80.28792720880708 # WRONG — +39.27 XRP phantom
rlusd_fills_only = get_current_balance(RLUSD) = 86.22001773779789 # contains deposit
rlusd_overlay = get_capital_delta_total("RLUSD") = +85.00
self._rlusd_balance = fills_only + overlay = 171.22001773779789 # WRONG — +85.00 RLUSD phantom
At ask=14 mid ≈ 1.33 RLUSD/XRP: - Phantom notional = 39.27 × 1.33 + 85.00 ≈ 137 RLUSD of inventory the engine doesn't actually have.
This would break WAC, sizing, the needs_seed check, session starting_value, and PnL all in one go on the very first post-FLAG-030 restart.
Why my test suite didn't catch it¶
The rebuild tests I wrote all start from an empty ledger + no starting_balance seed, so has_inventory_ledger_entries() returns False and the fills-only base is either seed (paper) or the starting_balance key (live) set to a value that does NOT also include the capital event. test_rebuild_is_idempotent proves repeated rebuilds with no new events are stable — it does not prove the baseline was right to begin with.
The live DB has one condition my tests don't: starting_balance and capital_events both exist, starting_balance is GREATER than the sum of capital_events (there was pre-existing dust), and the earliest ledger row's implied prior balance = starting_balance.
That's the bug: overlay assumes fills_only is truly fills-only. On this live DB, fills_only = fills_only + pre-engine capital events, because the on-chain seed swallowed them.
Options (I don't have authority to pick; flagging for you)¶
A. One-time data migration before FLAG-030 ships
Subtract SUM(capital_events.amount * sign) from live.starting_balance_xrp and live.starting_balance_rlusd. After migration, starting_balance = true_fills_only_baseline and the overlay correctly re-adds the events. Clean in steady state, but it mutates a seed key that's been in place for 4 days. Needs audit trail.
B. created_at boundary filter in get_capital_delta_total()
Overlay only capital_events with created_at >= (SELECT MIN(created_at) FROM inventory_ledger WHERE asset = ?). The 2 pre-engine rows post at 00:18, the first ledger row is at 01:00:30 → they are correctly excluded. This is essentially the "use the real boundary, not ledger_seq" fix Atlas gestured at.
Edge case: fresh DB with capital_events but no ledger → MIN(created_at) is NULL. Need an explicit fallback (probably: overlay nothing, document that fresh deployments must seed starting_balance to reflect their capital_events).
C. Reclassify the 2 rows as synthetic_initial_basis
Add a new event_type (or update source_note to flag them) and have get_capital_delta_total() exclude them. This is effectively a targeted, auditable data fix and dovetails with FLAG-031's synthetic basis row idea.
D. Route ALL capital events through inventory_ledger as event_type='capital' rows
Then rebuild needs no overlay, idempotency is automatic, and the "what's baked in vs what isn't" question disappears. Bigger refactor. Out of scope for FLAG-030 as scoped, but probably the right long-term shape — FLAG-030 as-scoped is working against the grain.
My ranking: B for FLAG-030 now + D as the tech-debt follow-up. A and C both require DB surgery that can bite us again next time an operator forgets to reconcile seed vs events.
Current state of the work¶
- Branch
fix/flag-008-capital-event-validation(FLAG-008) is already committed at6823484and was your green light, unrelated to this. - FLAG-030 work is in the working tree on
main(no new branch), uncommitted. Files touched: neo_engine/inventory_manager.py— overlay attrs, rebuild(), apply_fill() stripneo_engine/state_manager.py—get_capital_delta_total()addedtests/test_inventory_manager.py— 17 new tests (34 total passing)- I can revert, or hold, on your call. Nothing ships to the live engine until commit + deploy.
Recommended next step¶
Pick B (add created_at boundary filter). I'll:
1. Patch get_capital_delta_total() to take optional min_created_at OR to apply the boundary internally.
2. Add 3 tests: (i) the live-DB scenario reproduced (pre-engine events excluded), (ii) fresh DB with capital_events but no ledger (explicit fallback behavior documented), (iii) events interleaved with ledger rows (only post-ledger events overlaid).
3. Dry-run rebuild against /tmp/live_post_s32.db and confirm totals match get_current_balance() within tolerance.
4. Commit on a new branch fix/flag-030-capital-overlay, push when credentials available, request review.
Waiting on your decision before I touch anything else.
— Orion