<psyche-context id="todlando">
<memformat>
<?xml version="1.0"?>
<memformat version="1">
<!-- Changelog: v1 - initial seed -->
<topics>
  <topic name="events">Notable or unusual events and requests</topic>
  <topic name="interactions">Agent interactions -- who, roles, contributions</topic>
  <topic name="reflections">Thoughts on recent work -- what went well, what was tricky</topic>
  <topic name="ideas">Ideas to raise with the user</topic>
  <topic name="improvements">Further improvement ideas for recent problem spaces</topic>
  <topic name="timeline">What happened when, timing, sequencing</topic>
  <topic name="blockers">Current blockers or concerns</topic>
  <topic name="focus">Current focus and immediate next steps</topic>
</topics>
</memformat>
</memformat>

<current machine="HFENDULEAM" project="spt-core" branch="v0.13.0-delivery-control" head_sha="e404aeb42335c2d22744f1d389f1539495f00ccf" head_subject="fix(daemon): W1b — effect journal no longer wedges interactive input (RE…" commits_since="0" commits_unpulled="0"/>
**Session checkpoint — pausing, resume next session. Tree CLEAN @e404aeb.**

**The two-subagent conflict (snapshot's UNRESOLVED) — RESOLVED.** The operator caught it: I had TWO spt-test-engineer agents (original a4f3a273 + a redundant fixer a412e08a I launched) both editing `inject_control_wedge.rs` + running cargo → artifact-dir lock collision + the original's orphaned flood/test processes (`0xffffffff` abnormal int exit). Fix: TaskStop'd BOTH, killed orphan processes, then INDEPENDENTLY re-verified the on-disk result from a quiesced tree (not trust-the-green). Disk was green: units 7/7, int 3/3 clean exit 0, clippy clean. The last-writer (a412e08a's teardown fixes) left a coherent passing file.
**LESSON (wired to memory candidates):** never run two test-engineer subagents on the same crate/`target/` concurrently — serialize or worktree-isolate. And subagent monitor/Tee wait-loops burned ~1M tokens producing non-answers → constrain test subagents to DIRECT timeout'd runs, ≤4 iterations, no Monitor.

**W1b — DOYLE GATE PASS @e404aeb** (independently re-verified by doyle: clippy clean, units 7/7, int 3/3 via `cargo nextest`, traceable EXIT=0, KH 7.14). **Operator re-dogfooded the REAL build: the wedge is GONE, typing smooth.** REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE activated [impl,unit,int].

**doyle dispatched W7-followup (small/quick, NOT yet started):** upgrade `rc.rs normalize_key_byte` (cfg(windows)) from the one-way map to a SWAP — `0x08→0x7f`, `0x7f→0x08`, else passthrough (detach `0x02` still handled before normalize). Recovers Ctrl+Backspace=word-delete (native Win11 convention the operator rejected losing). Update unit `normalize_maps_backspace_but_leaves_detach_and_others` to assert the swap; update KH 7.13 (one-way→swap) + REQ note (extend [impl,unit], don't re-mint). Gate: clippy + unit + traceable. Tests via spt-test-engineer.

**Interactions:** doyle = designer/gate (gated W1b, dispatched W7-swap, holds W4/W5/W6 queue). spt-test-engineer = all test authoring (user reaffirmed: invoke it for every test). Operator (user) = caught the subagent conflict, paused me for doyle's gate, set "resume next session".

**IMMEDIATE NEXT STEPS (resume order — user to confirm; my recommendation):**
1. **W7 swap** (fast, operator-requested real win) — the byte swap + unit + KH 7.13 + REQ note.
2. **W1b follow-up F1** (cheap footgun): `cargo test --test inject_control_wedge` (threads-in-one-process) HANGS on Windows (cost doyle 30min); CI is safe (nextest = process-per-test) but add a `serial_test` guard OR a module-doc "run via nextest / `--test-threads=1` on Windows" note.
3. **W2 Layer C proper** (the big build — see project-context). Full spec already in `V0.13.0-DELIVERY-CONTROL-JIT.md` §W2 build-status.

**Other W1b follow-ups (tracked, fold in):** F2 = `apply_once` Phase-1 also short-circuit on `pending.contains(key)` (cheap same-key-concurrency hardening beyond the model invariant). F3 = Fix(2) Unix-forkpty park bound (bounded `write_input` + DSR off the writer mutex) — gravity-linux, the int's cfg(unix) park-(b) assert tracks it.<project-context-resolved name="spt-core"/>
**v0.13.0-delivery-control — END GOAL (unchanged):** 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 into v0.13.0.

**WAVE STATUS — DELTA since snapshot:**

- **W1b (NEW, the real operator wedge, DONE/GATE-PASSED @e404aeb)** — W1 fixed the OUTPUT drain; W1b is the INPUT/effect-journal path W1 never touched. ROOT: `EffectJournal::apply_once` held its global lock across `write_line(PENDING)` (fsync) → `effect()` (blocking PtyWrite) → `write_line(DONE)` (fsync), so every keystroke = 2 fsync under a global lock with the PTY write inside it → (A) stutter, (B) a blocked PtyWrite held the lock forever → dispatch couldn't open attaches → `spt rc` "brain IPC read deadline". FIX (1)+(3): `apply_once` RELEASES the lock across `effect()` (reserve key + fsync PENDING for DURABLE under lock → RELEASE → effect() OFF lock → re-acquire → finalize); `EffectKind::is_durable()` = PtyWrite false (ephemeral, no journal lines/no fsync, in-memory dedup only); durable kinds keep fsync. Brain-crash exactly-once preserved (gauntlet green). Refutes the W2-deferred "ConPTY-benign" ruling (the wedge is the LOCK HOLD). KH 7.14.

- **W2 (delivery redesign) — LAYERS BANKED, contract LOCKED:** ALL contract decisions doyle-gated. Manifest = TABLE `[message-idle-translation-binary]` with `path` scalar (OPT-B; no `deny_unknown_fields`, forward-compat). Atomic-sequence boundary = explicit `{commit:true}` per-event terminator (OPT-1): each `{type:event}` OPENS the inject floor (buffers controller input), `{commit}` CLOSES it (flush after); concurrent events QUEUE; `{type:input}` pings keep flowing during buffering; operator keystrokes = the buffered bytes. MANDATORY `INJECT_COMMIT_DEADLINE` (~5s): no commit → flush + release + FAULT the binary (terminate, no respawn → degrade to pass-through). doyle gate-reminders: `{type:input}` ping CONTENT-FREE (no PTY content leak); lifecycle kill BOUNDED+reaped (no zombie).
  - Layers: **A@c73eb6a** manifest table key+validate+schema. **B@611205c** `spt-daemon/translation.rs` (ToBinary init/event/input + KeyCmd key/text/delay_ms/commit untagged + `key_to_bytes` send-keys map + `TranslationChild` piped-stdin/stdout-reader-thread/Drop-bounded-reap). **D-plumbing@7166f52** `SpawnReq.translation_binary` threaded manifest→broker via harnesshost. **C-kernel@79f5a40** `InjectFloor` (pure controller-input buffer, unit-tested).
  - **REMAINING (full spec in JIT §W2 build-status):** Layer C proper = broker atomic-apply: inject-worker thread (owns `TranslationChild` + `Receiver<KeyCmd>`), per-event open-floor→feed binary→drain key/text(write_input)/delay(sleep)/commit→close-floor→flush buffered controller bytes, `INJECT_COMMIT_DEADLINE` guard + FAULT path; `dispatch_input` buffers via `InjectFloor.buffer_if_held` (op_id branch buffers inside the journal `apply_once` closure to keep ack+dedup) + emits `{type:input}` ping. Layer E = `dispatch_endpoint_input` routes `{type:event}` to the binary (raw inject = degenerate fallback when none/faulted) + poll-listener preference. Layer F = Unix park bound (shared with W1b F3). Layer G = int gate (real dummy translation-binary choreography ctrl+s→delay→text→enter→delay→ctrl+s→commit into rc-attached session w/o breaking control; + no-commit-deadline test; + content-free + no-zombie asserts; via spt-test-engineer). doyle gates at int.

- **W7 → SWAP (dispatched, not started):** `normalize_key_byte` one-way→swap (see live-context). Extends REQ-HAZARD-RC-INPUT-KEY-ENCODING [impl,unit].

- **W1/W3 = DONE (unchanged). W4/W5 = queued in worktrees (doyle holds). W6 = perri builds the translation binary after W2 lands.**

**Binding discipline (unchanged):** repro-first on REAL dummy-harness fixture + real daemon, never mocks; verify on the real terminal/binary; tag REQ evidence in the same commit; gates = `cargo clippy --workspace --all-targets -- -D warnings` (exact CI form) + `traceable-reqs check` EXIT=0 + rebuild real `target\debug\spt.exe` (`cargo build -p spt --bin spt`), never clobber a running daemon's exe.</psyche-context>

<spacetime-reorientation agent_id="todlando">
You are agent "todlando". Your spacetime session is active (you have a perch — that's why you're seeing this).

Send messages:
- $OWL send <target>              <<'EOF' ... EOF  — fire-and-forget
- $OWL send --reply-to <sender>   <<'EOF' ... EOF  — respond to whoever messaged you
- $OWL ring <target>              <<'EOF' ... EOF  — send and wait for reply (blocks; alias: `ask`)

Your ID (todlando) is auto-detected. If that fails, append it: `$OWL send <target> todlando`.

Receive messages (three paths, handle all identically):
- Primary (Monitor stream): your background `$OWL poll todlando` task running via the Monitor tool emits one EVENT envelope per delivery to stdout. Regular messages: `<EVENT type="msg" from="<sender>">body</EVENT>`. Self-originated alarms: `<EVENT type="alarm" target-time="<ISO>" current-time="<ISO>">body</EVENT>`. Long bodies arrive as N consecutive `<EVENT-PART seq="K/M" id="<8-hex>" [type=... from=... timestamp=... note=...]>chunk</EVENT-PART>` lines — collect parts sharing the same id, order by seq numerator, verify count equals seq denominator, concatenate chunk contents in seq order, then apply the body parsing rules to the reassembled body. The original envelope attrs appear on seq="1/M" only; continuations carry only seq + id. Drop orphan parts silently (seq K/M arriving without matching seq 1/M same id — earlier parts lost across a session boundary). The stream stays alive across deliveries — do not re-register after each message.
- Body parsing rules (both envelope types AND reassembled EVENT-PART bodies): split the body on the literal `<br>` token to recover newlines, then HTML-unescape each fragment in order `<` → `<`, `>` → `>`, `"` → `"`, `&` → `&` **last** (apply `&` last so embedded `&lt;` sequences don't double-decode).
- Fallback (Bash `--once`): when Monitor is unavailable, run `$OWL poll todlando --once` as a Bash background task (`run_in_background: true`). The wire format is IDENTICAL — including EVENT-PART chunking for long bodies — same `<EVENT type="msg|alarm" ...>body</EVENT>` envelope shapes as the primary path. The only difference: the process exits after one event, so you re-register a fresh Bash background task after each delivery (legacy one-shot loop).
- Hook delivery (orthogonal, when busy): the PreToolUse hook injects `<owl_messages><owl_message from="..."/></owl_messages>` XML into your next tool call context. Process it the same way as either of the above paths.

Reading liveness (do NOT mis-diagnose a healthy listener):
- `pid:"BUSY"` in `$OWL list` means your listener is ALIVE and mid-delivery — it is ONLINE, not dead. Never re-arm or stop on a BUSY reading.
- Your poll is actually down ONLY when your `todlando` row is MISSING from `$OWL list`, or shows OFFLINE / [orphaned].

After a /clear: DO NOTHING to your poll.
- Your poll listener survives /clear INTACT and keeps delivering to this same session. There is no orphan to kill and no listener to rebuild. Do NOT re-arm, do NOT start a second listener, do NOT stop anything.
- If you try to re-arm anyway you will get DUPLICATE/COLLISION — that is PROOF your listener is still alive and working, NOT evidence of a "stale" perch. Stop there; do not escalate to a stop command.

Re-arm a stale poll (genuinely rare — binary handoff or confirmed listener death only):
- Under stream mode (primary, Monitor), the poll listener is long-running and stays alive across messages AND across /clear. Re-arming is needed ONLY after a binary handoff (Phase 18.4/18.5 machinery) or after confirmed listener death (your `todlando` row MISSING from `$OWL list`) — NOT after a /clear, NOT after every message, and NOT on a BUSY reading.
- Re-arm primary: relaunch `$OWL poll todlando listen --live` via the Monitor tool with `persistent: true` and `description: "« spt event »"`.
- Re-arm fallback: relaunch `$OWL poll todlando listen --live --once` via Bash with `run_in_background: true`, then re-register after each delivered message.

Other commands:
- $OWL list — list active agents
- $OWL stop todlando — stop your listener
- $OWL new-alarm todlando <time> -- <message> — schedule a self-reminder
- $OWL doctor — diagnostics

Talking to your Psyche (todlando-psyche):
- Your Psyche is a DETACHED, self-healing process — not part of this session. It appears in `$OWL list` as a child under your Self perch, and in `$LIVE list-psyches`. It may briefly drop from the listing while it is ingesting a commune (its wrapper is busy running a resume) — that is transient and self-heals. Do NOT treat a briefly-absent Psyche as a bug and do NOT restart it. Only act if it stays gone for several minutes.
- NEVER run `$LIVE stop` to "clean up" your Psyche or a listener you think is "stale" — `$LIVE stop` tears down your Psyche. After /clear there is nothing to clean up (your listener survived intact). Reserve `$LIVE revive todlando` for a Psyche that is genuinely, persistently dead.
- Send a commune (delta update): use the Write tool to drop `.claude/todlando-commune.md`. Your Self listener detects the file, your Psyche wrapper ingests it via the existing Psyche session, and the file is deleted on success. See /spt:commune.
- Sign off cleanly: use the Write tool to drop `.claude/todlando-signoff.md` (empty body = plain signoff; non-empty body = prepended as FINAL COMMUNE before INIT_SIGNOFF). See /spt:signoff.
- Inbound messages from "todlando-psyche" (echo communes, briefs) are FYI context — read them; no ACK required. You may still message your Psyche directly with $OWL send or $OWL ring if needed.
</spacetime-reorientation>


Full state recovered. Queue clear: (1) W7-swap (2) F1 hang-guard (3) W2 Layer C. Starting W7-swap — fast, operator-requested. First look at current code.


Bash

grep -rn "normalize_key_byte\|normalize_maps_backspace" --include=*.rs | head -30

Locate normalize_key_byte + its unit test


crates/spt/src/rc.rs:90:fn normalize_key_byte(b: u8) -> u8 {
crates/spt/src/rc.rs:207:            let forward: Vec<u8> = parsed.forward.into_iter().map(normalize_key_byte).collect();
crates/spt/src/rc.rs:611:    fn normalize_maps_backspace_but_leaves_detach_and_others() {
crates/spt/src/rc.rs:612:        assert_eq!(normalize_key_byte(0x08), 0x7f, "Backspace ^H -> VT DEL");
crates/spt/src/rc.rs:613:        assert_eq!(normalize_key_byte(0x02), 0x02, "ctrl-b detach prefix untouched");
crates/spt/src/rc.rs:614:        assert_eq!(normalize_key_byte(0x7f), 0x7f, "VT DEL passes through");
crates/spt/src/rc.rs:615:        assert_eq!(normalize_key_byte(b'a'), b'a', "ordinary byte untouched");
crates/spt/src/rc.rs:616:        assert_eq!(normalize_key_byte(0x0d), 0x0d, "Enter (CR) untouched");
crates/spt/src/rc.rs:617:        assert_eq!(normalize_key_byte(0x1b), 0x1b, "ESC (CSI lead) untouched");


80	/// `ENABLE_VIRTUAL_TERMINAL_INPUT` was the WRONG lever — on Windows Terminal it
81	/// yields win32-input-mode (6-field `ESC[Vk;Sc;Uc;Kd;Cs;Rc_` key records), not
82	/// legacy xterm VT, which broke the ctrl-b detach (0x02 wrapped, never seen as a
83	/// raw byte) and fed win32-input-mode into Claude Code's own ConPTY input
84	/// negotiation. Staying a raw byte pump + this one-byte map keeps detach working
85	/// and is terminal-convention-minimal. Ctrl+Backspace on this terminal already
86	/// sends 0x7f, so it stays char-delete (word-delete lost on the secondary key —
87	/// accepted; an optional ctrl+w/0x17 remap is a follow-up pending CC confirm).
88	// [impl->REQ-HAZARD-RC-INPUT-KEY-ENCODING]
89	#[cfg(windows)]
90	fn normalize_key_byte(b: u8) -> u8 {
91	    if b == 0x08 {
92	        0x7f
93	    } else {
94	        b
95	    }
96	}
97
98	/// Restore the terminal out of raw mode no matter how the pump returns.
99	struct RawGuard(bool);
100	impl RawGuard {
101	    fn enable() -> Self {
102	        RawGuard(crossterm::terminal::enable_raw_mode().is_ok())
103	    }
104	}
105	impl Drop for RawGuard {
106	    fn drop(&mut self) {
107	        if self.0 {
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

/// `ENABLE_VIRTUAL_TERMINAL_INPUT` was the WRONG lever — on Windows Terminal it
/// yields win32-input-mode (6-field `ESC[Vk;Sc;Uc;Kd;Cs;Rc_` key records), not
/// legacy xterm VT, which broke the ctrl-b detach (0x02 wrapped, never seen as a
/// raw byte) and fed win32-input-mode into Claude Code's own ConPTY input
/// negotiation. Staying a raw byte pump + this one-byte map keeps detach working
/// and is terminal-convention-minimal. Ctrl+Backspace on this terminal already
/// sends 0x7f, so it stays char-delete (word-delete lost on the secondary key —
/// accepted; an optional ctrl+w/0x17 remap is a follow-up pending CC confirm).
// [impl->REQ-HAZARD-RC-INPUT-KEY-ENCODING]
#[cfg(windows)]
fn normalize_key_byte(b: u8) -> u8 {
    if b == 0x08 {
        0x7f
    } else {
        b
    }
}

/// Restore the terminal out of raw mode no matter how the pump returns.
struct RawGuard(bool);
impl RawGuard {
    fn enable() -> Self {
        RawGuard(crossterm::terminal::enable_raw_mode().is_ok())
    }
}
impl Drop for RawGuard {
    fn drop(&mut self) {
        if self.0 {
            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


Now let me see the detach-handling order (0x02 must be caught before normalize) + the KNOWN-HAZARDS 7.13 entry + REQ.


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	            // W7 (REQ-HAZARD-RC-INPUT-KEY-ENCODING): on Windows the legacy console
202	            // sends Backspace as 0x08 (^H → CC kill-word); normalize it to 0x7f
203	            // (VT DEL) in the forward path, AFTER the detach state machine has
204	            // consumed any 0x02 prefix. Unix is already VT (and a real ctrl+h
205	            // there must stay 0x08), so the map is Windows-only.
206	            #[cfg(windows)]
207	            let forward: Vec<u8> = parsed.forward.into_iter().map(normalize_key_byte).collect();
208	            #[cfg(unix)]
209	            let forward = parsed.forward;
210	            if !forward.is_empty() && tx.send(StdinMsg::Bytes(forward)).is_err() {
211	                break;
212	            }
213	            if parsed.detach {
214	                let _ = tx.send(StdinMsg::Detach);
215	                return;
216	            }
217	        }
218	    });
219	}

            // 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]);
            // 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;
            }
        }
    });
}


grep -n "7.13\|RC-INPUT-KEY-ENCODING" docs/KNOWN-HAZARDS.md traceable-reqs.toml

Find KH 7.13 + REQ entry


docs/KNOWN-HAZARDS.md:420:### 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]`
docs/KNOWN-HAZARDS.md:479:| 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` |
traceable-reqs.toml:1219:id = "REQ-HAZARD-RC-INPUT-KEY-ENCODING"
traceable-reqs.toml:1221: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.


Swap correctness confirmed: CC maps `0x08`(^H)→kill-word, `0x7f`(DEL)→char-delete. Swapping input bytes gives Backspace→char, Ctrl+Backspace→word = native Win11. doyle's design sound. Building W7-swap.

The unit test already exists — only expectation flip needed (trivial, inline with impl, not new test authoring). Edits:


Edit

/// and is terminal-convention-minimal. Ctrl+Backspace on this terminal already
/// sends 0x7f, so it stays char-delete (word-delete lost on the secondary key —
/// accepted; an optional ctrl+w/0x17 remap is a follow-up pending CC confirm).
// [impl->REQ-HAZARD-RC-INPUT-KEY-ENCODING]
#[cfg(windows)]
fn normalize_key_byte(b: u8) -> u8 {
    if b == 0x08 {
        0x7f
    } else {
        b
    }
}

/// and is terminal-convention-minimal. SWAP (not one-way): this terminal sends
/// Backspace as 0x08 (^H) and Ctrl+Backspace as 0x7f (DEL); CC reads ^H as
/// backward-kill-word and DEL as char-delete. Swapping the two input bytes maps
/// Backspace→char-delete and Ctrl+Backspace→word-delete — the native Win11
/// convention (the operator rejected losing word-delete). 0x02 detach is consumed
/// upstream before normalize; every other byte passes through.
// [impl->REQ-HAZARD-RC-INPUT-KEY-ENCODING]
#[cfg(windows)]
fn normalize_key_byte(b: u8) -> u8 {
    match b {
        0x08 => 0x7f,
        0x7f => 0x08,
        other => other,
    }
}


<owl_messages>
<owl_message from="todlando-psyche" priority="normal"><EVENT type="echo_commune" from="todlando-psyche" timestamp="2026-06-19T07:01:02-07:00" machine="HFENDULEAM" project="spt-core" branch="v0.13.0-delivery-control" head_sha="e404aeb42335c2d22744f1d389f1539495f00ccf" head_subject="fix(daemon): W1b — effect journal no longer wedges interactive input (RE…" note="Echo commune brief — auto-fired">&lt;live-context&gt;
**Session checkpoint — pausing, resume next session. Tree CLEAN @e404aeb.**

**The two-subagent conflict (snapshot's UNRESOLVED) — RESOLVED.** The operator caught it: I had TWO spt-test-engineer agents (original a4f3a273 + a redundant fixer a412e08a I launched) both editing `inject_control_wedge.rs` + running cargo → artifact-dir lock collision + the original's orphaned flood/test processes (`0xffffffff` abnormal int exit). Fix: TaskStop'd BOTH, killed orphan processes, then INDEPENDENTLY re-verified the on-disk result from a quiesced tree (not trust-the-green). Disk was green: units 7/7, int 3/3 clean exit 0, clippy clean. The last-writer (a412e08a's teardown fixes) left a coherent passing file.
**LESSON (wired to memory candidates):** never run two test-engineer subagents on the same crate/`target/` concurrently — serialize or worktree-isolate. And subagent monitor/Tee wait-loops burned ~1M tokens producing non-answers → constrain test subagents to DIRECT timeout'd runs, ≤4 iterations, no Monitor.

**W1b — DOYLE GATE PASS @e404aeb** (independently re-verified by doyle: clippy clean, units 7/7, int 3/3 via `cargo nextest`, traceable EXIT=0, KH 7.14). **Operator re-dogfooded the REAL build: the wedge is GONE, typing smooth.** REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE activated [impl,unit,int].

**doyle dispatched W7-followup (small/quick, NOT yet started):** upgrade `rc.rs normalize_key_byte` (cfg(windows)) from the one-way map to a SWAP — `0x08→0x7f`, `0x7f→0x08`, else passthrough (detach `0x02` still handled before normalize). Recovers Ctrl+Backspace=word-delete (native Win11 convention the operator rejected losing). Update unit `normalize_maps_backspace_but_leaves_detach_and_others` to assert the swap; update KH 7.13 (one-way→swap) + REQ note (extend [impl,unit], don't re-mint). Gate: clippy + unit + traceable. Tests via spt-test-engineer.

**Interactions:** doyle = designer/gate (gated W1b, dispatched W7-swap, holds W4/W5/W6 queue). spt-test-engineer = all test authoring (user reaffirmed: invoke it for every test). Operator (user) = caught the subagent conflict, paused me for doyle's gate, set &quot;resume next session&quot;.

**IMMEDIATE NEXT STEPS (resume order — user to confirm; my recommendation):**
1. **W7 swap** (fast, operator-requested real win) — the byte swap + unit + KH 7.13 + REQ note.
2. **W1b follow-up F1** (cheap footgun): `cargo test --test inject_control_wedge` (threads-in-one-process) HANGS on Windows (cost doyle 30min); CI is safe (nextest = process-per-test) but add a `serial_test` guard OR a module-doc &quot;run via nextest / `--test-threads=1` on Windows&quot; note.
3. **W2 Layer C proper** (the big build — see project-context). Full spec already in `V0.13.0-DELIVERY-CONTROL-JIT.md` §W2 build-status.

**Other W1b follow-ups (tracked, fold in):** F2 = `apply_once` Phase-1 also short-circuit on `pending.contains(key)` (cheap same-key-concurrency hardening beyond the model invariant). F3 = Fix(2) Unix-forkpty park bound (bounded `write_input` + DSR off the writer mutex) — gravity-linux, the int's cfg(unix) park-(b) assert tracks it.
&lt;/live-context&gt;
&lt;project-context&gt;
**v0.13.0-delivery-control — END GOAL (unchanged):** 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 into v0.13.0.

**WAVE STATUS — DELTA since snapshot:**

- **W1b (NEW, the real operator wedge, DONE/GATE-PASSED @e404aeb)** — W1 fixed the OUTPUT drain; W1b is the INPUT/effect-journal path W1 never touched. ROOT: `EffectJournal::apply_once` held its global lock across `write_line(PENDING)` (fsync) → `effect()` (blocking PtyWrite) → `write_line(DONE)` (fsync), so every keystroke = 2 fsync under a global lock with the PTY write inside it → (A) stutter, (B) a blocked PtyWrite held the lock forever → dispatch couldn't open attaches → `spt rc` &quot;brain IPC read deadline&quot;. FIX (1)+(3): `apply_once` RELEASES the lock across `effect()` (reserve key + fsync PENDING for DURABLE under lock → RELEASE → effect() OFF lock → re-acquire → finalize); `EffectKind::is_durable()` = PtyWrite false (ephemeral, no journal lines/no fsync, in-memory dedup only); durable kinds keep fsync. Brain-crash exactly-once preserved (gauntlet green). Refutes the W2-deferred &quot;ConPTY-benign&quot; ruling (the wedge is the LOCK HOLD). KH 7.14.

- **W2 (delivery redesign) — LAYERS BANKED, contract LOCKED:** ALL contract decisions doyle-gated. Manifest = TABLE `[message-idle-translation-binary]` with `path` scalar (OPT-B; no `deny_unknown_fields`, forward-compat). Atomic-sequence boundary = explicit `{commit:true}` per-event terminator (OPT-1): each `{type:event}` OPENS the inject floor (buffers controller input), `{commit}` CLOSES it (flush after); concurrent events QUEUE; `{type:input}` pings keep flowing during buffering; operator keystrokes = the buffered bytes. MANDATORY `INJECT_COMMIT_DEADLINE` (~5s): no commit → flush + release + FAULT the binary (terminate, no respawn → degrade to pass-through). doyle gate-reminders: `{type:input}` ping CONTENT-FREE (no PTY content leak); lifecycle kill BOUNDED+reaped (no zombie).
  - Layers: **A@c73eb6a** manifest table key+validate+schema. **B@611205c** `spt-daemon/translation.rs` (ToBinary init/event/input + KeyCmd key/text/delay_ms/commit untagged + `key_to_bytes` send-keys map + `TranslationChild` piped-stdin/stdout-reader-thread/Drop-bounded-reap). **D-plumbing@7166f52** `SpawnReq.translation_binary` threaded manifest→broker via harnesshost. **C-kernel@79f5a40** `InjectFloor` (pure controller-input buffer, unit-tested).
  - **REMAINING (full spec in JIT §W2 build-status):** Layer C proper = broker atomic-apply: inject-worker thread (owns `TranslationChild` + `Receiver&lt;KeyCmd&gt;`), per-event open-floor→feed binary→drain key/text(write_input)/delay(sleep)/commit→close-floor→flush buffered controller bytes, `INJECT_COMMIT_DEADLINE` guard + FAULT path; `dispatch_input` buffers via `InjectFloor.buffer_if_held` (op_id branch buffers inside the journal `apply_once` closure to keep ack+dedup) + emits `{type:input}` ping. Layer E = `dispatch_endpoint_input` routes `{type:event}` to the binary (raw inject = degenerate fallback when none/faulted) + poll-listener preference. Layer F = Unix park bound (shared with W1b F3). Layer G = int gate (real dummy translation-binary choreography ctrl+s→delay→text→enter→delay→ctrl+s→commit into rc-attached session w/o breaking control; + no-commit-deadline test; + content-free + no-zombie asserts; via spt-test-engineer). doyle gates at int.

- **W7 → SWAP (dispatched, not started):** `normalize_key_byte` one-way→swap (see live-context). Extends REQ-HAZARD-RC-INPUT-KEY-ENCODING [impl,unit].

- **W1/W3 = DONE (unchanged). W4/W5 = queued in worktrees (doyle holds). W6 = perri builds the translation binary after W2 lands.**

**Binding discipline (unchanged):** repro-first on REAL dummy-harness fixture + real daemon, never mocks; verify on the real terminal/binary; tag REQ evidence in the same commit; gates = `cargo clippy --workspace --all-targets -- -D warnings` (exact CI form) + `traceable-reqs check` EXIT=0 + rebuild real `target\debug\spt.exe` (`cargo build -p spt --bin spt`), never clobber a running daemon's exe.
&lt;/project-context&gt;</EVENT></owl_message>
</owl_messages>


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)

/// and is terminal-convention-minimal. Ctrl+Backspace on this terminal already
/// sends 0x7f, so it stays char-delete (word-delete lost on the secondary key —
/// accepted; an optional ctrl+w/0x17 remap is a follow-up pending CC confirm).
// [impl->REQ-HAZARD-RC-INPUT-KEY-ENCODING]
#[cfg(windows)]
fn normalize_key_byte(b: u8) -> u8 {
    if b == 0x08 {
        0x7f
    } else {
        b
    }
}

/// and is terminal-convention-minimal. SWAP (not one-way): this terminal sends
/// Backspace as 0x08 (^H) and Ctrl+Backspace as 0x7f (DEL); CC reads ^H as
/// backward-kill-word and DEL as char-delete. Swapping the two input bytes maps
/// Backspace→char-delete and Ctrl+Backspace→word-delete — the native Win11
/// convention (the operator rejected losing word-delete). 0x02 detach is consumed
/// upstream before normalize; every other byte passes through.
// [impl->REQ-HAZARD-RC-INPUT-KEY-ENCODING]
#[cfg(windows)]
fn normalize_key_byte(b: u8) -> u8 {
    match b {
        0x08 => 0x7f,
        0x7f => 0x08,
        other => other,
    }
}

/// legacy xterm VT, which broke the ctrl-b detach (0x02 wrapped, never seen as a

/// raw byte) and fed win32-input-mode into Claude Code's own ConPTY input

/// negotiation. Staying a raw byte pump + this one-byte map keeps detach working

-/// and is terminal-convention-minimal. Ctrl+Backspace on this terminal already

-/// sends 0x7f, so it stays char-delete (word-delete lost on the secondary key —

-/// accepted; an optional ctrl+w/0x17 remap is a follow-up pending CC confirm).

+/// and is terminal-convention-minimal. SWAP (not one-way): this terminal sends

+/// Backspace as 0x08 (^H) and Ctrl+Backspace as 0x7f (DEL); CC reads ^H as

+/// backward-kill-word and DEL as char-delete. Swapping the two input bytes maps

+/// Backspace→char-delete and Ctrl+Backspace→word-delete — the native Win11

+/// convention (the operator rejected losing word-delete). 0x02 detach is consumed

+/// upstream before normalize; every other byte passes through.


Echo commune = FYI, confirms plan. Continuing. Update unit test assertions for swap:


609	    // every other byte pass through untouched (so detach still works and a CR /
610	    // arrow-CSI / payload byte is never corrupted). The live keypress is
611	    // HITL-verified by the operator re-capture, not here.
612	    #[cfg(windows)]
613	    #[test]
614	    fn normalize_maps_backspace_but_leaves_detach_and_others() {
615	        assert_eq!(normalize_key_byte(0x08), 0x7f, "Backspace ^H -> VT DEL");
616	        assert_eq!(normalize_key_byte(0x02), 0x02, "ctrl-b detach prefix untouched");
617	        assert_eq!(normalize_key_byte(0x7f), 0x7f, "VT DEL passes through");
618	        assert_eq!(normalize_key_byte(b'a'), b'a', "ordinary byte untouched");
619	        assert_eq!(normalize_key_byte(0x0d), 0x0d, "Enter (CR) untouched");
620	        assert_eq!(normalize_key_byte(0x1b), 0x1b, "ESC (CSI lead) untouched");
621	    }
622

    // 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");
    }


604	    }
605
606	    // [unit->REQ-HAZARD-RC-INPUT-KEY-ENCODING] the Windows key-byte normalize:
607	    // the legacy console's Backspace ^H (0x08) maps to VT DEL (0x7f, char-delete)
608	    // so Claude Code stops kill-wording it; the ctrl-b detach prefix (0x02) and
609	    // every other byte pass through untouched (so detach still works and a CR /

    }

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


    // [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 Windows key-byte normalize is a
    // SWAP: the legacy console's Backspace ^H (0x08) maps to VT DEL (0x7f,
    // char-delete) and Ctrl+Backspace DEL (0x7f) maps to ^H (0x08, kill-word) — so
    // Claude Code sees Backspace=char-delete and Ctrl+Backspace=word-delete (native
    // Win11). 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.
    #[cfg(windows)]
    #[test]
    fn normalize_maps_backspace_but_leaves_detach_and_others() {
        assert_eq!(normalize_key_byte(0x08), 0x7f, "Backspace ^H -> VT DEL (char-del)");
        assert_eq!(normalize_key_byte(0x7f), 0x08, "Ctrl+Backspace DEL -> ^H (kill-word)");
        assert_eq!(normalize_key_byte(0x02), 0x02, "ctrl-b detach prefix untouched");
        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 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 Windows key-byte normalize is a
    // SWAP: the legacy console's Backspace ^H (0x08) maps to VT DEL (0x7f,
    // char-delete) and Ctrl+Backspace DEL (0x7f) maps to ^H (0x08, kill-word) — so
    // Claude Code sees Backspace=char-delete and Ctrl+Backspace=word-delete (native
    // Win11). 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.
    #[cfg(windows)]
    #[test]
    fn normalize_maps_backspace_but_leaves_detach_and_others() {
        assert_eq!(normalize_key_byte(0x08), 0x7f, "Backspace ^H -> VT DEL (char-del)");
        assert_eq!(normalize_key_byte(0x7f), 0x08, "Ctrl+Backspace DEL -> ^H (kill-word)");
        assert_eq!(normalize_key_byte(0x02), 0x02, "ctrl-b detach prefix untouched");
        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 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.

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

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

+    // char-delete) and Ctrl+Backspace DEL (0x7f) maps to ^H (0x08, kill-word) — so

+    // Claude Code sees Backspace=char-delete and Ctrl+Backspace=word-delete (native

+    // Win11). 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.

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

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

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

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


Now the forward-path comment at line 201-205:


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

            // W7 (REQ-HAZARD-RC-INPUT-KEY-ENCODING): on Windows the legacy console
            // sends Backspace as 0x08 (^H → CC kill-word) and Ctrl+Backspace as
            // 0x7f (DEL → CC char-delete); SWAP them in the forward path so CC sees
            // Backspace=char-delete and Ctrl+Backspace=word-delete (native Win11),
            // AFTER the detach state machine has consumed any 0x02 prefix. Unix is
            // already VT (and a real ctrl+h there must stay 0x08), so it's Win-only.


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

            // W7 (REQ-HAZARD-RC-INPUT-KEY-ENCODING): on Windows the legacy console
            // sends Backspace as 0x08 (^H → CC kill-word) and Ctrl+Backspace as
            // 0x7f (DEL → CC char-delete); SWAP them in the forward path so CC sees
            // Backspace=char-delete and Ctrl+Backspace=word-delete (native Win11),
            // AFTER the detach state machine has consumed any 0x02 prefix. Unix is
            // already VT (and a real ctrl+h there must stay 0x08), so it's Win-only.

let parsed = parse_stdin_chunk(&mut armed, &buf[..n]);

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

+            // sends Backspace as 0x08 (^H → CC kill-word) and Ctrl+Backspace as

+            // 0x7f (DEL → CC char-delete); SWAP them in the forward path so CC sees

+            // Backspace=char-delete and Ctrl+Backspace=word-delete (native Win11),

+            // AFTER the detach state machine has consumed any 0x02 prefix. Unix is

+            // already VT (and a real ctrl+h there must stay 0x08), so it's Win-only.

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


Now KNOWN-HAZARDS 7.13. Read both the heading section + the table row:


420	### 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]`
421	- **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.
422	- **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.)
423	- **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.
424	- **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.
425	- **Source:** v0.13.0 W7 (operator dogfooding; doyle /diagnose + byte confirm + VT-backfire re-capture 2026-06-19).
426
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]`
428	- **Failure (operator dogfooding + doyle /diagnose, MEASURED on the real Windows box, 2026-06-19, post-W1 escape):** `EffectJournal::apply_once` held its global `inner` mutex ACROSS `write_line(PENDING)` → `effect()` → `write_line(DONE)`, and `write_line` does `flush()+sync_all()` (a full fsync). Every operator keystroke is a `PtyWrite` effect (via `send_effect` → `dispatch_input` op_id branch), so each paid TWO fsyncs serialized under a GLOBAL lock with the blocking PTY write INSIDE the lock. Two facets, one root: **(A) stutter** — measured fsync on `%LOCALAPPDATA%\spt-core` median 6.5 ms / spikes 198 ms, ×2 per keystroke → choppy, worsens with volume; **(B) HARD WEDGE** — a blocking `PtyWrite` (ConPTY/forkpty input buffer not draining) held the lock indefinitely, so the single-threaded dispatch could open NO attaches → every `spt rc --view/--take` died with `brain IPC read deadline elapsed` (broker control-plane KIND queries still answered = a different thread). DISTINCT from 7.12 (the OUTPUT drain); this is the INPUT/effect-journal path 7.12 never touched. **Refutes** 7.12's "input parks are Windows-benign (ConPTY absorbs)" deferral — on the real box the input path wedges regardless, because the wedge is the LOCK HOLD, not the PTY write blocking per se.
429	- **Invariant:** `apply_once` RELEASES the inner lock across `effect()` — reserve the key (fsync `PENDING` for DURABLE kinds) under the lock, RELEASE, run the effect OFF the lock, then re-acquire to finalize (fsync `DONE` for durable, mark applied). Crash-idempotency comes from the per-key reservation + the applied-set, not from holding the lock across the effect (the brain-crash guarantee is unchanged: the broker survives the brain, so reserve→effect→finalize runs uninterrupted on the broker thread; a replay hits the applied-set and dedups). `EffectKind::PtyWrite` is **EPHEMERAL** — NO journal lines, NO fsync, in-memory dedup only (a keystroke lost to a broker crash is retyped; PTY state is never rebuilt from keystroke replay) — while durable kinds (spool/registry/net) keep their fsync'd markers. The PtyWrite itself must additionally be bounded/fail-fast and must not hold the writer mutex across a blocking write (the Unix-forkpty park bound; ConPTY absorbs = benign).
430	- **spt-core mapping:** impl `effect.rs` `apply_once` (reserve/release/finalize) + `EffectKind::is_durable`; unit (`effect.rs`) a barrier proving the lock is NOT held across `effect()` (a different key's `apply_once` completes while one effect blocks) + `PtyWrite` writes no journal line while a durable kind (`NetSend`) does (the no-fsync proxy); int `crates/spt-daemon/tests/inject_control_wedge.rs` (real broker+PTY, a stalled consumer + sustained blocking input effect: a concurrent `spt rc` attach stays serviceable AND actually receives PTY bytes — the assertion W1's gate lacked). Unix park-(b) folded here, gated on gravity-linux. doyle GATE-GUARD: assert structurally, NEVER on fsync wall-clock (env-dependent, flaky); the stutter is fixed as a consequence of the no-fsync path, proven by the structural unit.
431	- **Source:** v0.13.0 W1b (operator dogfooding post-W1 escape; doyle /diagnose + measurement 2026-06-19).
432
433	---
434
435	## Conformance checklist (condensed)
436
437	| # | Invariant | spt-core surface |
438	|---|---|---|
439	| 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown |

### 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).

### 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]`
- **Failure (operator dogfooding + doyle /diagnose, MEASURED on the real Windows box, 2026-06-19, post-W1 escape):** `EffectJournal::apply_once` held its global `inner` mutex ACROSS `write_line(PENDING)` → `effect()` → `write_line(DONE)`, and `write_line` does `flush()+sync_all()` (a full fsync). Every operator keystroke is a `PtyWrite` effect (via `send_effect` → `dispatch_input` op_id branch), so each paid TWO fsyncs serialized under a GLOBAL lock with the blocking PTY write INSIDE the lock. Two facets, one root: **(A) stutter** — measured fsync on `%LOCALAPPDATA%\spt-core` median 6.5 ms / spikes 198 ms, ×2 per keystroke → choppy, worsens with volume; **(B) HARD WEDGE** — a blocking `PtyWrite` (ConPTY/forkpty input buffer not draining) held the lock indefinitely, so the single-threaded dispatch could open NO attaches → every `spt rc --view/--take` died with `brain IPC read deadline elapsed` (broker control-plane KIND queries still answered = a different thread). DISTINCT from 7.12 (the OUTPUT drain); this is the INPUT/effect-journal path 7.12 never touched. **Refutes** 7.12's "input parks are Windows-benign (ConPTY absorbs)" deferral — on the real box the input path wedges regardless, because the wedge is the LOCK HOLD, not the PTY write blocking per se.
- **Invariant:** `apply_once` RELEASES the inner lock across `effect()` — reserve the key (fsync `PENDING` for DURABLE kinds) under the lock, RELEASE, run the effect OFF the lock, then re-acquire to finalize (fsync `DONE` for durable, mark applied). Crash-idempotency comes from the per-key reservation + the applied-set, not from holding the lock across the effect (the brain-crash guarantee is unchanged: the broker survives the brain, so reserve→effect→finalize runs uninterrupted on the broker thread; a replay hits the applied-set and dedups). `EffectKind::PtyWrite` is **EPHEMERAL** — NO journal lines, NO fsync, in-memory dedup only (a keystroke lost to a broker crash is retyped; PTY state is never rebuilt from keystroke replay) — while durable kinds (spool/registry/net) keep their fsync'd markers. The PtyWrite itself must additionally be bounded/fail-fast and must not hold the writer mutex across a blocking write (the Unix-forkpty park bound; ConPTY absorbs = benign).
- **spt-core mapping:** impl `effect.rs` `apply_once` (reserve/release/finalize) + `EffectKind::is_durable`; unit (`effect.rs`) a barrier proving the lock is NOT held across `effect()` (a different key's `apply_once` completes while one effect blocks) + `PtyWrite` writes no journal line while a durable kind (`NetSend`) does (the no-fsync proxy); int `crates/spt-daemon/tests/inject_control_wedge.rs` (real broker+PTY, a stalled consumer + sustained blocking input effect: a concurrent `spt rc` attach stays serviceable AND actually receives PTY bytes — the assertion W1's gate lacked). Unix park-(b) folded here, gated on gravity-linux. doyle GATE-GUARD: assert structurally, NEVER on fsync wall-clock (env-dependent, flaky); the stutter is fixed as a consequence of the no-fsync path, proven by the structural unit.
- **Source:** v0.13.0 W1b (operator dogfooding post-W1 escape; doyle /diagnose + measurement 2026-06-19).

---

## Conformance checklist (condensed)

| # | Invariant | spt-core surface |
|---|---|---|
| 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown |


### 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).

### 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]`
- **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 — **SWAP** the two delete bytes: `0x08` → `0x7f` (VT DEL, char-delete) AND `0x7f` → `0x08` (^H, kill-word), AFTER the detach state machine consumes any `0x02`, leaving `0x02` and every other byte untouched. Because CC reads `^H`=word-delete and DEL=char-delete, swapping the *input* bytes gives Backspace=char-delete and Ctrl+Backspace=word-delete — the native Win11 convention (the operator rejected losing word-delete; the earlier one-way `0x08`→`0x7f` map dropped it). cfg(windows) only (Unix is already VT; a real `ctrl+h`/`ctrl+?` there must pass through). Accepted minor loss: a real `ctrl+h` (0x08) and a real DEL (0x7f) are swapped on Windows — both are delete keys, so the practical effect is the Backspace/Ctrl+Backspace native mapping.
- **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 swap, 0x02/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; one-way→swap upgrade 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 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).

### 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]`
- **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 — **SWAP** the two delete bytes: `0x08` → `0x7f` (VT DEL, char-delete) AND `0x7f` → `0x08` (^H, kill-word), AFTER the detach state machine consumes any `0x02`, leaving `0x02` and every other byte untouched. Because CC reads `^H`=word-delete and DEL=char-delete, swapping the *input* bytes gives Backspace=char-delete and Ctrl+Backspace=word-delete — the native Win11 convention (the operator rejected losing word-delete; the earlier one-way `0x08`→`0x7f` map dropped it). cfg(windows) only (Unix is already VT; a real `ctrl+h`/`ctrl+?` there must pass through). Accepted minor loss: a real `ctrl+h` (0x08) and a real DEL (0x7f) are swapped on Windows — both are delete keys, so the practical effect is the Backspace/Ctrl+Backspace native mapping.
- **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 swap, 0x02/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; one-way→swap upgrade 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 normalize Windows legacy-console Backspace (`^H`→VT DEL); do NOT enable VT console input  `[REQ-HAZARD-RC-INPUT-KEY-ENCODING]`

+### 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]`

-- **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).

+- **Invariant:** stay a raw byte pump and NARROW-normalize in the forward path — **SWAP** the two delete bytes: `0x08` → `0x7f` (VT DEL, char-delete) AND `0x7f` → `0x08` (^H, kill-word), AFTER the detach state machine consumes any `0x02`, leaving `0x02` and every other byte untouched. Because CC reads `^H`=word-delete and DEL=char-delete, swapping the *input* bytes gives Backspace=char-delete and Ctrl+Backspace=word-delete — the native Win11 convention (the operator rejected losing word-delete; the earlier one-way `0x08`→`0x7f` map dropped it). cfg(windows) only (Unix is already VT; a real `ctrl+h`/`ctrl+?` there must pass through). Accepted minor loss: a real `ctrl+h` (0x08) and a real DEL (0x7f) are swapped on Windows — both are delete keys, so the practical effect is the Backspace/Ctrl+Backspace native mapping.

+- **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 swap, 0x02/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; one-way→swap upgrade 2026-06-19).

### 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]`

- **Failure (operator dogfooding + doyle /diagnose, MEASURED on the real Windows box, 2026-06-19, post-W1 escape):** `EffectJournal::apply_once` held its global `inner` mutex ACROSS `write_line(PENDING)` → `effect()` → `write_line(DONE)`, and `write_line` does `flush()+sync_all()` (a full fsync). Every operator keystroke is a `PtyWrite` effect (via `send_effect` → `dispatch_input` op_id branch), so each paid TWO fsyncs serialized under a GLOBAL lock with the blocking PTY write INSIDE the lock. Two facets, one root: **(A) stutter** — measured fsync on `%LOCALAPPDATA%\spt-core` median 6.5 ms / spikes 198 ms, ×2 per keystroke → choppy, worsens with volume; **(B) HARD WEDGE** — a blocking `PtyWrite` (ConPTY/forkpty input buffer not draining) held the lock indefinitely, so the single-threaded dispatch could open NO attaches → every `spt rc --view/--take` died with `brain IPC read deadline elapsed` (broker control-plane KIND queries still answered = a different thread). DISTINCT from 7.12 (the OUTPUT drain); this is the INPUT/effect-journal path 7.12 never touched. **Refutes** 7.12's "input parks are Windows-benign (ConPTY absorbs)" deferral — on the real box the input path wedges regardless, because the wedge is the LOCK HOLD, not the PTY write blocking per se.


Now the conformance-table row (line 479) + the toml REQ note:


479	| 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` |
480	| 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.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.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.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.13 | `spt rc` SWAPS Windows legacy-console delete bytes `^H` (0x08)↔VT DEL (0x7f) in the raw forward path (Backspace→char-delete, Ctrl+Backspace→word-delete; 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` 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.13 | `spt rc` SWAPS Windows legacy-console delete bytes `^H` (0x08)↔VT DEL (0x7f) in the raw forward path (Backspace→char-delete, Ctrl+Backspace→word-delete; 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.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` |


Now the toml REQ note — read the exact block:


1217	# ── W7 (operator /diagnose 2026-06-19): rc Backspace key forwards the wrong byte ──
1218	[[requirements]]
1219	id = "REQ-HAZARD-RC-INPUT-KEY-ENCODING"
1220	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)"
1221	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.
1222

# ── W7 (operator /diagnose 2026-06-19): rc Backspace key forwards the wrong byte ──
[[requirements]]
id = "REQ-HAZARD-RC-INPUT-KEY-ENCODING"
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 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.


Append swap-upgrade note to the REQ comment (stages stay `["impl","unit"]`, no re-mint):


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.

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. UPGRADE (2026-06-19, doyle-dispatched, operator-requested): one-way map → SWAP. normalize_key_byte now maps 0x08→0x7f AND 0x7f→0x08 (else passthrough; 0x02 detach still consumed upstream). Because CC reads ^H=backward-kill-word and DEL=char-delete, swapping the input bytes recovers Backspace=char-delete AND Ctrl+Backspace=word-delete (native Win11 — the operator rejected losing word-delete). unit assertions extended (0x7f→0x08). [impl,unit] unchanged (extend, not re-mint)."


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

+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. UPGRADE (2026-06-19, doyle-dispatched, operator-requested): one-way map → SWAP. normalize_key_byte now maps 0x08→0x7f AND 0x7f→0x08 (else passthrough; 0x02 detach still consumed upstream). Because CC reads ^H=backward-kill-word and DEL=char-delete, swapping the input bytes recovers Backspace=char-delete AND Ctrl+Backspace=word-delete (native Win11 — the operator rejected losing word-delete). unit assertions extended (0x7f→0x08). [impl,unit] unchanged (extend, not re-mint)."

# ── W1b (operator /diagnose 2026-06-19, post-W1 dogfood escape): the effect-journal PTY-input wedge ──


Edits done. Gate: clippy + unit + traceable. Run all three:


Bash

cargo test -p spt --lib normalize_maps_backspace 2>&1 | tail -20

Run the swap unit test


error: no library targets found in package `spt`


cargo test -p spt --bin spt normalize_maps_backspace 2>&1 | tail -20

Run swap unit test against binary target


   Compiling spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling spt v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 21.62s
     Running unittests src\main.rs (target\debug\deps\spt-1f46f552567a1e08.exe)

running 1 test
test rc::tests::normalize_maps_backspace_but_leaves_detach_and_others ... ok

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


Unit green. Now clippy (CI form) + traceable + real bin rebuild:


cargo clippy --workspace --all-targets -- -D warnings 2>&1 | tail -8 && echo "===TRACEABLE===" && traceable-reqs check 2>&1 | tail -15

Workspace clippy + traceable check


    Checking spt v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 5.98s
===TRACEABLE===
       No endpoint process terminates/suspends during self-update
  [OK] REQ-UPD-4  required: [impl, unit]  stages: -doc +impl +unit -int
       Update gated on user confirmation by default; opt-in full-auto
  [OK] REQ-UPD-5  required: [impl, unit]  stages: -doc +impl +unit -int
       spt-core ripple-updates registered adapters
  [OK] REQ-UPD-6  required: [doc, impl, unit, int]  stages: +doc +impl +unit +int
       Platform-targeted update sets and debug rollout: signed multi-platform update metadata, recipient platform selection, channel-scoped monotonic counters, debug-channel opt-in via release-key overlay, local staging plus pull-based peer propagation, and maintainer-only convergence tooling (ADR-0016)
  [OK] REQ-UPD-7  required: [impl, unit]  stages: -doc +impl +unit -int
       Origin-source update bootstrap (`spt update fetch`): pull the latest signed release directly from the GitHub release origin (`SaberMage/spt-releases`) — the per-platform artifact + its `<asset>.release.json` SignedRelease metadata — and stage it through the EXISTING verify→stage pipeline (the same `plan_verified` gate: two-key signature + channel + monotonic rollback floor + SHA-256), after which the normal consent-notif / `spt update apply` flow is unchanged. Closes the peer-only-discovery gap (REQ-UPD-1): a first-in-fleet / isolated node can update with no peer to pull from. The signed-release anchor keeps the GitHub transport untrusted-but-verified.
  [OK] REQ-UPD-8  required: [impl, unit]  stages: -doc +impl +unit -int
       Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3.2 single, or a single stamped for another OS, fail-safe refuses — the guard that alone prevents the v0.3.1 brick where a Linux ELF was applied as `spt.exe`). UX: a friendly post-apply message (`Updated spt-core to vX.Y.Z.` + changelog URL) driven by an additive `product_version` metadata field, with a release-counter fallback when absent.
  [OK] REQ-UPD-9  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       `gh_release` adapter [update] avenue (optional signing): an adapter declares `[update] avenue = "gh_release", repo = "user/repo"` (+ optional `asset`, default `adapter.spt`; + optional Ed25519 `signing_key`); spt-core's ripple compares the repo's LATEST GitHub release version against the installed adapter version and, when newer, auto-updates by fetching the release `.spt` archive (the REQ-INSTALL-9 `--release` fetch primitive) → verifies the `.spt` against `signing_key` if declared, else HTTPS+GitHub first-acquisition trust → re-extracts + re-registers the adapter root. Lets a harness adapter ship updates from its own GitHub releases with NO signing tooling or plugin coupling (removes the perri file_pull/delegated avenue blockers). Acquisition-trust mirrors `--release` + the installer first-fetch; does not alter spt-core self-update (REQ-UPD-1..8).
  [OK] REQ-WHOAMI-1  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       `spt whoami` is a thin ALIAS for `spt endpoint list` (full output: the SELF pin + the subnet roster) — the standalone bare-id command is dropped (the `id=$(spt whoami)` capture was never a real pattern: env vars don't persist between agent tool calls). The one new render: the `endpoint list` SELF pin carries the Self endpoint's authored `endpoint description` (info::read_info(...).resources) when present, inline after the liveness state. whoami stays a top-level hot-path verb (parse unchanged, REQ-MSG-9).


All OK. Verify exit code + the specific REQ row, and rebuild real bin:


traceable-reqs check >/tmp/tr.txt 2>&1; echo "EXIT=$?"; grep -i "RC-INPUT-KEY-ENCODING" /tmp/tr.txt; echo "===BUILD==="; cargo build -p spt --bin spt 2>&1 | tail -3

Traceable exit code + REQ row + real bin rebuild


EXIT=0
  [OK] REQ-HAZARD-RC-INPUT-KEY-ENCODING  required: [impl, unit]  stages: -doc +impl +unit -int
===BUILD===
   Compiling spt v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.96s


All gates green: clippy clean, unit pass, traceable EXIT=0 (`[impl,unit]` OK), real bin rebuilt. Commit W7-swap.


git log -1 --format='%b' | tail -5; echo "---staging---"; git add -A && git status --short

Check trailer format + stage W7 changes


[impl->REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE] [unit->REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE] [int->REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE]

Co-authored by: todlando

---staging---
M  crates/spt/src/rc.rs
M  docs/KNOWN-HAZARDS.md
M  traceable-reqs.toml


git commit -m "$(cat <<'EOF'
fix(rc): W7 — SWAP Windows delete bytes so Ctrl+Backspace = word-delete

One-way 0x08->0x7f map upgraded to a SWAP (0x08<->0x7f). CC reads ^H as
backward-kill-word and DEL as char-delete, so swapping the input bytes
gives Backspace=char-delete AND Ctrl+Backspace=word-delete (native Win11).
Operator rejected losing word-delete on the secondary key. cfg(windows);
0x02 detach still consumed upstream; all other bytes pass through.

KNOWN-HAZARDS 7.13 + REQ note updated (one-way -> swap; [impl,unit] extend).

[impl->REQ-HAZARD-RC-INPUT-KEY-ENCODING] [unit->REQ-HAZARD-RC-INPUT-KEY-ENCODING]

Co-authored by: todlando
EOF
)"

Commit W7-swap


[v0.13.0-delivery-control 95f742f] fix(rc): W7 — SWAP Windows delete bytes so Ctrl+Backspace = word-delete
 3 files changed, 30 insertions(+), 25 deletions(-)


W7-swap committed @95f742f. Next: **F1 test-hang guard**. Look at the int test file to pick the fix (serial_test guard vs module-doc note).


grep -n "#\[test\]\|fn \|serial\|test-threads\|nextest\|^//!" crates/spt-daemon/tests/inject_control_wedge.rs | head -40; echo "---deps---"; grep -n "serial_test\|^\[dev-dependencies\]\|^\[dependencies\]" crates/spt-daemon/Cargo.toml

Inspect int test structure + serial_test availability


1://! v0.13.0 W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) — the CONTROLLER-DELIVERY
2://! non-wedge gate, on a REAL broker hosting a REAL PTY child over the real local
3://! IPC (no mocks of the PTY or the transport).
4://!
5://! ## The bug this gate locks down (park (a), the output-stall / wedge root)
6://!
7://! PRE-FIX (v0.12.x), [`OutputLog::append`] fanned each output chunk to the
8://! CONTROLLER via a SYNCHRONOUS, BLOCKING `write_frame` held INLINE on the
9://! session's single drain thread, while the `Mutex<OutputLog>` was locked.
10://! VIEWERS instead get a dedicated writer thread + a bounded, evicting
11://! `sync_channel` and can never stall the drain. So a slow / non-draining
12://! controller (here a controller connection that stops reading while the child
13://! floods) parked the drain INSIDE `append` STILL HOLDING the log lock — output
14://! stalled and any path needing that log lock (a fresh attach, or the global
15://! `KIND_SESSIONS` enumeration, which reads each log's `delivered_through`)
16://! blocked FOREVER, while the broker process stayed alive. The repro that proved
17://! this (captured 2026-06-19): `APPEND_CTRL_ENTER sid=1 seq=2` with no matching
18://! `APPEND_CTRL_EXIT`, `broker_alive=true` but `sessions_answered=false`.
19://!
20://! POST-FIX (W1): controller delivery moved off the drain thread onto a dedicated
21://! writer thread + bounded channel (the viewer_writer pattern, but authoritative:
22://! the writer advances `delivered_through`). The drain hands each chunk off with a
23://! bounded `send_timeout` OFF the log lock; a fully-stalled controller is detached
24://! after `CONTROLLER_WRITE_DEADLINE`. So even while the controller is fully backed
25://! up, the session does NOT wedge — `KIND_SESSIONS` still answers. THIS test
26://! asserts that fixed behavior.
57:fn unique_name() -> String {
62:fn kill_pid(pid: u32) {
76:fn flood_spawn_req(endpoint: &str) -> SpawnReq {
99:fn send(conn: &mut Stream, kind: &str, payload: serde_json::Value) {
107:fn probe(name: &str, cmd_kind: &'static str, want_kind: &'static str, deadline: Duration) -> bool {
140:#[test]
141:fn a_backed_up_controller_does_not_wedge_the_session() {
219:#[test]
220:fn injecting_a_large_payload_into_a_stdin_ignoring_child() {
373:fn init_wedge_home() {
381:fn hermetic() -> NetConfig {
391:fn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {
402:fn connect_retry(name: &str) -> Brain {
412:fn count(hay: &[u8], needle: &[u8]) -> usize {
---deps---
8:[dependencies]
54:[dev-dependencies]