{
  "summary": "Mapped signed staging, explicit live apply, plain composite update, fixture dependencies, readiness evidence, and isolation hazards. The existing real-daemon entry has no hermetic valid-identity network configuration: it hardcodes mDNS/all-interface binding and invokes firewall verification/possible repair. Same-source candidates can exercise swap lifecycle, not establish version-specific regression reproduction. All findings are static; no commands, builds, tests, fixtures, network probes, or mutations were executed.",
  "files": [
    {
      "path": ".worktrees/hertz-304/docs/DEBUG-ROLLOUT.md",
      "description": "Canonical signed update-set staging runbook; stage-dir/state/channel/product-version options; explicit apply versus distribution."
    },
    {
      "path": ".worktrees/hertz-304/docs/adr/0016-platform-targeted-update-sets.md",
      "description": "Signed per-platform artifact trust, channel-specific monotonicity, brain-only endpoint preservation, debug opt-in."
    },
    {
      "path": ".worktrees/hertz-304/crates/xtask/src/main.rs:2343-2477",
      "description": "debug_rollout parses flags, reads artifacts, signs and verifies metadata/digests, stages through ReleaseCache, writes local sequence; no distribution performed by this function."
    },
    {
      "path": ".worktrees/hertz-304/crates/spt-daemon/src/applyhost.rs:110-430",
      "description": "At-rest verification/classification, already-applied/quarantine guards, live connect-before-swap, binary aside and pending trial, brain restart signal, refresh without swap."
    },
    {
      "path": ".worktrees/hertz-304/crates/spt-daemon/src/relcache.rs:34-120,258-286",
      "description": "Cache filenames, durable AppliedPending/Applied/RolledBack protocol, public stage_update_set caller-verification contract."
    },
    {
      "path": ".worktrees/hertz-304/crates/spt/tests/daemon_refresh_e2e.rs",
      "description": "Real daemon and mock-session refresh fixture; bounded pid/generation and ONLINE checks, but corrupt identity and no persistent frontend continuity proof."
    },
    {
      "path": ".worktrees/hertz-304/crates/spt/tests/brain_survive.rs:1-300",
      "description": "Real supervised brain image/pid/hash checks, hermetic NetConfig and broker PTY; in-process broker and path-selection flip, not signed production apply."
    },
    {
      "path": ".worktrees/hertz-304/crates/spt/tests/composite_e2e.rs:1-245",
      "description": "Private executable seat, fixture signing key, fake-gh release layout, adapter archive, command output/order evidence; daemonless and non-executable candidate bytes."
    },
    {
      "path": ".worktrees/hertz-304/crates/spt/tests/fixtures/gh_fixture.rs:1-87",
      "description": "Reusable offline gh executable contract and invocation log."
    },
    {
      "path": ".worktrees/hertz-304/crates/spt/src/cli.rs:9318-9550",
      "description": "Explicit apply dispatch and outcome reporting; ordinary composite fetch/apply then adapters; --restart is separate lethal-last plan."
    },
    {
      "path": ".worktrees/hertz-304/crates/spt/src/cli.rs:21925-21940,22042-22046",
      "description": "Adapter updated/up-to-date/local-skip/refused/failed summary tokens; local registration is observable no-op, not a core no-op."
    },
    {
      "path": ".worktrees/hertz-304/crates/spt-daemon/src/daemon.rs:529-560",
      "description": "Production network configuration hardcodes mDNS/all interfaces and runs firewall verification; critical isolation gap."
    },
    {
      "path": ".worktrees/hertz-304/crates/spt-daemon/src/firewall.rs:269-274,584-627",
      "description": "Install-only no-firewall gate versus daemon verification path that can repair Windows rules when elevated."
    },
    {
      "path": ".worktrees/hertz-304/crates/spt-daemon/src/brainproc.rs:40-57,222-320",
      "description": "30-second readiness trial, three pre-ready failure budget, bounded backoff and BRAIN_PHASE progress logging."
    },
    {
      "path": ".worktrees/hertz-304/crates/spt-store/src/nodeid.rs:1-109",
      "description": "Valid private identity is a 64-character hex seed; load_or_create_at generates only when absent and refuses corrupt existing files."
    }
  ],
  "architecture": "SignedUpdateSet + fixture trust overlay → verified per-platform cache → explicit apply or composite fetch/apply → classify BrainOnly → connect existing broker → rename private executable aside and land candidate → AppliedPending → broker-supervised new brain → readiness/promotion or rollback. Broker/PTYS remain resident; persistent rc I/O must independently prove continuity. Adapter sweep is a separate composite leg.",
  "report": "## Scope and interpretation\nRead the preserved source map and the named frozen-tree sources. No issue research was repeated. No runtime result is inferred; #302/#49/#267 remain unproven. Paths below are relative to `.worktrees/hertz-304` unless stated otherwise. This is an implementation recipe for Main, not an execution authorization or a claim that existing fixtures already meet W3.\n\n## 1. Reusable staging contract\n\n`crates/spt-daemon/src/relcache.rs:258-286` exposes `ReleaseCache::open(dir).stage_update_set(&SignedUpdateSet, &BTreeMap<String, Vec<u8>>)`. It writes platform artifacts first, signed metadata last; it explicitly requires the caller to have verified the bytes. It is storage, not a signature or rollback gate. Use `verify_update_set_metadata` followed by `verify_update_set_artifact` before direct staging. `xtask::debug_rollout` already follows that sequence (`crates/xtask/src/main.rs:2436-2465`). Its self-check uses current_version=0: repeated fixture staging must still choose a genuinely higher counter relative to the fixture's state rather than assume the tool enforces recipient monotonicity.\n\nCanonical on-disk locations: `$SPT_HOME/identity/release-keys.json`, `$SPT_HOME/releases/release.json`, per-platform files under `releases/artifacts`, and `releases/applied-state.json`. `release.json` is the signed envelope, not the distribution asset named `update-set.json`. `SignedUpdateSet` holds the exact serialized `metadata_json` and Ed25519 `signature_hex`; sign those exact UTF-8 bytes. Metadata must include version u64, channel, unexpired expires_at_ms, fixture key_id, artifact map keyed by current Rust target triple, SHA-256, brain_ipc_version, broker_resource_abi; supply truthful product_version and provenance. Git provenance emitted by xtask describes its checkout, not proof of where arbitrary supplied artifact bytes were built.\n\nFor Windows use `x86_64-pc-windows-msvc`; do not relabel a foreign artifact. Use the candidate's actual compatibility constants and the running broker's compatible ABI. The xtask tool stamps its own compiled constants (`main.rs:2404-2417`), so a stale xtask executable or mismatched candidate provenance is a prerequisite failure. Fetch needs a usable `asset_name`; direct cache apply selects by platform. Omit docs for the minimum continuity case (`docs: None`), rather than introducing a docs archive or incidental docs installation work.\n\nFixture trust: use a newly generated fixture-local Ed25519 signing seed/public key pair, a unique key ID and an overlay such as `{\"keys\":{\"w3-local\":\"<public-key-hex>\"},\"channel\":\"stable\"}`. Stable here names a private fixture policy, not a real stable release. Alternatively debug channel is valid only with a matching debug overlay; do not cross the two cases. Built-in release keys remain overlaid rather than replaced (`release.rs:311-338`), so a private overlay alone does not isolate network trust exposure. Never use the coordinator's long-lived real debug seed or production overlay. The deterministic `[7;32]` test key in `composite_e2e.rs` is an existing code pattern, not appropriate fleet trust material.\n\n### Proposed staging command, not executed\nUsing an already available, matching xtask binary and candidate:\n\n`<private-or-matching-xtask> debug-rollout --key-id w3-local --channel stable --version <N> --product-version <actual-semver> --artifact \"x86_64-pc-windows-msvc=<private-candidate-path>\" --stage-dir \"<private-home>/releases\" --state \"<private-root>/rollout-state.json\"`\n\nSet `SPT_DEBUG_RELEASE_SEED` only in this child environment to the fixture seed. Do NOT use `--build-current`, debug-converge, pump pokes, subnet distribution, fleet keys, or default stage/state paths. The function performs local signing/staging and prints `DEBUG_ROLLOUT_STAGED`; its following peer-propagation sentence is guidance, not evidence propagation occurred. If invoking xtask is considered out of the assigned non-rollout scope, the public verification/cache APIs above are the narrower helper implementation; same signed format and normal apply path, no publishing.\n\n## 2. Independent cases — do not substitute one for another\n\n### A. Refresh, no staged update\nReuse real broker launch/endpoint registration shape from `daemon_refresh_e2e.rs`, but replace its invalid-identity isolation and add a genuinely persistent rc frontend plus numbered input and independent PTY output. Proposed trigger: `<private-seat>/spt daemon refresh`. Before and after, executable digest must remain the same; broker/frontend/hosted-child identities stay constant, new brain pid and strictly advanced generation are expected. Existing fixture only checks harness survival and ONLINE, which cannot rule out a frozen terminal.\n\n### B. Explicit signed live apply\nFresh private home and binary seat; executable candidate already staged and verified. Proposed trigger: `<private-seat>/spt update apply` with no `--finish`. Run daemon and apply CLI from the SAME private executable seat: CLI applies to `std::env::current_exe()` (`cli.rs:9384-9430`), not a separately discovered installation. Invoking a shared target/debug binary would modify the wrong artifact and is prohibited. Ensure daemon-up detection resolves the fixture's seed socket; an accidental daemonless apply returning success does not count.\n\n`applyhost.rs:110-343`: quarantine/already-applied check → reverify at rest (floor 0 because staging owns monotonicity) → verify platform and digest → require BrainOnly → connect broker BEFORE rename → rename current image to sibling `<filename>.old-<N>` → write executable candidate → write AppliedPending including rollback path → request supervisor brain restart. No adapter action is part of this command. The current implementation refuses any non-BrainOnly class outright; do not assume ADR's eventual quiesced broker-touching flow is implemented by this path.\n\nObserve `applied-state.json` phase `applied-pending` if sampling catches it, then `applied` for N; transient pending can be too brief to observe and is not itself a polling requirement. `brain.ready` must identify a new pid/generation and expected candidate exe_hash, and the broker must remain the original process. `RolledBack`, wrong hash, quarantine, unchanged generation, frontend reattach, or PTY stream interruption are distinct outcomes, not success. CLI AppliedPending returns 0 (`cli.rs:9340-9350`), so exit zero/printed update notice alone cannot establish promotion or continuity.\n\n### C. Plain composite update, core apply plus explicitly no-op adapter sweep\nUse a new independent home/state, not B already applied. Proposed trigger: `<private-seat>/spt update`. It plans `FetchApply` then `Adapters` (`cli.rs:9488-9548`); it is NOT equivalent to explicit apply and must exercise fetch through the controlled carrier. A core failure aborts adapter work. With local registration lacking `[update] avenue=\"gh_release\"`, adapter outcomes include `ADAPTER_UPDATE_SKIP:<name>` and `ADAPTER_UPDATE_SUMMARY:<name>: skipped (local registration — no gh_release avenue)` (`cli.rs:21931-21933,22042-22046`). This proves the adapter leg ran and skipped; it says nothing about whether core applied. Capture core cache/image/generation outcomes separately.\n\nA distinct already-current composite control may legitimately perform no core swap yet reach adapter skip/up-to-date. Label it no-op control; never count it as live-swap proof. If adapter actual replacement is needed later, `composite_e2e.rs` supplies a gh_release adapter archive pattern, but that adds translation/install lifecycle concerns outside the minimum no-op-adapter case.\n\nDo NOT use `update --restart` or `update apply --finish` for these continuity cases. They invoke full daemon restart/finish semantics, guarded separately and capable of ending hosted PTYs; ordinary composite specifically avoids that lethal leg.\n\n## 3. Offline composite carrier and minimum dependencies\n\n`tests/fixtures/gh_fixture.rs` is an existing nonshipping binary, copied to private `gh.exe` on a PATH-prepended fixture bin directory. Pass `SPT_FAKE_GH_ROOT` and `SPT_FAKE_GH_LOG` to the invoking CLI; all fake gh children inherit them. Required tree is `tag.txt`, `assets/update-set.json`, and `assets/<signed asset_name>` containing the REAL executable candidate. `auth` returns success; `api` prints tag; `release download` copies named assets locally and exits 1 for missing assets. Log lines capture subprocess argv; verify the expected metadata/artifact downloads and no unexpected invocation instead of assuming PATH interception.\n\nThe fixture implementation is looser than its comment: all `auth` and all `api` invocations are accepted, rather than exact argv validation. It also does not check repo identity. Therefore retain its log and assert the consumed asset/digest, not just subprocess success. Minimum no-op adapter case needs no `.spt` tarball or system tar. Actual-adapter case adds `adapter.spt`, a valid manifest preserving its gh_release avenue, and system tar if copying `composite_e2e.rs` construction verbatim.\n\nNever reuse `NEW_CORE_BYTES` from `composite_e2e.rs`: those bytes are intentionally non-executable and only work because that test is daemonless. Its output assertion is not evidence of live ready promotion. Additional reusable binaries for the existing hosted fixture are `mock-session` and a private `spt` copy used as `psychebin`; existing manifest uses dummy session and heartbeat 200ms. The new numbered-output harness/frontend-driver requirements belong to the apparatus rig; existing dummy-heartbeat survival is insufficient.\n\n## 4. Valid private identity and isolation gap\n\nUse `spt_store::nodeid::load_or_create_at(<home>/identity/node.key)` to mint a fresh fixture identity, or explicitly persist an `Identity` seed as 64 hex characters. No production identity/subnet/trust/registry copy. All subprocesses need explicit SPT_HOME; do not mutate global environment in a multi-case concurrent runner. Existing `daemon_refresh_e2e.rs` intentionally writes `not-a-valid-seed`, which causes `try_start_net` to return None. That workaround violates the valid-identity requirement and changes real-rc preconditions.\n\nUseful but insufficient private `daemon.json` values are relay=disabled, peer_pump=false and full_auto_update=false (`config.rs:52-115`). They prevent relays/pump/autonomous application but do not prevent network bootstrap. `daemon.rs:529-560` always builds `LocalDiscovery::Mdns`, `BindScope::All`, production membership, and on successful bind calls `firewall::verify_and_record_self`. Thus a valid seed + otherwise empty private home is NOT a no-network recipe. `SPT_TEST_EPHEMERAL_ADVISORY_PORTS=1` only isolates advisory listener ports, not QUIC/mDNS.\n\nWorse, the daemon verification path can repair Windows product firewall rules when elevated (`firewall.rs:614-627`). `SPT_INSTALL_NO_FIREWALL` gates the separate installer reconciliation (`269-274`), NOT this daemon path. Never advertise that variable as complete protection here. Non-elevated execution avoids that repair branch but still exposes real interface binding/discovery and probes; it does not satisfy the requirement.\n\nExisting fully hermetic construction is `NetConfig { identity, relay: RelayPolicy::Disabled, discovery: LocalDiscovery::Off, scope: BindScope::Loopback, membership: None }` → `NetHost::start` → `Broker::bind_in_with_net`, shown at `brain_survive.rs:106-130`. Loopback scope also disables pairing NTP (`nethost.rs:1285`). This can be hosted by a separately allocated fixture broker process, but must retain actual seed-control/supervisor/ProductionTrialEnv semantics if testing CLI live apply; simply borrowing an in-process broker or NoTrialEnv does not test the real daemon entry. A separately allocated rig-only isolation seam or an already-isolated no-external-network execution environment is a missing prerequisite for the full real-daemon case. No such change belongs on frozen W1. Main must choose integration; source does not supply an environment-only real-daemon solution.\n\n## 5. Same-source candidate honesty\n\nYes: a genuine same-source rebuilt candidate with matching ABI can test signed cache verification, executable replacement, old/new brain process transition, readiness promotion, and persistent frontend/PTY continuity. Numeric release counter is independent of product semver, so choose N above the fixture's staged/applied state and keep actual product_version rather than fabricating a new release identity. Anti-rollback permits later counters even for prior known-good bytes (ADR-0016).\n\nRecord both SHA-256s, source boundary/provenance, build profile and target. If A and B differ in digest, expected post-apply exe_hash=B gives an independent byte-selection check. If reproducibility produces identical bytes, do not claim a hash flip: generation/pid plus staged/pending/applied/aside evidence can demonstrate same-image swap lifecycle, but cannot distinguish code versions. `brain_survive.rs:204-223` creates B=A+4096 padding to force a hash distinction and uses a supervisor path flip; that is a precedent for lifecycle-only testing, not an actual rebuild or normal signed apply. None of these same-source patterns reproduces a version-specific #302/#49/#267 regression by itself.\n\n## 6. Bounded observation and resource plan inputs\n\nProposed per-case wall budgets, not measured durations: broker ready 45s; endpoint start 45s; endpoint ONLINE 25s; rc/first numbered roundtrip 15s; refresh CLI 30s; explicit apply CLI 45s; composite CLI 120s; new brain/promotion 45s from trigger; post-trigger stream 15s; cleanup 20s graceful plus 10s scoped hard reap. Use one overall case deadline so sequential sub-waits cannot expand indefinitely; a proposed 240s active-case ceiling plus 30s cleanup is explicit, adjustable before running from candidate sizes/readiness costs. Do not extend silently on stalls.\n\nEvery wait must log elapsed wall time, last received input acknowledgment sequence, independent output sequence and counts/gaps/duplicates, frontend pid/exit state, current brain pid/generation/hash, applied phase and last relevant daemon log offset. Retain `BRAIN_PHASE:announce/resume/ready` progression including hash cost (brainproc.rs:222-320), rather than treating unchanged process liveness as progress. Supervisor has 30s healthy/readiness window, three pre-ready failures, 2s base/60s cap backoff; an outer W3 success budget should stop on rollback/quarantine instead of waiting through indefinite supervisor retries.\n\n[INFERENCE/design cap] A one-endpoint sequential rig normally needs driver, broker, brain, numbered harness, persistent rc, and optionally psyche = 5–6 steady processes, plus one trigger CLI and one gh child = 7–8; reserve old/new brain overlap, OS terminal host and helper/launcher overhead explicitly. Proposed hard fixture-owned cap 12 processes for the minimal design, raised only after apparatus enumerates actual OS/helper roles. This is NOT a source-proven process count: Windows conhost/wrappers and rc apparatus choice determine the final cap. No extra network peer process is needed for local persistent rc unless the independent remote-path case demands it. Cases should run sequentially to bound simultaneous residency.\n\nArtifact sizing inputs to inventory before execution: size A of original executable, size B of candidate, size G of gh fixture, size M of session/numbered harness, optional psyche copy P (existing fixture P=A), log rate and duration T. Explicit staging requires roughly original private seat/aside A + candidate input B + cached B + landed B = A+3B, plus P+M and metadata/logs; composite additionally needs fixture-served asset and transient fetch copies, so reserve another 1–2B unless candidate source and fixture asset intentionally share an owned path. Atomic writes and filesystem copy semantics can add transient copies. In-memory apply reads the whole artifact into a Vec; xtask holds all supplied artifacts at once, hence include only one target. Do not report guessed MB, RSS or free-space sufficiency; collect real file sizes and free disk before Main allocates. Bound numbered-output rate and capture files so continuity instrumentation itself does not create disk/memory pressure.\n\n## 7. Cleanup hazards and evidence boundaries\n\nDo not delete `.old-N` while the original broker/image is still live; Windows image locks can persist and the aside is rollback material. `swap_and_record` removes a stale same-version aside before renaming: reusing N risks collisions; fresh home per case is cleaner. Do not clear release state mid-trial. Stop the supervisor before force-killing brain descendants to prevent respawn; prefer private daemon stop, then fixture-owned pid-tree reap, always bounded and revalidated for identity/PID reuse. Existing refresh fixture reaps harness/psyche and broker, but manual cleanup after assertions is not robust against early panic; Main's driver should retain child handles/ownership in unconditional teardown.\n\nArchive logs, numbered traces, initial/final digests, all observed ready/applied phases and the fake gh log before removing private roots. Remove fixture key material and child-only env afterwards; never delete production state or shared Cargo artifacts. No install/uninstall/autostart, global process-name kill, firewall repair, network distribution, or borrowed production daemon is required or acceptable. Existing tests' source comments describe their intended proofs; none was executed here and none establishes W3 frontend continuity without the added persistent rc measurements."
}