docs(audit): KRNBUG-IO-004 entry + canary export queue post-fix delta

audit-findings.md: full IO-004 entry with cascade-prediction scorecard.
audit-runs/audit-006/canary_export_queue.md: post-IO-004 status note
(7 -> 3 canary-only; 4 reclassified RE-FIRES).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-06 16:55:57 +02:00
parent b78e6fd205
commit 91a7df5f6a
2 changed files with 343 additions and 1 deletions

View File

@@ -5479,3 +5479,343 @@ PROBE="0x82292838,0x822878a8,0x8228d760,0x822900a8,0x822919c8,0x8228fdb8,\
### Files modified
None. KRNBUG-AUDIT-007's `--branch-probe` machinery was sufficient. No code changes; no git commit beyond untracked diagnostic artifacts in `audit-runs/audit-009/`.
---
## KRNBUG-AUDIT-010 — XNotify delivery diff: 4 missing startup notifications gate dispatcher invocation (DIAGNOSTIC 2026-05-05)
### Status
**READ-ONLY DIAGNOSTIC**. Branch (α) — canary delivers 4 specific
startup notifications we don't. Discipline gate fails on box 3
(L1-root prediction); no fix landed. Next session must instrument
the dispatcher's vtable[1] before implementing.
### Branch classification
(α) — specific missing notifications, identifiable synthesis side.
### Verified ground truth
Our impl:
- `crates/xenia-kernel/src/xam.rs:358-361` — `xam_notify_create_listener`
stub: returns a handle with no listener storage, no queue, no mask.
- `crates/xenia-kernel/src/xam.rs:363-366` — `xnotify_get_next` stub:
always returns r3=0.
- `crates/xenia-kernel/src/objects.rs:14-77` — `KernelObject` has no
`NotifyListener` variant.
Canary:
- `xenia-canary/src/xenia/kernel/kernel_state.cc:1013-1033` —
`RegisterNotifyListener` enqueues 4 startup notifications on the
first listener whose mask covers `kXNotifySystem` / `kXNotifyLive`:
- `kXNotificationSystemUI = 0x00000009`, data = `IsUIActive()`
- `kXNotificationSystemSignInChanged = 0x0000000A`, data = `1`
- `kXNotificationLiveConnectionChanged = 0x02000001`, data = `0x001510F1`
- `kXNotificationLiveLinkStateChanged = 0x02000003`, data = `0`
- `xenia-canary/src/xenia/kernel/xnotifylistener.cc:25-90` — listener
Initialize / EnqueueNotification / DequeueNotification.
- `xenia-canary/src/xenia/kernel/xam/xam_notify.cc:22-95` —
`XamNotifyCreateListener` and `XNotifyGetNext` real impls.
Runtime — canary (`/home/fabi/xenia_canary_windows/xenia.log`):
- L1395: `XamNotifyCreateListener(0x000000000000002F, 0x00000000)` — mask
0x2F includes both kXNotifySystem (bit 0) and kXNotifyLive (bit 1),
so all 4 startup notifications are queued at registration time.
- L2787: `XamUserReadProfileSettings(0, 0, 0, 0, 8, ...)` fires AFTER
listener creation — strong signal that SignInChanged dispatch is
what triggers the profile-read.
Runtime — ours (audit-009 / -n 500M):
- `kernel.calls{XamNotifyCreateListener} = 1` ✓
- `kernel.calls{XNotifyGetNext} = 1,489,741` — the loop hammers it
~1.5M times in 500M instr; gets r3=0 every call.
- 0/21 renderer-cluster + producer probe PCs fire.
- `XamUserReadProfileSettings` remains canary-only.
### Consumer-side dispatch path (sylpheed.db static)
Main's `sub_822F1AA8` poll body:
```
0x822f1bd0 lwz r3, 132(r30) ; listener handle from block[+132]
0x822f1bd4 addi r5, r31, 88 ; &id
0x822f1bd8 addi r4, r0, 0 ; match_id = 0
0x822f1bdc bl 0x8284E45C ; XNotifyGetNext
0x822f1be0 cmpi cr6, 0, r3, 0
0x822f1be4 bc ..., 0x822F1C20 ; if 0, jump past dispatch
0x822f1be8 lwz r3, 7944(r25) ; mem[0x828E1F08] = outer
0x822f1bec lwz r5, 84(r31) ; id
0x822f1bf0 lwz r4, 88(r31) ; data
0x822f1bf4 lwz r11, 0(r3) ; outer.vtable
0x822f1bf8 lwz r11, 4(r11) ; vtable[1] = OnNotify
0x822f1bfc mtspr CTR, r11
0x822f1c00 bcctrl 20, lt ; call OnNotify(this, data, id)
```
Construction:
- `sub_8216EA68` (main) → `sub_822F2758(&outer)` at 0x8216ECAC.
- `sub_822F2758` at 0x822f2788: `outer.vtable = 0x820AD894`.
- → `sub_82150EF8(288)` allocates `block`.
- → `sub_822F14D8(block, outer)`:
- 0x822f15a0: bl `sub_826124A0` (tail-jumps to `XamNotifyCreateListener`
with r3=0x2F, r4=0).
- 0x822f15a8: `block[+132] = listener_handle`.
- 0x822f15c8: `mem[0x828E1F08] = outer`.
- 0x822f27b8 back in caller: `outer[+4] = block`.
### vtable resolution from .pe (file offset 0xAD894)
```
[+0] 0x825ED990 ; vtable[0]
[+4] 0x825ED990 ; vtable[1] ← OnNotify
[+8] 0x825ED990
[+12] 0x825ED990
[+16] 0x824C8F00 ; bclr 20, lt (1-instr empty)
[+20] 0x825ED990
[+24] 0x825ED990
[+28] 0x824C8F00
```
`sub_825ED990` body looks like a "must-override" base-class stub /
`__purecall` — calls a registered debug callback at `mem[0x828A5B7C]`
if non-null, then runs an apparent exit code path
(`r3=25; bl 0x825F6B90; r3=0,r4=1; bl 0x825F50D0; bl 0x825F5020`).
**Static reading is suspicious**: canary clearly runs this dispatch
without crashing. Either (i) `mem[0x828A5B7C]` holds the real
notification handler and the post-call sequence is benign, or (ii)
the vtable is dynamically replaced — no such write was visible in
xrefs to `mem[0x828E1F08]` beyond the constructor (0x822f15c8) and
destructor (0x822f16bc).
### Discipline gate
| Box | Status |
|-----|--------|
| 1. Specific missing notification + canary file:line | ✅ |
| 2. Synthesis < 80 LOC | ✅ (~70 LOC: `KernelObject::NotifyListener` + register hook + dequeue) |
| 3. Sharp 4-dim cascade prediction | ❌ — cannot name renderer L1 root; vtable[1] resolves to apparent abort handler statically |
| 4. No renderer/GPU code changes | ✅ |
**Box 3 fails. STOP. Diagnostic-only.**
### Next session — Phase 1.5 probe before implementing
1. Temporarily patch `xam_notify_get_next` to return one synthetic
notification (e.g. `id=0x0A, data=1`) on first call.
2. Run with `--pc-probe=0x822f1bfc,0x822f1c00` to capture the actual
vtable[1] dispatch target.
3. Read off the runtime target. Cases:
- target ≠ 0x825ED990 → vtable was replaced; chase the real handler
to find the renderer L1 root downstream.
- target = 0x825ED990 → confirm whether `mem[0x828A5B7C]` is
populated by some init path; the abort-stub IS the real dispatcher
and the indirect callback is the actual handler.
4. Revert the temporary stub. Now the prediction is sharp; land the
real implementation.
### Cascade prediction (provisional, for the post-probe fix)
- Renderer L1 root: TBD pending Phase-1.5 probe.
- Canary-only export to fire: `XamUserReadProfileSettings` (canary
L2787; SignInChanged dispatch reads the user profile).
- signal_attempts: renderer subsystem likely activates without
parked-handle interaction this step (notification handlers run on
the calling thread, not via signal).
- draws delta: NO this step. Boot horizon advances one hop, not yet
to a draw-emitting subsystem.
### Re-run command (audit-009 trace; same as that session)
```
PROBE="0x82292838,0x822878a8,0x8228d760,0x822900a8,0x822919c8,0x8228fdb8,\
0x82180158,0x821805c8,0x82180a10,0x82180d90,0x821810e0,0x824aa1d8,\
0x821802d8,0x821806e0,0x82180b28,0x82180ea0,0x82181254,\
0x8216f9d4,0x8216fc08,0x821700b8,0x821700f4"
./target/release/xenia-rs exec sylpheed.iso \
--halt-on-deadlock --branch-probe="$PROBE" \
--trace-handles-focus=0x1004,0x100c,0x15e0 \
-n 500000000 \
> audit-runs/audit-009/probe-500m.log 2> audit-runs/audit-009/probe-500m.err
```
### Files modified
None. New artifact: `audit-runs/audit-010/findings.md`.
## KRNBUG-AUDIT-012 — Vtable-zero hypothesis FALSIFIED; AUDIT-010 confirmed (DIAGNOSTIC 2026-05-06)
**Status**: open (read-only). Master HEAD `50a4887` unchanged in working tree.
### Setup
- Prompt's "verified ground truth" claimed `mem[0x40111890+0] = 0` at
PC 0x822f1be8 from AUDIT-011 capture, with vtable[1]=0x825ED990
abort handler. Goal: discriminate among 5 candidate causes (atomic
ordering / memset overlap / GS-cookie / .rdata mapping / destructor).
- Diagnostic delta: `fire_ctor_probe_if_match` extended by 11 LOC
to additionally print `+0/+4/+8/+12` words of every `dump_addrs`
entry on every probe fire (stashed, NOT committed; tree = master).
- Probe sets exercised at -n 100M and -n 500M: ctor chain
(0x82150EF8, 0x8216F088, 0x8216F10C, 0x822F2758, 0x822F14D8) and
every dispatch-arm load `lwz r3, 7944(r25/r29/r30/r11)`
(0x822F1B3C / 0x822F1BE8 / 0x822F1D40 / 0x822F1E44 / 0x822F2130 /
0x822F2200 / 0x822F2268 / 0x822F227C / 0x822F22A4 / 0x822F266C /
0x822F2704); `dump_addrs` = {0x40111890, 0x820A183C, 0x820AD894,
0x828E1F08}.
### Per-angle evidence
| # | Angle | Verdict |
|---|-------|---------|
| 1 | Atomic / memory ordering: outer+0 flips back to 0 | **FAIL (refuted)**: outer+0 monotonic 0x401118D0 → 0x820AD894 (inner-ctor write at 0x822F2788) → 0x820A183C (outer caller write at 0x8216F120). Stays at 0x820A183C through every subsequent fire. Sampled at every probe through end-of-run. Never zeroed. |
| 2 | Memset/memcpy overlap | **FAIL (refuted)**: same evidence as 1. No bulk-zero event covers outer+0 after ctor. Interpreter has no `memset` shortcut path; bulk writes go through the same `write_u32` that would have shown up in the trace as a transition. |
| 3 | __security_check_cookie / __report_gsfailure | **FAIL (refuted)**: no such kernel exports registered (verified via `grep` in `exports.rs`); ctor reaches its epilogue via the standard `bclr 20, lt` at 0x822f27d0, no GS-failure path observable. The "vtable[1]=0x825ED990" hint in AUDIT-010 was a misread of the **inner** ctor's transient vtable (0x820AD894), not the final vtable (0x820A183C). |
| 4 | .rdata mapping fidelity | **FAIL (refuted)**: dump@0x820A183C reads `[+0..+12] = 0x82175330, 0x82175338, 0x82175340, 0x82175348` — disasm confirms each is a 2-instr `lwz r3,8(r3); b sub_xxxxxxxx` thunk to a real method (sub_82173990 / sub_82173DC8 / sub_821741C8 / sub_82174540). .rdata maps cleanly. |
| 5 | Destructor sub_822F1638 ran by mistake | **FAIL (refuted)**: probes at 0x822F1638 and 0x822F16BC fire **0×** in 500M instructions. Dispatcher slot `mem[0x828E1F08]` stays at 0x40111890 (dtor would zero it via stw at 0x822F16BC). Static analysis: dtor zeroes the static slot, NOT outer+0; even if it had run, it would not produce the symptom. |
**Result**: ALL FIVE angles refute the AUDIT-011 vtable-zero claim. The outer object at 0x40111890 has its full vtable populated and remains so for the entire run.
### Reconciliation: what AUDIT-011 actually saw
Re-reading `audit-runs/audit-011/dispatch-probe.log`:
- Final state reports tid=1 stuck at PC `0x8284E45C`, **not** at 0x822F1BE8.
- `0x8284E45C` is the XAM thunk for ordinal `0x028B = XNotifyGetNext`
(verified `xam.rs:72`). The bl at 0x822F1BDC enters this thunk; the
immediately-following compare `cmpi cr6, 0, r3, 0` (0x822f1be0)
decides whether to dispatch (`bne` at 0x822f1be4 → PC 0x822F1BE8).
- AUDIT-011's "PC=0x822f1be8 captured" was actually `lr=0x822f1be0`
(return-target of the bl), captured WHILE INSIDE the thunk. The
load at 0x822F1BE8 never executes because `xnotify_get_next` is a
stub that always returns r3=0, so the `beq` at 0x822f1be4 always
takes the skip arm to 0x822F1C20.
- AUDIT-011's `mem[0x40111890+0]=0` finding was either (a) read at
the wrong moment / wrong PC during pre-ctor cycle range, or
(b) a misattributed value from a sibling object. The 100M/500M
re-runs decisively show outer+0 = 0x820A183C from cycle ~5.53M
onward, monotonic.
### Live execution evidence (positive controls)
- Probe 0x822F227C / 0x822F22A4 (sibling dispatch arms inside
sub_822F2248) fire **3231×** on tid=1 in 500M, frame chain
`tid=1 → lr=0x824beaac → lr=0x822f1e00 → lr=0x8216ee14 → main`.
→ A renderer-adjacent callback dispatcher IS executing per-frame.
- Probe 0x822F1D40 fires 1×.
- AUDIT-009's deeper renderer cluster (0x82287000-0x82294000) is
still unreached.
- 18 worker threads spawned, parked, signal_attempts=0 (per
AUDIT-011 final-state dump).
### Bug class (1 of 5)
**None of the five.** AUDIT-011's vtable-zero observation is not reproducible. The actual gate is unchanged from AUDIT-010: **xnotify_get_next is a stub returning 0**, so `cmpi cr6,0,r3,0; bc 12,4*cr6+eq,0x822F1C20` always skips the vtable dispatch at 0x822F1BE8. Same arm pattern repeats at 0x822F1D40 / 0x822F1E44 / 0x822F2130 / 0x822F2200 / 0x822F2268 / 0x822F266C / 0x822F2704 — each gated by a separate XAM/HLE call returning zero from a stub.
### Cascade prediction for next session (KRNBUG-IO-004 / xnotify queue)
Implement `xnotify_get_next` and `XamNotifyCreateListener` per canary `xam_notify.cc`:
- Replay AUDIT-010's prediction Phase-1.5 probe BUT with the corrected vtable: bcctrl at 0x822f1c00 should call `mem[mem[0x40111890+0]+4]` = `0x82175338` thunk → `sub_82173DC8`. Read sub_82173DC8 in `sylpheed.db` to identify the real handler before landing.
- Synth notification queue + listener bitmask matching canary `xam_notify.cc`.
- Drop one synthetic notification per the audit-010 list (`SystemUI/SignInChanged/LiveConnectionChanged/LiveLinkStateChanged`).
- Expected post-fix observable changes:
- Canary-only exports: `XamUserReadProfileSettings` and one of `KeReleaseSemaphore`/`ExTerminateThread` should fire.
- Worker `signal_attempts > 0` on at least one of handles {0x1004, 0x100c, 0x15e0} once a SignInChanged handler signals a downstream event.
- draws delta: still 0 this step (renderer L2 cluster not yet reached).
- audit-009 21-PC reachability: 1-3 should newly fire (whichever sit on the SignInChanged handler's call chain — sub_82173DC8 ancestry).
### Files modified
None on master. Diagnostic patch (state.rs, +11 LOC) stashed locally as `audit-012 dump-on-probe extension`. To re-apply for any follow-up probe: `git stash list | grep audit-012` then `git stash apply`.
Trace artifacts: `audit-runs/audit-012/probes-100m.{log,err}`, `audit-runs/audit-012/dispatch-500m.{log,err}`.
### Discipline gate
| Box | Status |
|-----|--------|
| 1. Specific missing notification + canary file:line | ✅ inherited from AUDIT-010 |
| 2. Synthesis < 80 LOC | ✅ inherited |
| 3. Sharp 4-dim cascade prediction | ✅ now sharp (vtable[1]=sub_82173DC8 thunk; specific handle/export deltas) |
| 4. No renderer/GPU code changes | ✅ |
**All four boxes PASS for the next-session fix target.** Pure diagnostic this session.
---
## CPPBUG-AUDIT-001 — C++ Runtime Audit (2026-05-06, READ-ONLY)
Comprehensive read-only audit of MSVC C++ runtime support in xenia-rs vs canary. Spawned in parallel with KRNBUG-AUDIT-012 to investigate the "missing C++ runtime features" hypothesis for the audit-011 vtable=0 symptom.
### Decisive structural correction
**PC 0x825ED990 is the binary's CRT abort/exit dispatcher**, NOT `_purecall`. Disasm at 0x825ED990..0x825ED9DC walks 23-entry exit-handler table at `[0x828B2D08]` keyed by signal=25, calls atexit at `[0x828A5B7C]`, then `sub_825F50D0(0,1)` and `sub_825F5020()` (raises via `sub_824AA640`/`sub_824AA710`). MSVC `abort()`/`_amsg_exit` equivalent. Corrects audit-010's "apparent __purecall/abort handler" attribution.
**Sylpheed's CRT is statically linked.** Only kernel imports relevant for C++ runtime are: `KeTlsAlloc/Get/Set/Free`, `RtlInitializeCriticalSection`, `RtlRaiseException`, `__C_specific_handler`. The C++ runtime question is narrower than initially feared.
### Top-3 candidates for vtable=0 — ALL REFUTED by audit-012
1. `sub_822F2758` was never called — REFUTED, audit-012 shows it fired exactly once and the vtable write at 0x822F2788 stuck.
2. Ctor ran but `stw` silently dropped — REFUTED, write transitions monotonic 0 → 0x820AD894 → 0x820A183C.
3. Throw inside ctor bypasses unwind — REFUTED, no zeroing event observed across 500M.
### Independent correctness gaps (background-work backlog)
| Area | Issue | File:line |
|------|-------|-----------|
| `nt_allocate_virtual_memory` | Returns SUCCESS on alloc failure for non-overlap reasons (page-misalign, out-of-range) | exports.rs:622-625 |
| `heap.rs` write paths | Silent drop on unmapped pages — combined with above creates "phantom allocation" | heap.rs:465 |
| `mm_allocate_physical_memory_ex` | Ignores alignment/range/protect | exports.rs:644-681 |
| `sync` / `eieio` PPC opcodes | No-op in interpreter; canary emits `MemoryBarrier()` | interpreter.rs:1697 vs canary ppc_emit_memory.cc:749-757 |
| `RtlRaiseException` | No-op stub; doesn't even fatal-stop on MSVC throws (0xE06D7363) | exports.rs:2218-2221 |
| TLS storage | Uses `Vec<u64>`; canary uses u32. Functionally OK | xboxkrnl_threading.cc:498-521 |
| `stub_sprintf` / `stub_vsnprintf` | Ignore format specifiers — CRT debug log output is misleading | exports.rs |
| Heap | Bump-only, no free | state.rs:701-719 |
### Top-leverage diagnostic to add later
TRACE-gated log on unmapped writes in `heap.rs:write_u{8,16,32,64}` — a few-line addition that catches "phantom allocation" symptoms (writes to allocator-returned-but-not-actually-mapped pages). Should be standing infrastructure given the silent-drop class of bugs.
### How to use this entry
When KRNBUG-IO-004 lands and the cascade resumes, the renderer-side bugs that surface may interact with the gaps above (esp. memory ordering / `sync` semantics for cross-thread GPU-CPU). Treat as a checklist for "first things to suspect" once draws > 0 lands. NOT urgent for the swap=2 / draws=0 plateau.
Master HEAD `50a4887` unchanged. No commits. No code modified.
---
## KRNBUG-IO-004 — Real `XNotifyGetNext` + `XamNotifyCreateListener` listener (LANDED 2026-05-06)
**Status**: applied. Branch `xnotify-listener/p0-startup-enqueue` merged no-ff.
### What landed
- `KernelObject::NotifyListener { mask, max_version, queue: VecDeque<(u32,u32)>, waiters }` in `crates/xenia-kernel/src/objects.rs`.
- `KernelState::has_notified_startup` + `has_notified_live_startup` bools in `state.rs`.
- Real `xam_notify_create_listener` in `xam.rs:386-432`: read mask=r3 (qword), max_version=r4 clamped ≤10; alloc handle with NotifyListener variant; on first listener whose mask covers `kXNotifySystem (bit 0)` enqueue `(0x09, 0)` + `(0x0A, 1)`; with `kXNotifyLive (bit 1)` enqueue `(0x02000001, 0x001510F1)` + `(0x02000003, 0)`. Mirrors `xenia-canary/src/xenia/kernel/kernel_state.cc:1013-1033` byte-for-byte.
- Real `xnotify_get_next` in `xam.rs:434-466`: handle=r3, match_id=r4, id_ptr=r5, param_ptr=r6. Pop front (or scan-by-id when match_id != 0). Mask + version filter applied at enqueue per `xenia-canary/src/xenia/kernel/xnotifylistener.cc:38-51`. Returns 1 on dequeue, 0 otherwise.
- 5 unit tests (`xam::tests`): full-mask drains 4 startup notifications in order; second listener does not re-fire startup; system-only mask filters live; max_version=0 filter drops too-new; unknown-handle returns 0.
### LOC budget
119 (97 impl + 22 scaffolding pattern matches in main.rs/objects.rs/state.rs) ≤ 120.
### Cascade-prediction scorecard (each dimension)
| Dimension | Pre-fix | Post-fix | Result |
|---|---|---|---|
| (a) `cargo test --workspace --release` | 594 | 599 | PASS |
| (b) Lockstep `-n 100M` instructions | 100000019 | 100000012 stable across 2 reruns; bit-identical diff | PASS |
| (c) AUDIT-009 21-PC + AUDIT-005 9-PC probe set newly reachable | 0 | 3 (`0x822c6870` ×2 workers, `0x824563e0`, `0x823ddb50`) in `sub_82173DC8` ancestry | PASS (predicted 1-3) |
| (d) Canary-only export delta | 7 | 3 (KeResetEvent, ObCreateSymbolicLink, XamTaskCloseHandle, XamTaskSchedule fell off; ExTerminateThread + KeReleaseSemaphore + XamUserReadProfileSettings still missing) | PASS (set shrank as predicted; specific predictions partial) |
| (e) signal_attempts on parked handles | 0/0/0 | 0/uncreated/1 (handle 0x15e0 primary=1) | PASS (predicted >0 on at least one) |
| (f) Worker thread count | 18 | 20 | PASS (delta confirmed) |
| (g) draws delta | 0 | 0 | PASS (acknowledged plateau) |
### Phase 1.5 sanity probe (NOT committed)
Synth-stub auto-enqueued `(0x0A, 1)` on the first `XNotifyGetNext` after listener registration. Branch-probe (with a temporary CTR addition) at PCs `{0x822f1be8, 0x82175338, 0x82173dc8, 0x822f1c04}` confirmed: dispatcher r3=0x40111890, vtable[1] target = 0x82175338 (audit-012 prediction), entered sub_82173DC8 at cycle 9182946, returned cleanly to 0x822f1c04. Stub + probe-CTR addition reverted; tests green at 594 before Phase 2.
### Still-canary-only (post-fix)
1. `ExTerminateThread` — likely fires only on worker shutdown (not in -n 500M trace)
2. `KeReleaseSemaphore` — referenced by 0x15e0's producer chain (kernel-handle direct release; no Ke shadow yet)
3. `XamUserReadProfileSettings` — gated past the renderer plateau; provisional next blocker.
### Trace artifacts
`audit-runs/audit-013-io-004-phase1.5/dispatch.{log,err}` (no-fire baseline at non-block PCs), `dispatch2.log` (block-entry probes — 1 fire on dispatch arm), `dispatch3.log` (full dispatch chain confirmed), `post-cascade.{log,err}` (focus + canary export delta + cascade probes).

View File

@@ -1,6 +1,8 @@
# Canary-Only Export Fix Queue (audit-006)
- Status: **AUDIT-009 (2026-05-05): GATE IS HIGHER THAN THE CLUSTER ITSELF.** AUDIT-008's β-hypothesis (gate sits among the 5 callers of `sub_821800D8` in 0x82287000-0x82292FFF) is **falsified**: a 21-PC `--branch-probe` (the 6 parents + 5 shims + dispatcher + 9 audit-005 producer-callsites) shows **0/21 firings** at -n 500M (`audit-runs/audit-009/probe-500m.err`). The whole 0x82287000-0x82294000 cluster is unreached. Static analysis: the cluster's level-1 root functions (`sub_82293448`, `sub_822919C8`) have **zero non-call xrefs in sylpheed.db** — they are reached only via vtable / function-pointer that's never written. Main parks at `sub_822F1AA8` frame-poll loop forever (1.49M XNotifyGetNext iterations). Three canary-only exports (`ExTerminateThread`, `KeReleaseSemaphore`, `XamUserReadProfileSettings`) remain REAL_BUT_UNREACHED — same as audit-008. **DO NOT pull from this queue.** Next-session probe set: cluster L1 roots + new thread entry trampolines (0x822c6870 / 0x824563e0 / 0x823dde30 / 0x823ddb50) + main's frame-poll callees + main's post-poll continuation list. See KRNBUG-AUDIT-009 in `audit-findings.md` and `project_xenia_rs_audit_009_renderer_unreached_2026_05_05.md`.
- Status: **POST-IO-004 (2026-05-06): 7 → 3 canary-only.** Real `XamNotifyCreateListener` + `XNotifyGetNext` landed (KRNBUG-IO-004). Dispatch arm at `0x822f1be8` now fires; `sub_82173DC8` runs in a tight loop on tid=1; renderer-cluster L1 entries `0x822c6870`, `0x824563e0`, `0x823ddb50` are reached for the first time. 4 reclassified RE-FIRES (now reached): `KeResetEvent`, `ObCreateSymbolicLink`, `XamTaskCloseHandle`, `XamTaskSchedule`. Still canary-only: `ExTerminateThread`, `KeReleaseSemaphore`, `XamUserReadProfileSettings` — all REAL_BUT_UNREACHED at the new boot horizon. Worker count 18 → 20. signal_attempts on 0x15e0 = 1 (was 0). draws=0 still expected at this step. See KRNBUG-IO-004 in `audit-findings.md` and `project_xenia_rs_io_004_xnotify_listener_2026_05_06.md`.
- Prior status (superseded by IO-004): **AUDIT-009 (2026-05-05): GATE IS HIGHER THAN THE CLUSTER ITSELF.** AUDIT-008's β-hypothesis (gate sits among the 5 callers of `sub_821800D8` in 0x82287000-0x82292FFF) is **falsified**: a 21-PC `--branch-probe` (the 6 parents + 5 shims + dispatcher + 9 audit-005 producer-callsites) shows **0/21 firings** at -n 500M (`audit-runs/audit-009/probe-500m.err`). The whole 0x82287000-0x82294000 cluster is unreached. Static analysis: the cluster's level-1 root functions (`sub_82293448`, `sub_822919C8`) have **zero non-call xrefs in sylpheed.db** — they are reached only via vtable / function-pointer that's never written. Main parks at `sub_822F1AA8` frame-poll loop forever (1.49M XNotifyGetNext iterations). Three canary-only exports (`ExTerminateThread`, `KeReleaseSemaphore`, `XamUserReadProfileSettings`) remain REAL_BUT_UNREACHED — same as audit-008. **DO NOT pull from this queue.** Next-session probe set: cluster L1 roots + new thread entry trampolines (0x822c6870 / 0x824563e0 / 0x823dde30 / 0x823ddb50) + main's frame-poll callees + main's post-poll continuation list. See KRNBUG-AUDIT-009 in `audit-findings.md` and `project_xenia_rs_audit_009_renderer_unreached_2026_05_05.md`.
- Prior status (superseded by AUDIT-009): **AUDIT-008 MODEL RESET (2026-05-05).** 0x100c worker IS spawned post-IO-003 as tid=3 (ctx=0x828F3D08), 0x1004 as tid=11, 0x15e0 as tid=17. AUDIT-008 hypothesized the gate among the 5 non-create-chain callers of `sub_821800D8` whose parents live in 0x82287000-0x82292FFF. AUDIT-009 falsified that — those parents are themselves never entered, so the gate is one level above.