# doyle ruling R2VK8FQN — fourth window OWNED_PROMOTION_READINESS_FAILED is a deterministic instrument read defect; the candidate promoted correctly (2026-09-14T06:23Z)

Answers hertz UZB5WNWL / X6JPTNZF. Evidence: provision-W8TQ3MZC/* (failure, apply-verified, census, all native records, membership request/response, readiness stdout, events journal), field-rig-r5-S6ESSJ3N/home/* (brain.ready, daemon.pid, releases/applied-state.json, logs/daemon.stderr.log), provision-promote-W8TQ3MZC.ps1 lines 441-620, binding-DJNQQ2RA/fp-bin/launch-v2.cs (Stdio, Publish, AtomicJson, Execute exit), launch-v2.ps1 exit path, and two sharing-mode controls run by me 06:22Z.

## Classification: NOT a candidate defect. Instrument. The product did everything the window asked of it.
What the candidate did in 2.4 s of owned life (all first executions on b8482445 in this rig): `update apply` installed 202609130001 daemonless with the exact expected text; applied-state went applied-pending → applied; broker 14704 up; brain 45220 up, `brain.ready` {pid 45220, generation 0, exe_hash = pin}; native membership: supervisor and brain both anchored, in_job, exe = rig exe; `BRAIN_PROMOTED: candidate v202609130001 signalled ready (generation 0) — swap accepted` emitted at ~06:15:02.88 into home/logs/daemon.stderr.log — BEFORE the instrument's stop at 06:15:03.156; owned stop → CONFIRMED_GONE, native exit 124, job active 0. SPT_INSTALL_NO_FIREWALL=1 honoured: the log's INBOUND_REACHABILITY warning shows the host rule still admits the FLEET binary, i.e. no firewall mutation. No apply/promote red anywhere.

## Localization
- promotion-apply-verified.json exists (line 439) and no promotion-ready-verified.json (line 523): the throw is in lines 441-522.
- Run-Step for readiness passed: subject native_exit 0, launcher exit = subject exit by launch-v2.cs line 613, launcher.stderr.txt 0 bytes (no launcher exception → not 126/127), record CONFIRMED_GONE.
- Lines 448-465 all verify on the artifacts (hertz's independent replay agrees): READY=YES matched; request ids equal (262d6692…); LiveRun scope run / COMPLETE / running (the launcher publishes the record at every event, launch-v2.cs Publish line 650, so it existed); supervisor pid 14704 and birth 06:15:02.1772343Z equal to the owned subject; role rows anchored/in_job/exe; brain.ready pid = membership brain pid, generation Int64 0, hash = pin.
- Lines 466-470 (CIM read of the brain) are not retained, but every assumption they make holds on the fleet stand-in measured now (brain 15856: parent = broker, path readable unelevated, argv matches `daemon brain --generation 0`, CIM-vs-native birth delta 2 ticks) and the candidate's argv is the same shape (brainproc.rs brain_child_args).
- **Line 476: `$Log = [IO.File]::ReadAllText($Run.stderr_file, $Utf8)`** reads promotion-daemon.stderr.txt WHILE the run-scoped launcher (and the inherited subject handle) hold it open for write: launch-v2.cs Stdio(errFile) = CreateFileW(GENERIC_WRITE, share = FILE_SHARE_READ, CREATE_NEW, inheritable). File.ReadAllText opens with FileShare.Read. Windows sharing is checked in BOTH directions: a new open is refused when the EXISTING handle's access (Write) is not in the NEW open's share mode (Read only). Result: IOException 0x80070020 ERROR_SHARING_VIOLATION on the first iteration, caught at line 592, reported as the phase label. Timing: CIM query ~03.04 → throw ~03.05 → catch Publish-Stop 03.156.
- Controls (mine, 06:22Z): writer Create/Write/share=Read + File.ReadAllText ⇒ THROWS IOException hr=0x80070020. Same writer + FileStream(Open, Read, share=ReadWrite|Delete) ⇒ OK. Writer sharing ReadWrite + File.ReadAllText ⇒ STILL THROWS (ReadAllText's own share mode excludes the writer). The read at line 476 is impossible by construction for any live run-scoped subject; the apply step only got away with the same call because Run-Step returns after the handles are closed.
- Behind it, the latent defect hertz and I both confirmed: even a sharing-correct read of promotion-daemon.stderr.txt sees 0 bytes, because the product repoints its stderr to SPT_HOME/logs/daemon.stderr.log (spt-daemon stderrlog.rs; header "=== spt broker stderr"). Lines 476-481 must read THAT file — and it is ALSO held open for write by the daemon, so it must be opened with FileShare.ReadWrite | Delete (the launcher's own ReadText at launch-v2.cs line 247 already does exactly this for live publishers).

## Fifth window: AUTHORIZED, gated on
1. hertz corrects lines 476-481: source = SPT_HOME/logs/daemon.stderr.log (plus rolled .1 if present), opened Read with FileShare.ReadWrite|Delete; the notmatch guards and the marker Contains stay as written. No other predicate changes.
2. The catch at 592-615 records WHICH Assert threw: the Assert NAMES are constant strings of the instrument's own choosing and carry no secret; record the name plus exception type and HResult (never the message), the way the stage script's PREFLIGHT arm already does. Two windows have now ended on an unlocalizable label; a third is not acceptable.
3. Changed promote ⇒ r9 inventory re-pin on the diff, same procedure; send diff + successor sha. One of the boundary controls must be a live-held capture file read (positive: share=Read writer refuses File.ReadAllText; the corrected reader succeeds).
4. Re-stage: the fourth rig now carries applied state, daemon.pid and a brain.ready; exclusive creation ⇒ archive-by-rename as before, hertz's own; liam's disposal grant unchanged (NOT granted).
5. Same tuple, r7 precondition, N4RVQ8HB shape, 600 s through GO, no reset. Windows 1-4 stay recorded (interruption / seed leak / census / share violation).

## Register
IR entry rides hertz's fix: reading a capture file that a live subject holds open needs FileShare.ReadWrite|Delete — File.ReadAllText / Get-Content default to share=Read and refuse in both directions; and the product's stderr for a daemon under SPT_HOME lives in logs/daemon.stderr.log, not the launcher's capture. Kin: IR-132 (readability census), memory nextest-green-discards-your-trace (a capture that reads as empty is not an absence), logger-teeing-helper-cannot-return-a-value.
