{
  "summary": "PINNED ROOT\n\nThe 0.31.0 source deterministically performs a whole-executable SHA-256 and an atomic `brain.ready` replacement on every successful brain heartbeat. `crates/spt-daemon/src/brainproc.rs:62-63` sets `BRAIN_HEARTBEAT = 500 ms`; `run_brain` sleeps that duration at `:249-250`, completes synchronous `net_status()` and `sessions()` IPC at `:254` and `:286`, then unconditionally calls `write_ready(generation)` at `:287`. `write_ready` at `:355-362` unconditionally attempts `current_exe_hash()` at `:359`, then unconditionally attempts the atomic persistence at `:362`. `current_exe_hash` at `:371-374` executes `std::env::current_exe()` (`:372`, Linux `readlink(\"/proc/self/exe\")`), `std::fs::read(exe)` (`:373`, opens and reads the entire executable into a newly allocated `Vec<u8>`), and `release::sha256_hex` (`:374`). The SHA primitive is `crates/spt-daemon/src/release.rs:702-705`: `Sha256::new()`, `update(all bytes)`, `finalize()`. This exactly explains issue #1's `readlink → open /usr/local/bin/spt → statx`, its 73-76% `sha2::sha256::compress256` samples, and the recurring `brain.ready.tmp… → rename` trace.\n\nExact persistence tail: `brainproc.rs:362` → `spt_store::atomic::atomic_write_string`; `crates/spt-store/src/atomic.rs:58-59` → `atomic_write_bytes`; `:74-75` → `write_then_rename`; `:117-132` increments process-global `TMP_SEQ`, constructs `<name>.tmp.<pid>-<seq>`, and for this non-durable caller runs `fs::write(&tmp, body)` at `:130`; `:132` calls `rename_with_retry`; `:145-146` executes `fs::rename(from, to)`. The target comes from `crates/spt-daemon/src/endpoint.rs:74-84`, `<spt_home>/brain.ready`. Each successful publication therefore creates a sibling like `brain.ready.tmp.<pid>-<seq>` and renames it over `brain.ready`.\n\nCADENCE ARITHMETIC AND CONDITIONALITY\n\n* Startup: one `write_ready` is made at `brainproc.rs:221`, immediately after resume completes or fails non-fatally, before the steady loop. It hashes and writes once.\n* Steady state: nominal maximum is `1 / 0.500 s = 2 publications/s`, hence 2 full executable hashes, 2 temp writes, and 2 renames per second. This is a delay-after-work loop, not a fixed-rate timer: actual period is `500 ms + net_status round trip + optional one-time consumer spawn + sessions round trip + executable read/SHA + ready write/rename`. [INFERENCE] If that work consumes roughly another 500 ms on the reported VPS, the trace appears roughly once per second, exactly as reported. There is no cadence jitter or backoff in the healthy heartbeat.\n* `net_status()` is unconditional after every sleep. If it errors, `run_brain` returns at `:256-259`, so neither `sessions()` nor ready publication occurs on that failed cycle. Net-consumer spawning is conditional (`status.enabled && !consumers_started`) and happens at most once per process. `sessions()` is attempted unconditionally on every cycle that passed `net_status`; its error is ignored, and `write_ready` still follows. `write_ready` always calls the hash function. The `exe_hash` JSON insertion is conditional on hash success, but the ready write is attempted either way. `current_exe()` precedes file read; file read precedes SHA, so a failure short-circuits later steps. Atomic `fs::write` precedes rename; a write failure prevents rename. Rename retries only transient errors: `atomic.rs:29-30` allows 10 attempts and `:152-153` sleeps linearly 2,4,…,18 ms after the first 9 failures, at most 90 ms total sleep before the tenth attempt.\n* Supervisor/trial cadence is separate and not the CPU root: `brainproc.rs:48` gives a 30 s readiness window, `:55` gives 3 pre-ready exits, and `:57` gives a 200 ms poll tick, so an alive pending trial can perform at most about 150 generation/file polls over 30 s. `run_trial` checks `ready_generation()` at `:685`, and after readiness/drain the promotion arm invokes `bytes_gate` once at `:811`; `bytes_gate` at `:970-981` compares the already-published hash string to staged metadata. It never hashes `current_exe`. Outside `AppliedPending`, the supervisor does not poll readiness for promotion.\n* Brain broker-connect retry is `300 × 10 ms ≈ 3 s` before failure (`brainproc.rs:61` and `:325-334`), unrelated to the steady hot loop.\n* The other brain-owned live reconciliation loop is 5 s (`crates/spt-daemon/src/livehost.rs:47-50`, sleep sliced in 100 ms chunks at `:1047-1052`); it does not publish `brain.ready` or hash the executable.\n\nINTENDED INVARIANT VERSUS IMPLEMENTATION\n\nThe intended readiness invariant is: generation N may promote only after the generation-N child has resumed and published ready, the old generation's control plane has drained, and, when both hashes exist, the child-reported resident-image hash matches the signed/staged platform artifact. Evidence: post-resume initial publication is `brainproc.rs:211-221`; generation equality and drained gating are `:649-709`; promotion bytes comparison is `:953-981`; `ProductionTrialEnv` reads the ready file and staged metadata at `:528-559`; stale cross-lifetime readiness is removed only before an `AppliedPending` spawn at `:769-781`. `RESTORATION-D6-PLAN.md:172-203` explicitly identifies generation equality plus clear-before-trial as the stale-file defense. Repeated hashing is not part of this invariant.\n\nThe D7 design explicitly intended one executable hash per brain start: `RESTORATION-D7-PLAN.md:220-235` says the field is written by the real brain post-resume with “one current_exe read + hash per brain start — negligible”; the same intent is restated at `:507-514`. The implementation comment at `brainproc.rs:365-370` repeats “One exe read + hash per brain start.” But the hash was placed inside the pre-existing heartbeat-called `write_ready`, converting “per brain start” into “per ready publication.” This is the defect.\n\nHISTORY\n\nThe heartbeat/readiness skeleton predates the hash: `.git/logs/HEAD:400` records commit `0c954353b256f46e45a600be7a7fb1f3768e9d30`, `feat(daemon): restoration D1 — broker/brain process-split skeleton`. `RESTORATION-D6-PLAN.md:79-84` records that the D1-era code already wrote `brain.ready` both after connect and on heartbeat. The defect was introduced by `.git/logs/HEAD:432`, commit `a52f8c6331e368777348a14c703363afc0585183` (2026-06-10, reflog timestamp `1781128024 -0700`), `feat(daemon): restoration D7-1a — exe_hash breadcrumb on brain.ready (V5)`. That commit composed the new self-hash into the already-cadenced writer. `.git/packed-refs:102` identifies v0.4.0 as `b0fdc015fd37172ab3782fcc71878cfa67da7b3d`, after the D7 commit (`.git/logs/HEAD:441`); therefore it shipped in v0.4.0 and remains present in the checked 0.31.0 source (`Cargo.toml:18-19`). No cache or fix exists in current `brainproc.rs:355-374`.\n\nCAUSE RANKING / FALSIFICATION\n\n1. **Pinned cause, direct:** readiness refresh hashes the complete current executable every 500 ms nominally. Every observed syscall and hot symbol lies on this exact unconditional call graph.\n2. **Not the cause:** stale-image/update reconciliation. The trial supervisor polls the small JSON file every 200 ms only during a pending 30 s trial and compares strings once at promotion (`brainproc.rs:685,811,970-981`; `relcache.rs:390-408`). It does not open or hash `current_exe`.\n3. **Not the cause:** broker-image status. `brain.rs:1136-1144` makes an on-demand IPC request; `broker.rs:3850-3857` replies with compile-time `CARGO_PKG_VERSION`, no file I/O or SHA.\n4. **Not the idle cause:** artifact integrity verification. `release.rs:642-697` hashes downloaded/staged byte buffers only when verification is invoked, not each heartbeat, and does not call `current_exe`.\n5. **Not the 500 ms source:** livehost reconciliation runs at 5 s and has no readiness/hash call (`livehost.rs:47-50,971-1053`).\n\nMINIMAL CORRECT FIX AND SAFE INVALIDATION\n\nCache the first successful self-image digest at `run_brain` process scope and pass a borrowed cached hash into every ready publication. Preserve the existing 500 ms `brain.ready` atomic rewrite for the minimal behavioral cut: `endpoint.rs:74-81` documents refresh-while-attached, and source-local consumers may not use mtime but external operators could. Removing writes is a separate contract decision and should not be bundled into this CPU fix.\n\nUse success-only caching: a mutable `Option<String>` local to `run_brain`; when empty, attempt `current_exe_hash`; once `Some`, never recompute. This preserves current best-effort recovery from a transient initial `current_exe`/read failure while guaranteeing exactly one SHA on the healthy path. Persistent lookup/read failures may retry, but they never reach SHA unless the full read succeeds; the reported successful-hash hot loop is eliminated.\n\nNo metadata key, mtime check, path watch, heartbeat invalidation, or staged-update invalidation is safe or needed. `applyhost.rs:263-280` renames the current binary aside, atomically lands new bytes, and records `AppliedPending`; `:339` asks the broker to restart the brain. The supervisor captures the canonical path once at broker start (`brainproc.rs:1092-1100`) and every update/crash/rollback is a new child process. Thus cache lifetime is naturally exact:\n\n* old child keeps hash A while the canonical path is swapped, correctly describing its still-resident old image;\n* new candidate child starts a fresh cache and computes hash B before its first ready publication;\n* rollback child starts a fresh cache and computes the old-image hash;\n* heartbeat and update staging do not invalidate anything.\n\nInvalidating on pathname metadata would be semantically worse: during rename-based apply, the path changes while the old process's resident code does not. Re-hashing a newly landed canonical file from an old process can report bytes the process is not running. Process lifetime is the correct cache key. A future true in-process code loader would need an explicit loader-owned invalidation event, but no such mechanism exists in this architecture.\n\nDETERMINISTIC REPRODUCTION / REGRESSION SEAM\n\nExtract the ready-publication core in `brainproc.rs` behind injected hash and persist callables while keeping production behavior unchanged. Suggested narrow shape: `publish_ready_with(generation, &mut Option<String>, hash_fn, persist_fn)`, where `hash_fn: FnOnce() -> Option<String>` is invoked only while the cache is empty and `persist_fn(&str)` is invoked every publication. Production binds `current_exe_hash` and `atomic_write_string(brain_ready_path(), body)`; `run_brain` owns the cache and calls this helper at the initial ready point and after each heartbeat.\n\nFocused deterministic tests in the existing `brainproc.rs` unit module:\n\n1. `ready_publication_hashes_once_but_writes_every_time`: counters in injected closures; make one initial call plus four synthetic heartbeats. Assert `hash_calls == 1`, `write_calls == 5`, and all five complete JSON bodies contain identical `{pid,generation,exe_hash}`. This fails the old call placement's observable contract (`hash_calls == 5`) and requires no clock, real executable, daemon, filesystem watcher, or sleep.\n2. `ready_publication_retries_hash_only_until_success`: scripted hash results `[None, Some(\"abc\")]`, three publications. Assert `hash_calls == 2`, `write_calls == 3`; first body omits `exe_hash`, second and third contain `abc`; third does not hash again. This pins best-effort recovery without reintroducing steady CPU.\n3. Retain existing semantic guards: `brainproc.rs:1392-1407` pins additive/N-1 hash parsing, `:1737-1745` pins the bytes-gate truth table, and `:1754+` pins wrong-bytes rollback. The process-level `crates/spt/tests/brain_respawn_rename.rs:21-29,40-68` and `brain_survive.rs:292-328` already prove a restarted process publishes the new image's hash, which validates automatic per-process invalidation.\n\nOptional but not required for this root: once the digest succeeds, cache the serialized JSON body as well because pid, generation, and digest are invariant for the process. The load-bearing regression criterion remains: over N healthy publications, hash count is exactly 1 while persistence count remains N.",
  "files": [
    {
      "path": "crates/spt-daemon/src/brainproc.rs",
      "description": "Primary root. `BRAIN_HEARTBEAT` at 62-63; initial ready at 221; 500 ms steady loop and ready call at 249-287; self-hash and ready writer at 340-375; trial/stale/promotion logic at 419-981; process restart/cache lifetime boundary at 998-1124."
    },
    {
      "path": "crates/spt-store/src/atomic.rs",
      "description": "Persistence tail. Atomic string/bytes wrappers at 58-75; unique temp creation, non-durable write, and rename at 117-146; 10-attempt transient rename policy and backoff at 29-30 and 152-153."
    },
    {
      "path": "crates/spt-daemon/src/release.rs",
      "description": "SHA-256 primitive at 702-705 and non-idle artifact verification calls at 642-697."
    },
    {
      "path": "crates/spt-daemon/src/endpoint.rs",
      "description": "Defines `<spt_home>/brain.ready` and documents refresh-while-attached semantics at 74-84."
    },
    {
      "path": "crates/spt-daemon/src/applyhost.rs",
      "description": "Shows supported update invalidation boundary: rename old image, atomically land new, record `AppliedPending`, request brain process restart at 248-339; atomic artifact landing at 419-423."
    },
    {
      "path": "crates/spt-daemon/src/relcache.rs",
      "description": "Staged artifact hash lookup at 390-408 reads signed metadata strings for promotion; it does not hash the executable."
    },
    {
      "path": "crates/spt-daemon/src/brain.rs",
      "description": "On-demand broker image version request at 1135-1145; unrelated to self hashing."
    },
    {
      "path": "crates/spt-daemon/src/broker.rs",
      "description": "Broker image reply at 3849-3860 returns compile-time package version only."
    },
    {
      "path": "crates/spt-daemon/src/livehost.rs",
      "description": "Independent 5-second brain-host reconciliation cadence at 47-50 and 971-1053; no ready publication/self-hash."
    },
    {
      "path": "RESTORATION-D7-PLAN.md",
      "description": "Exact design intent at 220-235 and 507-514: `exe_hash` is a resident-bytes breadcrumb with one current-exe read/hash per brain start."
    },
    {
      "path": "RESTORATION-D6-PLAN.md",
      "description": "Documents pre-hash ready heartbeat and the generation/clear-before-trial stale-readiness invariant at 79-84 and 172-203."
    },
    {
      "path": ".git/logs/HEAD",
      "description": "History evidence: D1 heartbeat skeleton commit at line 400 and D7 exe-hash introduction commit `a52f8c…` at line 432; v0.4.0 release ancestry at line 441."
    },
    {
      "path": ".git/packed-refs",
      "description": "Pins v0.4.0 tag to `b0fdc015…` at line 102."
    },
    {
      "path": "Cargo.toml",
      "description": "Pins inspected workspace source to version 0.31.0 at lines 18-19."
    },
    {
      "path": "crates/spt/tests/brain_respawn_rename.rs",
      "description": "Existing cross-platform process test proves a respawned brain recomputes the new process image hash; useful invalidation/regression coverage."
    },
    {
      "path": "crates/spt/tests/brain_survive.rs",
      "description": "Existing process-level swap test asserts pre/post process hashes A→B at 292-328, preserving update correctness after caching."
    }
  ],
  "architecture": "The broker is the stable process; `run_brain` is a restartable child process. Readiness is a generation-stamped, atomic file breadcrumb. During an update, apply renames the old canonical executable, lands a verified new artifact, records an `AppliedPending` trial, and asks the broker supervisor to kill/spawn the brain. The child publishes ready post-resume; the broker-side trial gate accepts only matching generation plus drained old control plane, then compares the published child image hash with the staged signed hash. That topology makes self-image identity immutable for one brain process and makes process construction the natural cache invalidation boundary. The bug crossed two otherwise sound pieces: D1's 500 ms readiness refresh and D7's intended once-per-start resident-image digest. Keeping publication cadenced but digest process-cached restores the intended architecture without changing readiness, rollback, N-1, or update semantics."
}