150	pub mod wan;
151	pub mod xfer;
152
153	#[cfg(test)]
154	mod test_home;
155
156	pub use access::{access_check, classify_inbound, note_outbound, AccessDecision, InboundClass};
157	pub use adapter_update::{
158	    conduct_ripple, plan_adapter_update, ripple_registered, AdapterRipple, AdapterUpdateOutcome,
159	    FilePullPayload, SkipReason,
160	};
161	pub use applyhost::{apply_staged, ApplyStagedOutcome};
162	pub use attach::{request_attach, send_attach_input, serve_attach, AttachServeOutcome};
163	pub use brain::{Brain, BrainState, BrokerEvent};
164	pub use broker::Broker;
165	pub use config::{DaemonConfig, RelayChoice};
166	pub use consent::{decide as decide_consent, most_recently_active, ConsentDecision, ConsentTarget};
167	pub use daemon::{ensure_running, is_running, Daemon};
168	pub use digest::{ingest_chunk, DigestEngine, DigestUpdate};
169	pub use digesthub::{
170	    digest_to_json, follow, pull_snapshot, render_digest, render_update, serve_digest_control,
171	    update_to_json, DigestHub,
172	};
173	pub use effect::{EffectJournal, EffectKey, EffectKind, Outcome};
174	pub use endpoint::{broker_socket_name, daemon_pid_path, digest_socket_name, seed_socket_name};
175	pub use frame::{
176	    accept_hello, Envelope, HandshakeError, Hello, Role, IPC_PROTOCOL_VERSION,
177	    MIN_COMPATIBLE_VERSION,
178	};
179	pub use lifecycle::{BrainLifecycle, TickReport};
180	pub use linkhost::{
181	    drive_shell, relink_shell, request_shell_link, ShellLinkError, ShellLinkRequestOutcome,
182	};
183	pub use nethost::{NetConfig, NetHost, NET_EFFECT_SESSION};
184	pub use notif::{
185	    first_fire, most_recently_active_visible, produce_and_first_fire, produce_consent_notif,
186	    resurface_at_boundary, FirstFireOutcome, NotifSurfacePolicy, ResurfaceOutcome,
187	    NOTIF_KIND_AGENT, NOTIF_KIND_CONSENT, NOTIF_KIND_PSYCHE, SUPPRESSION_WINDOW_MS,
188	};
189	pub use notifsync::{apply_notif_feed, emit_notif_feed, NotifApplyVerdict, NotifPolicy};
190	pub use propagate::{request_update, serve_update, UpdatePullOutcome, UpdateServeOutcome};
191	pub use psyrelay::{
192	    psyche_turn_and_relay, relay_psyche_outbound, PsycheNotifCtx, RelayOutcome, PSYCHE_BODY_CAP,
193	};
194	pub use relay::Relay;
195	pub use relcache::ReleaseCache;
196	pub use release::{
197	    current_platform, parse_verifying_key, sha256_hex, verify_artifact, verify_metadata,
198	    verify_signature, verify_update_set_artifact, verify_update_set_metadata, RejectReason,
199	    ReleaseMetadata, SignedRelease, SignedUpdateSet, UpdateArtifactMetadata, UpdateSetMetadata,
200	    UpdateSetProvenance, VerifyPolicy,
201	};
202	pub use resthost::{request_rest, serve_rest, RestRequestOutcome, RestServeOutcome};
203	pub use resting::{
204	    apply_event, arm_transition_echo, daemon_rest_event, effective_auto_suspend, fire_wake_effects,
205	    read_rest, request_freshness_pull, take_freshness_pull, transition, write_rest, EdgeReport,
206	    RestEvent, RestRecord, RestState, PULL_MARKER_FILE,
207	};
208	pub use seedmap::{put_seed, take_seed, SeedRegistry};
209	pub use sync::{
210	    reconcile_after_sync, request_sync, select_refs, serve_sync, ReconcileWiring, SyncPolicy,
211	    SyncPullReport, SyncServeOutcome,
212	};
213	pub use transport::{DaemonTransport, LocalSocketTransport};
214	pub use update::{
215	    apply_brain_only, classify, plan_update, plan_verified, plan_verified_update_set, ApplyError,
216	    BrokerAbi, ReleaseSpec, UpdateClass, UpdatePlan, BROKER_RESOURCE_ABI,
217	};
218	pub use wan::{receive_wan, WanOutcome};
219	pub use xfer::{fetch_file, push_file, serve_xfer, XferServeOutcome};
220

pub mod wan;
pub mod xfer;

#[cfg(test)]
mod test_home;

pub use access::{access_check, classify_inbound, note_outbound, AccessDecision, InboundClass};
pub use adapter_update::{
    conduct_ripple, plan_adapter_update, ripple_registered, AdapterRipple, AdapterUpdateOutcome,
    FilePullPayload, SkipReason,
};
pub use applyhost::{apply_staged, ApplyStagedOutcome};
pub use attach::{request_attach, send_attach_input, serve_attach, AttachServeOutcome};
pub use brain::{Brain, BrainState, BrokerEvent};
pub use broker::Broker;
pub use config::{DaemonConfig, RelayChoice};
pub use consent::{decide as decide_consent, most_recently_active, ConsentDecision, ConsentTarget};
pub use daemon::{ensure_running, is_running, Daemon};
pub use digest::{ingest_chunk, DigestEngine, DigestUpdate};
pub use digesthub::{
    digest_to_json, follow, pull_snapshot, render_digest, render_update, serve_digest_control,
    update_to_json, DigestHub,
};
pub use effect::{EffectJournal, EffectKey, EffectKind, Outcome};
pub use endpoint::{broker_socket_name, daemon_pid_path, digest_socket_name, seed_socket_name};
pub use frame::{
    accept_hello, Envelope, HandshakeError, Hello, Role, IPC_PROTOCOL_VERSION,
    MIN_COMPATIBLE_VERSION,
};
pub use lifecycle::{BrainLifecycle, TickReport};
pub use linkhost::{
    drive_shell, relink_shell, request_shell_link, ShellLinkError, ShellLinkRequestOutcome,
};
pub use nethost::{NetConfig, NetHost, NET_EFFECT_SESSION};
pub use notif::{
    first_fire, most_recently_active_visible, produce_and_first_fire, produce_consent_notif,
    resurface_at_boundary, FirstFireOutcome, NotifSurfacePolicy, ResurfaceOutcome,
    NOTIF_KIND_AGENT, NOTIF_KIND_CONSENT, NOTIF_KIND_PSYCHE, SUPPRESSION_WINDOW_MS,
};
pub use notifsync::{apply_notif_feed, emit_notif_feed, NotifApplyVerdict, NotifPolicy};
pub use propagate::{request_update, serve_update, UpdatePullOutcome, UpdateServeOutcome};
pub use psyrelay::{
    psyche_turn_and_relay, relay_psyche_outbound, PsycheNotifCtx, RelayOutcome, PSYCHE_BODY_CAP,
};
pub use relay::Relay;
pub use relcache::ReleaseCache;
pub use release::{
    current_platform, parse_verifying_key, sha256_hex, verify_artifact, verify_metadata,
    verify_signature, verify_update_set_artifact, verify_update_set_metadata, RejectReason,
    ReleaseMetadata, SignedRelease, SignedUpdateSet, UpdateArtifactMetadata, UpdateSetMetadata,
    UpdateSetProvenance, VerifyPolicy,
};
pub use resthost::{request_rest, serve_rest, RestRequestOutcome, RestServeOutcome};
pub use resting::{
    apply_event, arm_transition_echo, daemon_rest_event, effective_auto_suspend, fire_wake_effects,
    read_rest, request_freshness_pull, take_freshness_pull, transition, write_rest, EdgeReport,
    RestEvent, RestRecord, RestState, PULL_MARKER_FILE,
};
pub use seedmap::{put_seed, take_seed, SeedRegistry};
pub use sync::{
    reconcile_after_sync, request_sync, select_refs, serve_sync, ReconcileWiring, SyncPolicy,
    SyncPullReport, SyncServeOutcome,
};
pub use transport::{DaemonTransport, LocalSocketTransport};
pub use update::{
    apply_brain_only, classify, plan_update, plan_verified, plan_verified_update_set, ApplyError,
    BrokerAbi, ReleaseSpec, UpdateClass, UpdatePlan, BROKER_RESOURCE_ABI,
};
pub use wan::{receive_wan, WanOutcome};
pub use xfer::{fetch_file, push_file, serve_xfer, XferServeOutcome};


Edit

pub use daemon::{ensure_running, is_running, Daemon};

pub use daemon::{ensure_running, is_running, request_stop, Daemon};


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

pub use config::{DaemonConfig, RelayChoice};

pub use consent::{decide as decide_consent, most_recently_active, ConsentDecision, ConsentTarget};

-pub use daemon::{ensure_running, is_running, Daemon};

+pub use daemon::{ensure_running, is_running, request_stop, Daemon};

pub use digest::{ingest_chunk, DigestEngine, DigestUpdate};

digest_to_json, follow, pull_snapshot, render_digest, render_update, serve_digest_control,


pub use endpoint::{broker_socket_name, daemon_pid_path, digest_socket_name, seed_socket_name};

pub use endpoint::{
    broker_socket_name, daemon_pid_path, digest_socket_name, pump_heartbeat_path,
    read_pump_heartbeat, seed_socket_name,
};


pub use endpoint::{
    broker_socket_name, daemon_pid_path, digest_socket_name, pump_heartbeat_path,
    read_pump_heartbeat, seed_socket_name,
};

pub use effect::{EffectJournal, EffectKey, EffectKind, Outcome};

-pub use endpoint::{broker_socket_name, daemon_pid_path, digest_socket_name, seed_socket_name};

+    broker_socket_name, daemon_pid_path, digest_socket_name, pump_heartbeat_path,

+    read_pump_heartbeat, seed_socket_name,

accept_hello, Envelope, HandshakeError, Hello, Role, IPC_PROTOCOL_VERSION,


Ratify M8-PLAN.md — fold A–I resolutions in, status DRAFT→RATIFIED

Move resolved decisions A-G + new H/I into ratified table; update D1-D5 deliverable text (NTP, debug-converge, peer-addrs.json, pump liveness, hazard entry); update acceptance; commit.

Mint M8 requirements in traceable-reqs.toml (rule 3)

REQ-CLI-* (endpoint noun, daemon noun, hot-path), REQ-SUBNET-* (attach/detach, prune, re-pair overwrite, status render), REQ-INSTALL-* (Linux elevation leg, firewall, OS-service), NTP/TOTP offset, pump liveness, addr seeding, event advertisement, debug-converge activation (REQ-UPD-6 int), epoch-reset REQ-HAZARD-* (stages=[]). Activate only what D1 starts; rest staged per rule 5.

D1 — endpoint + daemon nouns (breaking CLI reorg)

Namespace skeletons; move fork/suspend/wake/shutdown/rename/stop/digest + access (1:1, decision F) + description under endpoint; merged endpoint list (SELF pinned, subnet-grouped); notify → subnet notify; daemon run|stop|status (status renders pump heartbeat per H); help groups; how-to topics; cli/reference.md regen.

D2 — subnet serve-state, trust lifecycle, status UX

attach/detach machinery + --auto persistence; subnet leave (elevation); subnet prune (elevation, BIGNET 09ef831e fixture); re-pair trust overwrite (machine id per G: OS id hashed, UUID fallback; additive serde field); status render fixes (decisions 11/12). Disband deferred (A).

D3 — install & elevation hardening

Linux: install.sh symlink to /usr/local/bin + first-sudo account election + KH 5.7 verify. Windows: firewall rule (elevated leg) + blocked-inbound self-detection render. OS-service per B: systemd user service + enable-linger; Windows scheduled task at-logon. E2E rungs env-gated.

D4 — convergence polish + pump resilience

peer-addrs.json seeding per E/14 (both cold starts, id-only fallback); event-driven advertisement (decision 15); NTP TOTP-offset per C/C2 (ceremony-time, system-clock fallback); pump supervision per H (panic catch + capped-backoff restart, heartbeat file); xtask debug-converge watcher per D (REQ-UPD-6 int).

D5 — docs + CI + acceptance prep

Docs-site verb sweep; quickstart/how-to deltas; README; llms.txt; reference regen; KNOWN-HAZARDS epoch-reset entry per I; quickstart_e2e + twohost ladder updates; traceable-reqs check + xtask check green.


resources --> new flag under `spt endpoint list`: `--detail` --- change the `spt subnet attach/detach --auto` flag to `--save`. clearer semantics.


Update CLI flags for endpoint and subnet commands


How-to text + tests rewrite in cli.rs.


Going offline cleanly: `spt stop <id>` (the spool is preserved; messages sent
while you are away queue and drain on your next `ready`).
";

Going offline cleanly: `spt endpoint stop <id>` (the spool is preserved;
messages sent while you are away queue and drain on your next `ready`).
";


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)

Going offline cleanly: `spt stop <id>` (the spool is preserved; messages sent
while you are away queue and drain on your next `ready`).
";

Going offline cleanly: `spt endpoint stop <id>` (the spool is preserved;
messages sent while you are away queue and drain on your next `ready`).
";

Replying: pipe the body to `spt send --reply-to <sender>` — `<sender>` is the

envelope's `from` attribute (empty = anonymous; no reply possible).

-Going offline cleanly: `spt stop <id>` (the spool is preserved; messages sent

-while you are away queue and drain on your next `ready`).

+Going offline cleanly: `spt endpoint stop <id>` (the spool is preserved;

+messages sent while you are away queue and drain on your next `ready`).

/// `spt how-to send` — delivery guidance for agents.


        assert!(matches!(parse(&["spt", "list"]).unwrap().cmd, Cmd::List));
        assert!(matches!(
            parse(&["spt", "stop", "bob"]).unwrap().cmd,
            Cmd::Stop { .. }
        ));
        assert!(matches!(
            parse(&["spt", "whoami"]).unwrap().cmd,
            Cmd::Whoami
        ));
        // The hidden daemon entry point parses (auto-started by `api`).
        assert!(matches!(
            parse(&["spt", "daemon"]).unwrap().cmd,
            Cmd::Daemon
        ));
        // [unit->REQ-INST-11] the rename surface parses (two positional ids).
        assert!(matches!(
            parse(&["spt", "rename", "ling", "oak"]).unwrap().cmd,
            Cmd::Rename { .. }
        ));
        assert!(
            parse(&["spt", "rename", "ling"]).is_err(),
            "new id required"
        );
        // [unit->REQ-INST-15] the fork surface parses; the target is required.
        assert!(matches!(
            parse(&["spt", "fork", "ling", "ling-work", "--subnet", "work"])
                .unwrap()
                .cmd,
            Cmd::Fork {
                delete_source: false,
                ..
            }
        ));
        assert!(matches!(
            parse(&[
                "spt",
                "fork",
                "ling",
                "l2",
                "--subnet",
                "work",
                "--delete-source"
            ])
            .unwrap()
            .cmd,
            Cmd::Fork {
                delete_source: true,
                ..
            }
        ));
        assert!(
            parse(&["spt", "fork", "ling", "l2"]).is_err(),
            "--subnet required"
        );
        assert!(parse(&["spt", "fork", "ling"]).is_err(), "new id required");
        // [unit->REQ-INST-14] the resources surfaces parse.
        assert!(matches!(
            parse(&["spt", "resources", "set", "rust builds", "--id", "ling"])
                .unwrap()
                .cmd,
            Cmd::Resources {
                action: ResourcesCmd::Set { .. }
            }
        ));
        assert!(matches!(
            parse(&["spt", "resources", "show"]).unwrap().cmd,
            Cmd::Resources {
                action: ResourcesCmd::Show { id: None }
            }
        ));
        assert!(matches!(
            parse(&["spt", "resources", "list", "--subnet", "home"])
                .unwrap()
                .cmd,
            Cmd::Resources {
                action: ResourcesCmd::List { subnet: Some(_) }
            }
        ));
        assert!(
            parse(&["spt", "resources", "set"]).is_err(),
            "blurb text required"
        );
        // [unit->REQ-INST-3] the resting-command surfaces parse (one local id).
        assert!(matches!(
            parse(&["spt", "suspend", "ling"]).unwrap().cmd,
            Cmd::Suspend { .. }
        ));
        assert!(matches!(
            parse(&["spt", "wake", "ling"]).unwrap().cmd,
            Cmd::Wake { .. }
        ));
        // [unit->REQ-SHELL-2] shutdown parses with and without the self-default id.
        assert!(matches!(
            parse(&["spt", "shutdown"]).unwrap().cmd,
            Cmd::Shutdown { id: None }
        ));
        assert!(matches!(
            parse(&["spt", "shutdown", "ling"]).unwrap().cmd,
            Cmd::Shutdown { id: Some(_) }
        ));
        assert!(parse(&["spt", "suspend"]).is_err(), "id required");
        assert!(parse(&["spt", "wake"]).is_err(), "id required");
        // The digest surface + its flags.
        assert!(matches!(
            parse(&["spt", "digest", "doyle"]).unwrap().cmd,
            Cmd::Digest { .. }
        ));
        assert!(matches!(
            parse(&["spt", "digest", "doyle", "--follow", "--json"])
                .unwrap()
                .cmd,
            Cmd::Digest {
                follow: true,
                json: true,
                ..
            }
        ));
    }

        assert!(matches!(
            parse(&["spt", "whoami"]).unwrap().cmd,
            Cmd::Whoami
        ));
    }

    // [unit->REQ-CLI-3] the agent HOT PATH stays flat across the M8 reorg
    // (decision 3): send/ring/ready/whoami/how-to parse at the top level,
    // and every MOVED verb's old top-level shape is GONE (decision 9 — no
    // shims; `spt fork` etc. are unknown subcommands now).
    #[test]
    fn hot_path_flat_and_moved_verbs_gone() {
        for hot in [
            vec!["spt", "send", "bob"],
            vec!["spt", "ring", "bob"],
            vec!["spt", "ready", "bob"],
            vec!["spt", "whoami"],
            vec!["spt", "how-to"],
        ] {
            assert!(parse(&hot).is_ok(), "hot path broken: {hot:?}");
        }
        for gone in [
            vec!["spt", "list"],
            vec!["spt", "stop", "bob"],
            vec!["spt", "fork", "a", "b", "--subnet", "s"],
            vec!["spt", "suspend", "ling"],
            vec!["spt", "wake", "ling"],
            vec!["spt", "shutdown"],
            vec!["spt", "rename", "a", "b"],
            vec!["spt", "digest", "doyle"],
            vec!["spt", "access", "list"],
            vec!["spt", "resources", "show"],
            vec!["spt", "notify", "body"],
        ] {
            assert!(
                parse(&gone).is_err(),
                "moved verb still parses at top level: {gone:?}"
            );
        }
    }

    // [unit->REQ-CLI-1] the endpoint noun's verb surfaces parse with their
    // pre-M8 arg shapes (decisions 1–2, 25): list flags, fork, the rest
    // verbs, stop, rename, digest, description set/show.
    #[test]
    fn endpoint_subcommands_parse() {
        // Bare `spt endpoint` = the merged list (no action).
        assert!(matches!(
            parse(&["spt", "endpoint"]).unwrap().cmd,
            Cmd::Endpoint { action: None }
        ));
        assert!(matches!(
            parse(&["spt", "endpoint", "list", "--local"]).unwrap().cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::List { local: true, .. })
            }
        ));
        assert!(matches!(
            parse(&["spt", "endpoint", "list", "--subnet", "home", "--detail"])
                .unwrap()
                .cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::List {
                    local: false,
                    subnet: Some(_),
                    detail: true,
                })
            }
        ));
        // --local conflicts with the subnet-view flags (it IS the other view).
        assert!(parse(&["spt", "endpoint", "list", "--local", "--detail"]).is_err());
        // [unit->REQ-INST-11] rename (two positional ids).
        assert!(matches!(
            parse(&["spt", "endpoint", "rename", "ling", "oak"])
                .unwrap()
                .cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Rename { .. })
            }
        ));
        assert!(
            parse(&["spt", "endpoint", "rename", "ling"]).is_err(),
            "new id required"
        );
        // [unit->REQ-INST-15] fork; the target subnet is required.
        assert!(matches!(
            parse(&[
                "spt", "endpoint", "fork", "ling", "ling-work", "--subnet", "work"
            ])
            .unwrap()
            .cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Fork {
                    delete_source: false,
                    ..
                })
            }
        ));
        assert!(
            parse(&["spt", "endpoint", "fork", "ling", "l2"]).is_err(),
            "--subnet required"
        );
        // [unit->REQ-INST-3] the resting verbs (one local id).
        assert!(matches!(
            parse(&["spt", "endpoint", "suspend", "ling"]).unwrap().cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Suspend { .. })
            }
        ));
        assert!(matches!(
            parse(&["spt", "endpoint", "wake", "ling"]).unwrap().cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Wake { .. })
            }
        ));
        // [unit->REQ-SHELL-2] shutdown with and without the self-default id.
        assert!(matches!(
            parse(&["spt", "endpoint", "shutdown"]).unwrap().cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Shutdown { id: None })
            }
        ));
        assert!(matches!(
            parse(&["spt", "endpoint", "stop", "bob"]).unwrap().cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Stop { .. })
            }
        ));
        // The digest surface + its flags.
        assert!(matches!(
            parse(&["spt", "endpoint", "digest", "doyle", "--follow", "--json"])
                .unwrap()
                .cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Digest {
                    follow: true,
                    json: true,
                    ..
                })
            }
        ));
        // [unit->REQ-INST-14] description set/show (the ex-resources pair);
        // bare `description` = show self (decision 25c).
        assert!(matches!(
            parse(&[
                "spt",
                "endpoint",
                "description",
                "set",
                "rust builds",
                "--id",
                "ling"
            ])
            .unwrap()
            .cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Description {
                    action: Some(DescriptionCmd::Set { .. })
                })
            }
        ));
        assert!(matches!(
            parse(&["spt", "endpoint", "description"]).unwrap().cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Description { action: None })
            }
        ));
        assert!(
            parse(&["spt", "endpoint", "description", "set"]).is_err(),
            "blurb text required"
        );
    }

    // [unit->REQ-CLI-2] the daemon noun parses: bare (= status view),
    // run/stop/status; the old bare-`daemon` RUN semantic is gone (the
    // spawn sites pass `daemon run` now).
    #[test]
    fn daemon_subcommands_parse() {
        assert!(matches!(
            parse(&["spt", "daemon"]).unwrap().cmd,
            Cmd::Daemon { action: None }
        ));
        assert!(matches!(
            parse(&["spt", "daemon", "run"]).unwrap().cmd,
            Cmd::Daemon {
                action: Some(DaemonCmd::Run)
            }
        ));
        assert!(matches!(
            parse(&["spt", "daemon", "stop"]).unwrap().cmd,
            Cmd::Daemon {
                action: Some(DaemonCmd::Stop)
            }
        ));
        assert!(matches!(
            parse(&["spt", "daemon", "status"]).unwrap().cmd,
            Cmd::Daemon {
                action: Some(DaemonCmd::Status)
            }
        ));
    }


        assert!(matches!(parse(&["spt", "list"]).unwrap().cmd, Cmd::List));
        assert!(matches!(
            parse(&["spt", "stop", "bob"]).unwrap().cmd,
            Cmd::Stop { .. }
        ));
        assert!(matches!(
            parse(&["spt", "whoami"]).unwrap().cmd,
            Cmd::Whoami
        ));
        // The hidden daemon entry point parses (auto-started by `api`).
        assert!(matches!(
            parse(&["spt", "daemon"]).unwrap().cmd,
            Cmd::Daemon
        ));
        // [unit->REQ-INST-11] the rename surface parses (two positional ids).
        assert!(matches!(
            parse(&["spt", "rename", "ling", "oak"]).unwrap().cmd,
            Cmd::Rename { .. }
        ));
        assert!(
            parse(&["spt", "rename", "ling"]).is_err(),
            "new id required"
        );
        // [unit->REQ-INST-15] the fork surface parses; the target is required.
        assert!(matches!(
            parse(&["spt", "fork", "ling", "ling-work", "--subnet", "work"])
                .unwrap()
                .cmd,
            Cmd::Fork {
                delete_source: false,
                ..
            }
        ));
        assert!(matches!(
            parse(&[
                "spt",
                "fork",
                "ling",
                "l2",
                "--subnet",
                "work",
                "--delete-source"
            ])
            .unwrap()
            .cmd,
            Cmd::Fork {
                delete_source: true,
                ..
            }
        ));
        assert!(
            parse(&["spt", "fork", "ling", "l2"]).is_err(),
            "--subnet required"
        );
        assert!(parse(&["spt", "fork", "ling"]).is_err(), "new id required");
        // [unit->REQ-INST-14] the resources surfaces parse.
        assert!(matches!(
            parse(&["spt", "resources", "set", "rust builds", "--id", "ling"])
                .unwrap()
                .cmd,
            Cmd::Resources {
                action: ResourcesCmd::Set { .. }
            }
        ));
        assert!(matches!(
            parse(&["spt", "resources", "show"]).unwrap().cmd,
            Cmd::Resources {
                action: ResourcesCmd::Show { id: None }
            }
        ));
        assert!(matches!(
            parse(&["spt", "resources", "list", "--subnet", "home"])
                .unwrap()
                .cmd,
            Cmd::Resources {
                action: ResourcesCmd::List { subnet: Some(_) }
            }
        ));
        assert!(
            parse(&["spt", "resources", "set"]).is_err(),
            "blurb text required"
        );
        // [unit->REQ-INST-3] the resting-command surfaces parse (one local id).
        assert!(matches!(
            parse(&["spt", "suspend", "ling"]).unwrap().cmd,
            Cmd::Suspend { .. }
        ));
        assert!(matches!(
            parse(&["spt", "wake", "ling"]).unwrap().cmd,
            Cmd::Wake { .. }
        ));
        // [unit->REQ-SHELL-2] shutdown parses with and without the self-default id.
        assert!(matches!(
            parse(&["spt", "shutdown"]).unwrap().cmd,
            Cmd::Shutdown { id: None }
        ));
        assert!(matches!(
            parse(&["spt", "shutdown", "ling"]).unwrap().cmd,
            Cmd::Shutdown { id: Some(_) }
        ));
        assert!(parse(&["spt", "suspend"]).is_err(), "id required");
        assert!(parse(&["spt", "wake"]).is_err(), "id required");
        // The digest surface + its flags.
        assert!(matches!(
            parse(&["spt", "digest", "doyle"]).unwrap().cmd,
            Cmd::Digest { .. }
        ));
        assert!(matches!(
            parse(&["spt", "digest", "doyle", "--follow", "--json"])
                .unwrap()
                .cmd,
            Cmd::Digest {
                follow: true,
                json: true,
                ..
            }
        ));
    }

        assert!(matches!(
            parse(&["spt", "whoami"]).unwrap().cmd,
            Cmd::Whoami
        ));
    }

    // [unit->REQ-CLI-3] the agent HOT PATH stays flat across the M8 reorg
    // (decision 3): send/ring/ready/whoami/how-to parse at the top level,
    // and every MOVED verb's old top-level shape is GONE (decision 9 — no
    // shims; `spt fork` etc. are unknown subcommands now).
    #[test]
    fn hot_path_flat_and_moved_verbs_gone() {
        for hot in [
            vec!["spt", "send", "bob"],
            vec!["spt", "ring", "bob"],
            vec!["spt", "ready", "bob"],
            vec!["spt", "whoami"],
            vec!["spt", "how-to"],
        ] {
            assert!(parse(&hot).is_ok(), "hot path broken: {hot:?}");
        }
        for gone in [
            vec!["spt", "list"],
            vec!["spt", "stop", "bob"],
            vec!["spt", "fork", "a", "b", "--subnet", "s"],
            vec!["spt", "suspend", "ling"],
            vec!["spt", "wake", "ling"],
            vec!["spt", "shutdown"],
            vec!["spt", "rename", "a", "b"],
            vec!["spt", "digest", "doyle"],
            vec!["spt", "access", "list"],
            vec!["spt", "resources", "show"],
            vec!["spt", "notify", "body"],
        ] {
            assert!(
                parse(&gone).is_err(),
                "moved verb still parses at top level: {gone:?}"
            );
        }
    }

    // [unit->REQ-CLI-1] the endpoint noun's verb surfaces parse with their
    // pre-M8 arg shapes (decisions 1–2, 25): list flags, fork, the rest
    // verbs, stop, rename, digest, description set/show.
    #[test]
    fn endpoint_subcommands_parse() {
        // Bare `spt endpoint` = the merged list (no action).
        assert!(matches!(
            parse(&["spt", "endpoint"]).unwrap().cmd,
            Cmd::Endpoint { action: None }
        ));
        assert!(matches!(
            parse(&["spt", "endpoint", "list", "--local"]).unwrap().cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::List { local: true, .. })
            }
        ));
        assert!(matches!(
            parse(&["spt", "endpoint", "list", "--subnet", "home", "--detail"])
                .unwrap()
                .cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::List {
                    local: false,
                    subnet: Some(_),
                    detail: true,
                })
            }
        ));
        // --local conflicts with the subnet-view flags (it IS the other view).
        assert!(parse(&["spt", "endpoint", "list", "--local", "--detail"]).is_err());
        // [unit->REQ-INST-11] rename (two positional ids).
        assert!(matches!(
            parse(&["spt", "endpoint", "rename", "ling", "oak"])
                .unwrap()
                .cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Rename { .. })
            }
        ));
        assert!(
            parse(&["spt", "endpoint", "rename", "ling"]).is_err(),
            "new id required"
        );
        // [unit->REQ-INST-15] fork; the target subnet is required.
        assert!(matches!(
            parse(&[
                "spt", "endpoint", "fork", "ling", "ling-work", "--subnet", "work"
            ])
            .unwrap()
            .cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Fork {
                    delete_source: false,
                    ..
                })
            }
        ));
        assert!(
            parse(&["spt", "endpoint", "fork", "ling", "l2"]).is_err(),
            "--subnet required"
        );
        // [unit->REQ-INST-3] the resting verbs (one local id).
        assert!(matches!(
            parse(&["spt", "endpoint", "suspend", "ling"]).unwrap().cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Suspend { .. })
            }
        ));
        assert!(matches!(
            parse(&["spt", "endpoint", "wake", "ling"]).unwrap().cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Wake { .. })
            }
        ));
        // [unit->REQ-SHELL-2] shutdown with and without the self-default id.
        assert!(matches!(
            parse(&["spt", "endpoint", "shutdown"]).unwrap().cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Shutdown { id: None })
            }
        ));
        assert!(matches!(
            parse(&["spt", "endpoint", "stop", "bob"]).unwrap().cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Stop { .. })
            }
        ));
        // The digest surface + its flags.
        assert!(matches!(
            parse(&["spt", "endpoint", "digest", "doyle", "--follow", "--json"])
                .unwrap()
                .cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Digest {
                    follow: true,
                    json: true,
                    ..
                })
            }
        ));
        // [unit->REQ-INST-14] description set/show (the ex-resources pair);
        // bare `description` = show self (decision 25c).
        assert!(matches!(
            parse(&[
                "spt",
                "endpoint",
                "description",
                "set",
                "rust builds",
                "--id",
                "ling"
            ])
            .unwrap()
            .cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Description {
                    action: Some(DescriptionCmd::Set { .. })
                })
            }
        ));
        assert!(matches!(
            parse(&["spt", "endpoint", "description"]).unwrap().cmd,
            Cmd::Endpoint {
                action: Some(EndpointCmd::Description { action: None })
            }
        ));
        assert!(
            parse(&["spt", "endpoint", "description", "set"]).is_err(),
            "blurb text required"
        );
    }

    // [unit->REQ-CLI-2] the daemon noun parses: bare (= status view),
    // run/stop/status; the old bare-`daemon` RUN semantic is gone (the
    // spawn sites pass `daemon run` now).
    #[test]
    fn daemon_subcommands_parse() {
        assert!(matches!(
            parse(&["spt", "daemon"]).unwrap().cmd,
            Cmd::Daemon { action: None }
        ));
        assert!(matches!(
            parse(&["spt", "daemon", "run"]).unwrap().cmd,
            Cmd::Daemon {
                action: Some(DaemonCmd::Run)
            }
        ));
        assert!(matches!(
            parse(&["spt", "daemon", "stop"]).unwrap().cmd,
            Cmd::Daemon {
                action: Some(DaemonCmd::Stop)
            }
        ));
        assert!(matches!(
            parse(&["spt", "daemon", "status"]).unwrap().cmd,
            Cmd::Daemon {
                action: Some(DaemonCmd::Status)
            }
        ));
    }

-        assert!(matches!(parse(&["spt", "list"]).unwrap().cmd, Cmd::List));

-            parse(&["spt", "stop", "bob"]).unwrap().cmd,

-        // The hidden daemon entry point parses (auto-started by `api`).

+    // [unit->REQ-CLI-3] the agent HOT PATH stays flat across the M8 reorg

+    // (decision 3): send/ring/ready/whoami/how-to parse at the top level,

+    // and every MOVED verb's old top-level shape is GONE (decision 9 — no

+    // shims; `spt fork` etc. are unknown subcommands now).

+    fn hot_path_flat_and_moved_verbs_gone() {

+            vec!["spt", "send", "bob"],

+            vec!["spt", "ring", "bob"],

+            vec!["spt", "ready", "bob"],

+            assert!(parse(&hot).is_ok(), "hot path broken: {hot:?}");

+            vec!["spt", "stop", "bob"],

+            vec!["spt", "suspend", "ling"],

+            vec!["spt", "wake", "ling"],

+            vec!["spt", "digest", "doyle"],

+            vec!["spt", "access", "list"],

+            vec!["spt", "resources", "show"],

+            vec!["spt", "notify", "body"],

+                "moved verb still parses at top level: {gone:?}"

+    // [unit->REQ-CLI-1] the endpoint noun's verb surfaces parse with their

+    // pre-M8 arg shapes (decisions 1–2, 25): list flags, fork, the rest

+    // verbs, stop, rename, digest, description set/show.

+        // Bare `spt endpoint` = the merged list (no action).

-            parse(&["spt", "daemon"]).unwrap().cmd,

+            parse(&["spt", "endpoint"]).unwrap().cmd,

+            Cmd::Endpoint { action: None }

-        // [unit->REQ-INST-11] the rename surface parses (two positional ids).

-            parse(&["spt", "rename", "ling", "oak"]).unwrap().cmd,

+            parse(&["spt", "endpoint", "list", "--local"]).unwrap().cmd,

+                action: Some(EndpointCmd::List { local: true, .. })

-            parse(&["spt", "rename", "ling"]).is_err(),

-        // [unit->REQ-INST-15] the fork surface parses; the target is required.

-            parse(&["spt", "fork", "ling", "ling-work", "--subnet", "work"])

+            parse(&["spt", "endpoint", "list", "--subnet", "home", "--detail"])

+                action: Some(EndpointCmd::List {

+        // --local conflicts with the subnet-view flags (it IS the other view).

+        assert!(parse(&["spt", "endpoint", "list", "--local", "--detail"]).is_err());

+        // [unit->REQ-INST-11] rename (two positional ids).

+            parse(&["spt", "endpoint", "rename", "ling", "oak"])

+                action: Some(EndpointCmd::Rename { .. })

+            parse(&["spt", "endpoint", "rename", "ling"]).is_err(),

+        // [unit->REQ-INST-15] fork; the target subnet is required.

+                "spt", "endpoint", "fork", "ling", "ling-work", "--subnet", "work"

+                action: Some(EndpointCmd::Fork {

+                    delete_source: false,

+            parse(&["spt", "endpoint", "fork", "ling", "l2"]).is_err(),

-        assert!(parse(&["spt", "fork", "ling"]).is_err(), "new id required");

-        // [unit->REQ-INST-14] the resources surfaces parse.

+        // [unit->REQ-INST-3] the resting verbs (one local id).

-            parse(&["spt", "resources", "set", "rust builds", "--id", "ling"])

-                action: ResourcesCmd::Set { .. }

+            parse(&["spt", "endpoint", "suspend", "ling"]).unwrap().cmd,

+                action: Some(EndpointCmd::Suspend { .. })

-            parse(&["spt", "resources", "show"]).unwrap().cmd,

-                action: ResourcesCmd::Show { id: None }

+            parse(&["spt", "endpoint", "wake", "ling"]).unwrap().cmd,

+                action: Some(EndpointCmd::Wake { .. })

+        // [unit->REQ-SHELL-2] shutdown with and without the self-default id.

-            parse(&["spt", "resources", "list", "--subnet", "home"])

+            parse(&["spt", "endpoint", "shutdown"]).unwrap().cmd,

+                action: Some(EndpointCmd::Shutdown { id: None })

+            parse(&["spt", "endpoint", "stop", "bob"]).unwrap().cmd,

+                action: Some(EndpointCmd::Stop { .. })

+        // The digest surface + its flags.

+            parse(&["spt", "endpoint", "digest", "doyle", "--follow", "--json"])

-                action: ResourcesCmd::List { subnet: Some(_) }

+                action: Some(EndpointCmd::Digest {

-            parse(&["spt", "resources", "set"]).is_err(),

-        // [unit->REQ-INST-3] the resting-command surfaces parse (one local id).

+        // [unit->REQ-INST-14] description set/show (the ex-resources pair);

+        // bare `description` = show self (decision 25c).

-            parse(&["spt", "suspend", "ling"]).unwrap().cmd,

+                action: Some(EndpointCmd::Description {

+                    action: Some(DescriptionCmd::Set { .. })

-            parse(&["spt", "wake", "ling"]).unwrap().cmd,

+            parse(&["spt", "endpoint", "description"]).unwrap().cmd,

+                action: Some(EndpointCmd::Description { action: None })

-        // [unit->REQ-SHELL-2] shutdown parses with and without the self-default id.

+            parse(&["spt", "endpoint", "description", "set"]).is_err(),

+    // [unit->REQ-CLI-2] the daemon noun parses: bare (= status view),

+    // run/stop/status; the old bare-`daemon` RUN semantic is gone (the

+    // spawn sites pass `daemon run` now).

-            parse(&["spt", "shutdown"]).unwrap().cmd,

+            parse(&["spt", "daemon"]).unwrap().cmd,

+            Cmd::Daemon { action: None }

-            parse(&["spt", "shutdown", "ling"]).unwrap().cmd,

-            Cmd::Shutdown { id: Some(_) }

+            parse(&["spt", "daemon", "run"]).unwrap().cmd,

+                action: Some(DaemonCmd::Run)

-        assert!(parse(&["spt", "suspend"]).is_err(), "id required");

-        assert!(parse(&["spt", "wake"]).is_err(), "id required");

-        // The digest surface + its flags.

-            parse(&["spt", "digest", "doyle"]).unwrap().cmd,

+            parse(&["spt", "daemon", "stop"]).unwrap().cmd,

+                action: Some(DaemonCmd::Stop)

-            parse(&["spt", "digest", "doyle", "--follow", "--json"])

+            parse(&["spt", "daemon", "status"]).unwrap().cmd,

+                action: Some(DaemonCmd::Status)


    // [unit->REQ-NOTIF-2] the notify/notif surfaces parse: notify takes an
    // optional body + --subnet/--from; notif list/dismiss shapes hold.
    #[test]
    fn notify_surface_parses() {
        assert!(matches!(
            parse(&[
                "spt",
                "notify",
                "build done",
                "--subnet",
                "home",
                "--from",
                "doyle"
            ])
            .unwrap()
            .cmd,
            Cmd::Notify { .. }
        ));
        assert!(matches!(
            parse(&["spt", "notify"]).unwrap().cmd,
            Cmd::Notify { body: None, .. }
        ));

    // [unit->REQ-NOTIF-2] the notify/notif surfaces parse: `subnet notify`
    // (the M8 decision-4 home) takes an optional body + --target/--from;
    // notif list/dismiss shapes hold at the top level (user recall).
    #[test]
    fn notify_surface_parses() {
        assert!(matches!(
            parse(&[
                "spt",
                "subnet",
                "notify",
                "build done",
                "--target",
                "home",
                "--from",
                "doyle"
            ])
            .unwrap()
            .cmd,
            Cmd::Subnet {
                action: Some(SubnetCmd::Notify { .. })
            }
        ));
        assert!(matches!(
            parse(&["spt", "subnet", "notify"]).unwrap().cmd,
            Cmd::Subnet {
                action: Some(SubnetCmd::Notify { body: None, .. })
            }
        ));


    // [unit->REQ-NOTIF-2] the notify/notif surfaces parse: notify takes an
    // optional body + --subnet/--from; notif list/dismiss shapes hold.
    #[test]
    fn notify_surface_parses() {
        assert!(matches!(
            parse(&[
                "spt",
                "notify",
                "build done",
                "--subnet",
                "home",
                "--from",
                "doyle"
            ])
            .unwrap()
            .cmd,
            Cmd::Notify { .. }
        ));
        assert!(matches!(
            parse(&["spt", "notify"]).unwrap().cmd,
            Cmd::Notify { body: None, .. }
        ));

    // [unit->REQ-NOTIF-2] the notify/notif surfaces parse: `subnet notify`
    // (the M8 decision-4 home) takes an optional body + --target/--from;
    // notif list/dismiss shapes hold at the top level (user recall).
    #[test]
    fn notify_surface_parses() {
        assert!(matches!(
            parse(&[
                "spt",
                "subnet",
                "notify",
                "build done",
                "--target",
                "home",
                "--from",
                "doyle"
            ])
            .unwrap()
            .cmd,
            Cmd::Subnet {
                action: Some(SubnetCmd::Notify { .. })
            }
        ));
        assert!(matches!(
            parse(&["spt", "subnet", "notify"]).unwrap().cmd,
            Cmd::Subnet {
                action: Some(SubnetCmd::Notify { body: None, .. })
            }
        ));

-    // [unit->REQ-NOTIF-2] the notify/notif surfaces parse: notify takes an

-    // optional body + --subnet/--from; notif list/dismiss shapes hold.

+    // [unit->REQ-NOTIF-2] the notify/notif surfaces parse: `subnet notify`

+    // (the M8 decision-4 home) takes an optional body + --target/--from;

+    // notif list/dismiss shapes hold at the top level (user recall).

+                action: Some(SubnetCmd::Notify { .. })

-            parse(&["spt", "notify"]).unwrap().cmd,

-            Cmd::Notify { body: None, .. }

+            parse(&["spt", "subnet", "notify"]).unwrap().cmd,

+                action: Some(SubnetCmd::Notify { body: None, .. })

parse(&["spt", "notif", "list", "--subnet", "home"])


    // [unit->REQ-NOTIF-2] subnet resolution refuses to guess: explicit
    // non-member refused; zero subnets refused; exactly one auto-selects;
    // several force --subnet.
    #[test]
    fn notif_subnet_resolution_never_guesses() {
        let mut store = SubnetStore::default();
        assert_eq!(
            resolve_notif_subnet(&store, None),
            Err(1),
            "no subnet => refuse"
        );
        assert_eq!(
            resolve_notif_subnet(&store, Some("home".into())),
            Err(1),
            "non-member => refuse"
        );

        store.create_subnet("home").unwrap();
        assert_eq!(
            resolve_notif_subnet(&store, None),
            Ok("home".to_string()),
            "sole subnet"
        );
        assert_eq!(
            resolve_notif_subnet(&store, Some("home".into())),
            Ok("home".to_string())
        );

        store.create_subnet("work").unwrap();
        assert_eq!(
            resolve_notif_subnet(&store, None),
            Err(1),
            "several => qualify"
        );
        assert_eq!(
            resolve_notif_subnet(&store, Some("work".into())),
            Ok("work".to_string())
        );
    }

    // [unit->REQ-NOTIF-2] notify subnet resolution (M8 decision 25a): an
    // explicit --target must be a member; with no flag the issuer's HOME
    // subnet is the defined default; no resolvable home (or a home this
    // node does not hold) refuses — never a guess.
    #[test]
    fn notify_subnet_resolution_home_default() {
        let mut store = SubnetStore::default();
        // No target, no home => refuse.
        assert_eq!(resolve_notify_subnet(&store, None, None), Err(1));
        // Explicit non-member target => refuse.
        assert_eq!(
            resolve_notify_subnet(&store, Some("home".into()), None),
            Err(1)
        );

        store.create_subnet("home").unwrap();
        store.create_subnet("work").unwrap();
        // Explicit member target wins, home ignored.
        assert_eq!(
            resolve_notify_subnet(&store, Some("work".into()), Some("home".into())),
            Ok("work".to_string())
        );
        // No target: the home subnet is the defined default (several held —
        // home still resolves, unlike the old sole-subnet guess).
        assert_eq!(
            resolve_notify_subnet(&store, None, Some("home".into())),
            Ok("home".to_string())
        );
        // A home this node does not hold refuses (deny, don't guess).
        assert_eq!(
            resolve_notify_subnet(&store, None, Some("elsewhere".into())),
            Err(1)
        );
        // No home resolvable => refuse even with subnets held.
        assert_eq!(resolve_notify_subnet(&store, None, None), Err(1));
    }


    // [unit->REQ-NOTIF-2] subnet resolution refuses to guess: explicit
    // non-member refused; zero subnets refused; exactly one auto-selects;
    // several force --subnet.
    #[test]
    fn notif_subnet_resolution_never_guesses() {
        let mut store = SubnetStore::default();
        assert_eq!(
            resolve_notif_subnet(&store, None),
            Err(1),
            "no subnet => refuse"
        );
        assert_eq!(
            resolve_notif_subnet(&store, Some("home".into())),
            Err(1),
            "non-member => refuse"
        );

        store.create_subnet("home").unwrap();
        assert_eq!(
            resolve_notif_subnet(&store, None),
            Ok("home".to_string()),
            "sole subnet"
        );
        assert_eq!(
            resolve_notif_subnet(&store, Some("home".into())),
            Ok("home".to_string())
        );

        store.create_subnet("work").unwrap();
        assert_eq!(
            resolve_notif_subnet(&store, None),
            Err(1),
            "several => qualify"
        );
        assert_eq!(
            resolve_notif_subnet(&store, Some("work".into())),
            Ok("work".to_string())
        );
    }

    // [unit->REQ-NOTIF-2] notify subnet resolution (M8 decision 25a): an
    // explicit --target must be a member; with no flag the issuer's HOME
    // subnet is the defined default; no resolvable home (or a home this
    // node does not hold) refuses — never a guess.
    #[test]
    fn notify_subnet_resolution_home_default() {
        let mut store = SubnetStore::default();
        // No target, no home => refuse.
        assert_eq!(resolve_notify_subnet(&store, None, None), Err(1));
        // Explicit non-member target => refuse.
        assert_eq!(
            resolve_notify_subnet(&store, Some("home".into()), None),
            Err(1)
        );

        store.create_subnet("home").unwrap();
        store.create_subnet("work").unwrap();
        // Explicit member target wins, home ignored.
        assert_eq!(
            resolve_notify_subnet(&store, Some("work".into()), Some("home".into())),
            Ok("work".to_string())
        );
        // No target: the home subnet is the defined default (several held —
        // home still resolves, unlike the old sole-subnet guess).
        assert_eq!(
            resolve_notify_subnet(&store, None, Some("home".into())),
            Ok("home".to_string())
        );
        // A home this node does not hold refuses (deny, don't guess).
        assert_eq!(
            resolve_notify_subnet(&store, None, Some("elsewhere".into())),
            Err(1)
        );
        // No home resolvable => refuse even with subnets held.
        assert_eq!(resolve_notify_subnet(&store, None, None), Err(1));
    }

-    // [unit->REQ-NOTIF-2] subnet resolution refuses to guess: explicit

-    // non-member refused; zero subnets refused; exactly one auto-selects;

+    // [unit->REQ-NOTIF-2] notify subnet resolution (M8 decision 25a): an

+    // explicit --target must be a member; with no flag the issuer's HOME

+    // subnet is the defined default; no resolvable home (or a home this

+    // node does not hold) refuses — never a guess.

-    fn notif_subnet_resolution_never_guesses() {

+    fn notify_subnet_resolution_home_default() {

+        // No target, no home => refuse.

+        assert_eq!(resolve_notify_subnet(&store, None, None), Err(1));

+        // Explicit non-member target => refuse.

-            resolve_notif_subnet(&store, None),

+            resolve_notify_subnet(&store, Some("home".into()), None),

-            resolve_notif_subnet(&store, Some("home".into())),

+        store.create_subnet("work").unwrap();

+        // Explicit member target wins, home ignored.

+            resolve_notify_subnet(&store, Some("work".into()), Some("home".into())),

+        // No target: the home subnet is the defined default (several held —

+        // home still resolves, unlike the old sole-subnet guess).

+            resolve_notify_subnet(&store, None, Some("home".into())),

-        store.create_subnet("work").unwrap();

+        // A home this node does not hold refuses (deny, don't guess).

+            resolve_notify_subnet(&store, None, Some("elsewhere".into())),

-            resolve_notif_subnet(&store, Some("work".into())),

+        // No home resolvable => refuse even with subnets held.

// [unit->REQ-SEC-1] the access whitelist surface parses: allow/revoke take


    // [unit->REQ-SEC-1] the access whitelist surface parses: allow/revoke take
    // (endpoint, node); open takes the endpoint; list takes an optional one.
    #[test]
    fn access_subcommands_parse() {
        assert!(matches!(
            parse(&["spt", "access", "allow", "ling", "aa11"])
                .unwrap()
                .cmd,
            Cmd::Access {
                action: AccessCmd::Allow { .. }
            }
        ));
        assert!(matches!(
            parse(&["spt", "access", "revoke", "ling", "aa11"])
                .unwrap()
                .cmd,
            Cmd::Access {
                action: AccessCmd::Revoke { .. }
            }
        ));
        assert!(matches!(
            parse(&["spt", "access", "open", "ling"]).unwrap().cmd,
            Cmd::Access {
                action: AccessCmd::Open { .. }
            }
        ));
        assert!(matches!(
            parse(&["spt", "access", "list"]).unwrap().cmd,
            Cmd::Access {
                action: AccessCmd::List { endpoint: None }
            }
        ));
        assert!(matches!(
            parse(&["spt", "access", "list", "ling"]).unwrap().cmd,
            Cmd::Access {
                action: AccessCmd::List { endpoint: Some(_) }
            }
        ));
        assert!(
            parse(&["spt", "access", "allow", "ling"]).is_err(),
            "node required"
        );
    }

    // [unit->REQ-SEC-1] the access whitelist surface parses 1:1 under the
    // endpoint noun (M8 decision 21 — location-only break): allow/revoke
    // take (endpoint, node); open takes the endpoint; list an optional one.
    #[test]
    fn access_subcommands_parse() {
        let access = |args: &[&str]| -> AccessCmd {
            let mut full = vec!["spt", "endpoint", "access"];
            full.extend_from_slice(args);
            match parse(&full).unwrap().cmd {
                Cmd::Endpoint {
                    action: Some(EndpointCmd::Access { action }),
                } => action,
                other => panic!("expected endpoint access, got {other:?}"),
            }
        };
        assert!(matches!(
            access(&["allow", "ling", "aa11"]),
            AccessCmd::Allow { .. }
        ));
        assert!(matches!(
            access(&["revoke", "ling", "aa11"]),
            AccessCmd::Revoke { .. }
        ));
        assert!(matches!(access(&["open", "ling"]), AccessCmd::Open { .. }));
        assert!(matches!(
            access(&["list"]),
            AccessCmd::List { endpoint: None }
        ));
        assert!(matches!(
            access(&["list", "ling"]),
            AccessCmd::List { endpoint: Some(_) }
        ));
        assert!(
            parse(&["spt", "endpoint", "access", "allow", "ling"]).is_err(),
            "node required"
        );
    }


    // [unit->REQ-SEC-1] the access whitelist surface parses: allow/revoke take
    // (endpoint, node); open takes the endpoint; list takes an optional one.
    #[test]
    fn access_subcommands_parse() {
        assert!(matches!(
            parse(&["spt", "access", "allow", "ling", "aa11"])
                .unwrap()
                .cmd,
            Cmd::Access {
                action: AccessCmd::Allow { .. }
            }
        ));
        assert!(matches!(
            parse(&["spt", "access", "revoke", "ling", "aa11"])
                .unwrap()
                .cmd,
            Cmd::Access {
                action: AccessCmd::Revoke { .. }
            }
        ));
        assert!(matches!(
            parse(&["spt", "access", "open", "ling"]).unwrap().cmd,
            Cmd::Access {
                action: AccessCmd::Open { .. }
            }
        ));
        assert!(matches!(
            parse(&["spt", "access", "list"]).unwrap().cmd,
            Cmd::Access {
                action: AccessCmd::List { endpoint: None }
            }
        ));
        assert!(matches!(
            parse(&["spt", "access", "list", "ling"]).unwrap().cmd,
            Cmd::Access {
                action: AccessCmd::List { endpoint: Some(_) }
            }
        ));
        assert!(
            parse(&["spt", "access", "allow", "ling"]).is_err(),
            "node required"
        );
    }

    // [unit->REQ-SEC-1] the access whitelist surface parses 1:1 under the
    // endpoint noun (M8 decision 21 — location-only break): allow/revoke
    // take (endpoint, node); open takes the endpoint; list an optional one.
    #[test]
    fn access_subcommands_parse() {
        let access = |args: &[&str]| -> AccessCmd {
            let mut full = vec!["spt", "endpoint", "access"];
            full.extend_from_slice(args);
            match parse(&full).unwrap().cmd {
                Cmd::Endpoint {
                    action: Some(EndpointCmd::Access { action }),
                } => action,
                other => panic!("expected endpoint access, got {other:?}"),
            }
        };
        assert!(matches!(
            access(&["allow", "ling", "aa11"]),
            AccessCmd::Allow { .. }
        ));
        assert!(matches!(
            access(&["revoke", "ling", "aa11"]),
            AccessCmd::Revoke { .. }
        ));
        assert!(matches!(access(&["open", "ling"]), AccessCmd::Open { .. }));
        assert!(matches!(
            access(&["list"]),
            AccessCmd::List { endpoint: None }
        ));
        assert!(matches!(
            access(&["list", "ling"]),
            AccessCmd::List { endpoint: Some(_) }
        ));
        assert!(
            parse(&["spt", "endpoint", "access", "allow", "ling"]).is_err(),
            "node required"
        );
    }

assert_eq!(resolve_notify_subnet(&store, None, None), Err(1));

-    // [unit->REQ-SEC-1] the access whitelist surface parses: allow/revoke take

-    // (endpoint, node); open takes the endpoint; list takes an optional one.

+    // [unit->REQ-SEC-1] the access whitelist surface parses 1:1 under the

+    // endpoint noun (M8 decision 21 — location-only break): allow/revoke

+    // take (endpoint, node); open takes the endpoint; list an optional one.

-            parse(&["spt", "access", "allow", "ling", "aa11"])

-                action: AccessCmd::Allow { .. }

+        let access = |args: &[&str]| -> AccessCmd {

+            let mut full = vec!["spt", "endpoint", "access"];

+            full.extend_from_slice(args);

+            match parse(&full).unwrap().cmd {

+                    action: Some(EndpointCmd::Access { action }),

+                other => panic!("expected endpoint access, got {other:?}"),

-            parse(&["spt", "access", "revoke", "ling", "aa11"])

-                action: AccessCmd::Revoke { .. }

+            access(&["allow", "ling", "aa11"]),

-            parse(&["spt", "access", "open", "ling"]).unwrap().cmd,

-                action: AccessCmd::Open { .. }

+            access(&["revoke", "ling", "aa11"]),

+        assert!(matches!(access(&["open", "ling"]), AccessCmd::Open { .. }));

-            parse(&["spt", "access", "list"]).unwrap().cmd,

-                action: AccessCmd::List { endpoint: None }

+            AccessCmd::List { endpoint: None }

-            parse(&["spt", "access", "list", "ling"]).unwrap().cmd,

-                action: AccessCmd::List { endpoint: Some(_) }

+            AccessCmd::List { endpoint: Some(_) }

-            parse(&["spt", "access", "allow", "ling"]).is_err(),

+            parse(&["spt", "endpoint", "access", "allow", "ling"]).is_err(),


other => panic!("expected endpoint access, got {other:?}"),

_ => panic!("expected endpoint access"),


action: Some(EndpointCmd::Access { action }),

-                other => panic!("expected endpoint access, got {other:?}"),

+                _ => panic!("expected endpoint access"),


4348:            parse(&["spt", "daemon"]).unwrap().cmd,
4352:            parse(&["spt", "daemon", "run"]).unwrap().cmd,
4358:            parse(&["spt", "daemon", "stop"]).unwrap().cmd,
4364:            parse(&["spt", "daemon", "status"]).unwrap().cmd,
5577:        match parse(&["spt", "digest", "doyle"]).unwrap().cmd {


5555
5556	        // A foreign link token (ling's instance) never drives doyle's shell:
5557	        // the token names ITS instance, and the id must match.
5558	        let ling_token = spt_daemon::shellhost::read_link_token(
5559	            &perch::resolve_shell_perch_path_in(&owlery, "ling", "other-shell-0"),
5560	        )
5561	        .expect("ling token parked");
5562	        assert_eq!(
5563	            crate::api::delivery::cmd_poll_shell("mock-shell-0", &ling_token),
5564	            crate::api::EXIT_REFUSED,
5565	            "a foreign token never drains another shell's spool"
5566	        );
5567	        assert_eq!(
5568	            crate::api::reporting::cmd_emit("mock-shell-0", "bumped", "x", &ling_token),
5569	            1,
5570	            "a foreign token never emits as another shell"
5571	        );
5572	    }
5573
5574	    // [unit->REQ-TERM-4] digest defaults: a one-shot, human-rendered snapshot.
5575	    #[test]
5576	    fn digest_flag_defaults() {
5577	        match parse(&["spt", "digest", "doyle"]).unwrap().cmd {
5578	            Cmd::Digest { id, follow, json } => {
5579	                assert_eq!(id, "doyle");
5580	                assert!(!follow, "snapshot by default");
5581	                assert!(!json, "human render by default");
5582	            }
5583	            _ => panic!("expected Digest"),
5584	        }
5585	    }
5586
5587	    // [unit->REQ-MSG-2] ring timeout defaults to 60s when unspecified.
5588	    #[test]
5589	    fn ring_timeout_defaults() {
5590	        match parse(&["spt", "ring", "bob"]).unwrap().cmd {
5591	            Cmd::Ring { timeout, .. } => assert_eq!(timeout, 60),
5592	            _ => panic!("expected Ring"),
5593	        }
5594	    }
5595
5596	    // [unit->REQ-MSG-2] send target is optional (supplied via --reply-to instead).
5597	    #[test]
5598	    fn send_target_optional_with_reply_to() {
5599	        match parse(&["spt", "send", "--reply-to", "alice"]).unwrap().cmd {
5600	            Cmd::Send {
5601	                target, reply_to, ..
5602	            } => {
5603	                assert!(target.is_none());
5604	                assert_eq!(reply_to.as_deref(), Some("alice"));
5605	            }
5606	            _ => panic!("expected Send"),
5607	        }
5608	    }
5609
5610	    // [unit->REQ-MSG-2] unknown subcommand / missing required arg are usage errors.
5611	    #[test]
5612	    fn invalid_usage_errors() {
5613	        assert!(parse(&["spt", "bogus"]).is_err());
5614	        assert!(parse(&["spt", "ring"]).is_err()); // missing required target

        // A foreign link token (ling's instance) never drives doyle's shell:
        // the token names ITS instance, and the id must match.
        let ling_token = spt_daemon::shellhost::read_link_token(
            &perch::resolve_shell_perch_path_in(&owlery, "ling", "other-shell-0"),
        )
        .expect("ling token parked");
        assert_eq!(
            crate::api::delivery::cmd_poll_shell("mock-shell-0", &ling_token),
            crate::api::EXIT_REFUSED,
            "a foreign token never drains another shell's spool"
        );
        assert_eq!(
            crate::api::reporting::cmd_emit("mock-shell-0", "bumped", "x", &ling_token),
            1,
            "a foreign token never emits as another shell"
        );
    }

    // [unit->REQ-TERM-4] digest defaults: a one-shot, human-rendered snapshot.
    #[test]
    fn digest_flag_defaults() {
        match parse(&["spt", "digest", "doyle"]).unwrap().cmd {
            Cmd::Digest { id, follow, json } => {
                assert_eq!(id, "doyle");
                assert!(!follow, "snapshot by default");
                assert!(!json, "human render by default");
            }
            _ => panic!("expected Digest"),
        }
    }

    // [unit->REQ-MSG-2] ring timeout defaults to 60s when unspecified.
    #[test]
    fn ring_timeout_defaults() {
        match parse(&["spt", "ring", "bob"]).unwrap().cmd {
            Cmd::Ring { timeout, .. } => assert_eq!(timeout, 60),
            _ => panic!("expected Ring"),
        }
    }

    // [unit->REQ-MSG-2] send target is optional (supplied via --reply-to instead).
    #[test]
    fn send_target_optional_with_reply_to() {
        match parse(&["spt", "send", "--reply-to", "alice"]).unwrap().cmd {
            Cmd::Send {
                target, reply_to, ..
            } => {
                assert!(target.is_none());
                assert_eq!(reply_to.as_deref(), Some("alice"));
            }
            _ => panic!("expected Send"),
        }
    }

    // [unit->REQ-MSG-2] unknown subcommand / missing required arg are usage errors.
    #[test]
    fn invalid_usage_errors() {
        assert!(parse(&["spt", "bogus"]).is_err());
        assert!(parse(&["spt", "ring"]).is_err()); // missing required target


    // [unit->REQ-TERM-4] digest defaults: a one-shot, human-rendered snapshot.
    #[test]
    fn digest_flag_defaults() {
        match parse(&["spt", "digest", "doyle"]).unwrap().cmd {
            Cmd::Digest { id, follow, json } => {
                assert_eq!(id, "doyle");
                assert!(!follow, "snapshot by default");
                assert!(!json, "human render by default");
            }
            _ => panic!("expected Digest"),
        }
    }

    // [unit->REQ-TERM-4] digest defaults: a one-shot, human-rendered snapshot.
    #[test]
    fn digest_flag_defaults() {
        match parse(&["spt", "endpoint", "digest", "doyle"]).unwrap().cmd {
            Cmd::Endpoint {
                action: Some(EndpointCmd::Digest { id, follow, json }),
            } => {
                assert_eq!(id, "doyle");
                assert!(!follow, "snapshot by default");
                assert!(!json, "human render by default");
            }
            _ => panic!("expected endpoint digest"),
        }
    }


    // [unit->REQ-TERM-4] digest defaults: a one-shot, human-rendered snapshot.
    #[test]
    fn digest_flag_defaults() {
        match parse(&["spt", "digest", "doyle"]).unwrap().cmd {
            Cmd::Digest { id, follow, json } => {
                assert_eq!(id, "doyle");
                assert!(!follow, "snapshot by default");
                assert!(!json, "human render by default");
            }
            _ => panic!("expected Digest"),
        }
    }

    // [unit->REQ-TERM-4] digest defaults: a one-shot, human-rendered snapshot.
    #[test]
    fn digest_flag_defaults() {
        match parse(&["spt", "endpoint", "digest", "doyle"]).unwrap().cmd {
            Cmd::Endpoint {
                action: Some(EndpointCmd::Digest { id, follow, json }),
            } => {
                assert_eq!(id, "doyle");
                assert!(!follow, "snapshot by default");
                assert!(!json, "human render by default");
            }
            _ => panic!("expected endpoint digest"),
        }
    }

-        match parse(&["spt", "digest", "doyle"]).unwrap().cmd {

-            Cmd::Digest { id, follow, json } => {

+        match parse(&["spt", "endpoint", "digest", "doyle"]).unwrap().cmd {

+                action: Some(EndpointCmd::Digest { id, follow, json }),

-            _ => panic!("expected Digest"),

+            _ => panic!("expected endpoint digest"),


D3 — install & elevation hardening

D5 — docs + CI + acceptance prep


Update user-facing strings naming moved verbs, then build.


    let Some(id) = id.or_else(roster::detect_self_id) else {
        eprintln!("NO_SELF: shutdown needs an endpoint (spt shutdown <id> or $OWL_SESSION_ID)");
        return 1;
    };
    if id.contains('@') || id.contains(':') {
        // Deliberately NOT lifted with the D5b remote rest arm: an agent
        // shuts down *its own* endpoint — remote shutdown is not a surface;
        // `spt suspend <id@node>` covers the remote need.
        eprintln!(
            "LOCAL_ONLY:{id} — shutdown is an endpoint's own verb; use `spt suspend {id}` remotely"
        );
        return 1;
    }

    let Some(id) = id.or_else(roster::detect_self_id) else {
        eprintln!(
            "NO_SELF: shutdown needs an endpoint (spt endpoint shutdown <id> or $OWL_SESSION_ID)"
        );
        return 1;
    };
    if id.contains('@') || id.contains(':') {
        // Deliberately NOT lifted with the D5b remote rest arm: an agent
        // shuts down *its own* endpoint — remote shutdown is not a surface;
        // `spt endpoint suspend <id@node>` covers the remote need.
        eprintln!(
            "LOCAL_ONLY:{id} — shutdown is an endpoint's own verb; use \
             `spt endpoint suspend {id}` remotely"
        );
        return 1;
    }


    let Some(id) = id.or_else(roster::detect_self_id) else {
        eprintln!("NO_SELF: shutdown needs an endpoint (spt shutdown <id> or $OWL_SESSION_ID)");
        return 1;
    };
    if id.contains('@') || id.contains(':') {
        // Deliberately NOT lifted with the D5b remote rest arm: an agent
        // shuts down *its own* endpoint — remote shutdown is not a surface;
        // `spt suspend <id@node>` covers the remote need.
        eprintln!(
            "LOCAL_ONLY:{id} — shutdown is an endpoint's own verb; use `spt suspend {id}` remotely"
        );
        return 1;
    }

    let Some(id) = id.or_else(roster::detect_self_id) else {
        eprintln!(
            "NO_SELF: shutdown needs an endpoint (spt endpoint shutdown <id> or $OWL_SESSION_ID)"
        );
        return 1;
    };
    if id.contains('@') || id.contains(':') {
        // Deliberately NOT lifted with the D5b remote rest arm: an agent
        // shuts down *its own* endpoint — remote shutdown is not a surface;
        // `spt endpoint suspend <id@node>` covers the remote need.
        eprintln!(
            "LOCAL_ONLY:{id} — shutdown is an endpoint's own verb; use \
             `spt endpoint suspend {id}` remotely"
        );
        return 1;
    }

fn cmd_shutdown(id: Option<String>) -> i32 {

-        eprintln!("NO_SELF: shutdown needs an endpoint (spt shutdown <id> or $OWL_SESSION_ID)");

+            "NO_SELF: shutdown needs an endpoint (spt endpoint shutdown <id> or $OWL_SESSION_ID)"

-        // `spt suspend <id@node>` covers the remote need.

+        // `spt endpoint suspend <id@node>` covers the remote need.

-            "LOCAL_ONLY:{id} — shutdown is an endpoint's own verb; use `spt suspend {id}` remotely"

+            "LOCAL_ONLY:{id} — shutdown is an endpoint's own verb; use \

+             `spt endpoint suspend {id}` remotely"


358:        /// The origin node's pubkey hex (as shown by `spt list` / pairing).
1764:/// `spt list` view, moved under the endpoint noun: M8 decision 2).
2962:                         `spt access open {endpoint}` returns it to default-open"
4029:                         recover: rerun `spt rename {old_id} {new_id}` after fixing the cause \


/// The origin node's pubkey hex (as shown by `spt list` / pairing).

/// The origin node's pubkey hex (as shown by `spt endpoint list` / pairing).


-        /// The origin node's pubkey hex (as shown by `spt list` / pairing).

+        /// The origin node's pubkey hex (as shown by `spt endpoint list` / pairing).

/// Remove a node from an endpoint's whitelist. Never widens: revoking the


`spt access open {endpoint}` returns it to default-open"

`spt endpoint access open {endpoint}` returns it to default-open"


"ACCESS_LOCKED:{endpoint} now refuses ALL unsolicited remote inbound; \

-                         `spt access open {endpoint}` returns it to default-open"

+                         `spt endpoint access open {endpoint}` returns it to default-open"


recover: rerun `spt rename {old_id} {new_id}` after fixing the cause \

recover: rerun `spt endpoint rename {old_id} {new_id}` after fixing the cause \


if let Err(e) = cs.rename_endpoint(old_id, new_id) {

"ERROR: perches renamed but context rename failed: {e}\n\

-                         recover: rerun `spt rename {old_id} {new_id}` after fixing the cause \

+                         recover: rerun `spt endpoint rename {old_id} {new_id}` after fixing the cause \

(perch arm will report NotFound and the context arm will retry)"


Fix shell_sleepwake_e2e argv, then build.


205	            std::thread::sleep(Duration::from_millis(50));
206	        }
207	    }
208	    let _ = std::fs::remove_file(&keeper_evidence); // second life writes fresh
209
210	    // ── END ONE: `spt shutdown` — the graceful self-suspend. Persistent
211	    // keeper closes offline (binary dead, pre-close frame parked in its
212	    // spool); the ephemeral sibling is GONE, history and all.
213	    let keeper_pid: u32 = std::fs::read_to_string(keeper_perch.join("shell.pid"))
214	        .unwrap()
215	        .trim()
216	        .parse()
217	        .unwrap();
218	    let out = spt(&["shutdown", "doyle"]);
219	    assert!(
220	        out.status.success(),
221	        "shutdown: {}",
222	        String::from_utf8_lossy(&out.stderr)
223	    );
224	    assert_eq!(
225	        spt_daemon::read_rest(&owner_perch).unwrap().state,
226	        spt_daemon::RestState::Suspended,
227	        "the suspend edge ran"
228	    );
229	    assert!(

            std::thread::sleep(Duration::from_millis(50));
        }
    }
    let _ = std::fs::remove_file(&keeper_evidence); // second life writes fresh

    // ── END ONE: `spt shutdown` — the graceful self-suspend. Persistent
    // keeper closes offline (binary dead, pre-close frame parked in its
    // spool); the ephemeral sibling is GONE, history and all.
    let keeper_pid: u32 = std::fs::read_to_string(keeper_perch.join("shell.pid"))
        .unwrap()
        .trim()
        .parse()
        .unwrap();
    let out = spt(&["shutdown", "doyle"]);
    assert!(
        out.status.success(),
        "shutdown: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    assert_eq!(
        spt_daemon::read_rest(&owner_perch).unwrap().state,
        spt_daemon::RestState::Suspended,
        "the suspend edge ran"
    );
    assert!(


let out = spt(&["endpoint", "shutdown", "doyle"]);


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

-    let out = spt(&["shutdown", "doyle"]);

+    let out = spt(&["endpoint", "shutdown", "doyle"]);


PowerShell

cargo build -q -p spt -p spt-daemon 2>&1 | Select-Object -First 60

Build spt + spt-daemon


Error: Exit code 101
error[E0425]: cannot find function `resolve_notif_subnet` in this scope
    --> crates\spt\src\cli.rs:3801:24
     |
1309 | / fn resolve_notify_subnet(
1310 | |     store: &SubnetStore,
1311 | |     target: Option<String>,
1312 | |     home: Option<String>,
...    |
1340 | | }
     | |_- similarly named function `resolve_notify_subnet` defined here
...
3801 |       let subnet = match resolve_notif_subnet(&subnets, None) {
     |                          ^^^^^^^^^^^^^^^^^^^^ help: a function with a similar name exists: `resolve_notify_subnet`

error[E0277]: the size for values of type `str` cannot be known at compilation time
    --> crates\spt\src\cli.rs:3801:9
     |
3801 |     let subnet = match resolve_notif_subnet(&subnets, None) {
     |         ^^^^^^ doesn't have a size known at compile-time
     |
     = help: the trait `Sized` is not implemented for `str`
     = note: all local variables must have a statically known size

error[E0277]: the size for values of type `str` cannot be known at compilation time
    --> crates\spt\src\cli.rs:3802:12
     |
3802 |         Ok(s) => s,
     |            ^ doesn't have a size known at compile-time
     |
     = help: the trait `Sized` is not implemented for `str`
     = note: all local variables must have a statically known size

error[E0277]: the size for values of type `str` cannot be known at compilation time
    --> crates\spt\src\cli.rs:3802:9
     |
3802 |         Ok(s) => s,
     |         ^^^^^ doesn't have a size known at compile-time
     |
     = help: the trait `Sized` is not implemented for `str`
note: required by a bound in `std::prelude::v1::Ok`
    --> C:\Users\decid\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\core\src\result.rs:557:17
     |
 557 | pub enum Result<T, E> {
     |                 ^ required by this bound in `std::prelude::v1::Ok`
...
 561 |     Ok(#[stable(feature = "rust1", since = "1.0.0")] T),
     |     -- required by a bound in this tuple variant

Some errors have detailed explanations: E0277, E0425.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `spt` (bin "spt") due to 4 previous errors


3780	/// persistence is legal (`require_approval = "remembered"`; the per-spawn
3781	/// modes — `always`, `over_cap = "approve"` — suppress it by definition).
3782	// [impl->REQ-CONSENT-2]
3783	fn parse_escalation_choice(
3784	    input: &str,
3785	    persist_allowed: bool,
3786	) -> spt_daemon::grants::EscalationAnswer {
3787	    use spt_daemon::grants::EscalationAnswer;
3788	    match input.trim().to_ascii_lowercase().as_str() {
3789	        "o" | "once" => EscalationAnswer::AllowOnce,
3790	        "a" | "always" if persist_allowed => EscalationAnswer::AllowAlways,
3791	        _ => EscalationAnswer::Deny,
3792	    }
3793	}
3794
3795	/// Fire the spawn-approval consent notif at the user's most-recently-active
3796	/// session (D1b's `produce_escalation_notif`, riding the boundary-resurface
3797	/// path when no session is live). Best-effort: the refusal it accompanies is
3798	/// already decided — a notif problem only loses the prompt, never the gate.
3799	fn fire_spawn_consent_notif(owlery: &std::path::Path, ask: &spt_daemon::grants::EscalationAsk) {
3800	    let subnets = SubnetStore::load();
3801	    let subnet = match resolve_notif_subnet(&subnets, None) {
3802	        Ok(s) => s,
3803	        Err(_) => return, // subnet-less node: nowhere for a notif to live
3804	    };
3805	    let Ok(store) = spt_store::notif::NotifStore::open() else {
3806	        return;
3807	    };
3808	    let mut epochs = spt_store::epoch::EpochSource::load();
3809	    let policy = spt_daemon::NotifSurfacePolicy::load();
3810	    match spt_daemon::grants::produce_escalation_notif(
3811	        ask,
3812	        &store,
3813	        &ask.node,
3814	        &mut epochs,
3815	        &subnet,
3816	        &policy,
3817	        owlery,
3818	        now_ms(),
3819	    ) {
3820	        Ok((row, _)) => eprintln!(
3821	            "CONSENT_NOTIF:{} (answer it or use the grant-add line it carries)",
3822	            row.notif_id
3823	        ),
3824	        Err(e) => eprintln!("CONSENT_NOTIF_FAIL: {e}"),

/// persistence is legal (`require_approval = "remembered"`; the per-spawn
/// modes — `always`, `over_cap = "approve"` — suppress it by definition).
// [impl->REQ-CONSENT-2]
fn parse_escalation_choice(
    input: &str,
    persist_allowed: bool,
) -> spt_daemon::grants::EscalationAnswer {
    use spt_daemon::grants::EscalationAnswer;
    match input.trim().to_ascii_lowercase().as_str() {
        "o" | "once" => EscalationAnswer::AllowOnce,
        "a" | "always" if persist_allowed => EscalationAnswer::AllowAlways,
        _ => EscalationAnswer::Deny,
    }
}

/// Fire the spawn-approval consent notif at the user's most-recently-active
/// session (D1b's `produce_escalation_notif`, riding the boundary-resurface
/// path when no session is live). Best-effort: the refusal it accompanies is
/// already decided — a notif problem only loses the prompt, never the gate.
fn fire_spawn_consent_notif(owlery: &std::path::Path, ask: &spt_daemon::grants::EscalationAsk) {
    let subnets = SubnetStore::load();
    let subnet = match resolve_notif_subnet(&subnets, None) {
        Ok(s) => s,
        Err(_) => return, // subnet-less node: nowhere for a notif to live
    };
    let Ok(store) = spt_store::notif::NotifStore::open() else {
        return;
    };
    let mut epochs = spt_store::epoch::EpochSource::load();
    let policy = spt_daemon::NotifSurfacePolicy::load();
    match spt_daemon::grants::produce_escalation_notif(
        ask,
        &store,
        &ask.node,
        &mut epochs,
        &subnet,
        &policy,
        owlery,
        now_ms(),
    ) {
        Ok((row, _)) => eprintln!(
            "CONSENT_NOTIF:{} (answer it or use the grant-add line it carries)",
            row.notif_id
        ),
        Err(e) => eprintln!("CONSENT_NOTIF_FAIL: {e}"),


124:pub struct EscalationAsk {
125-    pub capability: String,
126-    pub agent: String,
127-    pub node: String,
128-    pub qualifier: Option<String>,
129-}
130-
131-/// The body-line prefix marking a grants-escalation consent notif (vs the
132-/// update-consent producer sharing the same notif kind).
133-pub const ESCALATION_PREFIX: &str = "consent-needed";
134-
135-impl EscalationAsk {
136-    \ Encode as the notif body: a machine-parseable first line
137-    /// (`consent-needed cap=… agent=… node=… [qual=…]`) + a human hint.
138-    /// Fields are SPT ids / pubkey hex / adapter names — whitespace-free by
139-    /// charset (REQ-HAZARD-ID-CHARSET), so space-separated `key=value` holds.


Edit

    let subnets = SubnetStore::load();
    let subnet = match resolve_notif_subnet(&subnets, None) {
        Ok(s) => s,
        Err(_) => return, // subnet-less node: nowhere for a notif to live
    };

    let subnets = SubnetStore::load();
    let subnet = match resolve_notify_subnet(&subnets, None, issuer_home_subnet(&ask.agent)) {
        Ok(s) => s,
        // Best-effort internal routing (not a user-facing guess): a
        // home-less requester on a single-subnet node still gets the prompt.
        Err(_) if subnets.subnets.len() == 1 => subnets.subnets[0].name.clone(),
        Err(_) => return, // unroutable: nowhere for a notif to live
    };


    let subnets = SubnetStore::load();
    let subnet = match resolve_notif_subnet(&subnets, None) {
        Ok(s) => s,
        Err(_) => return, // subnet-less node: nowhere for a notif to live
    };

    let subnets = SubnetStore::load();
    let subnet = match resolve_notify_subnet(&subnets, None, issuer_home_subnet(&ask.agent)) {
        Ok(s) => s,
        // Best-effort internal routing (not a user-facing guess): a
        // home-less requester on a single-subnet node still gets the prompt.
        Err(_) if subnets.subnets.len() == 1 => subnets.subnets[0].name.clone(),
        Err(_) => return, // unroutable: nowhere for a notif to live
    };

/// already decided — a notif problem only loses the prompt, never the gate.

fn fire_spawn_consent_notif(owlery: &std::path::Path, ask: &spt_daemon::grants::EscalationAsk) {

-    let subnet = match resolve_notif_subnet(&subnets, None) {

+    let subnet = match resolve_notify_subnet(&subnets, None, issuer_home_subnet(&ask.agent)) {

-        Err(_) => return, // subnet-less node: nowhere for a notif to live

+        // Best-effort internal routing (not a user-facing guess): a

+        // home-less requester on a single-subnet node still gets the prompt.

+        Err(_) if subnets.subnets.len() == 1 => subnets.subnets[0].name.clone(),

+        Err(_) => return, // unroutable: nowhere for a notif to live

let Ok(store) = spt_store::notif::NotifStore::open() else {


/// Resolve which subnet a notif targets (M8 decision 25a): an explicit
/// `--target` must be a member; with no flag the calling endpoint's HOME
/// subnet is used — a defined default, never a guess; no resolvable home ⇒
/// refuse with the flag named. Pure over its inputs for the decision-table
/// unit test.
// [impl->REQ-NOTIF-2]
fn resolve_notify_subnet(
    store: &SubnetStore,
    target: Option<String>,
    home: Option<String>,
) -> Result<String, i32> {
    match target {
        Some(name) => {
            if store.find(&name).is_some() {
                Ok(name)
            } else {
                eprintln!("NO_SUBNET:{name} — this node is not a member");
                Err(1)
            }
        }
        None => match home {
            Some(h) if store.find(&h).is_some() => Ok(h),
            Some(h) => {
                eprintln!(
                    "HOME_NOT_MEMBER:{h} — the issuer's home subnet is not held on this \
                     node; pass --target <subnet>"
                );
                Err(1)
            }
            None => {
                eprintln!(
                    "NO_HOME_SUBNET: the issuer has no home subnet — pass --target <subnet>"
                );
                Err(1)
            }
        },
    }
}

/// Resolve which subnet a notif targets (M8 decision 25a): an explicit
/// `--target` must be a member; with no flag the calling endpoint's HOME
/// subnet is used — a defined default, never a guess; no resolvable home ⇒
/// refuse with the flag named. Pure over its inputs (the `Err` is the
/// refusal message; callers decide whether it prints) for the
/// decision-table unit test.
// [impl->REQ-NOTIF-2]
fn resolve_notify_subnet(
    store: &SubnetStore,
    target: Option<String>,
    home: Option<String>,
) -> Result<String, String> {
    match target {
        Some(name) => {
            if store.find(&name).is_some() {
                Ok(name)
            } else {
                Err(format!("NO_SUBNET:{name} — this node is not a member"))
            }
        }
        None => match home {
            Some(h) if store.find(&h).is_some() => Ok(h),
            Some(h) => Err(format!(
                "HOME_NOT_MEMBER:{h} — the issuer's home subnet is not held on this \
                 node; pass --target <subnet>"
            )),
            None => Err(
                "NO_HOME_SUBNET: the issuer has no home subnet — pass --target <subnet>"
                    .to_string(),
            ),
        },
    }
}


/// Resolve which subnet a notif targets (M8 decision 25a): an explicit
/// `--target` must be a member; with no flag the calling endpoint's HOME
/// subnet is used — a defined default, never a guess; no resolvable home ⇒
/// refuse with the flag named. Pure over its inputs for the decision-table
/// unit test.
// [impl->REQ-NOTIF-2]
fn resolve_notify_subnet(
    store: &SubnetStore,
    target: Option<String>,
    home: Option<String>,
) -> Result<String, i32> {
    match target {
        Some(name) => {
            if store.find(&name).is_some() {
                Ok(name)
            } else {
                eprintln!("NO_SUBNET:{name} — this node is not a member");
                Err(1)
            }
        }
        None => match home {
            Some(h) if store.find(&h).is_some() => Ok(h),
            Some(h) => {
                eprintln!(
                    "HOME_NOT_MEMBER:{h} — the issuer's home subnet is not held on this \
                     node; pass --target <subnet>"
                );
                Err(1)
            }
            None => {
                eprintln!(
                    "NO_HOME_SUBNET: the issuer has no home subnet — pass --target <subnet>"
                );
                Err(1)
            }
        },
    }
}

/// Resolve which subnet a notif targets (M8 decision 25a): an explicit
/// `--target` must be a member; with no flag the calling endpoint's HOME
/// subnet is used — a defined default, never a guess; no resolvable home ⇒
/// refuse with the flag named. Pure over its inputs (the `Err` is the
/// refusal message; callers decide whether it prints) for the
/// decision-table unit test.
// [impl->REQ-NOTIF-2]
fn resolve_notify_subnet(
    store: &SubnetStore,
    target: Option<String>,
    home: Option<String>,
) -> Result<String, String> {
    match target {
        Some(name) => {
            if store.find(&name).is_some() {
                Ok(name)
            } else {
                Err(format!("NO_SUBNET:{name} — this node is not a member"))
            }
        }
        None => match home {
            Some(h) if store.find(&h).is_some() => Ok(h),
            Some(h) => Err(format!(
                "HOME_NOT_MEMBER:{h} — the issuer's home subnet is not held on this \
                 node; pass --target <subnet>"
            )),
            None => Err(
                "NO_HOME_SUBNET: the issuer has no home subnet — pass --target <subnet>"
                    .to_string(),
            ),
        },
    }
}

-/// refuse with the flag named. Pure over its inputs for the decision-table

+/// refuse with the flag named. Pure over its inputs (the `Err` is the

+/// refusal message; callers decide whether it prints) for the

-                eprintln!("NO_SUBNET:{name} — this node is not a member");

+                Err(format!("NO_SUBNET:{name} — this node is not a member"))

-                    "HOME_NOT_MEMBER:{h} — the issuer's home subnet is not held on this \

-                     node; pass --target <subnet>"

-                    "NO_HOME_SUBNET: the issuer has no home subnet — pass --target <subnet>"

+                "HOME_NOT_MEMBER:{h} — the issuer's home subnet is not held on this \

+                 node; pass --target <subnet>"

+                "NO_HOME_SUBNET: the issuer has no home subnet — pass --target <subnet>"


    let subnets = SubnetStore::load();
    let home = issuer_home_subnet(&issuer);
    let subnet = match resolve_notify_subnet(&subnets, target, home) {
        Ok(s) => s,
        Err(code) => return code,
    };

    let subnets = SubnetStore::load();
    let home = issuer_home_subnet(&issuer);
    let subnet = match resolve_notify_subnet(&subnets, target, home) {
        Ok(s) => s,
        Err(msg) => {
            eprintln!("{msg}");
            return 1;
        }
    };


    let subnets = SubnetStore::load();
    let home = issuer_home_subnet(&issuer);
    let subnet = match resolve_notify_subnet(&subnets, target, home) {
        Ok(s) => s,
        Err(code) => return code,
    };

    let subnets = SubnetStore::load();
    let home = issuer_home_subnet(&issuer);
    let subnet = match resolve_notify_subnet(&subnets, target, home) {
        Ok(s) => s,
        Err(msg) => {
            eprintln!("{msg}");
            return 1;
        }
    };

let body = body.unwrap_or_else(read_stdin);


    #[test]
    fn notify_subnet_resolution_home_default() {
        let mut store = SubnetStore::default();
        // No target, no home => refuse.
        assert_eq!(resolve_notify_subnet(&store, None, None), Err(1));
        // Explicit non-member target => refuse.
        assert_eq!(
            resolve_notify_subnet(&store, Some("home".into()), None),
            Err(1)
        );

        store.create_subnet("home").unwrap();
        store.create_subnet("work").unwrap();
        // Explicit member target wins, home ignored.
        assert_eq!(
            resolve_notify_subnet(&store, Some("work".into()), Some("home".into())),
            Ok("work".to_string())
        );
        // No target: the home subnet is the defined default (several held —
        // home still resolves, unlike the old sole-subnet guess).
        assert_eq!(
            resolve_notify_subnet(&store, None, Some("home".into())),
            Ok("home".to_string())
        );
        // A home this node does not hold refuses (deny, don't guess).
        assert_eq!(
            resolve_notify_subnet(&store, None, Some("elsewhere".into())),
            Err(1)
        );
        // No home resolvable => refuse even with subnets held.
        assert_eq!(resolve_notify_subnet(&store, None, None), Err(1));
    }

    #[test]
    fn notify_subnet_resolution_home_default() {
        let mut store = SubnetStore::default();
        // No target, no home => refuse.
        assert!(resolve_notify_subnet(&store, None, None).is_err());
        // Explicit non-member target => refuse.
        assert!(resolve_notify_subnet(&store, Some("home".into()), None).is_err());

        store.create_subnet("home").unwrap();
        store.create_subnet("work").unwrap();
        // Explicit member target wins, home ignored.
        assert_eq!(
            resolve_notify_subnet(&store, Some("work".into()), Some("home".into())),
            Ok("work".to_string())
        );
        // No target: the home subnet is the defined default (several held —
        // home still resolves, unlike the old sole-subnet guess).
        assert_eq!(
            resolve_notify_subnet(&store, None, Some("home".into())),
            Ok("home".to_string())
        );
        // A home this node does not hold refuses (deny, don't guess).
        assert!(resolve_notify_subnet(&store, None, Some("elsewhere".into())).is_err());
        // No home resolvable => refuse even with subnets held.
        assert!(resolve_notify_subnet(&store, None, None).is_err());
    }


    #[test]
    fn notify_subnet_resolution_home_default() {
        let mut store = SubnetStore::default();
        // No target, no home => refuse.
        assert_eq!(resolve_notify_subnet(&store, None, None), Err(1));
        // Explicit non-member target => refuse.
        assert_eq!(
            resolve_notify_subnet(&store, Some("home".into()), None),
            Err(1)
        );

        store.create_subnet("home").unwrap();
        store.create_subnet("work").unwrap();
        // Explicit member target wins, home ignored.
        assert_eq!(
            resolve_notify_subnet(&store, Some("work".into()), Some("home".into())),
            Ok("work".to_string())
        );
        // No target: the home subnet is the defined default (several held —
        // home still resolves, unlike the old sole-subnet guess).
        assert_eq!(
            resolve_notify_subnet(&store, None, Some("home".into())),
            Ok("home".to_string())
        );
        // A home this node does not hold refuses (deny, don't guess).
        assert_eq!(
            resolve_notify_subnet(&store, None, Some("elsewhere".into())),
            Err(1)
        );
        // No home resolvable => refuse even with subnets held.
        assert_eq!(resolve_notify_subnet(&store, None, None), Err(1));
    }

    #[test]
    fn notify_subnet_resolution_home_default() {
        let mut store = SubnetStore::default();
        // No target, no home => refuse.
        assert!(resolve_notify_subnet(&store, None, None).is_err());
        // Explicit non-member target => refuse.
        assert!(resolve_notify_subnet(&store, Some("home".into()), None).is_err());

        store.create_subnet("home").unwrap();
        store.create_subnet("work").unwrap();
        // Explicit member target wins, home ignored.
        assert_eq!(
            resolve_notify_subnet(&store, Some("work".into()), Some("home".into())),
            Ok("work".to_string())
        );
        // No target: the home subnet is the defined default (several held —
        // home still resolves, unlike the old sole-subnet guess).
        assert_eq!(
            resolve_notify_subnet(&store, None, Some("home".into())),
            Ok("home".to_string())
        );
        // A home this node does not hold refuses (deny, don't guess).
        assert!(resolve_notify_subnet(&store, None, Some("elsewhere".into())).is_err());
        // No home resolvable => refuse even with subnets held.
        assert!(resolve_notify_subnet(&store, None, None).is_err());
    }

-        assert_eq!(resolve_notify_subnet(&store, None, None), Err(1));

+        assert!(resolve_notify_subnet(&store, None, None).is_err());

-            resolve_notify_subnet(&store, Some("home".into()), None),

+        assert!(resolve_notify_subnet(&store, Some("home".into()), None).is_err());

-            resolve_notify_subnet(&store, None, Some("elsewhere".into())),

+        assert!(resolve_notify_subnet(&store, None, Some("elsewhere".into())).is_err());

// [unit->REQ-SEC-1] the access whitelist surface parses 1:1 under the


PowerShell

cargo build -q -p spt -p spt-daemon 2>&1 | Select-Object -First 60

Build spt + spt-daemon


(PowerShell completed with no output)


Builds. Run tests.


cargo test -q -p spt --bin spt 2>&1 | Select-Object -Last 25

Run spt bin unit tests


Error: Exit code 101
running 97 tests
......................................... 41/97
cli::tests::help_groups_cover_every_command --- FAILED
.......................................................
failures:

---- cli::tests::help_groups_cover_every_command stdout ----

thread 'cli::tests::help_groups_cover_every_command' (40592) panicked at crates\spt\src\cli.rs:4120:13:
endpoint: about too long for an unwrapped 80-col entry (71 chars)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    cli::tests::help_groups_cover_every_command

test result: FAILED. 96 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 28.81s

error: test failed, to rerun pass `-p spt --bin spt`


Edit

    /// Endpoint operations: list, lifecycle, fork, digest, access, description.
    ///
    /// The noun home for per-endpoint verbs (M8 decision 1). Bare

    /// Endpoint operations: list, lifecycle, fork, digest, access.
    ///
    /// The noun home for per-endpoint verbs (M8 decision 1). Bare


    /// Endpoint operations: list, lifecycle, fork, digest, access, description.
    ///
    /// The noun home for per-endpoint verbs (M8 decision 1). Bare

    /// Endpoint operations: list, lifecycle, fork, digest, access.
    ///
    /// The noun home for per-endpoint verbs (M8 decision 1). Bare

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

-    /// Endpoint operations: list, lifecycle, fork, digest, access, description.

+    /// Endpoint operations: list, lifecycle, fork, digest, access.

/// `spt endpoint` renders the merged listing — every member subnet's


D3 — install & elevation hardening

D5 — docs + CI + acceptance prep


PowerShell

cargo test -q -p spt --bin spt cli::tests 2>&1 | Select-Object -Last 8

Re-run cli unit tests


running 44 tests
............................................
test result: ok. 44 passed; 0 failed; 0 ignored; 0 measured; 53 filtered out; finished in 27.70s


cargo test -q -p spt-daemon --lib seedmap 2>&1 | Select-Object -Last 8

Run seedmap tests incl. stop op


running 4 tests
....
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 190 filtered out; finished in 0.03s


cargo test -q -p spt 2>&1 | Select-Object -Last 40

Full spt crate test sweep


..........
test result: ok. 97 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 28.75s

running 4 tests
SEEDED:14704
SEEDED:14704
....
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.55s

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

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

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

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

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

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


cargo test -q -p spt-daemon --lib 2>&1 | Select-Object -Last 6

spt-daemon lib tests


running 194 tests
....................................................................................... 87/194
....................................................................................... 174/194
....................
test result: ok. 194 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.52s


cargo run -q -p xtask -- gen 2>&1 | Select-Object -Last 3

Regenerate CLI reference


xtask gen: wrote C:\Users\decid\Documents\projects\spt-core\docs-site/src/cli/reference.md