# RCA — golden 35313912102 (aa8a2978 = main 0ab1e48b + release shape) Windows Phase B red: spt::rc_attach_truth::resume_never_bound_reads_unbound_and_attaches
doyle, 2026-09-18 07:50Z. Sources: job-105501438554.log (gh api jobs/<id>/logs), test src crates/spt/tests/rc_attach_truth.rs:451-515, adapters/mock/src/main.rs:118-232, crates/spt/src/cli.rs:4119-4163 (cmd_endpoint_resume), 4560-4590 (resolve_resume_session use), 4700-4800 (resume UNBOUND stamp + spawn), crates/spt-daemon/src/livehost.rs:794-995 (reconcile_hosted_liveness), :440-480 (custody mint = daemon wake-resume leg only), :1430 (poll site), crates/spt-store/src/resume_custody.rs, crates/spt-store/src/info.rs terminal_normalize.

## Verdict: PRODUCT DEFECT (record-vs-reality), exposed by a timing race. Route todlando. No same-sha rerun.

Phase A 3471/3471. Phase B 242/243; refresh_freeze (golden #1's red) PASSED both cells. Sole red = this test, 34.7 s, panicked at rc_attach_truth.rs:500: "a resuming perch must read UNBOUND (not offline) through the pre-bind window". Product delta vs golden #1's pass (f348ed0c): NONE — only test rig files (046bad44/0ab1e48b) and version strings (Cargo.toml/Cargo.lock, 14 lines 0.70.0->0.71.0). Preserved corpus: 30+ PASS records of this test, zero prior FAIL. First occurrence.

## Sequence (brain stderr panel; wall_ms from CONN_LIFECYCLE lines)
1. run1 `endpoint start res1` (dummy mode): spawn phases pty_created 27 ms, row_inserted 38 ms. Mock binds via `spt api bind --set-session-id <provisional 9a7154f6f7bbda2c>` (the `{session_id}` fill IS the spt-core provisional by design). Test sees ONLINE, taskkill /T the harness at ~692 ms of session life. Broker reaps session 1; reconcile #1 `LIVENESS_RECONCILE_OFFLINE:res1` (log ~4448). Test sees OFFLINE. All as designed.
2. run2 `endpoint resume res1` under the re-registered hold-unbound harness:
   a. cmd_endpoint_resume pops the ledger's last row = the provisional; resolve_resume_session finds no harness-reported row → `RESUME_NO_HARNESS_SESSION` → FRESH branch (`fresh=true` in SPAWN_PHASE). This is the NORMAL path for this test (the mock never reports a non-provisional id); not the defect.
   b. cli.rs:4713 `resume_unbound_stamp` stamps the EXISTING offline record UNBOUND with session_id UNCHANGED (still 9a7154…) — by design, so the later rollback/bind CAS can discriminate.
   c. cli spawns via the broker. THIS spawn was slow: pty_created t=2585 ms, row_inserted t=4003 ms (run1: 27/38 ms). The 2.5 s ConPTY create is not explained (ambient; no CI co-tenant — Phase B is serialized, Windows n1-gate queued, Linux legs finished 06:46Z, no local cargo per deployah and doyle).
   d. INSIDE that window the brain's periodic liveness poll (livehost.rs:1430, ~5 s cadence) ran reconcile_hosted_liveness: res1 status=UNBOUND; `live_sessions` lacks res1 (row not inserted yet); `resume_in_flight(perch)` = false; not relay-held → `mark_offline(perch, Some("9a7154…"))` → terminal_normalize CAS on session_id PASSES (the UNBOUND stamp kept it) → status=offline, rest=suspended. Log: second `LIVENESS_RECONCILE_OFFLINE:res1` (~4455), between `pty_created t_ms=2585` and `log_ready t_ms=4003`.
   e. row_inserted t=4003. Nothing re-asserts UNBOUND after insert; hold-unbound never binds. Perch stays offline → wait_status(UNBOUND, 15 s) false → assert at :500. The perch dump in the panic confirms: status "offline", session_id 9a7154…, pid 34232 / parent 46488 (run1's bind record, offlined).

## Why the existing guards did not hold
- `resume_in_flight` (custody) is minted ONLY by the daemon's wake-resume leg (livehost.rs:470). The CLI `endpoint resume`/`run` path never mints custody, so the reconcile's LIVENESS_RECONCILE_DEFER arm cannot fire for an operator-driven resume — on either the true-resume or the fresh-fallback branch.
- terminal_normalize's CAS is on session_id; REQ-RESUME-UNBOUND-STAMP deliberately preserves session_id on the stamp, so the CAS cannot tell "this UNBOUND row is a spawn in flight" from "this row's harness died".
- The broker HAS the fact (SPAWN_PHASE gate_claimed at t=0) but the reconcile's census (`live_sessions`) counts only inserted rows.

Window = [UNBOUND stamp .. row_inserted] vs a ~5 s tick: normally ~40 ms (≈1 % per run — consistent with 30+ passes then one red), here ~4 s. The defect is the guard gap, not the latency; the latency only sized the window.

## Classification and route
- PRODUCT (doyle ruling): an operator `spt endpoint resume` whose pre-bind window overlaps a liveness tick has its row offlined while the session is live — the exact field surface REQ-RESUME-UNBOUND-STAMP exists to kill (ADR-0042 d2), reachable in the field whenever spawn latency exceeds the tick phase. New hazard; not KNOWN-HAZARDS bind-vs-RMW (line ~322), which is a different writer pair.
- todlando (product), regression test in the same lane. hertz on call for a deterministic rig if asked.
- Same-sha rerun REFUSED: a green would be the ~99 % intermittent green (zero information), and the product must change anyway. deployah reshapes v0.71.0 on the successor carrying the fix (thin PR, CI, ff, fresh shape).
- Golden #2's other legs were left running by deployah; they do not change this ruling.
