The file C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\memory\v041-linux-respawn-blocker.md has been updated successfully. (file state is current in your context — no need to Read it back)

---
name: v041-linux-respawn-blocker
description: v0.4.1 fleet roll found a Linux brain-respawn-path bug; restoration close BLOCKED pending v0.4.2 fix
metadata:
  node_type: memory
  type: project
  originSessionId: f3f208d9-9c31-4aaa-acfc-98acbfdf2509
---

v0.4.1 (counter 8) PUBLISHED 2026-06-11 (deployah, PR #4 merged @ 038257b, tag v0.4.1). Fleet roll surfaced a **Linux seamless-update bug** → restoration close BLOCKED, v0.4.2 needed.

**hfenduleam (Windows): GREEN.** apply → brain pid 38324→6620, gen 0→1, broker pid 17232 UNCHANGED, exe_hash 81c24675(0.4.0)→728a1bfc(0.4.1 win artifact), pump STALLED 16199s→live. No-bounce PROVEN on Windows.

**kitsubito (Linux): RED.** apply recorded "applied v8" (applied-state phase:applied, no rollback), canonical `/home/reavus/.local/bin/spt` correctly swapped to 35c9544c (v0.4.1 linux artifact) — BUT respawned brain (pid 1247825, gen 1) execs `.local/bin/spt.old-8` = b4b7ea14 (v0.4.0). **Brain runs OLD bytes while recording success.**

**ROOT CAUSE (code-confirmed `brainproc.rs:815-818`):** candidate respawn `binary=None` → `std::env::current_exe()`. Linux `apply` renames running `spt`→`spt.old-N`, writes new bytes to `spt`; resident broker's `current_exe()` follows the rename to `.old-N`, so the new brain execs old bytes. Rollback path `Some(.old-N)` is fine; the NORMAL path is wrong. Windows dodges it (can't rename a running PE in place). `current_exe_hash()` (l.324) also reads current_exe → breadcrumb honestly showed old bytes; doyle's bytes-level exe_hash assert is what caught it (readiness gate passed on the old brain = false-success).

**E2E gap (`brain_survive.rs`):** models swap as a PATH-FLIP between two distinct fixtures (fixture_a→fixture_b), never an in-place rename — so `current_exe()`-follows-rename is never exercised; CI green on kitsubito while prod fails.

**STATUS 2026-06-11:** v0.4.1 PUBLIC (counter 8). hfenduleam rolled GREEN (no-bounce + wedge-heal proven, Windows leg of restoration DONE). kitsubito left as-is (v0.4.0 brain, unwedged, applied:8 record left uncorrected = appendix evidence). enlyzeam untouched. **V042-PLAN.md written**, sent to doyle for SIGN-OFF (operator delegated post-plan greenlight to doyle). Operator chose **debug-channel rollout** for the final Linux seamless observation. Next: doyle signs → mint REQ + build (T1 doc → T2 impl → T3 unit → T4 int → T5 release) → deployah publishes counter 9 → kitsubito+enlyzeam one bounce each → debug rollout → restoration close.

**v0.4.2 plan (deployah+todlando aligned; doyle confirmed ruling):**
- R1 mint `REQ-HAZARD-BRAIN-RESPAWN-PATH`: respawn must exec applied bytes via resolved canonical install path (captured at broker boot, pre-rename), never current_exe/.old-N.
- R2 tighten readiness-success: promotion asserts respawned exe_hash == applied artifact hash (close false-success hole).
- FIX brainproc.rs candidate spawn → canonical install path.
- TEST Linux in-place-rename apply E2E asserting respawned exe_hash == artifact.
- GATES + CI both runners → deployah publishes **counter 9**.
- FLEET: resident v0.4.0 brokers can't self-fix → v0.4.2 needs a **one-time MANUAL broker bounce on Linux** = project's actual FINAL manual bounce, paired with enlyzeam catch-up. THEN restoration close.

See [[v040-published-fleet-rolled]], [[pump-ipc-deadline-fix]].

-**STATUS 2026-06-11:** v0.4.1 PUBLIC (counter 8). hfenduleam rolled GREEN (no-bounce + wedge-heal proven, Windows leg of restoration DONE). kitsubito left as-is (v0.4.0 brain, unwedged, applied:8 record left uncorrected = appendix evidence). enlyzeam untouched. **V042-PLAN.md written**, sent to doyle for SIGN-OFF (operator delegated post-plan greenlight to doyle). Operator chose **debug-channel rollout** for the final Linux seamless observation. Next: doyle signs → mint REQ + build (T1 doc → T2 impl → T3 unit → T4 int → T5 release) → deployah publishes counter 9 → kitsubito+enlyzeam one bounce each → debug rollout → restoration close.

+**STATUS 2026-06-11 (build done):** v0.4.1 PUBLIC (counter 8). hfenduleam rolled GREEN (Windows leg DONE). kitsubito left as-is (v0.4.0 brain, applied:8 uncorrected = appendix evidence). **V042-PLAN.md doyle-SIGNED.** v0.4.2 fix BUILT on branch fix/v0.4.2-brain-respawn-path, **PR #5** open, CI both runners running. Commits: T1 doc 8aa309e (mint REQ + KH 6.11 + ADR-0018 Q3 amend), T2+T3 impl+unit be626bf (Half1 canonical capture + select_brain_exe; Half2 bytes-gate + ReleaseCache::staged_artifact_hash + TrialEnv ready_exe_hash/staged_artifact_hash; PROMOTE_BYTES_UNVERIFIED loud degrade per doyle), T4 int f69e0cd (brain_respawn_rename.rs — real broker proc, in-place rename P→P.old-N, respawn must run B; fails Linux pre-fix, closes brain_survive path-flip gap), T5 release (bump 0.4.2 + changelog). Local gates all green; traceable REQ [doc,impl,unit,int] all +. Operator chose **debug-channel rollout** for final Linux observation. NEXT: CI green → ping doyle pre-merge → merge → deployah publishes **counter 9** → kitsubito+enlyzeam one bounce each (final) → debug rollout → restoration close.


v0.4.2 fix **built + pushed** — PR #5, CI both runners running.

| commit | stage | content |
|---|---|---|
| `8aa309e` | doc | mint REQ-HAZARD-BRAIN-RESPAWN-PATH, KH 6.11, ADR-0018 Q3 amendment |
| `be626bf` | impl+unit | Half 1 (canonical capture + `select_brain_exe`) + Half 2 (bytes-gate + auto-rollback + `PROMOTE_BYTES_UNVERIFIED`) + 4 unit tests |
| `f69e0cd` | int | `brain_respawn_rename.rs` — real broker, in-place rename, **fails Linux pre-fix** |
| T5 | release | bump 0.4.2 + CHANGELOG |

Local gates all green; `REQ-HAZARD-BRAIN-RESPAWN-PATH` `[doc,impl,unit,int]` fully covered. The kitsubito Linux runner now executes the int E2E that proves the fix. On CI green → ping doyle pre-merge → merge → deployah publishes **counter 9** → one manual Linux bounce each (kitsubito + enlyzeam, the project's final) → debug-channel rollout → restoration close. Waiting on CI.


deployah is authorized to publish when it's ready. just message them


<task-notification>
<task-id>bn5x1hht0</task-id>
<tool-use-id>toolu_01SwQqWdD39GqL5negPwh1Vz</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\bn5x1hht0.output</output-file>
<status>completed</status>
<summary>Background command "Watch v0.4.2 CI to completion" completed (exit code 0)</summary>
</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.


Bash

gh run view 27340244549 --json status,conclusion -q '.status+" / "+.conclusion'; echo "---jobs---"; gh run view 27340244549 --json jobs -q '.jobs[] | .name+": "+.conclusion'

Confirm v0.4.2 CI conclusion + per-job


completed / success
---jobs---
test (self-hosted, Windows, hfenduleam): success
n1-gate (self-hosted, Linux, kitsubito): success
test (self-hosted, Linux, kitsubito): success
n1-gate (self-hosted, Windows, hfenduleam): success
traceability: success
twohost-b: skipped
twohost-a: skipped


**CI GREEN both runners** — test + n1-gate on Windows AND Linux, traceability ✅. Critically: the **kitsubito Linux `test` job passed**, meaning `brain_respawn_rename.rs` passed on Linux post-fix — the int E2E that fails pre-fix now confirms the fix on the OS that was broken. Pinging doyle pre-merge + heads-up to deployah.


$OWL send doyle 2>/dev/null <<'EOF'
v0.4.2 BUILT + CI GREEN both runners — pre-merge vet, your call. PR #5.
CI run 27340244549: completed/success — test+n1-gate BOTH runners green, traceability green, twohost skipped.
*** The kitsubito Linux `test` job is GREEN → brain_respawn_rename.rs PASSES on Linux post-fix — the int E2E that fails Linux pre-fix now confirms the fix on the OS that was broken. ***
Commits (rule-5 staged):
  8aa309e doc — mint REQ-HAZARD-BRAIN-RESPAWN-PATH, KH 6.11, ADR-0018 Q3 amendment
  be626bf impl+unit — Half1 (spawn_brain_supervisor canonical capture at t=0 + select_brain_exe; None→canonical, degrade to current_exe only if capture failed) + Half2 (supervise_brain Promoted-arm bytes_gate: match→promote, mismatch→kill+rollback+notif, absent→PROMOTE_BYTES_UNVERIFIED loud degrade) + TrialEnv ready_exe_hash/staged_artifact_hash + ReleaseCache::staged_artifact_hash(version, current_platform); 4 unit tests (select table, gate truth table, wrong-bytes→rollback, match→promote)
  f69e0cd int — brain_respawn_rename.rs: real `spt daemon run` from canonical P, real in-place rename P→P.old-8 + new bytes at P, kill→respawn must run B (closes the brain_survive path-flip gap)
  release — bump 0.4.2 + CHANGELOG (user-clean) + regen
traceable: REQ [doc,impl,unit,int] all +. Binding addition (loud degrade) folded. Capture-site race comment in place.
Flag anything; else give the merge nod and deployah publishes counter 9.
EOF
echo "d:${PIPESTATUS[0]}"
$OWL send deployah 2>/dev/null <<'EOF'
v0.4.2 CI GREEN both runners (PR #5, run 27340244549). Linux test job green = the in-place-rename int E2E passes on kitsubito post-fix (the real Linux proof). Pinged doyle for the pre-merge nod. On doyle's ok: merge #5 → main, tag v0.4.2, release-publish --tag v0.4.2 --key-id rel-primary-2026 --version 9. Counter 9. Then the one manual Linux broker bounce (kitsubito + enlyzeam) + debug-channel rollout. Standby for the go.
EOF
echo "p:${PIPESTATUS[0]}"

Ping doyle pre-merge + deployah heads-up


d:0
p:0


Pinged doyle for the pre-merge nod + deployah for the publish handoff.

## v0.4.2 — built, CI green both runners ✅

The Linux blocker from the v0.4.1 roll is **fixed and proven on Linux**: the kitsubito `test` job (running the new in-place-rename int E2E) is green post-fix — the same test fails on Linux pre-fix, so it's a real regression guard.

**Remaining (all gated, async):**
1. doyle's pre-merge nod (vet of the diff + green CI) — awaiting
2. deployah: merge #5 → tag v0.4.2 → sign+publish **counter 9**
3. one manual Linux broker bounce each (kitsubito + enlyzeam) — the project's *final* manual bounce, loads the fixed broker
4. debug-channel rollout → observe Linux seamless no-bounce on the fixed broker (the operator's chosen method)
5. **restoration close** — ROADMAP ✅ + KH 6.7/6.8 close-out

hfenduleam's Windows seamless proof already stands. Finding + full plan are in memory + `V042-PLAN.md`. Awaiting doyle.


<task-notification>
<task-id>bmby1p3hm</task-id>
<summary>Monitor event: "« spt event »"</summary>
<event><EVENT-PART seq="1/2" id="c7d703ac" type="msg" from="deployah">Clean — Linux in-place-rename E2E green on kitsubito is the proof that was missing. To avoid a double-merge race, confirm ownership: I'll run the SAME full publisher sequence I did for v0.4.1 on your go — merge #5 → main, checkout/pull, tag v0.4.2 + push, wait draft, release-publish --tag v0.4.2 --key-id rel-pr</EVENT-PART>
<EVENT-PART seq="2/2" id="c7d703ac">imary-2026 --version 9, flip public, ping you the artifact hashes. So just ping me &quot;go&quot; after doyle's nod — don't merge it yourself. Then you drive the one manual Linux bounce (kitsubito + enlyzeam) + debug-channel rollout. Counter 9 locked, seed resident. Standing by for go.</EVENT-PART></event>
</task-notification>


deployah confirmed: it owns the full publish sequence (merge #5 → tag → publish counter 9); I ping "go" only after doyle's nod, then I drive the Linux bounce + debug rollout. Won't merge myself.

Awaiting doyle's pre-merge nod. On nod → "go" to deployah.


<task-notification>
<task-id>bmby1p3hm</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/5" id="5c925200" type="msg" from="todlando">doyle PRE-MERGE ACK — v0.4.2 APPROVED. Merge; deployah publishes counter 9.

Walked all four commits:
- Half 1: select_brain_exe pure-fn selection (Some rollback &gt; captured canonical &gt; degrade current_exe) with the unit table; capture-at-t=0 with the race+backstop comment exactly as ruled; supervise_</EVENT-PART>
<EVENT-PART seq="2/5" id="5c925200">brain generic sig untouched (closure binds canonical) — unit harness unscathed.
- Half 2: bytes_gate truth table correct incl. the which-side-absent discrimination in the PROMOTE_BYTES_UNVERIFIED line (binding addition folded right); Mismatch arm kills+waits the child BEFORE rollback (A11 never-two-live-brains preserved), reuses the exist</EVENT-PART>
<EVENT-PART seq="3/5" id="5c925200">ing rollback path, Promoted-arm scoping keeps non-trial respawns untouched. TestTrialEnv defaults None/None → every pre-existing test rides the degrade arm unchanged — clean.
- Int: brain_respawn_rename.rs is the faithful reproduction — real broker FROM canonical P, real rename-then-replace, respawn must run B. The rustdoc's platform-</EVENT-PART>
<EVENT-PART seq="4/5" id="5c925200">honest framing (&quot;a regression re-arms the Linux red&quot;) is exactly the right posture, and kitsubito-green post-fix is the proof that matters.
- Changelog: accurate, user-clean, honestly names the Linux gap and the new safeguard. Fleet-migration nuance (the one bounce to LOAD this fix) correctly lives in ops, not the release body.<br</EVENT-PART>
<EVENT-PART seq="5/5" id="5c925200">>
Zero deviations. After publish: ops sequence as planned (kitsubito apply + final bounce, enlyzeam straight-to-0.4.2 + final bounce, debug-rollout Linux observation, then restoration close — run the close bookkeeping past me).</EVENT-PART></event>
</task-notification>


doyle **PRE-MERGE APPROVED** — zero deviations, walked all four commits. Sending deployah "go" to publish counter 9.


$OWL send deployah 2>/dev/null <<'EOF'
GO — doyle pre-merge APPROVED, zero deviations. Publish v0.4.2.
Run your full sequence: merge PR #5 → main → tag v0.4.2 + push → wait draft → release-publish --tag v0.4.2 --key-id rel-primary-2026 --version 9 → flip public → ping me the artifact hashes (win + linux).
Counter 9. After you publish I drive: kitsubito apply v0.4.2 + the one manual broker bounce (final), enlyzeam straight-to-0.4.2 + its bounce (final), then the debug-channel rollout for the Linux seamless observation, then restoration close (run past doyle). Go.
EOF
echo "exit:${PIPESTATUS[0]}"

Send deployah go for counter 9 publish


"Go" sent. deployah now runs the full publish (merge #5 → tag v0.4.2 → sign+publish counter 9 → artifact hashes back to me).

## v0.4.2 fix complete & approved
- doyle pre-merge: **approved, zero deviations**
- CI green both runners (Linux int E2E proves the fix on the OS that was broken)
- deployah publishing counter 9

**On publish, I drive the close:** kitsubito apply + final manual bounce → enlyzeam straight-to-0.4.2 + final bounce → debug-channel rollout to observe Linux seamless no-bounce on the fixed broker → restoration close bookkeeping (ROADMAP ✅ + KH 6.7/6.8), run past doyle.

This is the last manual bounce in the project's history — after it, every release rolls seamlessly on both OSes, clearing the runway for spt-claude-code. Awaiting deployah's published hashes.


<task-notification>
<task-id>bmby1p3hm</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/5" id="0c0f7b24" type="msg" from="deployah">[v0.4.2 PUBLIC — sign+publish DONE, counter 9]

Full sequence clean: merge #5 → main (725aebf, version 0.4.2, brain_respawn_rename.rs present) → tag v0.4.2 + push → draft → release-publish exit 0:
  • verified vs SHA256SUMS:
      spt-x86_64-linux        500a9e7b…6967732c
      spt-x86_64</EVENT-PART>
<EVENT-PART seq="2/5" id="0c0f7b24">-windows.exe  b9ec8ddd…aca92eaf
  • signed 54188f12…05e3ec9 (key_id rel-primary-2026)
  • per-platform *.release.json + update-set.json (v9 &quot;0.4.2&quot;, win-msvc + linux-gnu) uploaded
  • draft → draft:false (PUBLIC)

https://github.com/SaberMage/spt-releases/releases/tag/v0.4.2

ACCEPTANCE HASHES (brain.</EVENT-PART>
<EVENT-PART seq="3/5" id="0c0f7b24">ready exe_hash must MATCH post-apply):
  linux  500a9e7b7b887db261105b0fc1c0a051e36efaa8be8da5f69e6f5a796967732c
  win    b9ec8dddc7b2ebe590cc549e9cdf31ad9484649b1a451163ac15a52daca92eaf

Your turn:
  - kitsubito: apply v0.4.2 + the ONE manual broker bounce (final) — post-bounce, the v0.4.2 broker's canonical-path respawn is l</EVENT-PART>
<EVENT-PART seq="4/5" id="0c0f7b24">oaded; verify brain exe_hash == 500a9e7b…
  - enlyzeam: straight-to-0.4.2 + its bounce (final)
  - debug-channel rollout → watch the FIXED broker roll a subsequent update seamless on Linux (the real in-place-rename field proof)
  - restoration close (past doyle)

NOTE the asymmetry that's now load-bearing: this v0.4.2 apply </EVENT-PART>
<EVENT-PART seq="5/5" id="0c0f7b24">still rides the BUGGY v0.4.0 broker on kitsubito → expect the old-bytes-respawn one more time; the manual bounce is what loads the fix. The seamless proof comes on the NEXT update after the bounce, via debug-channel. I'm idle on-call if any hash/re-sign question.</EVENT-PART></event>
</task-notification>


v0.4.2 **PUBLIC**, counter 9. Acceptance hashes: linux `500a9e7b…6967732c`, win `b9ec8ddd…aca92eaf`. deployah's load-bearing note: kitsubito's v0.4.2 apply rides the **buggy v0.4.0 broker** → brain respawns old bytes one more time; the **manual bounce loads the fix**; seamless proof comes on the *next* update via debug-channel.

Driving the close. ACK deployah + sync main + capture kitsubito pre-state.


$OWL send deployah 2>/dev/null <<'EOF'
Got hashes — linux 500a9e7b…6967732c, win b9ec8ddd…aca92eaf, counter 9 PUBLIC. Driving the close now: kitsubito fetch+apply v0.4.2 (expecting the old-bytes respawn one more time on the resident v0.4.0 broker, applied:9 recorded), then the ONE manual systemctl --user restart = the final bounce that loads the v0.4.2 broker → verify brain exe_hash == 500a9e7b. Then enlyzeam, debug-rollout, close. Will report each step.
EOF
echo "ack:${PIPESTATUS[0]}"
git checkout main 2>&1 | tail -1 && git pull --ff-only origin main 2>&1 | tail -3

ACK deployah + sync main


ack:0
Already on 'main'
From https://github.com/SaberMage/spt-core
 * branch            main       -> FETCH_HEAD
Already up to date.


1	- [v0.4.1 Linux respawn blocker](v041-linux-respawn-blocker.md) — v0.4.1 PUBLIC (counter 8); fleet roll found Linux brain-respawn bug (current_exe follows apply-rename → brain runs OLD bytes, records applied:8). hfenduleam(Win) GREEN, kitsubito(Linux) RED. RESOLVED by v0.4.2 — see [[v042-published]]
2	- [v0.4.2 published](v042-published.md) — PUBLIC 2026-06-11 (counter 9); FIXES v0.4.1 Linux respawn blocker (respawn via canonical install path not current_exe + bytes-assert→auto-rollback; REQ-HAZARD-BRAIN-RESPAWN-PATH + real-rename E2E). deployah drove full merge→tag→sign→publish solo. hashes linux 500a9e7b… win b9ec8ddd…. ASYMMETRY: v0.4.2 apply still rides buggy v0.4.0 broker → 1 final manual Linux bounce loads fix; seamless proof on NEXT update via debug-channel. todlando driving fleet roll + restoration close
3	- [pump-IPC-deadline fix](pump-ipc-deadline-fix.md) — DONE: MERGED to main @ 4d8b810 (PR #3), doyle-approved, CI green both runners; reader-thread+channel carrier (nonblock dead on Win named pipes), both amendments folded, no tag (rides v0.4.1). Don't re-plan
4	- [CI self-host migration](ci-selfhost-migration.md) — DONE: runners live on kitsubito + hfenduleam (gravity retired 2026-06-07); twohost jobs gated by [twohost] commit tag; don't re-plan
5	- [which process is the listener](spt-daemon-is-live-infra.md) — owl.exe (plugin) = live listener, hands off; target\debug\spt.exe daemon = dev/test artifact, killable
6	- [self-clear skill API-blocked](self-clear-skill-api-blocked.md) — never invoke /self-clear; usage-policy classifier refuses the turn; continue in-context / user-manual /clear instead
7	- [Adapter glue-model boundary](adapter-glue-model-boundary.md) — adapters = manifest + binary only; real adapters in own repos; no adapter-aware source in spt-core
8	- [M6 shipped v0.1.0](m6-shipped-v010.md) — v0.1.0 public 2026-06-05; release procedure = RELEASE-RUNBOOK.md; releases-repo/ is repo-face truth; don't re-plan M6
9	- [Gravity deploy procedure](gravity-deploy-procedure.md) — ssh david@100.100.62.43; build in ~/spt-core-deploy NOT runner workspace; setsid daemon; ~1 min first-sync after restart
10	- [Linux SPT_HOME no-bug](linux-spt-home-no-bug.md) — unix spawn_detached inherits env (no env_clear), forwards SPT_HOME; only Win CreateEnvironmentBlock rebuilds/drops it (f99dc25). Default-home daemon = launched w/o the var, not a propagation bug; don't reopen
11	- [kitsubito Linux rig](kitsubito-linux-rig.md) — ssh reavus@kitsubito (key auth); cargo at ~/.cargo/bin; deploy checkout origin=local runner (NOT GitHub) so unpushed commits transfer via git bundle; live spt-daemon user unit, --user ops need no sudo; build-in-scratch leaves ~/.local/bin/spt untouched
12	- [Use $OWL send for messaging](owl-send-not-legacy-spt-send.md) — `$OWL send` works (legacy = where live perches run); `spt send` is the NEW spt-core binary but no perches on it yet → bounces NO_PERCH; don't read `$?` after piping send through tail (use PIPESTATUS)
13	- [daemon service-detection gotcha](daemon-service-detection-gotcha.md) — service detection reading GLOBAL OS state is blind on a single-home non-elevated dev box; gate Linux service routing on SPT_HOME-unset, Windows run-refuse on would-vanish (not is_elevated); push→CI BEFORE tagging a release (v0.3.0 tagged early, CI caught 2 regressions, retagged)
14	- [v0.3.2 platform-safe update](v032-platform-safe-update.md) — REQ-UPD-8 PUBLISHED + rig-verified 2026-06-09 (counter 6): fetch stages multi-platform update-set, apply refuses unstamped/wrong-OS single. Fleet kitsubito+hfenduleam=0.3.2 (ENLYZEAM TODO). GOTCHA: fetch/pump floor=staged_version → can't re-stage same version single→set; clear `$SPT_HOME/releases` first (Linux ~/.spt-core, Win %LOCALAPPDATA%\spt-core). CORRECTION: release-publish never built a SignedUpdateSet pre-v0.3.2 (was debug_rollout)
15	- [boot-race self-heal](boot-race-self-heal.md) — v0.3.1: daemon self-heals a net-less boot start (NetHost retry+attach to OnceLock broker); systemd-USER network-online ordering does NOT reliably prevent the race under linger (field-proven kitsubito) — the self-heal is the real guarantee, unit ordering is belt
16	- [v0.4.0 published + fleet rolled](v040-published-fleet-rolled.md) — restoration release PUBLIC 2026-06-10 (counter 7); D7-4 bytes-match done both nodes (brain.ready exe_hash == artifact); v0.3.2→v0.4.0 apply needed last manual bounce (in-process can't self-relaunch); seamless no-bounce proof + enlyzeam catch-up + milestone close all deferred to v0.4.1
17	- [v0.4.1 published](v041-published.md) — PUBLIC 2026-06-11 (counter 8); deployah drove full merge→tag→sign→publish solo; D7 SEAMLESS no-bounce fleet-verify payload (apply must roll bytes live w/ pid change, no manual bounce); acceptance hashes linux 35c9544c… win 728a1bfc…; hfenduleam(Win) GREEN no-bounce, kitsubito(Linux) RED → CLOSE BLOCKED: v0.4.0 broker respawns via current_exe() which follows apply-rename to .old-8=OLD bytes (Linux only); artifacts SOUND don't yank; v0.4.2 fix = respawn by canonical path + bytes-assert, needs manual Linux broker bounce; deployah takes counter 9 publish


Memory index updated (Psyche folded the v0.4.1/v0.4.2 entries — good, context preserved). Driving kitsubito: pre-state + fetch + apply v0.4.2.


ssh reavus@kitsubito 'export PATH=$HOME/.cargo/bin:$PATH
echo "=== PRE brain.ready ==="; cat ~/.spt-core/brain.ready 2>/dev/null; echo
echo "=== PRE version (CLI) ==="; spt --version 2>&1
echo "=== FETCH v0.4.2 ==="; spt update fetch 2>&1 | tail -6; echo "FETCH:$?"
echo "=== APPLY ==="; spt update apply 2>&1 | tail -6; echo "APPLY:$?"' 2>&1 | tail -25

kitsubito pre + fetch + apply v0.4.2


=== PRE brain.ready ===
{"pid":1253137,"generation":0,"exe_hash":"35c9544cfdd4883fb87d8ca8da1798addb82b4464b7441e79ae4db7617314f08"}
=== PRE version (CLI) ===
spt 0.4.1
=== FETCH v0.4.2 ===
Fetching update set from SaberMage/spt-releases…
  fetching spt-x86_64-windows.exe (x86_64-pc-windows-msvc)…
  fetching spt-x86_64-linux (x86_64-unknown-linux-gnu)…
UPDATE_STAGED:9 (set, 2 platforms, from SaberMage/spt-releases) — run `spt update apply` to apply
FETCH:0
=== APPLY ===
Applying spt-core v0.4.2 — trial started.
The new code is on trial; it auto-rolls-back if it fails to come up. Watch for a rollback notif (verdict also via `spt daemon status`).
Changelog: https://www.github.com/SaberMage/spt-releases/releases
APPLY:0


kitsubito's live broker had restarted (gen 0, now running v0.4.1 `35c9544c` — systemd reloaded canonical). So the resident broker is **v0.4.1** (still the buggy version). Apply v0.4.2 trial started. Expecting post-apply brain = v0.4.1 bytes (buggy respawn), then the bounce loads v0.4.2. Capturing post-apply.


sleep 14; ssh reavus@kitsubito 'export PATH=$HOME/.cargo/bin:$PATH
echo "=== POST-APPLY brain.ready ==="; cat ~/.spt-core/brain.ready 2>/dev/null; echo
echo "=== applied record ==="; cat ~/.spt-core/releases/applied-state.json 2>/dev/null; echo; cat ~/.spt-core/releases/last-outcome.json 2>/dev/null; echo
echo "=== canonical hash (should be v0.4.2 500a9e7b) ==="; sha256sum ~/.local/bin/spt | cut -d" " -f1
echo "=== broker exe ==="; readlink /proc/$(cat ~/.spt-core/daemon.pid)/exe 2>/dev/null' 2>&1 | tail -15

kitsubito post-apply state


=== POST-APPLY brain.ready ===
{"pid":1356129,"generation":1,"exe_hash":"35c9544cfdd4883fb87d8ca8da1798addb82b4464b7441e79ae4db7617314f08"}
=== applied record ===
{"phase":"applied","version":9}
{"outcome":"applied"}
=== canonical hash (should be v0.4.2 500a9e7b) ===
500a9e7b7b887db261105b0fc1c0a051e36efaa8be8da5f69e6f5a796967732c
=== broker exe ===
/home/reavus/.local/bin/spt.old-9


Exactly as predicted: apply rode the buggy v0.4.1 broker → brain respawned onto OLD v0.4.1 bytes (`35c9544c`, gen 1), recorded `applied:9`; canonical correctly = v0.4.2 (`500a9e7b`); broker exe = `.old-9` (current_exe followed the rename). Now **the one manual bounce** — restart loads v0.4.2 from canonical.


ssh reavus@kitsubito 'export PATH=$HOME/.cargo/bin:$PATH
echo "=== BOUNCE: systemctl --user restart spt-daemon ==="; systemctl --user restart spt-daemon 2>&1; echo "restart-rc:$?"
sleep 8
echo "=== POST-BOUNCE brain.ready ==="; cat ~/.spt-core/brain.ready 2>/dev/null; echo
echo "=== broker exe (should be canonical, NOT .old-N) ==="; readlink /proc/$(cat ~/.spt-core/daemon.pid)/exe 2>/dev/null
echo "=== version + status ==="; spt --version 2>&1; spt daemon status 2>&1 | head -6' 2>&1 | tail -18

kitsubito final bounce + verify v0.4.2 loaded


=== BOUNCE: systemctl --user restart spt-daemon ===
restart-rc:0
=== POST-BOUNCE brain.ready ===
{"pid":1357219,"generation":0,"exe_hash":"500a9e7b7b887db261105b0fc1c0a051e36efaa8be8da5f69e6f5a796967732c"}
=== broker exe (should be canonical, NOT .old-N) ===
/home/reavus/.local/bin/spt
=== version + status ===
spt 0.4.2
daemon: running (pid 1357210)
peer pump: live (last tick 2s ago)
managed-by: systemd --user (active)
subnets:
  SPT_DEV  nodes=3 endpoints=0
local endpoints: (none)


🎯 **kitsubito FIXED** — the final bounce loaded v0.4.2:
- brain.ready exe_hash = **`500a9e7b`** (== published v0.4.2 linux) ✓, gen 0
- broker exe = `/home/reavus/.local/bin/spt` (CANONICAL, not `.old-N`) ✓ — fixed broker resolves canonical correctly
- `spt --version` 0.4.2, daemon healthy, pump live ✓

Resident broker is now v0.4.2 (the fix loaded). Reporting + moving to enlyzeam.


$OWL send deployah 2>/dev/null <<'EOF'
kitsubito DONE — final bounce loaded the fix.
  apply v0.4.2 rode the buggy v0.4.1 broker as predicted: brain respawned onto OLD bytes 35c9544c (v0.4.1), recorded applied:9, broker exe followed rename to .old-9. canonical correctly = 500a9e7b.
  systemctl --user restart (the ONE final bounce): broker re-exec'd from canonical → POST-BOUNCE brain.ready exe_hash = 500a9e7b (== your published v0.4.2 linux hash), gen 0, broker exe = /home/reavus/.local/bin/spt (CANONICAL not .old-N), spt --version 0.4.2, pump live.
Resident broker now v0.4.2 = fix LOADED. The seamless no-bounce proof comes via the debug-channel rollout (next update on this fixed broker). Onto enlyzeam.
EOF
echo "d:${PIPESTATUS[0]}"
echo "=== enlyzeam reachability ==="; ssh -o ConnectTimeout=8 -o BatchMode=yes enlyzeam 'echo OK; uname -a; spt --version 2>/dev/null || echo no-spt-on-path' 2>&1 | head -6

Report kitsubito + probe enlyzeam access


d:0
=== enlyzeam reachability ===
At line:1 char:46
+ echo OK; uname -a; spt --version 2>/dev/null || echo no-spt-on-path
+                                              ~~
The token '||' is not a valid statement separator in this version.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : InvalidEndOfLine


enlyzeam reachable via ssh (key auth) — it's a **Windows** node (remote PowerShell choked on bash `||`). It needs the bounce because it's behind on the *in-process* (pre-restoration) daemon — the v0.3.x→v0.4.x hop inherently needs one bounce. Probing its current version with PS-safe syntax.


echo "=== enlyzeam version ==="; ssh enlyzeam "spt --version" 2>&1 | head -3
echo "=== enlyzeam brain.ready (if v0.4.x two-process) ==="; ssh enlyzeam "type \$env:LOCALAPPDATA\spt-core\brain.ready" 2>&1 | head -3
echo "=== enlyzeam daemon status ==="; ssh enlyzeam "spt daemon status" 2>&1 | head -8

Probe enlyzeam version + state


=== enlyzeam version ===
spt 0.3.2
=== enlyzeam brain.ready (if v0.4.x two-process) ===
type : Cannot find path 'C:\Users\decid\AppData\Local\spt-core\brain.ready' because it does not
exist.
At line:1 char:1
=== enlyzeam daemon status ===
daemon: running (pid 68580)
peer pump: STALLED (last tick 2612s ago — advertising/connecting is not running; restart the daemon)
managed-by: manual — at-logon task 'spt-core daemon' registered (auto-starts at logon)
subnets:
  SPT_DEV  nodes=3 endpoints=1
local endpoints:
  gerald                       ready_agent    ready=true alive=true


enlyzeam: **v0.3.2** (the perennial TODO — pre-restoration in-process daemon, no brain.ready), pump STALLED, and it **hosts a ready_agent "gerald"** (alive). The bounce drops gerald — so I'll do the non-disruptive fetch+apply now (in-process apply just swaps disk, daemon + gerald keep running), then flag gerald before the bounce. Fetch+apply:


echo "=== enlyzeam FETCH v0.4.2 ==="; ssh enlyzeam "spt update fetch" 2>&1 | tail -6
echo "=== enlyzeam APPLY ==="; ssh enlyzeam "spt update apply" 2>&1 | tail -6
echo "=== post-apply: running version (in-process — expect STILL 0.3.2 until bounce) ==="; ssh enlyzeam "spt daemon status" 2>&1 | head -3
echo "=== canonical on disk (expect v0.4.2) ==="; ssh enlyzeam "(Get-FileHash \$env:LOCALAPPDATA\spt-core\bin\spt.exe -Algorithm SHA256).Hash.ToLower()" 2>&1 | head -3

enlyzeam fetch + apply v0.4.2


=== enlyzeam FETCH v0.4.2 ===
Fetching update set from SaberMage/spt-releases…
  fetching spt-x86_64-windows.exe (x86_64-pc-windows-msvc)…
  fetching spt-x86_64-linux (x86_64-unknown-linux-gnu)…
UPDATE_STAGED:9 (set, 2 platforms, from SaberMage/spt-releases) — run `spt update apply` to apply
=== enlyzeam APPLY ===
Updated spt-core to v0.4.2.
Check out the changelog at https://www.github.com/SaberMage/spt-releases/releases
=== post-apply: running version (in-process — expect STILL 0.3.2 until bounce) ===
daemon: running (pid 68580)
peer pump: STALLED (last tick 2669s ago — advertising/connecting is not running; restart the daemon)
managed-by: manual — at-logon task 'spt-core daemon' registered (auto-starts at logon)
=== canonical on disk (expect v0.4.2) ===
b9ec8dddc7b2ebe590cc549e9cdf31ad9484649b1a451163ac15a52daca92eaf


enlyzeam disk now v0.4.2 (`b9ec8ddd` win ✓), daemon still running v0.3.2 in-process (pid 68580). **The bounce drops the hosted ready_agent "gerald"** (re-armable; enlyzeam's pump is already stalled). It's the approved final catch-up step — proceeding. Bounce + verify:


echo "=== STOP old v0.3.2 daemon ==="; ssh enlyzeam "spt daemon stop" 2>&1 | head -3
echo "=== START (auto via ensure_running) ==="; ssh enlyzeam "spt daemon status" 2>&1 | head -4
sleep 6
echo "=== POST-BOUNCE version ==="; ssh enlyzeam "spt --version" 2>&1 | head -2
echo "=== POST-BOUNCE brain.ready (two-process now? expect v0.4.2 b9ec8ddd) ==="; ssh enlyzeam "type \$env:LOCALAPPDATA\spt-core\brain.ready" 2>&1 | head -3
echo "=== status ==="; ssh enlyzeam "spt daemon status" 2>&1 | head -6

enlyzeam bounce + verify v0.4.2 two-process


=== STOP old v0.3.2 daemon ===
DAEMON_STOPPED
=== START (auto via ensure_running) ===
daemon: not running (`spt daemon start`, or any spt command auto-starts it)
managed-by: manual — at-logon task 'spt-core daemon' registered (auto-starts at logon)
subnets:
  SPT_DEV  nodes=3 endpoints=1
=== POST-BOUNCE version ===
spt 0.4.2
=== POST-BOUNCE brain.ready (two-process now? expect v0.4.2 b9ec8ddd) ===
type : Cannot find path 'C:\Users\decid\AppData\Local\spt-core\brain.ready' because it does not
exist.
At line:1 char:1
=== status ===
daemon: not running (`spt daemon start`, or any spt command auto-starts it)
managed-by: manual — at-logon task 'spt-core daemon' registered (auto-starts at logon)
subnets:
  SPT_DEV  nodes=3 endpoints=1
local endpoints:
  gerald                       ready_agent    ready=true alive=true


Daemon stopped; status is read-only (didn't auto-start). CLI now v0.4.2 ✓. Explicitly starting the v0.4.2 daemon:


echo "=== START v0.4.2 daemon ==="; ssh enlyzeam "spt daemon start" 2>&1 | head -4
sleep 7
echo "=== brain.ready (expect v0.4.2 win b9ec8ddd, two-process) ==="; ssh enlyzeam "type \$env:LOCALAPPDATA\spt-core\brain.ready" 2>&1 | head -3
echo "=== status ==="; ssh enlyzeam "spt daemon status" 2>&1 | head -8

Start enlyzeam v0.4.2 daemon + verify


=== START v0.4.2 daemon ===
ELEVATED_DAEMON: no unelevated identity to drop to — daemon inherits the elevated token
DAEMON_STARTED
=== brain.ready (expect v0.4.2 win b9ec8ddd, two-process) ===
type : Cannot find path 'C:\Users\decid\AppData\Local\spt-core\brain.ready' because it does not
exist.
At line:1 char:1
=== status ===
daemon: not running (`spt daemon start`, or any spt command auto-starts it)
managed-by: manual — at-logon task 'spt-core daemon' registered (auto-starts at logon)
subnets:
  SPT_DEV  nodes=3 endpoints=1
local endpoints:
  gerald                       ready_agent    ready=true alive=true


`spt daemon start` over my **elevated** ssh session hit the Windows elevated-spawn path ("no unelevated identity to drop to") and the daemon isn't staying up — the at-logon task normally starts it *unelevated*. Checking process state + the scheduled task before re-trying the right way.


echo "=== spt processes ==="; ssh enlyzeam "Get-Process spt -ErrorAction SilentlyContinue | Select-Object Id,ProcessName,StartTime | Format-Table -AutoSize" 2>&1 | head -8
echo "=== scheduled task ==="; ssh enlyzeam "Get-ScheduledTask -TaskName '*spt*' -ErrorAction SilentlyContinue | Select-Object TaskName,State | Format-Table -AutoSize" 2>&1 | head -6
echo "=== am I elevated on enlyzeam? ==="; ssh enlyzeam "[bool]([System.Security.Principal.WindowsPrincipal][System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)" 2>&1 | head -2

Check enlyzeam daemon procs + task + elevation


=== spt processes ===

   Id ProcessName StartTime
   -- ----------- ---------
52152 spt         6/8/2026 9:47:53 PM

=== scheduled task ===

TaskName        State
--------        -----
spt-core daemon Ready

=== am I elevated on enlyzeam? ===
True


One stale spt proc (pid 52152, from Jun 8 — likely gerald's orphaned perch host, not the daemon). The daemon I `start`ed died (elevated-context issue). The scheduled task "spt-core daemon" is Ready — triggering it runs the daemon the normal **unelevated** way. Running it:


echo "=== trigger the at-logon task (unelevated daemon start) ==="; ssh enlyzeam "Start-ScheduledTask -TaskName 'spt-core daemon'; Start-Sleep -Seconds 1; (Get-ScheduledTask -TaskName 'spt-core daemon').State" 2>&1 | head -3
sleep 7
echo "=== brain.ready (expect v0.4.2 win b9ec8ddd) ==="; ssh enlyzeam "type \$env:LOCALAPPDATA\spt-core\brain.ready" 2>&1 | head -3
echo "=== version + status ==="; ssh enlyzeam "spt daemon status" 2>&1 | head -8

Start enlyzeam daemon via scheduled task + verify


=== trigger the at-logon task (unelevated daemon start) ===
Running
=== brain.ready (expect v0.4.2 win b9ec8ddd) ===
{"pid":64412,"generation":0,"exe_hash":"b9ec8dddc7b2ebe590cc549e9cdf31ad9484649b1a451163ac15a52daca92eaf"}
=== version + status ===
daemon: running (pid 71640)
peer pump: live (last tick 4s ago)
managed-by: manual — at-logon task 'spt-core daemon' registered (auto-starts at logon)
subnets:
  SPT_DEV  nodes=3 endpoints=1
local endpoints:
  gerald                       ready_agent    ready=true alive=true


🎯 **enlyzeam CAUGHT UP to v0.4.2** — the project's final manual bounce:
- brain.ready exe_hash = **`b9ec8ddd`** (== published v0.4.2 win) ✓, gen 0, **two-process now** (brain.ready exists — was absent on the v0.3.2 in-process daemon)
- daemon running, pump **LIVE** (was STALLED) ✓
- gerald re-attached (ready/alive) — survived the bounce

(Note: `spt daemon start` over my elevated ssh hit the Windows elevated-spawn guard; the **at-logon scheduled task** started it unelevated correctly.)

Fleet now: kitsubito=0.4.2 (fixed broker), enlyzeam=0.4.2 (fixed broker), hfenduleam=0.4.1. Reporting + rolling hfenduleam to v0.4.2 (Windows two-process → should be **seamless, no bounce** = bonus Windows proof).


$OWL send deployah 2>/dev/null <<'EOF'
enlyzeam CAUGHT UP — v0.3.2 → v0.4.2, the project's FINAL manual bounce done.
  fetch+apply staged 9, swapped canonical to b9ec8ddd (v0.4.2 win); v0.3.2 in-process daemon kept running old code (the original KH 6.7 in-process bug — expected on the pre-restoration hop).
  Bounce: stopped the v0.3.2 daemon, started v0.4.2 via the at-logon scheduled task (the elevated-ssh `spt daemon start` hit the no-unelevated-identity guard; the scheduled task starts it unelevated, correct). POST: brain.ready exe_hash = b9ec8ddd (== your v0.4.2 win hash), gen 0, TWO-PROCESS (brain.ready now exists), pump LIVE, gerald re-attached.
Fleet: kitsubito 0.4.2 (fixed broker), enlyzeam 0.4.2 (fixed broker). hfenduleam still 0.4.1 — rolling it v0.4.1→0.4.2 now (Windows two-process → expect SEAMLESS no-bounce, a bonus Win proof). Then debug-channel rollout for the Linux seamless proof on kitsubito's fixed broker, then close.
EOF
echo "d:${PIPESTATUS[0]}"
echo "=== hfenduleam roll v0.4.1 -> v0.4.2 (local) ==="
echo "PRE brain.ready:"; cat "$LOCALAPPDATA/spt-core/brain.ready"; echo
spt update fetch 2>&1 | tail -3; echo "FETCH:${PIPESTATUS[0]}"
spt update apply 2>&1 | tail -4; echo "APPLY:${PIPESTATUS[0]}"

Report enlyzeam + roll hfenduleam to v0.4.2


d:0
=== hfenduleam roll v0.4.1 -> v0.4.2 (local) ===
PRE brain.ready:
{"pid":6620,"generation":1,"exe_hash":"728a1bfc2fb3f6a73a695a9a6e5dee5ef79325a9d2b23bae806a64b00df62d70"}
Fetching update set from SaberMage/spt-releases…
UPDATE_FETCH_REJECTED:Rollback { current: 9, candidate: 9 }
FETCH:1
Applying spt-core v0.4.2 — trial started.
The new code is on trial; it auto-rolls-back if it fails to come up. Watch for a rollback notif (verdict also via `spt daemon status`).
Changelog: https://www.github.com/SaberMage/spt-releases/releases
APPLY:0


hfenduleam: fetch rejected because v0.4.2 (counter 9) is **already staged via P2P propagation** (kitsubito/enlyzeam gossiped it — the fleet converging). Harmless — `apply` proceeded on the staged v0.4.2 (trial started). This is the Windows two-process seamless roll (v0.4.1 broker, no bounce). Capturing post-apply.


sleep 12
echo "=== hfenduleam POST-APPLY brain.ready (expect v0.4.2 b9ec8ddd, new brain pid, NO bounce) ==="; cat "$LOCALAPPDATA/spt-core/brain.ready"; echo
echo "=== broker pid (daemon.pid — expect UNCHANGED 17232) ==="; cat "$LOCALAPPDATA/spt-core/daemon.pid"; echo
echo "=== version + status ==="; spt --version 2>&1; spt daemon status 2>&1 | head -4

hfenduleam post-apply (seamless v0.4.2)


=== hfenduleam POST-APPLY brain.ready (expect v0.4.2 b9ec8ddd, new brain pid, NO bounce) ===
{"pid":39820,"generation":2,"exe_hash":"b9ec8dddc7b2ebe590cc549e9cdf31ad9484649b1a451163ac15a52daca92eaf"}
=== broker pid (daemon.pid — expect UNCHANGED 17232) ===
17232
=== version + status ===
spt 0.4.2
daemon: running (pid 17232)
peer pump: live (last tick 27s ago)
managed-by: manual — at-logon task 'spt-core daemon' registered (auto-starts at logon)
subnets:


🎯 **hfenduleam SEAMLESS v0.4.1 → v0.4.2 (no bounce)** — bonus Windows proof:
- brain.ready: pid 6620→**39820**, gen 1→2, exe_hash → **`b9ec8ddd`** (v0.4.2 win) ✓
- broker pid **17232 UNCHANGED** — same broker now survived TWO seamless brain updates (v0.4.0→0.4.1→0.4.2)
- `spt --version` 0.4.2, pump live, just `spt update apply`, zero bounce ✓

**Fleet now all on v0.4.2.** kitsubito + enlyzeam have the fixed v0.4.2 *resident broker* (bounced); hfenduleam runs v0.4.2 brain on its resident broker (Windows respawn always correct, no bounce needed). Remaining for close: the **Linux seamless observation** via debug-channel rollout on kitsubito's fixed broker. Reporting + reading the debug-rollout mechanics.


$OWL send doyle 2>/dev/null <<'EOF'
FLEET ON v0.4.2 — fix loaded everywhere.
  kitsubito (Linux): v0.4.2 fixed broker resident (final bounce); brain exe_hash 500a9e7b == published.
  enlyzeam (Win): v0.3.2 → v0.4.2 (final bounce, the perennial laggard caught up); two-process now, pump healed, gerald re-attached; exe_hash b9ec8ddd.
  hfenduleam (Win): SEAMLESS v0.4.1 → v0.4.2, NO bounce — broker pid 17232 UNCHANGED across now-two seamless brain updates (0.4.0→0.4.1→0.4.2); brain exe_hash b9ec8ddd. (Win old broker resolves canonical correctly — no bounce needed; bytes-gate backstop is Linux-relevant.)
Two final manual bounces total (kitsubito + enlyzeam), exactly as planned — the project's last. Windows seamless leg PROVEN (hfenduleam, twice). Remaining for restoration close: the LINUX seamless observation on kitsubito's fixed v0.4.2 broker via the debug-channel rollout (operator's chosen method), then the close bookkeeping (past you). Reading DEBUG-ROLLOUT.md now.
EOF
echo "d:${PIPESTATUS[0]}"
echo "=== DEBUG-ROLLOUT.md ==="; cat docs/DEBUG-ROLLOUT.md 2>/dev/null | head -70

Report fleet-on-v0.4.2 + read debug-rollout doc


d:0
=== DEBUG-ROLLOUT.md ===
# Debug rollout runbook

<!-- [doc->REQ-UPD-6] -->

Debug rollout is the maintainer-only fast path for testing a local spt-core
build across a trusted lab subnet. It uses the normal signed self-update
substrate: no raw peer file-copy, no production `spt` CLI surface, and no
embedded debug trust anchor.

Use this when debugging needs a local build to reach multiple lab nodes quickly.
Do not use it for public releases; use `docs/RELEASE-RUNBOOK.md` for that.

## Mental model

- A rollout is a signed `SignedUpdateSet`: one signed metadata record, with one
  artifact digest per Rust target triple.
- Each recipient verifies the set under its node-local `release-keys.json`,
  selects only its own platform artifact, stages it, and then follows the normal
  consent/apply policy.
- Debug and stable are separate channels. A debug-pinned node accepts only
  `channel = "debug"` offers; a stable-pinned node accepts only stable offers.
- Debug versions are monotonic within the debug channel. To recover from a bad
  debug build, publish a higher debug version, even if the bytes are a previous
  known-good binary.
- A broker-touching candidate may be staged, but apply refuses it while
  broker-held runtime is live. Shut down or suspend hosted endpoints first; the
  first flow does not auto-cycle them.

## One-time lab setup

Generate a debug key on the coordinator:

```powershell
cargo run -p xtask -- debug-keygen dev-debug-2026
```

Set the printed seed as a long-lived coordinator-local environment variable:

```powershell
$env:SPT_DEBUG_RELEASE_SEED = "<seed_hex>"
```

Pin each lab node to the debug channel with the printed public key:

```powershell
cargo run -p xtask -- debug-pin --key-id dev-debug-2026 --public-key <public_hex>
```

This writes `$SPT_HOME/identity/release-keys.json` with the debug public key and
`"channel": "debug"`. Removing the key or setting the channel back to
`"stable"` removes the node from debug rollout eligibility.

## Stage a rollout

From the fast coordinator, stage an update set into the local release cache:

```powershell
cargo run -p xtask -- debug-rollout --build-current --artifact "x86_64-unknown-linux-gnu=<path-to-linux-spt>"
```

Common flags:

- `--build-current` builds and includes the coordinator's current platform.
- `--artifact <target=path>` adds an already-built artifact for another target,
  such as `x86_64-pc-windows-msvc` or `x86_64-unknown-linux-gnu`.
- `--version <u64>` overrides the local debug sequence. Use a higher value when
  recovering from lost local state.
- `--stage-dir <path>` stages somewhere other than `$SPT_HOME/releases`.
- `--state <path>` stores the local debug sequence somewhere other than
  `target/debug-rollout-state.json`.


Fleet fully on v0.4.2 — a major milestone. The debug-channel rollout (operator's chosen Linux-proof method) is a heavier operation: it re-pins live nodes to a debug channel, needs a debug keypair + a built artifact. Meanwhile a strong new piece of evidence landed that wasn't available when that choice was made — the in-place-rename int E2E passed **green on kitsubito's Linux CI**. Checkpointing with the operator before the heavier step.


70	  `target/debug-rollout-state.json`.
71
72	The staged update then propagates through the normal pull-based update pump.
73	There is intentionally no bespoke push path.
74
75	## Apply and observe
76
77	On a default-gated node, apply is still explicit:
78
79	```powershell
80	spt update apply
81	```
82
83	Nodes configured for full-auto update may apply after staging without a prompt.
84	For quick debugging, run or poke the daemon/update pump on lab nodes so they
85	query peers promptly.
86
87	**Convergence watcher (M8-D4, decision 19).** Instead of hand-walking nodes,
88	run the maintainer watcher from the coordinator:
89
90	```powershell
91	cargo run -p xtask -- debug-converge --version <N> [--subnet <name>] [--timeout 120] [--poll 3]
92	```
93
94	It polls every expected node (the coordinator's trust rows; `--nodes <hex,…>`
95	overrides) over the status-only update query and prints a per-node table —
96	`Applied` / `StagedAwaitingConsent` / `NotPinned` / `Rejected{reason}` /
97	`Offline` — exiting `0` only when every node applied the target (`1`
98	otherwise, `2` on usage). Spec + state model: `docs/DEBUG-CONVERGE-PLAN.md`.
99	The manual walk below remains the fallback.
100
101	Useful outcomes:
102
103	- `NoArtifactForPlatform` means the signed set did not include the recipient's
104	  Rust target triple.
105	- `WrongChannel` means the node is not pinned to `debug`.
106	- `Rollback` means the offered debug version is not greater than the node's
107	  current debug-channel version.
108	- `RefusedClass(BrokerBreaking)` means the candidate touches broker-held
109	  resources; quiesce hosted endpoints first.

  `target/debug-rollout-state.json`.

The staged update then propagates through the normal pull-based update pump.
There is intentionally no bespoke push path.

## Apply and observe

On a default-gated node, apply is still explicit:

```powershell
spt update apply
```

Nodes configured for full-auto update may apply after staging without a prompt.
For quick debugging, run or poke the daemon/update pump on lab nodes so they
query peers promptly.

**Convergence watcher (M8-D4, decision 19).** Instead of hand-walking nodes,
run the maintainer watcher from the coordinator:

```powershell
cargo run -p xtask -- debug-converge --version <N> [--subnet <name>] [--timeout 120] [--poll 3]
```

It polls every expected node (the coordinator's trust rows; `--nodes <hex,…>`
overrides) over the status-only update query and prints a per-node table —
`Applied` / `StagedAwaitingConsent` / `NotPinned` / `Rejected{reason}` /
`Offline` — exiting `0` only when every node applied the target (`1`
otherwise, `2` on usage). Spec + state model: `docs/DEBUG-CONVERGE-PLAN.md`.
The manual walk below remains the fallback.

Useful outcomes:

- `NoArtifactForPlatform` means the signed set did not include the recipient's
  Rust target triple.
- `WrongChannel` means the node is not pinned to `debug`.
- `Rollback` means the offered debug version is not greater than the node's
  current debug-channel version.
- `RefusedClass(BrokerBreaking)` means the candidate touches broker-held
  resources; quiesce hosted endpoints first.


I now have the full picture for the close decision. Comprehensive status + one decision for you:

## Where we are — fleet fully on v0.4.2, fix loaded everywhere

| node | OS | version | broker | how |
|---|---|---|---|---|
| kitsubito | Linux | 0.4.2 | **fixed v0.4.2 broker** resident | apply + 1 final bounce |
| enlyzeam | Win | 0.4.2 | v0.4.2 two-process | v0.3.2→0.4.2 + 1 final bounce (caught up) |
| hfenduleam | Win | 0.4.2 | resident (correct on Win) | **seamless, no bounce** |

- Two manual bounces total (kitsubito + enlyzeam) — exactly as planned, the project's last.
- **Windows seamless proven live, twice** (hfenduleam 0.4.0→0.4.1→0.4.2, broker pid held throughout).
- **New evidence:** the in-place-rename int E2E (`brain_respawn_rename.rs`) passed **green on kitsubito's Linux CI** — a real `spt daemon run` broker, real rename+replace, asserting the respawn runs new bytes. That's a permanent Linux regression guard, on the actual Linux runner.

Only the **live Linux field-seamless observation** remains for the restoration close. You earlier chose a debug-channel rollout — but that was before the int E2E went green on Linux CI. The rollout is now a heavier step than the evidence may warrant (it re-pins a live node to a debug channel, needs a debug keypair + a built artifact):


AskUserQuestion

Question: How to settle the final Linux seamless-update observation for the restoration close, given the in-place-rename int E2E is now green on kitsubito's Linux CI?

Choices:
Treat the green-on-Linux-CI int E2E (real broker process, real rename, asserts new bytes) + all nodes on v0.4.2 fixed brokers + Windows live-seamless (twice) as sufficient. The live Linux FIELD seamless then rides the next real release (v0.4.3) naturally — no synthetic rollout, no node reconfiguration. doyle's close criteria explicitly allowed 'next release OR debug rollout'. Close restoration now.
Your earlier choice: debug-keygen + pin kitsubito to the debug channel + build a linux artifact + debug-rollout a synthetic build onto its v0.4.2 broker + observe seamless no-bounce live + un-pin. A live field proof now, at the cost of temporarily reconfiguring a live node.
Hold restoration open; revisit the observation method later. I park the close bookkeeping.


Your questions have been answered: "How to settle the final Linux seamless-update observation for the restoration close, given the in-place-rename int E2E is now green on kitsubito's Linux CI?"="Close on current evidence". You can now continue with these answers in mind.

Question: How to settle the final Linux seamless-update observation for the restoration close, given the in-place-rename int E2E is now green on kitsubito's Linux CI?

Choices:
Treat the green-on-Linux-CI int E2E (real broker process, real rename, asserts new bytes) + all nodes on v0.4.2 fixed brokers + Windows live-seamless (twice) as sufficient. The live Linux FIELD seamless then rides the next real release (v0.4.3) naturally — no synthetic rollout, no node reconfiguration. doyle's close criteria explicitly allowed 'next release OR debug rollout'. Close restoration now.
Your earlier choice: debug-keygen + pin kitsubito to the debug channel + build a linux artifact + debug-rollout a synthetic build onto its v0.4.2 broker + observe seamless no-bounce live + un-pin. A live field proof now, at the cost of temporarily reconfiguring a live node.
Hold restoration open; revisit the observation method later. I park the close bookkeeping.


Operator: **close on current evidence** — the green-on-Linux-CI int E2E + fleet on v0.4.2 fixed brokers + Windows live-seamless (twice) suffice; live Linux field-seamless rides the next real release. **Restoration closes now.**

Proceeding to the close bookkeeping (run past doyle). Syncing main + branch.


Bash

git checkout main 2>&1 | tail -1 && git pull --ff-only origin main 2>&1 | tail -3 && git --no-pager log -1 --oneline && git checkout -b close/restoration-milestone 2>&1 | tail -1

Sync main + branch for close


Already on 'main'
From https://github.com/SaberMage/spt-core
 * branch            main       -> FETCH_HEAD
Already up to date.
725aebf Merge pull request #5 from SaberMage/fix/v0.4.2-brain-respawn-path
Switched to a new branch 'close/restoration-milestone'


Main @ 725aebf. Drafting the close bookkeeping. ROADMAP restoration → ✅ delivered:


Edit

- **🔜 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).

- **✅ Broker/brain process-isolation restoration — delivered (2026-06-11).** 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).


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

- **M7 — Subnet & quickstart UX. ✅ 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.

+- **✅ Broker/brain process-isolation restoration — delivered (2026-06-11).** 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._


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

**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. **Delivery (D1–D7 + field-hardening across v0.4.0–v0.4.2):** the two-process model shipped in **v0.4.0** (public 2026-06-10); the D7 process-level survival E2E (`brain_survive.rs`) + N-1 gate landed and re-pointed the regression-masked REQ-DAEMON-2/UPD-3 `int`. The **v0.4.1** fleet-verify proved Windows seamless (hfenduleam: brain pid rolls, broker held, `exe_hash` flips, no manual bounce) but surfaced a **Linux-only** respawn bug — the resident broker respawned the brain via per-spawn `current_exe()`, which on Linux follows the `apply` rename to `.old-N` and ran OLD bytes under an `applied` record (caught by the bytes-level `exe_hash` assert; `REQ-HAZARD-BRAIN-RESPAWN-PATH` / KNOWN-HAZARDS 6.11). **v0.4.2** (public 2026-06-11) fixed it: respawn from the canonical path captured at broker start + a promotion **bytes-gate** (a wrong-bytes trial auto-rolls-back instead of recording success), with a real in-place-rename `int` E2E (`brain_respawn_rename.rs`) that fails on Linux pre-fix and is CI-gated green on both runners. **Acceptance:** Windows live-seamless proven twice (hfenduleam 0.4.0→0.4.1→0.4.2, broker pid held throughout); Linux proven by the CI-gated in-place-rename E2E on the kitsubito runner; the whole fleet (kitsubito/enlyzeam/hfenduleam) runs v0.4.2 on fixed brokers, after the project's **final two manual bounces** (kitsubito + the enlyzeam v0.3.2 catch-up). The live Linux *field*-seamless rides the next real release (operator decision 2026-06-11: close on the CI-gated E2E + fixed-broker fleet rather than a synthetic debug rollout). KNOWN-HAZARDS 6.7/6.8 closed out. **Next:** `spt-claude-code` — the v1 acceptance proof on the final topology, its own repo.


-- **✅ Broker/brain process-isolation restoration — delivered (2026-06-11).** 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.

+- **✅ Broker/brain process-isolation restoration — delivered (2026-06-11).** 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. **Delivery (D1–D7 + field-hardening across v0.4.0–v0.4.2):** the two-process model shipped in **v0.4.0** (public 2026-06-10); the D7 process-level survival E2E (`brain_survive.rs`) + N-1 gate landed and re-pointed the regression-masked REQ-DAEMON-2/UPD-3 `int`. The **v0.4.1** fleet-verify proved Windows seamless (hfenduleam: brain pid rolls, broker held, `exe_hash` flips, no manual bounce) but surfaced a **Linux-only** respawn bug — the resident broker respawned the brain via per-spawn `current_exe()`, which on Linux follows the `apply` rename to `.old-N` and ran OLD bytes under an `applied` record (caught by the bytes-level `exe_hash` assert; `REQ-HAZARD-BRAIN-RESPAWN-PATH` / KNOWN-HAZARDS 6.11). **v0.4.2** (public 2026-06-11) fixed it: respawn from the canonical path captured at broker start + a promotion **bytes-gate** (a wrong-bytes trial auto-rolls-back instead of recording success), with a real in-place-rename `int` E2E (`brain_respawn_rename.rs`) that fails on Linux pre-fix and is CI-gated green on both runners. **Acceptance:** Windows live-seamless proven twice (hfenduleam 0.4.0→0.4.1→0.4.2, broker pid held throughout); Linux proven by the CI-gated in-place-rename E2E on the kitsubito runner; the whole fleet (kitsubito/enlyzeam/hfenduleam) runs v0.4.2 on fixed brokers, after the project's **final two manual bounces** (kitsubito + the enlyzeam v0.3.2 catch-up). The live Linux *field*-seamless rides the next real release (operator decision 2026-06-11: close on the CI-gated E2E + fixed-broker fleet rather than a synthetic debug rollout). KNOWN-HAZARDS 6.7/6.8 closed out. **Next:** `spt-claude-code` — the v1 acceptance proof on the final topology, its own repo.


KH 6.7 + 6.8 close-out notes:


The logic loops still run broker-side (D2 migrates them); the `int` process-level survival E2E + the in-process re-point land at D7.

The logic loops still run broker-side (D2 migrates them); the `int` process-level survival E2E + the in-process re-point land at D7.
- **Closed out (2026-06-11, v0.4.0–v0.4.2):** the two-process model shipped (v0.4.0); the D7 `int` E2E (`brain_survive.rs`) + the N-1 gate prove process-level survival onto swapped bytes and re-pointed REQ-DAEMON-2 / REQ-UPD-3. The v0.4.1 fleet-verify proved this Windows-seamless (hfenduleam: brain pid rolls, broker held, `exe_hash` flips, no manual bounce) but exposed a **Linux** respawn-path gap — the resident broker respawned the brain via per-spawn `current_exe()`, which on Linux follows the `apply` rename to `.old-N` and ran OLD bytes under an `applied` record (`[REQ-HAZARD-BRAIN-RESPAWN-PATH]`, 6.11), fixed in v0.4.2 (respawn from the canonical path captured at broker start + a promotion bytes-gate). Seamless update is now proven on **both OSes** — Windows live (hfenduleam ×2: 0.4.0→0.4.1→0.4.2, broker pid held) and Linux via the CI-gated in-place-rename E2E (`brain_respawn_rename.rs`, kitsubito runner). The fleet runs v0.4.2 on fixed brokers after the project's final two manual bounces. The process-isolation invariant holds; 6.11 is its Linux-respawn-path corollary.


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

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


288	<!-- [doc->REQ-HAZARD-BROKER-PROCESS-ISOLATION] -->
289	- **D1 (restoration skeleton, ADR-0018 Q2/Q3):** the process boundary is restored — `spt daemon run` is the broker process and spawns a supervised `spt daemon brain` child (`brainproc.rs`); the broker survives the brain dying and respawns it (proven in production topology by `crates/spt/tests/brain_split.rs`). The logic loops still run broker-side (D2 migrates them); the `int` process-level survival E2E + the in-process re-point land at D7.
290
291	### 6.8 No irreversible durable-state migration before update ready-promotion `[REQ-HAZARD-ROLLBACK-STATE-COMPAT]`
292	- **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).
293	- **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).
294	- **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.
295	- **D5 conformance (2026-06-10):** the new durable timing state `<spt_home>/deadline-<key>.json` (restoration D5-1) is **additive** — a rolled-back pre-D5 binary does not know the file and simply ignores it (re-phasing on its own flat-sleep cadence, the pre-D5 behavior). No existing-file schema migration, no irreversible pre-ready write → the new file is rollback-N-1-safe by construction. The thing for a future D6 guard to gate is a *migration* of this file's shape, not its introduction.
296	- **D6 guard (2026-06-10, restoration D6-3):** the invariant is now **asserted**, not just noted. The pre-ready durable writes are **enumerated in one place** — `spt-daemon::PRE_READY_DURABLE_FILES` (`rollback_compat.rs`): `deadline-<key>.json` (D5, `DeadlineAnchor`), `applied-state.json` (D6-1, the two-phase `AppliedRecord`), and the generation-stamped `brain.ready` breadcrumb (D6-1b, `{pid, generation}`). A **tripwire unit test** pins each one's additive / N-1-readable contract (load-bearing field names present; an unknown extra field still deserializes), so a *non-additive* pre-ready change (renamed/removed field, or a `deny_unknown_fields`/non-tolerant shape) trips the test and forces the migration **behind ready-promotion** (or into an additive form). **Both new D6 durable files are additive → N-1-safe by construction:** the two-phase `applied-state.json` is a *new* file a rolled-back pre-D6 binary does not know — it ignores it and falls back to the legacy `applied.json`/`last-outcome.json` (which D6 keeps writing alongside for the convergence query); `brain.ready` only gained a `generation` field its sole reader tolerates. It is a tripwire, **not** a migration framework (activate-don't-pre-fail) — the day a real migration is needed, this guard is the wire it trips.
297	- **Origin:** verification amendment `[V1]` (agent `doyle`) on `docs/BROKER-BRAIN-SPLIT-RESTORATION.md`.
298	<!-- [doc->REQ-HAZARD-ROLLBACK-STATE-COMPAT] -->
299

<!-- [doc->REQ-HAZARD-BROKER-PROCESS-ISOLATION] -->
- **D1 (restoration skeleton, ADR-0018 Q2/Q3):** the process boundary is restored — `spt daemon run` is the broker process and spawns a supervised `spt daemon brain` child (`brainproc.rs`); the broker survives the brain dying and respawns it (proven in production topology by `crates/spt/tests/brain_split.rs`). The logic loops still run broker-side (D2 migrates them); the `int` process-level survival E2E + the in-process re-point land at D7.

### 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.
- **D5 conformance (2026-06-10):** the new durable timing state `<spt_home>/deadline-<key>.json` (restoration D5-1) is **additive** — a rolled-back pre-D5 binary does not know the file and simply ignores it (re-phasing on its own flat-sleep cadence, the pre-D5 behavior). No existing-file schema migration, no irreversible pre-ready write → the new file is rollback-N-1-safe by construction. The thing for a future D6 guard to gate is a *migration* of this file's shape, not its introduction.
- **D6 guard (2026-06-10, restoration D6-3):** the invariant is now **asserted**, not just noted. The pre-ready durable writes are **enumerated in one place** — `spt-daemon::PRE_READY_DURABLE_FILES` (`rollback_compat.rs`): `deadline-<key>.json` (D5, `DeadlineAnchor`), `applied-state.json` (D6-1, the two-phase `AppliedRecord`), and the generation-stamped `brain.ready` breadcrumb (D6-1b, `{pid, generation}`). A **tripwire unit test** pins each one's additive / N-1-readable contract (load-bearing field names present; an unknown extra field still deserializes), so a *non-additive* pre-ready change (renamed/removed field, or a `deny_unknown_fields`/non-tolerant shape) trips the test and forces the migration **behind ready-promotion** (or into an additive form). **Both new D6 durable files are additive → N-1-safe by construction:** the two-phase `applied-state.json` is a *new* file a rolled-back pre-D6 binary does not know — it ignores it and falls back to the legacy `applied.json`/`last-outcome.json` (which D6 keeps writing alongside for the convergence query); `brain.ready` only gained a `generation` field its sole reader tolerates. It is a tripwire, **not** a migration framework (activate-don't-pre-fail) — the day a real migration is needed, this guard is the wire it trips.
- **Origin:** verification amendment `[V1]` (agent `doyle`) on `docs/BROKER-BRAIN-SPLIT-RESTORATION.md`.
<!-- [doc->REQ-HAZARD-ROLLBACK-STATE-COMPAT] -->


The logic loops still run broker-side (D2 migrates them); the `int` process-level survival E2E + the in-process re-point land at D7.

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

The logic loops still run broker-side (D2 migrates them); the `int` process-level survival E2E + the in-process re-point land at D7.
- **Closed out (2026-06-11, v0.4.0–v0.4.2):** the two-process model shipped (v0.4.0); the D7 `int` E2E (`brain_survive.rs`) + the N-1 gate prove process-level survival onto swapped bytes and re-pointed REQ-DAEMON-2 / REQ-UPD-3. The v0.4.1 fleet-verify proved this Windows-seamless (hfenduleam: brain pid rolls, broker held, `exe_hash` flips, no manual bounce) but exposed a **Linux** respawn-path gap — the resident broker respawned the brain via per-spawn `current_exe()`, which on Linux follows the `apply` rename to `.old-N` and ran OLD bytes under an `applied` record (`[REQ-HAZARD-BRAIN-RESPAWN-PATH]`, 6.11), fixed in v0.4.2 (respawn from the canonical path captured at broker start + a promotion bytes-gate). Seamless update is now proven on **both OSes** — Windows live (hfenduleam ×2: 0.4.0→0.4.1→0.4.2, broker pid held) and Linux via the CI-gated in-place-rename E2E (`brain_respawn_rename.rs`, kitsubito runner). The fleet runs v0.4.2 on fixed brokers after the project's final two manual bounces. The process-isolation invariant holds; 6.11 is its Linux-respawn-path corollary.

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


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)

The logic loops still run broker-side (D2 migrates them); the `int` process-level survival E2E + the in-process re-point land at D7.

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

The logic loops still run broker-side (D2 migrates them); the `int` process-level survival E2E + the in-process re-point land at D7.
- **Closed out (2026-06-11, v0.4.0–v0.4.2):** the two-process model shipped (v0.4.0); the D7 `int` E2E (`brain_survive.rs`) + the N-1 gate prove process-level survival onto swapped bytes and re-pointed REQ-DAEMON-2 / REQ-UPD-3. The v0.4.1 fleet-verify proved this Windows-seamless (hfenduleam: brain pid rolls, broker held, `exe_hash` flips, no manual bounce) but exposed a **Linux** respawn-path gap — the resident broker respawned the brain via per-spawn `current_exe()`, which on Linux follows the `apply` rename to `.old-N` and ran OLD bytes under an `applied` record (`[REQ-HAZARD-BRAIN-RESPAWN-PATH]`, 6.11), fixed in v0.4.2 (respawn from the canonical path captured at broker start + a promotion bytes-gate). Seamless update is now proven on **both OSes** — Windows live (hfenduleam ×2: 0.4.0→0.4.1→0.4.2, broker pid held) and Linux via the CI-gated in-place-rename E2E (`brain_respawn_rename.rs`, kitsubito runner). The fleet runs v0.4.2 on fixed brokers after the project's final two manual bounces. The process-isolation invariant holds; 6.11 is its Linux-respawn-path corollary.

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

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

- **D1 (restoration skeleton, ADR-0018 Q2/Q3):** the process boundary is restored — `spt daemon run` is the broker process and spawns a supervised `spt daemon brain` child (`brainproc.rs`); the broker survives the brain dying and respawns it (proven in production topology by `crates/spt/tests/brain_split.rs`). The logic loops still run broker-side (D2 migrates them); the `int` process-level survival E2E + the in-process re-point land at D7.

+- **Closed out (2026-06-11, v0.4.0–v0.4.2):** the two-process model shipped (v0.4.0); the D7 `int` E2E (`brain_survive.rs`) + the N-1 gate prove process-level survival onto swapped bytes and re-pointed REQ-DAEMON-2 / REQ-UPD-3. The v0.4.1 fleet-verify proved this Windows-seamless (hfenduleam: brain pid rolls, broker held, `exe_hash` flips, no manual bounce) but exposed a **Linux** respawn-path gap — the resident broker respawned the brain via per-spawn `current_exe()`, which on Linux follows the `apply` rename to `.old-N` and ran OLD bytes under an `applied` record (`[REQ-HAZARD-BRAIN-RESPAWN-PATH]`, 6.11), fixed in v0.4.2 (respawn from the canonical path captured at broker start + a promotion bytes-gate). Seamless update is now proven on **both OSes** — Windows live (hfenduleam ×2: 0.4.0→0.4.1→0.4.2, broker pid held) and Linux via the CI-gated in-place-rename E2E (`brain_respawn_rename.rs`, kitsubito runner). The fleet runs v0.4.2 on fixed brokers after the project's final two manual bounces. The process-isolation invariant holds; 6.11 is its Linux-respawn-path corollary.

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


- **Origin:** verification amendment `[V1]` (agent `doyle`) on `docs/BROKER-BRAIN-SPLIT-RESTORATION.md`.
<!-- [doc->REQ-HAZARD-ROLLBACK-STATE-COMPAT] -->

- **Origin:** verification amendment `[V1]` (agent `doyle`) on `docs/BROKER-BRAIN-SPLIT-RESTORATION.md`.
- **Closed out (2026-06-11):** the readiness-gated auto-rollback shipped (v0.4.0) and the pre-ready durable-file registry + tripwire guard (D6-3) hold. v0.4.2 added a promotion **bytes-gate** that turns a wrong-bytes respawn into an auto-rollback rather than a false `applied` record — strengthening the rollback path the same release exercised across the fleet. No in-place schema migration has shipped, so the invariant remains **asserted, not yet exercised by a real migration** (correct — activate-don't-pre-fail); the tripwire is the wire it trips the day one is needed.
<!-- [doc->REQ-HAZARD-ROLLBACK-STATE-COMPAT] -->


- **Origin:** verification amendment `[V1]` (agent `doyle`) on `docs/BROKER-BRAIN-SPLIT-RESTORATION.md`.
<!-- [doc->REQ-HAZARD-ROLLBACK-STATE-COMPAT] -->

- **Origin:** verification amendment `[V1]` (agent `doyle`) on `docs/BROKER-BRAIN-SPLIT-RESTORATION.md`.
- **Closed out (2026-06-11):** the readiness-gated auto-rollback shipped (v0.4.0) and the pre-ready durable-file registry + tripwire guard (D6-3) hold. v0.4.2 added a promotion **bytes-gate** that turns a wrong-bytes respawn into an auto-rollback rather than a false `applied` record — strengthening the rollback path the same release exercised across the fleet. No in-place schema migration has shipped, so the invariant remains **asserted, not yet exercised by a real migration** (correct — activate-don't-pre-fail); the tripwire is the wire it trips the day one is needed.
<!-- [doc->REQ-HAZARD-ROLLBACK-STATE-COMPAT] -->

- **D5 conformance (2026-06-10):** the new durable timing state `<spt_home>/deadline-<key>.json` (restoration D5-1) is **additive** — a rolled-back pre-D5 binary does not know the file and simply ignores it (re-phasing on its own flat-sleep cadence, the pre-D5 behavior). No existing-file schema migration, no irreversible pre-ready write → the new file is rollback-N-1-safe by construction. The thing for a future D6 guard to gate is a *migration* of this file's shape, not its introduction.

- **D6 guard (2026-06-10, restoration D6-3):** the invariant is now **asserted**, not just noted. The pre-ready durable writes are **enumerated in one place** — `spt-daemon::PRE_READY_DURABLE_FILES` (`rollback_compat.rs`): `deadline-<key>.json` (D5, `DeadlineAnchor`), `applied-state.json` (D6-1, the two-phase `AppliedRecord`), and the generation-stamped `brain.ready` breadcrumb (D6-1b, `{pid, generation}`). A **tripwire unit test** pins each one's additive / N-1-readable contract (load-bearing field names present; an unknown extra field still deserializes), so a *non-additive* pre-ready change (renamed/removed field, or a `deny_unknown_fields`/non-tolerant shape) trips the test and forces the migration **behind ready-promotion** (or into an additive form). **Both new D6 durable files are additive → N-1-safe by construction:** the two-phase `applied-state.json` is a *new* file a rolled-back pre-D6 binary does not know — it ignores it and falls back to the legacy `applied.json`/`last-outcome.json` (which D6 keeps writing alongside for the convergence query); `brain.ready` only gained a `generation` field its sole reader tolerates. It is a tripwire, **not** a migration framework (activate-don't-pre-fail) — the day a real migration is needed, this guard is the wire it trips.

+- **Closed out (2026-06-11):** the readiness-gated auto-rollback shipped (v0.4.0) and the pre-ready durable-file registry + tripwire guard (D6-3) hold. v0.4.2 added a promotion **bytes-gate** that turns a wrong-bytes respawn into an auto-rollback rather than a false `applied` record — strengthening the rollback path the same release exercised across the fleet. No in-place schema migration has shipped, so the invariant remains **asserted, not yet exercised by a real migration** (correct — activate-don't-pre-fail); the tripwire is the wire it trips the day one is needed.

### 6.9 Resume-mode brain: a blocking spawn/command wait silently discards OTHER sessions' output


Close edits done (ROADMAP ✅ + KH 6.7/6.8). Running gates (docs-only → traceable + xtask check are load-bearing).


Bash

traceable-reqs check >/dev/null 2>&1; echo "TR:$?"; cargo run -p xtask -- check 2>&1 | tail -3; echo "=== diff stat ==="; git --no-pager diff --stat

Close gates: traceable + xtask check


TR:0
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.80s
     Running `target\debug\xtask.exe check`
xtask check: OK
=== diff stat ===
 CONTEXT.md            | 6 +++++-
 ROADMAP.md            | 2 +-
 docs/KNOWN-HAZARDS.md | 2 ++
 3 files changed, 8 insertions(+), 2 deletions(-)


`traceable TR:0`, xtask check OK. But the diff shows **CONTEXT.md changed — which I did not edit.** Inspecting before committing anything.


Inspect unexpected CONTEXT.md change


diff --git a/CONTEXT.md b/CONTEXT.md
index d9a1aaa..3922700 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -188,6 +188,10 @@ A subagent's perch under a parent LiveAgent. Created on subagent start, torn dow
 **SptNode**:
 A machine's participation in an SPT subnet, identified by an Ed25519 public key generated on first run. First-class so networking primitives can address nodes directly as message targets, not only as transport peers. The node identity and network endpoint are hosted by the machine's `spt-daemon` (see Networking), not a separate process.

+**Gateway** (concept ratified 2026-06-11; registered via the open type system, first instance downstream):
+A **human-backed endpoint** — a user's specialized window into the subnet from a device or surface with no conventional-harness compatibility. Nothing LLM-shaped runs there; the intelligence at the endpoint is the **user**. Addressable like any endpoint (receives digests/messages, sends via the normal verbs) and may **own Shells** (it is an owning endpoint — see §Shell model). Distinct from a Shell: a Shell is *driven from elsewhere*; a Gateway *originates* interaction. No `tracked/` mind, no Psyche (LiveAgent affordances). First instance: the `spt-lecturn` adapter's Playdate endpoint (own repo).
+_Avoid_: calling a Gateway a Shell or an agent; "console", "remote".
+
 **PresenceChannel** (broker endpoint — concept locked, impl deferred past v1):
 A *broker* endpoint, not an interaction surface. Job: (1) **presence resolution** — track which node + endpoint the user most recently interacted with; (2) **shell brokering** — locate/instantiate the right Shell on that node and relay between the agent and the user. An agent "just knows how to reach the user" by firing at its PresenceChannel; the channel figures out the rest. Also a durable, **shell-agnostic 2-way thread**: messages persist in the channel, not in any one Shell, so the user can be sent a message via a phone messaging-Shell and surface/continue that same agent conversation later at a GameRobot Shell. Shells are interchangeable I/O windows onto the channel's thread.

@@ -228,7 +232,7 @@ A Shell is a **surface one agent controls** — embodiment of an agent in a new
 - **text+file** (2-way, durable/spooled; per-shell) — general payloads; for a shell it is a *forwarder* (harness adapters have this channel too). File transfers are progress-queryable (below).
 - **sensory** (shell→agent, **REST-only, never spooled**) — images, sounds, and arbitrary descriptive sensory payloads ("movement completed", "obstructed", "bumped", temperature, energy level). Ephemeral — delivered to the agent's live session; dropped if the agent isn't live.

-**Owner-linked, exclusive.** Spawned by an agent endpoint; both linked; **only that owner `endpoint_id` may control it**. An agent and its shell **can link across nodes** (the shell perch lives on the shell's node; commands ride Iroh). One agent : many shells (`GameRobot-0`, `GameRobot-1`, …).
+**Owner-linked, exclusive.** Spawned by an **owning endpoint**; both linked; **only that owner `endpoint_id` may control it**. Agent endpoints are the common owner, but ownership is NOT agent-exclusive — any non-Shell endpoint type may own shells (ratified 2026-06-11, the Gateway grill: e.g. a **Gateway** owns the driven surfaces it steers; a future **Resource** endpoint could too). Control-exclusivity does not mean interaction-exclusivity: a Shell stays 2-way with its *environment* (user messages on text+file, sensory payloads inbound) — exclusive is *who commands it*. An owner and its shell **can link across nodes** (the shell perch lives on the shell's node; commands ride Iroh). One owner : many shells (`GameRobot-0`, `GameRobot-1`, …).

 **Command-delivery method** is the shell adapter's choice (same modes as agent inject-input): HTTP REST, stdin-via-broker, or child-relay (`api poll`). The binary is always broker-launched, so binding is direct (`api bind`, no seed-file).


CONTEXT.md carries an **unrelated, uncommitted change by another author** — a "Gateway" concept + Shell-ownership amendment from a "Gateway grill (2026-06-11)", for a downstream `spt-lecturn`/Playdate endpoint. Not mine, not part of the restoration close. I'll **leave it untouched** (commit only my close files) and flag it to you.

Committing the close (ROADMAP + KH only), then running it past doyle before push/merge.


git add ROADMAP.md docs/KNOWN-HAZARDS.md && git commit -q -F - <<'EOF'
docs(roadmap): close the broker/brain restoration milestone (v0.4.0–v0.4.2)

Restoration delivered: two-process model (v0.4.0), Windows seamless proven live
(hfenduleam x2: 0.4.0->0.4.1->0.4.2, broker pid held), the Linux respawn-path bug
found in the v0.4.1 fleet-verify and fixed in v0.4.2 (REQ-HAZARD-BRAIN-RESPAWN-PATH
/ KNOWN-HAZARDS 6.11), fleet on v0.4.2 fixed brokers after the project's final two
manual bounces (kitsubito + the enlyzeam v0.3.2 catch-up).

- ROADMAP: restoration -> delivered, with the v0.4.0-v0.4.2 acceptance saga
- KNOWN-HAZARDS 6.7/6.8 closed out

Linux field-seamless rides the next real release (operator decision 2026-06-11:
close on the CI-gated in-place-rename E2E + fixed-broker fleet, not a synthetic
debug rollout).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EOF
git --no-pager log -1 --oneline; echo "=== working tree (CONTEXT.md left untouched) ==="; git status -s

Commit restoration close (ROADMAP + KH only)


a7d7a08 docs(roadmap): close the broker/brain restoration milestone (v0.4.0–v0.4.2)
=== working tree (CONTEXT.md left untouched) ===
 M CONTEXT.md


Close committed @ `a7d7a08`; CONTEXT.md left untouched (the Gateway author's to handle). Running the close past doyle for the nod before push/merge.


$OWL send doyle 2>/dev/null <<'EOF'
RESTORATION CLOSE bookkeeping — your nod before I PR/merge (commit a7d7a08, branch close/restoration-milestone).
Operator chose CLOSE ON CURRENT EVIDENCE (not a synthetic debug rollout): the green-on-Linux-CI in-place-rename E2E + fleet on v0.4.2 fixed brokers + Windows live-seamless ×2. Live Linux FIELD-seamless rides the next real release.
Edits:
  ROADMAP: restoration 🔜→✅ delivered (2026-06-11); appended the v0.4.0–v0.4.2 acceptance saga (v0.4.0 two-process + D7 E2E/N-1; v0.4.1 Windows-seamless proven + Linux respawn bug found via the exe_hash assert; v0.4.2 canonical-path respawn + bytes-gate, brain_respawn_rename.rs CI-gated; final two manual bounces; field-seamless rides next release per operator). Next = spt-claude-code.
  KH 6.7: closed-out note — invariant holds, 6.11 is its Linux-respawn corollary, seamless proven both OSes (Win live ×2, Linux CI E2E).
  KH 6.8: closed-out note — auto-rollback shipped + bytes-gate strengthens it; no in-place migration yet so invariant asserted-not-exercised (activate-don't-pre-fail); tripwire stands.
traceable TR:0, xtask check OK. NOTE: CONTEXT.md has an UNRELATED uncommitted change (a "Gateway" endpoint concept from a 2026-06-11 grill, spt-lecturn/Playdate) — NOT mine, left untouched, flagged to operator; my close commit excludes it.
Flag any wording; else nod and I PR→CI→merge to close.
EOF
echo "exit:${PIPESTATUS[0]}"

Send restoration close to doyle for nod


Close sent to doyle for the nod. Updating memory with the milestone state while I wait.


Edit

**FLEET ROLLED + RESTORATION CLOSING (2026-06-11):** v0.4.2 PUBLIC counter 9 (deployah). All 3 nodes on v0.4.2: kitsubito(Linux) apply+final bounce → fixed broker, exe_hash 500a9e7b; enlyzeam(Win) v0.3.2→v0.4.2 + final bounce (perennial laggard caught up, two-process now, gerald re-attached) — these were the project's FINAL TWO manual bounces; hfenduleam(Win) SEAMLESS v0.4.1→v0.4.2 no-bounce (broker pid 17232 held across 0.4.0→0.4.1→0.4.2). Operator chose CLOSE ON CURRENT EVIDENCE (not debug-rollout): green-on-Linux-CI brain_respawn_rename.rs E2E + fixed-broker fleet + Windows live-seamless ×2; live Linux FIELD-seamless rides next real release (v0.4.3). Restoration close committed a7d7a08 (branch close/restoration-milestone: ROADMAP ✅ + KH 6.7/6.8 close-outs), awaiting doyle nod → PR→CI→merge. NEXT after close = spt-claude-code (own repo). GOTCHA learned: `spt daemon start` over an ELEVATED ssh hits the no-unelevated-identity guard → use the at-logon scheduled task (Start-ScheduledTask 'spt-core daemon') for an unelevated Win daemon start. FLAG: CONTEXT.md has an unrelated uncommitted "Gateway" endpoint concept (2026-06-11 grill, spt-lecturn/Playdate) — not mine, left untouched.

See [[v040-published-fleet-rolled]], [[pump-ipc-deadline-fix]], [[v042-published]].