On the LIVE entry path, the exposure/spend/position caps are alarms, not brakes — fast_reserve does a bare INSERT and evaluates no caps, and the post-check that runs after it only increments a Telegram counter. Nothing cancels, unwinds, or blocks the order. The regression test that asserts otherwise had not compiled in months, so it never ran and nothing said so. Discovered 2026-07-09 while repairing the db-test suite. Pre-existing bug; low current exposure; fix proposed and awaiting Andras (babylon #1172).
Caps do NOT stop a live order
total_exposure_cap_usd,daily_spend_cap_usd,max_open_positions,max_copies_per_eventare enforced synchronously only inreserve()— the DRY path, where no money moves. On the live path they run in a spawned post-check that only records a digest counter. Exactly backwards: the money path has no brake, the no-money path has the brake.
The Bug
- The live entry path calls
fast_reserve(execute.rs:125) — a bare INSERT that evaluates no caps. evaluate_caps_post(execute.rs:133) runs AFTER the reserve, inside atokio::spawn, concurrently withplace_buy.- On a cap breach it only calls
agg.record(Category::CapSkip, &spec.wallet_id, None)— a Telegram digest counter. Nothing cancels, unwinds, or blocks. Signing and submission proceed regardless. - So on live, all four caps are ALARMS, not BRAKES.
- They are enforced synchronously — but only in
reserve(), the DRY path, where no money moves.
Root cause: fast_reserve replaced reserve() on the live path for latency (see pmv2-entry-config-cache-design-2026-07-09). The cap evaluation went with it into a spawned post-check, and the spawn made it advisory. The DRY path kept the synchronous reserve() with real caps — so the guardrail ended up on the wrong path.
Why It Hid — a test that cannot compile is not protecting you
crates/position_manager/tests/mode_control_ledger.rs::live_cap_event_breach asserts ExecOutcome::Skipped("cap:event"). It is precisely the regression test for this bug. It never ran:
- That test file had not compiled since
PositionManager::newgained anaggparameter. consumer_ack.rsandops_commands.rswere dead the same way (the latter referencedCommand::Enable/Command::Disable, removed by a contracts re-pin).cargo test --features db-testsfailed with 7 compile errors on main.
The durable lesson
A test that cannot compile is not protecting you, and nothing tells you. Unit tests were green (213 passing) the whole time; only the db-tests — the ones that touch the money path — were dead. CI does not run them (they need Docker / testcontainers), so green CI meant nothing here. A dead regression test is worse than no test: it reads as coverage.
For Agents
Same failure class as pmv2-sqlx-runtime-type-decode-bug-db-tests-2026-07-02: the money path is only exercised by the
feature=db-teststestcontainers suite, which offline/unit tests and CI do not run. When a db-test file won’t compile, treat it as an outage of that guardrail, not a nuisance. Wire--features db-tests(or per---test) into CI.
Current Exposure
Low. Caps sit at 20,000 versus wallets holding $100–235. It is a missing guardrail, not an active loss — the caps are nowhere near binding at current bankroll. This is why the fix is queued rather than hot-patched.
The Fix (proposed, awaiting Andras — babylon #1172)
Fold the cap conditions into the new guarded reserve INSERT — the CTE that already gates the INSERT on the DB’s own mode / breaker_tripped state at zero extra round-trips (from the config-cache work). The caps become additional gate conditions on that same INSERT, so a breach rejects the reserve synchronously before any signing.
Do NOT revert the live path to
reserve()Reverting to
reserve()paysBEGIN+SELECT FOR UPDATE+ serialSUMqueries across a Zurich→Dublin cross-region hop (~183ms per round-trip) — exactly the latency the config-cache work just removed. The right move is to add the cap gates to the round-trip we already pay (the INSERT), not to bring back the multi-query transaction. See Key architectural idea — move the authority check to a round-trip you already pay.
Related
- pmv2-entry-config-cache-design-2026-07-09 — the config-cache work whose guarded-INSERT CTE is the vehicle for the fix; explains the ~183ms cross-region hop and why
reserve()is not the answer - pmv2-sqlx-runtime-type-decode-bug-db-tests-2026-07-02 — same “invisible to offline tests, only db-tests catch it, wire into CI” pattern; also the
ops_commands.rsdrift blocking--features db-tests - pmv2-measure-before-mitigating-priority-tiers-2026-07-09 — companion learning from the same 2026-07-09 session
- pmv2-multi-signer-wallet-2026-07-01 — where the per-wallet advisory caps + single global breaker model was established
- pmv2-pm-live-real-money-status-2026-07-02 — PM is LIVE real money; why a missing live brake matters
- position_manager — PM overview