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	        }
175	        std::thread::sleep(Duration::from_millis(150));
176	    }
177
178	    // The broker-spawned harness pid (from `ENDPOINT_RUN:… pid=Some(NNN)`) — the
179	    // long-lived PTY child itself, NOT the transient `spt api bind` subprocess that
180	    // stamps info.json's pid. It must stay alive (heartbeating).
181	    let run_stderr = String::from_utf8_lossy(&run.stderr).to_string();
182	    let harness_pid: Option<u32> = run_stderr
183	        .split("pid=Some(")
184	        .nth(1)
185	        .and_then(|s| s.split(')').next())
186	        .and_then(|s| s.trim().parse().ok());
187	    let alive_after = {
188	        std::thread::sleep(Duration::from_millis(700));
189	        harness_pid.map(spt_store::proc::is_process_alive).unwrap_or(false)
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	    assert!(
282	        rc_connected,
283	        "`spt rc <id>` must ATTACH to the live endpoint-run session (pump reader spawned, \
284	         no RC_FAIL) — the real broker-PTY attach path is wired.\n=== rc stderr ===\n{rc_stderr}\
285	         \n=== brain stderr ===\n{brain_stderr}"
286	    );
287	    // NOTE (v0.12.1 Wave 2 lead): `rc_saw_tick` is a DIAGNOSTIC, not yet an assertion.
288	    // A clean attach to this live, heartbeating endpoint-run harness currently receives
289	    // ZERO of its [session.self] stdout — the operator's "attach shows no output" symptom
290	    // reproduced with NO death/wedge. Whether the broker drains+fans an endpoint-run
291	    // (SpawnReq) harness PTY into the OutputLog is the immediate Wave 2 root-cause lead
292	    // (likely a THIRD root alongside VIEWER-CLOSE-DETACH + ATTACH-WEDGE). Once that REQ
293	    // lands, promote this to a hard assertion.
294	    if !rc_saw_tick {
295	        eprintln!(
296	            "WAVE2-LEAD: rc attached but received {} bytes, no DUMMY_HARNESS_TICK — \
297	             endpoint-run harness PTY output not reaching the attach (root-cause pending).",
298	            rc_captured.len()
299	        );
300	    }
301	}
302

//! Wave 1 (v0.12.1) — the DUMMY-HARNESS fixture smoke test. The permanent
//! real-harness regression fixture the v0.12.0 mock tests never had: it drives the
//! REAL spt-hosted bringup path end-to-end —
//! `spt endpoint run` → `launch_harness_brokered_in` → broker PTY → `spt rc` attach
//! — against a REAL detached `spt daemon run`, NOT an in-proc `reconcile_once` and
//! NOT a mock that exits immediately.
//!
//! The dummy harness (`mock-session --mode dummy`) binds its perch on startup (the
//! harness contract), prints a stdout heartbeat on an interval, and stays alive
//! until killed — so "broker wedged / endpoint died" is cleanly separable from "a
//! real harness failed to launch." Wave 2's lifecycle gates (VIEWER-CLOSE-DETACH,
//! ATTACH-WEDGE, daemon-stop) build on exactly this bring-up.
//!
//! Asserts: `endpoint run --start` brings the dummy up, its perch goes ONLINE, the
//! PTY child stays alive (heartbeating), and `spt rc <id>` attaches and receives the
//! live heartbeat output. Bounded; all spawned pids reaped scoped (never
//! machine-wide — shared runner).

use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output, Stdio};
use std::time::{Duration, Instant};

mod common;
use common::CommandNoWindowExt;

use spt_store::perch::{self, ParentHint};

fn kill_pid(pid: u32) {
    #[cfg(windows)]
    let _ = Command::new("taskkill")
        .no_window()
        .args(["/PID", &pid.to_string(), "/F", "/T"])
        .output();
    #[cfg(unix)]
    let _ = Command::new("kill").args(["-9", &pid.to_string()]).output();
}

fn output_bounded(mut cmd: Command, deadline: Duration) -> Output {
    let (tx, rx) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        let _ = tx.send(cmd.output());
    });
    rx.recv_timeout(deadline)
        .expect("captured spt call must complete within its deadline")
        .expect("run spt")
}

fn ready_pid(path: &Path) -> Option<u32> {
    let s = std::fs::read_to_string(path).ok()?;
    serde_json::from_str::<serde_json::Value>(&s)
        .ok()?
        .get("pid")?
        .as_u64()
        .map(|p| p as u32)
}

fn wait_for_ready_pid(path: &Path, budget: Duration) -> Option<u32> {
    let deadline = Instant::now() + budget;
    while Instant::now() < deadline {
        if let Some(p) = ready_pid(path) {
            return Some(p);
        }
        std::thread::sleep(Duration::from_millis(50));
    }
    None
}

fn sibling_bin(name: &str) -> PathBuf {
    PathBuf::from(env!("CARGO_BIN_EXE_spt"))
        .with_file_name(format!("{name}{}", std::env::consts::EXE_SUFFIX))
}

#[test]
fn endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches() {
    // ── (1) Isolated SPT_HOME. NET-ENABLED (the daemon self-initializes a real node
    //    identity): `spt rc` attaches over a broker-minted LOOPBACK conn that surfaces
    //    as net-stream events, so it requires a net-bound broker — a net-less broker
    //    refuses the loopback dial ("net disabled"). Unlike the livehost/reconcile
    //    tests (which only inspect perch state), the rc-attach path needs net up. ──
    let home = tempfile::tempdir().unwrap();
    std::env::set_var("SPT_HOME", home.path());

    let spt_bin = PathBuf::from(env!("CARGO_BIN_EXE_spt"));
    let exe_suffix = spt_bin
        .extension()
        .map(|e| format!(".{}", e.to_string_lossy()))
        .unwrap_or_default();
    let mock_session = sibling_bin("mock-session");
    assert!(
        mock_session.exists(),
        "the dummy-harness program must be built: {} \
         (pre-build: cargo build -p mock-adapter --bin mock-session)",
        mock_session.display()
    );

    // ── (2) Register the DUMMY-HARNESS adapter: a kind=harness adapter whose
    //    [session.self] runs the long-lived dummy. The broker fills {id}/{session_id};
    //    the program token is the absolute mock-session path. The dummy shells the
    //    same `spt` to bind (via --spt). ──
    let spt_for_cmd = spt_bin.to_string_lossy().replace('\\', "/");
    let mock_for_cmd = mock_session.to_string_lossy().replace('\\', "/");
    let self_cmd = format!(
        "{mock_for_cmd} --mode dummy --id {{id}} --session-id {{session_id}} \
         --spt {spt_for_cmd} --mock-adapter dummyharness --type live_agent --heartbeat-ms 200"
    );
    // A faithful live_agent fixture DECLARES `[session.psyche_init]`: cmd_bind only
    // marks status=online for `live_agent && psyche_init.is_some()` (startup.rs:464 —
    // the online signal is gated to the reconcile host predicate). A LONG-LIVED psyche
    // (a copy of `spt` run as `ready {id}`, mirroring brain_restart_psyche_dup) so the
    // brain's reconcile hosts a resident `{id}-psyche` — the same shape the Wave 2
    // VIEWER-CLOSE / psyche-reap gates need.
    let src = perch::spt_home().join("srcs").join("dummyharness");
    std::fs::create_dir_all(&src).unwrap();
    let psyche_bin = src.join(format!("psychebin{exe_suffix}"));
    std::fs::copy(&spt_bin, &psyche_bin).expect("stage the psyche binary fixture");
    let drops = home.path().join("drops");
    std::fs::create_dir_all(&drops).unwrap();
    let dir = drops.to_string_lossy().replace('\\', "/");
    let manifest_toml = format!(
        "[adapter]\nname=\"dummyharness\"\nkind=\"harness\"\nversion=\"1\"\n\
         min_spt_core_version=\"0\"\n\n[session.self]\ncommand='{self_cmd}'\n\n\
         [session]\ncommune_dir=\"{dir}\"\nsignoff_dir=\"{dir}\"\n\n\
         [session.psyche_init]\ncommand='psychebin ready {{id}}'\ncwd=\"{{psyche_dir}}\"\nkeys=[]\n"
    );
    std::fs::write(src.join("manifest.toml"), &manifest_toml).unwrap();
    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();

    // ── (3) The REAL daemon broker process. ──
    let brain_log = home.path().join("brain.stderr.log");
    let brain_log_file = std::fs::File::create(&brain_log).expect("create brain stderr log");
    let mut broker: Child = Command::new(&spt_bin)
        .no_window()
        .args(["daemon", "run"])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::null())
        .stderr(Stdio::from(brain_log_file))
        .spawn()
        .expect("spawn spt daemon run (broker process)");
    let brain_pid = match wait_for_ready_pid(&home.path().join("brain.ready"), Duration::from_secs(30))
    {
        Some(p) => p,
        None => {
            let _ = broker.kill();
            let _ = broker.wait();
            panic!(
                "PRECONDITION: brain never came up.\n{}",
                std::fs::read_to_string(&brain_log).unwrap_or_default()
            );
        }
    };

    // ── (4) The REAL spt-hosted bringup: endpoint run --start (broker spawns the
    //    dummy into a PTY; the dummy binds its perch + heartbeats). ──
    let id = "dummy7";
    let run = {
        let mut cmd = Command::new(&spt_bin);
        cmd.no_window()
            .args(["endpoint", "run", "--adapter", "dummyharness", "--id", id, "--start"])
            .env("SPT_HOME", home.path());
        output_bounded(cmd, Duration::from_secs(45))
    };

    let self_perch = perch::resolve_perch_path(id, ParentHint::Infer);
    let status = || spt_store::info::read_info(&self_perch).and_then(|i| i.status);

    // The dummy binds its perch live within a few seconds of the broker PTY spawn.
    let mut online = false;
    let deadline = Instant::now() + Duration::from_secs(20);
    while Instant::now() < deadline {
        if status().as_deref() == Some(spt_store::liveness::STATUS_ONLINE) {
            online = true;
            break;
        }
        std::thread::sleep(Duration::from_millis(150));
    }

    // The broker-spawned harness pid (from `ENDPOINT_RUN:… pid=Some(NNN)`) — the
    // long-lived PTY child itself, NOT the transient `spt api bind` subprocess that
    // stamps info.json's pid. It must stay alive (heartbeating).
    let run_stderr = String::from_utf8_lossy(&run.stderr).to_string();
    let harness_pid: Option<u32> = run_stderr
        .split("pid=Some(")
        .nth(1)
        .and_then(|s| s.split(')').next())
        .and_then(|s| s.trim().parse().ok());
    let alive_after = {
        std::thread::sleep(Duration::from_millis(700));
        harness_pid.map(spt_store::proc::is_process_alive).unwrap_or(false)
    };

    // ── (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:?}"
    );
    assert!(
        rc_connected,
        "`spt rc <id>` must ATTACH to the live endpoint-run session (pump reader spawned, \
         no RC_FAIL) — the real broker-PTY attach path is wired.\n=== rc stderr ===\n{rc_stderr}\
         \n=== brain stderr ===\n{brain_stderr}"
    );
    // NOTE (v0.12.1 Wave 2 lead): `rc_saw_tick` is a DIAGNOSTIC, not yet an assertion.
    // A clean attach to this live, heartbeating endpoint-run harness currently receives
    // ZERO of its [session.self] stdout — the operator's "attach shows no output" symptom
    // reproduced with NO death/wedge. Whether the broker drains+fans an endpoint-run
    // (SpawnReq) harness PTY into the OutputLog is the immediate Wave 2 root-cause lead
    // (likely a THIRD root alongside VIEWER-CLOSE-DETACH + ATTACH-WEDGE). Once that REQ
    // lands, promote this to a hard assertion.
    if !rc_saw_tick {
        eprintln!(
            "WAVE2-LEAD: rc attached but received {} bytes, no DUMMY_HARNESS_TICK — \
             endpoint-run harness PTY output not reaching the attach (root-cause pending).",
            rc_captured.len()
        );
    }
}


Now I see the full fixture. Before running (slow), check the known-good attach.rs test (doyle's STEP-1 reference) to see exactly what child it spawns + how it differs, and check KH 7.6.


400	        "the session outlives the viewport"
401	    );
402	    target.kill_session().expect("cleanup kill");
403	}
404
405	// [int->REQ-RC-1] LOCAL attach rides the byte-identical pump (M12 W1.5, B1):
406	// a single broker spawns a real PTY session; the operator acquires a
407	// broker-minted in-process LOOPBACK conn (`net_dial_loopback` — NOT a QUIC
408	// self-dial, which iroh refuses) and drives the PTY through the SAME
409	// `serve_attach`+render pump a cross-node operator uses. The proof that "both
410	// transports → one pump" is BY CONSTRUCTION: this test and the cross-node
411	// `remote_attach_drives_a_real_pty_cross_daemon` call the identical
412	// `attach_drive_detach` helper; they differ only in how `conn_id` is acquired
413	// (`net_dial_loopback` vs `net_dial`) and in the expected origin. The loopback
414	// origin is THIS node's own id, gating same-node (access short-circuit Allow in
415	// production; default-open in this hermetic home — both Allow). The session
416	// outlives the viewport.
417	#[test]
418	fn local_attach_via_loopback_conn_rides_the_same_pump() {
419	    init_home();
420	    let dir = tempfile::tempdir().expect("tempdir");
421	    let name = unique_name();
422	    let broker = net_broker(&name, &dir.path().join("solo"));
423
424	    // One broker plays BOTH target and operator (the local case). Spawn the
425	    // echo child, then attach to it over a loopback conn from a second brain
426	    // connection to the SAME broker.
427	    let mut target = connect_retry(&name);
428	    let sid = target
429	        .spawn_session(echo_spawn_req())
430	        .expect("spawn echo child");
431
432	    // The operator acquires the broker's loopback conn; its `remote_id_hex` is
433	    // this node's OWN broker-minted id — the origin the peer row carries and the
434	    // serve gates same-node against.
435	    let mut operator = connect_retry(&name);
436	    let dialed = operator.net_dial_loopback().expect("operator loopback dial");
437	    let own_origin = dialed.remote_id_hex.clone();
438	    let (_viewport, mut target) = attach_drive_detach(
439	        &mut operator,
440	        target,
441	        dialed.conn_id,
442	        sid,
443	        "ling",
444	        &own_origin,
445	        b"LOCAL_DRIVE_OK",
446	    );
447	    assert_eq!(
448	        broker.session_count(),
449	        1,
450	        "the session outlives the local viewport"
451	    );
452	    target.kill_session().expect("cleanup kill");
453	}
454
455	// [int->REQ-HOST-RUN-2] the broker spawns the PTY child in the requested
456	// PROJECT cwd: a `SpawnReq.cwd` rides the wire through the broker into the
457	// portable-pty CommandBuilder, and a child that prints its cwd reports the dir
458	// we asked for — end to end (wire field → broker spawn → PTY cwd). Asserted on
459	// the unique tempdir leaf (robust against path canonicalization). The child
460	// prints its cwd then blocks (cat/findstr) so the read never races an exit.
461	#[test]
462	fn broker_spawns_the_pty_child_in_the_requested_cwd() {
463	    init_home();
464	    let dir = tempfile::tempdir().expect("tempdir");
465	    let proj = dir.path().join("the-project-root");
466	    std::fs::create_dir_all(&proj).expect("project dir");
467	    let leaf = "the-project-root";
468
469	    let name = unique_name();
470	    let _broker = net_broker(&name, &dir.path().join("broker"));
471	    let mut brain = connect_retry(&name);
472
473	    // A child that prints its cwd, then stays alive on stdin (no exit race).
474	    #[cfg(unix)]
475	    let (program, args) = (
476	        "sh".to_string(),
477	        vec!["-c".to_string(), "pwd; cat".to_string()],
478	    );
479	    #[cfg(windows)]
480	    let (program, args) = (
481	        "cmd".to_string(),
482	        vec!["/c".to_string(), "cd & findstr \".\"".to_string()],
483	    );
484
485	    let sid = brain
486	        .spawn_session(SpawnReq {
487	            program,
488	            args,
489	            rows: 24,
490	            cols: 80,
491	            endpoint: String::new(),
492	            cwd: Some(proj.to_string_lossy().into_owned()),
493	            env: Default::default(),
494	        })
495	        .expect("spawn cwd-printing child in the project cwd");
496
497	    brain.attach(sid, 0).expect("attach to read output");
498	    let mut out = Vec::new();
499	    brain
500	        .read_output_until(leaf.as_bytes(), &mut out)
501	        .expect("child prints its cwd");
502	    assert!(
503	        count(&out, leaf.as_bytes()) >= 1,
504	        "the child's cwd must be the requested project dir (leaf {leaf:?}), got: {:?}",
505	        String::from_utf8_lossy(&out)
506	    );
507	    brain.kill_session().expect("cleanup kill");
508	}
509
510	// [unit->REQ-HAZARD-RESTART-IDEMPOTENT] the viewport survives a target-brain
511	// crash mid-attach: the successor re-serves the SAME broker-held stream from
512	// seq 0 (worst-case stale cursor) — the replayed Request/input/output all
513	// dedup through the journals, input B sent INTO the dead window arrives, and
514	// B's viewport holds every marker exactly once.
515	// [unit->REQ-INST-8]
516	#[test]
517	fn attach_survives_target_brain_restart_exactly_once() {
518	    init_home();
519	    let dir = tempfile::tempdir().expect("tempdir");

        "the session outlives the viewport"
    );
    target.kill_session().expect("cleanup kill");
}

// [int->REQ-RC-1] LOCAL attach rides the byte-identical pump (M12 W1.5, B1):
// a single broker spawns a real PTY session; the operator acquires a
// broker-minted in-process LOOPBACK conn (`net_dial_loopback` — NOT a QUIC
// self-dial, which iroh refuses) and drives the PTY through the SAME
// `serve_attach`+render pump a cross-node operator uses. The proof that "both
// transports → one pump" is BY CONSTRUCTION: this test and the cross-node
// `remote_attach_drives_a_real_pty_cross_daemon` call the identical
// `attach_drive_detach` helper; they differ only in how `conn_id` is acquired
// (`net_dial_loopback` vs `net_dial`) and in the expected origin. The loopback
// origin is THIS node's own id, gating same-node (access short-circuit Allow in
// production; default-open in this hermetic home — both Allow). The session
// outlives the viewport.
#[test]
fn local_attach_via_loopback_conn_rides_the_same_pump() {
    init_home();
    let dir = tempfile::tempdir().expect("tempdir");
    let name = unique_name();
    let broker = net_broker(&name, &dir.path().join("solo"));

    // One broker plays BOTH target and operator (the local case). Spawn the
    // echo child, then attach to it over a loopback conn from a second brain
    // connection to the SAME broker.
    let mut target = connect_retry(&name);
    let sid = target
        .spawn_session(echo_spawn_req())
        .expect("spawn echo child");

    // The operator acquires the broker's loopback conn; its `remote_id_hex` is
    // this node's OWN broker-minted id — the origin the peer row carries and the
    // serve gates same-node against.
    let mut operator = connect_retry(&name);
    let dialed = operator.net_dial_loopback().expect("operator loopback dial");
    let own_origin = dialed.remote_id_hex.clone();
    let (_viewport, mut target) = attach_drive_detach(
        &mut operator,
        target,
        dialed.conn_id,
        sid,
        "ling",
        &own_origin,
        b"LOCAL_DRIVE_OK",
    );
    assert_eq!(
        broker.session_count(),
        1,
        "the session outlives the local viewport"
    );
    target.kill_session().expect("cleanup kill");
}

// [int->REQ-HOST-RUN-2] the broker spawns the PTY child in the requested
// PROJECT cwd: a `SpawnReq.cwd` rides the wire through the broker into the
// portable-pty CommandBuilder, and a child that prints its cwd reports the dir
// we asked for — end to end (wire field → broker spawn → PTY cwd). Asserted on
// the unique tempdir leaf (robust against path canonicalization). The child
// prints its cwd then blocks (cat/findstr) so the read never races an exit.
#[test]
fn broker_spawns_the_pty_child_in_the_requested_cwd() {
    init_home();
    let dir = tempfile::tempdir().expect("tempdir");
    let proj = dir.path().join("the-project-root");
    std::fs::create_dir_all(&proj).expect("project dir");
    let leaf = "the-project-root";

    let name = unique_name();
    let _broker = net_broker(&name, &dir.path().join("broker"));
    let mut brain = connect_retry(&name);

    // A child that prints its cwd, then stays alive on stdin (no exit race).
    #[cfg(unix)]
    let (program, args) = (
        "sh".to_string(),
        vec!["-c".to_string(), "pwd; cat".to_string()],
    );
    #[cfg(windows)]
    let (program, args) = (
        "cmd".to_string(),
        vec!["/c".to_string(), "cd & findstr \".\"".to_string()],
    );

    let sid = brain
        .spawn_session(SpawnReq {
            program,
            args,
            rows: 24,
            cols: 80,
            endpoint: String::new(),
            cwd: Some(proj.to_string_lossy().into_owned()),
            env: Default::default(),
        })
        .expect("spawn cwd-printing child in the project cwd");

    brain.attach(sid, 0).expect("attach to read output");
    let mut out = Vec::new();
    brain
        .read_output_until(leaf.as_bytes(), &mut out)
        .expect("child prints its cwd");
    assert!(
        count(&out, leaf.as_bytes()) >= 1,
        "the child's cwd must be the requested project dir (leaf {leaf:?}), got: {:?}",
        String::from_utf8_lossy(&out)
    );
    brain.kill_session().expect("cleanup kill");
}

// [unit->REQ-HAZARD-RESTART-IDEMPOTENT] the viewport survives a target-brain
// crash mid-attach: the successor re-serves the SAME broker-held stream from
// seq 0 (worst-case stale cursor) — the replayed Request/input/output all
// dedup through the journals, input B sent INTO the dead window arrives, and
// B's viewport holds every marker exactly once.
// [unit->REQ-INST-8]
#[test]
fn attach_survives_target_brain_restart_exactly_once() {
    init_home();
    let dir = tempfile::tempdir().expect("tempdir");


The known-good `broker_spawns_the_pty_child_in_the_requested_cwd` (L462) proves SpawnReq-spawn + attach + read delivers — but it attaches on the **same, still-open** brain conn. Both known-goods keep the spawning conn OPEN. Endpoint-run's unique difference: spawning conn (`cold_start`) **closes immediately**. Replay logic looks sound on paper, so I need runtime data to split ring-empty (a/b/c) vs replay/forward-broken (d).

Add targeted broker-side instrumentation, run the fixture, read the signal, then revert.


909	            sessions_exit.lock().unwrap().remove(&id);
910	        });
911
912	        self.sessions.lock().unwrap().insert(
913	            id,
914	            HostedSession {
915	                session,
916	                drain,
917	                log,
918	                endpoint: req.endpoint.clone(),
919	            },
920	        );
921
922	        let resp = Spawned {
923	            session_id: id,
924	            pid,
925	        };
926	        let frame = Envelope::new(
927	            KIND_SPAWNED,
928	            serde_json::to_value(resp).expect("Spawned serializes"),
929	        );
930	        send_frame(send, &frame);
931	        Ok(id)
932	    }
933
934	    /// Re-attach a brain to a session's output under the W2.5 controller/viewer
935	    /// model (REQ-RCVIEW-1 / REQ-KICK-1), replaying from `from_seq` for gapless,
936	    /// exactly-once resume. The `intent` + `by` select the role; the broker
937	    /// resolves it (controller take / viewer add / busy-refuse / loud displace)
938	    /// and replies the [`SubscribeOutcome`] so the serving brain can wake
939	    /// (controller-only), stay wake-neutral (viewer), or refuse (busy). A
940	    /// same-`by` re-subscribe silently re-takes the controller — the gate-#7
941	    /// self-kick guard.
942	    fn dispatch_subscribe(&self, env: Envelope, send: &SharedSend) -> Result<u64, String> {
943	        let req: SubscribeReq = serde_json::from_value(env.payload)
944	            .map_err(|e| format!("bad subscribe payload: {e}"))?;
945	        // Clone the log Arc out from under the sessions lock, then resolve under
946	        // the log lock — never hold both locks at once.
947	        //
948	        // NOTE (REQ-HAZARD-RC-ATTACH-FAILFAST): a `session.try_wait()` dead-child

            sessions_exit.lock().unwrap().remove(&id);
        });

        self.sessions.lock().unwrap().insert(
            id,
            HostedSession {
                session,
                drain,
                log,
                endpoint: req.endpoint.clone(),
            },
        );

        let resp = Spawned {
            session_id: id,
            pid,
        };
        let frame = Envelope::new(
            KIND_SPAWNED,
            serde_json::to_value(resp).expect("Spawned serializes"),
        );
        send_frame(send, &frame);
        Ok(id)
    }

    /// Re-attach a brain to a session's output under the W2.5 controller/viewer
    /// model (REQ-RCVIEW-1 / REQ-KICK-1), replaying from `from_seq` for gapless,
    /// exactly-once resume. The `intent` + `by` select the role; the broker
    /// resolves it (controller take / viewer add / busy-refuse / loud displace)
    /// and replies the [`SubscribeOutcome`] so the serving brain can wake
    /// (controller-only), stay wake-neutral (viewer), or refuse (busy). A
    /// same-`by` re-subscribe silently re-takes the controller — the gate-#7
    /// self-kick guard.
    fn dispatch_subscribe(&self, env: Envelope, send: &SharedSend) -> Result<u64, String> {
        let req: SubscribeReq = serde_json::from_value(env.payload)
            .map_err(|e| format!("bad subscribe payload: {e}"))?;
        // Clone the log Arc out from under the sessions lock, then resolve under
        // the log lock — never hold both locks at once.
        //
        // NOTE (REQ-HAZARD-RC-ATTACH-FAILFAST): a `session.try_wait()` dead-child


Add instrumentation. First the drain closure (does the ring fill?) and become_controller (ring state at attach):


Edit

        // Output drain → appends to the log (which live-sends to the subscriber).
        let log_drain = Arc::clone(&log);
        let drain = session
            .drain(move |chunk| {
                log_drain.lock().unwrap().append(chunk);
            })
            .map_err(|e| format!("drain failed: {e}"))?;

        // Output drain → appends to the log (which live-sends to the subscriber).
        let log_drain = Arc::clone(&log);
        let drain = session
            .drain(move |chunk| {
                eprintln!("DIAG_DRAIN sid={id} chunk_bytes={}", chunk.len());
                log_drain.lock().unwrap().append(chunk);
            })
            .map_err(|e| format!("drain failed: {e}"))?;


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

        // Output drain → appends to the log (which live-sends to the subscriber).
        let log_drain = Arc::clone(&log);
        let drain = session
            .drain(move |chunk| {
                log_drain.lock().unwrap().append(chunk);
            })
            .map_err(|e| format!("drain failed: {e}"))?;

        // Output drain → appends to the log (which live-sends to the subscriber).
        let log_drain = Arc::clone(&log);
        let drain = session
            .drain(move |chunk| {
                eprintln!("DIAG_DRAIN sid={id} chunk_bytes={}", chunk.len());
                log_drain.lock().unwrap().append(chunk);
            })
            .map_err(|e| format!("drain failed: {e}"))?;

+                eprintln!("DIAG_DRAIN sid={id} chunk_bytes={}", chunk.len());


    fn become_controller(&mut self, sub: SharedSend, by: Option<String>, from_seq: u64) {
        self.controller = Some(Arc::clone(&sub));
        self.controller_by = by;
        let mut last_ok: Option<u64> = None;

    fn become_controller(&mut self, sub: SharedSend, by: Option<String>, from_seq: u64) {
        eprintln!(
            "DIAG_BECOME_CTRL sid={} from_seq={from_seq} ring_len={} next_seq={} by={by:?}",
            self.session_id,
            self.ring.len(),
            self.next_seq
        );
        self.controller = Some(Arc::clone(&sub));
        self.controller_by = by;
        let mut last_ok: Option<u64> = None;


    fn become_controller(&mut self, sub: SharedSend, by: Option<String>, from_seq: u64) {
        self.controller = Some(Arc::clone(&sub));
        self.controller_by = by;
        let mut last_ok: Option<u64> = None;

    fn become_controller(&mut self, sub: SharedSend, by: Option<String>, from_seq: u64) {
        eprintln!(
            "DIAG_BECOME_CTRL sid={} from_seq={from_seq} ring_len={} next_seq={} by={by:?}",
            self.session_id,
            self.ring.len(),
            self.next_seq
        );
        self.controller = Some(Arc::clone(&sub));
        self.controller_by = by;
        let mut last_ok: Option<u64> = None;

/// to the last-successfully-written seq + 1 (D4-1: never the unsent tail, and

/// only ever forward). Stamps `driven_by` to the new controller's identity.

+            "DIAG_BECOME_CTRL sid={} from_seq={from_seq} ring_len={} next_seq={} by={by:?}",


                            intent = req_intent;
                            attached_session = Some(session_id);

                            eprintln!(
                                "DIAG_SERVE_ATTACH session_id={session_id} from_seq={from_seq} intent={req_intent:?} origin={origin_node}"
                            );
                            intent = req_intent;
                            attached_session = Some(session_id);


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

                            eprintln!(
                                "DIAG_SERVE_ATTACH session_id={session_id} from_seq={from_seq} intent={req_intent:?} origin={origin_node}"
                            );
                            intent = req_intent;
                            attached_session = Some(session_id);

brain.net_stream_send(stream_id, &[], None, true)?;

+                                "DIAG_SERVE_ATTACH session_id={session_id} from_seq={from_seq} intent={req_intent:?} origin={origin_node}"

// Role-aware subscribe (REQ-RCVIEW-1): the broker


263	            // ── Session → wire: output and exit. ────────────────────────────
264	            BrokerEvent::Output { seq, bytes, .. } if attached => {
265	                let line = ndjson::encode_line(&AttachRecord::Output {
266	                    seq,
267	                    data_b64: encode_bytes(&bytes),
268	                });
269	                // Unjournaled by design (see module docs): `seq` rides inside
270	                // the record, and the operator's render cursor is the dedup.
271	                brain.net_stream_send(stream_id, &line, None, false)?;
272	            }
273	            BrokerEvent::Exit { code, .. } if attached => {
274	                let line = ndjson::encode_line(&AttachRecord::Exit { code });
275	                brain.net_stream_send(stream_id, &line, None, true)?;
276	                if role == Some(ServeRole::Controller) {
277	                    feed_rest(endpoint, crate::resting::RestEvent::Detach);
278	                }
279	                return Ok(AttachServeOutcome::Exited(code));
280	            }
281
282	            BrokerEvent::Error { message } => {

            // ── Session → wire: output and exit. ────────────────────────────
            BrokerEvent::Output { seq, bytes, .. } if attached => {
                let line = ndjson::encode_line(&AttachRecord::Output {
                    seq,
                    data_b64: encode_bytes(&bytes),
                });
                // Unjournaled by design (see module docs): `seq` rides inside
                // the record, and the operator's render cursor is the dedup.
                brain.net_stream_send(stream_id, &line, None, false)?;
            }
            BrokerEvent::Exit { code, .. } if attached => {
                let line = ndjson::encode_line(&AttachRecord::Exit { code });
                brain.net_stream_send(stream_id, &line, None, true)?;
                if role == Some(ServeRole::Controller) {
                    feed_rest(endpoint, crate::resting::RestEvent::Detach);
                }
                return Ok(AttachServeOutcome::Exited(code));
            }

            BrokerEvent::Error { message } => {


            BrokerEvent::Output { seq, bytes, .. } if attached => {
                let line = ndjson::encode_line(&AttachRecord::Output {
                    seq,
                    data_b64: encode_bytes(&bytes),
                });
                // Unjournaled by design (see module docs): `seq` rides inside
                // the record, and the operator's render cursor is the dedup.
                brain.net_stream_send(stream_id, &line, None, false)?;
            }

            BrokerEvent::Output { seq, bytes, .. } if attached => {
                eprintln!("DIAG_SERVE_FWD seq={seq} bytes={}", bytes.len());
                let line = ndjson::encode_line(&AttachRecord::Output {
                    seq,
                    data_b64: encode_bytes(&bytes),
                });
                // Unjournaled by design (see module docs): `seq` rides inside
                // the record, and the operator's render cursor is the dedup.
                brain.net_stream_send(stream_id, &line, None, false)?;
            }


            BrokerEvent::Output { seq, bytes, .. } if attached => {
                let line = ndjson::encode_line(&AttachRecord::Output {
                    seq,
                    data_b64: encode_bytes(&bytes),
                });
                // Unjournaled by design (see module docs): `seq` rides inside
                // the record, and the operator's render cursor is the dedup.
                brain.net_stream_send(stream_id, &line, None, false)?;
            }

            BrokerEvent::Output { seq, bytes, .. } if attached => {
                eprintln!("DIAG_SERVE_FWD seq={seq} bytes={}", bytes.len());
                let line = ndjson::encode_line(&AttachRecord::Output {
                    seq,
                    data_b64: encode_bytes(&bytes),
                });
                // Unjournaled by design (see module docs): `seq` rides inside
                // the record, and the operator's render cursor is the dedup.
                brain.net_stream_send(stream_id, &line, None, false)?;
            }

// ── Session → wire: output and exit. ────────────────────────────

+                eprintln!("DIAG_SERVE_FWD seq={seq} bytes={}", bytes.len());


Diagnostics write to the daemon's stderr (brain_log), which the test only prints on the `online` assert failure. Add brain_stderr to the always-printed diagnostic so I can see the DIAG_* lines:


    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>(),
    );

    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{}\n=== brain/broker stderr ===\n{brain_stderr}",
        run.status.success(),
        rc_captured.len(),
        String::from_utf8_lossy(&run.stderr),
        rc_captured.chars().take(400).collect::<String>(),
    );


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

    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>(),
    );

    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{}\n=== brain/broker stderr ===\n{brain_stderr}",
        run.status.success(),
        rc_captured.len(),
        String::from_utf8_lossy(&run.stderr),
        rc_captured.chars().take(400).collect::<String>(),
    );

+         \n=== rc stdout (first 400) ===\n{}\n=== brain/broker stderr ===\n{brain_stderr}",


Build mock-session + run the fixture in the seam target dir, serial, nocapture:


Bash

CARGO_TARGET_DIR=target-seam cargo build -p mock-adapter --bin mock-session 2>&1 | tail -5

Build mock-session into seam target dir


Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.21s


Now run the fixture (rebuilds spt-daemon with instrumentation):


CARGO_TARGET_DIR=target-seam cargo test -p spt --test dummy_harness_e2e -- --nocapture --test-threads=1 2>&1 | tail -80

Run dummy harness fixture with instrumentation


   Compiling spt-daemon v0.12.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling spt v0.12.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 11.09s
     Running tests\dummy_harness_e2e.rs (target-seam\debug\deps\dummy_harness_e2e-9c610c804600b6fe.exe)

running 1 test
test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=true harness_pid=Some(208292) alive_after=true rc_connected=true rc_saw_tick=false rc_stdout_bytes=0 ===
=== endpoint run stderr ===
ENDPOINT_RUN:dummy7 adapter=dummyharness pid=Some(208292) session=f555405c778af8a2 (harness binds its perch on startup)
ENDPOINT_RUN_STARTED:dummy7 (attach with `spt rc dummy7`)

=== rc stderr ===
PUMP_IPC_READER: spawned

=== rc stdout (first 400) ===

=== brain/broker stderr ===
BRAIN_UP: connected to broker spt-daemon-broker-c30a980b9c95944c.sock (pid 25624, generation 0, start-reason cold)
DIAG_BECOME_CTRL sid=1 from_seq=0 ring_len=0 next_seq=0 by=None
DIAG_DRAIN sid=1 chunk_bytes=16
DIAG_DRAIN sid=1 chunk_bytes=110
DIAG_DRAIN sid=1 chunk_bytes=47
DIAG_DRAIN sid=1 chunk_bytes=28
DIAG_DRAIN sid=1 chunk_bytes=29
DIAG_DRAIN sid=1 chunk_bytes=29
BRAIN_NET_CONSUMERS_UP: net enabled (node 45421a4bbff630a8a40f787f353ab62bf7e5452a82931f3b89680432cb243994) — dispatcher + peer pump started
PUMP_IPC_READER: spawned
DIAG_DRAIN sid=1 chunk_bytes=29
DIAG_DRAIN sid=1 chunk_bytes=29
DIAG_DRAIN sid=1 chunk_bytes=29
DIAG_SERVE_ATTACH session_id=1 from_seq=0 intent=Control origin=45421a4bbff630a8a40f787f353ab62bf7e5452a82931f3b89680432cb243994
DIAG_BECOME_CTRL sid=1 from_seq=0 ring_len=9 next_seq=9 by=Some("45421a4bbff630a8a40f787f353ab62bf7e5452a82931f3b89680432cb243994")
DIAG_SERVE_FWD seq=0 bytes=16
DIAG_SERVE_FWD seq=1 bytes=110
DIAG_DRAIN sid=1 chunk_bytes=29

WAVE2-LEAD: rc attached but received 0 bytes, no DUMMY_HARNESS_TICK — endpoint-run harness PTY output not reaching the attach (root-cause pending).
ok

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


Decisive data. Reading the broker stderr:

- `DIAG_DRAIN ... chunk_bytes=16/110/47/28/29...` → **drain IS appending; ring fills.** Candidates (a)/(b)/(c) RULED OUT — dummy stdout reaches the broker drain.
- `DIAG_SERVE_ATTACH session_id=1 from_seq=0` → rc's serve_attach attaches to the RIGHT session.
- `DIAG_BECOME_CTRL ... ring_len=9` → 9 chunks buffered, replay from seq 0 should fire.
- `DIAG_SERVE_FWD seq=0 bytes=16` + `seq=1 bytes=110` → serve forwarded **only 2** of 9, then STOPPED. And **rc received 0 bytes**.

So: not a drain bug. Either serve stalls after 2 forwards, or the loopback stream → rc subscription drops everything. Need rc-side + loopback-layer data. Check `attach_as` (does the replay write block?) and add rc-pump instrumentation.


Grep

fn attach_as|fn net_stream_send|fn net_stream_subscribe|fn net_dial_loopback


crates\spt-daemon\src\brain.rs:768:    pub fn attach_as(
crates\spt-daemon\src\brain.rs:897:    pub fn net_dial_loopback(&mut self) -> io::Result<NetDialed> {
crates\spt-daemon\src\brain.rs:939:    pub fn net_stream_send(
crates\spt-daemon\src\brain.rs:1058:    pub fn net_stream_subscribe(&mut self, stream_id: u64, from_seq: u64) -> io::Result<()> {


768	    pub fn attach_as(
769	        &mut self,
770	        session_id: u64,
771	        from_seq: u64,
772	        intent: AttachIntent,
773	        by: Option<&str>,
774	    ) -> io::Result<()> {
775	        self.session_id = Some(session_id);
776	        self.next_seq = from_seq;
777	        self.subscribe_with(session_id, from_seq, intent, by.map(str::to_string))
778	    }
779
780	    /// Cold-start multi-session resume (restoration D4-2, ADR-0018 Q6): query the
781	    /// broker for **every** hosted session and re-attach **each** in resume mode
782	    /// from the broker's per-session delivered cursor (`resume_seq`). This is the
783	    /// production replacement for the retired single-session `BrainState` handoff
784	    /// frame — a brain the supervisor respawns (crash *or* update) reconstructs
785	    /// all session continuity by querying the persistent side, never a brain→brain
786	    /// message. Returns the ids re-attached (empty when the broker hosts none —
787	    /// the supervised daemon brain's no-op-today case).
788	    ///
789	    /// Each session is seeded into [`Self::session_cursors`] at its `resume_seq`,
790	    /// so [`Brain::read_event`] dedups the broker's at-least-once boundary re-send
791	    /// and accepts a post-eviction ring-floor jump (no reject-gap). Output is
792	    /// at-least-once; input/effects stay exactly-once via the broker-owned
793	    /// `EffectJournal`. The generation is **not** touched — this is a cold start
794	    /// (generation custody is the broker's, ADR-0018 V2), not a `handoff`.
795	    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]
796	    // [impl->REQ-DAEMON-2]
797	    pub fn resume_sessions(&mut self) -> io::Result<Vec<u64>> {
798	        let reply = self.sessions()?;
799	        let mut resumed = Vec::with_capacity(reply.sessions.len());
800	        for info in reply.sessions {
801	            // Seed the dedup cursor at the broker's resume point BEFORE the
802	            // subscribe replay starts arriving, then re-attach from there.
803	            self.session_cursors
804	                .insert(info.session_id, info.resume_seq);
805	            self.subscribe(info.session_id, info.resume_seq)?;
806	            resumed.push(info.session_id);
807	        }
808	        Ok(resumed)
809	    }
810
811	    /// The per-session resume cursor (the next seq still needed for a session
812	    /// re-attached by [`Brain::resume_sessions`]) — observability / tests.
813	    pub fn session_cursor(&self, session_id: u64) -> Option<u64> {
814	        self.session_cursors.get(&session_id).copied()
815	    }
816
817
818	    /// Query the broker-owned net endpoint's status (D4a): node id, dialable
819	    /// address, conn count — or `enabled: false` on a net-less broker. Reads
820	    /// until the reply (consuming interleaved events like [`Brain::spawn_session`]).
821	    // [impl->REQ-DAEMON-2]
822	    pub fn net_status(&mut self) -> io::Result<NetStatusReply> {
823	        self.send(KIND_NET_STATUS, serde_json::Value::Null)?;
824	        loop {
825	            match self.read_event()? {
826	                BrokerEvent::NetStatus(s) => return Ok(s),
827	                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
828	                _ => continue,
829	            }
830	        }
831	    }
832
833	    /// Run the joiner leg of the subnet pairing ceremony on the broker (M7
834	    /// D3, REQ-SUBNET-2): the broker owns the node-identity endpoint the
835	    /// ceremony binds and pins. Blocks for the ceremony's bounded life; the
836	    /// reply classifies failures for the CLI's guided UX. Not journaled —
837	    /// the code must never persist, and a re-join is store-idempotent.
838	    // [impl->REQ-SUBNET-2]
839	    pub fn pair_join(
840	        &mut self,
841	        subnet: &str,
842	        code: &str,
843	        responder_addr: Option<serde_json::Value>,
844	    ) -> io::Result<PairJoinReply> {
845	        self.send(
846	            KIND_PAIR_JOIN,
847	            serde_json::to_value(PairJoinReq {
848	                subnet: subnet.to_string(),
849	                code: code.to_string(),
850	                responder_addr,
851	            })
852	            .expect("PairJoinReq serializes"),
853	        )?;
854	        loop {
855	            match self.read_event()? {
856	                BrokerEvent::PairJoined(r) => return Ok(r),
857	                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
858	                _ => continue,
859	            }
860	        }
861	    }
862
863	    /// Dial a peer over the broker-owned endpoint (D4a). `addr` is the peer's
864	    /// serialized `EndpointAddr` (as another node's `net-status` reply carries).
865	    /// With `op_id` the dial is exactly-once: a replay of the same durable id
866	    /// after a brain crash is deduped at the broker and answered with the
867	    /// already-open connection ([`NetDialed::applied_now`] distinguishes).
868	    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]
869	    pub fn net_dial(
870	        &mut self,
871	        addr: serde_json::Value,
872	        op_id: Option<u64>,
873	    ) -> io::Result<NetDialed> {
874	        self.send(
875	            KIND_NET_DIAL,
876	            serde_json::to_value(NetDialReq { addr, op_id }).expect("NetDialReq serializes"),
877	        )?;
878	        let deadline = self.call_deadline();
879	        loop {
880	            match self.read_event_until(deadline)? {
881	                BrokerEvent::NetDialed(d) => return Ok(d),
882	                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
883	                _ => continue,
884	            }
885	        }
886	    }
887
888	    /// Mint (or reuse) the broker's single in-process loopback connection
889	    /// (M12 W1.5, B1) — the transport a LOCAL `spt rc` rides, because a QUIC
890	    /// self-dial is refused by iroh ("Connecting to ourself is not supported").
891	    /// The returned [`NetDialed::remote_id_hex`] is THIS node's own id, so a
892	    /// stream served over it gates same-node (access short-circuit Allow). No
893	    /// op id: the singleton is idempotent by reuse, and there is no network leg
894	    /// a crash-replay could double. Identical from `request_attach` up — the
895	    /// one-pump invariant lives at the transport leaf, not here.
896	    // [impl->REQ-RC-1]
897	    pub fn net_dial_loopback(&mut self) -> io::Result<NetDialed> {

    pub fn attach_as(
        &mut self,
        session_id: u64,
        from_seq: u64,
        intent: AttachIntent,
        by: Option<&str>,
    ) -> io::Result<()> {
        self.session_id = Some(session_id);
        self.next_seq = from_seq;
        self.subscribe_with(session_id, from_seq, intent, by.map(str::to_string))
    }

    /// Cold-start multi-session resume (restoration D4-2, ADR-0018 Q6): query the
    /// broker for **every** hosted session and re-attach **each** in resume mode
    /// from the broker's per-session delivered cursor (`resume_seq`). This is the
    /// production replacement for the retired single-session `BrainState` handoff
    /// frame — a brain the supervisor respawns (crash *or* update) reconstructs
    /// all session continuity by querying the persistent side, never a brain→brain
    /// message. Returns the ids re-attached (empty when the broker hosts none —
    /// the supervised daemon brain's no-op-today case).
    ///
    /// Each session is seeded into [`Self::session_cursors`] at its `resume_seq`,
    /// so [`Brain::read_event`] dedups the broker's at-least-once boundary re-send
    /// and accepts a post-eviction ring-floor jump (no reject-gap). Output is
    /// at-least-once; input/effects stay exactly-once via the broker-owned
    /// `EffectJournal`. The generation is **not** touched — this is a cold start
    /// (generation custody is the broker's, ADR-0018 V2), not a `handoff`.
    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]
    // [impl->REQ-DAEMON-2]
    pub fn resume_sessions(&mut self) -> io::Result<Vec<u64>> {
        let reply = self.sessions()?;
        let mut resumed = Vec::with_capacity(reply.sessions.len());
        for info in reply.sessions {
            // Seed the dedup cursor at the broker's resume point BEFORE the
            // subscribe replay starts arriving, then re-attach from there.
            self.session_cursors
                .insert(info.session_id, info.resume_seq);
            self.subscribe(info.session_id, info.resume_seq)?;
            resumed.push(info.session_id);
        }
        Ok(resumed)
    }

    /// The per-session resume cursor (the next seq still needed for a session
    /// re-attached by [`Brain::resume_sessions`]) — observability / tests.
    pub fn session_cursor(&self, session_id: u64) -> Option<u64> {
        self.session_cursors.get(&session_id).copied()
    }

    /// Query the broker-owned net endpoint's status (D4a): node id, dialable
    /// address, conn count — or `enabled: false` on a net-less broker. Reads
    /// until the reply (consuming interleaved events like [`Brain::spawn_session`]).
    // [impl->REQ-DAEMON-2]
    pub fn net_status(&mut self) -> io::Result<NetStatusReply> {
        self.send(KIND_NET_STATUS, serde_json::Value::Null)?;
        loop {
            match self.read_event()? {
                BrokerEvent::NetStatus(s) => return Ok(s),
                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
                _ => continue,
            }
        }
    }

    /// Run the joiner leg of the subnet pairing ceremony on the broker (M7
    /// D3, REQ-SUBNET-2): the broker owns the node-identity endpoint the
    /// ceremony binds and pins. Blocks for the ceremony's bounded life; the
    /// reply classifies failures for the CLI's guided UX. Not journaled —
    /// the code must never persist, and a re-join is store-idempotent.
    // [impl->REQ-SUBNET-2]
    pub fn pair_join(
        &mut self,
        subnet: &str,
        code: &str,
        responder_addr: Option<serde_json::Value>,
    ) -> io::Result<PairJoinReply> {
        self.send(
            KIND_PAIR_JOIN,
            serde_json::to_value(PairJoinReq {
                subnet: subnet.to_string(),
                code: code.to_string(),
                responder_addr,
            })
            .expect("PairJoinReq serializes"),
        )?;
        loop {
            match self.read_event()? {
                BrokerEvent::PairJoined(r) => return Ok(r),
                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
                _ => continue,
            }
        }
    }

    /// Dial a peer over the broker-owned endpoint (D4a). `addr` is the peer's
    /// serialized `EndpointAddr` (as another node's `net-status` reply carries).
    /// With `op_id` the dial is exactly-once: a replay of the same durable id
    /// after a brain crash is deduped at the broker and answered with the
    /// already-open connection ([`NetDialed::applied_now`] distinguishes).
    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]
    pub fn net_dial(
        &mut self,
        addr: serde_json::Value,
        op_id: Option<u64>,
    ) -> io::Result<NetDialed> {
        self.send(
            KIND_NET_DIAL,
            serde_json::to_value(NetDialReq { addr, op_id }).expect("NetDialReq serializes"),
        )?;
        let deadline = self.call_deadline();
        loop {
            match self.read_event_until(deadline)? {
                BrokerEvent::NetDialed(d) => return Ok(d),
                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
                _ => continue,
            }
        }
    }

    /// Mint (or reuse) the broker's single in-process loopback connection
    /// (M12 W1.5, B1) — the transport a LOCAL `spt rc` rides, because a QUIC
    /// self-dial is refused by iroh ("Connecting to ourself is not supported").
    /// The returned [`NetDialed::remote_id_hex`] is THIS node's own id, so a
    /// stream served over it gates same-node (access short-circuit Allow). No
    /// op id: the singleton is idempotent by reuse, and there is no network leg
    /// a crash-replay could double. Identical from `request_attach` up — the
    /// one-pump invariant lives at the transport leaf, not here.
    // [impl->REQ-RC-1]
    pub fn net_dial_loopback(&mut self) -> io::Result<NetDialed> {


`attach_as` → `subscribe_with`. Critical question: does `subscribe_with` block reading until the `Subscribed` reply (consuming+discarding the replay Output frames that arrive first, like the spawn_session_pid hazard at brain.rs:393)? Check it.


crates\spt-daemon\src\brain.rs:1099:    fn subscribe(&mut self, session_id: u64, from_seq: u64) -> io::Result<()> {
crates\spt-daemon\src\brain.rs:1119:    fn subscribe_with(
crates\spt-daemon\src\digesthub.rs:144:    fn subscribe(&self, id: &str) -> (Digest, u64, Receiver<DigestUpdate>) {


1099	    fn subscribe(&mut self, session_id: u64, from_seq: u64) -> io::Result<()> {
1100	        self.subscribe_with(session_id, from_seq, AttachIntent::Control, None)
1101	    }
1102
1103	    /// Release this connection's controller/viewer role on `session_id`
1104	    /// (REQ-RCVIEW-1) — the explicit detach a `serve_attach` worker sends when
1105	    /// the viewport ends so the broker clears `driven_by` / `viewer_count`
1106	    /// promptly. Best-effort: a dropped connection clears the role anyway.
1107	    pub fn detach_session(&mut self, session_id: u64) -> io::Result<()> {
1108	        self.send(
1109	            KIND_UNSUBSCRIBE,
1110	            serde_json::to_value(UnsubscribeReq { session_id })
1111	                .expect("UnsubscribeReq serializes"),
1112	        )
1113	    }
1114
1115	    /// The role-aware subscribe (REQ-RCVIEW-1): `intent` selects viewer/control/
1116	    /// take, `by` carries the operator identity (`Some(node)` remote, `None`
1117	    /// local). The broker replies the resolved [`SubscribeOutcome`] as a
1118	    /// [`BrokerEvent::Subscribed`].
1119	    fn subscribe_with(
1120	        &mut self,
1121	        session_id: u64,
1122	        from_seq: u64,
1123	        intent: AttachIntent,
1124	        by: Option<String>,
1125	    ) -> io::Result<()> {
1126	        self.send(
1127	            KIND_SUBSCRIBE,
1128	            serde_json::to_value(SubscribeReq {
1129	                session_id,
1130	                from_seq,
1131	                intent,
1132	                by,
1133	            })
1134	            .expect("SubscribeReq serializes"),
1135	        )
1136	    }
1137
1138	    fn send(&mut self, kind: &str, payload: serde_json::Value) -> io::Result<()> {
1139	        let env = Envelope::new(kind, payload);
1140	        match &mut self.conn {
1141	            BrainConn::Whole(stream) => write_frame(stream, &env),
1142	            BrainConn::Split { send, .. } => write_frame(send, &env),
1143	        }
1144	    }
1145
1146	    /// Read one framed [`Envelope`], bounded by an optional wall-clock
1147	    /// `deadline`. The carrier dispatch (REQ-HAZARD-PUMP-IPC-DEADLINE):
1148	    ///
1149	    /// - [`BrainConn::Whole`] — the ordinary blocking client: a direct
1150	    ///   [`read_frame`]; `deadline` is irrelevant (a whole brain never sets
1151	    ///   `io_timeout`, so callers always pass `None` here).
1152	    /// - [`BrainConn::Split`] — the pump carrier: the reader thread already owns
1153	    ///   the blocking read, so a bounded read is just [`Receiver::recv_timeout`]
1154	    ///   on the remaining budget. A `Timeout` surfaces [`io::ErrorKind::TimedOut`]
1155	    ///   — the poison signal the pump escalates to a supervised restart (NEVER a
1156	    ///   per-peer retry). A `Disconnected` channel means the reader thread exited
1157	    ///   (the conn hung up); surface it as `UnexpectedEof`.
1158	    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]
1159	    fn read_frame_until(&mut self, deadline: Option<Instant>) -> io::Result<Envelope> {
1160	        match &mut self.conn {
1161	            BrainConn::Whole(stream) => read_frame(stream),
1162	            BrainConn::Split { frames, .. } => match deadline {
1163	                Some(dl) => {
1164	                    // Total-wait bound: each recv is the REMAINING budget to the
1165	                    // call's entry deadline, so unrelated frames dripping in
1166	                    // cannot reset it (the per-read-timeout wedge the ruling
1167	                    // forbids). A non-positive remaining → recv_timeout(ZERO),
1168	                    // which still drains an already-queued frame before timing out.
1169	                    let remaining = dl.saturating_duration_since(Instant::now());
1170	                    match frames.recv_timeout(remaining) {
1171	                        Ok(framed) => framed,
1172	                        Err(RecvTimeoutError::Timeout) => Err(io::Error::new(
1173	                            io::ErrorKind::TimedOut,
1174	                            "brain IPC read deadline elapsed",
1175	                        )),
1176	                        Err(RecvTimeoutError::Disconnected) => Err(io::Error::new(
1177	                            io::ErrorKind::UnexpectedEof,
1178	                            "brain IPC reader thread ended (conn closed)",
1179	                        )),
1180	                    }
1181	                }
1182	                None => frames.recv().map_err(|_| {
1183	                    io::Error::new(
1184	                        io::ErrorKind::UnexpectedEof,
1185	                        "brain IPC reader thread ended (conn closed)",
1186	                    )
1187	                })?,
1188	            },

    fn subscribe(&mut self, session_id: u64, from_seq: u64) -> io::Result<()> {
        self.subscribe_with(session_id, from_seq, AttachIntent::Control, None)
    }

    /// Release this connection's controller/viewer role on `session_id`
    /// (REQ-RCVIEW-1) — the explicit detach a `serve_attach` worker sends when
    /// the viewport ends so the broker clears `driven_by` / `viewer_count`
    /// promptly. Best-effort: a dropped connection clears the role anyway.
    pub fn detach_session(&mut self, session_id: u64) -> io::Result<()> {
        self.send(
            KIND_UNSUBSCRIBE,
            serde_json::to_value(UnsubscribeReq { session_id })
                .expect("UnsubscribeReq serializes"),
        )
    }

    /// The role-aware subscribe (REQ-RCVIEW-1): `intent` selects viewer/control/
    /// take, `by` carries the operator identity (`Some(node)` remote, `None`
    /// local). The broker replies the resolved [`SubscribeOutcome`] as a
    /// [`BrokerEvent::Subscribed`].
    fn subscribe_with(
        &mut self,
        session_id: u64,
        from_seq: u64,
        intent: AttachIntent,
        by: Option<String>,
    ) -> io::Result<()> {
        self.send(
            KIND_SUBSCRIBE,
            serde_json::to_value(SubscribeReq {
                session_id,
                from_seq,
                intent,
                by,
            })
            .expect("SubscribeReq serializes"),
        )
    }

    fn send(&mut self, kind: &str, payload: serde_json::Value) -> io::Result<()> {
        let env = Envelope::new(kind, payload);
        match &mut self.conn {
            BrainConn::Whole(stream) => write_frame(stream, &env),
            BrainConn::Split { send, .. } => write_frame(send, &env),
        }
    }

    /// Read one framed [`Envelope`], bounded by an optional wall-clock
    /// `deadline`. The carrier dispatch (REQ-HAZARD-PUMP-IPC-DEADLINE):
    ///
    /// - [`BrainConn::Whole`] — the ordinary blocking client: a direct
    ///   [`read_frame`]; `deadline` is irrelevant (a whole brain never sets
    ///   `io_timeout`, so callers always pass `None` here).
    /// - [`BrainConn::Split`] — the pump carrier: the reader thread already owns
    ///   the blocking read, so a bounded read is just [`Receiver::recv_timeout`]
    ///   on the remaining budget. A `Timeout` surfaces [`io::ErrorKind::TimedOut`]
    ///   — the poison signal the pump escalates to a supervised restart (NEVER a
    ///   per-peer retry). A `Disconnected` channel means the reader thread exited
    ///   (the conn hung up); surface it as `UnexpectedEof`.
    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]
    fn read_frame_until(&mut self, deadline: Option<Instant>) -> io::Result<Envelope> {
        match &mut self.conn {
            BrainConn::Whole(stream) => read_frame(stream),
            BrainConn::Split { frames, .. } => match deadline {
                Some(dl) => {
                    // Total-wait bound: each recv is the REMAINING budget to the
                    // call's entry deadline, so unrelated frames dripping in
                    // cannot reset it (the per-read-timeout wedge the ruling
                    // forbids). A non-positive remaining → recv_timeout(ZERO),
                    // which still drains an already-queued frame before timing out.
                    let remaining = dl.saturating_duration_since(Instant::now());
                    match frames.recv_timeout(remaining) {
                        Ok(framed) => framed,
                        Err(RecvTimeoutError::Timeout) => Err(io::Error::new(
                            io::ErrorKind::TimedOut,
                            "brain IPC read deadline elapsed",
                        )),
                        Err(RecvTimeoutError::Disconnected) => Err(io::Error::new(
                            io::ErrorKind::UnexpectedEof,
                            "brain IPC reader thread ended (conn closed)",
                        )),
                    }
                }
                None => frames.recv().map_err(|_| {
                    io::Error::new(
                        io::ErrorKind::UnexpectedEof,
                        "brain IPC reader thread ended (conn closed)",
                    )
                })?,
            },


`subscribe_with` is fire-and-forget (no read loop) — rules out the spawn_session_pid-style discard. So serve_attach read & forwarded seq 0,1 then stopped at 2 of 9, AND rc got 0 bytes despite 2 forwards. This implicates the **loopback stream serve→rc delivery**. Add rc-pump instrumentation + read the nethost loopback stream send/subscribe path.


Edit

        seen_any = true; // any broker event proves the attach stream is live
        match ev {
            BrokerEvent::NetStreamData { stream_id: sid, bytes, .. } if sid == stream_id => {

        seen_any = true; // any broker event proves the attach stream is live
        eprintln!("DIAG_RC_EV {ev:?}");
        match ev {
            BrokerEvent::NetStreamData { stream_id: sid, bytes, .. } if sid == stream_id => {


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

        seen_any = true; // any broker event proves the attach stream is live
        match ev {
            BrokerEvent::NetStreamData { stream_id: sid, bytes, .. } if sid == stream_id => {

        seen_any = true; // any broker event proves the attach stream is live
        eprintln!("DIAG_RC_EV {ev:?}");
        match ev {
            BrokerEvent::NetStreamData { stream_id: sid, bytes, .. } if sid == stream_id => {

+        eprintln!("DIAG_RC_EV {ev:?}");


109:/// established BY CONSTRUCTION. A loopback conn can ONLY be minted broker-side
110:/// via [`NetHost::dial_loopback`]; it can never arise from an inbound handshake
114:/// supported"), so local attach rides a broker loopback conn instead.
119:    /// A broker-minted in-process loopback (no iroh handle, no handshake). Its
125:/// One live connection the broker holds (QUIC or loopback).
130:    /// handshake; for loopback, THIS node's own id (the broker-minted identity).
148:struct StreamLog {
341:/// made structural: a loopback attach and a cross-node attach diverge at this
371:/// QUIC reads via quinn's chunk API; loopback via the read end of the duplex.
379:/// One live stream the broker holds (QUIC or loopback): the send half (brain
381:struct StreamEntry {
523:    // (QUIC chunk API vs the loopback duplex's `AsyncRead`); both append to the
529:    // QUIC stream flow-control (or the loopback duplex's bounded buffer) lags the
605:    /// The broker's single in-process loopback connection (M12 W1.5, B1),
606:    /// minted lazily on the first [`Self::dial_loopback`] and reused thereafter
609:    loopback_conn: Mutex<Option<u64>>,
610:    /// Serializes [`Self::open_loopback_pair`] (M11-W3) so the peer-row discovery
611:    /// (a before/after diff on the loopback conn's inbound rows) sees exactly the
612:    /// one row this open minted — concurrent pair-opens on the shared loopback conn
614:    loopback_pair_lock: Mutex<()>,
635:        // Hermeticity guard (REQ-PAIR-8): a loopback-scoped endpoint is the
746:            loopback_conn: Mutex::new(None),
747:            loopback_pair_lock: Mutex::new(()),
943:                // A loopback conn has no path to observe — nothing to seed.
958:    /// Mint (or reuse) the broker's single in-process loopback connection
968:    pub fn dial_loopback(&self) -> (u64, String) {
970:        let mut slot = self.loopback_conn.lock().unwrap();
1175:    /// candidate set [`Self::open_loopback_pair`] diffs to find the peer it just
1188:    /// Open a same-node loopback tunnel stream PAIR (M11-W3, REQ-SHELL-4) and
1190:    /// drives and the cross-wired peer row the shell side drives. The loopback
1195:    pub fn open_loopback_pair(&self) -> io::Result<(u64, u64)> {
1196:        let _guard = self.loopback_pair_lock.lock().unwrap();
1197:        let (conn_id, _) = self.dial_loopback();
1205:            .ok_or_else(|| io::Error::other("loopback peer row not registered"))?;
1234:    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {
1295:    // over hermetic loopback QUIC; both sides register the connection — the
1298:    fn two_hosts_dial_over_loopback() {
1327:    // [unit->REQ-RC-1] the broker loopback conn (M12 W1.5, B1): `dial_loopback`
1333:    fn loopback_conn_is_a_reused_singleton_that_cross_wires_a_stream_pair() {
1338:        let (c1, hex1) = host.dial_loopback();
1339:        let (c2, hex2) = host.dial_loopback();
1340:        assert_eq!(c1, c2, "the loopback conn is a reused singleton");
1344:            "loopback origin is THIS node's own id (broker-minted)"
1347:        assert_eq!(host.conn_count(), 1, "one loopback conn held");
1350:        let op_stream = host.open_stream(c1).expect("open loopback stream");
1420:    // [unit->REQ-SHELL-4] the loopback tunnel pair under BACKPRESSURE (M11-W3,
1426:    fn loopback_tunnel_backpressures_without_loss() {
1430:        let (owner, shell) = host.open_loopback_pair().expect("open tunnel pair");


148	struct StreamLog {
149	    stream_id: u64,
150	    next_seq: u64,
151	    ring: VecDeque<(u64, Vec<u8>)>,
152	    cap_chunks: usize,
153	    subscriber: Option<SharedSend>,
154	    /// Read side finished cleanly (peer called finish / conn ended).
155	    finished: bool,
156	    /// **Retentive backpressure cap** (M11-W3, REQ-SHELL-4), in chunks; `0` ⇒ not
157	    /// retentive (ordinary bounded-overwrite stream). A shell-tunnel stream must
158	    /// NOT drop bytes — CONTEXT:262 "tunneled protocols cannot drop frames, so
159	    /// congestion surfaces as lag, never loss." So a retentive log never cap-evicts
160	    /// on append (no loss), and the read pump REFUSES to pull the next chunk once
161	    /// the undrained ring reaches this cap (no unbounded growth ⇒ no OOM) — the
162	    /// transport's native flow-control then lags the *sender*. A chunk leaves the
163	    /// ring ONLY when [`drain`](Self::drain) (the recv cursor) pops it; the pump is
164	    /// woken to resume via the entry's `room` notifier. Ordinary streams keep
165	    /// `retentive_cap = 0` (a slow/absent brain must not OOM the broker via an
166	    /// unbounded ring) and are drained by subscribe, never the sync cursor.
167	    retentive_cap: usize,
168	}
169
170	impl StreamLog {
171	    fn new(stream_id: u64, cap_chunks: usize) -> Self {
172	        StreamLog {
173	            stream_id,
174	            next_seq: 0,
175	            ring: VecDeque::new(),
176	            cap_chunks,
177	            subscriber: None,
178	            finished: false,
179	            retentive_cap: 0,
180	        }
181	    }
182
183	    /// Mark this log retentive with backpressure `cap` (chunks). Idempotent set.
184	    fn set_retentive(&mut self, cap: usize) {
185	        self.retentive_cap = cap;
186	    }
187
188	    /// Append a freshly-read chunk: assign the next `seq`, retain it in the ring,
189	    /// live-send to the current subscriber. Read-pump only. A non-retentive log
190	    /// cap-evicts the oldest chunks past `cap_chunks`; a retentive (tunnel) log
191	    /// never evicts — bytes leave only via [`drain`](Self::drain).
192	    fn append(&mut self, bytes: &[u8]) {
193	        let seq = self.next_seq;
194	        self.next_seq += 1;
195	        self.ring.push_back((seq, bytes.to_vec()));
196	        if self.retentive_cap == 0 {
197	            while self.ring.len() > self.cap_chunks {
198	                self.ring.pop_front();
199	            }
200	        }
201	        if let Some(sub) = &self.subscriber {
202	            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);
203	            if let Ok(mut s) = sub.lock() {
204	                let _ = write_frame(&mut *s, &frame);
205	            }
206	        }
207	    }
208
209	    /// Drain every undrained chunk in order (the shell-tunnel recv cursor, W3).
210	    /// Pops the whole ring front-to-back (FIFO by `seq`) and concatenates the
211	    /// bytes — chunk boundaries are arbitrary read-pump splits, irrelevant to an
212	    /// opaque byte stream. Lossless + ordered: a byte leaves the ring only here,
213	    /// once. Returns `(bytes, finished)`; `finished` reflects a clean/torn end so
214	    /// the poller sees EOF and closes. Empty bytes with `finished=false` ⇒ nothing
215	    /// pending yet.
216	    fn drain(&mut self) -> (Vec<u8>, bool) {
217	        let mut out = Vec::new();
218	        while let Some((_, chunk)) = self.ring.pop_front() {
219	            out.extend_from_slice(&chunk);
220	        }
221	        (out, self.finished)
222	    }
223
224	    /// Mark the read side finished and tell the subscriber (with the final
225	    /// cursor, so it can verify completeness).
226	    fn finish(&mut self) {
227	        self.finished = true;
228	        if let Some(sub) = &self.subscriber {
229	            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);
230	            if let Ok(mut s) = sub.lock() {
231	                let _ = write_frame(&mut *s, &frame);
232	            }
233	        }
234	    }
235
236	    /// Attach `sub` as the subscriber, replaying every retained chunk with
237	    /// `seq >= from_seq` (then eof, if already finished) before live streaming.
238	    fn attach(&mut self, sub: SharedSend, from_seq: u64) {
239	        self.subscriber = Some(Arc::clone(&sub));
240	        for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {
241	            let frame = net_stream_data_envelope(self.stream_id, *seq, bytes);
242	            if let Ok(mut s) = sub.lock() {
243	                let _ = write_frame(&mut *s, &frame);
244	            }
245	        }
246	        if self.finished {
247	            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);
248	            if let Ok(mut s) = sub.lock() {
249	                let _ = write_frame(&mut *s, &frame);
250	            }
251	        }
252	    }
253
254	    /// Detach `sub` iff it is the current subscriber (a late disconnect must
255	    /// not unsubscribe a newer brain).
256	    fn detach_if(&mut self, sub: &SharedSend) {
257	        if let Some(cur) = &self.subscriber {
258	            if Arc::ptr_eq(cur, sub) {
259	                self.subscriber = None;
260	            }
261	        }
262	    }
263	}
264
265	/// A bounded, sequenced log of connection liveness events — the broker-owned
266	/// PresenceChannel seam (D4c, REQ-EP-4): the StreamLog ring/cursor discipline
267	/// applied to the conn table itself. `register_conn` appends `connected`; each

struct StreamLog {
    stream_id: u64,
    next_seq: u64,
    ring: VecDeque<(u64, Vec<u8>)>,
    cap_chunks: usize,
    subscriber: Option<SharedSend>,
    /// Read side finished cleanly (peer called finish / conn ended).
    finished: bool,
    /// **Retentive backpressure cap** (M11-W3, REQ-SHELL-4), in chunks; `0` ⇒ not
    /// retentive (ordinary bounded-overwrite stream). A shell-tunnel stream must
    /// NOT drop bytes — CONTEXT:262 "tunneled protocols cannot drop frames, so
    /// congestion surfaces as lag, never loss." So a retentive log never cap-evicts
    /// on append (no loss), and the read pump REFUSES to pull the next chunk once
    /// the undrained ring reaches this cap (no unbounded growth ⇒ no OOM) — the
    /// transport's native flow-control then lags the *sender*. A chunk leaves the
    /// ring ONLY when [`drain`](Self::drain) (the recv cursor) pops it; the pump is
    /// woken to resume via the entry's `room` notifier. Ordinary streams keep
    /// `retentive_cap = 0` (a slow/absent brain must not OOM the broker via an
    /// unbounded ring) and are drained by subscribe, never the sync cursor.
    retentive_cap: usize,
}

impl StreamLog {
    fn new(stream_id: u64, cap_chunks: usize) -> Self {
        StreamLog {
            stream_id,
            next_seq: 0,
            ring: VecDeque::new(),
            cap_chunks,
            subscriber: None,
            finished: false,
            retentive_cap: 0,
        }
    }

    /// Mark this log retentive with backpressure `cap` (chunks). Idempotent set.
    fn set_retentive(&mut self, cap: usize) {
        self.retentive_cap = cap;
    }

    /// Append a freshly-read chunk: assign the next `seq`, retain it in the ring,
    /// live-send to the current subscriber. Read-pump only. A non-retentive log
    /// cap-evicts the oldest chunks past `cap_chunks`; a retentive (tunnel) log
    /// never evicts — bytes leave only via [`drain`](Self::drain).
    fn append(&mut self, bytes: &[u8]) {
        let seq = self.next_seq;
        self.next_seq += 1;
        self.ring.push_back((seq, bytes.to_vec()));
        if self.retentive_cap == 0 {
            while self.ring.len() > self.cap_chunks {
                self.ring.pop_front();
            }
        }
        if let Some(sub) = &self.subscriber {
            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);
            if let Ok(mut s) = sub.lock() {
                let _ = write_frame(&mut *s, &frame);
            }
        }
    }

    /// Drain every undrained chunk in order (the shell-tunnel recv cursor, W3).
    /// Pops the whole ring front-to-back (FIFO by `seq`) and concatenates the
    /// bytes — chunk boundaries are arbitrary read-pump splits, irrelevant to an
    /// opaque byte stream. Lossless + ordered: a byte leaves the ring only here,
    /// once. Returns `(bytes, finished)`; `finished` reflects a clean/torn end so
    /// the poller sees EOF and closes. Empty bytes with `finished=false` ⇒ nothing
    /// pending yet.
    fn drain(&mut self) -> (Vec<u8>, bool) {
        let mut out = Vec::new();
        while let Some((_, chunk)) = self.ring.pop_front() {
            out.extend_from_slice(&chunk);
        }
        (out, self.finished)
    }

    /// Mark the read side finished and tell the subscriber (with the final
    /// cursor, so it can verify completeness).
    fn finish(&mut self) {
        self.finished = true;
        if let Some(sub) = &self.subscriber {
            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);
            if let Ok(mut s) = sub.lock() {
                let _ = write_frame(&mut *s, &frame);
            }
        }
    }

    /// Attach `sub` as the subscriber, replaying every retained chunk with
    /// `seq >= from_seq` (then eof, if already finished) before live streaming.
    fn attach(&mut self, sub: SharedSend, from_seq: u64) {
        self.subscriber = Some(Arc::clone(&sub));
        for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {
            let frame = net_stream_data_envelope(self.stream_id, *seq, bytes);
            if let Ok(mut s) = sub.lock() {
                let _ = write_frame(&mut *s, &frame);
            }
        }
        if self.finished {
            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);
            if let Ok(mut s) = sub.lock() {
                let _ = write_frame(&mut *s, &frame);
            }
        }
    }

    /// Detach `sub` iff it is the current subscriber (a late disconnect must
    /// not unsubscribe a newer brain).
    fn detach_if(&mut self, sub: &SharedSend) {
        if let Some(cur) = &self.subscriber {
            if Arc::ptr_eq(cur, sub) {
                self.subscriber = None;
            }
        }
    }
}

/// A bounded, sequenced log of connection liveness events — the broker-owned
/// PresenceChannel seam (D4c, REQ-EP-4): the StreamLog ring/cursor discipline
/// applied to the conn table itself. `register_conn` appends `connected`; each


StreamLog has ONE subscriber slot; `append` live-sends, `attach(from_seq)` replays ring. For serve→rc: serve's `net_stream_send` → loopback delivers to rc-stream's read pump → `append` → live-send to rc's subscriber. rc's `net_stream_subscribe(0)` → `attach` replays. Add diagnostics to nethost `append`/`attach` + rerun with the rc-event diag to see the full chain.


    fn append(&mut self, bytes: &[u8]) {
        let seq = self.next_seq;
        self.next_seq += 1;
        self.ring.push_back((seq, bytes.to_vec()));
        if self.retentive_cap == 0 {
            while self.ring.len() > self.cap_chunks {
                self.ring.pop_front();
            }
        }
        if let Some(sub) = &self.subscriber {
            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);
            if let Ok(mut s) = sub.lock() {
                let _ = write_frame(&mut *s, &frame);
            }
        }
    }

    fn append(&mut self, bytes: &[u8]) {
        let seq = self.next_seq;
        self.next_seq += 1;
        self.ring.push_back((seq, bytes.to_vec()));
        if self.retentive_cap == 0 {
            while self.ring.len() > self.cap_chunks {
                self.ring.pop_front();
            }
        }
        eprintln!(
            "DIAG_SLOG_APPEND stream={} seq={seq} bytes={} has_sub={}",
            self.stream_id,
            bytes.len(),
            self.subscriber.is_some()
        );
        if let Some(sub) = &self.subscriber {
            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);
            if let Ok(mut s) = sub.lock() {
                let _ = write_frame(&mut *s, &frame);
            }
        }
    }


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

    fn append(&mut self, bytes: &[u8]) {
        let seq = self.next_seq;
        self.next_seq += 1;
        self.ring.push_back((seq, bytes.to_vec()));
        if self.retentive_cap == 0 {
            while self.ring.len() > self.cap_chunks {
                self.ring.pop_front();
            }
        }
        if let Some(sub) = &self.subscriber {
            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);
            if let Ok(mut s) = sub.lock() {
                let _ = write_frame(&mut *s, &frame);
            }
        }
    }

    fn append(&mut self, bytes: &[u8]) {
        let seq = self.next_seq;
        self.next_seq += 1;
        self.ring.push_back((seq, bytes.to_vec()));
        if self.retentive_cap == 0 {
            while self.ring.len() > self.cap_chunks {
                self.ring.pop_front();
            }
        }
        eprintln!(
            "DIAG_SLOG_APPEND stream={} seq={seq} bytes={} has_sub={}",
            self.stream_id,
            bytes.len(),
            self.subscriber.is_some()
        );
        if let Some(sub) = &self.subscriber {
            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);
            if let Ok(mut s) = sub.lock() {
                let _ = write_frame(&mut *s, &frame);
            }
        }
    }

+            "DIAG_SLOG_APPEND stream={} seq={seq} bytes={} has_sub={}",


    fn attach(&mut self, sub: SharedSend, from_seq: u64) {
        self.subscriber = Some(Arc::clone(&sub));
        for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {

    fn attach(&mut self, sub: SharedSend, from_seq: u64) {
        eprintln!(
            "DIAG_SLOG_ATTACH stream={} from_seq={from_seq} ring_len={} next_seq={}",
            self.stream_id,
            self.ring.len(),
            self.next_seq
        );
        self.subscriber = Some(Arc::clone(&sub));
        for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {


    fn attach(&mut self, sub: SharedSend, from_seq: u64) {
        self.subscriber = Some(Arc::clone(&sub));
        for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {

    fn attach(&mut self, sub: SharedSend, from_seq: u64) {
        eprintln!(
            "DIAG_SLOG_ATTACH stream={} from_seq={from_seq} ring_len={} next_seq={}",
            self.stream_id,
            self.ring.len(),
            self.next_seq
        );
        self.subscriber = Some(Arc::clone(&sub));
        for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {

/// Attach `sub` as the subscriber, replaying every retained chunk with

/// `seq >= from_seq` (then eof, if already finished) before live streaming.

+            "DIAG_SLOG_ATTACH stream={} from_seq={from_seq} ring_len={} next_seq={}",

let frame = net_stream_data_envelope(self.stream_id, *seq, bytes);


Bash

CARGO_TARGET_DIR=target-seam cargo test -p spt --test dummy_harness_e2e -- --nocapture --test-threads=1 2>&1 | tail -90

Rerun fixture with nethost + rc diagnostics