108	            let _ = crossterm::terminal::disable_raw_mode();
109	        }
110	    }
111	}
112
113	/// Resolve a live endpoint id to its broker session id via the broker's hosted
114	/// session table (the spawn's endpoint label, brain.sessions()). `None` ⇒ no
115	/// live session under that endpoint.
116	fn resolve_session(brain: &mut Brain, endpoint_id: &str) -> std::io::Result<Option<u64>> {
117	    let reply = brain.sessions()?;
118	    Ok(reply
119	        .sessions
120	        .into_iter()
121	        .find(|s| s.endpoint == endpoint_id)
122	        .map(|s| s.session_id))
123	}
124
125	/// The node currently driving `endpoint_id`, if any — read off the perch's
126	/// broker-owned `driven_by` marker (REQ-RCVIEW-1). Drives the client-side
127	/// busy-refuse guidance. `None` ⇒ free (or no perch).
128	fn current_driver(endpoint_id: &str) -> Option<String> {
129	    let perch = spt_store::perch::resolve_perch_path(
130	        endpoint_id,
131	        spt_store::perch::ParentHint::Infer,
132	    );
133	    spt_store::info::read_info(&perch).and_then(|i| i.driven_by)
134	}
135
136	/// What one stdin chunk yielded after the detach state machine: the bytes to
137	/// forward to the session, and whether the detach keybind fired.
138	struct DetachParse {
139	    forward: Vec<u8>,
140	    detach: bool,
141	}
142
143	/// Apply the `ctrl-b` detach state machine to one raw stdin chunk. `armed` holds
144	/// the "saw the prefix" state across chunk boundaries (a prefix can end one read
145	/// and its companion key begin the next). `ctrl-b d` → detach; `ctrl-b ctrl-b` →
146	/// one literal `ctrl-b`; `ctrl-b <other>` → prefix + other verbatim; every other
147	/// byte passes through. Pure so it unit-tests without a terminal. On detach the
148	/// bytes BEFORE the keybind are returned to forward; bytes after are dropped (the
149	/// viewport is ending).
150	fn parse_stdin_chunk(armed: &mut bool, input: &[u8]) -> DetachParse {
151	    let mut forward = Vec::with_capacity(input.len());
152	    for &b in input {
153	        if *armed {
154	            *armed = false;
155	            match b {
156	                DETACH_KEY => return DetachParse { forward, detach: true },
157	                DETACH_PREFIX => forward.push(DETACH_PREFIX), // literal ctrl-b
158	                other => {
159	                    forward.push(DETACH_PREFIX);

            let _ = crossterm::terminal::disable_raw_mode();
        }
    }
}

/// Resolve a live endpoint id to its broker session id via the broker's hosted
/// session table (the spawn's endpoint label, brain.sessions()). `None` ⇒ no
/// live session under that endpoint.
fn resolve_session(brain: &mut Brain, endpoint_id: &str) -> std::io::Result<Option<u64>> {
    let reply = brain.sessions()?;
    Ok(reply
        .sessions
        .into_iter()
        .find(|s| s.endpoint == endpoint_id)
        .map(|s| s.session_id))
}

/// The node currently driving `endpoint_id`, if any — read off the perch's
/// broker-owned `driven_by` marker (REQ-RCVIEW-1). Drives the client-side
/// busy-refuse guidance. `None` ⇒ free (or no perch).
fn current_driver(endpoint_id: &str) -> Option<String> {
    let perch = spt_store::perch::resolve_perch_path(
        endpoint_id,
        spt_store::perch::ParentHint::Infer,
    );
    spt_store::info::read_info(&perch).and_then(|i| i.driven_by)
}

/// What one stdin chunk yielded after the detach state machine: the bytes to
/// forward to the session, and whether the detach keybind fired.
struct DetachParse {
    forward: Vec<u8>,
    detach: bool,
}

/// Apply the `ctrl-b` detach state machine to one raw stdin chunk. `armed` holds
/// the "saw the prefix" state across chunk boundaries (a prefix can end one read
/// and its companion key begin the next). `ctrl-b d` → detach; `ctrl-b ctrl-b` →
/// one literal `ctrl-b`; `ctrl-b <other>` → prefix + other verbatim; every other
/// byte passes through. Pure so it unit-tests without a terminal. On detach the
/// bytes BEFORE the keybind are returned to forward; bytes after are dropped (the
/// viewport is ending).
fn parse_stdin_chunk(armed: &mut bool, input: &[u8]) -> DetachParse {
    let mut forward = Vec::with_capacity(input.len());
    for &b in input {
        if *armed {
            *armed = false;
            match b {
                DETACH_KEY => return DetachParse { forward, detach: true },
                DETACH_PREFIX => forward.push(DETACH_PREFIX), // literal ctrl-b
                other => {
                    forward.push(DETACH_PREFIX);


175	/// Spawn the stdin reader: raw bytes → `StdinMsg::Bytes`, watching for the
176	/// detach keybind ([`parse_stdin_chunk`]). Detaching sends `StdinMsg::Detach`
177	/// and ends the thread; the leaked blocking `read` (if any) dies with the
178	/// process on exit.
179	fn spawn_stdin_reader(tx: mpsc::Sender<StdinMsg>) {
180	    std::thread::spawn(move || {
181	        let mut stdin = std::io::stdin().lock();
182	        let mut buf = [0u8; 1024];
183	        let mut armed = false; // saw the detach prefix, awaiting the next byte
184	        loop {
185	            let n = match stdin.read(&mut buf) {
186	                Ok(0) => break, // stdin closed
187	                Ok(n) => n,
188	                Err(_) => break,
189	            };
190	            // W7 (REQ-HAZARD-RC-INPUT-KEY-ENCODING) confirm-first hexdump: with
191	            // SPT_RC_DEBUG_KEYS set, print the RAW bytes the console delivered for
192	            // each keystroke. Press Backspace then ctrl+Backspace in a real rc
193	            // session — a Backspace that arrives as 0x08 (^H) instead of 0x7f
194	            // (VT DEL) confirms the legacy-console root (ENABLE_VIRTUAL_TERMINAL_INPUT
195	            // off). Gate the root before the fix.
196	            if std::env::var_os("SPT_RC_DEBUG_KEYS").is_some() {
197	                let hex: Vec<String> = buf[..n].iter().map(|b| format!("{b:02x}")).collect();
198	                eprintln!("RC_KEYS n={n} bytes=[{}]", hex.join(" "));
199	            }
200	            let parsed = parse_stdin_chunk(&mut armed, &buf[..n]);
201	            if !parsed.forward.is_empty() && tx.send(StdinMsg::Bytes(parsed.forward)).is_err() {
202	                break;
203	            }
204	            if parsed.detach {
205	                let _ = tx.send(StdinMsg::Detach);
206	                return;
207	            }
208	        }
209	    });
210	}
211
212	/// Attach a local terminal to the broker-held session for `endpoint_id` under
213	/// `intent` (REQ-RCVIEW-1 / REQ-KICK-1): `Control` drives a free endpoint (and is
214	/// busy-refused with guidance on a controlled one), `Viewer` watches read-only,

/// Spawn the stdin reader: raw bytes → `StdinMsg::Bytes`, watching for the
/// detach keybind ([`parse_stdin_chunk`]). Detaching sends `StdinMsg::Detach`
/// and ends the thread; the leaked blocking `read` (if any) dies with the
/// process on exit.
fn spawn_stdin_reader(tx: mpsc::Sender<StdinMsg>) {
    std::thread::spawn(move || {
        let mut stdin = std::io::stdin().lock();
        let mut buf = [0u8; 1024];
        let mut armed = false; // saw the detach prefix, awaiting the next byte
        loop {
            let n = match stdin.read(&mut buf) {
                Ok(0) => break, // stdin closed
                Ok(n) => n,
                Err(_) => break,
            };
            // W7 (REQ-HAZARD-RC-INPUT-KEY-ENCODING) confirm-first hexdump: with
            // SPT_RC_DEBUG_KEYS set, print the RAW bytes the console delivered for
            // each keystroke. Press Backspace then ctrl+Backspace in a real rc
            // session — a Backspace that arrives as 0x08 (^H) instead of 0x7f
            // (VT DEL) confirms the legacy-console root (ENABLE_VIRTUAL_TERMINAL_INPUT
            // off). Gate the root before the fix.
            if std::env::var_os("SPT_RC_DEBUG_KEYS").is_some() {
                let hex: Vec<String> = buf[..n].iter().map(|b| format!("{b:02x}")).collect();
                eprintln!("RC_KEYS n={n} bytes=[{}]", hex.join(" "));
            }
            let parsed = parse_stdin_chunk(&mut armed, &buf[..n]);
            if !parsed.forward.is_empty() && tx.send(StdinMsg::Bytes(parsed.forward)).is_err() {
                break;
            }
            if parsed.detach {
                let _ = tx.send(StdinMsg::Detach);
                return;
            }
        }
    });
}

/// Attach a local terminal to the broker-held session for `endpoint_id` under
/// `intent` (REQ-RCVIEW-1 / REQ-KICK-1): `Control` drives a free endpoint (and is
/// busy-refused with guidance on a controlled one), `Viewer` watches read-only,


Edit

            let parsed = parse_stdin_chunk(&mut armed, &buf[..n]);
            if !parsed.forward.is_empty() && tx.send(StdinMsg::Bytes(parsed.forward)).is_err() {
                break;
            }
            if parsed.detach {
                let _ = tx.send(StdinMsg::Detach);
                return;
            }

            let parsed = parse_stdin_chunk(&mut armed, &buf[..n]);
            // W7 (REQ-HAZARD-RC-INPUT-KEY-ENCODING): on Windows the legacy console
            // sends Backspace as 0x08 (^H → CC kill-word); normalize it to 0x7f
            // (VT DEL) in the forward path, AFTER the detach state machine has
            // consumed any 0x02 prefix. Unix is already VT (and a real ctrl+h
            // there must stay 0x08), so the map is Windows-only.
            #[cfg(windows)]
            let forward: Vec<u8> = parsed.forward.into_iter().map(normalize_key_byte).collect();
            #[cfg(unix)]
            let forward = parsed.forward;
            if !forward.is_empty() && tx.send(StdinMsg::Bytes(forward)).is_err() {
                break;
            }
            if parsed.detach {
                let _ = tx.send(StdinMsg::Detach);
                return;
            }


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

            let parsed = parse_stdin_chunk(&mut armed, &buf[..n]);
            if !parsed.forward.is_empty() && tx.send(StdinMsg::Bytes(parsed.forward)).is_err() {
                break;
            }
            if parsed.detach {
                let _ = tx.send(StdinMsg::Detach);
                return;
            }

            let parsed = parse_stdin_chunk(&mut armed, &buf[..n]);
            // W7 (REQ-HAZARD-RC-INPUT-KEY-ENCODING): on Windows the legacy console
            // sends Backspace as 0x08 (^H → CC kill-word); normalize it to 0x7f
            // (VT DEL) in the forward path, AFTER the detach state machine has
            // consumed any 0x02 prefix. Unix is already VT (and a real ctrl+h
            // there must stay 0x08), so the map is Windows-only.
            #[cfg(windows)]
            let forward: Vec<u8> = parsed.forward.into_iter().map(normalize_key_byte).collect();
            #[cfg(unix)]
            let forward = parsed.forward;
            if !forward.is_empty() && tx.send(StdinMsg::Bytes(forward)).is_err() {
                break;
            }
            if parsed.detach {
                let _ = tx.send(StdinMsg::Detach);
                return;
            }

eprintln!("RC_KEYS n={n} bytes=[{}]", hex.join(" "));

-            if !parsed.forward.is_empty() && tx.send(StdinMsg::Bytes(parsed.forward)).is_err() {

+            // W7 (REQ-HAZARD-RC-INPUT-KEY-ENCODING): on Windows the legacy console

+            // sends Backspace as 0x08 (^H → CC kill-word); normalize it to 0x7f

+            // (VT DEL) in the forward path, AFTER the detach state machine has

+            // consumed any 0x02 prefix. Unix is already VT (and a real ctrl+h

+            // there must stay 0x08), so the map is Windows-only.

+            let forward: Vec<u8> = parsed.forward.into_iter().map(normalize_key_byte).collect();

+            let forward = parsed.forward;

+            if !forward.is_empty() && tx.send(StdinMsg::Bytes(forward)).is_err() {


Replace the now-obsolete `with_vt_input` unit test with a `normalize_key_byte` test.


    // [unit->REQ-HAZARD-RC-INPUT-KEY-ENCODING] the VT-input mode-bit decision
    // seam: OR-in the ENABLE_VIRTUAL_TERMINAL_INPUT bit, preserve every other
    // flag, idempotent. This is what flips the legacy console from emitting ^H
    // (0x08, → CC kill-word) to VT DEL (0x7f) for Backspace. The impure
    // GetStdHandle/Get/SetConsoleMode shell is HITL-verified by the operator key
    // re-capture, not here.
    #[cfg(windows)]
    #[test]
    fn with_vt_input_sets_the_bit_and_preserves_other_flags() {
        // Sets the VT-input bit from a clean slate.
        assert_eq!(
            with_vt_input(0) & ENABLE_VIRTUAL_TERMINAL_INPUT,
            ENABLE_VIRTUAL_TERMINAL_INPUT
        );
        // OR-in only: every pre-existing (raw-mode-ish) flag survives untouched.
        let raw = 0x0001 | 0x0040 | 0x0080;
        let got = with_vt_input(raw);
        assert_eq!(got & raw, raw, "no existing console flag is cleared");
        assert_eq!(got, raw | ENABLE_VIRTUAL_TERMINAL_INPUT);
        // Idempotent — already-set is a no-op (the restore-on-exit relies on this
        // being a pure superset so the saved prior mode round-trips).
        assert_eq!(with_vt_input(got), got);
    }

    // [unit->REQ-HAZARD-RC-INPUT-KEY-ENCODING] the Windows key-byte normalize:
    // the legacy console's Backspace ^H (0x08) maps to VT DEL (0x7f, char-delete)
    // so Claude Code stops kill-wording it; the ctrl-b detach prefix (0x02) and
    // every other byte pass through untouched (so detach still works and a CR /
    // arrow-CSI / payload byte is never corrupted). The live keypress is
    // HITL-verified by the operator re-capture, not here.
    #[cfg(windows)]
    #[test]
    fn normalize_maps_backspace_but_leaves_detach_and_others() {
        assert_eq!(normalize_key_byte(0x08), 0x7f, "Backspace ^H -> VT DEL");
        assert_eq!(normalize_key_byte(0x02), 0x02, "ctrl-b detach prefix untouched");
        assert_eq!(normalize_key_byte(0x7f), 0x7f, "VT DEL passes through");
        assert_eq!(normalize_key_byte(b'a'), b'a', "ordinary byte untouched");
        assert_eq!(normalize_key_byte(0x0d), 0x0d, "Enter (CR) untouched");
        assert_eq!(normalize_key_byte(0x1b), 0x1b, "ESC (CSI lead) untouched");
    }


    // [unit->REQ-HAZARD-RC-INPUT-KEY-ENCODING] the VT-input mode-bit decision
    // seam: OR-in the ENABLE_VIRTUAL_TERMINAL_INPUT bit, preserve every other
    // flag, idempotent. This is what flips the legacy console from emitting ^H
    // (0x08, → CC kill-word) to VT DEL (0x7f) for Backspace. The impure
    // GetStdHandle/Get/SetConsoleMode shell is HITL-verified by the operator key
    // re-capture, not here.
    #[cfg(windows)]
    #[test]
    fn with_vt_input_sets_the_bit_and_preserves_other_flags() {
        // Sets the VT-input bit from a clean slate.
        assert_eq!(
            with_vt_input(0) & ENABLE_VIRTUAL_TERMINAL_INPUT,
            ENABLE_VIRTUAL_TERMINAL_INPUT
        );
        // OR-in only: every pre-existing (raw-mode-ish) flag survives untouched.
        let raw = 0x0001 | 0x0040 | 0x0080;
        let got = with_vt_input(raw);
        assert_eq!(got & raw, raw, "no existing console flag is cleared");
        assert_eq!(got, raw | ENABLE_VIRTUAL_TERMINAL_INPUT);
        // Idempotent — already-set is a no-op (the restore-on-exit relies on this
        // being a pure superset so the saved prior mode round-trips).
        assert_eq!(with_vt_input(got), got);
    }

    // [unit->REQ-HAZARD-RC-INPUT-KEY-ENCODING] the Windows key-byte normalize:
    // the legacy console's Backspace ^H (0x08) maps to VT DEL (0x7f, char-delete)
    // so Claude Code stops kill-wording it; the ctrl-b detach prefix (0x02) and
    // every other byte pass through untouched (so detach still works and a CR /
    // arrow-CSI / payload byte is never corrupted). The live keypress is
    // HITL-verified by the operator re-capture, not here.
    #[cfg(windows)]
    #[test]
    fn normalize_maps_backspace_but_leaves_detach_and_others() {
        assert_eq!(normalize_key_byte(0x08), 0x7f, "Backspace ^H -> VT DEL");
        assert_eq!(normalize_key_byte(0x02), 0x02, "ctrl-b detach prefix untouched");
        assert_eq!(normalize_key_byte(0x7f), 0x7f, "VT DEL passes through");
        assert_eq!(normalize_key_byte(b'a'), b'a', "ordinary byte untouched");
        assert_eq!(normalize_key_byte(0x0d), 0x0d, "Enter (CR) untouched");
        assert_eq!(normalize_key_byte(0x1b), 0x1b, "ESC (CSI lead) untouched");
    }

-    // [unit->REQ-HAZARD-RC-INPUT-KEY-ENCODING] the VT-input mode-bit decision

-    // seam: OR-in the ENABLE_VIRTUAL_TERMINAL_INPUT bit, preserve every other

-    // flag, idempotent. This is what flips the legacy console from emitting ^H

-    // (0x08, → CC kill-word) to VT DEL (0x7f) for Backspace. The impure

-    // GetStdHandle/Get/SetConsoleMode shell is HITL-verified by the operator key

+    // [unit->REQ-HAZARD-RC-INPUT-KEY-ENCODING] the Windows key-byte normalize:

+    // the legacy console's Backspace ^H (0x08) maps to VT DEL (0x7f, char-delete)

+    // so Claude Code stops kill-wording it; the ctrl-b detach prefix (0x02) and

+    // every other byte pass through untouched (so detach still works and a CR /

+    // arrow-CSI / payload byte is never corrupted). The live keypress is

+    // HITL-verified by the operator re-capture, not here.

-    fn with_vt_input_sets_the_bit_and_preserves_other_flags() {

-        // Sets the VT-input bit from a clean slate.

-            with_vt_input(0) & ENABLE_VIRTUAL_TERMINAL_INPUT,

-            ENABLE_VIRTUAL_TERMINAL_INPUT

-        // OR-in only: every pre-existing (raw-mode-ish) flag survives untouched.

-        assert_eq!(got & raw, raw, "no existing console flag is cleared");

-        assert_eq!(got, raw | ENABLE_VIRTUAL_TERMINAL_INPUT);

-        // Idempotent — already-set is a no-op (the restore-on-exit relies on this

-        // being a pure superset so the saved prior mode round-trips).

-        assert_eq!(with_vt_input(got), got);

+    fn normalize_maps_backspace_but_leaves_detach_and_others() {

+        assert_eq!(normalize_key_byte(0x08), 0x7f, "Backspace ^H -> VT DEL");

+        assert_eq!(normalize_key_byte(0x02), 0x02, "ctrl-b detach prefix untouched");

+        assert_eq!(normalize_key_byte(0x7f), 0x7f, "VT DEL passes through");

+        assert_eq!(normalize_key_byte(b'a'), b'a', "ordinary byte untouched");

+        assert_eq!(normalize_key_byte(0x0d), 0x0d, "Enter (CR) untouched");

+        assert_eq!(normalize_key_byte(0x1b), 0x1b, "ESC (CSI lead) untouched");

// [unit->REQ-HAZARD-RC-ATTACH-FAILFAST] path (b): the generous first-event


/spt:commune with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session


Configure test engineer subagent profile


Rewrite KNOWN-HAZARDS 7.13 for the normalize approach + the VT backfire.


### 7.13 `spt rc` must enable VT console input on Windows (legacy `^H` breaks the hosted TUI key map)  `[REQ-HAZARD-RC-INPUT-KEY-ENCODING]`
- **Failure (operator dogfooding):** `spt rc` is a raw verbatim stdin byte pump, but Windows crossterm raw mode does NOT set `ENABLE_VIRTUAL_TERMINAL_INPUT`, so the LEGACY console delivers `^H` (0x08) for Backspace — and Claude Code maps `^H` → backward-kill-word, i.e. every Backspace deletes a whole word. Confirmed bytes (operator HITL capture, real Windows Terminal): Backspace=0x08, Ctrl+Backspace=0x7f — the inverted-from-VT signature.
- **Invariant:** enable `ENABLE_VIRTUAL_TERMINAL_INPUT` on the stdin console alongside raw mode (NOT a blind 0x08→0x7f normalize, which would conflate Backspace with Ctrl+Backspace), and RESTORE the prior console mode on exit. Under VT input Backspace→0x7f (char-delete), Ctrl+Backspace→0x08 (CC kill-word), arrows/Home/End→CSI — the whole key map matches Unix; ctrl-b (0x02) detach is a control byte, unaffected. cfg(windows) only; Unix is already VT.
- **spt-core mapping:** impl `rc.rs` `with_vt_input`/`enable_vt_input`/`restore_in_mode` + `RawGuard`; unit the pure mode-bit seam (`with_vt_input`: OR-in + preserve other flags + idempotent); the FFI shell + keypress are HITL operator re-capture.
- **Source:** v0.13.0 W7 (operator dogfooding; doyle /diagnose + byte confirm 2026-06-19).

### 7.13 `spt rc` must normalize Windows legacy-console Backspace (`^H`→VT DEL); do NOT enable VT console input  `[REQ-HAZARD-RC-INPUT-KEY-ENCODING]`
- **Failure (operator dogfooding):** `spt rc` is a raw verbatim stdin byte pump; the legacy Windows console delivers Backspace as `^H` (0x08), and Claude Code maps `^H` → backward-kill-word, so every Backspace deletes a whole word. Confirmed bytes (operator HITL capture, real Windows Terminal): Backspace=0x08, Ctrl+Backspace=0x7f.
- **REJECTED fix (dc07c39, reverted):** enabling `ENABLE_VIRTUAL_TERMINAL_INPUT` on the stdin console BACKFIRED — on Windows Terminal that flag yields **win32-input-mode**, not legacy xterm VT. Every key then arrives as a 6-field `ESC[Vk;Sc;Uc;Kd;Cs;Rc_` key-down/up record: (1) the ctrl-b detach broke (0x02 is wrapped, so `parse_stdin_chunk` never sees the raw `DETACH_PREFIX`), and (2) win32-input-mode was forwarded into Claude Code's own ConPTY input negotiation (garbage). The VT lever is wrong for Windows Terminal. (Lesson: verify a terminal-input fix on the REAL terminal via the operator re-capture — unit/clippy never exercise the live key path.)
- **Invariant:** stay a raw byte pump and NARROW-normalize in the forward path — map a lone `0x08` → `0x7f` (VT DEL, char-delete) AFTER the detach state machine consumes any `0x02`, leaving `0x02` and every other byte untouched. cfg(windows) only (Unix is already VT; a real `ctrl+h` there must stay 0x08). Accepted minor loss: a real `ctrl+h` is conflated with Backspace, and Ctrl+Backspace (0x7f on this terminal) stays char-delete (word-delete lost on the secondary key). Optional follow-up (pending CC-binding confirm): remap Ctrl+Backspace 0x7f→`ctrl+w` (0x17) to restore word-delete.
- **spt-core mapping:** impl `rc.rs` `normalize_key_byte` (applied in `spawn_stdin_reader`'s forward path); unit `normalize_maps_backspace_but_leaves_detach_and_others` (0x08→0x7f, 0x02/0x7f/CR/ESC/ordinary untouched); the live keypress is HITL operator re-capture.
- **Source:** v0.13.0 W7 (operator dogfooding; doyle /diagnose + byte confirm + VT-backfire re-capture 2026-06-19).


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)

### 7.13 `spt rc` must enable VT console input on Windows (legacy `^H` breaks the hosted TUI key map)  `[REQ-HAZARD-RC-INPUT-KEY-ENCODING]`
- **Failure (operator dogfooding):** `spt rc` is a raw verbatim stdin byte pump, but Windows crossterm raw mode does NOT set `ENABLE_VIRTUAL_TERMINAL_INPUT`, so the LEGACY console delivers `^H` (0x08) for Backspace — and Claude Code maps `^H` → backward-kill-word, i.e. every Backspace deletes a whole word. Confirmed bytes (operator HITL capture, real Windows Terminal): Backspace=0x08, Ctrl+Backspace=0x7f — the inverted-from-VT signature.
- **Invariant:** enable `ENABLE_VIRTUAL_TERMINAL_INPUT` on the stdin console alongside raw mode (NOT a blind 0x08→0x7f normalize, which would conflate Backspace with Ctrl+Backspace), and RESTORE the prior console mode on exit. Under VT input Backspace→0x7f (char-delete), Ctrl+Backspace→0x08 (CC kill-word), arrows/Home/End→CSI — the whole key map matches Unix; ctrl-b (0x02) detach is a control byte, unaffected. cfg(windows) only; Unix is already VT.
- **spt-core mapping:** impl `rc.rs` `with_vt_input`/`enable_vt_input`/`restore_in_mode` + `RawGuard`; unit the pure mode-bit seam (`with_vt_input`: OR-in + preserve other flags + idempotent); the FFI shell + keypress are HITL operator re-capture.
- **Source:** v0.13.0 W7 (operator dogfooding; doyle /diagnose + byte confirm 2026-06-19).

### 7.13 `spt rc` must normalize Windows legacy-console Backspace (`^H`→VT DEL); do NOT enable VT console input  `[REQ-HAZARD-RC-INPUT-KEY-ENCODING]`
- **Failure (operator dogfooding):** `spt rc` is a raw verbatim stdin byte pump; the legacy Windows console delivers Backspace as `^H` (0x08), and Claude Code maps `^H` → backward-kill-word, so every Backspace deletes a whole word. Confirmed bytes (operator HITL capture, real Windows Terminal): Backspace=0x08, Ctrl+Backspace=0x7f.
- **REJECTED fix (dc07c39, reverted):** enabling `ENABLE_VIRTUAL_TERMINAL_INPUT` on the stdin console BACKFIRED — on Windows Terminal that flag yields **win32-input-mode**, not legacy xterm VT. Every key then arrives as a 6-field `ESC[Vk;Sc;Uc;Kd;Cs;Rc_` key-down/up record: (1) the ctrl-b detach broke (0x02 is wrapped, so `parse_stdin_chunk` never sees the raw `DETACH_PREFIX`), and (2) win32-input-mode was forwarded into Claude Code's own ConPTY input negotiation (garbage). The VT lever is wrong for Windows Terminal. (Lesson: verify a terminal-input fix on the REAL terminal via the operator re-capture — unit/clippy never exercise the live key path.)
- **Invariant:** stay a raw byte pump and NARROW-normalize in the forward path — map a lone `0x08` → `0x7f` (VT DEL, char-delete) AFTER the detach state machine consumes any `0x02`, leaving `0x02` and every other byte untouched. cfg(windows) only (Unix is already VT; a real `ctrl+h` there must stay 0x08). Accepted minor loss: a real `ctrl+h` is conflated with Backspace, and Ctrl+Backspace (0x7f on this terminal) stays char-delete (word-delete lost on the secondary key). Optional follow-up (pending CC-binding confirm): remap Ctrl+Backspace 0x7f→`ctrl+w` (0x17) to restore word-delete.
- **spt-core mapping:** impl `rc.rs` `normalize_key_byte` (applied in `spawn_stdin_reader`'s forward path); unit `normalize_maps_backspace_but_leaves_detach_and_others` (0x08→0x7f, 0x02/0x7f/CR/ESC/ordinary untouched); the live keypress is HITL operator re-capture.
- **Source:** v0.13.0 W7 (operator dogfooding; doyle /diagnose + byte confirm + VT-backfire re-capture 2026-06-19).

- **spt-core mapping:** impl `broker.rs` `OutputLog::append`/`controller_writer`/`become_controller`/`ControllerJob::deliver`/`clear_controller`; int `crates/spt-daemon/tests/inject_control_wedge.rs` (a fully-backed-up controller keeps `KIND_SESSIONS` answering = no wedge); unit the bounded-deliver + monotonic-cursor kernels. The input-side parks (write_input `write_all` on a full buffer; DSR-answer writer-mutex contention) are BENIGN on Windows ConPTY (absorbs a large inject), real only on Unix forkpty → bounded by W2's atomic-write substrate.

- **Source:** v0.12.x operator wedge → v0.13.0 W1 (doyle GATE-PASS 2026-06-19).

-### 7.13 `spt rc` must enable VT console input on Windows (legacy `^H` breaks the hosted TUI key map)  `[REQ-HAZARD-RC-INPUT-KEY-ENCODING]`

-- **Failure (operator dogfooding):** `spt rc` is a raw verbatim stdin byte pump, but Windows crossterm raw mode does NOT set `ENABLE_VIRTUAL_TERMINAL_INPUT`, so the LEGACY console delivers `^H` (0x08) for Backspace — and Claude Code maps `^H` → backward-kill-word, i.e. every Backspace deletes a whole word. Confirmed bytes (operator HITL capture, real Windows Terminal): Backspace=0x08, Ctrl+Backspace=0x7f — the inverted-from-VT signature.

-- **Invariant:** enable `ENABLE_VIRTUAL_TERMINAL_INPUT` on the stdin console alongside raw mode (NOT a blind 0x08→0x7f normalize, which would conflate Backspace with Ctrl+Backspace), and RESTORE the prior console mode on exit. Under VT input Backspace→0x7f (char-delete), Ctrl+Backspace→0x08 (CC kill-word), arrows/Home/End→CSI — the whole key map matches Unix; ctrl-b (0x02) detach is a control byte, unaffected. cfg(windows) only; Unix is already VT.

-- **spt-core mapping:** impl `rc.rs` `with_vt_input`/`enable_vt_input`/`restore_in_mode` + `RawGuard`; unit the pure mode-bit seam (`with_vt_input`: OR-in + preserve other flags + idempotent); the FFI shell + keypress are HITL operator re-capture.

-- **Source:** v0.13.0 W7 (operator dogfooding; doyle /diagnose + byte confirm 2026-06-19).

+### 7.13 `spt rc` must normalize Windows legacy-console Backspace (`^H`→VT DEL); do NOT enable VT console input  `[REQ-HAZARD-RC-INPUT-KEY-ENCODING]`

+- **Failure (operator dogfooding):** `spt rc` is a raw verbatim stdin byte pump; the legacy Windows console delivers Backspace as `^H` (0x08), and Claude Code maps `^H` → backward-kill-word, so every Backspace deletes a whole word. Confirmed bytes (operator HITL capture, real Windows Terminal): Backspace=0x08, Ctrl+Backspace=0x7f.

+- **REJECTED fix (dc07c39, reverted):** enabling `ENABLE_VIRTUAL_TERMINAL_INPUT` on the stdin console BACKFIRED — on Windows Terminal that flag yields **win32-input-mode**, not legacy xterm VT. Every key then arrives as a 6-field `ESC[Vk;Sc;Uc;Kd;Cs;Rc_` key-down/up record: (1) the ctrl-b detach broke (0x02 is wrapped, so `parse_stdin_chunk` never sees the raw `DETACH_PREFIX`), and (2) win32-input-mode was forwarded into Claude Code's own ConPTY input negotiation (garbage). The VT lever is wrong for Windows Terminal. (Lesson: verify a terminal-input fix on the REAL terminal via the operator re-capture — unit/clippy never exercise the live key path.)

+- **Invariant:** stay a raw byte pump and NARROW-normalize in the forward path — map a lone `0x08` → `0x7f` (VT DEL, char-delete) AFTER the detach state machine consumes any `0x02`, leaving `0x02` and every other byte untouched. cfg(windows) only (Unix is already VT; a real `ctrl+h` there must stay 0x08). Accepted minor loss: a real `ctrl+h` is conflated with Backspace, and Ctrl+Backspace (0x7f on this terminal) stays char-delete (word-delete lost on the secondary key). Optional follow-up (pending CC-binding confirm): remap Ctrl+Backspace 0x7f→`ctrl+w` (0x17) to restore word-delete.

+- **spt-core mapping:** impl `rc.rs` `normalize_key_byte` (applied in `spawn_stdin_reader`'s forward path); unit `normalize_maps_backspace_but_leaves_detach_and_others` (0x08→0x7f, 0x02/0x7f/CR/ESC/ordinary untouched); the live keypress is HITL operator re-capture.

+- **Source:** v0.13.0 W7 (operator dogfooding; doyle /diagnose + byte confirm + VT-backfire re-capture 2026-06-19).


Update the conformance-checklist row + the W7 REQ comment.


| 7.13 | `spt rc` enables `ENABLE_VIRTUAL_TERMINAL_INPUT` on Windows stdin (Backspace→0x7f, not legacy `^H`), restoring the prior console mode on exit | `rc.rs` `RawGuard`/`with_vt_input` |

| 7.13 | `spt rc` narrow-normalizes Windows legacy-console Backspace `^H` (0x08)→VT DEL (0x7f) in the raw forward path (0x02 detach + other bytes untouched); does NOT enable VT console input (= win32-input-mode on WT, broke detach) | `rc.rs` `normalize_key_byte` in `spawn_stdin_reader` |


| 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` enables `ENABLE_VIRTUAL_TERMINAL_INPUT` on Windows stdin (Backspace→0x7f, not legacy `^H`), restoring the prior console mode on exit | `rc.rs` `RawGuard`/`with_vt_input` |

+| 7.13 | `spt rc` narrow-normalizes Windows legacy-console Backspace `^H` (0x08)→VT DEL (0x7f) in the raw forward path (0x02 detach + other bytes untouched); does NOT enable VT console input (= win32-input-mode on WT, broke detach) | `rc.rs` `normalize_key_byte` in `spawn_stdin_reader` |


<owl_messages>
<owl_message from="doyle" priority="high">Two things, one urgent correction (you're mid-revert):

A) W3 GATE — clippy FAIL, fix before close. Workspace clippy (the CI form: cargo clippy --workspace --all-targets -D warnings) errors: establish_perch (startup.rs:179) is now 8/7 args → clippy::too_many_arguments → CI RED. Your `cargo build -p spt --bin spt` is a BUILD, not clippy — it can't catch a lint. Fix: bundle the bind params into a small struct, OR #[allow(clippy::too_many_arguments)] with a one-line justification. W3 is OTHERWISE gate-PASS (source matches my RCA, REQ [impl,unit,int] activated, traceable EXIT=0, fix correct) — pending only this lint. Re-run the EXACT CI clippy before declaring W3 done (the ci-clippy-preflight-workspace lesson).

B) W7 — NEW operator data you haven't seen (it crossed your revert msg). The operator reports: AFTER the VT flip, Claude Code input was NORMAL — Backspace AND all keys behaved correctly. CC UNDERSTANDS the win32-input-mode records. The ONLY regression was the rc ctrl-b DETACH hotkey (0x02 wrapped). The "wedge" they hit is the W1 daemon-side bug — their RUNNING daemon is the STALE installed binary (no 8b5583e); the fresh rc client can't fix the old broker. So my earlier "win32-input-mode = garbage to CC" was WRONG — it worked.

RULING (revised, but your revert+normalize STILL stands as the baseline): proceed with revert+narrow-normalize — it's the conservative HARNESS-AGNOSTIC fix (clean 0x7f every TUI understands; win32-input-mode ASSUMES the hosted app opted into it = NOT agnostic, a non-CC adapter could get garbage). Ship that for W7. BUT LOG A FOLLOW-UP, don't lose the finding: the IDEAL is clean xterm VT (Backspace→0x7f, arrows→ESC[, ctrl-b→0x02 RAW so detach survives) = agnostic AND full key fidelity AND working detach. What we got (win32-input-mode) is NOT that. Next-session investigation: can Windows Terminal emit legacy xterm VT instead of win32-input-mode (the right lever), or do we accept narrow-normalize (backspace-only; arrows/Home/End stay whatever legacy console sends)? Mint a follow-up REQ or note it on REQ-HAZARD-RC-INPUT-KEY-ENCODING.

Net: revert+normalize = shippable W7 now; clean-VT-fidelity = tracked follow-up. Ping the revert+normalize commit AND the W3 clippy fix; I rebuild isolated, operator re-captures next session.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


1221	required_stages = ["impl", "unit"]  # ACTIVATED v0.13.0 W7 (todlando). BYTE CONFIRMED (operator HITL re-capture via SPT_RC_DEBUG_KEYS on the fresh debug binary, real Windows Terminal): pre-fix Backspace=0x08 (^H), Ctrl+Backspace=0x7f (DEL) — the INVERTED-from-VT signature of ENABLE_VIRTUAL_TERMINAL_INPUT being OFF (CC maps ^H → backward-kill-word = the whole-word delete). FIX (a, doyle ruling, NOT the 0x08→0x7f normalize which would conflate Backspace/Ctrl+Backspace): RawGuard now enables ENABLE_VIRTUAL_TERMINAL_INPUT on the stdin console alongside crossterm raw mode and restores the prior mode on drop (cfg(windows); Unix passthrough unchanged) — Backspace→0x7f, Ctrl+Backspace→0x08, arrows/Home/End→CSI, ctrl-b(0x02) detach unaffected. impl = rc.rs with_vt_input/enable_vt_input/restore_in_mode + RawGuard. unit = with_vt_input_sets_the_bit_and_preserves_other_flags (the pure mode-bit seam; the FFI shell + keypress are HITL operator re-capture). doc = KNOWN-HAZARDS.md entry. The SPT_RC_DEBUG_KEYS hexdump (committed f05566b) confirmed the root before the patch.
1222


doyle: (A) **W3 clippy FAIL** — `establish_perch` now 8 args → `too_many_arguments` → CI red (my `cargo build` ≠ clippy). (B) **W7** — operator data flips his earlier read: CC *did* understand win32-input-mode; only the ctrl-b detach broke (the "wedge" was the stale-binary W1 daemon bug). Ruling holds: ship revert+narrow-normalize (harness-agnostic), log clean-xterm-VT as a follow-up. Acking.


Bash

$OWL send doyle <<'EOF'
ACK both.

A) W3 clippy: right — `cargo build` can't catch a lint; I never ran the workspace clippy form. establish_perch is 8/7 args now. Fixing with #[allow(clippy::too_many_arguments)] + a one-line justification (private bind helper; a params struct is the heavier option, deferring that to avoid wind-down churn). Will re-run the EXACT CI form `cargo clippy --workspace --all-targets -- -D warnings` before declaring W3 done. ci-clippy-preflight-workspace lesson logged.

B) W7: noted the operator correction — CC understood win32-input-mode; the only real regression was rc ctrl-b detach (0x02 wrapped), and the wedge was the STALE installed daemon binary (no 8b5583e), not the VT fix. Your "garbage to CC" was wrong; good catch. Ruling accepted: revert+narrow-normalize SHIPS W7 (harness-agnostic — clean 0x7f every TUI groks; win32-input-mode assumes the app opted in, a non-CC adapter could choke). I'm mid-revert, will finish it. FOLLOW-UP logged on REQ-HAZARD-RC-INPUT-KEY-ENCODING: the IDEAL is clean xterm VT (Backspace→0x7f, arrows→ESC[, ctrl-b→0x02 RAW so detach survives) — investigate next session whether Windows Terminal can be driven to legacy xterm VT instead of win32-input-mode; narrow-normalize is backspace-only (arrows/Home/End stay legacy).

Landing: (1) W3 clippy fix commit, (2) W7 revert+normalize commit, both after the workspace-clippy preflight passes. Will ping both commits. W7 stays OPEN for your isolated rebuild + operator re-capture next session.
EOF

Ack doyle W3 clippy + W7 ruling


➜ SENT:doyle


W3 clippy fix — `#[allow]` with justification on the private `establish_perch`.


Edit

// [impl->REQ-INST-15]
fn establish_perch(
    id: &str,

// [impl->REQ-INST-15]
// W3 added `cwd`, bringing this private bind helper to 8 params (> clippy's 7).
// A params struct is the cleaner shape but a heavier refactor across both call
// sites — deferred to a follow-up; allow the lint on this internal fn for now.
#[allow(clippy::too_many_arguments)]
fn establish_perch(
    id: &str,


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

// [impl->REQ-INST-15]
fn establish_perch(
    id: &str,

// [impl->REQ-INST-15]
// W3 added `cwd`, bringing this private bind helper to 8 params (> clippy's 7).
// A params struct is the cleaner shape but a heavier refactor across both call
// sites — deferred to a follow-up; allow the lint on this internal fn for now.
#[allow(clippy::too_many_arguments)]
fn establish_perch(
    id: &str,

/// and the auto-suspend override — a re-bind rewrites `info.json` and must

+// W3 added `cwd`, bringing this private bind helper to 8 params (> clippy's 7).

+// A params struct is the cleaner shape but a heavier refactor across both call

+// sites — deferred to a follow-up; allow the lint on this internal fn for now.


Update the W7 REQ comment for the normalize approach + log the clean-VT follow-up.


required_stages = ["impl", "unit"]  # ACTIVATED v0.13.0 W7 (todlando). BYTE CONFIRMED (operator HITL re-capture via SPT_RC_DEBUG_KEYS on the fresh debug binary, real Windows Terminal): pre-fix Backspace=0x08 (^H), Ctrl+Backspace=0x7f (DEL) — the INVERTED-from-VT signature of ENABLE_VIRTUAL_TERMINAL_INPUT being OFF (CC maps ^H → backward-kill-word = the whole-word delete). FIX (a, doyle ruling, NOT the 0x08→0x7f normalize which would conflate Backspace/Ctrl+Backspace): RawGuard now enables ENABLE_VIRTUAL_TERMINAL_INPUT on the stdin console alongside crossterm raw mode and restores the prior mode on drop (cfg(windows); Unix passthrough unchanged) — Backspace→0x7f, Ctrl+Backspace→0x08, arrows/Home/End→CSI, ctrl-b(0x02) detach unaffected. impl = rc.rs with_vt_input/enable_vt_input/restore_in_mode + RawGuard. unit = with_vt_input_sets_the_bit_and_preserves_other_flags (the pure mode-bit seam; the FFI shell + keypress are HITL operator re-capture). doc = KNOWN-HAZARDS.md entry. The SPT_RC_DEBUG_KEYS hexdump (committed f05566b) confirmed the root before the patch.

required_stages = ["impl", "unit"]  # ACTIVATED v0.13.0 W7 (todlando). BYTE CONFIRMED (operator HITL via SPT_RC_DEBUG_KEYS, real Windows Terminal): pre-fix Backspace=0x08 (^H), Ctrl+Backspace=0x7f — legacy console sends ^H, CC maps ^H → backward-kill-word = whole-word delete. REJECTED FIX (dc07c39, REVERTED): enabling ENABLE_VIRTUAL_TERMINAL_INPUT BACKFIRED — on Windows Terminal it yields win32-input-mode (6-field ESC[Vk;Sc;Uc;Kd;Cs;Rc_ records), NOT legacy xterm VT; it broke the ctrl-b detach (0x02 wrapped → parse_stdin_chunk never sees the raw prefix). (Operator note: CC itself DID understand win32-input-mode; the only regression was detach. But the VT flag is harness-SPECIFIC — a non-CC adapter could choke — so it's rejected on agnosticism grounds, not just the detach break.) SHIPPED FIX (doyle ruling, revert+narrow): stay a raw byte pump; normalize_key_byte maps a lone 0x08→0x7f in spawn_stdin_reader's forward path AFTER the detach state machine (0x02 + every other byte untouched). cfg(windows) only (Unix already VT; a real ctrl+h must stay 0x08). Accepted loss: ctrl+h conflated with Backspace; Ctrl+Backspace (0x7f here) stays char-delete (word-delete lost on the secondary key). impl = rc.rs normalize_key_byte (in spawn_stdin_reader). unit = normalize_maps_backspace_but_leaves_detach_and_others. doc = KNOWN-HAZARDS 7.13. SPT_RC_DEBUG_KEYS hexdump (f05566b) gated the root. FOLLOW-UP (not yet a stage): the IDEAL is clean xterm VT (Backspace→0x7f, arrows→ESC[, ctrl-b→0x02 RAW so detach survives) = agnostic + full key fidelity + working detach; narrow-normalize is backspace-ONLY (arrows/Home/End stay legacy). Investigate next session whether Windows Terminal can be driven to legacy xterm VT instead of win32-input-mode; mint a follow-up REQ if pursued.


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

title = "An `spt rc` session forwards the Backspace key as the VT DEL byte (0x7f), so the hosted TUI (Claude Code) deletes ONE character — never a whole word. SYMPTOM (operator dogfooding): Backspace in an rc session always behaves like ctrl+Backspace — deletes the entire last word. ROOT (doyle /diagnose, code-grounded, byte PENDING HITL confirm): rc is a RAW VERBATIM byte pump — spawn_stdin_reader (rc.rs:152) reads std::io::stdin() bytes under crossterm raw mode and forwards them unchanged (parse_stdin_chunk only intercepts the ctrl-b detach prefix); there is NO key-event encoding and NO 0x08↔0x7f normalization ANYWHERE in the tree (grep: zero SetConsoleMode / ENABLE_VIRTUAL_TERMINAL_INPUT). On Windows, crossterm enable_raw_mode does NOT set ENABLE_VIRTUAL_TERMINAL_INPUT, so the LEGACY console delivers ^H (0x08, ctrl+h) for Backspace instead of VT DEL (0x7f); Claude Code maps ^H → backward-kill-word → the observed whole-word delete. CONFIRM-FIRST (build the loop): an env-gated hexdump in spawn_stdin_reader (SPT_RC_DEBUG_KEYS) prints the forwarded byte; operator presses Backspace + ctrl+Backspace in a real rc session. FIX CANDIDATES: (a) enable ENABLE_VIRTUAL_TERMINAL_INPUT on the rc stdin console on Windows so the console emits proper VT (Backspace→0x7f, arrows/Home/End as CSI) — cleanest, fixes the whole key map not just Backspace; (b) narrow normalize bare 0x08→0x7f in the rc input path (riskier — a real ctrl+h is also 0x08). Prefer (a) unless it regresses other keys. Add a KNOWN-HAZARDS.md entry on landing. (v0.13.0)"

-required_stages = ["impl", "unit"]  # ACTIVATED v0.13.0 W7 (todlando). BYTE CONFIRMED (operator HITL re-capture via SPT_RC_DEBUG_KEYS on the fresh debug binary, real Windows Terminal): pre-fix Backspace=0x08 (^H), Ctrl+Backspace=0x7f (DEL) — the INVERTED-from-VT signature of ENABLE_VIRTUAL_TERMINAL_INPUT being OFF (CC maps ^H → backward-kill-word = the whole-word delete). FIX (a, doyle ruling, NOT the 0x08→0x7f normalize which would conflate Backspace/Ctrl+Backspace): RawGuard now enables ENABLE_VIRTUAL_TERMINAL_INPUT on the stdin console alongside crossterm raw mode and restores the prior mode on drop (cfg(windows); Unix passthrough unchanged) — Backspace→0x7f, Ctrl+Backspace→0x08, arrows/Home/End→CSI, ctrl-b(0x02) detach unaffected. impl = rc.rs with_vt_input/enable_vt_input/restore_in_mode + RawGuard. unit = with_vt_input_sets_the_bit_and_preserves_other_flags (the pure mode-bit seam; the FFI shell + keypress are HITL operator re-capture). doc = KNOWN-HAZARDS.md entry. The SPT_RC_DEBUG_KEYS hexdump (committed f05566b) confirmed the root before the patch.

+required_stages = ["impl", "unit"]  # ACTIVATED v0.13.0 W7 (todlando). BYTE CONFIRMED (operator HITL via SPT_RC_DEBUG_KEYS, real Windows Terminal): pre-fix Backspace=0x08 (^H), Ctrl+Backspace=0x7f — legacy console sends ^H, CC maps ^H → backward-kill-word = whole-word delete. REJECTED FIX (dc07c39, REVERTED): enabling ENABLE_VIRTUAL_TERMINAL_INPUT BACKFIRED — on Windows Terminal it yields win32-input-mode (6-field ESC[Vk;Sc;Uc;Kd;Cs;Rc_ records), NOT legacy xterm VT; it broke the ctrl-b detach (0x02 wrapped → parse_stdin_chunk never sees the raw prefix). (Operator note: CC itself DID understand win32-input-mode; the only regression was detach. But the VT flag is harness-SPECIFIC — a non-CC adapter could choke — so it's rejected on agnosticism grounds, not just the detach break.) SHIPPED FIX (doyle ruling, revert+narrow): stay a raw byte pump; normalize_key_byte maps a lone 0x08→0x7f in spawn_stdin_reader's forward path AFTER the detach state machine (0x02 + every other byte untouched). cfg(windows) only (Unix already VT; a real ctrl+h must stay 0x08). Accepted loss: ctrl+h conflated with Backspace; Ctrl+Backspace (0x7f here) stays char-delete (word-delete lost on the secondary key). impl = rc.rs normalize_key_byte (in spawn_stdin_reader). unit = normalize_maps_backspace_but_leaves_detach_and_others. doc = KNOWN-HAZARDS 7.13. SPT_RC_DEBUG_KEYS hexdump (f05566b) gated the root. FOLLOW-UP (not yet a stage): the IDEAL is clean xterm VT (Backspace→0x7f, arrows→ESC[, ctrl-b→0x02 RAW so detach survives) = agnostic + full key fidelity + working detach; narrow-normalize is backspace-ONLY (arrows/Home/End stay legacy). Investigate next session whether Windows Terminal can be driven to legacy xterm VT instead of win32-input-mode; mint a follow-up REQ if pursued.


Run the EXACT CI gate doyle specified — workspace clippy — plus the rc normalize unit test + traceable.


Bash

cargo clippy --workspace --all-targets -- -D warnings 2>&1 | tail -12 && echo "===RC UNIT===" && cargo test -p spt --bin spt rc:: -- --nocapture 2>&1 | tail -8 && echo "===TRACE===" && traceable-reqs check >/dev/null 2>&1; echo "TRACE_EXIT=$?"

Workspace clippy (CI form) + rc unit + traceable


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


<task-notification>
<task-id>bpodydkvv</task-id>
<tool-use-id>toolu_01QavbbMWsz8NAc3Raw8gj5W</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\bpodydkvv.output</output-file>
<status>completed</status>
<summary>Background command "Workspace clippy (CI form) + rc unit + traceable" completed (exit code 0)</summary>
</task-notification>


While workspace clippy runs (slow), save the verification-gate lessons as a durable feedback memory.


Write

---
name: spt-verification-gates
description: Pre-"done" gates for spt-core waves — workspace clippy form, real-binary rebuild, real-terminal verify, shared-tree hygiene
metadata:
  type: feedback
---

Before declaring an spt-core wave/fix done (doyle gates each wave), pass these — `cargo build` and per-crate clippy are NOT enough:

1. **EXACT CI clippy form:** `cargo clippy --workspace --all-targets -- -D warnings`. A per-crate `cargo clippy -p spt` or a `cargo build` MISSES workspace lints — e.g. adding a param pushed `establish_perch` to 8 args → `clippy::too_many_arguments` → CI red, invisible to build. Run the workspace form before "done."
2. **`cargo test`/`clippy` ≠ rebuilding the installed binary.** They build the lib + test targets, not `target\debug\spt.exe`. To verify/ship the REAL binary (e.g. an operator capture): `cargo build -p spt --bin spt`. Never clobber a running daemon's `spt.exe` (Windows file lock) — daemon stop or the release path.
3. **Terminal-input fixes must be verified on the REAL terminal** (operator HITL re-capture). Unit/clippy never exercise the live key path — the W7 VT-input fix passed unit+clippy but backfired live (Windows Terminal → win32-input-mode, broke ctrl-b detach).
4. **The working tree is SHARED** with doyle/CI/operator. Never leave it mid-edit — commit/park, or do WIP in a `git worktree`. `cfg(test)`/`tests/` edits are build-excluded (safe); only non-test impl mid-edit breaks others' builds.

**Why:** all four are real misses this session (doyle caught 1–4). **How to apply:** run gate 1 + (for binary-facing changes) gate 2 before pinging doyle a wave is done; gate 3 for any rc/terminal change; gate 4 always. See [[delegate-tests-to-spt-test-engineer]] for who writes the tests.


File created successfully at: C:\Users\decid\.claude\projects\C--Users-decid-Documents-projects-spt-core\memory\spt-verification-gates.md (file state is current in your context — no need to Read it back)

---
name: spt-verification-gates
description: "Pre-\"done\" gates for spt-core waves — workspace clippy form, real-binary rebuild, real-terminal verify, shared-tree hygiene"
metadata:
  node_type: memory
  type: feedback
  originSessionId: 73618172-412e-4954-b8aa-e2c8955dfd49
---

Before declaring an spt-core wave/fix done (doyle gates each wave), pass these — `cargo build` and per-crate clippy are NOT enough:

1. **EXACT CI clippy form:** `cargo clippy --workspace --all-targets -- -D warnings`. A per-crate `cargo clippy -p spt` or a `cargo build` MISSES workspace lints — e.g. adding a param pushed `establish_perch` to 8 args → `clippy::too_many_arguments` → CI red, invisible to build. Run the workspace form before "done."
2. **`cargo test`/`clippy` ≠ rebuilding the installed binary.** They build the lib + test targets, not `target\debug\spt.exe`. To verify/ship the REAL binary (e.g. an operator capture): `cargo build -p spt --bin spt`. Never clobber a running daemon's `spt.exe` (Windows file lock) — daemon stop or the release path.
3. **Terminal-input fixes must be verified on the REAL terminal** (operator HITL re-capture). Unit/clippy never exercise the live key path — the W7 VT-input fix passed unit+clippy but backfired live (Windows Terminal → win32-input-mode, broke ctrl-b detach).
4. **The working tree is SHARED** with doyle/CI/operator. Never leave it mid-edit — commit/park, or do WIP in a `git worktree`. `cfg(test)`/`tests/` edits are build-excluded (safe); only non-test impl mid-edit breaks others' builds.

**Why:** all four are real misses this session (doyle caught 1–4). **How to apply:** run gate 1 + (for binary-facing changes) gate 2 before pinging doyle a wave is done; gate 3 for any rc/terminal change; gate 4 always. See [[delegate-tests-to-spt-test-engineer]] for who writes the tests.


Prompt:
<task-notification>
<task-id>bpodydkvv</task-id>
<tool-use-id>toolu_01QavbbMWsz8NAc3Raw8gj5W</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\bpodydkvv.output</output-file>
<status>completed</status>
<summary>Background command "Workspace clippy (CI form) + rc unit + traceable" completed (exit code 0)</summary>
</task-notification>


1	    Checking spt-proto v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-proto)
2	    Checking spt-store v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-store)
3	    Checking spt-msg v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-msg)
4	    Checking spt-term v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-term)
5	    Checking spt-runtime v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime)
6	    Checking spt-net v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
7	    Checking spt-live v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-live)
8	    Checking mock-adapter v0.12.1 (C:\Users\decid\Documents\projects\spt-core\adapters\mock)
9	    Checking spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
10	    Checking spt v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
11	    Checking xtask v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
12	    Finished `dev` profile [unoptimized + debuginfo] target(s) in 12.15s
13	===RC UNIT===
14	Endpoint 'stopped-ep' is offline — nothing to attach to.
15	  spt endpoint run --adapter <adapter> --id stopped-ep   to start it
16	Endpoint 'stopped-ep' is offline — nothing to attach to.
17	  spt endpoint run --adapter <adapter> --id stopped-ep   to start it
18	test rc::tests::attach_offline_endpoint_short_circuits_clean ... ok
19
20	test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 191 filtered out; finished in 0.01s
21
22	===TRACE===
23	TRACE_EXIT=0
24

    Checking spt-proto v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-proto)
    Checking spt-store v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-store)
    Checking spt-msg v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-msg)
    Checking spt-term v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-term)
    Checking spt-runtime v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime)
    Checking spt-net v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
    Checking spt-live v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-live)
    Checking mock-adapter v0.12.1 (C:\Users\decid\Documents\projects\spt-core\adapters\mock)
    Checking spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Checking spt v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Checking xtask v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 12.15s
===RC UNIT===
Endpoint 'stopped-ep' is offline — nothing to attach to.
  spt endpoint run --adapter <adapter> --id stopped-ep   to start it
Endpoint 'stopped-ep' is offline — nothing to attach to.
  spt endpoint run --adapter <adapter> --id stopped-ep   to start it
test rc::tests::attach_offline_endpoint_short_circuits_clean ... ok

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

===TRACE===
TRACE_EXIT=0


All green — workspace clippy (CI form) clean, rc unit 7/7 (incl normalize), traceable EXIT=0. Commit W3 clippy fix, then W7 revert+normalize.


Bash

cd C:/Users/decid/Documents/projects/spt-core && git add crates/spt/src/api/startup.rs && git commit -q -m "fix(bind): W3 — silence too_many_arguments on establish_perch (CI clippy)

W3 added cwd, taking the private establish_perch helper to 8 params (> clippy 7
threshold) → cargo clippy --workspace --all-targets -D warnings (the CI form)
was RED, invisible to cargo build. Allow the lint on this internal fn with a
one-line justification; a params struct is the cleaner shape, deferred as a
follow-up to avoid churn. Workspace clippy now clean.

Co-authored by: todlando
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVVByRoZnz85acTkVJ1uAP" && echo "A_DONE" && git log --oneline -1

Commit W3 clippy fix


A_DONE
f1ae5c6 fix(bind): W3 — silence too_many_arguments on establish_perch (CI clippy)


Write

fix(rc): W7 — revert VT-input, narrow-normalize Backspace 0x08->0x7f instead

REQ-HAZARD-RC-INPUT-KEY-ENCODING. The dc07c39 VT-input fix BACKFIRED on the
real terminal (operator re-capture): enabling ENABLE_VIRTUAL_TERMINAL_INPUT on
Windows Terminal yields win32-input-mode (6-field ESC[Vk;Sc;Uc;Kd;Cs;Rc_ key
records), NOT legacy xterm VT. That broke the rc ctrl-b detach (0x02 gets
wrapped, so parse_stdin_chunk never sees the raw DETACH_PREFIX). (Claude Code
itself understood the win32-input-mode records — but the VT flag is
harness-SPECIFIC; a non-CC adapter could choke — so it's rejected on
agnosticism grounds, not only the detach break.)

Revert + narrow: stay a raw byte pump; normalize_key_byte maps a lone 0x08 ->
0x7f (VT DEL, char-delete) in spawn_stdin_reader's forward path, AFTER the
detach state machine has consumed any 0x02 — so detach works and every other
byte (CR, ESC/CSI, payload) is untouched. cfg(windows) only (Unix is already
VT; a real ctrl+h there must stay 0x08). Accepted loss: a real ctrl+h is
conflated with Backspace, and Ctrl+Backspace (0x7f on this terminal) stays
char-delete (word-delete lost on the secondary key).

impl = rc.rs normalize_key_byte (applied in spawn_stdin_reader); RawGuard back
to the plain raw-mode form. unit = normalize_maps_backspace_but_leaves_detach_
and_others (0x08->0x7f; 0x02/0x7f/CR/ESC/ordinary untouched). KNOWN-HAZARDS 7.13
rewritten (the VT-backfire lesson). Workspace clippy clean; traceable EXIT=0.

FOLLOW-UP (logged on the REQ): the IDEAL is clean xterm VT (Backspace->0x7f,
arrows->ESC[, ctrl-b->0x02 RAW so detach survives) = agnostic + full key
fidelity + working detach; narrow-normalize is backspace-only. Investigate next
session whether Windows Terminal can be driven to legacy xterm VT instead of
win32-input-mode.

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


File created successfully at: C:\Users\decid\Documents\projects\spt-core\.git\W7v2_MSG.txt (file state is current in your context — no need to Read it back)

fix(rc): W7 — revert VT-input, narrow-normalize Backspace 0x08->0x7f instead

REQ-HAZARD-RC-INPUT-KEY-ENCODING. The dc07c39 VT-input fix BACKFIRED on the
real terminal (operator re-capture): enabling ENABLE_VIRTUAL_TERMINAL_INPUT on
Windows Terminal yields win32-input-mode (6-field ESC[Vk;Sc;Uc;Kd;Cs;Rc_ key
records), NOT legacy xterm VT. That broke the rc ctrl-b detach (0x02 gets
wrapped, so parse_stdin_chunk never sees the raw DETACH_PREFIX). (Claude Code
itself understood the win32-input-mode records — but the VT flag is
harness-SPECIFIC; a non-CC adapter could choke — so it's rejected on
agnosticism grounds, not only the detach break.)

Revert + narrow: stay a raw byte pump; normalize_key_byte maps a lone 0x08 ->
0x7f (VT DEL, char-delete) in spawn_stdin_reader's forward path, AFTER the
detach state machine has consumed any 0x02 — so detach works and every other
byte (CR, ESC/CSI, payload) is untouched. cfg(windows) only (Unix is already
VT; a real ctrl+h there must stay 0x08). Accepted loss: a real ctrl+h is
conflated with Backspace, and Ctrl+Backspace (0x7f on this terminal) stays
char-delete (word-delete lost on the secondary key).

impl = rc.rs normalize_key_byte (applied in spawn_stdin_reader); RawGuard back
to the plain raw-mode form. unit = normalize_maps_backspace_but_leaves_detach_
and_others (0x08->0x7f; 0x02/0x7f/CR/ESC/ordinary untouched). KNOWN-HAZARDS 7.13
rewritten (the VT-backfire lesson). Workspace clippy clean; traceable EXIT=0.

FOLLOW-UP (logged on the REQ): the IDEAL is clean xterm VT (Backspace->0x7f,
arrows->ESC[, ctrl-b->0x02 RAW so detach survives) = agnostic + full key
fidelity + working detach; narrow-normalize is backspace-only. Investigate next
session whether Windows Terminal can be driven to legacy xterm VT instead of
win32-input-mode.

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


Bash

cd C:/Users/decid/Documents/projects/spt-core && git add crates/spt/src/rc.rs traceable-reqs.toml docs/KNOWN-HAZARDS.md && git commit -q -F .git/W7v2_MSG.txt && rm -f .git/W7v2_MSG.txt && echo "B_DONE" && git status --short && git log --oneline -6

Commit W7 revert+normalize


B_DONE
fbd6c98 fix(rc): W7 — revert VT-input, narrow-normalize Backspace 0x08->0x7f instead
f1ae5c6 fix(bind): W3 — silence too_many_arguments on establish_perch (CI clippy)
c4fae19 fix(bind): W3 — set info.cwd on bind so a fresh endpoint shows under its project
dc07c39 fix(rc): W7 — enable VT console input on Windows (Backspace no longer kill-word)
bab7d44 test(broker): W1 — cfg(unix) park-(b) is informational, not asserted
66dadef v0.13.0: track the W1-deferred Unix PTY-write bound under W2


Both committed (`f1ae5c6` W3 clippy, `fbd6c98` W7 revert+normalize); tree clean. Add the memory index pointer, then ping doyle.


Edit

- [Delegate tests to spt-test-engineer](delegate-tests-to-spt-test-engineer.md) — write spt-core tests via the `spt-test-engineer` subagent (Agent tool subagent_type), not inline.

- [Delegate tests to spt-test-engineer](delegate-tests-to-spt-test-engineer.md) — write spt-core tests via the `spt-test-engineer` subagent (Agent tool subagent_type), not inline.
- [spt verification gates](spt-verification-gates.md) — before "done": workspace clippy (CI form), rebuild the real bin (not cargo test), verify terminal fixes live, keep the shared tree compilable.


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

- [Delegate tests to spt-test-engineer](delegate-tests-to-spt-test-engineer.md) — write spt-core tests via the `spt-test-engineer` subagent (Agent tool subagent_type), not inline.
- [spt verification gates](spt-verification-gates.md) — before "done": workspace clippy (CI form), rebuild the real bin (not cargo test), verify terminal fixes live, keep the shared tree compilable.

# Memory index

- [Cross-machine test rig](cross-machine-test-rig.md) — enlyzeam (Windows, ssh key) for two-host network spikes + Tailscale-down gotcha; gravity-linux (Ubuntu, david/bigscreen pw-auth + askpass shim) as the Linux/forkpty build host.
- [CI traceability gate](ci-traceability-gate.md) — how CI installs traceable-reqs (TRACEABLE_REQS_TOKEN PAT + latest BigscreenVR release); the v0.1.0-stale-binary gotcha + the v0.1.1 fix; I have admin to cut releases.
- [Attractor build harness](attractor-build-harness.md) — attractor+pi/codex setup at ../attractor; codex model = openai-codex/gpt-5.5 (NOT the SPEC's gpt-5.3-codex); viable for bounded single-machine spt-core tasks (SPIKE-07b gix pilot).
- [Windows UAC binary-name 740](windows-uac-binary-name-740.md) — exe names containing update/setup/install/patch trigger UAC elevation (os error 740); name self-update artifacts + test bins to dodge it.
- [M4 resume state](m4-resume-state.md) — M4 (spt-net) in progress 2026-06-03; D0+D1 done (iroh =0.98.2 endpoint, identity-bound, relay policy, mDNS); spt-net = first async crate; next = D2 pairing.
- [Linux runner migration](linux-runner-migration.md) — replace gravity CI Linux runner with kitsubito (LAN Linux), not WSL (disk cost); deferred until after v0.1.1; retarget `gravity` host-label in ci/release/docs-publish workflows to a role label.
- [Delegate tests to spt-test-engineer](delegate-tests-to-spt-test-engineer.md) — write spt-core tests via the `spt-test-engineer` subagent (Agent tool subagent_type), not inline.

+- [spt verification gates](spt-verification-gates.md) — before "done": workspace clippy (CI form), rebuild the real bin (not cargo test), verify terminal fixes live, keep the shared tree compilable.


Bash

$OWL send doyle <<'EOF'
Both up, tree CLEAN:
- f1ae5c6 — W3 clippy fix (#[allow(too_many_arguments)] + justification on establish_perch). Re-ran the EXACT CI form `cargo clippy --workspace --all-targets -- -D warnings` = clean. W3 should be fully gate-PASS now.
- fbd6c98 — W7 revert+narrow-normalize. dc07c39's VT-input reverted; RawGuard back to raw byte forwarding; normalize_key_byte maps lone 0x08→0x7f in spawn_stdin_reader's forward path AFTER the detach state machine (0x02 + all else untouched); cfg(windows) only. unit normalize_maps_backspace_but_leaves_detach_and_others 7/7; KNOWN-HAZARDS 7.13 rewritten with the VT-backfire lesson; traceable EXIT=0. FOLLOW-UP (clean xterm VT vs win32-input-mode) logged on the REQ.

Expect on the operator re-capture (your isolated rebuild): Backspace→[7f], ctrl-b d detach WORKS again, arrows still legacy. W7 stays OPEN until that confirms.

Session is paused (operator). Resume next session: close W7 on your re-capture, then W5 (thin clear_controller reconcile-reuse) + W4 (picker UX) in git worktrees. Pinging per wave as before.
EOF

Ping doyle: W3 clippy + W7 revert+normalize commits


➜ SENT:doyle


Update the commune with the final state (W7 flipped VT→normalize, W3 clippy fix, new gate lessons) so next-session resume is accurate.


Write

<live-context>
SESSION WIND-DOWN (final state for next-session resume). Branch v0.13.0-delivery-control, tree CLEAN.

**Commits this session:** 8b5583e + bab7d44 (W1 wedge fix, gate-passed) · f05566b (W7 hexdump) · c4fae19 (W3 bind-cwd) · f1ae5c6 (W3 clippy fix) · dc07c39→REVERTED-by fbd6c98 (W7 revert+normalize).

**W1** — banked, gate-passed. Controller delivery OFF the drain thread (dedicated writer + bounded deadline-detach via clear_controller). DONE.

**W3 (bind-cwd)** — DONE. info.cwd now set on bind (establish_perch cwd param; cmd_bind→current_dir, bind_from_seed→seed.cwd; revive carries prior). 3 unit + 1 int (bind_cwd_project_e2e.rs), mutation-verified. Clippy follow-up f1ae5c6 (#[allow(too_many_arguments)] — establish_perch is 8 args; a params-struct refactor is a logged follow-up). REQ [impl,unit,int], traceable EXIT=0. Up for doyle's final gate (he flagged + I fixed the clippy).

**W7 (rc Backspace)** — APPROACH FLIPPED, still OPEN. dc07c39 enabled ENABLE_VIRTUAL_TERMINAL_INPUT → BACKFIRED: on Windows Terminal that = win32-input-mode (6-field key records), broke ctrl-b detach (0x02 wrapped). REVERTED by fbd6c98 → narrow-normalize: normalize_key_byte maps lone 0x08→0x7f in spawn_stdin_reader forward path (after detach SM; 0x02 + all else untouched; cfg(windows)). unit normalize_maps_backspace_but_leaves_detach_and_others. KNOWN-HAZARDS 7.13 rewritten. **DO NOT close W7** until doyle's operator re-capture on his isolated rebuild confirms Backspace→[7f] + ctrl-b d detach works. FOLLOW-UP logged on the REQ: ideal = clean xterm VT (Backspace→0x7f, arrows→ESC[, ctrl-b→0x02 raw) — investigate next session whether WT can be driven to legacy xterm VT vs win32-input-mode (narrow-normalize is backspace-ONLY; arrows/Home/End stay legacy).

**LESSONS this session (now in my auto-memory + spt-verification-gates):**
1. EXACT CI clippy = `cargo clippy --workspace --all-targets -- -D warnings`. cargo build + per-crate clippy MISS workspace lints (the W3 too_many_arguments red).
2. cargo test/clippy ≠ rebuilding target\debug\spt.exe; use `cargo build -p spt --bin spt` for the real binary; never clobber a running daemon's exe (Win lock).
3. Verify terminal-input fixes on the REAL terminal (operator re-capture); unit/clippy don't exercise the live key path (the W7 VT backfire).
4. Shared working tree — never leave mid-edit; commit/park/worktree. (cfg(test)/tests/ are build-excluded = safe.)

**IMMEDIATE NEXT STEPS:**
1. Await doyle's W3 final gate + his W7 operator re-capture → close W7.
2. W5 (THIN: reconcile loop calls the SAME clear_controller for the non-deadline vanished-controller case) — in a `git worktree`. doyle gates as a thin confirm on W1.
3. W4 (picker UX: open on Pick-existing/n=Create new; auto-attach after Start-new + Resume-from-history/h=headless; controlled-by = node NAME not hex; drop pid=Some()+internals leak; xtask gen if clap help changes; no REQ/M#-W# in /// help) — `git worktree`.
4. ALL test authoring → spt-test-engineer subagent (now wired/available).
5. Optional follow-ups: W3 params-struct refactor; W7 clean-xterm-VT investigation; W3 cwd slash-unify (cmd_bind raw vs cmd_seed forward-slash).
</live-context>
<project-context>
**v0.13.0-delivery-control — END GOAL:** spt-hosted idle message delivery redesign (ADR-0022: adapter `[message-idle-translation-binary]`, pure stdin→stdout filter, spt-core owns every PTY write + lifecycle) + the control/wedge bug cluster fixed. Folds suspended v0.12.2 work (15fdf58 window-fix, 2435a74 MD-sweep, b3bb387 ATX) into the v0.13.0 release.

**Waves:** W0 design (ADR-0022, done). W1 KEYSTONE controller-delivery wedge = DONE/GATE-PASSED. W2 = delivery redesign + the unified atomic PTY-write substrate (OWNS the deferred Unix park-(b)/(c) bound, @66dadef). W3 bind-cwd = DONE (c4fae19 + clippy f1ae5c6), final gate pending. W4 picker UX. W5 driven_by self-heal (thin, reuses W1 clear_controller). W6 perri builds the translation binary (after W2). W7 rc Backspace = narrow-normalize landed (fbd6c98), OPEN pending re-capture.

**W1 architecture lock (load-bearing):** controller output delivery is OFF the session drain thread — dedicated controller writer thread + bounded channel does the blocking write; OutputLog::append hands each chunk to ControllerJob::deliver (bounded off-lock send, CONTROLLER_WRITE_DEADLINE=5s → clear_controller detach/unlatch, never park, never silent-evict a live operator). Controller authoritative: writer advances delivered_through (Arc<AtomicU64>, monotonic). Pre-fix bug: append wrote the controller INLINE on the drain thread holding Mutex<OutputLog> → a backed-up controller (slow operator / full 64KB rc loopback duplex under TUI redraw) wedged the session. KNOWN-HAZARDS 7.12.

**(b)/(c) facets:** input-side parks BENIGN on Windows ConPTY, real only on Unix forkpty → deferred to W2's atomic-write substrate. cfg(unix) repro assertion in inject_control_wedge.rs is INFORMATIONAL (gravity-linux CI = observation venue).

**Repro discipline (binding):** every fix gated on a REAL dummy-harness fixture + real daemon, never mocks/theory; repro-first; verify on the real terminal/binary; don't assert a bug you haven't reproduced.
</project-context>


File created successfully at: C:\Users\decid\Documents\projects\spt-core\.claude\todlando-commune.md (file state is current in your context — no need to Read it back)

<live-context>
SESSION WIND-DOWN (final state for next-session resume). Branch v0.13.0-delivery-control, tree CLEAN.

**Commits this session:** 8b5583e + bab7d44 (W1 wedge fix, gate-passed) · f05566b (W7 hexdump) · c4fae19 (W3 bind-cwd) · f1ae5c6 (W3 clippy fix) · dc07c39→REVERTED-by fbd6c98 (W7 revert+normalize).

**W1** — banked, gate-passed. Controller delivery OFF the drain thread (dedicated writer + bounded deadline-detach via clear_controller). DONE.

**W3 (bind-cwd)** — DONE. info.cwd now set on bind (establish_perch cwd param; cmd_bind→current_dir, bind_from_seed→seed.cwd; revive carries prior). 3 unit + 1 int (bind_cwd_project_e2e.rs), mutation-verified. Clippy follow-up f1ae5c6 (#[allow(too_many_arguments)] — establish_perch is 8 args; a params-struct refactor is a logged follow-up). REQ [impl,unit,int], traceable EXIT=0. Up for doyle's final gate (he flagged + I fixed the clippy).

**W7 (rc Backspace)** — APPROACH FLIPPED, still OPEN. dc07c39 enabled ENABLE_VIRTUAL_TERMINAL_INPUT → BACKFIRED: on Windows Terminal that = win32-input-mode (6-field key records), broke ctrl-b detach (0x02 wrapped). REVERTED by fbd6c98 → narrow-normalize: normalize_key_byte maps lone 0x08→0x7f in spawn_stdin_reader forward path (after detach SM; 0x02 + all else untouched; cfg(windows)). unit normalize_maps_backspace_but_leaves_detach_and_others. KNOWN-HAZARDS 7.13 rewritten. **DO NOT close W7** until doyle's operator re-capture on his isolated rebuild confirms Backspace→[7f] + ctrl-b d detach works. FOLLOW-UP logged on the REQ: ideal = clean xterm VT (Backspace→0x7f, arrows→ESC[, ctrl-b→0x02 raw) — investigate next session whether WT can be driven to legacy xterm VT vs win32-input-mode (narrow-normalize is backspace-ONLY; arrows/Home/End stay legacy).

**LESSONS this session (now in my auto-memory + spt-verification-gates):**
1. EXACT CI clippy = `cargo clippy --workspace --all-targets -- -D warnings`. cargo build + per-crate clippy MISS workspace lints (the W3 too_many_arguments red).
2. cargo test/clippy ≠ rebuilding target\debug\spt.exe; use `cargo build -p spt --bin spt` for the real binary; never clobber a running daemon's exe (Win lock).
3. Verify terminal-input fixes on the REAL terminal (operator re-capture); unit/clippy don't exercise the live key path (the W7 VT backfire).
4. Shared working tree — never leave mid-edit; commit/park/worktree. (cfg(test)/tests/ are build-excluded = safe.)

**IMMEDIATE NEXT STEPS:**
1. Await doyle's W3 final gate + his W7 operator re-capture → close W7.
2. W5 (THIN: reconcile loop calls the SAME clear_controller for the non-deadline vanished-controller case) — in a `git worktree`. doyle gates as a thin confirm on W1.
3. W4 (picker UX: open on Pick-existing/n=Create new; auto-attach after Start-new + Resume-from-history/h=headless; controlled-by = node NAME not hex; drop pid=Some()+internals leak; xtask gen if clap help changes; no REQ/M#-W# in /// help) — `git worktree`.
4. ALL test authoring → spt-test-engineer subagent (now wired/available).
5. Optional follow-ups: W3 params-struct refactor; W7 clean-xterm-VT investigation; W3 cwd slash-unify (cmd_bind raw vs cmd_seed forward-slash).
</live-context>
<project-context>
**v0.13.0-delivery-control — END GOAL:** spt-hosted idle message delivery redesign (ADR-0022: adapter `[message-idle-translation-binary]`, pure stdin→stdout filter, spt-core owns every PTY write + lifecycle) + the control/wedge bug cluster fixed. Folds suspended v0.12.2 work (15fdf58 window-fix, 2435a74 MD-sweep, b3bb387 ATX) into the v0.13.0 release.

**Waves:** W0 design (ADR-0022, done). W1 KEYSTONE controller-delivery wedge = DONE/GATE-PASSED. W2 = delivery redesign + the unified atomic PTY-write substrate (OWNS the deferred Unix park-(b)/(c) bound, @66dadef). W3 bind-cwd = DONE (c4fae19 + clippy f1ae5c6), final gate pending. W4 picker UX. W5 driven_by self-heal (thin, reuses W1 clear_controller). W6 perri builds the translation binary (after W2). W7 rc Backspace = narrow-normalize landed (fbd6c98), OPEN pending re-capture.

**W1 architecture lock (load-bearing):** controller output delivery is OFF the session drain thread — dedicated controller writer thread + bounded channel does the blocking write; OutputLog::append hands each chunk to ControllerJob::deliver (bounded off-lock send, CONTROLLER_WRITE_DEADLINE=5s → clear_controller detach/unlatch, never park, never silent-evict a live operator). Controller authoritative: writer advances delivered_through (Arc<AtomicU64>, monotonic). Pre-fix bug: append wrote the controller INLINE on the drain thread holding Mutex<OutputLog> → a backed-up controller (slow operator / full 64KB rc loopback duplex under TUI redraw) wedged the session. KNOWN-HAZARDS 7.12.

**(b)/(c) facets:** input-side parks BENIGN on Windows ConPTY, real only on Unix forkpty → deferred to W2's atomic-write substrate. cfg(unix) repro assertion in inject_control_wedge.rs is INFORMATIONAL (gravity-linux CI = observation venue).

**Repro discipline (binding):** every fix gated on a REAL dummy-harness fixture + real daemon, never mocks/theory; repro-first; verify on the real terminal/binary; don't assert a bug you haven't reproduced.
</project-context>


All landed clean. Session wound down.

## Final state — `v0.13.0-delivery-control`, tree clean

| Wave | Status | Commits |
|---|---|---|
| **W1** wedge fix | banked, gate-passed | `8b5583e` `bab7d44` |
| **W3** bind-cwd | done, gate pending | `c4fae19` + clippy `f1ae5c6` |
| **W7** rc Backspace | **open** (re-capture) | `f05566b` → `fbd6c98` (VT reverted → narrow-normalize) |

**This session's arc on W7** — a good repro-first lesson: the VT-input fix passed unit+clippy but **backfired live** (Windows Terminal → win32-input-mode, broke ctrl-b detach). doyle's real-terminal re-capture caught it; reverted to the conservative `0x08→0x7f` normalize (harness-agnostic). Held open until his isolated rebuild + operator confirm Backspace→`0x7f` and detach works.

**Verified before each ping:** workspace clippy (CI form), rc unit 7/7, traceable EXIT=0.

**Saved for next time:** [spt-verification-gates](memory) (workspace-clippy form, real-binary rebuild, real-terminal verify, shared-tree hygiene) + the test-engineer-subagent preference. **Commune updated** with the final state.

**Resume:** close W7 on doyle's re-capture → W5 (thin) + W4 (picker UX) in git worktrees → tests via the subagent. doyle pinged both commits.

Paused and reachable.


<task-notification>
<task-id>bv8r78fk1</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/5" id="a0ff5928" type="msg" from="doyle">Both gated on-machine, not on the relay:

W3 = FULL GATE-PASS. f1ae5c6 #[allow] + justification is sound (private helper, struct refactor a fair deferred follow-up). I re-ran the EXACT CI form `cargo clippy --workspace --all-targets -- -D warnings` = CLEAN (the too_many_arguments is gone). Units green: bind_record</EVENT-PART>
<EVENT-PART seq="2/5" id="a0ff5928">s_cwd / revive_without_cwd_preserves / seed_bind_records all ok. Source matches the RCA, REQ [impl,unit,int], traceable EXIT=0. W3 CLOSED — the refuted v0.12.1 P1 delivered for real.

W7 = CODE-GATE PASS (stays OPEN for the operator re-capture only). fbd6c98 read: VT-input fully reverted (const/with_vt_input/enable_vt_input/restore_in_</EVENT-PART>
<EVENT-PART seq="3/5" id="a0ff5928">mode gone, RawGuard back to raw forwarding), normalize_key_byte maps lone 0x08→0x7f, 0x02 detach + all else untouched, cfg(windows), doc records the VT-backfire lesson. unit normalize_maps_backspace_but_leaves_detach_and_others ok. 198 passed/0 failed total. Conservative + harness-agnostic — correct.

So W1+W3 banked, W7 code-ready (</EVENT-PART>
<EVENT-PART seq="4/5" id="a0ff5928">operator confirms Backspace→7f + ctrl-b detach next session on my isolated rebuild).

NEXT SESSION (your queue): W5 (thin clear_controller reconcile-reuse) + W4 (picker UX) in worktrees; then the SPINE — W2 keystone (translation-binary, ADR-0022) + W6 perri handoff, still undispatched. I'll dispatch W2/W6 when you've got capacity. Cl</EVENT-PART>
<EVENT-PART seq="5/5" id="a0ff5928">ean wind-down. Good session — the real-capture gating caught the W7 backfire + the clippy slip before either shipped.</EVENT-PART></event>
</task-notification>


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


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


doyle gated both on-machine: **W3 = full gate-pass, CLOSED** (the refuted v0.12.1 P1 delivered for real); **W7 = code-gate pass**, open only for the operator re-capture; 198 passed/0 failed. Brief ack to close the loop.


Bash

$OWL send doyle <<'EOF'
Received — W3 CLOSED, W7 code-gate pass (open for the re-capture). Thanks for the on-machine gates.

Next-session queue locked: W5 (thin clear_controller reconcile-reuse) + W4 (picker UX) in git worktrees → then the SPINE: W2 keystone (translation-binary, ADR-0022) + W6 perri handoff when you dispatch. I'll close W7 the moment your isolated rebuild + operator re-capture confirm Backspace→7f + ctrl-b detach.

Agreed — real-capture gating + the workspace-clippy preflight earned their keep this session (caught the W7 VT backfire + the clippy slip pre-ship). Good session. Paused; ping me to resume.
EOF

Ack doyle final gates + next-session queue


Loop closed with doyle. Session complete.

## Wrap — 3 waves landed, gated

- **W1** (controller-delivery wedge) — banked, gate-passed
- **W3** (bind-cwd / fresh endpoint under its project) — **CLOSED**, full gate-pass; the refuted v0.12.1 P1 delivered for real
- **W7** (rc Backspace) — code-gate pass; **open** only for doyle's operator re-capture next session

**Tree clean.** All commits on `v0.13.0-delivery-control`. 198 tests pass; workspace clippy clean; traceable EXIT=0.

**Next session (queued):** W5 (thin, reuses `clear_controller`) + W4 (picker UX) in git worktrees → then the spine: **W2** keystone (ADR-0022 translation-binary) + W6 perri handoff when doyle dispatches.

Resume state is in the commune + memory; git history is authoritative. Paused and reachable — ping to resume.


<command-message>spt:commune</command-message>
<command-name>/spt:commune</command-name>
<command-args>with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session</command-args>


Base directory for this skill: C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.25\skills\commune

# /spt:commune

All commands use `$LIVE` env var, auto-injected by the plugin's SessionStart hook. If commands fail with "command not found", restart the Claude Code session so the SessionStart hook re-runs.

> **Identity auto-detection:** Your identity is auto-detected from your session. Pass your ID explicitly only if auto-detection fails.

## Flow

1. **Download Psyche's *current* context** — it shifts during the session as background echo communes fire. Re-download right before composing; the gen-N snapshot you read at session start is already stale:
   ```bash
   $LIVE psyche-download <your-id>
   ```

   > **Output truncation.** If the Bash tool reports `Output too large (N KB)` and saves the full result to a `tool-results/<id>.txt` file, use the `Read` tool on that saved path to access the full content. Do NOT re-run `psyche-download` to grep/awk out sections — repeated invocations re-download the same context, waste tokens, and may duplicate `## Pending Commune` sections in `live_context.md`.

2. **Diff your knowledge against this fresh download** — only what's missing from *it* is the real delta (new work, decisions, intentions). Anything already absorbed is not. If the download includes a `<memformat>` section, use it as a guide for what topics to cover. A `## Pending Commune (uncommitted)` section means a previous commune is still queued — do NOT re-send it.

3. **Commune only the delta** -- use the **Write tool** to create
   `.claude/{your-id}-commune.md` with the two-slice body as its contents
   (see `## Two-slice body shape (Phase 25 D-10/D-11)` below for the required
   envelope wrapping). Do not use Bash/heredoc -- Write is the canonical path:
   no shell escaping, no `EOF` collision risk, and Write auto-creates the
   `.claude/` parent directory.

   Your Self listener detects the file on its next poll iteration, notifies
   your Psyche wrapper, and the wrapper ingests the content via the existing
   Psyche session. On success (subprocess exit code 0) the wrapper deletes the
   file. On error (token rate limit, etc) the file is retained and retried on
   the next consume cycle. If you write again before the wrapper consumes, the
   latest write wins (last-write-wins overwrite semantics).

If Psyche's context is already current, no commune is needed. Say so and move on.

Read `commune.md` (in this skill directory) for the full commune protocol -- when to send, what to include, and diligence triggers.

**Agent-type guard:** Only works on live agents. If target is not live, refuses with: "Communes require a live agent with Psyche."

## Two-slice body shape (Phase 25 D-10/D-11)

The body you write in `.claude/{your-id}-commune.md` MUST itself be wrapped in two-slice envelopes per Phase 25 D-10/D-11. The taxonomy — what belongs in `<live-context>` vs `<project-context>` — is defined canonically in `psyche.md` §`<output_envelope>` (lines 282–323). Read that section as the source of truth; this skill doc teaches only when and how to apply it.

**In-project detection rule (D-25.1-04).** Before composing your commune body, run `$LIVE psyche-download <your-id>` (Step 1 of `## Flow` above) and inspect the `<current ... />` tag at the top of the output. The `<current/>` tag carries a `project="..."` attribute whenever any payload is produced — its presence means you are inside a tracked project (inside a git repo, `project` is the repo name; outside a git repo per D-11, `project` is the cwd-basename fallback, which still counts as a project context for routing purposes). A `psyche-download` invocation that returns no payload at all (NO-CONTEXT exit) means no project resolved. So: `project="..."` populated on `<current/>` → emit BOTH envelopes (apply rule 1 or 2 below); no payload at all → emit ONLY `<live-context>` (rule 3). Secondary signal: the `<project-context-resolved name="..."/>` sentinel further down the output indicates the project file already has prior content — useful for spotting first-time-in-project pairings, but NOT the routing rule.

**Three rules (D-25.1-01..03):**

- **In-project, both slices populated** (marker present, you have project-specific content this cycle): emit `<live-context>...</live-context>` AND `<project-context>...</project-context>`, both with non-empty bodies.
- **In-project, no project-specific update this cycle** (marker present, but nothing project-bound to report): emit `<live-context>...</live-context>` AND `<project-context></project-context>` (empty body — the deliberate "in-project but quiet" signal per D-25.1-02). Distinguishable from "no project resolved".
- **Outside any tracked project** (marker absent): emit ONLY `<live-context>...</live-context>`. Do NOT emit an empty `<project-context>` envelope (missing tag means "no project resolved" per D-25.1-03).

**Nested envelopes.** The Phase 23 `<EVENT type="commune">` envelope (see `## Envelope shape (Phase 23 v1.8)` below) is composed by `$LIVE commune` automatically — you NEVER write `<EVENT>` tags. You write ONLY the inner two-slice body. The two envelope layers nest: outer EVENT wraps inner two-slice body.

(See `psyche.md` §`<output_envelope>` for the canonical taxonomy and per-slice content rules.)

## Envelope shape (Phase 23 v1.8)

As of v1.8 Phase 23, commune deliveries are wrapped in a typed EVENT envelope carrying a 5-field stamp (`machine`, `project`, `branch`, `head_sha`, `head_subject`); the prose `COMMUNE (timestamp): body` form is gone.

```xml
<EVENT type="commune" timestamp="2026-05-20T06:00:00-07:00" machine="hostname" project="claude_skill_owl" branch="main" head_sha="abc123..." head_subject="subject line capped at 72 chars">body</EVENT>
```

Outside a git repo (D-11), `branch`, `head_sha`, and `head_subject` are omitted entirely (not empty-string-set):

```xml
<EVENT type="commune" timestamp="2026-05-20T06:00:00-07:00" machine="hostname" project="cwd-basename">body</EVENT>
```

`$LIVE commune` composes this envelope automatically — Self does NOT manually construct EVENT XML. The `body` placeholder in the example above is the two-slice body (see `## Two-slice body shape (Phase 25 D-10/D-11)` above); the two envelope layers nest. The wrapper-side parser ignores unknown attrs, so the stamp fields are forward-compatible with older receivers. For drift detection on resume, see the `live` skill.

ARGUMENTS: with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session