crates\spt-term\tests\inject.rs:84:        session.try_wait().expect("poll").is_none(),
crates\spt-term\tests\inject.rs:93:        if session.try_wait().expect("poll").is_some() {
crates\spt\tests\quickstart_e2e.rs:26:    // ---- step 2: bring an agent online (`spt poll bob`) ----
crates\spt\tests\quickstart_e2e.rs:28:        .args(["poll", "bob"])
crates\spt\tests\quickstart_e2e.rs:66:        .args(["poll", "bob", "--once"])
crates\spt\tests\contract_e2e.rs:123:        .args(["api", "--adapter", "mock", "poll", id, "--session-id", session_id])
crates\spt\tests\contract_e2e.rs:137:        .args(["api", "--adapter", "mock", "poll", id])
crates\spt-daemon\tests\twohost.rs:24://!   `spt pair show-totp` screen (the wire ceremony itself is the REAL
crates\spt\src\cli.rs:33:/// `spt pair show-totp` refused the fetch for lack of OS elevation. Distinct from the
crates\spt\src\cli.rs:119:    /// Also mints new subnets (`show-totp --create-new`). Pairing is how
crates\spt\src\cli.rs:123:        action: PairCmd,
crates\spt\src\cli.rs:506:enum PairCmd {
crates\spt\src\cli.rs:528:    &["api", "notify", "poll", "ready", "resources", "ring", "send", "shell", "shutdown", "whoami"];
crates\spt\src\cli.rs:578:        Cmd::Poll { id, once, subnet } => run_listen(&id, once, subnet.as_deref()),
crates\spt\src\cli.rs:583:            PairCmd::ShowTotp { subnet, create_new } => cmd_pair_show_totp(subnet, create_new),
crates\spt\src\cli.rs:1005:/// force qualification (no guessing — the D3c/`show-totp` stance).
crates\spt\src\cli.rs:1431:/// What `show-totp` resolved its arguments to against the node's subnet store — the
crates\spt\src\cli.rs:1450:/// The rendered outcome of `show-totp`: an exit code, the optional stdout block (the
crates\spt\src\cli.rs:1461:/// Resolve `show-totp` arguments against the store (mutating it iff `--create-new`
crates\spt\src\cli.rs:1556:/// `spt pair show-totp` — surface a subnet's current code off this node, behind the
crates\spt\src\cli.rs:2739:        assert!(matches!(parse(&["spt", "poll", "bob", "--once"]).unwrap().cmd, Cmd::Poll { once: true, .. }));
crates\spt\src\cli.rs:3809:    // ── pair show-totp ─────────────────────────────────────────────────────────
crates\spt\src\cli.rs:3811:    fn show_totp(args: &[&str]) -> PairCmd {
crates\spt\src\cli.rs:3818:    // [unit->REQ-PAIR-3] the show-totp arg shapes parse: bare, --subnet, --create-new.
crates\spt\src\cli.rs:3822:            show_totp(&["spt", "pair", "show-totp"]),
crates\spt\src\cli.rs:3823:            PairCmd::ShowTotp { subnet: None, create_new: None }
crates\spt\src\cli.rs:3826:            show_totp(&["spt", "pair", "show-totp", "--subnet", "home"]),
crates\spt\src\cli.rs:3827:            PairCmd::ShowTotp { subnet: Some(s), .. } if s == "home"
crates\spt\src\cli.rs:3830:            show_totp(&["spt", "pair", "show-totp", "--create-new", "home"]),
crates\spt\src\cli.rs:3831:            PairCmd::ShowTotp { create_new: Some(s), .. } if s == "home"
crates\spt\src\cli.rs:3838:        assert!(parse(&["spt", "pair", "show-totp", "--subnet", "a", "--create-new", "b"]).is_err());
crates\spt\src\api\mod.rs:311:        ApiCmd::Poll { id, include_deferred, link, auth } => match link {
crates\spt\src\api\mod.rs:414:        assert!(parse(&["spt", "--adapter", "m", "poll", "alice", "--include-deferred"]).is_ok());
crates\spt\src\api\mod.rs:415:        assert!(parse(&["spt", "--adapter", "m", "poll", "shell-0", "--link", "cafe"]).is_ok());


3795	        // delete-source removes exactly the source.
3796	        assert_eq!(cmd_fork("ling", "ling-cold", "work", true), 0);
3797	        assert!(spt_store::info::read_info(&src_perch).is_none(), "source perch gone");
3798	        assert!(
3799	            !cs.branch_store().has_branch(&agent_branch("ling")).unwrap(),
3800	            "source mind gone"
3801	        );
3802	        assert!(spt_store::info::read_info(&new_perch).is_some(), "earlier fork untouched");
3803	        assert!(
3804	            cs.branch_store().has_branch(&agent_branch("ling-cold")).unwrap(),
3805	            "the deleting fork's own mind stands"
3806	        );
3807	    }
3808
3809	    // ── pair show-totp ─────────────────────────────────────────────────────────
3810
3811	    fn show_totp(args: &[&str]) -> PairCmd {
3812	        match parse(args).unwrap().cmd {
3813	            Cmd::Pair { action } => action,
3814	            _ => panic!("expected Pair"),
3815	        }
3816	    }
3817
3818	    // [unit->REQ-PAIR-3] the show-totp arg shapes parse: bare, --subnet, --create-new.
3819	    #[test]
3820	    fn pair_show_totp_arg_shapes_parse() {
3821	        assert!(matches!(
3822	            show_totp(&["spt", "pair", "show-totp"]),
3823	            PairCmd::ShowTotp { subnet: None, create_new: None }
3824	        ));
3825	        assert!(matches!(
3826	            show_totp(&["spt", "pair", "show-totp", "--subnet", "home"]),
3827	            PairCmd::ShowTotp { subnet: Some(s), .. } if s == "home"
3828	        ));
3829	        assert!(matches!(
3830	            show_totp(&["spt", "pair", "show-totp", "--create-new", "home"]),
3831	            PairCmd::ShowTotp { create_new: Some(s), .. } if s == "home"
3832	        ));
3833	    }
3834
3835	    // [unit->REQ-PAIR-3] --subnet and --create-new are mutually exclusive.
3836	    #[test]
3837	    fn pair_show_totp_subnet_and_create_new_conflict() {
3838	        assert!(parse(&["spt", "pair", "show-totp", "--subnet", "a", "--create-new", "b"]).is_err());
3839	    }
3840
3841	    fn store_with(names: &[&str]) -> SubnetStore {
3842	        let mut s = SubnetStore::default();
3843	        for n in names {
3844	            s.create_subnet(n).expect("create");
3845	        }
3846	        s
3847	    }
3848
3849	    // [unit->REQ-PAIR-3] a lone subnet is auto-selected with no flag; elevated, the
3850	    // printed code matches TotpSeed computed from that subnet's stored seed.
3851	    #[test]
3852	    fn single_subnet_auto_selects_and_code_matches_seed() {
3853	        let mut store = store_with(&["home"]);
3854	        let seed = store.find("home").unwrap().seed_bytes().unwrap();
3855	        let now = 1_111_111_109;
3856	        let r = decide_show_totp(resolve(&mut store, None, None), now, Elevation::Elevated);
3857	        assert_eq!(r.code, 0);
3858	        let expected = TotpSeed::from_bytes(seed).code_at(now);
3859	        let out = r.out.expect("elevated show prints the code");
3860	        assert!(out.starts_with(&expected), "stdout starts with the code");
3861	        assert!(out.contains("otpauth://"), "stdout carries the provisioning URI");
3862	        assert!(!r.save, "an existing subnet is not re-persisted");
3863	    }
3864
3865	    // [unit->REQ-PAIR-3] several subnets + no --subnet refuses (no guessing); naming
3866	    // one resolves it.
3867	    #[test]
3868	    fn multi_subnet_refuses_without_flag_then_resolves_with_it() {
3869	        let mut store = store_with(&["home", "work"]);
3870	        let amb = decide_show_totp(resolve(&mut store, None, None), 0, Elevation::Elevated);
3871	        assert_eq!(amb.code, 2);
3872	        assert!(amb.out.is_none());
3873	        assert!(amb.msg.contains("AMBIGUOUS_SUBNET"));
3874
3875	        let work_seed = store.find("work").unwrap().seed_bytes().unwrap();
3876	        let now = 1_234_567_890;
3877	        let r = decide_show_totp(
3878	            resolve(&mut store, Some("work".into()), None),
3879	            now,
3880	            Elevation::Elevated,
3881	        );
3882	        assert_eq!(r.code, 0);
3883	        assert!(r.out.unwrap().starts_with(&TotpSeed::from_bytes(work_seed).code_at(now)));
3884	    }
3885
3886	    // [unit->REQ-PAIR-3] --create-new on a fresh name mints + shows, and flags the
3887	    // store for persistence (save = true) on the elevated path.
3888	    #[test]
3889	    fn create_new_mints_shows_and_requests_save() {
3890	        let mut store = SubnetStore::default();
3891	        let r = decide_show_totp(
3892	            resolve(&mut store, None, Some("fresh".into())),
3893	            42,
3894	            Elevation::Elevated,
3895	        );
3896	        assert_eq!(r.code, 0);
3897	        assert!(r.save, "a freshly minted subnet must be persisted on show");
3898	        assert!(r.out.unwrap().contains("otpauth://"));
3899	        assert!(store.find("fresh").is_some(), "mint landed in the in-memory store");
3900	    }
3901
3902	    // [unit->REQ-PAIR-3] --create-new for an existing name is a usage error.
3903	    #[test]
3904	    fn create_new_existing_name_is_usage_error() {
3905	        let mut store = store_with(&["home"]);
3906	        let r = decide_show_totp(
3907	            resolve(&mut store, None, Some("home".into())),
3908	            0,
3909	            Elevation::Elevated,
3910	        );
3911	        assert_eq!(r.code, 2);
3912	        assert!(r.msg.contains("SUBNET_EXISTS"));
3913	    }
3914
3915	    // [unit->REQ-PAIR-3] zero subnets + no flag points the user at pairing / --create-new.
3916	    #[test]
3917	    fn empty_store_no_flag_guides_to_pair_or_create() {
3918	        let mut store = SubnetStore::default();
3919	        let r = decide_show_totp(resolve(&mut store, None, None), 0, Elevation::Elevated);
3920	        assert_eq!(r.code, 2);
3921	        assert!(r.msg.contains("NO_SUBNETS"));
3922	    }
3923
3924	    // [unit->REQ-PAIR-3] a named-but-absent subnet is a usage error.
3925	    #[test]
3926	    fn unknown_named_subnet_is_usage_error() {
3927	        let mut store = store_with(&["home"]);
3928	        let r = decide_show_totp(
3929	            resolve(&mut store, Some("ghost".into()), None),
3930	            0,
3931	            Elevation::Elevated,
3932	        );
3933	        assert_eq!(r.code, 2);
3934	        assert!(r.msg.contains("NO_SUCH_SUBNET:ghost"));
3935	    }
3936
3937	    // [unit->REQ-PAIR-6] not elevated: the fetch is refused — distinct exit code, NO
3938	    // code in stdout (no leak), fallback message names the authenticator app, and a
3939	    // would-be --create-new mint is NOT persisted (refused = no durable side effect).
3940	    #[test]
3941	    fn not_elevated_refuses_without_leaking_code_or_saving() {
3942	        let mut store = SubnetStore::default();
3943	        let r = decide_show_totp(
3944	            resolve(&mut store, None, Some("fresh".into())),
3945	            0,
3946	            Elevation::NotElevated,
3947	        );
3948	        assert_eq!(r.code, EXIT_NOT_ELEVATED);
3949	        assert!(r.out.is_none(), "no code leaks on the refused path");
3950	        assert!(r.msg.contains("authenticator app"));
3951	        assert!(!r.save, "a refused fetch persists no mint");
3952	    }
3953
3954	    // [unit->REQ-PAIR-6] an undeterminable elevation (Unknown) also falls back — the
3955	    // same fail-safe path as NotElevated, never leaking the code.
3956	    #[test]
3957	    fn unknown_elevation_falls_back() {
3958	        let mut store = store_with(&["home"]);
3959	        let r = decide_show_totp(resolve(&mut store, None, None), 0, Elevation::Unknown);
3960	        assert_eq!(r.code, EXIT_NOT_ELEVATED);
3961	        assert!(r.out.is_none());
3962	    }
3963	}
3964

        // delete-source removes exactly the source.
        assert_eq!(cmd_fork("ling", "ling-cold", "work", true), 0);
        assert!(spt_store::info::read_info(&src_perch).is_none(), "source perch gone");
        assert!(
            !cs.branch_store().has_branch(&agent_branch("ling")).unwrap(),
            "source mind gone"
        );
        assert!(spt_store::info::read_info(&new_perch).is_some(), "earlier fork untouched");
        assert!(
            cs.branch_store().has_branch(&agent_branch("ling-cold")).unwrap(),
            "the deleting fork's own mind stands"
        );
    }

    // ── pair show-totp ─────────────────────────────────────────────────────────

    fn show_totp(args: &[&str]) -> PairCmd {
        match parse(args).unwrap().cmd {
            Cmd::Pair { action } => action,
            _ => panic!("expected Pair"),
        }
    }

    // [unit->REQ-PAIR-3] the show-totp arg shapes parse: bare, --subnet, --create-new.
    #[test]
    fn pair_show_totp_arg_shapes_parse() {
        assert!(matches!(
            show_totp(&["spt", "pair", "show-totp"]),
            PairCmd::ShowTotp { subnet: None, create_new: None }
        ));
        assert!(matches!(
            show_totp(&["spt", "pair", "show-totp", "--subnet", "home"]),
            PairCmd::ShowTotp { subnet: Some(s), .. } if s == "home"
        ));
        assert!(matches!(
            show_totp(&["spt", "pair", "show-totp", "--create-new", "home"]),
            PairCmd::ShowTotp { create_new: Some(s), .. } if s == "home"
        ));
    }

    // [unit->REQ-PAIR-3] --subnet and --create-new are mutually exclusive.
    #[test]
    fn pair_show_totp_subnet_and_create_new_conflict() {
        assert!(parse(&["spt", "pair", "show-totp", "--subnet", "a", "--create-new", "b"]).is_err());
    }

    fn store_with(names: &[&str]) -> SubnetStore {
        let mut s = SubnetStore::default();
        for n in names {
            s.create_subnet(n).expect("create");
        }
        s
    }

    // [unit->REQ-PAIR-3] a lone subnet is auto-selected with no flag; elevated, the
    // printed code matches TotpSeed computed from that subnet's stored seed.
    #[test]
    fn single_subnet_auto_selects_and_code_matches_seed() {
        let mut store = store_with(&["home"]);
        let seed = store.find("home").unwrap().seed_bytes().unwrap();
        let now = 1_111_111_109;
        let r = decide_show_totp(resolve(&mut store, None, None), now, Elevation::Elevated);
        assert_eq!(r.code, 0);
        let expected = TotpSeed::from_bytes(seed).code_at(now);
        let out = r.out.expect("elevated show prints the code");
        assert!(out.starts_with(&expected), "stdout starts with the code");
        assert!(out.contains("otpauth://"), "stdout carries the provisioning URI");
        assert!(!r.save, "an existing subnet is not re-persisted");
    }

    // [unit->REQ-PAIR-3] several subnets + no --subnet refuses (no guessing); naming
    // one resolves it.
    #[test]
    fn multi_subnet_refuses_without_flag_then_resolves_with_it() {
        let mut store = store_with(&["home", "work"]);
        let amb = decide_show_totp(resolve(&mut store, None, None), 0, Elevation::Elevated);
        assert_eq!(amb.code, 2);
        assert!(amb.out.is_none());
        assert!(amb.msg.contains("AMBIGUOUS_SUBNET"));

        let work_seed = store.find("work").unwrap().seed_bytes().unwrap();
        let now = 1_234_567_890;
        let r = decide_show_totp(
            resolve(&mut store, Some("work".into()), None),
            now,
            Elevation::Elevated,
        );
        assert_eq!(r.code, 0);
        assert!(r.out.unwrap().starts_with(&TotpSeed::from_bytes(work_seed).code_at(now)));
    }

    // [unit->REQ-PAIR-3] --create-new on a fresh name mints + shows, and flags the
    // store for persistence (save = true) on the elevated path.
    #[test]
    fn create_new_mints_shows_and_requests_save() {
        let mut store = SubnetStore::default();
        let r = decide_show_totp(
            resolve(&mut store, None, Some("fresh".into())),
            42,
            Elevation::Elevated,
        );
        assert_eq!(r.code, 0);
        assert!(r.save, "a freshly minted subnet must be persisted on show");
        assert!(r.out.unwrap().contains("otpauth://"));
        assert!(store.find("fresh").is_some(), "mint landed in the in-memory store");
    }

    // [unit->REQ-PAIR-3] --create-new for an existing name is a usage error.
    #[test]
    fn create_new_existing_name_is_usage_error() {
        let mut store = store_with(&["home"]);
        let r = decide_show_totp(
            resolve(&mut store, None, Some("home".into())),
            0,
            Elevation::Elevated,
        );
        assert_eq!(r.code, 2);
        assert!(r.msg.contains("SUBNET_EXISTS"));
    }

    // [unit->REQ-PAIR-3] zero subnets + no flag points the user at pairing / --create-new.
    #[test]
    fn empty_store_no_flag_guides_to_pair_or_create() {
        let mut store = SubnetStore::default();
        let r = decide_show_totp(resolve(&mut store, None, None), 0, Elevation::Elevated);
        assert_eq!(r.code, 2);
        assert!(r.msg.contains("NO_SUBNETS"));
    }

    // [unit->REQ-PAIR-3] a named-but-absent subnet is a usage error.
    #[test]
    fn unknown_named_subnet_is_usage_error() {
        let mut store = store_with(&["home"]);
        let r = decide_show_totp(
            resolve(&mut store, Some("ghost".into()), None),
            0,
            Elevation::Elevated,
        );
        assert_eq!(r.code, 2);
        assert!(r.msg.contains("NO_SUCH_SUBNET:ghost"));
    }

    // [unit->REQ-PAIR-6] not elevated: the fetch is refused — distinct exit code, NO
    // code in stdout (no leak), fallback message names the authenticator app, and a
    // would-be --create-new mint is NOT persisted (refused = no durable side effect).
    #[test]
    fn not_elevated_refuses_without_leaking_code_or_saving() {
        let mut store = SubnetStore::default();
        let r = decide_show_totp(
            resolve(&mut store, None, Some("fresh".into())),
            0,
            Elevation::NotElevated,
        );
        assert_eq!(r.code, EXIT_NOT_ELEVATED);
        assert!(r.out.is_none(), "no code leaks on the refused path");
        assert!(r.msg.contains("authenticator app"));
        assert!(!r.save, "a refused fetch persists no mint");
    }

    // [unit->REQ-PAIR-6] an undeterminable elevation (Unknown) also falls back — the
    // same fail-safe path as NotElevated, never leaking the code.
    #[test]
    fn unknown_elevation_falls_back() {
        let mut store = store_with(&["home"]);
        let r = decide_show_totp(resolve(&mut store, None, None), 0, Elevation::Unknown);
        assert_eq!(r.code, EXIT_NOT_ELEVATED);
        assert!(r.out.is_none());
    }
}


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	/// Query the OS for this process's elevation level. `cfg`-split; an unsupported
69	/// platform or a failed OS call returns [`Elevation::Unknown`] (→ fail safe).
70	// [impl->REQ-PAIR-6]
71	pub fn current() -> Elevation {
72	    detect()
73	}
74
75	#[cfg(unix)]
76	fn detect() -> Elevation {
77	    // Root (euid 0) is the Unix "elevated" equivalent. `geteuid` cannot fail.
78	    if unsafe { libc::geteuid() } == 0 {
79	        Elevation::Elevated
80	    } else {
81	        Elevation::NotElevated
82	    }
83	}
84
85	#[cfg(windows)]
86	fn detect() -> Elevation {
87	    use core::ffi::c_void;
88	    use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
89	    use windows_sys::Win32::Security::{
90	        GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY,
91	    };
92	    use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
93
94	    // Open the current process token, query TokenElevation. Any failure → Unknown
95	    // (fail safe). GetCurrentProcess returns a pseudo-handle we must NOT close; only
96	    // the opened token handle is released.
97	    unsafe {
98	        let mut token: HANDLE = std::ptr::null_mut();
99	        if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) == 0 {
100	            return Elevation::Unknown;
101	        }
102	        let mut info = TOKEN_ELEVATION { TokenIsElevated: 0 };
103	        let mut ret_len: u32 = 0;
104	        let ok = GetTokenInformation(
105	            token,
106	            TokenElevation,
107	            &mut info as *mut _ as *mut c_void,
108	            std::mem::size_of::<TOKEN_ELEVATION>() as u32,
109	            &mut ret_len,
110	        );
111	        CloseHandle(token);
112	        if ok == 0 {
113	            Elevation::Unknown
114	        } else if info.TokenIsElevated != 0 {
115	            Elevation::Elevated
116	        } else {
117	            Elevation::NotElevated
118	        }
119	    }
120	}
121
122	#[cfg(not(any(unix, windows)))]
123	fn detect() -> Elevation {
124	    // Unsupported platform: we cannot confirm privilege, so refuse to assume it.
125	    Elevation::Unknown
126	}
127
128	#[cfg(test)]
129	mod tests {
130	    use super::*;
131
132	    // [unit->REQ-PAIR-6] only confirmed elevation shows the code; NotElevated AND the
133	    // undeterminable Unknown both fall back (fail safe — no code without proof).
134	    #[test]
135	    fn gate_shows_only_when_confirmed_elevated() {
136	        assert_eq!(gate_for(Elevation::Elevated), Gate::Show);
137	        assert_eq!(gate_for(Elevation::NotElevated), Gate::Fallback);
138	        assert_eq!(gate_for(Elevation::Unknown), Gate::Fallback);
139	    }
140
141	    // [unit->REQ-PAIR-6] the fallback names the authenticator app (the unprivileged
142	    // path to the same code) and the subnet it concerns.
143	    #[test]
144	    fn fallback_message_names_authenticator_app_and_subnet() {
145	        let msg = fallback_message("home fleet");
146	        assert!(msg.contains("authenticator app"));
147	        assert!(msg.contains("home fleet"));
148	    }
149
150	    // The live OS probe is environment-dependent (the test runner's own privilege), so
151	    // we only assert it returns a determinate value here on the supported platforms —
152	    // the show/fallback *decision* is covered hermetically via `gate_for` above. Real
153	    // elevated-vs-not end-to-end verification is manual (todlando / D9).
154	    #[test]
155	    fn current_returns_a_determinate_level_on_supported_platforms() {
156	        let e = current();
157	        if cfg!(any(unix, windows)) {
158	            assert!(matches!(e, Elevation::Elevated | Elevation::NotElevated));
159	        }
160	    }
161	}
162

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

/// Query the OS for this process's elevation level. `cfg`-split; an unsupported
/// platform or a failed OS call returns [`Elevation::Unknown`] (→ fail safe).
// [impl->REQ-PAIR-6]
pub fn current() -> Elevation {
    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));
        }
    }
}


1	//! M6-D4 quickstart rung — the published human quickstart
2	//! (docs-site/src/quickstart/messaging.md) executed step for step against the
3	//! real binary: poll brings bob online → live send delivers (`SENT`) and the
4	//! body + `__REPLY_TO__` header land on bob's stdout → bob goes away → send
5	//! spools (`QUEUED`) → `poll --once` drains the backlog. If the documented
6	//! flow stops working, this fails — the doc cannot silently rot (REQ-DOCS-2's
7	//! "runnable, deterministic" promise, CI-run on both platforms).
8	//!
9	//! Hermetic: isolated SPT_HOME per run; not env-gated (cheap, local-only).
10	//
11	// [int->REQ-DOCS-2]
12
13	use std::io::{BufRead, BufReader, Read};
14	use std::process::{Command, Stdio};
15	use std::time::Duration;
16
17	fn spt() -> Command {
18	    Command::new(env!("CARGO_BIN_EXE_spt"))
19	}
20
21	#[test]
22	fn published_messaging_quickstart_runs_as_written() {
23	    let home = tempfile::tempdir().expect("tempdir");
24	    let home = home.path();
25
26	    // ---- step 2: bring an agent online (`spt poll bob`) ----
27	    let mut bob = spt()
28	        .args(["poll", "bob"])
29	        .env("SPT_HOME", home)
30	        .stdout(Stdio::piped())
31	        .stderr(Stdio::piped())
32	        .spawn()
33	        .expect("spawn poll bob");
34	    // READY:bob is the online signal (stderr).
35	    let mut bob_err = BufReader::new(bob.stderr.take().unwrap());
36	    let mut line = String::new();
37	    bob_err.read_line(&mut line).expect("read READY line");
38	    assert!(line.contains("READY:bob"), "expected READY:bob, got {line:?}");
39
40	    // ---- step 3: live send from alice ----
41	    let out = send(home, &["send", "bob", "--from", "alice"], "hello bob - alice here");
42	    assert!(
43	        out.contains("SENT:bob"),
44	        "live delivery must report SENT:bob, got {out:?}"
45	    );
46	    // The documented receive shape: routing header + body on bob's stdout.
47	    let mut bob_out = BufReader::new(bob.stdout.take().unwrap());
48	    let mut l1 = String::new();
49	    let mut l2 = String::new();
50	    bob_out.read_line(&mut l1).expect("read header");
51	    bob_out.read_line(&mut l2).expect("read body");
52	    assert!(l1.contains("__REPLY_TO__:alice"), "header line, got {l1:?}");
53	    assert!(l2.contains("hello bob - alice here"), "body line, got {l2:?}");
54
55	    // ---- step 4: bob goes away; the send spools ----
56	    bob.kill().expect("stop bob");
57	    let _ = bob.wait();
58	    let out = send(home, &["send", "bob", "--from", "alice"], "ping while you were away");
59	    assert!(
60	        out.contains("QUEUED:bob"),
61	        "offline delivery must report QUEUED:bob, got {out:?}"
62	    );
63
64	    // ---- bob comes back: the backlog drains ----
65	    let drained = spt()
66	        .args(["poll", "bob", "--once"])
67	        .env("SPT_HOME", home)
68	        .output()
69	        .expect("poll --once");
70	    let stdout = String::from_utf8_lossy(&drained.stdout);
71	    assert!(
72	        stdout.contains("ping while you were away"),
73	        "backlog must drain on return, got {stdout:?}"
74	    );
75	}
76
77	/// Run a one-shot spt command with `body` on stdin, returning stdout+stderr.
78	fn send(home: &std::path::Path, args: &[&str], body: &str) -> String {
79	    let mut child = spt()
80	        .args(args)
81	        .env("SPT_HOME", home)
82	        .stdin(Stdio::piped())
83	        .stdout(Stdio::piped())
84	        .stderr(Stdio::piped())
85	        .spawn()
86	        .expect("spawn spt");
87	    {
88	        use std::io::Write;
89	        child
90	            .stdin
91	            .take()
92	            .unwrap()
93	            .write_all(body.as_bytes())
94	            .expect("write body");
95	    }
96	    // Bounded read (KNOWN-HAZARDS 5.6 posture): a hang here means a detached
97	    // child inherited the pipe.
98	    let (tx, rx) = std::sync::mpsc::channel();
99	    std::thread::spawn(move || {
100	        let out = child.wait_with_output();
101	        let _ = tx.send(out);
102	    });
103	    let out = rx
104	        .recv_timeout(Duration::from_secs(30))
105	        .expect("spt call must complete")
106	        .expect("spt runs");
107	    let mut s = String::from_utf8_lossy(&out.stdout).into_owned();
108	    let mut e = String::new();
109	    let _ = std::io::Cursor::new(out.stderr).read_to_string(&mut e);
110	    s.push_str(&e);
111	    s
112	}
113

//! M6-D4 quickstart rung — the published human quickstart
//! (docs-site/src/quickstart/messaging.md) executed step for step against the
//! real binary: poll brings bob online → live send delivers (`SENT`) and the
//! body + `__REPLY_TO__` header land on bob's stdout → bob goes away → send
//! spools (`QUEUED`) → `poll --once` drains the backlog. If the documented
//! flow stops working, this fails — the doc cannot silently rot (REQ-DOCS-2's
//! "runnable, deterministic" promise, CI-run on both platforms).
//!
//! Hermetic: isolated SPT_HOME per run; not env-gated (cheap, local-only).
//
// [int->REQ-DOCS-2]

use std::io::{BufRead, BufReader, Read};
use std::process::{Command, Stdio};
use std::time::Duration;

fn spt() -> Command {
    Command::new(env!("CARGO_BIN_EXE_spt"))
}

#[test]
fn published_messaging_quickstart_runs_as_written() {
    let home = tempfile::tempdir().expect("tempdir");
    let home = home.path();

    // ---- step 2: bring an agent online (`spt poll bob`) ----
    let mut bob = spt()
        .args(["poll", "bob"])
        .env("SPT_HOME", home)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn poll bob");
    // READY:bob is the online signal (stderr).
    let mut bob_err = BufReader::new(bob.stderr.take().unwrap());
    let mut line = String::new();
    bob_err.read_line(&mut line).expect("read READY line");
    assert!(line.contains("READY:bob"), "expected READY:bob, got {line:?}");

    // ---- step 3: live send from alice ----
    let out = send(home, &["send", "bob", "--from", "alice"], "hello bob - alice here");
    assert!(
        out.contains("SENT:bob"),
        "live delivery must report SENT:bob, got {out:?}"
    );
    // The documented receive shape: routing header + body on bob's stdout.
    let mut bob_out = BufReader::new(bob.stdout.take().unwrap());
    let mut l1 = String::new();
    let mut l2 = String::new();
    bob_out.read_line(&mut l1).expect("read header");
    bob_out.read_line(&mut l2).expect("read body");
    assert!(l1.contains("__REPLY_TO__:alice"), "header line, got {l1:?}");
    assert!(l2.contains("hello bob - alice here"), "body line, got {l2:?}");

    // ---- step 4: bob goes away; the send spools ----
    bob.kill().expect("stop bob");
    let _ = bob.wait();
    let out = send(home, &["send", "bob", "--from", "alice"], "ping while you were away");
    assert!(
        out.contains("QUEUED:bob"),
        "offline delivery must report QUEUED:bob, got {out:?}"
    );

    // ---- bob comes back: the backlog drains ----
    let drained = spt()
        .args(["poll", "bob", "--once"])
        .env("SPT_HOME", home)
        .output()
        .expect("poll --once");
    let stdout = String::from_utf8_lossy(&drained.stdout);
    assert!(
        stdout.contains("ping while you were away"),
        "backlog must drain on return, got {stdout:?}"
    );
}

/// Run a one-shot spt command with `body` on stdin, returning stdout+stderr.
fn send(home: &std::path::Path, args: &[&str], body: &str) -> String {
    let mut child = spt()
        .args(args)
        .env("SPT_HOME", home)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn spt");
    {
        use std::io::Write;
        child
            .stdin
            .take()
            .unwrap()
            .write_all(body.as_bytes())
            .expect("write body");
    }
    // Bounded read (KNOWN-HAZARDS 5.6 posture): a hang here means a detached
    // child inherited the pipe.
    let (tx, rx) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        let out = child.wait_with_output();
        let _ = tx.send(out);
    });
    let out = rx
        .recv_timeout(Duration::from_secs(30))
        .expect("spt call must complete")
        .expect("spt runs");
    let mut s = String::from_utf8_lossy(&out.stdout).into_owned();
    let mut e = String::new();
    let _ = std::io::Cursor::new(out.stderr).read_to_string(&mut e);
    s.push_str(&e);
    s
}


1	[package]
2	name = "spt"
3	version.workspace = true
4	edition = "2021"
5	publish = false
6	description = "spt-core CLI: the single `spt` binary. Thin command layer over spt-msg (send/ring/ready/poll/list/stop/whoami) + the harness-contract `api` surface (spt-runtime)."
7
8	[[bin]]
9	name = "spt"
10	path = "src/main.rs"
11
12	[dependencies]
13	spt-proto = { path = "../spt-proto" }
14	spt-store = { path = "../spt-store" }
15	spt-msg = { path = "../spt-msg" }
16	spt-runtime = { path = "../spt-runtime" }
17	spt-live = { path = "../spt-live" }
18	spt-daemon = { path = "../spt-daemon" }
19	# The reference binary ships the WAN transport on by default (ADR-0002). D2g reuses its
20	# RFC-6238 TotpSeed (spt_net::net::pairing::totp) to render a subnet's local pairing
21	# code — reused, not reimplemented, so the CLI code matches the ceremony's exactly.
22	spt-net = { path = "../spt-net" }
23	# `wrap_help`: wrap long help text to the terminal width with a hanging indent
24	# (without it clap emits unwrapped lines and the terminal hard-wraps to col 0).
25	clap = { version = "4", features = ["derive", "wrap_help"] }
26	# The WAN send leg (D9-1-4) serializes the dial address + reads registry
27	# snapshots - same pinned shape as spt-daemon's.
28	serde_json = { version = "1", features = ["preserve_order"] }
29
30	# OS privilege-elevation probe for the node-bound code fetch (REQ-PAIR-6). Only the
31	# binary pulls the OS deps; the core stays dep-minimal. Matches the workspace's existing
32	# libc/windows-sys usage (spt-msg, spt-store) rather than the heavyweight `windows`.
33	[target.'cfg(unix)'.dependencies]
34	libc = "0.2"
35
36	[target.'cfg(windows)'.dependencies]
37	windows-sys = { version = "0.59", features = [
38	    "Win32_Foundation",
39	    "Win32_Security",
40	    "Win32_System_Threading",
41	] }
42
43	[dev-dependencies]
44	tempfile = "3"
45	toml = "0.8"
46	# install_e2e stages the built binary as a release asset (SHA256SUMS) for the
47	# D2 installer-script rung — same sha2 the workspace already carries.
48	sha2 = "0.10"
49

[package]
name = "spt"
version.workspace = true
edition = "2021"
publish = false
description = "spt-core CLI: the single `spt` binary. Thin command layer over spt-msg (send/ring/ready/poll/list/stop/whoami) + the harness-contract `api` surface (spt-runtime)."

[[bin]]
name = "spt"
path = "src/main.rs"

[dependencies]
spt-proto = { path = "../spt-proto" }
spt-store = { path = "../spt-store" }
spt-msg = { path = "../spt-msg" }
spt-runtime = { path = "../spt-runtime" }
spt-live = { path = "../spt-live" }
spt-daemon = { path = "../spt-daemon" }
# The reference binary ships the WAN transport on by default (ADR-0002). D2g reuses its
# RFC-6238 TotpSeed (spt_net::net::pairing::totp) to render a subnet's local pairing
# code — reused, not reimplemented, so the CLI code matches the ceremony's exactly.
spt-net = { path = "../spt-net" }
# `wrap_help`: wrap long help text to the terminal width with a hanging indent
# (without it clap emits unwrapped lines and the terminal hard-wraps to col 0).
clap = { version = "4", features = ["derive", "wrap_help"] }
# The WAN send leg (D9-1-4) serializes the dial address + reads registry
# snapshots - same pinned shape as spt-daemon's.
serde_json = { version = "1", features = ["preserve_order"] }

# OS privilege-elevation probe for the node-bound code fetch (REQ-PAIR-6). Only the
# binary pulls the OS deps; the core stays dep-minimal. Matches the workspace's existing
# libc/windows-sys usage (spt-msg, spt-store) rather than the heavyweight `windows`.
[target.'cfg(unix)'.dependencies]
libc = "0.2"

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.59", features = [
    "Win32_Foundation",
    "Win32_Security",
    "Win32_System_Threading",
] }

[dev-dependencies]
tempfile = "3"
toml = "0.8"
# install_e2e stages the built binary as a release asset (SHA256SUMS) for the
# D2 installer-script rung — same sha2 the workspace already carries.
sha2 = "0.10"


Plan clear. Tasks:


ToolSearch

Question: select:TaskCreate,TaskUpdate


Question: select:TaskCreate,TaskUpdate


TaskCreate

Activate REQ-SUBNET-1 + REQ-DOCS-6 stages in traceable-reqs.toml

Set required_stages = ["impl","unit"] (rule 5 activation at M7 D1 start); int stages added later (D5).


Task #1 created successfully: Activate REQ-SUBNET-1 + REQ-DOCS-6 stages in traceable-reqs.toml


Build spt subnet namespace: status (basic), create (+QR), show-totp; delete pair

cli.rs: Subnet cmd with optional subcommand (bare = status view). create <NAME> elevation-gated mint + code + otpauth + unicode QR + join hint. show-totp [NAME] positional, REQ-PAIR-3/6 semantics. Basic status render (names/node/endpoint counts, hint footer, zero-subnet text — hybrid liveness + --nodes is D2). Remove Cmd::Pair/PairCmd. Add qrcode crate dep.


Task #2 created successfully: Build spt subnet namespace: status (basic), create (+QR), show-totp; delete pair


Remove spt poll; add ready --once

Drop Cmd::Poll, add --once to Ready (run_listen absorbs). Update AGENT_COMMANDS. quickstart_e2e.rs switches poll→ready (full rewrite is D5).


Task #3 created successfully: Remove spt poll; add ready --once

Remove spt poll; add ready --once


Add spt how-to command with ready/send topics

In-binary agent instructions (REQ-DOCS-6). Bare how-to lists topics; topics: ready (background-task guidance, --once fallback loop, reply mechanics), send (send/ring, reply-to, SENT vs QUEUED). Agent-commands help group.


Task #4 created successfully: Add spt how-to command with ready/send topics


Update unit tests + regen cli/reference.md + green checks

Update subcommands_parse, pair→subnet tests, how-to parse tests; help_groups_cover_every_command passes. cargo test -p spt, xtask gen, traceable-reqs check, commit.


Task #5 created successfully: Update unit tests + regen cli/reference.md + green checks


Updated task #1 status


Grep

pub fn load_snapshots|pub struct|pub fn instances|pub fn rows|pub fn nodes


357-/// Load every per-subnet registry snapshot under `dir`. Missing dir or an
358-/// unparseable snapshot degrade to absent (the reader's stale-tolerant
359-/// contract) — resolution then simply finds nothing there.
360:pub fn load_snapshots(dir: &Path) -> BTreeMap<String, SubnetRegistry> {
361-    let mut out = BTreeMap::new();
362-    let Ok(entries) = std::fs::read_dir(dir) else {
363-        return out;


pub struct SubnetRegistry|impl SubnetRegistry|pub fn|pub [a-z_]+:


74:    pub node: String,
76:    pub status: Status,
79:    pub epoch: u64,
89:    pub resources: Option<String>,
102:    pub last_active_ms: Option<u64>,
116:    pub shell_adapters: Vec<String>,
122:pub struct SubnetRegistry {
139:impl SubnetRegistry {
141:    pub fn new() -> SubnetRegistry {
146:    pub fn instances(&self, endpoint_id: &str) -> &[Instance] {
151:    pub fn endpoint_ids(&self) -> impl Iterator<Item = &str> {
162:    pub fn merge_instance(&mut self, endpoint_id: &str, incoming: Instance) -> MergeOutcome {
184:    pub fn routable(self) -> bool {
195:    pub endpoint_id: String,
196:    pub node: String,
197:    pub status: Status,
200:    pub resources: Option<String>,
210:pub fn resource_projection(
241:    pub endpoint_id: String,
243:    pub nodes: Vec<String>,
259:impl SubnetRegistry {
281:    pub fn join_endpoint(
343:pub fn rename_endpoint(
395:pub fn advertise_if_visible(
434:impl SubnetRegistry {
441:    pub fn resolve(&self, address: &Address, local_node: &str) -> Resolution {
452:    pub fn resolve_visible(
496:pub fn resolve_across(
510:pub fn resolve_across_visible(


118
119	/// The per-subnet registry: `endpoint_id → [instance...]`. `BTreeMap`/`Vec` for stable,
120	/// deterministic serde ordering (no hash-seed nondeterminism across nodes).
121	#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
122	pub struct SubnetRegistry {
123	    #[serde(default)]
124	    by_id: BTreeMap<String, Vec<Instance>>,
125	}
126
127	/// What [`merge_instance`] did with an update — useful for replication accounting and
128	/// for asserting the lease in tests.
129	#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130	pub enum MergeOutcome {
131	    /// First instance for this `(endpoint_id, node)` — added.
132	    Inserted,
133	    /// A strictly-newer epoch superseded the stored instance.
134	    Updated,
135	    /// Equal or lower epoch — dropped as stale (the lease at work).
136	    Stale,
137	}
138
139	impl SubnetRegistry {
140	    /// An empty registry.
141	    pub fn new() -> SubnetRegistry {
142	        SubnetRegistry::default()
143	    }
144
145	    /// The instances known for an endpoint id (empty slice if none).
146	    pub fn instances(&self, endpoint_id: &str) -> &[Instance] {
147	        self.by_id.get(endpoint_id).map(Vec::as_slice).unwrap_or(&[])
148	    }
149
150	    /// Every endpoint id in the registry (sorted, by `BTreeMap`).
151	    pub fn endpoint_ids(&self) -> impl Iterator<Item = &str> {
152	        self.by_id.keys().map(String::as_str)
153	    }
154
155	    /// Merge an instance update under the epoch lease (#8). For the matching
156	    /// `(endpoint_id, node)`: a **strictly greater** epoch supersedes the stored state
157	    /// ([`Updated`](MergeOutcome::Updated)); an equal/lower epoch is dropped
158	    /// ([`Stale`](MergeOutcome::Stale)) so a lagging `Active` cannot overwrite a newer
159	    /// `Offline`; a new node is added ([`Inserted`](MergeOutcome::Inserted)). Distinct
160	    /// nodes coexist as separate rows — cross-instance ambiguity is the resolver's call.
161	    // [impl->REQ-HAZARD-REGISTRY-EPOCH-LEASE]
162	    pub fn merge_instance(&mut self, endpoint_id: &str, incoming: Instance) -> MergeOutcome {

/// The per-subnet registry: `endpoint_id → [instance...]`. `BTreeMap`/`Vec` for stable,
/// deterministic serde ordering (no hash-seed nondeterminism across nodes).
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct SubnetRegistry {
    #[serde(default)]
    by_id: BTreeMap<String, Vec<Instance>>,
}

/// What [`merge_instance`] did with an update — useful for replication accounting and
/// for asserting the lease in tests.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MergeOutcome {
    /// First instance for this `(endpoint_id, node)` — added.
    Inserted,
    /// A strictly-newer epoch superseded the stored instance.
    Updated,
    /// Equal or lower epoch — dropped as stale (the lease at work).
    Stale,
}

impl SubnetRegistry {
    /// An empty registry.
    pub fn new() -> SubnetRegistry {
        SubnetRegistry::default()
    }

    /// The instances known for an endpoint id (empty slice if none).
    pub fn instances(&self, endpoint_id: &str) -> &[Instance] {
        self.by_id.get(endpoint_id).map(Vec::as_slice).unwrap_or(&[])
    }

    /// Every endpoint id in the registry (sorted, by `BTreeMap`).
    pub fn endpoint_ids(&self) -> impl Iterator<Item = &str> {
        self.by_id.keys().map(String::as_str)
    }

    /// Merge an instance update under the epoch lease (#8). For the matching
    /// `(endpoint_id, node)`: a **strictly greater** epoch supersedes the stored state
    /// ([`Updated`](MergeOutcome::Updated)); an equal/lower epoch is dropped
    /// ([`Stale`](MergeOutcome::Stale)) so a lagging `Active` cannot overwrite a newer
    /// `Offline`; a new node is added ([`Inserted`](MergeOutcome::Inserted)). Distinct
    /// nodes coexist as separate rows — cross-instance ambiguity is the resolver's call.
    // [impl->REQ-HAZARD-REGISTRY-EPOCH-LEASE]
    pub fn merge_instance(&mut self, endpoint_id: &str, incoming: Instance) -> MergeOutcome {


Activate stages:


Edit

title = "spt subnet noun namespace: status view (bare + status [NAME] [--nodes]), create (QR/otpauth), show-totp; spt pair deleted"
required_stages = []  # M7 D1/D2 (plan decisions 3, 5, 6, 11).

title = "spt subnet noun namespace: status view (bare + status [NAME] [--nodes]), create (QR/otpauth), show-totp; spt pair deleted"
required_stages = ["impl", "unit"]  # activated M7 D1 (2026-06-05): namespace + create/show-totp ports land; D2 adds the full status views. M7 D1/D2 (plan decisions 3, 5, 6, 11).


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

title = "spt subnet noun namespace: status view (bare + status [NAME] [--nodes]), create (QR/otpauth), show-totp; spt pair deleted"
required_stages = []  # M7 D1/D2 (plan decisions 3, 5, 6, 11).

title = "spt subnet noun namespace: status view (bare + status [NAME] [--nodes]), create (QR/otpauth), show-totp; spt pair deleted"
required_stages = ["impl", "unit"]  # activated M7 D1 (2026-06-05): namespace + create/show-totp ports land; D2 adds the full status views. M7 D1/D2 (plan decisions 3, 5, 6, 11).

-required_stages = []  # M7 D1/D2 (plan decisions 3, 5, 6, 11). Bare `spt subnet` = flagless status (names, paired-node counts, endpoint counts; never epochs/codes; hint footer; zero-subnet explanatory text). `status [NAME] [--nodes]` adds per-node rows (label, online/offline via gossip-recency + probe-stale hybrid, online/total endpoints). `create <NAME>` mints (sole seed-holder) + prints code/otpauth/terminal-QR + join hint. `show-totp [name]` keeps REQ-PAIR-3/6 semantics. `spt pair` namespace removed outright (no shim).

+required_stages = ["impl", "unit"]  # activated M7 D1 (2026-06-05): namespace + create/show-totp ports land; D2 adds the full status views. M7 D1/D2 (plan decisions 3, 5, 6, 11). Bare `spt subnet` = flagless status (names, paired-node counts, endpoint counts; never epochs/codes; hint footer; zero-subnet explanatory text). `status [NAME] [--nodes]` adds per-node rows (label, online/offline via gossip-recency + probe-stale hybrid, online/total endpoints). `create <NAME>` mints (sole seed-holder) + prints code/otpauth/terminal-QR + join hint. `show-totp [name]` keeps REQ-PAIR-3/6 semantics. `spt pair` namespace removed outright (no shim).

title = "Guided join e2e: spt subnet join CLI initiator + always-on daemon pairing responder"


title = "spt how-to <topic>: in-binary task-oriented agent instructions (anti-drift; quickstart prompts point agents at it)"
required_stages = []  # M7 D1/D4 (plan decision 12).

title = "spt how-to <topic>: in-binary task-oriented agent instructions (anti-drift; quickstart prompts point agents at it)"
required_stages = ["impl", "unit"]  # activated M7 D1 (2026-06-05): command + ready/send topics land; int (quickstart_e2e how-to assert) added at D5. M7 D1/D4 (plan decision 12).


title = "spt how-to <topic>: in-binary task-oriented agent instructions (anti-drift; quickstart prompts point agents at it)"
required_stages = []  # M7 D1/D4 (plan decision 12).

title = "spt how-to <topic>: in-binary task-oriented agent instructions (anti-drift; quickstart prompts point agents at it)"
required_stages = ["impl", "unit"]  # activated M7 D1 (2026-06-05): command + ready/send topics land; int (quickstart_e2e how-to assert) added at D5. M7 D1/D4 (plan decision 12).

-required_stages = []  # M7 D1/D4 (plan decision 12). Visible command, Agent-commands help group. Topics v1: `ready` (background-task guidance, --once fallback loop, reply mechanics) + `send` (send/ring, reply-to, SENT vs QUEUED); bare `how-to` lists topics. Single source = the binary — docs-site never duplicates the content (the DOCS-STRATEGY anti-drift posture applied to agent guidance); quickstart prompt blocks instruct agents to run it and follow. int = quickstart_e2e asserts the how-to output names the commands the guide relies on.

+required_stages = ["impl", "unit"]  # activated M7 D1 (2026-06-05): command + ready/send topics land; int (quickstart_e2e how-to assert) added at D5. M7 D1/D4 (plan decision 12). Visible command, Agent-commands help group. Topics v1: `ready` (background-task guidance, --once fallback loop, reply mechanics) + `send` (send/ring, reply-to, SENT vs QUEUED); bare `how-to` lists topics. Single source = the binary — docs-site never duplicates the content (the DOCS-STRATEGY anti-drift posture applied to agent guidance); quickstart prompt blocks instruct agents to run it and follow. int = quickstart_e2e asserts the how-to output names the commands the guide relies on.

# ── Endpoint access control (ADR-0009, 2026-06-02; activate M4 node-tier) ──


Updated task #2 status


Now cli.rs surgery. Cmd enum first:


    /// Become reachable: register the perch and listen (blocks).
    Ready {
        /// This agent's perch id.
        id: String,
        /// Home subnet for a NEW endpoint (required on a multi-subnet
        /// node — home is assigned at creation, never guessed).
        #[arg(long)]
        subnet: Option<String>,
    },
    /// Receive messages: drain backlog, then listen.
    ///
    /// Each received message prints to stdout.
    Poll {
        /// This agent's perch id.
        id: String,
        /// Run a single drain+receive cycle, then exit (legacy one-shot).
        #[arg(long)]
        once: bool,
        /// Home subnet for a NEW endpoint (see `ready`).
        #[arg(long)]
        subnet: Option<String>,
    },

    /// Become reachable: register the perch and listen (blocks).
    ///
    /// Drains the spooled backlog first; each received message prints to
    /// stdout. With --once, runs a single drain+receive cycle and exits.
    Ready {
        /// This agent's perch id.
        id: String,
        /// Run a single drain+receive cycle, then exit (one-shot fallback
        /// for harnesses that cannot host a long-running listener).
        #[arg(long)]
        once: bool,
        /// Home subnet for a NEW endpoint (required on a multi-subnet
        /// node — home is assigned at creation, never guessed).
        #[arg(long)]
        subnet: Option<String>,
    },


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

    /// Become reachable: register the perch and listen (blocks).
    Ready {
        /// This agent's perch id.
        id: String,
        /// Home subnet for a NEW endpoint (required on a multi-subnet
        /// node — home is assigned at creation, never guessed).
        #[arg(long)]
        subnet: Option<String>,
    },
    /// Receive messages: drain backlog, then listen.
    ///
    /// Each received message prints to stdout.
    Poll {
        /// This agent's perch id.
        id: String,
        /// Run a single drain+receive cycle, then exit (legacy one-shot).
        #[arg(long)]
        once: bool,
        /// Home subnet for a NEW endpoint (see `ready`).
        #[arg(long)]
        subnet: Option<String>,
    },

    /// Become reachable: register the perch and listen (blocks).
    ///
    /// Drains the spooled backlog first; each received message prints to
    /// stdout. With --once, runs a single drain+receive cycle and exits.
    Ready {
        /// This agent's perch id.
        id: String,
        /// Run a single drain+receive cycle, then exit (one-shot fallback
        /// for harnesses that cannot host a long-running listener).
        #[arg(long)]
        once: bool,
        /// Home subnet for a NEW endpoint (required on a multi-subnet
        /// node — home is assigned at creation, never guessed).
        #[arg(long)]
        subnet: Option<String>,
    },

+    /// Drains the spooled backlog first; each received message prints to

+    /// stdout. With --once, runs a single drain+receive cycle and exits.

+        /// Run a single drain+receive cycle, then exit (one-shot fallback

+        /// for harnesses that cannot host a long-running listener).

-    /// Receive messages: drain backlog, then listen.

-    /// Each received message prints to stdout.

-        /// Run a single drain+receive cycle, then exit (legacy one-shot).

-        /// Home subnet for a NEW endpoint (see `ready`).

/// List this node's perches (id, state, address, ready, alive).

/// Soft-stop a perch (spool preserved).


    /// Subnet pairing: show this node's current pairing code.
    ///
    /// Also mints new subnets (`show-totp --create-new`). Pairing is how
    /// machines join a private subnet.
    Pair {
        #[command(subcommand)]
        action: PairCmd,
    },

    /// Subnet membership: status, create, show-totp.
    ///
    /// A subnet is a private group of paired machines — your agents reach
    /// each other across every member node. Bare `spt subnet` shows the
    /// membership status view.
    Subnet {
        #[command(subcommand)]
        action: Option<SubnetCmd>,
    },
    /// Task-oriented instructions for agents: `how-to <topic>`.
    ///
    /// The binary's own usage guidance, written for an agent to read and
    /// follow. Bare `how-to` lists the topics.
    #[command(name = "how-to")]
    HowTo {
        /// The topic to print (omit to list available topics).
        topic: Option<String>,
    },


    /// Subnet pairing: show this node's current pairing code.
    ///
    /// Also mints new subnets (`show-totp --create-new`). Pairing is how
    /// machines join a private subnet.
    Pair {
        #[command(subcommand)]
        action: PairCmd,
    },

    /// Subnet membership: status, create, show-totp.
    ///
    /// A subnet is a private group of paired machines — your agents reach
    /// each other across every member node. Bare `spt subnet` shows the
    /// membership status view.
    Subnet {
        #[command(subcommand)]
        action: Option<SubnetCmd>,
    },
    /// Task-oriented instructions for agents: `how-to <topic>`.
    ///
    /// The binary's own usage guidance, written for an agent to read and
    /// follow. Bare `how-to` lists the topics.
    #[command(name = "how-to")]
    HowTo {
        /// The topic to print (omit to list available topics).
        topic: Option<String>,
    },

/// Resolved from $OWL_SESSION_ID / $SPT_AGENT_ID.

-    /// Subnet pairing: show this node's current pairing code.

+    /// Subnet membership: status, create, show-totp.

-    /// Also mints new subnets (`show-totp --create-new`). Pairing is how

-    /// machines join a private subnet.

+    /// A subnet is a private group of paired machines — your agents reach

+    /// each other across every member node. Bare `spt subnet` shows the

+    /// Task-oriented instructions for agents: `how-to <topic>`.

+    /// The binary's own usage guidance, written for an agent to read and

+    /// follow. Bare `how-to` lists the topics.

+        /// The topic to print (omit to list available topics).

/// Show a hosted session's live activity buffer (PTY digest).

/// The at-a-glance "what is this agent doing now" view. Pulls a


/// `spt pair <action>` subcommands.
#[derive(Subcommand)]
enum PairCmd {
    /// Show a subnet's current 6-digit pairing code + `otpauth://` URI —
    /// gated behind OS elevation (or read the code from your authenticator
    /// app). With no flag the node's sole subnet is used; if it holds
    /// several, `--subnet <name>` is required (never guessed).
    /// `--create-new <name>` mints a fresh subnet first.
    ShowTotp {
        /// Which subnet's code to show. Required only when the node holds several.
        #[arg(long)]
        subnet: Option<String>,
        /// Mint a fresh subnet with this name (this node becomes the sole seed-holder),
        /// then show its code. Mutually exclusive with `--subnet`.
        #[arg(long, value_name = "NAME", conflicts_with = "subnet")]
        create_new: Option<String>,
    },
}

/// `spt subnet <action>` subcommands (REQ-SUBNET-1). Noun namespace with
/// subcommand verbs and positional subnet names — never `--subnet` flags
/// (M7 plan decision 3).
#[derive(Subcommand)]
enum SubnetCmd {
    /// Show subnet membership: name, paired nodes, endpoints.
    ///
    /// Never prints seeds, epochs, or pairing codes. Bare `spt subnet` is
    /// the same view.
    Status {
        /// Limit to one subnet (all member subnets otherwise).
        name: Option<String>,
    },
    /// Mint a fresh subnet and print its joining material.
    ///
    /// This node becomes the sole seed-holder. Prints the current 6-digit
    /// code, the `otpauth://` provisioning URI, and a terminal QR of it.
    /// Gated behind OS elevation (the seed-reveal path).
    Create {
        /// The new subnet's name.
        name: String,
    },
    /// Show a subnet's current 6-digit pairing code + `otpauth://` URI.
    ///
    /// Gated behind OS elevation (or read the code from your authenticator
    /// app). With no name the node's sole subnet is used; if it holds
    /// several, the name is required (never guessed).
    ShowTotp {
        /// Which subnet's code to show. Required only when the node holds several.
        name: Option<String>,
    },
}


/// `spt pair <action>` subcommands.
#[derive(Subcommand)]
enum PairCmd {
    /// Show a subnet's current 6-digit pairing code + `otpauth://` URI —
    /// gated behind OS elevation (or read the code from your authenticator
    /// app). With no flag the node's sole subnet is used; if it holds
    /// several, `--subnet <name>` is required (never guessed).
    /// `--create-new <name>` mints a fresh subnet first.
    ShowTotp {
        /// Which subnet's code to show. Required only when the node holds several.
        #[arg(long)]
        subnet: Option<String>,
        /// Mint a fresh subnet with this name (this node becomes the sole seed-holder),
        /// then show its code. Mutually exclusive with `--subnet`.
        #[arg(long, value_name = "NAME", conflicts_with = "subnet")]
        create_new: Option<String>,
    },
}

/// `spt subnet <action>` subcommands (REQ-SUBNET-1). Noun namespace with
/// subcommand verbs and positional subnet names — never `--subnet` flags
/// (M7 plan decision 3).
#[derive(Subcommand)]
enum SubnetCmd {
    /// Show subnet membership: name, paired nodes, endpoints.
    ///
    /// Never prints seeds, epochs, or pairing codes. Bare `spt subnet` is
    /// the same view.
    Status {
        /// Limit to one subnet (all member subnets otherwise).
        name: Option<String>,
    },
    /// Mint a fresh subnet and print its joining material.
    ///
    /// This node becomes the sole seed-holder. Prints the current 6-digit
    /// code, the `otpauth://` provisioning URI, and a terminal QR of it.
    /// Gated behind OS elevation (the seed-reveal path).
    Create {
        /// The new subnet's name.
        name: String,
    },
    /// Show a subnet's current 6-digit pairing code + `otpauth://` URI.
    ///
    /// Gated behind OS elevation (or read the code from your authenticator
    /// app). With no name the node's sole subnet is used; if it holds
    /// several, the name is required (never guessed).
    ShowTotp {
        /// Which subnet's code to show. Required only when the node holds several.
        name: Option<String>,
    },
}

+/// `spt subnet <action>` subcommands (REQ-SUBNET-1). Noun namespace with

+/// subcommand verbs and positional subnet names — never `--subnet` flags

-    /// Show a subnet's current 6-digit pairing code + `otpauth://` URI —

-    /// gated behind OS elevation (or read the code from your authenticator

-    /// app). With no flag the node's sole subnet is used; if it holds

-    /// several, `--subnet <name>` is required (never guessed).

-    /// `--create-new <name>` mints a fresh subnet first.

+    /// Show subnet membership: name, paired nodes, endpoints.

+    /// Never prints seeds, epochs, or pairing codes. Bare `spt subnet` is

+        /// Limit to one subnet (all member subnets otherwise).

+    /// Mint a fresh subnet and print its joining material.

+    /// This node becomes the sole seed-holder. Prints the current 6-digit

+    /// code, the `otpauth://` provisioning URI, and a terminal QR of it.

+    /// Gated behind OS elevation (the seed-reveal path).

+    /// Show a subnet's current 6-digit pairing code + `otpauth://` URI.

+    /// Gated behind OS elevation (or read the code from your authenticator

+    /// app). With no name the node's sole subnet is used; if it holds

+    /// several, the name is required (never guessed).

-        /// Mint a fresh subnet with this name (this node becomes the sole seed-holder),

-        /// then show its code. Mutually exclusive with `--subnet`.

-        #[arg(long, value_name = "NAME", conflicts_with = "subnet")]


Now AGENT_COMMANDS + dispatch:


const AGENT_COMMANDS: &[&str] =
    &["api", "notify", "poll", "ready", "resources", "ring", "send", "shell", "shutdown", "whoami"];

const AGENT_COMMANDS: &[&str] =
    &["api", "how-to", "notify", "ready", "resources", "ring", "send", "shell", "shutdown", "whoami"];


const AGENT_COMMANDS: &[&str] =
    &["api", "notify", "poll", "ready", "resources", "ring", "send", "shell", "shutdown", "whoami"];

const AGENT_COMMANDS: &[&str] =
    &["api", "how-to", "notify", "ready", "resources", "ring", "send", "shell", "shutdown", "whoami"];

/// Grouping is render-time only (clap has no native subcommand help

/// headings); `help_groups_cover_every_command` keeps this set honest.

-    &["api", "notify", "poll", "ready", "resources", "ring", "send", "shell", "shutdown", "whoami"];

+    &["api", "how-to", "notify", "ready", "resources", "ring", "send", "shell", "shutdown", "whoami"];

/// Build the top-level help template: the default clap layout, with the flat

/// `Commands:` block replaced by two alphabetized groups (user-facing first,


        Cmd::Ready { id, subnet } => run_listen(&id, false, subnet.as_deref()),
        Cmd::Poll { id, once, subnet } => run_listen(&id, once, subnet.as_deref()),
        Cmd::List => cmd_list(),
        Cmd::Stop { id } => cmd_stop(&id),
        Cmd::Whoami => cmd_whoami(),
        Cmd::Pair { action } => match action {
            PairCmd::ShowTotp { subnet, create_new } => cmd_pair_show_totp(subnet, create_new),
        },

        Cmd::Ready { id, once, subnet } => run_listen(&id, once, subnet.as_deref()),
        Cmd::List => cmd_list(),
        Cmd::Stop { id } => cmd_stop(&id),
        Cmd::Whoami => cmd_whoami(),
        Cmd::Subnet { action } => match action.unwrap_or(SubnetCmd::Status { name: None }) {
            SubnetCmd::Status { name } => cmd_subnet_status(name),
            SubnetCmd::Create { name } => cmd_subnet_create(name),
            SubnetCmd::ShowTotp { name } => cmd_subnet_show_totp(name),
        },
        Cmd::HowTo { topic } => cmd_how_to(topic.as_deref()),


        Cmd::Ready { id, subnet } => run_listen(&id, false, subnet.as_deref()),
        Cmd::Poll { id, once, subnet } => run_listen(&id, once, subnet.as_deref()),
        Cmd::List => cmd_list(),
        Cmd::Stop { id } => cmd_stop(&id),
        Cmd::Whoami => cmd_whoami(),
        Cmd::Pair { action } => match action {
            PairCmd::ShowTotp { subnet, create_new } => cmd_pair_show_totp(subnet, create_new),
        },

        Cmd::Ready { id, once, subnet } => run_listen(&id, once, subnet.as_deref()),
        Cmd::List => cmd_list(),
        Cmd::Stop { id } => cmd_stop(&id),
        Cmd::Whoami => cmd_whoami(),
        Cmd::Subnet { action } => match action.unwrap_or(SubnetCmd::Status { name: None }) {
            SubnetCmd::Status { name } => cmd_subnet_status(name),
            SubnetCmd::Create { name } => cmd_subnet_create(name),
            SubnetCmd::ShowTotp { name } => cmd_subnet_show_totp(name),
        },
        Cmd::HowTo { topic } => cmd_how_to(topic.as_deref()),

Cmd::Send { target, from, reply_to, deferred } => cmd_send(target, from, reply_to, deferred),

Cmd::Ring { target, from, timeout } => cmd_ring(target, from, timeout),

-        Cmd::Ready { id, subnet } => run_listen(&id, false, subnet.as_deref()),

-        Cmd::Poll { id, once, subnet } => run_listen(&id, once, subnet.as_deref()),

+        Cmd::Ready { id, once, subnet } => run_listen(&id, once, subnet.as_deref()),

-        Cmd::Pair { action } => match action {

-            PairCmd::ShowTotp { subnet, create_new } => cmd_pair_show_totp(subnet, create_new),

+        Cmd::Subnet { action } => match action.unwrap_or(SubnetCmd::Status { name: None }) {

+            SubnetCmd::Status { name } => cmd_subnet_status(name),

+            SubnetCmd::Create { name } => cmd_subnet_create(name),

+            SubnetCmd::ShowTotp { name } => cmd_subnet_show_totp(name),

+        Cmd::HowTo { topic } => cmd_how_to(topic.as_deref()),

Cmd::Access { action } => cmd_access(action),

Cmd::Grant { action } => cmd_grant(action),

Cmd::Adapter { action } => cmd_adapter(action),


Now the show-totp core message rewording + create/status/how-to functions:


/// `spt pair show-totp` refused the fetch for lack of OS elevation. Distinct from the
/// crate-wide codes (`0` ok · `1` runtime refusal · `2` usage): `3` says the fetch was
/// *refused, not failed* — re-run elevated, or read the code from your authenticator app.
const EXIT_NOT_ELEVATED: i32 = 3;

/// `spt subnet show-totp`/`create` refused the fetch for lack of OS elevation. Distinct
/// from the crate-wide codes (`0` ok · `1` runtime refusal · `2` usage): `3` says the
/// fetch was *refused, not failed* — re-run elevated, or read the code from your
/// authenticator app.
const EXIT_NOT_ELEVATED: i32 = 3;


/// `spt pair show-totp` refused the fetch for lack of OS elevation. Distinct from the
/// crate-wide codes (`0` ok · `1` runtime refusal · `2` usage): `3` says the fetch was
/// *refused, not failed* — re-run elevated, or read the code from your authenticator app.
const EXIT_NOT_ELEVATED: i32 = 3;

/// `spt subnet show-totp`/`create` refused the fetch for lack of OS elevation. Distinct
/// from the crate-wide codes (`0` ok · `1` runtime refusal · `2` usage): `3` says the
/// fetch was *refused, not failed* — re-run elevated, or read the code from your
/// authenticator app.
const EXIT_NOT_ELEVATED: i32 = 3;

/// issuer label). Stable user-facing string.

-/// `spt pair show-totp` refused the fetch for lack of OS elevation. Distinct from the

-/// crate-wide codes (`0` ok · `1` runtime refusal · `2` usage): `3` says the fetch was

-/// *refused, not failed* — re-run elevated, or read the code from your authenticator app.

+/// `spt subnet show-totp`/`create` refused the fetch for lack of OS elevation. Distinct

+/// from the crate-wide codes (`0` ok · `1` runtime refusal · `2` usage): `3` says the

+/// fetch was *refused, not failed* — re-run elevated, or read the code from your


        Resolved::Ambiguous(names) => ShowResult {
            code: 2,
            out: None,
            msg: format!(
                "AMBIGUOUS_SUBNET: this node holds {} subnets ({}) — pass --subnet <name>",
                names.len(),
                names.join(", ")
            ),
            save: false,
        },
        Resolved::Empty => ShowResult {
            code: 2,
            out: None,
            msg: "NO_SUBNETS: this node belongs to no subnet — pair first, or mint one with --create-new <name>".to_string(),
            save: false,
        },
        Resolved::NotFound(n) => ShowResult {
            code: 2,
            out: None,
            msg: format!("NO_SUCH_SUBNET:{n}"),
            save: false,
        },
        Resolved::Exists(n) => ShowResult {
            code: 2,
            out: None,
            msg: format!("SUBNET_EXISTS:{n} — already a member; drop --create-new to show its code"),
            save: false,
        },

        Resolved::Ambiguous(names) => ShowResult {
            code: 2,
            out: None,
            msg: format!(
                "AMBIGUOUS_SUBNET: this node holds {} subnets ({}) — name one: `spt subnet show-totp <name>`",
                names.len(),
                names.join(", ")
            ),
            save: false,
        },
        Resolved::Empty => ShowResult {
            code: 2,
            out: None,
            msg: "NO_SUBNETS: this node belongs to no subnet — mint one with `spt subnet create <name>`".to_string(),
            save: false,
        },
        Resolved::NotFound(n) => ShowResult {
            code: 2,
            out: None,
            msg: format!("NO_SUCH_SUBNET:{n}"),
            save: false,
        },
        Resolved::Exists(n) => ShowResult {
            code: 2,
            out: None,
            msg: format!("SUBNET_EXISTS:{n} — already a member; `spt subnet show-totp {n}` shows its code"),
            save: false,
        },


        Resolved::Ambiguous(names) => ShowResult {
            code: 2,
            out: None,
            msg: format!(
                "AMBIGUOUS_SUBNET: this node holds {} subnets ({}) — pass --subnet <name>",
                names.len(),
                names.join(", ")
            ),
            save: false,
        },
        Resolved::Empty => ShowResult {
            code: 2,
            out: None,
            msg: "NO_SUBNETS: this node belongs to no subnet — pair first, or mint one with --create-new <name>".to_string(),
            save: false,
        },
        Resolved::NotFound(n) => ShowResult {
            code: 2,
            out: None,
            msg: format!("NO_SUCH_SUBNET:{n}"),
            save: false,
        },
        Resolved::Exists(n) => ShowResult {
            code: 2,
            out: None,
            msg: format!("SUBNET_EXISTS:{n} — already a member; drop --create-new to show its code"),
            save: false,
        },

        Resolved::Ambiguous(names) => ShowResult {
            code: 2,
            out: None,
            msg: format!(
                "AMBIGUOUS_SUBNET: this node holds {} subnets ({}) — name one: `spt subnet show-totp <name>`",
                names.len(),
                names.join(", ")
            ),
            save: false,
        },
        Resolved::Empty => ShowResult {
            code: 2,
            out: None,
            msg: "NO_SUBNETS: this node belongs to no subnet — mint one with `spt subnet create <name>`".to_string(),
            save: false,
        },
        Resolved::NotFound(n) => ShowResult {
            code: 2,
            out: None,
            msg: format!("NO_SUCH_SUBNET:{n}"),
            save: false,
        },
        Resolved::Exists(n) => ShowResult {
            code: 2,
            out: None,
            msg: format!("SUBNET_EXISTS:{n} — already a member; `spt subnet show-totp {n}` shows its code"),
            save: false,
        },

-                "AMBIGUOUS_SUBNET: this node holds {} subnets ({}) — pass --subnet <name>",

+                "AMBIGUOUS_SUBNET: this node holds {} subnets ({}) — name one: `spt subnet show-totp <name>`",

-            msg: "NO_SUBNETS: this node belongs to no subnet — pair first, or mint one with --create-new <name>".to_string(),

+            msg: "NO_SUBNETS: this node belongs to no subnet — mint one with `spt subnet create <name>`".to_string(),

-            msg: format!("SUBNET_EXISTS:{n} — already a member; drop --create-new to show its code"),

+            msg: format!("SUBNET_EXISTS:{n} — already a member; `spt subnet show-totp {n}` shows its code"),


Set required_stages = ["impl","unit"] (rule 5 activation at M7 D1 start); int stages added later (D5).

cli.rs: Subnet cmd with optional subcommand (bare = status view). create <NAME> elevation-gated mint + code + otpauth + unicode QR + join hint. show-totp [NAME] positional, REQ-PAIR-3/6 semantics. Basic status render (names/node/endpoint counts, hint footer, zero-subnet text — hybrid liveness + --nodes is D2). Remove Cmd::Pair/PairCmd. Add qrcode crate dep.

Remove spt poll; add ready --once

Drop Cmd::Poll, add --once to Ready (run_listen absorbs). Update AGENT_COMMANDS. quickstart_e2e.rs switches poll→ready (full rewrite is D5).

In-binary agent instructions (REQ-DOCS-6). Bare how-to lists topics; topics: ready (background-task guidance, --once fallback loop, reply mechanics), send (send/ring, reply-to, SENT vs QUEUED). Agent-commands help group.

Update subcommands_parse, pair→subnet tests, how-to parse tests; help_groups_cover_every_command passes. cargo test -p spt, xtask gen, traceable-reqs check, commit.


Now replace the `cmd_pair_show_totp` wrapper with the full subnet command set:


/// `spt pair show-totp` — surface a subnet's current code off this node, behind the
/// OS-elevation gate (REQ-PAIR-3/6). Wires the real store, clock, and elevation probe
/// into the unit-tested [`resolve`]/[`decide_show_totp`] core.
// [impl->REQ-PAIR-3] [impl->REQ-PAIR-6]
fn cmd_pair_show_totp(subnet: Option<String>, create_new: Option<String>) -> i32 {
    let mut store = SubnetStore::load();
    let resolved = resolve(&mut store, subnet, create_new);
    let result = decide_show_totp(resolved, now_unix(), elevation::current());

    if result.save {
        if let Err(e) = store.save() {
            eprintln!("SAVE_FAIL: freshly minted subnet not persisted: {e}");
            return 1;
        }
        // First-join home adoption (REQ-INST-15): a fresh subnet on a
        // previously-unpaired node homes every local-only Self perch.
        let mut vis = spt_store::visibility::VisibilityStore::load();
        let adopted =
            spt_store::home::adopt_for_unset(&perch::owlery_dir(), &store, &mut vis);
        if !adopted.is_empty() {
            let _ = vis.save();
            eprintln!("HOME_ADOPTED:{}", adopted.join(","));
        }
    }
    if let Some(out) = result.out {
        print!("{out}");
        let _ = std::io::stdout().flush();
    }
    eprintln!("{}", result.msg);
    result.code
}

/// Decide `subnet create`'s rendered outcome from a resolution + clock + elevation.
/// Same gate-last shape as [`decide_show_totp`] — a refused mint persists nothing and
/// leaks nothing — but the elevated output is the full joining material (M7 plan
/// decision 11): code, `otpauth://` URI, terminal QR, and the join hint.
// [impl->REQ-SUBNET-1]
fn decide_create(resolved: Resolved, now: u64, elevation: Elevation) -> ShowResult {
    match resolved {
        Resolved::Subnet { name, seed, minted } => match elevation::gate_for(elevation) {
            Gate::Show => {
                let totp = TotpSeed::from_bytes(seed);
                let uri = totp.otpauth_uri(ISSUER, &name);
                let mut out = format!("{}\n{}\n", totp.code_at(now), uri);
                // Terminal QR of the provisioning URI — scan it into an
                // authenticator app from the other machine's side of the desk.
                if let Some(qr) = qr_unicode(&uri) {
                    out.push_str(&qr);
                    out.push('\n');
                }
                out.push_str(&format!(
                    "Run `spt subnet join {name}` on the other machine to pair it in.\n"
                ));
                ShowResult {
                    code: 0,
                    out: Some(out),
                    msg: format!("SUBNET_CREATED:{name} (this node is the sole seed-holder)"),
                    save: minted,
                }
            }
            // Refused mint: nothing persists, nothing leaks (the seed-reveal
            // path needs confirmed privilege — same posture as show-totp).
            Gate::Fallback => ShowResult {
                code: EXIT_NOT_ELEVATED,
                out: None,
                msg: format!(
                    "ELEVATION_REQUIRED: creating subnet '{name}' reveals its joining secret — \
                     re-run elevated (run as administrator / root)"
                ),
                save: false,
            },
        },
        other => decide_show_totp(other, now, elevation),
    }
}

/// Render the provisioning URI as a terminal QR (unicode half-blocks). `None` when
/// encoding fails (URI too long for the QR alphabet) — the URI text above it is the
/// fallback, so create degrades gracefully instead of refusing.
fn qr_unicode(uri: &str) -> Option<String> {
    let code = qrcode::QrCode::new(uri.as_bytes()).ok()?;
    Some(code.render::<qrcode::render::unicode::Dense1x2>().quiet_zone(true).build())
}

/// Persist a freshly-minted subnet + fire first-join home adoption (REQ-INST-15):
/// a fresh subnet on a previously-unpaired node homes every local-only Self perch.
fn save_minted(store: &SubnetStore) -> i32 {
    if let Err(e) = store.save() {
        eprintln!("SAVE_FAIL: freshly minted subnet not persisted: {e}");
        return 1;
    }
    let mut vis = spt_store::visibility::VisibilityStore::load();
    let adopted = spt_store::home::adopt_for_unset(&perch::owlery_dir(), store, &mut vis);
    if !adopted.is_empty() {
        let _ = vis.save();
        eprintln!("HOME_ADOPTED:{}", adopted.join(","));
    }
    0
}

/// Print a [`ShowResult`]: stdout block (if any), stderr status, exit code.
fn finish_show(result: ShowResult, store: &SubnetStore) -> i32 {
    if result.save {
        let code = save_minted(store);
        if code != 0 {
            return code;
        }
    }
    if let Some(out) = result.out {
        print!("{out}");
        let _ = std::io::stdout().flush();
    }
    eprintln!("{}", result.msg);
    result.code
}

/// `spt subnet show-totp [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`, M7
/// plan decision 3). Wires the real store, clock, and elevation probe into the
/// unit-tested [`resolve`]/[`decide_show_totp`] core.
// [impl->REQ-PAIR-3] [impl->REQ-PAIR-6] [impl->REQ-SUBNET-1]
fn cmd_subnet_show_totp(name: Option<String>) -> i32 {
    let mut store = SubnetStore::load();
    let resolved = resolve(&mut store, name, None);
    let result = decide_show_totp(resolved, now_unix(), elevation::current());
    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, M7 plan decisions 3 + 11). Elevation-gated:
/// the output reveals the subnet's joining secret.
// [impl->REQ-SUBNET-1]
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());
    finish_show(result, &store)
}

/// 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. Pure over
/// its inputs so the view is unit-tested hermetically. Counts are
/// gossip-derived and stale-tolerant: a missing snapshot renders as this node
/// alone with no advertised endpoints (counts catch up as gossip lands).
// [impl->REQ-SUBNET-1]
fn subnet_status_rows(
    store: &SubnetStore,
    regs: &std::collections::BTreeMap<String, spt_net::net::registry::SubnetRegistry>,
    only: Option<&str>,
) -> Vec<SubnetStatusRow> {
    store
        .subnets
        .iter()
        .filter(|s| only.is_none_or(|n| n == s.name))
        .map(|s| {
            let (mut nodes, mut endpoints) = (std::collections::BTreeSet::new(), 0usize);
            if let Some(reg) = regs.get(&s.name) {
                for id in reg.endpoint_ids() {
                    endpoints += 1;
                    for inst in reg.instances(id) {
                        nodes.insert(inst.node.clone());
                    }
                }
            }
            // This node is a member even before its first row gossips in.
            SubnetStatusRow { name: s.name.clone(), nodes: nodes.len().max(1), endpoints }
        })
        .collect()
}

/// Render the status view (M7 plan decision 5): aligned rows, the hint footer
/// always, explanatory standalone-node text on zero subnets. D2 adds
/// `--nodes` per-node rows + hybrid liveness; D3 adds the join hint.
// [impl->REQ-SUBNET-1]
fn render_subnet_status(rows: &[SubnetStatusRow]) -> String {
    let mut out = String::new();
    if rows.is_empty() {
        out.push_str(
            "This node belongs to no subnet — a standalone node. Local messaging \
             works without one; a subnet links your machines into one fabric.\n",
        );
    } else {
        let pad = rows.iter().map(|r| r.name.len()).max().unwrap_or(0).max("SUBNET".len());
        out.push_str(&format!("{:<pad$}  NODES  ENDPOINTS\n", "SUBNET"));
        for r in rows {
            out.push_str(&format!("{:<pad$}  {:<5}  {}\n", r.name, r.nodes, r.endpoints));
        }
    }
    out.push_str(
        "\nhint: `spt subnet create <name>` mints a new subnet on this machine\n\
         hint: `spt subnet show-totp [name]` shows a subnet's joining code (elevated)\n",
    );
    out
}

/// `spt subnet [status [NAME]]` — the flagless membership view (REQ-SUBNET-1, M7
/// plan decision 5). Read-only and ungated: prints membership shape, never secrets.
// [impl->REQ-SUBNET-1]
fn cmd_subnet_status(name: Option<String>) -> i32 {
    let store = SubnetStore::load();
    if let Some(n) = &name {
        if store.find(n).is_none() {
            eprintln!("NO_SUCH_SUBNET:{n}");
            return 2;
        }
    }
    let regs = crate::wansend::load_snapshots(&perch::identity_dir().join("registry"));
    let rows = subnet_status_rows(&store, &regs, name.as_deref());
    print!("{}", render_subnet_status(&rows));
    let _ = std::io::stdout().flush();
    0
}