{
  "summary": "Root cause and history are pinned. The sustained SHA-256 load is a D7 implementation mistake, not broker-staleness or update reconciliation. `run_brain` sleeps for `BRAIN_HEARTBEAT = 500 ms`, performs `net_status()` and `sessions()`, then calls `write_ready(generation)` on every tick (`crates/spt-daemon/src/brainproc.rs:62-63,249-287`). `write_ready` calls `current_exe_hash()`, which executes `current_exe()` + `fs::read(exe)` + SHA-256 of the full binary, then atomically rewrites `brain.ready` (`brainproc.rs:355-374`). This exactly predicts the issue trace: `/proc/self/exe` readlink, executable open/read, unique `brain.ready.tmp.<pid>-<seq>` creation, and rename. IPC work can stretch the nominal 500 ms loop toward the observed roughly one-second interval, but it does not cause the SHA hotspot.\n\nINTRODUCTION/VERSION EVIDENCE\n- `brain.ready` itself and its recurring heartbeat were introduced by D1 commit `0c954353b256f46e45a600be7a7fb1f3768e9d30`, reflog message `feat(daemon): restoration D1 — broker/brain process-split skeleton (REQ-HAZARD-BROKER-PROCESS-ISOLATION)`. `RESTORATION-D1-PLAN.md:23-36` explicitly specifies a ready breadcrumb written at startup and heartbeated while the child idles.\n- The seamless update restart path was introduced by D3 commit `4d8f34c815142c44c2329521284a06d67290b083`, `feat(daemon): restoration D3-3 — real brain-process restart on update (Q3)`. Current `supervise_brain` kills the old child on `BrainRestart`, increments generation, and immediately spawns the replacement with `StartReason::Update` (`brainproc.rs:873-909`).\n- Generation-stamped, post-resume readiness was introduced by D6 commit `8859c9b1d004e5ab824a089eef4c07b4027df56b`, `feat(daemon): restoration D6-1 — two-phase applied record + gen-stamped ready (Q7)`. D6 deliberately retained the existing file channel and heartbeat, changing the body from bare pid to `{pid,generation}` and moving the first write after session resume (`RESTORATION-D6-PLAN.md:79-83,172-205`).\n- The exact offending change is commit `a52f8c6331e368777348a14c703363afc0585183` on 2026-06-10, reflog message `feat(daemon): restoration D7-1a — exe_hash breadcrumb on brain.ready (V5)`. It added `exe_hash` calculation inside the already-recurring `write_ready` function.\n- The design intent contradicts the implementation: `RESTORATION-D7-PLAN.md:222-235` says the field is written always but costs **“one current_exe read + hash per brain start — negligible.”** Current `brainproc.rs:365-370` repeats the same claim, while the call placement makes it one full-binary read/hash per ready publication, initially plus every 500 ms.\n- The change first shipped in `v0.4.0`: packed tag `v0.4.0` points at `b0fdc015fd37172ab3782fcc71878cfa67da7b3d`, which contains `a52f8c6`. `CHANGELOG.md:841-850` describes the two-process seamless swap and rollback. `docs/TWO-HOST-RUNBOOK.md:379-417` records the v0.4.0 field verification using `brain.ready.exe_hash` against release SHA256SUMS.\n- Linux respawn correctness was then hardened in v0.4.2: commit `be626bf83329bfb90fef3f2fee15aab87199ce13`, `fix(daemon): respawn brain from canonical exe + bytes-gate promotion`, followed by E2E commit `f69e0cd8e11c75f18eef74d4c939a5c21ebf1b96`. Tag `v0.4.2` points at `725aebf647f8ebe4415fcfd2274488b41cc561f0`. `CHANGELOG.md:829-834`, `V042-PLAN.md:1-126`, and `KNOWN-HAZARDS.md:356-360` document the Linux `current_exe()` rename trap and the rule that readiness alone is not proof of new bytes.\n- v0.31.0 inherited this D7 hot path unchanged. Its release commit is `97a0e4f7d14c8a260e600c455e3286579cb0f85e`; the v0.31.0 changelog is unrelated messaging/identity work and says that release requires a full daemon restart (`CHANGELOG.md:12-22`). Thus 0.31.0 exposed an old defect rather than introducing it.\n\nDESIGN REASON AND REQUIRED INVARIANTS\n1. Broker/brain isolation: the broker is the long-lived resource custodian; a brain-only update must restart only the brain, preserving every hosted endpoint process, PTY, and QUIC connection. This is `REQ-UPD-3` and `REQ-HAZARD-BROKER-PROCESS-ISOLATION` (`traceable-reqs.toml:411-414,987-989`; ADR-0018; `docs-site/src/self-update/overview.md`).\n2. Readiness meaning: generation N may stamp ready only after it reconnects and re-establishes session cursors. The supervisor accepts only a stamp whose `generation == N`; before a trial it deletes stale `brain.ready` to close the broker-reboot/gen-0 collision (`brainproc.rs:211-221,438-446,648-715,774-778`).\n3. Promotion meaning: a candidate promotes only after readiness and old-generation drain; if both hashes exist, its `brain.ready.exe_hash` must equal the staged artifact hash. A mismatch kills the candidate and rolls back (`brainproc.rs:656-685,810-824,950-982`; `relcache.rs:390-409`; `REQ-UPDATE-PROMOTE-DRAINED`).\n4. Resident-image identity: `exe_hash` means the bytes this **brain process** is running. Those bytes are immutable for that process lifetime even if an update renames/replaces the canonical executable on disk. Therefore the digest is process-generation state, not heartbeat state and not mutable file-cache state.\n5. Seamless replacement provides natural invalidation: `apply_staged` swaps the canonical binary, asks the broker to restart the brain (`applyhost.rs:303-350`), the old child is killed, and the new OS process computes its own digest. No in-process invalidation is required or desirable.\n6. `brain.ready` may continue to be atomically refreshed for the existing breadcrumb contract, but refresh must reuse invariant `{pid,generation,exe_hash}` data. Liveness of the daemon itself is seed-control ping, not this file (`endpoint.rs:73-84`).\n7. N-1 disk compatibility remains: `exe_hash` is additive and an older stamp without it parses to `None`; `rollback_compat.rs:139-173` guards that shape.\n\nBROKER IMAGE/STALENESS FALSIFICATION\n- The issue’s `broker image 0.31.0 / broker not stale` observation is independent. `Broker::dispatch_broker_image` returns the compile-time `env!(\"CARGO_PKG_VERSION\")` string over IPC (`broker.rs:3849-3860`); CLI status compares that string with its own compile-time version (`spt/src/cli.rs:4137-4164`). No executable read or hash occurs.\n- This surface was added in v0.21.0 (`CHANGELOG.md:377-394`) to report an old resident broker after an update. It is queried on `daemon status`, not every brain heartbeat. A non-stale broker therefore rules out a version mismatch but neither causes nor suppresses the hash loop.\n- No update-fetch/reconcile call appears in `run_brain`’s heartbeat. The only per-tick operations are `net_status`, optional once-only consumer start, `sessions` (added to drive controller reap), and `write_ready`. The SHA-256 is directly inside the last operation.\n\nRANKED FIXES\n1. **Correct/minimal: capture the self digest once per brain process, reuse it for every publication.** At `run_brain` startup, after resume and immediately before the first ready stamp, compute `let exe_hash = current_exe_hash();`; pass `exe_hash.as_deref()` into all initial/heartbeat ready publications, or construct an immutable serialized `ReadyStamp` once and atomically publish that body repeatedly. This restores the explicit D7 requirement, preserves all existing ready-file cadence and compatibility, and naturally recomputes after crash, rollback, cold start, or seamless update because every generation is a new OS process. Risk: a transient first-read failure leaves `exe_hash` absent for that process instead of allowing a later heartbeat retry. Existing semantics already define omission as best-effort and the bytes gate degrades loudly to unverified; if maintainers judge transient reads realistic, use a small bounded startup retry, never indefinite heartbeat retries.\n2. **Architecturally cleaner but broader: publish `brain.ready` once per generation and stop rewriting it on heartbeat.** Current in-repo readers inspect pid/generation/hash, not mtime; daemon liveness is seed control, and trial readiness only needs the post-resume stamp. This removes both full hashing and the ongoing temp-write/rename churn. Risk: comments and possibly out-of-repo scripts may treat mtime refresh as an undocumented heartbeat. It changes more behavior than issue #1 requires, so audit external consumers or ship separately. Even with this option, calculate the digest once per process.\n3. **Metadata cache: not recommended.** Checking size/mtime/inode each heartbeat retains syscalls and complexity. Metadata is not content identity; same-size/same-mtime replacement can evade it, path/inode semantics differ across Linux/Windows, and an on-disk replacement does not alter the already-running process image. A metadata-invalidated digest can therefore describe disk state rather than resident state. It solves the performance symptom less completely while weakening semantics.\n4. **Update-trigger invalidation: incorrect.** The outgoing process’s resident bytes do not change when apply replaces disk bytes. Invalidating its cache could make that old process publish the incoming file’s hash while still running old code, exactly contrary to the breadcrumb’s purpose. It also adds coordination/race and potentially N-1 IPC surface for no benefit. The update already invalidates correctly by process death and replacement.\n5. **Use staged/release metadata instead of self hashing: incorrect as the primary value.** The v0.4.1 Linux incident proved why expected artifact metadata cannot establish which file the supervisor actually exec’d. The breadcrumb must remain independently derived from the child’s own executable; only the frequency changes.\n\nDETERMINISTIC REPRODUCTION/REGRESSION SEAM\n- Factor immutable capture from publication, for example `ReadyStamp::capture(generation, hash_fn: impl FnOnce() -> Option<String>)` plus `ReadyStamp::publish(path)`. The stamp owns one serialized body containing pid/generation/optional hash. Production passes `current_exe_hash`; tests pass a counting closure. This is deterministic and tests behavior rather than source text or timing.\n- Contract test A: construct one publisher with a hasher that increments an `AtomicU32`; publish initial ready plus N heartbeat refreshes; assert count is exactly 1, every file body parses to the same pid/generation/hash, and no temp sibling remains. This fails on the current plausible bug.\n- Contract test B, replacement semantics: capture A, replace/mutate the backing fixture to B, publish again from the same publisher and assert it remains A; construct a new publisher (modeling the respawned process) and assert it captures B. This defends the core rule that disk replacement does not invalidate a resident-process digest, while a new process does.\n- Contract test C, failure behavior: injected hasher returns `None`; N publications omit `exe_hash` and do not repeatedly invoke the failing reader. `parse_ready_exe_hash` remains `None`, preserving N-1/best-effort behavior.\n- Performance guard: use the invocation counter, not a wall-clock threshold. Optionally use a large fixture to ensure N publications still cause exactly one file-read/hash call, but the counter is the stable acceptance metric. A timing assertion would be flaky and weaker.\n- Keep and run existing update-correctness tests after the fix: `brainproc::bytes_gate_truth_table` (match/mismatch/unverified); `rollback_compat::pre_ready_durable_files_are_additive_and_n1_readable`; `crates/spt/tests/brain_survive.rs::pty_and_quic_survive_brain_process_restart_onto_swapped_binary` (new pid, A→B hash, unchanged PTY child, QUIC/functional continuity); and `crates/spt/tests/brain_respawn_rename.rs::brain_respawns_onto_applied_bytes_after_in_place_rename` (real Linux rename choreography, canonical-path fix). These tests currently defend value correctness across process replacement but none counts hash invocations, which is why D7’s per-start requirement regressed unnoticed.\n\nREAD-ONLY VERIFICATION STATUS\n- No files were edited, no build/test command was run, and no daemon or endpoint lifecycle was touched, per assignment constraints. Findings are source/history/document/issue grounded. The public issue evidence reports 40–55% CPU, 73–76% SHA samples, and the exact executable-open/ready-rename trace; source supplies the matching deterministic call chain and 500 ms cadence.",
  "files": [
    {
      "path": "crates/spt-daemon/src/brainproc.rs",
      "description": "Primary hot path and lifecycle: 500 ms heartbeat, `write_ready`, `current_exe_hash`, readiness/bytes gate, planned restart, canonical executable selection, and existing unit coverage."
    },
    {
      "path": "crates/spt-daemon/src/endpoint.rs",
      "description": "Defines `brain.ready` and documents it as a heartbeat-refreshed best-effort breadcrumb, while daemon liveness remains seed-control IPC."
    },
    {
      "path": "crates/spt-store/src/atomic.rs",
      "description": "Explains the traced `brain.ready.tmp.<pid>-<seq>` write-then-rename sequence and its ongoing per-publication file cost."
    },
    {
      "path": "crates/spt-daemon/src/applyhost.rs",
      "description": "Applies verified bytes, records `AppliedPending`, and requests the broker-supervised brain process restart after swap."
    },
    {
      "path": "crates/spt-daemon/src/relcache.rs",
      "description": "Supplies the staged artifact hash used to compare the candidate’s self-observed `brain.ready.exe_hash` during promotion."
    },
    {
      "path": "crates/spt-daemon/src/rollback_compat.rs",
      "description": "N-1 compatibility tripwire for `{pid,generation,exe_hash}` and missing-hash tolerance."
    },
    {
      "path": "crates/spt/tests/brain_survive.rs",
      "description": "Process-level seamless-update E2E proving A→B executable hash change while PTY and QUIC resources survive."
    },
    {
      "path": "crates/spt/tests/brain_respawn_rename.rs",
      "description": "Cross-platform regression E2E for Linux in-place rename semantics and canonical-path respawn onto applied bytes."
    },
    {
      "path": "crates/spt-daemon/src/broker.rs",
      "description": "Broker image query returns a compile-time version string; establishes that broker-staleness reporting performs no hashing."
    },
    {
      "path": "crates/spt/src/cli.rs",
      "description": "Daemon status computes `broker_stale` by comparing reported and installed compile-time versions, only on status query."
    },
    {
      "path": "RESTORATION-D1-PLAN.md",
      "description": "Historical design for initial `brain.ready` startup signal plus recurring heartbeat."
    },
    {
      "path": "RESTORATION-D6-PLAN.md",
      "description": "Historical rationale for generation-stamped post-resume readiness, stale-file clearing, and file-channel choice."
    },
    {
      "path": "RESTORATION-D7-PLAN.md",
      "description": "Authoritative design intent for the added self hash: always present but only one executable read/hash per brain start."
    },
    {
      "path": "V042-PLAN.md",
      "description": "Documents the Linux current_exe rename failure, readiness-versus-bytes distinction, canonical-path fix, bytes gate, and regression plan."
    },
    {
      "path": "docs/adr/0018-broker-brain-process-isolation-restoration.md",
      "description": "Governing seamless update architecture and v0.4.2 amendment for executable path semantics."
    },
    {
      "path": "docs/KNOWN-HAZARDS.md",
      "description": "Historical incident record and invariants for process isolation, rollback state compatibility, and applied-byte respawn."
    },
    {
      "path": "docs/TWO-HOST-RUNBOOK.md",
      "description": "Field evidence comparing brain.ready self hashes with published v0.4.0 artifacts and explaining the seamless v0.4.1 follow-on proof."
    },
    {
      "path": "CHANGELOG.md",
      "description": "Release evidence: v0.4.0 first shipped process replacement/self-hash era, v0.4.2 fixed Linux respawn correctness, v0.21.0 added broker-staleness reporting, and v0.31.0 inherited the path."
    },
    {
      "path": "traceable-reqs.toml",
      "description": "Exact lifecycle requirements and evidence pointers for no endpoint termination, process isolation, applied-byte respawn, and drained promotion."
    },
    {
      "path": ".git/logs/refs/heads/main",
      "description": "Exact introducing and follow-on commit hashes/messages for D1, D3, D6, D7, and release preparation."
    },
    {
      "path": ".git/packed-refs",
      "description": "Maps v0.4.0 and v0.4.2 tags to commits, proving first-shipped version containment."
    }
  ],
  "architecture": "The broker is the durable per-machine kernel. It owns hosted processes, PTYs, sockets, and the brain supervisor. `spt update apply` verifies and swaps the canonical executable, writes `AppliedPending`, then sends `KIND_BRAIN_RESTART`; the broker kills the old brain and spawns generation N+1 from the canonical path captured at broker startup. The new brain reconnects, resumes cursors, computes/stamps its own executable hash, and publishes `brain.ready`. The broker-side trial gate accepts that generation only after the old control plane drains and, when hashes are available, the self hash matches signed staged metadata. The executable digest therefore belongs to one brain process generation. Heartbeat publication may reuse it, but must never recompute or invalidate it based on mutable on-disk state. Process replacement is the cache invalidation boundary."
}