Question: Decision D: `xtask debug-converge` watcher in M8?

Choices:
Acceptance 6 is coarse (seconds vs 60s) — eyeball-verifiable. Pull in mid-M8 only if D4 debugging actually stalls without it.
Build during D4 — tick-level convergence table for development + acceptance evidence. Activates REQ-UPD-6 int.
Out regardless; first multi-node debug rollout only. Risks tool-less D4 debugging if event path misbehaves.


Your questions have been answered: "Decision D: `xtask debug-converge` watcher in M8?"="In scope". You can now continue with these answers in mind.

Question: Decision D: `xtask debug-converge` watcher in M8?

Choices:
Acceptance 6 is coarse (seconds vs 60s) — eyeball-verifiable. Pull in mid-M8 only if D4 debugging actually stalls without it.
Build during D4 — tick-level convergence table for development + acceptance evidence. Activates REQ-UPD-6 int.
Out regardless; first multi-node debug rollout only. Risks tool-less D4 debugging if event path misbehaves.


AskUserQuestion

Question: Decision C: NTP-first clock source in M8?

Choices:
No field failure ever; ±1 TOTP window + OS NTP covers rig. Hazard 5.9 was monotonic-clock, not wall-clock — NTP irrelevant to it. Revisit on first skew report.
Add NTP query to pairing/TOTP path now. New network dependency, no observed problem.


Your questions have been answered: "Decision C: NTP-first clock source in M8?"="in scope because i have a machine (enlyzeam) that i know for a fact has a system clock that's off by >1min. there may also be need to modify system clock on a future dev machine.". You can now continue with these answers in mind.

Question: Decision C: NTP-first clock source in M8?

Choices:
No field failure ever; ±1 TOTP window + OS NTP covers rig. Hazard 5.9 was monotonic-clock, not wall-clock — NTP irrelevant to it. Revisit on first skew report.
Add NTP query to pairing/TOTP path now. New network dependency, no observed problem.

in scope because i have a machine (enlyzeam) that i know for a fact has a system clock that's off by >1min. there may also be need to modify system clock on a future dev machine.


Question: Decision C2: NTP shape?

Choices:
Query NTP at ceremony time, apply offset to TOTP calc in-process only. System-clock fallback when unreachable — offline pairing unaffected. Never sets system clock.
NTP offset applied to every wall-clock read daemon-wide. Wider blast radius; epoch/lease ordering is already monotonic so gain unclear.
Actually correct the OS clock. Needs elevation, OS-specific, side effects on everything else running. KH-grade hazard.


Your questions have been answered: "Decision C2: NTP shape?"="TOTP offset only (Recommended)". You can now continue with these answers in mind.

Question: Decision C2: NTP shape?

Choices:
Query NTP at ceremony time, apply offset to TOTP calc in-process only. System-clock fallback when unreachable — offline pairing unaffected. Never sets system clock.
NTP offset applied to every wall-clock read daemon-wide. Wider blast radius; epoch/lease ordering is already monotonic so gain unclear.
Actually correct the OS clock. Needs elevation, OS-specific, side effects on everything else running. KH-grade hazard.


/grill-me over M8, then execute once open decisions are made


C2: **TOTP offset only**. Next — F (access verb shapes). Quick look at existing surface.


Found 2 files
crates\spt\src\cli.rs
crates\spt\src\wansend.rs


155:    /// Endpoint access whitelist for unsolicited off-node inbound.
161:    Access {
163:        action: AccessCmd,
167:    /// Default-deny (the access whitelist's opposite polarity). An
294:/// `spt access <action>` subcommands (REQ-SEC-1).
296:enum AccessCmd {
652:        Cmd::Access { action } => cmd_access(action),
990:                    "{verb}_REFUSED:{id}: {node} closed the stream without a reply (access gate)"
2657:/// `spt access allow|revoke|open|list` — operate the endpoint access whitelist
2658:/// (`identity/trust/access.json`, ADR-0009). The daemon's gate re-reads the
2661:fn cmd_access(action: AccessCmd) -> i32 {
2662:    use spt_store::access::AccessStore;
2669:    fn save(store: &AccessStore) -> i32 {
2680:        AccessCmd::Allow { endpoint, node } => {
2686:            let mut s = AccessStore::load();
2694:        AccessCmd::Revoke { endpoint, node } => {
2696:            let mut s = AccessStore::load();
2711:                         `spt access open {endpoint}` returns it to default-open"
2717:        AccessCmd::Open { endpoint } => {
2718:            let mut s = AccessStore::load();
2729:        AccessCmd::List { endpoint } => {
2730:            let s = AccessStore::load();
3044:            // (non-minting, the access-gate stance).
3504:                "SHELL_REMOTE_REFUSED:{shell_ref}: {node} closed the stream without a reply (access gate)"
3644:/// This node's pubkey hex — non-minting (the daemon access-gate stance):
4129:    // [unit->REQ-SEC-1] the access whitelist surface parses: allow/revoke take
4132:    fn access_subcommands_parse() {
4134:            parse(&["spt", "access", "allow", "ling", "aa11"])
4137:            Cmd::Access {
4138:                action: AccessCmd::Allow { .. }

[Showing results with pagination = limit: 30]


Question: Decision F: `endpoint access` verb shapes?

Choices:
spt endpoint access allow|revoke|open|list — same verbs, args, semantics. Location-only break; consent framework owns any future verb reshape.
Rename to grant/revoke/list while moving. Breaks ADR-0009's allow/open polarity naming for no semantic gain; risks second break at consent-framework touch.


Your questions have been answered: "Decision F: `endpoint access` verb shapes?"="Port 1:1 (Recommended)". You can now continue with these answers in mind.

Question: Decision F: `endpoint access` verb shapes?

Choices:
spt endpoint access allow|revoke|open|list — same verbs, args, semantics. Location-only break; consent framework owns any future verb reshape.
Rename to grant/revoke/list while moving. Breaks ADR-0009's allow/open polarity naming for no semantic gain; risks second break at consent-framework touch.


Question: Decision H: pump-liveness handling in M8?

Choices:
Pump heartbeat rendered in daemon status + subnet status; pump-task panic caught and restarted with capped backoff. Self-heals 5.9-class deaths visibly.
Honest render (pump stalled since X), no auto-restart. Operator must bounce daemon manually — still 100x better than silence.
Pump panic kills daemon; OS service restarts whole thing. Throws away the usefully-alive inbound dispatcher; at-logon task won't auto-restart.
v0.1.1 fixes the known trigger; structural half-death waits. Risk: next novel pump panic is silent again.


Your questions have been answered: "Decision H: pump-liveness handling in M8?"="Status + supervise (Recommended)". You can now continue with these answers in mind.

Question: Decision H: pump-liveness handling in M8?

Choices:
Pump heartbeat rendered in daemon status + subnet status; pump-task panic caught and restarted with capped backoff. Self-heals 5.9-class deaths visibly.
Honest render (pump stalled since X), no auto-restart. Operator must bounce daemon manually — still 100x better than silence.
Pump panic kills daemon; OS service restarts whole thing. Throws away the usefully-alive inbound dispatcher; at-logon task won't auto-restart.
v0.1.1 fixes the known trigger; structural half-death waits. Risk: next novel pump panic is silent again.


Question: Decision I: epoch-reset hazard scope in M8?

Choices:
KNOWN-HAZARDS entry + REQ-HAZARD-* with stages=[] (rule 5). Acceptance 7 verifies re-pair eviction clears peer epoch memory. Guard waits for field hit of the narrow slice.
Design + test an epoch high-water recovery path now. New protocol surface for an unobserved narrow case — scope creep in an already-large milestone.
No hazard entry. Violates the project's paid-for-bug doctrine — this one cost a night's diagnosis.


Your questions have been answered: "Decision I: epoch-reset hazard scope in M8?"="Document + mint inactive (Recommended)". You can now continue with these answers in mind.

Question: Decision I: epoch-reset hazard scope in M8?

Choices:
KNOWN-HAZARDS entry + REQ-HAZARD-* with stages=[] (rule 5). Acceptance 7 verifies re-pair eviction clears peer epoch memory. Guard waits for field hit of the narrow slice.
Design + test an epoch high-water recovery path now. New protocol surface for an unobserved narrow case — scope creep in an already-large milestone.
No hazard entry. Violates the project's paid-for-bug doctrine — this one cost a night's diagnosis.


ToolSearch

Question: select:TaskCreate,TaskUpdate


Question: select:TaskCreate,TaskUpdate


TaskCreate

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

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


Task #1 created successfully: Ratify M8-PLAN.md — fold A–I resolutions in, status DRAFT→RATIFIED


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

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


Task #2 created successfully: Mint M8 requirements in traceable-reqs.toml (rule 3)


D1 — endpoint + daemon nouns (breaking CLI reorg)

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


Task #3 created successfully: D1 — endpoint + daemon nouns (breaking CLI reorg)


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

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


Task #4 created successfully: D2 — subnet serve-state, trust lifecycle, status UX


D3 — install & elevation hardening

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


Task #5 created successfully: D3 — install & elevation hardening

D3 — install & elevation hardening


D4 — convergence polish + pump resilience

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


Task #6 created successfully: D4 — convergence polish + pump resilience


D5 — docs + CI + acceptance prep

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


Task #7 created successfully: D5 — docs + CI + acceptance prep

D5 — docs + CI + acceptance prep


Updated task #1 status, blocks


Updated task #3 blocks, blockedBy


Updated task #6 blocks, blockedBy


Now ratify plan doc.


Edit

> **Status: DRAFT (2026-06-07).** Core decisions were user-ratified in the
> 2026-06-06 M7-closeout grills (nounification shape, subnet attachment verbs,
> Linux elevation model); the **scope-inclusion decisions in §Open decisions
> need a ratification pass before execution starts**. Requirements minted
> before work begins (TRACEABILITY rule 3).

> **Status: RATIFIED (2026-06-07).** Core decisions were user-ratified in the
> 2026-06-06 M7-closeout grills (nounification shape, subnet attachment verbs,
> Linux elevation model); the scope-inclusion decisions (former §Open
> decisions A–G, plus H/I from the 2026-06-07 pump diagnosis) were ratified
> in the 2026-06-07 grill — see decisions 16–24. Requirements minted before
> work begins (TRACEABILITY rule 3).


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

> **Status: DRAFT (2026-06-07).** Core decisions were user-ratified in the
> 2026-06-06 M7-closeout grills (nounification shape, subnet attachment verbs,
> Linux elevation model); the **scope-inclusion decisions in §Open decisions
> need a ratification pass before execution starts**. Requirements minted
> before work begins (TRACEABILITY rule 3).

> **Status: RATIFIED (2026-06-07).** Core decisions were user-ratified in the
> 2026-06-06 M7-closeout grills (nounification shape, subnet attachment verbs,
> Linux elevation model); the scope-inclusion decisions (former §Open
> decisions A–G, plus H/I from the 2026-06-07 pump diagnosis) were ratified
> in the 2026-06-07 grill — see decisions 16–24. Requirements minted before
> work begins (TRACEABILITY rule 3).

# M8 — CLI nounification & install hardening

-> **Status: DRAFT (2026-06-07).** Core decisions were user-ratified in the

+> **Status: RATIFIED (2026-06-07).** Core decisions were user-ratified in the

-> Linux elevation model); the **scope-inclusion decisions in §Open decisions

-> need a ratification pass before execution starts**. Requirements minted

-> before work begins (TRACEABILITY rule 3).

+> Linux elevation model); the scope-inclusion decisions (former §Open

+> decisions A–G, plus H/I from the 2026-06-07 pump diagnosis) were ratified

+> in the 2026-06-07 grill — see decisions 16–24. Requirements minted before

Goal: finish the CLI's breaking reorg **while the adapter window is still

open** (no downstream adapter depends on the surface yet — spt-claude-code


## Open decisions (grill before execution)

| # | Question | Lean |
|---|----------|------|
| A | `spt subnet disband <NAME>` in scope? (elevation + current TOTP per the spec sketch) | Defer unless cheap — `leave` covers the field need; disband is a destructive fleet op with no current user |
| B | OS-service shape: Linux systemd **user** service vs system service; Windows service vs scheduled task | User service + scheduled task — both stay in the user's profile universe (consistent with KH 5.7 never-root stance); system-level variants need the elevation story anyway |
| C | NTP-first clock source (DEFERRED row): in M8 or wait for a field report? | Out — ±1 TOTP window covers small skew; no field failures yet. Revisit on first report |
| D | `xtask debug-converge` watcher (REQ-UPD-6 int, `docs/DEBUG-CONVERGE-PLAN.md`): in M8 or first multi-node debug rollout? | In **only if** M8's acceptance run itself wants the convergence table (3-node fleet now: HFENDULEAM, kitsubito, gravity); otherwise stays deferred |
| E | Post-join address seeding: full addr-cache seam, or narrow "ceremony writes peer addr into the pump's existing cache" patch? | Narrow patch — the seam generalization has no second consumer yet |
| F | Does `endpoint access` get verb shapes (`grant`/`revoke`/`list`) now, or port the existing surface 1:1? | Port 1:1; verb-shape redesign rides the consent framework's next touch |
| G | Machine-identifier source for re-pair overwrite (decision 13): OS machine id (`/etc/machine-id` / Windows `MachineGuid`) vs hashed first-MAC vs spt-minted persisted UUID | OS machine id, hashed before gossip (stable across NIC switches, no raw hardware id on the wire); spt-minted UUID as fallback where the OS id is unreadable |

## Ratified decisions (2026-06-07 grill — former open decisions A–G + new H/I)

| # | Was | Decision |
|---|-----|----------|
| 16 | A | **`spt subnet disband` DEFERRED.** `leave` covers the field need; adding `disband` later is non-breaking, so the adapter window doesn't force it. |
| 17 | B | **OS-service shape: Linux systemd *user* service + `loginctl enable-linger`** (linger enable rides the elevated install leg; daemon starts at boot pre-login, user-universe per KH 5.7, managed via `systemctl --user`). **Windows: scheduled task at-logon** (interactive session, no stored credentials, no session-0 isolation breaking terminal hosting). At-startup S4U variant = later opt-in if a headless Windows node ever exists. |
| 18 | C | **NTP IN SCOPE — trigger fired**: enlyzeam's system clock is known >1 min off (exceeds the ±1 TOTP window). Shape (C2): **TOTP offset-correction only** — query NTP at ceremony time (both sides), apply the offset to the TOTP calculation in-process; **system-clock fallback** when NTP unreachable (offline LAN pairing unaffected — NTP failure never blocks a pairing that succeeds today); never sets the OS clock; no background sync loop. Epoch/lease ordering stays monotonic-clock — untouched. |
| 19 | D | **`xtask debug-converge` IN SCOPE** (builds in D4, activates REQ-UPD-6 int, `docs/DEBUG-CONVERGE-PLAN.md` is the spec). The 2026-06-07 pump diagnosis was the hand-rolled version of this table — the tool pays for itself; acceptance 6 gains instrumented evidence. |
| 20 | E | **Address seeding = narrow patch, concretized**: there is NO existing addr cache (production resolution is the id-only `PeerResolver`, `peerloop.rs`). New minimal `peer-addrs.json` in the identity dir (sibling of `pump-ops.json`): pubkey-hex → last-known iroh NodeAddr. Resolver consults it FIRST, falls back to id-only discovery on miss **or dial failure** (a stale addr never strands a peer). Two writers: pairing ceremony (both sides, from the live connection) + the pump on successful connect. Serves both decision-14 cold starts with one file. No generalized cache seam (no third consumer). |
| 21 | F | **`endpoint access` ports 1:1**: `spt endpoint access allow\|revoke\|open\|list`, same args/semantics (ADR-0009 polarity naming preserved). M8's break is location-only; any verb reshape belongs to the consent framework's touch. |
| 22 | G | **Machine identifier = OS machine id** (`/etc/machine-id` / Windows `MachineGuid`), **domain-separated SHA-256 before gossip** (`sha256("spt-machine-id:" + raw)` — never raw on the wire). spt-minted persisted UUID as fallback where the OS id is unreadable. (Minted-UUID-as-primary fails the exact scenario decision 13 targets: a state-dir wipe destroys it. Cloned-VM id collision is acceptable — overwrite only fires inside a completed ceremony, so the worst case is visible and recoverable by re-pairing.) |
| 23 | — | **Pump liveness (NEW — from the 2026-06-07 hfenduleam half-death)**: (1) the pump writes a heartbeat (last-tick timestamp); `spt daemon status` renders it and `subnet status` honesty accounts for a stalled pump ("pump stalled since <time>" — never implied-healthy while half-dead); (2) the daemon supervises the pump task — a panic is caught, logged, and the pump restarts with capped backoff (≤5 min), so a 5.9-class death self-heals visibly instead of 24 h of silence. Fail-fast rejected: the inbound dispatcher was usefully alive; the at-logon task shape doesn't auto-restart anyway. |
| 24 | — | **Epoch-reset hazard (NEW): document + mint inactive.** KNOWN-HAZARDS entry + `REQ-HAZARD-*` with `required_stages = []` (rule 5): a node whose advertisement-epoch counter resets re-advertises low; peers' higher last-seen epoch drops the fresh rows as stale. The common case (full reinstall) is already mitigated by decision 13's re-pair eviction (epoch memory dies with the deleted row) — acceptance 7 verifies that explicitly. The residual narrow slice (epoch file lost, identity kept) waits for a field hit before a guard is designed. |


## Open decisions (grill before execution)

| # | Question | Lean |
|---|----------|------|
| A | `spt subnet disband <NAME>` in scope? (elevation + current TOTP per the spec sketch) | Defer unless cheap — `leave` covers the field need; disband is a destructive fleet op with no current user |
| B | OS-service shape: Linux systemd **user** service vs system service; Windows service vs scheduled task | User service + scheduled task — both stay in the user's profile universe (consistent with KH 5.7 never-root stance); system-level variants need the elevation story anyway |
| C | NTP-first clock source (DEFERRED row): in M8 or wait for a field report? | Out — ±1 TOTP window covers small skew; no field failures yet. Revisit on first report |
| D | `xtask debug-converge` watcher (REQ-UPD-6 int, `docs/DEBUG-CONVERGE-PLAN.md`): in M8 or first multi-node debug rollout? | In **only if** M8's acceptance run itself wants the convergence table (3-node fleet now: HFENDULEAM, kitsubito, gravity); otherwise stays deferred |
| E | Post-join address seeding: full addr-cache seam, or narrow "ceremony writes peer addr into the pump's existing cache" patch? | Narrow patch — the seam generalization has no second consumer yet |
| F | Does `endpoint access` get verb shapes (`grant`/`revoke`/`list`) now, or port the existing surface 1:1? | Port 1:1; verb-shape redesign rides the consent framework's next touch |
| G | Machine-identifier source for re-pair overwrite (decision 13): OS machine id (`/etc/machine-id` / Windows `MachineGuid`) vs hashed first-MAC vs spt-minted persisted UUID | OS machine id, hashed before gossip (stable across NIC switches, no raw hardware id on the wire); spt-minted UUID as fallback where the OS id is unreadable |

## Ratified decisions (2026-06-07 grill — former open decisions A–G + new H/I)

| # | Was | Decision |
|---|-----|----------|
| 16 | A | **`spt subnet disband` DEFERRED.** `leave` covers the field need; adding `disband` later is non-breaking, so the adapter window doesn't force it. |
| 17 | B | **OS-service shape: Linux systemd *user* service + `loginctl enable-linger`** (linger enable rides the elevated install leg; daemon starts at boot pre-login, user-universe per KH 5.7, managed via `systemctl --user`). **Windows: scheduled task at-logon** (interactive session, no stored credentials, no session-0 isolation breaking terminal hosting). At-startup S4U variant = later opt-in if a headless Windows node ever exists. |
| 18 | C | **NTP IN SCOPE — trigger fired**: enlyzeam's system clock is known >1 min off (exceeds the ±1 TOTP window). Shape (C2): **TOTP offset-correction only** — query NTP at ceremony time (both sides), apply the offset to the TOTP calculation in-process; **system-clock fallback** when NTP unreachable (offline LAN pairing unaffected — NTP failure never blocks a pairing that succeeds today); never sets the OS clock; no background sync loop. Epoch/lease ordering stays monotonic-clock — untouched. |
| 19 | D | **`xtask debug-converge` IN SCOPE** (builds in D4, activates REQ-UPD-6 int, `docs/DEBUG-CONVERGE-PLAN.md` is the spec). The 2026-06-07 pump diagnosis was the hand-rolled version of this table — the tool pays for itself; acceptance 6 gains instrumented evidence. |
| 20 | E | **Address seeding = narrow patch, concretized**: there is NO existing addr cache (production resolution is the id-only `PeerResolver`, `peerloop.rs`). New minimal `peer-addrs.json` in the identity dir (sibling of `pump-ops.json`): pubkey-hex → last-known iroh NodeAddr. Resolver consults it FIRST, falls back to id-only discovery on miss **or dial failure** (a stale addr never strands a peer). Two writers: pairing ceremony (both sides, from the live connection) + the pump on successful connect. Serves both decision-14 cold starts with one file. No generalized cache seam (no third consumer). |
| 21 | F | **`endpoint access` ports 1:1**: `spt endpoint access allow\|revoke\|open\|list`, same args/semantics (ADR-0009 polarity naming preserved). M8's break is location-only; any verb reshape belongs to the consent framework's touch. |
| 22 | G | **Machine identifier = OS machine id** (`/etc/machine-id` / Windows `MachineGuid`), **domain-separated SHA-256 before gossip** (`sha256("spt-machine-id:" + raw)` — never raw on the wire). spt-minted persisted UUID as fallback where the OS id is unreadable. (Minted-UUID-as-primary fails the exact scenario decision 13 targets: a state-dir wipe destroys it. Cloned-VM id collision is acceptable — overwrite only fires inside a completed ceremony, so the worst case is visible and recoverable by re-pairing.) |
| 23 | — | **Pump liveness (NEW — from the 2026-06-07 hfenduleam half-death)**: (1) the pump writes a heartbeat (last-tick timestamp); `spt daemon status` renders it and `subnet status` honesty accounts for a stalled pump ("pump stalled since <time>" — never implied-healthy while half-dead); (2) the daemon supervises the pump task — a panic is caught, logged, and the pump restarts with capped backoff (≤5 min), so a 5.9-class death self-heals visibly instead of 24 h of silence. Fail-fast rejected: the inbound dispatcher was usefully alive; the at-logon task shape doesn't auto-restart anyway. |
| 24 | — | **Epoch-reset hazard (NEW): document + mint inactive.** KNOWN-HAZARDS entry + `REQ-HAZARD-*` with `required_stages = []` (rule 5): a node whose advertisement-epoch counter resets re-advertises low; peers' higher last-seen epoch drops the fresh rows as stale. The common case (full reinstall) is already mitigated by decision 13's re-pair eviction (epoch memory dies with the deleted row) — acceptance 7 verifies that explicitly. The residual narrow slice (epoch file lost, identity kept) waits for a field hit before a guard is designed. |

| 14 | **Fast convergence covers daemon bringup too** (user 2026-06-07): D4's address seeding targets both cold starts — post-join (seed from the ceremony connection) and post-restart (persist + reload the pump's last-known peer addresses), so a restarted daemon reconnects in seconds, not 60s+. |

| 15 | **Fast-track advertisement on node state change** (user 2026-06-07): an endpoint coming online / going offline (ready-listener start/stop, rest-state transition, perch death) triggers an immediate `advertise_local` + peer push instead of waiting out the pump cadence. The cadence stays the steady-state floor; the event path is the fast lane. Field motivation: a re-listened endpoint stayed rendered `[0/1]` for up to a full cadence after coming online (2026-06-07 diagnosis tail). |

-## Open decisions (grill before execution)

+## Ratified decisions (2026-06-07 grill — former open decisions A–G + new H/I)

-| A | `spt subnet disband <NAME>` in scope? (elevation + current TOTP per the spec sketch) | Defer unless cheap — `leave` covers the field need; disband is a destructive fleet op with no current user |

-| B | OS-service shape: Linux systemd **user** service vs system service; Windows service vs scheduled task | User service + scheduled task — both stay in the user's profile universe (consistent with KH 5.7 never-root stance); system-level variants need the elevation story anyway |

-| C | NTP-first clock source (DEFERRED row): in M8 or wait for a field report? | Out — ±1 TOTP window covers small skew; no field failures yet. Revisit on first report |

-| D | `xtask debug-converge` watcher (REQ-UPD-6 int, `docs/DEBUG-CONVERGE-PLAN.md`): in M8 or first multi-node debug rollout? | In **only if** M8's acceptance run itself wants the convergence table (3-node fleet now: HFENDULEAM, kitsubito, gravity); otherwise stays deferred |

-| E | Post-join address seeding: full addr-cache seam, or narrow "ceremony writes peer addr into the pump's existing cache" patch? | Narrow patch — the seam generalization has no second consumer yet |

-| F | Does `endpoint access` get verb shapes (`grant`/`revoke`/`list`) now, or port the existing surface 1:1? | Port 1:1; verb-shape redesign rides the consent framework's next touch |

-| G | Machine-identifier source for re-pair overwrite (decision 13): OS machine id (`/etc/machine-id` / Windows `MachineGuid`) vs hashed first-MAC vs spt-minted persisted UUID | OS machine id, hashed before gossip (stable across NIC switches, no raw hardware id on the wire); spt-minted UUID as fallback where the OS id is unreadable |

+| 16 | A | **`spt subnet disband` DEFERRED.** `leave` covers the field need; adding `disband` later is non-breaking, so the adapter window doesn't force it. |

+| 17 | B | **OS-service shape: Linux systemd *user* service + `loginctl enable-linger`** (linger enable rides the elevated install leg; daemon starts at boot pre-login, user-universe per KH 5.7, managed via `systemctl --user`). **Windows: scheduled task at-logon** (interactive session, no stored credentials, no session-0 isolation breaking terminal hosting). At-startup S4U variant = later opt-in if a headless Windows node ever exists. |

+| 18 | C | **NTP IN SCOPE — trigger fired**: enlyzeam's system clock is known >1 min off (exceeds the ±1 TOTP window). Shape (C2): **TOTP offset-correction only** — query NTP at ceremony time (both sides), apply the offset to the TOTP calculation in-process; **system-clock fallback** when NTP unreachable (offline LAN pairing unaffected — NTP failure never blocks a pairing that succeeds today); never sets the OS clock; no background sync loop. Epoch/lease ordering stays monotonic-clock — untouched. |

+| 19 | D | **`xtask debug-converge` IN SCOPE** (builds in D4, activates REQ-UPD-6 int, `docs/DEBUG-CONVERGE-PLAN.md` is the spec). The 2026-06-07 pump diagnosis was the hand-rolled version of this table — the tool pays for itself; acceptance 6 gains instrumented evidence. |

+| 20 | E | **Address seeding = narrow patch, concretized**: there is NO existing addr cache (production resolution is the id-only `PeerResolver`, `peerloop.rs`). New minimal `peer-addrs.json` in the identity dir (sibling of `pump-ops.json`): pubkey-hex → last-known iroh NodeAddr. Resolver consults it FIRST, falls back to id-only discovery on miss **or dial failure** (a stale addr never strands a peer). Two writers: pairing ceremony (both sides, from the live connection) + the pump on successful connect. Serves both decision-14 cold starts with one file. No generalized cache seam (no third consumer). |

+| 21 | F | **`endpoint access` ports 1:1**: `spt endpoint access allow\|revoke\|open\|list`, same args/semantics (ADR-0009 polarity naming preserved). M8's break is location-only; any verb reshape belongs to the consent framework's touch. |

+| 22 | G | **Machine identifier = OS machine id** (`/etc/machine-id` / Windows `MachineGuid`), **domain-separated SHA-256 before gossip** (`sha256("spt-machine-id:" + raw)` — never raw on the wire). spt-minted persisted UUID as fallback where the OS id is unreadable. (Minted-UUID-as-primary fails the exact scenario decision 13 targets: a state-dir wipe destroys it. Cloned-VM id collision is acceptable — overwrite only fires inside a completed ceremony, so the worst case is visible and recoverable by re-pairing.) |

+| 23 | — | **Pump liveness (NEW — from the 2026-06-07 hfenduleam half-death)**: (1) the pump writes a heartbeat (last-tick timestamp); `spt daemon status` renders it and `subnet status` honesty accounts for a stalled pump ("pump stalled since <time>" — never implied-healthy while half-dead); (2) the daemon supervises the pump task — a panic is caught, logged, and the pump restarts with capped backoff (≤5 min), so a 5.9-class death self-heals visibly instead of 24 h of silence. Fail-fast rejected: the inbound dispatcher was usefully alive; the at-logon task shape doesn't auto-restart anyway. |

+| 24 | — | **Epoch-reset hazard (NEW): document + mint inactive.** KNOWN-HAZARDS entry + `REQ-HAZARD-*` with `required_stages = []` (rule 5): a node whose advertisement-epoch counter resets re-advertises low; peers' higher last-seen epoch drops the fresh rows as stale. The common case (full reinstall) is already mitigated by decision 13's re-pair eviction (epoch memory dies with the deleted row) — acceptance 7 verifies that explicitly. The residual narrow slice (epoch file lost, identity kept) waits for a field hit before a guard is designed. |


Now deliverables + acceptance.


namespace skeletons; move fork/suspend/wake/shutdown/rename/stop/digest +
access + description under `endpoint` (decisions 1–3, F); merged
`endpoint list` with SELF pinned + subnet grouping; `notify` → `subnet
notify`; `daemon run|stop|status`; help-group placement
(`help_groups_cover_every_command` keeps passing); `how-to` topics updated;
`cli/reference.md` regen (drift gate).

namespace skeletons; move fork/suspend/wake/shutdown/rename/stop/digest +
access (1:1 per decision 21) + description under `endpoint` (decisions 1–3);
merged `endpoint list` with SELF pinned + subnet grouping; `notify` → `subnet
notify`; `daemon run|stop|status` — **status renders the pump heartbeat**
(decision 23 leg 1; the heartbeat writer itself lands in D4); help-group
placement (`help_groups_cover_every_command` keeps passing); `how-to` topics
updated; `cli/reference.md` regen (drift gate).


namespace skeletons; move fork/suspend/wake/shutdown/rename/stop/digest +
access + description under `endpoint` (decisions 1–3, F); merged
`endpoint list` with SELF pinned + subnet grouping; `notify` → `subnet
notify`; `daemon run|stop|status`; help-group placement
(`help_groups_cover_every_command` keeps passing); `how-to` topics updated;
`cli/reference.md` regen (drift gate).

namespace skeletons; move fork/suspend/wake/shutdown/rename/stop/digest +
access (1:1 per decision 21) + description under `endpoint` (decisions 1–3);
merged `endpoint list` with SELF pinned + subnet grouping; `notify` → `subnet
notify`; `daemon run|stop|status` — **status renders the pump heartbeat**
(decision 23 leg 1; the heartbeat writer itself lands in D4); help-group
placement (`help_groups_cover_every_command` keeps passing); `how-to` topics
updated; `cli/reference.md` regen (drift gate).

**D1 — `endpoint` + `daemon` nouns (breaking reorg, no new machinery):**

-access + description under `endpoint` (decisions 1–3, F); merged

-`endpoint list` with SELF pinned + subnet grouping; `notify` → `subnet

-notify`; `daemon run|stop|status`; help-group placement

-(`help_groups_cover_every_command` keeps passing); `how-to` topics updated;

+access (1:1 per decision 21) + description under `endpoint` (decisions 1–3);

+merged `endpoint list` with SELF pinned + subnet grouping; `notify` → `subnet

+notify`; `daemon run|stop|status` — **status renders the pump heartbeat**

+(decision 23 leg 1; the heartbeat writer itself lands in D4); help-group

+placement (`help_groups_cover_every_command` keeps passing); `how-to` topics

+updated; `cli/reference.md` regen (drift gate).

**D2 — subnet serve-state, trust lifecycle + status UX:**

per-subnet attach/detach machinery (selective advertise/connect in the peer


**Re-pair trust overwrite**
(decisions 13/G): machine identifier rides the registry rows (additive
serde-default, old rows parse clean — the M7 label pattern), ceremony-time
same-label+same-id match deletes the superseded identity's trust + registry
rows on the seed-holder and replicates the eviction. **Status render fixes**
(decisions 11/12): daemon-aware standalone text, hint lines moved to bare
`spt subnet` only. Decision A resolves disband.

**Re-pair trust overwrite**
(decisions 13/22): machine identifier = hashed OS machine id (minted-UUID
fallback) riding the registry rows (additive serde-default, old rows parse
clean — the M7 label pattern), ceremony-time same-label+same-id match
deletes the superseded identity's trust + registry rows on the seed-holder
and replicates the eviction. **Status render fixes** (decisions 11/12):
daemon-aware standalone text, hint lines moved to bare `spt subnet` only.
Disband deferred (decision 16).


**Re-pair trust overwrite**
(decisions 13/G): machine identifier rides the registry rows (additive
serde-default, old rows parse clean — the M7 label pattern), ceremony-time
same-label+same-id match deletes the superseded identity's trust + registry
rows on the seed-holder and replicates the eviction. **Status render fixes**
(decisions 11/12): daemon-aware standalone text, hint lines moved to bare
`spt subnet` only. Decision A resolves disband.

**Re-pair trust overwrite**
(decisions 13/22): machine identifier = hashed OS machine id (minted-UUID
fallback) riding the registry rows (additive serde-default, old rows parse
clean — the M7 label pattern), ceremony-time same-label+same-id match
deletes the superseded identity's trust + registry rows on the seed-holder
and replicates the eviction. **Status render fixes** (decisions 11/12):
daemon-aware standalone text, hint lines moved to bare `spt subnet` only.
Disband deferred (decision 16).

all-attached banner's per-subnet states, `subnet leave` (elevation),

`subnet prune <node>` (elevation; kills the dead-identity dials; BIGNET's

09ef831e rows are the acceptance fixture). **Re-pair trust overwrite**

-(decisions 13/G): machine identifier rides the registry rows (additive

-serde-default, old rows parse clean — the M7 label pattern), ceremony-time

-same-label+same-id match deletes the superseded identity's trust + registry

-rows on the seed-holder and replicates the eviction. **Status render fixes**

-(decisions 11/12): daemon-aware standalone text, hint lines moved to bare

-`spt subnet` only. Decision A resolves disband.

+(decisions 13/22): machine identifier = hashed OS machine id (minted-UUID

+fallback) riding the registry rows (additive serde-default, old rows parse

+clean — the M7 label pattern), ceremony-time same-label+same-id match

+deletes the superseded identity's trust + registry rows on the seed-holder

+and replicates the eviction. **Status render fixes** (decisions 11/12):

+daemon-aware standalone text, hint lines moved to bare `spt subnet` only.

**D3 — install & elevation hardening (the installer's owed touch):**

Linux: install.sh symlink leg into `/usr/local/bin` (graceful when


Both OSes: OS-service registration per decision B (REQ-INSTALL-1 third leg —
reboot-survivable daemon). Installer-script E2E rungs extended
(`SPT_INSTALL_E2E` stays hermetic — service/firewall legs env-gated off in
CI, proven in acceptance instead).

Both OSes: OS-service registration per decision 17 — Linux systemd user
service + `enable-linger` (linger rides the elevated install leg); Windows
scheduled task at-logon (REQ-INSTALL-1 third leg — reboot-survivable
daemon). Installer-script E2E rungs extended (`SPT_INSTALL_E2E` stays
hermetic — service/firewall legs env-gated off in CI, proven in acceptance
instead).


Both OSes: OS-service registration per decision B (REQ-INSTALL-1 third leg —
reboot-survivable daemon). Installer-script E2E rungs extended
(`SPT_INSTALL_E2E` stays hermetic — service/firewall legs env-gated off in
CI, proven in acceptance instead).

Both OSes: OS-service registration per decision 17 — Linux systemd user
service + `enable-linger` (linger rides the elevated install leg); Windows
scheduled task at-logon (REQ-INSTALL-1 third leg — reboot-survivable
daemon). Installer-script E2E rungs extended (`SPT_INSTALL_E2E` stays
hermetic — service/firewall legs env-gated off in CI, proven in acceptance
instead).

rule (`New-NetFirewallRule`, inbound UDP) **and** the daemon self-detects

blocked inbound → rendered as the "no connection" state in `subnet status` +

coming-online banner (covers user-scope installs that skip the elevated leg).

-Both OSes: OS-service registration per decision B (REQ-INSTALL-1 third leg —

-reboot-survivable daemon). Installer-script E2E rungs extended

-(`SPT_INSTALL_E2E` stays hermetic — service/firewall legs env-gated off in

+Both OSes: OS-service registration per decision 17 — Linux systemd user

+service + `enable-linger` (linger rides the elevated install leg); Windows

+scheduled task at-logon (REQ-INSTALL-1 third leg — reboot-survivable

+daemon). Installer-script E2E rungs extended (`SPT_INSTALL_E2E` stays

+hermetic — service/firewall legs env-gated off in CI, proven in acceptance

address seeding per decisions E/14, both cold starts: post-join (seed the


**D4 — convergence polish:**
address seeding per decisions E/14, both cold starts: post-join (seed the
pump's addr cache from the live ceremony connection, both sides) and
post-bringup (persist last-known peer addresses; reload on daemon start so
reconnect skips id-only discovery). **Event-driven advertisement**
(decision 15): endpoint online/offline transitions nudge the pump's
registry leg immediately (a wake of the existing loop — no second
advertisement path, so the epoch lease and visibility gates ride
unchanged); the cadence remains the catch-all floor. Observed-fix target
for each: first `status --nodes` convergence in seconds, not ~1 min.
Decision C resolves NTP.

**D4 — convergence polish + pump resilience:**
address seeding per decisions 20/14, both cold starts: post-join (ceremony
writes both sides' addrs into the new `peer-addrs.json`) and post-bringup
(pump reloads it on start so reconnect skips id-only discovery; id-only
fallback on miss or dial failure). **Event-driven advertisement**
(decision 15): endpoint online/offline transitions nudge the pump's
registry leg immediately (a wake of the existing loop — no second
advertisement path, so the epoch lease and visibility gates ride
unchanged); the cadence remains the catch-all floor. Observed-fix target
for each: first `status --nodes` convergence in seconds, not ~1 min.
**NTP TOTP-offset** (decision 18): ceremony-time NTP query → in-process
TOTP offset, system-clock fallback, never sets the OS clock. **Pump
supervision + heartbeat** (decision 23): panic caught → capped-backoff
restart; last-tick heartbeat written for D1's `daemon status` render and
`subnet status` honesty. **`xtask debug-converge` watcher** (decision 19,
`docs/DEBUG-CONVERGE-PLAN.md`, activates REQ-UPD-6 int) — built here,
used by acceptance 6.


**D4 — convergence polish:**
address seeding per decisions E/14, both cold starts: post-join (seed the
pump's addr cache from the live ceremony connection, both sides) and
post-bringup (persist last-known peer addresses; reload on daemon start so
reconnect skips id-only discovery). **Event-driven advertisement**
(decision 15): endpoint online/offline transitions nudge the pump's
registry leg immediately (a wake of the existing loop — no second
advertisement path, so the epoch lease and visibility gates ride
unchanged); the cadence remains the catch-all floor. Observed-fix target
for each: first `status --nodes` convergence in seconds, not ~1 min.
Decision C resolves NTP.

**D4 — convergence polish + pump resilience:**
address seeding per decisions 20/14, both cold starts: post-join (ceremony
writes both sides' addrs into the new `peer-addrs.json`) and post-bringup
(pump reloads it on start so reconnect skips id-only discovery; id-only
fallback on miss or dial failure). **Event-driven advertisement**
(decision 15): endpoint online/offline transitions nudge the pump's
registry leg immediately (a wake of the existing loop — no second
advertisement path, so the epoch lease and visibility gates ride
unchanged); the cadence remains the catch-all floor. Observed-fix target
for each: first `status --nodes` convergence in seconds, not ~1 min.
**NTP TOTP-offset** (decision 18): ceremony-time NTP query → in-process
TOTP offset, system-clock fallback, never sets the OS clock. **Pump
supervision + heartbeat** (decision 23): panic caught → capped-backoff
restart; last-tick heartbeat written for D1's `daemon status` render and
`subnet status` honesty. **`xtask debug-converge` watcher** (decision 19,
`docs/DEBUG-CONVERGE-PLAN.md`, activates REQ-UPD-6 int) — built here,
used by acceptance 6.

hermetic — service/firewall legs env-gated off in CI, proven in acceptance

-address seeding per decisions E/14, both cold starts: post-join (seed the

-pump's addr cache from the live ceremony connection, both sides) and

-post-bringup (persist last-known peer addresses; reload on daemon start so

-reconnect skips id-only discovery). **Event-driven advertisement**

+**D4 — convergence polish + pump resilience:**

+address seeding per decisions 20/14, both cold starts: post-join (ceremony

+writes both sides' addrs into the new `peer-addrs.json`) and post-bringup

+(pump reloads it on start so reconnect skips id-only discovery; id-only

+fallback on miss or dial failure). **Event-driven advertisement**

+**NTP TOTP-offset** (decision 18): ceremony-time NTP query → in-process

+TOTP offset, system-clock fallback, never sets the OS clock. **Pump

+supervision + heartbeat** (decision 23): panic caught → capped-backoff

+restart; last-tick heartbeat written for D1's `daemon status` render and

+`subnet status` honesty. **`xtask debug-converge` watcher** (decision 19,

+`docs/DEBUG-CONVERGE-PLAN.md`, activates REQ-UPD-6 int) — built here,

docs-site sweep for every renamed verb (no page references `spt fork`, bare


docs-site sweep for every renamed verb (no page references `spt fork`, bare
`spt notify`, hidden `daemon`…), quickstart/how-to deltas, README status
table, llms.txt, reference regen; `quickstart_e2e` + twohost ladder updated
where verbs moved; `traceable-reqs check` + `xtask check` green.

docs-site sweep for every renamed verb (no page references `spt fork`, bare
`spt notify`, hidden `daemon`…), quickstart/how-to deltas, README status
table, llms.txt, reference regen; **KNOWN-HAZARDS epoch-reset entry +
inactive `REQ-HAZARD-*`** (decision 24); `quickstart_e2e` + twohost ladder
updated where verbs moved; `traceable-reqs check` + `xtask check` green.


docs-site sweep for every renamed verb (no page references `spt fork`, bare
`spt notify`, hidden `daemon`…), quickstart/how-to deltas, README status
table, llms.txt, reference regen; `quickstart_e2e` + twohost ladder updated
where verbs moved; `traceable-reqs check` + `xtask check` green.

docs-site sweep for every renamed verb (no page references `spt fork`, bare
`spt notify`, hidden `daemon`…), quickstart/how-to deltas, README status
table, llms.txt, reference regen; **KNOWN-HAZARDS epoch-reset entry +
inactive `REQ-HAZARD-*`** (decision 24); `quickstart_e2e` + twohost ladder
updated where verbs moved; `traceable-reqs check` + `xtask check` green.

-table, llms.txt, reference regen; `quickstart_e2e` + twohost ladder updated

-where verbs moved; `traceable-reqs check` + `xtask check` green.

+table, llms.txt, reference regen; **KNOWN-HAZARDS epoch-reset entry +

+inactive `REQ-HAZARD-*`** (decision 24); `quickstart_e2e` + twohost ladder

+updated where verbs moved; `traceable-reqs check` + `xtask check` green.

Order: **D1 → D2 ∥ D3 → D4 → D5** (docs last so captured outputs are real).

D1 first — everything downstream renders the new verbs.


6. Post-join first sync AND post-restart resync both converge in seconds on
   the rig (daemon restart on one node; the peer re-appears in
   `status --nodes` without the 60s id-only rediscovery). An endpoint
   flipping online/offline is visible in the peer's `status --nodes` within
   seconds (event-driven advertisement, not cadence wait).
7. Re-pair proof: regenerate a rig node's identity, re-join with the same
   label — the subnet ends with ONE trust row + ONE registry identity for
   that machine (old rows auto-evicted at ceremony), no manual prune needed.
8. `spt subnet status` with the daemon stopped says so (no "messaging works"
   implication); hint lines appear on bare `spt subnet` only.

6. Post-join first sync AND post-restart resync both converge in seconds on
   the rig (daemon restart on one node; the peer re-appears in
   `status --nodes` without the 60s id-only rediscovery). An endpoint
   flipping online/offline is visible in the peer's `status --nodes` within
   seconds (event-driven advertisement, not cadence wait). Evidence via the
   `xtask debug-converge` table (decision 19).
7. Re-pair proof: regenerate a rig node's identity, re-join with the same
   label — the subnet ends with ONE trust row + ONE registry identity for
   that machine (old rows auto-evicted at ceremony), no manual prune needed.
   Sub-check (decision 24): the evicted identity's peer-side epoch memory is
   gone with the row — the re-paired node's fresh epochs are not dropped as
   stale.
8. `spt subnet status` with the daemon stopped says so (no "messaging works"
   implication); hint lines appear on bare `spt subnet` only.
9. Skewed-clock pairing (decision 18): a node with its clock ≥1 min off
   (enlyzeam, or a rig node with the clock deliberately skewed) completes
   the pairing ceremony via the NTP TOTP offset; with NTP blocked, behavior
   matches today's (system clock, ±1 window).
10. Pump-death honesty (decision 23): with the pump task killed/stalled,
    `spt daemon status` and `subnet status` render the stall (never
    implied-healthy); a panicking pump restarts with capped backoff
    (observed via the supervised-restart log line) instead of dying silent.


6. Post-join first sync AND post-restart resync both converge in seconds on
   the rig (daemon restart on one node; the peer re-appears in
   `status --nodes` without the 60s id-only rediscovery). An endpoint
   flipping online/offline is visible in the peer's `status --nodes` within
   seconds (event-driven advertisement, not cadence wait).
7. Re-pair proof: regenerate a rig node's identity, re-join with the same
   label — the subnet ends with ONE trust row + ONE registry identity for
   that machine (old rows auto-evicted at ceremony), no manual prune needed.
8. `spt subnet status` with the daemon stopped says so (no "messaging works"
   implication); hint lines appear on bare `spt subnet` only.

6. Post-join first sync AND post-restart resync both converge in seconds on
   the rig (daemon restart on one node; the peer re-appears in
   `status --nodes` without the 60s id-only rediscovery). An endpoint
   flipping online/offline is visible in the peer's `status --nodes` within
   seconds (event-driven advertisement, not cadence wait). Evidence via the
   `xtask debug-converge` table (decision 19).
7. Re-pair proof: regenerate a rig node's identity, re-join with the same
   label — the subnet ends with ONE trust row + ONE registry identity for
   that machine (old rows auto-evicted at ceremony), no manual prune needed.
   Sub-check (decision 24): the evicted identity's peer-side epoch memory is
   gone with the row — the re-paired node's fresh epochs are not dropped as
   stale.
8. `spt subnet status` with the daemon stopped says so (no "messaging works"
   implication); hint lines appear on bare `spt subnet` only.
9. Skewed-clock pairing (decision 18): a node with its clock ≥1 min off
   (enlyzeam, or a rig node with the clock deliberately skewed) completes
   the pairing ceremony via the NTP TOTP offset; with NTP blocked, behavior
   matches today's (system clock, ±1 window).
10. Pump-death honesty (decision 23): with the pump task killed/stalled,
    `spt daemon status` and `subnet status` render the stall (never
    implied-healthy); a panicking pump restarts with capped backoff
    (observed via the supervised-restart log line) instead of dying silent.

-   seconds (event-driven advertisement, not cadence wait).

+   seconds (event-driven advertisement, not cadence wait). Evidence via the

+   `xtask debug-converge` table (decision 19).

+   Sub-check (decision 24): the evicted identity's peer-side epoch memory is

+   gone with the row — the re-paired node's fresh epochs are not dropped as

+9. Skewed-clock pairing (decision 18): a node with its clock ≥1 min off

+   (enlyzeam, or a rig node with the clock deliberately skewed) completes

+   the pairing ceremony via the NTP TOTP offset; with NTP blocked, behavior

+   matches today's (system clock, ±1 window).

+10. Pump-death honesty (decision 23): with the pump task killed/stalled,

+    `spt daemon status` and `subnet status` render the stall (never

+    implied-healthy); a panicking pump restarts with capped backoff

+    (observed via the supervised-restart log line) instead of dying silent.


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

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

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

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

D3 — install & elevation hardening

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

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

D5 — docs + CI + acceptance prep

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


## Non-goals

- spt-claude-code (separate repo; scoping starts after M8 closes).
- PresenceChannel, instantiate-anywhere, remote-exec, sandboxing, sidecar
  adapters, manifest composition (DEFERRED — triggers unmet).
- Tier-2 agent docs (MCP server / `--help --json`).
- Elevated spt-hosted endpoints (consented elevation satellite — own
  deliverable, rides REQ-CONSENT-2 when triggered).

## Non-goals

- spt-claude-code (separate repo; scoping starts after M8 closes).
- PresenceChannel, instantiate-anywhere, remote-exec, sandboxing, sidecar
  adapters, manifest composition (DEFERRED — triggers unmet).
- Tier-2 agent docs (MCP server / `--help --json`).
- Elevated spt-hosted endpoints (consented elevation satellite — own
  deliverable, rides REQ-CONSENT-2 when triggered).
- `spt subnet disband` (decision 16 — deferred, non-breaking to add later).
- Epoch-reset *guard* (decision 24 — documented hazard only; guard waits
  for a field hit of the narrow slice).
- Windows at-startup S4U task variant (decision 17 — opt-in if a headless
  Windows node ever exists).
- Daemon-wide NTP time correction / setting the OS clock (decision 18 —
  TOTP offset only).


## Non-goals

- spt-claude-code (separate repo; scoping starts after M8 closes).
- PresenceChannel, instantiate-anywhere, remote-exec, sandboxing, sidecar
  adapters, manifest composition (DEFERRED — triggers unmet).
- Tier-2 agent docs (MCP server / `--help --json`).
- Elevated spt-hosted endpoints (consented elevation satellite — own
  deliverable, rides REQ-CONSENT-2 when triggered).
- `spt subnet disband` (decision 16 — deferred, non-breaking to add later).
- Epoch-reset *guard* (decision 24 — documented hazard only; guard waits
  for a field hit of the narrow slice).
- Windows at-startup S4U task variant (decision 17 — opt-in if a headless
  Windows node ever exists).
- Daemon-wide NTP time correction / setting the OS clock (decision 18 —
  TOTP offset only).

+- `spt subnet disband` (decision 16 — deferred, non-breaking to add later).

+- Epoch-reset *guard* (decision 24 — documented hazard only; guard waits

+  for a field hit of the narrow slice).

+- Windows at-startup S4U task variant (decision 17 — opt-in if a headless

+- Daemon-wide NTP time correction / setting the OS clock (decision 18 —


PowerShell

Show last commit message body for trailer format


docs(plan): M8 — event-driven advertisement on endpoint state change

Decision 15 (user 2026-06-07): endpoint online/offline transitions nudge
the pump's registry leg immediately instead of waiting out the cadence;
the cadence stays the steady-state floor. Implemented as a wake of the
existing loop, not a second advertisement path, so the epoch lease and
visibility gates ride unchanged. Field motivation: a re-listened
endpoint stayed rendered [0/1] for up to a full cadence during the
2026-06-07 underflow diagnosis tail.

D4 reshaped, acceptance 6 extended.

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


git add M8-PLAN.md && git commit -m @'
docs(plan): M8 ratified — open decisions A-G + pump-diagnosis items resolved

2026-06-07 grill outcomes, now decisions 16-24:

- 16 (A): subnet disband DEFERRED — leave covers the field need
- 17 (B): systemd user service + enable-linger / Windows at-logon task
- 18 (C): NTP IN SCOPE — enlyzeam clock >1min off fired the trigger;
  TOTP offset-correction only, ceremony-time query, system-clock
  fallback, never sets the OS clock
- 19 (D): xtask debug-converge IN SCOPE (REQ-UPD-6 int, builds in D4)
- 20 (E): addr seeding = narrow patch; NEW peer-addrs.json (no existing
  addr cache — production resolution is the id-only PeerResolver),
  resolver-first with id-only fallback on miss/dial-fail
- 21 (F): endpoint access ports 1:1 — location-only break
- 22 (G): machine id = OS machine id, domain-separated SHA-256 before
  gossip, minted-UUID fallback
- 23 (NEW): pump liveness — heartbeat rendered in daemon/subnet status +
  supervised pump restart with capped backoff (hfenduleam half-death)
- 24 (NEW): epoch-reset hazard documented + minted inactive; acceptance 7
  verifies re-pair eviction clears peer-side epoch memory

Deliverables D1/D2/D3/D4/D5 retargeted; acceptance 6/7 extended, 9/10
added; non-goals updated. Status DRAFT -> RATIFIED.

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

Commit ratified M8 plan


warning: in the working copy of 'M8-PLAN.md', LF will be replaced by CRLF the next time Git touches it
[dev-freeform 5c7df02] docs(plan): M8 ratified — open decisions A-G + pump-diagnosis items resolved
 1 file changed, 72 insertions(+), 39 deletions(-)


Updated task #1 status


Updated task #2 status


Plan ratified + committed (`5c7df02`). Now requirements minting — read registry format.