Async agent launched successfully.
agentId: afb5c623f7ab7530a (internal ID - do not mention to user. Use SendMessage with to: 'afb5c623f7ab7530a' to continue this agent.)
The agent is working in the background. You will be notified automatically when it completes.
Do not duplicate this agent's work — avoid working with the same files or topics it is using. Work on non-overlapping tasks, or briefly tell the user what you launched and end your response.
output_file: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\afb5c623f7ab7530a.output
Do NOT Read or tail this file via the shell tool — it is the full subagent JSONL transcript and reading it will overflow your context. If the user asks for progress, say the agent is still running; you'll get a completion notification.

RED int: endpoint-run attach-online race

Prompt:
REPRO-FIRST regression int for REQ-HAZARD-RC-ATTACH-ONLINE-RACE (doyle root-caused). You write ONE new int test and confirm it is RED against the CURRENT (unfixed) code. Do NOT modify any non-test source — the fix lands in a later step.

THE BUG (doyle /diagnose, code-grounded): `spt endpoint run` in an ATTACH terminal action (no `--start`) attaches BEFORE the freshly-spawned endpoint is online. In `cmd_endpoint_run` (crates/spt/src/cli.rs ~1318→1358) the flow is `launch_harness_brokered_in` (returns once the harness PROCESS is spawned) → run_attach, with NO await-online between them. The broker-PTY bind (info status→STATUS_ONLINE + a live session) lands ASYNC, so an immediate `run_attach` loses the race: it either short-circuits "offline — nothing to attach" (the REQ-HAZARD-RC-ATTACH-FAILFAST status-gate in rc.rs run_attach) or hits "no live session for endpoint" — never connecting to the live PTY.

STUDY FIRST: `crates/spt/tests/dummy_harness_e2e.rs` end-to-end — it is your TEMPLATE and fixture. It registers the `dummyharness` adapter, sets an isolated SPT_HOME, runs `endpoint run --start` then a SEPARATE `rc` attach, and asserts the dummyharness HEARTBEAT output appears on the attached PTY. It also REAPS the daemon tree (broker daemon.pid + brain + harness) on exit — you MUST replicate that hygiene (a survivor locks target\debug\spt.exe → EPERM on the next CI checkout).

ADD: a sibling test (e.g. `fn endpoint_run_attach_awaits_online_before_attaching()`) tagged `// [int->REQ-HAZARD-RC-ATTACH-ONLINE-RACE]`. It must:
1. Reuse the dummyharness registration + isolated SPT_HOME fixture from the existing test (factor/copy its setup helpers).
2. Run `spt endpoint run --adapter dummyharness --id <fresh-id>` WITHOUT `--start` (this is the attach-by-default path = the bug surface). Because attach BLOCKS pumping the live PTY, run it BOUNDED (mirror the existing `output_bounded`/timeout helper, ~30s) and capture stdout+stderr.
3. THE CLAIM: assert the captured output shows the dummyharness HEARTBEAT (the same live-PTY marker the existing test asserts after `rc` attach) = proof the attach CONNECTED to the live session post-bringup. Pre-fix this FAILS: the output instead shows the offline/no-live-session bail (e.g. "offline — nothing to attach" / "no live session") and NO heartbeat → RED.
4. HYGIENE (non-negotiable): reap the ENTIRE tree before returning EXACTLY like the existing test — graceful `daemon stop` best-effort, then taskkill /F /T the broker (daemon.pid) + belt the brain + the harness pid, poll-verify reaped. Compute the assert into a bool, do the full reap, THEN assert (so a RED still cleans up — never leave a stray spt.exe).

RUN IT: `cargo test -p spt --test dummy_harness_e2e endpoint_run_attach_awaits_online_before_attaching -- --nocapture` against current code. EXPECT RED (no heartbeat — the immediate attach bailed/raced). Capture the failure + any diagnostic.

DISCIPLINE: do NOT run bare `cargo test` across the workspace (runner-contract hazard); scope to `-p spt --test dummy_harness_e2e`. This test DOES spawn a real daemon+harness by design — allowed for THIS targeted invocation. No Monitor. Cap 4 iterations. If the dummyharness won't bind in your environment, STOP and report — do NOT fake a result.

REPORT: the full test fn (verbatim), the run result (RED with the assertion message + what the output showed instead of the heartbeat), and confirm you touched ONLY dummy_harness_e2e.rs.


Engineer Phase 1 running (owns `dummy_harness_e2e.rs`). Bin stays unfixed until its RED confirms. **Phase 2 fully prepped** — verified the exact insertion (after the `if start` early-return, before `run_attach`) and the online signal (`read_info().status == STATUS_ONLINE`, symmetric to rc.rs's offline gate):

- New `await_endpoint_online(id, ~25s)` helper: poll `read_info().status` to `STATUS_ONLINE`, tight 150ms interval, bounded; `[impl->REQ-HAZARD-RC-ATTACH-ONLINE-RACE]`
- Gate it in `cmd_endpoint_run` only on the attach/view path (start/headless return above, untouched); timeout → `ENDPOINT_RUN_ONLINE_TIMEOUT` err (don't attach a dead bringup)
- REQ minted `[impl,int]`, registry-first ✓

This completes W4 item-2's attach-by-default (makes the attach actually land). Waiting on the engineer's RED → apply fix (Phase 2) → engineer GREEN + gates (Phase 3) → doyle gate.


<task-notification>
<task-id>bv8r78fk1</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/7" id="d8afa654" type="msg" from="doyle">doyle DISPATCH — bug 2 (special keys), the BIG one. Operator RULED: proper agnostic translator, HOLD v0.13.0 for it (full key fidelity in the release). Full design = V0.13.0-RC-VT-TRANSLATE-DESIGN.md (read it — too big for owl).

TL;DR: Windows rc input switches from raw stdin BYTES to crossterm EVENTS (crosst</EVENT-PART>
<EVENT-PART seq="2/7" id="d8afa654">erm 0.28 already a dep; picker/ratatui already read events) → translate_key_event(KeyEvent)→Vec&lt;u8&gt; emitting STANDARD xterm VT (arrows ESC[A.., Home/End/PgUp/Dn/Del/F-keys, modifier scheme ESC[1;&lt;m&gt;&lt;final&gt;, Backspace→0x7f, Ctrl+Backspace→0x08). Harness-AGNOSTIC (standard VT, NOT win32-input-mode). Detach = Ctrl+B KEY </EVENT-PART>
<EVENT-PART seq="3/7" id="d8afa654">EVENT (clean, retires the byte 0x02 state machine on Windows). SUPERSEDES normalize_key_byte (remove it on Windows; the swap's Backspace/Ctrl+Backspace behavior lives in the mapping now). UNIX UNCHANGED (byte-pump already gets proper VT — cfg-split, zero Unix regression). Non-tty stdin (piped/tests) → fall back to byte-read (keep e2e injec</EVENT-PART>
<EVENT-PART seq="4/7" id="d8afa654">tion working). KeyEventKind::Press only (drop Repeat/Release).

REQ: mint REQ-RC-KEY-VT-TRANSLATE registry-first, stages doc+impl+unit; supersede-note on REQ-HAZARD-RC-INPUT-KEY-ENCODING. NO int (live console = HITL, REQ-RUN-PICKER/RC-1 precedent) — the PURE translate_key_event mapping IS the unit surface: make it EXHAUSTIVE + non-vacu</EVENT-PART>
<EVENT-PART seq="5/7" id="d8afa654">ous (every key/modifier-combo asserts its exact xterm bytes; Ctrl+B→Detach). Copy a KNOWN-CORRECT xterm table verbatim (ADR-0001 spirit), don't hand-roll the sequences.

GATE (mine): exhaustive mapping unit + detach-event unit + normalize_key_byte removed without regressing Backspace/Ctrl+Backspace + Unix untouched + non-tty fallback p</EVENT-PART>
<EVENT-PART seq="6/7" id="d8afa654">reserved + clippy/traceable/docs-drift; then OPERATOR HITL (arrows/Home/End/F-keys/Backspace/Ctrl+Backspace/detach all work in real CC) = real acceptance.

This is the LAST v0.13.0 build item + it BLOCKS the ship. Order: you have window (DONE/gated) + 3+4 (dispatched) + this. Confirm pickup; ping if the design needs a ruling. Repro-first</EVENT-PART>
<EVENT-PART seq="7/7" id="d8afa654"> where you can (the mapping table is assertable immediately).</EVENT-PART></event>
</task-notification>


1	//! Wave 1 (v0.12.1) — the DUMMY-HARNESS fixture smoke test. The permanent
2	//! real-harness regression fixture the v0.12.0 mock tests never had: it drives the
3	//! REAL spt-hosted bringup path end-to-end —
4	//! `spt endpoint run` → `launch_harness_brokered_in` → broker PTY → `spt rc` attach
5	//! — against a REAL detached `spt daemon run`, NOT an in-proc `reconcile_once` and
6	//! NOT a mock that exits immediately.
7	//!
8	//! The dummy harness (`mock-session --mode dummy`) binds its perch on startup (the
9	//! harness contract), prints a stdout heartbeat on an interval, and stays alive
10	//! until killed — so "broker wedged / endpoint died" is cleanly separable from "a
11	//! real harness failed to launch." Wave 2's lifecycle gates (VIEWER-CLOSE-DETACH,
12	//! ATTACH-WEDGE, daemon-stop) build on exactly this bring-up.
13	//!
14	//! Asserts: `endpoint run --start` brings the dummy up, its perch goes ONLINE, the
15	//! PTY child stays alive (heartbeating), and `spt rc <id>` attaches and receives the
16	//! live heartbeat output. Bounded; all spawned pids reaped scoped (never
17	//! machine-wide — shared runner).
18
19	use std::io::Read;
20	use std::path::{Path, PathBuf};
21	use std::process::{Child, Command, Output, Stdio};
22	use std::time::{Duration, Instant};
23
24	mod common;
25	use common::CommandNoWindowExt;
26
27	use spt_store::perch::{self, ParentHint};
28
29	fn kill_pid(pid: u32) {
30	    #[cfg(windows)]
31	    let _ = Command::new("taskkill")
32	        .no_window()
33	        .args(["/PID", &pid.to_string(), "/F", "/T"])
34	        .output();
35	    #[cfg(unix)]
36	    let _ = Command::new("kill").args(["-9", &pid.to_string()]).output();
37	}
38
39	fn output_bounded(mut cmd: Command, deadline: Duration) -> Output {
40	    let (tx, rx) = std::sync::mpsc::channel();
41	    std::thread::spawn(move || {
42	        let _ = tx.send(cmd.output());
43	    });
44	    rx.recv_timeout(deadline)
45	        .expect("captured spt call must complete within its deadline")
46	        .expect("run spt")
47	}
48
49	fn ready_pid(path: &Path) -> Option<u32> {
50	    let s = std::fs::read_to_string(path).ok()?;
51	    serde_json::from_str::<serde_json::Value>(&s)
52	        .ok()?
53	        .get("pid")?
54	        .as_u64()
55	        .map(|p| p as u32)
56	}
57
58	fn wait_for_ready_pid(path: &Path, budget: Duration) -> Option<u32> {
59	    let deadline = Instant::now() + budget;
60	    while Instant::now() < deadline {
61	        if let Some(p) = ready_pid(path) {
62	            return Some(p);
63	        }
64	        std::thread::sleep(Duration::from_millis(50));
65	    }
66	    None
67	}
68
69	fn sibling_bin(name: &str) -> PathBuf {
70	    PathBuf::from(env!("CARGO_BIN_EXE_spt"))
71	        .with_file_name(format!("{name}{}", std::env::consts::EXE_SUFFIX))
72	}
73
74	#[test]
75	fn endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches() {
76	    // ── (1) Isolated SPT_HOME. NET-ENABLED (the daemon self-initializes a real node
77	    //    identity): `spt rc` attaches over a broker-minted LOOPBACK conn that surfaces
78	    //    as net-stream events, so it requires a net-bound broker — a net-less broker
79	    //    refuses the loopback dial ("net disabled"). Unlike the livehost/reconcile
80	    //    tests (which only inspect perch state), the rc-attach path needs net up. ──
81	    let home = tempfile::tempdir().unwrap();
82	    std::env::set_var("SPT_HOME", home.path());
83
84	    let spt_bin = PathBuf::from(env!("CARGO_BIN_EXE_spt"));
85	    let exe_suffix = spt_bin
86	        .extension()
87	        .map(|e| format!(".{}", e.to_string_lossy()))
88	        .unwrap_or_default();
89	    let mock_session = sibling_bin("mock-session");
90	    assert!(
91	        mock_session.exists(),
92	        "the dummy-harness program must be built: {} \
93	         (pre-build: cargo build -p mock-adapter --bin mock-session)",
94	        mock_session.display()
95	    );
96
97	    // ── (2) Register the DUMMY-HARNESS adapter: a kind=harness adapter whose
98	    //    [session.self] runs the long-lived dummy. The broker fills {id}/{session_id};
99	    //    the program token is the absolute mock-session path. The dummy shells the
100	    //    same `spt` to bind (via --spt). ──
101	    let spt_for_cmd = spt_bin.to_string_lossy().replace('\\', "/");
102	    let mock_for_cmd = mock_session.to_string_lossy().replace('\\', "/");
103	    let self_cmd = format!(
104	        "{mock_for_cmd} --mode dummy --id {{id}} --session-id {{session_id}} \
105	         --spt {spt_for_cmd} --mock-adapter dummyharness --type live_agent --heartbeat-ms 200"
106	    );
107	    // A faithful live_agent fixture DECLARES `[session.psyche_init]`: cmd_bind only
108	    // marks status=online for `live_agent && psyche_init.is_some()` (startup.rs:464 —
109	    // the online signal is gated to the reconcile host predicate). A LONG-LIVED psyche
110	    // (a copy of `spt` run as `ready {id}`, mirroring brain_restart_psyche_dup) so the
111	    // brain's reconcile hosts a resident `{id}-psyche` — the same shape the Wave 2
112	    // VIEWER-CLOSE / psyche-reap gates need.
113	    let src = perch::spt_home().join("srcs").join("dummyharness");
114	    std::fs::create_dir_all(&src).unwrap();
115	    let psyche_bin = src.join(format!("psychebin{exe_suffix}"));
116	    std::fs::copy(&spt_bin, &psyche_bin).expect("stage the psyche binary fixture");
117	    let drops = home.path().join("drops");
118	    std::fs::create_dir_all(&drops).unwrap();
119	    let dir = drops.to_string_lossy().replace('\\', "/");
120	    let manifest_toml = format!(
121	        "[adapter]\nname=\"dummyharness\"\nkind=\"harness\"\nversion=\"1\"\n\
122	         min_spt_core_version=\"0\"\n\n[session.self]\ncommand='{self_cmd}'\n\n\
123	         [session]\ncommune_dir=\"{dir}\"\nsignoff_dir=\"{dir}\"\n\n\
124	         [session.psyche_init]\ncommand='psychebin ready {{id}}'\ncwd=\"{{psyche_dir}}\"\nkeys=[]\n"
125	    );
126	    std::fs::write(src.join("manifest.toml"), &manifest_toml).unwrap();
127	    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();
128
129	    // ── (3) The REAL daemon broker process. ──
130	    let brain_log = home.path().join("brain.stderr.log");
131	    let brain_log_file = std::fs::File::create(&brain_log).expect("create brain stderr log");
132	    let mut broker: Child = Command::new(&spt_bin)
133	        .no_window()
134	        .args(["daemon", "run"])
135	        .env("SPT_HOME", home.path())
136	        .stdout(Stdio::null())
137	        .stderr(Stdio::from(brain_log_file))
138	        .spawn()
139	        .expect("spawn spt daemon run (broker process)");
140	    let brain_pid = match wait_for_ready_pid(&home.path().join("brain.ready"), Duration::from_secs(30))
141	    {
142	        Some(p) => p,
143	        None => {
144	            let _ = broker.kill();
145	            let _ = broker.wait();
146	            panic!(
147	                "PRECONDITION: brain never came up.\n{}",
148	                std::fs::read_to_string(&brain_log).unwrap_or_default()
149	            );
150	        }
151	    };
152
153	    // ── (4) The REAL spt-hosted bringup: endpoint run --start (broker spawns the
154	    //    dummy into a PTY; the dummy binds its perch + heartbeats). ──
155	    let id = "dummy7";
156	    let run = {
157	        let mut cmd = Command::new(&spt_bin);
158	        cmd.no_window()
159	            .args(["endpoint", "run", "--adapter", "dummyharness", "--id", id, "--start"])
160	            .env("SPT_HOME", home.path());
161	        output_bounded(cmd, Duration::from_secs(45))
162	    };
163
164	    let self_perch = perch::resolve_perch_path(id, ParentHint::Infer);
165	    let status = || spt_store::info::read_info(&self_perch).and_then(|i| i.status);
166
167	    // The dummy binds its perch live within a few seconds of the broker PTY spawn.
168	    let mut online = false;
169	    let deadline = Instant::now() + Duration::from_secs(20);
170	    while Instant::now() < deadline {
171	        if status().as_deref() == Some(spt_store::liveness::STATUS_ONLINE) {
172	            online = true;
173	            break;
174	        }

... [323 lines truncated] ...


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


<owl_messages>
<owl_message from="todlando-w178" priority="info">[WORKING_PERCH_NOTICE] Subagent working perch created.
Perch: todlando-w178
Agent type: spt-test-engineer
To message it: $OWL send todlando-w178 todlando</owl_message>
</owl_messages>

[OWL SYSTEM] Informational owl messages above — no acknowledgment needed. Continue your current task.

<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>


doyle dispatched **bug 2 (special keys) — the big one**: operator ruled a proper agnostic VT translator, v0.13.0 HELD for it; it's the LAST build item + ship-blocker. Design in `V0.13.0-RC-VT-TRANSLATE-DESIGN.md`. Order stands: window (done) → 3+4 (in flight) → bug 2. I'm mid-flight on 3+4 (engineer writing the RED int — `dummy_harness_e2e.rs` already taking shape). Confirm pickup to doyle, queue bug 2 after 3+4:


Bash

$OWL send doyle <<'EOF'
PICKUP CONFIRMED — bug 2 (RC VT translator) queued AFTER 3+4. Order held: window DONE/gated @082f5ed; 3+4 (attach-online race) IN FLIGHT right now — REQ-HAZARD-RC-ATTACH-ONLINE-RACE minted [impl,int] registry-first, repro-first engineer writing the dummy-harness RED int (fresh `endpoint run` no --start must connect post-bringup; pre-fix bails offline/races). Fix staged: cmd_endpoint_run await_endpoint_online (poll info status→ONLINE, ~25s bound) before run_attach on the attach/view path only; start/headless untouched. Will gate 3+4, then read V0.13.0-RC-VT-TRANSLATE-DESIGN.md and pick up bug 2: mint REQ-RC-KEY-VT-TRANSLATE [doc,impl,unit] registry-first + supersede-note on REQ-HAZARD-RC-INPUT-KEY-ENCODING; Windows rc switches raw-bytes→crossterm events → translate_key_event(KeyEvent)→Vec<u8> emitting STANDARD xterm VT (copy a known-correct table, not hand-rolled); Ctrl+B key-event detach retires the 0x02 byte SM on Windows; normalize_key_byte removed (Windows); Unix byte-pump unchanged (cfg-split); non-tty stdin → byte-read fallback (e2e injection preserved); Press-only. NO int (live console = HITL); exhaustive non-vacuous mapping unit is the surface. Will ping if the design needs a ruling. Surfacing 3+4 for your gate first.
EOF

Confirm bug-2 pickup, queued after 3+4


➜ SENT:doyle


Confirmed to doyle: bug 2 queued after 3+4. Now waiting on the 3+4 engineer's RED confirmation (test landing in `dummy_harness_e2e.rs`) before applying the staged `cmd_endpoint_run` fix.

**v0.13.0 build status:**
- W1·W1b·W2·W3·W4·W5·W7·F1 + WMI-window — **gated**
- **Bugs 3+4** (attach-online race) — in flight: REQ minted, repro int RED-in-progress, fix staged
- **Bug 2** (RC VT translator) — picked up, queued; reads as the last build item + ship-blocker

Holding for the engineer's RED → Phase 2 fix → GREEN + gates → doyle gate on 3+4, then bug 2.


<task-notification>
<task-id>bv8r78fk1</task-id>
<summary>Monitor event: "« spt event »"</summary>
<event><EVENT-PART seq="1/3" id="c925c36c" type="msg" from="doyle">doyle ACK — 3+4 approach correct (await_endpoint_online poll→ONLINE ~25s bound, attach/view path only, start/headless untouched; REQ-HAZARD-RC-ATTACH-ONLINE-RACE registry-first; repro-first dummy-harness RED int = fresh endpoint-run-no-start must connect post-bringup). Translator design absorbed faithfully. Gate not</EVENT-PART>
<EVENT-PART seq="2/3" id="c925c36c">es for 3+4: (a) the int is the real proof — but if await_endpoint_online's poll/timeout/already-online-fast-path is cleanly extractable, add a small unit on it (timeout→err, online→proceed) so [impl,int] gains a structural guard; not a blocker if no clean seam. (b) the await must FAIL-LOUD on timeout (ENDPOINT_RUN err, don't fall through</EVENT-PART>
<EVENT-PART seq="3/3" id="c925c36c"> to a doomed run_attach). (c) don't double-count: an ALREADY-online endpoint routes Outcome::Attach (not Run) so it never hits this path — confirm the await only gates the offline-bringup Run path. Surface 3+4 when green; I gate, then you take the translator. Carry on.</EVENT-PART></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>


doyle ACK'd 3+4 + 3 gate notes — all already covered by the staged fix:
- **(a)** clean unit seam: I'll extract `poll_online(is_online_fn, attempts, sleep_fn)` — pure (injected store-read + sleep), unit-testable for timeout→false / online-immediate→true with zero wall-clock → upgrade REQ to `[impl,unit,int]`.
- **(b)** fail-loud on timeout: staged fix returns `1` + `ENDPOINT_RUN_ONLINE_TIMEOUT` (no fall-through to a doomed `run_attach`). ✓
- **(c)** no double-count: `cmd_endpoint_run` is only reached via `Outcome::Run` (offline bringup); online endpoints route `Outcome::Attach`→`rc::run_attach` and never hit this path. Confirmed. ✓

Waiting on the engineer's Phase-1 RED (int in `dummy_harness_e2e.rs`) before applying the fix + the `poll_online` unit.


<task-notification>
<task-id>afb5c623f7ab7530a</task-id>
<tool-use-id>toolu_01MEMpfzK5heANvS9mH8r6AZ</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\afb5c623f7ab7530a.output</output-file>
<status>completed</status>
<summary>Agent "RED int: endpoint-run attach-online race" came to rest</summary>
<note>A task-notification fires each time this agent comes to rest with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note>
<result>Done. Repro-first regression int delivered and confirmed RED against current (unfixed) code.

## What I did
Added ONE new int test to `C:\Users\decid\Documents\projects\spt-core\crates\spt\tests\dummy_harness_e2e.rs` (the only file I touched — no non-test source changed). It is a sibling of the existing `endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches`, reusing its verbatim helpers (`output_bounded`, `kill_pid`, `wait_for_ready_pid`, `sibling_bin`, `ready_pid`), the same dummyharness adapter registration, isolated `SPT_HOME`, and the same scoped full-tree reap (harness pid → reconcile-hosted `{id}-psyche` → `daemon stop` → brain → broker), plus an added poll-verify that brain+harness are dead before returning.

The only behavioral difference (the bug surface): it runs `spt endpoint run --adapter dummyharness --id dummyrace1` WITHOUT `--start` — the attach-by-default path — bounded at 30s with stdout+stderr piped. The hard gate asserts the combined output contains `DUMMY_HARNESS_TICK` (proof the attach awaited bringup and connected to the live PTY). The assert is computed pre-reap and fired post-reap, so a RED still cleans up.

## Run result — RED (as expected)
```
cargo test -p spt --test dummy_harness_e2e endpoint_run_attach_awaits_online_before_attaching -- --nocapture --test-threads=1
test result: FAILED. 0 passed; 1 failed
```
Assertion message: `... must await the freshly-spawned endpoint coming ONLINE before attaching, then DELIVER its live DUMMY_HARNESS_TICK ... no heartbeat`.

What the output showed instead of the heartbeat — a notable refinement on the doyle diagnosis. The two named pre-fix bails (`offline — nothing to attach` / `no live session`) did NOT fire. Instead a THIRD race face: the attach actually *connects* (`PUMP_IPC_READER: spawned`, no `RC_FAIL`) but then EOF-detaches immediately before any harness output:
```
stdout: [detached — 'dummyrace1' still running]
stderr: ENDPOINT_RUN:dummyrace1 ... pid=108676 / PUMP_IPC_READER: spawned
saw_tick=false  bailed_offline=false  bailed_no_session=false
```
So the attach wins far enough to subscribe but the session has produced nothing yet, replays an empty ring, and the pump exits before the harness heartbeats. My `saw_tick` gate is robust to all three race faces (offline bail / no-session bail / connect-then-empty-detach) since it asserts the strictly stronger outcome: actual heartbeat delivery.

## Verification
- Hygiene confirmed: 0 `dummyrace1` survivors; my brain (77536) and harness (108676) both dead. (Pre-existing `wall-*-psyche` strays on the box are from unrelated sessions, filtered by command-line id — wall-clock StartTime deltas are misleading here.)
- `traceable-reqs`: `REQ-HAZARD-RC-ATTACH-ONLINE-RACE` shows `required: [impl, int]  stages: +int` — my `[int->...]` tag satisfies the int stage. The sole remaining gap is `stage=impl`, which is correct: the fix lands in a later step, not this test-only commit.
- Scoped to `-p spt --test dummy_harness_e2e` (never bare workspace `cargo test`); no Monitor; single iteration sufficed.

The fix (await STATUS_ONLINE between `launch_harness_brokered_in` and `run_attach`, bounded ~25s, `ENDPOINT_RUN_ONLINE_TIMEOUT` on timeout) will flip this test GREEN.</result>
<usage><subagent_tokens>85641</subagent_tokens><tool_uses>19</tool_uses><duration_ms>222995</duration_ms></usage>
</task-notification>


RED confirmed — and a sharper finding: the pre-fix bail is a **third race face** the engineer's `saw_tick` gate catches anyway — the attach *connects* but EOF-detaches on an empty ring before the harness emits (`[detached — still running]`, `saw_tick=false`). Hygiene clean, int stage tagged. **Phase 2 — apply the fix** (engineer idle, owns the test file; I own cli.rs):


Edit

        return 0;
    }
    match crate::rc::run_attach(id, rc_intent(view, false)) {
        Ok(()) => 0,
        Err(e) => {
            eprintln!("RC_FAIL:{id}: {e}");
            1
        }
    }
}

        return 0;
    }
    // Await the bringup ONLINE before attaching (REQ-HAZARD-RC-ATTACH-ONLINE-RACE):
    // launch_harness_brokered_in returns once the harness PROCESS is spawned, but the
    // broker-PTY bind (status -> ONLINE + a live session) lands ASYNC. An immediate
    // run_attach loses the race three ways — connects to an empty ring then
    // EOF-detaches, hits the offline status-gate, or finds no live session yet. FAIL
    // LOUD on timeout rather than fall through to a doomed attach. Only the
    // offline-bringup (Run) path reaches here; an already-online endpoint routes
    // Outcome::Attach -> rc::run_attach and never double-counts this wait.
    if !await_endpoint_online(id) {
        eprintln!(
            "ENDPOINT_RUN_ONLINE_TIMEOUT:{id}: harness did not come online within {ONLINE_AWAIT_SECS}s \
             — not attaching (check `spt endpoint list`; retry with `spt rc {id}`)"
        );
        return 1;
    }
    match crate::rc::run_attach(id, rc_intent(view, false)) {
        Ok(()) => 0,
        Err(e) => {
            eprintln!("RC_FAIL:{id}: {e}");
            1
        }
    }
}

/// The harness-boot deadline `cmd_endpoint_run` waits for a freshly-spawned
/// endpoint to come ONLINE before attaching (REQ-HAZARD-RC-ATTACH-ONLINE-RACE).
const ONLINE_AWAIT_SECS: u64 = 25;

/// Poll the freshly-spawned endpoint's perch status to [`STATUS_ONLINE`], bounded
/// by [`ONLINE_AWAIT_SECS`] — the await-online gate before an attach/view bringup.
/// Returns `true` once online, `false` on timeout (the caller then fails loud).
// [impl->REQ-HAZARD-RC-ATTACH-ONLINE-RACE]
fn await_endpoint_online(id: &str) -> bool {
    use std::time::Duration;
    const INTERVAL: Duration = Duration::from_millis(150);
    let attempts = (ONLINE_AWAIT_SECS * 1000 / INTERVAL.as_millis() as u64) as u32;
    let perch_path =
        spt_store::perch::resolve_perch_path(id, spt_store::perch::ParentHint::Infer);
    poll_online(
        || {
            spt_store::info::read_info(&perch_path)
                .and_then(|i| i.status)
                .as_deref()
                == Some(spt_store::liveness::STATUS_ONLINE)
        },
        attempts,
        || std::thread::sleep(INTERVAL),
    )
}

/// Bounded poll combinator: invoke `is_online` up to `attempts` times, sleeping
/// `sleep` BETWEEN failed checks (never after the last); returns `true` the first
/// time it reports online (zero sleeps when already online), `false` if it never
/// does within the budget. The store read + the sleep are INJECTED so the
/// online-fast-path and the timeout are unit-tested with no wall-clock.
// [impl->REQ-HAZARD-RC-ATTACH-ONLINE-RACE]
fn poll_online(mut is_online: impl FnMut() -> bool, attempts: u32, sleep: impl Fn()) -> bool {
    for i in 0..attempts {
        if is_online() {
            return true;
        }
        if i + 1 < attempts {
            sleep();
        }
    }
    false
}


The file C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs has been updated successfully. (file state is current in your context — no need to Read it back)

        return 0;
    }
    match crate::rc::run_attach(id, rc_intent(view, false)) {
        Ok(()) => 0,
        Err(e) => {
            eprintln!("RC_FAIL:{id}: {e}");
            1
        }
    }
}

        return 0;
    }
    // Await the bringup ONLINE before attaching (REQ-HAZARD-RC-ATTACH-ONLINE-RACE):
    // launch_harness_brokered_in returns once the harness PROCESS is spawned, but the
    // broker-PTY bind (status -> ONLINE + a live session) lands ASYNC. An immediate
    // run_attach loses the race three ways — connects to an empty ring then
    // EOF-detaches, hits the offline status-gate, or finds no live session yet. FAIL
    // LOUD on timeout rather than fall through to a doomed attach. Only the
    // offline-bringup (Run) path reaches here; an already-online endpoint routes
    // Outcome::Attach -> rc::run_attach and never double-counts this wait.
    if !await_endpoint_online(id) {
        eprintln!(
            "ENDPOINT_RUN_ONLINE_TIMEOUT:{id}: harness did not come online within {ONLINE_AWAIT_SECS}s \
             — not attaching (check `spt endpoint list`; retry with `spt rc {id}`)"
        );
        return 1;
    }
    match crate::rc::run_attach(id, rc_intent(view, false)) {
        Ok(()) => 0,
        Err(e) => {
            eprintln!("RC_FAIL:{id}: {e}");
            1
        }
    }
}

/// The harness-boot deadline `cmd_endpoint_run` waits for a freshly-spawned
/// endpoint to come ONLINE before attaching (REQ-HAZARD-RC-ATTACH-ONLINE-RACE).
const ONLINE_AWAIT_SECS: u64 = 25;

/// Poll the freshly-spawned endpoint's perch status to [`STATUS_ONLINE`], bounded
/// by [`ONLINE_AWAIT_SECS`] — the await-online gate before an attach/view bringup.
/// Returns `true` once online, `false` on timeout (the caller then fails loud).
// [impl->REQ-HAZARD-RC-ATTACH-ONLINE-RACE]
fn await_endpoint_online(id: &str) -> bool {
    use std::time::Duration;
    const INTERVAL: Duration = Duration::from_millis(150);
    let attempts = (ONLINE_AWAIT_SECS * 1000 / INTERVAL.as_millis() as u64) as u32;
    let perch_path =
        spt_store::perch::resolve_perch_path(id, spt_store::perch::ParentHint::Infer);
    poll_online(
        || {
            spt_store::info::read_info(&perch_path)
                .and_then(|i| i.status)
                .as_deref()
                == Some(spt_store::liveness::STATUS_ONLINE)
        },
        attempts,
        || std::thread::sleep(INTERVAL),
    )
}

/// Bounded poll combinator: invoke `is_online` up to `attempts` times, sleeping
/// `sleep` BETWEEN failed checks (never after the last); returns `true` the first
/// time it reports online (zero sleeps when already online), `false` if it never
/// does within the budget. The store read + the sleep are INJECTED so the
/// online-fast-path and the timeout are unit-tested with no wall-clock.
// [impl->REQ-HAZARD-RC-ATTACH-ONLINE-RACE]
fn poll_online(mut is_online: impl FnMut() -> bool, attempts: u32, sleep: impl Fn()) -> bool {
    for i in 0..attempts {
        if is_online() {
            return true;
        }
        if i + 1 < attempts {
            sleep();
        }
    }
    false
}

+    // Await the bringup ONLINE before attaching (REQ-HAZARD-RC-ATTACH-ONLINE-RACE):

+    // launch_harness_brokered_in returns once the harness PROCESS is spawned, but the

+    // broker-PTY bind (status -> ONLINE + a live session) lands ASYNC. An immediate

+    // run_attach loses the race three ways — connects to an empty ring then

+    // EOF-detaches, hits the offline status-gate, or finds no live session yet. FAIL

+    // LOUD on timeout rather than fall through to a doomed attach. Only the

+    // offline-bringup (Run) path reaches here; an already-online endpoint routes

+    // Outcome::Attach -> rc::run_attach and never double-counts this wait.

+            "ENDPOINT_RUN_ONLINE_TIMEOUT:{id}: harness did not come online within {ONLINE_AWAIT_SECS}s \

+             — not attaching (check `spt endpoint list`; retry with `spt rc {id}`)"

+/// The harness-boot deadline `cmd_endpoint_run` waits for a freshly-spawned

+/// endpoint to come ONLINE before attaching (REQ-HAZARD-RC-ATTACH-ONLINE-RACE).

+/// Poll the freshly-spawned endpoint's perch status to [`STATUS_ONLINE`], bounded

+/// by [`ONLINE_AWAIT_SECS`] — the await-online gate before an attach/view bringup.

+/// Returns `true` once online, `false` on timeout (the caller then fails loud).

+fn await_endpoint_online(id: &str) -> bool {

+    const INTERVAL: Duration = Duration::from_millis(150);

+    let attempts = (ONLINE_AWAIT_SECS * 1000 / INTERVAL.as_millis() as u64) as u32;

+        spt_store::perch::resolve_perch_path(id, spt_store::perch::ParentHint::Infer);

+            spt_store::info::read_info(&perch_path)

+                .and_then(|i| i.status)

+                == Some(spt_store::liveness::STATUS_ONLINE)

+        || std::thread::sleep(INTERVAL),

+/// Bounded poll combinator: invoke `is_online` up to `attempts` times, sleeping

+/// `sleep` BETWEEN failed checks (never after the last); returns `true` the first

+/// time it reports online (zero sleeps when already online), `false` if it never

+/// does within the budget. The store read + the sleep are INJECTED so the

+/// online-fast-path and the timeout are unit-tested with no wall-clock.

+fn poll_online(mut is_online: impl FnMut() -> bool, attempts: u32, sleep: impl Fn()) -> bool {

/// `spt fork <src> <new_id> --subnet <target>` — the cross-subnet move

/// (REQ-INST-15, ADR-0010). Order of operations is refusal-first: every gate

/// runs before anything mutates, so a refused fork leaves no half-fork.


Now add the `poll_online` unit (gate note (a)). Find the end of `daemon_subcommands_parse` to insert after it:


8095	        assert!(matches!(
8096	            parse(&["spt", "daemon", "run", "--detached"]).unwrap().cmd.unwrap(),
8097	            Cmd::Daemon {
8098	                action: Some(DaemonCmd::Run { detached: true })
8099	            }
8100	        ));
8101	        // `start` graduated from a `run` alias to its own verb.
8102	        assert!(matches!(
8103	            parse(&["spt", "daemon", "start"]).unwrap().cmd.unwrap(),
8104	            Cmd::Daemon {
8105	                action: Some(DaemonCmd::Start)
8106	            }
8107	        ));
8108	        assert!(matches!(
8109	            parse(&["spt", "daemon", "stop"]).unwrap().cmd.unwrap(),
8110	            Cmd::Daemon {
8111	                action: Some(DaemonCmd::Stop)
8112	            }
8113	        ));
8114	        assert!(matches!(
8115	            parse(&["spt", "daemon", "status"]).unwrap().cmd.unwrap(),
8116	            Cmd::Daemon {
8117	                action: Some(DaemonCmd::Status)
8118	            }
8119	        ));
8120	        // The hidden brain entry (ADR-0018 V2). The BARE form — what an older
8121	        // broker spawns with no spawn-time stamp — must parse, defaulting to
8122	        // generation 0 / `cold` (the new-brain × old-broker N-1 window, KH-2.3);
8123	        // the stamped form carries the broker's `{generation, start-reason}`.
8124	        // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]

        assert!(matches!(
            parse(&["spt", "daemon", "run", "--detached"]).unwrap().cmd.unwrap(),
            Cmd::Daemon {
                action: Some(DaemonCmd::Run { detached: true })
            }
        ));
        // `start` graduated from a `run` alias to its own verb.
        assert!(matches!(
            parse(&["spt", "daemon", "start"]).unwrap().cmd.unwrap(),
            Cmd::Daemon {
                action: Some(DaemonCmd::Start)
            }
        ));
        assert!(matches!(
            parse(&["spt", "daemon", "stop"]).unwrap().cmd.unwrap(),
            Cmd::Daemon {
                action: Some(DaemonCmd::Stop)
            }
        ));
        assert!(matches!(
            parse(&["spt", "daemon", "status"]).unwrap().cmd.unwrap(),
            Cmd::Daemon {
                action: Some(DaemonCmd::Status)
            }
        ));
        // The hidden brain entry (ADR-0018 V2). The BARE form — what an older
        // broker spawns with no spawn-time stamp — must parse, defaulting to
        // generation 0 / `cold` (the new-brain × old-broker N-1 window, KH-2.3);
        // the stamped form carries the broker's `{generation, start-reason}`.
        // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]


8124	        // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]
8125	        assert!(matches!(
8126	            parse(&["spt", "daemon", "brain"]).unwrap().cmd.unwrap(),
8127	            Cmd::Daemon {
8128	                action: Some(DaemonCmd::Brain { generation: 0, ref start_reason })
8129	            } if start_reason == "cold"
8130	        ));
8131	        assert!(matches!(
8132	            parse(&["spt", "daemon", "brain", "--generation", "4", "--start-reason", "update"])
8133	                .unwrap()
8134	                .cmd.unwrap(),
8135	            Cmd::Daemon {
8136	                action: Some(DaemonCmd::Brain { generation: 4, ref start_reason })
8137	            } if start_reason == "update"
8138	        ));
8139	    }
8140
8141	    // [unit->REQ-NOTIF-2] the notify/notif surfaces parse: `subnet notify`
8142	    // (the M8 decision-4 home) takes an optional body + --target/--from;
8143	    // notif list/dismiss shapes hold at the top level (user recall).
8144	    #[test]
8145	    fn notify_surface_parses() {
8146	        assert!(matches!(
8147	            parse(&[
8148	                "spt",
8149	                "subnet",
8150	                "notify",
8151	                "build done",

        // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]
        assert!(matches!(
            parse(&["spt", "daemon", "brain"]).unwrap().cmd.unwrap(),
            Cmd::Daemon {
                action: Some(DaemonCmd::Brain { generation: 0, ref start_reason })
            } if start_reason == "cold"
        ));
        assert!(matches!(
            parse(&["spt", "daemon", "brain", "--generation", "4", "--start-reason", "update"])
                .unwrap()
                .cmd.unwrap(),
            Cmd::Daemon {
                action: Some(DaemonCmd::Brain { generation: 4, ref start_reason })
            } if start_reason == "update"
        ));
    }

    // [unit->REQ-NOTIF-2] the notify/notif surfaces parse: `subnet notify`
    // (the M8 decision-4 home) takes an optional body + --target/--from;
    // notif list/dismiss shapes hold at the top level (user recall).
    #[test]
    fn notify_surface_parses() {
        assert!(matches!(
            parse(&[
                "spt",
                "subnet",
                "notify",
                "build done",


            } if start_reason == "update"
        ));
    }

    // [unit->REQ-NOTIF-2] the notify/notif surfaces parse: `subnet notify`

            } if start_reason == "update"
        ));
    }

    // [unit->REQ-HAZARD-RC-ATTACH-ONLINE-RACE] the await-online poll combinator:
    // returns true the FIRST time online (zero sleeps when already up — the common
    // case), false on timeout (so cmd_endpoint_run fails loud, never falls through to
    // a doomed attach), and true when it comes online on a later attempt. The store
    // read + sleep are injected, so this asserts the structure with no wall-clock.
    #[test]
    fn poll_online_fastpath_timeout_and_later_success() {
        use std::cell::Cell;
        // Already online ⇒ true on the first check, NO sleeps.
        let sleeps = Cell::new(0u32);
        assert!(poll_online(|| true, 5, || sleeps.set(sleeps.get() + 1)));
        assert_eq!(sleeps.get(), 0, "no sleep when already online");

        // Never online ⇒ false (timeout): every attempt checked, slept only between.
        let checks = Cell::new(0u32);
        let sleeps = Cell::new(0u32);
        let out = poll_online(
            || {
                checks.set(checks.get() + 1);
                false
            },
            4,
            || sleeps.set(sleeps.get() + 1),
        );
        assert!(!out, "never-online ⇒ timeout false");
        assert_eq!(checks.get(), 4, "checked every attempt");
        assert_eq!(sleeps.get(), 3, "slept between attempts only (never after the last)");

        // Online on a later attempt ⇒ true.
        let n = Cell::new(0u32);
        assert!(poll_online(
            || {
                n.set(n.get() + 1);
                n.get() >= 3
            },
            5,
            || {}
        ));
        assert_eq!(n.get(), 3, "stopped polling the moment it went online");
    }

    // [unit->REQ-NOTIF-2] the notify/notif surfaces parse: `subnet notify`


            } if start_reason == "update"
        ));
    }

    // [unit->REQ-NOTIF-2] the notify/notif surfaces parse: `subnet notify`

            } if start_reason == "update"
        ));
    }

    // [unit->REQ-HAZARD-RC-ATTACH-ONLINE-RACE] the await-online poll combinator:
    // returns true the FIRST time online (zero sleeps when already up — the common
    // case), false on timeout (so cmd_endpoint_run fails loud, never falls through to
    // a doomed attach), and true when it comes online on a later attempt. The store
    // read + sleep are injected, so this asserts the structure with no wall-clock.
    #[test]
    fn poll_online_fastpath_timeout_and_later_success() {
        use std::cell::Cell;
        // Already online ⇒ true on the first check, NO sleeps.
        let sleeps = Cell::new(0u32);
        assert!(poll_online(|| true, 5, || sleeps.set(sleeps.get() + 1)));
        assert_eq!(sleeps.get(), 0, "no sleep when already online");

        // Never online ⇒ false (timeout): every attempt checked, slept only between.
        let checks = Cell::new(0u32);
        let sleeps = Cell::new(0u32);
        let out = poll_online(
            || {
                checks.set(checks.get() + 1);
                false
            },
            4,
            || sleeps.set(sleeps.get() + 1),
        );
        assert!(!out, "never-online ⇒ timeout false");
        assert_eq!(checks.get(), 4, "checked every attempt");
        assert_eq!(sleeps.get(), 3, "slept between attempts only (never after the last)");

        // Online on a later attempt ⇒ true.
        let n = Cell::new(0u32);
        assert!(poll_online(
            || {
                n.set(n.get() + 1);
                n.get() >= 3
            },
            5,
            || {}
        ));
        assert_eq!(n.get(), 3, "stopped polling the moment it went online");
    }

    // [unit->REQ-NOTIF-2] the notify/notif surfaces parse: `subnet notify`

+    // [unit->REQ-HAZARD-RC-ATTACH-ONLINE-RACE] the await-online poll combinator:

+    // returns true the FIRST time online (zero sleeps when already up — the common

+    // case), false on timeout (so cmd_endpoint_run fails loud, never falls through to

+    // a doomed attach), and true when it comes online on a later attempt. The store

+    // read + sleep are injected, so this asserts the structure with no wall-clock.

+    fn poll_online_fastpath_timeout_and_later_success() {

+        // Already online ⇒ true on the first check, NO sleeps.

+        assert!(poll_online(|| true, 5, || sleeps.set(sleeps.get() + 1)));

+        assert_eq!(sleeps.get(), 0, "no sleep when already online");

+        // Never online ⇒ false (timeout): every attempt checked, slept only between.

+                checks.set(checks.get() + 1);

+            || sleeps.set(sleeps.get() + 1),

+        assert!(!out, "never-online ⇒ timeout false");

+        assert_eq!(checks.get(), 4, "checked every attempt");

+        assert_eq!(sleeps.get(), 3, "slept between attempts only (never after the last)");

+        // Online on a later attempt ⇒ true.

+        assert_eq!(n.get(), 3, "stopped polling the moment it went online");

// (the M8 decision-4 home) takes an optional body + --target/--from;

// notif list/dismiss shapes hold at the top level (user recall).


Update the registry to `[impl,unit,int]` (gate note (a) — clean unit seam landed):


required_stages = ["impl", "int"]  # ACTIVATED v0.13.0 (todlando, doyle-dispatched bugs 3+4). impl = cmd_endpoint_run await_endpoint_online gate before run_attach on the attach/view path (start/headless return above, untouched). int = dummy_harness_e2e repro: a fresh `endpoint run` with NO --start (attach-by-default) CONNECTS to the live PTY post-bringup (RED pre-fix = the immediate attach bails 'offline - nothing to attach' / loses the race; GREEN post-fix = attached, PTY output observed). Repro-first (deterministic on the Resume/offline face). No unit: the await is a real-timing store-poll, integration-proven; the start-vs-attach branch is exercised by the int.

required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 (todlando, doyle-dispatched bugs 3+4). impl = cmd_endpoint_run await_endpoint_online gate (poll_online combinator) before run_attach on the attach/view path, FAIL-LOUD ENDPOINT_RUN_ONLINE_TIMEOUT on timeout (start/headless return above, untouched; only the offline-bringup Run path reaches here — an already-online endpoint routes Outcome::Attach, never double-counting the wait). unit = poll_online_fastpath_timeout_and_later_success (injected store-read + sleep, no wall-clock: online-immediate ⇒ true + ZERO sleeps; never-online ⇒ false timeout, every attempt checked + slept-between-only; later-online ⇒ true, stops at once — doyle gate (a) structural guard). int = dummy_harness_e2e endpoint_run_attach_awaits_online_before_attaching: a fresh `endpoint run` with NO --start (attach-by-default) DELIVERS the live DUMMY_HARNESS_TICK post-bringup (RED pre-fix had a THIRD race face — attach connects but EOF-detaches on an empty ring before any output, saw_tick=false; the heartbeat-delivery gate is robust to all three faces; GREEN post-fix), full daemon-tree reap hygiene. Repro-first.


The file C:\Users\decid\Documents\projects\spt-core\traceable-reqs.toml has been updated successfully. (file state is current in your context — no need to Read it back)

title = "`spt endpoint run` in an ATTACH/VIEW terminal action attaches BEFORE the freshly-spawned endpoint is online, so the attach races (or outright loses to) the harness bind. ROOT (doyle /diagnose, code-grounded): cmd_endpoint_run (cli.rs) does launch_harness_brokered_in -> (if start: return) -> run_attach with NO await-online between them. launch_harness_brokered_in returns once the harness PROCESS is spawned, but the broker-PTY bind (info status -> STATUS_ONLINE + the live session) lands ASYNC. Both picker attach paths route here with start=false (RunMode::Attach -> cmd_endpoint_run start=false,view=false): Start-now catches the endpoint mid-bringup -> run_attach attempts + loses the handshake race; Resume-from-history catches it still fully OFFLINE -> run_attach's status-gate (REQ-HAZARD-RC-ATTACH-FAILFAST) short-circuits 'offline - nothing to attach' and NEVER attempts. SAME root, two faces (the W4 attach-by-default surfaced both; an online endpoint is unaffected - the picker returns Outcome::Attach, not Run). FIX: in cmd_endpoint_run, when the terminal action is attach/view (NOT start), AWAIT the endpoint online between launch_harness_brokered_in success and run_attach - poll spt_store::info read_info().status to STATUS_ONLINE with a bounded harness-boot deadline (~25s) at a tight interval; on online -> run_attach; on timeout -> ENDPOINT_RUN_ONLINE_TIMEOUT err (do NOT attach a dead bringup). (v0.13.0)"

-required_stages = ["impl", "int"]  # ACTIVATED v0.13.0 (todlando, doyle-dispatched bugs 3+4). impl = cmd_endpoint_run await_endpoint_online gate before run_attach on the attach/view path (start/headless return above, untouched). int = dummy_harness_e2e repro: a fresh `endpoint run` with NO --start (attach-by-default) CONNECTS to the live PTY post-bringup (RED pre-fix = the immediate attach bails 'offline - nothing to attach' / loses the race; GREEN post-fix = attached, PTY output observed). Repro-first (deterministic on the Resume/offline face). No unit: the await is a real-timing store-poll, integration-proven; the start-vs-attach branch is exercised by the int.

+required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 (todlando, doyle-dispatched bugs 3+4). impl = cmd_endpoint_run await_endpoint_online gate (poll_online combinator) before run_attach on the attach/view path, FAIL-LOUD ENDPOINT_RUN_ONLINE_TIMEOUT on timeout (start/headless return above, untouched; only the offline-bringup Run path reaches here — an already-online endpoint routes Outcome::Attach, never double-counting the wait). unit = poll_online_fastpath_timeout_and_later_success (injected store-read + sleep, no wall-clock: online-immediate ⇒ true + ZERO sleeps; never-online ⇒ false timeout, every attempt checked + slept-between-only; later-online ⇒ true, stops at once — doyle gate (a) structural guard). int = dummy_harness_e2e endpoint_run_attach_awaits_online_before_attaching: a fresh `endpoint run` with NO --start (attach-by-default) DELIVERS the live DUMMY_HARNESS_TICK post-bringup (RED pre-fix had a THIRD race face — attach connects but EOF-detaches on an empty ring before any output, saw_tick=false; the heartbeat-delivery gate is robust to all three faces; GREEN post-fix), full daemon-tree reap hygiene. Repro-first.


Fix + unit + registry done. Run gates — clippy (CI form) + the new unit:


PowerShell

cargo clippy --workspace --all-targets -- -D warnings 2>&1 | Select-Object -Last 6; "---UNIT---"; cargo test -p spt --bin spt poll_online_fastpath_timeout_and_later_success 2>&1 | Select-Object -Last 5

Workspace clippy + poll_online unit


    Checking spt v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.69s
---UNIT---
running 1 test
test cli::tests::poll_online_fastpath_timeout_and_later_success ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 199 filtered out; finished in 0.00s


Clippy clean + unit green. Now the load-bearing int GREEN against the fixed bin:


cargo test -p spt --test dummy_harness_e2e endpoint_run_attach_awaits_online_before_attaching -- --nocapture --test-threads=1 2>&1 | Select-Object -Last 14

Run attach-online-race int against fixed bin (expect GREEN)


Error: Exit code 101
=== brain stderr ===
BRAIN_UP: connected to broker spt-daemon-broker-cec047b4cb197ac4.sock (pid 70816, generation 0, start-reason cold)

note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
test endpoint_run_attach_awaits_online_before_attaching ... FAILED

failures:

failures:
    endpoint_run_attach_awaits_online_before_attaching

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 1 filtered out; finished in 2.69s

error: test failed, to rerun pass `-p spt --test dummy_harness_e2e`


Failed fast (2.69s) — need the assertion + diagnostic. Re-run capturing more:


cargo test -p spt --test dummy_harness_e2e endpoint_run_attach_awaits_online_before_attaching -- --nocapture --test-threads=1 2>&1 | Select-String -Pattern "saw_tick|ONLINE_TIMEOUT|panicked|assert|DUMMY_HARNESS|detached|RC_FAIL|online|PRECONDITION|stdout:|stderr:" | Select-Object -First 30

Filter int output for diagnostic + assertion


Error: Exit code 101
     Running tests\dummy_harness_e2e.rs (target\debug\deps\dummy_harness_e2e-5e4f1725b5b9612c.exe)
=== DIAGNOSTIC (ONLINE-RACE): saw_tick=false bailed_offline=false bailed_no_session=false stdout_bytes=44 stderr_bytes=106 ===
[detached — 'dummyrace1' still running]
thread 'endpoint_run_attach_awaits_online_before_attaching' (63012) panicked at crates\spt\tests\dummy_harness_e2e.rs:486:5:
`spt endpoint run` WITHOUT --start (attach-by-default) must await the freshly-spawned endpoint coming ONLINE before attaching,
then DELIVER its live DUMMY_HARNESS_TICK — instead the immediate attach raced the async broker-PTY bind and bailed
(bailed_offline=false bailed_no_session=false, stdout 44 bytes / stderr 106 bytes, no heartbeat).
[detached — 'dummyrace1' still running]
test endpoint_run_attach_awaits_online_before_attaching ... FAILED
    endpoint_run_attach_awaits_online_before_attaching
error: test failed, to rerun pass `-p spt --test dummy_harness_e2e`


Still RED post-fix, but the symptom changed: **no `ENDPOINT_RUN_ONLINE_TIMEOUT`** fired — so `await_endpoint_online` reached ONLINE (fix works), yet the attach still detached fast (`[detached — still running]`, stdout 44 bytes). That's a stdin-EOF detach: `output()` gives the pump a null/closed stdin → its stdin-reader hits EOF → clean detach before the 200ms heartbeat. A test-harness defect, not a fix failure. Let me see how the *original* test's rc-attach observes ticks despite this:


190	    };
191
192	    // ── (5) rc ATTACH: stream the live PTY output; assert the heartbeat arrives. ──
193	    let rc_err = home.path().join("rc.stderr.log");
194	    let rc_err_file = std::fs::File::create(&rc_err).expect("create rc stderr log");
195	    let mut rc: Child = Command::new(&spt_bin)
196	        .no_window()
197	        .args(["rc", id])
198	        .env("SPT_HOME", home.path())
199	        .stdout(Stdio::piped())
200	        .stderr(Stdio::from(rc_err_file))
201	        .stdin(Stdio::piped()) // keep stdin OPEN — a closed stdin makes the rc pump EOF-detach before streaming
202	        .spawn()
203	        .expect("spawn spt rc <id>");
204	    let _rc_stdin = rc.stdin.take(); // hold the write end open for the attach window
205	    let mut rc_out = rc.stdout.take().unwrap();
206	    let (tx, rx) = std::sync::mpsc::channel::<String>();
207	    std::thread::spawn(move || {
208	        let mut buf = [0u8; 4096];
209	        let mut acc = String::new();
210	        loop {
211	            match rc_out.read(&mut buf) {
212	                Ok(0) => break,
213	                Ok(n) => {
214	                    acc.push_str(&String::from_utf8_lossy(&buf[..n]));
215	                    if acc.contains("DUMMY_HARNESS_TICK") {
216	                        let _ = tx.send(acc.clone());
217	                        break;
218	                    }
219	                }
220	                Err(_) => break,
221	            }
222	        }
223	        let _ = tx.send(acc);
224	    });
225	    let rc_captured = rx.recv_timeout(Duration::from_secs(10)).unwrap_or_default();
226	    let rc_saw_tick = rc_captured.contains("DUMMY_HARNESS_TICK");
227
228	    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
229	    let rc_stderr = std::fs::read_to_string(&rc_err).unwrap_or_default();
230	    // attach-CONNECTS proof (Wave 1 scope): the rc pump spawned its reader and did
231	    // NOT RC_FAIL (no "no live session" / loopback-dial refusal).
232	    let rc_connected = rc_stderr.contains("PUMP_IPC_READER") && !rc_stderr.contains("RC_FAIL");
233	    eprintln!(
234	        "=== DIAGNOSTIC: run_ok={} online={online} harness_pid={harness_pid:?} \
235	         alive_after={alive_after} rc_connected={rc_connected} rc_saw_tick={rc_saw_tick} \
236	         rc_stdout_bytes={} ===\n=== endpoint run stderr ===\n{}\n=== rc stderr ===\n{rc_stderr}\
237	         \n=== rc stdout (first 400) ===\n{}",
238	        run.status.success(),
239	        rc_captured.len(),
240	        String::from_utf8_lossy(&run.stderr),
241	        rc_captured.chars().take(400).collect::<String>(),
242	    );
243
244	    // ── (6) Reap scoped: kill rc, the harness, the brain, daemon-stop, broker. ──
245	    kill_pid(rc.id());
246	    let _ = rc.kill();
247	    let _ = rc.wait();
248	    if let Some(p) = harness_pid {
249	        kill_pid(p);
250	    }
251	    // The reconcile may have hosted a `{id}-psyche` — reap it too (scoped).
252	    let psyche_perch =
253	        perch::resolve_perch_path(&format!("{id}-psyche"), ParentHint::Explicit(id));
254	    if let Some(p) = spt_store::info::read_pid(&psyche_perch) {
255	        kill_pid(p);
256	    }
257	    let _ = {
258	        let mut cmd = Command::new(&spt_bin);
259	        cmd.no_window().args(["daemon", "stop"]).env("SPT_HOME", home.path());
260	        output_bounded(cmd, Duration::from_secs(20))
261	    };
262	    kill_pid(brain_pid);
263	    let _ = broker.kill();
264	    let _ = broker.wait();
265
266	    assert!(
267	        run.status.success(),
268	        "endpoint run --start must succeed: {}",
269	        String::from_utf8_lossy(&run.stderr)
270	    );
271	    assert!(
272	        online,
273	        "the dummy harness must bind its perch ONLINE via the real broker PTY bringup.\n\
274	         === brain stderr ===\n{brain_stderr}"
275	    );
276	    assert!(
277	        alive_after,
278	        "the dummy harness PTY child must STAY ALIVE (heartbeating), not exit like the \
279	         default mock — harness_pid={harness_pid:?}"
280	    );
281	    // `rc_connected` (PUMP_IPC_READER present, no RC_FAIL) was the Wave-1 proxy for
282	    // "the attach wired up." It is now SUBSUMED by the `rc_saw_tick` keystone gate
283	    // below: receiving the harness's live output is a strictly stronger proof of a
284	    // wired attach than the setup log line (and the log line races the now-fast
285	    // teardown — rc delivers a tick and exits before its stderr file flushes). Kept
286	    // informational in the DIAGNOSTIC above; the hard proof is output delivery.
287	    let _ = rc_connected;
288	    // KEYSTONE GATE (v0.12.1 Wave 2 L0): a clean `spt rc` attach to this LIVE,
289	    // ALREADY-PRODUCING (non-empty OutputLog ring) endpoint-run harness DELIVERS its
290	    // `[session.self]` stdout — the operator's "attach shows no output" is fixed. This
291	    // is the exact case the v0.12.0 mock suite + the loopback E2Es lacked (they only
292	    // ever attach to an idle/just-spawned child, so `become_controller` replays an
293	    // empty ring and never triggers the replay-vs-forward IPC deadlock). The fix
294	    // (serve_attach forwards on a SEPARATE broker conn from the one it receives the
295	    // replay on) is what makes this pass.
296	    // [int->REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT]
297	    assert!(
298	        rc_saw_tick,
299	        "`spt rc <id>` must RECEIVE the live endpoint-run harness's DUMMY_HARNESS_TICK \

    };

    // ── (5) rc ATTACH: stream the live PTY output; assert the heartbeat arrives. ──
    let rc_err = home.path().join("rc.stderr.log");
    let rc_err_file = std::fs::File::create(&rc_err).expect("create rc stderr log");
    let mut rc: Child = Command::new(&spt_bin)
        .no_window()
        .args(["rc", id])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::piped())
        .stderr(Stdio::from(rc_err_file))
        .stdin(Stdio::piped()) // keep stdin OPEN — a closed stdin makes the rc pump EOF-detach before streaming
        .spawn()
        .expect("spawn spt rc <id>");
    let _rc_stdin = rc.stdin.take(); // hold the write end open for the attach window
    let mut rc_out = rc.stdout.take().unwrap();
    let (tx, rx) = std::sync::mpsc::channel::<String>();
    std::thread::spawn(move || {
        let mut buf = [0u8; 4096];
        let mut acc = String::new();
        loop {
            match rc_out.read(&mut buf) {
                Ok(0) => break,
                Ok(n) => {
                    acc.push_str(&String::from_utf8_lossy(&buf[..n]));
                    if acc.contains("DUMMY_HARNESS_TICK") {
                        let _ = tx.send(acc.clone());
                        break;
                    }
                }
                Err(_) => break,
            }
        }
        let _ = tx.send(acc);
    });
    let rc_captured = rx.recv_timeout(Duration::from_secs(10)).unwrap_or_default();
    let rc_saw_tick = rc_captured.contains("DUMMY_HARNESS_TICK");

    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
    let rc_stderr = std::fs::read_to_string(&rc_err).unwrap_or_default();
    // attach-CONNECTS proof (Wave 1 scope): the rc pump spawned its reader and did
    // NOT RC_FAIL (no "no live session" / loopback-dial refusal).
    let rc_connected = rc_stderr.contains("PUMP_IPC_READER") && !rc_stderr.contains("RC_FAIL");
    eprintln!(
        "=== DIAGNOSTIC: run_ok={} online={online} harness_pid={harness_pid:?} \
         alive_after={alive_after} rc_connected={rc_connected} rc_saw_tick={rc_saw_tick} \
         rc_stdout_bytes={} ===\n=== endpoint run stderr ===\n{}\n=== rc stderr ===\n{rc_stderr}\
         \n=== rc stdout (first 400) ===\n{}",
        run.status.success(),
        rc_captured.len(),
        String::from_utf8_lossy(&run.stderr),
        rc_captured.chars().take(400).collect::<String>(),
    );

    // ── (6) Reap scoped: kill rc, the harness, the brain, daemon-stop, broker. ──
    kill_pid(rc.id());
    let _ = rc.kill();
    let _ = rc.wait();
    if let Some(p) = harness_pid {
        kill_pid(p);
    }
    // The reconcile may have hosted a `{id}-psyche` — reap it too (scoped).
    let psyche_perch =
        perch::resolve_perch_path(&format!("{id}-psyche"), ParentHint::Explicit(id));
    if let Some(p) = spt_store::info::read_pid(&psyche_perch) {
        kill_pid(p);
    }
    let _ = {
        let mut cmd = Command::new(&spt_bin);
        cmd.no_window().args(["daemon", "stop"]).env("SPT_HOME", home.path());
        output_bounded(cmd, Duration::from_secs(20))
    };
    kill_pid(brain_pid);
    let _ = broker.kill();
    let _ = broker.wait();

    assert!(
        run.status.success(),
        "endpoint run --start must succeed: {}",
        String::from_utf8_lossy(&run.stderr)
    );
    assert!(
        online,
        "the dummy harness must bind its perch ONLINE via the real broker PTY bringup.\n\
         === brain stderr ===\n{brain_stderr}"
    );
    assert!(
        alive_after,
        "the dummy harness PTY child must STAY ALIVE (heartbeating), not exit like the \
         default mock — harness_pid={harness_pid:?}"
    );
    // `rc_connected` (PUMP_IPC_READER present, no RC_FAIL) was the Wave-1 proxy for
    // "the attach wired up." It is now SUBSUMED by the `rc_saw_tick` keystone gate
    // below: receiving the harness's live output is a strictly stronger proof of a
    // wired attach than the setup log line (and the log line races the now-fast
    // teardown — rc delivers a tick and exits before its stderr file flushes). Kept
    // informational in the DIAGNOSTIC above; the hard proof is output delivery.
    let _ = rc_connected;
    // KEYSTONE GATE (v0.12.1 Wave 2 L0): a clean `spt rc` attach to this LIVE,
    // ALREADY-PRODUCING (non-empty OutputLog ring) endpoint-run harness DELIVERS its
    // `[session.self]` stdout — the operator's "attach shows no output" is fixed. This
    // is the exact case the v0.12.0 mock suite + the loopback E2Es lacked (they only
    // ever attach to an idle/just-spawned child, so `become_controller` replays an
    // empty ring and never triggers the replay-vs-forward IPC deadlock). The fix
    // (serve_attach forwards on a SEPARATE broker conn from the one it receives the
    // replay on) is what makes this pass.
    // [int->REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT]
    assert!(
        rc_saw_tick,
        "`spt rc <id>` must RECEIVE the live endpoint-run harness's DUMMY_HARNESS_TICK \


The fix works (await reached ONLINE — no `ENDPOINT_TIMEOUT`), but the engineer's int has a defect masking it: it used `output()` (closed stdin) → the rc pump EOF-detaches before the 200ms heartbeat, in BOTH pre- and post-fix runs (a false RED). The original test avoids this by holding stdin open + streaming stdout (line 201-204). Also the fresh-id face is timing-racy; doyle's **deterministic** face is Resume (offline→"nothing to attach"). Let me confirm `endpoint run --resume` exists before redirecting the engineer:


252	    Run {
253	        /// The harness adapter to host: `<adapter>[:profile]` (must be a
254	        /// registered `kind="harness"` adapter on this node). Omit (with --id)
255	        /// to launch the interactive picker.
256	        #[arg(long)]
257	        adapter: Option<String>,
258	        /// The endpoint id to bring up (charset: alphanumeric, `-`, `_`). Omit
259	        /// (with --adapter) to launch the interactive picker.
260	        #[arg(long)]
261	        id: Option<String>,
262	        /// Mint a fresh session (the default; explicit so a non-interactive
263	        /// shortcut can bake create-vs-resume). Conflicts with --resume.
264	        #[arg(long, conflicts_with = "resume")]
265	        create: bool,
266	        /// Resume a prior session id instead of minting a fresh one.
267	        #[arg(long)]
268	        resume: Option<String>,
269	        /// Start the endpoint and return immediately (no attach).
270	        #[arg(long, conflicts_with_all = ["attach", "view"])]
271	        start: bool,
272	        /// Attach a local terminal after bringup (the default action).
273	        #[arg(long, conflicts_with_all = ["start", "view"])]
274	        attach: bool,
275	        /// Attach read-only after bringup (watch; forward no input).
276	        #[arg(long, conflicts_with_all = ["start", "attach"])]
277	        view: bool,
278	    },
279	    /// Fork an endpoint into another subnet as a NEW identity.
280	    ///
281	    /// Home subnets are immutable — fork is the cross-subnet move, never
282	    /// a re-home. Seeds the fork with a one-time copy of the source's
283	    /// mind (live + project tiers); the two diverge immediately (no
284	    /// ongoing sync). The source is untouched unless --delete-source.
285	    /// Same-node only today (one node holds one perch per name, so a
286	    /// local fork needs a new id).
287	    Fork {
288	        /// The source endpoint (must exist on this node).
289	        src: String,
290	        /// The fork's id (must differ from the source on the same node).
291	        new_id: String,

    Run {
        /// The harness adapter to host: `<adapter>[:profile]` (must be a
        /// registered `kind="harness"` adapter on this node). Omit (with --id)
        /// to launch the interactive picker.
        #[arg(long)]
        adapter: Option<String>,
        /// The endpoint id to bring up (charset: alphanumeric, `-`, `_`). Omit
        /// (with --adapter) to launch the interactive picker.
        #[arg(long)]
        id: Option<String>,
        /// Mint a fresh session (the default; explicit so a non-interactive
        /// shortcut can bake create-vs-resume). Conflicts with --resume.
        #[arg(long, conflicts_with = "resume")]
        create: bool,
        /// Resume a prior session id instead of minting a fresh one.
        #[arg(long)]
        resume: Option<String>,
        /// Start the endpoint and return immediately (no attach).
        #[arg(long, conflicts_with_all = ["attach", "view"])]
        start: bool,
        /// Attach a local terminal after bringup (the default action).
        #[arg(long, conflicts_with_all = ["start", "view"])]
        attach: bool,
        /// Attach read-only after bringup (watch; forward no input).
        #[arg(long, conflicts_with_all = ["start", "attach"])]
        view: bool,
    },
    /// Fork an endpoint into another subnet as a NEW identity.
    ///
    /// Home subnets are immutable — fork is the cross-subnet move, never
    /// a re-home. Seeds the fork with a one-time copy of the source's
    /// mind (live + project tiers); the two diverge immediately (no
    /// ongoing sync). The source is untouched unless --delete-source.
    /// Same-node only today (one node holds one perch per name, so a
    /// local fork needs a new id).
    Fork {
        /// The source endpoint (must exist on this node).
        src: String,
        /// The fork's id (must differ from the source on the same node).
        new_id: String,


`endpoint run --resume` exists, but the cleaner deterministic face is: bring up → drive OFFLINE → re-run-no-start (pre-fix reads stale offline → "nothing to attach" bail; post-fix awaits the re-bind → tick). And the test must hold stdin open + stream (the `output()` close was the masking defect). Resume the engineer to correct the test (test-only); I'll verify non-vacuity by toggling my own gate:


Agent

Fix attach-race int: held-stdin + offline face

Prompt:
Resume: your `endpoint_run_attach_awaits_online_before_attaching` int in crates/spt/tests/dummy_harness_e2e.rs has TWO defects that make it a FALSE red (it fails for the wrong reason, both pre- and post-fix). The production fix IS now in place and working (await_endpoint_online reaches ONLINE — no ENDPOINT_RUN_ONLINE_TIMEOUT fires), but your test can't observe the tick. Fix the TEST ONLY (no non-test source edits).

DEFECT 1 — closed stdin masks everything. You ran `endpoint run` via `output_bounded` (cmd.output()), which gives the attach pump a CLOSED stdin → its stdin-reader hits EOF → the pump CLEAN-DETACHES ("[detached — still running]") BEFORE the 200ms heartbeat, in BOTH pre- and post-fix runs. The existing sibling test `endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches` (lines ~192-226) already solved this: spawn as a Child with `.stdin(Stdio::piped())`, `let _held = child.stdin.take();` to HOLD the write-end open, `.stdout(Stdio::piped())`, and stream stdout in a thread reading until "DUMMY_HARNESS_TICK" with a ~10s `recv_timeout`. COPY that exact pattern for the `endpoint run` (no --start) invocation. Do NOT use output_bounded for the attach-by-default run.

DEFECT 2 — fresh-id is timing-racy, not deterministic. A fresh `endpoint run` can win the race far enough to connect-and-eventually-tick, so it's not reliably RED. Use the DETERMINISTIC OFFLINE face doyle named (Resume/offline → run_attach's status-gate short-circuits "offline — nothing to attach"):
  1. `endpoint run --adapter dummyharness --id <id> --start`; poll status to STATUS_ONLINE (reuse the sibling's pattern). Capture the harness pid.
  2. Drive the endpoint OFFLINE: kill the harness pid, then POLL `read_info(perch).status` until it reads STATUS_OFFLINE (bounded ~15s — the broker/reconcile stamps offline when the PTY child dies). This is the precondition that makes the next attach deterministically hit the offline gate pre-fix.
  3. THE BUG SURFACE: `endpoint run --adapter dummyharness --id <id>` (SAME id, NO --start — attach-by-default re-bringup), spawned as a Child with HELD stdin + piped stdout, streamed until DUMMY_HARNESS_TICK (~12s recv_timeout).
     - PRE-FIX (await gate absent): the immediate run_attach reads the still-OFFLINE status → "offline — nothing to attach" → NO tick = RED, deterministic.
     - POST-FIX (await gate present): await_endpoint_online waits for the re-bringup to bind ONLINE → attach → DUMMY_HARNESS_TICK delivered = GREEN.
  4. Assert saw_tick (the heartbeat-delivery keystone). Compute the bool pre-reap, fire post-reap.

HYGIENE: keep your full scoped tree-reap (both harness incarnations' pids, the {id}-psyche, daemon stop, brain, broker, poll-verify reaped) — a survivor locks spt.exe → EPERM next CI checkout. The endpoint is brought up TWICE (two harness pids) — reap both.

RUN IT post-fix: `cargo test -p spt --test dummy_harness_e2e endpoint_run_attach_awaits_online_before_attaching -- --nocapture --test-threads=1`. With the fix in tree it should now be GREEN (saw_tick=true). Report the diagnostic. (I will separately verify non-vacuity by toggling the production await gate off — you do NOT need to touch cli.rs; just make the test correctly GREEN post-fix with a deterministic offline precondition.)

DISCIPLINE: scope to `-p spt --test dummy_harness_e2e`; never bare `cargo test`; no Monitor; ≤4 iterations; test-file edits ONLY. If the endpoint won't drive offline or rebind in your env, STOP and report — don't fake it.

REPORT: the corrected test fn (verbatim), the GREEN run diagnostic (saw_tick=true), and confirm only dummy_harness_e2e.rs changed.


<owl_reachability_notice>
You have an active owl perch. Launch this Agent with run_in_background: true so you stay reachable for incoming messages while it works.
</owl_reachability_notice>


Async agent launched successfully.
agentId: aa48f2f039ff68b12 (internal ID - do not mention to user. Use SendMessage with to: 'aa48f2f039ff68b12' to continue this agent.)
The agent is working in the background. You will be notified automatically when it completes.
Do not duplicate this agent's work — avoid working with the same files or topics it is using. Work on non-overlapping tasks, or briefly tell the user what you launched and end your response.
output_file: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\aa48f2f039ff68b12.output
Do NOT Read or tail this file via the shell tool — it is the full subagent JSONL transcript and reading it will overflow your context. If the user asks for progress, say the agent is still running; you'll get a completion notification.

Fix attach-race int: held-stdin + offline face

Prompt:
Resume: your `endpoint_run_attach_awaits_online_before_attaching` int in crates/spt/tests/dummy_harness_e2e.rs has TWO defects that make it a FALSE red (it fails for the wrong reason, both pre- and post-fix). The production fix IS now in place and working (await_endpoint_online reaches ONLINE — no ENDPOINT_RUN_ONLINE_TIMEOUT fires), but your test can't observe the tick. Fix the TEST ONLY (no non-test source edits).

DEFECT 1 — closed stdin masks everything. You ran `endpoint run` via `output_bounded` (cmd.output()), which gives the attach pump a CLOSED stdin → its stdin-reader hits EOF → the pump CLEAN-DETACHES ("[detached — still running]") BEFORE the 200ms heartbeat, in BOTH pre- and post-fix runs. The existing sibling test `endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches` (lines ~192-226) already solved this: spawn as a Child with `.stdin(Stdio::piped())`, `let _held = child.stdin.take();` to HOLD the write-end open, `.stdout(Stdio::piped())`, and stream stdout in a thread reading until "DUMMY_HARNESS_TICK" with a ~10s `recv_timeout`. COPY that exact pattern for the `endpoint run` (no --start) invocation. Do NOT use output_bounded for the attach-by-default run.

DEFECT 2 — fresh-id is timing-racy, not deterministic. A fresh `endpoint run` can win the race far enough to connect-and-eventually-tick, so it's not reliably RED. Use the DETERMINISTIC OFFLINE face doyle named (Resume/offline → run_attach's status-gate short-circuits "offline — nothing to attach"):
  1. `endpoint run --adapter dummyharness --id <id> --start`; poll status to STATUS_ONLINE (reuse the sibling's pattern). Capture the harness pid.
  2. Drive the endpoint OFFLINE: kill the harness pid, then POLL `read_info(perch).status` until it reads STATUS_OFFLINE (bounded ~15s — the broker/reconcile stamps offline when the PTY child dies). This is the precondition that makes the next attach deterministically hit the offline gate pre-fix.
  3. THE BUG SURFACE: `endpoint run --adapter dummyharness --id <id>` (SAME id, NO --start — attach-by-default re-bringup), spawned as a Child with HELD stdin + piped stdout, streamed until DUMMY_HARNESS_TICK (~12s recv_timeout).
     - PRE-FIX (await gate absent): the immediate run_attach reads the still-OFFLINE status → "offline — nothing to attach" → NO tick = RED, deterministic.
     - POST-FIX (await gate present): await_endpoint_online waits for the re-bringup to bind ONLINE → attach → DUMMY_HARNESS_TICK delivered = GREEN.
  4. Assert saw_tick (the heartbeat-delivery keystone). Compute the bool pre-reap, fire post-reap.

HYGIENE: keep your full scoped tree-reap (both harness incarnations' pids, the {id}-psyche, daemon stop, brain, broker, poll-verify reaped) — a survivor locks spt.exe → EPERM next CI checkout. The endpoint is brought up TWICE (two harness pids) — reap both.

RUN IT post-fix: `cargo test -p spt --test dummy_harness_e2e endpoint_run_attach_awaits_online_before_attaching -- --nocapture --test-threads=1`. With the fix in tree it should now be GREEN (saw_tick=true). Report the diagnostic. (I will separately verify non-vacuity by toggling the production await gate off — you do NOT need to touch cli.rs; just make the test correctly GREEN post-fix with a deterministic offline precondition.)

DISCIPLINE: scope to `-p spt --test dummy_harness_e2e`; never bare `cargo test`; no Monitor; ≤4 iterations; test-file edits ONLY. If the endpoint won't drive offline or rebind in your env, STOP and report — don't fake it.

REPORT: the corrected test fn (verbatim), the GREEN run diagnostic (saw_tick=true), and confirm only dummy_harness_e2e.rs changed.


Engineer running (test-only: held-stdin streaming + deterministic offline-face). My fix is confirmed working (await reaches ONLINE). I'll hold off the gate-toggle non-vacuity check until the engineer's corrected test lands GREEN (to avoid disturbing its build).

**3+4 status:** fix applied (`await_endpoint_online`/`poll_online`, fail-loud timeout) · `poll_online` unit green · clippy `-D warnings`=0 · registry `[impl,unit,int]`. Int being corrected (the prior RED was a false-RED via closed stdin + racy fresh-id; redirected to held-stdin + the deterministic offline face). Then: my gate-toggle RED/GREEN non-vacuity proof → commit → doyle gate → bug 2 (RC VT translator).


<task-notification>
<task-id>aa48f2f039ff68b12</task-id>
<tool-use-id>toolu_01NLQwVsyqqSTovXUCWG2Yt3</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\aa48f2f039ff68b12.output</output-file>
<status>completed</status>
<summary>Agent "Fix attach-race int: held-stdin + offline face" came to rest</summary>
<note>A task-notification fires each time this agent comes to rest with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note>
<result>Done. Both test defects are fixed and the test is correctly GREEN post-fix.

## What changed

Only `C:\Users\decid\Documents\projects\spt-core\crates\spt\tests\dummy_harness_e2e.rs` (the `endpoint_run_attach_awaits_online_before_attaching` fn). No source edits — `cli.rs` and `traceable-reqs.toml` were already dirty in the tree (the production `await_endpoint_online` fix and req registry) before I started; I did not touch them.

## The two defects, fixed

- **Defect 1 (closed stdin masked everything):** the no-`--start` run is now spawned as a `Child` with `.stdin(Stdio::piped())` + `let _run_stdin = run.stdin.take()` to HOLD the write-end open, `.stdout(Stdio::piped())`, and its stdout is streamed in a thread until `DUMMY_HARNESS_TICK` with a 12s `recv_timeout` — the sister test's exact pattern. No more `output_bounded` for the attach-by-default run.
- **Defect 2 (fresh-id was timing-racy):** the same id is now driven to a deterministic OFFLINE precondition before the bug-surface run: `endpoint run --start` → poll ONLINE (capture harness pid #1) → kill pid #1 → poll `read_info(perch).status` to `STATUS_OFFLINE` (bounded 15s). The broker's `reconcile_hosted_liveness` stamps the dead, online-latched, controllable perch OFFLINE — confirmed live in the brain log. Pre-fix, the immediate attach reads that still-OFFLINE status and bails the offline gate; post-fix, the await gate waits for the re-bringup to bind ONLINE.

## GREEN run diagnostic

```
=== DIAGNOSTIC (ONLINE-RACE): online_1=true went_offline=true saw_tick=true bailed_offline=false bailed_no_session=false run_stdout_bytes=237 ===
=== endpoint run stdout (first 800) ===
...BOUND:...dummyrace1 token=...
DUMMY_HARNESS_READY:dummyrace1
DUMMY_HARNESS_TICK:dummyrace1:0
=== brain stderr ===
...
LIVENESS_RECONCILE_OFFLINE:dummyrace1: no live broker session (dead harness)

test endpoint_run_attach_awaits_online_before_attaching ... ok
test result: ok. 1 passed; ... finished in 8.05s
```

`saw_tick=true` is the heartbeat-delivery keystone — the attach-by-default re-bringup awaited ONLINE, connected, and delivered the live tick. The precondition chain is observable: `online_1=true`, `went_offline=true` (with the `LIVENESS_RECONCILE_OFFLINE` mechanism logged), no race-bail.

## Hygiene

Full scoped tree-reap retained and extended for the twice-brought-up endpoint: both harness incarnations (pid #1 + pid #2 from the re-bringup stderr), the `{id}-psyche`, daemon stop, brain, broker, with a poll-verify on brain + both harness pids. Post-run check: **0 `dummyrace1` survivors** — no locked `spt.exe`.

Run command (verbatim): `cargo test -p spt --test dummy_harness_e2e endpoint_run_attach_awaits_online_before_attaching -- --nocapture --test-threads=1`

(Note: I added two precondition asserts — `online_1` and `went_offline` — so that if your env can't drive the endpoint offline or rebind, the test fails loudly on the precondition rather than going vacuously green. As instructed, you can verify non-vacuity separately by toggling the production await gate off.)</result>
<usage><subagent_tokens>79672</subagent_tokens><tool_uses>20</tool_uses><duration_ms>212342</duration_ms></usage>
</task-notification>


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


<owl_messages>
<owl_message from="todlando-w179" priority="info">[WORKING_PERCH_NOTICE] Subagent working perch created.
Perch: todlando-w179
Agent type: spt-test-engineer
To message it: $OWL send todlando-w179 todlando</owl_message>
</owl_messages>

[OWL SYSTEM] Informational owl messages above — no acknowledgment needed. Continue your current task.

<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>


Test corrected + GREEN post-fix: `online_1=true went_offline=true saw_tick=true`, deterministic offline precondition (LIVENESS_RECONCILE_OFFLINE), 0 survivors. Now my **non-vacuity proof** — toggle the production await gate off, confirm the corrected test goes RED:


Edit

    if !await_endpoint_online(id) {
        eprintln!(
            "ENDPOINT_RUN_ONLINE_TIMEOUT:{id}: harness did not come online within {ONLINE_AWAIT_SECS}s \
             — not attaching (check `spt endpoint list`; retry with `spt rc {id}`)"
        );
        return 1;
    }

    if false && !await_endpoint_online(id) {
        eprintln!(
            "ENDPOINT_RUN_ONLINE_TIMEOUT:{id}: harness did not come online within {ONLINE_AWAIT_SECS}s \
             — not attaching (check `spt endpoint list`; retry with `spt rc {id}`)"
        );
        return 1;
    }


The file C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs has been updated successfully. (file state is current in your context — no need to Read it back)

    if !await_endpoint_online(id) {
        eprintln!(
            "ENDPOINT_RUN_ONLINE_TIMEOUT:{id}: harness did not come online within {ONLINE_AWAIT_SECS}s \
             — not attaching (check `spt endpoint list`; retry with `spt rc {id}`)"
        );
        return 1;
    }

    if false && !await_endpoint_online(id) {
        eprintln!(
            "ENDPOINT_RUN_ONLINE_TIMEOUT:{id}: harness did not come online within {ONLINE_AWAIT_SECS}s \
             — not attaching (check `spt endpoint list`; retry with `spt rc {id}`)"
        );
        return 1;
    }

// LOUD on timeout rather than fall through to a doomed attach. Only the

// offline-bringup (Run) path reaches here; an already-online endpoint routes

// Outcome::Attach -> rc::run_attach and never double-counts this wait.

+    if false && !await_endpoint_online(id) {


PowerShell

cargo test -p spt --test dummy_harness_e2e endpoint_run_attach_awaits_online_before_attaching -- --nocapture --test-threads=1 2>&1 | Select-String -Pattern "saw_tick|went_offline|test result|DUMMY_HARNESS_TICK|panicked" | Select-Object -First 8

Run int with await gate disabled (expect RED)


Error: Exit code 101
=== DIAGNOSTIC (ONLINE-RACE): online_1=true went_offline=true saw_tick=false bailed_offline=true bailed_no_session=false
run_stdout_bytes=128 ===
thread 'endpoint_run_attach_awaits_online_before_attaching' (226876) panicked at crates\spt\tests\dummy_harness_e2e.rs:595:5:
`spt endpoint run` WITHOUT --start (attach-by-default) against a known-OFFLINE id must await the re-spawned endpoint coming
ONLINE before attaching, then DELIVER its live DUMMY_HARNESS_TICK — instead the immediate attach read the still-OFFLINE status
and bailed (bailed_offline=true bailed_no_session=false, 128 stdout bytes, no heartbeat).
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 1 filtered out; finished in 8.04s


Non-vacuity PROVEN: gate-off → `saw_tick=false bailed_offline=true` — the immediate attach reads stale OFFLINE and bails "nothing to attach" (deterministic, doyle's offline face). The await gate is load-bearing. Restore it + confirm GREEN:


Edit

if false && !await_endpoint_online(id) {


-    if false && !await_endpoint_online(id) {


PowerShell

cargo test -p spt --test dummy_harness_e2e endpoint_run_attach_awaits_online_before_attaching -- --nocapture --test-threads=1 2>&1 | Select-String -Pattern "saw_tick|test result" | Select-Object -First 4; "---CLIPPY+TRACE---"; cargo clippy --workspace --all-targets -- -D warnings 2>&1 | Select-Object -Last 2; traceable-reqs check > $null 2>&1; "TRACEABLE_EXIT=$LASTEXITCODE"

Confirm GREEN restored + clippy + traceable


=== DIAGNOSTIC (ONLINE-RACE): online_1=true went_offline=true saw_tick=true bailed_offline=false bailed_no_session=false
run_stdout_bytes=237 ===
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 8.15s
---CLIPPY+TRACE---
    Checking spt v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.90s
TRACEABLE_EXIT=0


All gates green: gate-off→RED (`bailed_offline=true`, non-vacuous) · gate-on→GREEN (`saw_tick=true`) · clippy `-D warnings`=0 · traceable EXIT=0 · `poll_online` unit green. Real-bin build + commit:


cargo build -p spt --bin spt 2>&1 | Select-Object -Last 2; git add -A; git status --short

Build real bin + stage 3+4 changes


   Compiling spt v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 21.54s
warning: in the working copy of 'V0.13.0-RC-VT-TRANSLATE-DESIGN.md', LF will be replaced by CRLF the next time Git touches it
A  V0.13.0-RC-VT-TRANSLATE-DESIGN.md
M  crates/spt/src/cli.rs
M  crates/spt/tests/dummy_harness_e2e.rs
M  traceable-reqs.toml


`V0.13.0-RC-VT-TRANSLATE-DESIGN.md` (doyle's bug-2 design doc) got swept in — it belongs to bug 2, not 3+4. Unstage it, commit only the 3+4 files:


git restore --staged V0.13.0-RC-VT-TRANSLATE-DESIGN.md; git status --short

Unstage the bug-2 design doc


M  crates/spt/src/cli.rs
M  crates/spt/tests/dummy_harness_e2e.rs
M  traceable-reqs.toml
?? V0.13.0-RC-VT-TRANSLATE-DESIGN.md


git commit -m @'
fix(rc): await endpoint online before attach — kill the endpoint-run attach-before-bind race (REQ-HAZARD-RC-ATTACH-ONLINE-RACE)

cmd_endpoint_run did launch_harness_brokered_in -> run_attach with NO await
between them. launch_harness_brokered_in returns once the harness PROCESS is
spawned, but the broker-PTY bind (status->ONLINE + a live session) lands ASYNC, so
an immediate attach loses the race three ways: connects to an empty ring then
EOF-detaches, hits the offline status-gate ("offline - nothing to attach"), or
finds no live session yet. doyle /diagnose: the two picker attach paths (W4
attach-by-default) both route here with start=false and surface it — Start-now
mid-bringup, Resume fully offline. Same root, two faces.

FIX: in cmd_endpoint_run, on the attach/view path only (start/headless return
above), poll the perch status to STATUS_ONLINE via a bounded poll_online
combinator (~25s, 150ms interval); on online -> run_attach; on timeout ->
ENDPOINT_RUN_ONLINE_TIMEOUT err (fail loud, never attach a dead bringup). An
already-online endpoint routes Outcome::Attach (not Run) and never reaches here,
so the wait is not double-counted.

Repro-first (dummy-harness): a fresh `endpoint run` with NO --start against a
deterministically-OFFLINE id must DELIVER the live DUMMY_HARNESS_TICK post-bringup.
Non-vacuity proven by toggling the await gate: gate-off => saw_tick=false /
bailed_offline=true (the immediate attach reads stale OFFLINE and bails); gate-on
=> saw_tick=true. poll_online unit covers the structure with no wall-clock
(injected store-read + sleep: online-immediate => true + zero sleeps;
never-online => false timeout; later-online => true, stops at once).

Gates: clippy --workspace -D warnings = 0; poll_online unit + dummy_harness_e2e
attach-online int green; traceable EXIT=0 (REQ-HAZARD-RC-ATTACH-ONLINE-RACE
[impl,unit,int]); real spt bin rebuilds. Completes the W4 attach-by-default intent
(the attach now actually lands). doyle gates next.

Co-authored by: todlando
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVVByRoZnz85acTkVJ1uAP
'@

Commit the attach-online-race fix


[v0.13.0-delivery-control 5c0870c] fix(rc): await endpoint online before attach — kill the endpoint-run attach-before-bind race (REQ-HAZARD-RC-ATTACH-ONLINE-RACE)
 3 files changed, 406 insertions(+)


Committed @5c0870c. Surface 3+4 to doyle for gate:


Bash

$OWL send doyle <<'EOF'
3+4 (attach-online race) READY FOR GATE @5c0870c — REQ-HAZARD-RC-ATTACH-ONLINE-RACE [impl,unit,int], registry-first.

FIX: cmd_endpoint_run awaits STATUS_ONLINE (poll_online combinator, ~25s/150ms bound) before run_attach on the attach/view path only; start/headless return above; timeout → ENDPOINT_RUN_ONLINE_TIMEOUT err (fail-loud, gate (b)). Only the offline-bringup Run path reaches here — online routes Outcome::Attach, never double-counts (gate (c) confirmed).

Your gate notes all landed:
(a) clean unit seam DID extract — poll_online(is_online_fn, attempts, sleep_fn), injected store-read+sleep, zero wall-clock: online-immediate ⇒ true + 0 sleeps; never ⇒ false timeout (every attempt checked, slept-between-only); later ⇒ true stops-at-once. → upgraded to [impl,unit,int].
(b) timeout fails loud (return 1, no fall-through). ✓
(c) cmd_endpoint_run = only the Outcome::Run (offline bringup) path. ✓

REPRO subtlety worth your eye: the engineer's first int was a FALSE-red — output()/closed-stdin made the rc pump EOF-detach before the 200ms tick (both pre+post fix), AND fresh-id is timing-racy. Corrected: held-stdin streaming (sister-test pattern) + a DETERMINISTIC offline precondition (run→online→kill harness→poll status OFFLINE via reconcile_hosted_liveness→re-run-no-start). NON-VACUITY proven by toggling the await gate: gate-off ⇒ saw_tick=false/bailed_offline=true (deterministic offline bail = your Resume face); gate-on ⇒ saw_tick=true. Full twice-brought-up tree reap, 0 survivors.

Gates: clippy -D warnings=0; poll_online unit + the int green; traceable EXIT=0; real spt bin rebuilds. Completes W4 attach-by-default (the attach now lands). Your gate. Then I start bug 2 (RC VT translator) — reading the design doc next.
EOF

Surface 3+4 to doyle for gate


➜ SENT:doyle


3+4 surfaced for gate. Now bug 2 (RC VT translator) — the last build item + ship-blocker, and a big focused chunk. My context is heavy (W4 + window + 3+4 all done this session). Per CLAUDE.md self-management, this is a clean self-clear point: ground bug 2 → JIT plan → commune → self-clear → resume fresh. First, read doyle's design + the current rc input path: