SAC-1 — census of trading-path persist sites: "state advanced or keyed without regard to run completion" (member four already found)
status: open · raised panel run 15 (2026-08-31), answering the owner handoff in 60a6630 / RESEARCH.md's top banner ("sweep for member four instead of waiting for it") · class: loudness & safety (live path) · judgement: YES · effort ~0.5–1 pd · horizon: before momentum's next halted-selection retry sequence (the 08-19/20 meanrev sequence puts the trigger cadence at roughly monthly)
Plain-language summary for an owner reading one paragraph. The last month produced three separately-discovered defects with one shape: the system wrote down what it intended to do — or what day it believed it was — without checking that the run actually completed. RWG-1 leg 2 (the turnover-blend base advanced during a safety halt), STAMP-1/STAMP-2 (a wall-clock date consumed the next session's idempotency slot), and the Kelly double-booking fixed in PR #120. You asked the panel to sweep for member four instead of waiting for it to fire. The sweep found it in under an hour — which is the evidence the class is productive and a one-shot census is worth a row.
Member four (verified this run; recorded here so the census consumes it rather than rediscovers it)
src/thales/execution/daily.py:821-831 persists last_construction
(including the intended vol_scalar) inside _size_targets, gated only on
dry_run. _size_targets runs at daily.py:1638; execute_orders at
:1719 — the persist precedes the safety gate. Consumption chain:
daily.py:658-662 reads last_construction.vol_scalar into every subsequent
daily portfolio snapshot, and _gross_returns (:714-721) divides each
realized return by it for the vol-target estimator. So a selection that sizes,
persists the scalar, then safety-HALTs leaves the estimator attributing the
real (old-scalar) book's returns to a scalar that was never applied — for up
to a month on momentum, which sizes from state this corrupts.
Mechanism — the census
One-shot enumeration of every persist site in the trading path (daily.py,
vrp_daily.py, pipeline.py, state.py, tca.py), each classified on two
axes:
- (a) gated on run completion (halt/exception) vs ungated;
- (b) keyed on the unit of work (period/session) vs wall-clock.
For each ungated site, a ruling: deliberate (fact-recording/telemetry —
e.g. append_equity, portfolio snapshots record what IS, regardless of what
executed) vs defect (intent persisted as if executed — e.g.
last_construction, the class members above). Deliverable: the classification
table as a memo in research/. Propose-only; no gate ships from this row.
Scope boundary (C0 — stated so this cannot collide with existing rows)
- Trading-path persist sites only. The capture writers are STAMP-1/STAMP-2's owned territory and are out of scope here.
- Per-site rulings for
last_targetsand the kill-switch Kelly-snapshot wipe defer to RWG-1 (legs 2–3), which owns them. The census enumerates and classifies; it does not re-litigate a row that already owns a site.
Data plan
Static code reading, cross-checked against the committed natural experiment: for each site, diff its persisted value across meanrev's 08-19/20 halted runs and the 08-21 recovery (the same commits that confirmed the Kelly double-booking). Observed advancement during a halt is ground truth, not inference. Owned; zero external data; zero registry rows.
Test design
For every site the owner rules "must be gated": a halt-injection test — force a safety HALT, assert the site's persisted state is unchanged — with a negative control per gate (revert the gate; only its own test reds). Sites ruled deliberately ungated get a pinned comment naming the ruling, not a test.
Kill criterion (pre-registered)
If the census finds zero ungated-defect sites beyond the already-recorded
members (RWG-1 legs 2/3) plus last_construction (named above), the class is
declared fully enumerated, the row closes with the table as its record, and
the class may not be reopened without a new fired instance.
Cap note for triage
queue/open/ is at/over the 12-cap. Rank this against the tree explicitly
rather than letting it slide in — if something must yield, the natural
fold-partner is the STAMP-1+STAMP-2 shared fix-sitting (same family), never
RWG-1 (deliberately narrowed).
Dated note — 2026-09-05 (maintainer, from the external design review r2 verification)
Two more sites for the census, both verified at cd571b2, neither owned by
another row at the time of writing:
_record_safety_eventarms the same-day idempotency gate on a HALT.pipeline.py:274writes arun_summaryfor any safety HALT;already_ran_today(state.py:180-187) honours anyrun_summaryfor the date;daily.py:1392then skips with"Already ran today", which is not incli.py:1107'scritical_skips, so the run exits 0. RUNBOOK.md's post-halt instruction ("fix the cause, thenthales run --skip-market-checklocally", :253-254) is therefore a green no-op without--force— which the same sentence says never to use casually. Axis (a): ungated — a HALT is recorded as if the day's unit of work completed. Ruling needed: is a HALT a completed unit of work for idempotency purposes? Damage is bounded to one lost day because the month stays open viaselection_completed=False(daily.py:502-511), which is why the reviewer filed it as a near-miss rather than a finding.- Kill-switch
active: Truepersisted before the liquidation is attempted (daily.py:1024-1026, then_liquidate_to_cash→execute_ordersat:1133). Owned by KSP-1 (raised the same day) for the fix; listed here so the census consumes it rather than rediscovers it. Same class: intent written as fact.
Neither changes this row's kill criterion; both count as members beyond the already-recorded ones, so the criterion's zero-branch cannot fire on the current tree.
Triage note — 2026-09-07 (annotation only; the body above is untouched)
Two of the five named members are CLOSED by KSP-1 (#151, 47ea024); three
stand, and they are the three that corrupt sizing state. Verified at HEAD
e25f80b:
| member | status |
|---|---|
last_construction / vol_scalar (member four) | live — daily.py:841-849, gated only on dry_run (:840); _size_targets at :1781, execute_orders at :1862 |
_record_safety_event arms same-day idempotency | live — pipeline.py:274 writes a run_summary on any HALT; state.already_ran_today honours any of them (state.py:180-187); daily.py:1523 then skips, and "Already ran today" is not in cli.py:1107's critical_skips, so the run exits 0 |
kill-switch active: True before liquidation | CLOSED (KSP-1: deferred publish, daily.py:1103-1106 → :1227-1230) |
| Kelly-snapshot wipe on a halted liquidation | CLOSED (KSP-1: non-halt branch only, daily.py:1231-1234) |
last_targets (deferred to RWG-1) | live — daily.py:1795-1803 |
The kill criterion's zero-branch therefore cannot fire, exactly as the 09-05 dated note anticipated. STAMP-1/2/3 closed the wall-clock keying axis for the two run wrappers; they closed none of the persist sites above, which sit on axis (a).
The deliverable does not exist yet: no census memo among
research/2026-09-*; a grep of research/ for SAC-1 returns only queue files
and the two design-review memos. Triage carries this into the decide-list
folded with RWG-1 legs 2+3, per this row's own sequencing request — one
ruling sitting, not two.