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

- **Alarms have no durable daemon scheduler.** In the spt-core daemon, `alarm` exists only as an event *shape* (`spt-proto`) + relay handling (`psyrelay.rs:93`) + a test fixture; `target-time` is only a wire attribute. The actual "wait until target, fire the TIMED PULSE" timer lives in the legacy owl listener in-memory. Porting alarms into the daemon as a durable scheduler (which would then ride the Q4 one-shot mechanism) is its own gap.

- **Registry one-cadence reconverge window.** `RegistryHost` is intentionally in-memory (`registryhost.rs:19-26`): a restart forgets peers' rows and reconverges within one pump cadence (self-rows re-advertised each tick; mirrored to `identity/registry/` JSON for out-of-process readers). Across a brain restart this is a brief reconverge window, accepted by design — not a blocker, noted for completeness.

-- **Multipart inter-agent message truncation (delivery-integrity bug, surfaced *by* this session).** During the verification exchange, an 18-part chunked message (`EVENT-PART seq=K/18`) was delivered with only ~parts 1–8; a long resend also raced/dropped its tail. Long inter-agent messages **silently lose their tail** — a delivery-integrity violation in the KH §4 envelope family (the same family as the `\r`-corruption bug that opened this whole thread). Candidate causes: chunk-reassembly drop or a per-message `EVENT-PART` cap, possibly interacting with a resident pre-restart daemon. Needs a deterministic repro (N-part message; assert reassembled byte-count == sent) + a DEFERRED/hazard row. Unrelated to the split, but must not be lost — tracked here pending operator ratification. (`doyle` can partner on a repro harness on demand.)

+- **Multipart inter-agent message truncation — belongs to LEGACY `claude_skill_owl`, not spt-core.** During the verification exchange (which ran over the legacy live-agent messaging infra — the `owl.exe` listener, not the spt-core daemon), an 18-part chunked message reached the recipient as ~parts 1–8; a long resend also dropped its tail. Long messages silently lose their tail — a delivery-integrity issue in the chunk-reassembly path. **This is a legacy-project bug** (the agents in this exchange run on `claude_skill_owl`'s transport); spt-core's *own* reassembly is already covered by `REQ-HAZARD-EVENTPART-REASSEMBLY`. It is **not an spt-core artifact** — flagged here only because the session surfaced it; route the repro + fix to the legacy project. (`doyle` can partner on a repro harness.)

- **Stale DEFERRED mesh row.** Side-flag from verification: the mesh row in `docs/DEFERRED.md` is stale (mesh shipped at v0.3.0). Minor cleanup, noted for a docs pass.


Write

# Restore broker/brain process isolation (correct the in-process-collapse regression)

## Status

Proposed (2026-06-09) — **extends and amends ADR-0004.** The design was independently verified (agent `doyle`, verified-with-amendments); implementation is a dedicated future milestone, sequenced next (before `spt-claude-code`). Full rationale, the `file:line` audit, and the per-decision alternatives live in `docs/BROKER-BRAIN-SPLIT-RESTORATION.md`.

## Context

ADR-0004 decided the broker/brain split **as two processes** — a stable broker kernel (PTY masters, harness children, sockets, the Iroh/QUIC endpoint) beneath a freely-restartable brain — specifically to satisfy the no-endpoint-drop self-update invariant (REQ-UPD-3): a routine (brain-only) update restarts the brain while the broker process survives, so no hosted endpoint terminates. The spikes (01/03/04/05/06) proved this with two separate binaries.

On 2026-06-09, while verifying the v0.3.2 cross-OS update fix on the live fleet, a regression was discovered: **the production daemon runs the broker as a background thread inside the single `spt daemon` process** (`daemon.rs:165-170`: `Broker::bind_in_with_net` → `Arc<Broker>` served on `thread::spawn`; no broker child-spawn exists anywhere in the repo). This is **unintended drift** — no doc, plan, commit, or comment marks the collapse as a deliberate interim (the operator confirmed no such decision was made).

Consequences of the drift:
- The brain cannot restart onto a new binary without killing the in-process broker thread, which would close every PTY, orphan every harness child, and drop every listening socket. **The no-endpoint-drop self-update pillar is therefore silently unrealized.**
- `spt update apply` performs an in-process `Brain::handoff` that re-attaches a subscriber within the *same old process* — a no-op for a binary swap. `update.rs:233-234`'s "the live daemon execs the verified new binary" is aspirational and was never wired.
- New code does not run until an unrelated restart/logon. Observed live: `enlyzeam` ran 0.3.0 with the valid 0.3.2 binary on disk for ~a day, continuing to reproduce the `\r`-corruption bug the update was meant to fix.
- **REQ-DAEMON-2 and REQ-UPD-3 carry `int` evidence that proves only the in-process handoff shape** (`tests/update.rs`, `brain_swap.rs`, the M3b-B9 daemon E2E) — i.e. the regression is masked in the requirement registry: the tests pass while proving the wrong thing.

## Decision

**Restore the ADR-0004 two-process model.** The brain runs new code by restarting onto the swapped binary while the broker process — holding every continuity-bearing resource — survives. The design decisions (grill Q2–Q8, hardened by six verification amendments `[V1]`–`[V6]`):

1. **Supervision (Q2).** The broker is the always-up per-machine anchor (one per `SPT_HOME`, present even with zero endpoints). It owns the seed-control lock + liveness (`ensure_running`/`is_running` target) and supervises/spawns the brain as its child.
2. **Update trigger (Q3).** `apply` swaps the binary on disk, then signals the brain to snapshot + self-exit; the broker auto-respawns from the executable path (now the new binary); the new brain re-attaches. Reuses the existing snapshot→drop→re-attach primitive; an update is a *planned* crash on the path the broker already recovers from.
3. **Loop-timing continuity (Q4).** Durable absolute-deadline state on disk, not a handoff snapshot. Periodic loops persist `(anchor, interval)` once on a fresh/crash start and derive `next_fire` functionally (no per-fire writes); an update restart keeps deriving (phase preserved), a crash restart resets the anchor. One-shot deadlines persist at creation and survive crash + update. **`[V3]`** The one-shot *rule* is fixed here; its *machinery* is built with the alarm port (no untested dead code). **`[V4]`** Only phase-significant loops convert — the idempotent pump cadences need none.
4. **IPC boundary (Q5).** ADR-0004 §B adjudicates: net bring-up → broker (already broker-owned via `broker.rs:175` `OnceLock` — near-free), digest hub → broker / parse → brain, seed-lock → broker; every brain→broker call becomes a versioned IPC verb (no shared `Arc`). **Exception:** shellwake watcher children stay brain-side, re-reconciled from disk on start (rare updates make the window tolerable). Broker enlargement accepted (ADR-0004:63 precedent). **`[V6]`** Steady state after any update is new-brain × old-broker, so a CI-real old-broker × new-brain compat test across the verb surface is required (else KH-2.3 returns).
5. **Multi-session handoff (Q6).** The broker becomes cursor-of-record per session; the new brain re-attaches **all** sessions in resume mode. Output is at-least-once (matches the SPIKE-05 terminal-stream contract); input/effects stay exactly-once via the broker-owned `EffectJournal`. The explicit `BrainState` handoff *message* retires (no brain→brain channel exists under the self-exit trigger).
6. **Failure atomicity (Q7).** Bounded-retry → auto-rollback to the last-known-good binary, gated on a brain `ready` signal (reuse the `peerloop.rs:805` supervise-backoff + healthy-run). Quarantine the failed version; loud notif. The applied record becomes **two-phase** (`applied-pending` → `applied` on ready, or `rolled-back` on failure) — fixing today's optimistic `record_applied` before boot (`applyhost.rs:176`). **`[V1]`** Forward invariant minted: a brain must not irreversibly migrate durable state before ready-promotion (pre-ready writes stay N-1-readable), or auto-rollback silently breaks on the first schema migration.
7. **Generation custody (Q2/`[V2]`).** Retiring `BrainState` would orphan the KH-2.4 generation counter; the **broker** owns it, increments on every spawn, and hands `{generation, start-reason}` to the brain at spawn via a versioned argv/hello field (KH-2.3 compat). The same channel carries Q4's update-vs-crash discriminator.
8. **Cross-platform uniformity (Q8).** The broker spawns the brain as a child process (`Command::spawn`) + socket IPC — no `exec`, so no Windows/Unix divergence. ConPTY/forkpty handles never leave the broker.

## Consequences

- **Decomposition:** `spt daemon run` becomes the broker process entry (binds seed-control + broker socket + NetHost + digest hub; holds children + `EffectJournal`; spawns the brain); a new hidden `spt daemon brain` is the brain entry (connects, runs the logic loops, rehydrates from disk, emits `ready`). `ensure_running`/`is_running`/`daemon stop` contracts unchanged. The de-elevation guard applies at the broker entry.
- **Requirement registry:** REQ-DAEMON-2 and REQ-UPD-3's `int` evidence must be **re-pointed** to a productionized SPIKE-01/03 E2E proving *process-level* endpoint survival (`[V5]`). Two new hazards are minted: `REQ-HAZARD-BROKER-PROCESS-ISOLATION` (a brain restart must never drop a hosted endpoint) and `REQ-HAZARD-ROLLBACK-STATE-COMPAT`. A KNOWN-HAZARDS §6.7 entry records the regression.
- **Sequencing:** the restoration is the next milestone, before `spt-claude-code` scoping (operator-accepted 2026-06-09). Rationale: it is the *last* release that needs a manual fleet daemon bounce — paid 3× for v0.3.2 — so every adapter-era release rolls seamlessly; and the adapter is better built on the final topology than atop a daemon under later surgery while it hosts the user's daily driver. The split changes daemon internals, not the M8-frozen CLI/api surface.
- **Out of scope (unchanged):** the broker-touching update classes (broker-compatible / broker-breaking) remain as ADR-0004 left them; whole-daemon FD-passing stays the deferred "future polish" (ADR-0004:38); a durable in-daemon alarm scheduler is a separate gap (alarms are legacy-listener-only today).


before ADR, reqs, or known hazards, please write a doc that details the reason for this grill session, the decisions that were made and the reasons for both. i will then have another agent verify that…


Release publish process and signing procedure


File created successfully at: C:\Users\decid\Documents\projects\spt-core\docs\adr\0018-broker-brain-process-isolation-restoration.md (file state is current in your context — no need to Read it back)

# Restore broker/brain process isolation (correct the in-process-collapse regression)

## Status

Proposed (2026-06-09) — **extends and amends ADR-0004.** The design was independently verified (agent `doyle`, verified-with-amendments); implementation is a dedicated future milestone, sequenced next (before `spt-claude-code`). Full rationale, the `file:line` audit, and the per-decision alternatives live in `docs/BROKER-BRAIN-SPLIT-RESTORATION.md`.

## Context

ADR-0004 decided the broker/brain split **as two processes** — a stable broker kernel (PTY masters, harness children, sockets, the Iroh/QUIC endpoint) beneath a freely-restartable brain — specifically to satisfy the no-endpoint-drop self-update invariant (REQ-UPD-3): a routine (brain-only) update restarts the brain while the broker process survives, so no hosted endpoint terminates. The spikes (01/03/04/05/06) proved this with two separate binaries.

On 2026-06-09, while verifying the v0.3.2 cross-OS update fix on the live fleet, a regression was discovered: **the production daemon runs the broker as a background thread inside the single `spt daemon` process** (`daemon.rs:165-170`: `Broker::bind_in_with_net` → `Arc<Broker>` served on `thread::spawn`; no broker child-spawn exists anywhere in the repo). This is **unintended drift** — no doc, plan, commit, or comment marks the collapse as a deliberate interim (the operator confirmed no such decision was made).

Consequences of the drift:
- The brain cannot restart onto a new binary without killing the in-process broker thread, which would close every PTY, orphan every harness child, and drop every listening socket. **The no-endpoint-drop self-update pillar is therefore silently unrealized.**
- `spt update apply` performs an in-process `Brain::handoff` that re-attaches a subscriber within the *same old process* — a no-op for a binary swap. `update.rs:233-234`'s "the live daemon execs the verified new binary" is aspirational and was never wired.
- New code does not run until an unrelated restart/logon. Observed live: `enlyzeam` ran 0.3.0 with the valid 0.3.2 binary on disk for ~a day, continuing to reproduce the `\r`-corruption bug the update was meant to fix.
- **REQ-DAEMON-2 and REQ-UPD-3 carry `int` evidence that proves only the in-process handoff shape** (`tests/update.rs`, `brain_swap.rs`, the M3b-B9 daemon E2E) — i.e. the regression is masked in the requirement registry: the tests pass while proving the wrong thing.

## Decision

**Restore the ADR-0004 two-process model.** The brain runs new code by restarting onto the swapped binary while the broker process — holding every continuity-bearing resource — survives. The design decisions (grill Q2–Q8, hardened by six verification amendments `[V1]`–`[V6]`):

1. **Supervision (Q2).** The broker is the always-up per-machine anchor (one per `SPT_HOME`, present even with zero endpoints). It owns the seed-control lock + liveness (`ensure_running`/`is_running` target) and supervises/spawns the brain as its child.
2. **Update trigger (Q3).** `apply` swaps the binary on disk, then signals the brain to snapshot + self-exit; the broker auto-respawns from the executable path (now the new binary); the new brain re-attaches. Reuses the existing snapshot→drop→re-attach primitive; an update is a *planned* crash on the path the broker already recovers from.
3. **Loop-timing continuity (Q4).** Durable absolute-deadline state on disk, not a handoff snapshot. Periodic loops persist `(anchor, interval)` once on a fresh/crash start and derive `next_fire` functionally (no per-fire writes); an update restart keeps deriving (phase preserved), a crash restart resets the anchor. One-shot deadlines persist at creation and survive crash + update. **`[V3]`** The one-shot *rule* is fixed here; its *machinery* is built with the alarm port (no untested dead code). **`[V4]`** Only phase-significant loops convert — the idempotent pump cadences need none.
4. **IPC boundary (Q5).** ADR-0004 §B adjudicates: net bring-up → broker (already broker-owned via `broker.rs:175` `OnceLock` — near-free), digest hub → broker / parse → brain, seed-lock → broker; every brain→broker call becomes a versioned IPC verb (no shared `Arc`). **Exception:** shellwake watcher children stay brain-side, re-reconciled from disk on start (rare updates make the window tolerable). Broker enlargement accepted (ADR-0004:63 precedent). **`[V6]`** Steady state after any update is new-brain × old-broker, so a CI-real old-broker × new-brain compat test across the verb surface is required (else KH-2.3 returns).
5. **Multi-session handoff (Q6).** The broker becomes cursor-of-record per session; the new brain re-attaches **all** sessions in resume mode. Output is at-least-once (matches the SPIKE-05 terminal-stream contract); input/effects stay exactly-once via the broker-owned `EffectJournal`. The explicit `BrainState` handoff *message* retires (no brain→brain channel exists under the self-exit trigger).
6. **Failure atomicity (Q7).** Bounded-retry → auto-rollback to the last-known-good binary, gated on a brain `ready` signal (reuse the `peerloop.rs:805` supervise-backoff + healthy-run). Quarantine the failed version; loud notif. The applied record becomes **two-phase** (`applied-pending` → `applied` on ready, or `rolled-back` on failure) — fixing today's optimistic `record_applied` before boot (`applyhost.rs:176`). **`[V1]`** Forward invariant minted: a brain must not irreversibly migrate durable state before ready-promotion (pre-ready writes stay N-1-readable), or auto-rollback silently breaks on the first schema migration.
7. **Generation custody (Q2/`[V2]`).** Retiring `BrainState` would orphan the KH-2.4 generation counter; the **broker** owns it, increments on every spawn, and hands `{generation, start-reason}` to the brain at spawn via a versioned argv/hello field (KH-2.3 compat). The same channel carries Q4's update-vs-crash discriminator.
8. **Cross-platform uniformity (Q8).** The broker spawns the brain as a child process (`Command::spawn`) + socket IPC — no `exec`, so no Windows/Unix divergence. ConPTY/forkpty handles never leave the broker.

## Consequences

- **Decomposition:** `spt daemon run` becomes the broker process entry (binds seed-control + broker socket + NetHost + digest hub; holds children + `EffectJournal`; spawns the brain); a new hidden `spt daemon brain` is the brain entry (connects, runs the logic loops, rehydrates from disk, emits `ready`). `ensure_running`/`is_running`/`daemon stop` contracts unchanged. The de-elevation guard applies at the broker entry.
- **Requirement registry:** REQ-DAEMON-2 and REQ-UPD-3's `int` evidence must be **re-pointed** to a productionized SPIKE-01/03 E2E proving *process-level* endpoint survival (`[V5]`). Two new hazards are minted: `REQ-HAZARD-BROKER-PROCESS-ISOLATION` (a brain restart must never drop a hosted endpoint) and `REQ-HAZARD-ROLLBACK-STATE-COMPAT`. A KNOWN-HAZARDS §6.7 entry records the regression.
- **Sequencing:** the restoration is the next milestone, before `spt-claude-code` scoping (operator-accepted 2026-06-09). Rationale: it is the *last* release that needs a manual fleet daemon bounce — paid 3× for v0.3.2 — so every adapter-era release rolls seamlessly; and the adapter is better built on the final topology than atop a daemon under later surgery while it hosts the user's daily driver. The split changes daemon internals, not the M8-frozen CLI/api surface.
- **Out of scope (unchanged):** the broker-touching update classes (broker-compatible / broker-breaking) remain as ADR-0004 left them; whole-daemon FD-passing stays the deferred "future polish" (ADR-0004:38); a durable in-daemon alarm scheduler is a separate gap (alarms are legacy-listener-only today).


Now the KNOWN-HAZARDS §6.7 entry, after 6.6:


Edit

- **Origin:** ADR-0013 design invariant (red-team #7's "wall-clock loses concurrent writes" closed M4-D6), not a sister bug — registered ahead of the wire path so D6c is born conformant.

---

## 7. Boundary & delivery integrity (added 2026-05-31 — Stage A red-team)

- **Origin:** ADR-0013 design invariant (red-team #7's "wall-clock loses concurrent writes" closed M4-D6), not a sister bug — registered ahead of the wire path so D6c is born conformant.

### 6.7 Broker and brain MUST be separate processes (in-process collapse silently breaks no-endpoint-drop update) `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`
- **Failure:** the daemon hosts the broker as a background *thread* in the single `spt daemon` process (`daemon.rs:165-170`, `Arc<Broker>` + `thread::spawn(serve)`) instead of a separate process. A brain restart onto a swapped binary then cannot happen without killing the broker thread — closing every PTY, orphaning every harness child, dropping every socket. So `spt update apply` degrades to an in-process `Brain::handoff` no-op: the binary swaps on disk but the running daemon keeps executing the old code until an unrelated restart/logon. The no-endpoint-drop self-update pillar (REQ-UPD-3, ADR-0004) is silently unrealized. Observed live 2026-06-09: `enlyzeam` ran 0.3.0 with 0.3.2 on disk for ~a day, still reproducing the bug the update fixed.
- **Invariant:** the broker runs as its own long-lived process that survives every brain restart; the brain restarts onto the new binary and re-attaches via the versioned IPC. A routine (brain-only) update must leave every hosted endpoint untouched at the *process* level — not merely re-subscribe a brain within the same process. The evidence for REQ-UPD-3 / REQ-DAEMON-2 must prove process-level survival (a PTY child + a live QUIC conn survive a brain-process restart onto a swapped binary — SPIKE-01/03 productionized as `int`), NOT the in-process handoff shape that masks this regression.
- **spt-core mapping:** restoration is ADR-0018 (next milestone). The current `int` tags on REQ-DAEMON-2 / REQ-UPD-3 are regression-masked and re-point at restoration; the broker becomes the always-up per-machine anchor (seed-lock + liveness + brain supervisor). Two-process supervision, generation custody, durable-deadline loop timing, broker-cursor-of-record, and readiness-gated auto-rollback all hang off this.
- **Origin:** unintended spec/impl drift from ADR-0004 (the broker *process* was specced + spiked but built in-process), discovered during the v0.3.2 fleet update verify. Full audit + decisions: `docs/BROKER-BRAIN-SPLIT-RESTORATION.md` (verified) + ADR-0018.

### 6.8 No irreversible durable-state migration before update ready-promotion `[REQ-HAZARD-ROLLBACK-STATE-COMPAT]`
- **Failure:** the readiness-gated auto-rollback (ADR-0018 Q7) spawns the *previous* binary against durable state the *new* brain already wrote. The first release that migrates a durable-state schema in place would make the old binary unable to read it — silently bricking rollback exactly when it is needed (a logic-bricking update that can no longer fall back).
- **Invariant:** a brain must not irreversibly migrate durable state before it is ready-promoted; equivalently, every pre-ready write must remain readable by the N-1 brain. Schema migrations are gated behind ready-promotion (or written in an N-1-tolerant additive form).
- **spt-core mapping:** lands with ADR-0018's auto-rollback. Free to assert now (a 2026-06-09 source audit confirmed zero state-migration code exists); unmintable retroactively once a migration ships.
- **Origin:** verification amendment `[V1]` (agent `doyle`) on `docs/BROKER-BRAIN-SPLIT-RESTORATION.md`.

---

## 7. Boundary & delivery integrity (added 2026-05-31 — Stage A red-team)


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

- **Origin:** ADR-0013 design invariant (red-team #7's "wall-clock loses concurrent writes" closed M4-D6), not a sister bug — registered ahead of the wire path so D6c is born conformant.

---

## 7. Boundary & delivery integrity (added 2026-05-31 — Stage A red-team)

- **Origin:** ADR-0013 design invariant (red-team #7's "wall-clock loses concurrent writes" closed M4-D6), not a sister bug — registered ahead of the wire path so D6c is born conformant.

### 6.7 Broker and brain MUST be separate processes (in-process collapse silently breaks no-endpoint-drop update) `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`
- **Failure:** the daemon hosts the broker as a background *thread* in the single `spt daemon` process (`daemon.rs:165-170`, `Arc<Broker>` + `thread::spawn(serve)`) instead of a separate process. A brain restart onto a swapped binary then cannot happen without killing the broker thread — closing every PTY, orphaning every harness child, dropping every socket. So `spt update apply` degrades to an in-process `Brain::handoff` no-op: the binary swaps on disk but the running daemon keeps executing the old code until an unrelated restart/logon. The no-endpoint-drop self-update pillar (REQ-UPD-3, ADR-0004) is silently unrealized. Observed live 2026-06-09: `enlyzeam` ran 0.3.0 with 0.3.2 on disk for ~a day, still reproducing the bug the update fixed.
- **Invariant:** the broker runs as its own long-lived process that survives every brain restart; the brain restarts onto the new binary and re-attaches via the versioned IPC. A routine (brain-only) update must leave every hosted endpoint untouched at the *process* level — not merely re-subscribe a brain within the same process. The evidence for REQ-UPD-3 / REQ-DAEMON-2 must prove process-level survival (a PTY child + a live QUIC conn survive a brain-process restart onto a swapped binary — SPIKE-01/03 productionized as `int`), NOT the in-process handoff shape that masks this regression.
- **spt-core mapping:** restoration is ADR-0018 (next milestone). The current `int` tags on REQ-DAEMON-2 / REQ-UPD-3 are regression-masked and re-point at restoration; the broker becomes the always-up per-machine anchor (seed-lock + liveness + brain supervisor). Two-process supervision, generation custody, durable-deadline loop timing, broker-cursor-of-record, and readiness-gated auto-rollback all hang off this.
- **Origin:** unintended spec/impl drift from ADR-0004 (the broker *process* was specced + spiked but built in-process), discovered during the v0.3.2 fleet update verify. Full audit + decisions: `docs/BROKER-BRAIN-SPLIT-RESTORATION.md` (verified) + ADR-0018.

### 6.8 No irreversible durable-state migration before update ready-promotion `[REQ-HAZARD-ROLLBACK-STATE-COMPAT]`
- **Failure:** the readiness-gated auto-rollback (ADR-0018 Q7) spawns the *previous* binary against durable state the *new* brain already wrote. The first release that migrates a durable-state schema in place would make the old binary unable to read it — silently bricking rollback exactly when it is needed (a logic-bricking update that can no longer fall back).
- **Invariant:** a brain must not irreversibly migrate durable state before it is ready-promoted; equivalently, every pre-ready write must remain readable by the N-1 brain. Schema migrations are gated behind ready-promotion (or written in an N-1-tolerant additive form).
- **spt-core mapping:** lands with ADR-0018's auto-rollback. Free to assert now (a 2026-06-09 source audit confirmed zero state-migration code exists); unmintable retroactively once a migration ships.
- **Origin:** verification amendment `[V1]` (agent `doyle`) on `docs/BROKER-BRAIN-SPLIT-RESTORATION.md`.

---

## 7. Boundary & delivery integrity (added 2026-05-31 — Stage A red-team)

- **spt-core mapping:** `ContextStore::record_conflict` (tracked `.conflicts/` artifacts, content-hash named, idempotent, replicate like context) / `list_conflicts` / `clear_conflicts` (dominating-write-only). Local working file stays untouched while a conflict is pending.

+### 6.7 Broker and brain MUST be separate processes (in-process collapse silently breaks no-endpoint-drop update) `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`

+- **Failure:** the daemon hosts the broker as a background *thread* in the single `spt daemon` process (`daemon.rs:165-170`, `Arc<Broker>` + `thread::spawn(serve)`) instead of a separate process. A brain restart onto a swapped binary then cannot happen without killing the broker thread — closing every PTY, orphaning every harness child, dropping every socket. So `spt update apply` degrades to an in-process `Brain::handoff` no-op: the binary swaps on disk but the running daemon keeps executing the old code until an unrelated restart/logon. The no-endpoint-drop self-update pillar (REQ-UPD-3, ADR-0004) is silently unrealized. Observed live 2026-06-09: `enlyzeam` ran 0.3.0 with 0.3.2 on disk for ~a day, still reproducing the bug the update fixed.

+- **Invariant:** the broker runs as its own long-lived process that survives every brain restart; the brain restarts onto the new binary and re-attaches via the versioned IPC. A routine (brain-only) update must leave every hosted endpoint untouched at the *process* level — not merely re-subscribe a brain within the same process. The evidence for REQ-UPD-3 / REQ-DAEMON-2 must prove process-level survival (a PTY child + a live QUIC conn survive a brain-process restart onto a swapped binary — SPIKE-01/03 productionized as `int`), NOT the in-process handoff shape that masks this regression.

+- **spt-core mapping:** restoration is ADR-0018 (next milestone). The current `int` tags on REQ-DAEMON-2 / REQ-UPD-3 are regression-masked and re-point at restoration; the broker becomes the always-up per-machine anchor (seed-lock + liveness + brain supervisor). Two-process supervision, generation custody, durable-deadline loop timing, broker-cursor-of-record, and readiness-gated auto-rollback all hang off this.

+- **Origin:** unintended spec/impl drift from ADR-0004 (the broker *process* was specced + spiked but built in-process), discovered during the v0.3.2 fleet update verify. Full audit + decisions: `docs/BROKER-BRAIN-SPLIT-RESTORATION.md` (verified) + ADR-0018.

+### 6.8 No irreversible durable-state migration before update ready-promotion `[REQ-HAZARD-ROLLBACK-STATE-COMPAT]`

+- **Failure:** the readiness-gated auto-rollback (ADR-0018 Q7) spawns the *previous* binary against durable state the *new* brain already wrote. The first release that migrates a durable-state schema in place would make the old binary unable to read it — silently bricking rollback exactly when it is needed (a logic-bricking update that can no longer fall back).

+- **Invariant:** a brain must not irreversibly migrate durable state before it is ready-promoted; equivalently, every pre-ready write must remain readable by the N-1 brain. Schema migrations are gated behind ready-promotion (or written in an N-1-tolerant additive form).

+- **spt-core mapping:** lands with ADR-0018's auto-rollback. Free to assert now (a 2026-06-09 source audit confirmed zero state-migration code exists); unmintable retroactively once a migration ships.

+- **Origin:** verification amendment `[V1]` (agent `doyle`) on `docs/BROKER-BRAIN-SPLIT-RESTORATION.md`.


Now the two new `REQ-HAZARD` mints in `traceable-reqs.toml` — inactive (`required_stages = []`) per the activate-don't-pre-fail rule, activating at the restoration milestone:


# --- Added 2026-06-03 from grill-with-docs (ADR-0012, hazard 7.3); inactive until M4-D7.5 ---
[[requirements]]
id = "REQ-HAZARD-PSYCHE-OUTBOUND-PROXY"

# --- Added 2026-06-09 from grill-with-docs (ADR-0018: broker/brain in-process-collapse regression); rule 3: register before satisfying, rule 5: inactive until the broker/brain split restoration milestone (next milestone) ---
[[requirements]]
id = "REQ-HAZARD-BROKER-PROCESS-ISOLATION"
title = "Broker and brain are separate processes: the broker runs as its own long-lived per-machine process that survives every brain restart, so a routine (brain-only) self-update restarts the brain onto the swapped binary while every hosted endpoint (PTY child, live QUIC conn, listening socket) stays untouched at the PROCESS level. The in-process-thread broker (daemon.rs:165-170) is a regression that silently unrealizes REQ-UPD-3 — apply degrades to an in-process Brain::handoff no-op and new code does not run until an unrelated restart (KNOWN-HAZARDS 6.7). Evidence must prove process-level survival (SPIKE-01/03 productionized as int: PTY child + live QUIC survive a brain-PROCESS restart onto a swapped binary), re-pointing the regression-masked in-process int tags currently on REQ-DAEMON-2 / REQ-UPD-3 (ADR-0018)."
required_stages = []  # inactive: activates at the broker/brain split restoration milestone (ADR-0018). doc evidence = ADR-0018 + docs/BROKER-BRAIN-SPLIT-RESTORATION.md; impl/unit/int activate with the milestone. int = productionized SPIKE-01/03 process-level survival E2E + the new-brain x old-broker N-1 verb-surface compat test (V6)
[[requirements]]
id = "REQ-HAZARD-ROLLBACK-STATE-COMPAT"
title = "A brain must not irreversibly migrate durable state before update ready-promotion: the readiness-gated auto-rollback (ADR-0018 Q7) spawns the N-1 binary against durable state the new brain may have written, so every pre-ready write must stay N-1-readable (schema migrations gated behind ready-promotion, or written N-1-tolerant/additive). Else the first in-place schema migration silently bricks rollback (KNOWN-HAZARDS 6.8). Free now — a 2026-06-09 audit confirmed zero state-migration code exists; unmintable retroactively once a migration ships."
required_stages = []  # inactive: activates with ADR-0018 auto-rollback. doc evidence = ADR-0018 + KNOWN-HAZARDS 6.8
# --- Added 2026-06-03 from grill-with-docs (ADR-0012, hazard 7.3); inactive until M4-D7.5 ---
[[requirements]]
id = "REQ-HAZARD-PSYCHE-OUTBOUND-PROXY"


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

# --- Added 2026-06-09 from grill-with-docs (ADR-0018: broker/brain in-process-collapse regression); rule 3: register before satisfying, rule 5: inactive until the broker/brain split restoration milestone (next milestone) ---
[[requirements]]
id = "REQ-HAZARD-BROKER-PROCESS-ISOLATION"
title = "Broker and brain are separate processes: the broker runs as its own long-lived per-machine process that survives every brain restart, so a routine (brain-only) self-update restarts the brain onto the swapped binary while every hosted endpoint (PTY child, live QUIC conn, listening socket) stays untouched at the PROCESS level. The in-process-thread broker (daemon.rs:165-170) is a regression that silently unrealizes REQ-UPD-3 — apply degrades to an in-process Brain::handoff no-op and new code does not run until an unrelated restart (KNOWN-HAZARDS 6.7). Evidence must prove process-level survival (SPIKE-01/03 productionized as int: PTY child + live QUIC survive a brain-PROCESS restart onto a swapped binary), re-pointing the regression-masked in-process int tags currently on REQ-DAEMON-2 / REQ-UPD-3 (ADR-0018)."
required_stages = []  # inactive: activates at the broker/brain split restoration milestone (ADR-0018). doc evidence = ADR-0018 + docs/BROKER-BRAIN-SPLIT-RESTORATION.md; impl/unit/int activate with the milestone. int = productionized SPIKE-01/03 process-level survival E2E + the new-brain x old-broker N-1 verb-surface compat test (V6)
[[requirements]]
id = "REQ-HAZARD-ROLLBACK-STATE-COMPAT"
title = "A brain must not irreversibly migrate durable state before update ready-promotion: the readiness-gated auto-rollback (ADR-0018 Q7) spawns the N-1 binary against durable state the new brain may have written, so every pre-ready write must stay N-1-readable (schema migrations gated behind ready-promotion, or written N-1-tolerant/additive). Else the first in-place schema migration silently bricks rollback (KNOWN-HAZARDS 6.8). Free now — a 2026-06-09 audit confirmed zero state-migration code exists; unmintable retroactively once a migration ships."
required_stages = []  # inactive: activates with ADR-0018 auto-rollback. doc evidence = ADR-0018 + KNOWN-HAZARDS 6.8
# --- Added 2026-06-03 from grill-with-docs (ADR-0012, hazard 7.3); inactive until M4-D7.5 ---
[[requirements]]
id = "REQ-HAZARD-PSYCHE-OUTBOUND-PROXY"

title = "Daemon-hosted perches (Psyche, spt-hosted Self) derive liveness from the daemon endpoint table + info.json status, never is_process_alive(info.pid) (2.5)"

required_stages = ["impl", "unit", "int"]  # int activated M3b-B9: the daemon E2E spawns the hosted Psyche, then proves a dead summarizer pid does NOT flip the daemon-online perch offline

+# --- Added 2026-06-09 from grill-with-docs (ADR-0018: broker/brain in-process-collapse regression); rule 3: register before satisfying, rule 5: inactive until the broker/brain split restoration milestone (next milestone) ---

+title = "Broker and brain are separate processes: the broker runs as its own long-lived per-machine process that survives every brain restart, so a routine (brain-only) self-update restarts the brain onto the swapped binary while every hosted endpoint (PTY child, live QUIC conn, listening socket) stays untouched at the PROCESS level. The in-process-thread broker (daemon.rs:165-170) is a regression that silently unrealizes REQ-UPD-3 — apply degrades to an in-process Brain::handoff no-op and new code does not run until an unrelated restart (KNOWN-HAZARDS 6.7). Evidence must prove process-level survival (SPIKE-01/03 productionized as int: PTY child + live QUIC survive a brain-PROCESS restart onto a swapped binary), re-pointing the regression-masked in-process int tags currently on REQ-DAEMON-2 / REQ-UPD-3 (ADR-0018)."

+required_stages = []  # inactive: activates at the broker/brain split restoration milestone (ADR-0018). doc evidence = ADR-0018 + docs/BROKER-BRAIN-SPLIT-RESTORATION.md; impl/unit/int activate with the milestone. int = productionized SPIKE-01/03 process-level survival E2E + the new-brain x old-broker N-1 verb-surface compat test (V6)

+title = "A brain must not irreversibly migrate durable state before update ready-promotion: the readiness-gated auto-rollback (ADR-0018 Q7) spawns the N-1 binary against durable state the new brain may have written, so every pre-ready write must stay N-1-readable (schema migrations gated behind ready-promotion, or written N-1-tolerant/additive). Else the first in-place schema migration silently bricks rollback (KNOWN-HAZARDS 6.8). Free now — a 2026-06-09 audit confirmed zero state-migration code exists; unmintable retroactively once a migration ships."

+required_stages = []  # inactive: activates with ADR-0018 auto-rollback. doc evidence = ADR-0018 + KNOWN-HAZARDS 6.8


Now record it on the ROADMAP as the next planned milestone (the file is behind — no M8 — so I'll label it descriptively and flag the drift rather than guess a number):


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

- **🔜 NEXT milestone (planned 2026-06-09) — Broker/brain process-isolation restoration.** Design: [`docs/BROKER-BRAIN-SPLIT-RESTORATION.md`](./docs/BROKER-BRAIN-SPLIT-RESTORATION.md) (verified-with-amendments by `doyle`); decision: **ADR-0018**. Corrects an unintended regression — the broker/brain split (ADR-0004) was specced + spiked as two *processes*, but the production daemon runs the broker as an in-process *thread* (`daemon.rs:165-170`), so `spt update apply` swaps the binary on disk yet never restarts the running code. Every release to date needed a manual fleet daemon bounce (paid 3× on v0.3.2; `enlyzeam` reproduced the `\r`-corruption for ~a day with the fix on disk). Restore the two-process model: broker = always-up per-machine anchor (seed-lock + liveness + brain supervisor) spawning the brain as a child; update = brain snapshot+self-exit → broker respawns from the swapped binary → re-attach; durable absolute-deadline loop timing; broker cursor-of-record with all-session resume; readiness-gated auto-rollback to last-known-good (two-phase applied record + the no-pre-ready-migration invariant); broker-owned generation custody; the §B-adjudicated direct-call→versioned-IPC-verb surface (net near-free, shellwake stays brain, new-brain × old-broker N-1 compat test). Re-points the regression-masked REQ-DAEMON-2 / REQ-UPD-3 `int` evidence to a productionized SPIKE-01/03 process-level survival E2E; mints `REQ-HAZARD-BROKER-PROCESS-ISOLATION` + `REQ-HAZARD-ROLLBACK-STATE-COMPAT` (KNOWN-HAZARDS 6.7/6.8). **Sequenced before `spt-claude-code`** (operator 2026-06-09): the *last* release needing a manual bounce, so every adapter-era release rolls seamlessly and the adapter lands on the final topology; changes daemon internals only, leaving the M8-frozen CLI/api surface untouched.

> _Roadmap note: this milestone list is behind reality — M8 (CLI nounification + acceptance) and the v0.3.x release line shipped after M7 but are tracked in commits/CHANGELOG, not itemized here. A full roadmap reconciliation is a separate docs pass._

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


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

- **🔜 NEXT milestone (planned 2026-06-09) — Broker/brain process-isolation restoration.** Design: [`docs/BROKER-BRAIN-SPLIT-RESTORATION.md`](./docs/BROKER-BRAIN-SPLIT-RESTORATION.md) (verified-with-amendments by `doyle`); decision: **ADR-0018**. Corrects an unintended regression — the broker/brain split (ADR-0004) was specced + spiked as two *processes*, but the production daemon runs the broker as an in-process *thread* (`daemon.rs:165-170`), so `spt update apply` swaps the binary on disk yet never restarts the running code. Every release to date needed a manual fleet daemon bounce (paid 3× on v0.3.2; `enlyzeam` reproduced the `\r`-corruption for ~a day with the fix on disk). Restore the two-process model: broker = always-up per-machine anchor (seed-lock + liveness + brain supervisor) spawning the brain as a child; update = brain snapshot+self-exit → broker respawns from the swapped binary → re-attach; durable absolute-deadline loop timing; broker cursor-of-record with all-session resume; readiness-gated auto-rollback to last-known-good (two-phase applied record + the no-pre-ready-migration invariant); broker-owned generation custody; the §B-adjudicated direct-call→versioned-IPC-verb surface (net near-free, shellwake stays brain, new-brain × old-broker N-1 compat test). Re-points the regression-masked REQ-DAEMON-2 / REQ-UPD-3 `int` evidence to a productionized SPIKE-01/03 process-level survival E2E; mints `REQ-HAZARD-BROKER-PROCESS-ISOLATION` + `REQ-HAZARD-ROLLBACK-STATE-COMPAT` (KNOWN-HAZARDS 6.7/6.8). **Sequenced before `spt-claude-code`** (operator 2026-06-09): the *last* release needing a manual bounce, so every adapter-era release rolls seamlessly and the adapter lands on the final topology; changes daemon internals only, leaving the M8-frozen CLI/api surface untouched.

> _Roadmap note: this milestone list is behind reality — M8 (CLI nounification + acceptance) and the v0.3.x release line shipped after M7 but are tracked in commits/CHANGELOG, not itemized here. A full roadmap reconciliation is a separate docs pass._

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

- **M7 — Subnet & quickstart UX. ✅ delivered (2026-06-06** — planned 2026-06-05, task breakdown [`M7-PLAN.md`](./M7-PLAN.md)). Subnet setup is now a first-class, guided, secure product surface and the public quickstart is honest about personas. Shipped: the **join ceremony's product surface** (always-on daemon pairing responder + guided `spt subnet join` CLI initiator over the rig-proven SPAKE2 wire) · the `spt subnet` noun namespace (`status [NAME] [--nodes]` / `create` / `show-code` / `join`; `spt pair` deleted) · **elevation-gated membership mutations** (ADR-0005 amendment) · **node labels** (hostname-default, gossiped, addressable in `@node` qualifiers, refuse-on-ambiguity) · `spt poll` removed (`ready` + `ready --once`) · **`spt how-to <topic>`** in-binary agent guidance · quickstart rewritten as human-pairs / agents-message prompt blocks · README/docs-site sweep + `quickstart_e2e` + the twohost ladder's product-surface join rung. **Acceptance ran on REAL hardware:** fresh two-machine setup via `subnet create` + the user's own fully-guided join (elevation prompts honored), `status --nodes` labeled+online both sides, cross-machine `SENT(WAN)` both directions (HFENDULEAM ↔ gravity-linux). The run surfaced two publish-blocking field bugs, **both fixed at closeout**: elevated daemon spawn → the de-elevation seam (KNOWN-HAZARDS 5.7) and immortal ghost registry rows → silent-peer eviction (KNOWN-HAZARDS 4.10), plus refusals now rendering copy-paste qualified targets. Harvest (Linux elevation model, NTP-first clock, firewall registration at install, post-join address seeding, trust-prune verb, M8 nounification spec) → `docs/DEFERRED.md`. **Next:** `spt-claude-code` scoping (the v1 acceptance proof, own repo).

+- **🔜 NEXT milestone (planned 2026-06-09) — Broker/brain process-isolation restoration.** Design: [`docs/BROKER-BRAIN-SPLIT-RESTORATION.md`](./docs/BROKER-BRAIN-SPLIT-RESTORATION.md) (verified-with-amendments by `doyle`); decision: **ADR-0018**. Corrects an unintended regression — the broker/brain split (ADR-0004) was specced + spiked as two *processes*, but the production daemon runs the broker as an in-process *thread* (`daemon.rs:165-170`), so `spt update apply` swaps the binary on disk yet never restarts the running code. Every release to date needed a manual fleet daemon bounce (paid 3× on v0.3.2; `enlyzeam` reproduced the `\r`-corruption for ~a day with the fix on disk). Restore the two-process model: broker = always-up per-machine anchor (seed-lock + liveness + brain supervisor) spawning the brain as a child; update = brain snapshot+self-exit → broker respawns from the swapped binary → re-attach; durable absolute-deadline loop timing; broker cursor-of-record with all-session resume; readiness-gated auto-rollback to last-known-good (two-phase applied record + the no-pre-ready-migration invariant); broker-owned generation custody; the §B-adjudicated direct-call→versioned-IPC-verb surface (net near-free, shellwake stays brain, new-brain × old-broker N-1 compat test). Re-points the regression-masked REQ-DAEMON-2 / REQ-UPD-3 `int` evidence to a productionized SPIKE-01/03 process-level survival E2E; mints `REQ-HAZARD-BROKER-PROCESS-ISOLATION` + `REQ-HAZARD-ROLLBACK-STATE-COMPAT` (KNOWN-HAZARDS 6.7/6.8). **Sequenced before `spt-claude-code`** (operator 2026-06-09): the *last* release needing a manual bounce, so every adapter-era release rolls seamlessly and the adapter lands on the final topology; changes daemon internals only, leaving the M8-frozen CLI/api surface untouched.

+> _Roadmap note: this milestone list is behind reality — M8 (CLI nounification + acceptance) and the v0.3.x release line shipped after M7 but are tracked in commits/CHANGELOG, not itemized here. A full roadmap reconciliation is a separate docs pass._

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


All artifacts written. Per the traceability contract, running `traceable-reqs check` to confirm the new mints keep the gate green:


Bash

Run traceable-reqs check after REQ mints


  [OK] REQ-SUBNET-6  required: [impl, unit]  stages: -doc +impl +unit -int
       Trust lifecycle verbs, elevation-gated: spt subnet leave <NAME> (membership exit) and spt subnet prune <node> (removes a dead identity's trust + registry rows, killing its dead dials; trust mutation = security surface, REQ-PAIR-6 gate machinery) (M8 decisions 6-7)
  [OK] REQ-SUBNET-7  required: [impl, unit]  stages: -doc +impl +unit -int
       Per-machine re-pair trust overwrite: registry rows carry a hashed stable machine identifier (OS machine id /etc/machine-id|MachineGuid, domain-separated SHA-256 before gossip, spt-minted persisted UUID fallback; additive serde-default field — old rows parse clean); a COMPLETED pairing ceremony presenting the same node label AND machine id as an existing trusted row evicts the superseded identity's trust + registry rows on the seed-holder and replicates the eviction; a gossiped claim alone never evicts trust (M8 decisions 13, 22)
  [OK] REQ-SUBNET-8  required: [impl, unit]  stages: -doc +impl +unit -int
       Status render honesty: zero-subnet text is daemon-aware ('No subnets registered — this node is standalone.' + daemon-running-dependent blurb, never implying messaging works while the daemon is down); hint footer prints on bare spt subnet only (status drops it); a stalled pump is surfaced in subnet status, never rendered implied-healthy (M8 decisions 11-12, 23)
  [OK] REQ-TERM-1  required: [impl, unit]  stages: -doc +impl +unit -int
       Process-supervisor terminal wrapper hosting broker PTYs
  [OK] REQ-TERM-2  required: [impl, unit]  stages: -doc +impl +unit -int
       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: [impl, unit, int]  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
  [OK] REQ-UPD-6  required: [doc, impl, unit, int]  stages: +doc +impl +unit +int
       Platform-targeted update sets and debug rollout: signed multi-platform update metadata, recipient platform selection, channel-scoped monotonic counters, debug-channel opt-in via release-key overlay, local staging plus pull-based peer propagation, and maintainer-only convergence tooling (ADR-0016)
  [OK] REQ-UPD-7  required: [impl, unit]  stages: -doc +impl +unit -int
       Origin-source update bootstrap (`spt update fetch`): pull the latest signed release directly from the GitHub release origin (`SaberMage/spt-releases`) — the per-platform artifact + its `<asset>.release.json` SignedRelease metadata — and stage it through the EXISTING verify→stage pipeline (the same `plan_verified` gate: two-key signature + channel + monotonic rollback floor + SHA-256), after which the normal consent-notif / `spt update apply` flow is unchanged. Closes the peer-only-discovery gap (REQ-UPD-1): a first-in-fleet / isolated node can update with no peer to pull from. The signed-release anchor keeps the GitHub transport untrusted-but-verified.
  [OK] REQ-UPD-8  required: [impl, unit]  stages: -doc +impl +unit -int
       Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3.2 single, or a single stamped for another OS, fail-safe refuses — the guard that alone prevents the v0.3.1 brick where a Linux ELF was applied as `spt.exe`). UX: a friendly post-apply message (`Updated spt-core to vX.Y.Z.` + changelog URL) driven by an additive `product_version` metadata field, with a release-counter fallback when absent.


Tail shows all `[OK]`. Confirming the overall verdict (exit code + no failures + the two new ids parsed):


traceable-reqs check > /tmp/tr.txt 2>&1; echo "EXIT=$?"; grep -iE 'FAIL|ERROR|missing|invalid|panic' /tmp/tr.txt | head; echo "---new ids---"; grep -iE 'BROKER-PROCESS-ISOLATION|ROLLBACK-STATE-COMPAT' /tmp/tr.txt; echo "---summary tail---"; tail -3 /tmp/tr.txt

Confirm traceable-reqs exit code + new ids + no failures


EXIT=0
       Peer address seeding, both cold starts: durable peer-addrs.json (identity dir) maps peer pubkey → last-known dialable address; the pump's resolver consults it FIRST with id-only discovery fallback on miss or dial failure (a stale addr never strands a peer); written by the pairing ceremony (both sides, from the live connection) and by the pump on successful connect; post-join first sync and post-restart resync converge in seconds, not ~1 min (M8 decisions 14, 20)
       Pump liveness: the peer pump writes a last-tick heartbeat consumed by daemon status / subnet status (decision 23 render legs in REQ-CLI-2/REQ-SUBNET-8); the daemon supervises the pump task — a panic is caught, logged loudly, and the pump restarts with capped backoff (≤5 min), so a 5.9-class death self-heals visibly instead of silently halving the daemon (M8 decision 23; field motivation: hfenduleam 2026-06-07 half-death)
       Net-bind boot-race resilience: a daemon that comes up net-less (NetHost::start failed — e.g. the systemd unit autostarted before the network/DNS stack was ready, `Failed to create an address lookup service`) must SELF-HEAL — retry the net bring-up in the background with capped backoff and, on success, attach net to the broker + spawn the dispatcher/peer-pump (which today are gated on `net_up` at boot and so never start, leaving the node silently unreachable until a manual restart — kitsubito 2026-06-08). Status surfaces the net-less state honestly (a net-less broker renders as 'no connection', not only a pump-STALLED line with a bogus pre-boot heartbeat age). The installer's autostart unit waits for the network (`Wants=/After=network-online.target`) as belt-and-suspenders.
       A surfaced concurrent context pair is durably preserved (both versions, tracked artifacts) until a strictly dominating write clears it; no reconcile failure path discards an unmerged version (6.6, ADR-0013)
       Two-slice envelope parser is panic-free and tolerant (4.2)
       Scheduling never subtracts a Duration from Instant::now() (underflow-panics on a host booted more recently than the offset); 'due now / never run' is Option<Instant>=None gated on forward duration_since only (5.9)
       Concurrent SQLite openers (registry/spool) must not fail with 'database is locked' (4.7)
       Stale registry entries degrade to fallback, never hard-fail (4.3)
       NTP TOTP offset: the pairing ceremony queries NTP at ceremony time (both sides) and applies the derived offset to the TOTP calculation in-process only; system-clock fallback when NTP is unreachable (offline LAN pairing unaffected — NTP failure never blocks a pairing that succeeds today); never sets the OS clock; no background sync loop (M8 decision 18; field trigger: enlyzeam clock >1 min off exceeds the ±1 window)
       Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3.2 single, or a single stamped for another OS, fail-safe refuses — the guard that alone prevents the v0.3.1 brick where a Linux ELF was applied as `spt.exe`). UX: a friendly post-apply message (`Updated spt-core to vX.Y.Z.` + changelog URL) driven by an additive `product_version` metadata field, with a release-counter fallback when absent.
---new ids---
  [OK] REQ-HAZARD-BROKER-PROCESS-ISOLATION  required: []  stages: -doc -impl -unit -int
  [OK] REQ-HAZARD-ROLLBACK-STATE-COMPAT  required: []  stages: -doc -impl -unit -int
---summary tail---
       Origin-source update bootstrap (`spt update fetch`): pull the latest signed release directly from the GitHub release origin (`SaberMage/spt-releases`) — the per-platform artifact + its `<asset>.release.json` SignedRelease metadata — and stage it through the EXISTING verify→stage pipeline (the same `plan_verified` gate: two-key signature + channel + monotonic rollback floor + SHA-256), after which the normal consent-notif / `spt update apply` flow is unchanged. Closes the peer-only-discovery gap (REQ-UPD-1): a first-in-fleet / isolated node can update with no peer to pull from. The signed-release anchor keeps the GitHub transport untrusted-but-verified.
  [OK] REQ-UPD-8  required: [impl, unit]  stages: -doc +impl +unit -int
       Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3.2 single, or a single stamped for another OS, fail-safe refuses — the guard that alone prevents the v0.3.1 brick where a Linux ELF was applied as `spt.exe`). UX: a friendly post-apply message (`Updated spt-core to vX.Y.Z.` + changelog URL) driven by an additive `product_version` metadata field, with a release-counter fallback when absent.


`traceable-reqs check` → **EXIT=0**, no failures; both new mints parsed `[OK] required: []` (inactive, correct). Gate green.

**Artifacts written (all gated items done):**
| Artifact | What |
|---|---|
| `docs/adr/0018-...restoration.md` | New ADR — drift finding + Q2–Q8 + the 6 amendments, extends ADR-0004 |
| `docs/KNOWN-HAZARDS.md §6.7, §6.8` | The in-process-collapse regression + the rollback-state-compat invariant |
| `traceable-reqs.toml` | 2 new `REQ-HAZARD-*` mints (inactive; activate at the milestone) |
| `ROADMAP.md` | Restoration recorded as the next planned milestone + a drift note |
| `docs/BROKER-BRAIN-SPLIT-RESTORATION.md` | Status → **verified-with-amendments**; V1–V6 folded; truncation bug reattributed to legacy |
| `CONTEXT.md` | (earlier) one-broker-per-machine clause |

Now communing for next-session resume, per your instruction:


Skill

Immediate next steps + broad project status + end goal, for resume next session.

IMMEDIATE NEXT STEPS:
- The broker/brain split RESTORATION is the verified next milestone. All design artifacts are written and traceable-reqs is green (EXIT=0). Next session: kick off the restoration milestone — write its JIT/M-plan, which ACTIVATES the two new REQ-HAZARD mints (REQ-HAZARD-BROKER-PROCESS-ISOLATION, REQ-HAZARD-ROLLBACK-STATE-COMPAT) and RE-POINTS the regression-masked int evidence on REQ-DAEMON-2 + REQ-UPD-3 to a productionized SPIKE-01/03 process-level survival E2E.
- Reference docs: docs/BROKER-BRAIN-SPLIT-RESTORATION.md (verified-with-amendments by doyle, decisions Q2-Q8 + amendments V1-V6), docs/adr/0018-broker-brain-process-isolation-restoration.md, KNOWN-HAZARDS 6.7/6.8.
- Side items (separate, low priority): route the multipart-message-truncation bug to the LEGACY claude_skill_owl project (it is NOT an spt-core bug — spt-core's own reassembly is covered by REQ-HAZARD-EVENTPART-REASSEMBLY); reconcile ROADMAP.md (M8/v0.3.x shipped but not itemized); clean the stale DEFERRED mesh row (mesh shipped v0.3.0).

PROJECT STATUS:
- spt-core v0.3.2 SIGNED + PUBLISHED and verified end-to-end across the live 3-node fleet (kitsubito Linux, hfenduleam Windows, enlyzeam Windows/gerald) — the cross-OS update-set brick (REQ-UPD-8) is fixed; each node correctly pulled ITS platform's artifact. The original CR-corruption bug (Windows \r mangling the EVENT envelope) that opened this session was already shipped fixed in v0.3.1 (REQ-HAZARD-ENVELOPE-CR-LINESAFE). M8 acceptance complete.
- HEADLINE FINDING this session: the broker/brain split (ADR-0004) is unrealized in production — the broker runs as an in-process thread, not a separate process (daemon.rs:165-170), so self-update swaps the binary on disk but never restarts the running code. Every release to date needed a manual fleet daemon bounce; enlyzeam ran 0.3.0 with 0.3.2 on disk for ~a day. Fully grilled, designed, independently verified, and documented — ready to implement.

END GOAL:
A harness-independent core for an agent ecosystem (messaging, live-agent lifecycle, terminal hosting, first-class P2P networking, runtime-manifest harness contract) — the same agent running natively across all of a user's machines with a synced mind, reachable and operable from anywhere, with SEAMLESS self-update and zero-config cross-machine messaging. First thing built atop it: a rebuilt spt plugin (spt-claude-code, separate repo) at parity with today's SPT. The restoration milestone is precisely what makes self-update seamless (today it needs manual bounces) — it is sequenced BEFORE spt-claude-code so the adapter era lands on the final daemon topology and every adapter-era release rolls without a manual bounce.


Launching skill: spt:commune


Base directory for this skill: C:\Users\decid\.ccs\instances\bigscreen\plugins\cache\cplugs\spt\1.11.25\skills\commune

# /spt:commune

All commands use `$LIVE` env var, auto-injected by the plugin's SessionStart hook. If commands fail with "command not found", restart the Claude Code session so the SessionStart hook re-runs.

> **Identity auto-detection:** Your identity is auto-detected from your session. Pass your ID explicitly only if auto-detection fails.

## Flow

1. **Download Psyche's *current* context** — it shifts during the session as background echo communes fire. Re-download right before composing; the gen-N snapshot you read at session start is already stale:
   ```bash
   $LIVE psyche-download <your-id>
   ```

   > **Output truncation.** If the Bash tool reports `Output too large (N KB)` and saves the full result to a `tool-results/<id>.txt` file, use the `Read` tool on that saved path to access the full content. Do NOT re-run `psyche-download` to grep/awk out sections — repeated invocations re-download the same context, waste tokens, and may duplicate `## Pending Commune` sections in `live_context.md`.

2. **Diff your knowledge against this fresh download** — only what's missing from *it* is the real delta (new work, decisions, intentions). Anything already absorbed is not. If the download includes a `<memformat>` section, use it as a guide for what topics to cover. A `## Pending Commune (uncommitted)` section means a previous commune is still queued — do NOT re-send it.

3. **Commune only the delta** -- use the **Write tool** to create
   `.claude/{your-id}-commune.md` with the two-slice body as its contents
   (see `## Two-slice body shape (Phase 25 D-10/D-11)` below for the required
   envelope wrapping). Do not use Bash/heredoc -- Write is the canonical path:
   no shell escaping, no `EOF` collision risk, and Write auto-creates the
   `.claude/` parent directory.

   Your Self listener detects the file on its next poll iteration, notifies
   your Psyche wrapper, and the wrapper ingests the content via the existing
   Psyche session. On success (subprocess exit code 0) the wrapper deletes the
   file. On error (token rate limit, etc) the file is retained and retried on
   the next consume cycle. If you write again before the wrapper consumes, the
   latest write wins (last-write-wins overwrite semantics).

If Psyche's context is already current, no commune is needed. Say so and move on.

Read `commune.md` (in this skill directory) for the full commune protocol -- when to send, what to include, and diligence triggers.

**Agent-type guard:** Only works on live agents. If target is not live, refuses with: "Communes require a live agent with Psyche."

## Two-slice body shape (Phase 25 D-10/D-11)

The body you write in `.claude/{your-id}-commune.md` MUST itself be wrapped in two-slice envelopes per Phase 25 D-10/D-11. The taxonomy — what belongs in `<live-context>` vs `<project-context>` — is defined canonically in `psyche.md` §`<output_envelope>` (lines 282–323). Read that section as the source of truth; this skill doc teaches only when and how to apply it.

**In-project detection rule (D-25.1-04).** Before composing your commune body, run `$LIVE psyche-download <your-id>` (Step 1 of `## Flow` above) and inspect the `<current ... />` tag at the top of the output. The `<current/>` tag carries a `project="..."` attribute whenever any payload is produced — its presence means you are inside a tracked project (inside a git repo, `project` is the repo name; outside a git repo per D-11, `project` is the cwd-basename fallback, which still counts as a project context for routing purposes). A `psyche-download` invocation that returns no payload at all (NO-CONTEXT exit) means no project resolved. So: `project="..."` populated on `<current/>` → emit BOTH envelopes (apply rule 1 or 2 below); no payload at all → emit ONLY `<live-context>` (rule 3). Secondary signal: the `<project-context-resolved name="..."/>` sentinel further down the output indicates the project file already has prior content — useful for spotting first-time-in-project pairings, but NOT the routing rule.

**Three rules (D-25.1-01..03):**

- **In-project, both slices populated** (marker present, you have project-specific content this cycle): emit `<live-context>...</live-context>` AND `<project-context>...</project-context>`, both with non-empty bodies.
- **In-project, no project-specific update this cycle** (marker present, but nothing project-bound to report): emit `<live-context>...</live-context>` AND `<project-context></project-context>` (empty body — the deliberate "in-project but quiet" signal per D-25.1-02). Distinguishable from "no project resolved".
- **Outside any tracked project** (marker absent): emit ONLY `<live-context>...</live-context>`. Do NOT emit an empty `<project-context>` envelope (missing tag means "no project resolved" per D-25.1-03).

**Nested envelopes.** The Phase 23 `<EVENT type="commune">` envelope (see `## Envelope shape (Phase 23 v1.8)` below) is composed by `$LIVE commune` automatically — you NEVER write `<EVENT>` tags. You write ONLY the inner two-slice body. The two envelope layers nest: outer EVENT wraps inner two-slice body.

(See `psyche.md` §`<output_envelope>` for the canonical taxonomy and per-slice content rules.)

## Envelope shape (Phase 23 v1.8)

As of v1.8 Phase 23, commune deliveries are wrapped in a typed EVENT envelope carrying a 5-field stamp (`machine`, `project`, `branch`, `head_sha`, `head_subject`); the prose `COMMUNE (timestamp): body` form is gone.

```xml
<EVENT type="commune" timestamp="2026-05-20T06:00:00-07:00" machine="hostname" project="claude_skill_owl" branch="main" head_sha="abc123..." head_subject="subject line capped at 72 chars">body</EVENT>
```

Outside a git repo (D-11), `branch`, `head_sha`, and `head_subject` are omitted entirely (not empty-string-set):

```xml
<EVENT type="commune" timestamp="2026-05-20T06:00:00-07:00" machine="hostname" project="cwd-basename">body</EVENT>
```

`$LIVE commune` composes this envelope automatically — Self does NOT manually construct EVENT XML. The `body` placeholder in the example above is the two-slice body (see `## Two-slice body shape (Phase 25 D-10/D-11)` above); the two envelope layers nest. The wrapper-side parser ignores unknown attrs, so the stamp fields are forward-compatible with older receivers. For drift detection on resume, see the `live` skill.

ARGUMENTS: Immediate next steps + broad project status + end goal, for resume next session.

IMMEDIATE NEXT STEPS:
- The broker/brain split RESTORATION is the verified next milestone. All design artifacts are written and traceable-reqs is green (EXIT=0). Next session: kick off the restoration milestone — write its JIT/M-plan, which ACTIVATES the two new REQ-HAZARD mints (REQ-HAZARD-BROKER-PROCESS-ISOLATION, REQ-HAZARD-ROLLBACK-STATE-COMPAT) and RE-POINTS the regression-masked int evidence on REQ-DAEMON-2 + REQ-UPD-3 to a productionized SPIKE-01/03 process-level survival E2E.
- Reference docs: docs/BROKER-BRAIN-SPLIT-RESTORATION.md (verified-with-amendments by doyle, decisions Q2-Q8 + amendments V1-V6), docs/adr/0018-broker-brain-process-isolation-restoration.md, KNOWN-HAZARDS 6.7/6.8.
- Side items (separate, low priority): route the multipart-message-truncation bug to the LEGACY claude_skill_owl project (it is NOT an spt-core bug — spt-core's own reassembly is covered by REQ-HAZARD-EVENTPART-REASSEMBLY); reconcile ROADMAP.md (M8/v0.3.x shipped but not itemized); clean the stale DEFERRED mesh row (mesh shipped v0.3.0).

PROJECT STATUS:
- spt-core v0.3.2 SIGNED + PUBLISHED and verified end-to-end across the live 3-node fleet (kitsubito Linux, hfenduleam Windows, enlyzeam Windows/gerald) — the cross-OS update-set brick (REQ-UPD-8) is fixed; each node correctly pulled ITS platform's artifact. The original CR-corruption bug (Windows \r mangling the EVENT envelope) that opened this session was already shipped fixed in v0.3.1 (REQ-HAZARD-ENVELOPE-CR-LINESAFE). M8 acceptance complete.
- HEADLINE FINDING this session: the broker/brain split (ADR-0004) is unrealized in production — the broker runs as an in-process thread, not a separate process (daemon.rs:165-170), so self-update swaps the binary on disk but never restarts the running code. Every release to date needed a manual fleet daemon bounce; enlyzeam ran 0.3.0 with 0.3.2 on disk for ~a day. Fully grilled, designed, independently verified, and documented — ready to implement.

END GOAL:
A harness-independent core for an agent ecosystem (messaging, live-agent lifecycle, terminal hosting, first-class P2P networking, runtime-manifest harness contract) — the same agent running natively across all of a user's machines with a synced mind, reachable and operable from anywhere, with SEAMLESS self-update and zero-config cross-machine messaging. First thing built atop it: a rebuilt spt plugin (spt-claude-code, separate repo) at parity with today's SPT. The restoration milestone is precisely what makes self-update seamless (today it needs manual bounces) — it is sequenced BEFORE spt-claude-code so the adapter era lands on the final daemon topology and every adapter-era release rolls without a manual bounce.


Following the commune protocol — first re-download Psyche's current context to diff the delta + detect project routing:


Bash

$LIVE psyche-download todlando 2>&1 | head -50

Download current Psyche context for commune diff


<memformat>
<?xml version="1.0"?>
<memformat version="1">
<!-- Changelog: v1 - initial seed -->
<topics>
  <topic name="events">Notable or unusual events and requests</topic>
  <topic name="interactions">Agent interactions -- who, roles, contributions</topic>
  <topic name="reflections">Thoughts on recent work -- what went well, what was tricky</topic>
  <topic name="ideas">Ideas to raise with the user</topic>
  <topic name="improvements">Further improvement ideas for recent problem spaces</topic>
  <topic name="timeline">What happened when, timing, sequencing</topic>
  <topic name="blockers">Current blockers or concerns</topic>
  <topic name="focus">Current focus and immediate next steps</topic>
</topics>
</memformat>
</memformat>

<current machine="HFENDULEAM" project="spt-core" branch="main" head_sha="cabdc614fc0416e3a57de0a21b229db485089320" head_subject="release: prep v0.3.2 (version bump + CHANGELOG)" commits_since="0" commits_unpulled="0"/>
**Artifact: BROKER-BRAIN-SPLIT-RESTORATION.md — verified-with-amendments**. Verification session (Doyle source-level audit) completed. Six amendments accepted and folded in (marked [V1]–[V6]): rollback-state-compat invariant, generation custody rule (broker owns counter, versioned argv/hello), alarm machinery deferred to spec-phase, §6 wording tightened, REQ-UPD-3 mis-evidenced (current tags only prove in-process handoff, not real process swap — needs SPIKE-01/03 int stage), N-1 compat CI gap identified. Plus new delivery bug: inter-agent multipart messages silently truncate tail (~8 of 18 parts reached), KH §4 envelope family. Awaiting user ratification before ADR/REQ/KNOWN-HAZARDS.

**Last commune:** 2026-06-09T17:36 — HFENDULEAM main @ cabdc614 (no new commits; verification+[V1–V6] folded; awaiting ratification → ADR/REQ/KNOWN-HAZARDS)<project-context-resolved name="spt-core"/>
**Implementation roadmap:** Blocked on verification → ratification. Post-ratification: (1) ADR-0004 amendment (spec vs impl drift), (2) REQ stubs for rollback-compat + generation custody + alarm machinization + N-1 compat + SPIKE-01/03 (real process swap), (3) KNOWN-HAZARDS entry (delivery truncation + missing int stage). V5/V6 require implementation work outside scope of this session.


Psyche is `project="spt-core"` (in-project → both slices) but **stale**: it still says "awaiting ratification" and "blocked on verification → ratification." The delta to commune: the operator **ratified**, the artifacts are **written**, the gate is **green**, and the truncation bug was **reattributed to legacy**. Writing the delta:


Write

<live-context>
Delta since the 17:36 commune (which you have as "awaiting ratification — blocked"): that block is CLEARED. The operator ratified everything and I wrote the artifacts this same session.

What I (todlando) did after the last commune:
- Closed the Doyle verification thread (deduped a truncated resend; all 6 amendments accepted on the merits).
- Operator RATIFIED both open decisions via AskUserQuestion: (1) sequencing = restoration is the NEXT milestone, before spt-claude-code; (2) write the §9 artifacts now.
- Wrote all artifacts and confirmed `traceable-reqs check` EXIT=0 (green).
- One operator correction absorbed: the multipart-message-truncation bug is a LEGACY claude_skill_owl bug, NOT spt-core — reattributed in the design doc §7; spt-core's own reassembly is already covered by REQ-HAZARD-EVENTPART-REASSEMBLY. Do not file it as an spt-core artifact.

My immediate next focus (next session): KICK OFF the restoration milestone — write its JIT/M-plan. That plan ACTIVATES the two new REQ-HAZARD mints and RE-POINTS the regression-masked int evidence on REQ-DAEMON-2 + REQ-UPD-3 to a productionized SPIKE-01/03 process-level survival E2E. No longer "blocked on ratification" — it's ratified; the next step is planning the build.
</live-context>
<project-context>
spt-core — broker/brain split restoration is RATIFIED and DOCUMENTED; ready to plan the build next session.

Artifacts written this session (HFENDULEAM, main @ cabdc614; uncommitted working-tree changes):
- docs/adr/0018-broker-brain-process-isolation-restoration.md — NEW ADR, extends ADR-0004: the in-process-collapse regression + decisions Q2–Q8 + amendments V1–V6.
- docs/KNOWN-HAZARDS.md — NEW §6.7 (REQ-HAZARD-BROKER-PROCESS-ISOLATION: broker & brain must be separate processes; in-process thread silently unrealizes REQ-UPD-3) + §6.8 (REQ-HAZARD-ROLLBACK-STATE-COMPAT: no irreversible durable-state migration before ready-promotion).
- traceable-reqs.toml — 2 new REQ-HAZARD mints, required_stages = [] (INACTIVE; activate at the milestone). Gate green (EXIT=0).
- ROADMAP.md — restoration recorded as the next planned milestone (before spt-claude-code) + a note that the roadmap is behind (M8/v0.3.x not itemized).
- docs/BROKER-BRAIN-SPLIT-RESTORATION.md — status → verified-with-amendments; V1–V6 folded; truncation bug reattributed to legacy.
- CONTEXT.md — one-broker-per-machine clause (not one-per-endpoint).

Restoration milestone plan (next session): write JIT/M-plan → activate REQ-HAZARD-BROKER-PROCESS-ISOLATION + REQ-HAZARD-ROLLBACK-STATE-COMPAT; re-point REQ-DAEMON-2 + REQ-UPD-3 int evidence (currently prove only the in-process handoff) to a productionized SPIKE-01/03 E2E proving PTY-child + live-QUIC survival across a real brain-PROCESS restart onto a swapped binary; build the N-1 new-brain × old-broker verb-surface CI compat test.

Project status: v0.3.2 signed+published, verified end-to-end across the 3-node fleet (kitsubito/hfenduleam/enlyzeam); REQ-UPD-8 cross-OS brick fixed; the original CR-corruption fixed back in v0.3.1; M8 acceptance complete. End goal unchanged: the same agent native across all the user's machines with synced mind + seamless self-update + zero-config cross-machine messaging; first build atop it = spt-claude-code (separate repo). The restoration is what makes self-update actually seamless (today every release needs a manual fleet daemon bounce) — hence sequenced before the adapter era.

Low-priority side items: route the truncation bug to the legacy project; reconcile ROADMAP (M8 not itemized); clean the stale DEFERRED mesh row (mesh shipped v0.3.0).
</project-context>


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

<live-context>
Delta since the 17:36 commune (which you have as "awaiting ratification — blocked"): that block is CLEARED. The operator ratified everything and I wrote the artifacts this same session.

What I (todlando) did after the last commune:
- Closed the Doyle verification thread (deduped a truncated resend; all 6 amendments accepted on the merits).
- Operator RATIFIED both open decisions via AskUserQuestion: (1) sequencing = restoration is the NEXT milestone, before spt-claude-code; (2) write the §9 artifacts now.
- Wrote all artifacts and confirmed `traceable-reqs check` EXIT=0 (green).
- One operator correction absorbed: the multipart-message-truncation bug is a LEGACY claude_skill_owl bug, NOT spt-core — reattributed in the design doc §7; spt-core's own reassembly is already covered by REQ-HAZARD-EVENTPART-REASSEMBLY. Do not file it as an spt-core artifact.

My immediate next focus (next session): KICK OFF the restoration milestone — write its JIT/M-plan. That plan ACTIVATES the two new REQ-HAZARD mints and RE-POINTS the regression-masked int evidence on REQ-DAEMON-2 + REQ-UPD-3 to a productionized SPIKE-01/03 process-level survival E2E. No longer "blocked on ratification" — it's ratified; the next step is planning the build.
</live-context>
<project-context>
spt-core — broker/brain split restoration is RATIFIED and DOCUMENTED; ready to plan the build next session.

Artifacts written this session (HFENDULEAM, main @ cabdc614; uncommitted working-tree changes):
- docs/adr/0018-broker-brain-process-isolation-restoration.md — NEW ADR, extends ADR-0004: the in-process-collapse regression + decisions Q2–Q8 + amendments V1–V6.
- docs/KNOWN-HAZARDS.md — NEW §6.7 (REQ-HAZARD-BROKER-PROCESS-ISOLATION: broker & brain must be separate processes; in-process thread silently unrealizes REQ-UPD-3) + §6.8 (REQ-HAZARD-ROLLBACK-STATE-COMPAT: no irreversible durable-state migration before ready-promotion).
- traceable-reqs.toml — 2 new REQ-HAZARD mints, required_stages = [] (INACTIVE; activate at the milestone). Gate green (EXIT=0).
- ROADMAP.md — restoration recorded as the next planned milestone (before spt-claude-code) + a note that the roadmap is behind (M8/v0.3.x not itemized).
- docs/BROKER-BRAIN-SPLIT-RESTORATION.md — status → verified-with-amendments; V1–V6 folded; truncation bug reattributed to legacy.
- CONTEXT.md — one-broker-per-machine clause (not one-per-endpoint).

Restoration milestone plan (next session): write JIT/M-plan → activate REQ-HAZARD-BROKER-PROCESS-ISOLATION + REQ-HAZARD-ROLLBACK-STATE-COMPAT; re-point REQ-DAEMON-2 + REQ-UPD-3 int evidence (currently prove only the in-process handoff) to a productionized SPIKE-01/03 E2E proving PTY-child + live-QUIC survival across a real brain-PROCESS restart onto a swapped binary; build the N-1 new-brain × old-broker verb-surface CI compat test.

Project status: v0.3.2 signed+published, verified end-to-end across the 3-node fleet (kitsubito/hfenduleam/enlyzeam); REQ-UPD-8 cross-OS brick fixed; the original CR-corruption fixed back in v0.3.1; M8 acceptance complete. End goal unchanged: the same agent native across all the user's machines with synced mind + seamless self-update + zero-config cross-machine messaging; first build atop it = spt-claude-code (separate repo). The restoration is what makes self-update actually seamless (today every release needs a manual fleet daemon bounce) — hence sequenced before the adapter era.

Low-priority side items: route the truncation bug to the legacy project; reconcile ROADMAP (M8 not itemized); clean the stale DEFERRED mesh row (mesh shipped v0.3.0).
</project-context>


Commune written to `.claude/todlando-commune.md` — Psyche will ingest on the next poll and the stale "awaiting ratification / blocked" state gets cleared. Session work complete.

## Wrap — ready for next-session resume

**This session, end to end:**
1. Diagnosed the original bug → CR-corruption already shipped fixed in **v0.3.1**.
2. Drove the **v0.3.2** release ceremony (signed, published) + verified the cross-OS update-set fix end-to-end across all 3 fleet nodes.
3. That verify surfaced the **headline finding** — the broker/brain split (ADR-0004) is unrealized: broker runs in-process, so self-update never restarts the running code.
4. Grilled the full **restoration design** (Q2–Q8), got it **independently verified** by doyle (verified-with-amendments, V1–V6), and you **ratified** it.
5. Wrote all the durable artifacts; `traceable-reqs` green; communed for resume.

**Decisions locked:** restoration = **next milestone**, before `spt-claude-code`.

**Next session picks up at:** kick off the restoration milestone — write its JIT/M-plan, which activates the two new `REQ-HAZARD` mints and re-points the regression-masked `REQ-DAEMON-2`/`REQ-UPD-3` int evidence to a productionized SPIKE-01/03 process-level survival test.

**One housekeeping note:** all the artifacts are **uncommitted working-tree changes** (alongside the pre-existing `Cargo.lock`/`Cargo.toml`/`cli.rs` from session start). I didn't commit — say the word next session and I'll stage a clean commit. Resume whenever.


commit now


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


On it. Checking exact state + the repo's commit trailer convention before staging only my doc artifacts (not the pre-existing `Cargo.*`/`cli.rs` WIP, not the transient commune drop):


Bash

git status --short && echo "---trailer in recent commits---" && git log -3 --format='%h%n%b%n==='

Check git status + recent commit trailer convention


 M CONTEXT.md
 M ROADMAP.md
 M docs/DEFERRED.md
 M docs/KNOWN-HAZARDS.md
 M traceable-reqs.toml
?? docs/BROKER-BRAIN-SPLIT-RESTORATION.md
?? docs/adr/0018-broker-brain-process-isolation-restoration.md
---trailer in recent commits---
cabdc61
Workspace 0.3.1 → 0.3.2 and the user-facing CHANGELOG section (the verbatim
GitHub Release body): the platform-safe update fetch + apply guard fix and the
friendly apply message. Release counter for v0.3.2 = 6 (signed by todlando).

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

===
a65b508
v0.3.1 cross-OS brick fix. `spt update fetch` staged a single, platform-blind
`SignedRelease`; a peer pulled it over P2P and `apply` swapped a Linux ELF in
as `spt.exe` (no platform check in the single path) — bricking hfenduleam.

- fetch (cli): download the signed multi-platform `update-set.json` + every
  platform artifact it names, verify each digest, stage the SET. Local apply
  selects `current_platform()`; a P2P re-serve lets each peer select its own.
  No single-release fetch fallback — a missing update-set.json points at the
  installer rather than staging a platform-blind single.
- apply guard (applyhost): the Single path refuses unless the cache carries a
  platform stamp equal to this node's platform. An unstamped single (every
  P2P-pulled single, and any pre-v0.3.2 stage) or a single stamped for another
  OS fail-safe refuses — this guard alone would have stopped the brick.
- relcache: a `platform.json` stamp written only by `stage_single_with_platform`
  (a path that truthfully knows the artifact's platform); a bare `stage()` and
  `stage_update_set()` clear it, so an unstamped single can never go stale.
- product_version (release.rs): additive `#[serde(default)]` field on
  ReleaseMetadata + UpdateSetMetadata, driving a friendly post-apply message
  (`Updated spt-core to vX.Y.Z.` + changelog URL; release-counter fallback).
- xtask release-publish: build + sign + verify + upload `update-set.json`
  covering both platforms, stamped with product_version from the tag.

unit: fetch URL/asset derivation; apply refuses platform-blind/mismatched
single + applies a matching-platform single; the message render. propagate
left unchanged — the apply guard is the backstop for any legacy single.

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

===
4e774a3
v0.3.1's `spt update fetch` staged a single-platform SignedRelease (platform-less
ReleaseMetadata) → a Windows peer pulled the Linux artifact over P2P and applied
an ELF as spt.exe (bricked, recovered). Plan: fetch stages the multi-platform
SignedUpdateSet (reuse REQ-UPD-6 platform selection) + a defense-in-depth apply
platform guard + a friendly apply message ("Updated spt-core to vX.Y.Z" + changelog
URL, needs a product_version semver field). Mint REQ-UPD-8. Implement next session.

DO NOT use `spt update fetch` until v0.3.2 ships — it bricks cross-platform peers.

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

===


Different from session start — the `Cargo.*`/`cli.rs` WIP is gone (committed in the release flow), and `docs/DEFERRED.md` shows modified though I didn't touch it. Also the psyche stamp says branch `main`, but session-start said `dev-freeform`. Checking the live branch + the DEFERRED.md change before I stage anything:


echo "BRANCH: $(git branch --show-current)"; echo "---DEFERRED.md diff (not mine — inspecting)---"; git diff docs/DEFERRED.md | head -40

Confirm current branch + inspect unexplained DEFERRED.md change


BRANCH: main
---DEFERRED.md diff (not mine — inspecting)---
diff --git a/docs/DEFERRED.md b/docs/DEFERRED.md
index ffe0d5e..031e43b 100644
--- a/docs/DEFERRED.md
+++ b/docs/DEFERRED.md
@@ -35,3 +35,5 @@ Intended features explicitly cut from v1 scope but committed for later. Not back
 | **Subnet full-mesh visibility (PLANNED MILESTONE — `SUBNET-MESH-PLAN.md`)**: non-directly-paired members never see each other — a subnet is currently the PAIRING GRAPH, not a mesh (A↔B, C↔B; B offline → A and C invisible; even with B online, B doesn't relay). Root: pairwise TOFU trust (`trust.rs`) + own-rows-only gossip / no transitive relay (`peerloop.rs`, KH 4.10/7.5) + no member roster in the pairing seed transfer. FIX = member-AUTHENTICATED subnet data (seed-MAC and/or per-node signatures) unlocking transitive trust (seed-holder = member = trusted) + transitive gossip relay + roster propagation; MUST re-solve eviction/epoch-lease under relay (supersedes KH 4.10's "no transitive gossip" safety assumption). ADR-level; mint REQ-MESH-* first. Folds in the deferred pairing-time hostname capture + post-join address seeding (same roster/ceremony seam) | A→B→C invisibility test (user 2026-06-08) — confirmed two-gap design limitation, not a code defect | **DESIGN RATIFIED 2026-06-08** (grill): symmetric **seed-proof membership** + **roster-only relay** (rows stay own-authored, no third-party relay → KH 4.10/7.5 PRESERVED, not superseded). Full design = **ADR-0017**; reqs = **REQ-MESH-1..6** (inactive); plan = `SUBNET-MESH-PLAN.md`; glossary = CONTEXT §Pairing & trust. Folds in the pairing-time hostname-capture + post-join address-seeding rows (roster fields). Next: plan-phase over REQ-MESH-* | Next milestone — design done 2026-06-08; build pending plan-phase |
 | `xtask debug-converge` — automated convergence watcher for debug rollout (the REQ-UPD-6 first-slice item ADR-0016 names): watch every expected debug-pinned reachable lab node until all report the target version applied, else a per-node timeout table (Offline / StagedAwaitingConsent / BlockedByBrokerResources / Rejected{reason}). Activates REQ-UPD-6's `int` stage. Full build plan: `docs/DEBUG-CONVERGE-PLAN.md` | REQ-UPD-6 first slice (2026-06-06) | The first slice ships manual "Apply and observe" (`DEBUG-ROLLOUT.md`); the watcher's one genuinely new surface is a node status-query wire leg (no-fetch `{channel, applied_version, last_outcome}` over the existing update handshake) — assembly otherwise | First multi-node debug rollout where hand-walking nodes is the bottleneck, or M8 update polish |
 | **Origin-source update bootstrap (`spt update fetch` — pull the latest signed release from GitHub into the existing verify→stage→apply pipeline)**: today update DISCOVERY is **peer-only** (REQ-UPD-1: a daemon pulls offers from roster peers via `request_update`; `propagate.rs`/`peerloop.rs`). There is NO origin fetch, so the **first node in a fleet — or any isolated/solo node — cannot get a new release without a peer that already staged it** (the fleet converges by gossip, but only *after* a maintainer seeds the first node via the release-publish path). The installer DOES fetch from GitHub (`github.com/SaberMage/spt-releases/releases/latest/download/<asset>` + `SHA256SUMS`, `installer/install.sh:57-73`) but only at install time, outside the daemon's signed-release gate, and never re-runs. FIX = a new **origin-fetch leg** that downloads the per-platform artifact + its `<asset>.release.json` (the `SignedRelease` metadata) from the release origin and feeds them through the **SAME** `plan_verified` gate (`verify_metadata` two-key signature/channel/expiry/rollback + `verify_artifact` SHA-256, `update.rs:157`/`release.rs`), then `cache.stage()` — after which the EXISTING consent-notif / `spt update apply` flow is unchanged. So it reuses ~all the machinery; it only adds an HTTPS GET from the origin as an alternate SOURCE to the P2P `request_update` pull. Surface: `spt update fetch [--channel <ch>] [--tag vX.Y.Z]` (default: latest on the node's pinned channel); optionally a daemon-config `origin_check` cadence so a lone node self-bootstraps alongside P2P, then re-propagates to peers normally. **Key design note:** the daemon has **zero HTTP surface today** — all transport is iroh QUIC; `reqwest` is only a transitive dep (via iroh), not reachable. This leg needs a deliberate HTTPS-client decision (add `reqwest`/a minimal client as a direct dep, vs shell out to `curl`/`Invoke-WebRequest` like the installer — fragile). Honor the channel pin (`release-keys.json`) + the monotonic rollback floor; configurable repo (mirror `SPT_INSTALL_REPO`). The two-key signed-release anchor keeps the GitHub transport **untrusted-but-verified** (strictly stronger than the installer's HTTPS+SHA256SUMS-only first fetch). Mint **REQ-UPD-7** (origin-source fetch) first. | Update subsystem v1 (ADR-0016, REQ-UPD family) — P2P-pull only by design | v1 update flow is peer-propagation by design (seed one node, the fleet converges by gossip); origin-fetch is additive and the maintainer seed path covered bootstrap so far. No HTTP client is wired into the daemon yet | First isolated / first-in-fleet node that must update with no peer to pull from; or making `spt update` self-sufficient for solo / single-node users (user flagged the gap 2026-06-08) |
+| **Test broker socket-bind hardening (CI-flake removal)**: `applyhost` tests' `served_broker` helper does `Broker::bind(name).expect("bind broker")` (`crates/spt-daemon/src/applyhost.rs:293`) where `name` = `spt-daemon-d7b-{pid}-{seq}.sock`. Flakes intermittently on the **kitsubito** self-hosted Linux runner (likely AF_UNIX path collision / stale socket file under the runner's reused workspace — Windows named pipes don't hit it). Cost the v0.3.2 release a full red CI run + a `--failed` rerun (2026-06-09); same code passed clean on rerun → flake, not a logic bug. FIX = make the test bind deterministic: unlink-before-bind, OR abstract-namespace socket (Linux `@`-prefixed, no filesystem entry), OR per-test `TempDir` isolation so each bind has a guaranteed-fresh path. Applies to every `served_broker`/socket-name test helper, not just applyhost | Triaged during v0.3.2 release (2026-06-09) — flake, shipped over it | Non-blocking: a real flake that taxes CI (one red cycle + rerun per hit) but never gates a correct build; hardening is test-infra work, not product. Held to keep the v0.3.2 release moving | Next recurrence that blocks/slows a release, or M8 test-infra/CI-reliability polish |
+| **`spt update fetch` single→set shape-upgrade at same version (REQ-UPD-8 transitional trap)**: `cmd_update_fetch` sets the rollback floor = `staged_version` (the monotonic counter), and the gate is a plain `candidate > floor`. A node that fetched+staged version N as a platform **single** (pre-0.3.2 fetch behavior) can never re-fetch version N as a multi-platform **set** — same counter → `Rollback{current:N, candidate:N}`. Bites the **seed-node bootstrap**: the first node to origin-`fetch` a release applies the single for its OWN platform, then can't re-stage the same version as a set → has no Windows/other-platform artifact to SERVE peers over P2P. Surfaced live in the v0.3.2 cross-platform rig verify (2026-06-09): kitsubito staged+applied the v6 single, then its v6-set re-fetch was refused; unblock was a manual `releases/` cache-clear before re-fetch. Counter vs semver is working as designed (counter = trust spine, semver = cosmetic) — the gap is purely shape-blind: the floor compares version only, ignoring single-vs-set. FIX options: (a) allow a same-version re-fetch when staged shape is `single` and an available `set` is strictly-richer (shape-upgrade allowance), or (b) seed-server flow stages the SET before applying its own single, or (c) a `--force`/`--restage` flag. NOTE — only bites the one migration hop onto 0.3.2; once a node is on 0.3.2 with a fresh cache, future releases are vN+1 and stage clean. Mint a REQ before fixing | v0.3.2 cross-platform rig verify (2026-06-09; user-flagged via session watch) | Transitional-only: the workaround (cache-clear before staging the set) is reliable and was used to complete the v0.3.2 fleet roll; a code fix only saves the manual clear on same-version shape upgrades, which won't recur after the 0.3.2 hop | A future release that again changes staged-artifact SHAPE at a same/non-incrementing counter, or making `spt update fetch` self-sufficient for solo seed-nodes (pairs with the origin-source bootstrap row above) |


Two things resolved:
- **On `main`** (the release/verify work moved here — the recent release commits all landed on main, so committing docs here matches repo practice).
- **`docs/DEFERRED.md` is doyle's** — two rows doyle logged from the v0.3.2 verify (the socket-bind CI flake + the single→set rollback trap). Not mine — I'll **exclude** it (doyle commits their own; mixing it under my message would misattribute).

Staging only my six artifacts and committing:


cd "C:/Users/decid/Documents/projects/spt-core" && git add CONTEXT.md ROADMAP.md docs/KNOWN-HAZARDS.md traceable-reqs.toml docs/BROKER-BRAIN-SPLIT-RESTORATION.md docs/adr/0018-broker-brain-process-isolation-restoration.md && git commit -F - <<'EOF'
docs(restoration): broker/brain split regression — design + ADR-0018 + hazards + REQ mints

The v0.3.2 fleet update verify surfaced a regression: the broker/brain split
(ADR-0004) was specced + spiked as two processes, but the production daemon runs
the broker as an in-process thread (daemon.rs:165-170), so `spt update apply`
swaps the binary on disk yet never restarts the running code — every release
needs a manual fleet daemon bounce (enlyzeam ran 0.3.0 with 0.3.2 on disk for
~a day, still reproducing the CR bug the update had fixed).

Grilled the restoration design (Q2-Q8); independently verified (doyle,
verified-with-amendments V1-V6). Decisions: broker = always-up per-machine
anchor + brain supervisor; update = brain snapshot+self-exit → broker respawns
from the swapped binary → re-attach; durable absolute-deadline loop timing;
broker cursor-of-record with all-session resume; readiness-gated auto-rollback
to last-known-good (two-phase applied record + no-pre-ready-migration invariant);
broker-owned generation custody; §B-adjudicated direct-call→versioned-IPC-verb
surface (net near-free, shellwake stays brain, new-brain × old-broker N-1 compat
test). Sequenced as the next milestone, before spt-claude-code.

- docs/BROKER-BRAIN-SPLIT-RESTORATION.md: design rationale, verified-with-amendments
- docs/adr/0018-...: ADR extending ADR-0004
- docs/KNOWN-HAZARDS.md: §6.7 broker-process-isolation + §6.8 rollback-state-compat
- traceable-reqs.toml: REQ-HAZARD-BROKER-PROCESS-ISOLATION + REQ-HAZARD-ROLLBACK-STATE-COMPAT (inactive)
- ROADMAP.md: next-milestone entry; CONTEXT.md: one-broker-per-machine clause

traceable-reqs check green. The regression-masked int evidence on REQ-DAEMON-2 /
REQ-UPD-3 re-points at the milestone.

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

Stage the 6 design artifacts and commit on main


warning: in the working copy of 'docs/BROKER-BRAIN-SPLIT-RESTORATION.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'docs/adr/0018-broker-brain-process-isolation-restoration.md', LF will be replaced by CRLF the next time Git touches it
[main 44497f4] docs(restoration): broker/brain split regression — design + ADR-0018 + hazards + REQ mints
 6 files changed, 306 insertions(+), 1 deletion(-)
 create mode 100644 docs/BROKER-BRAIN-SPLIT-RESTORATION.md
 create mode 100644 docs/adr/0018-broker-brain-process-isolation-restoration.md


Committed — **`main 44497f4`**, 6 files, +306/−1. (LF→CRLF warnings are just Windows line-ending normalization, harmless.)

- My six restoration artifacts only — `docs/DEFERRED.md` left untouched for doyle.
- Local commit on `main`, **not pushed**. Say push and I'll push (no tag, just the docs commit — no CI/release implications).

Session fully wrapped: v0.3.2 shipped + fleet-verified, regression found + designed + verified + documented + committed, Psyche communed. Resume next session at the restoration milestone kickoff.