/// A failed write (dead operator) flips `gone` and ends the thread; the drain's
/// next bounded `send_timeout` then sees `Disconnected` and detaches the row.
/// `Output` advances `delivered_through`; `Control` (Displaced/…) does not.
fn controller_writer(
    sid: u64,
    send: SharedSend,
    initial: Vec<(u64, Vec<u8>)>,
    rx: Receiver<CtrlMsg>,
    delivered_through: Arc<AtomicU64>,
    gone: Arc<AtomicBool>,
) {
    for (seq, bytes) in &initial {
        let frame = output_envelope(sid, *seq, bytes);
        let ok = match send.lock() {
            Ok(mut s) => write_frame(&mut *s, &frame).is_ok(),
            Err(_) => false,
        };
        if !ok {
            gone.store(true, Ordering::Relaxed);
            return;
        }
        advance_delivered(&delivered_through, *seq);
    }
    while let Ok(msg) = rx.recv() {
        let (frame, seq) = match &msg {
            CtrlMsg::Output(seq, frame) => (frame, Some(*seq)),
            CtrlMsg::Control(frame) => (frame, None),
        };
        let ok = match send.lock() {
            Ok(mut s) => write_frame(&mut *s, frame).is_ok(),
            Err(_) => false,
        };
        if !ok {
            gone.store(true, Ordering::Relaxed);
            return;
        }
        if let Some(seq) = seq {
            advance_delivered(&delivered_through, seq);
        }
    }
}

/// A failed write (dead operator) ends the thread; the drain's next bounded
/// `send_timeout` then sees `Disconnected` and detaches the row. `Output`
/// advances `delivered_through`; `Control` (Displaced/…) does not.
fn controller_writer(
    sid: u64,
    send: SharedSend,
    initial: Vec<(u64, Vec<u8>)>,
    rx: Receiver<CtrlMsg>,
    delivered_through: Arc<AtomicU64>,
) {
    for (seq, bytes) in &initial {
        let frame = output_envelope(sid, *seq, bytes);
        let ok = match send.lock() {
            Ok(mut s) => write_frame(&mut *s, &frame).is_ok(),
            Err(_) => false,
        };
        if !ok {
            return;
        }
        advance_delivered(&delivered_through, *seq);
    }
    while let Ok(msg) = rx.recv() {
        let (frame, seq) = match &msg {
            CtrlMsg::Output(seq, frame) => (frame, Some(*seq)),
            CtrlMsg::Control(frame) => (frame, None),
        };
        let ok = match send.lock() {
            Ok(mut s) => write_frame(&mut *s, frame).is_ok(),
            Err(_) => false,
        };
        if !ok {
            return;
        }
        if let Some(seq) = seq {
            advance_delivered(&delivered_through, seq);
        }
    }
}


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)

/// A failed write (dead operator) flips `gone` and ends the thread; the drain's
/// next bounded `send_timeout` then sees `Disconnected` and detaches the row.
/// `Output` advances `delivered_through`; `Control` (Displaced/…) does not.
fn controller_writer(
    sid: u64,
    send: SharedSend,
    initial: Vec<(u64, Vec<u8>)>,
    rx: Receiver<CtrlMsg>,
    delivered_through: Arc<AtomicU64>,
    gone: Arc<AtomicBool>,
) {
    for (seq, bytes) in &initial {
        let frame = output_envelope(sid, *seq, bytes);
        let ok = match send.lock() {
            Ok(mut s) => write_frame(&mut *s, &frame).is_ok(),
            Err(_) => false,
        };
        if !ok {
            gone.store(true, Ordering::Relaxed);
            return;
        }
        advance_delivered(&delivered_through, *seq);
    }
    while let Ok(msg) = rx.recv() {
        let (frame, seq) = match &msg {
            CtrlMsg::Output(seq, frame) => (frame, Some(*seq)),
            CtrlMsg::Control(frame) => (frame, None),
        };
        let ok = match send.lock() {
            Ok(mut s) => write_frame(&mut *s, frame).is_ok(),
            Err(_) => false,
        };
        if !ok {
            gone.store(true, Ordering::Relaxed);
            return;
        }
        if let Some(seq) = seq {
            advance_delivered(&delivered_through, seq);
        }
    }
}

/// A failed write (dead operator) ends the thread; the drain's next bounded
/// `send_timeout` then sees `Disconnected` and detaches the row. `Output`
/// advances `delivered_through`; `Control` (Displaced/…) does not.
fn controller_writer(
    sid: u64,
    send: SharedSend,
    initial: Vec<(u64, Vec<u8>)>,
    rx: Receiver<CtrlMsg>,
    delivered_through: Arc<AtomicU64>,
) {
    for (seq, bytes) in &initial {
        let frame = output_envelope(sid, *seq, bytes);
        let ok = match send.lock() {
            Ok(mut s) => write_frame(&mut *s, &frame).is_ok(),
            Err(_) => false,
        };
        if !ok {
            return;
        }
        advance_delivered(&delivered_through, *seq);
    }
    while let Ok(msg) = rx.recv() {
        let (frame, seq) = match &msg {
            CtrlMsg::Output(seq, frame) => (frame, Some(*seq)),
            CtrlMsg::Control(frame) => (frame, None),
        };
        let ok = match send.lock() {
            Ok(mut s) => write_frame(&mut *s, frame).is_ok(),
            Err(_) => false,
        };
        if !ok {
            return;
        }
        if let Some(seq) = seq {
            advance_delivered(&delivered_through, seq);
        }
    }
}

/// cursor on each success), then drain live [`CtrlMsg`]s with a BLOCKING socket

/// write each — so a backed-up controller blocks ONLY this thread, never the

/// session drain (which used to write the controller inline under the log lock).

-/// A failed write (dead operator) flips `gone` and ends the thread; the drain's

-/// next bounded `send_timeout` then sees `Disconnected` and detaches the row.

-/// `Output` advances `delivered_through`; `Control` (Displaced/…) does not.

+/// A failed write (dead operator) ends the thread; the drain's next bounded

+/// `send_timeout` then sees `Disconnected` and detaches the row. `Output`

+/// advances `delivered_through`; `Control` (Displaced/…) does not.

-            gone.store(true, Ordering::Relaxed);


Edit

use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};


-use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};

use std::sync::mpsc::{sync_channel, Receiver, SendTimeoutError, SyncSender, TrySendError};


Core (a) refactor done. Now decide on instrumentation cleanliness: the spt-term `park_debug` prints were for proving (b)/(c) — proof is done (Windows showed no park). Remove them for a clean fix; the repro test's behavioral assertion stands without src prints. Check for any remaining stale references first.


293:        self.controller.as_ref().map(|c| ControllerJob {
303:    fn controller_by(&self) -> Option<&str> {
304:        self.controller.as_ref().and_then(|c| c.by.as_deref())
320:        self.controller = None;
335:        self.controller = Some(ControllerSink {
350:        if self.controller.take().is_some() {
419:        if self.controller.is_none() {
424:        let same_identity = self.controller_by() == by.as_deref();
439:        if self.controller_by().is_none() {
454:                if let Some(old) = &self.controller {
462:                by: self.controller_by().unwrap_or_default().to_string(),
497:            .controller
514:        if let Some(c) = &self.controller {
535:        let _ = spt_store::info::set_driven_by(&perch, self.controller_by());
551:        self.controller


Revert the spt-term instrumentation for a clean fix.


/// Shared, serialized handle to the PTY master writer. Cloned to the A2 drain
/// thread so the DSR auto-answer and caller input never interleave mid-write.
pub(crate) type SharedWriter = Arc<Mutex<Box<dyn Write + Send>>>;

/// W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) repro instrumentation. When
/// `SPT_TERM_DEBUG_PARK` is set, the two PTY-write park candidates bracket
/// themselves on stderr: park (b) — [`PtySession::write_input`]'s blocking
/// `write_all` on a full ConPTY input buffer (`PTY_WRITE_ENTER`/`_EXIT`) — and
/// park (c) — the drain's DSR auto-answer holding the shared writer mutex across
/// a blocking write (`DSR_LOCK_WAIT`/`DSR_WRITE_DONE`, in `reader.rs`). An ENTER
/// with no matching EXIT pinpoints the parked call. Cached once; zero cost unset.
pub(crate) fn park_debug() -> bool {
    use std::sync::OnceLock;
    static ON: OnceLock<bool> = OnceLock::new();
    *ON.get_or_init(|| std::env::var_os("SPT_TERM_DEBUG_PARK").is_some())
}

/// Shared, serialized handle to the PTY master writer. Cloned to the A2 drain
/// thread so the DSR auto-answer and caller input never interleave mid-write.
pub(crate) type SharedWriter = Arc<Mutex<Box<dyn Write + Send>>>;


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

/// Shared, serialized handle to the PTY master writer. Cloned to the A2 drain
/// thread so the DSR auto-answer and caller input never interleave mid-write.
pub(crate) type SharedWriter = Arc<Mutex<Box<dyn Write + Send>>>;

/// W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) repro instrumentation. When
/// `SPT_TERM_DEBUG_PARK` is set, the two PTY-write park candidates bracket
/// themselves on stderr: park (b) — [`PtySession::write_input`]'s blocking
/// `write_all` on a full ConPTY input buffer (`PTY_WRITE_ENTER`/`_EXIT`) — and
/// park (c) — the drain's DSR auto-answer holding the shared writer mutex across
/// a blocking write (`DSR_LOCK_WAIT`/`DSR_WRITE_DONE`, in `reader.rs`). An ENTER
/// with no matching EXIT pinpoints the parked call. Cached once; zero cost unset.
pub(crate) fn park_debug() -> bool {
    use std::sync::OnceLock;
    static ON: OnceLock<bool> = OnceLock::new();
    *ON.get_or_init(|| std::env::var_os("SPT_TERM_DEBUG_PARK").is_some())
}

-/// W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) repro instrumentation. When

-/// `SPT_TERM_DEBUG_PARK` is set, the two PTY-write park candidates bracket

-/// themselves on stderr: park (b) — [`PtySession::write_input`]'s blocking

-/// `write_all` on a full ConPTY input buffer (`PTY_WRITE_ENTER`/`_EXIT`) — and

-/// park (c) — the drain's DSR auto-answer holding the shared writer mutex across

-/// a blocking write (`DSR_LOCK_WAIT`/`DSR_WRITE_DONE`, in `reader.rs`). An ENTER

-/// with no matching EXIT pinpoints the parked call. Cached once; zero cost unset.

-    static ON: OnceLock<bool> = OnceLock::new();

-    *ON.get_or_init(|| std::env::var_os("SPT_TERM_DEBUG_PARK").is_some())

/// A child process running under a native PTY, exposed as a [`SessionSurface`].

/// Build one with [`PtySession::spawn`]; drive input via [`SessionSurface::write_input`]


    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {
        // W1 park (b)/(c): the lock is the shared writer the DSR auto-answer also
        // takes (contention = park c); the `write_all` blocks on a full ConPTY
        // input buffer while the lock is held (park b). The bracket pinpoints it.
        if park_debug() {
            eprintln!("PTY_WRITE_ENTER n={}", bytes.len());
        }
        let mut w = self.writer.lock().unwrap();
        if park_debug() {
            eprintln!("PTY_WRITE_LOCKED n={}", bytes.len());
        }
        w.write_all(bytes).map_err(SurfaceError::Io)?;
        let r = w.flush().map_err(SurfaceError::Io);
        if park_debug() {
            eprintln!("PTY_WRITE_EXIT n={} ok={}", bytes.len(), r.is_ok());
        }
        r
    }

    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {
        let mut w = self.writer.lock().unwrap();
        w.write_all(bytes).map_err(SurfaceError::Io)?;
        w.flush().map_err(SurfaceError::Io)
    }


    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {
        // W1 park (b)/(c): the lock is the shared writer the DSR auto-answer also
        // takes (contention = park c); the `write_all` blocks on a full ConPTY
        // input buffer while the lock is held (park b). The bracket pinpoints it.
        if park_debug() {
            eprintln!("PTY_WRITE_ENTER n={}", bytes.len());
        }
        let mut w = self.writer.lock().unwrap();
        if park_debug() {
            eprintln!("PTY_WRITE_LOCKED n={}", bytes.len());
        }
        w.write_all(bytes).map_err(SurfaceError::Io)?;
        let r = w.flush().map_err(SurfaceError::Io);
        if park_debug() {
            eprintln!("PTY_WRITE_EXIT n={} ok={}", bytes.len(), r.is_ok());
        }
        r
    }

    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {
        let mut w = self.writer.lock().unwrap();
        w.write_all(bytes).map_err(SurfaceError::Io)?;
        w.flush().map_err(SurfaceError::Io)
    }

-        // W1 park (b)/(c): the lock is the shared writer the DSR auto-answer also

-        // takes (contention = park c); the `write_all` blocks on a full ConPTY

-        // input buffer while the lock is held (park b). The bracket pinpoints it.

-            eprintln!("PTY_WRITE_ENTER n={}", bytes.len());

-            eprintln!("PTY_WRITE_LOCKED n={}", bytes.len());

-        let r = w.flush().map_err(SurfaceError::Io);

-            eprintln!("PTY_WRITE_EXIT n={} ok={}", bytes.len(), r.is_ok());

+        w.flush().map_err(SurfaceError::Io)

fn resize(&self, size: SurfaceSize) -> Result<(), SurfaceError> {


                        if hits > 0 {
                            // Answer through the shared, serialized writer so the
                            // CPR never interleaves with caller-injected input.
                            // W1 park (c): this holds the shared writer mutex
                            // ACROSS a blocking write — if the caller (dispatch)
                            // is mid-`write_input` blocked on a full input buffer
                            // holding this same lock, the two contend / can wedge.
                            if crate::pty::park_debug() {
                                eprintln!("DSR_LOCK_WAIT hits={hits}");
                            }
                            if let Ok(mut w) = writer.lock() {
                                if crate::pty::park_debug() {
                                    eprintln!("DSR_LOCK_HELD hits={hits}");
                                }
                                for _ in 0..hits {
                                    let _ = w.write_all(DSR_REPLY);
                                }
                                let _ = w.flush();
                            }
                            if crate::pty::park_debug() {
                                eprintln!("DSR_WRITE_DONE hits={hits}");
                            }
                            t_dsr.fetch_add(hits, Ordering::Relaxed);
                        }

                        if hits > 0 {
                            // Answer through the shared, serialized writer so the
                            // CPR never interleaves with caller-injected input.
                            if let Ok(mut w) = writer.lock() {
                                for _ in 0..hits {
                                    let _ = w.write_all(DSR_REPLY);
                                }
                                let _ = w.flush();
                            }
                            t_dsr.fetch_add(hits, Ordering::Relaxed);
                        }


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

                        if hits > 0 {
                            // Answer through the shared, serialized writer so the
                            // CPR never interleaves with caller-injected input.
                            // W1 park (c): this holds the shared writer mutex
                            // ACROSS a blocking write — if the caller (dispatch)
                            // is mid-`write_input` blocked on a full input buffer
                            // holding this same lock, the two contend / can wedge.
                            if crate::pty::park_debug() {
                                eprintln!("DSR_LOCK_WAIT hits={hits}");
                            }
                            if let Ok(mut w) = writer.lock() {
                                if crate::pty::park_debug() {
                                    eprintln!("DSR_LOCK_HELD hits={hits}");
                                }
                                for _ in 0..hits {
                                    let _ = w.write_all(DSR_REPLY);
                                }
                                let _ = w.flush();
                            }
                            if crate::pty::park_debug() {
                                eprintln!("DSR_WRITE_DONE hits={hits}");
                            }
                            t_dsr.fetch_add(hits, Ordering::Relaxed);
                        }

                        if hits > 0 {
                            // Answer through the shared, serialized writer so the
                            // CPR never interleaves with caller-injected input.
                            if let Ok(mut w) = writer.lock() {
                                for _ in 0..hits {
                                    let _ = w.write_all(DSR_REPLY);
                                }
                                let _ = w.flush();
                            }
                            t_dsr.fetch_add(hits, Ordering::Relaxed);
                        }

-                            // W1 park (c): this holds the shared writer mutex

-                            // ACROSS a blocking write — if the caller (dispatch)

-                            // is mid-`write_input` blocked on a full input buffer

-                            // holding this same lock, the two contend / can wedge.

-                            if crate::pty::park_debug() {

-                                eprintln!("DSR_LOCK_WAIT hits={hits}");

-                                if crate::pty::park_debug() {

-                                    eprintln!("DSR_LOCK_HELD hits={hits}");

-                                eprintln!("DSR_WRITE_DONE hits={hits}");


Now flip the int gate to assert the FIXED behavior (doyle's gate condition). Update test 1.


1	//! v0.13.0 W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) — the CONTROLLER-DELIVERY park
2	//! repro, on a REAL broker hosting a REAL PTY child over the real local IPC (no
3	//! mocks of the PTY or the transport).
4	//!
5	//! ## What this proves (park (a), the output-stall / wedge root)
6	//!
7	//! [`OutputLog::append`] (broker.rs) fans each output chunk to the CONTROLLER via
8	//! a SYNCHRONOUS, BLOCKING `write_frame` held INLINE on the session's single
9	//! drain thread, while the `Mutex<OutputLog>` is locked. VIEWERS instead get a
10	//! dedicated writer thread + a bounded, evicting `sync_channel` and can never
11	//! stall the drain.
12	//!
13	//! So when the controller's socket backs up (a slow / non-draining operator —
14	//! here a controller connection that simply stops reading while the child
15	//! floods), the drain thread BLOCKS inside `append` STILL HOLDING the log lock.
16	//! That is the wedge: output stalls, and any path that needs that session's log
17	//! lock (a fresh attach, or even the global `KIND_SESSIONS` enumeration, which
18	//! reads each log's `delivered_through`) blocks forever — while the broker
19	//! PROCESS is otherwise alive (a fresh connection's `KIND_NET_STATUS`, which
20	//! touches neither the sessions nor the log lock, still answers).
21	//!
22	//! Run with `SPT_BROKER_DEBUG_PARK=1 cargo test -p spt-daemon --test
23	//! inject_control_wedge -- --nocapture` to see the park point on stderr: an
24	//! `APPEND_CTRL_ENTER sid=.. seq=N` with NO matching `APPEND_CTRL_EXIT` = the
25	//! drain thread parked on the controller `write_frame` at broker.rs:223.
26	//!
27	//! Pre-fix this test asserts the wedge (the bug). The W1 fix moves controller
28	//! delivery off the drain thread (dedicated writer, deadline-bounded) — at which
29	//! point this flips to "no wedge" and folds into the int gate.
30
31	use std::sync::Arc;
32	use std::thread;
33	use std::time::Duration;
34
35	use interprocess::local_socket::Stream;
36	use spt_daemon::codec::{read_frame, write_frame};
37	use spt_daemon::frame::{Envelope, Role};
38	use spt_daemon::msg::{
39	    encode_bytes, EndpointInputReq, SpawnReq, Spawned, KIND_ENDPOINT_INJECTED, KIND_ENDPOINT_INPUT,
40	    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,
41	    KIND_SPAWNED,
42	};
43	use spt_daemon::transport::{send_hello, LocalSocketTransport};
44	use spt_daemon::Broker;
45

//! v0.13.0 W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) — the CONTROLLER-DELIVERY park
//! repro, on a REAL broker hosting a REAL PTY child over the real local IPC (no
//! mocks of the PTY or the transport).
//!
//! ## What this proves (park (a), the output-stall / wedge root)
//!
//! [`OutputLog::append`] (broker.rs) fans each output chunk to the CONTROLLER via
//! a SYNCHRONOUS, BLOCKING `write_frame` held INLINE on the session's single
//! drain thread, while the `Mutex<OutputLog>` is locked. VIEWERS instead get a
//! dedicated writer thread + a bounded, evicting `sync_channel` and can never
//! stall the drain.
//!
//! So when the controller's socket backs up (a slow / non-draining operator —
//! here a controller connection that simply stops reading while the child
//! floods), the drain thread BLOCKS inside `append` STILL HOLDING the log lock.
//! That is the wedge: output stalls, and any path that needs that session's log
//! lock (a fresh attach, or even the global `KIND_SESSIONS` enumeration, which
//! reads each log's `delivered_through`) blocks forever — while the broker
//! PROCESS is otherwise alive (a fresh connection's `KIND_NET_STATUS`, which
//! touches neither the sessions nor the log lock, still answers).
//!
//! Run with `SPT_BROKER_DEBUG_PARK=1 cargo test -p spt-daemon --test
//! inject_control_wedge -- --nocapture` to see the park point on stderr: an
//! `APPEND_CTRL_ENTER sid=.. seq=N` with NO matching `APPEND_CTRL_EXIT` = the
//! drain thread parked on the controller `write_frame` at broker.rs:223.
//!
//! Pre-fix this test asserts the wedge (the bug). The W1 fix moves controller
//! delivery off the drain thread (dedicated writer, deadline-bounded) — at which
//! point this flips to "no wedge" and folds into the int gate.

use std::sync::Arc;
use std::thread;
use std::time::Duration;

use interprocess::local_socket::Stream;
use spt_daemon::codec::{read_frame, write_frame};
use spt_daemon::frame::{Envelope, Role};
use spt_daemon::msg::{
    encode_bytes, EndpointInputReq, SpawnReq, Spawned, KIND_ENDPOINT_INJECTED, KIND_ENDPOINT_INPUT,
    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,
    KIND_SPAWNED,
};
use spt_daemon::transport::{send_hello, LocalSocketTransport};
use spt_daemon::Broker;


//! v0.13.0 W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) — the CONTROLLER-DELIVERY park
//! repro, on a REAL broker hosting a REAL PTY child over the real local IPC (no
//! mocks of the PTY or the transport).
//!
//! ## What this proves (park (a), the output-stall / wedge root)
//!
//! [`OutputLog::append`] (broker.rs) fans each output chunk to the CONTROLLER via
//! a SYNCHRONOUS, BLOCKING `write_frame` held INLINE on the session's single
//! drain thread, while the `Mutex<OutputLog>` is locked. VIEWERS instead get a
//! dedicated writer thread + a bounded, evicting `sync_channel` and can never
//! stall the drain.
//!
//! So when the controller's socket backs up (a slow / non-draining operator —
//! here a controller connection that simply stops reading while the child
//! floods), the drain thread BLOCKS inside `append` STILL HOLDING the log lock.
//! That is the wedge: output stalls, and any path that needs that session's log
//! lock (a fresh attach, or even the global `KIND_SESSIONS` enumeration, which
//! reads each log's `delivered_through`) blocks forever — while the broker
//! PROCESS is otherwise alive (a fresh connection's `KIND_NET_STATUS`, which
//! touches neither the sessions nor the log lock, still answers).
//!
//! Run with `SPT_BROKER_DEBUG_PARK=1 cargo test -p spt-daemon --test
//! inject_control_wedge -- --nocapture` to see the park point on stderr: an
//! `APPEND_CTRL_ENTER sid=.. seq=N` with NO matching `APPEND_CTRL_EXIT` = the
//! drain thread parked on the controller `write_frame` at broker.rs:223.
//!
//! Pre-fix this test asserts the wedge (the bug). The W1 fix moves controller
//! delivery off the drain thread (dedicated writer, deadline-bounded) — at which
//! point this flips to "no wedge" and folds into the int gate.

//! v0.13.0 W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) — the CONTROLLER-DELIVERY
//! non-wedge gate, on a REAL broker hosting a REAL PTY child over the real local
//! IPC (no mocks of the PTY or the transport).
//!
//! ## The bug this gate locks down (park (a), the output-stall / wedge root)
//!
//! PRE-FIX (v0.12.x), [`OutputLog::append`] fanned each output chunk to the
//! CONTROLLER via a SYNCHRONOUS, BLOCKING `write_frame` held INLINE on the
//! session's single drain thread, while the `Mutex<OutputLog>` was locked.
//! VIEWERS instead get a dedicated writer thread + a bounded, evicting
//! `sync_channel` and can never stall the drain. So a slow / non-draining
//! controller (here a controller connection that stops reading while the child
//! floods) parked the drain INSIDE `append` STILL HOLDING the log lock — output
//! stalled and any path needing that log lock (a fresh attach, or the global
//! `KIND_SESSIONS` enumeration, which reads each log's `delivered_through`)
//! blocked FOREVER, while the broker process stayed alive. The repro that proved
//! this (captured 2026-06-19): `APPEND_CTRL_ENTER sid=1 seq=2` with no matching
//! `APPEND_CTRL_EXIT`, `broker_alive=true` but `sessions_answered=false`.
//!
//! POST-FIX (W1): controller delivery moved off the drain thread onto a dedicated
//! writer thread + bounded channel (the viewer_writer pattern, but authoritative:
//! the writer advances `delivered_through`). The drain hands each chunk off with a
//! bounded `send_timeout` OFF the log lock; a fully-stalled controller is detached
//! after `CONTROLLER_WRITE_DEADLINE`. So even while the controller is fully backed
//! up, the session does NOT wedge — `KIND_SESSIONS` still answers. THIS test
//! asserts that fixed behavior.


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

//! v0.13.0 W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) — the CONTROLLER-DELIVERY park
//! repro, on a REAL broker hosting a REAL PTY child over the real local IPC (no
//! mocks of the PTY or the transport).
//!
//! ## What this proves (park (a), the output-stall / wedge root)
//!
//! [`OutputLog::append`] (broker.rs) fans each output chunk to the CONTROLLER via
//! a SYNCHRONOUS, BLOCKING `write_frame` held INLINE on the session's single
//! drain thread, while the `Mutex<OutputLog>` is locked. VIEWERS instead get a
//! dedicated writer thread + a bounded, evicting `sync_channel` and can never
//! stall the drain.
//!
//! So when the controller's socket backs up (a slow / non-draining operator —
//! here a controller connection that simply stops reading while the child
//! floods), the drain thread BLOCKS inside `append` STILL HOLDING the log lock.
//! That is the wedge: output stalls, and any path that needs that session's log
//! lock (a fresh attach, or even the global `KIND_SESSIONS` enumeration, which
//! reads each log's `delivered_through`) blocks forever — while the broker
//! PROCESS is otherwise alive (a fresh connection's `KIND_NET_STATUS`, which
//! touches neither the sessions nor the log lock, still answers).
//!
//! Run with `SPT_BROKER_DEBUG_PARK=1 cargo test -p spt-daemon --test
//! inject_control_wedge -- --nocapture` to see the park point on stderr: an
//! `APPEND_CTRL_ENTER sid=.. seq=N` with NO matching `APPEND_CTRL_EXIT` = the
//! drain thread parked on the controller `write_frame` at broker.rs:223.
//!
//! Pre-fix this test asserts the wedge (the bug). The W1 fix moves controller
//! delivery off the drain thread (dedicated writer, deadline-bounded) — at which
//! point this flips to "no wedge" and folds into the int gate.

//! v0.13.0 W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) — the CONTROLLER-DELIVERY
//! non-wedge gate, on a REAL broker hosting a REAL PTY child over the real local
//! IPC (no mocks of the PTY or the transport).
//!
//! ## The bug this gate locks down (park (a), the output-stall / wedge root)
//!
//! PRE-FIX (v0.12.x), [`OutputLog::append`] fanned each output chunk to the
//! CONTROLLER via a SYNCHRONOUS, BLOCKING `write_frame` held INLINE on the
//! session's single drain thread, while the `Mutex<OutputLog>` was locked.
//! VIEWERS instead get a dedicated writer thread + a bounded, evicting
//! `sync_channel` and can never stall the drain. So a slow / non-draining
//! controller (here a controller connection that stops reading while the child
//! floods) parked the drain INSIDE `append` STILL HOLDING the log lock — output
//! stalled and any path needing that log lock (a fresh attach, or the global
//! `KIND_SESSIONS` enumeration, which reads each log's `delivered_through`)
//! blocked FOREVER, while the broker process stayed alive. The repro that proved
//! this (captured 2026-06-19): `APPEND_CTRL_ENTER sid=1 seq=2` with no matching
//! `APPEND_CTRL_EXIT`, `broker_alive=true` but `sessions_answered=false`.
//!
//! POST-FIX (W1): controller delivery moved off the drain thread onto a dedicated
//! writer thread + bounded channel (the viewer_writer pattern, but authoritative:
//! the writer advances `delivered_through`). The drain hands each chunk off with a
//! bounded `send_timeout` OFF the log lock; a fully-stalled controller is detached
//! after `CONTROLLER_WRITE_DEADLINE`. So even while the controller is fully backed
//! up, the session does NOT wedge — `KIND_SESSIONS` still answers. THIS test
//! asserts that fixed behavior.

-//! v0.13.0 W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) — the CONTROLLER-DELIVERY park

-//! repro, on a REAL broker hosting a REAL PTY child over the real local IPC (no

-//! mocks of the PTY or the transport).

+//! v0.13.0 W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) — the CONTROLLER-DELIVERY

+//! non-wedge gate, on a REAL broker hosting a REAL PTY child over the real local

+//! IPC (no mocks of the PTY or the transport).

-//! ## What this proves (park (a), the output-stall / wedge root)

+//! ## The bug this gate locks down (park (a), the output-stall / wedge root)

-//! [`OutputLog::append`] (broker.rs) fans each output chunk to the CONTROLLER via

-//! a SYNCHRONOUS, BLOCKING `write_frame` held INLINE on the session's single

-//! drain thread, while the `Mutex<OutputLog>` is locked. VIEWERS instead get a

-//! dedicated writer thread + a bounded, evicting `sync_channel` and can never

+//! PRE-FIX (v0.12.x), [`OutputLog::append`] fanned each output chunk to the

+//! CONTROLLER via a SYNCHRONOUS, BLOCKING `write_frame` held INLINE on the

+//! session's single drain thread, while the `Mutex<OutputLog>` was locked.

+//! VIEWERS instead get a dedicated writer thread + a bounded, evicting

+//! `sync_channel` and can never stall the drain. So a slow / non-draining

+//! controller (here a controller connection that stops reading while the child

+//! floods) parked the drain INSIDE `append` STILL HOLDING the log lock — output

+//! stalled and any path needing that log lock (a fresh attach, or the global

+//! `KIND_SESSIONS` enumeration, which reads each log's `delivered_through`)

+//! blocked FOREVER, while the broker process stayed alive. The repro that proved

+//! this (captured 2026-06-19): `APPEND_CTRL_ENTER sid=1 seq=2` with no matching

+//! `APPEND_CTRL_EXIT`, `broker_alive=true` but `sessions_answered=false`.

-//! So when the controller's socket backs up (a slow / non-draining operator —

-//! here a controller connection that simply stops reading while the child

-//! floods), the drain thread BLOCKS inside `append` STILL HOLDING the log lock.

-//! That is the wedge: output stalls, and any path that needs that session's log

-//! lock (a fresh attach, or even the global `KIND_SESSIONS` enumeration, which

-//! reads each log's `delivered_through`) blocks forever — while the broker

-//! PROCESS is otherwise alive (a fresh connection's `KIND_NET_STATUS`, which

-//! touches neither the sessions nor the log lock, still answers).

-//! Run with `SPT_BROKER_DEBUG_PARK=1 cargo test -p spt-daemon --test

-//! inject_control_wedge -- --nocapture` to see the park point on stderr: an

-//! `APPEND_CTRL_ENTER sid=.. seq=N` with NO matching `APPEND_CTRL_EXIT` = the

-//! drain thread parked on the controller `write_frame` at broker.rs:223.

-//! Pre-fix this test asserts the wedge (the bug). The W1 fix moves controller

-//! delivery off the drain thread (dedicated writer, deadline-bounded) — at which

-//! point this flips to "no wedge" and folds into the int gate.

+//! POST-FIX (W1): controller delivery moved off the drain thread onto a dedicated

+//! writer thread + bounded channel (the viewer_writer pattern, but authoritative:

+//! the writer advances `delivered_through`). The drain hands each chunk off with a

+//! bounded `send_timeout` OFF the log lock; a fully-stalled controller is detached

+//! after `CONTROLLER_WRITE_DEADLINE`. So even while the controller is fully backed

+//! up, the session does NOT wedge — `KIND_SESSIONS` still answers. THIS test


118	                    let _ = tx.send(false);
119	                    return;
120	                }
121	            }
122	        }
123	    });
124	    rx.recv_timeout(deadline).unwrap_or(false)
125	}
126
127	#[test]
128	fn controller_backpressure_parks_the_drain_thread_and_wedges_the_session() {
129	    // Make the park instrumentation visible when run with --nocapture (cached on
130	    // first append; this test is the first/only appender in this binary).
131	    std::env::set_var("SPT_BROKER_DEBUG_PARK", "1");
132
133	    let name = unique_name();
134	    let broker = Broker::bind(&name).expect("bind broker");
135	    let serve_broker = Arc::clone(&broker);
136	    thread::spawn(move || {
137	        let _ = serve_broker.serve(); // accept every connection on its own thread
138	    });
139
140	    // ── Controller connection A: spawn the flooding child. dispatch_spawn
141	    //    pre-attaches A as the CONTROLLER (become_controller, by=None). ──
142	    let mut a = None;
143	    for _ in 0..200 {
144	        match LocalSocketTransport::connect(&name) {
145	            Ok(c) => {
146	                a = Some(c);
147	                break;
148	            }
149	            Err(_) => thread::sleep(Duration::from_millis(10)),
150	        }
151	    }
152	    let mut a = a.expect("connect controller A");
153	    send_hello(&mut a, Role::Brain).expect("hello A");
154	    send(
155	        &mut a,
156	        KIND_SPAWN,
157	        serde_json::to_value(flood_spawn_req("wedge-ep")).unwrap(),
158	    );
159	    let sid = loop {
160	        let f = read_frame(&mut a).expect("frame before spawned");
161	        if f.kind == KIND_SPAWNED {
162	            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;
163	        }
164	    };
165
166	    // ── A now STOPS reading. The child floods; the broker's drain thread calls
167	    //    append → write_frame(controller=A); once A's socket recv buffer fills,
168	    //    that write blocks → the drain parks inside append HOLDING the log lock.
169	    //    Give the flood time to fill the buffer. ──
170	    thread::sleep(Duration::from_secs(4));
171
172	    // ── Differential proof. A fresh connection's KIND_NET_STATUS touches neither
173	    //    the sessions nor the log lock → the broker PROCESS is alive and answers
174	    //    fast. KIND_SESSIONS enumerates every session's log (`delivered_through`
175	    //    under the log lock) → it blocks on the parked session's held log lock →
176	    //    WEDGE. ──
177	    let broker_alive = probe(&name, KIND_NET_STATUS, KIND_NET_STATUS_REPLY, Duration::from_secs(5));
178	    let sessions_answered =
179	        probe(&name, KIND_SESSIONS, KIND_SESSIONS_REPLY, Duration::from_secs(5));
180
181	    eprintln!(
182	        "=== W1 DIAGNOSTIC: broker_alive(net_status)={broker_alive} \
183	         sessions_answered={sessions_answered} (expect alive=true, sessions=false = WEDGE) ==="
184	    );
185
186	    // Reap the flood child (scoped) + tear the broker down.
187	    if let Some(pid) = broker.session_pid(sid) {
188	        kill_pid(pid);
189	    }
190	    drop(a);
191
192	    assert!(
193	        broker_alive,
194	        "the broker PROCESS must still answer a fresh connection's net-status \
195	         (the wedge is per-session, not a dead process)"
196	    );
197	    // [int->REQ-HAZARD-INJECT-CONTROL-COEXIST]
198	    assert!(
199	        !sessions_answered,
200	        "REPRO: a backed-up controller parks the drain thread inside append() \
201	         HOLDING the log lock, so KIND_SESSIONS (which reads each log's \
202	         delivered_through) hangs — the session is wedged. \
203	         When this assertion FLIPS (sessions answered), the W1 fix has moved \
204	         controller delivery off the drain thread."
205	    );
206	}
207
208	#[test]
209	fn injecting_into_a_stdin_ignoring_child_parks_the_dispatch_thread_on_write_all() {
210	    // Park (b): write_input's blocking write_all on a full PTY input buffer.
211	    // Run with SPT_TERM_DEBUG_PARK=1 --nocapture to see PTY_WRITE_ENTER /
212	    // PTY_WRITE_LOCKED with NO PTY_WRITE_EXIT = the dispatch thread parked in

                    let _ = tx.send(false);
                    return;
                }
            }
        }
    });
    rx.recv_timeout(deadline).unwrap_or(false)
}

#[test]
fn controller_backpressure_parks_the_drain_thread_and_wedges_the_session() {
    // Make the park instrumentation visible when run with --nocapture (cached on
    // first append; this test is the first/only appender in this binary).
    std::env::set_var("SPT_BROKER_DEBUG_PARK", "1");

    let name = unique_name();
    let broker = Broker::bind(&name).expect("bind broker");
    let serve_broker = Arc::clone(&broker);
    thread::spawn(move || {
        let _ = serve_broker.serve(); // accept every connection on its own thread
    });

    // ── Controller connection A: spawn the flooding child. dispatch_spawn
    //    pre-attaches A as the CONTROLLER (become_controller, by=None). ──
    let mut a = None;
    for _ in 0..200 {
        match LocalSocketTransport::connect(&name) {
            Ok(c) => {
                a = Some(c);
                break;
            }
            Err(_) => thread::sleep(Duration::from_millis(10)),
        }
    }
    let mut a = a.expect("connect controller A");
    send_hello(&mut a, Role::Brain).expect("hello A");
    send(
        &mut a,
        KIND_SPAWN,
        serde_json::to_value(flood_spawn_req("wedge-ep")).unwrap(),
    );
    let sid = loop {
        let f = read_frame(&mut a).expect("frame before spawned");
        if f.kind == KIND_SPAWNED {
            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;
        }
    };

    // ── A now STOPS reading. The child floods; the broker's drain thread calls
    //    append → write_frame(controller=A); once A's socket recv buffer fills,
    //    that write blocks → the drain parks inside append HOLDING the log lock.
    //    Give the flood time to fill the buffer. ──
    thread::sleep(Duration::from_secs(4));

    // ── Differential proof. A fresh connection's KIND_NET_STATUS touches neither
    //    the sessions nor the log lock → the broker PROCESS is alive and answers
    //    fast. KIND_SESSIONS enumerates every session's log (`delivered_through`
    //    under the log lock) → it blocks on the parked session's held log lock →
    //    WEDGE. ──
    let broker_alive = probe(&name, KIND_NET_STATUS, KIND_NET_STATUS_REPLY, Duration::from_secs(5));
    let sessions_answered =
        probe(&name, KIND_SESSIONS, KIND_SESSIONS_REPLY, Duration::from_secs(5));

    eprintln!(
        "=== W1 DIAGNOSTIC: broker_alive(net_status)={broker_alive} \
         sessions_answered={sessions_answered} (expect alive=true, sessions=false = WEDGE) ==="
    );

    // Reap the flood child (scoped) + tear the broker down.
    if let Some(pid) = broker.session_pid(sid) {
        kill_pid(pid);
    }
    drop(a);

    assert!(
        broker_alive,
        "the broker PROCESS must still answer a fresh connection's net-status \
         (the wedge is per-session, not a dead process)"
    );
    // [int->REQ-HAZARD-INJECT-CONTROL-COEXIST]
    assert!(
        !sessions_answered,
        "REPRO: a backed-up controller parks the drain thread inside append() \
         HOLDING the log lock, so KIND_SESSIONS (which reads each log's \
         delivered_through) hangs — the session is wedged. \
         When this assertion FLIPS (sessions answered), the W1 fix has moved \
         controller delivery off the drain thread."
    );
}

#[test]
fn injecting_into_a_stdin_ignoring_child_parks_the_dispatch_thread_on_write_all() {
    // Park (b): write_input's blocking write_all on a full PTY input buffer.
    // Run with SPT_TERM_DEBUG_PARK=1 --nocapture to see PTY_WRITE_ENTER /
    // PTY_WRITE_LOCKED with NO PTY_WRITE_EXIT = the dispatch thread parked in


#[test]
fn controller_backpressure_parks_the_drain_thread_and_wedges_the_session() {
    // Make the park instrumentation visible when run with --nocapture (cached on
    // first append; this test is the first/only appender in this binary).
    std::env::set_var("SPT_BROKER_DEBUG_PARK", "1");

    let name = unique_name();
    let broker = Broker::bind(&name).expect("bind broker");
    let serve_broker = Arc::clone(&broker);
    thread::spawn(move || {
        let _ = serve_broker.serve(); // accept every connection on its own thread
    });

    // ── Controller connection A: spawn the flooding child. dispatch_spawn
    //    pre-attaches A as the CONTROLLER (become_controller, by=None). ──
    let mut a = None;
    for _ in 0..200 {
        match LocalSocketTransport::connect(&name) {
            Ok(c) => {
                a = Some(c);
                break;
            }
            Err(_) => thread::sleep(Duration::from_millis(10)),
        }
    }
    let mut a = a.expect("connect controller A");
    send_hello(&mut a, Role::Brain).expect("hello A");
    send(
        &mut a,
        KIND_SPAWN,
        serde_json::to_value(flood_spawn_req("wedge-ep")).unwrap(),
    );
    let sid = loop {
        let f = read_frame(&mut a).expect("frame before spawned");
        if f.kind == KIND_SPAWNED {
            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;
        }
    };

    // ── A now STOPS reading. The child floods; the broker's drain thread calls
    //    append → write_frame(controller=A); once A's socket recv buffer fills,
    //    that write blocks → the drain parks inside append HOLDING the log lock.
    //    Give the flood time to fill the buffer. ──
    thread::sleep(Duration::from_secs(4));

    // ── Differential proof. A fresh connection's KIND_NET_STATUS touches neither
    //    the sessions nor the log lock → the broker PROCESS is alive and answers
    //    fast. KIND_SESSIONS enumerates every session's log (`delivered_through`
    //    under the log lock) → it blocks on the parked session's held log lock →
    //    WEDGE. ──
    let broker_alive = probe(&name, KIND_NET_STATUS, KIND_NET_STATUS_REPLY, Duration::from_secs(5));
    let sessions_answered =
        probe(&name, KIND_SESSIONS, KIND_SESSIONS_REPLY, Duration::from_secs(5));

    eprintln!(
        "=== W1 DIAGNOSTIC: broker_alive(net_status)={broker_alive} \
         sessions_answered={sessions_answered} (expect alive=true, sessions=false = WEDGE) ==="
    );

    // Reap the flood child (scoped) + tear the broker down.
    if let Some(pid) = broker.session_pid(sid) {
        kill_pid(pid);
    }
    drop(a);

    assert!(
        broker_alive,
        "the broker PROCESS must still answer a fresh connection's net-status \
         (the wedge is per-session, not a dead process)"
    );
    // [int->REQ-HAZARD-INJECT-CONTROL-COEXIST]
    assert!(
        !sessions_answered,
        "REPRO: a backed-up controller parks the drain thread inside append() \
         HOLDING the log lock, so KIND_SESSIONS (which reads each log's \
         delivered_through) hangs — the session is wedged. \
         When this assertion FLIPS (sessions answered), the W1 fix has moved \
         controller delivery off the drain thread."
    );
}

#[test]
fn a_backed_up_controller_does_not_wedge_the_session() {
    let name = unique_name();
    let broker = Broker::bind(&name).expect("bind broker");
    let serve_broker = Arc::clone(&broker);
    thread::spawn(move || {
        let _ = serve_broker.serve(); // accept every connection on its own thread
    });

    // ── Controller connection A: spawn the flooding child. dispatch_spawn
    //    pre-attaches A as the CONTROLLER (become_controller, by=None). ──
    let mut a = None;
    for _ in 0..200 {
        match LocalSocketTransport::connect(&name) {
            Ok(c) => {
                a = Some(c);
                break;
            }
            Err(_) => thread::sleep(Duration::from_millis(10)),
        }
    }
    let mut a = a.expect("connect controller A");
    send_hello(&mut a, Role::Brain).expect("hello A");
    send(
        &mut a,
        KIND_SPAWN,
        serde_json::to_value(flood_spawn_req("wedge-ep")).unwrap(),
    );
    let sid = loop {
        let f = read_frame(&mut a).expect("frame before spawned");
        if f.kind == KIND_SPAWNED {
            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;
        }
    };

    // ── A now STOPS reading. The child floods → A's socket recv buffer fills →
    //    the controller writer thread blocks on it → the controller channel fills
    //    → the drain's bounded send_timeout starts blocking. PRE-FIX this parked
    //    the drain inside append HOLDING the log lock (permanent wedge). POST-FIX
    //    the send is OFF the log lock, so the session must stay responsive even
    //    while the controller is fully backed up. Probe DURING the stall (before
    //    CONTROLLER_WRITE_DEADLINE = 5s elapses). ──
    thread::sleep(Duration::from_secs(3));

    // A fresh connection's KIND_NET_STATUS touches neither the sessions nor the
    // log lock (proves the process is alive). KIND_SESSIONS enumerates every
    // session's log (`delivered_through`) under each log lock — PRE-FIX it hung on
    // the parked session's held lock; POST-FIX it answers because the drain no
    // longer holds the lock during the controller send.
    let broker_alive = probe(&name, KIND_NET_STATUS, KIND_NET_STATUS_REPLY, Duration::from_secs(5));
    let sessions_answered =
        probe(&name, KIND_SESSIONS, KIND_SESSIONS_REPLY, Duration::from_secs(5));

    eprintln!(
        "=== W1 GATE: broker_alive(net_status)={broker_alive} \
         sessions_answered={sessions_answered} (fixed = both true; pre-fix sessions=false) ==="
    );

    // Reap the flood child (scoped) + tear the broker down.
    if let Some(pid) = broker.session_pid(sid) {
        kill_pid(pid);
    }
    drop(a);

    assert!(
        broker_alive,
        "the broker PROCESS must answer a fresh connection's net-status"
    );
    // [int->REQ-HAZARD-INJECT-CONTROL-COEXIST]
    assert!(
        sessions_answered,
        "a fully-backed-up controller must NOT wedge the session: KIND_SESSIONS \
         (which reads each log's delivered_through under the log lock) must still \
         answer because the controller write is delivered OFF the log lock on a \
         dedicated writer thread (W1 fix). Pre-fix this hung — the inline \
         controller write parked the drain holding the log lock."
    );
}


#[test]
fn controller_backpressure_parks_the_drain_thread_and_wedges_the_session() {
    // Make the park instrumentation visible when run with --nocapture (cached on
    // first append; this test is the first/only appender in this binary).
    std::env::set_var("SPT_BROKER_DEBUG_PARK", "1");

    let name = unique_name();
    let broker = Broker::bind(&name).expect("bind broker");
    let serve_broker = Arc::clone(&broker);
    thread::spawn(move || {
        let _ = serve_broker.serve(); // accept every connection on its own thread
    });

    // ── Controller connection A: spawn the flooding child. dispatch_spawn
    //    pre-attaches A as the CONTROLLER (become_controller, by=None). ──
    let mut a = None;
    for _ in 0..200 {
        match LocalSocketTransport::connect(&name) {
            Ok(c) => {
                a = Some(c);
                break;
            }
            Err(_) => thread::sleep(Duration::from_millis(10)),
        }
    }
    let mut a = a.expect("connect controller A");
    send_hello(&mut a, Role::Brain).expect("hello A");
    send(
        &mut a,
        KIND_SPAWN,
        serde_json::to_value(flood_spawn_req("wedge-ep")).unwrap(),
    );
    let sid = loop {
        let f = read_frame(&mut a).expect("frame before spawned");
        if f.kind == KIND_SPAWNED {
            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;
        }
    };

    // ── A now STOPS reading. The child floods; the broker's drain thread calls
    //    append → write_frame(controller=A); once A's socket recv buffer fills,
    //    that write blocks → the drain parks inside append HOLDING the log lock.
    //    Give the flood time to fill the buffer. ──
    thread::sleep(Duration::from_secs(4));

    // ── Differential proof. A fresh connection's KIND_NET_STATUS touches neither
    //    the sessions nor the log lock → the broker PROCESS is alive and answers
    //    fast. KIND_SESSIONS enumerates every session's log (`delivered_through`
    //    under the log lock) → it blocks on the parked session's held log lock →
    //    WEDGE. ──
    let broker_alive = probe(&name, KIND_NET_STATUS, KIND_NET_STATUS_REPLY, Duration::from_secs(5));
    let sessions_answered =
        probe(&name, KIND_SESSIONS, KIND_SESSIONS_REPLY, Duration::from_secs(5));

    eprintln!(
        "=== W1 DIAGNOSTIC: broker_alive(net_status)={broker_alive} \
         sessions_answered={sessions_answered} (expect alive=true, sessions=false = WEDGE) ==="
    );

    // Reap the flood child (scoped) + tear the broker down.
    if let Some(pid) = broker.session_pid(sid) {
        kill_pid(pid);
    }
    drop(a);

    assert!(
        broker_alive,
        "the broker PROCESS must still answer a fresh connection's net-status \
         (the wedge is per-session, not a dead process)"
    );
    // [int->REQ-HAZARD-INJECT-CONTROL-COEXIST]
    assert!(
        !sessions_answered,
        "REPRO: a backed-up controller parks the drain thread inside append() \
         HOLDING the log lock, so KIND_SESSIONS (which reads each log's \
         delivered_through) hangs — the session is wedged. \
         When this assertion FLIPS (sessions answered), the W1 fix has moved \
         controller delivery off the drain thread."
    );
}

#[test]
fn a_backed_up_controller_does_not_wedge_the_session() {
    let name = unique_name();
    let broker = Broker::bind(&name).expect("bind broker");
    let serve_broker = Arc::clone(&broker);
    thread::spawn(move || {
        let _ = serve_broker.serve(); // accept every connection on its own thread
    });

    // ── Controller connection A: spawn the flooding child. dispatch_spawn
    //    pre-attaches A as the CONTROLLER (become_controller, by=None). ──
    let mut a = None;
    for _ in 0..200 {
        match LocalSocketTransport::connect(&name) {
            Ok(c) => {
                a = Some(c);
                break;
            }
            Err(_) => thread::sleep(Duration::from_millis(10)),
        }
    }
    let mut a = a.expect("connect controller A");
    send_hello(&mut a, Role::Brain).expect("hello A");
    send(
        &mut a,
        KIND_SPAWN,
        serde_json::to_value(flood_spawn_req("wedge-ep")).unwrap(),
    );
    let sid = loop {
        let f = read_frame(&mut a).expect("frame before spawned");
        if f.kind == KIND_SPAWNED {
            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;
        }
    };

    // ── A now STOPS reading. The child floods → A's socket recv buffer fills →
    //    the controller writer thread blocks on it → the controller channel fills
    //    → the drain's bounded send_timeout starts blocking. PRE-FIX this parked
    //    the drain inside append HOLDING the log lock (permanent wedge). POST-FIX
    //    the send is OFF the log lock, so the session must stay responsive even
    //    while the controller is fully backed up. Probe DURING the stall (before
    //    CONTROLLER_WRITE_DEADLINE = 5s elapses). ──
    thread::sleep(Duration::from_secs(3));

    // A fresh connection's KIND_NET_STATUS touches neither the sessions nor the
    // log lock (proves the process is alive). KIND_SESSIONS enumerates every
    // session's log (`delivered_through`) under each log lock — PRE-FIX it hung on
    // the parked session's held lock; POST-FIX it answers because the drain no
    // longer holds the lock during the controller send.
    let broker_alive = probe(&name, KIND_NET_STATUS, KIND_NET_STATUS_REPLY, Duration::from_secs(5));
    let sessions_answered =
        probe(&name, KIND_SESSIONS, KIND_SESSIONS_REPLY, Duration::from_secs(5));

    eprintln!(
        "=== W1 GATE: broker_alive(net_status)={broker_alive} \
         sessions_answered={sessions_answered} (fixed = both true; pre-fix sessions=false) ==="
    );

    // Reap the flood child (scoped) + tear the broker down.
    if let Some(pid) = broker.session_pid(sid) {
        kill_pid(pid);
    }
    drop(a);

    assert!(
        broker_alive,
        "the broker PROCESS must answer a fresh connection's net-status"
    );
    // [int->REQ-HAZARD-INJECT-CONTROL-COEXIST]
    assert!(
        sessions_answered,
        "a fully-backed-up controller must NOT wedge the session: KIND_SESSIONS \
         (which reads each log's delivered_through under the log lock) must still \
         answer because the controller write is delivered OFF the log lock on a \
         dedicated writer thread (W1 fix). Pre-fix this hung — the inline \
         controller write parked the drain holding the log lock."
    );
}

-    // Make the park instrumentation visible when run with --nocapture (cached on

-    // first append; this test is the first/only appender in this binary).

-    std::env::set_var("SPT_BROKER_DEBUG_PARK", "1");

-    // ── A now STOPS reading. The child floods; the broker's drain thread calls

-    //    append → write_frame(controller=A); once A's socket recv buffer fills,

-    //    that write blocks → the drain parks inside append HOLDING the log lock.

-    //    Give the flood time to fill the buffer. ──

-    thread::sleep(Duration::from_secs(4));

+    // ── A now STOPS reading. The child floods → A's socket recv buffer fills →

+    //    the controller writer thread blocks on it → the controller channel fills

+    //    → the drain's bounded send_timeout starts blocking. PRE-FIX this parked

+    //    the drain inside append HOLDING the log lock (permanent wedge). POST-FIX

+    //    the send is OFF the log lock, so the session must stay responsive even

+    //    while the controller is fully backed up. Probe DURING the stall (before

+    //    CONTROLLER_WRITE_DEADLINE = 5s elapses). ──

+    thread::sleep(Duration::from_secs(3));

-    // ── Differential proof. A fresh connection's KIND_NET_STATUS touches neither

-    //    the sessions nor the log lock → the broker PROCESS is alive and answers

-    //    fast. KIND_SESSIONS enumerates every session's log (`delivered_through`

-    //    under the log lock) → it blocks on the parked session's held log lock →

+    // A fresh connection's KIND_NET_STATUS touches neither the sessions nor the

+    // log lock (proves the process is alive). KIND_SESSIONS enumerates every

+    // session's log (`delivered_through`) under each log lock — PRE-FIX it hung on

+    // the parked session's held lock; POST-FIX it answers because the drain no

+    // longer holds the lock during the controller send.

-        "=== W1 DIAGNOSTIC: broker_alive(net_status)={broker_alive} \

-         sessions_answered={sessions_answered} (expect alive=true, sessions=false = WEDGE) ==="

+        "=== W1 GATE: broker_alive(net_status)={broker_alive} \

+         sessions_answered={sessions_answered} (fixed = both true; pre-fix sessions=false) ==="

-        "the broker PROCESS must still answer a fresh connection's net-status \

-         (the wedge is per-session, not a dead process)"

+        "the broker PROCESS must answer a fresh connection's net-status"

-        "REPRO: a backed-up controller parks the drain thread inside append() \

-         HOLDING the log lock, so KIND_SESSIONS (which reads each log's \

-         delivered_through) hangs — the session is wedged. \

-         When this assertion FLIPS (sessions answered), the W1 fix has moved \

-         controller delivery off the drain thread."

+        "a fully-backed-up controller must NOT wedge the session: KIND_SESSIONS \

+         (which reads each log's delivered_through under the log lock) must still \

+         answer because the controller write is delivered OFF the log lock on a \

+         dedicated writer thread (W1 fix). Pre-fix this hung — the inline \

+         controller write parked the drain holding the log lock."


Clean test 2 (instrumentation refs removed) + rename for accuracy.


204	}
205
206	#[test]
207	fn injecting_into_a_stdin_ignoring_child_parks_the_dispatch_thread_on_write_all() {
208	    // Park (b): write_input's blocking write_all on a full PTY input buffer.
209	    // Run with SPT_TERM_DEBUG_PARK=1 --nocapture to see PTY_WRITE_ENTER /
210	    // PTY_WRITE_LOCKED with NO PTY_WRITE_EXIT = the dispatch thread parked in
211	    // write_all at pty.rs:238.
212	    std::env::set_var("SPT_TERM_DEBUG_PARK", "1");
213
214	    let name = unique_name();
215	    let broker = Broker::bind(&name).expect("bind broker");
216	    let serve_broker = Arc::clone(&broker);
217	    thread::spawn(move || {
218	        let _ = serve_broker.serve();
219	    });
220
221	    // Controller A spawns the flood child (which IGNORES stdin) and then DRAINS
222	    // its frames forever on a background thread — so the output-side park (a)
223	    // does NOT fire and we isolate the input-side park (b).
224	    let mut a = None;
225	    for _ in 0..200 {
226	        match LocalSocketTransport::connect(&name) {
227	            Ok(c) => {
228	                a = Some(c);
229	                break;
230	            }
231	            Err(_) => thread::sleep(Duration::from_millis(10)),
232	        }
233	    }
234	    let mut a = a.expect("connect controller A");
235	    send_hello(&mut a, Role::Brain).expect("hello A");
236	    send(
237	        &mut a,
238	        KIND_SPAWN,
239	        serde_json::to_value(flood_spawn_req("inj-ep")).unwrap(),
240	    );
241	    let sid = loop {
242	        let f = read_frame(&mut a).expect("frame before spawned");
243	        if f.kind == KIND_SPAWNED {

}

#[test]
fn injecting_into_a_stdin_ignoring_child_parks_the_dispatch_thread_on_write_all() {
    // Park (b): write_input's blocking write_all on a full PTY input buffer.
    // Run with SPT_TERM_DEBUG_PARK=1 --nocapture to see PTY_WRITE_ENTER /
    // PTY_WRITE_LOCKED with NO PTY_WRITE_EXIT = the dispatch thread parked in
    // write_all at pty.rs:238.
    std::env::set_var("SPT_TERM_DEBUG_PARK", "1");

    let name = unique_name();
    let broker = Broker::bind(&name).expect("bind broker");
    let serve_broker = Arc::clone(&broker);
    thread::spawn(move || {
        let _ = serve_broker.serve();
    });

    // Controller A spawns the flood child (which IGNORES stdin) and then DRAINS
    // its frames forever on a background thread — so the output-side park (a)
    // does NOT fire and we isolate the input-side park (b).
    let mut a = None;
    for _ in 0..200 {
        match LocalSocketTransport::connect(&name) {
            Ok(c) => {
                a = Some(c);
                break;
            }
            Err(_) => thread::sleep(Duration::from_millis(10)),
        }
    }
    let mut a = a.expect("connect controller A");
    send_hello(&mut a, Role::Brain).expect("hello A");
    send(
        &mut a,
        KIND_SPAWN,
        serde_json::to_value(flood_spawn_req("inj-ep")).unwrap(),
    );
    let sid = loop {
        let f = read_frame(&mut a).expect("frame before spawned");
        if f.kind == KIND_SPAWNED {


#[test]
fn injecting_into_a_stdin_ignoring_child_parks_the_dispatch_thread_on_write_all() {
    // Park (b): write_input's blocking write_all on a full PTY input buffer.
    // Run with SPT_TERM_DEBUG_PARK=1 --nocapture to see PTY_WRITE_ENTER /
    // PTY_WRITE_LOCKED with NO PTY_WRITE_EXIT = the dispatch thread parked in
    // write_all at pty.rs:238.
    std::env::set_var("SPT_TERM_DEBUG_PARK", "1");

    let name = unique_name();

#[test]
fn injecting_a_large_payload_into_a_stdin_ignoring_child() {
    // Park (b) probe: write_input's blocking write_all on a full PTY input buffer.
    // PLATFORM-DEPENDENT (recorded inline at the assertions below): Windows ConPTY
    // ABSORBS the inject (no park, benign — the operator platform); Unix forkpty's
    // small kernel input queue is expected to park (real there).
    let name = unique_name();


#[test]
fn injecting_into_a_stdin_ignoring_child_parks_the_dispatch_thread_on_write_all() {
    // Park (b): write_input's blocking write_all on a full PTY input buffer.
    // Run with SPT_TERM_DEBUG_PARK=1 --nocapture to see PTY_WRITE_ENTER /
    // PTY_WRITE_LOCKED with NO PTY_WRITE_EXIT = the dispatch thread parked in
    // write_all at pty.rs:238.
    std::env::set_var("SPT_TERM_DEBUG_PARK", "1");

    let name = unique_name();

#[test]
fn injecting_a_large_payload_into_a_stdin_ignoring_child() {
    // Park (b) probe: write_input's blocking write_all on a full PTY input buffer.
    // PLATFORM-DEPENDENT (recorded inline at the assertions below): Windows ConPTY
    // ABSORBS the inject (no park, benign — the operator platform); Unix forkpty's
    // small kernel input queue is expected to park (real there).
    let name = unique_name();

-    // Park (b): write_input's blocking write_all on a full PTY input buffer.

-    // Run with SPT_TERM_DEBUG_PARK=1 --nocapture to see PTY_WRITE_ENTER /

-    // PTY_WRITE_LOCKED with NO PTY_WRITE_EXIT = the dispatch thread parked in

-    std::env::set_var("SPT_TERM_DEBUG_PARK", "1");

+    // Park (b) probe: write_input's blocking write_all on a full PTY input buffer.

+    // PLATFORM-DEPENDENT (recorded inline at the assertions below): Windows ConPTY

+    // ABSORBS the inject (no park, benign — the operator platform); Unix forkpty's

+    // small kernel input queue is expected to park (real there).

let broker = Broker::bind(&name).expect("bind broker");


Build + run the updated W1 gate (verifies the fix flips it to no-wedge). Background.


Bash

cargo test -p spt-daemon --test inject_control_wedge -- --test-threads=1 --nocapture 2>&1 | tail -40

Build + run W1 gate post-fix


Command running in background with ID: b6np90v0j. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\b6np90v0j.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>b6np90v0j</task-id>
<tool-use-id>toolu_01LDnwe8AYdEbNJtN8Un2BVd</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\b6np90v0j.output</output-file>
<status>completed</status>
<summary>Background command "Build + run W1 gate post-fix" completed (exit code 0)</summary>
</task-notification>


Check compile/run result.


1
2	error[E0308]: mismatched types
3	   --> crates\spt-daemon\src\broker.rs:336:13
4	    |
5	336 |             tx,
6	    |             ^^ expected `SyncSender<Envelope>`, found `SyncSender<CtrlMsg>`
7	    |
8	    = note: expected struct `SyncSender<Envelope>`
9	               found struct `SyncSender<CtrlMsg>`
10
11	error[E0308]: mismatched types
12	   --> crates\spt-daemon\src\broker.rs:456:45
13	    |
14	456 |                     let _ = old.tx.try_send(CtrlMsg::Control(frame));
15	    |                                    -------- ^^^^^^^^^^^^^^^^^^^^^^^ expected `Envelope`, found `CtrlMsg`
16	    |                                    |
17	    |                                    arguments to this method are incorrect
18	    |
19	help: the return type of this call is `CtrlMsg` due to the type of the argument passed
20	   --> crates\spt-daemon\src\broker.rs:456:29
21	    |
22	456 |                     let _ = old.tx.try_send(CtrlMsg::Control(frame));
23	    |                             ^^^^^^^^^^^^^^^^-----------------------^
24	    |                                             |
25	    |                                             this argument influences the return type of `try_send`
26	note: method defined here
27	   --> C:\Users\decid\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\std\src\sync\mpsc.rs:731:12
28	    |
29	731 |     pub fn try_send(&self, t: T) -> Result<(), TrySendError<T>> {
30	    |            ^^^^^^^^
31
32	error[E0658]: use of unstable library feature `std_internals`
33	   --> crates\spt-daemon\src\broker.rs:621:14
34	    |
35	621 |             .send_timeout(CtrlMsg::Output(self.seq, self.frame), CONTROLLER_WRITE_DEADLINE)
36	    |              ^^^^^^^^^^^^
37
38	Some errors have detailed explanations: E0063, E0308, E0432, E0658.
39	For more information about an error, try `rustc --explain E0063`.
40	error: could not compile `spt-daemon` (lib) due to 6 previous errors
41

error[E0308]: mismatched types
   --> crates\spt-daemon\src\broker.rs:336:13
    |
336 |             tx,
    |             ^^ expected `SyncSender<Envelope>`, found `SyncSender<CtrlMsg>`
    |
    = note: expected struct `SyncSender<Envelope>`
               found struct `SyncSender<CtrlMsg>`

error[E0308]: mismatched types
   --> crates\spt-daemon\src\broker.rs:456:45
    |
456 |                     let _ = old.tx.try_send(CtrlMsg::Control(frame));
    |                                    -------- ^^^^^^^^^^^^^^^^^^^^^^^ expected `Envelope`, found `CtrlMsg`
    |                                    |
    |                                    arguments to this method are incorrect
    |
help: the return type of this call is `CtrlMsg` due to the type of the argument passed
   --> crates\spt-daemon\src\broker.rs:456:29
    |
456 |                     let _ = old.tx.try_send(CtrlMsg::Control(frame));
    |                             ^^^^^^^^^^^^^^^^-----------------------^
    |                                             |
    |                                             this argument influences the return type of `try_send`
note: method defined here
   --> C:\Users\decid\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\std\src\sync\mpsc.rs:731:12
    |
731 |     pub fn try_send(&self, t: T) -> Result<(), TrySendError<T>> {
    |            ^^^^^^^^

error[E0658]: use of unstable library feature `std_internals`
   --> crates\spt-daemon\src\broker.rs:621:14
    |
621 |             .send_timeout(CtrlMsg::Output(self.seq, self.frame), CONTROLLER_WRITE_DEADLINE)
    |              ^^^^^^^^^^^^

Some errors have detailed explanations: E0063, E0308, E0432, E0658.
For more information about an error, try `rustc --explain E0063`.
error: could not compile `spt-daemon` (lib) due to 6 previous errors


Prompt:
<task-notification>
<task-id>b6np90v0j</task-id>
<tool-use-id>toolu_01LDnwe8AYdEbNJtN8Un2BVd</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\b6np90v0j.output</output-file>
<status>completed</status>
<summary>Background command "Build + run W1 gate post-fix" completed (exit code 0)</summary>
</task-notification>


Compile errors — `SyncSender::send_timeout` is unstable in std, the `tx` field type wasn't updated, and the `ControllerJob` literal misses `seq`. Fix: switch `deliver()` to a bounded `try_send` poll loop (std-stable), fix field type, add `seq`.


Edit

struct ControllerSink {
    /// The bounded live-frame queue to the controller's writer thread.
    tx: SyncSender<Envelope>,

struct ControllerSink {
    /// The bounded live-frame queue to the controller's writer thread.
    tx: SyncSender<CtrlMsg>,


struct ControllerSink {
    /// The bounded live-frame queue to the controller's writer thread.
    tx: SyncSender<Envelope>,

struct ControllerSink {
    /// The bounded live-frame queue to the controller's writer thread.
    tx: SyncSender<CtrlMsg>,

/// on a single full `try_send` — only after [`CONTROLLER_WRITE_DEADLINE`].

/// The controller's IPC send half — kept for identity matching on detach and

/// for the `exit`-frame fan-out ([`OutputLog::all_sinks`]).


use std::sync::mpsc::{sync_channel, Receiver, SendTimeoutError, SyncSender, TrySendError};
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::time::Duration;

use std::sync::mpsc::{sync_channel, Receiver, SyncSender, TrySendError};
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};


use std::sync::mpsc::{sync_channel, Receiver, SendTimeoutError, SyncSender, TrySendError};
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::time::Duration;

use std::sync::mpsc::{sync_channel, Receiver, SyncSender, TrySendError};
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};

-use std::sync::mpsc::{sync_channel, Receiver, SendTimeoutError, SyncSender, TrySendError};

+use std::sync::mpsc::{sync_channel, Receiver, SyncSender, TrySendError};

use interprocess::local_socket::{prelude::*, SendHalf, Stream};

use spt_term::{Drain, PtySession, SessionSurface, SurfaceSize};


        // Controller: hand the frame to the caller for an off-lock, bounded send
        // into the controller writer thread's channel (which does the blocking
        // socket write + advances the authoritative cursor). The drain never
        // blocks under the log lock again.
        self.controller.as_ref().map(|c| ControllerJob {
            tx: c.tx.clone(),
            frame,
            epoch: self.controller_epoch,
        })

        // Controller: hand the frame to the caller for an off-lock, bounded send
        // into the controller writer thread's channel (which does the blocking
        // socket write + advances the authoritative cursor). The drain never
        // blocks under the log lock again.
        self.controller.as_ref().map(|c| ControllerJob {
            tx: c.tx.clone(),
            seq,
            frame,
            epoch: self.controller_epoch,
        })


        // Controller: hand the frame to the caller for an off-lock, bounded send
        // into the controller writer thread's channel (which does the blocking
        // socket write + advances the authoritative cursor). The drain never
        // blocks under the log lock again.
        self.controller.as_ref().map(|c| ControllerJob {
            tx: c.tx.clone(),
            frame,
            epoch: self.controller_epoch,
        })

        // Controller: hand the frame to the caller for an off-lock, bounded send
        // into the controller writer thread's channel (which does the blocking
        // socket write + advances the authoritative cursor). The drain never
        // blocks under the log lock again.
        self.controller.as_ref().map(|c| ControllerJob {
            tx: c.tx.clone(),
            seq,
            frame,
            epoch: self.controller_epoch,
        })


Rewrite `deliver()` as a bounded `try_send` poll loop (std-stable). Add the poll interval const.


    /// Deliver into the controller's writer-thread channel with a bounded
    /// deadline. `Ok(())` = queued (the writer does the blocking socket write);
    /// `Err(epoch)` = the controller is fully stalled or gone and must be
    /// detached (the wedge is thereby BOUNDED, never permanent — the v0.12.x
    /// root). A live-but-slow controller drains a slot before the deadline and is
    /// untouched.
    fn deliver(self) -> Result<(), u64> {
        match self
            .tx
            .send_timeout(CtrlMsg::Output(self.seq, self.frame), CONTROLLER_WRITE_DEADLINE)
        {
            Ok(()) => Ok(()),
            Err(SendTimeoutError::Timeout(_)) | Err(SendTimeoutError::Disconnected(_)) => {
                Err(self.epoch)
            }
        }
    }
}

    /// Deliver into the controller's writer-thread channel with a bounded
    /// deadline. `Ok(())` = queued (the writer does the blocking socket write);
    /// `Err(epoch)` = the controller is fully stalled or gone and must be
    /// detached (the wedge is thereby BOUNDED, never permanent — the v0.12.x
    /// root). A live-but-slow controller drains a slot before the deadline and is
    /// untouched.
    ///
    /// A `try_send` poll loop (std `SyncSender` has no stable `send_timeout`): the
    /// common case (channel has room) succeeds on the first `try_send` with zero
    /// added latency; only a FULL channel polls every [`CONTROLLER_SEND_POLL`] up
    /// to [`CONTROLLER_WRITE_DEADLINE`]. This runs OFF the log lock (in the drain
    /// closure), so a stalled controller never holds the lock.
    fn deliver(self) -> Result<(), u64> {
        let mut msg = CtrlMsg::Output(self.seq, self.frame);
        let deadline = Instant::now() + CONTROLLER_WRITE_DEADLINE;
        loop {
            match self.tx.try_send(msg) {
                Ok(()) => return Ok(()),
                Err(TrySendError::Disconnected(_)) => return Err(self.epoch),
                Err(TrySendError::Full(returned)) => {
                    if Instant::now() >= deadline {
                        return Err(self.epoch);
                    }
                    msg = returned;
                    thread::sleep(CONTROLLER_SEND_POLL);
                }
            }
        }
    }
}


    /// Deliver into the controller's writer-thread channel with a bounded
    /// deadline. `Ok(())` = queued (the writer does the blocking socket write);
    /// `Err(epoch)` = the controller is fully stalled or gone and must be
    /// detached (the wedge is thereby BOUNDED, never permanent — the v0.12.x
    /// root). A live-but-slow controller drains a slot before the deadline and is
    /// untouched.
    fn deliver(self) -> Result<(), u64> {
        match self
            .tx
            .send_timeout(CtrlMsg::Output(self.seq, self.frame), CONTROLLER_WRITE_DEADLINE)
        {
            Ok(()) => Ok(()),
            Err(SendTimeoutError::Timeout(_)) | Err(SendTimeoutError::Disconnected(_)) => {
                Err(self.epoch)
            }
        }
    }
}

    /// Deliver into the controller's writer-thread channel with a bounded
    /// deadline. `Ok(())` = queued (the writer does the blocking socket write);
    /// `Err(epoch)` = the controller is fully stalled or gone and must be
    /// detached (the wedge is thereby BOUNDED, never permanent — the v0.12.x
    /// root). A live-but-slow controller drains a slot before the deadline and is
    /// untouched.
    ///
    /// A `try_send` poll loop (std `SyncSender` has no stable `send_timeout`): the
    /// common case (channel has room) succeeds on the first `try_send` with zero
    /// added latency; only a FULL channel polls every [`CONTROLLER_SEND_POLL`] up
    /// to [`CONTROLLER_WRITE_DEADLINE`]. This runs OFF the log lock (in the drain
    /// closure), so a stalled controller never holds the lock.
    fn deliver(self) -> Result<(), u64> {
        let mut msg = CtrlMsg::Output(self.seq, self.frame);
        let deadline = Instant::now() + CONTROLLER_WRITE_DEADLINE;
        loop {
            match self.tx.try_send(msg) {
                Ok(()) => return Ok(()),
                Err(TrySendError::Disconnected(_)) => return Err(self.epoch),
                Err(TrySendError::Full(returned)) => {
                    if Instant::now() >= deadline {
                        return Err(self.epoch);
                    }
                    msg = returned;
                    thread::sleep(CONTROLLER_SEND_POLL);
                }
            }
        }
    }
}

+    /// A `try_send` poll loop (std `SyncSender` has no stable `send_timeout`): the

+    /// common case (channel has room) succeeds on the first `try_send` with zero

+    /// added latency; only a FULL channel polls every [`CONTROLLER_SEND_POLL`] up

+    /// to [`CONTROLLER_WRITE_DEADLINE`]. This runs OFF the log lock (in the drain

+    /// closure), so a stalled controller never holds the lock.

-            .send_timeout(CtrlMsg::Output(self.seq, self.frame), CONTROLLER_WRITE_DEADLINE)

-            Err(SendTimeoutError::Timeout(_)) | Err(SendTimeoutError::Disconnected(_)) => {

+        let mut msg = CtrlMsg::Output(self.seq, self.frame);

+        let deadline = Instant::now() + CONTROLLER_WRITE_DEADLINE;

+            match self.tx.try_send(msg) {

+                Err(TrySendError::Disconnected(_)) => return Err(self.epoch),

+                Err(TrySendError::Full(returned)) => {

+                    if Instant::now() >= deadline {

+                        return Err(self.epoch);

+                    thread::sleep(CONTROLLER_SEND_POLL);


const CONTROLLER_WRITE_DEADLINE: Duration = Duration::from_secs(5);

const CONTROLLER_WRITE_DEADLINE: Duration = Duration::from_secs(5);

/// Poll interval for the controller `try_send` deadline loop (std `SyncSender`
/// has no stable `send_timeout`). Only used while the channel is FULL — the
/// common (has-room) path never sleeps.
const CONTROLLER_SEND_POLL: Duration = Duration::from_millis(5);


const CONTROLLER_WRITE_DEADLINE: Duration = Duration::from_secs(5);

/// Poll interval for the controller `try_send` deadline loop (std `SyncSender`
/// has no stable `send_timeout`). Only used while the channel is FULL — the
/// common (has-room) path never sleeps.
const CONTROLLER_SEND_POLL: Duration = Duration::from_millis(5);

/// merely slow operator drains a slot before this elapses and is untouched.

+/// Poll interval for the controller `try_send` deadline loop (std `SyncSender`

+/// has no stable `send_timeout`). Only used while the channel is FULL — the

+/// common (has-room) path never sleeps.

+const CONTROLLER_SEND_POLL: Duration = Duration::from_millis(5);

/// The interactive CONTROLLER's isolated sink (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).

/// Symmetric with [`ViewerSink`] — a dedicated writer thread does the BLOCKING

/// socket write so a backed-up controller can never stall the session drain


Bash

cargo test -p spt-daemon --test inject_control_wedge -- --test-threads=1 --nocapture 2>&1 | tail -45

Rebuild + run W1 gate post-fix


Command running in background with ID: bxmfw1rc1. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\bxmfw1rc1.output. You will be notified when it completes. To check interim output, use Read on that file path.


Check rebuild result.


<system-reminder>Warning: the file exists but is shorter than the provided offset (1). The file has 1 lines.</system-reminder>


Compiling. Wait for completion.


until [ -s "C:/Users/decid/AppData/Local/Temp/claude/C--Users-decid-Documents-projects-spt-core/73618172-412e-4954-b8aa-e2c8955dfd49/tasks/bxmfw1rc1.output" ] && grep -qE "test result:|error\[|error:|panicked" "C:/Users/decid/AppData/Local/Temp/claude/C--Users-decid-Documents-projects-spt-core/73618172-412e-4954-b8aa-e2c8955dfd49/tasks/bxmfw1rc1.output"; do sleep 3; done; echo "=== DONE ==="

Wait for W1 gate test to finish


Command running in background with ID: b35sqkzmj. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\b35sqkzmj.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>bxmfw1rc1</task-id>
<tool-use-id>toolu_01BbSnGaDAxEUMU4f2L2wD2e</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\bxmfw1rc1.output</output-file>
<status>completed</status>
<summary>Background command "Rebuild + run W1 gate post-fix" completed (exit code 0)</summary>
</task-notification>


<task-notification>
<task-id>b35sqkzmj</task-id>
<tool-use-id>toolu_01CyDqXjuHUPnvPViMR1XyJD</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\b35sqkzmj.output</output-file>
<status>completed</status>
<summary>Background command "Wait for W1 gate test to finish" completed (exit code 0)</summary>
</task-notification>


Waiting on the build+test. I'll be notified when it completes.


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


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


1	   Compiling spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
2	    Finished `test` profile [unoptimized + debuginfo] target(s) in 8.03s
3	     Running tests\inject_control_wedge.rs (target\debug\deps\inject_control_wedge-237ab1e3f78e76cc.exe)
4
5	running 2 tests
6	test a_backed_up_controller_does_not_wedge_the_session ... === W1 GATE: broker_alive(net_status)=true sessions_answered=true (fixed = both true; pre-fix sessions=false) ===
7	ok
8	test injecting_a_large_payload_into_a_stdin_ignoring_child ... ENDPOINT_INJECT:inj-ep (4194304 bytes, idle-direct — activity not yet gated)
9	=== W1 PARK-B DIAGNOSTIC: inject_acked=true broker_alive=true (expect inject_acked=false = write_input parked on write_all) ===
10	ok
11
12	test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.77s
13
14

   Compiling spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 8.03s
     Running tests\inject_control_wedge.rs (target\debug\deps\inject_control_wedge-237ab1e3f78e76cc.exe)

running 2 tests
test a_backed_up_controller_does_not_wedge_the_session ... === W1 GATE: broker_alive(net_status)=true sessions_answered=true (fixed = both true; pre-fix sessions=false) ===
ok
test injecting_a_large_payload_into_a_stdin_ignoring_child ... ENDPOINT_INJECT:inj-ep (4194304 bytes, idle-direct — activity not yet gated)
=== W1 PARK-B DIAGNOSTIC: inject_acked=true broker_alive=true (expect inject_acked=false = write_input parked on write_all) ===
ok

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


✅ Fix verified: `sessions_answered=true` now (pre-fix: false). Backed-up controller no longer wedges. Both tests pass.

Now regression-check: the OutputLog/controller refactor touches attach/dispatch/resume/brain-swap paths. Run the full spt-daemon + spt-term suites.


Run spt-term test suite


Command running in background with ID: b9mdsvfyw. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\b9mdsvfyw.output. You will be notified when it completes. To check interim output, use Read on that file path.


cargo test -p spt-daemon --test broker --test attach --test dispatch --test resume --test brain_swap --test idempotent 2>&1 | tail -50

Run core broker/attach/dispatch/resume daemon tests


Command running in background with ID: b3iv91wlr. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\b3iv91wlr.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>b9mdsvfyw</task-id>
<tool-use-id>toolu_01NPxg2PHaN11sNGBMy8uQ7K</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\b9mdsvfyw.output</output-file>
<status>completed</status>
<summary>Background command "Run spt-term test suite" completed (exit code 0)</summary>
</task-notification>


Regression suites running. Meanwhile, add traceability evidence (doyle: activate [impl,unit,int] on build). Check for an existing unit-test module + the advanced_cursor test tag.


231:fn advanced_cursor(current: u64, seq: u64, write_ok: bool) -> u64 {
1714:#[cfg(test)]
1715:mod tests {
1723:    // [unit->REQ-HAZARD-BROKER-PROCESS-ISOLATION]


Prompt:
<task-notification>
<task-id>b9mdsvfyw</task-id>
<tool-use-id>toolu_01NPxg2PHaN11sNGBMy8uQ7K</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\b9mdsvfyw.output</output-file>
<status>completed</status>
<summary>Background command "Run spt-term test suite" completed (exit code 0)</summary>
</task-notification>


<task-notification>
<task-id>b3iv91wlr</task-id>
<tool-use-id>toolu_015bbnYyLW6aUpLUYwq6xVrQ</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\b3iv91wlr.output</output-file>
<status>completed</status>
<summary>Background command "Run core broker/attach/dispatch/resume daemon tests" completed (exit code 0)</summary>
</task-notification>


1	test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s
2
3	     Running tests\stream.rs (target\debug\deps\stream-8f92fec383861bc2.exe)
4
5	running 2 tests
6	test delivers_every_value_under_resize ... ok
7	test bounded_backpressure_stalls_an_unconsumed_reader ... ok
8
9	test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.02s
10
11	     Running tests\surface.rs (target\debug\deps\surface-3ea8c9e5cb711e73.exe)
12
13	running 1 test
14	test resize_under_load_does_not_hang_or_panic ... ok
15
16	test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s
17
18	     Running tests\winspawn.rs (target\debug\deps\winspawn-547761d422d6f43f.exe)
19
20	running 1 test
21	test pty_spawns_a_cmd_script_via_cmd_wrap ... ok
22
23	test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
24
25	   Doc-tests spt_term
26
27	running 0 tests
28
29	test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
30
31

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

     Running tests\stream.rs (target\debug\deps\stream-8f92fec383861bc2.exe)

running 2 tests
test delivers_every_value_under_resize ... ok
test bounded_backpressure_stalls_an_unconsumed_reader ... ok

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

     Running tests\surface.rs (target\debug\deps\surface-3ea8c9e5cb711e73.exe)

running 1 test
test resize_under_load_does_not_hang_or_panic ... ok

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

     Running tests\winspawn.rs (target\debug\deps\winspawn-547761d422d6f43f.exe)

running 1 test
test pty_spawns_a_cmd_script_via_cmd_wrap ... ok

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

   Doc-tests spt_term

running 0 tests

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


1714	#[cfg(test)]
1715	mod tests {
1716	    use super::*;
1717
1718	    /// The cursor-advance kernel (restoration D4-1, doyle amendments 1–3):
1719	    /// advance to `seq+1` ONLY on a successful write, monotonic, frozen on a
1720	    /// failed write. This is the decision the old `let _ = write_frame(...)`
1721	    /// swallow dropped — advancing past a failed write would degrade
1722	    /// at-least-once output to at-most-once (the Q6 failure class).
1723	    // [unit->REQ-HAZARD-BROKER-PROCESS-ISOLATION]
1724	    #[test]
1725	    fn delivered_cursor_advances_only_on_ok_and_is_monotonic() {
1726	        // Fresh log: cursor at 0.
1727	        let mut c = 0u64;
1728	        // Amendment 1: a successful write advances to seq+1.
1729	        c = advanced_cursor(c, 0, true);
1730	        assert_eq!(c, 1, "ok write of seq 0 -> cursor 1");
1731	        c = advanced_cursor(c, 1, true);
1732	        assert_eq!(c, 2, "ok write of seq 1 -> cursor 2");
1733	        // Amendment 1: a FAILED write (dying-but-not-detached subscriber) freezes
1734	        // the cursor — the next chunk is NOT marked delivered, so resume replays
1735	        // it (nothing skipped). This is the exact mid-stream-socket-death case.
1736	        let frozen = advanced_cursor(c, 2, false);
1737	        assert_eq!(frozen, 2, "failed write of seq 2 -> cursor stays at 2");
1738	        // A later successful write resumes advancing from the frozen point.
1739	        assert_eq!(
1740	            advanced_cursor(frozen, 2, true),
1741	            3,
1742	            "ok re-send of seq 2 -> 3"
1743	        );
1744	        // Amendment 3: monotonic — an attach(sub, 0) rewind re-sends seq 0 (a
1745	        // successful write of an already-delivered seq) but cannot LOWER the
1746	        // cursor (max keeps it forward), preventing a resume-cursor reset.
1747	        assert_eq!(
1748	            advanced_cursor(2, 0, true),
1749	            2,
1750	            "rewind re-send never decreases"
1751	        );
1752	        assert_eq!(
1753	            advanced_cursor(2, 1, true),
1754	            2,
1755	            "boundary re-send never decreases"
1756	        );
1757	    }
1758
1759	    /// The viewer fan-out eviction decision (REQ-HAZARD-VIEWER-ISOLATION): the
1760	    /// drain `try_send`s into each viewer's bounded queue and evicts on a FULL
1761	    /// queue (the viewer fell behind the live stream) or a DISCONNECTED one (its
1762	    /// writer thread ended). It NEVER blocks — a wedged viewer backs up only its
1763	    /// own queue, never the drain thread / controller / child.
1764	    // [unit->REQ-HAZARD-VIEWER-ISOLATION]
1765	    #[test]
1766	    fn viewer_overflow_or_disconnect_evicts_never_blocks() {
1767	        // Depth-1 queue: the first send fits; a second (the viewer hasn't drained)
1768	        // is `Full` → evict.
1769	        let (tx, rx) = sync_channel::<u8>(1);
1770	        tx.try_send(1).expect("first fits");
1771	        assert!(
1772	            viewer_send_evicts(&tx.try_send(2)),
1773	            "a full viewer queue evicts (drain does not block)"

#[cfg(test)]
mod tests {
    use super::*;

    /// The cursor-advance kernel (restoration D4-1, doyle amendments 1–3):
    /// advance to `seq+1` ONLY on a successful write, monotonic, frozen on a
    /// failed write. This is the decision the old `let _ = write_frame(...)`
    /// swallow dropped — advancing past a failed write would degrade
    /// at-least-once output to at-most-once (the Q6 failure class).
    // [unit->REQ-HAZARD-BROKER-PROCESS-ISOLATION]
    #[test]
    fn delivered_cursor_advances_only_on_ok_and_is_monotonic() {
        // Fresh log: cursor at 0.
        let mut c = 0u64;
        // Amendment 1: a successful write advances to seq+1.
        c = advanced_cursor(c, 0, true);
        assert_eq!(c, 1, "ok write of seq 0 -> cursor 1");
        c = advanced_cursor(c, 1, true);
        assert_eq!(c, 2, "ok write of seq 1 -> cursor 2");
        // Amendment 1: a FAILED write (dying-but-not-detached subscriber) freezes
        // the cursor — the next chunk is NOT marked delivered, so resume replays
        // it (nothing skipped). This is the exact mid-stream-socket-death case.
        let frozen = advanced_cursor(c, 2, false);
        assert_eq!(frozen, 2, "failed write of seq 2 -> cursor stays at 2");
        // A later successful write resumes advancing from the frozen point.
        assert_eq!(
            advanced_cursor(frozen, 2, true),
            3,
            "ok re-send of seq 2 -> 3"
        );
        // Amendment 3: monotonic — an attach(sub, 0) rewind re-sends seq 0 (a
        // successful write of an already-delivered seq) but cannot LOWER the
        // cursor (max keeps it forward), preventing a resume-cursor reset.
        assert_eq!(
            advanced_cursor(2, 0, true),
            2,
            "rewind re-send never decreases"
        );
        assert_eq!(
            advanced_cursor(2, 1, true),
            2,
            "boundary re-send never decreases"
        );
    }

    /// The viewer fan-out eviction decision (REQ-HAZARD-VIEWER-ISOLATION): the
    /// drain `try_send`s into each viewer's bounded queue and evicts on a FULL
    /// queue (the viewer fell behind the live stream) or a DISCONNECTED one (its
    /// writer thread ended). It NEVER blocks — a wedged viewer backs up only its
    /// own queue, never the drain thread / controller / child.
    // [unit->REQ-HAZARD-VIEWER-ISOLATION]
    #[test]
    fn viewer_overflow_or_disconnect_evicts_never_blocks() {
        // Depth-1 queue: the first send fits; a second (the viewer hasn't drained)
        // is `Full` → evict.
        let (tx, rx) = sync_channel::<u8>(1);
        tx.try_send(1).expect("first fits");
        assert!(
            viewer_send_evicts(&tx.try_send(2)),
            "a full viewer queue evicts (drain does not block)"


Prompt:
<task-notification>
<task-id>b3iv91wlr</task-id>
<tool-use-id>toolu_015bbnYyLW6aUpLUYwq6xVrQ</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\b3iv91wlr.output</output-file>
<status>completed</status>
<summary>Background command "Run core broker/attach/dispatch/resume daemon tests" completed (exit code 0)</summary>
</task-notification>


spt-term green. Check the core daemon tests.


1
2	running 1 test
3	test brain_only_update_swaps_logic_with_zero_endpoint_interruption ... ok
4
5	test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s
6
7	     Running tests\broker.rs (target\debug\deps\broker-54c297b3f204878a.exe)
8
9	running 5 tests
10	test endpoint_keyed_inject_reaches_hosted_pty ... ok
11	test broker_hosts_pty_child_streams_output_and_accepts_input ... ok
12	test dead_session_subscribe_does_not_hang ... ok
13	test spawn_env_reaches_child ... ok
14	test wall_b_endpoint_run_env_then_send_reaches_pty ... ok
15
16	test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.10s
17
18	     Running tests\dispatch.rs (target\debug\deps\dispatch-f6cc9643b7d50f23.exe)
19
20	running 11 tests
21	test classify_routes_a_node_label_feed_to_registry ... ok
22	test dispatcher_funnels_wan_messages_undriven ... ok
23	test dispatcher_surfaces_a_remote_won_notif_at_the_winning_node ... ok
24	test dispatcher_survives_an_unknown_stream ... ok
25	test dispatcher_serves_a_file_fetch_undriven ... ok
26	test dispatcher_serves_a_sync_pull_undriven ... ok
27	test dispatcher_serves_a_cross_node_shell_link ... ok
28	test dispatcher_applies_a_notif_feed_undriven ... ok
29	test dispatcher_serves_a_remote_drive_attach_undriven ... ok
30	test dispatcher_serves_an_update_pull_undriven ... ok
31	test dispatcher_serves_a_subnet_serve_probe ... ok
32
33	test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.46s
34
35	     Running tests\idempotent.rs (target\debug\deps\idempotent-5ae4715fbd3c2d1f.exe)
36
37	running 1 test
38	test pty_writes_are_exactly_once_across_brain_crashes ... ok
39
40	test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s
41
42	     Running tests\resume.rs (target\debug\deps\resume-08176e81096788f8.exe)
43
44	running 3 tests
45	test resume_mode_brain_spawning_new_sessions_delivers_each ... ok
46	test cold_start_resumes_all_sessions_from_the_broker_cursor ... ok
47	test resume_below_ring_floor_clamps_forward_without_gap_error ... ok
48
49	test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.45s
50
51

running 1 test
test brain_only_update_swaps_logic_with_zero_endpoint_interruption ... ok

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

     Running tests\broker.rs (target\debug\deps\broker-54c297b3f204878a.exe)

running 5 tests
test endpoint_keyed_inject_reaches_hosted_pty ... ok
test broker_hosts_pty_child_streams_output_and_accepts_input ... ok
test dead_session_subscribe_does_not_hang ... ok
test spawn_env_reaches_child ... ok
test wall_b_endpoint_run_env_then_send_reaches_pty ... ok

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

     Running tests\dispatch.rs (target\debug\deps\dispatch-f6cc9643b7d50f23.exe)

running 11 tests
test classify_routes_a_node_label_feed_to_registry ... ok
test dispatcher_funnels_wan_messages_undriven ... ok
test dispatcher_surfaces_a_remote_won_notif_at_the_winning_node ... ok
test dispatcher_survives_an_unknown_stream ... ok
test dispatcher_serves_a_file_fetch_undriven ... ok
test dispatcher_serves_a_sync_pull_undriven ... ok
test dispatcher_serves_a_cross_node_shell_link ... ok
test dispatcher_applies_a_notif_feed_undriven ... ok
test dispatcher_serves_a_remote_drive_attach_undriven ... ok
test dispatcher_serves_an_update_pull_undriven ... ok
test dispatcher_serves_a_subnet_serve_probe ... ok

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

     Running tests\idempotent.rs (target\debug\deps\idempotent-5ae4715fbd3c2d1f.exe)

running 1 test
test pty_writes_are_exactly_once_across_brain_crashes ... ok

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

     Running tests\resume.rs (target\debug\deps\resume-08176e81096788f8.exe)

running 3 tests
test resume_mode_brain_spawning_new_sessions_delivers_each ... ok
test cold_start_resumes_all_sessions_from_the_broker_cursor ... ok
test resume_below_ring_floor_clamps_forward_without_gap_error ... ok

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


All core daemon tests green (broker 5, dispatch 11, idempotent, resume 3, brain_swap, attach). No regressions. Now add traceability evidence: impl tag + a unit test for the W1 delivery kernel, then activate the REQ.