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

required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 P1b (todlando, operator-ruled scroll fix bundled with the ack-deadlock; doyle gates). NO int (live console mouse = HITL, REQ-RUN-PICKER/RC-1 precedent; the pure translator + scan unit-tested). impl = (cfg(windows)) MouseMode{enabled,sgr atomics} + MouseModeScanner (carry-buffer DECSET scan, parse_decset_private/apply_mouse_mode) fed from the pump's output-render path + scroll_dir + scroll_sgr (SGR encode) + the spawn_stdin_reader_events scroll arm (forward when enabled&&sgr) threaded via Arc<MouseMode> (pump writes / reader reads). unit = scroll_dir classify (wheel-only) + scroll_sgr EXACT xterm bytes (up=64/down=65, col+1/row+1) + DECSET scan (1000/1002/1003/1006 set&reset, combined ESC[?1002;1006h, mixed stream, and SPLIT across two feeds — the carry-buffer edge). doc = KNOWN-HAZARDS entry (rc Windows scroll forwarded as SGR when the harness reports mouse; capture is for right-click paste). doyle gates; operator HITL (scroll works in a real CC rc session; right-click still pastes)."

# ── P1c (operator-ruled root-fix before v0.13.0 ship; doyle root-cause via instrumented repro on kitsubito, 2026-06-20): controller-writer reorder on brain-restart re-serve ──
[[requirements]]
id = "REQ-HAZARD-CONTROLLER-WRITER-REORDER"
title = "Two `controller_writer` threads must never race ONE brain↔broker connection's socket. ROOT (doyle, instrumented RACEDIAG repro on kitsubito): on a brain-restart re-serve the handoff brain registers as controller on the SAME session TWICE over the SAME `Brain::conn` socket — (1) `Brain::handoff` eagerly `subscribe(prior.session_id, prior.next_seq=1)` → `become_controller(from_seq=1)`, initial=[1], spawns writer-A (writes seq 1); (2) `serve_attach` re-handles the replayed `Request{from_seq:0}` → `attach_as(sid,0)` → `become_controller(from_seq=0)`, initial=[0,1], spawns writer-B (writes 0 then 1). `become_controller` (broker.rs) drops the prior `ControllerSink` (its `tx`) but does NOT stop the prior writer thread — writer-A keeps flushing its owned `initial` batch, and both writers hold clones of the same `SharedSend` (`Arc<Mutex<socket>>`) with NO inter-thread ordering. When writer-A's seq 1 wins the socket before writer-B's seq 0, the strict legacy consumer (brain.rs read_event reject-gap path) sees `output gap: got seq 1 want 0` → the test `attach_survives_target_brain_restart_exactly_once` panics at `.expect(\"re-serve\")` OR HANGS in `render_until` (serve thread died on the gap → MARKER_TWO never reaches the wire). `prior.next_seq` is life1's CONSUMPTION cursor, NOT life2's connection state — life2's socket has been sent NOTHING, so a `from_seq=0` full replay on a connection that already streamed seq 1 is contradictory. Snap-above tolerance ALONE can't fix it (it would dedup-drop the late seq 0 → byte loss → the exactly-once byte-identity assert fails). PRE-EXISTING, surfaced by the v0.13.0 green-both-runners release gate; P1b is INNOCENT (its diff touches only input-ack machinery, proven mechanically + the test passes post-P1b in isolation). Sibling flaky cluster: `inject_control_wedge::g2`, `broker::spawn_env_reaches_child`. INVARIANT: on a single brain↔broker connection the controller output-frame stream is monotonic non-decreasing in seq (modulo dedup re-sends); exactly ONE `controller_writer` is ever live per connection; a SUPERSEDED writer writes NO further frames; a re-serve never replays a seq below what the connection already received. FIX (doyle design, V0.13.0-P1C-CONTROLLER-WRITER-REORDER-PLAN.md, three parts): (1) PRIMARY — `Brain::handoff` no longer eagerly `subscribe`s-as-controller; `serve_attach`'s `attach_as(sid,0)` is the SOLE controller registration + replay for that connection (handoff is test-only per the brain.rs comment; no production caller depends on the eager subscribe — production resumes via `cold_start`+`resume_sessions`). (2) BROKER HARDENING — `controller_writer` is epoch-gated: `controller_epoch` becomes a shared `Arc<AtomicU64>`, the writer snapshots its own epoch and checks the shared one before each frame (initial-batch AND live loop), returning the moment it is superseded — so a double-registration can never silently keep a superseded writer flushing (W1-safe: never blocks the drain under `Mutex<OutputLog>`). (3) DEFENSE-IN-DEPTH — `Brain::handoff` seeds `session_cursors` at `prior.next_seq` so the consumer uses the production dedup-below+snap-above path (matching `resume_sessions`), not the strict reject-gap legacy trap. (v0.13.0)"
required_stages = ["doc", "impl", "unit", "int"]  # ACTIVATED v0.13.0 P1c (todlando, the last v0.13.0 ship-blocker — pre-existing flaky resume race that gates green-both-runners; doyle root-caused + designed, doyle gates). doc = KNOWN-HAZARDS entry (the reorder + the single-live-writer-per-connection invariant + the two compounding defects) + CONTEXT note. impl = (1) Brain::handoff drops the eager subscribe(prior.session_id, prior.next_seq) call; (2) OutputLog.controller_epoch -> Arc<AtomicU64>, become_controller passes the new epoch + Arc::clone into controller_writer, which returns when controller_epoch != its snapshot (both the initial-batch loop and the live recv loop), become_controller/mark_controller_gone/the ControllerJob epoch read switch to atomic load/fetch_add; (3) Brain::handoff seeds session_cursors.insert(prior.session_id, prior.next_seq). unit = epoch-gated controller_writer: a superseded writer (epoch bumped) writes NOTHING from its initial batch (real broker socket, two become_controller on one connection -> only the latest writer's monotonic 0,1 reaches the wire) + handoff seeds session_cursors (session_cursor(sid) == prior.next_seq) + handoff issues no eager subscribe. int = the keystone: deterministically force two become_controller-on-one-connection (handoff-then-reserve ordering) on a real broker+brain, NO mocks -> PRE-FIX the wire reorders (got seq 1 want 0 / gap or byte-drop) -> POST-FIX the wire stays monotonic + byte-exact + the session stays live; PLUS attach_survives_target_brain_restart_exactly_once green (doyle runs it 20x isolated single-threaded timeout-wrapped on Linux/kitsubito — the deterministic RED-on-revert carrier). Full seam sweep (attach/broker/resume/brain_swap/inject_control_wedge — the shared W1 controller-writer model). clippy/traceable/docs-drift."

title = "On Windows, `spt rc` must FORWARD scroll-wheel events to the harness when the harness has mouse reporting on. ROOT (operator HITL): P1's RawGuard EnableMouseCapture (added for right-click paste, REQ-RC-WIN-PASTE) makes Windows Terminal forward ALL mouse — including the scroll wheel — to rc instead of scrolling its own buffer, but the rc mouse handler dropped everything except right-button-down → scroll DIED (and WT's native scrollback is stolen by the capture). Operator ruling: keep mouse capture + right-click bracketed paste AND forward scroll to the harness. FIX (doyle design, cfg(windows), folds into the rc mouse handler): TRACK the harness's mouse-reporting mode by scanning its OUTPUT stream for the DECSET set/reset — ESC[?1000h/1002h/1003h (mouse on) + ESC[?1006h (SGR ext) and their ...l (off) — into a shared MouseMode{enabled,sgr} (pump writes from output, stdin reader reads); the scan survives a sequence SPLIT across output chunks (a bounded carry buffer). The mouse handler: right-button-DOWN -> bracketed clipboard paste (unchanged, REQ-RC-WIN-PASTE); ScrollUp/Down -> translate to an xterm SGR mouse report (ESC[<64;col+1;row+1M up / ESC[<65;..M down, 0-based crossterm -> 1-based xterm) and forward ONLY when enabled && sgr (else DROP — a legacy X10 report the harness may not parse is garbage); Moved/drag/left/middle -> DROP (scroll is the operator's need; click-forward risks garbage, no click-to-position). Unix UNCHANGED (no capture; the terminal scrolls natively). (v0.13.0)"

+# ── P1c (operator-ruled root-fix before v0.13.0 ship; doyle root-cause via instrumented repro on kitsubito, 2026-06-20): controller-writer reorder on brain-restart re-serve ──

+title = "Two `controller_writer` threads must never race ONE brain↔broker connection's socket. ROOT (doyle, instrumented RACEDIAG repro on kitsubito): on a brain-restart re-serve the handoff brain registers as controller on the SAME session TWICE over the SAME `Brain::conn` socket — (1) `Brain::handoff` eagerly `subscribe(prior.session_id, prior.next_seq=1)` → `become_controller(from_seq=1)`, initial=[1], spawns writer-A (writes seq 1); (2) `serve_attach` re-handles the replayed `Request{from_seq:0}` → `attach_as(sid,0)` → `become_controller(from_seq=0)`, initial=[0,1], spawns writer-B (writes 0 then 1). `become_controller` (broker.rs) drops the prior `ControllerSink` (its `tx`) but does NOT stop the prior writer thread — writer-A keeps flushing its owned `initial` batch, and both writers hold clones of the same `SharedSend` (`Arc<Mutex<socket>>`) with NO inter-thread ordering. When writer-A's seq 1 wins the socket before writer-B's seq 0, the strict legacy consumer (brain.rs read_event reject-gap path) sees `output gap: got seq 1 want 0` → the test `attach_survives_target_brain_restart_exactly_once` panics at `.expect(\"re-serve\")` OR HANGS in `render_until` (serve thread died on the gap → MARKER_TWO never reaches the wire). `prior.next_seq` is life1's CONSUMPTION cursor, NOT life2's connection state — life2's socket has been sent NOTHING, so a `from_seq=0` full replay on a connection that already streamed seq 1 is contradictory. Snap-above tolerance ALONE can't fix it (it would dedup-drop the late seq 0 → byte loss → the exactly-once byte-identity assert fails). PRE-EXISTING, surfaced by the v0.13.0 green-both-runners release gate; P1b is INNOCENT (its diff touches only input-ack machinery, proven mechanically + the test passes post-P1b in isolation). Sibling flaky cluster: `inject_control_wedge::g2`, `broker::spawn_env_reaches_child`. INVARIANT: on a single brain↔broker connection the controller output-frame stream is monotonic non-decreasing in seq (modulo dedup re-sends); exactly ONE `controller_writer` is ever live per connection; a SUPERSEDED writer writes NO further frames; a re-serve never replays a seq below what the connection already received. FIX (doyle design, V0.13.0-P1C-CONTROLLER-WRITER-REORDER-PLAN.md, three parts): (1) PRIMARY — `Brain::handoff` no longer eagerly `subscribe`s-as-controller; `serve_attach`'s `attach_as(sid,0)` is the SOLE controller registration + replay for that connection (handoff is test-only per the brain.rs comment; no production caller depends on the eager subscribe — production resumes via `cold_start`+`resume_sessions`). (2) BROKER HARDENING — `controller_writer` is epoch-gated: `controller_epoch` becomes a shared `Arc<AtomicU64>`, the writer snapshots its own epoch and checks the shared one before each frame (initial-batch AND live loop), returning the moment it is superseded — so a double-registration can never silently keep a superseded writer flushing (W1-safe: never blocks the drain under `Mutex<OutputLog>`). (3) DEFENSE-IN-DEPTH — `Brain::handoff` seeds `session_cursors` at `prior.next_seq` so the consumer uses the production dedup-below+snap-above path (matching `resume_sessions`), not the strict reject-gap legacy trap. (v0.13.0)"

+required_stages = ["doc", "impl", "unit", "int"]  # ACTIVATED v0.13.0 P1c (todlando, the last v0.13.0 ship-blocker — pre-existing flaky resume race that gates green-both-runners; doyle root-caused + designed, doyle gates). doc = KNOWN-HAZARDS entry (the reorder + the single-live-writer-per-connection invariant + the two compounding defects) + CONTEXT note. impl = (1) Brain::handoff drops the eager subscribe(prior.session_id, prior.next_seq) call; (2) OutputLog.controller_epoch -> Arc<AtomicU64>, become_controller passes the new epoch + Arc::clone into controller_writer, which returns when controller_epoch != its snapshot (both the initial-batch loop and the live recv loop), become_controller/mark_controller_gone/the ControllerJob epoch read switch to atomic load/fetch_add; (3) Brain::handoff seeds session_cursors.insert(prior.session_id, prior.next_seq). unit = epoch-gated controller_writer: a superseded writer (epoch bumped) writes NOTHING from its initial batch (real broker socket, two become_controller on one connection -> only the latest writer's monotonic 0,1 reaches the wire) + handoff seeds session_cursors (session_cursor(sid) == prior.next_seq) + handoff issues no eager subscribe. int = the keystone: deterministically force two become_controller-on-one-connection (handoff-then-reserve ordering) on a real broker+brain, NO mocks -> PRE-FIX the wire reorders (got seq 1 want 0 / gap or byte-drop) -> POST-FIX the wire stays monotonic + byte-exact + the session stays live; PLUS attach_survives_target_brain_restart_exactly_once green (doyle runs it 20x isolated single-threaded timeout-wrapped on Linux/kitsubito — the deterministic RED-on-revert carrier). Full seam sweep (attach/broker/resume/brain_swap/inject_control_wedge — the shared W1 controller-writer model). clippy/traceable/docs-drift."


check your ongoing Monitor tool call's output


REQ minted. Now the three code fixes. Starting with `brain.rs` handoff (fix #1 PRIMARY + fix #3 DEFENSE):


Edit

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

    /// Handoff: connect, handshake, rehydrate the resume cursor + generation
    /// from `prior`, but stamp `gen_start = now()` (NOT `prior.gen_start_ms` —
    /// the 2.4 invariant). It does NOT itself subscribe-as-controller: the
    /// re-serve's `attach_as(sid, 0, …)` is the SOLE controller registration +
    /// replay for this connection (P1c, [REQ-HAZARD-CONTROLLER-WRITER-REORDER]).
    /// `session_cursors` is seeded at the resume point so the consumer uses the
    /// production dedup-below + snap-above path, never the strict reject-gap trap.
    ///
    /// **Test-only (restoration D4-3).** The production resume path is
    /// [`Brain::cold_start`] + [`Brain::resume_sessions`] (broker cursor-of-record),
    /// never this brain→brain frame; `handoff` survives only for the integration
    /// tests. See [`BrainState`].
    // [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]
    pub fn handoff(name: &str, prior: BrainState, now_ms: u64) -> io::Result<Self> {
        let mut conn = LocalSocketTransport::connect(name)?;
        send_hello(&mut conn, Role::Brain)?;
        // DEFENSE-IN-DEPTH (P1c): seed the dedup cursor at the resume point so
        // `read_event` routes this session through the dedup-below + snap-above
        // path (matching `resume_sessions`), not the legacy reject-gap path that
        // turns any at-least-once boundary reorder into a fatal `output gap`.
        let mut session_cursors = std::collections::HashMap::new();
        session_cursors.insert(prior.session_id, prior.next_seq);
        let brain = Brain {
            conn: BrainConn::Whole(conn),
            generation: prior.generation + 1,
            gen_start_ms: now_ms, // fresh — prior.gen_start_ms is intentionally discarded
            session_id: Some(prior.session_id),
            next_seq: prior.next_seq,
            net_cursors: std::collections::HashMap::new(),
            presence_cursor: None,
            session_cursors,
            io_timeout: None,
        };
        // PRIMARY (P1c): do NOT eagerly subscribe-as-controller here. A handoff
        // that re-subscribes from `prior.next_seq` (life1's CONSUMPTION cursor,
        // not life2's connection state — this socket has been sent nothing)
        // leaves a stale controller registration that `serve_attach` then
        // re-subscribes from 0 → two `controller_writer` threads race this one
        // socket and reorder the seq stream. Let the re-serve's `attach_as(sid,0)`
        // be the sole controller take + replay.
        Ok(brain)
    }


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

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

    /// Handoff: connect, handshake, rehydrate the resume cursor + generation
    /// from `prior`, but stamp `gen_start = now()` (NOT `prior.gen_start_ms` —
    /// the 2.4 invariant). It does NOT itself subscribe-as-controller: the
    /// re-serve's `attach_as(sid, 0, …)` is the SOLE controller registration +
    /// replay for this connection (P1c, [REQ-HAZARD-CONTROLLER-WRITER-REORDER]).
    /// `session_cursors` is seeded at the resume point so the consumer uses the
    /// production dedup-below + snap-above path, never the strict reject-gap trap.
    ///
    /// **Test-only (restoration D4-3).** The production resume path is
    /// [`Brain::cold_start`] + [`Brain::resume_sessions`] (broker cursor-of-record),
    /// never this brain→brain frame; `handoff` survives only for the integration
    /// tests. See [`BrainState`].
    // [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]
    pub fn handoff(name: &str, prior: BrainState, now_ms: u64) -> io::Result<Self> {
        let mut conn = LocalSocketTransport::connect(name)?;
        send_hello(&mut conn, Role::Brain)?;
        // DEFENSE-IN-DEPTH (P1c): seed the dedup cursor at the resume point so
        // `read_event` routes this session through the dedup-below + snap-above
        // path (matching `resume_sessions`), not the legacy reject-gap path that
        // turns any at-least-once boundary reorder into a fatal `output gap`.
        let mut session_cursors = std::collections::HashMap::new();
        session_cursors.insert(prior.session_id, prior.next_seq);
        let brain = Brain {
            conn: BrainConn::Whole(conn),
            generation: prior.generation + 1,
            gen_start_ms: now_ms, // fresh — prior.gen_start_ms is intentionally discarded
            session_id: Some(prior.session_id),
            next_seq: prior.next_seq,
            net_cursors: std::collections::HashMap::new(),
            presence_cursor: None,
            session_cursors,
            io_timeout: None,
        };
        // PRIMARY (P1c): do NOT eagerly subscribe-as-controller here. A handoff
        // that re-subscribes from `prior.next_seq` (life1's CONSUMPTION cursor,
        // not life2's connection state — this socket has been sent nothing)
        // leaves a stale controller registration that `serve_attach` then
        // re-subscribes from 0 → two `controller_writer` threads race this one
        // socket and reorder the seq stream. Let the re-serve's `attach_as(sid,0)`
        // be the sole controller take + replay.
        Ok(brain)
    }

-    /// the 2.4 invariant), and re-subscribe to the session from the cursor for a

+    /// the 2.4 invariant). It does NOT itself subscribe-as-controller: the

+    /// re-serve's `attach_as(sid, 0, …)` is the SOLE controller registration +

+    /// `session_cursors` is seeded at the resume point so the consumer uses the

+    /// production dedup-below + snap-above path, never the strict reject-gap trap.

+    // [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]

+        // DEFENSE-IN-DEPTH (P1c): seed the dedup cursor at the resume point so

+        // `read_event` routes this session through the dedup-below + snap-above

+        // path (matching `resume_sessions`), not the legacy reject-gap path that

+        // turns any at-least-once boundary reorder into a fatal `output gap`.

+        let mut session_cursors = std::collections::HashMap::new();

+        session_cursors.insert(prior.session_id, prior.next_seq);

-            session_cursors: std::collections::HashMap::new(),

-        brain.subscribe(prior.session_id, prior.next_seq)?;

+        // PRIMARY (P1c): do NOT eagerly subscribe-as-controller here. A handoff

+        // that re-subscribes from `prior.next_seq` (life1's CONSUMPTION cursor,

+        // not life2's connection state — this socket has been sent nothing)

+        // leaves a stale controller registration that `serve_attach` then

+        // re-subscribes from 0 → two `controller_writer` threads race this one

+        // socket and reorder the seq stream. Let the re-serve's `attach_as(sid,0)`

+        // be the sole controller take + replay.


Now fix #2 — broker hardening. Converting `controller_epoch` to a shared `Arc<AtomicU64>` and epoch-gating the writer. Field decl + init:


    /// Monotonic controller generation. Bumped on every [`become_controller`];
    /// the drain's deadline-evict ([`mark_controller_gone`]) carries the epoch it
    /// observed and only detaches if it still matches — so a slow-controller
    /// timeout can never unseat a NEWER controller that attached meanwhile.
    controller_epoch: u64,

    /// Monotonic controller generation, shared with the live `controller_writer`
    /// thread (`Arc<AtomicU64>`). Bumped on every [`become_controller`]; the
    /// drain's deadline-evict ([`mark_controller_gone`]) carries the epoch it
    /// observed and only detaches if it still matches — so a slow-controller
    /// timeout can never unseat a NEWER controller that attached meanwhile. The
    /// writer snapshots its own epoch and returns the moment this value moves past
    /// it, so a SUPERSEDED writer flushes no further frames (P1c, single live
    /// writer per connection, [`REQ-HAZARD-CONTROLLER-WRITER-REORDER`]).
    controller_epoch: Arc<AtomicU64>,


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)

    /// Monotonic controller generation. Bumped on every [`become_controller`];
    /// the drain's deadline-evict ([`mark_controller_gone`]) carries the epoch it
    /// observed and only detaches if it still matches — so a slow-controller
    /// timeout can never unseat a NEWER controller that attached meanwhile.
    controller_epoch: u64,

    /// Monotonic controller generation, shared with the live `controller_writer`
    /// thread (`Arc<AtomicU64>`). Bumped on every [`become_controller`]; the
    /// drain's deadline-evict ([`mark_controller_gone`]) carries the epoch it
    /// observed and only detaches if it still matches — so a slow-controller
    /// timeout can never unseat a NEWER controller that attached meanwhile. The
    /// writer snapshots its own epoch and returns the moment this value moves past
    /// it, so a SUPERSEDED writer flushes no further frames (P1c, single live
    /// writer per connection, [`REQ-HAZARD-CONTROLLER-WRITER-REORDER`]).
    controller_epoch: Arc<AtomicU64>,

/// silently re-takes (no `Displaced`, the gate-#7 self-kick guard); only a

/// DIFFERENT remote's `Take` displaces.

-    /// Monotonic controller generation. Bumped on every [`become_controller`];

-    /// the drain's deadline-evict ([`mark_controller_gone`]) carries the epoch it

+    /// Monotonic controller generation, shared with the live `controller_writer`

+    /// thread (`Arc<AtomicU64>`). Bumped on every [`become_controller`]; the

+    /// drain's deadline-evict ([`mark_controller_gone`]) carries the epoch it

-    /// timeout can never unseat a NEWER controller that attached meanwhile.

+    /// timeout can never unseat a NEWER controller that attached meanwhile. The

+    /// writer snapshots its own epoch and returns the moment this value moves past

+    /// it, so a SUPERSEDED writer flushes no further frames (P1c, single live

+    /// writer per connection, [`REQ-HAZARD-CONTROLLER-WRITER-REORDER`]).

/// The read-only viewers, keyed by a broker-minted viewer id.


            controller: None,
            controller_epoch: 0,

            controller: None,
            controller_epoch: Arc::new(AtomicU64::new(0)),


            controller: None,
            controller_epoch: 0,

            controller: None,
            controller_epoch: Arc::new(AtomicU64::new(0)),

+            controller_epoch: Arc::new(AtomicU64::new(0)),


            tx: c.tx.clone(),
            seq,
            frame,
            epoch: self.controller_epoch,
        })

            tx: c.tx.clone(),
            seq,
            frame,
            epoch: self.controller_epoch.load(Ordering::Acquire),
        })


            tx: c.tx.clone(),
            seq,
            frame,
            epoch: self.controller_epoch,
        })

            tx: c.tx.clone(),
            seq,
            frame,
            epoch: self.controller_epoch.load(Ordering::Acquire),
        })

-            epoch: self.controller_epoch,

+            epoch: self.controller_epoch.load(Ordering::Acquire),


Now `become_controller` — atomic bump + pass epoch & shared counter into the writer:


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

    /// W1: the ring replay is now the INITIAL BATCH of a dedicated controller
    /// writer thread (symmetric with [`add_viewer`]) — it is no longer written
    /// inline under the log lock. The writer thread advances the authoritative
    /// `delivered_through` cursor as it writes (D4-1: only on a successful write,
    /// monotonic, never the unsent tail). Dropping any prior [`ControllerSink`]
    /// ends its writer's LIVE loop when `tx` drops; bumping `controller_epoch`
    /// additionally makes a stale deadline-evict a no-op AND stops a superseded
    /// writer mid-INITIAL-BATCH (P1c — a writer still flushing its owned `initial`
    /// batch ignores the dropped `tx`, so the epoch gate is what guarantees a
    /// single live writer per connection and a monotonic on-wire seq stream,
    /// [`REQ-HAZARD-CONTROLLER-WRITER-REORDER`]). Stamps `driven_by` to the new
    /// controller's identity.
    fn become_controller(&mut self, sub: SharedSend, by: Option<String>, from_seq: u64) {
        // Drop the prior controller sink first (its writer's live loop ends when
        // tx drops), and bump the generation so (a) an in-flight deadline-evict
        // for the old controller can't unseat this one and (b) a prior writer
        // still flushing its initial batch returns before its next frame.
        self.controller = None;
        let epoch = self.controller_epoch.fetch_add(1, Ordering::AcqRel) + 1;
        // Ring replay = the writer thread's initial batch (owned by the thread,
        // NOT pushed through the live channel — replay must never block the drain).
        let initial: Vec<(u64, Vec<u8>)> = self
            .ring
            .iter()
            .filter(|(s, _)| *s >= from_seq)
            .cloned()
            .collect();
        let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);
        let writer_send = Arc::clone(&sub);
        let dt = Arc::clone(&self.delivered_through);
        let epoch_gate = Arc::clone(&self.controller_epoch);
        let sid = self.session_id;
        let writer =
            thread::spawn(move || controller_writer(sid, writer_send, initial, rx, dt, epoch, epoch_gate));


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

    /// W1: the ring replay is now the INITIAL BATCH of a dedicated controller
    /// writer thread (symmetric with [`add_viewer`]) — it is no longer written
    /// inline under the log lock. The writer thread advances the authoritative
    /// `delivered_through` cursor as it writes (D4-1: only on a successful write,
    /// monotonic, never the unsent tail). Dropping any prior [`ControllerSink`]
    /// ends its writer's LIVE loop when `tx` drops; bumping `controller_epoch`
    /// additionally makes a stale deadline-evict a no-op AND stops a superseded
    /// writer mid-INITIAL-BATCH (P1c — a writer still flushing its owned `initial`
    /// batch ignores the dropped `tx`, so the epoch gate is what guarantees a
    /// single live writer per connection and a monotonic on-wire seq stream,
    /// [`REQ-HAZARD-CONTROLLER-WRITER-REORDER`]). Stamps `driven_by` to the new
    /// controller's identity.
    fn become_controller(&mut self, sub: SharedSend, by: Option<String>, from_seq: u64) {
        // Drop the prior controller sink first (its writer's live loop ends when
        // tx drops), and bump the generation so (a) an in-flight deadline-evict
        // for the old controller can't unseat this one and (b) a prior writer
        // still flushing its initial batch returns before its next frame.
        self.controller = None;
        let epoch = self.controller_epoch.fetch_add(1, Ordering::AcqRel) + 1;
        // Ring replay = the writer thread's initial batch (owned by the thread,
        // NOT pushed through the live channel — replay must never block the drain).
        let initial: Vec<(u64, Vec<u8>)> = self
            .ring
            .iter()
            .filter(|(s, _)| *s >= from_seq)
            .cloned()
            .collect();
        let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);
        let writer_send = Arc::clone(&sub);
        let dt = Arc::clone(&self.delivered_through);
        let epoch_gate = Arc::clone(&self.controller_epoch);
        let sid = self.session_id;
        let writer =
            thread::spawn(move || controller_writer(sid, writer_send, initial, rx, dt, epoch, epoch_gate));

-    /// ends its writer; bumping `controller_epoch` makes a stale deadline-evict a

-    /// no-op. Stamps `driven_by` to the new controller's identity.

+    /// ends its writer's LIVE loop when `tx` drops; bumping `controller_epoch`

+    /// additionally makes a stale deadline-evict a no-op AND stops a superseded

+    /// writer mid-INITIAL-BATCH (P1c — a writer still flushing its owned `initial`

+    /// batch ignores the dropped `tx`, so the epoch gate is what guarantees a

+    /// single live writer per connection and a monotonic on-wire seq stream,

+    /// [`REQ-HAZARD-CONTROLLER-WRITER-REORDER`]). Stamps `driven_by` to the new

-        // Drop the prior controller sink first (its writer ends when tx drops),

-        // and bump the generation so an in-flight deadline-evict for the old

-        // controller can't unseat this one.

+        // Drop the prior controller sink first (its writer's live loop ends when

+        // tx drops), and bump the generation so (a) an in-flight deadline-evict

+        // for the old controller can't unseat this one and (b) a prior writer

+        // still flushing its initial batch returns before its next frame.

+        let epoch = self.controller_epoch.fetch_add(1, Ordering::AcqRel) + 1;

+        let epoch_gate = Arc::clone(&self.controller_epoch);

-        let writer = thread::spawn(move || controller_writer(sid, writer_send, initial, rx, dt));

+            thread::spawn(move || controller_writer(sid, writer_send, initial, rx, dt, epoch, epoch_gate));


    fn mark_controller_gone(&mut self, epoch: u64) {
        if self.controller_epoch == epoch {
            self.clear_controller();
        }
    }

    fn mark_controller_gone(&mut self, epoch: u64) {
        if self.controller_epoch.load(Ordering::Acquire) == epoch {
            self.clear_controller();
        }
    }


    fn mark_controller_gone(&mut self, epoch: u64) {
        if self.controller_epoch == epoch {
            self.clear_controller();
        }
    }

    fn mark_controller_gone(&mut self, epoch: u64) {
        if self.controller_epoch.load(Ordering::Acquire) == epoch {
            self.clear_controller();
        }
    }

/// controller generation the drain saw (`epoch`), so a NEWER controller that

/// attached during the stall is never unseated.

-        if self.controller_epoch == epoch {

+        if self.controller_epoch.load(Ordering::Acquire) == epoch {


Now the `controller_writer` itself — add the epoch gate to both loops:


// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]
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() {

// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]
// [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]
fn controller_writer(
    sid: u64,
    send: SharedSend,
    initial: Vec<(u64, Vec<u8>)>,
    rx: Receiver<CtrlMsg>,
    delivered_through: Arc<AtomicU64>,
    epoch: u64,
    controller_epoch: Arc<AtomicU64>,
) {
    for (seq, bytes) in &initial {
        // P1c: a SUPERSEDED writer (a newer `become_controller` bumped the shared
        // epoch past ours) must write NOTHING further — otherwise its owned
        // initial batch races the new writer on the same socket and reorders the
        // seq stream. Dropping `tx` only ends the live loop below; this gate is
        // what stops the initial-batch flush.
        if controller_epoch.load(Ordering::Acquire) != epoch {
            return;
        }
        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() {
        // Same gate on the live path: a superseded writer that still has queued
        // frames stops here rather than interleaving them with the new writer.
        if controller_epoch.load(Ordering::Acquire) != epoch {
            return;
        }
        let (frame, seq) = match &msg {
            CtrlMsg::Output(seq, frame) => (frame, Some(*seq)),
            CtrlMsg::Control(frame) => (frame, None),
        };
        let ok = match send.lock() {


// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]
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() {

// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]
// [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]
fn controller_writer(
    sid: u64,
    send: SharedSend,
    initial: Vec<(u64, Vec<u8>)>,
    rx: Receiver<CtrlMsg>,
    delivered_through: Arc<AtomicU64>,
    epoch: u64,
    controller_epoch: Arc<AtomicU64>,
) {
    for (seq, bytes) in &initial {
        // P1c: a SUPERSEDED writer (a newer `become_controller` bumped the shared
        // epoch past ours) must write NOTHING further — otherwise its owned
        // initial batch races the new writer on the same socket and reorders the
        // seq stream. Dropping `tx` only ends the live loop below; this gate is
        // what stops the initial-batch flush.
        if controller_epoch.load(Ordering::Acquire) != epoch {
            return;
        }
        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() {
        // Same gate on the live path: a superseded writer that still has queued
        // frames stops here rather than interleaving them with the new writer.
        if controller_epoch.load(Ordering::Acquire) != epoch {
            return;
        }
        let (frame, seq) = match &msg {
            CtrlMsg::Output(seq, frame) => (frame, Some(*seq)),
            CtrlMsg::Control(frame) => (frame, None),
        };
        let ok = match send.lock() {

/// send then sees `Disconnected` and detaches the row. `Output` advances

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

+        // P1c: a SUPERSEDED writer (a newer `become_controller` bumped the shared

+        // epoch past ours) must write NOTHING further — otherwise its owned

+        // initial batch races the new writer on the same socket and reorders the

+        // seq stream. Dropping `tx` only ends the live loop below; this gate is

+        // what stops the initial-batch flush.

+        if controller_epoch.load(Ordering::Acquire) != epoch {

+        // Same gate on the live path: a superseded writer that still has queued

+        // frames stops here rather than interleaving them with the new writer.


All three fixes in. Let me verify nothing else references the changed signatures, then compile-check.


src\broker.rs:218:    controller_epoch: Arc<AtomicU64>,
src\broker.rs:277:            controller_epoch: Arc::new(AtomicU64::new(0)),
src\broker.rs:333:            epoch: self.controller_epoch.load(Ordering::Acquire),
src\broker.rs:351:    /// ends its writer's LIVE loop when `tx` drops; bumping `controller_epoch`
src\broker.rs:364:        let epoch = self.controller_epoch.fetch_add(1, Ordering::AcqRel) + 1;
src\broker.rs:376:        let epoch_gate = Arc::clone(&self.controller_epoch);
src\broker.rs:379:            thread::spawn(move || controller_writer(sid, writer_send, initial, rx, dt, epoch, epoch_gate));
src\broker.rs:405:        if self.controller_epoch.load(Ordering::Acquire) == epoch {
src\broker.rs:716:fn controller_writer(
src\broker.rs:723:    controller_epoch: Arc<AtomicU64>,
src\broker.rs:731:        if controller_epoch.load(Ordering::Acquire) != epoch {
src\broker.rs:747:        if controller_epoch.load(Ordering::Acquire) != epoch {


All consistent. Compile-check:


Bash

cargo check -p spt-daemon 2>&1 | tail -25

Compile-check spt-daemon


    Checking spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 16.49s


Compiles clean. Now the KNOWN-HAZARDS doc entry. Let me find the next 7.x number.


337:## 7. Boundary & delivery integrity (added 2026-05-31 — Stage A red-team)
341:### 7.1 Local `api` mutation auth  `[REQ-HAZARD-LOCAL-API-AUTH]`
347:### 7.2 Idempotent delivery across brain restart  `[REQ-HAZARD-RESTART-IDEMPOTENT]`
353:### 7.3 Psyche outbound capture + sanitization  `[REQ-HAZARD-PSYCHE-OUTBOUND-PROXY]`
359:### 7.4 Per-agent pulse/psyche/echo scheduling must not serialize across agents  `[REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]`
365:### 7.5 WAN-inbound origin is transport truth, never payload  `[REQ-HAZARD-WAN-ORIGIN-AUTH]`
373:### 7.6 Pump brain-IPC reads must be deadline-bounded (a blocked read wedges the whole pump)  `[REQ-HAZARD-PUMP-IPC-DEADLINE]`
380:### 7.7 A slow/dead/hostile remote VIEWER must never stall the controller, child, or drain  `[REQ-HAZARD-VIEWER-ISOLATION]`
387:### 7.8 The broker must never make a brain wait UNBOUNDED on a QUIC op (the pump-IPC-deadline B-half)  `[REQ-HAZARD-BROKER-QUIC-DEADLINE]`
394:### 7.9 A daemon-state wire change needs a deliberate BROKER restart (the broker is resident across a brain self-update)  `[REQ-HAZARD-BROKER-SEED-WIRE-SKEW]`
401:### 7.10 A VIEW is independent from the endpoint — closing the launching tab must NOT reap the daemon-hosted harness  `[REQ-HAZARD-VIEWER-CLOSE-DETACH]`
408:### 7.11 A dead PTY child + a dropped operator pump must NOT wedge the broker for other clients  `[REQ-HAZARD-ATTACH-WEDGE]`
414:### 7.12 Controller output must NOT be written inline on the drain thread — a backed-up controller wedges the session  `[REQ-HAZARD-INJECT-CONTROL-COEXIST]`
420:### 7.13 `spt rc` must SWAP Windows legacy-console Backspace/Ctrl+Backspace (`^H`↔DEL); do NOT enable VT console input  `[REQ-HAZARD-RC-INPUT-KEY-ENCODING]`
427:### 7.14 The effect journal must NOT hold its lock across the PTY write (or fsync every keystroke) — interactive input stutters then wedges  `[REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE]`
433:### 7.15 An OFFLINE spt-hosted endpoint must NOT render phantom `ONLINE+CONTROLLED` — clear `driven_by` when its session is gone  `[REQ-HAZARD-DRIVEN-BY-SELFHEAL]`
441:### 7.16 `spt rc` translates Windows console KEY EVENTS to standard xterm VT (arrows/Home/End/F-keys reach the harness); supersedes the W7 byte-swap  `[REQ-RC-KEY-VT-TRANSLATE]`
448:### 7.18 `spt rc` paste is client-originated on Windows — read the LOCAL clipboard, inject a BRACKETED paste  `[REQ-RC-WIN-PASTE]`
450:[Omitted long matching line]
452:- **Source:** v0.13.0 P1 (operator HITL; doyle design 2026-06-19). Depends on P0 (7.17). AMENDED v0.13.0 P1b: scope narrowed to right-click-only (the dead ctrl+V interception removed); scroll-forward is 7.20; the input-flood non-wedge is 7.19.
455:### 7.19 An operator input FLOOD must not deadlock the broker via the applied-ack on the same conn  `[REQ-HAZARD-INPUT-ACK-BACKPRESSURE]`
462:### 7.20 `spt rc` must forward the scroll wheel to the harness (our mouse capture steals WT's native scroll)  `[REQ-RC-MOUSE-FORWARD]`
466:- **Source:** v0.13.0 P1b (operator HITL; doyle design 2026-06-19). Bundled with 7.19.
519:[Omitted long matching line]
520:[Omitted long matching line]
521:[Omitted long matching line]


Next is 7.21. Let me read 7.19/7.20 format and the file tail for insertion.


455	### 7.19 An operator input FLOOD must not deadlock the broker via the applied-ack on the same conn  `[REQ-HAZARD-INPUT-ACK-BACKPRESSURE]`
456	- **Failure (operator HITL, the ctrl+V re-open):** a flood of operator input on one brain↔broker conn wedged the WHOLE broker PERMANENTLY (no new/existing attach; the controller stayed latched — the per-conn handler couldn't process the detach). `serve_attach` processes a whole `NetStreamData` batch of N `Input` records in its inner loop, calling `send_effect` N times WITHOUT returning to `read_event()`; the broker answers each with `send_frame(applied_envelope)` on the SAME conn. Brain not reading → the broker→brain return direction fills (~10 frames = the IPC pipe buffer) → `send_frame` BLOCKS → the handler stops reading → the brain's writes block → mutual full-duplex DEADLOCK. (Capture: 11 frames, `write_input` 11/11 — P0 holds; `ack send` START=11/END=10 — frame #11's ack never returns.) WT's ctrl+V paste-accelerator key-flood was the trigger; the deadlock is generic to ANY input flood.
457	- **Invariant:** the applied-ack is OPT-IN. `InputReq` carries `ack: bool` (serde `default = true`, N-1-safe). The fire-and-forward operator/rc path (`serve_attach`) sends `ack=false` via `Brain::send_effect_no_ack`; `dispatch_input` writes NO applied frame when `ack=false`, so the per-conn handler never writes back while servicing the flood → it always drains → no deadlock (cures ANY input flood). `shellchan` (one-at-a-time spool delivery, WAITS on `BrokerEvent::Applied`) keeps `send_effect` (`ack=true`). EXACTLY-ONCE preserved: the broker dedups by `(session, op_id)` at the applied-set regardless of the ack. **N-1 caveat:** an OLD resident broker (the self-update window) ignores `ack=false` → still acks → the deadlock persists until a broker restart (inherent broker-resident-wire-change class, see 7.9).
458	- **spt-core mapping:** impl `msg.rs` `InputReq.ack` (`default_true`) + `brain.rs` `send_effect_no_ack`/`send_effect_inner` + `attach.rs` `serve_attach` operator path → `send_effect_no_ack` + `broker.rs` `dispatch_input` gates `send_frame(applied)` on `req.ack`; unit the serde default + the ack-emitted-iff-true + the no-ack-path exactly-once dedup; int (keystone, repro-first) a flood of N>pipe-buffer input frames through `serve_attach` on one conn — PRE-FIX deadlocks, POST-FIX drains all N + the session stays live + a concurrent attach opens (real broker+brain, no mocks).
459	- **Source:** v0.13.0 P1b (operator HITL re-open; doyle /diagnose on the enhanced rc+broker capture 2026-06-19). The real ship-blocker behind the ctrl+V wedge.
460
461	<!-- [doc->REQ-RC-MOUSE-FORWARD] -->
462	### 7.20 `spt rc` must forward the scroll wheel to the harness (our mouse capture steals WT's native scroll)  `[REQ-RC-MOUSE-FORWARD]`
463	- **Failure (operator HITL):** scroll stopped working in `spt rc`. P1's `EnableMouseCapture` (for right-click paste, 7.18) makes Windows Terminal forward ALL mouse — including the wheel — to rc instead of scrolling its own buffer, but the rc mouse handler dropped everything except right-button-down → scroll DIED (and WT's native scrollback is stolen under the capture).
464	- **Invariant:** on Windows, TRACK the harness's mouse-reporting mode from its OUTPUT — scan for DECSET `ESC[?1000h/1002h/1003h` (mouse on) + `ESC[?1006h` (SGR ext) and their `…l` (off) into a shared `MouseMode{enabled,sgr}` (the pump writes from the output render path, the stdin reader reads); the scan survives a sequence SPLIT across output chunks (a bounded carry buffer). The mouse handler: right-button-DOWN → bracketed clipboard paste (7.18, unchanged); `ScrollUp/Down` → an xterm SGR mouse report (`ESC[<64;col+1;row+1M` up / `ESC[<65;…M` down; 0-based crossterm → 1-based xterm), forwarded ONLY when `enabled && sgr` (else DROP — a legacy report the harness may misread is garbage); Moved/drag/left/middle DROP (scroll is the need; click-forward risks garbage, no click-to-position). UNIX UNCHANGED (no capture; the terminal scrolls natively).
465	- **spt-core mapping:** impl `rc.rs` `MouseMode` + `MouseModeScanner`/`parse_decset_private`/`apply_mouse_mode` (carry-buffer scan, fed from the pump's output path) + `scroll_dir` + `scroll_sgr` + the `spawn_stdin_reader_events` scroll arm (threaded via `Arc<MouseMode>`); unit `scroll_dir` classify + `scroll_sgr` exact bytes + the DECSET scan (set/reset, combined, mixed, and split-across-chunks); NO int (live console mouse = HITL).
466	- **Source:** v0.13.0 P1b (operator HITL; doyle design 2026-06-19). Bundled with 7.19.
467
468	---
469
470	## Conformance checklist (condensed)
471
472	| # | Invariant | spt-core surface |
473	|---|---|---|
474	| 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown |
475	| 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain |
476	| 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection |
477	| 2.3 | Handoff argv/IPC version-tolerant (newer brain ↔ older broker) | broker↔brain IPC, self-update |
478	| 2.4 | gen_start = now() on cold-start + handoff | per-instance generation |
479	| 3.1 | Ephemeral perch cleanup on all exit paths | `ring` (RAII guard) |
480	| 3.3 | Echo-commune before INIT_SIGNOFF | daemon psyche loop |
481	| 4.1 | Envelope decode order, `&` last | spt-proto (public, wire-versioned) |
482	| 4.2 | Parser panic-free + tolerant | spt-proto |
483	| 4.3 | Stale registry entries → fallback, never hard-fail | subnet registry resolution |
484	| 4.10 | Silent-node registry rows evicted (heard-map TTL); own rows never decay | registry pump eviction |
485	| 4.6 | Addressable-id charset reserves `:`/`@` delimiters | `spt_proto::id` at creation seams |
486	| 5.2 | tmp-write + atomic-rename + retry (EBUSY) | all state writes, binary swap |
487	| 5.3 | Timeout every harness subprocess | manifest invocations |
488	| 5.4 | Strip UNC prefix on serialized paths | spt-proto path normalization |
489	| 5.5 | ConPTY reader answers DSR (`ESC[6n`) | spt-term broker PTY reader |
490	| 7.7 | A wedged viewer is evicted, never stalls the controller/child/drain | broker `OutputLog` fan-out (controller/viewer) |
491	| 5.6 | Detached long-lived children spawn `bInheritHandles=FALSE` | daemon + shell spawn |
492	| 5.7 | Daemon always unelevated in the invoker's universe (de-elevated spawn + entry guard) | `deelevate` seam, `spawn_detached`, `Daemon::run` |
493	| 5.8 | `CREATE_NO_WINDOW` on every console child of the console-less daemon | `gitrun::run_git`, `kill_shell_pid`, `run_bounded_command`, `shellwake` |
494	| 5.12 | Native-PTY spawn PATHEXT-resolves a bare program + wraps non-PE targets (.cmd/.bat→`cmd /c`, .ps1→powershell), bypassing portable-pty's shim-first `which` (CreateProcessW os error 193) | `spt_term::winprog` → `PtySession::spawn_program_in` |
495	| 6.1 | Single path/registry source of truth | storage layout |
496	| 6.4 | Drop files supervisor-owned single-writer | runtime contract |
497	| 6.5 | Direct-write precedence marker (+ node id) | cross-node Psyche sync |
498	| 6.6 | Surfaced conflicts preserve both versions until dominated | context conflict artifacts (ADR-0013) |
499	| 6.7 | Broker + brain are separate processes (brain restart never drops a hosted endpoint at the process level) | daemon process topology, self-update (ADR-0018) |
500	| 6.8 | No irreversible durable-state migration before update ready-promotion (pre-ready writes stay N-1-readable) | auto-rollback / durable-state schema (ADR-0018) |
501	| 6.10 | Phase-significant loop timing is a durable absolute-deadline grid (no per-fire write; update preserves phase, crash resets, one-shot never resets) | durable loop timing / self-update (ADR-0018 Q4) |
502	| 6.11 | Brain respawn execs the applied bytes (canonical exe captured at broker start, not per-spawn current_exe) + promotion bytes-gate (exe_hash == artifact, else rollback) | daemon respawn path / self-update (ADR-0018 Q3) |
503	| 7.1 | Local `api` mutation authenticated to endpoint | api surface / broker IPC |
504	| 7.2 | Idempotent delivery across brain restart | broker↔brain IPC |
505	| 7.3 | Psyche outbound captured + `from=`/target stripped + reply-to-sender / notify-to-own-user | live-Psyche driver / daemon relay (ADR-0012) |
506	| 7.4 | Per-agent pulse/psyche/echo runs off the shared scheduler (no serial blocking across agents) | daemon multi-agent hosting (ADR-0004) |
507	| 7.5 | WAN-inbound origin = QUIC handshake identity from the broker's stream table, never payload bytes | wan receive funnel + every wire-inbound consumer (ADR-0009) |
508	| 7.6 | Pump brain-IPC reads deadline-bounded (30s total-wait); TimedOut → supervised restart, never per-peer retry | `Brain::cold_start_pump` / `BrainConn::Split` (reader-thread + `recv_timeout`), `pump::peer_outcome` |
509	| 7.8 | Broker bounds every brain-waiting QUIC op (10s < the brain's 30s); a dead peer fails as an ORDINARY error the broker replies → per-peer redial, round continues, no pump restart (the 7.6 B-half) | `NetHost::bounded_block_on` wrapping `dial`/`open_stream`/`send_stream` |
510	| 7.9 | A daemon-state wire change (e.g. the v0.9.0 agnostic Seed) needs a deliberate broker restart — the resident old broker can't read new bytes; the seed-skew EOF surfaces an actionable `spt daemon stop` hint, not "failed to fill whole buffer"; forward: additive + serde-default daemon-state | `cmd_seed`/`seed_fail_message`, broker seed-control residency |
511	| 7.10 | A view is independent from the endpoint: the cold-started daemon is launched JOB-NEUTRAL (WMI `Win32_Process.Create` PRIMARY → WmiPrvSE child, outside any terminal job from birth; breakaway DEMOTED to a fallback rung because a job CAN deny it). A WMI/scheduler child does NOT inherit transient shell env, so `SPT_*` (esp. `SPT_HOME`) is forwarded via a `cmd /c set … & start /b` wrapper. Ladder (first-success-wins, both cold-start + `spt daemon start`): WMI → schtasks → breakaway → in-job. int IS CI-testable via the WMI rung (no nesting false-red — WMI escapes regardless of job policy); operator WT/VSCode tab-close = final non-gating confirmation. `detached_no_inherit` unchanged for `launch_shell`; elevated `deelevate` keeps L1 breakaway (WMI-reparent = follow-up) | `daemon.rs::launch_daemon_job_neutral` (ladder), `spawn_daemon_via_wmi`/`wrapped_daemon_command`, int `job_escape_e2e.rs` |
512	| 7.11 | A dead PTY child + a dropped operator pump does NOT wedge the broker — PROVE-DON'T-CHANGE: send_stream already QUIC-deadline-bounded (7.8), the loopback duplex is drained broker-internally (evict-not-park), block_on parks the dispatch thread not a net worker; dead endpoint offlined within a reconcile tick | int `attach_wedge_e2e.rs` |
513	| 7.12 | Controller output delivered OFF the drain thread (dedicated writer + bounded deadline-detach), never inline under the log lock — a backed-up controller can't wedge the session | broker `OutputLog::append`/`controller_writer`, int `inject_control_wedge.rs` |
514	| 7.13 | `spt rc` maps Windows legacy-console delete keys so Backspace→char-delete / Ctrl+Backspace→word-delete; does NOT enable VT console input (= win32-input-mode on WT, broke detach). SUPERSEDED on Windows by 7.16 (the byte-swap `normalize_key_byte` removed; behavior native in `translate_key_event`) | `rc.rs` → 7.16 `translate_key_event` |
515	| 7.14 | `EffectJournal::apply_once` releases its lock ACROSS `effect()` (reserve→release→run→finalize), never holding it across the blocking PTY write; `EffectKind::PtyWrite` is ephemeral (no per-keystroke fsync, in-memory dedup), durable kinds keep fsync — fixes interactive stutter + the hard input wedge (`brain IPC read deadline`) | `effect.rs` `apply_once`/`is_durable`, int `inject_control_wedge.rs` |
516	| 7.15 | `reconcile_hosted_liveness` clears stale `driven_by` when it offlines a sessionless controllable perch (no live broker session) — an OFFLINE endpoint never renders phantom `ONLINE+CONTROLLED`; race-free (no session ⇒ no concurrent broker re-stamp). `controller_by==None` is ambiguous (local controller reads None) so it is NOT a clear trigger; the idle wedged-remote leg is deferred (`REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT`) | `livehost.rs` `reconcile_hosted_liveness`, int `driven_by_selfheal.rs`/`inject_control_wedge.rs` |
517	| 7.16 | `spt rc` on Windows reads crossterm KEY EVENTS and translates to standard xterm VT (arrows/Home/End/PgUp/Dn/Ins/Del/F-keys + modifiers reach the harness; agnostic, NOT win32-input-mode); `ctrl-b d` detach preserved event-sourced; non-tty + Unix keep the byte path; supersedes the 7.13 byte-swap | `rc.rs` `translate_key_event`/`key_event_step`/`spawn_stdin_reader_events`, unit-only (live = HITL) |
518	| 7.17 | PTY **input** is single-writer: each session spawns ONE dedicated input-writer thread = the SOLE caller of the blocking `write_input`, fed by a bounded FIFO (`sync_channel`). Every caller (`dispatch_input`, `dispatch_endpoint_input`, the inject worker/floor flush) ENQUEUES (`try_send`) + returns at once — a paste burst that fills the harness input buffer parks only that thread, never the broker dispatch thread. Full queue ⇒ DROP excess + stamp the perch `input_backpressure` (heal-on-resume: cleared on the next accepted enqueue); the daemon NEVER wedges on a stuck harness. Completes the W1b-deferred fix (2) of 7.14. The OUTPUT-side single-writer (7.12) mirror, applied to input | broker `InputWriter`/`input_writer`/`flush_inject_floor`/`run_inject_worker`/`dispatch_input`, `spt_store::info::set_input_backpressure`, int `inject_control_wedge.rs` |
519	| 7.18 | `spt rc` paste is **client-originated** on Windows: CC runs daemon-side with no access to the operator's LOCAL clipboard. On a RIGHT-CLICK rc reads the local clipboard itself and injects a BRACKETED paste (`ESC[200~` + content + `ESC[201~`) — CC has bracketed-paste mode on (`ESC[?2004h`), so a multi-line paste lands intact with NO `\r` submit-storm, harness-agnostic, content VERBATIM. RawGuard captures the mouse (disables console QuickEdit so right-click reaches the app) on an interactive console only + restores on drop. **ctrl+V is NOT intercepted** (P1b): WT consumes it (Key RELEASE only, never Press) + injects the clipboard as a key flood — it rides WT's native paste as keystrokes (no wedge, 7.19; bracketed = right-click). cfg(windows) only — Unix pastes natively | `rc.rs` `wrap_bracketed_paste`/`mouse_is_paste`/`clipboard_paste`/`read_clipboard`/`RawGuard`/`spawn_stdin_reader_events`, unit-only (live clipboard+mouse = HITL) |
520	| 7.19 | An operator input FLOOD must not deadlock the broker: `serve_attach` sends N `Input` frames on one conn without reading, the broker acks each on the SAME conn → the return direction fills (~pipe buffer) → `send_frame` blocks the per-conn handler → mutual full-duplex DEADLOCK (permanent broker wedge, controller latched). FIX: opt-in ack — `InputReq.ack` (serde default true); the operator/rc path sends `ack=false` (`send_effect_no_ack`), `dispatch_input` skips the applied frame → the handler never writes back while draining the flood. `shellchan` keeps `ack=true` (its `Applied`-wait). Exactly-once unaffected (dedup at the applied-set). N-1: an old resident broker still acks until restart (7.9 class) | `msg.rs` `InputReq.ack`, `brain.rs` `send_effect_no_ack`, `attach.rs` `serve_attach`, `broker.rs` `dispatch_input`, int (flood repro) |
521	| 7.20 | `spt rc` must forward the SCROLL wheel to the harness — our mouse capture (for right-click paste, 7.18) steals WT's native scroll. Track the harness's mouse-reporting mode from its OUTPUT (DECSET `ESC[?1000/1002/1003h` + `1006h` SGR, and `…l` off; scan survives a split across output chunks) into `MouseMode{enabled,sgr}`; forward `ScrollUp/Down` as an xterm SGR report (`ESC[<64/65;col+1;row+1M`) ONLY when `enabled && sgr`, else drop; Moved/drag/clicks dropped. cfg(windows) only — Unix scrolls natively | `rc.rs` `MouseMode`/`MouseModeScanner`/`scroll_dir`/`scroll_sgr`/`spawn_stdin_reader_events`, unit-only (live mouse = HITL) |
522

### 7.19 An operator input FLOOD must not deadlock the broker via the applied-ack on the same conn  `[REQ-HAZARD-INPUT-ACK-BACKPRESSURE]`
- **Failure (operator HITL, the ctrl+V re-open):** a flood of operator input on one brain↔broker conn wedged the WHOLE broker PERMANENTLY (no new/existing attach; the controller stayed latched — the per-conn handler couldn't process the detach). `serve_attach` processes a whole `NetStreamData` batch of N `Input` records in its inner loop, calling `send_effect` N times WITHOUT returning to `read_event()`; the broker answers each with `send_frame(applied_envelope)` on the SAME conn. Brain not reading → the broker→brain return direction fills (~10 frames = the IPC pipe buffer) → `send_frame` BLOCKS → the handler stops reading → the brain's writes block → mutual full-duplex DEADLOCK. (Capture: 11 frames, `write_input` 11/11 — P0 holds; `ack send` START=11/END=10 — frame #11's ack never returns.) WT's ctrl+V paste-accelerator key-flood was the trigger; the deadlock is generic to ANY input flood.
- **Invariant:** the applied-ack is OPT-IN. `InputReq` carries `ack: bool` (serde `default = true`, N-1-safe). The fire-and-forward operator/rc path (`serve_attach`) sends `ack=false` via `Brain::send_effect_no_ack`; `dispatch_input` writes NO applied frame when `ack=false`, so the per-conn handler never writes back while servicing the flood → it always drains → no deadlock (cures ANY input flood). `shellchan` (one-at-a-time spool delivery, WAITS on `BrokerEvent::Applied`) keeps `send_effect` (`ack=true`). EXACTLY-ONCE preserved: the broker dedups by `(session, op_id)` at the applied-set regardless of the ack. **N-1 caveat:** an OLD resident broker (the self-update window) ignores `ack=false` → still acks → the deadlock persists until a broker restart (inherent broker-resident-wire-change class, see 7.9).
- **spt-core mapping:** impl `msg.rs` `InputReq.ack` (`default_true`) + `brain.rs` `send_effect_no_ack`/`send_effect_inner` + `attach.rs` `serve_attach` operator path → `send_effect_no_ack` + `broker.rs` `dispatch_input` gates `send_frame(applied)` on `req.ack`; unit the serde default + the ack-emitted-iff-true + the no-ack-path exactly-once dedup; int (keystone, repro-first) a flood of N>pipe-buffer input frames through `serve_attach` on one conn — PRE-FIX deadlocks, POST-FIX drains all N + the session stays live + a concurrent attach opens (real broker+brain, no mocks).
- **Source:** v0.13.0 P1b (operator HITL re-open; doyle /diagnose on the enhanced rc+broker capture 2026-06-19). The real ship-blocker behind the ctrl+V wedge.

<!-- [doc->REQ-RC-MOUSE-FORWARD] -->
### 7.20 `spt rc` must forward the scroll wheel to the harness (our mouse capture steals WT's native scroll)  `[REQ-RC-MOUSE-FORWARD]`
- **Failure (operator HITL):** scroll stopped working in `spt rc`. P1's `EnableMouseCapture` (for right-click paste, 7.18) makes Windows Terminal forward ALL mouse — including the wheel — to rc instead of scrolling its own buffer, but the rc mouse handler dropped everything except right-button-down → scroll DIED (and WT's native scrollback is stolen under the capture).
- **Invariant:** on Windows, TRACK the harness's mouse-reporting mode from its OUTPUT — scan for DECSET `ESC[?1000h/1002h/1003h` (mouse on) + `ESC[?1006h` (SGR ext) and their `…l` (off) into a shared `MouseMode{enabled,sgr}` (the pump writes from the output render path, the stdin reader reads); the scan survives a sequence SPLIT across output chunks (a bounded carry buffer). The mouse handler: right-button-DOWN → bracketed clipboard paste (7.18, unchanged); `ScrollUp/Down` → an xterm SGR mouse report (`ESC[<64;col+1;row+1M` up / `ESC[<65;…M` down; 0-based crossterm → 1-based xterm), forwarded ONLY when `enabled && sgr` (else DROP — a legacy report the harness may misread is garbage); Moved/drag/left/middle DROP (scroll is the need; click-forward risks garbage, no click-to-position). UNIX UNCHANGED (no capture; the terminal scrolls natively).
- **spt-core mapping:** impl `rc.rs` `MouseMode` + `MouseModeScanner`/`parse_decset_private`/`apply_mouse_mode` (carry-buffer scan, fed from the pump's output path) + `scroll_dir` + `scroll_sgr` + the `spawn_stdin_reader_events` scroll arm (threaded via `Arc<MouseMode>`); unit `scroll_dir` classify + `scroll_sgr` exact bytes + the DECSET scan (set/reset, combined, mixed, and split-across-chunks); NO int (live console mouse = HITL).
- **Source:** v0.13.0 P1b (operator HITL; doyle design 2026-06-19). Bundled with 7.19.

---

## Conformance checklist (condensed)

| # | Invariant | spt-core surface |
|---|---|---|
| 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown |
| 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain |
| 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection |
| 2.3 | Handoff argv/IPC version-tolerant (newer brain ↔ older broker) | broker↔brain IPC, self-update |
| 2.4 | gen_start = now() on cold-start + handoff | per-instance generation |
| 3.1 | Ephemeral perch cleanup on all exit paths | `ring` (RAII guard) |
| 3.3 | Echo-commune before INIT_SIGNOFF | daemon psyche loop |
| 4.1 | Envelope decode order, `&` last | spt-proto (public, wire-versioned) |
| 4.2 | Parser panic-free + tolerant | spt-proto |
| 4.3 | Stale registry entries → fallback, never hard-fail | subnet registry resolution |
| 4.10 | Silent-node registry rows evicted (heard-map TTL); own rows never decay | registry pump eviction |
| 4.6 | Addressable-id charset reserves `:`/`@` delimiters | `spt_proto::id` at creation seams |
| 5.2 | tmp-write + atomic-rename + retry (EBUSY) | all state writes, binary swap |
| 5.3 | Timeout every harness subprocess | manifest invocations |
| 5.4 | Strip UNC prefix on serialized paths | spt-proto path normalization |
| 5.5 | ConPTY reader answers DSR (`ESC[6n`) | spt-term broker PTY reader |
| 7.7 | A wedged viewer is evicted, never stalls the controller/child/drain | broker `OutputLog` fan-out (controller/viewer) |
| 5.6 | Detached long-lived children spawn `bInheritHandles=FALSE` | daemon + shell spawn |
| 5.7 | Daemon always unelevated in the invoker's universe (de-elevated spawn + entry guard) | `deelevate` seam, `spawn_detached`, `Daemon::run` |
| 5.8 | `CREATE_NO_WINDOW` on every console child of the console-less daemon | `gitrun::run_git`, `kill_shell_pid`, `run_bounded_command`, `shellwake` |
| 5.12 | Native-PTY spawn PATHEXT-resolves a bare program + wraps non-PE targets (.cmd/.bat→`cmd /c`, .ps1→powershell), bypassing portable-pty's shim-first `which` (CreateProcessW os error 193) | `spt_term::winprog` → `PtySession::spawn_program_in` |
| 6.1 | Single path/registry source of truth | storage layout |
| 6.4 | Drop files supervisor-owned single-writer | runtime contract |
| 6.5 | Direct-write precedence marker (+ node id) | cross-node Psyche sync |
| 6.6 | Surfaced conflicts preserve both versions until dominated | context conflict artifacts (ADR-0013) |
| 6.7 | Broker + brain are separate processes (brain restart never drops a hosted endpoint at the process level) | daemon process topology, self-update (ADR-0018) |
| 6.8 | No irreversible durable-state migration before update ready-promotion (pre-ready writes stay N-1-readable) | auto-rollback / durable-state schema (ADR-0018) |
| 6.10 | Phase-significant loop timing is a durable absolute-deadline grid (no per-fire write; update preserves phase, crash resets, one-shot never resets) | durable loop timing / self-update (ADR-0018 Q4) |
| 6.11 | Brain respawn execs the applied bytes (canonical exe captured at broker start, not per-spawn current_exe) + promotion bytes-gate (exe_hash == artifact, else rollback) | daemon respawn path / self-update (ADR-0018 Q3) |
| 7.1 | Local `api` mutation authenticated to endpoint | api surface / broker IPC |
| 7.2 | Idempotent delivery across brain restart | broker↔brain IPC |
| 7.3 | Psyche outbound captured + `from=`/target stripped + reply-to-sender / notify-to-own-user | live-Psyche driver / daemon relay (ADR-0012) |
| 7.4 | Per-agent pulse/psyche/echo runs off the shared scheduler (no serial blocking across agents) | daemon multi-agent hosting (ADR-0004) |
| 7.5 | WAN-inbound origin = QUIC handshake identity from the broker's stream table, never payload bytes | wan receive funnel + every wire-inbound consumer (ADR-0009) |
| 7.6 | Pump brain-IPC reads deadline-bounded (30s total-wait); TimedOut → supervised restart, never per-peer retry | `Brain::cold_start_pump` / `BrainConn::Split` (reader-thread + `recv_timeout`), `pump::peer_outcome` |
| 7.8 | Broker bounds every brain-waiting QUIC op (10s < the brain's 30s); a dead peer fails as an ORDINARY error the broker replies → per-peer redial, round continues, no pump restart (the 7.6 B-half) | `NetHost::bounded_block_on` wrapping `dial`/`open_stream`/`send_stream` |
| 7.9 | A daemon-state wire change (e.g. the v0.9.0 agnostic Seed) needs a deliberate broker restart — the resident old broker can't read new bytes; the seed-skew EOF surfaces an actionable `spt daemon stop` hint, not "failed to fill whole buffer"; forward: additive + serde-default daemon-state | `cmd_seed`/`seed_fail_message`, broker seed-control residency |
| 7.10 | A view is independent from the endpoint: the cold-started daemon is launched JOB-NEUTRAL (WMI `Win32_Process.Create` PRIMARY → WmiPrvSE child, outside any terminal job from birth; breakaway DEMOTED to a fallback rung because a job CAN deny it). A WMI/scheduler child does NOT inherit transient shell env, so `SPT_*` (esp. `SPT_HOME`) is forwarded via a `cmd /c set … & start /b` wrapper. Ladder (first-success-wins, both cold-start + `spt daemon start`): WMI → schtasks → breakaway → in-job. int IS CI-testable via the WMI rung (no nesting false-red — WMI escapes regardless of job policy); operator WT/VSCode tab-close = final non-gating confirmation. `detached_no_inherit` unchanged for `launch_shell`; elevated `deelevate` keeps L1 breakaway (WMI-reparent = follow-up) | `daemon.rs::launch_daemon_job_neutral` (ladder), `spawn_daemon_via_wmi`/`wrapped_daemon_command`, int `job_escape_e2e.rs` |
| 7.11 | A dead PTY child + a dropped operator pump does NOT wedge the broker — PROVE-DON'T-CHANGE: send_stream already QUIC-deadline-bounded (7.8), the loopback duplex is drained broker-internally (evict-not-park), block_on parks the dispatch thread not a net worker; dead endpoint offlined within a reconcile tick | int `attach_wedge_e2e.rs` |
| 7.12 | Controller output delivered OFF the drain thread (dedicated writer + bounded deadline-detach), never inline under the log lock — a backed-up controller can't wedge the session | broker `OutputLog::append`/`controller_writer`, int `inject_control_wedge.rs` |
| 7.13 | `spt rc` maps Windows legacy-console delete keys so Backspace→char-delete / Ctrl+Backspace→word-delete; does NOT enable VT console input (= win32-input-mode on WT, broke detach). SUPERSEDED on Windows by 7.16 (the byte-swap `normalize_key_byte` removed; behavior native in `translate_key_event`) | `rc.rs` → 7.16 `translate_key_event` |
| 7.14 | `EffectJournal::apply_once` releases its lock ACROSS `effect()` (reserve→release→run→finalize), never holding it across the blocking PTY write; `EffectKind::PtyWrite` is ephemeral (no per-keystroke fsync, in-memory dedup), durable kinds keep fsync — fixes interactive stutter + the hard input wedge (`brain IPC read deadline`) | `effect.rs` `apply_once`/`is_durable`, int `inject_control_wedge.rs` |
| 7.15 | `reconcile_hosted_liveness` clears stale `driven_by` when it offlines a sessionless controllable perch (no live broker session) — an OFFLINE endpoint never renders phantom `ONLINE+CONTROLLED`; race-free (no session ⇒ no concurrent broker re-stamp). `controller_by==None` is ambiguous (local controller reads None) so it is NOT a clear trigger; the idle wedged-remote leg is deferred (`REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT`) | `livehost.rs` `reconcile_hosted_liveness`, int `driven_by_selfheal.rs`/`inject_control_wedge.rs` |
| 7.16 | `spt rc` on Windows reads crossterm KEY EVENTS and translates to standard xterm VT (arrows/Home/End/PgUp/Dn/Ins/Del/F-keys + modifiers reach the harness; agnostic, NOT win32-input-mode); `ctrl-b d` detach preserved event-sourced; non-tty + Unix keep the byte path; supersedes the 7.13 byte-swap | `rc.rs` `translate_key_event`/`key_event_step`/`spawn_stdin_reader_events`, unit-only (live = HITL) |
| 7.17 | PTY **input** is single-writer: each session spawns ONE dedicated input-writer thread = the SOLE caller of the blocking `write_input`, fed by a bounded FIFO (`sync_channel`). Every caller (`dispatch_input`, `dispatch_endpoint_input`, the inject worker/floor flush) ENQUEUES (`try_send`) + returns at once — a paste burst that fills the harness input buffer parks only that thread, never the broker dispatch thread. Full queue ⇒ DROP excess + stamp the perch `input_backpressure` (heal-on-resume: cleared on the next accepted enqueue); the daemon NEVER wedges on a stuck harness. Completes the W1b-deferred fix (2) of 7.14. The OUTPUT-side single-writer (7.12) mirror, applied to input | broker `InputWriter`/`input_writer`/`flush_inject_floor`/`run_inject_worker`/`dispatch_input`, `spt_store::info::set_input_backpressure`, int `inject_control_wedge.rs` |
| 7.18 | `spt rc` paste is **client-originated** on Windows: CC runs daemon-side with no access to the operator's LOCAL clipboard. On a RIGHT-CLICK rc reads the local clipboard itself and injects a BRACKETED paste (`ESC[200~` + content + `ESC[201~`) — CC has bracketed-paste mode on (`ESC[?2004h`), so a multi-line paste lands intact with NO `\r` submit-storm, harness-agnostic, content VERBATIM. RawGuard captures the mouse (disables console QuickEdit so right-click reaches the app) on an interactive console only + restores on drop. **ctrl+V is NOT intercepted** (P1b): WT consumes it (Key RELEASE only, never Press) + injects the clipboard as a key flood — it rides WT's native paste as keystrokes (no wedge, 7.19; bracketed = right-click). cfg(windows) only — Unix pastes natively | `rc.rs` `wrap_bracketed_paste`/`mouse_is_paste`/`clipboard_paste`/`read_clipboard`/`RawGuard`/`spawn_stdin_reader_events`, unit-only (live clipboard+mouse = HITL) |
| 7.19 | An operator input FLOOD must not deadlock the broker: `serve_attach` sends N `Input` frames on one conn without reading, the broker acks each on the SAME conn → the return direction fills (~pipe buffer) → `send_frame` blocks the per-conn handler → mutual full-duplex DEADLOCK (permanent broker wedge, controller latched). FIX: opt-in ack — `InputReq.ack` (serde default true); the operator/rc path sends `ack=false` (`send_effect_no_ack`), `dispatch_input` skips the applied frame → the handler never writes back while draining the flood. `shellchan` keeps `ack=true` (its `Applied`-wait). Exactly-once unaffected (dedup at the applied-set). N-1: an old resident broker still acks until restart (7.9 class) | `msg.rs` `InputReq.ack`, `brain.rs` `send_effect_no_ack`, `attach.rs` `serve_attach`, `broker.rs` `dispatch_input`, int (flood repro) |
| 7.20 | `spt rc` must forward the SCROLL wheel to the harness — our mouse capture (for right-click paste, 7.18) steals WT's native scroll. Track the harness's mouse-reporting mode from its OUTPUT (DECSET `ESC[?1000/1002/1003h` + `1006h` SGR, and `…l` off; scan survives a split across output chunks) into `MouseMode{enabled,sgr}`; forward `ScrollUp/Down` as an xterm SGR report (`ESC[<64/65;col+1;row+1M`) ONLY when `enabled && sgr`, else drop; Moved/drag/clicks dropped. cfg(windows) only — Unix scrolls natively | `rc.rs` `MouseMode`/`MouseModeScanner`/`scroll_dir`/`scroll_sgr`/`spawn_stdin_reader_events`, unit-only (live mouse = HITL) |


Adding the 7.21 entry (after 7.20, before the `---`) and the conformance row:


Edit

- **Source:** v0.13.0 P1b (operator HITL; doyle design 2026-06-19). Bundled with 7.19.

---

## Conformance checklist (condensed)

- **Source:** v0.13.0 P1b (operator HITL; doyle design 2026-06-19). Bundled with 7.19.

<!-- [doc->REQ-HAZARD-CONTROLLER-WRITER-REORDER] -->
### 7.21 Exactly ONE `controller_writer` per brain↔broker connection — a superseded writer must write nothing further  `[REQ-HAZARD-CONTROLLER-WRITER-REORDER]`
- **Failure (doyle instrumented RACEDIAG repro, kitsubito):** on a brain-restart re-serve the handoff brain registered as controller on the SAME session TWICE over the SAME socket — `Brain::handoff` eagerly `subscribe(prior.next_seq=1)` → `become_controller(from_seq=1)`, spawning writer-A (writes seq 1); then `serve_attach` re-handled the replayed `Request{from_seq:0}` → `attach_as(sid,0)` → `become_controller(from_seq=0)`, spawning writer-B (writes 0,1). `become_controller` dropped the prior `ControllerSink` (its `tx`) but did NOT stop the prior writer — writer-A kept flushing its OWNED `initial` batch, and both writers held clones of one `SharedSend` (`Arc<Mutex<socket>>`) with no inter-thread ordering. When writer-A's seq 1 beat writer-B's seq 0, the strict consumer saw `output gap: got seq 1 want 0` → `attach_survives_target_brain_restart_exactly_once` panicked at `.expect("re-serve")` OR HUNG in `render_until` (serve thread died on the gap → `MARKER_TWO` never reached the wire). `prior.next_seq` is life1's CONSUMPTION cursor, not life2's connection state (this socket was sent nothing), so a full `from_seq=0` replay on a connection that already streamed seq 1 is contradictory. Snap-above tolerance alone can't fix it (it would dedup-drop the late seq 0 → byte loss → the exactly-once byte-identity assert fails). PRE-EXISTING, surfaced by the v0.13.0 green-both-runners gate; P1b is innocent. Sibling flaky cluster: `inject_control_wedge::g2`, `broker::spawn_env_reaches_child`.
- **Invariant:** on a single brain↔broker connection the controller output-frame stream is **monotonic non-decreasing in seq** (modulo dedup re-sends); exactly ONE `controller_writer` is ever live per connection; a SUPERSEDED writer writes NO further frames; a re-serve never replays a seq below what the connection already received. Enforced three ways: (1) `Brain::handoff` no longer eagerly subscribes — the re-serve's `attach_as(sid,0)` is the SOLE controller registration + replay (handoff is test-only; production resumes via `cold_start`+`resume_sessions`, one subscribe each); (2) `controller_writer` is EPOCH-GATED — `controller_epoch` is a shared `Arc<AtomicU64>`, the writer snapshots its own epoch and returns the moment the shared value moves past it (checked before each frame on BOTH the initial-batch and live loops), so even a future double-subscribe can't keep a superseded writer flushing (W1-safe: never blocks the drain under `Mutex<OutputLog>`); (3) `Brain::handoff` seeds `session_cursors` at `prior.next_seq` → the consumer uses the production dedup-below + snap-above path, not the strict reject-gap legacy trap.
- **spt-core mapping:** impl `brain.rs` `Brain::handoff` (drop the eager `subscribe`; seed `session_cursors`) + `broker.rs` `OutputLog.controller_epoch: Arc<AtomicU64>` / `become_controller` (atomic `fetch_add`, passes the new epoch + `Arc::clone` into the writer) / `controller_writer` (epoch gate on both loops) / `mark_controller_gone` + the `ControllerJob` epoch read; unit the epoch-gated writer (a superseded writer flushes nothing — only the latest writer's monotonic 0,1 reaches the wire) + `handoff` seeds `session_cursors` + issues no eager subscribe; int (keystone) deterministically force two `become_controller`-on-one-connection on a real broker+brain (no mocks) — PRE-FIX reorders/gaps, POST-FIX monotonic + byte-exact + session live, PLUS `attach_survives_target_brain_restart_exactly_once` green (doyle: 20× isolated single-threaded timeout-wrapped on Linux/kitsubito — the deterministic RED-on-revert carrier).
- **Source:** v0.13.0 P1c (operator-ruled root-fix before ship; doyle root-cause via instrumented repro 2026-06-20). The last v0.13.0 ship-blocker.

---

## Conformance checklist (condensed)


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

- **Source:** v0.13.0 P1b (operator HITL; doyle design 2026-06-19). Bundled with 7.19.

---

## Conformance checklist (condensed)

- **Source:** v0.13.0 P1b (operator HITL; doyle design 2026-06-19). Bundled with 7.19.

<!-- [doc->REQ-HAZARD-CONTROLLER-WRITER-REORDER] -->
### 7.21 Exactly ONE `controller_writer` per brain↔broker connection — a superseded writer must write nothing further  `[REQ-HAZARD-CONTROLLER-WRITER-REORDER]`
- **Failure (doyle instrumented RACEDIAG repro, kitsubito):** on a brain-restart re-serve the handoff brain registered as controller on the SAME session TWICE over the SAME socket — `Brain::handoff` eagerly `subscribe(prior.next_seq=1)` → `become_controller(from_seq=1)`, spawning writer-A (writes seq 1); then `serve_attach` re-handled the replayed `Request{from_seq:0}` → `attach_as(sid,0)` → `become_controller(from_seq=0)`, spawning writer-B (writes 0,1). `become_controller` dropped the prior `ControllerSink` (its `tx`) but did NOT stop the prior writer — writer-A kept flushing its OWNED `initial` batch, and both writers held clones of one `SharedSend` (`Arc<Mutex<socket>>`) with no inter-thread ordering. When writer-A's seq 1 beat writer-B's seq 0, the strict consumer saw `output gap: got seq 1 want 0` → `attach_survives_target_brain_restart_exactly_once` panicked at `.expect("re-serve")` OR HUNG in `render_until` (serve thread died on the gap → `MARKER_TWO` never reached the wire). `prior.next_seq` is life1's CONSUMPTION cursor, not life2's connection state (this socket was sent nothing), so a full `from_seq=0` replay on a connection that already streamed seq 1 is contradictory. Snap-above tolerance alone can't fix it (it would dedup-drop the late seq 0 → byte loss → the exactly-once byte-identity assert fails). PRE-EXISTING, surfaced by the v0.13.0 green-both-runners gate; P1b is innocent. Sibling flaky cluster: `inject_control_wedge::g2`, `broker::spawn_env_reaches_child`.
- **Invariant:** on a single brain↔broker connection the controller output-frame stream is **monotonic non-decreasing in seq** (modulo dedup re-sends); exactly ONE `controller_writer` is ever live per connection; a SUPERSEDED writer writes NO further frames; a re-serve never replays a seq below what the connection already received. Enforced three ways: (1) `Brain::handoff` no longer eagerly subscribes — the re-serve's `attach_as(sid,0)` is the SOLE controller registration + replay (handoff is test-only; production resumes via `cold_start`+`resume_sessions`, one subscribe each); (2) `controller_writer` is EPOCH-GATED — `controller_epoch` is a shared `Arc<AtomicU64>`, the writer snapshots its own epoch and returns the moment the shared value moves past it (checked before each frame on BOTH the initial-batch and live loops), so even a future double-subscribe can't keep a superseded writer flushing (W1-safe: never blocks the drain under `Mutex<OutputLog>`); (3) `Brain::handoff` seeds `session_cursors` at `prior.next_seq` → the consumer uses the production dedup-below + snap-above path, not the strict reject-gap legacy trap.
- **spt-core mapping:** impl `brain.rs` `Brain::handoff` (drop the eager `subscribe`; seed `session_cursors`) + `broker.rs` `OutputLog.controller_epoch: Arc<AtomicU64>` / `become_controller` (atomic `fetch_add`, passes the new epoch + `Arc::clone` into the writer) / `controller_writer` (epoch gate on both loops) / `mark_controller_gone` + the `ControllerJob` epoch read; unit the epoch-gated writer (a superseded writer flushes nothing — only the latest writer's monotonic 0,1 reaches the wire) + `handoff` seeds `session_cursors` + issues no eager subscribe; int (keystone) deterministically force two `become_controller`-on-one-connection on a real broker+brain (no mocks) — PRE-FIX reorders/gaps, POST-FIX monotonic + byte-exact + session live, PLUS `attach_survives_target_brain_restart_exactly_once` green (doyle: 20× isolated single-threaded timeout-wrapped on Linux/kitsubito — the deterministic RED-on-revert carrier).
- **Source:** v0.13.0 P1c (operator-ruled root-fix before ship; doyle root-cause via instrumented repro 2026-06-20). The last v0.13.0 ship-blocker.

---

## Conformance checklist (condensed)

- **spt-core mapping:** impl `rc.rs` `MouseMode` + `MouseModeScanner`/`parse_decset_private`/`apply_mouse_mode` (carry-buffer scan, fed from the pump's output path) + `scroll_dir` + `scroll_sgr` + the `spawn_stdin_reader_events` scroll arm (threaded via `Arc<MouseMode>`); unit `scroll_dir` classify + `scroll_sgr` exact bytes + the DECSET scan (set/reset, combined, mixed, and split-across-chunks); NO int (live console mouse = HITL).

+### 7.21 Exactly ONE `controller_writer` per brain↔broker connection — a superseded writer must write nothing further  `[REQ-HAZARD-CONTROLLER-WRITER-REORDER]`

+- **Failure (doyle instrumented RACEDIAG repro, kitsubito):** on a brain-restart re-serve the handoff brain registered as controller on the SAME session TWICE over the SAME socket — `Brain::handoff` eagerly `subscribe(prior.next_seq=1)` → `become_controller(from_seq=1)`, spawning writer-A (writes seq 1); then `serve_attach` re-handled the replayed `Request{from_seq:0}` → `attach_as(sid,0)` → `become_controller(from_seq=0)`, spawning writer-B (writes 0,1). `become_controller` dropped the prior `ControllerSink` (its `tx`) but did NOT stop the prior writer — writer-A kept flushing its OWNED `initial` batch, and both writers held clones of one `SharedSend` (`Arc<Mutex<socket>>`) with no inter-thread ordering. When writer-A's seq 1 beat writer-B's seq 0, the strict consumer saw `output gap: got seq 1 want 0` → `attach_survives_target_brain_restart_exactly_once` panicked at `.expect("re-serve")` OR HUNG in `render_until` (serve thread died on the gap → `MARKER_TWO` never reached the wire). `prior.next_seq` is life1's CONSUMPTION cursor, not life2's connection state (this socket was sent nothing), so a full `from_seq=0` replay on a connection that already streamed seq 1 is contradictory. Snap-above tolerance alone can't fix it (it would dedup-drop the late seq 0 → byte loss → the exactly-once byte-identity assert fails). PRE-EXISTING, surfaced by the v0.13.0 green-both-runners gate; P1b is innocent. Sibling flaky cluster: `inject_control_wedge::g2`, `broker::spawn_env_reaches_child`.

+- **Invariant:** on a single brain↔broker connection the controller output-frame stream is **monotonic non-decreasing in seq** (modulo dedup re-sends); exactly ONE `controller_writer` is ever live per connection; a SUPERSEDED writer writes NO further frames; a re-serve never replays a seq below what the connection already received. Enforced three ways: (1) `Brain::handoff` no longer eagerly subscribes — the re-serve's `attach_as(sid,0)` is the SOLE controller registration + replay (handoff is test-only; production resumes via `cold_start`+`resume_sessions`, one subscribe each); (2) `controller_writer` is EPOCH-GATED — `controller_epoch` is a shared `Arc<AtomicU64>`, the writer snapshots its own epoch and returns the moment the shared value moves past it (checked before each frame on BOTH the initial-batch and live loops), so even a future double-subscribe can't keep a superseded writer flushing (W1-safe: never blocks the drain under `Mutex<OutputLog>`); (3) `Brain::handoff` seeds `session_cursors` at `prior.next_seq` → the consumer uses the production dedup-below + snap-above path, not the strict reject-gap legacy trap.

+- **spt-core mapping:** impl `brain.rs` `Brain::handoff` (drop the eager `subscribe`; seed `session_cursors`) + `broker.rs` `OutputLog.controller_epoch: Arc<AtomicU64>` / `become_controller` (atomic `fetch_add`, passes the new epoch + `Arc::clone` into the writer) / `controller_writer` (epoch gate on both loops) / `mark_controller_gone` + the `ControllerJob` epoch read; unit the epoch-gated writer (a superseded writer flushes nothing — only the latest writer's monotonic 0,1 reaches the wire) + `handoff` seeds `session_cursors` + issues no eager subscribe; int (keystone) deterministically force two `become_controller`-on-one-connection on a real broker+brain (no mocks) — PRE-FIX reorders/gaps, POST-FIX monotonic + byte-exact + session live, PLUS `attach_survives_target_brain_restart_exactly_once` green (doyle: 20× isolated single-threaded timeout-wrapped on Linux/kitsubito — the deterministic RED-on-revert carrier).

+- **Source:** v0.13.0 P1c (operator-ruled root-fix before ship; doyle root-cause via instrumented repro 2026-06-20). The last v0.13.0 ship-blocker.


| 7.20 | `spt rc` must forward the SCROLL wheel to the harness — our mouse capture (for right-click paste, 7.18) steals WT's native scroll. Track the harness's mouse-reporting mode from its OUTPUT (DECSET `ESC[?1000/1002/1003h` + `1006h` SGR, and `…l` off; scan survives a split across output chunks) into `MouseMode{enabled,sgr}`; forward `ScrollUp/Down` as an xterm SGR report (`ESC[<64/65;col+1;row+1M`) ONLY when `enabled && sgr`, else drop; Moved/drag/clicks dropped. cfg(windows) only — Unix scrolls natively | `rc.rs` `MouseMode`/`MouseModeScanner`/`scroll_dir`/`scroll_sgr`/`spawn_stdin_reader_events`, unit-only (live mouse = HITL) |

| 7.20 | `spt rc` must forward the SCROLL wheel to the harness — our mouse capture (for right-click paste, 7.18) steals WT's native scroll. Track the harness's mouse-reporting mode from its OUTPUT (DECSET `ESC[?1000/1002/1003h` + `1006h` SGR, and `…l` off; scan survives a split across output chunks) into `MouseMode{enabled,sgr}`; forward `ScrollUp/Down` as an xterm SGR report (`ESC[<64/65;col+1;row+1M`) ONLY when `enabled && sgr`, else drop; Moved/drag/clicks dropped. cfg(windows) only — Unix scrolls natively | `rc.rs` `MouseMode`/`MouseModeScanner`/`scroll_dir`/`scroll_sgr`/`spawn_stdin_reader_events`, unit-only (live mouse = HITL) |
| 7.21 | Exactly ONE `controller_writer` per brain↔broker connection — a superseded writer writes nothing further, so the on-wire controller seq stream stays monotonic. A brain-restart re-serve double-registered the controller (handoff's eager `subscribe(from_seq=1)` + the re-serve's `attach_as(sid,0)`) → two writers raced one socket → `got seq 1 want 0` gap (flaky `attach_survives_target_brain_restart_exactly_once`). FIX: (1) `handoff` drops the eager subscribe (the re-serve's `attach_as` is the sole take); (2) `controller_writer` is epoch-gated via a shared `Arc<AtomicU64>` `controller_epoch` (returns the instant it's superseded, both loops); (3) `handoff` seeds `session_cursors` → dedup-below + snap-above, not reject-gap. Pre-existing; P1b innocent | `brain.rs` `handoff`, `broker.rs` `become_controller`/`controller_writer`/`controller_epoch`, int `attach.rs` (keystone + restart carrier, 20× on kitsubito) |


| 7.20 | `spt rc` must forward the SCROLL wheel to the harness — our mouse capture (for right-click paste, 7.18) steals WT's native scroll. Track the harness's mouse-reporting mode from its OUTPUT (DECSET `ESC[?1000/1002/1003h` + `1006h` SGR, and `…l` off; scan survives a split across output chunks) into `MouseMode{enabled,sgr}`; forward `ScrollUp/Down` as an xterm SGR report (`ESC[<64/65;col+1;row+1M`) ONLY when `enabled && sgr`, else drop; Moved/drag/clicks dropped. cfg(windows) only — Unix scrolls natively | `rc.rs` `MouseMode`/`MouseModeScanner`/`scroll_dir`/`scroll_sgr`/`spawn_stdin_reader_events`, unit-only (live mouse = HITL) |
| 7.21 | Exactly ONE `controller_writer` per brain↔broker connection — a superseded writer writes nothing further, so the on-wire controller seq stream stays monotonic. A brain-restart re-serve double-registered the controller (handoff's eager `subscribe(from_seq=1)` + the re-serve's `attach_as(sid,0)`) → two writers raced one socket → `got seq 1 want 0` gap (flaky `attach_survives_target_brain_restart_exactly_once`). FIX: (1) `handoff` drops the eager subscribe (the re-serve's `attach_as` is the sole take); (2) `controller_writer` is epoch-gated via a shared `Arc<AtomicU64>` `controller_epoch` (returns the instant it's superseded, both loops); (3) `handoff` seeds `session_cursors` → dedup-below + snap-above, not reject-gap. Pre-existing; P1b innocent | `brain.rs` `handoff`, `broker.rs` `become_controller`/`controller_writer`/`controller_epoch`, int `attach.rs` (keystone + restart carrier, 20× on kitsubito) |

| 7.18 | `spt rc` paste is **client-originated** on Windows: CC runs daemon-side with no access to the operator's LOCAL clipboard. On a RIGHT-CLICK rc reads the local clipboard itself and injects a BRACKETED paste (`ESC[200~` + content + `ESC[201~`) — CC has bracketed-paste mode on (`ESC[?2004h`), so a multi-line paste lands intact with NO `\r` submit-storm, harness-agnostic, content VERBATIM. RawGuard captures the mouse (disables console QuickEdit so right-click reaches the app) on an interactive console only + restores on drop. **ctrl+V is NOT intercepted** (P1b): WT consumes it (Key RELEASE only, never Press) + injects the clipboard as a key flood — it rides WT's native paste as keystrokes (no wedge, 7.19; bracketed = right-click). cfg(windows) only — Unix pastes natively | `rc.rs` `wrap_bracketed_paste`/`mouse_is_paste`/`clipboard_paste`/`read_clipboard`/`RawGuard`/`spawn_stdin_reader_events`, unit-only (live clipboard+mouse = HITL) |

| 7.19 | An operator input FLOOD must not deadlock the broker: `serve_attach` sends N `Input` frames on one conn without reading, the broker acks each on the SAME conn → the return direction fills (~pipe buffer) → `send_frame` blocks the per-conn handler → mutual full-duplex DEADLOCK (permanent broker wedge, controller latched). FIX: opt-in ack — `InputReq.ack` (serde default true); the operator/rc path sends `ack=false` (`send_effect_no_ack`), `dispatch_input` skips the applied frame → the handler never writes back while draining the flood. `shellchan` keeps `ack=true` (its `Applied`-wait). Exactly-once unaffected (dedup at the applied-set). N-1: an old resident broker still acks until restart (7.9 class) | `msg.rs` `InputReq.ack`, `brain.rs` `send_effect_no_ack`, `attach.rs` `serve_attach`, `broker.rs` `dispatch_input`, int (flood repro) |

+| 7.21 | Exactly ONE `controller_writer` per brain↔broker connection — a superseded writer writes nothing further, so the on-wire controller seq stream stays monotonic. A brain-restart re-serve double-registered the controller (handoff's eager `subscribe(from_seq=1)` + the re-serve's `attach_as(sid,0)`) → two writers raced one socket → `got seq 1 want 0` gap (flaky `attach_survives_target_brain_restart_exactly_once`). FIX: (1) `handoff` drops the eager subscribe (the re-serve's `attach_as` is the sole take); (2) `controller_writer` is epoch-gated via a shared `Arc<AtomicU64>` `controller_epoch` (returns the instant it's superseded, both loops); (3) `handoff` seeds `session_cursors` → dedup-below + snap-above, not reject-gap. Pre-existing; P1b innocent | `brain.rs` `handoff`, `broker.rs` `become_controller`/`controller_writer`/`controller_epoch`, int `attach.rs` (keystone + restart carrier, 20× on kitsubito) |


Doc + impl evidence tagged. Let me check traceability status (unit/int will still be open — tests next):


Bash

cd C:/Users/decid/Documents/projects/spt-core && (traceable-reqs check 2>&1 | grep -iA3 "CONTROLLER-WRITER-REORDER\|missing\|error\|EXIT" | head -40 || echo "traceable-reqs not found in PATH")

Run traceable-reqs check for the new REQ