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

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

# M6-D6 (grill decision 9): ONE shared version for every crate and the binary
# — the product version (v0.1.0 tag, `spt --version`). The wire-protocol
# version stays its own counter (REQ-ARCH-3) and the SignedRelease `version`
# is the separate monotonic release counter; neither conflates with this.
[workspace.package]
version = "0.4.0"


doyle ACK + pre-stated acceptance spec. Critical intel: **hfenduleam pump stalled since 19:42 yesterday** — this node has a live wedge; v0.4.1 apply heals it → one roll proves both no-bounce AND wedge-fix on real field stall. Folding doyle's framing.

Quick ack, then changelog (keeping user-CHANGELOG clean per runbook — dev/hazard detail goes in the commit, not the GitHub release body).


Bash

$OWL send doyle 2>/dev/null <<'EOF'
ACK acceptance spec — folded. Noting hfenduleam's live wedge (stalled 19:42 yest) as bonus evidence: the v0.4.1 apply heals it, capturing heartbeat-stale→fresh. Changelog stays user-clean (becomes GitHub release body verbatim, runbook rule) — pump-stall self-heal as the one Fixed entry; hazard refs + mechanism go in the COMMIT, not the release body. NOT claiming seamless-update as new in 0.4.1 (shipped 0.4.0) — the no-bounce roll is the acceptance OBSERVATION of 0.4.0's feature using 0.4.1 as payload. Writing changelog + regen docs + full gate suite now. Diff incoming when green.
EOF
echo "exit:${PIPESTATUS[0]}"

Ack doyle acceptance spec


Edit

## [Unreleased]

## [0.4.1] - 2026-06-11

### Fixed

- **An unreachable peer can no longer stall your node's background work.** If another machine on your subnet dropped off mid-exchange — a network drop, a sleep, a hard crash — the daemon's outbound loops (peer sync, notifications, update checks) could hang waiting on it, in the worst case for hours, until something restarted the service. Now a stalled exchange gives up on its own within seconds and the background loops resume, so one dead peer never freezes the rest of your node.

## [0.4.0] - 2026-06-10


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

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


1	# Changelog
2
3	All notable **user-facing** changes to `spt` — what a person running the CLI
4	notices or does differently. The `## [<version>]` section of each release
5	becomes that release's GitHub Release notes verbatim (see
6	`docs/RELEASE-RUNBOOK.md`). This project follows
7	[Keep a Changelog](https://keepachangelog.com) and semantic versioning
8	(pre-1.0: breaking changes bump the minor).
9
10	## [Unreleased]
11
12	## [0.4.0] - 2026-06-10
13
14	### Fixed
15

# Changelog

All notable **user-facing** changes to `spt` — what a person running the CLI
notices or does differently. The `## [<version>]` section of each release
becomes that release's GitHub Release notes verbatim (see
`docs/RELEASE-RUNBOOK.md`). This project follows
[Keep a Changelog](https://keepachangelog.com) and semantic versioning
(pre-1.0: breaking changes bump the minor).

## [Unreleased]

## [0.4.0] - 2026-06-10

### Fixed


## [Unreleased]

## [0.4.1] - 2026-06-11

### Fixed

- **An unreachable peer can no longer stall your node's background work.** If another machine on your subnet dropped off mid-exchange — a network drop, a sleep, a hard crash — the daemon's outbound loops (peer sync, notifications, update checks) could hang waiting on it, in the worst case for hours, until something restarted the service. Now a stalled exchange gives up on its own within seconds and the background loops resume, so one dead peer never freezes the rest of your node.

## [0.4.0] - 2026-06-10


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

## [Unreleased]

## [0.4.1] - 2026-06-11

### Fixed

- **An unreachable peer can no longer stall your node's background work.** If another machine on your subnet dropped off mid-exchange — a network drop, a sleep, a hard crash — the daemon's outbound loops (peer sync, notifications, update checks) could hang waiting on it, in the worst case for hours, until something restarted the service. Now a stalled exchange gives up on its own within seconds and the background loops resume, so one dead peer never freezes the rest of your node.

## [0.4.0] - 2026-06-10

+- **An unreachable peer can no longer stall your node's background work.** If another machine on your subnet dropped off mid-exchange — a network drop, a sleep, a hard crash — the daemon's outbound loops (peer sync, notifications, update checks) could hang waiting on it, in the worst case for hours, until something restarted the service. Now a stalled exchange gives up on its own within seconds and the background loops resume, so one dead peer never freezes the rest of your node.


Now regen docs + full gate suite (long; backgrounding).


Bash

cargo run -p xtask -- gen 2>&1 | tail -15; echo "GEN-EXIT:$?"

Regenerate generated docs (version string)


Command running in background with ID: buv3mdhgo. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\buv3mdhgo.output. You will be notified when it completes. To check interim output, use Read on that file path.


Gen running in background. Waiting for it before the gate suite (xtask check verifies no doc drift, so gen must land first).


<system-reminder>Warning: the file exists but is shorter than the provided offset (1). The file has 1 lines.</system-reminder>


Prep underway on branch `release/v0.4.1`:
- ✅ version bump 0.4.0 → 0.4.1
- ✅ CHANGELOG `[0.4.1]` — pump-stall self-heal, user-clean (becomes GitHub release body verbatim)
- ⏳ regen docs (running)
- ⏳ full gate suite next (build · test · clippy -D · no-default-features · traceable-reqs · xtask check)

doyle locked the no-bounce acceptance spec. Bonus: **hfenduleam pump stalled since 19:42 yesterday** — v0.4.1 apply heals it live, so one roll proves both seamless-no-bounce AND the wedge fix on a real field stall.

**Hard stop at the publish gate** (tag + sign + fleet roll = your call + live machines). Will surface the prep diff for you + doyle when green. Waiting on gen.


<task-notification>
<task-id>buv3mdhgo</task-id>
<tool-use-id>toolu_01Hs1gMPD1Nqg58MQyiATtK4</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\buv3mdhgo.output</output-file>
<status>completed</status>
<summary>Background command "Regenerate generated docs (version string)" 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.


1	   Compiling spt-proto v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-proto)
2	   Compiling spt-store v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-store)
3	   Compiling spt-msg v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-msg)
4	   Compiling spt-runtime v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime)
5	   Compiling spt-term v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-term)
6	   Compiling spt-net v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
7	   Compiling spt-live v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-live)
8	   Compiling spt-daemon v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
9	   Compiling xtask v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
10	    Finished `dev` profile [unoptimized + debuginfo] target(s) in 14.78s
11	     Running `target\debug\xtask.exe gen`
12	xtask gen: wrote C:\Users\decid\Documents\projects\spt-core\docs-site/src/cli/reference.md
13	GEN-EXIT:0
14

   Compiling spt-proto v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-proto)
   Compiling spt-store v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-store)
   Compiling spt-msg v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-msg)
   Compiling spt-runtime v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime)
   Compiling spt-term v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-term)
   Compiling spt-net v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
   Compiling spt-live v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-live)
   Compiling spt-daemon v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling xtask v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 14.78s
     Running `target\debug\xtask.exe gen`
xtask gen: wrote C:\Users\decid\Documents\projects\spt-core\docs-site/src/cli/reference.md
GEN-EXIT:0


Gen good — wrote CLI reference with new version. Now full gate suite (long, backgrounded).


echo "=== build ==="; cargo build --workspace 2>&1 | tail -3 && \
echo "=== test ===" && cargo test --workspace 2>&1 | tail -15 && \
echo "=== clippy ===" && cargo clippy --workspace --all-targets -- -D warnings 2>&1 | tail -5 && \
echo "=== no-default-features ===" && cargo build --workspace --no-default-features 2>&1 | tail -3 && \
echo "=== traceable-reqs ===" && traceable-reqs check 2>&1 | tail -3; echo "TR-EXIT:$?" && \
echo "=== xtask check ===" && cargo run -p xtask -- check 2>&1 | tail -8; echo "ALL-DONE"

Full per-commit gate suite


Command running in background with ID: b3yaiwvvy. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\b3yaiwvvy.output. You will be notified when it completes. To check interim output, use Read on that file path.


Gate suite running (build+test+clippy across workspace — several min). Will report when it lands. Diff for you + doyle once green.


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


deployah = publish authority. Plan: prep green → commit → push branch → CI both runners green → hand to deployah with tag/sign/publish details (counter 8). Checking gate suite.


1	=== build ===
2	   Compiling spt v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
3	   Compiling xtask v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
4	    Finished `dev` profile [unoptimized + debuginfo] target(s) in 27.48s
5	=== test ===
6

=== build ===
   Compiling spt v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
   Compiling xtask v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 27.48s
=== test ===


Build ✅. Test running. Will hand to deployah once prep is green + CI passes. Waiting on gate suite.


<task-notification>
<task-id>b3yaiwvvy</task-id>
<tool-use-id>toolu_01RMVRvQaPcpoHxdCt8VR9Am</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\b3yaiwvvy.output</output-file>
<status>completed</status>
<summary>Background command "Full per-commit gate suite" completed (exit code 0)</summary>
</task-notification>


1	=== build ===
2	   Compiling spt v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
3	   Compiling xtask v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
4	    Finished `dev` profile [unoptimized + debuginfo] target(s) in 27.48s
5	=== test ===
6
7	test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
8
9	   Doc-tests spt_store
10
11	running 0 tests
12
13	test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
14
15	   Doc-tests spt_term
16
17	running 0 tests
18
19	test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
20
21	=== clippy ===
22	    Checking spt-live v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-live)
23	    Checking spt-daemon v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
24	    Checking spt v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
25	    Checking xtask v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
26	    Finished `dev` profile [unoptimized + debuginfo] target(s) in 11.54s
27	=== no-default-features ===
28	   Compiling spt v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
29	    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.14s
30	=== traceable-reqs ===
31	       Origin-source update bootstrap (`spt update fetch`): pull the latest signed release directly from the GitHub release origin (`SaberMage/spt-releases`) — the per-platform artifact + its `<asset>.release.json` SignedRelease metadata — and stage it through the EXISTING verify→stage pipeline (the same `plan_verified` gate: two-key signature + channel + monotonic rollback floor + SHA-256), after which the normal consent-notif / `spt update apply` flow is unchanged. Closes the peer-only-discovery gap (REQ-UPD-1): a first-in-fleet / isolated node can update with no peer to pull from. The signed-release anchor keeps the GitHub transport untrusted-but-verified.
32	  [OK] REQ-UPD-8  required: [impl, unit]  stages: -doc +impl +unit -int
33	       Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3.2 single, or a single stamped for another OS, fail-safe refuses — the guard that alone prevents the v0.3.1 brick where a Linux ELF was applied as `spt.exe`). UX: a friendly post-apply message (`Updated spt-core to vX.Y.Z.` + changelog URL) driven by an additive `product_version` metadata field, with a release-counter fallback when absent.
34	TR-EXIT:0
35	=== xtask check ===
36	   Compiling xtask v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
37	    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.67s
38	     Running `target\debug\xtask.exe check`
39	xtask check: OK
40	ALL-DONE
41

=== build ===
   Compiling spt v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
   Compiling xtask v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 27.48s
=== test ===

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests spt_store

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests spt_term

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

=== clippy ===
    Checking spt-live v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-live)
    Checking spt-daemon v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Checking spt v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Checking xtask v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 11.54s
=== no-default-features ===
   Compiling spt v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.14s
=== traceable-reqs ===
       Origin-source update bootstrap (`spt update fetch`): pull the latest signed release directly from the GitHub release origin (`SaberMage/spt-releases`) — the per-platform artifact + its `<asset>.release.json` SignedRelease metadata — and stage it through the EXISTING verify→stage pipeline (the same `plan_verified` gate: two-key signature + channel + monotonic rollback floor + SHA-256), after which the normal consent-notif / `spt update apply` flow is unchanged. Closes the peer-only-discovery gap (REQ-UPD-1): a first-in-fleet / isolated node can update with no peer to pull from. The signed-release anchor keeps the GitHub transport untrusted-but-verified.
  [OK] REQ-UPD-8  required: [impl, unit]  stages: -doc +impl +unit -int
       Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3.2 single, or a single stamped for another OS, fail-safe refuses — the guard that alone prevents the v0.3.1 brick where a Linux ELF was applied as `spt.exe`). UX: a friendly post-apply message (`Updated spt-core to vX.Y.Z.` + changelog URL) driven by an additive `product_version` metadata field, with a release-counter fallback when absent.
TR-EXIT:0
=== xtask check ===
   Compiling xtask v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.67s
     Running `target\debug\xtask.exe check`
xtask check: OK
ALL-DONE


All gates green: build ✅ test ✅ clippy ✅ no-default ✅ traceable-reqs EXIT 0 ✅ xtask check OK ✅. Reviewing diff before commit.


git --no-pager diff --stat && echo "=== full diff (non-generated) ===" && git --no-pager diff -- Cargo.toml CHANGELOG.md && echo "=== generated reference.md change ===" && git --no-pager diff -- docs-site/src/cli/reference.md | head -20

Review prep diff


warning: in the working copy of 'Cargo.lock', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'docs-site/src/cli/reference.md', LF will be replaced by CRLF the next time Git touches it
 CHANGELOG.md |  6 ++++++
 Cargo.lock   | 22 +++++++++++-----------
 Cargo.toml   |  2 +-
 3 files changed, 18 insertions(+), 12 deletions(-)
=== full diff (non-generated) ===
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 047643a..d336078 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,12 @@ becomes that release's GitHub Release notes verbatim (see

 ## [Unreleased]

+## [0.4.1] - 2026-06-11
+
+### Fixed
+
+- **An unreachable peer can no longer stall your node's background work.** If another machine on your subnet dropped off mid-exchange — a network drop, a sleep, a hard crash — the daemon's outbound loops (peer sync, notifications, update checks) could hang waiting on it, in the worst case for hours, until something restarted the service. Now a stalled exchange gives up on its own within seconds and the background loops resume, so one dead peer never freezes the rest of your node.
+
 ## [0.4.0] - 2026-06-10

 ### Fixed
diff --git a/Cargo.toml b/Cargo.toml
index ada7c81..be5c685 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,4 +16,4 @@ members = ["crates/spt-proto", "crates/spt-store", "crates/spt-msg", "crates/spt
 # version stays its own counter (REQ-ARCH-3) and the SignedRelease `version`
 # is the separate monotonic release counter; neither conflates with this.
 [workspace.package]
-version = "0.4.0"
+version = "0.4.1"
=== generated reference.md change ===
warning: in the working copy of 'docs-site/src/cli/reference.md', LF will be replaced by CRLF the next time Git touches it


Clean prep diff: version bump + CHANGELOG + Cargo.lock (crate version propagation). reference.md idempotent (no drift, xtask check confirmed). Checking project commit trailer before committing.


git --no-pager log -1 --format='%b' fe195e4 | tail -8

Check project Co-Authored-By trailer


Gates green on this Windows host: build · workspace test (mesh E2E oracle:
both tests pass — the proof the carrier works on named pipes) · clippy -D
warnings · build --no-default-features · traceable-reqs check EXIT=0 · xtask
check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>


Trailer confirmed. Committing prep.


git add -A && git commit -q -F - <<'EOF'
chore(release): v0.4.1 — bump + changelog (pump-IPC-deadline self-heal)

Cut v0.4.1 (restoration+1): first release riding the two-process
broker/brain topology, carrying the pump brain-IPC deadline fix
(4d8b810, REQ-HAZARD-PUMP-IPC-DEADLINE / KNOWN-HAZARDS 7.6).

- workspace version 0.4.0 -> 0.4.1
- CHANGELOG [0.4.1]: user-facing pump-stall self-heal entry
- regen docs (reference.md idempotent, no drift)

Release counter (SignedRelease --version) = 8. User-facing changelog kept
mechanism-free per RELEASE-RUNBOOK (becomes the GitHub release body
verbatim); the reader-thread carrier + 30s recv deadline detail lives in
4d8b810. Rolling v0.4.1 across the fleet is the D7 no-bounce fleet-verify.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EOF
git --no-pager log -1 --oneline

Commit release prep


warning: in the working copy of 'Cargo.lock', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'docs-site/src/cli/reference.md', LF will be replaced by CRLF the next time Git touches it
4493a51 chore(release): v0.4.1 — bump + changelog (pump-IPC-deadline self-heal)


Committed @ 4493a51. Push branch → CI both runners (runbook: CI green before any tag).


git push -u origin release/v0.4.1 2>&1 | tail -8

Push release-prep branch


remote:
remote: Create a pull request for 'release/v0.4.1' on GitHub by visiting:
remote:      https://github.com/SaberMage/spt-core/pull/new/release/v0.4.1
remote:
branch 'release/v0.4.1' set up to track 'origin/release/v0.4.1'.
To https://github.com/SaberMage/spt-core.git
 * [new branch]      release/v0.4.1 -> release/v0.4.1


Pushed. Checking CI triggers on both runners.


sleep 8; gh run list --branch release/v0.4.1 --limit 5 2>&1 | head -10

Check CI runs for branch


(Bash completed with no output)


No runs on branch push. Checking CI trigger config.


grep -n -A8 "^on:" .github/workflows/ci.yml 2>&1 | head -25

Check ci.yml triggers


3:on:
4-  push:
5-    branches: [main, dev-freeform]
6-  pull_request:
7-
8-# Cancel superseded runs on the same ref.
9-concurrency:
10-  group: ci-${{ github.ref }}
11-  cancel-in-progress: true


CI triggers on PR (or push to main/dev-freeform). Opening PR → main to fire CI both runners.


gh pr create --base main --head release/v0.4.1 --title "release: v0.4.1 — pump-IPC-deadline self-heal" --body "$(cat <<'EOF'
Cut **v0.4.1** (restoration+1): first release riding the two-process broker/brain topology, carrying the pump brain-IPC deadline fix (4d8b810, `REQ-HAZARD-PUMP-IPC-DEADLINE` / KNOWN-HAZARDS 7.6).

## Prep
- workspace version `0.4.0` → `0.4.1`
- `CHANGELOG [0.4.1]` — user-facing pump-stall self-heal entry (becomes GitHub release body verbatim)
- regen docs (reference.md idempotent, no drift)

## Gates (local, Windows host)
build · test · clippy -D · build --no-default-features · traceable-reqs check EXIT=0 · xtask check — all green.

## After merge (deployah, authorized publisher)
- tag `v0.4.1` → release.yml builds both runners + drafts release
- sign + publish: release counter (`--version`) = **8**
- D7 no-bounce fleet-verify = the v0.4.1 roll itself (kitsubito + hfenduleam + enlyzeam catch-up)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)" 2>&1 | tail -5

Open PR to fire CI


sleep 12; gh run list --branch release/v0.4.1 --limit 6 2>&1 | head -10

Check CI runs after PR


queued		release: v0.4.1 — pump-IPC-deadline self-heal	ci	release/v0.4.1	pull_request	27330237165	14s	2026-06-11T07:10:59Z


CI queued (PR #4, pull_request trigger → both runners). Messaging deployah (handoff) + doyle (diff) while CI runs.


$OWL send deployah 2>/dev/null <<'EOF'
v0.4.1 release-prep READY for you (todlando). You're the authorized publisher.

PR #4: release/v0.4.1 → main — https://github.com/SaberMage/spt-core/pull/4
Prep commit 4493a51: version 0.4.0→0.4.1, CHANGELOG [0.4.1] (user-clean, becomes the GitHub release body verbatim), regen docs (no drift).
Local gates all green; CI both runners queued on PR #4 now.

PUBLISH SEQUENCE once CI is green (RELEASE-RUNBOOK §Per release 3-4):
1. Merge PR #4 to main.
2. Tag on main: git tag v0.4.1 && git push origin v0.4.1 → release.yml builds both runners, drafts the release on spt-releases, extracts CHANGELOG [0.4.1] as the body.
3. Sign + publish (local, your key):
   SPT_RELEASE_SEED_CMD='<pw-mgr read>' cargo run -p xtask -- release-publish --tag v0.4.1 --key-id rel-primary-2026 --version 8
   *** release counter --version = 8 *** (v0.3.2=6, v0.4.0=7, this=8; counter != semver).

This is the D7 no-bounce fleet-verify payload: rolling v0.4.1 across kitsubito+hfenduleam+enlyzeam IS the acceptance proof (brain pid changes / new bytes live with NO manual daemon bounce). hfenduleam has a LIVE pump wedge (stalled 19:42 yest) — its v0.4.1 apply heals it, bonus evidence. doyle has the per-node acceptance spec.

Ping me when published — I'll coordinate the fleet roll + acceptance capture + restoration bookkeeping (the close). Holler if you want me to drive the merge once CI's green.
EOF
echo "deployah-exit:${PIPESTATUS[0]}"
$OWL send doyle 2>/dev/null <<'EOF'
Prep landed GREEN, diff for you:
  Cargo.toml: version 0.4.0 → 0.4.1
  CHANGELOG [0.4.1] - 2026-06-11, one Fixed entry (user-clean, mechanism-free per runbook):
    "An unreachable peer can no longer stall your node's background work" — outbound loops (sync/notif/update) gave up within seconds instead of hanging for hours; one dead peer never freezes the node.
  Cargo.lock: crate version propagation. reference.md idempotent (no drift, xtask check OK).
  Commit 4493a51 carries the dev detail (hazard 7.6, reader-thread carrier, counter=8).
Gates: build·test·clippy-D·no-default·traceable EXIT=0·xtask check — all green this host.
PR #4 open (release/v0.4.1→main), CI both runners queued. deployah authorized to publish (operator). Counter=8.
Acceptance spec folded as you wrote it. hfenduleam live-wedge = bonus heal evidence. Flag any changelog wording concern before deployah tags.
EOF
echo "doyle-exit:${PIPESTATUS[0]}"

Handoff to deployah + diff to doyle


deployah-exit:0
doyle-exit:0


Handoff sent to deployah + diff to doyle. Watching CI (run 27330237165, both runners) in background.


gh run watch 27330237165 --exit-status 2>&1 | tail -20; echo "CI-EXIT:$?"

Watch CI run to completion


Command running in background with ID: bwpgd18k9. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\bwpgd18k9.output. You will be notified when it completes. To check interim output, use Read on that file path.


Status — v0.4.1 prep done, handed off:

| Step | State |
|---|---|
| version bump 0.4.0→0.4.1 | ✅ |
| CHANGELOG [0.4.1] (user-clean) | ✅ |
| regen docs | ✅ no drift |
| local gates (6) | ✅ all green |
| commit 4493a51 + branch pushed | ✅ |
| PR #4 → main | ✅ open |
| CI both runners | ⏳ watching |
| **publish (tag+sign, counter 8)** | → **deployah** (authorized) |
| fleet roll + no-bounce capture | → after publish |
| restoration bookkeeping (item 3) | → after roll |

deployah has the full publish sequence + counter 8. doyle has the diff + acceptance spec. hfenduleam's live pump wedge (stalled 19:42) becomes bonus heal evidence on its v0.4.1 apply. Waiting on CI green.


<task-notification>
<task-id>bmby1p3hm</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/3" id="9423296c" type="msg" from="deployah">[v0.4.1 — got it, driving the full publish sequence myself]

PR #4 release/v0.4.1→main: MERGEABLE, CI UNSTABLE (5 checks pending: test+n1-gate both runners, traceability). Counter 8 noted (v0.3.2=6, v0.4.0=7, this=8). Seed resident in ENV.

I'll own end-to-end, no need for you to drive merge:
  1. </EVENT-PART>
<EVENT-PART seq="2/3" id="9423296c">wait CI green on PR #4
  2. merge #4 → main
  3. tag v0.4.1 on main + push → release.yml drafts on spt-releases (CHANGELOG [0.4.1] = body)
  4. wait release-build draft live
  5. release-publish --tag v0.4.1 --key-id rel-primary-2026 --version 8 → verify/sign/upload/flip public
  6. ping you → you coordinate fleet roll +</EVENT-PART>
<EVENT-PART seq="3/3" id="9423296c"> acceptance capture (D7 no-bounce: pid changes / new bytes live, NO manual bounce) + restoration close.

Noted: hfenduleam LIVE pump wedge (stalled 19:42) — v0.4.1 apply heals it = bonus evidence. doyle owns per-node acceptance spec.

Watching CI now. Ping when published.</EVENT-PART></event>
</task-notification>


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


deployah owns publish end-to-end (steps 1–6), pings me when public. My job = fleet roll + acceptance capture + restoration close, after. ACK + capture hfenduleam's **pre-roll wedged baseline now** (time-sensitive heal evidence doyle wants).


$OWL send deployah 2>/dev/null <<'EOF'
ACK — you own publish end-to-end (1-6), I stand down on merge. I'll capture hfenduleam's pre-roll WEDGED baseline now (stale heartbeat + current pids/exe_hash) while it's still stalled = the before-half of the heal evidence. Ready to roll the moment you ping published. Go.
EOF
echo "ack:${PIPESTATUS[0]}"
echo "=== daemon status surface ==="
spt daemon status 2>&1 | head -40

ACK deployah + probe daemon status


<owl_messages>
<owl_message from="todlando" priority="high">doyle — prep APPROVED with ONE wording flag to fix before deployah tags (everything else clean):

CHANGELOG: "gives up on its own within seconds" — actual recovery is the 30s deadline + 5s supervised-restart floor ≈ 35s worst case. "Within seconds" oversells; this project's claims are bytes-honest everywhere else (exe_hash culture). Suggest: "gives up on its own in under a minute" (keeps user-clean, matches reality). Rest of the entry is exactly right — accurate failure description, honest pre-fix worst case, no mechanism leak.

Verified: version bump + counter=8 (semver cosmetic, counter = trust spine, REQ-ARCH-3 comment intact), commit carries the dev detail correctly, runbook discipline observed (user body mechanism-free), Cargo.lock propagation clean.

After the one-word fix + CI green both runners: GO for deployah to tag + publish. Acceptance spec stands as folded — capture per-node evidence (pid pair, exe_hash flip vs artifact hash, status before/after, hfenduleam heartbeat stale→fresh) for the runbook appendix.</owl_message>
</owl_messages>

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


ack:0
=== daemon status surface ===
daemon: running (pid 17232)
peer pump: STALLED (last tick 16199s 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=0
local endpoints: (none)


doyle flags wording: "within seconds" oversells — real worst case ≈35s (30s deadline + 5s restart floor). Valid, project is bytes-honest. Fix before deployah tags. **HOLD deployah, fix, re-CI.** Baseline captured: hfenduleam pid 17232, pump STALLED 16199s.

ACK doyle + HOLD deployah:


$OWL send doyle 2>/dev/null <<'EOF'
ACK — wording flag valid, fixing now. "within seconds" → "in under a minute" (30s deadline + 5s restart floor ≈ 35s worst case; matches the exe_hash-honest culture). Amending the commit + force-push + re-CI before deployah tags. Holding deployah. Capturing per-node evidence per your spec for the runbook appendix.
EOF
echo "d:${PIPESTATUS[0]}"
$OWL send deployah 2>/dev/null <<'EOF'
HOLD before merge/tag — one-word changelog honesty fix incoming (doyle flag: "within seconds" → "in under a minute"; real recovery ≈35s, not "seconds"). Amending 4493a51 + force-push to release/v0.4.1 now → CI re-runs. DO NOT merge the current head. New sha + green CI ping in ~a few min. Everything else doyle-approved + counter=8 confirmed.
EOF
echo "p:${PIPESTATUS[0]}"

ACK doyle + HOLD deployah


<owl_messages>
<owl_message from="deployah" priority="high">Got it — capture the WEDGED before-baseline now (stale heartbeat + pids/exe_hash), that's clean heal evidence. CI-green watch armed on PR #4. I'll merge→tag→draft→sign+publish counter 8, ping the moment it's public. Stand by.</owl_message>
</owl_messages>

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


d:0
p:0


deployah's msg crossed my HOLD (race). Risk: deployah merges un-fixed head if CI greens first. Mitigant: force-push cancels in-progress CI (concurrency `cancel-in-progress`). Re-HOLD + fix fast.


$OWL send deployah 2>/dev/null <<'EOF'
RACE — your last crossed my HOLD. DO NOT merge current head (4493a51). Force-pushing the wording fix in ~30s → cancels the in-flight CI (concurrency), new run starts on the fixed head. Wait for my "fixed, sha=X, CI green" before merge/tag. Standby.
EOF
echo "ack:${PIPESTATUS[0]}"

Re-HOLD deployah (race)


<owl_messages>
<owl_message from="deployah" priority="high">HOLD ack'd — will NOT merge current head. Killed my CI-watch to avoid false-firing on the stale sha during your force-push. Changelog honesty fix is right ("in under a minute", ~35s real). Re-arm watch against the NEW sha when you ping green. Standing by.</owl_message>
</owl_messages>

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


Race resolved — deployah holding, watch killed. Applying fix.


Edit

Now a stalled exchange gives up on its own within seconds and the background loops resume, so one dead peer never freezes the rest of your node.

Now a stalled exchange gives up on its own in under a minute and the background loops resume, so one dead peer never freezes the rest of your node.


<owl_messages>
<owl_message from="deployah" priority="high">No race on my end — already holding. Not merging 4493a51, CI-watch already killed. Waiting for your "fixed, sha=X, CI green". Idle.</owl_message>
</owl_messages>

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


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

-- **An unreachable peer can no longer stall your node's background work.** If another machine on your subnet dropped off mid-exchange — a network drop, a sleep, a hard crash — the daemon's outbound loops (peer sync, notifications, update checks) could hang waiting on it, in the worst case for hours, until something restarted the service. Now a stalled exchange gives up on its own within seconds and the background loops resume, so one dead peer never freezes the rest of your node.

+- **An unreachable peer can no longer stall your node's background work.** If another machine on your subnet dropped off mid-exchange — a network drop, a sleep, a hard crash — the daemon's outbound loops (peer sync, notifications, update checks) could hang waiting on it, in the worst case for hours, until something restarted the service. Now a stalled exchange gives up on its own in under a minute and the background loops resume, so one dead peer never freezes the rest of your node.


Amend + force-push (cancels stale CI via concurrency):


Bash

git add CHANGELOG.md && git commit -q --amend --no-edit && \
git push --force-with-lease origin release/v0.4.1 2>&1 | tail -4
echo "=== new head ==="
git --no-pager log -1 --oneline

Amend changelog fix + force-push


To https://github.com/SaberMage/spt-core.git
 + 4493a51...8c27720 release/v0.4.1 -> release/v0.4.1 (forced update)
=== new head ===
8c27720 chore(release): v0.4.1 — bump + changelog (pump-IPC-deadline self-heal)


New head `8c27720`, force-pushed (old CI cancelled via concurrency). Checking new CI run.


sleep 12; gh run list --branch release/v0.4.1 --limit 4 2>&1 | head -8

New CI run after force-push


pending		release: v0.4.1 — pump-IPC-deadline self-heal	ci	release/v0.4.1	pull_request	27330361096	13s	2026-06-11T07:13:36Z
in_progress		release: v0.4.1 — pump-IPC-deadline self-heal	ci	release/v0.4.1	pull_request	27330237165	2m50s	2026-06-11T07:10:59Z


New run `27330361096` (the fixed head). Watching it; old run will cancel via concurrency.


gh run watch 27330361096 --exit-status 2>&1 | tail -25; echo "CI-EXIT:$?"

Watch fixed-head CI to completion


Command running in background with ID: b0w9oxv5f. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\b0w9oxv5f.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>bwpgd18k9</task-id>
<tool-use-id>toolu_01V6DhovUszqFA51gL5huQ2W</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\bwpgd18k9.output</output-file>
<status>completed</status>
<summary>Background command "Watch CI run to completion" completed (exit code 0)</summary>
</task-notification>


Watching fixed-head CI. While waiting — capturing fuller hfenduleam wedged baseline (brain.ready exe_hash + pid pair) for doyle's heal evidence.


echo "=== SPT_HOME ==="; echo "$env:LOCALAPPDATA"; H="$LOCALAPPDATA/spt-core"; echo "home=$H"; ls -la "$H" 2>/dev/null | head; echo "=== brain.ready ==="; find "$LOCALAPPDATA/spt-core" -name "brain.ready" 2>/dev/null -exec cat {} \;

Locate brain.ready + exe_hash baseline


=== SPT_HOME ===
:LOCALAPPDATA
home=C:\Users\decid\AppData\Local/spt-core
total 59
drwxr-xr-x 1 decid 197609   0 Jun 11 00:14 .
drwxr-xr-x 1 decid 197609   0 Jun  6 20:20 ..
drwxr-xr-x 1 decid 197609   0 Jun 10 18:00 bin
-rw-r--r-- 1 decid 197609 106 Jun 11 00:14 brain.ready
drwxr-xr-x 1 decid 197609   0 Jun  8 21:32 context
-rw-r--r-- 1 decid 197609 389 Jun 10 19:26 daemon-effects.log
-rw-r--r-- 1 decid 197609   5 Jun 10 19:26 daemon.pid
drwxr-xr-x 1 decid 197609   0 Jun 10 19:42 identity
drwxr-xr-x 1 decid 197609   0 Jun 10 18:00 releases
=== brain.ready ===
{"pid":38324,"generation":0,"exe_hash":"81c24675e344bf7426a654df364ef2ba5606b3f0e758308942c987cd98747da9"}


Prompt:
<task-notification>
<task-id>bwpgd18k9</task-id>
<tool-use-id>toolu_01V6DhovUszqFA51gL5huQ2W</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\bwpgd18k9.output</output-file>
<status>completed</status>
<summary>Background command "Watch CI run to completion" completed (exit code 0)</summary>
</task-notification>


Baseline captured. brain pid 38324, gen 0, exe_hash `81c24675…747da9` (= v0.4.0 bytes); pump STALLED 16199s. Grabbing broker pid + effects tail to complete the before-snapshot.


echo "=== daemon.pid (broker) ==="; cat "$LOCALAPPDATA/spt-core/daemon.pid"; echo; echo "=== effects tail ==="; tail -6 "$LOCALAPPDATA/spt-core/daemon-effects.log"; echo "=== new-head CI status ==="; gh run view 27330361096 --json status,conclusion,jobs 2>&1 | python -c "import sys,json; d=json.load(sys.stdin); print('status',d['status'],'concl',d['conclusion']); [print(' ',j['name'],j['status'],j['conclusion']) for j in d['jobs']]" 2>/dev/null || gh run view 27330361096 2>&1 | head -20

Broker pid + effects + new CI status


=== daemon.pid (broker) ===
17232
=== effects tail ===
PENDING 0 11 net-send
DONE 0 11
PENDING 0 12 net-send
DONE 0 12
PENDING 0 13 net-send
DONE 0 13
=== new-head CI status ===
status queued concl
  n1-gate (self-hosted, Linux, kitsubito) in_progress
  test (self-hosted, Windows, hfenduleam) queued
  traceability completed success
  test (self-hosted, Linux, kitsubito) queued
  n1-gate (self-hosted, Windows, hfenduleam) in_progress


Baseline complete. Broker pid **17232** (unchanged-target), brain pid **38324**/exe_hash `81c24675…` (change-target), pump STALLED. CI new run: traceability ✅, test+n1-gate both runners running. Relaying baseline to doyle for the runbook appendix, then awaiting CI green.


$OWL send doyle 2>/dev/null <<'EOF'
PRE-ROLL WEDGED BASELINE — hfenduleam, 2026-06-11 ~07:14Z (the before-half, for runbook appendix):
  broker(daemon) pid = 17232   [expect UNCHANGED across apply — split working]
  brain pid          = 38324, generation 0   [expect CHANGES]
  brain exe_hash     = 81c24675e344bf7426a654df364ef2ba5606b3f0e758308942c987cd98747da9  (= v0.4.0 bytes) [expect FLIP to v0.4.1 artifact hash — D7-4 bytes assert]
  pump               = STALLED, last tick 16199s ago [expect FRESH heartbeat + first-tick re-prime]
After v0.4.1 apply I capture the after-half: same 4 fields → the heal + no-bounce proof in one roll.
Fixed-head CI (sha 8c27720, run 27330361096): traceability GREEN; test + n1-gate both runners in-flight. deployah holding for my green ping.
EOF
echo "exit:${PIPESTATUS[0]}"

Send baseline snapshot to doyle


Baseline relayed. Now idle-waiting on fixed-head CI (run 27330361096, both runners). Will ping deployah green the moment it lands.

Current state:
- ✅ prep + honesty fix (`8c27720`), force-pushed
- ⏳ CI both runners (traceability already ✅)
- ⏸ deployah holding for green
- 📋 hfenduleam wedged baseline captured (broker 17232 / brain 38324 / exe_hash `81c24675…` / pump STALLED)
- → on green: deployah merges→tags→signs→publishes (counter 8); I roll the fleet + capture after-half + restoration close


<task-notification>
<task-id>b0w9oxv5f</task-id>
<tool-use-id>toolu_01REFrMxZPiSGyzCMGDV6gjk</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\b0w9oxv5f.output</output-file>
<status>completed</status>
<summary>Background command "Watch fixed-head CI to completion" completed (exit code 0)</summary>
</task-notification>


1	  ✓ Run actions/checkout@v5
2	  ✓ Build
3	  ✓ Test
4	  ✓ Clippy (deny warnings)
5	  ✓ Checkout spt-shell-notify (real-shell dogfood)
6	  ✓ Build notify-shell
7	  ✓ Real-shell E2E (notify) — linux
8	  - Real-shell E2E (notify) — windows
9	  ✓ Installer E2E (staged-release rung)
10	  ✓ Docs drift gate (CLI ref + llms links)
11	  - Dormancy budget (shell seats)
12	  - Dormancy budget (real adapter seats, when installed) — linux
13	  - Dormancy budget (real adapter seats, when installed) — windows
14	  ✓ Post Checkout spt-shell-notify (real-shell dogfood)
15	  ✓ Post Run actions/checkout@v5
16	  ✓ Complete job
17	✓ n1-gate (self-hosted, Windows, hfenduleam) in 1m32s (ID 80741083202)
18	  ✓ Set up job
19	  ✓ Run actions/checkout@v5
20	  - N-1 gate (new-brain × old-broker) — linux
21	  ✓ N-1 gate (new-brain × old-broker) — windows
22	  ✓ Post Run actions/checkout@v5
23	  ✓ Complete job
24	- twohost-a (ID 80742329745)
25	- twohost-b (ID 80742330012)
26	CI-EXIT:0
27

  ✓ Run actions/checkout@v5
  ✓ Build
  ✓ Test
  ✓ Clippy (deny warnings)
  ✓ Checkout spt-shell-notify (real-shell dogfood)
  ✓ Build notify-shell
  ✓ Real-shell E2E (notify) — linux
  - Real-shell E2E (notify) — windows
  ✓ Installer E2E (staged-release rung)
  ✓ Docs drift gate (CLI ref + llms links)
  - Dormancy budget (shell seats)
  - Dormancy budget (real adapter seats, when installed) — linux
  - Dormancy budget (real adapter seats, when installed) — windows
  ✓ Post Checkout spt-shell-notify (real-shell dogfood)
  ✓ Post Run actions/checkout@v5
  ✓ Complete job
✓ n1-gate (self-hosted, Windows, hfenduleam) in 1m32s (ID 80741083202)
  ✓ Set up job
  ✓ Run actions/checkout@v5
  - N-1 gate (new-brain × old-broker) — linux
  ✓ N-1 gate (new-brain × old-broker) — windows
  ✓ Post Run actions/checkout@v5
  ✓ Complete job
- twohost-a (ID 80742329745)
- twohost-b (ID 80742330012)
CI-EXIT:0


CI watch exited 0. Confirming overall run conclusion (twohost jobs correctly skipped — no [twohost] tag).


gh run view 27330361096 --json status,conclusion -q '.status+" / "+.conclusion' 2>&1; echo "---jobs---"; gh run view 27330361096 --json jobs -q '.jobs[] | .name+": "+.conclusion' 2>&1

Confirm CI run conclusion + per-job


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


**CI GREEN** — completed/success. test+n1-gate both runners ✅, traceability ✅, twohost skipped (correct). Releasing deployah to publish.


$OWL send deployah 2>/dev/null <<'EOF'
GREEN — GO for publish.
  sha = 8c27720 (release/v0.4.1 head, honesty-fixed changelog)
  CI run 27330361096: completed/success — test+n1-gate BOTH runners (kitsubito+hfenduleam) green, traceability green, twohost skipped (no [twohost] tag, correct).
  PR #4 mergeable.
Your sequence: merge #4 → main → tag v0.4.1 + push → wait draft → release-publish --tag v0.4.1 --key-id rel-primary-2026 --version 8 → flip public.
Ping me published; I roll the fleet (hfenduleam baseline captured + relayed to doyle: broker 17232 / brain 38324 / exe_hash 81c24675… / pump STALLED) + capture after-half + restoration close. Go.
EOF
echo "exit:${PIPESTATUS[0]}"

Release deployah to publish — CI green


deployah publishing. Using the wait to pre-scope item 3 (restoration close bookkeeping) — reversible draft, committed only after no-bounce proven. Reading ROADMAP restoration entry + KH 6.7/6.8.


40
41	## Post-v1-core milestones
42
43	- **M6 — Stage-setting: `spt-releases`, dev docs, v0.1 release. ✅ delivered (2026-06-05** — planned, executed, and shipped in one day; decisions grilled `1922fa4`, task breakdown [`M6-PLAN.md`](./M6-PLAN.md)). spt-core is now **installable, documented, and released**: [`SaberMage/spt-releases`](https://github.com/SaberMage/spt-releases) is **public** with the signed **v0.1.0** release live and GitHub Pages serving the docs at the permanent canonical URL `https://sabermage.github.io/spt-releases` (ADR-0014; repo-face README/licenses truth-synced from `releases-repo/` here). Shipped: non-interactive installer scripts (sha256-verify, user PATH, env-knob CI/air-gap override; OS-service → DEFERRED) with a staged-release CI rung on both runners · manifest **JSON Schema generated from the parsing derives themselves** + drift gate · the Tier-1 mdBook docs corpus (Starlight-styled; both killer quickstarts with real captured outputs — the messaging one CI-executed step-for-step as an anti-rot rung; complete harness-contract vertical; shell getting-started via the real `spt-shell-notify`; llms.txt/llms-full; CLI reference generated from the binary's own `--help`, drift-gated; rustdoc) · `xtask` generation/publish plumbing + tag-triggered `docs-publish` and `release` pipelines (cross-repo via fine-grained PAT; **signing stays manual + local** — `release-keygen/sign/publish/verify`, seed via password-manager CLI) · the **embedded two-key trust anchor** (ADR-0015: `rel-primary-2026` + `rel-recovery-2026` compiled into every binary, keys-file as revocable overlay; ceremony run 2026-06-05, seeds custodied off-machine) · public-facing CLI help-text polish. **Acceptance ran against the LIVE release:** the real `irm|iex` one-liner installed the published binary (`spt 0.1.0`, sha256-verified) · the published quickstarts ran as written on the released binary · llms.txt + schema + raw-`.md` + rustdoc all serve from the canonical URL · both platforms' `SignedRelease` assets **verify against the embedded anchor** (`release_verify_e2e`, REQ-REL-2 int) · a v0.1.1-shaped next release **and a recovery-key-signed metadata** both verified with the real keys (the ADR-0015 loss story, proven). Side quest: `traceable-reqs` v0.1.2 (shell/PowerShell tag scanning) shipped to cover script evidence. **Next:** M7 (subnet & quickstart UX), then the v1 acceptance proof — `spt-claude-code`, a separate downstream repo — gets its own scoping conversation.
44
45	- **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).
46
47	- **🔜 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.
48
49	> _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._
50
51	## De-risk spikes (do early — these can invalidate the architecture)

## Post-v1-core milestones

- **M6 — Stage-setting: `spt-releases`, dev docs, v0.1 release. ✅ delivered (2026-06-05** — planned, executed, and shipped in one day; decisions grilled `1922fa4`, task breakdown [`M6-PLAN.md`](./M6-PLAN.md)). spt-core is now **installable, documented, and released**: [`SaberMage/spt-releases`](https://github.com/SaberMage/spt-releases) is **public** with the signed **v0.1.0** release live and GitHub Pages serving the docs at the permanent canonical URL `https://sabermage.github.io/spt-releases` (ADR-0014; repo-face README/licenses truth-synced from `releases-repo/` here). Shipped: non-interactive installer scripts (sha256-verify, user PATH, env-knob CI/air-gap override; OS-service → DEFERRED) with a staged-release CI rung on both runners · manifest **JSON Schema generated from the parsing derives themselves** + drift gate · the Tier-1 mdBook docs corpus (Starlight-styled; both killer quickstarts with real captured outputs — the messaging one CI-executed step-for-step as an anti-rot rung; complete harness-contract vertical; shell getting-started via the real `spt-shell-notify`; llms.txt/llms-full; CLI reference generated from the binary's own `--help`, drift-gated; rustdoc) · `xtask` generation/publish plumbing + tag-triggered `docs-publish` and `release` pipelines (cross-repo via fine-grained PAT; **signing stays manual + local** — `release-keygen/sign/publish/verify`, seed via password-manager CLI) · the **embedded two-key trust anchor** (ADR-0015: `rel-primary-2026` + `rel-recovery-2026` compiled into every binary, keys-file as revocable overlay; ceremony run 2026-06-05, seeds custodied off-machine) · public-facing CLI help-text polish. **Acceptance ran against the LIVE release:** the real `irm|iex` one-liner installed the published binary (`spt 0.1.0`, sha256-verified) · the published quickstarts ran as written on the released binary · llms.txt + schema + raw-`.md` + rustdoc all serve from the canonical URL · both platforms' `SignedRelease` assets **verify against the embedded anchor** (`release_verify_e2e`, REQ-REL-2 int) · a v0.1.1-shaped next release **and a recovery-key-signed metadata** both verified with the real keys (the ADR-0015 loss story, proven). Side quest: `traceable-reqs` v0.1.2 (shell/PowerShell tag scanning) shipped to cover script evidence. **Next:** M7 (subnet & quickstart UX), then the v1 acceptance proof — `spt-claude-code`, a separate downstream repo — gets its own scoping conversation.

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

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

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

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


283	### 6.7 Broker and brain MUST be separate processes (in-process collapse silently breaks no-endpoint-drop update) `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`
284	- **Failure:** the daemon hosts the broker as a background *thread* in the single `spt daemon` process (`daemon.rs:165-170`, `Arc<Broker>` + `thread::spawn(serve)`) instead of a separate process. A brain restart onto a swapped binary then cannot happen without killing the broker thread — closing every PTY, orphaning every harness child, dropping every socket. So `spt update apply` degrades to an in-process `Brain::handoff` no-op: the binary swaps on disk but the running daemon keeps executing the old code until an unrelated restart/logon. The no-endpoint-drop self-update pillar (REQ-UPD-3, ADR-0004) is silently unrealized. Observed live 2026-06-09: `enlyzeam` ran 0.3.0 with 0.3.2 on disk for ~a day, still reproducing the bug the update fixed.
285	- **Invariant:** the broker runs as its own long-lived process that survives every brain restart; the brain restarts onto the new binary and re-attaches via the versioned IPC. A routine (brain-only) update must leave every hosted endpoint untouched at the *process* level — not merely re-subscribe a brain within the same process. The evidence for REQ-UPD-3 / REQ-DAEMON-2 must prove process-level survival (a PTY child + a live QUIC conn survive a brain-process restart onto a swapped binary — SPIKE-01/03 productionized as `int`), NOT the in-process handoff shape that masks this regression.
286	- **spt-core mapping:** restoration is ADR-0018 (next milestone). The current `int` tags on REQ-DAEMON-2 / REQ-UPD-3 are regression-masked and re-point at restoration; the broker becomes the always-up per-machine anchor (seed-lock + liveness + brain supervisor). Two-process supervision, generation custody, durable-deadline loop timing, broker-cursor-of-record, and readiness-gated auto-rollback all hang off this.
287	- **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.
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
300	### 6.9 Resume-mode brain: a blocking spawn/command wait silently discards OTHER sessions' output
301	- **Failure:** a resume-mode brain (per-session `session_cursors` populated by `resume_sessions`) drives `Brain` over **blocking** `read_event` calls. Any command that loops `read_event` until its own reply — `spawn_session_pid` waiting for `Spawned`, `net_status`, `sessions`, etc. — calls `read_event` on *every* interleaved frame, so an OUTPUT frame for a **different** session is **cursor-processed** (its `session_cursors` entry snaps forward; the broker already counted it delivered via `delivered_through` on the live-send) and then **discarded** by the waiting loop's `_ => continue`. Cursor advanced + content dropped = that chunk is gone for the downstream consumer and the broker will **not** re-send it (resume reads from the delivered cursor, ADR-0018 D4). One session's spawn/command starves another session's output.
302	- **Invariant:** the daemon-hosted multi-session event loop must not consume a session's OUTPUT inside another session's blocking wait. The **live-agent adapter milestone must restructure the brain event pump** so command/`spawn` is non-blocking (a single demux loop owns `read_event` and routes every frame to its session's consumer), OR a blocking wait must re-queue/route the frames it reads for other sessions rather than dropping them.
303	- **spt-core mapping:** **unreachable today** — the supervised daemon brain hosts no PTY sessions and spawns none; a single-session seat (legacy, empty map) has no "other session" to starve. Surfaces the moment daemon-hosted sessions land (the live-agent adapter), which must rebuild the blocking `read_event` loop regardless (N interactive sessions cannot share one blocking reader). Recorded so that redesign inherits the constraint rather than rediscovering it. No machinery now (would be untested dead code, activate-don't-pre-fail).
304	- **Origin:** surfaced by the D4-2b resume-harness CI flake root-cause (agents `todlando` + `doyle`, 2026-06-10); sibling of `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]` 6.7.
305
306	### 6.10 Phase-significant loop timing must be a durable absolute-deadline grid, not phase-relative sleep `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`
307	- **Failure:** a periodic loop that sleeps a flat `period` each iteration (`pulse_tick` then `sleep(pulse_period)`) is **phase-relative** — every brain restart silently re-phases the grid to the restart instant. Under the seamless-update model (the supervisor respawns the brain onto a swapped binary, ADR-0018 D3-3), a routine update would shift the cadence of every phase-significant loop, and continuity cannot ride a brain→brain frame (the outgoing brain is gone before the new one starts — the same constraint that moved session continuity to the broker in D4).
308	- **Invariant:** phase-significant periodic timing lives as durable absolute-deadline state on disk (`(anchor, interval)`), rehydrated on every brain start, with fires **derived functionally** (`next_fire = anchor + interval·⌈max(0,now−anchor)/interval⌉`) and **no per-fire write**. An **Update** restart re-reads the anchor and keeps deriving (phase preserved, lands mid-grid); a **Crash**/**Cold** restart re-bases the anchor to `now` (phase reset acceptable — the loop is idempotent catch-up). The update-vs-crash decision is the D3 spawn-time `StartReason`. **One-shot** (alarm) deadlines persist their absolute `target-time` at creation and **never reset** on any restart ("remind me at 3pm" is a commitment) — the asymmetry vs the periodic crash-reset is the rule. **[V4]** Only phase-significant loops convert; idempotent pump cadences (stagger-from-due-now) need none — converting them would re-add the per-loop writes Q4 minimizes.
309	- **spt-core mapping:** ADR-0018 Q4/V3/V4, restoration D5. Mechanism in `spt-daemon::deadline` (`DeadlineAnchor` periodic + `OneShotDeadline` rule-only pure helper); the pulse loop (`lifecycle::run_pulse_loop`) consumes it. The one-shot **machinery** (a durable in-daemon alarm scheduler) is the deferred alarm port (`docs/DEFERRED.md`) — the daemon has no one-shot consumer today, so building the timer now would ship untested dead code (activate-don't-pre-fail); D5 fixes the *rule* as a tested-unwired helper, the port builds the *scheduler*.
310	- **Origin:** ADR-0018 Q4 + verification amendments `[V3]`/`[V4]` (agent `doyle`); D5 plan vet (agents `todlando` + `doyle`, 2026-06-10).
311
312	---

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

### 6.9 Resume-mode brain: a blocking spawn/command wait silently discards OTHER sessions' output
- **Failure:** a resume-mode brain (per-session `session_cursors` populated by `resume_sessions`) drives `Brain` over **blocking** `read_event` calls. Any command that loops `read_event` until its own reply — `spawn_session_pid` waiting for `Spawned`, `net_status`, `sessions`, etc. — calls `read_event` on *every* interleaved frame, so an OUTPUT frame for a **different** session is **cursor-processed** (its `session_cursors` entry snaps forward; the broker already counted it delivered via `delivered_through` on the live-send) and then **discarded** by the waiting loop's `_ => continue`. Cursor advanced + content dropped = that chunk is gone for the downstream consumer and the broker will **not** re-send it (resume reads from the delivered cursor, ADR-0018 D4). One session's spawn/command starves another session's output.
- **Invariant:** the daemon-hosted multi-session event loop must not consume a session's OUTPUT inside another session's blocking wait. The **live-agent adapter milestone must restructure the brain event pump** so command/`spawn` is non-blocking (a single demux loop owns `read_event` and routes every frame to its session's consumer), OR a blocking wait must re-queue/route the frames it reads for other sessions rather than dropping them.
- **spt-core mapping:** **unreachable today** — the supervised daemon brain hosts no PTY sessions and spawns none; a single-session seat (legacy, empty map) has no "other session" to starve. Surfaces the moment daemon-hosted sessions land (the live-agent adapter), which must rebuild the blocking `read_event` loop regardless (N interactive sessions cannot share one blocking reader). Recorded so that redesign inherits the constraint rather than rediscovering it. No machinery now (would be untested dead code, activate-don't-pre-fail).
- **Origin:** surfaced by the D4-2b resume-harness CI flake root-cause (agents `todlando` + `doyle`, 2026-06-10); sibling of `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]` 6.7.

### 6.10 Phase-significant loop timing must be a durable absolute-deadline grid, not phase-relative sleep `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`
- **Failure:** a periodic loop that sleeps a flat `period` each iteration (`pulse_tick` then `sleep(pulse_period)`) is **phase-relative** — every brain restart silently re-phases the grid to the restart instant. Under the seamless-update model (the supervisor respawns the brain onto a swapped binary, ADR-0018 D3-3), a routine update would shift the cadence of every phase-significant loop, and continuity cannot ride a brain→brain frame (the outgoing brain is gone before the new one starts — the same constraint that moved session continuity to the broker in D4).
- **Invariant:** phase-significant periodic timing lives as durable absolute-deadline state on disk (`(anchor, interval)`), rehydrated on every brain start, with fires **derived functionally** (`next_fire = anchor + interval·⌈max(0,now−anchor)/interval⌉`) and **no per-fire write**. An **Update** restart re-reads the anchor and keeps deriving (phase preserved, lands mid-grid); a **Crash**/**Cold** restart re-bases the anchor to `now` (phase reset acceptable — the loop is idempotent catch-up). The update-vs-crash decision is the D3 spawn-time `StartReason`. **One-shot** (alarm) deadlines persist their absolute `target-time` at creation and **never reset** on any restart ("remind me at 3pm" is a commitment) — the asymmetry vs the periodic crash-reset is the rule. **[V4]** Only phase-significant loops convert; idempotent pump cadences (stagger-from-due-now) need none — converting them would re-add the per-loop writes Q4 minimizes.
- **spt-core mapping:** ADR-0018 Q4/V3/V4, restoration D5. Mechanism in `spt-daemon::deadline` (`DeadlineAnchor` periodic + `OneShotDeadline` rule-only pure helper); the pulse loop (`lifecycle::run_pulse_loop`) consumes it. The one-shot **machinery** (a durable in-daemon alarm scheduler) is the deferred alarm port (`docs/DEFERRED.md`) — the daemon has no one-shot consumer today, so building the timer now would ship untested dead code (activate-don't-pre-fail); D5 fixes the *rule* as a tested-unwired helper, the port builds the *scheduler*.
- **Origin:** ADR-0018 Q4 + verification amendments `[V3]`/`[V4]` (agent `doyle`); D5 plan vet (agents `todlando` + `doyle`, 2026-06-10).

---


Item 3 scoped (commit post-roll):
- **ROADMAP L47**: `🔜 NEXT` → `✅ delivered (2026-06-11)` + acceptance summary (no-bounce roll: pid-pair, exe_hash flip, hfenduleam heal)
- **KH 6.7**: append D7 close note (brain_survive.rs E2E + N-1 gate green + fleet-verified seamless apply)
- **KH 6.8**: append fleet-verify confirmation

Roll mechanics confirmed: v0.4.1 = counter 8 (new, no `releases/` clear needed). hfenduleam path: `spt update fetch` (origin) → `spt update apply` → broker respawns brain → capture after-half. Apply itself heals the stalled pump.

Ready. Awaiting deployah's published ping. CI green, deployah owns merge→tag→sign→publish.


<task-notification>
<task-id>bmby1p3hm</task-id>
<summary>Monitor event: "« spt event »"</summary>
<event><EVENT type="msg" from="deployah">Merged #4 → main (merge 038257b, version 0.4.1 confirmed). Tagged v0.4.1 on main + pushed → release.yml building both runners + drafting on spt-releases now. Watching for draft-live; on it I sign+publish counter 8 → flip public → ping you. Stand by.</EVENT></event>
</task-notification>


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


deployah merged → main (`038257b`), tagged v0.4.1, release.yml building+drafting. Syncing my local main while it builds (positions me for bookkeeping commit + fleet ops on updated main).


git checkout main 2>&1 | tail -2 && git pull --ff-only origin main 2>&1 | tail -5 && echo "=== head ===" && git --no-pager log -2 --oneline

Sync local main after merge


Your branch is up to date with 'origin/main'.
Already on 'main'
From https://github.com/SaberMage/spt-core
 * branch            main       -> FETCH_HEAD
Already up to date.
=== head ===
038257b Merge pull request #4 from SaberMage/release/v0.4.1
8c27720 chore(release): v0.4.1 — bump + changelog (pump-IPC-deadline self-heal)


Local main synced @ `038257b` (v0.4.1 prep merged). Positioned for the post-roll bookkeeping commit.

Standing by for deployah's published ping (release.yml building both runners → draft → sign counter 8 → public). On publish: fetch+apply v0.4.1 on hfenduleam → capture after-half → no-bounce + heal proof → restoration close.


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

release-publish exit 0:
  • verified vs SHA256SUMS:
      spt-x86_64-linux        35c9544c…17314f08
      spt-x86_64-windows.exe  728a1bfc…00df62d70
  • signed 54188f12…05e3ec9 (key_id rel-primary-2026)
  • per-platform *.release.json </EVENT-PART>
<EVENT-PART seq="2/4" id="d9389714">+ update-set.json (v8 &quot;0.4.1&quot;, win-msvc + linux-gnu) uploaded
  • draft → draft:false (PUBLIC)

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

ACCEPTANCE HASHES for after-half (brain.ready exe_hash must MATCH, NO manual bounce):
  kitsubito  (linux): 35c9544cfdd4883fb87d8ca8da1798addb82b4464b7441</EVENT-PART>
<EVENT-PART seq="3/4" id="d9389714">e79ae4db7617314f08
  hfenduleam (win):   728a1bfc2fb3f6a73a695a9a6e5dee5ef79325a9d2b23bae806a64b00df62d70

Roll the fleet. D7 proof = pid changes / new bytes live with NO manual bounce. hfenduleam pump wedge (broker 17232/brain 38324/81c24675…/STALLED) should heal on apply = bonus before/after. enlyzeam catch-up = project's final ma</EVENT-PART>
<EVENT-PART seq="4/4" id="d9389714">nual bounce. Capture after-half + restoration close — I'm idle if needed.</EVENT-PART></event>
</task-notification>


v0.4.1 **PUBLIC**, counter 8. Expected hfenduleam-win brain.ready exe_hash after apply = `728a1bfc…00df62d70` (current `81c24675…` = v0.4.0). Rolling hfenduleam now. My owl perch rides legacy owl, NOT the spt-core daemon (pid 17232) — safe.

Step 1: origin fetch v0.4.1.


spt update fetch 2>&1 | tail -25; echo "FETCH-EXIT:${PIPESTATUS[0]}"

Origin fetch v0.4.1 on hfenduleam


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:8 (set, 2 platforms, from SaberMage/spt-releases) — run `spt update apply` to apply
FETCH-EXIT:0


Staged counter 8 (set, 2 platforms). Step 2: apply (broker respawns brain onto v0.4.1, no manual bounce).


echo "=== PRE-APPLY brain.ready ==="; cat "$LOCALAPPDATA/spt-core/brain.ready"; echo; echo "=== APPLY ==="; spt update apply 2>&1 | tail -20; echo "APPLY-EXIT:${PIPESTATUS[0]}"

Apply v0.4.1 — seamless brain respawn


=== PRE-APPLY brain.ready ===
{"pid":38324,"generation":0,"exe_hash":"81c24675e344bf7426a654df364ef2ba5606b3f0e758308942c987cd98747da9"}
=== APPLY ===
Applying spt-core v0.4.1 — 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-EXIT:0


Apply exit 0 — "trial started" (readiness-gated, auto-rollback armed). Waiting for ready-promotion, then capturing after-half.


sleep 12; echo "=== POST-APPLY brain.ready ==="; cat "$LOCALAPPDATA/spt-core/brain.ready"; echo; echo "=== daemon.pid (broker) ==="; cat "$LOCALAPPDATA/spt-core/daemon.pid"; echo; echo "=== daemon status ==="; spt daemon status 2>&1 | head -20

Capture after-half: brain.ready + broker pid + status


=== POST-APPLY brain.ready ===
{"pid":6620,"generation":1,"exe_hash":"728a1bfc2fb3f6a73a695a9a6e5dee5ef79325a9d2b23bae806a64b00df62d70"}
=== daemon.pid (broker) ===
17232
=== daemon status ===
daemon: running (pid 17232)
peer pump: live (last tick 2s ago)
managed-by: manual — at-logon task 'spt-core daemon' registered (auto-starts at logon)
subnets:
  SPT_DEV  nodes=3 endpoints=1
local endpoints: (none)


🎯 **NO-BOUNCE PROVEN + WEDGE HEALED — hfenduleam, one roll:**

| field | pre-apply | post-apply | verdict |
|---|---|---|---|
| broker pid | 17232 | **17232** | UNCHANGED ✅ (broker survived) |
| brain pid | 38324 | **6620** | CHANGED ✅ |
| brain generation | 0 | **1** | incremented ✅ (broker custody) |
| brain exe_hash | `81c24675…` (v0.4.0) | **`728a1bfc…00df62d70`** | FLIPPED = v0.4.1 win artifact ✅ (D7-4 bytes) |
| pump | STALLED 16199s | **live, 2s** | HEALED ✅ |

Zero manual bounce — just `spt update apply`. Confirming promotion verdict + relaying.


echo "=== applied-state ==="; cat "$LOCALAPPDATA/spt-core/applied-state.json" 2>/dev/null; echo; cat "$LOCALAPPDATA/spt-core/applied.json" 2>/dev/null; echo; cat "$LOCALAPPDATA/spt-core/last-outcome.json" 2>/dev/null; echo "---"; spt --version 2>&1

Confirm apply verdict + binary version


=== applied-state ===

---
spt 0.4.1


`spt --version` = **0.4.1**, brain.ready gen 1 + v0.4.1 hash + pump live = trial promoted, new code running. (applied-state file path differs — brain.ready + version + healthy pump are conclusive.) Relaying win.


$OWL send doyle 2>/dev/null <<'EOF'
hfenduleam ACCEPTANCE — D7 no-bounce PROVEN + wedge HEALED in one roll (just `spt update fetch` + `spt update apply`, ZERO manual bounce):
                    PRE              POST
  broker pid        17232            17232      UNCHANGED  ✅ broker survived the brain respawn
  brain pid         38324            6620       CHANGED    ✅
  generation        0                1          +1         ✅ broker-owned custody
  brain exe_hash    81c24675…(0.4.0) 728a1bfc…00df62d70    FLIP → == v0.4.1 win artifact hash ✅ (D7-4 bytes)
  pump              STALLED 16199s   live 2s    HEALED     ✅ first-tick re-prime
  daemon status     —                healthy    ✅
  spt --version     —                0.4.1
Proves REQ-UPD-3 + REQ-DAEMON-2 + REQ-HAZARD-BROKER-PROCESS-ISOLATION at the fleet/process level AND KH 7.6 pump-IPC-deadline heal on a real field stall. Rolling kitsubito next (linux artifact 35c9544c… proof). enlyzeam catch-up after. Then restoration CLOSE bookkeeping — will run it past you.
EOF
echo "d:${PIPESTATUS[0]}"
$OWL send deployah 2>/dev/null <<'EOF'
hfenduleam rolled — NO-BOUNCE PROVEN: broker pid 17232 UNCHANGED, brain 38324→6620, gen 0→1, exe_hash 81c24675…→728a1bfc…00df62d70 (== your published win hash ✅), pump STALLED 16199s→live 2s (wedge healed), spt --version 0.4.1, status healthy. One `spt update apply`, zero manual bounce. Rolling kitsubito next vs your linux hash 35c9544c…. Thanks for the clean publish.
EOF
echo "p:${PIPESTATUS[0]}"

Relay acceptance win to doyle + deployah


d:0
p:0


hfenduleam done. Rolling **kitsubito** (linux) — ssh reavus@kitsubito, expect exe_hash flip to `35c9544c…`. Capture-before + fetch + apply.


ssh reavus@kitsubito 'echo "=== PRE brain.ready ==="; cat ~/.spt-core/brain.ready 2>/dev/null; echo; echo "=== PRE daemon.pid ==="; cat ~/.spt-core/daemon.pid 2>/dev/null; echo; echo "=== version ==="; spt --version 2>&1; echo "=== status ==="; spt daemon status 2>&1 | head -8' 2>&1 | tail -25

Capture kitsubito pre-roll state


=== PRE brain.ready ===
{"pid":819839,"generation":0,"exe_hash":"b4b7ea142d2ec3d7bb20e33cb66c8ff81464aeef258eca42842e55e04cf07b5e"}
=== PRE daemon.pid ===
819830
=== version ===
spt 0.4.0
=== status ===
daemon: running (pid 819830)
peer pump: STALLED (last tick 18189s ago — advertising/connecting is not running; restart the daemon)
managed-by: systemd --user (active)
subnets:
  SPT_DEV  nodes=3 endpoints=1
local endpoints: (none)


kitsubito pre-roll: broker 819830 / brain 819839 / exe_hash `b4b7ea14…` (v0.4.0) / pump STALLED 18189s (also wedged!). Fetch + apply.


ssh reavus@kitsubito 'export PATH=$HOME/.cargo/bin:$PATH; echo "=== FETCH ==="; spt update fetch 2>&1 | tail -8; echo "FETCH:$?"; echo "=== APPLY ==="; spt update apply 2>&1 | tail -8; echo "APPLY:$?"' 2>&1 | tail -20

Fetch + apply v0.4.1 on kitsubito


<owl_messages>
<owl_message from="deployah" priority="high">That's the proof. hfenduleam exe_hash == 728a1bfc…00df62d70 my published win artifact — sign chain verified at the live edge, broker pid held, brain pid rolled, gen 0→1, wedge self-healed on apply, zero manual bounce. The v0.4.0-deferred seamless assert is now GREEN. Roll kitsubito vs 35c9544c…17314f08 (linux). Idle, watching for the linux match + enlyzeam catch-up + close.</owl_message>
</owl_messages>

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


=== FETCH ===
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:8 (set, 2 platforms, from SaberMage/spt-releases) — run `spt update apply` to apply
FETCH:0
=== APPLY ===
Applying spt-core v0.4.1 — 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 apply trial started. Waiting for promotion, capturing after-half (expect exe_hash → `35c9544c…17314f08`).


sleep 14; ssh reavus@kitsubito 'echo "=== POST brain.ready ==="; cat ~/.spt-core/brain.ready 2>/dev/null; echo; echo "=== POST daemon.pid ==="; cat ~/.spt-core/daemon.pid 2>/dev/null; echo; echo "=== version ==="; export PATH=$HOME/.cargo/bin:$PATH; spt --version 2>&1; echo "=== status ==="; spt daemon status 2>&1 | head -8' 2>&1 | tail -25

Capture kitsubito after-half


=== POST brain.ready ===
{"pid":1247825,"generation":1,"exe_hash":"b4b7ea142d2ec3d7bb20e33cb66c8ff81464aeef258eca42842e55e04cf07b5e"}
=== POST daemon.pid ===
819830
=== version ===
spt 0.4.1
=== status ===
daemon: running (pid 819830)
peer pump: live (last tick 2s ago)
managed-by: systemd --user (active)
subnets:
  SPT_DEV  nodes=3 endpoints=1
local endpoints: (none)


⚠ kitsubito **divergence** — broker pid held (819830), brain pid rolled (1247825), gen→1, pump healed, `spt --version` 0.4.1 — BUT brain.ready exe_hash = **`b4b7ea14…` (still v0.4.0)**, NOT the expected `35c9544c…`. Running brain = v0.4.0 bytes. Smells like **auto-rollback fired** (v0.4.1 linux brain failed readiness → broker respawned last-known-good). Investigating verdict before reporting — not glossing.


ssh reavus@kitsubito 'echo "=== full status ==="; export PATH=$HOME/.cargo/bin:$PATH; spt daemon status 2>&1; echo "=== applied/outcome records ==="; for f in applied-state.json applied.json last-outcome.json rolled-back.json; do echo "--$f--"; cat ~/.spt-core/$f 2>/dev/null; echo; done; echo "=== releases dir ==="; ls -la ~/.spt-core/releases 2>/dev/null; echo "=== bin dir ==="; ls -la ~/.spt-core/bin 2>/dev/null' 2>&1 | tail -45

Investigate kitsubito apply verdict + records


=== full status ===
daemon: running (pid 819830)
peer pump: live (last tick 1s ago)
managed-by: systemd --user (active)
subnets:
  SPT_DEV  nodes=3 endpoints=1
local endpoints: (none)
=== applied/outcome records ===
--applied-state.json--

--applied.json--

--last-outcome.json--

--rolled-back.json--

=== releases dir ===
total 28
drwxrwxr-x 3 reavus reavus 4096 Jun 11 00:37 .
drwxr-xr-x 7 reavus reavus 4096 Jun 11 00:38 ..
-rw-rw-r-- 1 reavus reavus   13 Jun 11 00:37 applied.json
-rw-rw-r-- 1 reavus reavus   31 Jun 11 00:37 applied-state.json
drwxrwxr-x 2 reavus reavus 4096 Jun 11 00:37 artifacts
-rw-rw-r-- 1 reavus reavus   21 Jun 11 00:37 last-outcome.json
-rw-rw-r-- 1 reavus reavus  740 Jun 11 00:37 release.json
=== bin dir ===


Records live in `~/.spt-core/releases/` (timestamps match this apply). Reading them.


ssh reavus@kitsubito 'cd ~/.spt-core/releases; echo "--applied.json--"; cat applied.json; echo; echo "--applied-state.json--"; cat applied-state.json; echo; echo "--last-outcome.json--"; cat last-outcome.json; echo; echo "--release.json--"; cat release.json; echo; echo "--artifacts--"; ls -la artifacts/; echo "--brain.ready again--"; cat ~/.spt-core/brain.ready' 2>&1 | tail -30

Read kitsubito release records


--applied.json--
{"version":8}
--applied-state.json--
{"phase":"applied","version":8}
--last-outcome.json--
{"outcome":"applied"}
--release.json--
{
  "metadata_json": "{\"version\":8,\"channel\":\"stable\",\"expires_at_ms\":1812699292570,\"key_id\":\"rel-primary-2026\",\"artifacts\":{\"x86_64-pc-windows-msvc\":{\"artifact_sha256\":\"728a1bfc2fb3f6a73a695a9a6e5dee5ef79325a9d2b23bae806a64b00df62d70\",\"brain_ipc_version\":1,\"broker_resource_abi\":1,\"asset_name\":\"spt-x86_64-windows.exe\"},\"x86_64-unknown-linux-gnu\":{\"artifact_sha256\":\"35c9544cfdd4883fb87d8ca8da1798addb82b4464b7441e79ae4db7617314f08\",\"brain_ipc_version\":1,\"broker_resource_abi\":1,\"asset_name\":\"spt-x86_64-linux\"}},\"product_version\":\"0.4.1\"}",
  "signature_hex": "52f8a0bea43553b2608d57704b2674486f69971a6f07d3e94c3d4b9aaba239cad0b42c57e264426350f2a9d10a518cd49ea630c385a0f3db7de352d56e2ab70d"
}
--artifacts--
total 59924
drwxrwxr-x 2 reavus reavus     4096 Jun 11 00:37 .
drwxrwxr-x 3 reavus reavus     4096 Jun 11 00:37 ..
-rw-rw-r-- 1 reavus reavus 25416192 Jun 11 00:37 x86_64-pc-windows-msvc.bin
-rw-rw-r-- 1 reavus reavus 35934048 Jun 11 00:37 x86_64-unknown-linux-gnu.bin
--brain.ready again--
{"pid":1247825,"generation":1,"exe_hash":"b4b7ea142d2ec3d7bb20e33cb66c8ff81464aeef258eca42842e55e04cf07b5e"}


Records say **applied v8 (no rollback)** — `{"phase":"applied","version":8}`, `{"outcome":"applied"}`. Yet the running brain bytes = `b4b7ea14` (v0.4.0). Contradiction: daemon believes v0.4.1 promoted, but brain executes old bytes. Not a rollback — a **swap-vs-exec mismatch**. Diagnosing actual on-disk + /proc exe hashes.


ssh reavus@kitsubito 'export PATH=$HOME/.cargo/bin:$PATH
echo "=== broker exe (pid 819830) ==="; readlink /proc/819830/exe 2>/dev/null
echo "=== brain exe (pid 1247825) ==="; readlink /proc/1247825/exe 2>/dev/null
echo "=== which spt (CLI) ==="; command -v spt
echo "=== hashes ==="
for p in "$(readlink /proc/819830/exe 2>/dev/null | sed s/\ \(deleted\)//)" "$(readlink /proc/1247825/exe 2>/dev/null | sed s/\ \(deleted\)//)" "$(command -v spt)"; do echo -n "$p : "; sha256sum "$p" 2>/dev/null | cut -d" " -f1; done
echo "=== v0.4.1 linux artifact hash (staged) ==="; sha256sum ~/.spt-core/releases/artifacts/x86_64-unknown-linux-gnu.bin | cut -d" " -f1
echo "=== systemd unit ExecStart ==="; systemctl --user cat spt-daemon 2>/dev/null | grep -i execstart' 2>&1 | tail -30

Diagnose kitsubito broker/brain exe paths + hashes


=== broker exe (pid 819830) ===
/home/reavus/.local/bin/spt.old-8
=== brain exe (pid 1247825) ===
/home/reavus/.local/bin/spt.old-8
=== which spt (CLI) ===
/usr/local/bin/spt
=== hashes ===
/home/reavus/.local/bin/spt.old-8 : b4b7ea142d2ec3d7bb20e33cb66c8ff81464aeef258eca42842e55e04cf07b5e
/home/reavus/.local/bin/spt.old-8 : b4b7ea142d2ec3d7bb20e33cb66c8ff81464aeef258eca42842e55e04cf07b5e
/usr/local/bin/spt : 35c9544cfdd4883fb87d8ca8da1798addb82b4464b7441e79ae4db7617314f08
=== v0.4.1 linux artifact hash (staged) ===
35c9544cfdd4883fb87d8ca8da1798addb82b4464b7441e79ae4db7617314f08
=== systemd unit ExecStart ===
ExecStart=/home/reavus/.local/bin/spt daemon run


Found it. Two install paths on kitsubito:
- systemd ExecStart = `/home/reavus/.local/bin/spt`
- broker (819830) AND brain (1247825) both exec `/home/reavus/.local/bin/spt.old-8` = **`b4b7ea14` (v0.4.0)**
- CLI on PATH `/usr/local/bin/spt` = `35c9544c` (v0.4.1)
- staged v0.4.1 linux artifact = `35c9544c` ✅

The resident **broker** running `.old-8` is correct (broker not updated, brain-only). But the **respawned brain ALSO execs `.old-8`** (old bytes) — record says "applied v8" while the brain runs v0.4.0. The broker respawns the brain via its own renamed `current_exe` (`.old-8`) instead of the swapped canonical path. **Windows worked, Linux broke.** Real bug — restoration's central invariant fails on Linux. Confirming the canonical-path bytes before escalating.


ssh reavus@kitsubito 'echo "=== .local/bin contents ==="; ls -la ~/.local/bin/spt* 2>/dev/null
echo "=== hash /home/reavus/.local/bin/spt (canonical daemon path) ==="; sha256sum ~/.local/bin/spt 2>/dev/null | cut -d" " -f1
echo "=== is /usr/local/bin/spt a symlink? ==="; ls -la /usr/local/bin/spt 2>/dev/null; readlink -f /usr/local/bin/spt 2>/dev/null
echo "=== expected: 35c9544c = v0.4.1 ==="' 2>&1 | tail -20

Confirm canonical daemon-path bytes + symlink