---
name: counter39-field-bugs-1-3
description: "counter-39 scope: 3 operator field bugs root-caused (AcrossSubnets false refusal / NUL-corrupt info.json reads ONLINE / corrupt perch invisible locally); todlando builds, I gate, deployah publishes v0.19.1"
metadata: 
  node_type: memory
  type: project
  originSessionId: 5a63f400-f291-4ea5-ba2e-6b0f1cb46429
---

Operator reported 3 post-v0.19.0 field bugs (2026-07-01); all root-caused on HFENDULEAM. **Orchestration: todlando executes (build order sent), doyle (this session — `$OWL whoami` = doyle live=true) GATES per operator ruling, deployah publishes v0.19.1 (counter 39, verify from published metadata).** IDENTITY GOTCHA: session-start sptc hook injected perch id "SUBNET SPT_DEV" — that's the `spt endpoint list` HEADER line, a hook parse artifact, NOT an endpoint id; trust `$OWL whoami` over the hook banner.

**#1** `spt rc hall-a` → false "'hall-a' exists in several subnets" refusal. Root: `spt-net registry.rs resolve_across_visible` (~:908) counts SUBNET hits — no dedupe-by-node, no cross-subnet local-wins. ADR-0006 §1: identity node-global, same (id,node) in N subnets = ONE endpoint. Operator RULING: subnet NEVER a disambiguation axis; only NODES, and only when no local instance. Fix: union rows deduped by node (same-node twins → freshest epoch), single-subnet policy on union, REMOVE `Ambiguity::AcrossSubnets` variant + wansend render arm + 4 old-behavior tests + REQ-INST-10 toml text.

**#2** hall-a dead since machine restart but ONLINE everywhere. Root chain: hard-reset zero-filled `owlery/hall-a/info.json` (360 bytes ALL NUL; nested psyche 125 NUL) because `atomic_write_bytes` (spt-store atomic.rs) = fs::write(tmp)+rename **without sync_all** → NTFS keeps rename metadata, loses data blocks. Then 3 readers disagree on corrupt file: `list_self_perch_ids` exists⇒perch / `is_perch_alive`:106 unreadable⇒ALIVE / `advertised_status`:797 alive+recordless⇒ACTIVE → daemon self-gossips Active every round (epoch 173k+). Fix: fsync-before-rename + CORRUPT≠ABSENT distinction (corrupt⇒not alive⇒Suspended). New REQ-HAZARD-PERCH-RECORD-POWER-LOSS + REQ-HAZARD-CORRUPT-PERCH-COHERENCE.

**#3** hall-a only under picker Subnet tab, not Project/Local, `endpoint list` "(no local perches)". Root: `roster.rs:41` enumerate skips unparseable info.json ("not a perch") — third contradictory semantic; no local row ⇒ `reconcile_self_owned` can't override stale ONLINE subnet row. Fix: corrupt perch → offline "corrupt" roster row; project placement via project_history_for (context branch survives; cwd unrecoverable).

GOTCHA (diagnostic pattern): a file of correct length but ALL NUL bytes = classic NTFS hard-reset zero-fill of a non-fsynced write — check `Format-Hex`, don't trust Get-Content (renders blank). Fail-open defaults compound: "unreadable⇒alive" + "recordless⇒Active" turned destroyed state into permanently-ONLINE ghost.

**#4 (adapter-side, perri owns, IN PROGRESS 2026-07-01):** SessionStart hook banner id wrong ("SUBNET SPT_DEV"). Perri-confirmed root: `claude-spt hook` self_id() takes FIRST LINE of `spt whoami` HUMAN output (grouped roster — subnet header first when unperched, `SELF: <id>` when perched; first-line parse wrong in both shapes). My initial endpoint-list-parse guess was the right class, wrong command. Fix: `spt whoami --json` → `.self.id`, null⇒no-perch, parse-failure⇒EMPTY never a roster line; load-bearing regression = human-roster input ⇒ EMPTY. Perri publishes adapter version, pings doyle, doyle relays to operator.

**GATE 1 @77beeac RED (2026-07-01, doyle):** clippy 0 / traceable 0 / diff conformant, but full-workspace nextest caught 2: (a) STALE TEST `wan_send_ships_to_the_resolved_node` — ambiguity precondition = same-node-two-subnets (the field repro!) asserting OLD behavior; no AcrossSubnets token so greps missed it; fixed w/ distinct nodes @cba87bc. (b) REAL REGRESSION pinned by differential (base PASS 10.4s / 77beeac FAIL 42s) + intra-diff bisect (minus atomic.rs hunk → PASS 7.9s): **blanket sync_all in atomic_write_bytes fsync'd all ~26 callers** → daemon bringup missed 20s ONLINE budget; mutate_info held per-perch lock ACROSS fsync (W1b shape). todlando's box made the budget (his full run green) — gate rig is authority. CORRECTION (todlando executing): durable OPT-IN siblings (`atomic_write_*_durable`) for write_info + nodeid/machineid ONLY; default reverts to no-fsync (reconstructible state + 5.14 read-side makes post-crash NUL harmless); KH 5.13 + toml rescoped; recency-stamp exemption held as fallback if wedge e2e still slow. cba87bc = wansend fix only, SUPERSEDED, not gated. LESSON: blanket fsync on a SHARED atomic writer = wedge-class perf hazard — scope durability to records whose loss IS the incident; attach_wedge_e2e = the canary. AWAITING GATE2 sha (re-gate = full workspace + wedge e2e ×2, fresh target).

**GATE 2 BLOCKED (2026-07-01) — awaiting doyle RCA.** cba87bc (wansend distinct-node fix) landed FAIL 1. doyle ADDENDUM: FAIL 2 (attach_wedge) = REAL REGRESSION — my BLANKET sync_all in atomic_write_bytes fsync'd all ~26 callers → bringup missed 20s ONLINE budget (W1b lock-across-fsync via info.json under .info.lock). CORRECTION built (UNCOMMITTED working tree): atomic_write_bytes/_string reverted to non-durable + new atomic_write_bytes_durable/_string_durable; ONLY info::write_info + nodeid + machineid opt-in durable; KH 5.13 + REQ-HAZARD-PERCH-RECORD-POWER-LOSS toml rescoped. Builds/clippy/traceable/durable-units all green. BUT 3 bringup-ONLINE e2e now FAIL even rested+single-threaded+daemons-cleared: attach_wedge (victim_online=false), dummy_harness endpoint_run_attach_awaits_online + brings_up_long_lived (online=false). DIFFERENTIAL: 77beeac/blanket passed all EARLY session; NOW cba87bc/blanket ALSO fails (stash test) → identical committed code flips pass→fail across session = BOX STATE changed, NOT my scoped diff (scoped has FEWER fsyncs than blanket). Brain stderr: "NET_FAMILY_GATE binding IPv4-only (IPv6 unreachable/disabled)" — suspect [[broken-ipv6-poisons-iroh-discovery]] resurfacing. INFRA: e2e leak detached target\debug\spt.exe daemons (unreaped) → lock spt.exe (os err 5) + contend; killed workspace-target-only 3× (spared owl.exe / installed / doyle's c39-gate-target). OPERATOR DIRECTIVE: on test fail, report all to doyle + WAIT for his RCA+fix direction (sent full report, holding). NO GATE2 sha committed yet (won't commit until greenable).

**GATE 2 RCA FINAL (2026-07-01, doyle):** the post-cleanup e2e reds = **fixed-tmp-name collision in atomic_write** (`{name}.tmp` shared by ALL writers). At bind, daemon RMW + `spt api bind` establish write the same info.json concurrently (traced 700μs apart); first rename CONSUMES the shared tmp, loser hits os-error-2 → `BindError::Io` → mock-session bind-fatal exit → harness dead → never online. Latent forever (μs window); `sync_all` widened create→rename ~50× → near-certain at bind. Diagnosis chain: write-trace side-channel (every write_info: pid/status/sid) + step probes in cmd_bind → `PROBE cmd_bind establish ERR: Io(os error 2)`. Fix spec to todlando: unique tmp per write (`{name}.tmp.{pid}-{seq}`, AtomicU64) BOTH paths + REQ-HAZARD-ATOMIC-TMP-COLLISION (toml+KH 5.15) + multi-thread hammer unit (fails today) + fix old-tmp-name test asserts; scoped durability UNCHANGED; recency-exemption dropped. LESSONS: (a) trace-to-side-channel-file beats stderr when children scatter output across PTYs; (b) probe placement discipline — "write_info traced" ≠ "write succeeded"; (c) a widened timing window doesn't create races, it reveals them.

**GATE 2 FIX SHIPPED @fe385f5 (2026-07-01) — resubmitted, awaiting re-gate.** Applied doyle's RCA-final fix: (1) atomic.rs `write_then_rename` UNIQUE tmp per write `{name}.tmp.{pid}-{seq}` (process-local static AtomicU64, no clock/rand), BOTH paths — kills the fixed-tmp collision. (2) scoped durability UNCHANGED (write_info+nodeid+machineid durable; recency-exemption dropped). (3) REQ-HAZARD-ATOMIC-TMP-COLLISION (toml+KH 5.15); KH 5.13 rescoped. (4) RED-FIRST hammer unit `concurrent_writers_never_collide_on_tmp` (6 threads×100, std::sync::Barrier) — REDS on fixed-tmp @0.014s (os-error-2 panic), GREENS on unique @1.5s. (5) loader-check verified (new tmp lacks .json ext; load_snapshots/adapters/list_self_perch_ids/roster all exclude it). (6) old tmp-name asserts → glob "no *.tmp* sibling". GATES: clippy 0, traceable 0, FULL nextest 1438 passed/0 failed/1 skip, 3 bringup canaries explicit green (6.9/7.4/3.1s vs 40-62s wedge pre-fix). Box contamination RCA: doyle's OWN leaked c39-gate-target daemons (spawned 4:44PM) + disk 97% caused my post-cleanup reds — my sweeps were workspace-scoped, blind to his Temp targets; PROTOCOL (binding, shared box): daemon-kill sweeps scope to ALL build dirs (workspace\target AND Temp\*-target), never machine-wide, never owl.exe/spt-core\bin; sweep+delete throwaway targets after failed/cancelled e2e. Sent GATE2-C39-fe385f5. On GATE2-OK → deployah v0.19.1.

**GATE 2 RED @fe385f5 (2026-07-01, doyle) — SILENT LOST UPDATE, awaiting full fix spec (seq 8-17 truncated).** doyle re-gate (fresh worktree, clippy 0, traceable 331/331, diff PASS): full nextest 1436/1438, 2 FAIL deterministic (attach_wedge + dummy_harness attach_awaits) — tmp fix WORKS (bringup+serve fine) but a DEEPER latent race surfaced. ROOT: `establish_perch` (api/startup.rs:195→284) does read→conflict-check→`write_info` with NO `.info.lock`; daemon `mutate_info` (info.rs:268) serializes its RMW under `<perch>/.info.lock`. The 2 bind-time writers (~700µs apart) = this pair. Old fixed-tmp: interleave collided LOUD (os-error-2). My unique-tmp: BOTH writes land → RMW reads PRE-BIND, bind's full record (state=live_agent/controllable=true/session_id) renames in, RMW writes STALE pre-bind back + status=online → final = pre-bind shape + ONLINE → bringup PASSES, rc serves, child killed → `reconcile_hosted_liveness` sees state!=live_agent OR controllable!=Some(true) → SILENT `continue` (no log) → dead endpoint latched ONLINE forever. Why doyle-not-me: durable fsync sits INSIDE RMW read→write window; his disk (~ms fsync) makes interleave near-certain, mine too fast. DIFFERENTIAL: aebabaa 3/3 PASS; 77beeac 0/3 (blanket stall); 77beeac+baseline-atomic.rs 3/3 PASS (bugs #1-3 logic INNOCENT); fe385f5 2/3 (atomic rework = delta). Fix SHAPE (unconfirmed): establish_perch write must take the same .info.lock as mutate_info (serialize bind vs RMW) OR RMW re-reads-under-lock. Token GATE2RED-LOSTUPDATE-fe385f5. Race predates me (v0.19.0-era, was loud+µs) — unique-tmp converted loud collision→silent clobber. HOLDING for doyle seq 8-17 fix direction; NOT acting until confirmed.

**GATE 2 RED — FIX SPEC RESENT (2026-07-01, doyle, token GATE2RED-SPEC-RESEND).** Full spec re-delivered after seq 8-17 truncation (todlando's shape read confirmed CORRECT — serialize bind write under same sentinel): (1) public `write_info` acquires `.info.lock` then private `write_info_unlocked` (mutate_info keeps lock, calls unlocked — fs2 second-handle double-lock would deadlock); readers stay lock-free. (2) establish_perch = true CAS: one lock hold across prior-read + conflict-check + write (or `info::establish_locked` helper, lock module-private). (3) lock-across-fsync = ms-scale infrequent, NOT W1b per-keystroke shape — document. (4) REQ-HAZARD-INFO-RMW-LOST-UPDATE + KH 5.16 (cross-ref 5.15: unique tmp ⇒ last-writer-wins, only safe if ALL writers serialize). (5) RED-FIRST barrier hammer: mutate_info(stamp) vs write_info(bind-shaped record) pairs; stamp-present-but-bind-fields-missing = lost update, assert never; must red on fe385f5. (6) audit unlocked prod write_info callers (api/reporting:54, api/worker:45, msg/{ring:129,ready:95,listener:184}, live/psyche:176, store/{home:192,rename:140}, cli:{1882,2104}, linkhost:823) — fix #1 auto-serializes; confirm none needs #2-style multi-op CAS. (7) rest of fe385f5 KEEP (gated good). LESSONS: fixing a race's loud failure mode can convert it silent — whole-record last-writer-wins requires ALL writers locked; slow-disk gate box reds what fast build box can't (rig asset); long owl sends truncate receiver-side — content receipt tokens + resend tail. AWAITING GATE2-C39-<newsha> → full re-gate protocol.

**GATE 2 lost-update FIX built (2026-07-01, UNCOMMITTED) — awaiting doyle fold/park ruling.** doyle full spec (7 items): (1) info.rs `lock_perch_sentinel` helper + private `write_info_unlocked` (=durable body) + public `write_info` LOCKS then calls unlocked (sig unchanged→all callers serialize free); `mutate_info` keeps lock+calls unlocked (no double-lock). (2) `establish_locked(perch, build_fn)→io::Result<Result<InfoJson,E>>` runs read→check→build→write as ONE lock hold (true CAS); `establish_perch` (startup.rs) refactored to use it (create_dir_all moved above lock; build closure returns rec or BindError; returns written rec for the ledger cwd). (3) lock-across-fsync doc note (ms-scale, not W1b). (4) REQ-HAZARD-INFO-RMW-LOST-UPDATE toml + KH 5.16 (cross-ref 5.15). (5) RED-FIRST unit `write_info_racing_mutate_info_never_lost_update` (barrier A=set_status/B=write_info full bind record; assert status=online⇒state=live_agent) — REDS on unlocked write_info @0.025s (my fast box!), GREENS locked @5.2s. (6) AUDIT of 11 write_info callers: NONE needs establish/#2-shape. 6 lone-creates safe under #1 (worker/ring/psyche/cli:1882-skeleton/cli:2104-fork/linkhost=test). ready.rs:95 start_homed = establish-shape but harness-hosted (not daemon-raced) → leave. 4 read-modify-write-via-raw-write_info flagged (lock gap, want mutate_info): reporting.rs:54 rebind(session_id) + home.rs:192 adopt(home_subnet) = IMPORTANT fields → recommend fold; listener.rs:184 write_busy + rename.rs:140 rewrite_id = self-heal/rare → park. Sent doyle A(fold reporting+home)/B(park all)/C(all); my lean A. (7) keep all fe385f5 (scoped durability, unique tmp, 5.15) — gated GOOD. Local: 11/11 spt-store units green (lock restored). NOT committed — apply doyle's ruling then full nextest→commit→GATE2-C39-<newsha> (same token family, his rig final authority).

**GATE 2 ITEM-6 AUDIT RULED (2026-07-01, doyle, token GATE2RED-AUDIT-A).** RULING (A): reporting.rs:54 rc-rebind (session_id rotation) + home.rs:192 adopt_home (immutable-once home_subnet, closure re-checks is_none) → convert to mutate_info THIS commit. Rationale: unique tmp changed those sites' failure mode loud-collision→SILENT lost update — the fix chain raised their stakes, so load-bearing pair rides now. CONDITION: preserve absent-record semantics per call site (mutate_info NotFound must not turn a join/rebind fatal where raw write tolerated absence; state mapping in commit msg). PARKED + named in KH 5.16 prose as deliberate residue: listener.rs:184 write_busy (self-heals), rename.rs:140 rewrite_id (rare operator op), ready.rs:95 start_homed (harness-hosted single-writer today; revisit if a daemon-hosted path ever stamps it). Coverage for the 2 converts = unit-level closure semantics only; NO extra barrier hammers (5.16 hammer proves the mechanism once — no scope creep). AWAITING GATE2-C39-<newsha>.

**GATE 2 lost-update fix COMMITTED @043a6d1 + ring-cleanup self-catch (2026-07-01).** doyle ruled (A): folded reporting::cmd_boundary (rebind session_id) + home::adopt_for_unset (home_subnet) into mutate_info CAS (absent→NotFound mapped back to old soft behavior per site); parked listener::write_busy + rename::rewrite_id (self-heal/rare, named in KH 5.16); left ready::start_homed (harness-hosted single-writer). Committed 043a6d1: lock_perch_sentinel + write_info(locks)/write_info_unlocked split + establish_locked CAS (establish_perch uses it) + mutate_info now pub + REQ-HAZARD-INFO-RMW-LOST-UPDATE (toml+KH 5.16) + red-first lost-update hammer (reds unlocked @0.025s, greens locked @5.2s). clippy 0, traceable 0, affected units green (boundary_*/adoption_stamps/establish/race). BUT full nextest 1436/1439: 3 FAIL spt-msg ring::tests (perch_gone leak). SELF-RCA: fix #1 makes write_info create .info.lock in EVERY written perch incl ephemeral ring reply perches (never had one — use write_info not mutate_info); cleanup_ephemeral (ring.rs:142) does file-by-file + NON-recursive fs::remove_dir → stray .info.lock blocks it → dir leaks (REAL prod leak REQ-HAZARD-EPHEMERAL-CLEANUP). BLAST RADIUS = 1 (only non-recursive remove_dir in prod; all else remove_dir_all). Reported to doyle A(remove_dir_all)/B(explicit .info.lock rm); awaiting ruling → apply → full nextest → GATE2-C39-<newsha>. LESSON: adding a per-perch sidecar file (.info.lock) to a shared writer means EVERY surgical file-by-file teardown must account for it — remove_dir_all is the robust teardown.

**GATE 2 RESUBMITTED @f4b43c2 (2026-07-01) — awaiting doyle re-gate.** doyle ruled ring-A (remove_dir→remove_dir_all, keep named removes). Amended 043a6d1→f4b43c2 (lost-update lock refactor + reporting/home mutate_info converts + ring cleanup fix, folded onto fe385f5 scoped-durability+unique-tmp). Pushed (supersedes fe385f5). GATES: clippy 0, traceable 0, FULL nextest 1439 passed/0 failed/1 skip incl. 3 bringup canaries + 3 ring_*_cleans_up + red-first lost-update guard (reds unlocked @0.025s / greens locked @5.2s). GOTCHA hit again: first full-run EXIT 101 = relink os-error-5 from 4 leaked build-dir daemons (NOT a test fail) → swept per protocol → clean re-run. Sent GATE2-C39-f4b43c2. On GATE2-OK → deployah v0.19.1 (counter 39 published-metadata, bump-in-PR, CHANGELOG v0.19.0..HEAD, NO [twohost]). Full journey this milestone: 3 field bugs (BUG1 resolve/BUG2 corrupt+durable/BUG3 roster) → GATE1 RED (stale wansend test + blanket-fsync bringup stall) → scoped durability → GATE2 RED#1 (fixed-tmp collision os-error-2) → unique-tmp → GATE2 RED#2 (silent lost-update, establish vs RMW) → per-perch-lock serialization + establish_locked CAS. Each RED = a widened-window-reveals-latent-race lesson.

**GATE 2 GREEN @f4b43c2 (2026-07-01, doyle — token GATE2-OK-C39-f4b43c2).** Full re-gate my rig: diff review PASS (7 spec items + ruling A + ring-A faithful; establish conflict aborts with NO write; cwd_for_ledger captured in-closure; home pre-filter write-free at cadence; mutate_info→pub justified) · clippy 0 · traceable 332/332 · FULL nextest 1439/1439 0 failed (the rig that deterministically red the lost update) · canary pair ×2 = 6/6 @6.8-8.0s baseline speed (lock-across-fsync costs nothing measurable). GATE2-OK sent; todlando ack'd + handed deployah: v0.19.1, counter 39 from PUBLISHED metadata, bump-in-PR, CHANGELOG v0.19.0..HEAD (3 field bugs + 2 hardening: tmp-collision + lost-update lock), NO [twohost], Cargo.lock targeted first-party. Gate box swept (daemons by path, c39-gate3-target deleted); gate worktree ../spt-core-gate-c39 parked clean @f4b43c2. NEXT: verify deployah's published counter-39 metadata when it lands; relay to operator. MILESTONE ARC: 3 field bugs → GATE1 RED (stale test + blanket fsync) → GATE2 RED#1 (fixed-tmp collision) → GATE2 RED#2 (silent lost update) → GREEN. Meta-lesson: each fix's widened/narrowed window exposed the next latent race — gate until the layer stack stops yielding.

**GATE 2 PASSED @f4b43c2 (2026-07-01) — GATE2-OK-C39-f4b43c2, HANDED TO deployah.** doyle GREEN, fresh worktree @f4b43c2: diff PASS (7 spec items + ruling-A + ring-A faithful), clippy 0, traceable 332/332, full nextest 1439/1439, bringup canary ×2 = 6/6 @6.8-8.0s baseline (lock-across-fsync free) on the rig that deterministically red the lost-update. **todlando build+gate role COMPLETE.** Handed deployah release GO: v0.19.1, counter 39 (VERIFY from PUBLISHED spt-releases metadata; prior v0.19.0=38 @bac50fc), bump-in-PR, Cargo.lock TARGETED first-party only, CHANGELOG v0.19.0..HEAD = 3 field bugs (#1 REQ-INST-10 cross-subnet resolve / #2 corrupt-coherence + power-loss durability / #3 local corrupt roster row) + 2 hardening (unique-tmp collision, info.json per-perch-lock serialize) — dead-but-ONLINE class closed, no new surface; NO [twohost]. deployah now cuts. MILESTONE ARC (3 RCAs deep): blanket-fsync bringup stall → fixed-tmp collision (os-error-2) → silent lost-update (unlocked establish vs locked RMW); final = scoped durability + unique-tmp + per-perch-lock serialize + establish_locked CAS. Each RED widened a window that revealed the next latent race.

**MILESTONE CLOSED — v0.19.1 PUBLISHED counter 39 (2026-07-02T03:22:32Z, doyle PUBLISHED-CONFIRMED-C39-39).** PR#43 @827697b → main @fc92ac2 → tag v0.19.1 → signed+published. Independent gate-verify from PUBLISHED metadata: counter 39 both platforms, shas linux bb6d79a1…/win 5def2dc5… match SHA256SUMS, key rel-primary-2026, Latest, monotonic 38→39, update-set v39 (after transient stale-serve window — see [[v0191-published]] + [[release-updateset-stale-window]]). Residue parked for future milestones: listener::write_busy + rename::rewrite_id unlocked RMWs (KH 5.16 named), ready::start_homed (revisit if daemon-hosted-stamped), is_registry_entry_alive corrupt-row probe (KH 5.14), #3-local-view seed (mint-only-if-picked). Bugs #4/#5 = adapter/plugin side (perri), tracked separately.

**#5 (owl plugin 1.11.25, PINNED via perri control experiment):** `--reply-to` path FROM auto-detect stamps the REPLY_TO value as sender (from="doyle" on perri's ack); explicit FROM positional stamps correctly. Second manifestation of [[owl-reply-to-broken]] — plain `$OWL send <target> <self>` remains BINDING.

Related: [[counter38-field-bug-milestone]], [[v0190-published]], [[gate-clean-target-not-incremental]], [[presence-dead-shows-online]] (this is its sequel — W4 fixed cold⇒Suspended but corrupt perch bypasses via false-alive).

---
**BUILD-EXECUTION DETAIL (todlando, from doyle's 2-part owl build order 2026-07-01).** Base main @bac50fc, TRACEABILITY-FIRST per bug, build 1→2→3, ONE combined gate at end. **STARTED 2026-07-01** (todlando confirmed full spec via verbatim spec-echo after the delivery scare — see [[owl-send-unquoted]]; bug 1 first, most self-contained). Likely NO [twohost] (resolve/roster/atomic = local seams) — confirm at build.
- **#1 exact sites:** rewrite UNQUALIFIED branch of `resolve_across_visible` (registry.rs ~908-933); subnet-qualified path UNCHANGED. Remove `Ambiguity::AcrossSubnets`: render_refusal `wansend.rs:615`, test `wansend.rs:1208`, registry.rs `resolve_across_bare_id_in_two_subnets_refuses` `:1747`(→now AcrossNodes) / `join_same_name_in_different_subnet_is_legal` `:1841` / comment `:1949`; REQ-INST-10 toml text. NEW UNITS: (a) same (id,node) two subnets→Resolved [field repro]; (b) local one subnet + distinct remote node other→Resolved LOCAL; (c) distinct remote nodes across subnets→AcrossNodes; (d) same-node twin rows keep freshest epoch.
- **#2 exact fix:** (write) `atomic.rs atomic_write_bytes`: File::create(tmp)→write_all→**sync_all()**→rename_with_retry (NOT per-keystroke fsync — v0.13 W1b stays dropped [[v013-w1b-effect-journal-wedge]]). (read) CORRUPT (exists-but-unparseable after READ_ATTEMPTS) vs ABSENT: is_perch_alive corrupt⇒NOT alive (ABSENT unchanged=interim-parity-alive); advertised_status corrupt⇒Suspended never Active/Dormant. Add REQ-HAZARD-PERCH-RECORD-POWER-LOSS + REQ-HAZARD-CORRUPT-PERCH-COHERENCE (unit at EACH seam) → KNOWN-HAZARDS.md doc → tags same commit.
- **#3 exact fix:** `roster::enumerate` (roster.rs:41-43) — corrupt info.json dir yields a ROW (id=dirname, state "corrupt", alive=false, unbound=false) so endpoint list + picker Local show OFFLINE honestly; `reconcile_self_owned` (picker/data.rs:142) then overrides the stale ONLINE Subnet row with local-offline fact (unit). Project tab: cwd unrecoverable from corrupt file → project placement via project_history_for (context branch survives).
- **GATER RESOLVED (2026-07-01, corrected):** build-order owl came FROM doyle — todlando's attribution was RIGHT. The earlier "SUBNET SPT_DEV gates, not doyle" note was the hook-banner artifact (see identity gotcha above). **doyle gates counter-39**; todlando sends finished branch+sha to doyle. deployah publishes v0.19.1 (counter 39, verify from PUBLISHED metadata, monotonic 38→39).
- **#2 SEAM-GREP (gater instruction):** the 3 named readers (perch.rs:415 list_self_perch_ids / liveness.rs:106 is_perch_alive / registryhost.rs:797 advertised_status) may NOT be the only seams — grep ALL `read_info`/`read_raw` callers when in there; shared-seam rule → run ALL seam tests [[shared-seam-change-run-all-seam-tests]]; flag the gater if CORRUPT touches more read seams than the 3 named.
- **BUILD DONE + GATE REQUESTED (todlando 2026-07-01):** all 3 bugs BUILT, COMMITTED @**77beeac** (one commit, traceability-first), pushed to origin `counter39-field-bugs`. Gate request sent to doyle (receipt token `GATE-C39-77beeac`). #1: factored `resolve_among(&[&Instance],addr,local)` shared by resolve_visible + the rewritten union branch (dedupe-by-node BTreeMap, freshest-epoch twin, subnet re-tag on winner for route_wan remote-dial); AcrossSubnets fully removed; +4 units a–d. #2: atomic.rs sync_all-before-rename; liveness `RawRead::{Present,Absent,Corrupt}` tri-state, is_perch_alive corrupt⇒false (absent⇒alive parity kept) cascades advertised_status⇒Suspended; KNOWN-HAZARDS 5.13/5.14 + 2 REQ-HAZARDs. #3: roster::enumerate emits OFFLINE "corrupt" row (id=dirname) → reconcile_self_owned overrides stale Subnet gossip. **SEAM verdict:** only is_perch_alive→advertised_status makes the ghost; list_self_perch_ids kept (existence = #3's visibility); `is_registry_entry_alive` (corrupt→daemon-pid probe) PARKED (doesn't gossip) + doc'd in KH 5.14, flagged to doyle. Local gates all green: build workspace 0 / nextest spt-net 43-43 + store/daemon libs 125 (1 pre-existing brainproc leak) + spt bin reconcile+enumerate / traceable-reqs check 0 / clippy --workspace --all-targets 0. NO [twohost]. NEXT: await doyle gate verdict → deployah v0.19.1 (counter 39).
