Edit

**Remaining M12 after W2 gate:** W2.5 (attached-presence+Kick, scheduled) → W4 subnet QR → W5 whoami. Commit per operator. See [[traceable-per-wave-activation]], [[adapter-glue-model-boundary]], [[spt-claude-code-next]] (perri waits on M12 gate).

**W2 GATE: PASS ✓ (doyle 2026-06-14, @6e76f62).** Independently verified single-bringup-path two ways (grep picker/ empty of spawn/launch/net_dial minus cmd_endpoint_run/run_attach; read dispatch mod.rs) + reproduced traceable 200/200 + spot-reads faithful. "Clean wave."

**W2 FOLLOW-ON BATCH (3 operator items, built post-gate, UNCOMMITTED on m12-w1-bringup-rc — pinged doyle to gate; operator calls commit, W2's pre-auth was a one-off):**
1. **bare `spt` → picker** (operator idea): `Cli.cmd` → `Option<Cmd>`; run() None → `bare_invocation` → `decide_bare(stdin_tty, stdout_tty)` — BOTH TTY → picker, else PRINT HELP (mandatory TTY-guard: never launch the TUI in a pipe/CI → would hang scripts). `--help`/`-h`/`help` untouched (clap intercepts). ~60 existing cli tests updated `.cmd`→`.cmd.unwrap()`. Folds into REQ-RUN-PICKER (2 unit tests: bare_spt_parses_to_none, bare_tty_guard).
2. **REQ-MANIFEST-7 — adapter-declared shortcut basename** (operator: basename must be a MANIFEST element, not just code-default — else spt-claude-code couldn't actually emit cc-<id>). Manifest `[adapter] shortcut_basename: Option<String>` (serde-default skip-if-none, N-1-safe); manifest.schema.json re-blessed (`SPT_BLESS=1 cargo test -p spt-runtime`). Picker resolves SELECTED adapter's manifest via `data::shortcut_basename` (resolve_option → field, fallback `spt`); mod.rs drops the hardcoded const at the call site. DOCS (the adapter-author contract perri builds against): docs/MANIFEST.md `[adapter]` field + worked-example row (carries the `[doc->REQ-MANIFEST-7]` tag) AND docs-site/src/harness-contract/integration-checklist.md Group-3 row + claude-code clause (rides REQ-DOCS-2). doc+impl+unit.
3. **`spt spt` easter egg** (operator): hidden `Cmd::Spt` (`#[command(hide=true)]`, verified absent from --help) → prints exactly `Sentience Pocket Transacter\n` exit 0. REQ-LESS by design (no tag/doc — undocumented; traceable only gates declared REQs). CHANGELOG unreleased: ``spt spt` — ???` + a picker line. GOTCHA caught: first insert landed in adjacent DaemonCmd enum (E0599) → moved to real Cmd enum (closes line 213).
EVIDENCE: full workspace suite green (69 ok-lines/0 non-ok), clippy --workspace -D rc=0, traceable 201/201 (REQ-MANIFEST-7 OK), xtask gen+check OK (CLI reference regenerated for bare/--create/picker shape, drift-gate green).

**Remaining M12:** W2.5 (attached-presence+Kick, scheduled — own JIT+design-check+gate AFTER this) → W4 subnet QR → W5 whoami. Commit per operator. See [[traceable-per-wave-activation]], [[adapter-glue-model-boundary]], [[spt-claude-code-next]] (perri waits on M12 gate).


<tool_use_error>File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.</tool_use_error>

Error: File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.


33
34	**W2 GATE PASS (doyle 2026-06-14, independently verified — reproduced traceable 200/0 + `cargo test -p spt --bins` 151/0; verified SINGLE-BRINGUP-PATH two ways: grep picker/ for spawn paths EMPTY + READ dispatch mod.rs:247 = only Run→cmd_endpoint_run/Attach→run_attach, re-entrant opts = reenter_create screen transitions). COMMITTED @6e76f62** (operator pre-authorized history-tidy). Interactive `spt endpoint run` ratatui picker. doyle design ruling = `M12-W2-RULING.md`; plan = `M12-W2-PLAN.md` (crystallized). Operator authorized incremental commits ("commit as needed, history tidy") — committed pre-gate; doyle gates the committed state. In-process `spt/src/picker/` (model/data/view/shortcut/mod), action-enum single source of truth. **SINGLE-BRINGUP-PATH invariant** (doyle's gate condition): every terminal action → Outcome enum → dispatch routes Run→cmd_endpoint_run, Attach→rc::run_attach; ChangeAdapter/Instantiate/Fork = screen re-entry into create-new (still cmd_endpoint_run), NOT a 2nd spawn. Bare `spt endpoint run` (adapter/id → Option) = picker; flagged = untouched non-interactive path; additive `--create`. Deps ratatui 0.29 + nucleo-matcher 0.3. **doyle rulings absorbed:** Q1 attached blue ■ + Kick → **W2.5** (own broker attach-presence+force-detach slice AFTER W2, own JIT+gate; forward-pointer in plan + docs/DEFERRED.md); Q3 Resume-from-history BUILT (sessions::last_k ledger exists M10/REQ-TERM-6, offline+LOCAL); Q4 rev.2 shortcut basename = PARAMETER default `spt`→`spt-<id>` (adapter overrides→cc; spt-core NEVER bakes cc; .cmd not .ps1 PATHEXT; sentinel-guard); Q5 additive --create. REQ-RUN-PICKER + REQ-RUN-SHORTCUT (doc+impl+unit, NO int — Buffer+state-model unit asserts, live key loop = manual-verify per REQ-PAIR-6). EVIDENCE: 26 picker unit tests, full workspace suite green, clippy -D EXIT=0, traceable 200/200 both [OK].
35
36	**bare-`spt`→picker FOLLOW-ON** (operator idea, ruled AFTER @6e76f62 so NOT in it): bare `spt` (no subcmd) = alias for `spt endpoint run` picker, replacing print-help-on-no-args. MANDATORY TTY-guard: interactive→picker, NON-TTY (pipe/CI)→fall back to help NEVER launch TUI (else scripts hang); `spt --help`/`-h`/`help` always help. Folds into REQ-RUN-PICKER + non-TTY unit test. todlando doing as quick W2 follow-on → gate → THEN W4. Detail in M12-W2-RULING.md "Bare spt → picker".
37
38	**Remaining M12:** bare-spt follow-on (gate next) → W4 subnet QR/self-elevation → W5 whoami→alias → W2.5 attached-presence+Kick slice (scheduled, own broker surface). Commit per operator (W2 was pre-authorized). See [[traceable-per-wave-activation]], [[adapter-glue-model-boundary]], [[spt-claude-code-next]] (perri waits on full M12 gate).

**W2 GATE PASS (doyle 2026-06-14, independently verified — reproduced traceable 200/0 + `cargo test -p spt --bins` 151/0; verified SINGLE-BRINGUP-PATH two ways: grep picker/ for spawn paths EMPTY + READ dispatch mod.rs:247 = only Run→cmd_endpoint_run/Attach→run_attach, re-entrant opts = reenter_create screen transitions). COMMITTED @6e76f62** (operator pre-authorized history-tidy). Interactive `spt endpoint run` ratatui picker. doyle design ruling = `M12-W2-RULING.md`; plan = `M12-W2-PLAN.md` (crystallized). Operator authorized incremental commits ("commit as needed, history tidy") — committed pre-gate; doyle gates the committed state. In-process `spt/src/picker/` (model/data/view/shortcut/mod), action-enum single source of truth. **SINGLE-BRINGUP-PATH invariant** (doyle's gate condition): every terminal action → Outcome enum → dispatch routes Run→cmd_endpoint_run, Attach→rc::run_attach; ChangeAdapter/Instantiate/Fork = screen re-entry into create-new (still cmd_endpoint_run), NOT a 2nd spawn. Bare `spt endpoint run` (adapter/id → Option) = picker; flagged = untouched non-interactive path; additive `--create`. Deps ratatui 0.29 + nucleo-matcher 0.3. **doyle rulings absorbed:** Q1 attached blue ■ + Kick → **W2.5** (own broker attach-presence+force-detach slice AFTER W2, own JIT+gate; forward-pointer in plan + docs/DEFERRED.md); Q3 Resume-from-history BUILT (sessions::last_k ledger exists M10/REQ-TERM-6, offline+LOCAL); Q4 rev.2 shortcut basename = PARAMETER default `spt`→`spt-<id>` (adapter overrides→cc; spt-core NEVER bakes cc; .cmd not .ps1 PATHEXT; sentinel-guard); Q5 additive --create. REQ-RUN-PICKER + REQ-RUN-SHORTCUT (doc+impl+unit, NO int — Buffer+state-model unit asserts, live key loop = manual-verify per REQ-PAIR-6). EVIDENCE: 26 picker unit tests, full workspace suite green, clippy -D EXIT=0, traceable 200/200 both [OK].

**bare-`spt`→picker FOLLOW-ON** (operator idea, ruled AFTER @6e76f62 so NOT in it): bare `spt` (no subcmd) = alias for `spt endpoint run` picker, replacing print-help-on-no-args. MANDATORY TTY-guard: interactive→picker, NON-TTY (pipe/CI)→fall back to help NEVER launch TUI (else scripts hang); `spt --help`/`-h`/`help` always help. Folds into REQ-RUN-PICKER + non-TTY unit test. todlando doing as quick W2 follow-on → gate → THEN W4. Detail in M12-W2-RULING.md "Bare spt → picker".

**Remaining M12:** bare-spt follow-on (gate next) → W4 subnet QR/self-elevation → W5 whoami→alias → W2.5 attached-presence+Kick slice (scheduled, own broker surface). Commit per operator (W2 was pre-authorized). See [[traceable-per-wave-activation]], [[adapter-glue-model-boundary]], [[spt-claude-code-next]] (perri waits on full M12 gate).


**bare-`spt`→picker FOLLOW-ON** (operator idea, ruled AFTER @6e76f62 so NOT in it): bare `spt` (no subcmd) = alias for `spt endpoint run` picker, replacing print-help-on-no-args. MANDATORY TTY-guard: interactive→picker, NON-TTY (pipe/CI)→fall back to help NEVER launch TUI (else scripts hang); `spt --help`/`-h`/`help` always help. Folds into REQ-RUN-PICKER + non-TTY unit test. todlando doing as quick W2 follow-on → gate → THEN W4. Detail in M12-W2-RULING.md "Bare spt → picker".

**Remaining M12:** bare-spt follow-on (gate next) → W4 subnet QR/self-elevation → W5 whoami→alias → W2.5 attached-presence+Kick slice (scheduled, own broker surface). Commit per operator (W2 was pre-authorized). See [[traceable-per-wave-activation]], [[adapter-glue-model-boundary]], [[spt-claude-code-next]] (perri waits on full M12 gate).

**W2 FOLLOW-ON BATCH (3 operator items ruled AFTER @6e76f62 → NOT in it; BUILT + swept green, UNCOMMITTED on m12-w1-bringup-rc, pinged doyle to gate; operator calls commit — W2's pre-auth was one-off):**
1. **bare `spt` → picker**: `Cli.cmd`→`Option<Cmd>`; run() None → `bare_invocation` → `decide_bare(stdin_tty,stdout_tty)` BOTH TTY→picker else PRINT HELP (mandatory TTY-guard: never TUI in pipe/CI → would hang scripts). `--help`/`-h`/`help` untouched (clap intercepts). ~60 existing cli tests updated `.cmd`→`.cmd.unwrap()`. Folds REQ-RUN-PICKER (+bare_spt_parses_to_none, bare_tty_guard).
2. **REQ-MANIFEST-7 — adapter-declared shortcut basename** (operator: basename must be a MANIFEST element, not just code-default — else spt-claude-code couldn't actually emit cc-<id>; gap doyle verified). Manifest `[adapter] shortcut_basename: Option<String>` (serde-default skip-if-none, N-1-safe); manifest.schema.json re-blessed (`SPT_BLESS=1 cargo test -p spt-runtime`). Picker resolves SELECTED adapter's manifest via `data::shortcut_basename` (resolve_option→field, fallback `spt`); mod.rs drops the hardcoded const. DOCS (adapter-author contract perri builds against): docs/MANIFEST.md `[adapter]` field + worked-example row (carries `[doc->REQ-MANIFEST-7]`) AND docs-site integration-checklist.md Group-3 row + claude-code clause (rides REQ-DOCS-2). doc+impl+unit.
3. **`spt spt` easter egg** (operator): hidden `Cmd::Spt` (`#[command(hide=true)]`, absent from --help) → prints exactly `Sentience Pocket Transacter\n` exit 0. REQ-LESS by design (no tag/doc; traceable only gates declared REQs). CHANGELOG unreleased: ``spt spt` — ???` + picker line. GOTCHA: first insert landed in adjacent DaemonCmd enum (E0599) → moved to real Cmd enum (closes line 213).
BATCH EVIDENCE: full workspace suite green (69 ok-lines/0 non-ok), clippy --workspace -D rc=0, traceable 201/201 (REQ-MANIFEST-7 OK), xtask gen+check OK (CLI reference regenerated for bare/--create/picker shape, drift-gate green).

**Remaining M12:** [await doyle gate on follow-on batch] → W4 subnet QR/self-elevation → W5 whoami→alias → W2.5 attached-presence+Kick slice (scheduled, own broker surface + own JIT/design-check/gate). Commit per operator. See [[traceable-per-wave-activation]], [[adapter-glue-model-boundary]], [[spt-claude-code-next]] (perri waits on full M12 gate).


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

**W2 FOLLOW-ON BATCH (3 operator items ruled AFTER @6e76f62 → NOT in it; BUILT + swept green, UNCOMMITTED on m12-w1-bringup-rc, pinged doyle to gate; operator calls commit — W2's pre-auth was one-off):**
1. **bare `spt` → picker**: `Cli.cmd`→`Option<Cmd>`; run() None → `bare_invocation` → `decide_bare(stdin_tty,stdout_tty)` BOTH TTY→picker else PRINT HELP (mandatory TTY-guard: never TUI in pipe/CI → would hang scripts). `--help`/`-h`/`help` untouched (clap intercepts). ~60 existing cli tests updated `.cmd`→`.cmd.unwrap()`. Folds REQ-RUN-PICKER (+bare_spt_parses_to_none, bare_tty_guard).
2. **REQ-MANIFEST-7 — adapter-declared shortcut basename** (operator: basename must be a MANIFEST element, not just code-default — else spt-claude-code couldn't actually emit cc-<id>; gap doyle verified). Manifest `[adapter] shortcut_basename: Option<String>` (serde-default skip-if-none, N-1-safe); manifest.schema.json re-blessed (`SPT_BLESS=1 cargo test -p spt-runtime`). Picker resolves SELECTED adapter's manifest via `data::shortcut_basename` (resolve_option→field, fallback `spt`); mod.rs drops the hardcoded const. DOCS (adapter-author contract perri builds against): docs/MANIFEST.md `[adapter]` field + worked-example row (carries `[doc->REQ-MANIFEST-7]`) AND docs-site integration-checklist.md Group-3 row + claude-code clause (rides REQ-DOCS-2). doc+impl+unit.
3. **`spt spt` easter egg** (operator): hidden `Cmd::Spt` (`#[command(hide=true)]`, absent from --help) → prints exactly `Sentience Pocket Transacter\n` exit 0. REQ-LESS by design (no tag/doc; traceable only gates declared REQs). CHANGELOG unreleased: ``spt spt` — ???` + picker line. GOTCHA: first insert landed in adjacent DaemonCmd enum (E0599) → moved to real Cmd enum (closes line 213).
BATCH EVIDENCE: full workspace suite green (69 ok-lines/0 non-ok), clippy --workspace -D rc=0, traceable 201/201 (REQ-MANIFEST-7 OK), xtask gen+check OK (CLI reference regenerated for bare/--create/picker shape, drift-gate green).

**Remaining M12:** [await doyle gate on follow-on batch] → W4 subnet QR/self-elevation → W5 whoami→alias → W2.5 attached-presence+Kick slice (scheduled, own broker surface + own JIT/design-check/gate). Commit per operator. See [[traceable-per-wave-activation]], [[adapter-glue-model-boundary]], [[spt-claude-code-next]] (perri waits on full M12 gate).

---
name: m12-w1-progress
description: "M12 Wave 1 (spt endpoint run + spt rc) build state, decisions, and the self-dial-dead blocker"
metadata:
  node_type: memory
  type: project
  originSessionId: 1ae6a063-4622-4f5b-86b8-b4506d12ccfb
---

M12 W1 (spt-hosted harness bringup + `spt rc` PTY attach) — doyle handed execution to todlando 2026-06-14. Full spec: `M12-PLAN.md` + `M12-W1-PLAN.md` (durable build plan, resumable).

**Built + green:**
- Daemon `spt-daemon/src/harnesshost.rs`: `prepare_harness_spawn` (validate kind=harness + fill `[session.self]` template) + `launch_harness_brokered_in` (spawn into broker PTY via `spawn_session_pid`, mirrors `shellhost::launch_shell_brokered_in`) + `mint_session_id`. 3 unit tests.
- `spt/src/rc.rs`: operator attach pump (single-Brain main thread + stdin reader thread→mpsc, `cold_start_pump` Split carrier for bounded reads, seq-dedup, ctrl-b detach state machine). 3 unit tests.
- cli.rs: `EndpointCmd::Run` + `cmd_endpoint_run`; top-level `Cmd::Rc`; crossterm dep added.
- REQs registered: REQ-HOST-RUN-1 (spawn), REQ-RC-1 (attach), REQ-HOST-RUN-2 (project cwd — doyle-mandated, must land before M12 gate; W1 ships broker-inherited cwd as proof-shortcut only).

**KEY DECISIONS (doyle-validated):** spawn-first (harness self-registers its perch on bind via `api/startup.rs establish_perch`, no daemon pre-register); id rides argv via `{id}` template (NO SpawnReq wire change); one-pump invariant = transport swappable under one `serve_attach` pump.

**BLOCKER RESOLVED (doyle ruling 2026-06-14):** loopback self-dial IMPOSSIBLE (iroh refuses `net_dial(own_addr)`, pinned green test). doyle picked **B1 (broker in-process loopback-conn — local rides byte-identical pump+serve path, covered by cross-node tests; B2's pump-branch rejected as the lesser-tested local path warned against), scoped as a tight W1.5** (NOT in W1). Rationale: cross-node attach + endpoint-run bringup + pump are built+green → gate W1 NOW (bringup verifiable via cross-node attach to the spawned PTY); B1 is a sensitive broker-layer addition (6.7 zone) deserving its own focused task+gate; local lands W1.5 before M12 close. One-pump assertion (both transports → one pump) kept in W1.5.

**REQ-RC-1 split:** cross-node int evidence at W1 gate; local int evidence at W1.5 (per-wave activation).

**W1 GATE: PASS (doyle 2026-06-14).** Independently verified: traceable-reqs check reproduced (196 complete / 0 findings; REQ-HOST-RUN-1 + REQ-RC-1 impl+unit+int, REQ-HOST-RUN-2 []); int test `attach.rs:217` inspected — real `launch_harness_brokered_in` spawns [session.self] into broker-A PTY, B attaches cross-node, HOSTED_OK round-trips, detach→Detached+survives, origin=handshake-identity (KH 7.5); self-dial-refused pin genuine. Trusted reported full-suite (46 bins/0) + clippy -D. **UNCOMMITTED** (awaiting operator).

**W1.5 GATE PASS (doyle 2026-06-14, independently verified — read shared helper + both int tests + reproduced trace).** B1 broker loopback-conn done (design in `M12-W1.5-PLAN.md` "Crystallized design"). Transport seam = `tokio::io::duplex` (ONE full-duplex pair wires a whole operator↔peer stream); only the leaf send/recv become enums (`SendHalf`/`RecvHalf`), so StreamLog/subscribe/send_stream/read-pump are byte-identical across QUIC + loopback. `nethost::dial_loopback` = reused SINGLETON, `remote_id_hex` = own-node id; `open_stream` on a `ConnKind::Loopback` mints the cross-wired PAIR (operator row + peer row); peer row (initiated_locally=false, own origin) → dispatch loop → serve_attach. Explicit `net-dial-loopback` frame (brain.net_dial_loopback + broker.dispatch_net_dial_loopback) — net_dial untouched. access same-node short-circuit (access.rs:89) Allows own-node → NO access change. rc.rs ONE self-dial line swapped → net_dial_loopback. ONE-PUMP PROOF by construction: shared `attach_drive_detach` helper called by BOTH cross-node `remote_attach_drives_a_real_pty_cross_daemon` AND new `local_attach_via_loopback_conn_rides_the_same_pump` — diverge only in conn acquisition + expected_origin. EVIDENCE: attach int 6/6 + new nethost unit `loopback_conn_is_a_reused_singleton_that_cross_wires_a_stream_pair`; full spt+spt-daemon suite **498 passed/0 failed**; clippy -D EXIT=0; traceable EXIT=0 (REQ-RC-1 [OK] impl+unit+int). **UNCOMMITTED** (operator-gated, m12-w1-bringup-rc).

**REQ-HOST-RUN-2 GATE PASS (doyle 2026-06-14) — verified end-to-end.** doyle: "the confirmed-missing gating foundation is DONE + gated; the hard part of M12 is done." CONTINUE-vs-CHECKPOINT (W2-W5) routed to OPERATOR; HOLD building until operator relays. doyle suggested order if continue: **W3 first** (file-backed strings + adapter:profile fallback — most directly unblocks spt-claude-code) → then W2/W4/W5. Additive `SpawnReq.cwd: Option<String>` (serde default+skip_if_none, N-1 safe) → spt-term `PtySession::spawn_program_in(cwd)` (portable-pty CommandBuilder::cwd; `spawn_program` delegates None) → broker `dispatch_spawn` honors `req.cwd` → harnesshost `launch_harness_brokered_in(.., cwd: Option<&str>)` → cli `cmd_endpoint_run` derives PROJECT cwd from `std::env::current_dir()` (None-fallback, never aborts). Shells unchanged (cwd:None, out of scope). 17 SpawnReq literals patched +cwd:None. EVIDENCE: unit `spawn_program_in_lands_the_child_in_the_requested_cwd` (spt-term, unix-gated like capture test); int `broker_spawns_the_pty_child_in_the_requested_cwd` (CROSS-PLATFORM, passes Windows — wire→broker→PTY cwd e2e); full suite **527/0**; clippy EXIT=0; traceable EXIT=0 (REQ-HOST-RUN-2 [OK] impl+unit+int). UNCOMMITTED.

**COMMITTED 0c687fe (2026-06-14): W1.5 + REQ-HOST-RUN-2** on m12-w1-bringup-rc (operator chose commit-then-continue; 24 files, 668+/92-). W1.x bringup+rc foundation COMPLETE + gated (spt-hosted bringup + cross-node + LOCAL rc + project cwd).

**W3 GATE PASS (doyle 2026-06-14, independently verified — reproduced traceable + read every load-bearing test; praised read-time containment re-apply, local-pointer provenance, unchanged schema).** UNCOMMITTED (operator calls commit, same as W1.5+HR2 pattern). doyle design ruling = `M12-W3-RULING.md`; plan = `M12-W3-PLAN.md`. REQ-MANIFEST-5 (T3.1 doc+impl+unit) + REQ-MANIFEST-6 (T3.2 doc+unit, contract-only) registered, traceable EXIT=0 both [OK]. **T3.1** file-backed `[strings]`: `profile::as_file_pointer` = table with EXACTLY one key `file` (else opaque — back-compat); `{file="rel"}` resolves to file contents lazily at get_string; aux dir `adapters/<adapter>/strings/` (Copy-mode copy_dir_all at register, Pointer reads live); HAZARD containment (reject `..`/absolute → register REFUSES loud via `RegistryError::BadStringPointer`, records nothing); read-time skip-diag (eprintln STRINGS_FILE_SKIP → None, never crash/error); local-profile pointers resolve to user-owned profiles dir (update-safe). **T3.2**: resolve_option ALREADY adapter:profile-aware → contract-lock unit + CONTEXT.md:137 reconcile, NO config field added (no consumer; escalation clause untriggered). EVIDENCE: spt-runtime 49/0 (6 new tests); full spt-runtime+spt-daemon+spt+spt-term suite **576/0**; clippy EXIT=0; traceable EXIT=0. manifest.schema.json UNCHANGED (strings opaque Value). UNCOMMITTED on m12-w1-bringup-rc.

**W2 GATE PASS (doyle 2026-06-14, independently verified — reproduced traceable 200/0 + `cargo test -p spt --bins` 151/0; verified SINGLE-BRINGUP-PATH two ways: grep picker/ for spawn paths EMPTY + READ dispatch mod.rs:247 = only Run→cmd_endpoint_run/Attach→run_attach, re-entrant opts = reenter_create screen transitions). COMMITTED @6e76f62** (operator pre-authorized history-tidy). Interactive `spt endpoint run` ratatui picker. doyle design ruling = `M12-W2-RULING.md`; plan = `M12-W2-PLAN.md` (crystallized). Operator authorized incremental commits ("commit as needed, history tidy") — committed pre-gate; doyle gates the committed state. In-process `spt/src/picker/` (model/data/view/shortcut/mod), action-enum single source of truth. **SINGLE-BRINGUP-PATH invariant** (doyle's gate condition): every terminal action → Outcome enum → dispatch routes Run→cmd_endpoint_run, Attach→rc::run_attach; ChangeAdapter/Instantiate/Fork = screen re-entry into create-new (still cmd_endpoint_run), NOT a 2nd spawn. Bare `spt endpoint run` (adapter/id → Option) = picker; flagged = untouched non-interactive path; additive `--create`. Deps ratatui 0.29 + nucleo-matcher 0.3. **doyle rulings absorbed:** Q1 attached blue ■ + Kick → **W2.5** (own broker attach-presence+force-detach slice AFTER W2, own JIT+gate; forward-pointer in plan + docs/DEFERRED.md); Q3 Resume-from-history BUILT (sessions::last_k ledger exists M10/REQ-TERM-6, offline+LOCAL); Q4 rev.2 shortcut basename = PARAMETER default `spt`→`spt-<id>` (adapter overrides→cc; spt-core NEVER bakes cc; .cmd not .ps1 PATHEXT; sentinel-guard); Q5 additive --create. REQ-RUN-PICKER + REQ-RUN-SHORTCUT (doc+impl+unit, NO int — Buffer+state-model unit asserts, live key loop = manual-verify per REQ-PAIR-6). EVIDENCE: 26 picker unit tests, full workspace suite green, clippy -D EXIT=0, traceable 200/200 both [OK].

**bare-`spt`→picker FOLLOW-ON** (operator idea, ruled AFTER @6e76f62 so NOT in it): bare `spt` (no subcmd) = alias for `spt endpoint run` picker, replacing print-help-on-no-args. MANDATORY TTY-guard: interactive→picker, NON-TTY (pipe/CI)→fall back to help NEVER launch TUI (else scripts hang); `spt --help`/`-h`/`help` always help. Folds into REQ-RUN-PICKER + non-TTY unit test. todlando doing as quick W2 follow-on → gate → THEN W4. Detail in M12-W2-RULING.md "Bare spt → picker".

**Remaining M12:** bare-spt follow-on (gate next) → W4 subnet QR/self-elevation → W5 whoami→alias → W2.5 attached-presence+Kick slice (scheduled, own broker surface). Commit per operator (W2 was pre-authorized). See [[traceable-per-wave-activation]], [[adapter-glue-model-boundary]], [[spt-claude-code-next]] (perri waits on full M12 gate).

-**bare-`spt`→picker FOLLOW-ON** (operator idea, ruled AFTER @6e76f62 so NOT in it): bare `spt` (no subcmd) = alias for `spt endpoint run` picker, replacing print-help-on-no-args. MANDATORY TTY-guard: interactive→picker, NON-TTY (pipe/CI)→fall back to help NEVER launch TUI (else scripts hang); `spt --help`/`-h`/`help` always help. Folds into REQ-RUN-PICKER + non-TTY unit test. todlando doing as quick W2 follow-on → gate → THEN W4. Detail in M12-W2-RULING.md "Bare spt → picker".

+**W2 FOLLOW-ON BATCH (3 operator items ruled AFTER @6e76f62 → NOT in it; BUILT + swept green, UNCOMMITTED on m12-w1-bringup-rc, pinged doyle to gate; operator calls commit — W2's pre-auth was one-off):**

+1. **bare `spt` → picker**: `Cli.cmd`→`Option<Cmd>`; run() None → `bare_invocation` → `decide_bare(stdin_tty,stdout_tty)` BOTH TTY→picker else PRINT HELP (mandatory TTY-guard: never TUI in pipe/CI → would hang scripts). `--help`/`-h`/`help` untouched (clap intercepts). ~60 existing cli tests updated `.cmd`→`.cmd.unwrap()`. Folds REQ-RUN-PICKER (+bare_spt_parses_to_none, bare_tty_guard).

+2. **REQ-MANIFEST-7 — adapter-declared shortcut basename** (operator: basename must be a MANIFEST element, not just code-default — else spt-claude-code couldn't actually emit cc-<id>; gap doyle verified). Manifest `[adapter] shortcut_basename: Option<String>` (serde-default skip-if-none, N-1-safe); manifest.schema.json re-blessed (`SPT_BLESS=1 cargo test -p spt-runtime`). Picker resolves SELECTED adapter's manifest via `data::shortcut_basename` (resolve_option→field, fallback `spt`); mod.rs drops the hardcoded const. DOCS (adapter-author contract perri builds against): docs/MANIFEST.md `[adapter]` field + worked-example row (carries `[doc->REQ-MANIFEST-7]`) AND docs-site integration-checklist.md Group-3 row + claude-code clause (rides REQ-DOCS-2). doc+impl+unit.

+3. **`spt spt` easter egg** (operator): hidden `Cmd::Spt` (`#[command(hide=true)]`, absent from --help) → prints exactly `Sentience Pocket Transacter\n` exit 0. REQ-LESS by design (no tag/doc; traceable only gates declared REQs). CHANGELOG unreleased: ``spt spt` — ???` + picker line. GOTCHA: first insert landed in adjacent DaemonCmd enum (E0599) → moved to real Cmd enum (closes line 213).

+BATCH EVIDENCE: full workspace suite green (69 ok-lines/0 non-ok), clippy --workspace -D rc=0, traceable 201/201 (REQ-MANIFEST-7 OK), xtask gen+check OK (CLI reference regenerated for bare/--create/picker shape, drift-gate green).

-**Remaining M12:** bare-spt follow-on (gate next) → W4 subnet QR/self-elevation → W5 whoami→alias → W2.5 attached-presence+Kick slice (scheduled, own broker surface). Commit per operator (W2 was pre-authorized). See [[traceable-per-wave-activation]], [[adapter-glue-model-boundary]], [[spt-claude-code-next]] (perri waits on full M12 gate).

+**Remaining M12:** [await doyle gate on follow-on batch] → W4 subnet QR/self-elevation → W5 whoami→alias → W2.5 attached-presence+Kick slice (scheduled, own broker surface + own JIT/design-check/gate). Commit per operator. See [[traceable-per-wave-activation]], [[adapter-glue-model-boundary]], [[spt-claude-code-next]] (perri waits on full M12 gate).


write the JIT plan for W2, verify it with doyle, execute on their greenlight


Done. W2 shipped + gated; follow-on batch built and pinged for gate.

**W2 GATE: PASS ✓** (doyle, @6e76f62) — interactive `spt endpoint run` picker. Verified single-bringup-path two ways. "Clean wave."

**Follow-on batch** (3 operator items, built + swept green, uncommitted, pinged doyle to gate):
1. **bare `spt` → picker** — `Cli.cmd`→Option; TTY-guard `decide_bare` (both TTY→picker, pipe/CI→help, never hang scripts); `--help`/`-h`/`help` untouched.
2. **REQ-MANIFEST-7** — `[adapter] shortcut_basename` manifest field (closes the real gap: spt-claude-code can now actually brand `cc-<id>`). Schema re-blessed; picker resolves from selected adapter's manifest; docs/MANIFEST.md + integration-checklist.md.
3. **`spt spt`** egg — hidden, prints `Sentience Pocket Transacter`, REQ-less.

**Sweep:** full workspace suite green (69 ok / 0 non-ok), clippy `-D` rc=0, traceable **201/201**, xtask docs drift-gate OK.

Caught mid-build: first egg insert landed in adjacent `DaemonCmd` (E0599) → moved to real `Cmd`.

Next: doyle gates the follow-on → operator commits → **W4** subnet QR/self-elevation → W5 whoami → W2.5 (attached-presence + Kick, scheduled) → full M12 gate (unblocks perri). Memory updated.


<task-notification>
<task-id>bmby1p3hm</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/5" id="e8b203bc" type="msg" from="doyle">BATCH GATE: PASS ✓ (doyle, independently verified — bare-spt + REQ-MANIFEST-7 + spt spt egg)

Reproduced:
- traceable 201/201, 0 findings; REQ-MANIFEST-7 [doc+impl+unit] OK.
- cargo test -p spt --bins: 153/0 incl bare_spt_parses_to_none + bare_tty_guard.

Read + verified each:
1. bare-spt TTY-gu</EVENT-PART>
<EVENT-PART seq="2/5" id="e8b203bc">ard: decide_bare(stdin_tty, stdout_tty) = both TTY→Picker else Help (cli.rs:858). Non-TTY NEVER launches the TUI — the load-bearing safety rule holds. --help/-h/help clap-intercepted, untouched. ✓
2. REQ-MANIFEST-7: data::shortcut_basename (data.rs:55) = resolve_option → m.adapter.shortcut_basename, filter-nonempty, fallback DEFAULT</EVENT-PART>
<EVENT-PART seq="3/5" id="e8b203bc">_BASENAME &quot;spt&quot;; picker reads SELECTED adapter's resolved manifest, const no longer hardcoded at call site. Option&lt;String&gt; serde-default+skip_if_none = N-1-safe; schema re-blessed (additive). Docs both surfaces (MANIFEST.md field+example + integration-checklist.md Group-3 row+claude-code clause). ✓
3. `spt spt` egg: DIRECT</EVENT-PART>
<EVENT-PART seq="4/5" id="e8b203bc">-RUN verified — ./target/debug/spt.exe spt → &quot;Sentience Pocket Transacter\n&quot; exact (cat -A: ...Transacter$), exit 0. Confirmed ABSENT from --help (grep empty). REQ-less by design. ✓ (note: cargo-run-via-pipe gave a transient empty first try — shared-tree rebuild race; direct binary is authoritative + correct.)

Nice cat</EVENT-PART>
<EVENT-PART seq="5/5" id="e8b203bc">ch on the DaemonCmd misplacement (E0599) → moved to real Cmd enum.

BATCH CLEARED. Uncommitted — operator calls commit. Proceed to W4 subnet QR + self-elevating window (ping a design-check JIT like the others). W2.5 attach/kick still queued after W4-W5 per operator schedule.</EVENT-PART></event>
</task-notification>


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


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


BATCH GATE: **PASS ✓** (doyle verified all 3, direct-ran the egg). Operator's standing "commit as needed to keep history tidy" → committing the gated batch as its own clean commit before W4 work begins.


Bash

cd "C:\Users\decid\Documents\projects\spt-core" && git add -A && git commit -q -F - <<'EOF'
feat(picker,manifest): bare `spt` → picker (TTY-guarded), adapter shortcut_basename, `spt spt`

W2 follow-on batch (doyle-gated PASS). Three operator items:

1. Bare `spt` (no subcommand) opens the endpoint-run picker on an interactive
   terminal. Cli.cmd → Option<Cmd>; run() routes None → bare_invocation →
   decide_bare(stdin_tty, stdout_tty): both TTY → picker, else print help.
   MANDATORY TTY-guard — a non-TTY (pipe/redirect/CI) NEVER launches the TUI
   (would hang a script). --help/-h/help are clap-intercepted, untouched.
   (~60 existing cli tests updated .cmd → .cmd.unwrap() for the Option.)

2. REQ-MANIFEST-7: adapter-declared shortcut basename. Optional manifest
   `[adapter] shortcut_basename` (Option<String>, serde-default + skip_if_none →
   additive, N-1-safe; manifest.schema.json re-blessed). The picker resolves the
   SELECTED adapter's manifest (data::shortcut_basename → resolve_option →
   field, fallback "spt") instead of hardcoding the const — so spt-claude-code
   can brand its shortcuts cc-<id> from the PUBLISHED manifest, not spt-core
   code. Documented in docs/MANIFEST.md ([adapter] field + worked example) and
   docs-site integration-checklist.md (Group-3 row + claude-code clause).

3. `spt spt` — hidden Cmd::Spt (#[command(hide=true)], absent from help) prints
   "Sentience Pocket Transacter". Undocumented by design (REQ-less).

CLI reference regenerated (xtask gen); docs drift-gate green. Full workspace
suite green; clippy -D clean; traceable 201/201.

Co-authored by: todlando
EOF
echo "RC=$?" && git log --oneline -1


warning: in the working copy of 'M12-W2-RULING.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-runtime/manifest.schema.json', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt/src/picker/data.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt/src/picker/mod.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'docs-site/src/cli/reference.md', LF will be replaced by CRLF the next time Git touches it
RC=0
eb74b18 feat(picker,manifest): bare `spt` → picker (TTY-guarded), adapter shortcut_basename, `spt spt`


1	---
2	name: m12-w1-progress
3	description: "M12 Wave 1 (spt endpoint run + spt rc) build state, decisions, and the self-dial-dead blocker"
4	metadata:
5	  node_type: memory
6	  type: project
7	  originSessionId: 1ae6a063-4622-4f5b-86b8-b4506d12ccfb
8	---
9
10	M12 W1 (spt-hosted harness bringup + `spt rc` PTY attach) — doyle handed execution to todlando 2026-06-14. Full spec: `M12-PLAN.md` + `M12-W1-PLAN.md` (durable build plan, resumable).
11
12	**Built + green:**
13	- Daemon `spt-daemon/src/harnesshost.rs`: `prepare_harness_spawn` (validate kind=harness + fill `[session.self]` template) + `launch_harness_brokered_in` (spawn into broker PTY via `spawn_session_pid`, mirrors `shellhost::launch_shell_brokered_in`) + `mint_session_id`. 3 unit tests.
14	- `spt/src/rc.rs`: operator attach pump (single-Brain main thread + stdin reader thread→mpsc, `cold_start_pump` Split carrier for bounded reads, seq-dedup, ctrl-b detach state machine). 3 unit tests.
15	- cli.rs: `EndpointCmd::Run` + `cmd_endpoint_run`; top-level `Cmd::Rc`; crossterm dep added.
16	- REQs registered: REQ-HOST-RUN-1 (spawn), REQ-RC-1 (attach), REQ-HOST-RUN-2 (project cwd — doyle-mandated, must land before M12 gate; W1 ships broker-inherited cwd as proof-shortcut only).
17
18	**KEY DECISIONS (doyle-validated):** spawn-first (harness self-registers its perch on bind via `api/startup.rs establish_perch`, no daemon pre-register); id rides argv via `{id}` template (NO SpawnReq wire change); one-pump invariant = transport swappable under one `serve_attach` pump.
19
20	**BLOCKER RESOLVED (doyle ruling 2026-06-14):** loopback self-dial IMPOSSIBLE (iroh refuses `net_dial(own_addr)`, pinned green test). doyle picked **B1 (broker in-process loopback-conn — local rides byte-identical pump+serve path, covered by cross-node tests; B2's pump-branch rejected as the lesser-tested local path warned against), scoped as a tight W1.5** (NOT in W1). Rationale: cross-node attach + endpoint-run bringup + pump are built+green → gate W1 NOW (bringup verifiable via cross-node attach to the spawned PTY); B1 is a sensitive broker-layer addition (6.7 zone) deserving its own focused task+gate; local lands W1.5 before M12 close. One-pump assertion (both transports → one pump) kept in W1.5.
21
22	**REQ-RC-1 split:** cross-node int evidence at W1 gate; local int evidence at W1.5 (per-wave activation).
23
24	**W1 GATE: PASS (doyle 2026-06-14).** Independently verified: traceable-reqs check reproduced (196 complete / 0 findings; REQ-HOST-RUN-1 + REQ-RC-1 impl+unit+int, REQ-HOST-RUN-2 []); int test `attach.rs:217` inspected — real `launch_harness_brokered_in` spawns [session.self] into broker-A PTY, B attaches cross-node, HOSTED_OK round-trips, detach→Detached+survives, origin=handshake-identity (KH 7.5); self-dial-refused pin genuine. Trusted reported full-suite (46 bins/0) + clippy -D. **UNCOMMITTED** (awaiting operator).
25
26	**W1.5 GATE PASS (doyle 2026-06-14, independently verified — read shared helper + both int tests + reproduced trace).** B1 broker loopback-conn done (design in `M12-W1.5-PLAN.md` "Crystallized design"). Transport seam = `tokio::io::duplex` (ONE full-duplex pair wires a whole operator↔peer stream); only the leaf send/recv become enums (`SendHalf`/`RecvHalf`), so StreamLog/subscribe/send_stream/read-pump are byte-identical across QUIC + loopback. `nethost::dial_loopback` = reused SINGLETON, `remote_id_hex` = own-node id; `open_stream` on a `ConnKind::Loopback` mints the cross-wired PAIR (operator row + peer row); peer row (initiated_locally=false, own origin) → dispatch loop → serve_attach. Explicit `net-dial-loopback` frame (brain.net_dial_loopback + broker.dispatch_net_dial_loopback) — net_dial untouched. access same-node short-circuit (access.rs:89) Allows own-node → NO access change. rc.rs ONE self-dial line swapped → net_dial_loopback. ONE-PUMP PROOF by construction: shared `attach_drive_detach` helper called by BOTH cross-node `remote_attach_drives_a_real_pty_cross_daemon` AND new `local_attach_via_loopback_conn_rides_the_same_pump` — diverge only in conn acquisition + expected_origin. EVIDENCE: attach int 6/6 + new nethost unit `loopback_conn_is_a_reused_singleton_that_cross_wires_a_stream_pair`; full spt+spt-daemon suite **498 passed/0 failed**; clippy -D EXIT=0; traceable EXIT=0 (REQ-RC-1 [OK] impl+unit+int). **UNCOMMITTED** (operator-gated, m12-w1-bringup-rc).
27
28	**REQ-HOST-RUN-2 GATE PASS (doyle 2026-06-14) — verified end-to-end.** doyle: "the confirmed-missing gating foundation is DONE + gated; the hard part of M12 is done." CONTINUE-vs-CHECKPOINT (W2-W5) routed to OPERATOR; HOLD building until operator relays. doyle suggested order if continue: **W3 first** (file-backed strings + adapter:profile fallback — most directly unblocks spt-claude-code) → then W2/W4/W5. Additive `SpawnReq.cwd: Option<String>` (serde default+skip_if_none, N-1 safe) → spt-term `PtySession::spawn_program_in(cwd)` (portable-pty CommandBuilder::cwd; `spawn_program` delegates None) → broker `dispatch_spawn` honors `req.cwd` → harnesshost `launch_harness_brokered_in(.., cwd: Option<&str>)` → cli `cmd_endpoint_run` derives PROJECT cwd from `std::env::current_dir()` (None-fallback, never aborts). Shells unchanged (cwd:None, out of scope). 17 SpawnReq literals patched +cwd:None. EVIDENCE: unit `spawn_program_in_lands_the_child_in_the_requested_cwd` (spt-term, unix-gated like capture test); int `broker_spawns_the_pty_child_in_the_requested_cwd` (CROSS-PLATFORM, passes Windows — wire→broker→PTY cwd e2e); full suite **527/0**; clippy EXIT=0; traceable EXIT=0 (REQ-HOST-RUN-2 [OK] impl+unit+int). UNCOMMITTED.
29
30	**COMMITTED 0c687fe (2026-06-14): W1.5 + REQ-HOST-RUN-2** on m12-w1-bringup-rc (operator chose commit-then-continue; 24 files, 668+/92-). W1.x bringup+rc foundation COMPLETE + gated (spt-hosted bringup + cross-node + LOCAL rc + project cwd).
31
32	**W3 GATE PASS (doyle 2026-06-14, independently verified — reproduced traceable + read every load-bearing test; praised read-time containment re-apply, local-pointer provenance, unchanged schema).** UNCOMMITTED (operator calls commit, same as W1.5+HR2 pattern). doyle design ruling = `M12-W3-RULING.md`; plan = `M12-W3-PLAN.md`. REQ-MANIFEST-5 (T3.1 doc+impl+unit) + REQ-MANIFEST-6 (T3.2 doc+unit, contract-only) registered, traceable EXIT=0 both [OK]. **T3.1** file-backed `[strings]`: `profile::as_file_pointer` = table with EXACTLY one key `file` (else opaque — back-compat); `{file="rel"}` resolves to file contents lazily at get_string; aux dir `adapters/<adapter>/strings/` (Copy-mode copy_dir_all at register, Pointer reads live); HAZARD containment (reject `..`/absolute → register REFUSES loud via `RegistryError::BadStringPointer`, records nothing); read-time skip-diag (eprintln STRINGS_FILE_SKIP → None, never crash/error); local-profile pointers resolve to user-owned profiles dir (update-safe). **T3.2**: resolve_option ALREADY adapter:profile-aware → contract-lock unit + CONTEXT.md:137 reconcile, NO config field added (no consumer; escalation clause untriggered). EVIDENCE: spt-runtime 49/0 (6 new tests); full spt-runtime+spt-daemon+spt+spt-term suite **576/0**; clippy EXIT=0; traceable EXIT=0. manifest.schema.json UNCHANGED (strings opaque Value). UNCOMMITTED on m12-w1-bringup-rc.
33

... [9 lines truncated] ...


Committed **@eb74b18**. doyle: proceed to **W4** (subnet QR + self-elevating window), design-check JIT first like the others. Investigating W4 surfaces.


31:/// Product tag embedded in the `otpauth://` provisioning URI (the authenticator-app
718:    /// code, the `otpauth://` provisioning URI, and a terminal QR of it.
724:    /// Show a subnet's current 6-digit pairing code (+ URI and QR).
727:    /// `create` — current code, `otpauth://` URI, terminal QR, expiry.
2848:/// "re-provision the QR from any still-trusted node" is actually fulfilled): code,
2849:/// `otpauth://` URI, terminal QR, expiry.
2856:                let uri = totp.otpauth_uri(ISSUER, &name);
2864:                     next one, or scan the QR into your auth app.\n",
2918:/// decision 11): code, `otpauth://` URI, terminal QR, and the join hint. The gate is
2927:                let uri = totp.otpauth_uri(ISSUER, &name);
2929:                // Terminal QR of the provisioning URI — scan it into an
2937:                     Code expires at {}. To pair again, scan the QR with your auth app \
2964:/// Render the provisioning URI as a terminal QR (unicode half-blocks). `None` when
2965:/// encoding fails (URI too long for the QR alphabet) — the URI text above it is the
2968:    let code = qrcode::QrCode::new(uri.as_bytes()).ok()?;
2970:        code.render::<qrcode::render::unicode::Dense1x2>()
3106:fn cmd_subnet_create(name: String) -> i32 {
4260:fn cmd_subnet_join(name: Option<String>, code: Option<String>) -> i32 {
8169:    // code, otpauth URI, terminal QR, join hint, expiry (local-time, floored
8189:        assert!(out.contains("otpauth://"), "provisioning URI present");

[Showing results with pagination = limit: 20]


1	//! OS privilege-elevation gate for the node-bound pairing-code fetch (REQ-PAIR-6).
2	//!
3	//! Surfacing a subnet's live TOTP code off a trusted node is the *privileged* fetch
4	//! path: it hands out subnet-joining authority, so the process must be elevated
5	//! (Windows UAC / Linux root-or-equivalent). When it is not, we refuse the node-bound
6	//! fetch and point the user at their authenticator app — where the same seed was
7	//! provisioned at pairing (the `otpauth://` URI), giving the code without privilege.
8	//!
9	//! ## Structure (a thin testable seam over an environment-dependent OS call)
10	//!
11	//! [`current`] is the only environment-dependent part: a `cfg`-split OS query that
12	//! cannot run hermetically in CI (the test process's real euid/token is whatever the
13	//! runner happens to be). So the *decision* — does this elevation level show the code
14	//! or fall back? — is factored into [`gate_for`], a pure function unit-tested both
15	//! ways. An unknown/unsupported platform maps to [`Elevation::Unknown`], which
16	//! [`gate_for`] treats as NOT elevated: **fail safe** — never leak the code when we
17	//! cannot positively confirm privilege.
18	// [impl->REQ-PAIR-6]
19
20	/// The process's OS privilege level, as far as we can positively determine it.
21	#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22	pub enum Elevation {
23	    /// Confirmed elevated: Windows token is elevated, or Unix euid == 0.
24	    Elevated,
25	    /// Confirmed NOT elevated.
26	    NotElevated,
27	    /// Could not determine (unsupported platform, or the OS query failed). Treated as
28	    /// NOT elevated by [`gate_for`] — we never assume privilege we cannot confirm.
29	    ///
30	    /// Constructed only on Windows (an `OpenProcessToken`/`GetTokenInformation`
31	    /// failure) and on unsupported platforms; on Unix `geteuid` cannot fail, so the
32	    /// variant is unreachable there. `allow(dead_code)` keeps the fail-safe arm in the
33	    /// cross-platform API without a per-target warning.
34	    #[allow(dead_code)]
35	    Unknown,
36	}
37
38	/// Whether the gated fetch may surface the code, or must fall back to the app.
39	#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40	pub enum Gate {
41	    /// Elevated — render the code + `otpauth://` URI.
42	    Show,
43	    /// Not elevated (or undeterminable) — refuse, point at the authenticator app.
44	    Fallback,
45	}
46
47	/// The pure gate decision (the testable seam). Only a *positively confirmed* elevation
48	/// shows the code; `NotElevated` and `Unknown` both fall back (fail safe).
49	// [impl->REQ-PAIR-6]
50	pub fn gate_for(elevation: Elevation) -> Gate {
51	    match elevation {
52	        Elevation::Elevated => Gate::Show,
53	        Elevation::NotElevated | Elevation::Unknown => Gate::Fallback,
54	    }
55	}
56
57	/// The fallback guidance shown when the fetch is refused. Names the authenticator app
58	/// explicitly (the seed was provisioned there at pairing via the `otpauth://` URI), so
59	/// the user has an unprivileged path to the same code.
60	pub fn fallback_message(subnet: &str) -> String {
61	    format!(
62	        "ELEVATION_REQUIRED: showing the code for subnet '{subnet}' off this node needs an \
63	         elevated process (run as administrator / root). Without elevation, read the current \
64	         code from your authenticator app instead — the seed was provisioned there at pairing."
65	    )
66	}
67
68	/// How this OS expects a process to acquire privilege — drives both the
69	/// actionable guidance text and whether auto-elevation is even possible.
70	#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71	pub enum ElevateStyle {
72	    /// Unix: `sudo <program> <args>`. Never constructed on Windows builds
73	    /// (`current_style` yields `AdminTerminal` there) — `allow(dead_code)`
74	    /// keeps the cross-platform variant without a per-target warning; the
75	    /// unit tests construct it on every platform.
76	    #[cfg_attr(windows, allow(dead_code))]
77	    Sudo,
78	    /// Windows: a UAC terminal launch, not a command prefix. Symmetrically
79	    /// never constructed on non-Windows builds.
80	    #[cfg_attr(not(windows), allow(dead_code))]
81	    AdminTerminal,
82	}
83
84	/// The privilege-acquisition style for the current OS.
85	#[cfg(windows)]
86	pub fn current_style() -> ElevateStyle {
87	    ElevateStyle::AdminTerminal
88	}
89	#[cfg(not(windows))]
90	pub fn current_style() -> ElevateStyle {
91	    ElevateStyle::Sudo
92	}
93
94	/// The exact, copy-pasteable command that re-runs THIS invocation elevated.
95	///
96	/// `exe` MUST be the binary's ABSOLUTE path (callers pass `current_exe()`):
97	/// a user-local install (`~/.cargo/bin`, `~/.local/bin`) is NOT on sudo's
98	/// `secure_path`, so the bare-name `sudo spt …` a user naturally types fails
99	/// with `sudo: spt: command not found` (KNOWN-HAZARDS 5.10). An absolute path
100	/// is executed directly — `secure_path` only governs bare-name PATH lookup —
101	/// so `sudo /abs/spt …` always resolves. Returns `None` on Windows, where
102	/// elevation is a UAC terminal launch with no command-prefix equivalent.
103	/// [impl->REQ-HAZARD-SUDO-SECURE-PATH]
104	pub fn rerun_command(style: ElevateStyle, exe: &str, args: &[String]) -> Option<String> {
105	    match style {
106	        ElevateStyle::Sudo => {
107	            let mut parts = vec!["sudo".to_string(), shell_quote(exe)];
108	            parts.extend(args.iter().map(|a| shell_quote(a)));
109	            Some(parts.join(" "))
110	        }
111	        ElevateStyle::AdminTerminal => None,
112	    }
113	}
114
115	/// POSIX shell-quote a single argument so the re-run command survives
116	/// copy-paste with spaces or shell-meaningful characters (subnet names can
117	/// contain spaces). Bare when it only holds known-safe characters; otherwise
118	/// single-quoted with embedded quotes escaped the `'\''` way.
119	fn shell_quote(s: &str) -> String {
120	    if !s.is_empty()
121	        && s.chars()
122	            .all(|c| c.is_ascii_alphanumeric() || "-_./:@=+,".contains(c))
123	    {
124	        return s.to_string();
125	    }
126	    format!("'{}'", s.replace('\'', r"'\''"))
127	}
128
129	/// Whether to ATTEMPT automatic privilege escalation (re-exec under sudo)
130	/// rather than just printing the refusal. Pure for testing; the actual exec is
131	/// impure (it needs a real `sudo` + TTY). Auto-elevate only when: this is a
132	/// Unix host (`is_unix` — sudo is the mechanism), the session is interactive
133	/// (`interactive` — sudo must be able to prompt for a password on the TTY),
134	/// and elevation is positively NOT held. `Unknown`/`Elevated` never auto-elevate.
135	/// [impl->REQ-HAZARD-SUDO-SECURE-PATH]
136	pub fn should_auto_elevate(elevation: Elevation, interactive: bool, is_unix: bool) -> bool {
137	    is_unix && interactive && matches!(elevation, Elevation::NotElevated)
138	}
139
140	/// Set when the process **started** elevated and deliberately de-elevated
141	/// itself at entry (the sudo-invoker drop, KNOWN-HAZARDS 5.7): the user
142	/// proved privilege by sudo'ing; dropping to their own identity for state
143	/// safety must not also revoke the gate they paid for. Process-local by
144	/// design — never an env var, which any unprivileged caller could forge.
145	// [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
146	static ELEVATION_PROVEN: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
147
148	/// Record that elevation was positively confirmed before a deliberate
149	/// in-process de-elevation (called ONLY from the main-entry sudo drop).
150	/// The drop is unix-only (Windows respawns instead of dropping), so this
151	/// is uncalled on Windows builds — `allow(dead_code)` keeps the
152	/// cross-platform API without a per-target warning (the `Unknown` posture).
153	#[cfg_attr(windows, allow(dead_code))]
154	pub fn mark_proven() {
155	    ELEVATION_PROVEN.store(true, std::sync::atomic::Ordering::Relaxed);
156	}
157
158	/// Query the OS for this process's elevation level. `cfg`-split; an unsupported
159	/// platform or a failed OS call returns [`Elevation::Unknown`] (→ fail safe).
160	/// A process that proved elevation at entry and then dropped (the 5.7 sudo
161	/// de-elevation) still reads `Elevated` — the proof, not the current euid,
162	/// is what the gates authorize on.
163	// [impl->REQ-PAIR-6]
164	pub fn current() -> Elevation {
165	    if ELEVATION_PROVEN.load(std::sync::atomic::Ordering::Relaxed) {
166	        return Elevation::Elevated;
167	    }
168	    detect()
169	}
170
171	#[cfg(unix)]
172	fn detect() -> Elevation {
173	    // Root (euid 0) is the Unix "elevated" equivalent. `geteuid` cannot fail.
174	    if unsafe { libc::geteuid() } == 0 {
175	        Elevation::Elevated
176	    } else {
177	        Elevation::NotElevated
178	    }
179	}
180
181	#[cfg(windows)]
182	fn detect() -> Elevation {
183	    use core::ffi::c_void;
184	    use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
185	    use windows_sys::Win32::Security::{
186	        GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY,
187	    };
188	    use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
189
190	    // Open the current process token, query TokenElevation. Any failure → Unknown
191	    // (fail safe). GetCurrentProcess returns a pseudo-handle we must NOT close; only
192	    // the opened token handle is released.
193	    unsafe {
194	        let mut token: HANDLE = std::ptr::null_mut();
195	        if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) == 0 {
196	            return Elevation::Unknown;
197	        }
198	        let mut info = TOKEN_ELEVATION { TokenIsElevated: 0 };
199	        let mut ret_len: u32 = 0;
200	        let ok = GetTokenInformation(
201	            token,
202	            TokenElevation,
203	            &mut info as *mut _ as *mut c_void,
204	            std::mem::size_of::<TOKEN_ELEVATION>() as u32,
205	            &mut ret_len,
206	        );
207	        CloseHandle(token);
208	        if ok == 0 {
209	            Elevation::Unknown
210	        } else if info.TokenIsElevated != 0 {
211	            Elevation::Elevated
212	        } else {
213	            Elevation::NotElevated
214	        }
215	    }
216	}
217
218	#[cfg(not(any(unix, windows)))]
219	fn detect() -> Elevation {
220	    // Unsupported platform: we cannot confirm privilege, so refuse to assume it.
221	    Elevation::Unknown
222	}
223
224	#[cfg(test)]
225	mod tests {
226	    use super::*;
227
228	    // [unit->REQ-PAIR-6] only confirmed elevation shows the code; NotElevated AND the
229	    // undeterminable Unknown both fall back (fail safe — no code without proof).
230	    #[test]
231	    fn gate_shows_only_when_confirmed_elevated() {
232	        assert_eq!(gate_for(Elevation::Elevated), Gate::Show);
233	        assert_eq!(gate_for(Elevation::NotElevated), Gate::Fallback);
234	        assert_eq!(gate_for(Elevation::Unknown), Gate::Fallback);
235	    }
236
237	    // [unit->REQ-PAIR-6] the fallback names the authenticator app (the unprivileged
238	    // path to the same code) and the subnet it concerns.
239	    #[test]
240	    fn fallback_message_names_authenticator_app_and_subnet() {
241	        let msg = fallback_message("home fleet");
242	        assert!(msg.contains("authenticator app"));
243	        assert!(msg.contains("home fleet"));
244	    }
245
246	    // The live OS probe is environment-dependent (the test runner's own privilege), so
247	    // we only assert it returns a determinate value here on the supported platforms —
248	    // the show/fallback *decision* is covered hermetically via `gate_for` above. Real
249	    // elevated-vs-not end-to-end verification is manual (todlando / D9).
250	    #[test]
251	    fn current_returns_a_determinate_level_on_supported_platforms() {
252	        let e = current();
253	        if cfg!(any(unix, windows)) {
254	            assert!(matches!(e, Elevation::Elevated | Elevation::NotElevated));
255	        }
256	    }
257
258	    // [unit->REQ-HAZARD-SUDO-SECURE-PATH] the sudo re-run command MUST carry the
259	    // binary's absolute path: a bare `sudo spt …` dies "command not found" when
260	    // spt is a user-local install (~/.local/bin), which is not on sudo's
261	    // secure_path. An absolute path bypasses that lookup. This is the fix for
262	    // the Linux subnet-create dead-end.
263	    fn args(v: &[&str]) -> Vec<String> {
264	        v.iter().map(|s| s.to_string()).collect()
265	    }
266
267	    #[test]
268	    fn sudo_rerun_uses_absolute_path_not_bare_name() {
269	        let cmd = rerun_command(
270	            ElevateStyle::Sudo,
271	            "/home/reavus/.local/bin/spt",
272	            &args(&["subnet", "create", "SPT_DEV"]),
273	        )
274	        .expect("sudo style yields a command");
275	        assert_eq!(
276	            cmd,
277	            "sudo /home/reavus/.local/bin/spt subnet create SPT_DEV"
278	        );
279	        // The crux: an absolute program path under sudo (secure_path-proof).
280	        assert!(cmd.starts_with("sudo /"), "absolute path under sudo: {cmd}");
281	    }
282
283	    // [unit->REQ-HAZARD-SUDO-SECURE-PATH] copy-paste must survive spaces/meta in
284	    // a subnet name — the re-run is shell-quoted.
285	    #[test]
286	    fn sudo_rerun_shell_quotes_spaced_args() {
287	        let cmd = rerun_command(
288	            ElevateStyle::Sudo,
289	            "/usr/local/bin/spt",
290	            &args(&["subnet", "create", "home fleet"]),
291	        )
292	        .unwrap();
293	        assert_eq!(cmd, "sudo /usr/local/bin/spt subnet create 'home fleet'");
294	    }
295
296	    // [unit->REQ-HAZARD-SUDO-SECURE-PATH] Windows has no command-prefix elevation
297	    // (UAC is a terminal launch) — no sudo line to emit.
298	    #[test]
299	    fn admin_terminal_style_has_no_command_prefix() {
300	        assert!(rerun_command(
301	            ElevateStyle::AdminTerminal,
302	            "C:/spt.exe",
303	            &args(&["subnet", "create", "x"])
304	        )
305	        .is_none());
306	    }
307
308	    // [unit->REQ-HAZARD-SUDO-SECURE-PATH] auto-elevate fires only on an
309	    // interactive Unix session that is positively NOT elevated; every other
310	    // combination falls through to the printed hint.
311	    #[test]
312	    fn auto_elevate_only_when_unix_interactive_and_unelevated() {
313	        assert!(should_auto_elevate(Elevation::NotElevated, true, true));
314	        // Not unix (Windows): no sudo mechanism.
315	        assert!(!should_auto_elevate(Elevation::NotElevated, true, false));
316	        // Non-interactive: sudo cannot prompt for a password.
317	        assert!(!should_auto_elevate(Elevation::NotElevated, false, true));
318	        // Already elevated, or undeterminable: never re-elevate.
319	        assert!(!should_auto_elevate(Elevation::Elevated, true, true));
320	        assert!(!should_auto_elevate(Elevation::Unknown, true, true));
321	    }
322	}
323

//! OS privilege-elevation gate for the node-bound pairing-code fetch (REQ-PAIR-6).
//!
//! Surfacing a subnet's live TOTP code off a trusted node is the *privileged* fetch
//! path: it hands out subnet-joining authority, so the process must be elevated
//! (Windows UAC / Linux root-or-equivalent). When it is not, we refuse the node-bound
//! fetch and point the user at their authenticator app — where the same seed was
//! provisioned at pairing (the `otpauth://` URI), giving the code without privilege.
//!
//! ## Structure (a thin testable seam over an environment-dependent OS call)
//!
//! [`current`] is the only environment-dependent part: a `cfg`-split OS query that
//! cannot run hermetically in CI (the test process's real euid/token is whatever the
//! runner happens to be). So the *decision* — does this elevation level show the code
//! or fall back? — is factored into [`gate_for`], a pure function unit-tested both
//! ways. An unknown/unsupported platform maps to [`Elevation::Unknown`], which
//! [`gate_for`] treats as NOT elevated: **fail safe** — never leak the code when we
//! cannot positively confirm privilege.
// [impl->REQ-PAIR-6]

/// The process's OS privilege level, as far as we can positively determine it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Elevation {
    /// Confirmed elevated: Windows token is elevated, or Unix euid == 0.
    Elevated,
    /// Confirmed NOT elevated.
    NotElevated,
    /// Could not determine (unsupported platform, or the OS query failed). Treated as
    /// NOT elevated by [`gate_for`] — we never assume privilege we cannot confirm.
    ///
    /// Constructed only on Windows (an `OpenProcessToken`/`GetTokenInformation`
    /// failure) and on unsupported platforms; on Unix `geteuid` cannot fail, so the
    /// variant is unreachable there. `allow(dead_code)` keeps the fail-safe arm in the
    /// cross-platform API without a per-target warning.
    #[allow(dead_code)]
    Unknown,
}

/// Whether the gated fetch may surface the code, or must fall back to the app.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Gate {
    /// Elevated — render the code + `otpauth://` URI.
    Show,
    /// Not elevated (or undeterminable) — refuse, point at the authenticator app.
    Fallback,
}

/// The pure gate decision (the testable seam). Only a *positively confirmed* elevation
/// shows the code; `NotElevated` and `Unknown` both fall back (fail safe).
// [impl->REQ-PAIR-6]
pub fn gate_for(elevation: Elevation) -> Gate {
    match elevation {
        Elevation::Elevated => Gate::Show,
        Elevation::NotElevated | Elevation::Unknown => Gate::Fallback,
    }
}

/// The fallback guidance shown when the fetch is refused. Names the authenticator app
/// explicitly (the seed was provisioned there at pairing via the `otpauth://` URI), so
/// the user has an unprivileged path to the same code.
pub fn fallback_message(subnet: &str) -> String {
    format!(
        "ELEVATION_REQUIRED: showing the code for subnet '{subnet}' off this node needs an \
         elevated process (run as administrator / root). Without elevation, read the current \
         code from your authenticator app instead — the seed was provisioned there at pairing."
    )
}

/// How this OS expects a process to acquire privilege — drives both the
/// actionable guidance text and whether auto-elevation is even possible.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElevateStyle {
    /// Unix: `sudo <program> <args>`. Never constructed on Windows builds
    /// (`current_style` yields `AdminTerminal` there) — `allow(dead_code)`
    /// keeps the cross-platform variant without a per-target warning; the
    /// unit tests construct it on every platform.
    #[cfg_attr(windows, allow(dead_code))]
    Sudo,
    /// Windows: a UAC terminal launch, not a command prefix. Symmetrically
    /// never constructed on non-Windows builds.
    #[cfg_attr(not(windows), allow(dead_code))]
    AdminTerminal,
}

/// The privilege-acquisition style for the current OS.
#[cfg(windows)]
pub fn current_style() -> ElevateStyle {
    ElevateStyle::AdminTerminal
}
#[cfg(not(windows))]
pub fn current_style() -> ElevateStyle {
    ElevateStyle::Sudo
}

/// The exact, copy-pasteable command that re-runs THIS invocation elevated.
///
/// `exe` MUST be the binary's ABSOLUTE path (callers pass `current_exe()`):
/// a user-local install (`~/.cargo/bin`, `~/.local/bin`) is NOT on sudo's
/// `secure_path`, so the bare-name `sudo spt …` a user naturally types fails
/// with `sudo: spt: command not found` (KNOWN-HAZARDS 5.10). An absolute path
/// is executed directly — `secure_path` only governs bare-name PATH lookup —
/// so `sudo /abs/spt …` always resolves. Returns `None` on Windows, where
/// elevation is a UAC terminal launch with no command-prefix equivalent.
/// [impl->REQ-HAZARD-SUDO-SECURE-PATH]
pub fn rerun_command(style: ElevateStyle, exe: &str, args: &[String]) -> Option<String> {
    match style {
        ElevateStyle::Sudo => {
            let mut parts = vec!["sudo".to_string(), shell_quote(exe)];
            parts.extend(args.iter().map(|a| shell_quote(a)));
            Some(parts.join(" "))
        }
        ElevateStyle::AdminTerminal => None,
    }
}

/// POSIX shell-quote a single argument so the re-run command survives
/// copy-paste with spaces or shell-meaningful characters (subnet names can
/// contain spaces). Bare when it only holds known-safe characters; otherwise
/// single-quoted with embedded quotes escaped the `'\''` way.
fn shell_quote(s: &str) -> String {
    if !s.is_empty()
        && s.chars()
            .all(|c| c.is_ascii_alphanumeric() || "-_./:@=+,".contains(c))
    {
        return s.to_string();
    }
    format!("'{}'", s.replace('\'', r"'\''"))
}

/// Whether to ATTEMPT automatic privilege escalation (re-exec under sudo)
/// rather than just printing the refusal. Pure for testing; the actual exec is
/// impure (it needs a real `sudo` + TTY). Auto-elevate only when: this is a
/// Unix host (`is_unix` — sudo is the mechanism), the session is interactive
/// (`interactive` — sudo must be able to prompt for a password on the TTY),
/// and elevation is positively NOT held. `Unknown`/`Elevated` never auto-elevate.
/// [impl->REQ-HAZARD-SUDO-SECURE-PATH]
pub fn should_auto_elevate(elevation: Elevation, interactive: bool, is_unix: bool) -> bool {
    is_unix && interactive && matches!(elevation, Elevation::NotElevated)
}

/// Set when the process **started** elevated and deliberately de-elevated
/// itself at entry (the sudo-invoker drop, KNOWN-HAZARDS 5.7): the user
/// proved privilege by sudo'ing; dropping to their own identity for state
/// safety must not also revoke the gate they paid for. Process-local by
/// design — never an env var, which any unprivileged caller could forge.
// [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
static ELEVATION_PROVEN: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);

/// Record that elevation was positively confirmed before a deliberate
/// in-process de-elevation (called ONLY from the main-entry sudo drop).
/// The drop is unix-only (Windows respawns instead of dropping), so this
/// is uncalled on Windows builds — `allow(dead_code)` keeps the
/// cross-platform API without a per-target warning (the `Unknown` posture).
#[cfg_attr(windows, allow(dead_code))]
pub fn mark_proven() {
    ELEVATION_PROVEN.store(true, std::sync::atomic::Ordering::Relaxed);
}

/// Query the OS for this process's elevation level. `cfg`-split; an unsupported
/// platform or a failed OS call returns [`Elevation::Unknown`] (→ fail safe).
/// A process that proved elevation at entry and then dropped (the 5.7 sudo
/// de-elevation) still reads `Elevated` — the proof, not the current euid,
/// is what the gates authorize on.
// [impl->REQ-PAIR-6]
pub fn current() -> Elevation {
    if ELEVATION_PROVEN.load(std::sync::atomic::Ordering::Relaxed) {
        return Elevation::Elevated;
    }
    detect()
}

#[cfg(unix)]
fn detect() -> Elevation {
    // Root (euid 0) is the Unix "elevated" equivalent. `geteuid` cannot fail.
    if unsafe { libc::geteuid() } == 0 {
        Elevation::Elevated
    } else {
        Elevation::NotElevated
    }
}

#[cfg(windows)]
fn detect() -> Elevation {
    use core::ffi::c_void;
    use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
    use windows_sys::Win32::Security::{
        GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY,
    };
    use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};

    // Open the current process token, query TokenElevation. Any failure → Unknown
    // (fail safe). GetCurrentProcess returns a pseudo-handle we must NOT close; only
    // the opened token handle is released.
    unsafe {
        let mut token: HANDLE = std::ptr::null_mut();
        if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) == 0 {
            return Elevation::Unknown;
        }
        let mut info = TOKEN_ELEVATION { TokenIsElevated: 0 };
        let mut ret_len: u32 = 0;
        let ok = GetTokenInformation(
            token,
            TokenElevation,
            &mut info as *mut _ as *mut c_void,
            std::mem::size_of::<TOKEN_ELEVATION>() as u32,
            &mut ret_len,
        );
        CloseHandle(token);
        if ok == 0 {
            Elevation::Unknown
        } else if info.TokenIsElevated != 0 {
            Elevation::Elevated
        } else {
            Elevation::NotElevated
        }
    }
}

#[cfg(not(any(unix, windows)))]
fn detect() -> Elevation {
    // Unsupported platform: we cannot confirm privilege, so refuse to assume it.
    Elevation::Unknown
}

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

    // [unit->REQ-PAIR-6] only confirmed elevation shows the code; NotElevated AND the
    // undeterminable Unknown both fall back (fail safe — no code without proof).
    #[test]
    fn gate_shows_only_when_confirmed_elevated() {
        assert_eq!(gate_for(Elevation::Elevated), Gate::Show);
        assert_eq!(gate_for(Elevation::NotElevated), Gate::Fallback);
        assert_eq!(gate_for(Elevation::Unknown), Gate::Fallback);
    }

    // [unit->REQ-PAIR-6] the fallback names the authenticator app (the unprivileged
    // path to the same code) and the subnet it concerns.
    #[test]
    fn fallback_message_names_authenticator_app_and_subnet() {
        let msg = fallback_message("home fleet");
        assert!(msg.contains("authenticator app"));
        assert!(msg.contains("home fleet"));
    }

    // The live OS probe is environment-dependent (the test runner's own privilege), so
    // we only assert it returns a determinate value here on the supported platforms —
    // the show/fallback *decision* is covered hermetically via `gate_for` above. Real
    // elevated-vs-not end-to-end verification is manual (todlando / D9).
    #[test]
    fn current_returns_a_determinate_level_on_supported_platforms() {
        let e = current();
        if cfg!(any(unix, windows)) {
            assert!(matches!(e, Elevation::Elevated | Elevation::NotElevated));
        }
    }

    // [unit->REQ-HAZARD-SUDO-SECURE-PATH] the sudo re-run command MUST carry the
    // binary's absolute path: a bare `sudo spt …` dies "command not found" when
    // spt is a user-local install (~/.local/bin), which is not on sudo's
    // secure_path. An absolute path bypasses that lookup. This is the fix for
    // the Linux subnet-create dead-end.
    fn args(v: &[&str]) -> Vec<String> {
        v.iter().map(|s| s.to_string()).collect()
    }

    #[test]
    fn sudo_rerun_uses_absolute_path_not_bare_name() {
        let cmd = rerun_command(
            ElevateStyle::Sudo,
            "/home/reavus/.local/bin/spt",
            &args(&["subnet", "create", "SPT_DEV"]),
        )
        .expect("sudo style yields a command");
        assert_eq!(
            cmd,
            "sudo /home/reavus/.local/bin/spt subnet create SPT_DEV"
        );
        // The crux: an absolute program path under sudo (secure_path-proof).
        assert!(cmd.starts_with("sudo /"), "absolute path under sudo: {cmd}");
    }

    // [unit->REQ-HAZARD-SUDO-SECURE-PATH] copy-paste must survive spaces/meta in
    // a subnet name — the re-run is shell-quoted.
    #[test]
    fn sudo_rerun_shell_quotes_spaced_args() {
        let cmd = rerun_command(
            ElevateStyle::Sudo,
            "/usr/local/bin/spt",
            &args(&["subnet", "create", "home fleet"]),
        )
        .unwrap();
        assert_eq!(cmd, "sudo /usr/local/bin/spt subnet create 'home fleet'");
    }

    // [unit->REQ-HAZARD-SUDO-SECURE-PATH] Windows has no command-prefix elevation
    // (UAC is a terminal launch) — no sudo line to emit.
    #[test]
    fn admin_terminal_style_has_no_command_prefix() {
        assert!(rerun_command(
            ElevateStyle::AdminTerminal,
            "C:/spt.exe",
            &args(&["subnet", "create", "x"])
        )
        .is_none());
    }

    // [unit->REQ-HAZARD-SUDO-SECURE-PATH] auto-elevate fires only on an
    // interactive Unix session that is positively NOT elevated; every other
    // combination falls through to the printed hint.
    #[test]
    fn auto_elevate_only_when_unix_interactive_and_unelevated() {
        assert!(should_auto_elevate(Elevation::NotElevated, true, true));
        // Not unix (Windows): no sudo mechanism.
        assert!(!should_auto_elevate(Elevation::NotElevated, true, false));
        // Non-interactive: sudo cannot prompt for a password.
        assert!(!should_auto_elevate(Elevation::NotElevated, false, true));
        // Already elevated, or undeterminable: never re-elevate.
        assert!(!should_auto_elevate(Elevation::Elevated, true, true));
        assert!(!should_auto_elevate(Elevation::Unknown, true, true));
    }
}


QR already rendered inline (create/show-code/join), and elevation has probe + sudo-hint + auto-elevate *decision*. Check: does join show QR + is there an actual re-exec/UAC-window consumer?


3106	fn cmd_subnet_create(name: String) -> i32 {
3107	    let mut store = SubnetStore::load();
3108	    let resolved = resolve(&mut store, None, Some(name));
3109	    let result = decide_create(resolved, now_unix(), elevation::current());
3110	    // Elevation is the only refusal we can clear by re-running: auto-elevate
3111	    // (interactive Unix), else print the absolute-path hint. Nothing persisted
3112	    // yet on the refused path, so the elevated child mints cleanly.
3113	    if result.code == EXIT_NOT_ELEVATED {
3114	        if let Some(code) = try_auto_elevate() {
3115	            return code;
3116	        }
3117	        eprintln!("{}", with_elevation_hint(result.msg));
3118	        return EXIT_NOT_ELEVATED;
3119	    }
3120	    let adopted = if result.save {
3121	        match save_minted(&store) {
3122	            Ok(a) => a,
3123	            Err(code) => return code,
3124	        }
3125	    } else {
3126	        Vec::new()
3127	    };
3128	    // Membership implies reachability (CONTEXT §subnet attachment): the
3129	    // sole seed-holder must be answering joins by the time the join hint
3130	    // below prints. After the persist (a refused create starts nothing),
3131	    // before the payload (the banner concludes with its divider first).
3132	    // [impl->REQ-SUBNET-2]
3133	    if result.code == 0 {
3134	        if let Err(code) = ensure_daemon_announced() {
3135	            return code;
3136	        }
3137	    }
3138	    if let Some(out) = result.out {
3139	        print!("{out}");
3140	        let _ = std::io::stdout().flush();
3141	    }
3142	    eprintln!("{}", result.msg);
3143	    announce_sync_home(&adopted);
3144	    result.code
3145	}
3146
3147	/// One subnet's status-view row: membership shape only — never seeds, epochs,
3148	/// or codes (M7 plan decision 5).
3149	struct SubnetStatusRow {
3150	    name: String,
3151	    /// Paired nodes known for the subnet (this node included).
3152	    nodes: usize,
3153	    /// Endpoints visible in the subnet's registry snapshot.
3154	    endpoints: usize,
3155	}
3156
3157	/// Build the status rows from the subnet store + registry snapshots + the
3158	/// member roster. Pure over its inputs so the view is unit-tested hermetically.
3159	///
3160	/// Node membership is ROSTER-derived (self + roster members — the durable mesh
3161	/// membership record, Mesh-D6), with registry-row nodes unioned in as
3162	/// enrichment: a member node with zero advertised endpoints (fresh box, empty
3163	/// owlery) is still a member, and an offline peer whose rows the 4.10 eviction
3164	/// decayed must not vanish from its own subnet's view. Endpoint counts stay
3165	/// gossip-derived and stale-tolerant (they catch up as gossip lands).

fn cmd_subnet_create(name: String) -> i32 {
    let mut store = SubnetStore::load();
    let resolved = resolve(&mut store, None, Some(name));
    let result = decide_create(resolved, now_unix(), elevation::current());
    // Elevation is the only refusal we can clear by re-running: auto-elevate
    // (interactive Unix), else print the absolute-path hint. Nothing persisted
    // yet on the refused path, so the elevated child mints cleanly.
    if result.code == EXIT_NOT_ELEVATED {
        if let Some(code) = try_auto_elevate() {
            return code;
        }
        eprintln!("{}", with_elevation_hint(result.msg));
        return EXIT_NOT_ELEVATED;
    }
    let adopted = if result.save {
        match save_minted(&store) {
            Ok(a) => a,
            Err(code) => return code,
        }
    } else {
        Vec::new()
    };
    // Membership implies reachability (CONTEXT §subnet attachment): the
    // sole seed-holder must be answering joins by the time the join hint
    // below prints. After the persist (a refused create starts nothing),
    // before the payload (the banner concludes with its divider first).
    // [impl->REQ-SUBNET-2]
    if result.code == 0 {
        if let Err(code) = ensure_daemon_announced() {
            return code;
        }
    }
    if let Some(out) = result.out {
        print!("{out}");
        let _ = std::io::stdout().flush();
    }
    eprintln!("{}", result.msg);
    announce_sync_home(&adopted);
    result.code
}

/// One subnet's status-view row: membership shape only — never seeds, epochs,
/// or codes (M7 plan decision 5).
struct SubnetStatusRow {
    name: String,
    /// Paired nodes known for the subnet (this node included).
    nodes: usize,
    /// Endpoints visible in the subnet's registry snapshot.
    endpoints: usize,
}

/// Build the status rows from the subnet store + registry snapshots + the
/// member roster. Pure over its inputs so the view is unit-tested hermetically.
///
/// Node membership is ROSTER-derived (self + roster members — the durable mesh
/// membership record, Mesh-D6), with registry-row nodes unioned in as
/// enrichment: a member node with zero advertised endpoints (fresh box, empty
/// owlery) is still a member, and an offline peer whose rows the 4.10 eviction
/// decayed must not vanish from its own subnet's view. Endpoint counts stay
/// gossip-derived and stale-tolerant (they catch up as gossip lands).


crates\spt\src\elevation.rs:104:pub fn rerun_command(style: ElevateStyle, exe: &str, args: &[String]) -> Option<String> {
crates\spt\src\elevation.rs:129:/// Whether to ATTEMPT automatic privilege escalation (re-exec under sudo)
crates\spt\src\elevation.rs:136:pub fn should_auto_elevate(elevation: Elevation, interactive: bool, is_unix: bool) -> bool {
crates\spt\src\elevation.rs:269:        let cmd = rerun_command(
crates\spt\src\elevation.rs:287:        let cmd = rerun_command(
crates\spt\src\elevation.rs:300:        assert!(rerun_command(
crates\spt\src\elevation.rs:313:        assert!(should_auto_elevate(Elevation::NotElevated, true, true));
crates\spt\src\elevation.rs:315:        assert!(!should_auto_elevate(Elevation::NotElevated, true, false));
crates\spt\src\elevation.rs:317:        assert!(!should_auto_elevate(Elevation::NotElevated, false, true));
crates\spt\src\elevation.rs:319:        assert!(!should_auto_elevate(Elevation::Elevated, true, true));
crates\spt\src\elevation.rs:320:        assert!(!should_auto_elevate(Elevation::Unknown, true, true));
crates\spt\src\cli.rs:3040:    elevation::rerun_command(
crates\spt\src\cli.rs:3068:    if !elevation::should_auto_elevate(elevation::current(), interactive, cfg!(unix)) {
crates\spt\src\cli.rs:3072:    match std::process::Command::new("sudo")


Read the current `try_auto_elevate` / hint flow (cli.rs 3030-3105):


3030	        .ok()
3031	        .and_then(|p| p.into_os_string().into_string().ok())
3032	        .unwrap_or_else(|| "spt".to_string())
3033	}
3034
3035	/// The actionable "re-run elevated" line appended to a gated refusal, or `None`
3036	/// where the OS has no command-prefix elevation (Windows — the message already
3037	/// says "run as administrator"). Unix emits the absolute-path sudo command so
3038	/// it survives `secure_path` (KNOWN-HAZARDS 5.10).
3039	fn elevate_rerun_hint() -> Option<String> {
3040	    elevation::rerun_command(
3041	        elevation::current_style(),
3042	        &current_exe_str(),
3043	        &invocation_args(),
3044	    )
3045	    .map(|cmd| format!("re-run elevated (the full path clears sudo's secure_path):\n  {cmd}"))
3046	}
3047
3048	/// Append the actionable elevation hint to a refusal message (no-op on an OS
3049	/// with no command-prefix elevation).
3050	fn with_elevation_hint(msg: String) -> String {
3051	    match elevate_rerun_hint() {
3052	        Some(hint) => format!("{msg}\n{hint}"),
3053	        None => msg,
3054	    }
3055	}
3056
3057	/// The "Both" UX (M7 plan): when a gated command is refused for lack of
3058	/// privilege, FIRST try to re-run ourselves under sudo on an interactive Unix
3059	/// TTY — the elevated child does the real work and `main` de-elevates back to
3060	/// the user. Returns `Some(exit_code)` when sudo was launched (forward its
3061	/// code), or `None` when auto-elevation does not apply or `sudo` is absent, so
3062	/// the caller prints the actionable hint instead. Re-running ourselves cannot
3063	/// loop: the elevated child reads `Elevation::Elevated`, so its own gate shows.
3064	/// [impl->REQ-HAZARD-SUDO-SECURE-PATH]
3065	fn try_auto_elevate() -> Option<i32> {
3066	    use std::io::IsTerminal;
3067	    let interactive = std::io::stdin().is_terminal() && std::io::stderr().is_terminal();
3068	    if !elevation::should_auto_elevate(elevation::current(), interactive, cfg!(unix)) {
3069	        return None;
3070	    }
3071	    eprintln!("ELEVATING: re-running under sudo — you may be prompted for your password.");
3072	    match std::process::Command::new("sudo")
3073	        .arg(current_exe_str())
3074	        .args(invocation_args())
3075	        .status()
3076	    {
3077	        Ok(status) => Some(status.code().unwrap_or(1)),
3078	        Err(_) => None, // sudo not installed — fall back to the printed hint
3079	    }
3080	}
3081
3082	/// `spt subnet show-code [NAME]` — surface a subnet's current code off this node,
3083	/// behind the OS-elevation gate (REQ-PAIR-3/6; ported off `spt pair show-totp` (renamed show-code), M7
3084	/// plan decision 3). Wires the real store, clock, and elevation probe into the
3085	/// unit-tested [`resolve`]/[`decide_show_code`] core.
3086	// [impl->REQ-PAIR-3] [impl->REQ-PAIR-6] [impl->REQ-SUBNET-1]
3087	fn cmd_subnet_show_code(name: Option<String>) -> i32 {
3088	    let mut store = SubnetStore::load();
3089	    let resolved = resolve(&mut store, name, None);
3090	    let result = decide_show_code(resolved, now_unix(), elevation::current());
3091	    if result.code == EXIT_NOT_ELEVATED {
3092	        if let Some(code) = try_auto_elevate() {
3093	            return code;
3094	        }
3095	        eprintln!("{}", with_elevation_hint(result.msg));
3096	        return EXIT_NOT_ELEVATED;
3097	    }
3098	    finish_show(result, &store)
3099	}
3100
3101	/// `spt subnet create <NAME>` — mint a fresh subnet (this node becomes the sole
3102	/// seed-holder) and print its joining material (REQ-SUBNET-1; the verb-as-flag
3103	/// `show-totp --create-new` un-buried (now show-code), M7 plan decisions 3 + 11). Elevation-gated:
3104	/// the output reveals the subnet's joining secret.
3105	// [impl->REQ-SUBNET-1]

        .ok()
        .and_then(|p| p.into_os_string().into_string().ok())
        .unwrap_or_else(|| "spt".to_string())
}

/// The actionable "re-run elevated" line appended to a gated refusal, or `None`
/// where the OS has no command-prefix elevation (Windows — the message already
/// says "run as administrator"). Unix emits the absolute-path sudo command so
/// it survives `secure_path` (KNOWN-HAZARDS 5.10).
fn elevate_rerun_hint() -> Option<String> {
    elevation::rerun_command(
        elevation::current_style(),
        &current_exe_str(),
        &invocation_args(),
    )
    .map(|cmd| format!("re-run elevated (the full path clears sudo's secure_path):\n  {cmd}"))
}

/// Append the actionable elevation hint to a refusal message (no-op on an OS
/// with no command-prefix elevation).
fn with_elevation_hint(msg: String) -> String {
    match elevate_rerun_hint() {
        Some(hint) => format!("{msg}\n{hint}"),
        None => msg,
    }
}

/// The "Both" UX (M7 plan): when a gated command is refused for lack of
/// privilege, FIRST try to re-run ourselves under sudo on an interactive Unix
/// TTY — the elevated child does the real work and `main` de-elevates back to
/// the user. Returns `Some(exit_code)` when sudo was launched (forward its
/// code), or `None` when auto-elevation does not apply or `sudo` is absent, so
/// the caller prints the actionable hint instead. Re-running ourselves cannot
/// loop: the elevated child reads `Elevation::Elevated`, so its own gate shows.
/// [impl->REQ-HAZARD-SUDO-SECURE-PATH]
fn try_auto_elevate() -> Option<i32> {
    use std::io::IsTerminal;
    let interactive = std::io::stdin().is_terminal() && std::io::stderr().is_terminal();
    if !elevation::should_auto_elevate(elevation::current(), interactive, cfg!(unix)) {
        return None;
    }
    eprintln!("ELEVATING: re-running under sudo — you may be prompted for your password.");
    match std::process::Command::new("sudo")
        .arg(current_exe_str())
        .args(invocation_args())
        .status()
    {
        Ok(status) => Some(status.code().unwrap_or(1)),
        Err(_) => None, // sudo not installed — fall back to the printed hint
    }
}

/// `spt subnet show-code [NAME]` — surface a subnet's current code off this node,
/// behind the OS-elevation gate (REQ-PAIR-3/6; ported off `spt pair show-totp` (renamed show-code), M7
/// plan decision 3). Wires the real store, clock, and elevation probe into the
/// unit-tested [`resolve`]/[`decide_show_code`] core.
// [impl->REQ-PAIR-3] [impl->REQ-PAIR-6] [impl->REQ-SUBNET-1]
fn cmd_subnet_show_code(name: Option<String>) -> i32 {
    let mut store = SubnetStore::load();
    let resolved = resolve(&mut store, name, None);
    let result = decide_show_code(resolved, now_unix(), elevation::current());
    if result.code == EXIT_NOT_ELEVATED {
        if let Some(code) = try_auto_elevate() {
            return code;
        }
        eprintln!("{}", with_elevation_hint(result.msg));
        return EXIT_NOT_ELEVATED;
    }
    finish_show(result, &store)
}

/// `spt subnet create <NAME>` — mint a fresh subnet (this node becomes the sole
/// seed-holder) and print its joining material (REQ-SUBNET-1; the verb-as-flag
/// `show-totp --create-new` un-buried (now show-code), M7 plan decisions 3 + 11). Elevation-gated:
/// the output reveals the subnet's joining secret.
// [impl->REQ-SUBNET-1]


W4 state clear: **QR already renders** inline (create/show-code/join); **self-elevation partially built** — Unix-interactive inline-sudo re-exec (`try_auto_elevate`) + printed hint, but **no Windows UAC window, no Linux desktop pkexec/terminal path**. That's the T4.2 delta. Check the elevation REQ for granularity, then write the plan:


333:id = "REQ-PAIR-6"
334-title = "Elevation-gated per-subnet code fetch (UAC/root or elevated agent; else authenticator app)"
335:[Omitted long matching line]
336-[[requirements]]
337-id = "REQ-PAIR-7"
--
354:id = "REQ-SUBNET-4"
355-title = "Subnet membership mutations elevation-gated (create = seed reveal; join = trust-boundary enrollment)"
356:[Omitted long matching line]
357-[[requirements]]
358-id = "REQ-DOCS-6"
--
472:#    (buffer + state-model asserts as unit) and the live key loop is a manual-verify leg (REQ-PAIR-6
473-#    precedent); the integrating bringup path is already int-covered by REQ-HOST-RUN-1/REQ-RC-1. ──
474-[[requirements]]
--
718:[Omitted long matching line]
719-[[requirements]]
720-id = "REQ-HAZARD-LOCAL-API-AUTH"
--
803:required_stages = ["doc", "impl", "unit"]  # activated 2026-06-06 (M7 closeout fix 1): spt-daemon::deelevate (the OS-split de-elevation seam: windows linked-token respawn, unix sudo-invoker drop) consumed by daemon::spawn_detached + the Daemon::run entry guard. unit = the pure decision seams (sudo-invoker derivation from euid/env; the windows spawn-plan decision); the live elevated probe is environment-dependent (manual / acceptance rig)
804-[[requirements]]
805-id = "REQ-HAZARD-REGISTRY-GHOST-ROWS"
--
832:title = "Trust lifecycle verbs, elevation-gated: spt subnet leave <NAME> (membership exit) and spt subnet prune <node> (removes a dead identity's trust + registry rows, killing its dead dials; trust mutation = security surface, REQ-PAIR-6 gate machinery) (M8 decisions 6-7)"
833-[Omitted long context line]
834-[[requirements]]
--
880:required_stages = ["impl", "unit"]  # impl = cmd_daemon_run guards: Windows elevated→refuse (exit 2) before Daemon::run() (the hazard-tagged in-process respawn stays as untouched defense-in-depth, now unreached via the CLI); a conflict warning when a managed service is already active before binding inline. Daemon::run() foreground loop itself unchanged. unit = the pure run-mode decision (elevated×windows→refuse vs foreground) + `daemon run`/`daemon start` no longer share an alias.
881-[[requirements]]
882-id = "REQ-DAEMON-8"
--
910:[Omitted long matching line]
911:[Omitted long matching line]
912-[[requirements]]
913-id = "REQ-MESH-5"


Have enough to frame the W4 design-check. Writing the JIT plan:


Write

# M12 Wave 4 — subnet QR + self-elevating window — JIT plan

> todlando 2026-06-14, after the W2 + follow-on batch gate (PASS, committed
> 6e76f62 / eb74b18). doyle: "proceed to W4 subnet QR + self-elevating window;
> ping a design-check JIT like the others." **DESIGN-CHECK PENDING — build only
> after doyle's ruling.** The load-bearing unknown is T4.2 (self-elevating
> *window*): cross-platform + a security surface (spawning elevated processes /
> UAC). Spec: `M12-PLAN.md` Wave 4 + `M12-CANDIDATES.md`.

## Scope (M12-PLAN.md Wave 4)
- **T4.1** `spt subnet create/join/show-code`: surface a **QR code** of the TOTP
  seed (on create AND join).
- **T4.2** **Self-elevating behavior**: run normally if interactive + elevated;
  else re-run self in a self-elevating window with inline stdout ("Elevated
  terminal launched… / You can close this window"). Cross-platform — Win UAC
  window · Linux+desktop pkexec/polkit or x-terminal-emulator · Linux+TTY inline
  sudo · headless/no-TTY print the command for the human. Scope elevation to only
  the steps that need it (service/firewall/privileged-port; subnet-create itself
  is unprivileged-to-run but reveals a secret, so it is gated).

## What EXISTS today (investigation 2026-06-14)
- **QR already renders** (M7/M8, REQ-SUBNET-1): `render_qr` (cli.rs:2964) encodes
  the `otpauth://` URI as a unicode-half-block terminal QR. Printed on **create**
  (`cmd_subnet_create`), **show-code** (`cmd_subnet_show_code`), and the **join**
  flow (`cmd_subnet_join`). So T4.1's "QR on create AND join" is **largely already
  shipped** — the open question is whether anything is actually missing (Q1).
- **Elevation is PARTIALLY built** (`spt/src/elevation.rs`, REQ-PAIR-6 /
  REQ-HAZARD-SUDO-SECURE-PATH):
  - `current()` probe (Win token / Unix euid, fail-safe `Unknown`); `gate_for`
    (Show/Fallback); `fallback_message`.
  - `current_style()` (Unix `Sudo` / Win `AdminTerminal`); `rerun_command`
    (absolute-path sudo line, secure_path-proof); `should_auto_elevate` (Unix ∧
    interactive ∧ NotElevated).
  - `cli.rs::try_auto_elevate` ACTUALLY re-execs `sudo <abs-exe> <args>` on an
    **interactive Unix TTY** (the elevated child does the work; `main` de-elevates
    back via the 5.7 drop). `with_elevation_hint` prints the absolute-path sudo
    command otherwise. Consumed by `cmd_subnet_create` + `cmd_subnet_show_code`.
- **So the W4 T4.2 DELTA is the rest of the matrix:** today only **Unix +
  interactive-TTY** auto-elevates (inline sudo); **Windows prints "run as
  administrator" with NO actual UAC re-launch**; **Linux desktop without a TTY**
  (DISPLAY but no terminal) has no path; **headless** falls to the printed hint
  (Unix only). T4.2 = add the **Windows UAC window**, the **Linux desktop
  pkexec/terminal-emulator** path, and a **headless print** that covers Windows too.
- De-elevation seam exists (`spt-daemon::deelevate`, REQ-HAZARD-ELEVATED-DAEMON-SPAWN)
  — the elevated child drops back to the user for state safety; the
  `ELEVATION_PROVEN` flag keeps the gate authorized post-drop. The self-elevating
  re-launch is the INVERSE direction (unprivileged → elevated) and must compose
  with this drop without a loop (the elevated child reads `Elevated`, never
  re-elevates).

## Investigation TODOs (read before coding — gated on doyle scope)
1. **`cmd_subnet_join` (cli.rs:4260)**: confirm the QR actually renders on the
   joiner's side post-join (or only the code). Close any T4.1 gap if present.
2. **Windows `ShellExecuteW` "runas"**: the verb that triggers UAC + launches an
   elevated process. It opens a SEPARATE window; the elevated child's stdout does
   NOT come back to the original console. Determine the child-window-stays-open
   mechanism (so the user sees output): launch `cmd.exe /k <exe> <args>` under
   runas, or `<exe> <args>` with a trailing pause. windows-sys already in the dep
   set (`Win32_UI_Shell` feature may need adding).
3. **Linux desktop**: detect `DISPLAY`/`WAYLAND_DISPLAY`; prefer `pkexec` (polkit
   GUI auth, inherits stdio) when present, else `x-terminal-emulator -e sudo …`
   (Debian alternative; fallbacks: gnome-terminal/konsole). Decide the probe order
   + the "no path → print hint" floor.
4. **"Inline stdout" semantics**: confirm the intended UX — the ORIGINAL process
   prints "Elevated terminal launched…", the elevated CHILD window prints the work
   + "You can close this window." (The original cannot capture the elevated
   child's output across the privilege boundary on Windows.)
5. **Loop-safety + de-elevation interplay**: the elevated child must run the work
   then the 5.7 drop, and must NOT re-trigger self-elevation. Verify the
   `ELEVATION_PROVEN`/`current()==Elevated` path short-circuits any re-elevation.

## Open design questions for doyle
1. **T4.1 — is the QR already done?** The terminal QR renders on create +
   show-code + (apparently) join. Is T4.1 satisfied once I confirm the join path
   (Q-TODO 1), treating "window" as the inline terminal QR? Or does "window" imply
   a separate/GUI surface (no GUI milestone exists → I'd defer that)? Recommend:
   **T4.1 = verify + close any join-QR gap; "window" = the inline terminal QR**,
   no GUI work.
2. **Windows UAC child-window mechanism (Q-TODO 2/4).** Recommend: `ShellExecuteW`
   with the `runas` verb launching `<abs-exe> <args>` in a NEW elevated console
   that STAYS OPEN (a trailing "press any key" / the child prints "You can close
   this window"); the original prints "Elevated terminal launched…" and exits 0.
   Confirm the stays-open approach (child-pause vs `cmd /k`) + that we never try to
   pipe the elevated child's stdout back.
3. **Linux desktop elevation order (Q-TODO 3).** Recommend probe order:
   interactive-TTY → inline `sudo` (today); else DISPLAY ∧ `pkexec` → pkexec; else
   DISPLAY ∧ a terminal-emulator → `x-terminal-emulator -e sudo`; else → print the
   sudo hint. Confirm the order + the terminal-emulator fallback list.
4. **The pure decision seam.** Recommend extending `elevation.rs` with a pure
   `decide_elevation_path(os, elevation, interactive_tty, has_display, has_pkexec,
   has_term_emulator) -> ElevatePath { AlreadyElevated, InlineSudo, UacWindow,
   Pkexec, TerminalEmulator, PrintHint }`, unit-tested across the matrix; each
   variant's actual launch is the impure leg (manual-verify, REQ-PAIR-6 precedent).
   This generalizes today's `should_auto_elevate`. Confirm shape.
5. **REQ granularity.** T4.2 self-elevation is bigger than today's
   REQ-HAZARD-SUDO-SECURE-PATH (Unix sudo) and distinct from REQ-PAIR-6 (the
   show/fallback gate) and REQ-SUBNET-4 (which mutations are gated). Recommend a
   NEW **REQ-ELEVATE-1** (cross-platform self-elevating re-launch: the decision
   matrix + the per-OS window launch + inline-notice UX). Or extend REQ-SUBNET-4.
   Your call (lean: new REQ — it is a distinct mechanism reused by every gated
   command, not subnet-specific). doc+impl+unit (no int — launch is manual-verify).
6. **Security posture.** Self-elevation spawns a privileged process from an
   unprivileged one — the user's UAC/polkit/sudo prompt IS the consent gate (we
   never bypass it). We re-run the EXACT same invocation (no added privilege
   scope), absolute-exe-path (secure_path / no PATH hijack), and the elevated child
   de-elevates state back. Any KNOWN-HAZARDS entry to add (e.g. "self-elevation
   re-runs verbatim, never widens args; child drops state to the user")?

## Tentative tasks (pending doyle ruling — shapes will shift on Q1–Q6)
- **T4.0** Add the REQ to `traceable-reqs.toml` first (rule 3) — REQ-ELEVATE-1 (or
  extend REQ-SUBNET-4) per Q5. doc+impl+unit.
- **T4.1** Confirm/close the join-QR gap (likely tiny or a no-op).
- **T4.2a** `elevation.rs`: the pure `decide_elevation_path` matrix +
  `ElevatePath` enum (extends `should_auto_elevate`). Heavy unit tests.
- **T4.2b** The per-OS impure launchers: Windows `ShellExecuteW("runas")` elevated
  console (windows-sys `Win32_UI_Shell`); Linux `pkexec` + `x-terminal-emulator`
  spawns; reuse the Unix inline-sudo path; the headless/print floor (Windows too).
  Wire into the gated commands (replace the bare `try_auto_elevate` call sites with
  the matrix dispatch). Inline-notice UX ("Elevated terminal launched…").
- **T4.2c** Doc: CONTEXT.md elevation section + any KNOWN-HAZARDS entry (Q6).
- Activate stages as evidence lands. Full suite + clippy -D + traceable EXIT=0.
  W4 gate (doyle). Then **W5 whoami** → **W2.5 attach/kick** → full M12 gate.

## Notes
- The actual elevated launch is environment-dependent (UAC/polkit/sudo/TTY) — the
  DECISION matrix is the unit-tested seam, the launch is the manual-verify leg
  (REQ-PAIR-6 OS-probe precedent). No `int` stage on T4.2.
- Compose with the existing 5.7 de-elevation drop — re-launch is the inverse edge;
  must be loop-safe (elevated child never re-elevates).
- Build UNCOMMITTED until the operator calls the commit (W2's pre-auth was a
  one-off; the follow-on batch was operator-relayed commit too).