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

## Tasks (expanded 2026-06-03 at M4 start)

> **Authored against:** ADR-0002 (net baked-in), ADR-0003 (instances + #7/#8/#9 red-team),
> ADR-0005 (pairing + #10/#11/#12 red-team), ADR-0006 (multi-subnet), ADR-0007 (notif),
> ADR-0009 (endpoint ACL, node-tier), ADR-0004 §B/§D (broker ownership + update gate),
> KNOWN-HAZARDS 6.5 (`REQ-HAZARD-DIRECT-WRITE-PRECEDENCE`). Sub-tasks are the executable
> unit; each lands with its tag in the same commit (TRACEABILITY rule 1). Activate the
> listed reqs **at the sub-task that delivers them** (rule 5), not up front.

**Sequencing.** D0→D1 are the transport floor. D2 (pairing) and D3 (registry) both sit on
D1 and can interleave, but D3's cross-node resolution wants D2's trust material to be real,
so author D2 first. D4 (broker-owned endpoint) underpins D5/D7 (both ride broker QUIC
streams). D6 (Psyche sync) needs D3's per-subnet distribution. D8 (notif) reuses D3+D6
distribution. D9 is the closeout. **Each D-task is itself a milestone-sized body** — expect
a `/self-clear` + commune between most of them; commit atomically per sub-task.

### D0 — Scaffold `crates/spt-net`
- **D0a** New crate (sibling of spt-term/spt-runtime, above spt-msg). Workspace member; deps
  `spt-proto`/`spt-store`/`spt-msg`; `publish = false` (not public SDK, R-ARCH-2).
- **D0b** `net` cargo feature (default-on in `spt` ref binary, optional for lib consumers —
  ADR-0002). Empty `net`-gated module tree so a `--no-default-features` lib build stays clean.
- **Reqs:** REQ-NET-1 (`doc` stub only at D0; `impl`/`unit` land in D1). · **Acceptance:**
  `cargo check` + `--no-default-features` both compile; layering acyclic; `traceable-reqs check` green.

### D1 — Iroh endpoint + mDNS discovery
- **D1a** Iroh endpoint bound to the node's Ed25519 identity (`REQ-NODE-IDENTITY` already
  built in spt-proto — reuse, don't re-mint). Spike #2 shape (iroh 0.98).
- **D1b** n0 default relay + `self-host relay` config knob + plain-language disclosure string
  (ADR-0002 v1 relay stance).  · **REQ-NET-2**
- **D1c** mDNS LAN discovery: one node advertises, another finds it; connect on a `net` ALPN.
- **Reqs:** REQ-NET-1 (impl+unit), REQ-NET-2. · **Acceptance:** two local nodes discover + connect; E2E.

### D2 — TOTP-SPAKE2 pairing (security-critical)
- **D2a** TOTP seed = durable subnet secret; QR / `otpauth://` provisioning; SPAKE2 handshake
  using the current 6-digit code as the PAKE password (ADR-0005). Dedicated pre-trust pairing ALPN.
- **D2b** **Full pairing-transcript binding** — roles, both node pubkeys, subnet ID, seed
  epoch, TOTP time-step, confirmation MACs (red-team **#12**). Negative tests: MITM, replay,
  reflection, wrong-subnet, stale time-step.
- **D2c** **Seed epoch + rotation** — removing a node rotates the subnet seed so an old
  node/seed cannot rejoin; transcript binds the epoch (red-team **#10**). Trust-store delete
  alone is NOT revocation.
- **D2d** **Subnet-global rate limiting** — one active ceremony per subnet, shared attempt
  counter, exponential backoff; justify-or-drop the ±1 TOTP window (red-team **#11**).
- **D2e** Local trust store (authorized peer pubkeys) + TOFU + warn-on-key-change. · **REQ-PAIR-2**
- **D2f** Multi-subnet ceremony: discovery takes TOTP code + subnet-name; rendezvous token =
  `H(subnet-name ‖ TOTP-epoch)`; create-new names the subnet up front. · **REQ-PAIR-5, REQ-PAIR-4**
- **D2g** Elevation-gated per-subnet code fetch (Win UAC / Linux root or elevated agent
  endpoint; else authenticator app). `spt pair show-totp [--subnet|--create-new]`. · **REQ-PAIR-6, REQ-PAIR-3**
- **Reqs:** REQ-PAIR-1..6. · **New hazard reqs to register first (rule 3):** add
  `REQ-HAZARD-PAIR-TRANSCRIPT-BIND` (#12), `REQ-HAZARD-PAIR-SEED-ROTATION` (#10),
  `REQ-HAZARD-PAIR-RATE-LIMIT` (#11) to `traceable-reqs.toml` before satisfying.
- **Acceptance:** two machines pair via one TOTP code; trust material stored; all negative tests pass.
- **(REQ-PAIR-7 subnet icon = GUI-only consumer; defer to D9 unless trivial.)**

### D3 — Subnet registry + resolution policy
- **D3a** Per-subnet eventually-consistent registry `endpoint_id → [instances]` with
  status active/dormant/offline. · **REQ-INST-7**
- **D3b** **Registry lease/epoch via monotonic per-node epochs** (NOT wall-clock) so
  active/dormant transitions + partitions don't route to the wrong instance (red-team **#8**).
  Chaos-test stale entries + ambiguous active instance.
- **D3c** Resolution policy: local → most-recently-active → explicit `id@node`. Qualified
  addressing `[subnet:]id[@node]`; **ambiguity across visible subnets refuses + forces
  qualification** (no guessing). · **REQ-INST-10**
- **D3d** Multi-subnet membership (one node holds N trust-contexts/seeds; same-user only,
  cross-user seam shaped). Join-time bare-id collision check. · **REQ-INST-9**
- **D3e** Endpoint visibility per-`(endpoint,subnet)`: excluded = not advertised AND not
  routable; OR-of-defaults + per-`(E,S)` override; **visibility gates sync**. · **REQ-INST-12, REQ-INST-13**
- **D3f** `spt rename <id> <new_id>` rippled to all instances (registry, every perch,
  `a-<id>` context branch), collision-checked per target subnet, 6.5-reconciled. · **REQ-INST-11**
- **Reqs:** REQ-INST-7,9,10,11,12,13. **New hazard req (rule 3):** `REQ-HAZARD-REGISTRY-EPOCH-LEASE` (#8).
- **Acceptance:** an `endpoint_id` resolves to a live instance across nodes; ambiguous id refuses.

### D4 — QUIC-stream broker ownership (impl Spike #3 shape)
- **D4a** Move the Iroh endpoint + live QUIC streams into the **stable broker** per ADR-0004
  §B ownership table (Spike #3 validated the shape; build it for real). Broker owns the
  socket; brain attaches/detaches.
- **D4b** Stream survives a brain restart **gapless** (no endpoint interruption — composes
  with the M3b handoff substrate + REQ-UPD-3 guarantee).
- **D4c** `REQ-EP-4` PresenceChannel broker endpoint seam (day-one seam; minimal v1 surface).
- **Reqs:** ADR-0004 §B, REQ-EP-4 (seam). **Track (ADR-0002 SERIOUS #6):** start the
  fault-injection acceptance matrix (crash/hang Iroh, mDNS, registry, PTY broker independently).
- **Acceptance:** a live cross-node QUIC stream survives a brain restart gapless.

### D5 — Cross-node message + remote-drive of a running instance
- **D5a** WAN message delivery over the broker endpoint (the REQ-MSG path, now off-node). · **REQ-NET-1**
- **D5b** Remote-control mode (byte-stream terminal attach to an instance on another node;
  compute+files stay remote) — distinct from local operation. · **REQ-INST-8**
- **D5c** Off-node remote-drive **detection** + file transfer (handoff seam: `git pull` +
  fresh-with-preload resume; remote goes dormant, no teardown). · **REQ-REACH-1**
- **D5d** Endpoint access control (ADR-0009 node-tier): per-endpoint whitelist, origin-node
  gate, stateful-firewall (reply/outbound exempt), outer gate before grants. · **REQ-SEC-1**
- **Reqs:** REQ-NET-1, REQ-INST-8, REQ-REACH-1, REQ-SEC-1. · **Acceptance:** drive an agent
  on machine A from machine B; off-node access denied without a grant.

### D6 — Cross-node Psyche sync (P2P), retire gh-repo interim
- **D6a** P2P context replication replacing the interim `gh`-repo sync (ADR-0002 hub stays
  opt-in for `tracked/` only). Two-tier: live context → all instances; project context →
  same-project instances. · **REQ-NET-3, REQ-INST-2, REQ-INST-5**
- **D6b** **Distributed precedence/freshness merge** — precedence marker carries source +
  timestamp + **node identity**; freshness = newest-and-newer-than-mine; the distributed
  generalization of 6.5 (red-team **#7**). **Merge-model decision pending** (monotonic
  per-node epochs / vector clocks / CRDT-ish / explicit conflict review) — decide at D6
  authoring with the user; two concurrent active writers = conflict, not stale-write.
- **D6c** Subnet-exclusive sync honored (hidden ⟹ not synced; per-endpoint membership list).
- **Reqs:** REQ-NET-3, REQ-INST-2, REQ-INST-5; `REQ-HAZARD-DIRECT-WRITE-PRECEDENCE` (extend
  coverage to multi-writer/node-id). · **Acceptance:** the mind follows the user across two
  machines (two-tier); concurrent-write conflict surfaced, not silently dropped.

### D7 — Update peer-propagation transport (into the M3c engine seam)
- **D7a** Plug the P2P transport into the **existing M3c update-engine seam** (`update::`
  front door); the subnet self-heals to latest. No re-architecture of the engine.
- **D7b** **Every propagated binary still passes the M3c signature/rollback gate per node**
  before any handoff — one compromised node must not poison the subnet (ADR-0004 §D). · **REQ-UPD-1**
- **Reqs:** REQ-UPD-1. · **Acceptance:** a signed update self-heals across the subnet; the
  Ed25519+rollback gate runs on every receiving node; a tampered propagated binary is rejected.

### D8 — Subnet notification primitive
- **D8a** Notif as a first-class kind (user-directed, dismissable, resurfacing): per-subnet
  replicated spool (reuses D3 distribution), subnet-tagged, dismiss-state replicates
  subnet-wide. Two states seen/dismissed. · **REQ-NOTIF-1**
- **D8b** First-fire → most-recently-active endpoint in-subnet; resurface undismissed at
  reported boundaries (wake / api-clear / api-compact / new-session) gated by seen-per-endpoint
  + cross-endpoint suppression timeout (~1h). Scoped to visible subnets.
- **D8c** Refactor the M3c self-update prompt + consent escalation to be **notif producers**
  (one primitive, not three ad-hoc paths).
- **D8d** `spt notify` (agent-issued subnet notif) + `notif_command` manifest seam on harness
  + shell adapters; envelope `from` = issuer endpoint+node+subnet. · **REQ-NOTIF-2**
- **Reqs:** REQ-NOTIF-1, REQ-NOTIF-2. · **Acceptance:** a notif fires to the active endpoint,
  resurfaces at a boundary, dismiss replicates subnet-wide; `spt notify` reaches the user.

### D9 — Activation sweep + cross-node E2E + closeout
- **D9a** Dormancy resource budget (red-team **#9**): measure N dormant sessions on real
  adapters; define adapter warm/cold policy before locking warm default. · **REQ-INST-3, REQ-INST-4**
- **D9b** Two-host cross-node E2E on the real rig (gravity-linux ↔ a Windows host): pair →
  register → cross-node message → remote-drive → Psyche sync → update self-heal.
- **D9c** REQ-INST-14 (resource advertisement) + REQ-INST-15 (immutable home subnet + `spt
  fork`, ADR-0010) — register/activate if in V1-mid line; else document as forward seam.
- **D9d** Confirm every M4 req `required_stages` is satisfied; `traceable-reqs check` green;
  amend ROADMAP (M4 delivered) + CONTEXT glossary; CI matrix green (ubuntu+windows+traceability).
- **Acceptance:** the V1-mid scope line met; `check` green; CI green; cross-node proof demoed.

## Requirement → task coverage map

| Task | Activates |
|------|-----------|
| D0 | REQ-NET-1 (doc) |
| D1 | REQ-NET-1 (impl/unit), REQ-NET-2 |
| D2 | REQ-PAIR-1..6 + 3 new `REQ-HAZARD-PAIR-*` |
| D3 | REQ-INST-7,9,10,11,12,13 + `REQ-HAZARD-REGISTRY-EPOCH-LEASE` |
| D4 | REQ-EP-4 (seam), ADR-0004 §B |
| D5 | REQ-INST-8, REQ-REACH-1, REQ-SEC-1, REQ-NET-1 (WAN msg) |
| D6 | REQ-NET-3, REQ-INST-2, REQ-INST-5, `REQ-HAZARD-DIRECT-WRITE-PRECEDENCE` (extend) |
| D7 | REQ-UPD-1 |
| D8 | REQ-NOTIF-1, REQ-NOTIF-2 |
| D9 | REQ-INST-3,4 (+14,15 if in line); sweep |

**Deferred to M5 (out of M4, per Scope §Out):** REQ-EP-5 (shell instantiation), REQ-REACH-2
(remote command exec), PresenceChannel full impl, cross-user generalization of every per-subnet
key. Their seams are shaped in D4/D5/D8 but not built.

## Red-team obligations (binding — from the Stage-A review, must close in M4)

| Item | ADR | Lands in | Gate |
|------|-----|----------|------|
| #6 one-daemon failure domain | 0002 | D4 | fault-injection acceptance matrix |
| #7 wall-clock loses concurrent writes | 0003 | D6 | epoch/vector-clock merge, conflict surfaced |
| #8 ambiguous resolution under skew | 0003 | D3 | monotonic per-node epoch lease + chaos test |
| #9 dormant-warm resource cost | 0003 | D9 | measured budget + warm/cold policy |
| #10 trust-delete ≠ revocation | 0005 | D2c | seed epoch + rotation |
| #11 distributed PAKE guessing | 0005 | D2d | subnet-global rate limit, one ceremony |
| #12 transcript must bind | 0005 | D2b | full transcript spec + negative tests |

# M4 Plan — networking + instances (STUB)

> **Just-in-time, lightweight** — same pattern as `M0/M1/M2a/M2b/M3a/M3b/M3c-PLAN.md`.
> This is a **stub**: the ordered task layer is authored in full when M4 starts.
> Branch: `dev-freeform`. Authoritative architecture: **ADR-0002** (networking baked
> into core), **ADR-0003** (multi-instance node-anchored identity), **ADR-0005**
> (pairing), **ADR-0006/0007** (subnet membership + notification primitive), and
> `ROADMAP.md` §M4.

> **Upstream is done:** **M3 COMPLETE (2026-06-03)** — M3a (spt-term PTY) · M3b
> (broker/brain daemon) · M3c (signed self-update). The broker's resource-ownership
> table (ADR-0004 §B) and the update engine's transport seam are both built
> transport-agnostic precisely so M4 is an *integration*, not a re-architecture:
> M4 plugs the P2P transport into seams that already exist.

## Goal

Deliver the real cross-machine proof: `spt-net` (Iroh WAN + mDNS LAN + TOTP-SPAKE2
pairing + the eventually-consistent subnet registry) and the multi-instance model
(ADR-0003) so the same agent runs on each of a user's machines with a synced mind,
reachable and operable from anywhere — **cross-node message + drive an agent on
machine A from machine B**, plus update peer-propagation across the subnet.

## Scope

### In

- **`spt-net` crate** — the remaining sibling in the layering
  (`…→spt-msg→{spt-net, spt-term, spt-runtime}→…`): Iroh endpoint (WAN, the Spike #2
  shape) + mDNS (LAN discovery) + TOTP-SPAKE2 pairing (ADR-0005) (`R-NET-*`, `R-PAIR-*`).
- **Subnet registry** — eventually-consistent `endpoint_id → [instances]` + resolution
  policy (local → most-recently-active → `id@node`) (`REQ-INST-7`).
- **QUIC-stream broker ownership — the implementation.** Spike #3 validated the
  *shape* (a broker-owned Iroh endpoint + live QUIC stream survives a brain restart);
  M4 builds it for real into the broker's ADR-0004 §B ownership table.
- **Cross-node Psyche sync (P2P)** — replaces the interim gh-repo sync; the direct-write
  precedence marker + node id (KNOWN-HAZARDS 6.5).
- **Remote-drive of an already-running instance** — the network session-surface (the
  highest-value v1 slice; remote-drive detection + off-node file transfer).
- **Update peer-propagation transport (`REQ-UPD-1`)** — plug the P2P transport into the
  **M3c update-engine seam**: the subnet self-heals to latest, every propagated binary
  still passing the M3c signature/rollback gate before any handoff (one compromised node
  must not poison the subnet — ADR-0004 §D).

### Out

- **Shells, PresenceChannel, and the consent-gated capabilities** (remote-exec,
  instantiate-anywhere) — **M5**, landing into the v1 consent seam M3c-C2 already shaped.
- **Cross-user / per-(subnet, user)** generalization of the notif spool + dismissal — a
  documented forward seam, not v1.

## Requirements (activate per task when M4 starts)

`R-NET-*`, `R-PAIR-*`, `REQ-INST-7`, `REQ-UPD-1`, `REQ-EP-4` (PresenceChannel impl — or
M5), and the distributed precedence/freshness generalization of 6.5 — all currently
`required_stages = []`. Re-confirm the new-requirement assessment (TRACEABILITY rule 3)
at task-authoring time (pairing + subnet wire formats may surface `REQ-HAZARD-*`).

## Clean-room posture

Copy-verbatim only the sister's stable wire/registry **formats** that survive (ADR-0001);
clean-room the transport (the sister had no P2P). Reuse Spike #2/#3 Iroh learnings (iroh
0.98 API, the loopback QUIC-survival shape) — those were sandbox spikes, not in-repo crates.

## Tasks (to be expanded at M4 start)

| # | Task | Reqs / hazards | Acceptance |
|---|------|----------------|------------|
| D0 | Scaffold `crates/spt-net` (sibling of spt-term/spt-runtime, above spt-msg) | — | compiles; layering acyclic; `check` green |
| D1 | Iroh endpoint + mDNS discovery (one node advertises, another finds it) | R-NET-* | two local nodes discover + connect |
| D2 | TOTP-SPAKE2 pairing | R-PAIR-* | two machines pair via one TOTP code; trust material stored |
| D3 | Subnet registry + resolution policy (local → most-recently-active → `id@node`) | REQ-INST-7 | an `endpoint_id` resolves to a live instance across nodes |
| D4 | QUIC-stream broker ownership (impl Spike #3's shape into the broker) | ADR-0004 §B | a live cross-node stream survives a brain restart gapless |
| D5 | Cross-node message + remote-drive of a running instance | R-NET-*, REQ-INST-* | drive an agent on machine A from machine B |
| D6 | Cross-node Psyche sync (P2P), retire gh-repo interim | 6.5 (node id) | the mind follows the user across machines (two-tier) |
| D7 | Update peer-propagation transport into the M3c engine seam | REQ-UPD-1 | a signed update self-heals across the subnet; the gate still runs per node |
| D8 | Activation sweep + cross-node E2E; amend ROADMAP (M4 delivered) + CONTEXT | — | the V1-mid scope line met; `check` green; CI matrix green |

## Workspace change

Add `crates/spt-net` (the remaining sibling). Final layering (R-ARCH-1, acyclic):
`spt-proto → spt-store → spt-msg → {spt-net, spt-term, spt-runtime} → spt-live → spt-daemon → spt`.
`spt-net` is **not** public SDK (R-ARCH-2 stays proto/runtime/msg).

clean-room the transport (the sister had no P2P). Reuse Spike #2/#3 Iroh learnings (iroh

+## Tasks (expanded 2026-06-03 at M4 start)

-| # | Task | Reqs / hazards | Acceptance |

-| D0 | Scaffold `crates/spt-net` (sibling of spt-term/spt-runtime, above spt-msg) | — | compiles; layering acyclic; `check` green |

-| D1 | Iroh endpoint + mDNS discovery (one node advertises, another finds it) | R-NET-* | two local nodes discover + connect |

-| D2 | TOTP-SPAKE2 pairing | R-PAIR-* | two machines pair via one TOTP code; trust material stored |

-| D3 | Subnet registry + resolution policy (local → most-recently-active → `id@node`) | REQ-INST-7 | an `endpoint_id` resolves to a live instance across nodes |

-| D4 | QUIC-stream broker ownership (impl Spike #3's shape into the broker) | ADR-0004 §B | a live cross-node stream survives a brain restart gapless |

-| D5 | Cross-node message + remote-drive of a running instance | R-NET-*, REQ-INST-* | drive an agent on machine A from machine B |

-| D6 | Cross-node Psyche sync (P2P), retire gh-repo interim | 6.5 (node id) | the mind follows the user across machines (two-tier) |

-| D7 | Update peer-propagation transport into the M3c engine seam | REQ-UPD-1 | a signed update self-heals across the subnet; the gate still runs per node |

-| D8 | Activation sweep + cross-node E2E; amend ROADMAP (M4 delivered) + CONTEXT | — | the V1-mid scope line met; `check` green; CI matrix green |

+> **Authored against:** ADR-0002 (net baked-in), ADR-0003 (instances + #7/#8/#9 red-team),

+> ADR-0009 (endpoint ACL, node-tier), ADR-0004 §B/§D (broker ownership + update gate),

+> KNOWN-HAZARDS 6.5 (`REQ-HAZARD-DIRECT-WRITE-PRECEDENCE`). Sub-tasks are the executable

+> unit; each lands with its tag in the same commit (TRACEABILITY rule 1). Activate the

+> listed reqs **at the sub-task that delivers them** (rule 5), not up front.

+**Sequencing.** D0→D1 are the transport floor. D2 (pairing) and D3 (registry) both sit on

+D1 and can interleave, but D3's cross-node resolution wants D2's trust material to be real,

+so author D2 first. D4 (broker-owned endpoint) underpins D5/D7 (both ride broker QUIC

+streams). D6 (Psyche sync) needs D3's per-subnet distribution. D8 (notif) reuses D3+D6

+distribution. D9 is the closeout. **Each D-task is itself a milestone-sized body** — expect

+a `/self-clear` + commune between most of them; commit atomically per sub-task.

+- **D0a** New crate (sibling of spt-term/spt-runtime, above spt-msg). Workspace member; deps

+  `spt-proto`/`spt-store`/`spt-msg`; `publish = false` (not public SDK, R-ARCH-2).

+- **D0b** `net` cargo feature (default-on in `spt` ref binary, optional for lib consumers —

+  ADR-0002). Empty `net`-gated module tree so a `--no-default-features` lib build stays clean.

+- **Reqs:** REQ-NET-1 (`doc` stub only at D0; `impl`/`unit` land in D1). · **Acceptance:**

+  `cargo check` + `--no-default-features` both compile; layering acyclic; `traceable-reqs check` green.

+### D1 — Iroh endpoint + mDNS discovery

+- **D1a** Iroh endpoint bound to the node's Ed25519 identity (`REQ-NODE-IDENTITY` already

+  built in spt-proto — reuse, don't re-mint). Spike #2 shape (iroh 0.98).

+- **D1b** n0 default relay + `self-host relay` config knob + plain-language disclosure string

+  (ADR-0002 v1 relay stance).  · **REQ-NET-2**

+- **D1c** mDNS LAN discovery: one node advertises, another finds it; connect on a `net` ALPN.

+- **Reqs:** REQ-NET-1 (impl+unit), REQ-NET-2. · **Acceptance:** two local nodes discover + connect; E2E.

+### D2 — TOTP-SPAKE2 pairing (security-critical)

+- **D2a** TOTP seed = durable subnet secret; QR / `otpauth://` provisioning; SPAKE2 handshake

+  using the current 6-digit code as the PAKE password (ADR-0005). Dedicated pre-trust pairing ALPN.

+- **D2b** **Full pairing-transcript binding** — roles, both node pubkeys, subnet ID, seed

+  epoch, TOTP time-step, confirmation MACs (red-team **#12**). Negative tests: MITM, replay,

+  reflection, wrong-subnet, stale time-step.

+- **D2c** **Seed epoch + rotation** — removing a node rotates the subnet seed so an old

+  node/seed cannot rejoin; transcript binds the epoch (red-team **#10**). Trust-store delete

+- **D2d** **Subnet-global rate limiting** — one active ceremony per subnet, shared attempt

+  counter, exponential backoff; justify-or-drop the ±1 TOTP window (red-team **#11**).

+- **D2e** Local trust store (authorized peer pubkeys) + TOFU + warn-on-key-change. · **REQ-PAIR-2**

+- **D2f** Multi-subnet ceremony: discovery takes TOTP code + subnet-name; rendezvous token =

+  `H(subnet-name ‖ TOTP-epoch)`; create-new names the subnet up front. · **REQ-PAIR-5, REQ-PAIR-4**

+- **D2g** Elevation-gated per-subnet code fetch (Win UAC / Linux root or elevated agent

+  endpoint; else authenticator app). `spt pair show-totp [--subnet|--create-new]`. · **REQ-PAIR-6, REQ-PAIR-3**

+- **Reqs:** REQ-PAIR-1..6. · **New hazard reqs to register first (rule 3):** add

+  `REQ-HAZARD-PAIR-TRANSCRIPT-BIND` (#12), `REQ-HAZARD-PAIR-SEED-ROTATION` (#10),

+  `REQ-HAZARD-PAIR-RATE-LIMIT` (#11) to `traceable-reqs.toml` before satisfying.

+- **Acceptance:** two machines pair via one TOTP code; trust material stored; all negative tests pass.

+- **(REQ-PAIR-7 subnet icon = GUI-only consumer; defer to D9 unless trivial.)**

+### D3 — Subnet registry + resolution policy

+- **D3a** Per-subnet eventually-consistent registry `endpoint_id → [instances]` with

+  status active/dormant/offline. · **REQ-INST-7**

+- **D3b** **Registry lease/epoch via monotonic per-node epochs** (NOT wall-clock) so

+  active/dormant transitions + partitions don't route to the wrong instance (red-team **#8**).

+  Chaos-test stale entries + ambiguous active instance.

+- **D3c** Resolution policy: local → most-recently-active → explicit `id@node`. Qualified

+  addressing `[subnet:]id[@node]`; **ambiguity across visible subnets refuses + forces

+  qualification** (no guessing). · **REQ-INST-10**

+- **D3d** Multi-subnet membership (one node holds N trust-contexts/seeds; same-user only,

+  cross-user seam shaped). Join-time bare-id collision check. · **REQ-INST-9**

+- **D3e** Endpoint visibility per-`(endpoint,subnet)`: excluded = not advertised AND not

+  routable; OR-of-defaults + per-`(E,S)` override; **visibility gates sync**. · **REQ-INST-12, REQ-INST-13**

+- **D3f** `spt rename <id> <new_id>` rippled to all instances (registry, every perch,

+  `a-<id>` context branch), collision-checked per target subnet, 6.5-reconciled. · **REQ-INST-11**

+- **Reqs:** REQ-INST-7,9,10,11,12,13. **New hazard req (rule 3):** `REQ-HAZARD-REGISTRY-EPOCH-LEASE` (#8).

+- **Acceptance:** an `endpoint_id` resolves to a live instance across nodes; ambiguous id refuses.

+### D4 — QUIC-stream broker ownership (impl Spike #3 shape)

+- **D4a** Move the Iroh endpoint + live QUIC streams into the **stable broker** per ADR-0004

+  §B ownership table (Spike #3 validated the shape; build it for real). Broker owns the

+- **D4b** Stream survives a brain restart **gapless** (no endpoint interruption — composes

+  with the M3b handoff substrate + REQ-UPD-3 guarantee).

+- **D4c** `REQ-EP-4` PresenceChannel broker endpoint seam (day-one seam; minimal v1 surface).

+- **Reqs:** ADR-0004 §B, REQ-EP-4 (seam). **Track (ADR-0002 SERIOUS #6):** start the

+  fault-injection acceptance matrix (crash/hang Iroh, mDNS, registry, PTY broker independently).

+- **Acceptance:** a live cross-node QUIC stream survives a brain restart gapless.

+### D5 — Cross-node message + remote-drive of a running instance

+- **D5a** WAN message delivery over the broker endpoint (the REQ-MSG path, now off-node). · **REQ-NET-1**

+- **D5b** Remote-control mode (byte-stream terminal attach to an instance on another node;

+  compute+files stay remote) — distinct from local operation. · **REQ-INST-8**

+- **D5c** Off-node remote-drive **detection** + file transfer (handoff seam: `git pull` +

+  fresh-with-preload resume; remote goes dormant, no teardown). · **REQ-REACH-1**

+- **D5d** Endpoint access control (ADR-0009 node-tier): per-endpoint whitelist, origin-node

+  gate, stateful-firewall (reply/outbound exempt), outer gate before grants. · **REQ-SEC-1**

+- **Reqs:** REQ-NET-1, REQ-INST-8, REQ-REACH-1, REQ-SEC-1. · **Acceptance:** drive an agent

+  on machine A from machine B; off-node access denied without a grant.

+### D6 — Cross-node Psyche sync (P2P), retire gh-repo interim

+- **D6a** P2P context replication replacing the interim `gh`-repo sync (ADR-0002 hub stays

+  opt-in for `tracked/` only). Two-tier: live context → all instances; project context →

+  same-project instances. · **REQ-NET-3, REQ-INST-2, REQ-INST-5**

+- **D6b** **Distributed precedence/freshness merge** — precedence marker carries source +

+  timestamp + **node identity**; freshness = newest-and-newer-than-mine; the distributed

+  generalization of 6.5 (red-team **#7**). **Merge-model decision pending** (monotonic

+  per-node epochs / vector clocks / CRDT-ish / explicit conflict review) — decide at D6

+  authoring with the user; two concurrent active writers = conflict, not stale-write.

+- **D6c** Subnet-exclusive sync honored (hidden ⟹ not synced; per-endpoint membership list).

+- **Reqs:** REQ-NET-3, REQ-INST-2, REQ-INST-5; `REQ-HAZARD-DIRECT-WRITE-PRECEDENCE` (extend

+  coverage to multi-writer/node-id). · **Acceptance:** the mind follows the user across two

+  machines (two-tier); concurrent-write conflict surfaced, not silently dropped.

+### D7 — Update peer-propagation transport (into the M3c engine seam)

+- **D7a** Plug the P2P transport into the **existing M3c update-engine seam** (`update::`

+  front door); the subnet self-heals to latest. No re-architecture of the engine.

+- **D7b** **Every propagated binary still passes the M3c signature/rollback gate per node**

+  before any handoff — one compromised node must not poison the subnet (ADR-0004 §D). · **REQ-UPD-1**

+- **Reqs:** REQ-UPD-1. · **Acceptance:** a signed update self-heals across the subnet; the

+  Ed25519+rollback gate runs on every receiving node; a tampered propagated binary is rejected.

+- **D8a** Notif as a first-class kind (user-directed, dismissable, resurfacing): per-subnet

+  replicated spool (reuses D3 distribution), subnet-tagged, dismiss-state replicates

+  subnet-wide. Two states seen/dismissed. · **REQ-NOTIF-1**

+- **D8b** First-fire → most-recently-active endpoint in-subnet; resurface undismissed at

+  reported boundaries (wake / api-clear / api-compact / new-session) gated by seen-per-endpoint

+  + cross-endpoint suppression timeout (~1h). Scoped to visible subnets.

+- **D8c** Refactor the M3c self-update prompt + consent escalation to be **notif producers**

+  (one primitive, not three ad-hoc paths).

+- **D8d** `spt notify` (agent-issued subnet notif) + `notif_command` manifest seam on harness

+  + shell adapters; envelope `from` = issuer endpoint+node+subnet. · **REQ-NOTIF-2**

+- **Reqs:** REQ-NOTIF-1, REQ-NOTIF-2. · **Acceptance:** a notif fires to the active endpoint,

+  resurfaces at a boundary, dismiss replicates subnet-wide; `spt notify` reaches the user.

+### D9 — Activation sweep + cross-node E2E + closeout

+- **D9a** Dormancy resource budget (red-team **#9**): measure N dormant sessions on real

+  adapters; define adapter warm/cold policy before locking warm default. · **REQ-INST-3, REQ-INST-4**

+- **D9b** Two-host cross-node E2E on the real rig (gravity-linux ↔ a Windows host): pair →

+  register → cross-node message → remote-drive → Psyche sync → update self-heal.

+- **D9c** REQ-INST-14 (resource advertisement) + REQ-INST-15 (immutable home subnet + `spt

+  fork`, ADR-0010) — register/activate if in V1-mid line; else document as forward seam.

+- **D9d** Confirm every M4 req `required_stages` is satisfied; `traceable-reqs check` green;

+  amend ROADMAP (M4 delivered) + CONTEXT glossary; CI matrix green (ubuntu+windows+traceability).

+- **Acceptance:** the V1-mid scope line met; `check` green; CI green; cross-node proof demoed.

+| D1 | REQ-NET-1 (impl/unit), REQ-NET-2 |

+| D2 | REQ-PAIR-1..6 + 3 new `REQ-HAZARD-PAIR-*` |

+| D3 | REQ-INST-7,9,10,11,12,13 + `REQ-HAZARD-REGISTRY-EPOCH-LEASE` |

+| D5 | REQ-INST-8, REQ-REACH-1, REQ-SEC-1, REQ-NET-1 (WAN msg) |

+| D6 | REQ-NET-3, REQ-INST-2, REQ-INST-5, `REQ-HAZARD-DIRECT-WRITE-PRECEDENCE` (extend) |

+| D9 | REQ-INST-3,4 (+14,15 if in line); sweep |

+**Deferred to M5 (out of M4, per Scope §Out):** REQ-EP-5 (shell instantiation), REQ-REACH-2

+(remote command exec), PresenceChannel full impl, cross-user generalization of every per-subnet

+key. Their seams are shaped in D4/D5/D8 but not built.

+## Red-team obligations (binding — from the Stage-A review, must close in M4)

+| #6 one-daemon failure domain | 0002 | D4 | fault-injection acceptance matrix |

+| #7 wall-clock loses concurrent writes | 0003 | D6 | epoch/vector-clock merge, conflict surfaced |

+| #8 ambiguous resolution under skew | 0003 | D3 | monotonic per-node epoch lease + chaos test |

+| #9 dormant-warm resource cost | 0003 | D9 | measured budget + warm/cold policy |

+| #10 trust-delete ≠ revocation | 0005 | D2c | seed epoch + rotation |

+| #11 distributed PAKE guessing | 0005 | D2d | subnet-global rate limit, one ceremony |

+| #12 transcript must bind | 0005 | D2b | full transcript spec + negative tests |

Add `crates/spt-net` (the remaining sibling). Final layering (R-ARCH-1, acyclic):


NOTE FROM THEUSER: proceed with nextsteps.


Plan expanded. Clearing the STUB markers at the top.


Edit

# M4 Plan — networking + instances (STUB)

> **Just-in-time, lightweight** — same pattern as `M0/M1/M2a/M2b/M3a/M3b/M3c-PLAN.md`.
> This is a **stub**: the ordered task layer is authored in full when M4 starts.
> Branch: `dev-freeform`. Authoritative architecture: **ADR-0002** (networking baked

# M4 Plan — networking + instances

> **Just-in-time, lightweight** — same pattern as `M0/M1/M2a/M2b/M3a/M3b/M3c-PLAN.md`.
> Task layer authored in full 2026-06-03 at M4 start (see §Tasks).
> Branch: `dev-freeform`. Authoritative architecture: **ADR-0002** (networking baked


# M4 Plan — networking + instances (STUB)

> **Just-in-time, lightweight** — same pattern as `M0/M1/M2a/M2b/M3a/M3b/M3c-PLAN.md`.
> This is a **stub**: the ordered task layer is authored in full when M4 starts.
> Branch: `dev-freeform`. Authoritative architecture: **ADR-0002** (networking baked

# M4 Plan — networking + instances

> **Just-in-time, lightweight** — same pattern as `M0/M1/M2a/M2b/M3a/M3b/M3c-PLAN.md`.
> Task layer authored in full 2026-06-03 at M4 start (see §Tasks).
> Branch: `dev-freeform`. Authoritative architecture: **ADR-0002** (networking baked

-# M4 Plan — networking + instances (STUB)

-> This is a **stub**: the ordered task layer is authored in full when M4 starts.

+> Task layer authored in full 2026-06-03 at M4 start (see §Tasks).

> into core), **ADR-0003** (multi-instance node-anchored identity), **ADR-0005**

> (pairing), **ADR-0006/0007** (subnet membership + notification primitive), and


Task layer done. Marking complete, starting D0 scaffold. Reading spt-term lib.rs for header/lint style + checking spt-msg Cargo for the layer-three pattern.


Updated task #1 status


Updated task #2 status


1	//! spt-core session-surface mechanism — the broker's PTY half (M3a).
2	//!
3	//! `spt-term` is the **multiplatform session-surface mechanism** that every hosted
4	//! agent session runs inside: a real child under a native PTY — ConPTY on Windows
5	//! 10+, `forkpty(3)` on Unix — behind one [`SessionSurface`] trait, with input
6	//! injection, byte-stream output, and the live activity buffer (PTY digest) parser
7	//! primitive layered on top. It is the sixth crate in the acyclic stack
8	//! (`spt-proto -> spt-store -> spt-msg -> spt-term`), a sibling of `spt-runtime`.
9	//!
10	//! M3a delivers the *library mechanism*; the **M3b** `spt-daemon` broker delivers
11	//! the *process* that hosts many of these behind versioned IPC. `spt-term` is
12	//! deliberately **single-surface-per-handle** — it owns one child's PTY, not the
13	//! multiplexed name-addressable fleet (that supervisor is M3b). See `M3a-PLAN.md`.
14	//!
15	//! ## OS stream-contract neutrality (Spike #4 / #5, binding)
16	//!
17	//! The single biggest design risk this crate carries: `forkpty` is a **raw ordered
18	//! pipe**; ConPTY is a **screen buffer** (repaint-on-resize reorders + duplicates
19	//! the stream; a mid-session attach sees the viewport, not scrollback). The
20	//! [`SessionSurface`] trait and the byte-stream / digest contracts therefore must
21	//! **not** bake in either OS's semantics — the surface is modeled as a byte stream
22	//! with explicit, OS-independent attach/replay semantics.
23	//
24	// spt-term is internal broker machinery, NOT part of the public semver SDK
25	// surface (R-ARCH-2 stays spt-proto/spt-store/spt-msg/spt-runtime). The M3b
26	// daemon programs against it; external harness authors do not.
27
28	// Surfaces land across M3a (see M3a-PLAN.md task table):
29	//   A1  SessionSurface trait + ConPTY/forkpty backends     (REQ-TERM-1)       ← landed
30	//   A2  ConPTY-DSR auto-answer drain thread                 (REQ-HAZARD-CONPTY-DSR) ← landed
31	//   A3  send-keys / send-line injection                     (REQ-TERM-2)
32	//   A4  byte-stream local streaming (bounded backpressure)  (REQ-TERM-3)       ← landed
33	//   A5  live activity buffer (PTY digest) parser primitive  (REQ-TERM-4)       ← landed
34
35	pub mod digest;
36	pub mod pty;
37	pub mod reader;
38	pub mod stream;
39	pub mod surface;
40

//! spt-core session-surface mechanism — the broker's PTY half (M3a).
//!
//! `spt-term` is the **multiplatform session-surface mechanism** that every hosted
//! agent session runs inside: a real child under a native PTY — ConPTY on Windows
//! 10+, `forkpty(3)` on Unix — behind one [`SessionSurface`] trait, with input
//! injection, byte-stream output, and the live activity buffer (PTY digest) parser
//! primitive layered on top. It is the sixth crate in the acyclic stack
//! (`spt-proto -> spt-store -> spt-msg -> spt-term`), a sibling of `spt-runtime`.
//!
//! M3a delivers the *library mechanism*; the **M3b** `spt-daemon` broker delivers
//! the *process* that hosts many of these behind versioned IPC. `spt-term` is
//! deliberately **single-surface-per-handle** — it owns one child's PTY, not the
//! multiplexed name-addressable fleet (that supervisor is M3b). See `M3a-PLAN.md`.
//!
//! ## OS stream-contract neutrality (Spike #4 / #5, binding)
//!
//! The single biggest design risk this crate carries: `forkpty` is a **raw ordered
//! pipe**; ConPTY is a **screen buffer** (repaint-on-resize reorders + duplicates
//! the stream; a mid-session attach sees the viewport, not scrollback). The
//! [`SessionSurface`] trait and the byte-stream / digest contracts therefore must
//! **not** bake in either OS's semantics — the surface is modeled as a byte stream
//! with explicit, OS-independent attach/replay semantics.
//
// spt-term is internal broker machinery, NOT part of the public semver SDK
// surface (R-ARCH-2 stays spt-proto/spt-store/spt-msg/spt-runtime). The M3b
// daemon programs against it; external harness authors do not.

// Surfaces land across M3a (see M3a-PLAN.md task table):
//   A1  SessionSurface trait + ConPTY/forkpty backends     (REQ-TERM-1)       ← landed
//   A2  ConPTY-DSR auto-answer drain thread                 (REQ-HAZARD-CONPTY-DSR) ← landed
//   A3  send-keys / send-line injection                     (REQ-TERM-2)
//   A4  byte-stream local streaming (bounded backpressure)  (REQ-TERM-3)       ← landed
//   A5  live activity buffer (PTY digest) parser primitive  (REQ-TERM-4)       ← landed

pub mod digest;
pub mod pty;
pub mod reader;
pub mod stream;
pub mod surface;


Style clear. For D0 I'll keep all M4 reqs at `required_stages = []` — the scaffold satisfies no requirement yet (rule 5: activate when delivering, D1+). Writing the crate.


Write

[package]
name = "spt-net"
version = "0.0.0"
edition = "2021"
publish = false
description = "spt-core P2P networking (M4): the WAN/LAN transport every cross-machine capability rides — an Iroh endpoint (WAN, n0-relay default + self-host knob), mDNS LAN discovery, TOTP-seeded SPAKE2 pairing (ADR-0005), the eventually-consistent per-subnet registry (ADR-0006), cross-node Psyche sync, and the update peer-propagation transport into the M3c engine seam. The remaining sibling above spt-msg (`spt-proto -> spt-store -> spt-msg -> {spt-net, spt-term, spt-runtime}`). Internal broker machinery, NOT public SDK (R-ARCH-2). Whole transport gated behind the default-on `net` feature (ADR-0002) so minimal library consumers can build it out."

[features]
# ADR-0002: WAN transport stack is default-on in the reference binary, optional for
# embedded library consumers. D0 ships the flag + an empty gated module tree; D1
# wires iroh/mdns deps behind it.
default = ["net"]
net = []

[dependencies]
spt-proto = { path = "../spt-proto" }
spt-store = { path = "../spt-store" }
spt-msg = { path = "../spt-msg" }
# Wire/registry/pairing-transcript types cross the network + the broker IPC, so they
# are serde-serializable (lands with the D1+ types; declared now for the gated tree).
serde = { version = "1", features = ["derive"] }

[dev-dependencies]
tempfile = "3"


File created successfully at: C:\Users\decid\Documents\projects\spt-core\crates\spt-net\Cargo.toml (file state is current in your context — no need to Read it back)

[package]
name = "spt-net"
version = "0.0.0"
edition = "2021"
publish = false
description = "spt-core P2P networking (M4): the WAN/LAN transport every cross-machine capability rides — an Iroh endpoint (WAN, n0-relay default + self-host knob), mDNS LAN discovery, TOTP-seeded SPAKE2 pairing (ADR-0005), the eventually-consistent per-subnet registry (ADR-0006), cross-node Psyche sync, and the update peer-propagation transport into the M3c engine seam. The remaining sibling above spt-msg (`spt-proto -> spt-store -> spt-msg -> {spt-net, spt-term, spt-runtime}`). Internal broker machinery, NOT public SDK (R-ARCH-2). Whole transport gated behind the default-on `net` feature (ADR-0002) so minimal library consumers can build it out."

[features]
# ADR-0002: WAN transport stack is default-on in the reference binary, optional for
# embedded library consumers. D0 ships the flag + an empty gated module tree; D1
# wires iroh/mdns deps behind it.
default = ["net"]
net = []

[dependencies]
spt-proto = { path = "../spt-proto" }
spt-store = { path = "../spt-store" }
spt-msg = { path = "../spt-msg" }
# Wire/registry/pairing-transcript types cross the network + the broker IPC, so they
# are serde-serializable (lands with the D1+ types; declared now for the gated tree).
serde = { version = "1", features = ["derive"] }

[dev-dependencies]
tempfile = "3"


//! spt-core P2P networking — the cross-machine transport (M4).
//!
//! `spt-net` is the remaining sibling in the acyclic stack
//! (`spt-proto -> spt-store -> spt-msg -> {spt-net, spt-term, spt-runtime}`): the
//! WAN/LAN transport every cross-machine capability rides. It owns the Iroh
//! endpoint (WAN, with the n0-relay default + a self-host knob — ADR-0002), mDNS
//! LAN discovery, TOTP-seeded SPAKE2 pairing (ADR-0005), the eventually-consistent
//! per-subnet registry (ADR-0006), the subnet notification spool (ADR-0007),
//! cross-node Psyche sync (ADR-0003 + KNOWN-HAZARDS 6.5), and the update
//! peer-propagation transport plugged into the M3c update-engine seam (ADR-0004 §D).
//!
//! ## Why baked into core (ADR-0002)
//!
//! The sister project put networking in a separate `spt-node` daemon to decouple it
//! from a Claude-Code-tied binary. spt-core is already harness-independent, so that
//! reason is gone and WAN messaging becomes a **first-class, zero-config v1
//! capability**. The whole stack sits behind the default-on [`net`](#features)
//! feature: on in the reference `spt` binary, optional for embedded library
//! consumers, so a `--no-default-features` lib build stays Iroh-free.
//!
//! ## Not public SDK
//!
//! `spt-net` is internal broker machinery — the M3b/M4 `spt-daemon` programs against
//! it; external harness authors do not (R-ARCH-2 keeps the public surface at
//! `spt-proto`/`spt-store`/`spt-msg`/`spt-runtime`). The broker (not this crate)
//! *owns* the live Iroh endpoint + QUIC streams so they survive a brain restart
//! (ADR-0004 §B); `spt-net` provides the mechanism, the daemon owns the process.
//!
//! ## Build-out (see `M4-PLAN.md` task table)
//!
//!   D0  scaffold + `net` feature flag                         ← this commit
//!   D1  Iroh endpoint + n0 relay/self-host + mDNS discovery    (REQ-NET-1, REQ-NET-2)
//!   D2  TOTP-SPAKE2 pairing + transcript/seed-epoch/rate-limit (REQ-PAIR-1..6)
//!   D3  per-subnet registry + epoch-lease resolution           (REQ-INST-7,9..13)
//!   D4  broker-owned QUIC endpoint (ADR-0004 §B)               (REQ-EP-4 seam)
//!   D5  cross-node message + remote-drive + endpoint ACL       (REQ-INST-8, REQ-REACH-1, REQ-SEC-1)
//!   D6  cross-node Psyche sync (P2P), node-id precedence       (REQ-NET-3, REQ-INST-2/5)
//!   D7  update peer-propagation into the M3c seam              (REQ-UPD-1)
//!   D8  subnet notification primitive + `spt notify`           (REQ-NOTIF-1, REQ-NOTIF-2)
//
// The transport itself is clean-room (the sister had no P2P); only stable
// wire/registry *formats* that survive are copy-verbatim per ADR-0001. Reuses the
// Spike #2/#3 Iroh learnings (iroh 0.98 API; the loopback QUIC-survival shape).

// ── net-gated transport tree (empty at D0; populated D1+) ────────────────────────
#[cfg(feature = "net")]
pub mod net;


File created successfully at: C:\Users\decid\Documents\projects\spt-core\crates\spt-net\src\lib.rs (file state is current in your context — no need to Read it back)

//! spt-core P2P networking — the cross-machine transport (M4).
//!
//! `spt-net` is the remaining sibling in the acyclic stack
//! (`spt-proto -> spt-store -> spt-msg -> {spt-net, spt-term, spt-runtime}`): the
//! WAN/LAN transport every cross-machine capability rides. It owns the Iroh
//! endpoint (WAN, with the n0-relay default + a self-host knob — ADR-0002), mDNS
//! LAN discovery, TOTP-seeded SPAKE2 pairing (ADR-0005), the eventually-consistent
//! per-subnet registry (ADR-0006), the subnet notification spool (ADR-0007),
//! cross-node Psyche sync (ADR-0003 + KNOWN-HAZARDS 6.5), and the update
//! peer-propagation transport plugged into the M3c update-engine seam (ADR-0004 §D).
//!
//! ## Why baked into core (ADR-0002)
//!
//! The sister project put networking in a separate `spt-node` daemon to decouple it
//! from a Claude-Code-tied binary. spt-core is already harness-independent, so that
//! reason is gone and WAN messaging becomes a **first-class, zero-config v1
//! capability**. The whole stack sits behind the default-on [`net`](#features)
//! feature: on in the reference `spt` binary, optional for embedded library
//! consumers, so a `--no-default-features` lib build stays Iroh-free.
//!
//! ## Not public SDK
//!
//! `spt-net` is internal broker machinery — the M3b/M4 `spt-daemon` programs against
//! it; external harness authors do not (R-ARCH-2 keeps the public surface at
//! `spt-proto`/`spt-store`/`spt-msg`/`spt-runtime`). The broker (not this crate)
//! *owns* the live Iroh endpoint + QUIC streams so they survive a brain restart
//! (ADR-0004 §B); `spt-net` provides the mechanism, the daemon owns the process.
//!
//! ## Build-out (see `M4-PLAN.md` task table)
//!
//!   D0  scaffold + `net` feature flag                         ← this commit
//!   D1  Iroh endpoint + n0 relay/self-host + mDNS discovery    (REQ-NET-1, REQ-NET-2)
//!   D2  TOTP-SPAKE2 pairing + transcript/seed-epoch/rate-limit (REQ-PAIR-1..6)
//!   D3  per-subnet registry + epoch-lease resolution           (REQ-INST-7,9..13)
//!   D4  broker-owned QUIC endpoint (ADR-0004 §B)               (REQ-EP-4 seam)
//!   D5  cross-node message + remote-drive + endpoint ACL       (REQ-INST-8, REQ-REACH-1, REQ-SEC-1)
//!   D6  cross-node Psyche sync (P2P), node-id precedence       (REQ-NET-3, REQ-INST-2/5)
//!   D7  update peer-propagation into the M3c seam              (REQ-UPD-1)
//!   D8  subnet notification primitive + `spt notify`           (REQ-NOTIF-1, REQ-NOTIF-2)
//
// The transport itself is clean-room (the sister had no P2P); only stable
// wire/registry *formats* that survive are copy-verbatim per ADR-0001. Reuses the
// Spike #2/#3 Iroh learnings (iroh 0.98 API; the loopback QUIC-survival shape).

// ── net-gated transport tree (empty at D0; populated D1+) ────────────────────────
#[cfg(feature = "net")]
pub mod net;


//! The `net`-gated transport tree.
//!
//! Empty at D0 — the scaffold proves the crate compiles, layers acyclically above
//! `spt-msg`, and that the `net` feature gates the whole stack (a
//! `--no-default-features` lib build excludes this module entirely). The Iroh
//! endpoint, mDNS discovery, pairing, registry, sync, and update-propagation
//! submodules land behind this gate from D1 onward (see `M4-PLAN.md` / `lib.rs`).


File created successfully at: C:\Users\decid\Documents\projects\spt-core\crates\spt-net\src\net.rs (file state is current in your context — no need to Read it back)

//! The `net`-gated transport tree.
//!
//! Empty at D0 — the scaffold proves the crate compiles, layers acyclically above
//! `spt-msg`, and that the `net` feature gates the whole stack (a
//! `--no-default-features` lib build excludes this module entirely). The Iroh
//! endpoint, mDNS discovery, pairing, registry, sync, and update-propagation
//! submodules land behind this gate from D1 onward (see `M4-PLAN.md` / `lib.rs`).


Now wire into workspace + update the header comment.


Edit

# M3b adds spt-daemon (broker/brain supervisor) — seventh layer, above spt-live, below spt; internal (not public SDK).
[workspace]
resolver = "2"
members = ["crates/spt-proto", "crates/spt-store", "crates/spt-msg", "crates/spt-term", "crates/spt-runtime", "crates/spt-live", "crates/spt-daemon", "crates/spt", "adapters/mock"]

# M3b adds spt-daemon (broker/brain supervisor) — seventh layer, above spt-live, below spt; internal (not public SDK).
# M4 adds spt-net (P2P WAN/LAN transport) — sibling of spt-term/spt-runtime, above spt-msg; internal, default-on `net` feature.
[workspace]
resolver = "2"
members = ["crates/spt-proto", "crates/spt-store", "crates/spt-msg", "crates/spt-net", "crates/spt-term", "crates/spt-runtime", "crates/spt-live", "crates/spt-daemon", "crates/spt", "adapters/mock"]


<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>

Error: File has not been read yet. Read it first before writing to it.


1	# spt-core cargo workspace (M0). Acyclically-layered crates per R-ARCH-1:
2	#   spt-proto -> spt-store -> spt-msg -> {spt-net, spt-term, spt-runtime} -> spt-live -> spt-daemon -> spt
3	# M0 builds the bottom two: spt-proto (wire) + spt-store (persistence).
4	# M1 adds spt-msg (local messaging) — third layer, one-way above spt-store.
5	# M2a adds spt-runtime (harness contract: manifest schema + AgentRuntime) — fourth layer.
6	# M2b adds spt-live (live-agent lifecycle executor) — fifth layer, daemon-bound (not public SDK).
7	# M3a adds spt-term (session-surface mechanism: ConPTY/forkpty PTY layer) — sibling of spt-runtime, above spt-msg.
8	# M3b adds spt-daemon (broker/brain supervisor) — seventh layer, above spt-live, below spt; internal (not public SDK).
9	[workspace]
10	resolver = "2"
11	members = ["crates/spt-proto", "crates/spt-store", "crates/spt-msg", "crates/spt-term", "crates/spt-runtime", "crates/spt-live", "crates/spt-daemon", "crates/spt", "adapters/mock"]
12

# spt-core cargo workspace (M0). Acyclically-layered crates per R-ARCH-1:
#   spt-proto -> spt-store -> spt-msg -> {spt-net, spt-term, spt-runtime} -> spt-live -> spt-daemon -> spt
# M0 builds the bottom two: spt-proto (wire) + spt-store (persistence).
# M1 adds spt-msg (local messaging) — third layer, one-way above spt-store.
# M2a adds spt-runtime (harness contract: manifest schema + AgentRuntime) — fourth layer.
# M2b adds spt-live (live-agent lifecycle executor) — fifth layer, daemon-bound (not public SDK).
# M3a adds spt-term (session-surface mechanism: ConPTY/forkpty PTY layer) — sibling of spt-runtime, above spt-msg.
# M3b adds spt-daemon (broker/brain supervisor) — seventh layer, above spt-live, below spt; internal (not public SDK).
[workspace]
resolver = "2"
members = ["crates/spt-proto", "crates/spt-store", "crates/spt-msg", "crates/spt-term", "crates/spt-runtime", "crates/spt-live", "crates/spt-daemon", "crates/spt", "adapters/mock"]


# M3b adds spt-daemon (broker/brain supervisor) — seventh layer, above spt-live, below spt; internal (not public SDK).
[workspace]
resolver = "2"
members = ["crates/spt-proto", "crates/spt-store", "crates/spt-msg", "crates/spt-term", "crates/spt-runtime", "crates/spt-live", "crates/spt-daemon", "crates/spt", "adapters/mock"]

# M3b adds spt-daemon (broker/brain supervisor) — seventh layer, above spt-live, below spt; internal (not public SDK).
# M4 adds spt-net (P2P WAN/LAN transport) — sibling of spt-term/spt-runtime, above spt-msg; internal, default-on `net` feature.
[workspace]
resolver = "2"
members = ["crates/spt-proto", "crates/spt-store", "crates/spt-msg", "crates/spt-net", "crates/spt-term", "crates/spt-runtime", "crates/spt-live", "crates/spt-daemon", "crates/spt", "adapters/mock"]


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

# M3b adds spt-daemon (broker/brain supervisor) — seventh layer, above spt-live, below spt; internal (not public SDK).
# M4 adds spt-net (P2P WAN/LAN transport) — sibling of spt-term/spt-runtime, above spt-msg; internal, default-on `net` feature.
[workspace]
resolver = "2"
members = ["crates/spt-proto", "crates/spt-store", "crates/spt-msg", "crates/spt-net", "crates/spt-term", "crates/spt-runtime", "crates/spt-live", "crates/spt-daemon", "crates/spt", "adapters/mock"]

# spt-core cargo workspace (M0). Acyclically-layered crates per R-ARCH-1:
#   spt-proto -> spt-store -> spt-msg -> {spt-net, spt-term, spt-runtime} -> spt-live -> spt-daemon -> spt
# M0 builds the bottom two: spt-proto (wire) + spt-store (persistence).
# M1 adds spt-msg (local messaging) — third layer, one-way above spt-store.
# M2a adds spt-runtime (harness contract: manifest schema + AgentRuntime) — fourth layer.
# M2b adds spt-live (live-agent lifecycle executor) — fifth layer, daemon-bound (not public SDK).
# M3a adds spt-term (session-surface mechanism: ConPTY/forkpty PTY layer) — sibling of spt-runtime, above spt-msg.
# M3b adds spt-daemon (broker/brain supervisor) — seventh layer, above spt-live, below spt; internal (not public SDK).
[workspace]
resolver = "2"
members = ["crates/spt-proto", "crates/spt-store", "crates/spt-msg", "crates/spt-term", "crates/spt-runtime", "crates/spt-live", "crates/spt-daemon", "crates/spt", "adapters/mock"]

+# M4 adds spt-net (P2P WAN/LAN transport) — sibling of spt-term/spt-runtime, above spt-msg; internal, default-on `net` feature.

-members = ["crates/spt-proto", "crates/spt-store", "crates/spt-msg", "crates/spt-term", "crates/spt-runtime", "crates/spt-live", "crates/spt-daemon", "crates/spt", "adapters/mock"]

+members = ["crates/spt-proto", "crates/spt-store", "crates/spt-msg", "crates/spt-net", "crates/spt-term", "crates/spt-runtime", "crates/spt-live", "crates/spt-daemon", "crates/spt", "adapters/mock"]


Now build + verify: cargo check (default + no-default-features) and traceable-reqs check.


Bash

cargo check -p spt-net 2>&1 | tail -20; echo "===NO-DEFAULT==="; cargo check -p spt-net --no-default-features 2>&1 | tail -8

cargo check spt-net default + no-default-features


Command running in background with ID: bu8h947kg. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bu8h947kg.output. You will be notified when it completes. To check interim output, use Read on that file path.


Expand M4-PLAN.md D-task layer

Replace the stub Tasks table with a full layered D0-D8 breakdown: sub-tasks, requirement mapping (NET/PAIR/INST/NOTIF/SEC/UPD), red-team obligations (pairing #10/#11/#12, instances #7/#8/#9), acceptance gates, sequencing.

D0: scaffold crates/spt-net

New crate sibling of spt-term/spt-runtime, above spt-msg. Wire into workspace members, net feature flag, acyclic layering, compiles, traceable-reqs check green. Activate REQ-NET-1 doc stage as scaffold lands.


<task-notification>
<task-id>bu8h947kg</task-id>
<tool-use-id>toolu_0155MJaNMp9BHxG6GGGjNzqV</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bu8h947kg.output</output-file>
<status>completed</status>
<summary>Background command "cargo check spt-net default + no-default-features" completed (exit code 0)</summary>
</task-notification>


1	    Checking spt-store v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-store)
2	    Checking spt-msg v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-msg)
3	    Checking spt-net v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
4	    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
5	===NO-DEFAULT===
6	    Checking spt-net v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
7	    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
8

    Checking spt-store v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-store)
    Checking spt-msg v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-msg)
    Checking spt-net v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
===NO-DEFAULT===
    Checking spt-net v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s


Prompt:
<task-notification>
<task-id>bu8h947kg</task-id>
<tool-use-id>toolu_0155MJaNMp9BHxG6GGGjNzqV</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bu8h947kg.output</output-file>
<status>completed</status>
<summary>Background command "cargo check spt-net default + no-default-features" completed (exit code 0)</summary>
</task-notification>


Both compile (default + no-default-features), layering acyclic. Running full workspace check + traceable-reqs gate.


cargo check --workspace 2>&1 | tail -6; echo "===TRACE==="; traceable-reqs check 2>&1 | tail -15

Full workspace check + traceable-reqs gate


Command running in background with ID: bm00ecz8z. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bm00ecz8z.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>bm00ecz8z</task-id>
<tool-use-id>toolu_01FKzCLAGnhcMajPcXE1bDgc</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bm00ecz8z.output</output-file>
<status>completed</status>
<summary>Background command "Full workspace check + traceable-reqs gate" completed (exit code 0)</summary>
</task-notification>


1	    Checking spt-term v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-term)
2	    Checking spt-net v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
3	    Checking spt-live v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-live)
4	    Checking spt-daemon v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
5	    Checking spt v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
6	    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.09s
7	===TRACE===
8	       session-surface abstraction; send-keys + send-line injection
9	  [OK] REQ-TERM-3  required: [impl, unit]  stages: -doc +impl +unit -int
10	       Byte-stream remote terminal streaming for v1
11	  [OK] REQ-TERM-4  required: [impl, unit, int]  stages: +doc +impl +unit +int
12	       Live activity buffer (PTY digest): adapter-supplied patterns over broker PTY, spt digest pull + delta-stream, opt-in Path-B log
13	  [OK] REQ-UPD-1  required: []  stages: -doc -impl -unit -int
14	       Peer-propagated update over P2P
15	  [OK] REQ-UPD-2  required: [impl, unit]  stages: -doc +impl +unit -int
16	       All binaries signature-verified before handoff
17	  [OK] REQ-UPD-3  required: [impl, unit, int]  stages: -doc +impl +unit +int
18	       No endpoint process terminates/suspends during self-update
19	  [OK] REQ-UPD-4  required: [impl, unit]  stages: -doc +impl +unit -int
20	       Update gated on user confirmation by default; opt-in full-auto
21	  [OK] REQ-UPD-5  required: [impl, unit]  stages: -doc +impl +unit -int
22	       spt-core ripple-updates registered adapters
23

    Checking spt-term v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-term)
    Checking spt-net v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
    Checking spt-live v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-live)
    Checking spt-daemon v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Checking spt v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.09s
===TRACE===
       session-surface abstraction; send-keys + send-line injection
  [OK] REQ-TERM-3  required: [impl, unit]  stages: -doc +impl +unit -int
       Byte-stream remote terminal streaming for v1
  [OK] REQ-TERM-4  required: [impl, unit, int]  stages: +doc +impl +unit +int
       Live activity buffer (PTY digest): adapter-supplied patterns over broker PTY, spt digest pull + delta-stream, opt-in Path-B log
  [OK] REQ-UPD-1  required: []  stages: -doc -impl -unit -int
       Peer-propagated update over P2P
  [OK] REQ-UPD-2  required: [impl, unit]  stages: -doc +impl +unit -int
       All binaries signature-verified before handoff
  [OK] REQ-UPD-3  required: [impl, unit, int]  stages: -doc +impl +unit +int
       No endpoint process terminates/suspends during self-update
  [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


Prompt:
<task-notification>
<task-id>bm00ecz8z</task-id>
<tool-use-id>toolu_01FKzCLAGnhcMajPcXE1bDgc</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bm00ecz8z.output</output-file>
<status>completed</status>
<summary>Background command "Full workspace check + traceable-reqs gate" completed (exit code 0)</summary>
</task-notification>


<task-notification>
<task-id>bvrj02c5x</task-id>
<tool-use-id>toolu_01XA6RUK4tmK4SvVzfds6QNb</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bvrj02c5x.output</output-file>
<status>completed</status>
<summary>Background command "Confirm traceable-reqs exit code" completed (exit code 0)</summary>
</task-notification>


Workspace green. Confirming traceable-reqs exit code (pipeline masked it).


traceable-reqs check > $null 2>&1; echo "trace_exit=$LASTEXITCODE"

Confirm traceable-reqs exit code


Command running in background with ID: bvrj02c5x. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bvrj02c5x.output. You will be notified when it completes. To check interim output, use Read on that file path.


Prompt:
<task-notification>
<task-id>bvrj02c5x</task-id>
<tool-use-id>toolu_01XA6RUK4tmK4SvVzfds6QNb</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bvrj02c5x.output</output-file>
<status>completed</status>
<summary>Background command "Confirm traceable-reqs exit code" completed (exit code 0)</summary>
</task-notification>


1	/usr/bin/bash: line 4: $null: ambiguous redirect
2	trace_exit=
3

/usr/bin/bash: line 4: $null: ambiguous redirect
trace_exit=


Bash tool, not PowerShell. Re-running correctly.


traceable-reqs check >/dev/null 2>&1; echo "trace_exit=$?"

Confirm traceable-reqs exit code


Command running in background with ID: b9ds90ttk. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\b9ds90ttk.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>b9ds90ttk</task-id>
<tool-use-id>toolu_0155jxnVF9ZpWmXqP93b92ff</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\b9ds90ttk.output</output-file>
<status>completed</status>
<summary>Background command "Confirm traceable-reqs exit code" completed (exit code 0)</summary>
</task-notification>


1	trace_exit=0
2


Prompt:
<task-notification>
<task-id>b9ds90ttk</task-id>
<tool-use-id>toolu_0155jxnVF9ZpWmXqP93b92ff</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\b9ds90ttk.output</output-file>
<status>completed</status>
<summary>Background command "Confirm traceable-reqs exit code" completed (exit code 0)</summary>
</task-notification>


Expand M4-PLAN.md D-task layer

D0: scaffold crates/spt-net


All D0 acceptance gates green: compiles (both feature configs), layering acyclic, `traceable-reqs check` exit 0. Committing atomically per project rhythm. Getting the exact Co-Authored-By trailer first.


git log -1 --format='%(trailers:key=Co-authored-by)' 2>/dev/null; echo "---branch---"; git branch --show-current 2>/dev/null; echo "---status---"; git status --short

Get Co-Authored-By trailer, branch, status


Command running in background with ID: b763krt2l. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\b763krt2l.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>b763krt2l</task-id>
<tool-use-id>toolu_01TqJTHHS91kzCxH4Ax4nYY8</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\b763krt2l.output</output-file>
<status>completed</status>
<summary>Background command "Get Co-Authored-By trailer, branch, status" completed (exit code 0)</summary>
</task-notification>


1	Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2
3	---branch---
4	dev-freeform
5	---status---
6	 M Cargo.lock
7	 M Cargo.toml
8	 M M4-PLAN.md
9	?? crates/spt-net/
10

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

---branch---
dev-freeform
---status---
 M Cargo.lock
 M Cargo.toml
 M M4-PLAN.md
?? crates/spt-net/


Prompt:
<task-notification>
<task-id>b763krt2l</task-id>
<tool-use-id>toolu_01TqJTHHS91kzCxH4Ax4nYY8</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\b763krt2l.output</output-file>
<status>completed</status>
<summary>Background command "Get Co-Authored-By trailer, branch, status" completed (exit code 0)</summary>
</task-notification>


Trailer + branch confirmed. Two atomic commits: plan expansion (docs), then D0 scaffold (feat).


cd "C:/Users/decid/Documents/projects/spt-core" && git add M4-PLAN.md && git commit -q -F - <<'EOF'
docs(m4): expand M4-PLAN D-task layer at M4 start

Replace the stub Tasks table with the full executable breakdown (D0-D9)
authored against ADR-0002/0003/0005/0006/0007/0009 + ADR-0004 B/D and
KNOWN-HAZARDS 6.5. Each D-task gets sub-tasks, requirement mapping, and
acceptance gates; adds a requirement->task coverage map and a binding
red-team obligations table (pairing #10/#11/#12, instances #7/#8/#9,
daemon-failure-domain #6) tying each Stage-A finding to the task that
closes it. Folds in the multi-subnet dimension (REQ-INST-9..13, PAIR-5/6,
NOTIF, SEC-1) the ADRs activate at M4. Clears the STUB markers.

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

Commit M4-PLAN expansion


Command running in background with ID: b2c6j2ju4. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\b2c6j2ju4.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>b2c6j2ju4</task-id>
<tool-use-id>toolu_01KySdAig78XSKbzZkmX6xQV</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\b2c6j2ju4.output</output-file>
<status>completed</status>
<summary>Background command "Commit M4-PLAN expansion" completed (exit code 0)</summary>
</task-notification>


1	warning: in the working copy of 'M4-PLAN.md', LF will be replaced by CRLF the next time Git touches it
2	exit1=0
3

warning: in the working copy of 'M4-PLAN.md', LF will be replaced by CRLF the next time Git touches it
exit1=0


Prompt:
<task-notification>
<task-id>b2c6j2ju4</task-id>
<tool-use-id>toolu_01KySdAig78XSKbzZkmX6xQV</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\b2c6j2ju4.output</output-file>
<status>completed</status>
<summary>Background command "Commit M4-PLAN expansion" completed (exit code 0)</summary>
</task-notification>


cd "C:/Users/decid/Documents/projects/spt-core" && git add Cargo.toml Cargo.lock crates/spt-net && git commit -q -F - <<'EOF'
feat(net): M4-D0 scaffold spt-net crate

Add crates/spt-net, the remaining sibling above spt-msg in the acyclic
stack (spt-proto -> spt-store -> spt-msg -> {spt-net, spt-term,
spt-runtime}) and the home of the M4 P2P transport. Wire it into the
workspace members and document the layer in the root manifest.

Whole transport sits behind the default-on `net` feature (ADR-0002): on
in the reference binary, optional for embedded lib consumers. D0 ships
the flag + an empty net-gated module tree so both `cargo check` and
`--no-default-features` compile; the Iroh endpoint / pairing / registry /
sync / update-propagation submodules land behind the gate from D1 on.
Internal broker machinery, NOT public SDK (R-ARCH-2).

No requirement activated yet (rule 5: activate when delivering) -- the
scaffold satisfies no REQ; REQ-NET-1 lands impl/unit at D1. check green,
layering acyclic, traceable-reqs check green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EOF
echo "exit2=$?"; git log --oneline -3

Commit D0 scaffold


Command running in background with ID: bz4aashgx. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bz4aashgx.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>bz4aashgx</task-id>
<tool-use-id>toolu_01JK2cNduN8nvGVTVxbAsvd9</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bz4aashgx.output</output-file>
<status>completed</status>
<summary>Background command "Commit D0 scaffold" completed (exit code 0)</summary>
</task-notification>


1	warning: in the working copy of 'Cargo.lock', LF will be replaced by CRLF the next time Git touches it
2	warning: in the working copy of 'Cargo.toml', LF will be replaced by CRLF the next time Git touches it
3	warning: in the working copy of 'crates/spt-net/Cargo.toml', LF will be replaced by CRLF the next time Git touches it
4	warning: in the working copy of 'crates/spt-net/src/lib.rs', LF will be replaced by CRLF the next time Git touches it
5	warning: in the working copy of 'crates/spt-net/src/net.rs', LF will be replaced by CRLF the next time Git touches it
6	exit2=0
7	c47d2c6 feat(net): M4-D0 scaffold spt-net crate
8	ca6522b docs(m4): expand M4-PLAN D-task layer at M4 start
9	a14639a docs(m3c): M3c-C4 activation sweep + M3 complete + M4 stub
10

warning: in the working copy of 'Cargo.lock', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'Cargo.toml', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-net/Cargo.toml', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-net/src/lib.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-net/src/net.rs', LF will be replaced by CRLF the next time Git touches it
exit2=0
c47d2c6 feat(net): M4-D0 scaffold spt-net crate
ca6522b docs(m4): expand M4-PLAN D-task layer at M4 start
a14639a docs(m3c): M3c-C4 activation sweep + M3 complete + M4 stub


Prompt:
<task-notification>
<task-id>bz4aashgx</task-id>
<tool-use-id>toolu_01JK2cNduN8nvGVTVxbAsvd9</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bz4aashgx.output</output-file>
<status>completed</status>
<summary>Background command "Commit D0 scaffold" completed (exit code 0)</summary>
</task-notification>


Both committed (ca6522b plan, c47d2c6 scaffold). D0 done. Updating task + M4 resume memory.


Updated task #2 status


1	---
2	name: m4-resume-state
3	description: "spt-core milestone state — M3 COMPLETE (2026-06-03), M4 (spt-net) is next but NOT started; await user go."
4	metadata:
5	  node_type: memory
6	  type: project
7	  originSessionId: 3a001700-4a6a-4279-b688-454943c596f6
8	---
9
10	As of 2026-06-03: **M3 is COMPLETE + CI-green.** M3a (spt-term PTY) + M3b
11	(spt-daemon broker/brain) + M3c (signed self-update) all shipped on
12	`dev-freeform`. M3c = commits fe0acce→a14639a, CI run 26901923120 success
13	(ubuntu+windows+traceability). The full ADR-0004 architecture is live.
14
15	**NEXT = M4 (`spt-net`)** — the next milestone, **NOT yet started**. Do NOT
16	auto-start it; on wake, confirm with the user before expanding the D-task layer.
17	Stub authored at `M4-PLAN.md` (D0–D8 sketch). When M4 starts: read M4-PLAN.md +
18	ADR-0002/0003/0005/0006/0007; re-confirm TRACEABILITY rule-3 (pairing/subnet
19	wire formats may surface new REQ-HAZARD-*); activate
20	R-NET-*/R-PAIR-*/REQ-INST-7/REQ-UPD-1 per task. M4 plugs the update
21	peer-propagation transport (REQ-UPD-1) into the M3c engine seam.
22
23	CI gotchas in force: spt_home() writes must create_dir_all the parent; never
24	name a bin/test/staged-update artifact *update*/*setup*/*install*/*patch* on
25	Windows (UAC os error 740) — see [[windows-uac-binary-name-740]].
26
27	Supersedes the older psyche-seed "M3c next" framing. Delete/rewrite when M4 begins.
28

---
name: m4-resume-state
description: "spt-core milestone state — M3 COMPLETE (2026-06-03), M4 (spt-net) is next but NOT started; await user go."
metadata:
  node_type: memory
  type: project
  originSessionId: 3a001700-4a6a-4279-b688-454943c596f6
---

As of 2026-06-03: **M3 is COMPLETE + CI-green.** M3a (spt-term PTY) + M3b
(spt-daemon broker/brain) + M3c (signed self-update) all shipped on
`dev-freeform`. M3c = commits fe0acce→a14639a, CI run 26901923120 success
(ubuntu+windows+traceability). The full ADR-0004 architecture is live.

**NEXT = M4 (`spt-net`)** — the next milestone, **NOT yet started**. Do NOT
auto-start it; on wake, confirm with the user before expanding the D-task layer.
Stub authored at `M4-PLAN.md` (D0–D8 sketch). When M4 starts: read M4-PLAN.md +
ADR-0002/0003/0005/0006/0007; re-confirm TRACEABILITY rule-3 (pairing/subnet
wire formats may surface new REQ-HAZARD-*); activate
R-NET-*/R-PAIR-*/REQ-INST-7/REQ-UPD-1 per task. M4 plugs the update
peer-propagation transport (REQ-UPD-1) into the M3c engine seam.

CI gotchas in force: spt_home() writes must create_dir_all the parent; never
name a bin/test/staged-update artifact *update*/*setup*/*install*/*patch* on
Windows (UAC os error 740) — see [[windows-uac-binary-name-740]].

Supersedes the older psyche-seed "M3c next" framing. Delete/rewrite when M4 begins.


Write

---
name: m4-resume-state
description: "spt-core milestone state — M4 (spt-net) STARTED 2026-06-03; D0 scaffold done, plan expanded, next = D1 Iroh endpoint."
metadata:
  node_type: memory
  type: project
  originSessionId: 3a001700-4a6a-4279-b688-454943c596f6
---

**M3 COMPLETE + CI-green (2026-06-03).** M3a + M3b + M3c shipped on `dev-freeform`
(M3c = fe0acce→a14639a, CI run 26901923120). Full ADR-0004 architecture live.

**M4 (`spt-net`) STARTED 2026-06-03** (user gave explicit go). Done so far on
`dev-freeform`:
- **Plan expanded** (commit ca6522b): `M4-PLAN.md` Tasks now a full D0–D9
  breakdown with sub-tasks, requirement→task map, and a binding red-team
  obligations table (pairing #10 seed-rotation / #11 rate-limit / #12 transcript;
  instances #7 merge-clock / #8 epoch-lease / #9 dormant-budget; #6 failure-domain).
- **D0 scaffold** (commit c47d2c6): `crates/spt-net` created, wired into workspace,
  whole transport behind default-on `net` feature (ADR-0002). check green (default
  + `--no-default-features`), layering acyclic, `traceable-reqs check` green. No REQ
  activated yet (rule 5 — scaffold delivers none; REQ-NET-1 lands at D1).

**NEXT = D1** — Iroh endpoint (reuse spt-proto Ed25519 identity, iroh 0.98 / Spike #2
shape) + n0 relay default + self-host knob + disclosure (REQ-NET-2) + mDNS LAN
discovery. Acceptance: two local nodes discover + connect. Then D2 pairing (register
the 3 new REQ-HAZARD-PAIR-* in traceable-reqs.toml FIRST, rule 3). Each D-task is
milestone-sized — expect /self-clear + commune between most.

Not pushed yet (2 local commits ahead of a14639a). CI gotchas in force: spt_home()
writes must create_dir_all the parent; never name a bin/test/staged-update artifact
*update*/*setup*/*install*/*patch* on Windows (UAC os error 740) — see
[[windows-uac-binary-name-740]]. Cross-machine D9 E2E rig: [[cross-machine-test-rig]].


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

---
name: m4-resume-state
description: "spt-core milestone state — M4 (spt-net) STARTED 2026-06-03; D0 scaffold done, plan expanded, next = D1 Iroh endpoint."
metadata:
  node_type: memory
  type: project
  originSessionId: 3a001700-4a6a-4279-b688-454943c596f6
---

**M3 COMPLETE + CI-green (2026-06-03).** M3a + M3b + M3c shipped on `dev-freeform`
(M3c = fe0acce→a14639a, CI run 26901923120). Full ADR-0004 architecture live.

**M4 (`spt-net`) STARTED 2026-06-03** (user gave explicit go). Done so far on
`dev-freeform`:
- **Plan expanded** (commit ca6522b): `M4-PLAN.md` Tasks now a full D0–D9
  breakdown with sub-tasks, requirement→task map, and a binding red-team
  obligations table (pairing #10 seed-rotation / #11 rate-limit / #12 transcript;
  instances #7 merge-clock / #8 epoch-lease / #9 dormant-budget; #6 failure-domain).
- **D0 scaffold** (commit c47d2c6): `crates/spt-net` created, wired into workspace,
  whole transport behind default-on `net` feature (ADR-0002). check green (default
  + `--no-default-features`), layering acyclic, `traceable-reqs check` green. No REQ
  activated yet (rule 5 — scaffold delivers none; REQ-NET-1 lands at D1).

**NEXT = D1** — Iroh endpoint (reuse spt-proto Ed25519 identity, iroh 0.98 / Spike #2
shape) + n0 relay default + self-host knob + disclosure (REQ-NET-2) + mDNS LAN
discovery. Acceptance: two local nodes discover + connect. Then D2 pairing (register
the 3 new REQ-HAZARD-PAIR-* in traceable-reqs.toml FIRST, rule 3). Each D-task is
milestone-sized — expect /self-clear + commune between most.

Not pushed yet (2 local commits ahead of a14639a). CI gotchas in force: spt_home()
writes must create_dir_all the parent; never name a bin/test/staged-update artifact
*update*/*setup*/*install*/*patch* on Windows (UAC os error 740) — see
[[windows-uac-binary-name-740]]. Cross-machine D9 E2E rig: [[cross-machine-test-rig]].

-description: "spt-core milestone state — M3 COMPLETE (2026-06-03), M4 (spt-net) is next but NOT started; await user go."

+description: "spt-core milestone state — M4 (spt-net) STARTED 2026-06-03; D0 scaffold done, plan expanded, next = D1 Iroh endpoint."

-As of 2026-06-03: **M3 is COMPLETE + CI-green.** M3a (spt-term PTY) + M3b

-(spt-daemon broker/brain) + M3c (signed self-update) all shipped on

-`dev-freeform`. M3c = commits fe0acce→a14639a, CI run 26901923120 success

-(ubuntu+windows+traceability). The full ADR-0004 architecture is live.

+**M3 COMPLETE + CI-green (2026-06-03).** M3a + M3b + M3c shipped on `dev-freeform`

+(M3c = fe0acce→a14639a, CI run 26901923120). Full ADR-0004 architecture live.

-**NEXT = M4 (`spt-net`)** — the next milestone, **NOT yet started**. Do NOT

-auto-start it; on wake, confirm with the user before expanding the D-task layer.

-Stub authored at `M4-PLAN.md` (D0–D8 sketch). When M4 starts: read M4-PLAN.md +

-ADR-0002/0003/0005/0006/0007; re-confirm TRACEABILITY rule-3 (pairing/subnet

-wire formats may surface new REQ-HAZARD-*); activate

-R-NET-*/R-PAIR-*/REQ-INST-7/REQ-UPD-1 per task. M4 plugs the update

-peer-propagation transport (REQ-UPD-1) into the M3c engine seam.

+**M4 (`spt-net`) STARTED 2026-06-03** (user gave explicit go). Done so far on

+- **Plan expanded** (commit ca6522b): `M4-PLAN.md` Tasks now a full D0–D9

+  breakdown with sub-tasks, requirement→task map, and a binding red-team

+  obligations table (pairing #10 seed-rotation / #11 rate-limit / #12 transcript;

+  instances #7 merge-clock / #8 epoch-lease / #9 dormant-budget; #6 failure-domain).

+- **D0 scaffold** (commit c47d2c6): `crates/spt-net` created, wired into workspace,

+  whole transport behind default-on `net` feature (ADR-0002). check green (default

+  + `--no-default-features`), layering acyclic, `traceable-reqs check` green. No REQ

+  activated yet (rule 5 — scaffold delivers none; REQ-NET-1 lands at D1).

-CI gotchas in force: spt_home() writes must create_dir_all the parent; never

-name a bin/test/staged-update artifact *update*/*setup*/*install*/*patch* on

-Windows (UAC os error 740) — see [[windows-uac-binary-name-740]].

+**NEXT = D1** — Iroh endpoint (reuse spt-proto Ed25519 identity, iroh 0.98 / Spike #2

+shape) + n0 relay default + self-host knob + disclosure (REQ-NET-2) + mDNS LAN

+discovery. Acceptance: two local nodes discover + connect. Then D2 pairing (register

+the 3 new REQ-HAZARD-PAIR-* in traceable-reqs.toml FIRST, rule 3). Each D-task is

+milestone-sized — expect /self-clear + commune between most.

-Supersedes the older psyche-seed "M3c next" framing. Delete/rewrite when M4 begins.

+Not pushed yet (2 local commits ahead of a14639a). CI gotchas in force: spt_home()

+writes must create_dir_all the parent; never name a bin/test/staged-update artifact

+*update*/*setup*/*install*/*patch* on Windows (UAC os error 740) — see

+[[windows-uac-binary-name-740]]. Cross-machine D9 E2E rig: [[cross-machine-test-rig]].

---
name: m4-resume-state
description: "spt-core milestone state — M3 COMPLETE (2026-06-03), M4 (spt-net) is next but NOT started; await user go."
metadata:
  node_type: memory
  type: project
  originSessionId: 3a001700-4a6a-4279-b688-454943c596f6
---

As of 2026-06-03: **M3 is COMPLETE + CI-green.** M3a (spt-term PTY) + M3b
(spt-daemon broker/brain) + M3c (signed self-update) all shipped on
`dev-freeform`. M3c = commits fe0acce→a14639a, CI run 26901923120 success
(ubuntu+windows+traceability). The full ADR-0004 architecture is live.

**NEXT = M4 (`spt-net`)** — the next milestone, **NOT yet started**. Do NOT
auto-start it; on wake, confirm with the user before expanding the D-task layer.
Stub authored at `M4-PLAN.md` (D0–D8 sketch). When M4 starts: read M4-PLAN.md +
ADR-0002/0003/0005/0006/0007; re-confirm TRACEABILITY rule-3 (pairing/subnet
wire formats may surface new REQ-HAZARD-*); activate
R-NET-*/R-PAIR-*/REQ-INST-7/REQ-UPD-1 per task. M4 plugs the update
peer-propagation transport (REQ-UPD-1) into the M3c engine seam.

CI gotchas in force: spt_home() writes must create_dir_all the parent; never
name a bin/test/staged-update artifact *update*/*setup*/*install*/*patch* on
Windows (UAC os error 740) — see [[windows-uac-binary-name-740]].

Supersedes the older psyche-seed "M3c next" framing. Delete/rewrite when M4 begins.


Updating the MEMORY.md index hook to match.


Edit

- [M4 resume state](m4-resume-state.md) — M3 COMPLETE + CI-green (2026-06-03); M4 (spt-net) is next but NOT started — confirm with user before starting.

- [M4 resume state](m4-resume-state.md) — M4 (spt-net) STARTED 2026-06-03; plan expanded + D0 scaffold done (commits ca6522b/c47d2c6), next = D1 Iroh endpoint.


<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>


1	# Memory index
2
3	- [Cross-machine test rig](cross-machine-test-rig.md) — enlyzeam (Windows, ssh key) for two-host network spikes + Tailscale-down gotcha; gravity-linux (Ubuntu, david/bigscreen pw-auth + askpass shim) as the Linux/forkpty build host.
4	- [CI traceability gate](ci-traceability-gate.md) — how CI installs traceable-reqs (TRACEABLE_REQS_TOKEN PAT + latest BigscreenVR release); the v0.1.0-stale-binary gotcha + the v0.1.1 fix; I have admin to cut releases.
5	- [Attractor build harness](attractor-build-harness.md) — attractor+pi/codex setup at ../attractor; codex model = openai-codex/gpt-5.5 (NOT the SPEC's gpt-5.3-codex); viable for bounded single-machine spt-core tasks (SPIKE-07b gix pilot).
6	- [Windows UAC binary-name 740](windows-uac-binary-name-740.md) — exe names containing update/setup/install/patch trigger UAC elevation (os error 740); name self-update artifacts + test bins to dodge it.
7	- [M4 resume state](m4-resume-state.md) — M3 COMPLETE + CI-green (2026-06-03); M4 (spt-net) is next but NOT started — confirm with user before starting.
8

# Memory index

- [Cross-machine test rig](cross-machine-test-rig.md) — enlyzeam (Windows, ssh key) for two-host network spikes + Tailscale-down gotcha; gravity-linux (Ubuntu, david/bigscreen pw-auth + askpass shim) as the Linux/forkpty build host.
- [CI traceability gate](ci-traceability-gate.md) — how CI installs traceable-reqs (TRACEABLE_REQS_TOKEN PAT + latest BigscreenVR release); the v0.1.0-stale-binary gotcha + the v0.1.1 fix; I have admin to cut releases.
- [Attractor build harness](attractor-build-harness.md) — attractor+pi/codex setup at ../attractor; codex model = openai-codex/gpt-5.5 (NOT the SPEC's gpt-5.3-codex); viable for bounded single-machine spt-core tasks (SPIKE-07b gix pilot).
- [Windows UAC binary-name 740](windows-uac-binary-name-740.md) — exe names containing update/setup/install/patch trigger UAC elevation (os error 740); name self-update artifacts + test bins to dodge it.
- [M4 resume state](m4-resume-state.md) — M3 COMPLETE + CI-green (2026-06-03); M4 (spt-net) is next but NOT started — confirm with user before starting.


- [M4 resume state](m4-resume-state.md) — M3 COMPLETE + CI-green (2026-06-03); M4 (spt-net) is next but NOT started — confirm with user before starting.

- [M4 resume state](m4-resume-state.md) — M4 (spt-net) STARTED 2026-06-03; plan expanded + D0 scaffold done (commits ca6522b/c47d2c6), next = D1 Iroh endpoint.


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

# Memory index

- [Cross-machine test rig](cross-machine-test-rig.md) — enlyzeam (Windows, ssh key) for two-host network spikes + Tailscale-down gotcha; gravity-linux (Ubuntu, david/bigscreen pw-auth + askpass shim) as the Linux/forkpty build host.
- [CI traceability gate](ci-traceability-gate.md) — how CI installs traceable-reqs (TRACEABLE_REQS_TOKEN PAT + latest BigscreenVR release); the v0.1.0-stale-binary gotcha + the v0.1.1 fix; I have admin to cut releases.
- [Attractor build harness](attractor-build-harness.md) — attractor+pi/codex setup at ../attractor; codex model = openai-codex/gpt-5.5 (NOT the SPEC's gpt-5.3-codex); viable for bounded single-machine spt-core tasks (SPIKE-07b gix pilot).
- [Windows UAC binary-name 740](windows-uac-binary-name-740.md) — exe names containing update/setup/install/patch trigger UAC elevation (os error 740); name self-update artifacts + test bins to dodge it.
- [M4 resume state](m4-resume-state.md) — M3 COMPLETE + CI-green (2026-06-03); M4 (spt-net) is next but NOT started — confirm with user before starting.

-- [M4 resume state](m4-resume-state.md) — M3 COMPLETE + CI-green (2026-06-03); M4 (spt-net) is next but NOT started — confirm with user before starting.

+- [M4 resume state](m4-resume-state.md) — M4 (spt-net) STARTED 2026-06-03; plan expanded + D0 scaffold done (commits ca6522b/c47d2c6), next = D1 Iroh endpoint.


M4 launched. Two atomic commits landed on `dev-freeform`:

**`ca6522b` — plan expanded.** `M4-PLAN.md` stub → full D0–D9 task layer: sub-tasks, requirement→task map, binding red-team obligations table. Folded in multi-subnet dimension (INST-9..13, PAIR-5/6, NOTIF, SEC-1) the ADRs activate at M4. Each red-team finding tied to closing task:

| Finding | Closes in |
|---|---|
| #12 transcript bind / #10 seed rotation / #11 rate-limit | D2 pairing |
| #8 epoch-lease resolution | D3 registry |
| #7 merge-clock (concurrent writes) | D6 Psyche sync |
| #6 failure-domain matrix | D4 broker |
| #9 dormant budget | D9 |

**`c47d2c6` — D0 scaffold.** `crates/spt-net` created, sibling above spt-msg, wired into workspace. Whole transport behind default-on `net` feature (ADR-0002). Verified:
- `cargo check` default + `--no-default-features` → both compile
- layering acyclic (cargo rejects cycles)
- `traceable-reqs check` → exit 0

No REQ activated yet — scaffold delivers none (rule 5); REQ-NET-1 lands impl/unit at D1.

Memory updated: M4 STARTED, next = D1.

**Next = D1** — Iroh endpoint (reuse spt-proto Ed25519, iroh 0.98 / Spike #2 shape) + n0 relay default + self-host knob + mDNS LAN. Milestone-sized: adds the real iroh dep + first transport design. Two design calls will surface soon worth your input:
- **D1:** iroh version pin (Spike #2 used 0.98 — confirm or bump) + relay default wording.
- **D2:** the merge-model decision for #7 (epochs vs vector-clocks vs CRDT) and the 3 new `REQ-HAZARD-PAIR-*` ids.

Not pushed (2 commits ahead). Want me to start D1, or hold for the iroh-pin call first?


make both calls using best judgment and proceed


<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.


Both calls mine. D1: implement now. Merge-model (#7): record decision in plan. Reading Spike #2 for the proven iroh version/API + checking latest iroh.


TaskCreate

D1: Iroh endpoint + relay + mDNS discovery

Iroh endpoint bound to spt-proto Ed25519 identity (Spike #2 shape). n0 relay default + self-host knob + disclosure (REQ-NET-2). mDNS LAN discovery + connect on net ALPN. Acceptance: two local nodes discover + connect, E2E. Activates REQ-NET-1 impl/unit + REQ-NET-2.


Task #3 created successfully: D1: Iroh endpoint + relay + mDNS discovery


Bash

cd "C:/Users/decid/Documents/projects/spt-core" && echo "===SPIKE-02===" && find docs -iname '*spike*02*' -o -iname '*iroh*' 2>/dev/null | head; echo "---"; ls docs/spikes/ 2>/dev/null

Locate Spike #2 iroh doc


Command running in background with ID: bivp2kgyr. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bivp2kgyr.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>bivp2kgyr</task-id>
<tool-use-id>toolu_01T1WzPwEPjqtXLMmnRFwfPb</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bivp2kgyr.output</output-file>
<status>completed</status>
<summary>Background command "Locate Spike #2 iroh doc" completed (exit code 0)</summary>
</task-notification>


1	===SPIKE-02===
2	docs/spikes/SPIKE-02-iroh-smoke.md
3	---
4	SPIKE-01-broker-handoff.md
5	SPIKE-02-iroh-smoke.md
6	SPIKE-03-quic-survival.md
7	SPIKE-04-forkpty-parity.md
8	SPIKE-05-restart-stress.md
9	SPIKE-06-idempotent-boundary.md
10	SPIKE-07-branchstore.md
11

===SPIKE-02===
docs/spikes/SPIKE-02-iroh-smoke.md
---
SPIKE-01-broker-handoff.md
SPIKE-02-iroh-smoke.md
SPIKE-03-quic-survival.md
SPIKE-04-forkpty-parity.md
SPIKE-05-restart-stress.md
SPIKE-06-idempotent-boundary.md
SPIKE-07-branchstore.md


Prompt:
<task-notification>
<task-id>bivp2kgyr</task-id>
<tool-use-id>toolu_01T1WzPwEPjqtXLMmnRFwfPb</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bivp2kgyr.output</output-file>
<status>completed</status>
<summary>Background command "Locate Spike #2 iroh doc" completed (exit code 0)</summary>
</task-notification>