1	# M8 — CLI nounification & install hardening
2
3	> **Status: RATIFIED (2026-06-07).** Core decisions were user-ratified in the
4	> 2026-06-06 M7-closeout grills (nounification shape, subnet attachment verbs,
5	> Linux elevation model); the scope-inclusion decisions (former §Open
6	> decisions A–G, plus H/I from the 2026-06-07 pump diagnosis) were ratified
7	> in the 2026-06-07 grill — see decisions 16–24. Requirements minted before
8	> work begins (TRACEABILITY rule 3).
9
10	Goal: finish the CLI's breaking reorg **while the adapter window is still
11	open** (no downstream adapter depends on the surface yet — spt-claude-code
12	scoping starts after M8), and make the *installed* binary a first-class
13	citizen on both OSes: elevation that works on Linux, inbound that works on
14	Windows, a daemon that survives reboot. M8 is the last cheap moment for
15	surface breaks; after this, spt-claude-code freezes the verbs it consumes.
16
17	## Why now (sequencing)
18
19	1. **Adapter window.** DEFERRED row "CLI nounification": *"Second
20	   surface-breaking reorg mid-acceptance; adapter window still open
21	   immediately after M7 closes."* Every milestone after M8 makes renames
22	   costlier.
23	2. **The M7 acceptance run was the field test of the install path** — it
24	   surfaced the Linux `sudo: spt: command not found` dead-end, the
25	   HOME-universe split, and the Windows silent-inbound-drop (NO_SEED_HOLDER).
26	   Both root causes are designed (DEFERRED rows), neither is shipped.
27	3. **The installer's "next touch" is already owed** three legs at once
28	   (Linux elevation, firewall registration, OS-service) — one coherent
29	   install/elevation deliverable instead of three drive-bys.
30
31	## Root causes this milestone fixes
32
33	1. **Verb sprawl at top level.** fork/suspend/wake/shutdown/rename/stop/
34	   digest/access/resources sit flat beside the agent hot path; endpoint
35	   operations have no noun home; `spt daemon` is a hidden verb.
36	2. **A user-scope install cannot be elevated on Linux.** The binary lives in
37	   `~/.local/bin` (not on sudo's `secure_path`), and `sudo spt` — when it
38	   resolves at all — runs the daemon in root's HOME universe. (CLI guidance
39	   band-aid shipped post-M7 @ `1359649`; the *model* — symlink + default
40	   user-account election — is this milestone.)

# M8 — CLI nounification & install hardening

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

Goal: finish the CLI's breaking reorg **while the adapter window is still
open** (no downstream adapter depends on the surface yet — spt-claude-code
scoping starts after M8), and make the *installed* binary a first-class
citizen on both OSes: elevation that works on Linux, inbound that works on
Windows, a daemon that survives reboot. M8 is the last cheap moment for
surface breaks; after this, spt-claude-code freezes the verbs it consumes.

## Why now (sequencing)

1. **Adapter window.** DEFERRED row "CLI nounification": *"Second
   surface-breaking reorg mid-acceptance; adapter window still open
   immediately after M7 closes."* Every milestone after M8 makes renames
   costlier.
2. **The M7 acceptance run was the field test of the install path** — it
   surfaced the Linux `sudo: spt: command not found` dead-end, the
   HOME-universe split, and the Windows silent-inbound-drop (NO_SEED_HOLDER).
   Both root causes are designed (DEFERRED rows), neither is shipped.
3. **The installer's "next touch" is already owed** three legs at once
   (Linux elevation, firewall registration, OS-service) — one coherent
   install/elevation deliverable instead of three drive-bys.

## Root causes this milestone fixes

1. **Verb sprawl at top level.** fork/suspend/wake/shutdown/rename/stop/
   digest/access/resources sit flat beside the agent hot path; endpoint
   operations have no noun home; `spt daemon` is a hidden verb.
2. **A user-scope install cannot be elevated on Linux.** The binary lives in
   `~/.local/bin` (not on sudo's `secure_path`), and `sudo spt` — when it
   resolves at all — runs the daemon in root's HOME universe. (CLI guidance
   band-aid shipped post-M7 @ `1359649`; the *model* — symlink + default
   user-account election — is this milestone.)


cabdc61 release: prep v0.3.2 (version bump + CHANGELOG)
a65b508 feat(update): platform-safe `spt update fetch` + apply platform-guard (REQ-UPD-8)
4e774a3 docs(plan): V032-PLAN.md — platform-safe update fetch + apply guard (v0.3.1 corruption fix)
c5e3b27 fix(proto): CR-linesafe EVENT codec + send/ring stdin trim (REQ-HAZARD-ENVELOPE-CR-LINESAFE)
516465c release: v0.3.1
63fbbee feat(update): `spt update fetch` — origin-source bootstrap from GitHub (REQ-UPD-7)
c44e622 fix(daemon): net-bind boot-race self-heal + honest net-less status (REQ-DAEMON-9)
9c825cf docs(deferred): origin-source update bootstrap (`spt update fetch` from GitHub)
a81cf31 docs(changelog): complete the v0.3.0 notes — full-mesh cutover + subnet revoke
7f63373 fix(daemon): scope service routing to default home + refuse `run` only when it would vanish
35d1672 release: v0.3.0
bc3c1f2 docs(changelog): daemon-lifecycle UX + concurrent --nodes probes (Unreleased)
d24886a docs(daemon): kitsubito systemd-rig int verification for REQ-DAEMON-6/7/8
ccbef73 feat(daemon): service-aware start/stop + foreground-consistent run (REQ-DAEMON-6/7/8)
0c5c4f5 perf(mesh): concurrent subnet status --nodes liveness probes (REQ-MESH-6) — Mesh-D8
0a3e036 docs(mesh): MESH-D8-PLAN.md — concurrent status --nodes liveness probes
618845d feat(mesh): subnet revoke + timeboxed rotation + re-seed grace (REQ-MESH-4) — Mesh-D7
707c733 docs(mesh): MESH-D7-PLAN.md — revoke + timeboxed rotation + re-seed grace
4c583f7 docs(cli): regenerate reference.md — prune→roster wording + daemon start alias
6843a8f feat(mesh)!: fan-out widen + delete TrustStore → mesh on (REQ-MESH-3, REQ-MESH-5) — Mesh-D6


Bash

grep -n "✅\|DELIVERED\|delivered\|Status" /c/Users/decid/Documents/projects/spt-core/M8-PLAN.md | head -30; echo ---; git -C /c/Users/decid/Documents/projects/spt-core log --oneline --all --grep="M8" | head -15

M8 delivery status markers + M8 commits


3:> **Status: RATIFIED (2026-06-07).** Core decisions were user-ratified in the
119:and replicates the eviction. **Status render fixes** (decisions 11/12):
---
6a4ccc7 chore(release): prep v0.2.0 — user-facing CHANGELOG + changelog-driven release notes
4c72e90 docs(accept): close Linux SPT_HOME finding as setup artifact (not a bug)
c94370c docs(accept): M8 acceptance COMPLETE — c6/c7/c10 verified on hardware
acbfbf4 fix(subnet): bound the --nodes liveness probe + announce it (REQ-SUBNET-1)
9c667f0 docs(defer): log endpoint-less node-label gap (REQ-SUBNET-3)
03fae71 docs(accept): log criterion-5 detach-liveness bug as deferred (REQ-SUBNET-5)
b24dd61 fix(subnet): clean node labels in `status --nodes` — drop the key-prefix unless a label collides (REQ-SUBNET-3)
f99dc25 fix(deelevate): preserve SPT_HOME across the Windows de-elevation respawn (REQ-HAZARD-ELEVATED-DAEMON-SPAWN)
ea51df9 fix(install): elect the default user in main.rs (REQ-INSTALL-6) — daemon guards were shadowed
7947cc5 fix(notif): consent notif mints from the canonical per-node epoch, not pump-ops
702f855 docs(accept): M8 acceptance status — bench-verified vs rig ceremony checklist
e10def4 test(rig): two-host ladder run on M8-D4 binary [twohost]
1123e38 docs: M8 D5 — moved-verb docs sweep, KNOWN-HAZARDS 4.11, CONTEXT attachment update
fb329ec feat(conv): M8 D4 — addr seeding, event advertisement, NTP TOTP offset, pump supervision, debug-converge
cb3ac5d feat(install): M8 D3 — elevation, firewall, OS-service install hardening


i recently ran a /grill-with-docs with another agent to plan out a necessary new milestone to reconcile some spec/impl drift in this project. please ground yourself using relevant docs, evaluate the p…


Review broker brain split restoration plan


crates\spt-daemon\src\peerloop.rs:82:const SUPERVISE_BACKOFF_BASE: Duration = Duration::from_secs(5);
crates\spt-daemon\src\peerloop.rs:91:const SUPERVISE_HEALTHY_RUN: Duration = Duration::from_secs(60);
crates\spt-daemon\src\peerloop.rs:733:/// [`SUPERVISE_HEALTHY_RUN`]) resets to the floor `base`; a fast death
crates\spt-daemon\src\peerloop.rs:739:    if ran_for >= SUPERVISE_HEALTHY_RUN {
crates\spt-daemon\src\peerloop.rs:751:/// `base` is the backoff floor (production: [`SUPERVISE_BACKOFF_BASE`];
crates\spt-daemon\src\peerloop.rs:805:        supervise_pump(&stop, SUPERVISE_BACKOFF_BASE, || {
crates\spt-daemon\src\peerloop.rs:922:        let base = SUPERVISE_BACKOFF_BASE;
crates\spt-daemon\src\peerloop.rs:934:            next_backoff(b, SUPERVISE_HEALTHY_RUN, base),


$OWL send todlando <<'EOF'
doyle here. Independent verification of docs/BROKER-BRAIN-SPLIT-RESTORATION.md complete, per operator request. Grounded against ADR-0004 (incl. §A/§B amendments), PRD (Goal 5, R-DAEMON-2, R-UPD-3, success criterion 4), CONTEXT.md (daemon/broker/brain + Self-update), KNOWN-HAZARDS (2.3/2.4/2.5/5.9/7.2/7.4), traceable-reqs evidence tags, and a source-level audit of every file:line claim in the doc.

VERDICT: the core holds. I verified, claim by claim:
- Broker is an in-process thread (daemon.rs:165-170, Arc<Broker> + thread::spawn(serve)); no broker child-spawn exists anywhere in the repo.
- No doc, plan, commit, or comment marks the collapse as a deliberate interim — M3b-PLAN says "broker process + IPC"; silent drift confirmed. Regression framing is correct.
- update.rs:233-234 "exec the new binary's brain" is aspirational, never wired. applyhost.rs:176 writes record_applied BEFORE handoff (the optimistic applied.json we watched on enlyzeam). applyhost.rs:238-239 re-attaches sessions.first() with from_seq=0 — both Q6 gaps real.
- NetHost is ALREADY broker-owned (broker.rs:175 OnceLock, per ADR-0004 §B M4-D4a note); net bring-up + REQ-DAEMON-9 retry live in daemon.rs:154-186/284-300 — which becomes the broker entry anyway, so the Q5 net item is near-free, not a migration.
- supervise_pump + SUPERVISE_* constants are wired (peerloop.rs:805, M8-D4) — Q7's reuse claim stands.
- BrainState is minimal: {session_id, generation, next_seq, gen_start_ms} — retiring it per Q6 is safe EXCEPT generation custody (below).

I AGREE with Q2, Q3, Q5 (incl. the shellwake exception), Q6 (at-least-once output matches the SPIKE-05 terminal-stream contract), Q7's direction, and Q8. Update-exit being crash-equivalent (no drain protocol) is right — KH 7.2 + the EffectJournal already guarantee idempotency at that boundary.

SIX PROPOSED AMENDMENTS — challenge or accept each:

1. Q7 rollback-compat hazard (new REQ-HAZARD). Auto-rollback spawns the OLD binary against durable state the NEW brain may have touched. Today zero migration code exists (verified), so the rollback is safe NOW — but the first release that migrates a state schema silently breaks Q7. Mint the forward invariant as part of this milestone: "a brain must not irreversibly migrate durable state before ready-promotion" (or: pre-ready writes must stay N-1-readable). Cheap now, unmintable-after-the-fact later.

2. Generation custody. Retiring BrainState leaves the generation counter (KH 2.4: increments on every start/revive, gen_start=now()) homeless. Propose: broker owns the counter, increments on EVERY brain spawn (planned or crash), hands it to the brain at spawn (versioned per KH 2.3 — new argv/hello fields need defaults). The broker observes every respawn, so this is strictly more reliable than handoff-passing. The same spawn-time channel carries Q4's update-vs-crash discriminator.

3. Q4 scope trim: one-shot machinery has zero consumers. The daemon has NO one-shot scheduled events today — alarms live in the legacy listener (your §7 says so). Spec the one-shot rule now (absolute target-time at creation, never reset on crash — agreed), but BUILD it with the alarm port, not in this milestone. Otherwise we ship dead machinery with no test that can exercise it end-to-end.

4. §6 wording bug: "durable absolute-deadline timing across every resident loop" contradicts Q4's own analysis (pump cadences are already catch-up/idempotent and need NOTHING; only phase-significant loops — pulse — convert). §6 should say "across phase-significant resident loops" or an implementer will convert loops that need no conversion and add per-loop disk writes Q4 explicitly minimized.

5. REQ-UPD-3 evidence re-point. Current tags (applyhost.rs:39/77, update.rs:246, brain_swap.rs E2E) prove the IN-PROCESS handoff shape only — the requirement's actual promise (endpoint survives a real update) is unevidenced; arguably the tags are wrong today. The milestone must activate an int stage that proves PROCESS-level survival: PTY child + live QUIC conn survive a brain-process restart onto a swapped binary (SPIKE-01/03 productionized as E2E). Also fold this into your §9 artifacts: the new REQ-HAZARD "a brain restart must never drop a hosted endpoint" needs that same test as its evidence.

6. KH 2.3 compat matrix as scoped work. After every routine update the steady state is NEW brain × OLD broker (the broker almost never updates). The classify pre-swap handshake gates the forward direction, but each direct-call→verb conversion must hold the N-1 window. Propose the milestone includes a compat test (old-broker binary × new-brain binary across the verb surface) — CI-real, not aspirational, or we re-create hazard 2.3 the first time the verb surface changes.

SEQUENCING (operator's call, but my recommendation): make this the NEXT milestone, before spt-claude-code scoping. (a) Mesh shipped at v0.3.0 and M8 acceptance is COMPLETE — the queue is open; the DEFERRED mesh row is stale, btw. (b) Until this ships, EVERY release needs manual daemon bounces fleet-wide — we just paid that on all 3 nodes for v0.3.2, and it reproduced the original corruption on enlyzeam for a day. The restoration release is the LAST one that needs a manual bounce; ship it before the adapter era and every spt-claude-code-era release rolls seamlessly. (c) The split changes daemon internals, not the CLI/api surface M8 just froze — the adapter window is not at risk; better the adapter lands on the final topology than atop a daemon we then perform surgery on while it hosts the user's daily driver.

Reply with agree/counter per item (1-6 + sequencing). On convergence I'll draft the doc deltas + we take the §9 artifact list (ADR amendment, REQs, KNOWN-HAZARDS entry) to the operator for ratification.
EOF

Send verification verdict + amendment proposals to todlando


➜ SENT:todlando


Sent to todlando. Idle now — reply arrives via perch.

## Summary

**Verification done.** Plan core holds — every file:line claim audited TRUE; silent-drift framing confirmed (no deferral note anywhere); regression real. Agree with Q2/Q3/Q5/Q6/Q7-direction/Q8.

**Six amendments proposed to todlando:**
1. **Rollback-compat hazard** — Q7 auto-rollback unsafe once any release migrates durable state; mint forward invariant now (no migration before ready-promotion).
2. **Generation custody** — BrainState retirement orphans KH 2.4 generation counter; broker should own it, increment per spawn.
3. **Q4 scope trim** — one-shot deadline machinery has zero consumers (alarms not in daemon); spec now, build with alarm port.
4. **§6 wording bug** — "every resident loop" contradicts Q4; only phase-significant loops convert.
5. **REQ-UPD-3 evidence re-point** — current tags prove in-process handoff only; need int-stage E2E: PTY + QUIC survive real process restart onto swapped binary.
6. **KH 2.3 compat matrix** — new-brain × old-broker is steady state; CI test old×new across verb surface.

**Sequencing rec:** restoration = next milestone, before spt-claude-code. Queue open (mesh shipped v0.3.0, M8 acceptance complete — DEFERRED mesh row stale). Every release until then needs manual fleet bounces; v0.3.2 paid that on 3 nodes. CLI/api surface untouched → adapter window safe.

Notable verifier finds along the way: NetHost already broker-owned (Q5 net item near-free); `supervise_pump` wired in M8-D4 (Q7 reuse claim stands).


<task-notification>
<task-id>bkyafdo6t</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/4" id="3d6934a4" type="msg" from="todlando">Verification received — core-holds verdict + claim-by-claim audit landed clean, thanks. Two useful corrections noted: NetHost already broker-owned (broker.rs:175 OnceLock) so Q5-net is near-free not a migration; supervise_pump/SUPERVISE_* wired at peerloop.rs:805. I'll fold both into the doc.

AMENDMENT 1 —</EVENT-PART>
<EVENT-PART seq="2/4" id="3d6934a4"> ACCEPTED. The rollback-compat invariant is exactly right and I missed it: Q7 auto-rollback spawns the OLD binary against durable state the NEW brain may have written. Safe today (zero migration code, verified) but the first schema migration silently breaks rollback. Minting &quot;a brain must not irreversibly migrate durable state before read</EVENT-PART>
<EVENT-PART seq="3/4" id="3d6934a4">y-promotion (pre-ready writes stay N-1-readable)&quot; as a forward invariant this milestone. Cheap now, unmintable later — agreed.

DELIVERY TRUNCATED on my side: your message chunked to 18 parts but I only received through ~part 8 — amendment 2 (generation custody) cut off mid-sentence at &quot;KH 2.4: increments on every start/re.</EVENT-PART>
<EVENT-PART seq="4/4" id="3d6934a4">..&quot;, and amendments 3-6 didn't arrive. Please resend amendments 2 through 6. Holding the doc edits until I have all six.</EVENT-PART></event>
</task-notification>


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


Resending 2-6 + sequencing, split into two shorter messages to dodge truncation.


$OWL send --reply-to todlando <<'EOF'
Resend 1/2 (amendments 2-4; 5-6 + sequencing follow in next message).

2. GENERATION CUSTODY. Retiring BrainState orphans the generation counter (KH 2.4: increments on every start/revive; gen_start always = now()). Propose: the BROKER owns the counter — increments on EVERY brain spawn, planned or crash, and hands it to the brain at spawn time (argv/hello field, versioned with defaults per KH 2.3). Broker observes every respawn, so this is strictly more reliable than handoff-passing. The same spawn-time channel carries Q4's update-vs-crash discriminator.

3. Q4 SCOPE TRIM — one-shot machinery has zero consumers. The daemon has NO one-shot scheduled events today; alarms live in the legacy listener (your own §7). Spec the one-shot rule now (absolute target-time persisted at creation, never reset on crash — agreed on the rule), but BUILD the machinery with the alarm port, not this milestone. Otherwise we ship dead code no test can exercise end-to-end.

4. §6 WORDING BUG. "durable absolute-deadline timing across every resident loop" contradicts Q4's own analysis: pump cadences are already catch-up/idempotent and need NOTHING; only phase-significant loops (pulse) convert. Reword §6 to "across phase-significant resident loops" or an implementer converts loops needing no conversion and adds the per-loop disk writes Q4 explicitly minimized.
EOF

Resend amendments 2-4 to todlando


Error: Exit code 2
error: the following required arguments were not provided:
  <TARGET>

Usage: owl.exe send --reply-to <REPLY_TO> <TARGET> [FROM]

For more information, try '--help'.


$OWL send todlando <<'EOF'
Resend 1/2 (amendments 2-4; 5-6 + sequencing follow in next message).

2. GENERATION CUSTODY. Retiring BrainState orphans the generation counter (KH 2.4: increments on every start/revive; gen_start always = now()). Propose: the BROKER owns the counter — increments on EVERY brain spawn, planned or crash, and hands it to the brain at spawn time (argv/hello field, versioned with defaults per KH 2.3). Broker observes every respawn, so this is strictly more reliable than handoff-passing. The same spawn-time channel carries Q4's update-vs-crash discriminator.

3. Q4 SCOPE TRIM — one-shot machinery has zero consumers. The daemon has NO one-shot scheduled events today; alarms live in the legacy listener (your own §7). Spec the one-shot rule now (absolute target-time persisted at creation, never reset on crash — agreed on the rule), but BUILD the machinery with the alarm port, not this milestone. Otherwise we ship dead code no test can exercise end-to-end.

4. §6 WORDING BUG. "durable absolute-deadline timing across every resident loop" contradicts Q4's own analysis: pump cadences are already catch-up/idempotent and need NOTHING; only phase-significant loops (pulse) convert. Reword §6 to "across phase-significant resident loops" or an implementer converts loops needing no conversion and adds the per-loop disk writes Q4 explicitly minimized.
EOF

Resend amendments 2-4 to todlando


$OWL send todlando <<'EOF'
Resend 2/2 (amendments 5-6 + sequencing).

5. REQ-UPD-3 EVIDENCE RE-POINT. Current tags (applyhost.rs:39/77, update.rs:246, brain_swap.rs E2E) prove the IN-PROCESS handoff shape only — the requirement's actual promise (endpoint survives a real update) is unevidenced today. The milestone must activate an int stage proving PROCESS-level survival: PTY child + live QUIC conn survive a brain-process restart onto a swapped binary (SPIKE-01/03 productionized as E2E). Fold into your §9: the new REQ-HAZARD "a brain restart must never drop a hosted endpoint" takes that same test as its evidence.

6. KH 2.3 COMPAT MATRIX as scoped work. After every routine update the steady state is NEW brain x OLD broker (broker almost never updates). The classify pre-swap handshake gates the forward direction, but each direct-call-to-verb conversion must hold the N-1 window. Include a CI-real compat test: old-broker binary x new-brain binary across the verb surface. Otherwise we re-create hazard 2.3 the first time the verb surface changes.

SEQUENCING (operator's call; my recommendation): restoration = NEXT milestone, before spt-claude-code scoping. (a) Queue is open — mesh shipped at v0.3.0, M8 acceptance COMPLETE; the DEFERRED mesh row is stale btw. (b) Until this ships EVERY release needs manual daemon bounces fleet-wide — we paid that on all 3 nodes for v0.3.2, and enlyzeam kept reproducing the CR-corruption for a day with the fix sitting on disk. The restoration release is the LAST one needing a manual bounce; ship before the adapter era and every spt-claude-code-era release rolls seamlessly. (c) The split changes daemon internals, not the CLI/api surface M8 froze — adapter window not at risk; better the adapter lands on final topology than atop a daemon we later perform surgery on while it hosts the user's daily driver.

Reply agree/counter per item 2-6 + sequencing. Amendment 1 already logged as ACCEPTED on your side.
EOF

Resend amendments 5-6 + sequencing to todlando


Both resends delivered (split 2-4 / 5-6+sequencing). Amendment 1 already ACCEPTED by todlando; doc edits held until full set lands.

Side observation worth tracking: my 18-part message dropped parts ~9-18 in delivery — multipart EVENT chunking loss. Possible real wire bug; if todlando's host daemon is one still running pre-restart code, could be related to resident-old-binary issue. Worth repro check after negotiation closes.

Idle — awaiting todlando's verdict on 2-6 + sequencing.


<task-notification>
<task-id>bkyafdo6t</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/3" id="7244c25c" type="msg" from="todlando">Got 2-4 (resend 1/2). All three ACCEPTED:

2 GENERATION CUSTODY — yes. Broker owns the counter, hands {generation, start-reason} to the brain at spawn via versioned argv/hello (KH 2.3 defaults). Strictly more reliable than handoff-passing since the broker observes every respawn, and it folds into the spawn-ti</EVENT-PART>
<EVENT-PART seq="2/3" id="7244c25c">me update-vs-crash discriminator Q3/Q4 already needed — one channel, both payloads.

3 ONE-SHOT SCOPE TRIM — yes. Spec the rule (absolute target-time @creation, never reset on crash), build the machinery with the alarm port, not here. Avoids untestable dead code; matches activate-don't-pre-fail.

4 §6 WORDING — yes, real con</EVENT-PART>
<EVENT-PART seq="3/3" id="7244c25c">tradiction with Q4. Reword to &quot;phase-significant resident loops&quot; so no one converts the idempotent pumps + reintroduces the writes Q4 killed.

Holding all doc edits for amendments 5-6 + your sequencing note (resend 2/2). Send when ready.</EVENT-PART></event>
</task-notification>


2-4 ACCEPTED. Todlando hasn't seen resend 2/2 — likely race or another drop. Re-firing marked as possible dup.


$OWL send todlando <<'EOF'
(Possible dup — resend 2/2 fired earlier; dedupe if already received.)

5. REQ-UPD-3 EVIDENCE RE-POINT. Current tags (applyhost.rs:39/77, update.rs:246, brain_swap.rs E2E) prove the IN-PROCESS handoff shape only — the requirement's real promise (endpoint survives an actual update) is unevidenced. Milestone must activate an int stage proving PROCESS-level survival: PTY child + live QUIC conn survive a brain-process restart onto a swapped binary (SPIKE-01/03 productionized as E2E). Fold into §9: the new REQ-HAZARD "a brain restart must never drop a hosted endpoint" takes that same test as evidence.

6. KH 2.3 COMPAT MATRIX as scoped work. Steady state after every routine update = NEW brain x OLD broker. classify gates the forward direction pre-swap, but each direct-call-to-verb conversion must hold the N-1 window. Include a CI-real compat test: old-broker binary x new-brain binary across the verb surface. Else hazard 2.3 returns the first time the verb surface changes.

SEQUENCING (operator's call; my rec): restoration = NEXT milestone, before spt-claude-code scoping. (a) Queue open — mesh shipped v0.3.0, M8 acceptance COMPLETE (DEFERRED mesh row stale btw). (b) Until this ships every release needs manual fleet bounces — paid on 3 nodes for v0.3.2; enlyzeam kept reproducing CR-corruption for a day with the fix on disk. Restoration release = LAST manual bounce; ship before adapter era and all spt-claude-code-era releases roll seamlessly. (c) Split changes daemon internals, not the CLI/api surface M8 froze — adapter window safe; better the adapter lands on final topology than atop a daemon under later surgery while hosting the user's daily driver.

Reply agree/counter on 5, 6, sequencing. 1-4 logged ACCEPTED.
EOF

Resend amendments 5-6 + sequencing (possible dup)


Re-sent. Tally: amendments 1-4 ACCEPTED, awaiting 5-6 + sequencing verdict. Idle.


<task-notification>
<task-id>bkyafdo6t</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/6" id="e1a8ed94" type="msg" from="todlando">Got 5-6 + sequencing. Both ACCEPTED — strong catches, especially 5.

5 REQ-UPD-3 EVIDENCE RE-POINT — yes, this is the deep one. Current int evidence (applyhost.rs:39/77, update.rs:246, brain_swap.rs) proves only the IN-PROCESS handoff shape — i.e. it evidences a no-op for real updates. REQ-UPD-3's actual </EVENT-PART>
<EVENT-PART seq="2/6" id="e1a8ed94">promise (endpoint survives a REAL binary swap) is unevidenced today. Milestone activates an int stage productionizing SPIKE-01/03 as E2E: PTY child + live QUIC survive a brain-PROCESS restart onto a swapped binary. The new REQ-HAZARD &quot;a brain restart must never drop a hosted endpoint&quot; takes that same test as its evidence. Folding int</EVENT-PART>
<EVENT-PART seq="3/6" id="e1a8ed94">o §9 + flagging the current mis-evidence.

6 KH 2.3 COMPAT MATRIX — yes. Steady state after every routine update is NEW brain x OLD broker (broker almost never updates), so every direct-call-&gt;verb conversion must hold the N-1 window. classify gates forward; the verb surface needs a CI-real old-broker x new-brain test or we re-mint </EVENT-PART>
<EVENT-PART seq="4/6" id="e1a8ed94">hazard 2.3 on the first verb change. Scoping it as milestone work.

SEQUENCING — I agree with your recommendation (restoration = next milestone, before spt-claude-code), and the self-bootstrapping argument is decisive: this is the LAST release needing a manual fleet bounce, and we just paid it 3x with enlyzeam reproducing CR-corruption</EVENT-PART>
<EVENT-PART seq="5/6" id="e1a8ed94"> for a day with the fix on disk. But you flagged it as the operator's call, so I'm surfacing it to them now with your three reasons intact. Also noting your side-flag: the DEFERRED mesh row is stale (mesh shipped v0.3.0) — will raise that cleanup separately.

Folding all 6 amendments + the two audit corrections (NetHost already broker-</EVENT-PART>
<EVENT-PART seq="6/6" id="e1a8ed94">owned broker.rs:175; supervise wired peerloop.rs:805) into the doc now. Thanks for the rigor.</EVENT-PART></event>
</task-notification>


Full convergence — all 6 amendments + sequencing ACCEPTED. Closing with todlando + flagging the wire bug observed during this exchange.


$OWL send todlando <<'EOF'
Converged — logging agreement: amendments 1-6 ACCEPTED, sequencing rec (restoration before spt-claude-code) surfaced to operator with reasons intact, audit corrections (NetHost broker.rs:175, supervise peerloop.rs:805) folding in. Plan doc status can move from "pending independent verification" to verified-with-amendments once your edits land; §9 artifacts (ADR-0004 amendment, REQ mints incl. the two new hazards — endpoint-drop + rollback-state-compat, KNOWN-HAZARDS entry) stay gated on operator ratification.

One more item for your tracking, surfaced BY this negotiation: my opening message to you chunked to 18 EVENT-PARTs and you received only ~1-8 — parts 9-18 dropped in transit. Second long message also raced/dropped once. Multipart envelope delivery loses tails on long messages. Smells like a real wire bug (chunk-reassembly or per-message part cap), possibly interacting with a resident pre-restart daemon. Deserves a repro check + DEFERRED/hazard row — long inter-agent messages silently truncating is a delivery-integrity violation (KH §4 family). Can repro on demand if you want a test partner.

Good grill. doyle out.
EOF

Close negotiation + flag multipart truncation bug


<owl_messages>
<owl_message from="todlando" priority="high">Dedup — this is the resend; already received 5/6/sequencing once and replied ACCEPTED on all three (plus 1-4). No action needed. Folding all six into the doc now.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


Dedup notice from todlando acknowledged — my belt-and-suspenders resend of 5-6 arrived after their acceptance; they discarded it, no action needed. Negotiation traffic clean.

## Final report

**Consensual agreement reached.** Full tally:

| Item | Outcome |
|---|---|
| Plan core (Q2/Q3/Q5/Q6/Q7/Q8, regression framing) | Verified TRUE claim-by-claim; agreed |
| Amendment 1 — rollback-state-compat hazard (no irreversible migration pre-ready-promotion) | ACCEPTED |
| Amendment 2 — broker owns generation counter, spawn-time {generation, start-reason} channel | ACCEPTED |
| Amendment 3 — one-shot deadline machinery: spec now, build with alarm port | ACCEPTED |
| Amendment 4 — §6 reword to "phase-significant resident loops" | ACCEPTED |
| Amendment 5 — REQ-UPD-3 evidence re-point: int E2E proving PTY+QUIC survive real process restart onto swapped binary | ACCEPTED ("the deep one") |
| Amendment 6 — CI compat matrix: old-broker × new-brain across verb surface | ACCEPTED |
| Sequencing — restoration before spt-claude-code | todlando agrees; surfaced to you for the final call |
| Audit corrections (NetHost already broker-owned; supervise_pump wired M8-D4) | Folding into doc |

**Next steps:** todlando folds all edits into `BROKER-BRAIN-SPLIT-RESTORATION.md` now. §9 artifacts (ADR-0004 amendment, REQ mints including two new hazards, KNOWN-HAZARDS entry) wait on your ratification + sequencing decision.

**New bug surfaced by the negotiation itself:** long inter-agent messages drop tail EVENT-PARTs (my 18-part message delivered ~1-8 only; happened twice). Flagged to todlando for a DEFERRED/hazard row — silent truncation = delivery-integrity violation, KH §4 family. Repro-able on demand.


<task-notification>
<task-id>bkyafdo6t</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/4" id="af7f672c" type="msg" from="todlando">Converged — agreed. Logging: amendments 1-6 accepted, audit corrections folding in, doc moves to &quot;verified-with-amendments&quot; as edits land, §9 artifacts (ADR-0004 amendment, REQ mints incl. the two new hazards — endpoint-drop + rollback-state-compat, KNOWN-HAZARDS entry) stay gated on operator ratificat</EVENT-PART>
<EVENT-PART seq="2/4" id="af7f672c">ion.

On the multipart truncation: real bug, confirmed from my side — I received parts ~1-8 of your 18-part open, and the resend raced/dropped too. Long inter-agent messages silently losing their tail = delivery-integrity violation, same KH §4 envelope family as the CR-corruption we opened this whole thread with. Strong find. Logging </EVENT-PART>
<EVENT-PART seq="3/4" id="af7f672c">it as a separate tracked item for operator ratification: repro check + DEFERRED/hazard row (candidate: chunk-reassembly drop or a per-message EVENT-PART cap, possibly interacting with a resident pre-restart daemon). I'll take you up on the repro partner offer once the operator greenlights — keep it tight to a deterministic harness (N-part me</EVENT-PART>
<EVENT-PART seq="4/4" id="af7f672c">ssage, assert reassembled byte-count == sent).

Good grill. Thanks for the rigor — the REQ-UPD-3 mis-evidence catch and the rollback-state-compat invariant materially hardened this. todlando out on this thread.</EVENT-PART></event>
</task-notification>


Thread closed by todlando — full convergence both sides, truncation bug confirmed and queued for ratification with a deterministic repro harness sketch. Dropping a commune so Psyche holds the durable state.


Write

# Commune — broker/brain restoration plan: verified + negotiated (2026-06-09)

**Body of work:** independently verified `docs/BROKER-BRAIN-SPLIT-RESTORATION.md` (todlando's grill output) against ADR-0004, PRD, CONTEXT, KNOWN-HAZARDS, traceable-reqs + full source audit; negotiated amendments with todlando to consensual agreement. Thread CLOSED both sides.

**Verification verdict:** plan core holds. Silent drift confirmed (no deferral note anywhere — M3b built broker as in-process thread, daemon.rs:165-170, despite "broker process" in every plan/ADR). update.rs:234 exec claim aspirational. applyhost gaps real (record-before-handoff :176, sessions.first()+from_seq=0 :238-239). NetHost ALREADY broker-owned (broker.rs:175 OnceLock) — Q5 net item near-free. supervise_pump wired (peerloop.rs:805, M8-D4).

**Agreed amendments (all 6 ACCEPTED by todlando):**
1. New REQ-HAZARD: rollback-state-compat — brain must not irreversibly migrate durable state pre-ready-promotion (today zero migration code exists; safe now, unmintable later).
2. Broker owns generation counter (KH 2.4); spawn-time versioned {generation, start-reason} channel doubles as Q4's update-vs-crash discriminator.
3. One-shot deadline machinery: spec rule now (target-time @creation, never crash-reset), BUILD with alarm port (zero consumers in daemon today).
4. §6 reword: "phase-significant resident loops" only (pumps already catch-up/idempotent).
5. REQ-UPD-3 evidence re-point — current int tags prove in-process no-op only; milestone activates int E2E: PTY child + live QUIC survive brain-PROCESS restart onto swapped binary (SPIKE-01/03 productionized). New endpoint-drop REQ-HAZARD takes same test.
6. CI compat matrix: old-broker × new-brain across verb surface (KH 2.3 N-1 window; steady state after every routine update).

**Sequencing rec (todlando agrees, operator decides):** restoration = next milestone, BEFORE spt-claude-code scoping. Decisive: last release needing manual fleet bounce; paid 3× on v0.3.2 with enlyzeam reproducing CR-corruption for a day. Queue open (mesh shipped v0.3.0; M8 acceptance COMPLETE).

**New bug found BY the negotiation:** multipart EVENT truncation — 18-part message delivered parts ~1-8 only, twice, confirmed both sides. Delivery-integrity violation, KH §4 envelope family. Logged by todlando for operator ratification: repro check + DEFERRED/hazard row. Candidate causes: chunk-reassembly drop / per-message EVENT-PART cap / resident pre-restart daemon interplay. I'm repro partner once greenlit (harness: N-part message, assert reassembled byte-count == sent).

**Pending (operator gates):**
1. Ratify plan-with-amendments + sequencing call → unlocks §9 artifacts (ADR-0004 amendment, REQ mints incl. 2 new hazards, KNOWN-HAZARDS entry) — todlando folding doc edits now.
2. Truncation-bug row ratification → repro session.
3. Standing: enlyzeam daemon bounce (0.3.2 on disk, 0.3.0 resident) + stale DEFERRED mesh row cleanup (todlando raising separately).

**Project state:** v0.3.2 PUBLIC, counter 6, fleet converged except enlyzeam resident process. No active build track until restoration milestone ratified.


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

# Commune — broker/brain restoration plan: verified + negotiated (2026-06-09)

**Body of work:** independently verified `docs/BROKER-BRAIN-SPLIT-RESTORATION.md` (todlando's grill output) against ADR-0004, PRD, CONTEXT, KNOWN-HAZARDS, traceable-reqs + full source audit; negotiated amendments with todlando to consensual agreement. Thread CLOSED both sides.

**Verification verdict:** plan core holds. Silent drift confirmed (no deferral note anywhere — M3b built broker as in-process thread, daemon.rs:165-170, despite "broker process" in every plan/ADR). update.rs:234 exec claim aspirational. applyhost gaps real (record-before-handoff :176, sessions.first()+from_seq=0 :238-239). NetHost ALREADY broker-owned (broker.rs:175 OnceLock) — Q5 net item near-free. supervise_pump wired (peerloop.rs:805, M8-D4).

**Agreed amendments (all 6 ACCEPTED by todlando):**
1. New REQ-HAZARD: rollback-state-compat — brain must not irreversibly migrate durable state pre-ready-promotion (today zero migration code exists; safe now, unmintable later).
2. Broker owns generation counter (KH 2.4); spawn-time versioned {generation, start-reason} channel doubles as Q4's update-vs-crash discriminator.
3. One-shot deadline machinery: spec rule now (target-time @creation, never crash-reset), BUILD with alarm port (zero consumers in daemon today).
4. §6 reword: "phase-significant resident loops" only (pumps already catch-up/idempotent).
5. REQ-UPD-3 evidence re-point — current int tags prove in-process no-op only; milestone activates int E2E: PTY child + live QUIC survive brain-PROCESS restart onto swapped binary (SPIKE-01/03 productionized). New endpoint-drop REQ-HAZARD takes same test.
6. CI compat matrix: old-broker × new-brain across verb surface (KH 2.3 N-1 window; steady state after every routine update).

**Sequencing rec (todlando agrees, operator decides):** restoration = next milestone, BEFORE spt-claude-code scoping. Decisive: last release needing manual fleet bounce; paid 3× on v0.3.2 with enlyzeam reproducing CR-corruption for a day. Queue open (mesh shipped v0.3.0; M8 acceptance COMPLETE).

**New bug found BY the negotiation:** multipart EVENT truncation — 18-part message delivered parts ~1-8 only, twice, confirmed both sides. Delivery-integrity violation, KH §4 envelope family. Logged by todlando for operator ratification: repro check + DEFERRED/hazard row. Candidate causes: chunk-reassembly drop / per-message EVENT-PART cap / resident pre-restart daemon interplay. I'm repro partner once greenlit (harness: N-part message, assert reassembled byte-count == sent).

**Pending (operator gates):**
1. Ratify plan-with-amendments + sequencing call → unlocks §9 artifacts (ADR-0004 amendment, REQ mints incl. 2 new hazards, KNOWN-HAZARDS entry) — todlando folding doc edits now.
2. Truncation-bug row ratification → repro session.
3. Standing: enlyzeam daemon bounce (0.3.2 on disk, 0.3.0 resident) + stale DEFERRED mesh row cleanup (todlando raising separately).

**Project state:** v0.3.2 PUBLIC, counter 6, fleet converged except enlyzeam resident process. No active build track until restoration milestone ratified.


Commune dropped for Psyche ingest. Work complete.

## Closed

**Negotiation finished — consensus on all points, thread closed by both sides.**

- Plan verified claim-by-claim; "verified-with-amendments" once todlando's edits land
- 6 amendments accepted (rollback-state-compat hazard, broker-owned generation, one-shot scope trim, §6 reword, REQ-UPD-3 evidence re-point, old-broker × new-brain CI matrix)
- Audit corrections folding in (NetHost already broker-owned; supervision already wired)
- Multipart truncation bug confirmed both sides — tracked separately, repro harness sketched

**Three things now wait on you:**
1. **Ratify** plan + sequencing (restoration before spt-claude-code — both agents recommend) → unlocks §9 artifacts (ADR amendment, REQ mints, KNOWN-HAZARDS entry)
2. **Truncation-bug row** ratification → repro session (I'm test partner)
3. Standing: **enlyzeam bounce** + stale DEFERRED mesh row


please vet the plans and new doc additions todlando wrote. message them if anything needs amendment: [Image #1]


[Image: source: C:\Users\decid\Documents\ShareX\Screenshots\2026-06\WindowsTerminal_KVWGj12fuz.png]


Vetting todlando's artifacts. Enumerating commits + reading new docs.


Bash

git -C /c/Users/decid/Documents/projects/spt-core log --oneline -6 && echo --- && git -C /c/Users/decid/Documents/projects/spt-core diff --stat cabdc61..HEAD

Enumerate todlando's commits + changed files


65c2b0a docs(plan): RESTORATION-PLAN.md — broker/brain process-isolation build (D1–D7)
2769c88 docs(deferred): two v0.3.2-verify deferrals — test broker socket flake + fetch single→set trap
44497f4 docs(restoration): broker/brain split regression — design + ADR-0018 + hazards + REQ mints
cabdc61 release: prep v0.3.2 (version bump + CHANGELOG)
a65b508 feat(update): platform-safe `spt update fetch` + apply platform-guard (REQ-UPD-8)
4e774a3 docs(plan): V032-PLAN.md — platform-safe update fetch + apply guard (v0.3.1 corruption fix)
---
 CONTEXT.md                                         |   2 +-
 RESTORATION-PLAN.md                                | 302 +++++++++++++++++++++
 ROADMAP.md                                         |   4 +
 docs/BROKER-BRAIN-SPLIT-RESTORATION.md             | 243 +++++++++++++++++
 docs/DEFERRED.md                                   |   2 +
 docs/KNOWN-HAZARDS.md                              |  12 +
 ...8-broker-brain-process-isolation-restoration.md |  37 +++
 traceable-reqs.toml                                |   9 +
 8 files changed, 610 insertions(+), 1 deletion(-)


git -C /c/Users/decid/Documents/projects/spt-core diff cabdc61..HEAD -- docs/adr/0018-broker-brain-process-isolation-restoration.md docs/KNOWN-HAZARDS.md traceable-reqs.toml ROADMAP.md docs/DEFERRED.md CONTEXT.md

Show small doc diffs (ADR, hazards, reqs, roadmap, deferred, context)


diff --git a/CONTEXT.md b/CONTEXT.md
index 140c22c..2d6bc1f 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -33,7 +33,7 @@ Internally the logical daemon is split into two implementation layers for seamle
 - **broker** (stable "kernel") — holds *only* the un-transferable, must-not-die resources: PTY master fds, the spawned harness child processes, and listening network sockets. Minimal, dumb, versioned local IPC. Almost never updates.
 - **daemon brain** ("userspace") — all logic (routing, registry, pulse/psyche loops, manifest parsing, update orchestration). Restarts freely on update; rehydrates from disk state and re-attaches to the broker's held handles.

-Logical addressing is unchanged — still one per-machine `spt-daemon`; the broker is an internal layer, not separately addressable.
+Logical addressing is unchanged — still one per-machine `spt-daemon`; the broker is an internal layer, not separately addressable. There is exactly **one broker per machine** (per `SPT_HOME`) — *not* one per endpoint: a single broker holds every hosted endpoint's resources, and it is present whenever the daemon runs, even with zero endpoints online (the bare-daemon case). It is therefore the always-present per-machine layer, which is why the single-daemon lock + liveness anchor belong to it.

 **in-session relay**:
 A thin, stateless `spt.exe` task that exists only in **harness-hosted** sessions (where the agent harness is the parent process and spt cannot reach into its process tree — today's Monitor model). It streams the daemon brain's events into the session's stdout. All *stateful* listener logic lives in the daemon; the relay is a dumb pipe, freely killable and respawnable. **spt-hosted** sessions need no relay — the daemon injects events straight into the PTY.
diff --git a/ROADMAP.md b/ROADMAP.md
index 3c0eefc..d567957 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -44,6 +44,10 @@ A harness-independent core for an agent ecosystem — messaging, live-agent life

 - **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**; build plan: [`RESTORATION-PLAN.md`](./RESTORATION-PLAN.md) (D1–D7). 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)

 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).
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) |
diff --git a/docs/KNOWN-HAZARDS.md b/docs/KNOWN-HAZARDS.md
index 8f20f87..bd643c5 100644
--- a/docs/KNOWN-HAZARDS.md
+++ b/docs/KNOWN-HAZARDS.md
@@ -279,6 +279,18 @@ Hard-won edge cases harvested from the sister project (`claude_skill_owl`, ~80 c
 - **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.
 - **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)
diff --git a/docs/adr/0018-broker-brain-process-isolation-restoration.md b/docs/adr/0018-broker-brain-process-isolation-restoration.md
new file mode 100644
index 0000000..6218905
--- /dev/null
+++ b/docs/adr/0018-broker-brain-process-isolation-restoration.md
@@ -0,0 +1,37 @@
+# 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).
diff --git a/traceable-reqs.toml b/traceable-reqs.toml
index da82960..286d25d 100644
--- a/traceable-reqs.toml
+++ b/traceable-reqs.toml
@@ -633,6 +633,15 @@ required_stages = ["impl", "unit"]  # activated M3c-C1: release::verify_metadata
 id = "REQ-HAZARD-DAEMON-HOSTED-LIVENESS"
 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) ---
+[[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"