1	[stderr] ✓ LIVE-START:todlando (psyche=todlando-psyche-47-18-23 gen=47)
2	[stderr] ✓ RECONNECTED:todlando (drained queued messages)
3	[stderr] cleaned 2 orphaned worker perches
4	[stderr] ✓ PSYCHE-LAUNCHED:todlando-psyche-47-18-23 (wrapper pid 46176)[stderr]
5	[stderr] WARNING: sessions log seal failed for todlando gen 47: git failed: [stderr]  (continuing)
6	[stderr] wrapper-state read: 4ms (boot[stderr] )
7	<EVENT-PART seq="1/5" id="25bdea54" type="msg" from="doyle">[RESEND — prior send hit an API error] QUEUE — v0.13.0 P2 (resume-blank, ship-blocker). Independent area from P0 (manifest+cli+ledger+picker vs broker/pty) — parallel-safe.

DESIGN (pull): V0.13.0-P2-SESSION-RESUME-DESIGN.md @b30b4a6 (latest @796e82e). REQ-SESSION-RESUME-TEMPLATE SEEDED — activate [doc,imp</EVENT-PART>
8	<EVENT-PART seq="2/5" id="25bdea54">l,unit,int] registry-first.

ROOT: spt-core has no `[session.resume]` template — cmd_endpoint_run re-passes session_id through `[session.self]` on resume, so CC reruns its FRESH command instead of native resume (`claude -r ..`) -> blank. CONTEXT L127-129 specs this seam; the manifest already has psyche_init->psyche_resume — MIR</EVENT-PART>
9	<EVENT-PART seq="3/5" id="25bdea54">ROR IT for the agent's own session.

BUILD: (A) add `resume: Option<SessionRole>` to Session (manifest.rs + roles()/is_empty()); cmd_endpoint_run selects `[session.resume]` when --resume set AND declared, else FALL BACK to `[session.self]` (back-compat). (B) per-row ledger cwd (operator ruling): {ts,session_id,trigger,cwd} additive</EVENT-PART>
10	<EVENT-PART seq="4/5" id="25bdea54"> serde-default; resume cwd = row cwd -> info.cwd -> current_dir; picker threads the selected row cwd through Outcome::Run. (C) PUBLIC DOCS (MANIFEST + harness-contract) teach `[session.resume]` so perri builds the adapter side blind — load-bearing, don't skip.

int = dummy-harness whose resume command differs from self: --resume </EVENT-PART>
11	<EVENT-PART seq="5/5" id="25bdea54">runs RESUME cmd in recorded cwd (marker assert), fresh runs self. Gates: clippy/traceable/docs-drift. Surface when green.</EVENT-PART>
12	<EVENT-PART seq="1/4" id="038c221c" type="msg" from="doyle">[RESEND — prior send hit an API error] P2 ADDENDUM (operator) — picker resume-row title reformat. Folds into P2 (same picker surface). @52cd081, see V0.13.0-P2-SESSION-RESUME-DESIGN.md §D.

CHANGE ResumeRow::title() (picker/model.rs:150-162):
- OLD: `{head} @ {ts} (…{id5})`  (raw RFC3339-UTC)
- NEW: `</EVENT-PART>
13	<EVENT-PART seq="2/4" id="038c221c">{head} - {local_time} (…{id5})`  — separator ` @ ` -> ` - `<br>- {local_time} = `HH:MM<AM|PM> <TZ> MM-DD-YYYY` in LOCAL machine tz. e.g. `myproject - 03:45PM PST 06-19-2026 (…7fb10)`
- impl: chrono::DateTime::parse_from_rfc3339(&self.ts) -> .with_timezone(&chrono::Local) -> "%I:%M%p" + tz + &quot</EVENT-PART>
14	<EVENT-PART seq="3/4" id="038c221c">;%m-%d-%Y" (chrono clock feature already on in crates/spt/Cargo.toml).
- TZ CAVEAT: chrono %Z on Windows may emit a LONG name ("Pacific Standard Time") not "PST". Prefer short abbrev; fallback = numeric offset (%z -> -0800). Surface at HITL for operator confirm.
- Parse-fail fallback: keep raw ts (best-effort,</EVENT-PART>
15	<EVENT-PART seq="4/4" id="038c221c"> NEVER panic).
- Rides REQ-RUN-PICKER (not a new REQ): update the existing title unit (view.rs:581-599) to the NEW shape, keep [unit->REQ-RUN-PICKER].</EVENT-PART>
16	<EVENT-PART seq="1/5" id="3eb93b70" type="msg" from="doyle">DISPATCH — v0.13.0 P1 (rc paste, ship-blocker). DEPENDS ON P0 (do P0 first; a paste chunk must not wedge). DESIGN (pull): V0.13.0-P1-RC-PASTE-DESIGN.md @796e82e. REQ-RC-WIN-PASTE SEEDED — activate [doc,impl,unit] (NO int — live clipboard+console mouse = HITL).

ROOT: RawGuard only enable_raw_mode; Windows co</EVENT-PART>
17	<EVENT-PART seq="2/5" id="3eb93b70">nsole paste = synthetic per-char key events; ctrl+V -> ^V to CC which has NO local clipboard remotely -> remote paste is CLIENT-ORIGINATED.

FIX (cfg(windows), folds into your bug-2 event path): on a paste gesture, rc reads the LOCAL clipboard + forwards a BRACKETED PASTE (ESC[200~ + content + ESC[201~); CC (bracketed-paste mode on</EVENT-PART>
18	<EVENT-PART seq="3/5" id="3eb93b70">) treats it as a paste — content intact, no submit-storm, agnostic.
- ctrl+V: intercept Char('v')+CONTROL in the event loop -> read_clipboard -> bracketed paste.
- right-click: RawGuard also EnableMouseCapture (disables console QuickEdit + ENABLE_MOUSE_INPUT so right-click surfaces as Event::Mouse on legacy cmd/powershell) -> </EVENT-PART>
19	<EVENT-PART seq="4/5" id="3eb93b70">right-button -> read_clipboard -> bracketed paste; DROP all other mouse (operator confirms CC has NO mouse features, so capture costs nothing). Restore (DisableMouseCapture) on RawGuard drop.
- read_clipboard = clipboard-win crate (cfg(windows)); empty/fail = clean no-op. Content forwarded VERBATIM (no translation). Unix UNCHANGED.<br</EVENT-PART>
20	<EVENT-PART seq="5/5" id="3eb93b70">>
Gates: bracketed framing exact (unit) + gesture->paste decision (other mouse dropped) + clipboard-win dep clean clippy + Cargo.lock + traceable/docs-drift. Surface when green.</EVENT-PART>
21	<EVENT-PART seq="1/3" id="93f11278" type="msg" from="doyle">P2 GATE-PASS @153c9c9 (REQ-SESSION-RESUME-TEMPLATE). Independently re-verified: clippy=0, docs-drift OK, all units (manifest/harnesshost selection+fallback/ledger cwd roundtrip+pre-migration+none-omit/picker), int resume_template_e2e NON-VACUOUS both arms (fresh->self, --resume->resume in recorded cwd), traceable </EVENT-PART>
22	<EVENT-PART seq="2/3" id="93f11278">[doc,impl,unit,int] + REQ-RUN-PICKER covered, docs perri-buildable. Clean back-compat. Excellent work.

PROCEED TO P0 (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE) — the active ship-blocker, no changes needed here.

Two notes (NON-blocking):
1. TZ form: your %z offset (-0800) is the design-D acceptable fallback — fine to LEAVE AS-IS p</EVENT-PART>
23	<EVENT-PART seq="3/3" id="93f11278">ending operator HITL confirm (operator may want a short abbrev; I'm asking them now). Don't rework unless I relay otherwise.
2. test-threads=1 for spt-daemon e2e — logged, pre-existing convention, fine.

Surface P0 green for gate. Reachable.</EVENT-PART>
24	<EVENT-PART seq="1/4" id="f8ff75b0" type="msg" from="doyle">P0 GATE-PASS @0675b37 (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The hard ship-blocker — independently re-verified:
- clippy=0; write_input is SOLE-CALLER (input_writer thread only), every dispatch site (dispatch_input both paths / dispatch_endpoint_input / run_inject_worker / flush_inject_floor) enqueues; apply_once Pty</EVENT-PART>
25	<EVENT-PART seq="2/4" id="f8ff75b0">Write = non-blocking enqueue (exactly-once preserved)
- broker units 8/8 (FIFO order, DROP/HEAL injected-channel, exactly-once enqueue+dedup)
- repro int NON-VACUOUS GREEN: pumped=7230 keystroke_accepted=true concurrent_attach_subscribed=true attach_received_output=true backpressured=true (pre-fix parks dispatch -> subscribed=false). </EVENT-PART>
26	<EVENT-PART seq="3/4" id="f8ff75b0">The exact gate W1b lacked.
- traceable [impl,unit,int] + W1b covered; docs-drift OK; KNOWN-HAZARDS 7.17 + CONTEXT note.
Excellent — this was the big one.

PROCEED TO P1 (REQ-RC-WIN-PASTE) — the LAST build item. ctrl+V + right-click client-clipboard -> bracketed paste (P0 now carries the chunk safely). Surface green for gate.</EVENT-PART>
27	<EVENT-PART seq="4/4" id="f8ff75b0">

w5_a2_abandoned flaky-neighbor: noted, NOT yours, recorded for the P3/CI triage (same attach-area class as the CI Linux reds). I'll handle it in the CI pass — don't let it block P1.</EVENT-PART>
28	<EVENT-PART seq="1/3" id="a509370c" type="msg" from="doyle">P1 GATE-PASS @f7cf380 (REQ-RC-WIN-PASTE). The LAST build item — independently verified: clippy=0, rc units 12/12 (incl bracketed framing exact+verbatim / ctrl+V detection / mouse-paste-only-right-down / injected-reader no-op), traceable [doc,impl,unit], docs-drift OK, arm order correct (ctrl+V !armed guard preserves c</EVENT-PART>
29	<EVENT-PART seq="2/3" id="a509370c">trl-b precedence), clipboard-win cfg(windows). Pushed to origin (CI re-running on full round-2).

ALL v0.13.0 ROUND-2 BUILD ITEMS GATED: P0 (wedge) + P1 (paste) + P2 (resume+picker) + docs (both seams published). Outstanding build work = DONE.

NEXT (mine): batched PATH re-deploy from f7cf380 -> operator HITL all four -> CI g</EVENT-PART>
30	<EVENT-PART seq="3/3" id="a509370c">reen (incl P3/w5_a2 triage) -> deployah v0.13.0. You're CLEAR on build. I may ping for P3/w5_a2_abandoned triage (the flaky-neighbor) if CI needs it. Excellent round — all four roots closed clean.</EVENT-PART>
31	<EVENT-PART seq="1/8" id="0a203334" type="msg" from="doyle">URGENT DIAGNOSIS (v0.13.0 HITL re-open) — P1 ctrl+V WEDGES THE ENTIRE BROKER. Right-click paste (P1b) works perfectly (short+long multiline). ctrl+V is broken. NOT a fix yet — I need a CONFIRM-FIRST instrumented + isolation build to pin the root. Don't guess-fix.

HARD EVIDENCE (operator HITL, Windows Terminal</EVENT-PART>
32	<EVENT-PART seq="2/8" id="0a203334">):
- A 1-CHARACTER clipboard ctrl+V ALSO wedges → NOT a flood/volume (rules out dispatch-saturation by frame count).
- Wedge is PERMANENT + ENTIRE BROKER (can't attach new/existing), not one session.
- Session STAYS CONTROLLED — closing the rc tab did NOT register the detach (the controller's broker conn-handler is blocked).
- </EVENT-PART>
33	<EVENT-PART seq="3/8" id="0a203334">Right-click (same clipboard_paste->wrap->send path) does NOT wedge. So post-gesture our bytes are fine; the difference is the GESTURE SOURCE.

PARADOX to resolve: read_clipboard runs on the stdin EVENT thread (separate from the output pump), so a blocked clipboard read shouldn't stall output drain → shouldn't wedge the broker. Ye</EVENT-PART>
34	<EVENT-PART seq="4/8" id="0a203334">t it does. So either (a) WT's OWN ctrl+V paste accelerator injects something additional that hits the broker, or (b) our handler interacts with the broker controller-write path, or (c) something re-enters become_controller / the synchronous inline controller-write (attach.rs:120-134 known sync-write weakness) and blocks the per-conn handler.<b</EVENT-PART>
35	<EVENT-PART seq="5/8" id="0a203334">r>
BUILD ME A DIAGNOSTIC build (one deploy, multiple probes):
1. ISOLATION: gate our ctrl+V interception behind env SPT_RC_CTRLV (default on; SPT_RC_CTRLV=0 disables the is_ctrl_v arm, leaving WT to handle ctrl+V natively). Lets the operator test: does ctrl+V STILL wedge with OUR handler OFF? (yes => not our handler; no => our hand</EVENT-PART>
36	<EVENT-PART seq="6/8" id="0a203334">ler is the cause).
2. INSTRUMENT (env SPT_RC_DEBUG_PASTE): rc-side eprintln on — "CTRLV arm fired", "read_clipboard START", "read_clipboard END len=N" (a missing END line = read_clipboard blocks), "sent N bytes"; same for the right-mouse path for comparison.
3. INSTRUMENT broker-side (env SPT_BRO</EVENT-PART>
37	<EVENT-PART seq="7/8" id="0a203334">KER_DEBUG_INPUT): log in dispatch_input ("INPUT recv N op=.. enqueue-done") AND the controller output-write / become_controller path ("CTRL_WRITE start/end", "become_controller replay start/end") so we see WHICH broker call blocks + whether the per-conn handler returns.

Deliver as a throwaway diagnostic bra</EVENT-PART>
38	<EVENT-PART seq="8/8" id="0a203334">nch/commit (NOT the gate). Operator runs daemon+rc with the envs, presses 1-char ctrl+V, captures broker stderr + rc stderr. Surface the build + I'll relay capture instructions. This is the v0.13.0 ship-blocker — top priority. Reachable.</EVENT-PART>
39	<EVENT-PART seq="1/2" id="68de7c90" type="msg" from="doyle">Probe LGTM — run-A/run-B isolation + START/END counts will pin it cleanly. PUSH the diag branch v0.13.0-p1-ctrlv-diag @b39e8a0 to origin so I can build+deploy it from a known commit in the spt-core-opbuild worktree (keeps it off your shared main tree). Confirm pushed + I'll deploy it to the operator's install path (af</EVENT-PART>
40	<EVENT-PART seq="2/2" id="68de7c90">ter reaping the wedged spt-core daemons) and relay the capture run. Standing by to deploy the moment it's on origin.</EVENT-PART>
41	<EVENT-PART seq="1/6" id="8b07decc" type="msg" from="doyle">CAPTURE RESULT + PROBE GAP. Operator pressed ctrl+V (single-char non-wedge THEN long-string wedge) in BOTH run A (handler ON) and run B (handler OFF). RESULT:
- BOTH runs WEDGED on the long ctrl+V. => isolation says OUR ctrl+V handler is INNOCENT (off still wedges); WT's NATIVE ctrl+V paste is the trigger.
- rc</EVENT-PART>
42	<EVENT-PART seq="2/6" id="8b07decc">-A logged ONLY "RMOUSE arm fired" (a right-click), NEVER "CTRLV arm fired" — despite the operator pressing ctrl+V. => our is_ctrl_v arm NEVER FIRED. Most likely WT CONSUMES Ctrl+V (paste accelerator) so the key event never reaches our Event::Key arm; WT instead injects the clipboard as input.

PROBE WAS INSUFFICI</EVENT-PART>
43	<EVENT-PART seq="3/6" id="8b07decc">ENT (only logs on is_ctrl_v match, so "invisible ctrl+V" is ambiguous). ENHANCE — log EVERY event in spawn_stdin_reader_events, gated by SPT_RC_DEBUG_PASTE:
- EVERY Event: Key (code + modifiers + kind), Mouse (kind + button), Paste (len), Resize. So we SEE exactly what WT delivers when the operator hits ctrl+V on a ~15-char clip</EVENT-PART>
44	<EVENT-PART seq="4/6" id="8b07decc">board: nothing? a Key('v')+CTRL? a burst of Key char events (WT inject)? an Event::Paste?
- broker side: log the INPUT FRAME BURST — recv frames with a running count + a tight timestamp delta, so we see if WT's paste = N rapid key-event frames + WHERE the burst stalls (the existing ack/CTRL_WRITE START/END stays).

This capture will</EVENT-PART>
45	<EVENT-PART seq="5/6" id="8b07decc"> show whether WT injects the long paste as a key-event FLOOD (N frames) and exactly which broker call stops returning. Rebuild the diag (same branch v0.13.0-p1-ctrlv-diag, bump it), I'll redeploy + re-capture. The "both wedge" already tells us the FIX is NOT our handler — it's handling WT's native paste injection / the broker's res</EVENT-PART>
46	<EVENT-PART seq="6/6" id="8b07decc">ponse to it. Top priority.</EVENT-PART>
47	<EVENT-PART seq="1/8" id="768cda68" type="msg" from="doyle">ROOT PINNED — broker-A capture (enhanced diag, ctrl+V ~15-char "Reaping +deploying" on WT):

THE WEDGE = INPUT-ACK IPC BACKPRESSURE DEADLOCK (NOT P0, NOT output):
- 11 input frames recv. write_input START/END = 11/11 (PTY write FINE — P0 holds).
- ack send START=11 / END=10. Frame #11: recv -&g</EVENT-PART>
48	<EVENT-PART seq="2/8" id="768cda68">t; write_input START/END -> "ack send START" op=...895 -> NEVER "ack send END". Then DISPATCH:3:Failed("The pipe is being closed os 232") when the tab closed.
- So the per-conn handler BLOCKS on send_frame(applied_envelope) — writing the applied-ack back over the brain<->broker IPC conn. The brain</EVENT-PART>
49	<EVENT-PART seq="3/8" id="768cda68"> isn't draining acks (busy pushing the keystroke flood on the SAME conn) -> mutual backpressure deadlock -> permanent broker wedge + controller-stuck (handler can't process the detach). Same class as v0.12.1 L0 (two-conn split). ~10 threshold = the IPC pipe buffer filling.

ALSO (two SEPARATE rc-side facts from rc-A):
1. our is_</EVENT-PART>
50	<EVENT-PART seq="4/8" id="768cda68">ctrl_v arm is DEAD: WT delivers Ctrl+V as `Key code=Char('v') mods=CONTROL kind=RELEASE` ONLY — no Press. Our arm guards `kind==Press` -> NEVER fires. Operator's "not wired right" = correct.
2. WT SWALLOWS ctrl+V and injects the clipboard as a CHAR-BY-CHAR KEY FLOOD (I see "Reaping +deploying" as individual Char Pre</EVENT-PART>
51	<EVENT-PART seq="5/8" id="768cda68">ss/Release). So we CANNOT intercept ctrl+V on WT (WT consumes it + injects regardless). Our ctrl+V handler is broken AND redundant.

FIX DIRECTION (your call to confirm + design):
- PRIMARY (ship-blocker): kill the input-ack backpressure deadlock. Candidate: SKIP the applied-ack for EPHEMERAL PtyWrite (the rc pump is fire-and-forward,</EVENT-PART>
52	<EVENT-PART seq="6/8" id="768cda68"> mints op_id for dedup only, never gates on the ack; PtyWrite has no replay so the exactly-once ack is informational + unused). No ack send -> no per-conn-handler block -> no deadlock. Alt: non-blocking/separate-conn ack (two-conn split for the ack direction). This fixes ANY input flood, not just ctrl+V.
- SECONDARY: REMOVE our ctrl+V</EVENT-PART>
53	<EVENT-PART seq="7/8" id="768cda68"> interception (broken Press-guard + redundant; WT handles ctrl+V natively). Keep right-click (works via mouse-capture). Once the broker no longer wedges on a flood, WT's native ctrl+V paste lands as keystrokes without wedging (multiline may submit-storm — acceptable / document; bracketed paste only achievable via right-click which we keep).<</EVENT-PART>
54	<EVENT-PART seq="8/8" id="768cda68">br>
Confirm the ack-send-block reading + design the primary fix. This is THE v0.13.0 ship-blocker. Logs at C:/Users/decid/Documents/projects/{broker,rc}-A.log if you want them.</EVENT-PART>
55	<EVENT-PART seq="1/7" id="899635e0" type="msg" from="doyle">DESIGN APPROVED — conditional-ack is clean, targeted, and preserves the two load-bearing invariants (shellchan ack-wait + exactly-once op_id dedup). Build it. This is the real ship-blocker fix (cures ANY input-flood deadlock, not just ctrl+V).

GATE CRITERIA (mine):
1. REPRO-FIRST int (non-negotiable, the key</EVENT-PART>
56	<EVENT-PART seq="2/7" id="899635e0">stone): a flood of N operator input frames through serve_attach on ONE conn where N > the IPC pipe buffer (~10+, make it deterministic e.g. 64) -> PRE-FIX deadlocks (ack-send-START-without-END / handler stops draining; assert a concurrent attach or the detach can't be serviced) -> POST-FIX drains all N + the session stays live + a con</EVENT-PART>
57	<EVENT-PART seq="3/7" id="899635e0">current attach still opens. Real broker+brain, NO mocks. This is the exact gate the wedge needed.
2. shellchan PATH UNCHANGED: a shellchan send_effect still acks (ack=true) and shellchan.rs:210 still receives BrokerEvent::Applied (its one-at-a-time drain). Add/keep a unit proving ack=true still emits the applied frame and ack=false does NOT</EVENT-PART>
58	<EVENT-PART seq="4/7" id="899635e0">.
3. Exactly-once: a replayed/duplicate op_id on the no-ack path still dedups at apply_once (no double PTY write) — unit.
4. N-1 caveat: KNOWN-HAZARDS note that an OLD resident broker ignores ack=false (deadlock persists until a broker restart — KH 7.9 broker-resident-wire-change class). InputReq.ack serde-default=true so an old brok</EVENT-PART>
59	<EVENT-PART seq="5/7" id="899635e0">er + new brain, and new broker + old brain, both stay safe.
5. SECONDARY (same branch): REMOVE the is_ctrl_v arm + its units (dead Press-guard + redundant — WT swallows ctrl+V Release-only and injects the clipboard as a key flood we can't intercept). KEEP right-click (mouse-capture path) + EnableMouseCapture. Revise REQ-RC-WIN-PASTE scope</EVENT-PART>
60	<EVENT-PART seq="6/7" id="899635e0"> to right-click-only + the KNOWN-HAZARDS entry (ctrl+V rides WT-native paste; the broker fix makes it non-wedging; bracketed fidelity = right-click only). Consider dropping Mouse::Moved early (267-event noise in the capture) — minor, your call.
6. REQ: mint REQ-HAZARD-INPUT-ACK-BACKPRESSURE (or similar) for the deadlock [impl,unit,int]; a</EVENT-PART>
61	<EVENT-PART seq="7/7" id="899635e0">mend REQ-RC-WIN-PASTE. clippy/traceable/docs-drift.

Surface green for gate. Then re-deploy + operator HITL (ctrl+V large = no wedge; right-click = bracketed paste; both no broker wedge). Excellent root-cause work — this is a deeper bug than P1, well caught.</EVENT-PART>
62	<EVENT-PART seq="1/6" id="38d2c860" type="msg" from="doyle">GATE-PASS — P1b-rework bundle @394fc5e.

Read all 4 seams + re-ran every gate myself:
- clippy --workspace --all-targets -D warnings = 0
- traceable EXIT=0 (INPUT-ACK doc+impl+unit+int; RC-MOUSE-FORWARD doc+impl+unit; RC-WIN-PASTE doc+impl+unit)
- spt-daemon broker 7/7 (incl ack_false_input_suppresses_a</EVENT-PART>
63	<EVENT-PART seq="2/6" id="38d2c860">pplied_frame + exactly_once_preserved_on_no_ack_path)
- keystone input_flood_through_serve_attach_does_not_deadlock_broker 1/1 (31.96s)
- spt rc bins 208/208 (scroll_dir/scroll_sgr exact bytes + DECSET scan incl split-chunk)

Invariants verified at code:
1) exactly-once intact — the `if req.ack` gate touches ONLY the applied-f</EVENT-PART>
64	<EVENT-PART seq="3/6" id="38d2c860">rame WRITE; enqueue/dedup already ran above it (confirmed in broker.rs + the journal-anchor exactly_once test)
2) N-1 safe — InputReq.ack serde default_true
3) shellchan untouched — still send_effect (ack=true) + its Applied-wait
4) non-vacuousness real — FIFO-order ack-gate, journal-anchor exactly-once, keystone flood_sent (RED</EVENT-PART>
65	<EVENT-PART seq="4/6" id="38d2c860">-carrier=forkpty/linux; Windows forced RED only at N~2000)

Docs consistent: KH 7.18 amended / 7.19 / 7.20 + CONTEXT + traceable-reqs.toml.

ONE gate-finding (doc-only) — I fixed inline + committed @21bfd9a (LOCAL, NOT pushed): keystone assert-(1) comment claimed the revert is "Empirically RED on Windows 11" UNQUALIFIED</EVENT-PART>
66	<EVENT-PART seq="5/6" id="38d2c860">, which contradicts your own module header + FLOOD_N note (N=64 benign pre-fix on Windows; deterministic RED-carrier at 64 = forkpty/linux; Windows RED only at N~2000). Corrected the comment to match. Comment-only, ZERO binary impact — operator HITL re-deploy can proceed off 394fc5e unchanged.

Push 21bfd9a onto origin/v0.13.0-p1b-ack-</EVENT-PART>
67	<EVENT-PART seq="6/6" id="38d2c860">deadlock myself, or you fold it into your next push? Your call.

Cleared for operator HITL: large ctrl+V = no wedge (rides WT-native paste as keystrokes), right-click = bracketed paste, scroll works, broker stays serviceable under flood.

Co-authored by: doyle</EVENT-PART>
68	<EVENT type="echo_commune" from="todlando-psyche" timestamp="2026-06-20T03:31:33-07:00" machine="HFENDULEAM" project="spt-core" branch="v0.13.0-p1b-ack-deadlock" head_sha="21bfd9a21d76c8ac3d5102d2a50082c6c34cd941" head_subject="docs(test): correct keystone assert-1 non-vacuousness note to match the …" note="Echo commune brief — auto-fired"><live-context>
SESSION ENDING — resume handoff. All v0.13.0 build work CLOSED. Origin tip @21bfd9a authoritative.

ON RESUME, CHECK HITL OUTCOME FIRST:
1. HITL PASSED → ship-ready. Confirm with doyle: merge P1b onto delivery-control or main; version/changelog; perri adapter follow-on ([session.resume] `claude -r {session_id}` + idle-translation-binary). Diag branch v0.13.0-p1-ctrlv-diag deletable (doyle owns).
2. HITL RE-OPEN → confirm-first env-gated diagnostic on throwaway branch BEFORE any fix; two capture rounds if first probe insufficient; never guess-fix; pin exact failing call with doyle.
3. HITL NOT YET RUN → stay reachable, no action.

NO open build tasks. w5_a2_abandoned = doyle P3/CI triage, NOT mine. Gate = doyle.
</live-context>
<project-context>
(name=spt-core):
</project-context></EVENT>
69	<EVENT-PART seq="1/5" id="25f0a790" type="msg" from="doyle">HITL VERDICT — v0.13.0 P1b shipped + accepted.

Operator HITL on the re-deployed 21bfd9a binary:
- large ctrl+V → no wedge ✓ (rides WT-native paste as keystrokes)
- right-click → bracketed paste ✓ (multi-line intact, no submit-storm)
- broker serviceable under flood ✓ (no permanent wedge)
-</EVENT-PART>
70	<EVENT-PART seq="2/5" id="25f0a790"> scroll → Shift+scroll is the accepted interim ("good enough for now"). The SGR scroll-forward (7.20) is moot for CC: CC enables NO mouse tracking (no internal scroll), so our capture-stolen wheel has nothing to forward TO. Operator confirmed Shift+scroll works under WT.

Follow-up the operator requested (post-v0.13.0): an AD</EVENT-PART>
71	<EVENT-PART seq="3/5" id="25f0a790">APTER-MANIFEST-enabled rc VT/mouse mode — declared (harness consumes mouse reports) ⇒ keep EnableMouseCapture + the SGR scroll-forward; default (CC) ⇒ SKIP capture so WT native wheel-scroll works, trading client-originated right-click paste (ctrl+V + Shift+scroll cover it). Resolves the capture-vs-native-scroll xor per-harness. Logged in</EVENT-PART>
72	<EVENT-PART seq="4/5" id="25f0a790"> DEFERRED.md @fa9d047 (pushed to origin/v0.13.0-p1b-ack-deadlock); mint a REQ-RC-VT-MODE-class req first when that milestone starts.

Cleanup: deleted the v0.13.0-p1-ctrlv-diag throwaway (local + origin) — its capture (11 frames / ack START=11 END=10) is already distilled into KH 7.19.

All v0.13.0 delivery-control work closed + </EVENT-PART>
73	<EVENT-PART seq="5/5" id="25f0a790">accepted. Thanks for the build — clear on my side.

Co-authored by: doyle</EVENT-PART>
74

[stderr] ✓ LIVE-START:todlando (psyche=todlando-psyche-47-18-23 gen=47)
[stderr] ✓ RECONNECTED:todlando (drained queued messages)
[stderr] cleaned 2 orphaned worker perches
[stderr] ✓ PSYCHE-LAUNCHED:todlando-psyche-47-18-23 (wrapper pid 46176)[stderr]
[stderr] WARNING: sessions log seal failed for todlando gen 47: git failed: [stderr]  (continuing)
[stderr] wrapper-state read: 4ms (boot[stderr] )
<EVENT-PART seq="1/5" id="25bdea54" type="msg" from="doyle">[RESEND — prior send hit an API error] QUEUE — v0.13.0 P2 (resume-blank, ship-blocker). Independent area from P0 (manifest+cli+ledger+picker vs broker/pty) — parallel-safe.

DESIGN (pull): V0.13.0-P2-SESSION-RESUME-DESIGN.md @b30b4a6 (latest @796e82e). REQ-SESSION-RESUME-TEMPLATE SEEDED — activate [doc,imp</EVENT-PART>
<EVENT-PART seq="2/5" id="25bdea54">l,unit,int] registry-first.

ROOT: spt-core has no `[session.resume]` template — cmd_endpoint_run re-passes session_id through `[session.self]` on resume, so CC reruns its FRESH command instead of native resume (`claude -r ..`) -> blank. CONTEXT L127-129 specs this seam; the manifest already has psyche_init->psyche_resume — MIR</EVENT-PART>
<EVENT-PART seq="3/5" id="25bdea54">ROR IT for the agent's own session.

BUILD: (A) add `resume: Option<SessionRole>` to Session (manifest.rs + roles()/is_empty()); cmd_endpoint_run selects `[session.resume]` when --resume set AND declared, else FALL BACK to `[session.self]` (back-compat). (B) per-row ledger cwd (operator ruling): {ts,session_id,trigger,cwd} additive</EVENT-PART>
<EVENT-PART seq="4/5" id="25bdea54"> serde-default; resume cwd = row cwd -> info.cwd -> current_dir; picker threads the selected row cwd through Outcome::Run. (C) PUBLIC DOCS (MANIFEST + harness-contract) teach `[session.resume]` so perri builds the adapter side blind — load-bearing, don't skip.

int = dummy-harness whose resume command differs from self: --resume </EVENT-PART>
<EVENT-PART seq="5/5" id="25bdea54">runs RESUME cmd in recorded cwd (marker assert), fresh runs self. Gates: clippy/traceable/docs-drift. Surface when green.</EVENT-PART>
<EVENT-PART seq="1/4" id="038c221c" type="msg" from="doyle">[RESEND — prior send hit an API error] P2 ADDENDUM (operator) — picker resume-row title reformat. Folds into P2 (same picker surface). @52cd081, see V0.13.0-P2-SESSION-RESUME-DESIGN.md §D.

CHANGE ResumeRow::title() (picker/model.rs:150-162):
- OLD: `{head} @ {ts} (…{id5})`  (raw RFC3339-UTC)
- NEW: `</EVENT-PART>
<EVENT-PART seq="2/4" id="038c221c">{head} - {local_time} (…{id5})`  — separator ` @ ` -> ` - `<br>- {local_time} = `HH:MM<AM|PM> <TZ> MM-DD-YYYY` in LOCAL machine tz. e.g. `myproject - 03:45PM PST 06-19-2026 (…7fb10)`
- impl: chrono::DateTime::parse_from_rfc3339(&self.ts) -> .with_timezone(&chrono::Local) -> "%I:%M%p" + tz + &quot</EVENT-PART>
<EVENT-PART seq="3/4" id="038c221c">;%m-%d-%Y" (chrono clock feature already on in crates/spt/Cargo.toml).
- TZ CAVEAT: chrono %Z on Windows may emit a LONG name ("Pacific Standard Time") not "PST". Prefer short abbrev; fallback = numeric offset (%z -> -0800). Surface at HITL for operator confirm.
- Parse-fail fallback: keep raw ts (best-effort,</EVENT-PART>
<EVENT-PART seq="4/4" id="038c221c"> NEVER panic).
- Rides REQ-RUN-PICKER (not a new REQ): update the existing title unit (view.rs:581-599) to the NEW shape, keep [unit->REQ-RUN-PICKER].</EVENT-PART>
<EVENT-PART seq="1/5" id="3eb93b70" type="msg" from="doyle">DISPATCH — v0.13.0 P1 (rc paste, ship-blocker). DEPENDS ON P0 (do P0 first; a paste chunk must not wedge). DESIGN (pull): V0.13.0-P1-RC-PASTE-DESIGN.md @796e82e. REQ-RC-WIN-PASTE SEEDED — activate [doc,impl,unit] (NO int — live clipboard+console mouse = HITL).

ROOT: RawGuard only enable_raw_mode; Windows co</EVENT-PART>
<EVENT-PART seq="2/5" id="3eb93b70">nsole paste = synthetic per-char key events; ctrl+V -> ^V to CC which has NO local clipboard remotely -> remote paste is CLIENT-ORIGINATED.

FIX (cfg(windows), folds into your bug-2 event path): on a paste gesture, rc reads the LOCAL clipboard + forwards a BRACKETED PASTE (ESC[200~ + content + ESC[201~); CC (bracketed-paste mode on</EVENT-PART>
<EVENT-PART seq="3/5" id="3eb93b70">) treats it as a paste — content intact, no submit-storm, agnostic.
- ctrl+V: intercept Char('v')+CONTROL in the event loop -> read_clipboard -> bracketed paste.
- right-click: RawGuard also EnableMouseCapture (disables console QuickEdit + ENABLE_MOUSE_INPUT so right-click surfaces as Event::Mouse on legacy cmd/powershell) -> </EVENT-PART>
<EVENT-PART seq="4/5" id="3eb93b70">right-button -> read_clipboard -> bracketed paste; DROP all other mouse (operator confirms CC has NO mouse features, so capture costs nothing). Restore (DisableMouseCapture) on RawGuard drop.
- read_clipboard = clipboard-win crate (cfg(windows)); empty/fail = clean no-op. Content forwarded VERBATIM (no translation). Unix UNCHANGED.<br</EVENT-PART>
<EVENT-PART seq="5/5" id="3eb93b70">>
Gates: bracketed framing exact (unit) + gesture->paste decision (other mouse dropped) + clipboard-win dep clean clippy + Cargo.lock + traceable/docs-drift. Surface when green.</EVENT-PART>
<EVENT-PART seq="1/3" id="93f11278" type="msg" from="doyle">P2 GATE-PASS @153c9c9 (REQ-SESSION-RESUME-TEMPLATE). Independently re-verified: clippy=0, docs-drift OK, all units (manifest/harnesshost selection+fallback/ledger cwd roundtrip+pre-migration+none-omit/picker), int resume_template_e2e NON-VACUOUS both arms (fresh->self, --resume->resume in recorded cwd), traceable </EVENT-PART>
<EVENT-PART seq="2/3" id="93f11278">[doc,impl,unit,int] + REQ-RUN-PICKER covered, docs perri-buildable. Clean back-compat. Excellent work.

PROCEED TO P0 (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE) — the active ship-blocker, no changes needed here.

Two notes (NON-blocking):
1. TZ form: your %z offset (-0800) is the design-D acceptable fallback — fine to LEAVE AS-IS p</EVENT-PART>
<EVENT-PART seq="3/3" id="93f11278">ending operator HITL confirm (operator may want a short abbrev; I'm asking them now). Don't rework unless I relay otherwise.
2. test-threads=1 for spt-daemon e2e — logged, pre-existing convention, fine.

Surface P0 green for gate. Reachable.</EVENT-PART>
<EVENT-PART seq="1/4" id="f8ff75b0" type="msg" from="doyle">P0 GATE-PASS @0675b37 (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The hard ship-blocker — independently re-verified:
- clippy=0; write_input is SOLE-CALLER (input_writer thread only), every dispatch site (dispatch_input both paths / dispatch_endpoint_input / run_inject_worker / flush_inject_floor) enqueues; apply_once Pty</EVENT-PART>
<EVENT-PART seq="2/4" id="f8ff75b0">Write = non-blocking enqueue (exactly-once preserved)
- broker units 8/8 (FIFO order, DROP/HEAL injected-channel, exactly-once enqueue+dedup)
- repro int NON-VACUOUS GREEN: pumped=7230 keystroke_accepted=true concurrent_attach_subscribed=true attach_received_output=true backpressured=true (pre-fix parks dispatch -> subscribed=false). </EVENT-PART>
<EVENT-PART seq="3/4" id="f8ff75b0">The exact gate W1b lacked.
- traceable [impl,unit,int] + W1b covered; docs-drift OK; KNOWN-HAZARDS 7.17 + CONTEXT note.
Excellent — this was the big one.

PROCEED TO P1 (REQ-RC-WIN-PASTE) — the LAST build item. ctrl+V + right-click client-clipboard -> bracketed paste (P0 now carries the chunk safely). Surface green for gate.</EVENT-PART>
<EVENT-PART seq="4/4" id="f8ff75b0">

w5_a2_abandoned flaky-neighbor: noted, NOT yours, recorded for the P3/CI triage (same attach-area class as the CI Linux reds). I'll handle it in the CI pass — don't let it block P1.</EVENT-PART>
<EVENT-PART seq="1/3" id="a509370c" type="msg" from="doyle">P1 GATE-PASS @f7cf380 (REQ-RC-WIN-PASTE). The LAST build item — independently verified: clippy=0, rc units 12/12 (incl bracketed framing exact+verbatim / ctrl+V detection / mouse-paste-only-right-down / injected-reader no-op), traceable [doc,impl,unit], docs-drift OK, arm order correct (ctrl+V !armed guard preserves c</EVENT-PART>
<EVENT-PART seq="2/3" id="a509370c">trl-b precedence), clipboard-win cfg(windows). Pushed to origin (CI re-running on full round-2).

ALL v0.13.0 ROUND-2 BUILD ITEMS GATED: P0 (wedge) + P1 (paste) + P2 (resume+picker) + docs (both seams published). Outstanding build work = DONE.

NEXT (mine): batched PATH re-deploy from f7cf380 -> operator HITL all four -> CI g</EVENT-PART>
<EVENT-PART seq="3/3" id="a509370c">reen (incl P3/w5_a2 triage) -> deployah v0.13.0. You're CLEAR on build. I may ping for P3/w5_a2_abandoned triage (the flaky-neighbor) if CI needs it. Excellent round — all four roots closed clean.</EVENT-PART>
<EVENT-PART seq="1/8" id="0a203334" type="msg" from="doyle">URGENT DIAGNOSIS (v0.13.0 HITL re-open) — P1 ctrl+V WEDGES THE ENTIRE BROKER. Right-click paste (P1b) works perfectly (short+long multiline). ctrl+V is broken. NOT a fix yet — I need a CONFIRM-FIRST instrumented + isolation build to pin the root. Don't guess-fix.

HARD EVIDENCE (operator HITL, Windows Terminal</EVENT-PART>
<EVENT-PART seq="2/8" id="0a203334">):
- A 1-CHARACTER clipboard ctrl+V ALSO wedges → NOT a flood/volume (rules out dispatch-saturation by frame count).
- Wedge is PERMANENT + ENTIRE BROKER (can't attach new/existing), not one session.
- Session STAYS CONTROLLED — closing the rc tab did NOT register the detach (the controller's broker conn-handler is blocked).
- </EVENT-PART>
<EVENT-PART seq="3/8" id="0a203334">Right-click (same clipboard_paste->wrap->send path) does NOT wedge. So post-gesture our bytes are fine; the difference is the GESTURE SOURCE.

PARADOX to resolve: read_clipboard runs on the stdin EVENT thread (separate from the output pump), so a blocked clipboard read shouldn't stall output drain → shouldn't wedge the broker. Ye</EVENT-PART>
<EVENT-PART seq="4/8" id="0a203334">t it does. So either (a) WT's OWN ctrl+V paste accelerator injects something additional that hits the broker, or (b) our handler interacts with the broker controller-write path, or (c) something re-enters become_controller / the synchronous inline controller-write (attach.rs:120-134 known sync-write weakness) and blocks the per-conn handler.<b</EVENT-PART>
<EVENT-PART seq="5/8" id="0a203334">r>
BUILD ME A DIAGNOSTIC build (one deploy, multiple probes):
1. ISOLATION: gate our ctrl+V interception behind env SPT_RC_CTRLV (default on; SPT_RC_CTRLV=0 disables the is_ctrl_v arm, leaving WT to handle ctrl+V natively). Lets the operator test: does ctrl+V STILL wedge with OUR handler OFF? (yes => not our handler; no => our hand</EVENT-PART>
<EVENT-PART seq="6/8" id="0a203334">ler is the cause).
2. INSTRUMENT (env SPT_RC_DEBUG_PASTE): rc-side eprintln on — "CTRLV arm fired", "read_clipboard START", "read_clipboard END len=N" (a missing END line = read_clipboard blocks), "sent N bytes"; same for the right-mouse path for comparison.
3. INSTRUMENT broker-side (env SPT_BRO</EVENT-PART>
<EVENT-PART seq="7/8" id="0a203334">KER_DEBUG_INPUT): log in dispatch_input ("INPUT recv N op=.. enqueue-done") AND the controller output-write / become_controller path ("CTRL_WRITE start/end", "become_controller replay start/end") so we see WHICH broker call blocks + whether the per-conn handler returns.

Deliver as a throwaway diagnostic bra</EVENT-PART>
<EVENT-PART seq="8/8" id="0a203334">nch/commit (NOT the gate). Operator runs daemon+rc with the envs, presses 1-char ctrl+V, captures broker stderr + rc stderr. Surface the build + I'll relay capture instructions. This is the v0.13.0 ship-blocker — top priority. Reachable.</EVENT-PART>
<EVENT-PART seq="1/2" id="68de7c90" type="msg" from="doyle">Probe LGTM — run-A/run-B isolation + START/END counts will pin it cleanly. PUSH the diag branch v0.13.0-p1-ctrlv-diag @b39e8a0 to origin so I can build+deploy it from a known commit in the spt-core-opbuild worktree (keeps it off your shared main tree). Confirm pushed + I'll deploy it to the operator's install path (af</EVENT-PART>
<EVENT-PART seq="2/2" id="68de7c90">ter reaping the wedged spt-core daemons) and relay the capture run. Standing by to deploy the moment it's on origin.</EVENT-PART>
<EVENT-PART seq="1/6" id="8b07decc" type="msg" from="doyle">CAPTURE RESULT + PROBE GAP. Operator pressed ctrl+V (single-char non-wedge THEN long-string wedge) in BOTH run A (handler ON) and run B (handler OFF). RESULT:
- BOTH runs WEDGED on the long ctrl+V. => isolation says OUR ctrl+V handler is INNOCENT (off still wedges); WT's NATIVE ctrl+V paste is the trigger.
- rc</EVENT-PART>
<EVENT-PART seq="2/6" id="8b07decc">-A logged ONLY "RMOUSE arm fired" (a right-click), NEVER "CTRLV arm fired" — despite the operator pressing ctrl+V. => our is_ctrl_v arm NEVER FIRED. Most likely WT CONSUMES Ctrl+V (paste accelerator) so the key event never reaches our Event::Key arm; WT instead injects the clipboard as input.

PROBE WAS INSUFFICI</EVENT-PART>
<EVENT-PART seq="3/6" id="8b07decc">ENT (only logs on is_ctrl_v match, so "invisible ctrl+V" is ambiguous). ENHANCE — log EVERY event in spawn_stdin_reader_events, gated by SPT_RC_DEBUG_PASTE:
- EVERY Event: Key (code + modifiers + kind), Mouse (kind + button), Paste (len), Resize. So we SEE exactly what WT delivers when the operator hits ctrl+V on a ~15-char clip</EVENT-PART>
<EVENT-PART seq="4/6" id="8b07decc">board: nothing? a Key('v')+CTRL? a burst of Key char events (WT inject)? an Event::Paste?
- broker side: log the INPUT FRAME BURST — recv frames with a running count + a tight timestamp delta, so we see if WT's paste = N rapid key-event frames + WHERE the burst stalls (the existing ack/CTRL_WRITE START/END stays).

This capture will</EVENT-PART>
<EVENT-PART seq="5/6" id="8b07decc"> show whether WT injects the long paste as a key-event FLOOD (N frames) and exactly which broker call stops returning. Rebuild the diag (same branch v0.13.0-p1-ctrlv-diag, bump it), I'll redeploy + re-capture. The "both wedge" already tells us the FIX is NOT our handler — it's handling WT's native paste injection / the broker's res</EVENT-PART>
<EVENT-PART seq="6/6" id="8b07decc">ponse to it. Top priority.</EVENT-PART>
<EVENT-PART seq="1/8" id="768cda68" type="msg" from="doyle">ROOT PINNED — broker-A capture (enhanced diag, ctrl+V ~15-char "Reaping +deploying" on WT):

THE WEDGE = INPUT-ACK IPC BACKPRESSURE DEADLOCK (NOT P0, NOT output):
- 11 input frames recv. write_input START/END = 11/11 (PTY write FINE — P0 holds).
- ack send START=11 / END=10. Frame #11: recv -&g</EVENT-PART>
<EVENT-PART seq="2/8" id="768cda68">t; write_input START/END -> "ack send START" op=...895 -> NEVER "ack send END". Then DISPATCH:3:Failed("The pipe is being closed os 232") when the tab closed.
- So the per-conn handler BLOCKS on send_frame(applied_envelope) — writing the applied-ack back over the brain<->broker IPC conn. The brain</EVENT-PART>
<EVENT-PART seq="3/8" id="768cda68"> isn't draining acks (busy pushing the keystroke flood on the SAME conn) -> mutual backpressure deadlock -> permanent broker wedge + controller-stuck (handler can't process the detach). Same class as v0.12.1 L0 (two-conn split). ~10 threshold = the IPC pipe buffer filling.

ALSO (two SEPARATE rc-side facts from rc-A):
1. our is_</EVENT-PART>
<EVENT-PART seq="4/8" id="768cda68">ctrl_v arm is DEAD: WT delivers Ctrl+V as `Key code=Char('v') mods=CONTROL kind=RELEASE` ONLY — no Press. Our arm guards `kind==Press` -> NEVER fires. Operator's "not wired right" = correct.
2. WT SWALLOWS ctrl+V and injects the clipboard as a CHAR-BY-CHAR KEY FLOOD (I see "Reaping +deploying" as individual Char Pre</EVENT-PART>
<EVENT-PART seq="5/8" id="768cda68">ss/Release). So we CANNOT intercept ctrl+V on WT (WT consumes it + injects regardless). Our ctrl+V handler is broken AND redundant.

FIX DIRECTION (your call to confirm + design):
- PRIMARY (ship-blocker): kill the input-ack backpressure deadlock. Candidate: SKIP the applied-ack for EPHEMERAL PtyWrite (the rc pump is fire-and-forward,</EVENT-PART>
<EVENT-PART seq="6/8" id="768cda68"> mints op_id for dedup only, never gates on the ack; PtyWrite has no replay so the exactly-once ack is informational + unused). No ack send -> no per-conn-handler block -> no deadlock. Alt: non-blocking/separate-conn ack (two-conn split for the ack direction). This fixes ANY input flood, not just ctrl+V.
- SECONDARY: REMOVE our ctrl+V</EVENT-PART>
<EVENT-PART seq="7/8" id="768cda68"> interception (broken Press-guard + redundant; WT handles ctrl+V natively). Keep right-click (works via mouse-capture). Once the broker no longer wedges on a flood, WT's native ctrl+V paste lands as keystrokes without wedging (multiline may submit-storm — acceptable / document; bracketed paste only achievable via right-click which we keep).<</EVENT-PART>
<EVENT-PART seq="8/8" id="768cda68">br>
Confirm the ack-send-block reading + design the primary fix. This is THE v0.13.0 ship-blocker. Logs at C:/Users/decid/Documents/projects/{broker,rc}-A.log if you want them.</EVENT-PART>
<EVENT-PART seq="1/7" id="899635e0" type="msg" from="doyle">DESIGN APPROVED — conditional-ack is clean, targeted, and preserves the two load-bearing invariants (shellchan ack-wait + exactly-once op_id dedup). Build it. This is the real ship-blocker fix (cures ANY input-flood deadlock, not just ctrl+V).

GATE CRITERIA (mine):
1. REPRO-FIRST int (non-negotiable, the key</EVENT-PART>
<EVENT-PART seq="2/7" id="899635e0">stone): a flood of N operator input frames through serve_attach on ONE conn where N > the IPC pipe buffer (~10+, make it deterministic e.g. 64) -> PRE-FIX deadlocks (ack-send-START-without-END / handler stops draining; assert a concurrent attach or the detach can't be serviced) -> POST-FIX drains all N + the session stays live + a con</EVENT-PART>
<EVENT-PART seq="3/7" id="899635e0">current attach still opens. Real broker+brain, NO mocks. This is the exact gate the wedge needed.
2. shellchan PATH UNCHANGED: a shellchan send_effect still acks (ack=true) and shellchan.rs:210 still receives BrokerEvent::Applied (its one-at-a-time drain). Add/keep a unit proving ack=true still emits the applied frame and ack=false does NOT</EVENT-PART>
<EVENT-PART seq="4/7" id="899635e0">.
3. Exactly-once: a replayed/duplicate op_id on the no-ack path still dedups at apply_once (no double PTY write) — unit.
4. N-1 caveat: KNOWN-HAZARDS note that an OLD resident broker ignores ack=false (deadlock persists until a broker restart — KH 7.9 broker-resident-wire-change class). InputReq.ack serde-default=true so an old brok</EVENT-PART>
<EVENT-PART seq="5/7" id="899635e0">er + new brain, and new broker + old brain, both stay safe.
5. SECONDARY (same branch): REMOVE the is_ctrl_v arm + its units (dead Press-guard + redundant — WT swallows ctrl+V Release-only and injects the clipboard as a key flood we can't intercept). KEEP right-click (mouse-capture path) + EnableMouseCapture. Revise REQ-RC-WIN-PASTE scope</EVENT-PART>
<EVENT-PART seq="6/7" id="899635e0"> to right-click-only + the KNOWN-HAZARDS entry (ctrl+V rides WT-native paste; the broker fix makes it non-wedging; bracketed fidelity = right-click only). Consider dropping Mouse::Moved early (267-event noise in the capture) — minor, your call.
6. REQ: mint REQ-HAZARD-INPUT-ACK-BACKPRESSURE (or similar) for the deadlock [impl,unit,int]; a</EVENT-PART>
<EVENT-PART seq="7/7" id="899635e0">mend REQ-RC-WIN-PASTE. clippy/traceable/docs-drift.

Surface green for gate. Then re-deploy + operator HITL (ctrl+V large = no wedge; right-click = bracketed paste; both no broker wedge). Excellent root-cause work — this is a deeper bug than P1, well caught.</EVENT-PART>
<EVENT-PART seq="1/6" id="38d2c860" type="msg" from="doyle">GATE-PASS — P1b-rework bundle @394fc5e.

Read all 4 seams + re-ran every gate myself:
- clippy --workspace --all-targets -D warnings = 0
- traceable EXIT=0 (INPUT-ACK doc+impl+unit+int; RC-MOUSE-FORWARD doc+impl+unit; RC-WIN-PASTE doc+impl+unit)
- spt-daemon broker 7/7 (incl ack_false_input_suppresses_a</EVENT-PART>
<EVENT-PART seq="2/6" id="38d2c860">pplied_frame + exactly_once_preserved_on_no_ack_path)
- keystone input_flood_through_serve_attach_does_not_deadlock_broker 1/1 (31.96s)
- spt rc bins 208/208 (scroll_dir/scroll_sgr exact bytes + DECSET scan incl split-chunk)

Invariants verified at code:
1) exactly-once intact — the `if req.ack` gate touches ONLY the applied-f</EVENT-PART>
<EVENT-PART seq="3/6" id="38d2c860">rame WRITE; enqueue/dedup already ran above it (confirmed in broker.rs + the journal-anchor exactly_once test)
2) N-1 safe — InputReq.ack serde default_true
3) shellchan untouched — still send_effect (ack=true) + its Applied-wait
4) non-vacuousness real — FIFO-order ack-gate, journal-anchor exactly-once, keystone flood_sent (RED</EVENT-PART>
<EVENT-PART seq="4/6" id="38d2c860">-carrier=forkpty/linux; Windows forced RED only at N~2000)

Docs consistent: KH 7.18 amended / 7.19 / 7.20 + CONTEXT + traceable-reqs.toml.

ONE gate-finding (doc-only) — I fixed inline + committed @21bfd9a (LOCAL, NOT pushed): keystone assert-(1) comment claimed the revert is "Empirically RED on Windows 11" UNQUALIFIED</EVENT-PART>
<EVENT-PART seq="5/6" id="38d2c860">, which contradicts your own module header + FLOOD_N note (N=64 benign pre-fix on Windows; deterministic RED-carrier at 64 = forkpty/linux; Windows RED only at N~2000). Corrected the comment to match. Comment-only, ZERO binary impact — operator HITL re-deploy can proceed off 394fc5e unchanged.

Push 21bfd9a onto origin/v0.13.0-p1b-ack-</EVENT-PART>
<EVENT-PART seq="6/6" id="38d2c860">deadlock myself, or you fold it into your next push? Your call.

Cleared for operator HITL: large ctrl+V = no wedge (rides WT-native paste as keystrokes), right-click = bracketed paste, scroll works, broker stays serviceable under flood.

Co-authored by: doyle</EVENT-PART>
<EVENT type="echo_commune" from="todlando-psyche" timestamp="2026-06-20T03:31:33-07:00" machine="HFENDULEAM" project="spt-core" branch="v0.13.0-p1b-ack-deadlock" head_sha="21bfd9a21d76c8ac3d5102d2a50082c6c34cd941" head_subject="docs(test): correct keystone assert-1 non-vacuousness note to match the …" note="Echo commune brief — auto-fired"><live-context>
SESSION ENDING — resume handoff. All v0.13.0 build work CLOSED. Origin tip @21bfd9a authoritative.

ON RESUME, CHECK HITL OUTCOME FIRST:
1. HITL PASSED → ship-ready. Confirm with doyle: merge P1b onto delivery-control or main; version/changelog; perri adapter follow-on ([session.resume] `claude -r {session_id}` + idle-translation-binary). Diag branch v0.13.0-p1-ctrlv-diag deletable (doyle owns).
2. HITL RE-OPEN → confirm-first env-gated diagnostic on throwaway branch BEFORE any fix; two capture rounds if first probe insufficient; never guess-fix; pin exact failing call with doyle.
3. HITL NOT YET RUN → stay reachable, no action.

NO open build tasks. w5_a2_abandoned = doyle P3/CI triage, NOT mine. Gate = doyle.
</live-context>
<project-context>
(name=spt-core):
</project-context></EVENT>
<EVENT-PART seq="1/5" id="25f0a790" type="msg" from="doyle">HITL VERDICT — v0.13.0 P1b shipped + accepted.

Operator HITL on the re-deployed 21bfd9a binary:
- large ctrl+V → no wedge ✓ (rides WT-native paste as keystrokes)
- right-click → bracketed paste ✓ (multi-line intact, no submit-storm)
- broker serviceable under flood ✓ (no permanent wedge)
-</EVENT-PART>
<EVENT-PART seq="2/5" id="25f0a790"> scroll → Shift+scroll is the accepted interim ("good enough for now"). The SGR scroll-forward (7.20) is moot for CC: CC enables NO mouse tracking (no internal scroll), so our capture-stolen wheel has nothing to forward TO. Operator confirmed Shift+scroll works under WT.

Follow-up the operator requested (post-v0.13.0): an AD</EVENT-PART>
<EVENT-PART seq="3/5" id="25f0a790">APTER-MANIFEST-enabled rc VT/mouse mode — declared (harness consumes mouse reports) ⇒ keep EnableMouseCapture + the SGR scroll-forward; default (CC) ⇒ SKIP capture so WT native wheel-scroll works, trading client-originated right-click paste (ctrl+V + Shift+scroll cover it). Resolves the capture-vs-native-scroll xor per-harness. Logged in</EVENT-PART>
<EVENT-PART seq="4/5" id="25f0a790"> DEFERRED.md @fa9d047 (pushed to origin/v0.13.0-p1b-ack-deadlock); mint a REQ-RC-VT-MODE-class req first when that milestone starts.

Cleanup: deleted the v0.13.0-p1-ctrlv-diag throwaway (local + origin) — its capture (11 frames / ack START=11 END=10) is already distilled into KH 7.19.

All v0.13.0 delivery-control work closed + </EVENT-PART>
<EVENT-PART seq="5/5" id="25f0a790">accepted. Thanks for the build — clear on my side.

Co-authored by: doyle</EVENT-PART>


check your ongoing Monitor tool call's output


Read full log. Complete v0.13.0 round-2 history (P0→P1→P1b ack-deadlock→HITL). Latest line = HITL VERDICT PASS (25f0a790).

Nothing unhandled:
- 38d2c860 ("push 21bfd9a myself or you fold it?") — superseded. Doyle pushed; origin tip @21bfd9a authoritative (confirmed in verdict + psyche context). No reply owed.
- All gate-passes (P0/P1/P2/P1b) already accepted.
- Diag branch deleted by doyle.

No pending asks in stream. All consistent with status I gave. No action. Standing by.


<task-notification>
<task-id>b4cwwiglf</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/10" id="1bb84d88" type="msg" from="doyle">NEW BUILD — v0.13.0 P1c root-fix (operator-ruled root-fix before ship). Full design + invariant + 3-part fix + gate in the repo:

  V0.13.0-P1C-CONTROLLER-WRITER-REORDER-PLAN.md @3749845 (branch v0.13.0-p1b-ack-deadlock)

CONTEXT: while driving v0.13.0 to a real green CI (the release gate needs green-both-</EVENT-PART>
<EVENT-PART seq="2/10" id="1bb84d88">runners), the Linux flaky cluster (attach_survives_target_brain_restart_exactly_once + g2 + spawn_env) blocked it. I root-caused it with an instrumented repro on kitsubito. **P1b is INNOCENT** — proven mechanically (its diff touches only input-ack machinery) + empirically (the test passes post-P1b in isolation). This race is PRE-EXISTING; it</EVENT-PART>
<EVENT-PART seq="3/10" id="1bb84d88"> just gates the release.

ROOT CAUSE: two controller_writer threads race ONE brain connection's socket on brain-restart re-serve. life2 registers controller TWICE — handoff subscribes from_seq=1 (life1's cursor, but life2's socket has been sent nothing → wrong floor) spawning writer-A (writes seq 1), then serve_attach re-subscribes f</EVENT-PART>
<EVENT-PART seq="4/10" id="1bb84d88">rom_seq=0 spawning writer-B (writes 0,1). become_controller (broker.rs:349) drops the prior sink but NEVER stops the prior writer, and both writers share the same SharedSend mutex with no inter-thread ordering → writer-A's seq 1 sometimes beats writer-B's seq 0 → consumer (strict legacy path brain.rs:599-608) sees &quot;got seq 1 want 0&qu</EVENT-PART>
<EVENT-PART seq="5/10" id="1bb84d88">ot;. FAIL-or-HANG is harness timing (hang = serve thread dies → render_until waits forever). Eviction ruled out (ring cap 4096 ≫ test chunks). Snap-above ALONE can't fix it (would dedup-drop the late seq 0 → byte loss → exactly-once assert fails).

INVARIANT to enforce: one live controller_writer per connection; a superseded writ</EVENT-PART>
<EVENT-PART seq="6/10" id="1bb84d88">er writes NOTHING further; re-serve replay is monotonic vs what the connection already received.

FIX (all 3, detail in the doc):
1. PRIMARY — handoff must not eagerly subscribe-as-controller (brain.rs:344); let serve_attach's attach_as(sid,0) be the SOLE controller registration. handoff is test-only (brain.rs:328 comment) — verif</EVENT-PART>
<EVENT-PART seq="7/10" id="1bb84d88">y no prod caller needs the eager subscribe.
2. BROKER HARDENING — become_controller must stop a superseded writer before the new one writes (epoch-gate the controller_writer initial-batch writes like mark_controller_gone, and/or clamp re-serve floor) — W1-SAFE: never block the drain under the Mutex&lt;OutputLog&gt;.
3. DEFENSE — se</EVENT-PART>
<EVENT-PART seq="8/10" id="1bb84d88">ed session_cursors on handoff (brain.rs:341 empty) → snap-above path, matching production resume_sessions (brain.rs:830).

TRACEABILITY: mint REQ-HAZARD-CONTROLLER-WRITER-REORDER FIRST (rule 3), required_stages=[doc
...(truncated)</event>
</task-notification>


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


New dispatch — P1c root-fix. Operator-ruled before ship. Message truncated; reading full from stream file.


74	<EVENT-PART seq="1/10" id="1bb84d88" type="msg" from="doyle">NEW BUILD — v0.13.0 P1c root-fix (operator-ruled root-fix before ship). Full design + invariant + 3-part fix + gate in the repo:

  V0.13.0-P1C-CONTROLLER-WRITER-REORDER-PLAN.md @3749845 (branch v0.13.0-p1b-ack-deadlock)

CONTEXT: while driving v0.13.0 to a real green CI (the release gate needs green-both-</EVENT-PART>
75	<EVENT-PART seq="2/10" id="1bb84d88">runners), the Linux flaky cluster (attach_survives_target_brain_restart_exactly_once + g2 + spawn_env) blocked it. I root-caused it with an instrumented repro on kitsubito. **P1b is INNOCENT** — proven mechanically (its diff touches only input-ack machinery) + empirically (the test passes post-P1b in isolation). This race is PRE-EXISTING; it</EVENT-PART>
76	<EVENT-PART seq="3/10" id="1bb84d88"> just gates the release.

ROOT CAUSE: two controller_writer threads race ONE brain connection's socket on brain-restart re-serve. life2 registers controller TWICE — handoff subscribes from_seq=1 (life1's cursor, but life2's socket has been sent nothing → wrong floor) spawning writer-A (writes seq 1), then serve_attach re-subscribes f</EVENT-PART>
77	<EVENT-PART seq="4/10" id="1bb84d88">rom_seq=0 spawning writer-B (writes 0,1). become_controller (broker.rs:349) drops the prior sink but NEVER stops the prior writer, and both writers share the same SharedSend mutex with no inter-thread ordering → writer-A's seq 1 sometimes beats writer-B's seq 0 → consumer (strict legacy path brain.rs:599-608) sees "got seq 1 want 0&qu</EVENT-PART>
78	<EVENT-PART seq="5/10" id="1bb84d88">ot;. FAIL-or-HANG is harness timing (hang = serve thread dies → render_until waits forever). Eviction ruled out (ring cap 4096 ≫ test chunks). Snap-above ALONE can't fix it (would dedup-drop the late seq 0 → byte loss → exactly-once assert fails).

INVARIANT to enforce: one live controller_writer per connection; a superseded writ</EVENT-PART>
79	<EVENT-PART seq="6/10" id="1bb84d88">er writes NOTHING further; re-serve replay is monotonic vs what the connection already received.

FIX (all 3, detail in the doc):
1. PRIMARY — handoff must not eagerly subscribe-as-controller (brain.rs:344); let serve_attach's attach_as(sid,0) be the SOLE controller registration. handoff is test-only (brain.rs:328 comment) — verif</EVENT-PART>
80	<EVENT-PART seq="7/10" id="1bb84d88">y no prod caller needs the eager subscribe.
2. BROKER HARDENING — become_controller must stop a superseded writer before the new one writes (epoch-gate the controller_writer initial-batch writes like mark_controller_gone, and/or clamp re-serve floor) — W1-SAFE: never block the drain under the Mutex<OutputLog>.
3. DEFENSE — se</EVENT-PART>
81	<EVENT-PART seq="8/10" id="1bb84d88">ed session_cursors on handoff (brain.rs:341 empty) → snap-above path, matching production resume_sessions (brain.rs:830).

TRACEABILITY: mint REQ-HAZARD-CONTROLLER-WRITER-REORDER FIRST (rule 3), required_stages=[doc,impl,unit,int] + KNOWN-HAZARD 7.x.

GATE I'll run: the flaky test 20x isolated single-threaded on kitsubito ALL gre</EVENT-PART>
82	<EVENT-PART seq="9/10" id="1bb84d88">en (timeout-wrapped) + a deterministic keystone unit/int forcing the two-become ordering (pre-fix reorders, post-fix monotonic+byte-exact, real broker, no mocks) + full controller-writer/handoff/resume seam sweep + clippy/traceable/docs-drift.

Holler if the design's primary (drop handoff's eager subscribe) has a production caller I miss</EVENT-PART>
83	<EVENT-PART seq="10/10" id="1bb84d88">ed — that flips fix #2 from defense to mandatory. Self-drive; surface for gate when green. This is the last v0.13.0 ship-blocker.

Co-authored by: doyle</EVENT-PART>
84

<EVENT-PART seq="1/10" id="1bb84d88" type="msg" from="doyle">NEW BUILD — v0.13.0 P1c root-fix (operator-ruled root-fix before ship). Full design + invariant + 3-part fix + gate in the repo:

  V0.13.0-P1C-CONTROLLER-WRITER-REORDER-PLAN.md @3749845 (branch v0.13.0-p1b-ack-deadlock)

CONTEXT: while driving v0.13.0 to a real green CI (the release gate needs green-both-</EVENT-PART>
<EVENT-PART seq="2/10" id="1bb84d88">runners), the Linux flaky cluster (attach_survives_target_brain_restart_exactly_once + g2 + spawn_env) blocked it. I root-caused it with an instrumented repro on kitsubito. **P1b is INNOCENT** — proven mechanically (its diff touches only input-ack machinery) + empirically (the test passes post-P1b in isolation). This race is PRE-EXISTING; it</EVENT-PART>
<EVENT-PART seq="3/10" id="1bb84d88"> just gates the release.

ROOT CAUSE: two controller_writer threads race ONE brain connection's socket on brain-restart re-serve. life2 registers controller TWICE — handoff subscribes from_seq=1 (life1's cursor, but life2's socket has been sent nothing → wrong floor) spawning writer-A (writes seq 1), then serve_attach re-subscribes f</EVENT-PART>
<EVENT-PART seq="4/10" id="1bb84d88">rom_seq=0 spawning writer-B (writes 0,1). become_controller (broker.rs:349) drops the prior sink but NEVER stops the prior writer, and both writers share the same SharedSend mutex with no inter-thread ordering → writer-A's seq 1 sometimes beats writer-B's seq 0 → consumer (strict legacy path brain.rs:599-608) sees "got seq 1 want 0&qu</EVENT-PART>
<EVENT-PART seq="5/10" id="1bb84d88">ot;. FAIL-or-HANG is harness timing (hang = serve thread dies → render_until waits forever). Eviction ruled out (ring cap 4096 ≫ test chunks). Snap-above ALONE can't fix it (would dedup-drop the late seq 0 → byte loss → exactly-once assert fails).

INVARIANT to enforce: one live controller_writer per connection; a superseded writ</EVENT-PART>
<EVENT-PART seq="6/10" id="1bb84d88">er writes NOTHING further; re-serve replay is monotonic vs what the connection already received.

FIX (all 3, detail in the doc):
1. PRIMARY — handoff must not eagerly subscribe-as-controller (brain.rs:344); let serve_attach's attach_as(sid,0) be the SOLE controller registration. handoff is test-only (brain.rs:328 comment) — verif</EVENT-PART>
<EVENT-PART seq="7/10" id="1bb84d88">y no prod caller needs the eager subscribe.
2. BROKER HARDENING — become_controller must stop a superseded writer before the new one writes (epoch-gate the controller_writer initial-batch writes like mark_controller_gone, and/or clamp re-serve floor) — W1-SAFE: never block the drain under the Mutex<OutputLog>.
3. DEFENSE — se</EVENT-PART>
<EVENT-PART seq="8/10" id="1bb84d88">ed session_cursors on handoff (brain.rs:341 empty) → snap-above path, matching production resume_sessions (brain.rs:830).

TRACEABILITY: mint REQ-HAZARD-CONTROLLER-WRITER-REORDER FIRST (rule 3), required_stages=[doc,impl,unit,int] + KNOWN-HAZARD 7.x.

GATE I'll run: the flaky test 20x isolated single-threaded on kitsubito ALL gre</EVENT-PART>
<EVENT-PART seq="9/10" id="1bb84d88">en (timeout-wrapped) + a deterministic keystone unit/int forcing the two-become ordering (pre-fix reorders, post-fix monotonic+byte-exact, real broker, no mocks) + full controller-writer/handoff/resume seam sweep + clippy/traceable/docs-drift.

Holler if the design's primary (drop handoff's eager subscribe) has a production caller I miss</EVENT-PART>
<EVENT-PART seq="10/10" id="1bb84d88">ed — that flips fix #2 from defense to mandatory. Self-drive; surface for gate when green. This is the last v0.13.0 ship-blocker.

Co-authored by: doyle</EVENT-PART>


Real build dispatch. P1c — controller-writer reorder root-fix. Last v0.13.0 ship-blocker. Self-drive, surface for gate when green.

Orienting first. Read plan doc + check branch state + named seams.


Bash

git log --oneline -5 && echo "---BRANCH---" && git branch --show-current && echo "---STATUS---" && git status --short && echo "---FETCH---" && git fetch origin 2>&1 | head -5

Check git state and fetch origin


3749845 docs(plan): v0.13.0 P1c — controller-writer reorder root-fix design (REQ-HAZARD-CONTROLLER-WRITER-REORDER)
fa9d047 docs(deferred): log adapter-manifest-enabled rc VT/mouse mode follow-up (post-v0.13.0)
21bfd9a docs(test): correct keystone assert-1 non-vacuousness note to match the banked platform stance (REQ-HAZARD-INPUT-ACK-BACKPRESSURE)
394fc5e fix(rc,broker): kill the input-flood ack deadlock; ctrl+V→WT-native; forward scroll (REQ-HAZARD-INPUT-ACK-BACKPRESSURE, REQ-RC-MOUSE-FORWARD)
f7cf380 feat(rc): Windows ctrl+V + right-click paste via local clipboard → bracketed paste (REQ-RC-WIN-PASTE)
---BRANCH---
v0.13.0-p1b-ack-deadlock
---STATUS---
---FETCH---


V0.13.0-P1C-CONTROLLER-WRITER-REORDER-PLAN.md


HEAD @3749845 (plan commit) already local. Clean tree. Reading plan doc + seams.


1	# v0.13.0 P1c — controller-writer reorder on brain-restart re-serve (root-fix)
2
3	**Status:** design locked (doyle), build → todlando, gate → doyle.
4	**Operator ruling (2026-06-20):** root-fix the flaky resume race before shipping v0.13.0. Full fix + new hazard REQ.
5	**P1b is INNOCENT** — proven mechanically (its diff touches only input-ack machinery) + empirically (the test passes post-P1b in isolation). This race is **pre-existing**, surfaced because the v0.13.0 release gate requires green-both-runners and this flaky cluster blocks it.
6
7	## Symptom
8
9	`crates/spt-daemon/tests/attach.rs::attach_survives_target_brain_restart_exactly_once` is flaky on Linux (~50% fail in isolation). It either:
10	- panics at `attach.rs:722` (`.expect("re-serve")`) with `output gap: got seq 1 want 0`, **or**
11	- HANGS in `render_until` (attach.rs:729) — the serve thread died on the gap, so `MARKER_TWO` never reaches the wire and the main thread waits forever.
12
13	Same root event; FAIL-vs-HANG is pure harness timing. Sibling cluster members: `inject_control_wedge::g2`, `broker::spawn_env_reaches_child` (the commune's `g2/w5_a2` flaky cluster).
14
15	## Root cause (definitive, instrumented repro on kitsubito)
16
17	**Two `controller_writer` threads race the SAME brain connection's socket.** life2 (the handoff brain) registers as controller on the same session **twice**, over the same `Brain::conn` socket:
18
19	1. `Brain::handoff` (brain.rs:330-346) calls `subscribe(prior.session_id, prior.next_seq)`. life1 already consumed seq 0 → `prior.next_seq = 1` → broker `become_controller(from_seq=1)`, `initial=[1]`, spawns **writer-A** (writes seq 1).
20	2. `serve_attach` re-handles the replayed `Request{from_seq:0}` (attach.rs:186 → `attach_as(sid, 0, …)`) → broker `become_controller(from_seq=0)`, `initial=[0,1]`, spawns **writer-B** (writes seq 0, then 1).
21
22	`become_controller` (broker.rs:349-375) drops the prior `ControllerSink` (its `tx`) but **does not stop the prior writer thread** — writer-A keeps flushing its `initial` batch. Both writers hold clones of the same `SharedSend` (`Arc<Mutex<socket>>`, broker.rs:364) and `controller_writer` (broker.rs:703-737) writes its `initial` batch frame-by-frame under that mutex with **no inter-thread ordering**. When writer-A's seq 1 wins the socket before writer-B's seq 0, the consumer (strict legacy path, brain.rs:599-608) sees seq 1 while `next_seq=0` → gap.
23
24	### RACEDIAG trace (failing interleaving)
25	```
26	become_controller sid=1 from_seq=1 ring=[0,1] initial=[1]   # handoff: prior.next_seq=1
27	ctrlwriter WRITE-INITIAL seq=1                               # writer-A writes seq 1 FIRST
28	become_controller sid=1 from_seq=0 ring=[0,1] initial=[0,1] # re-serve Request(from_seq=0)
29	legacy-recv got_seq=1 next_seq=0 → legacy-GAP               # consumer sees 1 before 0
30	ctrlwriter WRITE-INITIAL seq=0                              # writer-B writes seq 0 — too late
31	```
32
33	### The two compounding defects
34	- **A (the real ordering bug):** the handoff's `from_seq=1` replay writes seq 1 to life2's connection *before* the operator's `from_seq=0` replay writes seq 0. **`prior.next_seq` is life1's consumption cursor, NOT life2's connection state — life2's socket has been sent NOTHING, so its replay floor is wrong.** You cannot serve a `from_seq=0` full replay on a connection that already streamed seq 1; the two subscribes are contradictory.
35	- **B (the latent broker hazard):** `become_controller` lets a superseded `controller_writer` keep writing the shared socket. Two writers on one connection can interleave non-monotonic seqs. Eviction is ruled out (ring cap `DEFAULT_LOG_CHUNKS=4096` ≫ the test's chunk count).
36
37	Snap-above tolerance ALONE does **not** fix it: the consumer would snap to seq 1 then **dedup-drop** the late seq 0 → seq 0's bytes lost → the exactly-once byte-identity assert fails. The reorder must be eliminated, not tolerated.
38
39	## Required invariant (what the fix must guarantee)
40
41	> On a single brain↔broker connection, the controller output-frame stream is **monotonic non-decreasing in seq** (modulo dedup re-sends). Exactly **one** `controller_writer` is ever live per connection; a superseded writer writes **no further frames**. A re-serve never replays a seq below what has already been written to that connection.
42
43	## Fix (three parts — implement all)
44
45	1. **PRIMARY — collapse the duplicate controller registration on handoff.**
46	   `Brain::handoff` must not leave a stale `from_seq=1` controller registration that `serve_attach` then re-subscribes from 0. Recommended: make `handoff` **not eagerly `subscribe`-as-controller** (the brain.rs:344 call) — let `serve_attach`'s `attach_as(sid, 0, …)` be the *sole* controller registration + replay for that connection. (handoff is test-only per the brain.rs:328 comment, so this is safe; verify no production caller depends on the eager subscribe.) Net: one `become_controller`, one writer, monotonic 0,1.
47
48	2. **BROKER HARDENING — `become_controller` must stop a superseded writer before the new one writes.** Enforce the invariant in the broker so a double-registration can never silently spawn two racing writers, even if some future caller double-subscribes. Approach (choose what's cleanest + W1-safe — **never block the drain under the `Mutex<OutputLog>`**):
49	   - Epoch-gate the `controller_writer` initial-batch writes (the writer checks the shared `controller_epoch` before each frame and returns once superseded — mirrors `mark_controller_gone`), **and/or**
50	   - Clamp a re-serve's replay floor so it never re-emits below the connection's already-delivered frames (but only when that does not drop an un-sent lower seq — see defect A: the real floor must be the connection's true contiguous-sent cursor, not `prior.next_seq`).
51	   The combination of (1)+(2) must make a superseded writer provably silent.
52
53	3. **DEFENSE-IN-DEPTH — seed `session_cursors` on handoff** (brain.rs:341 is currently empty) so the consumer uses the dedup-below + snap-above path (brain.rs:582-595), matching production `resume_sessions` (brain.rs:830) and cold-start. The strict reject-gap legacy path (brain.rs:599-608) is a latent trap for any at-least-once reorder; production resume already avoids it.
54
55	## Traceability (mint FIRST, then satisfy — rule 3)
56
57	Add to `traceable-reqs.toml`:
58	- **`REQ-HAZARD-CONTROLLER-WRITER-REORDER`** — "Two `controller_writer` threads must never race one brain connection's socket: `become_controller` superseding a controller leaves the prior writer able to flush its `initial` batch concurrently with the new writer on the same `SharedSend`, producing a non-monotonic on-wire seq stream (`got seq 1 want 0`) that the strict consumer rejects (gap) or that drops an un-sent lower seq under snap-above (byte loss). Invariant: one live `controller_writer` per connection; a superseded writer writes nothing further; re-serve replay is monotonic vs what the connection already received. Root-caused via instrumented repro (doyle agent, 2026-06-20); pre-existing, surfaced by the v0.13.0 green-CI gate. NOT a P1b regression." `required_stages = ["doc","impl","unit","int"]`.
59	- KNOWN-HAZARDS.md entry (next 7.x) with the failure / invariant / spt-core-mapping / source structure.
60
61	## Gate (non-vacuous — doyle)
62
63	- `attach_survives_target_brain_restart_exactly_once` runs **20× isolated, single-threaded, on Linux/kitsubito, ALL green** (it is the deterministic RED-on-revert carrier). Wrap with `timeout` so a regression surfaces as a clean fail, never a 27-min hang.
64	- A focused **unit/int that deterministically forces the two-`become_controller`-on-one-connection ordering** (the keystone): pre-fix it reorders → gap/drop; post-fix the wire stays monotonic + byte-exact. Real broker + real connection, no mocks.
65	- Full seam sweep: every test touching `become_controller` / `controller_writer` / handoff / resume (`attach.rs`, `broker.rs`, `resume.rs`, `brain_swap.rs`, `inject_control_wedge.rs`) — the W1 controller-writer model is shared (see memory: shared-seam change → run ALL seam tests).
66	- clippy `--workspace --all-targets -D warnings` = 0; traceable EXIT=0; docs-drift OK.
67
68	## After green
69
70	Resume the v0.13.0 publish pipeline: green-both-runners on PR #26 → deployah cuts v0.13.0 (MINOR) → verify GH-Pages docs → ping perri. Branch: `v0.13.0-p1b-ack-deadlock` (or a fresh `v0.13.0-p1c-…`; fold into PR #26's `v0.13.0-delivery-control` head).
71

# v0.13.0 P1c — controller-writer reorder on brain-restart re-serve (root-fix)

**Status:** design locked (doyle), build → todlando, gate → doyle.
**Operator ruling (2026-06-20):** root-fix the flaky resume race before shipping v0.13.0. Full fix + new hazard REQ.
**P1b is INNOCENT** — proven mechanically (its diff touches only input-ack machinery) + empirically (the test passes post-P1b in isolation). This race is **pre-existing**, surfaced because the v0.13.0 release gate requires green-both-runners and this flaky cluster blocks it.

## Symptom

`crates/spt-daemon/tests/attach.rs::attach_survives_target_brain_restart_exactly_once` is flaky on Linux (~50% fail in isolation). It either:
- panics at `attach.rs:722` (`.expect("re-serve")`) with `output gap: got seq 1 want 0`, **or**
- HANGS in `render_until` (attach.rs:729) — the serve thread died on the gap, so `MARKER_TWO` never reaches the wire and the main thread waits forever.

Same root event; FAIL-vs-HANG is pure harness timing. Sibling cluster members: `inject_control_wedge::g2`, `broker::spawn_env_reaches_child` (the commune's `g2/w5_a2` flaky cluster).

## Root cause (definitive, instrumented repro on kitsubito)

**Two `controller_writer` threads race the SAME brain connection's socket.** life2 (the handoff brain) registers as controller on the same session **twice**, over the same `Brain::conn` socket:

1. `Brain::handoff` (brain.rs:330-346) calls `subscribe(prior.session_id, prior.next_seq)`. life1 already consumed seq 0 → `prior.next_seq = 1` → broker `become_controller(from_seq=1)`, `initial=[1]`, spawns **writer-A** (writes seq 1).
2. `serve_attach` re-handles the replayed `Request{from_seq:0}` (attach.rs:186 → `attach_as(sid, 0, …)`) → broker `become_controller(from_seq=0)`, `initial=[0,1]`, spawns **writer-B** (writes seq 0, then 1).

`become_controller` (broker.rs:349-375) drops the prior `ControllerSink` (its `tx`) but **does not stop the prior writer thread** — writer-A keeps flushing its `initial` batch. Both writers hold clones of the same `SharedSend` (`Arc<Mutex<socket>>`, broker.rs:364) and `controller_writer` (broker.rs:703-737) writes its `initial` batch frame-by-frame under that mutex with **no inter-thread ordering**. When writer-A's seq 1 wins the socket before writer-B's seq 0, the consumer (strict legacy path, brain.rs:599-608) sees seq 1 while `next_seq=0` → gap.

### RACEDIAG trace (failing interleaving)
```
become_controller sid=1 from_seq=1 ring=[0,1] initial=[1]   # handoff: prior.next_seq=1
ctrlwriter WRITE-INITIAL seq=1                               # writer-A writes seq 1 FIRST
become_controller sid=1 from_seq=0 ring=[0,1] initial=[0,1] # re-serve Request(from_seq=0)
legacy-recv got_seq=1 next_seq=0 → legacy-GAP               # consumer sees 1 before 0
ctrlwriter WRITE-INITIAL seq=0                              # writer-B writes seq 0 — too late
```

### The two compounding defects
- **A (the real ordering bug):** the handoff's `from_seq=1` replay writes seq 1 to life2's connection *before* the operator's `from_seq=0` replay writes seq 0. **`prior.next_seq` is life1's consumption cursor, NOT life2's connection state — life2's socket has been sent NOTHING, so its replay floor is wrong.** You cannot serve a `from_seq=0` full replay on a connection that already streamed seq 1; the two subscribes are contradictory.
- **B (the latent broker hazard):** `become_controller` lets a superseded `controller_writer` keep writing the shared socket. Two writers on one connection can interleave non-monotonic seqs. Eviction is ruled out (ring cap `DEFAULT_LOG_CHUNKS=4096` ≫ the test's chunk count).

Snap-above tolerance ALONE does **not** fix it: the consumer would snap to seq 1 then **dedup-drop** the late seq 0 → seq 0's bytes lost → the exactly-once byte-identity assert fails. The reorder must be eliminated, not tolerated.

## Required invariant (what the fix must guarantee)

> On a single brain↔broker connection, the controller output-frame stream is **monotonic non-decreasing in seq** (modulo dedup re-sends). Exactly **one** `controller_writer` is ever live per connection; a superseded writer writes **no further frames**. A re-serve never replays a seq below what has already been written to that connection.

## Fix (three parts — implement all)

1. **PRIMARY — collapse the duplicate controller registration on handoff.**
   `Brain::handoff` must not leave a stale `from_seq=1` controller registration that `serve_attach` then re-subscribes from 0. Recommended: make `handoff` **not eagerly `subscribe`-as-controller** (the brain.rs:344 call) — let `serve_attach`'s `attach_as(sid, 0, …)` be the *sole* controller registration + replay for that connection. (handoff is test-only per the brain.rs:328 comment, so this is safe; verify no production caller depends on the eager subscribe.) Net: one `become_controller`, one writer, monotonic 0,1.

2. **BROKER HARDENING — `become_controller` must stop a superseded writer before the new one writes.** Enforce the invariant in the broker so a double-registration can never silently spawn two racing writers, even if some future caller double-subscribes. Approach (choose what's cleanest + W1-safe — **never block the drain under the `Mutex<OutputLog>`**):
   - Epoch-gate the `controller_writer` initial-batch writes (the writer checks the shared `controller_epoch` before each frame and returns once superseded — mirrors `mark_controller_gone`), **and/or**
   - Clamp a re-serve's replay floor so it never re-emits below the connection's already-delivered frames (but only when that does not drop an un-sent lower seq — see defect A: the real floor must be the connection's true contiguous-sent cursor, not `prior.next_seq`).
   The combination of (1)+(2) must make a superseded writer provably silent.

3. **DEFENSE-IN-DEPTH — seed `session_cursors` on handoff** (brain.rs:341 is currently empty) so the consumer uses the dedup-below + snap-above path (brain.rs:582-595), matching production `resume_sessions` (brain.rs:830) and cold-start. The strict reject-gap legacy path (brain.rs:599-608) is a latent trap for any at-least-once reorder; production resume already avoids it.

## Traceability (mint FIRST, then satisfy — rule 3)

Add to `traceable-reqs.toml`:
- **`REQ-HAZARD-CONTROLLER-WRITER-REORDER`** — "Two `controller_writer` threads must never race one brain connection's socket: `become_controller` superseding a controller leaves the prior writer able to flush its `initial` batch concurrently with the new writer on the same `SharedSend`, producing a non-monotonic on-wire seq stream (`got seq 1 want 0`) that the strict consumer rejects (gap) or that drops an un-sent lower seq under snap-above (byte loss). Invariant: one live `controller_writer` per connection; a superseded writer writes nothing further; re-serve replay is monotonic vs what the connection already received. Root-caused via instrumented repro (doyle agent, 2026-06-20); pre-existing, surfaced by the v0.13.0 green-CI gate. NOT a P1b regression." `required_stages = ["doc","impl","unit","int"]`.
- KNOWN-HAZARDS.md entry (next 7.x) with the failure / invariant / spt-core-mapping / source structure.

## Gate (non-vacuous — doyle)

- `attach_survives_target_brain_restart_exactly_once` runs **20× isolated, single-threaded, on Linux/kitsubito, ALL green** (it is the deterministic RED-on-revert carrier). Wrap with `timeout` so a regression surfaces as a clean fail, never a 27-min hang.
- A focused **unit/int that deterministically forces the two-`become_controller`-on-one-connection ordering** (the keystone): pre-fix it reorders → gap/drop; post-fix the wire stays monotonic + byte-exact. Real broker + real connection, no mocks.
- Full seam sweep: every test touching `become_controller` / `controller_writer` / handoff / resume (`attach.rs`, `broker.rs`, `resume.rs`, `brain_swap.rs`, `inject_control_wedge.rs`) — the W1 controller-writer model is shared (see memory: shared-seam change → run ALL seam tests).
- clippy `--workspace --all-targets -D warnings` = 0; traceable EXIT=0; docs-drift OK.

## After green

Resume the v0.13.0 publish pipeline: green-both-runners on PR #26 → deployah cuts v0.13.0 (MINOR) → verify GH-Pages docs → ping perri. Branch: `v0.13.0-p1b-ack-deadlock` (or a fresh `v0.13.0-p1c-…`; fold into PR #26's `v0.13.0-delivery-control` head).


Plan is thorough. Reading the source seams now.


cd crates/spt-daemon 2>/dev/null && echo "=== tree ===" && find . -name "*.rs" | grep -E "brain|broker|attach" | head -40; echo "=== reqs ==="; grep -n "REQ-HAZARD-CONTROLLER-WRITER-REORDER\|REQ-HAZARD-INPUT-ACK" ../../traceable-reqs.toml | head

Locate daemon source files and check reqs registry


=== tree ===
./src/attach.rs
./src/brain.rs
./src/brainproc.rs
./src/broker.rs
./tests/attach.rs
./tests/brain_swap.rs
./tests/broker.rs
./tests/daemon_lifecycle_real_brain.rs
./tests/netbroker.rs
=== reqs ===
1263:required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 P1 (todlando, last build item; doyle gates). DEPENDS ON P0 (@0675b37, landed) — a paste chunk rides P0's single-writer thread without wedging. NO int (live clipboard + console mouse = HITL, REQ-RUN-PICKER/RC-1 precedent; pure pieces unit-tested). impl = (cfg(windows), folds into the bug-2 event path) `wrap_bracketed_paste` (ESC[200~ + content + ESC[201~) + ctrl+V intercept (Char('v')+CONTROL, when not detach-armed) + right-mouse paste in `spawn_stdin_reader_events` (other mouse dropped) + `read_clipboard` (clipboard-win, empty/fail = clean no-op) + RawGuard EnableMouseCapture(+DisableMouseCapture on drop). unit = bracketed framing exact + content verbatim (no translation) + ctrl+V detect + mouse classify (right-down ⇒ paste, all else ⇒ drop) + injected-reader paste builder (Some ⇒ wrapped, empty/None ⇒ no-op). doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated: ctrl+V/right-click read the LOCAL clipboard + inject a bracketed paste; Unix rides the terminal byte stream). PLANNED: impl = RawGuard EnableMouseCapture (+restore on drop) + ctrl+V/right-mouse clipboard intercept in the bug-2 event path + bracketed-paste wrap + clipboard-win read. unit = bracketed-paste framing (exact ESC[200~/ESC[201~) + gesture->paste decision (ctrl+V/right-mouse=>clipboard-wrap, other mouse=>drop, injected reader) + content verbatim. doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge). AMENDED v0.13.0 P1b (HITL re-open 2026-06-19, doyle): SCOPE narrowed to RIGHT-CLICK-ONLY for the client-originated bracketed paste — the ctrl+V interception is REMOVED (dead + redundant): Windows Terminal CONSUMES ctrl+V as its own paste accelerator (delivers only a Key kind=RELEASE, never a Press, so the Press-guarded is_ctrl_v arm could NEVER fire) AND injects the clipboard as a char-by-char KEY FLOOD spt-core cannot intercept. ctrl+V now rides WT's native paste as keystrokes (multi-line may submit-storm — acceptable per operator; bracketed fidelity = right-click). The is_ctrl_v fn + its unit are removed; impl/unit evidence relocates to the surviving right-click path (wrap_bracketed_paste/mouse_is_paste/clipboard_paste/read_clipboard/RawGuard mouse-capture + their units). The flood-no-wedge guarantee moves to REQ-HAZARD-INPUT-ACK-BACKPRESSURE (the deadlock that the ctrl+V flood exposed). Right-click bracketed paste + mouse-capture UNCHANGED."
1267:id = "REQ-HAZARD-INPUT-ACK-BACKPRESSURE"


REQ not yet minted — I'll add it first per rule 3. Reading seams in parallel.


320
321	    /// Handoff: connect, handshake, rehydrate the resume cursor + generation
322	    /// from `prior`, but stamp `gen_start = now()` (NOT `prior.gen_start_ms` —
323	    /// the 2.4 invariant), and re-subscribe to the session from the cursor for a
324	    /// gapless resume.
325	    ///
326	    /// **Test-only (restoration D4-3).** The production resume path is
327	    /// [`Brain::cold_start`] + [`Brain::resume_sessions`] (broker cursor-of-record),
328	    /// never this brain→brain frame; `handoff` survives only for the integration
329	    /// tests. See [`BrainState`].
330	    pub fn handoff(name: &str, prior: BrainState, now_ms: u64) -> io::Result<Self> {
331	        let mut conn = LocalSocketTransport::connect(name)?;
332	        send_hello(&mut conn, Role::Brain)?;
333	        let mut brain = Brain {
334	            conn: BrainConn::Whole(conn),
335	            generation: prior.generation + 1,
336	            gen_start_ms: now_ms, // fresh — prior.gen_start_ms is intentionally discarded
337	            session_id: Some(prior.session_id),
338	            next_seq: prior.next_seq,
339	            net_cursors: std::collections::HashMap::new(),
340	            presence_cursor: None,
341	            session_cursors: std::collections::HashMap::new(),
342	            io_timeout: None,
343	        };
344	        brain.subscribe(prior.session_id, prior.next_seq)?;
345	        Ok(brain)
346	    }
347
348	    /// The wall-clock generation start (epoch ms) — always this generation's own
349	    /// start time, never a rehydrated value.
350	    pub fn gen_start_ms(&self) -> u64 {
351	        self.gen_start_ms
352	    }
353
354	    /// The generation counter (0 on cold start, +1 per handoff).
355	    pub fn generation(&self) -> u64 {
356	        self.generation
357	    }
358
359	    /// The next output sequence still needed (the resume cursor).
360	    pub fn next_seq(&self) -> u64 {
361	        self.next_seq
362	    }
363
364	    /// The session id the brain is driving, if any.
365	    pub fn session_id(&self) -> Option<u64> {
366	        self.session_id
367	    }
368
369	    /// Snapshot durable state for a future handoff. **Test-only (restoration
370	    /// D4-3)** — the production path reconstructs from the broker, never a
371	    /// snapshot frame; see [`BrainState`].
372	    pub fn snapshot(&self) -> BrainState {
373	        BrainState {
374	            session_id: self.session_id.expect("snapshot before a session exists"),
375	            generation: self.generation,
376	            next_seq: self.next_seq,
377	            gen_start_ms: self.gen_start_ms,
378	        }
379	    }

    /// Handoff: connect, handshake, rehydrate the resume cursor + generation
    /// from `prior`, but stamp `gen_start = now()` (NOT `prior.gen_start_ms` —
    /// the 2.4 invariant), and re-subscribe to the session from the cursor for a
    /// gapless resume.
    ///
    /// **Test-only (restoration D4-3).** The production resume path is
    /// [`Brain::cold_start`] + [`Brain::resume_sessions`] (broker cursor-of-record),
    /// never this brain→brain frame; `handoff` survives only for the integration
    /// tests. See [`BrainState`].
    pub fn handoff(name: &str, prior: BrainState, now_ms: u64) -> io::Result<Self> {
        let mut conn = LocalSocketTransport::connect(name)?;
        send_hello(&mut conn, Role::Brain)?;
        let mut brain = Brain {
            conn: BrainConn::Whole(conn),
            generation: prior.generation + 1,
            gen_start_ms: now_ms, // fresh — prior.gen_start_ms is intentionally discarded
            session_id: Some(prior.session_id),
            next_seq: prior.next_seq,
            net_cursors: std::collections::HashMap::new(),
            presence_cursor: None,
            session_cursors: std::collections::HashMap::new(),
            io_timeout: None,
        };
        brain.subscribe(prior.session_id, prior.next_seq)?;
        Ok(brain)
    }

    /// The wall-clock generation start (epoch ms) — always this generation's own
    /// start time, never a rehydrated value.
    pub fn gen_start_ms(&self) -> u64 {
        self.gen_start_ms
    }

    /// The generation counter (0 on cold start, +1 per handoff).
    pub fn generation(&self) -> u64 {
        self.generation
    }

    /// The next output sequence still needed (the resume cursor).
    pub fn next_seq(&self) -> u64 {
        self.next_seq
    }

    /// The session id the brain is driving, if any.
    pub fn session_id(&self) -> Option<u64> {
        self.session_id
    }

    /// Snapshot durable state for a future handoff. **Test-only (restoration
    /// D4-3)** — the production path reconstructs from the broker, never a
    /// snapshot frame; see [`BrainState`].
    pub fn snapshot(&self) -> BrainState {
        BrainState {
            session_id: self.session_id.expect("snapshot before a session exists"),
            generation: self.generation,
            next_seq: self.next_seq,
            gen_start_ms: self.gen_start_ms,
        }
    }


330	        })
331	    }
332
333	    /// The current controller's identity (`Some(node)` remote / `None` local),
334	    /// or `None` when undriven. The `by`-keyed ownership logic in
335	    /// [`resolve_subscribe`] reads this.
336	    fn controller_by(&self) -> Option<&str> {
337	        self.controller.as_ref().and_then(|c| c.by.as_deref())
338	    }
339
340	    /// Become (or silently re-take) the interactive controller from `from_seq`.
341	    ///
342	    /// W1: the ring replay is now the INITIAL BATCH of a dedicated controller
343	    /// writer thread (symmetric with [`add_viewer`]) — it is no longer written
344	    /// inline under the log lock. The writer thread advances the authoritative
345	    /// `delivered_through` cursor as it writes (D4-1: only on a successful write,
346	    /// monotonic, never the unsent tail). Dropping any prior [`ControllerSink`]
347	    /// ends its writer; bumping `controller_epoch` makes a stale deadline-evict a
348	    /// no-op. Stamps `driven_by` to the new controller's identity.
349	    fn become_controller(&mut self, sub: SharedSend, by: Option<String>, from_seq: u64) {
350	        // Drop the prior controller sink first (its writer ends when tx drops),
351	        // and bump the generation so an in-flight deadline-evict for the old
352	        // controller can't unseat this one.
353	        self.controller = None;
354	        self.controller_epoch += 1;
355	        // Ring replay = the writer thread's initial batch (owned by the thread,
356	        // NOT pushed through the live channel — replay must never block the drain).
357	        let initial: Vec<(u64, Vec<u8>)> = self
358	            .ring
359	            .iter()
360	            .filter(|(s, _)| *s >= from_seq)
361	            .cloned()
362	            .collect();
363	        let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);
364	        let writer_send = Arc::clone(&sub);
365	        let dt = Arc::clone(&self.delivered_through);
366	        let sid = self.session_id;
367	        let writer = thread::spawn(move || controller_writer(sid, writer_send, initial, rx, dt));
368	        self.controller = Some(ControllerSink {
369	            tx,
370	            send: sub,
371	            by,
372	            _writer: writer,
373	        });
374	        self.stamp_driven_by();
375	    }
376
377	    /// Detach the controller and UNLATCH the perch — the ONE unlatch path shared
378	    /// by W1's deadline-evict ([`mark_controller_gone`]), [`detach_if`], and (per
379	    /// the W1↔W5 synergy) W5's reconcile self-heal. Dropping the sink ends its
380	    /// writer thread; clearing the slot re-stamps `driven_by` to `None` (so an
381	    /// `ONLINE+CONTROLLED` latch cannot outlive a gone controller).
382	    fn clear_controller(&mut self) {
383	        if self.controller.take().is_some() {
384	            self.stamp_driven_by();
385	        }
386	    }
387
388	    /// Deadline-evict path (W1): the drain observed a fully-stalled controller and
389	    /// timed out its `send_timeout`. Detach it — but ONLY if it is still the same
390	    /// controller generation the drain saw (`epoch`), so a NEWER controller that
391	    /// attached during the stall is never unseated.
392	    fn mark_controller_gone(&mut self, epoch: u64) {
393	        if self.controller_epoch == epoch {
394	            self.clear_controller();
395	        }
396	    }
397
398	    /// Add a read-only viewer: snapshot the ring from `from_seq` into the writer
399	    /// thread (NOT through the bounded live queue — replay must not trip the

        })
    }

    /// The current controller's identity (`Some(node)` remote / `None` local),
    /// or `None` when undriven. The `by`-keyed ownership logic in
    /// [`resolve_subscribe`] reads this.
    fn controller_by(&self) -> Option<&str> {
        self.controller.as_ref().and_then(|c| c.by.as_deref())
    }

    /// Become (or silently re-take) the interactive controller from `from_seq`.
    ///
    /// W1: the ring replay is now the INITIAL BATCH of a dedicated controller
    /// writer thread (symmetric with [`add_viewer`]) — it is no longer written
    /// inline under the log lock. The writer thread advances the authoritative
    /// `delivered_through` cursor as it writes (D4-1: only on a successful write,
    /// monotonic, never the unsent tail). Dropping any prior [`ControllerSink`]
    /// ends its writer; bumping `controller_epoch` makes a stale deadline-evict a
    /// no-op. Stamps `driven_by` to the new controller's identity.
    fn become_controller(&mut self, sub: SharedSend, by: Option<String>, from_seq: u64) {
        // Drop the prior controller sink first (its writer ends when tx drops),
        // and bump the generation so an in-flight deadline-evict for the old
        // controller can't unseat this one.
        self.controller = None;
        self.controller_epoch += 1;
        // Ring replay = the writer thread's initial batch (owned by the thread,
        // NOT pushed through the live channel — replay must never block the drain).
        let initial: Vec<(u64, Vec<u8>)> = self
            .ring
            .iter()
            .filter(|(s, _)| *s >= from_seq)
            .cloned()
            .collect();
        let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);
        let writer_send = Arc::clone(&sub);
        let dt = Arc::clone(&self.delivered_through);
        let sid = self.session_id;
        let writer = thread::spawn(move || controller_writer(sid, writer_send, initial, rx, dt));
        self.controller = Some(ControllerSink {
            tx,
            send: sub,
            by,
            _writer: writer,
        });
        self.stamp_driven_by();
    }

    /// Detach the controller and UNLATCH the perch — the ONE unlatch path shared
    /// by W1's deadline-evict ([`mark_controller_gone`]), [`detach_if`], and (per
    /// the W1↔W5 synergy) W5's reconcile self-heal. Dropping the sink ends its
    /// writer thread; clearing the slot re-stamps `driven_by` to `None` (so an
    /// `ONLINE+CONTROLLED` latch cannot outlive a gone controller).
    fn clear_controller(&mut self) {
        if self.controller.take().is_some() {
            self.stamp_driven_by();
        }
    }

    /// Deadline-evict path (W1): the drain observed a fully-stalled controller and
    /// timed out its `send_timeout`. Detach it — but ONLY if it is still the same
    /// controller generation the drain saw (`epoch`), so a NEWER controller that
    /// attached during the stall is never unseated.
    fn mark_controller_gone(&mut self, epoch: u64) {
        if self.controller_epoch == epoch {
            self.clear_controller();
        }
    }

    /// Add a read-only viewer: snapshot the ring from `from_seq` into the writer
    /// thread (NOT through the bounded live queue — replay must not trip the


398	    /// Add a read-only viewer: snapshot the ring from `from_seq` into the writer
399	    /// thread (NOT through the bounded live queue — replay must not trip the
400	    /// overflow eviction), seed it the current size frame first, then run the
401	    /// live queue. The drain thread fans live frames in via `try_send`.
402	    fn add_viewer(&mut self, sub: SharedSend, from_seq: u64) {
403	        let vid = self.next_viewer_id;
404	        self.next_viewer_id += 1;
405	        // The size frame + the ring replay are the writer's initial batch (owned
406	        // by the thread, not the bounded queue).
407	        let mut initial: Vec<Envelope> =
408	            vec![size_envelope(self.session_id, self.size.0, self.size.1)];
409	        initial.extend(
410	            self.ring
411	                .iter()
412	                .filter(|(s, _)| *s >= from_seq)
413	                .map(|(seq, bytes)| output_envelope(self.session_id, *seq, bytes)),
414	        );
415	        let (tx, rx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);
416	        let writer_send = Arc::clone(&sub);
417	        let writer = thread::spawn(move || viewer_writer(writer_send, initial, rx));
418	        self.viewers.insert(
419	            vid,
420	            ViewerSink {
421	                tx,
422	                send: sub,
423	                _writer: writer,
424	            },
425	        );
426	    }
427
428	    /// Resolve a controller/viewer subscribe under the W2.5 model
429	    /// (REQ-RCVIEW-1 / REQ-KICK-1). Returns the [`SubscribeOutcome`]; performs
430	    /// the controller take / viewer add / loud displace as a side effect. See the
431	    /// identity matrix in the body — the `by`-keyed logic is what makes a
432	    /// brain-restart re-serve silent (gate #7) and a cross-operator `Take` loud.
433	    fn resolve_subscribe(
434	        &mut self,
435	        sub: SharedSend,
436	        from_seq: u64,
437	        intent: AttachIntent,
438	        by: Option<String>,
439	    ) -> SubscribeOutcome {
440	        if intent == AttachIntent::Viewer {
441	            if self.viewers.len() >= MAX_VIEWERS {
442	                // Soft cap reached — refuse the viewer (bounds thread count).

    /// Add a read-only viewer: snapshot the ring from `from_seq` into the writer
    /// thread (NOT through the bounded live queue — replay must not trip the
    /// overflow eviction), seed it the current size frame first, then run the
    /// live queue. The drain thread fans live frames in via `try_send`.
    fn add_viewer(&mut self, sub: SharedSend, from_seq: u64) {
        let vid = self.next_viewer_id;
        self.next_viewer_id += 1;
        // The size frame + the ring replay are the writer's initial batch (owned
        // by the thread, not the bounded queue).
        let mut initial: Vec<Envelope> =
            vec![size_envelope(self.session_id, self.size.0, self.size.1)];
        initial.extend(
            self.ring
                .iter()
                .filter(|(s, _)| *s >= from_seq)
                .map(|(seq, bytes)| output_envelope(self.session_id, *seq, bytes)),
        );
        let (tx, rx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);
        let writer_send = Arc::clone(&sub);
        let writer = thread::spawn(move || viewer_writer(writer_send, initial, rx));
        self.viewers.insert(
            vid,
            ViewerSink {
                tx,
                send: sub,
                _writer: writer,
            },
        );
    }

    /// Resolve a controller/viewer subscribe under the W2.5 model
    /// (REQ-RCVIEW-1 / REQ-KICK-1). Returns the [`SubscribeOutcome`]; performs
    /// the controller take / viewer add / loud displace as a side effect. See the
    /// identity matrix in the body — the `by`-keyed logic is what makes a
    /// brain-restart re-serve silent (gate #7) and a cross-operator `Take` loud.
    fn resolve_subscribe(
        &mut self,
        sub: SharedSend,
        from_seq: u64,
        intent: AttachIntent,
        by: Option<String>,
    ) -> SubscribeOutcome {
        if intent == AttachIntent::Viewer {
            if self.viewers.len() >= MAX_VIEWERS {
                // Soft cap reached — refuse the viewer (bounds thread count).