{
  "summary": "The CLI reads registry JSON afresh, but the running brain hydrates it only once and subsequently overwrites it from its in-memory map. Late direct solo.json injection therefore races an authoritative writer. Earlier injection alone is also insufficient: hydrated foreign rows have no in-memory heard stamp and the first pump round evicts them immediately. These are ranked causal predictions, not a classification of the unobserved failure sequence.",
  "files": [
    {
      "path": ".worktrees/asm-304-v3/crates/spt-daemon/src/registryhost.rs",
      "description": "new_at:183-224 seats snapshots once; apply_feed_flips:322-341 admits/stamps/persists live updates; advertise_local:530-824 rewrites snapshots; evict_silent_peers_at:847-869 immediately evicts unstamped foreign nodes; write_snapshots:951-977 replaces disk from memory; atomic_write:1322-1336 implements Windows-retried rename."
    },
    {
      "path": ".worktrees/asm-304-v3/crates/spt-daemon/src/brainproc.rs",
      "description": "run_brain writes brain.ready at 309 before starting net consumers; heartbeat:369-389 waits 500ms then gates startup on net enabled; spawn_net_consumers:433-445 constructs shared registry and optionally starts pump."
    },
    {
      "path": ".worktrees/asm-304-v3/crates/spt-daemon/src/pump/registry.rs",
      "description": "Registry worker poll_wake:225 and pre_round:231-270: repair eviction, silent-peer eviction, aged-offline eviction, rotations, local advertisement."
    },
    {
      "path": ".worktrees/asm-304-v3/crates/spt-daemon/src/pump/mod.rs",
      "description": "Initial worker deadlines are None at 661; node-local pre_rounds runs at 705 before peer fan-out; due:1628 treats first tick or wake as immediately due."
    },
    {
      "path": ".worktrees/asm-304-v3/crates/spt/src/wansend.rs",
      "description": "resolve_visible_owner_instance:340-350 loads identity/snapshots/exclusions directly; resolve_and_dial_owner:281-328 independently reloads snapshots; load_snapshots:1482-1524 degrades read/parse errors to absent rows."
    },
    {
      "path": ".worktrees/asm-304-v3/crates/spt/src/rc.rs",
      "description": "Preflight:2240-2270 refuses harness-only remote Active/Dormant rows before attach; no session path:2612-2623 emits reported No live session wording."
    },
    {
      "path": ".worktrees/asm-304-v3/crates/spt-net/src/net/registry.rs",
      "description": "resolve_across_visible:1020-1080 unions routable, nonexcluded rows across JSON-derived subnets, deduplicates by node and resolves."
    },
    {
      "path": ".worktrees/asm-304-v3/crates/spt/tests/knock_mutual_cross_node_e2e.rs",
      "description": "seed_home:202-228 explicitly disables peer_pump before daemon startup to protect seeded registry snapshots; existing supported hermetic-fixture pattern."
    },
    {
      "path": ".worktrees/asm-304-v3/crates/spt/tests/hide_new_remote_rows_e2e.rs",
      "description": "No-daemon snapshot fixture:35,80-85,125-151 writes rows and launches reader with explicit SPT_HOME; not evidence that injection is safe with an active writer."
    },
    {
      "path": ".worktrees/asm-304-v3/crates/spt-store/src/perch.rs",
      "description": "spt_home:34-40 reads current process environment on each call; identity_dir:245-247 derives from it, without a cached global home."
    }
  ],
  "architecture": "The daemon brain owns one shared RegistryHost for dispatcher and peer pump. RegistryHost hydrates disk once; admitted feeds and local scans mutate its map and publish atomic snapshots. rc is an independent snapshot consumer, not a RegistryHost RPC consumer. brain.ready certifies earlier startup work, not registry hydration or pump quiescence.",
  "report": "## Scope and evidence\nRead-only source trace; no tests, builds, formatters, lifecycle actions, or edits. Target test was searched for lock/home/helper references and its broker helper was read; its full body was not reread. Rust LSP was not available among exposed tools: xd:// lists only spt_checkpoint. Definitions/references were traced with targeted code search instead. Domain references: CONTEXT.md:29-39 (brain owns registry logic), :591-593 (pump and pre-round ownership), :827-836 (remote ownership/registry/visibility); ADR-0042 decision 3, docs/adr/0042-rc-attach-truth.md:62 onward requires truthful pre-stream refusal for known harness-hosted rows.\n\n## Authoritative lifetime and read/write seam\n1. RegistryHost::new() resolves perch::identity_dir()/registry, then new_at() calls presence::load_registry_snapshots exactly at construction (registryhost.rs:170-224; presence.rs:58-79). That parser reads *.json, ignores absent/unreadable/malformed snapshots, and seats the resulting map in Mutex<BTreeMap<...>>. The in-memory heard map starts EMPTY regardless of heard.meta.\n2. The running host does not refresh that map from later JSON edits. Its normal live input is admitted inbound feed, or advertise_local's owlery scan. apply_feed_flips:322-341 stamps heard for any admitted feed, even an admitted stale lease, then publishes snapshots. Other feed/apply paths also call write_snapshots at 440 and 466.\n3. write_snapshots:951-977 serializes each existing in-memory subnet and replaces <subnet>.json. It does not merge disk first. A disk-only hh2 row in a subnet already held in memory can disappear on the next advertisement or admitted feed. Caveat: it writes subnets that are in regs; it does not delete arbitrary extra JSON files when regs lacks their subnet. In this fixture solo must be represented in memory for solo.json to be overwritten; advertise_local creates member-subnet entries even in the ghost scan (750-754) and normally through local advertisement.\n4. advertise_local finishes with an unconditional write_snapshots (822-824). Registry pre_round runs before any peer steps (pump/mod.rs:705,875-879), so zero remote peers does NOT prevent node-local persistence. Default peer_pump=true, cadence=30s (config.rs:39,219-221), but first tick is immediately due (pump/mod.rs:661,1628-1629); advertise-now wakes can force an additional round (pump/registry.rs:225-227).\n5. Snapshot publication uses JSON temp-write plus rename (registryhost.rs:1322-1336), with five attempts and exponential 20ms delays on Windows. Errors are ignored by write_snapshots; an attempted rewrite is not proof a replacement occurred.\n\n## Readiness does not order hydration\nspawn_broker waits only for home/brain.ready (target helper:125-150). run_brain writes ready at brainproc.rs:309, then starts non-net hosts, then enters the heartbeat loop. The heartbeat sleeps 500ms (98,372), probes net_status, and only when net is enabled constructs RegistryHost and starts consumers (380-389,433-445). Therefore brain.ready and a passing local harness-only arm do not establish whether registry hydration happened before or after direct injection. Local refusal uses info.json and short-circuits the remote registry lookup (rc.rs:2249-2254), so that local pass is not a registry-readiness witness.\n\nThere are two different ways the synthetic row can vanish:\n- Injection AFTER hydration: hh2 never enters the live map; the next solo snapshot publication omits it.\n- Injection BEFORE hydration: hh2 enters the map but its author has no heard stamp. The first registry pre_round calls evict_silent_peers before advertise_local. The predicate at registryhost.rs:850-854 treats no heard stamp as silent immediately, not after the nominal five-minute TTL. It emits REGISTRY_EVICT:solo and rewrites. Hydration intentionally does not restore heard.meta. Merely moving the write earlier, waiting for brain.ready, or increasing sleeps is not a stable seeding strategy.\n\n## rc remote resolution\nrc.rs:2240 gates this preflight on !session_confirmed. The remote path calls resolve_visible_owner_instance, which loads the current node key, parses hh2, loads JSON snapshots afresh, loads Exclusions, and resolves across visible rows (wansend.rs:340-350). It returns None for parse/identity failure, missing rows, ambiguity, exclusion, or own-node winner. A foreign Active harness_only=true row qualifies directly (rc.rs:2253-2263); bound is NOT a condition at this gate, nor is a live remote network route required. If it qualifies, the function prints the truthful refusal and returns before establish_attach.\n\nIf it does not qualify, establish_attach can later reach resolve_and_dial_owner, which loads the snapshots again (wansend.rs:281-311). Therefore preflight and dial resolution are two distinct observations, not one shared snapshot. No-live-session wording alone does not identify which file contents either observed. load_snapshots logs read/parse outcome spans and treats errors as missing (1482-1524); a malformed/temporarily unreadable snapshot can also produce a miss. A readable valid single foreign Active row with no exclusion should resolve; the resolver has no requirement that the peer be in a trust roster merely to perform this preflight lookup.\n\n## Ranked causal predictions\n1. Highest: post-hydration direct-write race. Prediction: hydration precedes injection; hh2 appears in written bytes but disappears in a later solo snapshot before rc's relevant read. The replacement typically contains the brain's own rows/node labels and not hh2. No REGISTRY_EVICT for hh2 is required because it never entered memory.\n2. Next: pre-hydration injection followed by immediate unstamped-peer eviction. Prediction: hydration includes the injected row and REGISTRY_EVICT:solo occurs before rc reads it. Earlier seeding can still fail, and heard.meta pre-seeding does not fix this because constructor ignores that map.\n3. Lower: snapshot was present but reader observed read/parse failure, visibility exclusion, own-node selection, or another winning row. Prediction: actual CLI read and resolution evidence identifies one of these; preserving bytes alone will not establish which resolver inputs were consumed.\n4. Low based on inspected code: process-global home contamination. spt_home reads env on every call; there is no cached global home. The target binary has a file-local E2E_LOCK, and searches show every one of its six tests locks before setting SPT_HOME (299/301,352/354,389/391,452/454,523/525,623/625). spawn_broker explicitly passes home; brainproc's managed child spawning preserves broker environment on Windows (1225-1227,1272-1273). Different test executables cannot mutate one another's process environment. This does not rule out a concrete mispassed child environment/path, but the generic parallel-tests/global-home explanation lacks a matching seam. Main can compare fixture path with REGISTRY_SNAPSHOT_HYDRATE dir and ready path rather than infer contamination.\n\n## Valid existing fixture patterns\n- For snapshot-reader/RC preflight behavior, seed_home in knock_mutual_cross_node_e2e.rs:202-228 writes daemon.json with relay disabled and peer_pump=false BEFORE startup. Its comment explicitly says this prevents gossip and seeded snapshot rewriting. webserve_cross_node_e2e.rs:243 uses the same setting. This is the existing supported hermetic switch, not a new product knob. Inbound registry dispatch remains possible; keep the isolated no-feed premise explicit.\n- No-daemon reader fixtures freely write snapshots (hide_new_remote_rows_e2e.rs:35,125-151), but their safety does not transfer to a running pump.\n- For testing the actual live registry, use its admitted-feed seam: apply_feed_flips owns both row merge and heard stamping. registryhost.rs:1582-1634 demonstrates seeding via apply_feed and a new RegistryHost over the same directory for hydration; the test observes hydration before any pump eviction. For a process E2E, a real admitted feed rather than a disk-only edit is the analogous producer.\n\n## One discriminating measurement for Main\nRun a test-only controlled A/B using identical binaries and synthetic hh2 row, changing only pre-start daemon.json peer_pump=false in the hermetic arm. In both arms retain wall-stamped exact solo.json bytes immediately after injection and at rc launch/exit, plus brain logs containing REGISTRY_SNAPSHOT_HYDRATE, BRAIN_NET_CONSUMERS_UP, and REGISTRY_EVICT. Observe the actual snapshot consumed by the CLI if possible (debugger at load_snapshots/resolution boundary; before/after files alone cannot close the read-time race). Verify the disabled-pump arm's snapshot survives and its actual CLI input is the intended foreign Active harness_only row. Do not add arbitrary waits as a substitute for this boundary.\n\nTest/rig defect evidence: the failing arm demonstrably loses/never admits the synthetic row before the CLI's read, and the isolated stable-input arm returns the required pre-stream refusal. This establishes that the original fixture failed to provide its intended precondition, not that the product promised to ingest out-of-band late edits.\nProduct defect evidence: the CLI demonstrably consumes the correct home's parseable, unexcluded, single foreign Active harness_only=true winner with no honest local session, yet misses the refusal or starts attach. Alternatively, a legitimately admitted live feed is wrongly lost despite an unexpired heard stamp: that is a separate product-path claim requiring its own admission/expiry evidence.\n\nNo final classification: no runtime ordering, snapshot-consumption, successful rewrite, or eviction event was observed by this scout. The source establishes competing mechanisms and an existing stable seeding convention, not which event happened in the supplied red."
}