1	# spt-core Roadmap
2
3	> Lightweight-but-structured path from design → fruition. Not a GSD/phase-gated process — a clear sequence with de-risk gates. Planning corpus is complete (PRD + CONTEXT + 5 ADRs + 6 design docs); this is how we build it.
4
5	## End goal
6
7	A harness-independent core for an agent ecosystem — messaging, live-agent lifecycle, terminal hosting, **first-class P2P networking**, and a runtime-manifest harness contract — shipped as Rust crates + a single `spt` binary. The proof: the same agent runs natively across all of a user's machines with a synced mind, reachable and operable from anywhere, with seamless self-update and zero-config cross-machine messaging. First thing built atop it: a rebuilt `spt` plugin at parity with today's modern SPT.
8
9	## Scope decisions (locked)
10
11	- **v1 = the full PRD, networking included.** The networked element is the *reason* spt-core exists; it is not deferrable to v1.1.
12	- **The hard freeze on `claude_skill_owl` is not a clock.** It works today and delivers value as-is while spt-core is built — no schedule pressure from it.
13	- **Process: lightweight but structured.** GSD is too heavy. This roadmap + the design docs are the structure.
14
15	## Path to fruition (sequence)
16
17	**Stage A — Red-team review (before building).** Pressure-test the design adversarially. Focus on the riskiest architectural decisions, not bikeshedding. Tools: `plan-eng-review` (architecture) + `codex` (adversarial second opinion). Scope-cutting is *not* a goal (v1 is locked as full PRD), but surfacing contradictions / over-engineering / unproven assumptions is.
18
19	**Stage B — De-risk spikes.** Throwaway proofs of the load-bearing unknowns — anything that, if it fails, forces a redesign. Do these before committing to the full build. See *De-risk spikes* below.
20
21	**Stage C — Walking skeleton.** Build the killer-quickstart slice end-to-end through the real crate boundaries: **two agents exchange a message** (first local; ultimately cross-node — the real proof). Validates the whole stack early; then thicken.
22
23	**Stage D — Phased build (M0–M5).** Dependency-ordered, each milestone ships something usable.
24
25	## Milestones (all v1)
26
27	- **M0 — Workspace + commodity layer.** Cargo workspace; `spt-proto` (wire envelope grammar, endpoint types, framing, Ed25519 identity, typed+binary payloads) + `spt-store` (spool, perch layout, trust store, registry persistence). Copy-verbatim the stable formats from the sister project (ADR-0001).
28	- **M1 — Local messaging + binary.** `spt-msg` (delivery TCP+spool, routing, send/ring/ready) + the `spt` binary + CLI. **= the killer quickstart (local): two agents exchange a message.** Task breakdown: [`M1-PLAN.md`](./M1-PLAN.md).
29	- **M2 — Harness contract + lifecycle.** **Strictly harness-agnostic — zero Claude Code conventions in this repo.** spt-core defines the adapter contract; the only adapter-shaped artifact in-tree is a generic mock/test adapter (manifest fixture + fake `api` caller) that exercises the contract (PRD R-DOCS-2 dev-agent quickstart). **The rebuilt `spt` plugin (Claude Code) is a SEPARATE downstream project** built atop these crates/binary — spt-core v1's acceptance proof (PRD §"feature parity"), not an spt-core deliverable. It lives and builds outside this repo. **Split into two JIT milestones** (decision 2026-06-01):
30	  - **M2a — Harness contract. ✅ delivered (2026-06-01).** `spt-runtime` (AgentRuntime, ManifestRuntime, manifest schema) + the `api` subcommand surface (seed/listen/bind/state/echo-gate/poll/worker-*/boundary/session-end/presence/history-log/emit/capability) + the **harness-hosted** startup topology (seed→listen→bind, interim seed-file) + local-api-auth + the generic mock adapter + contract E2E. All M2a reqs activated + green. Task breakdown: [`M2a-PLAN.md`](./M2a-PLAN.md).
31	  - **M2b — Live-agent lifecycle. ✅ delivered (2026-06-01).** `spt-live` (fifth layer): history subsystem (fetcher/locate-normalize/native) + spawn-psyche (nested perch, spt-core-owned `$psyche_prompt`) + echo-commune (history brief → single-writer commune drop) + drop-file ingest (single-writer 6.4 + direct-write precedence 6.5) + interim pulse + signoff/boundary ordering invariants (echo-before-signoff 3.3, grace-before-signoff 1.1, stale-signoff sweep 3.2) + resume seam (continue-existing / fresh-with-preload). Hosted by the `api listen` runner + `api shutdown`; mock adapter gained the lifecycle templates; LiveAgent E2E proves spawn→Psyche-perch→commune-ingest→graceful-signoff harness-agnostically. All M2b reqs activated + green. Task breakdown: [`M2b-PLAN.md`](./M2b-PLAN.md).
32	  - **Interim, no daemon** (like M1): the `api listen` process holds the perch + relays; the seed bridges via a file. **The spt-hosted topology (PTY-launch) + the consolidated `spt-daemon` move to M3** (they need the `spt-term` broker) — this narrows the earlier "both topologies in M2" wording.
33	- **M3 — Terminal wrapper + daemon + self-update.** `spt-term` (session-surface, PTY, broker) + the consolidated `spt-daemon` (broker/brain split) + peer-propagated signed self-update. Split M3a/M3b/M3c. Scope + carried-forward Spike #1 gaps: [`M3-PLAN.md`](./M3-PLAN.md).
34	  - **M3a — `spt-term` session-surface mechanism. ✅ delivered (2026-06-02).** The sixth crate (`…→spt-msg→spt-term`, sibling of spt-runtime): the `SessionSurface` trait + native PTY backends (ConPTY/forkpty via `portable-pty`, REQ-TERM-1) · ConPTY-DSR auto-answer drain pump (REQ-HAZARD-CONPTY-DSR) · send-keys/send-line injection (REQ-TERM-2) · bounded byte-stream (REQ-TERM-3) · the PTY-digest parser primitive (REQ-TERM-4 impl/unit, ADR-0008). OS-neutral per Spike #4/#5 (no OS's stream contract baked in; ANSI-stripped, tail-wins repaint tolerance). Validated on Windows (ConPTY) + Linux (forkpty). All M3a reqs activated + green. Task breakdown: [`M3a-PLAN.md`](./M3a-PLAN.md).
35	  - **M3b — `spt-daemon` broker/brain process. ✅ delivered (2026-06-03).** The seventh crate (`…→spt-live→spt-daemon→spt`) hosts many `spt-term` surfaces behind a **versioned local IPC** (named-pipe/UDS behind one `DaemonTransport` trait, forward-compatible frame schema): a **broker** (stable kernel — PTY masters, harness children, sockets; no logic) + a **brain** (restartable logic) so a brain kill/restart leaves the hosted PTY child + its output stream **intact and gapless** (Spike #1 made real; `gen_start = now()` each handoff). Consolidates the interim no-daemon model — the `api listen` pulse/Psyche/drop-ingest loops fold into the brain as config-paced in-process loops (only a thin stateless relay residue) · daemon-authoritative liveness (`info.json` `status` supersedes `is_process_alive` for hosted perches) · exactly-once broker↔brain boundary (broker-owned `EffectJournal` + dedup-at-effect, Spike #6) · auto-start + in-memory seed (the seed file is dead) · supervised-crash orphan-watch (carries the M2b graceful ordering) · the **digest daemon-half** (manifest `pty_digest` seam → `DigestParser` over the broker PTY, `spt digest <id>` pull + delta-stream + opt-in Path-B persistence → REQ-TERM-4 `int`). All M3b reqs activated + green; daemon E2E (spawn→Psyche-loop→commune→brain-restart-survives→graceful-signoff) on the CI matrix (ubuntu + windows). Task breakdown: [`M3b-PLAN.md`](./M3b-PLAN.md).
36	  - **M3c — signed self-update. ✅ delivered (2026-06-03).** The gated, signed, ripple-capable self-update engine (no new crate — it lives in `spt-daemon`, signing primitives in `release.rs`): the **update-class taxonomy** (brain-only / broker-compatible / broker-breaking, ADR-0004 §A) with the **brain-only zero-interruption swap** that drives the M3b handoff substrate (snapshot→drop→re-attach) so a live PTY child + its output stream survive the logic swap **untouched** (REQ-UPD-3, proven E2E `tests/brain_swap.rs`) · **verify-before-handoff** (Ed25519 over signed release metadata + SHA-256 artifact binding; `plan_verified` is the front door — no unverified binary reaches a handoff, REQ-UPD-2) · **rollback hardening** (monotonic version, metadata expiry, channel pinning, key revocation — REQ-HAZARD-UPDATE-ROLLBACK) · **consent gating** (default-gated, delivered to the most-recently-active live session via the new `info.json` `last_active_ms` recency stamp; opt-in full-auto — REQ-UPD-4) · **adapter ripple-update** (self-then-adapters via each manifest `[update]` avenue, with adapter content signing against the adapter's own key for `file_pull` / a `self_verifies` attest for opaque `delegated` updaters — REQ-UPD-5, REQ-SEAM-UPDATE). **Peer-propagation transport (REQ-UPD-1) defers to M4** — the engine ships with self-fetch/out-of-band delivery into the same seam. All M3c reqs activated + green. Task breakdown: [`M3c-PLAN.md`](./M3c-PLAN.md).
37	  - **M3 COMPLETE** (M3a + M3b + M3c all ✅, 2026-06-03): the real ADR-0004 architecture is live — native PTY hosting, the consolidated broker/brain daemon, and seamless signed self-update. Next milestone: M4.
38	- **M4 — Networking + instances. ✅ delivered (2026-06-04).** The eighth crate `spt-net` (Iroh WAN endpoint bound to the node's own Ed25519 identity, mDNS LAN discovery, TOTP-SPAKE2 pairing over a dedicated pre-trust ALPN with transcript binding / rate limiting / seed rotation+transfer, subnet registry + NDJSON replication under the per-node epoch lease) · the multi-instance model (registry rows + bare-id resolution refuse-and-qualify + visibility/sync-membership gates + rename/collision + **resting-state machine** dormant/suspended with measured warm/cold policy [`docs/DORMANCY-BUDGET.md`, red-team #9 closed] + **resource advertisement** REQ-INST-14 + **immutable home subnet & `spt fork`** REQ-INST-15, ADR-0010) · **cross-node Psyche sync** (git-native bundles over broker QUIC streams, two-tier scope gates, vector-precedence merge with conflicts surfaced + Psyche-reconciled, ADR-0013; **the gh-repo interim is retired**) · WAN messaging + remote-drive attach + chunked file transfer + per-endpoint access whitelists · the subnet **notification primitive** (replicated spool, dismiss-replication, boundary resurfacing; update consent + escalations refactored as producers) · the **update peer-propagation transport** into the M3c engine seam (offer-then-fetch, verify-before-stage per node, consent-notified — REQ-UPD-1) · production trigger loops (peer pump + inbound dispatcher — the subnet self-drives). **The real proof ran on the real rig: `[twohost]` CI run `26958175812` — pair → register → cross-node message → remote-drive (A typed into B's live PTY and read the echo) → Psyche sync both directions → update self-heal → notif dismiss-replication, HFENDULEAM (Windows) ↔ gravity-linux, whole ladder ~8 s** (`docs/TWO-HOST-RUNBOOK.md` + evidence appendix; `tests/twohost.rs` carries the nine activated `int` stages). Task breakdowns: [`M4-PLAN.md`](./M4-PLAN.md) + per-task JIT plans (D1–D9).
39	- **M5 — Shells, presence, deferred capabilities. ✅ delivered (2026-06-04;** scope locked with user 2026-06-04, see [`M5-PLAN.md`](./M5-PLAN.md) + per-task JIT plans D0–D9). The consent framework seam (grant store + interactive escalation + pre-consent flags; remote-exec / instantiate-anywhere reserved-but-refusing) · adapter registration lifecycle (`spt adapter add/remove`, manifest-first) · **shell hosting machinery + sleep/wake** (link-token channels with per-frame MAC, broker-launched binaries, wake-watcher supervision, state-keyed wake resolution) proven by the mock **plus one real shell**: the OS-notification adapter — **standalone repo `spt-shell-notify`, zero spt-core source integration** (the manifest + binary are the whole glue; spt-core CI cross-repo hook drives its E2E) · deferred-message resting gate (REQ-INST-6) + remote `spt suspend/wake <id@node>` · **presence resolution** (the gossiped `last_active_ms` datum riding the registry rows + one most-recently-active API; notif first-fire redirects cross-node to the user's active endpoint) · the `[session.notif]` render seam generalized to shell adapters (`spt notify` → native toast) · **cross-node owner↔shell link** (relink + command drive over the wire; spawn stays same-node) · carried M4 seams closed (relay rendezvous meet routing, `spt update apply` consent-ack orchestration, self-hosted cross-runner CI). **The M5 proof ran on the real rig: `[twohost]` run `26998058816` — the full 11-rung ladder (M4's seven + remote suspend/wake, presence-shift redirect, cross-node shell relink+drive, notif→toast) green with the REAL standalone adapter rendering A's `spt notify` at B, the host the user last touched** (`docs/TWO-HOST-RUNBOOK.md` M5 evidence appendix).
40
41	## Post-v1-core milestones
42
43	- **M6 — Stage-setting: `spt-releases`, dev docs, v0.1 release. ✅ delivered (2026-06-05** — planned, executed, and shipped in one day; decisions grilled `1922fa4`, task breakdown [`M6-PLAN.md`](./M6-PLAN.md)). spt-core is now **installable, documented, and released**: [`SaberMage/spt-releases`](https://github.com/SaberMage/spt-releases) is **public** with the signed **v0.1.0** release live and GitHub Pages serving the docs at the permanent canonical URL `https://sabermage.github.io/spt-releases` (ADR-0014; repo-face README/licenses truth-synced from `releases-repo/` here). Shipped: non-interactive installer scripts (sha256-verify, user PATH, env-knob CI/air-gap override; OS-service → DEFERRED) with a staged-release CI rung on both runners · manifest **JSON Schema generated from the parsing derives themselves** + drift gate · the Tier-1 mdBook docs corpus (Starlight-styled; both killer quickstarts with real captured outputs — the messaging one CI-executed step-for-step as an anti-rot rung; complete harness-contract vertical; shell getting-started via the real `spt-shell-notify`; llms.txt/llms-full; CLI reference generated from the binary's own `--help`, drift-gated; rustdoc) · `xtask` generation/publish plumbing + tag-triggered `docs-publish` and `release` pipelines (cross-repo via fine-grained PAT; **signing stays manual + local** — `release-keygen/sign/publish/verify`, seed via password-manager CLI) · the **embedded two-key trust anchor** (ADR-0015: `rel-primary-2026` + `rel-recovery-2026` compiled into every binary, keys-file as revocable overlay; ceremony run 2026-06-05, seeds custodied off-machine) · public-facing CLI help-text polish. **Acceptance ran against the LIVE release:** the real `irm|iex` one-liner installed the published binary (`spt 0.1.0`, sha256-verified) · the published quickstarts ran as written on the released binary · llms.txt + schema + raw-`.md` + rustdoc all serve from the canonical URL · both platforms' `SignedRelease` assets **verify against the embedded anchor** (`release_verify_e2e`, REQ-REL-2 int) · a v0.1.1-shaped next release **and a recovery-key-signed metadata** both verified with the real keys (the ADR-0015 loss story, proven). Side quest: `traceable-reqs` v0.1.2 (shell/PowerShell tag scanning) shipped to cover script evidence. **Next:** the v1 acceptance proof — `spt-claude-code`, a separate downstream repo — gets its own scoping conversation.
44
45	## De-risk spikes (do early — these can invalidate the architecture)
46
47	1. **Broker/brain no-terminate handoff** — prove a PTY child + open socket survive a daemon-*logic* restart (FD-passing or stable-broker shim). The hardest invariant in the design (ADR-0004).
48	2. **Iroh + mDNS smoke test** — two nodes, separate NATs, one message end-to-end; measure direct-vs-relay + binary size (research brief §5). Validates the whole networking premise.
49	3. **ConPTY + `portable-pty`** — host and drive a real session in a broker PTY on Windows + Linux.
50	4. **Manifest-driven lifecycle** — a manifest drives one real Claude Code session through spawn → bind → commune → signoff, proving harness-independence works in practice.
51
52	## Immediate next steps
53
54	Progress (2026-05-31):
55	- ✅ **Stage A — codex adversarial pass** — done. 14 findings (5 FATAL, 9 SERIOUS) in `docs/reviews/STAGE-A-codex-redteam.md`.
56	- ✅ **Spike #1 (broker/brain handoff)** — PASS on Windows ConPTY. `docs/spikes/SPIKE-01-broker-handoff.md`. Found+fixed the ConPTY DSR-stall hazard. 4 open gaps carried to M3.
57	- ✅ **Spike #2 (Iroh smoke test)** — PASS (single-host). `docs/spikes/SPIKE-02-iroh-smoke.md`. iroh 0.98 API workable; 13.57 MB release. Cross-NAT/relay proof deferred to M4.
58	- ✅ **Findings folded** into ADR-0002/0003/0004/0005 + KNOWN-HAZARDS (+3) + traceable-reqs.toml (+4 REQ-HAZARD).
59
60	Remaining before M0:
61	1. **Stage A — `plan-eng-review`** (interactive architecture pass; second half of Stage A).
62	2. **Spike #4 (manifest-driven lifecycle)** — a manifest drives one real session spawn→bind→commune→signoff. (Spike #3 ConPTY hosting is largely covered by Spike #1.)
63	3. Resolve the open user decisions logged in the ADR amendments (context-merge model, seed-rotation, R-UPD-3 rewording).
64	4. **M0** (workspace + `spt-proto` + `spt-store`) — **started**; task breakdown in [`M0-PLAN.md`](./M0-PLAN.md). Each milestone gets its own just-in-time plan (this one is M0); the roadmap stays the milestone sequence, the JIT plan is the task layer.
65
66	## Tooling: requirement traceability (adopted)
67
68	`traceable-reqs` is adopted as the requirement-tracing gate. `traceable-reqs.toml` seeds the `REQ-*` registry from PRD `R-*` + KNOWN-HAZARDS invariants (all inactive until milestone activation). The development contract + enforcement (CI gate, milestone activation, agent rule, quality audit) is `docs/TRACEABILITY.md`; the agent rule lives in `CLAUDE.md`. **M0 task:** install the CLI, validate the seed, wire the CI workflow, activate M0 reqs.
69
70	## Open threads carried forward
71
72	- New deferred items logged this session (`docs/DEFERRED.md`): block-level OCC context merge; the rebalance / Memory-Health pass; the consent framework's gated capabilities.
73	- The dual-audience docs strategy (`docs/DOCS-STRATEGY.md`) governs the *shipped* docs, authored as code lands.
74

# spt-core Roadmap

> Lightweight-but-structured path from design → fruition. Not a GSD/phase-gated process — a clear sequence with de-risk gates. Planning corpus is complete (PRD + CONTEXT + 5 ADRs + 6 design docs); this is how we build it.

## End goal

A harness-independent core for an agent ecosystem — messaging, live-agent lifecycle, terminal hosting, **first-class P2P networking**, and a runtime-manifest harness contract — shipped as Rust crates + a single `spt` binary. The proof: the same agent runs natively across all of a user's machines with a synced mind, reachable and operable from anywhere, with seamless self-update and zero-config cross-machine messaging. First thing built atop it: a rebuilt `spt` plugin at parity with today's modern SPT.

## Scope decisions (locked)

- **v1 = the full PRD, networking included.** The networked element is the *reason* spt-core exists; it is not deferrable to v1.1.
- **The hard freeze on `claude_skill_owl` is not a clock.** It works today and delivers value as-is while spt-core is built — no schedule pressure from it.
- **Process: lightweight but structured.** GSD is too heavy. This roadmap + the design docs are the structure.

## Path to fruition (sequence)

**Stage A — Red-team review (before building).** Pressure-test the design adversarially. Focus on the riskiest architectural decisions, not bikeshedding. Tools: `plan-eng-review` (architecture) + `codex` (adversarial second opinion). Scope-cutting is *not* a goal (v1 is locked as full PRD), but surfacing contradictions / over-engineering / unproven assumptions is.

**Stage B — De-risk spikes.** Throwaway proofs of the load-bearing unknowns — anything that, if it fails, forces a redesign. Do these before committing to the full build. See *De-risk spikes* below.

**Stage C — Walking skeleton.** Build the killer-quickstart slice end-to-end through the real crate boundaries: **two agents exchange a message** (first local; ultimately cross-node — the real proof). Validates the whole stack early; then thicken.

**Stage D — Phased build (M0–M5).** Dependency-ordered, each milestone ships something usable.

## Milestones (all v1)

- **M0 — Workspace + commodity layer.** Cargo workspace; `spt-proto` (wire envelope grammar, endpoint types, framing, Ed25519 identity, typed+binary payloads) + `spt-store` (spool, perch layout, trust store, registry persistence). Copy-verbatim the stable formats from the sister project (ADR-0001).
- **M1 — Local messaging + binary.** `spt-msg` (delivery TCP+spool, routing, send/ring/ready) + the `spt` binary + CLI. **= the killer quickstart (local): two agents exchange a message.** Task breakdown: [`M1-PLAN.md`](./M1-PLAN.md).
- **M2 — Harness contract + lifecycle.** **Strictly harness-agnostic — zero Claude Code conventions in this repo.** spt-core defines the adapter contract; the only adapter-shaped artifact in-tree is a generic mock/test adapter (manifest fixture + fake `api` caller) that exercises the contract (PRD R-DOCS-2 dev-agent quickstart). **The rebuilt `spt` plugin (Claude Code) is a SEPARATE downstream project** built atop these crates/binary — spt-core v1's acceptance proof (PRD §"feature parity"), not an spt-core deliverable. It lives and builds outside this repo. **Split into two JIT milestones** (decision 2026-06-01):
  - **M2a — Harness contract. ✅ delivered (2026-06-01).** `spt-runtime` (AgentRuntime, ManifestRuntime, manifest schema) + the `api` subcommand surface (seed/listen/bind/state/echo-gate/poll/worker-*/boundary/session-end/presence/history-log/emit/capability) + the **harness-hosted** startup topology (seed→listen→bind, interim seed-file) + local-api-auth + the generic mock adapter + contract E2E. All M2a reqs activated + green. Task breakdown: [`M2a-PLAN.md`](./M2a-PLAN.md).
  - **M2b — Live-agent lifecycle. ✅ delivered (2026-06-01).** `spt-live` (fifth layer): history subsystem (fetcher/locate-normalize/native) + spawn-psyche (nested perch, spt-core-owned `$psyche_prompt`) + echo-commune (history brief → single-writer commune drop) + drop-file ingest (single-writer 6.4 + direct-write precedence 6.5) + interim pulse + signoff/boundary ordering invariants (echo-before-signoff 3.3, grace-before-signoff 1.1, stale-signoff sweep 3.2) + resume seam (continue-existing / fresh-with-preload). Hosted by the `api listen` runner + `api shutdown`; mock adapter gained the lifecycle templates; LiveAgent E2E proves spawn→Psyche-perch→commune-ingest→graceful-signoff harness-agnostically. All M2b reqs activated + green. Task breakdown: [`M2b-PLAN.md`](./M2b-PLAN.md).
  - **Interim, no daemon** (like M1): the `api listen` process holds the perch + relays; the seed bridges via a file. **The spt-hosted topology (PTY-launch) + the consolidated `spt-daemon` move to M3** (they need the `spt-term` broker) — this narrows the earlier "both topologies in M2" wording.
- **M3 — Terminal wrapper + daemon + self-update.** `spt-term` (session-surface, PTY, broker) + the consolidated `spt-daemon` (broker/brain split) + peer-propagated signed self-update. Split M3a/M3b/M3c. Scope + carried-forward Spike #1 gaps: [`M3-PLAN.md`](./M3-PLAN.md).
  - **M3a — `spt-term` session-surface mechanism. ✅ delivered (2026-06-02).** The sixth crate (`…→spt-msg→spt-term`, sibling of spt-runtime): the `SessionSurface` trait + native PTY backends (ConPTY/forkpty via `portable-pty`, REQ-TERM-1) · ConPTY-DSR auto-answer drain pump (REQ-HAZARD-CONPTY-DSR) · send-keys/send-line injection (REQ-TERM-2) · bounded byte-stream (REQ-TERM-3) · the PTY-digest parser primitive (REQ-TERM-4 impl/unit, ADR-0008). OS-neutral per Spike #4/#5 (no OS's stream contract baked in; ANSI-stripped, tail-wins repaint tolerance). Validated on Windows (ConPTY) + Linux (forkpty). All M3a reqs activated + green. Task breakdown: [`M3a-PLAN.md`](./M3a-PLAN.md).
  - **M3b — `spt-daemon` broker/brain process. ✅ delivered (2026-06-03).** The seventh crate (`…→spt-live→spt-daemon→spt`) hosts many `spt-term` surfaces behind a **versioned local IPC** (named-pipe/UDS behind one `DaemonTransport` trait, forward-compatible frame schema): a **broker** (stable kernel — PTY masters, harness children, sockets; no logic) + a **brain** (restartable logic) so a brain kill/restart leaves the hosted PTY child + its output stream **intact and gapless** (Spike #1 made real; `gen_start = now()` each handoff). Consolidates the interim no-daemon model — the `api listen` pulse/Psyche/drop-ingest loops fold into the brain as config-paced in-process loops (only a thin stateless relay residue) · daemon-authoritative liveness (`info.json` `status` supersedes `is_process_alive` for hosted perches) · exactly-once broker↔brain boundary (broker-owned `EffectJournal` + dedup-at-effect, Spike #6) · auto-start + in-memory seed (the seed file is dead) · supervised-crash orphan-watch (carries the M2b graceful ordering) · the **digest daemon-half** (manifest `pty_digest` seam → `DigestParser` over the broker PTY, `spt digest <id>` pull + delta-stream + opt-in Path-B persistence → REQ-TERM-4 `int`). All M3b reqs activated + green; daemon E2E (spawn→Psyche-loop→commune→brain-restart-survives→graceful-signoff) on the CI matrix (ubuntu + windows). Task breakdown: [`M3b-PLAN.md`](./M3b-PLAN.md).
  - **M3c — signed self-update. ✅ delivered (2026-06-03).** The gated, signed, ripple-capable self-update engine (no new crate — it lives in `spt-daemon`, signing primitives in `release.rs`): the **update-class taxonomy** (brain-only / broker-compatible / broker-breaking, ADR-0004 §A) with the **brain-only zero-interruption swap** that drives the M3b handoff substrate (snapshot→drop→re-attach) so a live PTY child + its output stream survive the logic swap **untouched** (REQ-UPD-3, proven E2E `tests/brain_swap.rs`) · **verify-before-handoff** (Ed25519 over signed release metadata + SHA-256 artifact binding; `plan_verified` is the front door — no unverified binary reaches a handoff, REQ-UPD-2) · **rollback hardening** (monotonic version, metadata expiry, channel pinning, key revocation — REQ-HAZARD-UPDATE-ROLLBACK) · **consent gating** (default-gated, delivered to the most-recently-active live session via the new `info.json` `last_active_ms` recency stamp; opt-in full-auto — REQ-UPD-4) · **adapter ripple-update** (self-then-adapters via each manifest `[update]` avenue, with adapter content signing against the adapter's own key for `file_pull` / a `self_verifies` attest for opaque `delegated` updaters — REQ-UPD-5, REQ-SEAM-UPDATE). **Peer-propagation transport (REQ-UPD-1) defers to M4** — the engine ships with self-fetch/out-of-band delivery into the same seam. All M3c reqs activated + green. Task breakdown: [`M3c-PLAN.md`](./M3c-PLAN.md).
  - **M3 COMPLETE** (M3a + M3b + M3c all ✅, 2026-06-03): the real ADR-0004 architecture is live — native PTY hosting, the consolidated broker/brain daemon, and seamless signed self-update. Next milestone: M4.
- **M4 — Networking + instances. ✅ delivered (2026-06-04).** The eighth crate `spt-net` (Iroh WAN endpoint bound to the node's own Ed25519 identity, mDNS LAN discovery, TOTP-SPAKE2 pairing over a dedicated pre-trust ALPN with transcript binding / rate limiting / seed rotation+transfer, subnet registry + NDJSON replication under the per-node epoch lease) · the multi-instance model (registry rows + bare-id resolution refuse-and-qualify + visibility/sync-membership gates + rename/collision + **resting-state machine** dormant/suspended with measured warm/cold policy [`docs/DORMANCY-BUDGET.md`, red-team #9 closed] + **resource advertisement** REQ-INST-14 + **immutable home subnet & `spt fork`** REQ-INST-15, ADR-0010) · **cross-node Psyche sync** (git-native bundles over broker QUIC streams, two-tier scope gates, vector-precedence merge with conflicts surfaced + Psyche-reconciled, ADR-0013; **the gh-repo interim is retired**) · WAN messaging + remote-drive attach + chunked file transfer + per-endpoint access whitelists · the subnet **notification primitive** (replicated spool, dismiss-replication, boundary resurfacing; update consent + escalations refactored as producers) · the **update peer-propagation transport** into the M3c engine seam (offer-then-fetch, verify-before-stage per node, consent-notified — REQ-UPD-1) · production trigger loops (peer pump + inbound dispatcher — the subnet self-drives). **The real proof ran on the real rig: `[twohost]` CI run `26958175812` — pair → register → cross-node message → remote-drive (A typed into B's live PTY and read the echo) → Psyche sync both directions → update self-heal → notif dismiss-replication, HFENDULEAM (Windows) ↔ gravity-linux, whole ladder ~8 s** (`docs/TWO-HOST-RUNBOOK.md` + evidence appendix; `tests/twohost.rs` carries the nine activated `int` stages). Task breakdowns: [`M4-PLAN.md`](./M4-PLAN.md) + per-task JIT plans (D1–D9).
- **M5 — Shells, presence, deferred capabilities. ✅ delivered (2026-06-04;** scope locked with user 2026-06-04, see [`M5-PLAN.md`](./M5-PLAN.md) + per-task JIT plans D0–D9). The consent framework seam (grant store + interactive escalation + pre-consent flags; remote-exec / instantiate-anywhere reserved-but-refusing) · adapter registration lifecycle (`spt adapter add/remove`, manifest-first) · **shell hosting machinery + sleep/wake** (link-token channels with per-frame MAC, broker-launched binaries, wake-watcher supervision, state-keyed wake resolution) proven by the mock **plus one real shell**: the OS-notification adapter — **standalone repo `spt-shell-notify`, zero spt-core source integration** (the manifest + binary are the whole glue; spt-core CI cross-repo hook drives its E2E) · deferred-message resting gate (REQ-INST-6) + remote `spt suspend/wake <id@node>` · **presence resolution** (the gossiped `last_active_ms` datum riding the registry rows + one most-recently-active API; notif first-fire redirects cross-node to the user's active endpoint) · the `[session.notif]` render seam generalized to shell adapters (`spt notify` → native toast) · **cross-node owner↔shell link** (relink + command drive over the wire; spawn stays same-node) · carried M4 seams closed (relay rendezvous meet routing, `spt update apply` consent-ack orchestration, self-hosted cross-runner CI). **The M5 proof ran on the real rig: `[twohost]` run `26998058816` — the full 11-rung ladder (M4's seven + remote suspend/wake, presence-shift redirect, cross-node shell relink+drive, notif→toast) green with the REAL standalone adapter rendering A's `spt notify` at B, the host the user last touched** (`docs/TWO-HOST-RUNBOOK.md` M5 evidence appendix).

## Post-v1-core milestones

- **M6 — Stage-setting: `spt-releases`, dev docs, v0.1 release. ✅ delivered (2026-06-05** — planned, executed, and shipped in one day; decisions grilled `1922fa4`, task breakdown [`M6-PLAN.md`](./M6-PLAN.md)). spt-core is now **installable, documented, and released**: [`SaberMage/spt-releases`](https://github.com/SaberMage/spt-releases) is **public** with the signed **v0.1.0** release live and GitHub Pages serving the docs at the permanent canonical URL `https://sabermage.github.io/spt-releases` (ADR-0014; repo-face README/licenses truth-synced from `releases-repo/` here). Shipped: non-interactive installer scripts (sha256-verify, user PATH, env-knob CI/air-gap override; OS-service → DEFERRED) with a staged-release CI rung on both runners · manifest **JSON Schema generated from the parsing derives themselves** + drift gate · the Tier-1 mdBook docs corpus (Starlight-styled; both killer quickstarts with real captured outputs — the messaging one CI-executed step-for-step as an anti-rot rung; complete harness-contract vertical; shell getting-started via the real `spt-shell-notify`; llms.txt/llms-full; CLI reference generated from the binary's own `--help`, drift-gated; rustdoc) · `xtask` generation/publish plumbing + tag-triggered `docs-publish` and `release` pipelines (cross-repo via fine-grained PAT; **signing stays manual + local** — `release-keygen/sign/publish/verify`, seed via password-manager CLI) · the **embedded two-key trust anchor** (ADR-0015: `rel-primary-2026` + `rel-recovery-2026` compiled into every binary, keys-file as revocable overlay; ceremony run 2026-06-05, seeds custodied off-machine) · public-facing CLI help-text polish. **Acceptance ran against the LIVE release:** the real `irm|iex` one-liner installed the published binary (`spt 0.1.0`, sha256-verified) · the published quickstarts ran as written on the released binary · llms.txt + schema + raw-`.md` + rustdoc all serve from the canonical URL · both platforms' `SignedRelease` assets **verify against the embedded anchor** (`release_verify_e2e`, REQ-REL-2 int) · a v0.1.1-shaped next release **and a recovery-key-signed metadata** both verified with the real keys (the ADR-0015 loss story, proven). Side quest: `traceable-reqs` v0.1.2 (shell/PowerShell tag scanning) shipped to cover script evidence. **Next:** the v1 acceptance proof — `spt-claude-code`, a separate downstream repo — gets its own scoping conversation.

## De-risk spikes (do early — these can invalidate the architecture)

1. **Broker/brain no-terminate handoff** — prove a PTY child + open socket survive a daemon-*logic* restart (FD-passing or stable-broker shim). The hardest invariant in the design (ADR-0004).
2. **Iroh + mDNS smoke test** — two nodes, separate NATs, one message end-to-end; measure direct-vs-relay + binary size (research brief §5). Validates the whole networking premise.
3. **ConPTY + `portable-pty`** — host and drive a real session in a broker PTY on Windows + Linux.
4. **Manifest-driven lifecycle** — a manifest drives one real Claude Code session through spawn → bind → commune → signoff, proving harness-independence works in practice.

## Immediate next steps

Progress (2026-05-31):
- ✅ **Stage A — codex adversarial pass** — done. 14 findings (5 FATAL, 9 SERIOUS) in `docs/reviews/STAGE-A-codex-redteam.md`.
- ✅ **Spike #1 (broker/brain handoff)** — PASS on Windows ConPTY. `docs/spikes/SPIKE-01-broker-handoff.md`. Found+fixed the ConPTY DSR-stall hazard. 4 open gaps carried to M3.
- ✅ **Spike #2 (Iroh smoke test)** — PASS (single-host). `docs/spikes/SPIKE-02-iroh-smoke.md`. iroh 0.98 API workable; 13.57 MB release. Cross-NAT/relay proof deferred to M4.
- ✅ **Findings folded** into ADR-0002/0003/0004/0005 + KNOWN-HAZARDS (+3) + traceable-reqs.toml (+4 REQ-HAZARD).

Remaining before M0:
1. **Stage A — `plan-eng-review`** (interactive architecture pass; second half of Stage A).
2. **Spike #4 (manifest-driven lifecycle)** — a manifest drives one real session spawn→bind→commune→signoff. (Spike #3 ConPTY hosting is largely covered by Spike #1.)
3. Resolve the open user decisions logged in the ADR amendments (context-merge model, seed-rotation, R-UPD-3 rewording).
4. **M0** (workspace + `spt-proto` + `spt-store`) — **started**; task breakdown in [`M0-PLAN.md`](./M0-PLAN.md). Each milestone gets its own just-in-time plan (this one is M0); the roadmap stays the milestone sequence, the JIT plan is the task layer.

## Tooling: requirement traceability (adopted)

`traceable-reqs` is adopted as the requirement-tracing gate. `traceable-reqs.toml` seeds the `REQ-*` registry from PRD `R-*` + KNOWN-HAZARDS invariants (all inactive until milestone activation). The development contract + enforcement (CI gate, milestone activation, agent rule, quality audit) is `docs/TRACEABILITY.md`; the agent rule lives in `CLAUDE.md`. **M0 task:** install the CLI, validate the seed, wire the CI workflow, activate M0 reqs.

## Open threads carried forward

- New deferred items logged this session (`docs/DEFERRED.md`): block-level OCC context merge; the rebalance / Memory-Health pass; the consent framework's gated capabilities.
- The dual-audience docs strategy (`docs/DOCS-STRATEGY.md`) governs the *shipped* docs, authored as code lands.


make roadmap changes, then commit. then /commune with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session


Fix help text wrapping and reorganize command list


Edit

## De-risk spikes (do early — these can invalidate the architecture)

- **M7 — Subnet & quickstart UX. 📋 planned (2026-06-05** — grill session; decisions ratified, task breakdown [`M7-PLAN.md`](./M7-PLAN.md)). Makes subnet setup a first-class, guided, secure product surface and the public quickstart honest about personas: the **join ceremony gets its product surface** (always-on daemon pairing responder + guided `spt subnet join` CLI initiator — today the rig-proven SPAKE2 wire is test-only) · new `spt subnet` noun namespace (`status [NAME] [--nodes]` / `create` / `show-totp` / `join`; `spt pair` deleted) · **elevation-gated membership mutations** (`create`/`join` — trust-boundary changes; ADR-0005 amendment) · **node labels** (hostname-default, gossiped, addressable in `@node` qualifiers with refuse-on-ambiguity) · `spt poll` removed (`ready` + `ready --once`) · **`spt how-to <topic>`** in-binary agent instructions (anti-drift: prompts point agents at the binary's own guidance) · quickstart rewritten as human-pairs / agents-message prompt blocks (lea + sergey; collapsible optional pairing section) · README/docs-site sweep + `quickstart_e2e` + a product-surface join rung on the twohost ladder. Runs **before** `spt-claude-code` scoping — CLI breaking changes are cheapest while no adapter depends on the surface.

## De-risk spikes (do early — these can invalidate the architecture)


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

- **M7 — Subnet & quickstart UX. 📋 planned (2026-06-05** — grill session; decisions ratified, task breakdown [`M7-PLAN.md`](./M7-PLAN.md)). Makes subnet setup a first-class, guided, secure product surface and the public quickstart honest about personas: the **join ceremony gets its product surface** (always-on daemon pairing responder + guided `spt subnet join` CLI initiator — today the rig-proven SPAKE2 wire is test-only) · new `spt subnet` noun namespace (`status [NAME] [--nodes]` / `create` / `show-totp` / `join`; `spt pair` deleted) · **elevation-gated membership mutations** (`create`/`join` — trust-boundary changes; ADR-0005 amendment) · **node labels** (hostname-default, gossiped, addressable in `@node` qualifiers with refuse-on-ambiguity) · `spt poll` removed (`ready` + `ready --once`) · **`spt how-to <topic>`** in-binary agent instructions (anti-drift: prompts point agents at the binary's own guidance) · quickstart rewritten as human-pairs / agents-message prompt blocks (lea + sergey; collapsible optional pairing section) · README/docs-site sweep + `quickstart_e2e` + a product-surface join rung on the twohost ladder. Runs **before** `spt-claude-code` scoping — CLI breaking changes are cheapest while no adapter depends on the surface.

## De-risk spikes (do early — these can invalidate the architecture)

- **M6 — Stage-setting: `spt-releases`, dev docs, v0.1 release. ✅ delivered (2026-06-05** — planned, executed, and shipped in one day; decisions grilled `1922fa4`, task breakdown [`M6-PLAN.md`](./M6-PLAN.md)). spt-core is now **installable, documented, and released**: [`SaberMage/spt-releases`](https://github.com/SaberMage/spt-releases) is **public** with the signed **v0.1.0** release live and GitHub Pages serving the docs at the permanent canonical URL `https://sabermage.github.io/spt-releases` (ADR-0014; repo-face README/licenses truth-synced from `releases-repo/` here). Shipped: non-interactive installer scripts (sha256-verify, user PATH, env-knob CI/air-gap override; OS-service → DEFERRED) with a staged-release CI rung on both runners · manifest **JSON Schema generated from the parsing derives themselves** + drift gate · the Tier-1 mdBook docs corpus (Starlight-styled; both killer quickstarts with real captured outputs — the messaging one CI-executed step-for-step as an anti-rot rung; complete harness-contract vertical; shell getting-started via the real `spt-shell-notify`; llms.txt/llms-full; CLI reference generated from the binary's own `--help`, drift-gated; rustdoc) · `xtask` generation/publish plumbing + tag-triggered `docs-publish` and `release` pipelines (cross-repo via fine-grained PAT; **signing stays manual + local** — `release-keygen/sign/publish/verify`, seed via password-manager CLI) · the **embedded two-key trust anchor** (ADR-0015: `rel-primary-2026` + `rel-recovery-2026` compiled into every binary, keys-file as revocable overlay; ceremony run 2026-06-05, seeds custodied off-machine) · public-facing CLI help-text polish. **Acceptance ran against the LIVE release:** the real `irm|iex` one-liner installed the published binary (`spt 0.1.0`, sha256-verified) · the published quickstarts ran as written on the released binary · llms.txt + schema + raw-`.md` + rustdoc all serve from the canonical URL · both platforms' `SignedRelease` assets **verify against the embedded anchor** (`release_verify_e2e`, REQ-REL-2 int) · a v0.1.1-shaped next release **and a recovery-key-signed metadata** both verified with the real keys (the ADR-0015 loss story, proven). Side quest: `traceable-reqs` v0.1.2 (shell/PowerShell tag scanning) shipped to cover script evidence. **Next:** the v1 acceptance proof — `spt-claude-code`, a separate downstream repo — gets its own scoping conversation.

+- **M7 — Subnet & quickstart UX. 📋 planned (2026-06-05** — grill session; decisions ratified, task breakdown [`M7-PLAN.md`](./M7-PLAN.md)). Makes subnet setup a first-class, guided, secure product surface and the public quickstart honest about personas: the **join ceremony gets its product surface** (always-on daemon pairing responder + guided `spt subnet join` CLI initiator — today the rig-proven SPAKE2 wire is test-only) · new `spt subnet` noun namespace (`status [NAME] [--nodes]` / `create` / `show-totp` / `join`; `spt pair` deleted) · **elevation-gated membership mutations** (`create`/`join` — trust-boundary changes; ADR-0005 amendment) · **node labels** (hostname-default, gossiped, addressable in `@node` qualifiers with refuse-on-ambiguity) · `spt poll` removed (`ready` + `ready --once`) · **`spt how-to <topic>`** in-binary agent instructions (anti-drift: prompts point agents at the binary's own guidance) · quickstart rewritten as human-pairs / agents-message prompt blocks (lea + sergey; collapsible optional pairing section) · README/docs-site sweep + `quickstart_e2e` + a product-surface join rung on the twohost ladder. Runs **before** `spt-claude-code` scoping — CLI breaking changes are cheapest while no adapter depends on the surface.

1. **Broker/brain no-terminate handoff** — prove a PTY child + open socket survive a daemon-*logic* restart (FD-passing or stable-broker shim). The hardest invariant in the design (ADR-0004).


**Next:** the v1 acceptance proof — `spt-claude-code`, a separate downstream repo — gets its own scoping conversation.

**Next:** M7 (subnet & quickstart UX), then the v1 acceptance proof — `spt-claude-code`, a separate downstream repo — gets its own scoping conversation.


-- **M6 — Stage-setting: `spt-releases`, dev docs, v0.1 release. ✅ delivered (2026-06-05** — planned, executed, and shipped in one day; decisions grilled `1922fa4`, task breakdown [`M6-PLAN.md`](./M6-PLAN.md)). spt-core is now **installable, documented, and released**: [`SaberMage/spt-releases`](https://github.com/SaberMage/spt-releases) is **public** with the signed **v0.1.0** release live and GitHub Pages serving the docs at the permanent canonical URL `https://sabermage.github.io/spt-releases` (ADR-0014; repo-face README/licenses truth-synced from `releases-repo/` here). Shipped: non-interactive installer scripts (sha256-verify, user PATH, env-knob CI/air-gap override; OS-service → DEFERRED) with a staged-release CI rung on both runners · manifest **JSON Schema generated from the parsing derives themselves** + drift gate · the Tier-1 mdBook docs corpus (Starlight-styled; both killer quickstarts with real captured outputs — the messaging one CI-executed step-for-step as an anti-rot rung; complete harness-contract vertical; shell getting-started via the real `spt-shell-notify`; llms.txt/llms-full; CLI reference generated from the binary's own `--help`, drift-gated; rustdoc) · `xtask` generation/publish plumbing + tag-triggered `docs-publish` and `release` pipelines (cross-repo via fine-grained PAT; **signing stays manual + local** — `release-keygen/sign/publish/verify`, seed via password-manager CLI) · the **embedded two-key trust anchor** (ADR-0015: `rel-primary-2026` + `rel-recovery-2026` compiled into every binary, keys-file as revocable overlay; ceremony run 2026-06-05, seeds custodied off-machine) · public-facing CLI help-text polish. **Acceptance ran against the LIVE release:** the real `irm|iex` one-liner installed the published binary (`spt 0.1.0`, sha256-verified) · the published quickstarts ran as written on the released binary · llms.txt + schema + raw-`.md` + rustdoc all serve from the canonical URL · both platforms' `SignedRelease` assets **verify against the embedded anchor** (`release_verify_e2e`, REQ-REL-2 int) · a v0.1.1-shaped next release **and a recovery-key-signed metadata** both verified with the real keys (the ADR-0015 loss story, proven). Side quest: `traceable-reqs` v0.1.2 (shell/PowerShell tag scanning) shipped to cover script evidence. **Next:** the v1 acceptance proof — `spt-claude-code`, a separate downstream repo — gets its own scoping conversation.

+- **M6 — Stage-setting: `spt-releases`, dev docs, v0.1 release. ✅ delivered (2026-06-05** — planned, executed, and shipped in one day; decisions grilled `1922fa4`, task breakdown [`M6-PLAN.md`](./M6-PLAN.md)). spt-core is now **installable, documented, and released**: [`SaberMage/spt-releases`](https://github.com/SaberMage/spt-releases) is **public** with the signed **v0.1.0** release live and GitHub Pages serving the docs at the permanent canonical URL `https://sabermage.github.io/spt-releases` (ADR-0014; repo-face README/licenses truth-synced from `releases-repo/` here). Shipped: non-interactive installer scripts (sha256-verify, user PATH, env-knob CI/air-gap override; OS-service → DEFERRED) with a staged-release CI rung on both runners · manifest **JSON Schema generated from the parsing derives themselves** + drift gate · the Tier-1 mdBook docs corpus (Starlight-styled; both killer quickstarts with real captured outputs — the messaging one CI-executed step-for-step as an anti-rot rung; complete harness-contract vertical; shell getting-started via the real `spt-shell-notify`; llms.txt/llms-full; CLI reference generated from the binary's own `--help`, drift-gated; rustdoc) · `xtask` generation/publish plumbing + tag-triggered `docs-publish` and `release` pipelines (cross-repo via fine-grained PAT; **signing stays manual + local** — `release-keygen/sign/publish/verify`, seed via password-manager CLI) · the **embedded two-key trust anchor** (ADR-0015: `rel-primary-2026` + `rel-recovery-2026` compiled into every binary, keys-file as revocable overlay; ceremony run 2026-06-05, seeds custodied off-machine) · public-facing CLI help-text polish. **Acceptance ran against the LIVE release:** the real `irm|iex` one-liner installed the published binary (`spt 0.1.0`, sha256-verified) · the published quickstarts ran as written on the released binary · llms.txt + schema + raw-`.md` + rustdoc all serve from the canonical URL · both platforms' `SignedRelease` assets **verify against the embedded anchor** (`release_verify_e2e`, REQ-REL-2 int) · a v0.1.1-shaped next release **and a recovery-key-signed metadata** both verified with the real keys (the ADR-0015 loss story, proven). Side quest: `traceable-reqs` v0.1.2 (shell/PowerShell tag scanning) shipped to cover script evidence. **Next:** M7 (subnet & quickstart UX), then the v1 acceptance proof — `spt-claude-code`, a separate downstream repo — gets its own scoping conversation.

- **M7 — Subnet & quickstart UX. 📋 planned (2026-06-05** — grill session; decisions ratified, task breakdown [`M7-PLAN.md`](./M7-PLAN.md)). Makes subnet setup a first-class, guided, secure product surface and the public quickstart honest about personas: the **join ceremony gets its product surface** (always-on daemon pairing responder + guided `spt subnet join` CLI initiator — today the rig-proven SPAKE2 wire is test-only) · new `spt subnet` noun namespace (`status [NAME] [--nodes]` / `create` / `show-totp` / `join`; `spt pair` deleted) · **elevation-gated membership mutations** (`create`/`join` — trust-boundary changes; ADR-0005 amendment) · **node labels** (hostname-default, gossiped, addressable in `@node` qualifiers with refuse-on-ambiguity) · `spt poll` removed (`ready` + `ready --once`) · **`spt how-to <topic>`** in-binary agent instructions (anti-drift: prompts point agents at the binary's own guidance) · quickstart rewritten as human-pairs / agents-message prompt blocks (lea + sergey; collapsible optional pairing section) · README/docs-site sweep + `quickstart_e2e` + a product-surface join rung on the twohost ladder. Runs **before** `spt-claude-code` scoping — CLI breaking changes are cheapest while no adapter depends on the surface.


Bash

git add M7-PLAN.md ROADMAP.md CONTEXT.md && git commit -m "$(cat <<'EOF'
plan(M7): subnet & quickstart UX milestone — grilled, ratified, planned

Grill session output: M7-PLAN.md (16 ratified decisions, deliverables
D1-D5, acceptance criteria), ROADMAP.md M7 entry, CONTEXT.md glossary
updates.

Root causes addressed by the plan:
- the join ceremony has no product surface (SPAKE2 wire is test-only;
  no daemon responder hosting, no CLI joiner) — M7 builds it
- subnet creation hides inside a fetch flag (pair show-totp --create-new)
  -> new `spt subnet` noun namespace; `spt pair` deleted
- `spt poll` duplicates `spt ready` -> removed, `ready --once` absorbs
- quickstart casts the human as the agent and never mentions subnets ->
  rewritten as human-pairs / agents-message prompt blocks (lea + sergey),
  collapsible optional pairing section, `spt how-to <topic>` in-binary
  agent instructions
- nodes render as bare pubkey hex -> gossiped hostname-default labels,
  addressable in @node qualifiers (refuse-on-ambiguity)

Security posture ratified: subnet membership mutations (create/join) are
elevation-gated — an unprivileged process must not enroll the machine
into an attacker's subnet or mint+leak a subnet secret; responder is
always-on per ADR-0005's one-sided-UX intent with the red-team #11 rate
limiter as the standing-listener guard (ADR-0005 amendment lands with D3).

CONTEXT.md: pairing-vs-join verbs, joiner/seed-holder roles, node label
(addressable), elevation gate extended to membership mutations,
show-totp/create surface modernized.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EOF
)"

Commit M7 plan, roadmap, glossary


warning: in the working copy of 'CONTEXT.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'ROADMAP.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'M7-PLAN.md', LF will be replaced by CRLF the next time Git touches it
[dev-freeform 828019a] plan(M7): subnet & quickstart UX milestone — grilled, ratified, planned
 3 files changed, 107 insertions(+), 3 deletions(-)
 create mode 100644 M7-PLAN.md


Committed `828019a`. Now communing:


Write

<live-context>
M7 PLANNED + COMMITTED (828019a, dev-freeform). Session was a grill-with-docs that produced the next milestone: **M7 — Subnet & quickstart UX** (M7-PLAN.md = authoritative, 16 user-ratified decisions; ROADMAP M7 entry; CONTEXT.md glossary updates committed alongside). Earlier in session: CLI --help fix shipped (fbedaba — wrap_help, one-line abouts + long_about, User/Agent grouped help via runtime template; xtask parser + reference.md regen; all green).

## M7 essence (read M7-PLAN.md before executing)
- Join ceremony gets its product surface: always-on daemon pairing responder ALPN + guided `spt subnet join` CLI initiator (today the SPAKE2 wire is test-only — root cause #1).
- New `spt subnet` namespace: bare = status view · `status [NAME] [--nodes]` (hybrid liveness) · `create <NAME>` (QR + otpauth, elevation-gated) · `show-totp [NAME]` · `join [NAME] [--code]` (elevation-gated — membership = trust-boundary mutation; ADR-0005 amendment due in D3). `spt pair` deleted outright.
- `spt poll` removed; `ready` + `ready --once` absorb it (recon proved zero external dependents; api poll untouched).
- Node labels: hostname-default, re-checked at daemon startup, gossiped (additive serde field), ADDRESSABLE in @node qualifiers, refuse-on-ambiguity.
- `spt how-to <topic>` (ready/send): in-binary agent instructions, visible, Agent commands group, docs never duplicate.
- Quickstart rewrite: human pairs (elevated, collapsible optional <details> section), agents message via prompt blocks; names alice→lea, sergey←bob; docs-site+README sweep; quickstart_e2e follows; twohost gains product-surface join rung.
- Execution order D1 CLI surface → D2 labels/status ∥ D3 join machinery → D4 docs → D5 CI. REQ-SUBNET-* family must be minted in traceable-reqs.toml BEFORE work starts (rule 3).

## Immediate next steps (next session)
1. Mint REQ-SUBNET-* ids + amend REQ-MSG-2 / REQ-DOCS-x in traceable-reqs.toml.
2. Execute D1 (CLI surface — no new machinery): subnet namespace skeleton, port create/show-totp off pair, delete pair, remove poll/add ready --once, how-to command, reference regen.
3. Then D2/D3 per plan. User resumes next session; no blockers.

## Pulse Log
- 2026-06-06 — M7 planned via grill; committed 828019a. Prior: help-fix fbedaba. Idle until next session.
</live-context>
<project-context>
Project status: v0.1.0 public + released (M6 closed). End goal: harness-independent agent-ecosystem core (messaging, lifecycle, terminal hosting, P2P, self-update) proven by a downstream spt-claude-code adapter at parity with modern SPT. Sequencing updated: **M7 (subnet & quickstart UX) now runs BEFORE spt-claude-code scoping** — breaking CLI changes are cheapest while no adapter depends on the surface. M7-PLAN.md is the task source of truth; key security posture: subnet create/join elevation-gated, pairing responder always-on with rate-limiter guard.
</project-context>


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

<live-context>
M7 PLANNED + COMMITTED (828019a, dev-freeform). Session was a grill-with-docs that produced the next milestone: **M7 — Subnet & quickstart UX** (M7-PLAN.md = authoritative, 16 user-ratified decisions; ROADMAP M7 entry; CONTEXT.md glossary updates committed alongside). Earlier in session: CLI --help fix shipped (fbedaba — wrap_help, one-line abouts + long_about, User/Agent grouped help via runtime template; xtask parser + reference.md regen; all green).

## M7 essence (read M7-PLAN.md before executing)
- Join ceremony gets its product surface: always-on daemon pairing responder ALPN + guided `spt subnet join` CLI initiator (today the SPAKE2 wire is test-only — root cause #1).
- New `spt subnet` namespace: bare = status view · `status [NAME] [--nodes]` (hybrid liveness) · `create <NAME>` (QR + otpauth, elevation-gated) · `show-totp [NAME]` · `join [NAME] [--code]` (elevation-gated — membership = trust-boundary mutation; ADR-0005 amendment due in D3). `spt pair` deleted outright.
- `spt poll` removed; `ready` + `ready --once` absorb it (recon proved zero external dependents; api poll untouched).
- Node labels: hostname-default, re-checked at daemon startup, gossiped (additive serde field), ADDRESSABLE in @node qualifiers, refuse-on-ambiguity.
- `spt how-to <topic>` (ready/send): in-binary agent instructions, visible, Agent commands group, docs never duplicate.
- Quickstart rewrite: human pairs (elevated, collapsible optional <details> section), agents message via prompt blocks; names alice→lea, sergey←bob; docs-site+README sweep; quickstart_e2e follows; twohost gains product-surface join rung.
- Execution order D1 CLI surface → D2 labels/status ∥ D3 join machinery → D4 docs → D5 CI. REQ-SUBNET-* family must be minted in traceable-reqs.toml BEFORE work starts (rule 3).

## Immediate next steps (next session)
1. Mint REQ-SUBNET-* ids + amend REQ-MSG-2 / REQ-DOCS-x in traceable-reqs.toml.
2. Execute D1 (CLI surface — no new machinery): subnet namespace skeleton, port create/show-totp off pair, delete pair, remove poll/add ready --once, how-to command, reference regen.
3. Then D2/D3 per plan. User resumes next session; no blockers.

## Pulse Log
- 2026-06-06 — M7 planned via grill; committed 828019a. Prior: help-fix fbedaba. Idle until next session.
</live-context>
<project-context>
Project status: v0.1.0 public + released (M6 closed). End goal: harness-independent agent-ecosystem core (messaging, lifecycle, terminal hosting, P2P, self-update) proven by a downstream spt-claude-code adapter at parity with modern SPT. Sequencing updated: **M7 (subnet & quickstart UX) now runs BEFORE spt-claude-code scoping** — breaking CLI changes are cheapest while no adapter depends on the surface. M7-PLAN.md is the task source of truth; key security posture: subnet create/join elevation-gated, pairing responder always-on with rate-limiter guard.
</project-context>


Done.

- **ROADMAP.md**: M7 entry added (📋 planned), M6's "Next:" line resequenced — M7 before spt-claude-code.
- **Committed** `828019a`: M7-PLAN.md + ROADMAP.md + CONTEXT.md glossary.
- **Commune dropped** — psyche gets full session state: M7 essence, immediate next steps (mint REQ-SUBNET-* first, then D1 CLI surface), project status + end goal.

Next session resumes at: traceable-reqs minting → D1. No blockers. Good night.


oh, create the reqs now too


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


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


255	[[requirements]]
256	id = "REQ-NET-1"
257	title = "WAN messaging first-class, behind default-on net feature flag"
258	required_stages = ["impl", "unit", "int"]  # activated M4-D1: spt-net NetEndpoint binds an iroh endpoint to the node's own spt-proto Ed25519 identity (EndpointId == node pubkey) behind the default-on `net` feature, with connect/accept over the SPT_NET_ALPN; unit proves identity-binding + a hermetic loopback QUIC bidi echo. int activated M4-D9-6: two-host rig E2E green (tests/twohost.rs — a real WAN message lands in the remote perch spool, HFENDULEAM↔gravity over tailscale, [twohost] CI run)
259	[[requirements]]
260	id = "REQ-NET-2"
261	title = "n0 relay default + self-host knob + plain-language disclosure"
262	required_stages = ["impl"]  # activated M4-D1: RelayPolicy {N0Default | SelfHosted(RelayMap) | Disabled} maps onto iroh RelayMode (n0 public relays are the zero-config default; self-host is the escape hatch; disabled = LAN/air-gapped + hermetic tests), plus the RELAY_DISCLOSURE plain-language string. Config plumbing — no unit/int stage required
263	[[requirements]]
264	id = "REQ-NET-3"
265	title = "Cross-node Psyche sync over P2P replaces gh-repo-sync"
266	required_stages = ["impl", "unit"]  # activated M4-D6c: git-native bundle sync (ADR-0013). D6c-1 store core: BranchStore bundle plumbing (create_bundle ^have incremental w/ unknown-have full fallback + up-to-date skip; fetch_bundle quarantines tips under refs/spt-sync/ — incoming can never clobber refs/heads before the driver rules; merge_commit_in_worktree two-parent join, FF for all-accepted propagation) + spt_store::syncmerge::apply_fetched — per-file vector verdicts (dominate→accept verbatim/dominated→drop/concurrent→record_conflict, hazard 6.6), replicated .conflicts artifacts with resurrection guard (local-dominates-or-equal ⟹ stale echo dropped), dominating accept clears pending artifacts (the only legal clear), drop-only passes still join the DAG (re-pull short-circuits on ancestry). D6c-2 = wire serve/request over broker QUIC + synced/visibility server gate. int = D9 two-host E2E. unit = adopt/incremental-FF/idempotent-repull + dominated-drop-joins-DAG + concurrent-surfaces-both-nodes + reconciled-write-propagates-clears + p-tier-union + legacy-⊥⊥-surfaces
267	[[requirements]]
268	id = "REQ-PAIR-1"
269	title = "TOTP-seeded SPAKE2 pairing"
270	required_stages = ["impl", "unit", "int"]  # activated M4-D2-wire: pairing ALPN ceremony driver (spt-net::net::pairing::wire) runs the 4-msg SPAKE2 ceremony over SPT_PAIR_ALPN, prepended by a responder Announce{epoch} RTT (joiner lacks the responder-authoritative seed epoch that Initiator::start binds into msg_a). Responder gates via PairingRateLimiter + ±1 step + SubnetStore seed; both sides write TrustStore.record on confirm. unit = hermetic loopback E2E (RelayPolicy::Disabled) like endpoint.rs. int activated M4-D9-6: two-host rig E2E green (tests/twohost.rs — the real ceremony over SPT_PAIR_ALPN cross-host; trust pinned both sides).
271	[[requirements]]
272	id = "REQ-PAIR-2"
273	title = "Local trust store with TOFU + warn-on-change"
274	required_stages = ["impl", "unit"]  # activated M4-D2e: spt-store::trust::TrustStore (identity/trust/peers.json) pins peer node pubkeys per-subnet post-ceremony (TOFU). record() => Pinned/AlreadyTrusted/KeyChanged{existing}; KeyChanged never auto-applied (repin on accepted rotation); is_trusted = connect gate; revoke = trust-delete (pair w/ seed rotation #10). Warn-on-change anchored on optional (subnet,user,label) — dormant until labels exist (GUI/D9), honest v1 residual. int = on-wire trusted-vs-untrusted gate at D2-wire/D9.
275	[[requirements]]
276	id = "REQ-PAIR-3"
277	title = "Fetch current pairing code from any paired node"
278	required_stages = ["impl", "unit"]  # activated M4-D2g: `spt pair show-totp [--subnet <name>|--create-new <name>]` surfaces a subnet's current code + otpauth:// URI off this node (cli.rs decide_show_totp). Per-subnet resolution: single auto-selects, multi refuses without --subnet (no guessing), --create-new mints a sole-holder subnet (SubnetStore::create_subnet). Code reuses spt_net TotpSeed (matches the ceremony exactly), never reimplemented. unit = single/multi/create-new/empty/unknown resolution + code-matches-seed. The cross-node fetch (off a *remote* paired node over IPC) is D4/D5; int = two-host at D9.
279	[[requirements]]
280	id = "REQ-PAIR-4"
281	title = "Subnet naming on first pairing"
282	required_stages = ["impl", "unit"]  # activated M4-D2f: SubnetStore::create_subnet names the subnet at creation (sole seed-holder, epoch 1; "naming moves to link-start" ADR-0006); the name rides every ceremony (joiner types it, transcript binds it) and reaches the joiner via D2f seed transfer. unit = wire create_new_then_join_learns_named_subnet. CLI naming prompt is later (daemon wiring, D4+); int = two-host at D9.
283	# ── Multi-subnet pairing ceremony (ADR-0005 amend, 2026-06-01; activate M4) ──
284	[[requirements]]
285	id = "REQ-PAIR-5"
286	title = "Multi-subnet pairing: subnet-name discovery input, create-new-names-up-front, rendezvous-token hashing"
287	required_stages = ["impl", "unit", "int"]  # activated M4-D2f: discovery takes code+subnet-name (the wire Hello carries the name; both are ceremony inputs); rendezvous-token H(name‖TOTP-step) derived in spt-net::net::pairing::rendezvous (TOTP-step = clock-derived window, not seed epoch — D2-wire Q2); seed transfer on join (wire F7 Seed -> SubnetStore::add_joined) makes the joiner a full member/seed-holder. unit = rendezvous tests + wire joiner_becomes_seed_holder/repair_member_keeps_seed. int activated M4-D9-6: two-host rig seed transfer green (tests/twohost.rs — the joiner holds the subnet seed post-ceremony); relay rendezvous routing stays the M5 Q1 deferral.
288	[[requirements]]
289	id = "REQ-PAIR-6"
290	title = "Elevation-gated per-subnet code fetch (UAC/root or elevated agent; else authenticator app)"
291	required_stages = ["impl", "unit"]  # activated M4-D2g: elevation.rs probes OS privilege (Linux libc::geteuid()==0; Windows GetTokenInformation(TokenElevation) via windows-sys; cfg-split) — only the binary pulls OS deps. gate_for: Elevated => Show, NotElevated|Unknown => Fallback (fail safe, never leak the code unconfirmed). Refused fetch leaks no code, persists no mint, distinct exit 3, and the fallback names the authenticator app (seed provisioned there at pairing). unit = gate decision both ways + Unknown fallback + no-leak/no-save on refusal; the live OS probe is environment-dependent (manual verification via todlando / D9). The elevated-*agent*-endpoint path (IPC) is D4/D5, not D2g.
292	[[requirements]]
293	id = "REQ-PAIR-7"
294	title = "Subnet icon (inline image metadata, GUI-only consumer)"
295	required_stages = []  # rule 5: GUI milestone (user 2026-06-04) — inline image metadata has no consumer until a GUI exists
296	# ── Endpoint access control (ADR-0009, 2026-06-02; activate M4 node-tier) ──
297	[[requirements]]
298	id = "REQ-SEC-1"
299	title = "Per-endpoint access whitelist: origin-node gate, stateful-firewall (reply/outbound exempt), node-now/user-later, outer gate before grants"
300	required_stages = ["impl", "unit"]
301	# ── Subnet notifications (ADR-0007, 2026-06-01; activate M4) ──
302	[[requirements]]
303	id = "REQ-NOTIF-1"
304	title = "Notification primitive: per-subnet replicated spool, seen/dismissed, resurface-at-boundary, subsumes update+consent prompts"
305	required_stages = ["impl", "unit", "int"]  # int activated M4-D9-6: two-host rig E2E green (tests/twohost.rs — insert on A fires on B; dismiss on B replicates back to A; the update consent notif also surfaced through the same primitive)
306	[[requirements]]
307	id = "REQ-NOTIF-2"
308	title = "spt notify (agent-issued subnet notif) + notif_command manifest seam (harness + shell adapters)"
309	required_stages = ["doc", "impl", "unit", "int"]  # int activated M5-D9b: the rig toast rung (tests/twohost.rs — A's notify rendered through B's [session.notif] shell template, run 26998058816 real_mode=true)
310
311	# ───────────────────────────── Self-update ──────────────────────────────────
312	[[requirements]]
313	id = "REQ-UPD-1"
314	title = "Peer-propagated update over P2P"

[[requirements]]
id = "REQ-NET-1"
title = "WAN messaging first-class, behind default-on net feature flag"
required_stages = ["impl", "unit", "int"]  # activated M4-D1: spt-net NetEndpoint binds an iroh endpoint to the node's own spt-proto Ed25519 identity (EndpointId == node pubkey) behind the default-on `net` feature, with connect/accept over the SPT_NET_ALPN; unit proves identity-binding + a hermetic loopback QUIC bidi echo. int activated M4-D9-6: two-host rig E2E green (tests/twohost.rs — a real WAN message lands in the remote perch spool, HFENDULEAM↔gravity over tailscale, [twohost] CI run)
[[requirements]]
id = "REQ-NET-2"
title = "n0 relay default + self-host knob + plain-language disclosure"
required_stages = ["impl"]  # activated M4-D1: RelayPolicy {N0Default | SelfHosted(RelayMap) | Disabled} maps onto iroh RelayMode (n0 public relays are the zero-config default; self-host is the escape hatch; disabled = LAN/air-gapped + hermetic tests), plus the RELAY_DISCLOSURE plain-language string. Config plumbing — no unit/int stage required
[[requirements]]
id = "REQ-NET-3"
title = "Cross-node Psyche sync over P2P replaces gh-repo-sync"
required_stages = ["impl", "unit"]  # activated M4-D6c: git-native bundle sync (ADR-0013). D6c-1 store core: BranchStore bundle plumbing (create_bundle ^have incremental w/ unknown-have full fallback + up-to-date skip; fetch_bundle quarantines tips under refs/spt-sync/ — incoming can never clobber refs/heads before the driver rules; merge_commit_in_worktree two-parent join, FF for all-accepted propagation) + spt_store::syncmerge::apply_fetched — per-file vector verdicts (dominate→accept verbatim/dominated→drop/concurrent→record_conflict, hazard 6.6), replicated .conflicts artifacts with resurrection guard (local-dominates-or-equal ⟹ stale echo dropped), dominating accept clears pending artifacts (the only legal clear), drop-only passes still join the DAG (re-pull short-circuits on ancestry). D6c-2 = wire serve/request over broker QUIC + synced/visibility server gate. int = D9 two-host E2E. unit = adopt/incremental-FF/idempotent-repull + dominated-drop-joins-DAG + concurrent-surfaces-both-nodes + reconciled-write-propagates-clears + p-tier-union + legacy-⊥⊥-surfaces
[[requirements]]
id = "REQ-PAIR-1"
title = "TOTP-seeded SPAKE2 pairing"
required_stages = ["impl", "unit", "int"]  # activated M4-D2-wire: pairing ALPN ceremony driver (spt-net::net::pairing::wire) runs the 4-msg SPAKE2 ceremony over SPT_PAIR_ALPN, prepended by a responder Announce{epoch} RTT (joiner lacks the responder-authoritative seed epoch that Initiator::start binds into msg_a). Responder gates via PairingRateLimiter + ±1 step + SubnetStore seed; both sides write TrustStore.record on confirm. unit = hermetic loopback E2E (RelayPolicy::Disabled) like endpoint.rs. int activated M4-D9-6: two-host rig E2E green (tests/twohost.rs — the real ceremony over SPT_PAIR_ALPN cross-host; trust pinned both sides).
[[requirements]]
id = "REQ-PAIR-2"
title = "Local trust store with TOFU + warn-on-change"
required_stages = ["impl", "unit"]  # activated M4-D2e: spt-store::trust::TrustStore (identity/trust/peers.json) pins peer node pubkeys per-subnet post-ceremony (TOFU). record() => Pinned/AlreadyTrusted/KeyChanged{existing}; KeyChanged never auto-applied (repin on accepted rotation); is_trusted = connect gate; revoke = trust-delete (pair w/ seed rotation #10). Warn-on-change anchored on optional (subnet,user,label) — dormant until labels exist (GUI/D9), honest v1 residual. int = on-wire trusted-vs-untrusted gate at D2-wire/D9.
[[requirements]]
id = "REQ-PAIR-3"
title = "Fetch current pairing code from any paired node"
required_stages = ["impl", "unit"]  # activated M4-D2g: `spt pair show-totp [--subnet <name>|--create-new <name>]` surfaces a subnet's current code + otpauth:// URI off this node (cli.rs decide_show_totp). Per-subnet resolution: single auto-selects, multi refuses without --subnet (no guessing), --create-new mints a sole-holder subnet (SubnetStore::create_subnet). Code reuses spt_net TotpSeed (matches the ceremony exactly), never reimplemented. unit = single/multi/create-new/empty/unknown resolution + code-matches-seed. The cross-node fetch (off a *remote* paired node over IPC) is D4/D5; int = two-host at D9.
[[requirements]]
id = "REQ-PAIR-4"
title = "Subnet naming on first pairing"
required_stages = ["impl", "unit"]  # activated M4-D2f: SubnetStore::create_subnet names the subnet at creation (sole seed-holder, epoch 1; "naming moves to link-start" ADR-0006); the name rides every ceremony (joiner types it, transcript binds it) and reaches the joiner via D2f seed transfer. unit = wire create_new_then_join_learns_named_subnet. CLI naming prompt is later (daemon wiring, D4+); int = two-host at D9.
# ── Multi-subnet pairing ceremony (ADR-0005 amend, 2026-06-01; activate M4) ──
[[requirements]]
id = "REQ-PAIR-5"
title = "Multi-subnet pairing: subnet-name discovery input, create-new-names-up-front, rendezvous-token hashing"
required_stages = ["impl", "unit", "int"]  # activated M4-D2f: discovery takes code+subnet-name (the wire Hello carries the name; both are ceremony inputs); rendezvous-token H(name‖TOTP-step) derived in spt-net::net::pairing::rendezvous (TOTP-step = clock-derived window, not seed epoch — D2-wire Q2); seed transfer on join (wire F7 Seed -> SubnetStore::add_joined) makes the joiner a full member/seed-holder. unit = rendezvous tests + wire joiner_becomes_seed_holder/repair_member_keeps_seed. int activated M4-D9-6: two-host rig seed transfer green (tests/twohost.rs — the joiner holds the subnet seed post-ceremony); relay rendezvous routing stays the M5 Q1 deferral.
[[requirements]]
id = "REQ-PAIR-6"
title = "Elevation-gated per-subnet code fetch (UAC/root or elevated agent; else authenticator app)"
required_stages = ["impl", "unit"]  # activated M4-D2g: elevation.rs probes OS privilege (Linux libc::geteuid()==0; Windows GetTokenInformation(TokenElevation) via windows-sys; cfg-split) — only the binary pulls OS deps. gate_for: Elevated => Show, NotElevated|Unknown => Fallback (fail safe, never leak the code unconfirmed). Refused fetch leaks no code, persists no mint, distinct exit 3, and the fallback names the authenticator app (seed provisioned there at pairing). unit = gate decision both ways + Unknown fallback + no-leak/no-save on refusal; the live OS probe is environment-dependent (manual verification via todlando / D9). The elevated-*agent*-endpoint path (IPC) is D4/D5, not D2g.
[[requirements]]
id = "REQ-PAIR-7"
title = "Subnet icon (inline image metadata, GUI-only consumer)"
required_stages = []  # rule 5: GUI milestone (user 2026-06-04) — inline image metadata has no consumer until a GUI exists
# ── Endpoint access control (ADR-0009, 2026-06-02; activate M4 node-tier) ──
[[requirements]]
id = "REQ-SEC-1"
title = "Per-endpoint access whitelist: origin-node gate, stateful-firewall (reply/outbound exempt), node-now/user-later, outer gate before grants"
required_stages = ["impl", "unit"]
# ── Subnet notifications (ADR-0007, 2026-06-01; activate M4) ──
[[requirements]]
id = "REQ-NOTIF-1"
title = "Notification primitive: per-subnet replicated spool, seen/dismissed, resurface-at-boundary, subsumes update+consent prompts"
required_stages = ["impl", "unit", "int"]  # int activated M4-D9-6: two-host rig E2E green (tests/twohost.rs — insert on A fires on B; dismiss on B replicates back to A; the update consent notif also surfaced through the same primitive)
[[requirements]]
id = "REQ-NOTIF-2"
title = "spt notify (agent-issued subnet notif) + notif_command manifest seam (harness + shell adapters)"
required_stages = ["doc", "impl", "unit", "int"]  # int activated M5-D9b: the rig toast rung (tests/twohost.rs — A's notify rendered through B's [session.notif] shell template, run 26998058816 real_mode=true)

# ───────────────────────────── Self-update ──────────────────────────────────
[[requirements]]
id = "REQ-UPD-1"
title = "Peer-propagated update over P2P"


242:id = "REQ-MSG-2"
243-title = "spt binary CLI surface: send/ring/ready/poll/list/stop/whoami, stable arg shapes + exit codes"
244-required_stages = ["impl", "unit"]  # activated M1 T6-T8: spt binary, clap CLI, all 7 subcommands wired
245-[[requirements]]
--
404:id = "REQ-DOCS-1"
405-title = "Dual-audience docs (human + AI dev-agent), markdown once / two depths"
406-[Omitted long context line]
407-[[requirements]]
408:id = "REQ-DOCS-2"
409-title = "Sub-10-minute runnable killer quickstart per audience"
410-required_stages = ["doc", "int"]  # activated M6-D4: both killer quickstarts authored with REAL captured outputs, zero placeholders (messaging: install→two agents message incl. the QUEUED/backlog beat; adapter: walked via the shipped mock adapter, every command verified against the binary). int = quickstart_e2e.rs runs the published messaging flow step for step in the normal CI sweep — the doc cannot silently rot
411-[[requirements]]
412:id = "REQ-DOCS-3"
413-title = "Diátaxis structure; one canonical way to do X"
414-required_stages = ["doc"]  # activated M6-D4: the corpus IS the structure — per-vertical overview/tutorial/how-to/reference separation (SUMMARY.md IA per DOCS-STRATEGY: getting-started → quickstarts → mental model → verticals in build-order), the contract stated on the landing page (doc tag, index.md §How these docs are organized); remaining verticals ship overview+reference stubs per the v0.1 Tier-1 scope
415-[[requirements]]
416:id = "REQ-DOCS-4"
417-title = "Agent-consumable layer (llms.txt, manifest schema, MCP, CLI help)"
418-required_stages = ["doc", "impl", "unit"]  # activated M6-D3 (schema leg): manifest_json_schema() generated from the SAME schemars derives that parse adapter manifests (drift impossible by construction), stable $id at the canonical Pages URL, checked-in crates/spt-runtime/manifest.schema.json = the site/release asset. llms.txt + CLI-ref legs land D5; MCP doc server + `--help --json` EXCLUDED → docs/DEFERRED.md (Tier 2, DOCS-STRATEGY §v0.1)
419-[[requirements]]
420:id = "REQ-DOCS-5"
421-title = "Anti-drift: rustdoc/schema/exports/CLI-help generated + CI-checked"
422-required_stages = ["impl", "int"]  # activated M6-D3 (schema gate): checked_in_schema_is_current — the checked-in asset must equal what the live derives generate, run in the normal CI sweep every push (regenerate via SPT_BLESS=1). CLI-ref + llms-export drift gates join at D5 through the same pattern
423-


Edit

id = "REQ-MSG-2"
title = "spt binary CLI surface: send/ring/ready/poll/list/stop/whoami, stable arg shapes + exit codes"
required_stages = ["impl", "unit"]  # activated M1 T6-T8: spt binary, clap CLI, all 7 subcommands wired

id = "REQ-MSG-2"
title = "spt binary CLI surface: send/ring/ready(+--once)/list/stop/whoami, stable arg shapes + exit codes"
required_stages = ["impl", "unit"]  # activated M1 T6-T8: spt binary, clap CLI, all 7 subcommands wired. M7 amendment (plan decision 2, 2026-06-05): `poll` removed — it duplicated `ready` (same run_listen dispatch); `ready --once` absorbs the drain-then-exit semantics. `spt api poll`/`api worker-poll` (machinery namespace) are unaffected. Pre-1.0, zero users, no deprecation shim.


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

id = "REQ-MSG-2"
title = "spt binary CLI surface: send/ring/ready/poll/list/stop/whoami, stable arg shapes + exit codes"
required_stages = ["impl", "unit"]  # activated M1 T6-T8: spt binary, clap CLI, all 7 subcommands wired

id = "REQ-MSG-2"
title = "spt binary CLI surface: send/ring/ready(+--once)/list/stop/whoami, stable arg shapes + exit codes"
required_stages = ["impl", "unit"]  # activated M1 T6-T8: spt binary, clap CLI, all 7 subcommands wired. M7 amendment (plan decision 2, 2026-06-05): `poll` removed — it duplicated `ready` (same run_listen dispatch); `ready --once` absorbs the drain-then-exit semantics. `spt api poll`/`api worker-poll` (machinery namespace) are unaffected. Pre-1.0, zero users, no deprecation shim.

required_stages = ["impl", "unit", "int"]  # activated M1 T1 (impl+unit); int added T9: killer-quickstart cross-perch E2E

-title = "spt binary CLI surface: send/ring/ready/poll/list/stop/whoami, stable arg shapes + exit codes"

-required_stages = ["impl", "unit"]  # activated M1 T6-T8: spt binary, clap CLI, all 7 subcommands wired

+title = "spt binary CLI surface: send/ring/ready(+--once)/list/stop/whoami, stable arg shapes + exit codes"

+required_stages = ["impl", "unit"]  # activated M1 T6-T8: spt binary, clap CLI, all 7 subcommands wired. M7 amendment (plan decision 2, 2026-06-05): `poll` removed — it duplicated `ready` (same run_listen dispatch); `ready --once` absorbs the drain-then-exit semantics. `spt api poll`/`api worker-poll` (machinery namespace) are unaffected. Pre-1.0, zero users, no deprecation shim.

title = "Ready-agent lifecycle: register perch (info.json + listener + registry address) on ready, drain spooled backlog on startup, clean teardown"


id = "REQ-PAIR-3"
title = "Fetch current pairing code from any paired node"
required_stages = ["impl", "unit"]  # activated M4-D2g: `spt pair show-totp [--subnet <name>|--create-new <name>]` surfaces a subnet's current code + otpauth:// URI off this node (cli.rs decide_show_totp).

id = "REQ-PAIR-3"
title = "Fetch current pairing code from any paired node"
required_stages = ["impl", "unit"]  # activated M4-D2g: `spt pair show-totp [--subnet <name>|--create-new <name>]` surfaces a subnet's current code + otpauth:// URI off this node (cli.rs decide_show_totp). M7 NOTE: surface moves to `spt subnet show-totp [name]` and creation splits into `spt subnet create <name>` (REQ-SUBNET-1); the gate/resolution semantics here are unchanged.


id = "REQ-PAIR-3"
title = "Fetch current pairing code from any paired node"
required_stages = ["impl", "unit"]  # activated M4-D2g: `spt pair show-totp [--subnet <name>|--create-new <name>]` surfaces a subnet's current code + otpauth:// URI off this node (cli.rs decide_show_totp). M7 NOTE: surface moves to `spt subnet show-totp [name]` and creation splits into `spt subnet create <name>` (REQ-SUBNET-1); the gate/resolution semantics here are unchanged.

-required_stages = ["impl", "unit"]  # activated M4-D2g: `spt pair show-totp [--subnet <name>|--create-new <name>]` surfaces a subnet's current code + otpauth:// URI off this node (cli.rs decide_show_totp). Per-subnet resolution: single auto-selects, multi refuses without --subnet (no guessing), --create-new mints a sole-holder subnet (SubnetStore::create_subnet). Code reuses spt_net TotpSeed (matches the ceremony exactly), never reimplemented. unit = single/multi/create-new/empty/unknown resolution + code-matches-seed. The cross-node fetch (off a *remote* paired node over IPC) is D4/D5; int = two-host at D9.

+required_stages = ["impl", "unit"]  # activated M4-D2g: `spt pair show-totp [--subnet <name>|--create-new <name>]` surfaces a subnet's current code + otpauth:// URI off this node (cli.rs decide_show_totp). M7 NOTE: surface moves to `spt subnet show-totp [name]` and creation splits into `spt subnet create <name>` (REQ-SUBNET-1); the gate/resolution semantics here are unchanged. Per-subnet resolution: single auto-selects, multi refuses without --subnet (no guessing), --create-new mints a sole-holder subnet (SubnetStore::create_subnet). Code reuses spt_net TotpSeed (matches the ceremony exactly), never reimplemented. unit = single/multi/create-new/empty/unknown resolution + code-matches-seed. The cross-node fetch (off a *remote* paired node over IPC) is D4/D5; int = two-host at D9.

title = "Subnet naming on first pairing"


unit = wire create_new_then_join_learns_named_subnet. CLI naming prompt is later (daemon wiring, D4+); int = two-host at D9.

unit = wire create_new_then_join_learns_named_subnet. CLI naming prompt is later (daemon wiring, D4+) — delivered by M7 REQ-SUBNET-2 (guided `spt subnet join`); int = two-host at D9.


-required_stages = ["impl", "unit"]  # activated M4-D2f: SubnetStore::create_subnet names the subnet at creation (sole seed-holder, epoch 1; "naming moves to link-start" ADR-0006); the name rides every ceremony (joiner types it, transcript binds it) and reaches the joiner via D2f seed transfer. unit = wire create_new_then_join_learns_named_subnet. CLI naming prompt is later (daemon wiring, D4+); int = two-host at D9.

+required_stages = ["impl", "unit"]  # activated M4-D2f: SubnetStore::create_subnet names the subnet at creation (sole seed-holder, epoch 1; "naming moves to link-start" ADR-0006); the name rides every ceremony (joiner types it, transcript binds it) and reaches the joiner via D2f seed transfer. unit = wire create_new_then_join_learns_named_subnet. CLI naming prompt is later (daemon wiring, D4+) — delivered by M7 REQ-SUBNET-2 (guided `spt subnet join`); int = two-host at D9.

# ── Multi-subnet pairing ceremony (ADR-0005 amend, 2026-06-01; activate M4) ──


Now the new family — inserting after the REQ-PAIR block:


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

# ── M7 subnet & quickstart UX (M7-PLAN.md, grilled 2026-06-05; activate at M7 start, rule 5) ──
[[requirements]]
id = "REQ-SUBNET-1"
title = "spt subnet noun namespace: status view (bare + status [NAME] [--nodes]), create (QR/otpauth), show-totp; spt pair deleted"
required_stages = []  # M7 D1/D2 (plan decisions 3, 5, 6, 11). Bare `spt subnet` = flagless status (names, paired-node counts, endpoint counts; never epochs/codes; hint footer; zero-subnet explanatory text). `status [NAME] [--nodes]` adds per-node rows (label, online/offline via gossip-recency + probe-stale hybrid, online/total endpoints). `create <NAME>` mints (sole seed-holder) + prints code/otpauth/terminal-QR + join hint. `show-totp [name]` keeps REQ-PAIR-3/6 semantics. `spt pair` namespace removed outright (no shim).
[[requirements]]
id = "REQ-SUBNET-2"
title = "Guided join e2e: spt subnet join CLI initiator + always-on daemon pairing responder"
required_stages = []  # M7 D3 (plan decisions 8, 10) — the join ceremony's PRODUCT surface (the SPAKE2 wire is rig-proven but test-only today). Every member daemon hosts the pre-trust pairing ALPN responder ALWAYS-ON (ADR-0005 one-sided-UX intent; the red-team #11 subnet-global rate limiter + one-ceremony-per-subnet is the standing-listener guard). `spt subnet join [NAME] [--code <CODE>]`: prompts for missing pieces, LAN+relay rendezvous, clean wrong-code re-prompt (bounded, limiter surfaced honestly), actionable no-seed-holder hints, seed transfer + trust pinning + REQ-INST-15 home adoption on join. int = twohost ladder gains a product-surface join rung (CLI initiator vs daemon responder) replacing the test-harness-driven ceremony as pairing evidence.
[[requirements]]
id = "REQ-SUBNET-3"
title = "Node labels: hostname-default, gossiped, addressable in @node qualifiers (refuse-on-ambiguity)"
required_stages = []  # M7 D2 (plan decision 7). Label defaults to OS hostname, re-checked at daemon startup (hostname change updates it), rides existing registry gossip as an additive serde-default field (old rows parse clean). Pubkey stays the identity; @node qualifiers accept label OR key-prefix; non-unique label resolution refuses-and-qualifies listing candidate key prefixes (CONTEXT.md §node label). Renders `HFENDULEAM (bcead52b…)` in subnet views.
[[requirements]]
id = "REQ-SUBNET-4"
title = "Subnet membership mutations elevation-gated (create = seed reveal; join = trust-boundary enrollment)"
required_stages = []  # M7 D3 (plan decision 9; ADR-0005 amendment lands with it). `subnet create` and `subnet join` require OS elevation via the existing REQ-PAIR-6 gate machinery (exit 3 on refusal, no leak/no mutation): an unprivileged process must not mint+leak a subnet secret nor enroll the machine into an attacker's subnet. `subnet status` stays ungated (read-only, no secrets). CONTEXT.md §Pairing & trust updated 2026-06-05.
[[requirements]]
id = "REQ-DOCS-6"
title = "spt how-to <topic>: in-binary task-oriented agent instructions (anti-drift; quickstart prompts point agents at it)"
required_stages = []  # M7 D1/D4 (plan decision 12). Visible command, Agent-commands help group. Topics v1: `ready` (background-task guidance, --once fallback loop, reply mechanics) + `send` (send/ring, reply-to, SENT vs QUEUED); bare `how-to` lists topics. Single source = the binary — docs-site never duplicates the content (the DOCS-STRATEGY anti-drift posture applied to agent guidance); quickstart prompt blocks instruct agents to run it and follow. int = quickstart_e2e asserts the how-to output names the commands the guide relies on.

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


# ── M7 subnet & quickstart UX (M7-PLAN.md, grilled 2026-06-05; activate at M7 start, rule 5) ──
[[requirements]]
id = "REQ-SUBNET-1"
title = "spt subnet noun namespace: status view (bare + status [NAME] [--nodes]), create (QR/otpauth), show-totp; spt pair deleted"
required_stages = []  # M7 D1/D2 (plan decisions 3, 5, 6, 11). Bare `spt subnet` = flagless status (names, paired-node counts, endpoint counts; never epochs/codes; hint footer; zero-subnet explanatory text). `status [NAME] [--nodes]` adds per-node rows (label, online/offline via gossip-recency + probe-stale hybrid, online/total endpoints). `create <NAME>` mints (sole seed-holder) + prints code/otpauth/terminal-QR + join hint. `show-totp [name]` keeps REQ-PAIR-3/6 semantics. `spt pair` namespace removed outright (no shim).
[[requirements]]
id = "REQ-SUBNET-2"
title = "Guided join e2e: spt subnet join CLI initiator + always-on daemon pairing responder"
required_stages = []  # M7 D3 (plan decisions 8, 10) — the join ceremony's PRODUCT surface (the SPAKE2 wire is rig-proven but test-only today). Every member daemon hosts the pre-trust pairing ALPN responder ALWAYS-ON (ADR-0005 one-sided-UX intent; the red-team #11 subnet-global rate limiter + one-ceremony-per-subnet is the standing-listener guard). `spt subnet join [NAME] [--code <CODE>]`: prompts for missing pieces, LAN+relay rendezvous, clean wrong-code re-prompt (bounded, limiter surfaced honestly), actionable no-seed-holder hints, seed transfer + trust pinning + REQ-INST-15 home adoption on join. int = twohost ladder gains a product-surface join rung (CLI initiator vs daemon responder) replacing the test-harness-driven ceremony as pairing evidence.
[[requirements]]
id = "REQ-SUBNET-3"
title = "Node labels: hostname-default, gossiped, addressable in @node qualifiers (refuse-on-ambiguity)"
required_stages = []  # M7 D2 (plan decision 7). Label defaults to OS hostname, re-checked at daemon startup (hostname change updates it), rides existing registry gossip as an additive serde-default field (old rows parse clean). Pubkey stays the identity; @node qualifiers accept label OR key-prefix; non-unique label resolution refuses-and-qualifies listing candidate key prefixes (CONTEXT.md §node label). Renders `HFENDULEAM (bcead52b…)` in subnet views.
[[requirements]]
id = "REQ-SUBNET-4"
title = "Subnet membership mutations elevation-gated (create = seed reveal; join = trust-boundary enrollment)"
required_stages = []  # M7 D3 (plan decision 9; ADR-0005 amendment lands with it). `subnet create` and `subnet join` require OS elevation via the existing REQ-PAIR-6 gate machinery (exit 3 on refusal, no leak/no mutation): an unprivileged process must not mint+leak a subnet secret nor enroll the machine into an attacker's subnet. `subnet status` stays ungated (read-only, no secrets). CONTEXT.md §Pairing & trust updated 2026-06-05.
[[requirements]]
id = "REQ-DOCS-6"
title = "spt how-to <topic>: in-binary task-oriented agent instructions (anti-drift; quickstart prompts point agents at it)"
required_stages = []  # M7 D1/D4 (plan decision 12). Visible command, Agent-commands help group. Topics v1: `ready` (background-task guidance, --once fallback loop, reply mechanics) + `send` (send/ring, reply-to, SENT vs QUEUED); bare `how-to` lists topics. Single source = the binary — docs-site never duplicates the content (the DOCS-STRATEGY anti-drift posture applied to agent guidance); quickstart prompt blocks instruct agents to run it and follow. int = quickstart_e2e asserts the how-to output names the commands the guide relies on.

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

title = "Subnet icon (inline image metadata, GUI-only consumer)"

required_stages = []  # rule 5: GUI milestone (user 2026-06-04) — inline image metadata has no consumer until a GUI exists

+# ── M7 subnet & quickstart UX (M7-PLAN.md, grilled 2026-06-05; activate at M7 start, rule 5) ──

+title = "spt subnet noun namespace: status view (bare + status [NAME] [--nodes]), create (QR/otpauth), show-totp; spt pair deleted"

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

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

+required_stages = []  # M7 D3 (plan decisions 8, 10) — the join ceremony's PRODUCT surface (the SPAKE2 wire is rig-proven but test-only today). Every member daemon hosts the pre-trust pairing ALPN responder ALWAYS-ON (ADR-0005 one-sided-UX intent; the red-team #11 subnet-global rate limiter + one-ceremony-per-subnet is the standing-listener guard). `spt subnet join [NAME] [--code <CODE>]`: prompts for missing pieces, LAN+relay rendezvous, clean wrong-code re-prompt (bounded, limiter surfaced honestly), actionable no-seed-holder hints, seed transfer + trust pinning + REQ-INST-15 home adoption on join. int = twohost ladder gains a product-surface join rung (CLI initiator vs daemon responder) replacing the test-harness-driven ceremony as pairing evidence.

+title = "Node labels: hostname-default, gossiped, addressable in @node qualifiers (refuse-on-ambiguity)"

+required_stages = []  # M7 D2 (plan decision 7). Label defaults to OS hostname, re-checked at daemon startup (hostname change updates it), rides existing registry gossip as an additive serde-default field (old rows parse clean). Pubkey stays the identity; @node qualifiers accept label OR key-prefix; non-unique label resolution refuses-and-qualifies listing candidate key prefixes (CONTEXT.md §node label). Renders `HFENDULEAM (bcead52b…)` in subnet views.

+title = "Subnet membership mutations elevation-gated (create = seed reveal; join = trust-boundary enrollment)"

+required_stages = []  # M7 D3 (plan decision 9; ADR-0005 amendment lands with it). `subnet create` and `subnet join` require OS elevation via the existing REQ-PAIR-6 gate machinery (exit 3 on refusal, no leak/no mutation): an unprivileged process must not mint+leak a subnet secret nor enroll the machine into an attacker's subnet. `subnet status` stays ungated (read-only, no secrets). CONTEXT.md §Pairing & trust updated 2026-06-05.

+title = "spt how-to <topic>: in-binary task-oriented agent instructions (anti-drift; quickstart prompts point agents at it)"

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


PowerShell

traceable-reqs check 2>&1 | Select-Object -Last 4

Validate requirement registry


  [OK] REQ-UPD-4  required: [impl, unit]  stages: -doc +impl +unit -int
       Update gated on user confirmation by default; opt-in full-auto
  [OK] REQ-UPD-5  required: [impl, unit]  stages: -doc +impl +unit -int
       spt-core ripple-updates registered adapters


traceable-reqs check 2>&1 | Select-String "SUBNET|DOCS-6|FAIL|exit" | Select-Object -First 10; $LASTEXITCODE

Confirm new REQ ids registered + exit code


       Consent grant store: capability x subject-agent x target-node rows, enforced at the target node, subnet-settable (replicates as
security material near the trust store), revocable; gated-capability ids (remote-exec, instantiate-anywhere) reserved-but-refusing; v1
consumers are the shell spawn gates (CONTEXT Consent & security gates)
  [OK] REQ-DOCS-6  required: []  stages: -doc -impl -unit -int
       A surfaced concurrent context pair is durably preserved (both versions, tracked artifacts) until a strictly dominating write
clears it; no reconcile failure path discards an unmerged version (6.6, ADR-0013)
       Ephemeral perch cleanup on every ring exit path (3.1)
       Subnet-global pairing rate limit: one active ceremony per subnet, shared attempt counter, exponential backoff — a public pre-trust
relay + multiple seed-holders otherwise enables distributed SPAKE2 guessing (and ±1 TOTP window triples the valid-password space)
(ADR-0005 #11)
       Removing a node rotates the subnet seed (epoch bump) so an old node/old seed cannot rejoin; trust-store delete alone is NOT
revocation because the seed is replicated to every trusted node (ADR-0005 #10)
       Pairing transcript binds roles, both node pubkeys, subnet ID, seed epoch, TOTP time-step, and confirmation MACs — or
unknown-key-share/reflection/wrong-subnet/replay pairing remain possible (ADR-0005 #12)
       Psyche outbound captured + sanitized: the live-Psyche turn driver captures stdout (never Stdio::null), and the daemon
strips/re-stamps Psyche-supplied from=/target and constrains routing (reply→__REPLY_TO__ sender, notify→own user/subnet) (7.3)
       Concurrent SQLite openers (registry/spool) must not fail with 'database is locked' (4.7)
       Stale registry entries degrade to fallback, never hard-fail (4.3)


traceable-reqs check *> $null; "exit=$LASTEXITCODE"; (Select-String -Path traceable-reqs.toml -Pattern '^id = "REQ-SUBNET-\d"').Line

Registry gate exit code + new ids present


exit=0
id = "REQ-SUBNET-1"
id = "REQ-SUBNET-2"
id = "REQ-SUBNET-3"
id = "REQ-SUBNET-4"


Bash

git add traceable-reqs.toml && git commit -m "$(cat <<'EOF'
reqs(M7): mint REQ-SUBNET-1..4 + REQ-DOCS-6; annotate poll merge + surface moves

New requirements for M7 (M7-PLAN.md), all required_stages = [] per rule 5
(activate when M7 work starts):

- REQ-SUBNET-1 — spt subnet namespace: status views, create (QR/otpauth),
  show-totp; spt pair deleted
- REQ-SUBNET-2 — guided join e2e: CLI initiator + always-on daemon pairing
  responder (the ceremony's product surface; twohost gains a join rung)
- REQ-SUBNET-3 — node labels: hostname-default, gossiped, addressable in
  @node qualifiers with refuse-on-ambiguity
- REQ-SUBNET-4 — membership mutations elevation-gated (create = seed
  reveal, join = trust-boundary enrollment; ADR-0005 amendment with D3)
- REQ-DOCS-6 — spt how-to <topic>: in-binary agent instructions, single
  source = binary, quickstart prompts point agents at it

Amendments: REQ-MSG-2 retitled/annotated for the poll->ready --once merge;
REQ-PAIR-3/4 notes point at the M7 surface moves (semantics unchanged).
traceable-reqs check green (exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EOF
)"

Commit M7 requirement minting


[dev-freeform a6cf4e1] reqs(M7): mint REQ-SUBNET-1..4 + REQ-DOCS-6; annotate poll merge + surface moves
 1 file changed, 26 insertions(+), 4 deletions(-)


Reqs minted + committed `a6cf4e1`. Updating commune delta so next session starts at D1:


Write

<live-context>
Delta on top of the M7 commune: requirement minting is DONE this session after all (a6cf4e1) — REQ-SUBNET-1..4 + REQ-DOCS-6 added to traceable-reqs.toml (all required_stages = [] per rule 5; activate at M7 start), REQ-MSG-2 retitled for the poll→ready --once merge, REQ-PAIR-3/4 annotated for the surface moves. traceable-reqs check green.

Next session starts directly at **M7 D1** (CLI surface): activate REQ-SUBNET-1 + REQ-MSG-2-amendment stages, build the subnet namespace skeleton, port create/show-totp off `spt pair`, delete `pair`, remove `poll`/add `ready --once`, add `how-to`, regen reference. M7-PLAN.md = task source of truth.

## Pulse Log
- 2026-06-06 — REQ minting committed (a6cf4e1). M7 ready to execute from D1. Session ending; user resumes next session.
</live-context>