Source changes (dormant parity infra, retained from iterate 2.AI/2.AO): - xenia-kernel/exports.rs: nt_create_event manual_reset polarity + related event wiring - xenia-gpu/mmio_region.rs: D1MODE_VBLANK_VLINE_STATUS hardcode parity Also lands the audit-runs/ analysis notes (.md/.txt/.json digests) for the iterate 2.x VSync/0x10e8/0x1004 wedge investigation. Raw trace dumps (.jsonl/.gz/.csv/.stdout) and agent worktrees (.claude/) are gitignored as regenerable local artifacts — see memory + HANDOFF for the running findings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.4 KiB
AUDIT-062 regression check (Phase C+19)
What AUDIT-062 verified
AUDIT-062 (2026-05-12, dossier:
xenia-rs/docs/functions/sub_821CB030.md, memory:
project_xenia_rs_audit_062_worker_wake_gap_2026_05_12.md) located
the worker-cluster wedge to "the producer never signals the worker-
idle event". It explicitly RULED OUT the NtDuplicate aliasing as the
bug, citing the live ours-ntdup.jsonl trace:
ours DOES dup the wedge (kernel-aliasing hypothesis falsified):
--lr-trace=0x8284DF7Ccapturedtid=13 cycle=26711 r3=0x000012ac r4=0x40541E80(out_ptr). Per ours'scrates/xenia-kernel/src/ exports.rs:4263, NtDup aliases — dup_id = source_id = 0x12AC, refcount++. NOT a kernel bug.
The load-bearing invariant from AUDIT-062 is: signal-on-dup wakes wait-on-source.
Pre-C+19 mechanism: dup_id collided with source_id, so the same
state.objects entry was hit by both paths.
Post-C+19 mechanism: dup_id is a fresh slot mapped to source_id via
state.handle_aliases; every lookup through resolve_handle
canonicalizes to source_id, hitting the same state.objects entry.
Risk assessment
| Risk | Pre-C+19 | Post-C+19 |
|---|---|---|
| Signal-on-dup wakes wait-on-source | YES (id collision) | YES (alias canonicalize) |
| File ops on dup work | YES (id collision) | YES (alias canonicalize) |
| Thread suspend/resume on dup | YES (id collision) | YES (alias canonicalize) |
| Close-dup keeps source alive | partial (refcount sharing) | YES (per-slot refcount + canonical_slot_count) |
| Close-source keeps dup alive | partial | YES |
| handle.destroy emitted per slot | NO (one per object) | YES (one per slot — canary parity) |
Tests proving AUDIT-062 invariant survives
11 new unit tests in xenia-kernel/src/exports.rs::tests:
nt_duplicate_object_allocates_fresh_handle_id— dup != source.nt_duplicate_object_signal_on_dup_wakes_wait_on_source— THE AUDIT-062 REGRESSION GUARD. Creates an Event, dups, signals the dup, asserts source Event'ssignaled == true. If this test ever fails, the C+19 fix has broken AUDIT-062's worker-cluster wedge resolution.nt_duplicate_object_signal_on_source_visible_via_dup— symmetric.nt_duplicate_object_refcount_lifecycle— per-slot refcount = 1 for both source and dup; canonical_slot_count = 2; alias map hasdup → source.nt_duplicate_object_then_close_dup_keeps_source_live— close dup, source still live and signalable.nt_duplicate_object_then_close_source_keeps_dup_live— close source, dup still live and signalable (incl. signal propagation test).nt_duplicate_object_close_both_destroys_underlying— close both → object gone; canonical_slot_count entry pruned.nt_duplicate_object_with_close_source_flag— DUPLICATE_CLOSE_SOURCE atomically dups and closes source.nt_duplicate_object_invalid_handle_returns_invalid_handle.nt_duplicate_object_dup_of_dup_canonicalizes— transitive aliasing flattens to original source.nt_duplicate_object_works_for_semaphore— non-Event type works identically.
All 11 pass. Kernel tests: 193 → 204 (+11). Full workspace test suite passes.
End-to-end runtime verification
Direct inspection of ours-cold.jsonl at tid=1 idx=102553:
idx=102551 kind=import.call name=NtDuplicateObject
idx=102552 kind=kernel.call name=NtDuplicateObject
idx=102553 kind=handle.create name= (FRESH slot) ← C+19 NEW
idx=102554 kind=kernel.return name=NtDuplicateObject ret=0
The handle.create at idx=102553 is the canary-symmetric event that
was missing pre-C+19. Verifies the fix lands at the observable
boundary.
Conclusion
AUDIT-062's load-bearing invariant — signal-on-dup wakes
wait-on-source — is PRESERVED by the C+19 fix. The invariant
relies on canonical kernel-object sharing, which is now achieved
via the alias map rather than id collision. The mechanism shift
is observation-equivalent to upstream callers: they pass dup_id
to Nt*/Ke* functions; ours resolves dup_id → source_id at lookup
time; the same KernelObject::Event (or whatever type) is
mutated regardless of which slot id the caller named.
The pre-C+19 mechanism (id collision) is a special case of the
post-C+19 mechanism (alias map): if no dup_id is ever allocated,
handle_aliases.get(h) returns None, resolve_handle(h) returns
h unchanged, and every lookup behaves exactly as it did before.
No AUDIT-062 regression detected.