Win32 multimedia timer retained return ownership

Base: exact integrated main d550de85ae9b46d23314d164a932d66478894999. Original task branch: task/mm-timer-owner-recovered-oct21. The original task left independent acceptance pending. The dated addenda below record subsequent source acceptance, runtime qualification and integration; nonlocal jump APIs remain disabled.

Reproduction and correction

MM_OWNER_REF=d550de85ae9b46d23314d164a932d66478894999 node test/test-mm-timer-ownership.js --baseline compiles the actual producer, save/restore functions, token allocator and CACA000A return arm. After timer entry, it enters an unrelated child invocation and executes the return arm. The baseline accepts that wrong owner, clears the callback guard and restores the interrupted caller. This is executed source evidence, not a census label.

CACA000A now retains the allocated child identity, root and expected return ESP in emulator-owned records. Admission precedes producer stack, timer-slot, wait and guard mutations. fire_mm_timer admits before due polling because polling itself can consume and signal event timers. A poll without a function callback cancels its provisional child; it still spends one monotonic identity. Exhaustion traps without wrapping or consuming a timer. Production loaders must already have established the invocation root; admission does not lazily reattach an uninitialized or retired instance.

The 32 KiB appended region contains 64 records for each of 16 thread slots. Its allocation leaves every existing region in place. Per-instance root/depth and the shared invocation validator reject retired roots. Expected ESP rejects a malformed return before restoration. The outer dispatcher checks CACA000A before resource scopes, thunk hooks or other dispatcher bookkeeping; the return helper checks again before popping the child. A rejected owner is not unwound.

Producer / return matrix

Producer Continuation and retained state Qualification
fire_mm_timer CACA000A; expected ESP = interrupted ESP − 40 Admission before due poll/consumption; one-shot, periodic, no-due and event routes exercised
DispatchMessageA MM_TIMER arm in 09a5-handlers-window.wat CACA000A; expected ESP = API-entry ESP − 32, accounting for completed RET 4 frame Admits before the handler changes ESP/EIP; message-carried callback/id/user survive already-retired one-shot slot
fire_audio_callback_bound, through wave/MIDI wrappers CACA000A; expected ESP = interrupted ESP − 40; record marks audio ownership Existing immutable registration, wait and saved-carry behavior preserved; no audio lifecycle redesign
mm_timer_thread_step CACA003B; existing pinned loop ESP Root validation and refusal to discard any live CACA000A record added, also at dispatcher entry. This does not claim retained child ownership for the dedicated thread's own CACA003B callback lifetime

Nested DispatchMessage timers have separate records. Their returns restore the prior active guard/resume reason and cannot restore an outer audio wait. Only the record that admitted audio calls wave_callback_restore_wait. Audio may interrupt a dedicated timer-thread callback, finish CACA000A, then allow that thread to continue through CACA003B. Neither continuation can consume the other's live save frame in the tested routes.

Validation

The tests use real production WAT slices with identity guest translation and modeled stdcall RET 20, not an x86 interpreter. The DispatchMessage test extracts its exact MM_TIMER arm; admission snapshots start at that producer boundary, not at the outer ordinary-API dispatcher bookkeeping. Return tests execute the actual early dispatcher preflight and actual continuation arm separately, not the entire dispatcher. Host queue/scheduler tests use their existing modeled transport environment. Full-build/browser/game/Windows behavior, CACA003B's independent retained lifetime, other deferred families and nonlocal jumps remain unqualified. The two existing full-module timer tests were adjusted to establish harness roots/owned frames but were not run under the no-full-build constraint. The full-module wave-wait test was also not run.

Evidence and recovery

Shared evidence: scratch/runs/20261021-mm-timer-owner-recovered/, including admission, baseline/final logs, result.json and publication.json. The read-only git-source-overlay.cjs supplies missing tracked files from exact HEAD for canonical scanners on this sparse checkout; actual edited files take precedence. It does not rewrite source or suppress a gate. Re-run on a complete checkout without that preload, or use NODE_OPTIONS=--require=<overlay> for the census test's child scanner.

The checkout uses a local Git object alternate to the verified source repository. The published task branch, based on the stated main commit, is the remote recovery source; no ignored binary or full build is required for these source-slice tests. No boxes were assigned, started, resumed, stopped or deleted; runtime lifecycle remains coordinator-owned.

Independent source acceptance — 2026-10-22

Scoped SOURCE ACCEPTANCE of exact candidate ce4ce4f95eacaedff1b3e386f3ce3ae5d85bd16c against d550de85ae9b46d23314d164a932d66478894999. No concrete source defect found within this review. This is not permission to merge or runtime qualification. Remote candidate matched; remote main bf99a428500d47ecacd3de2e86cc47cb0dfaacef differs from the base only in ops/COORDINATOR.md, ops/README.md, and ops/TELEGRAM.md. Those audit/policy documents were reconciled read-only; no candidate source was replaced by main.

Reviewed the actual changed WAT and all CACA000A producers, including DispatchMessageA and the outer dispatcher. The private record retains child, root, expected ESP, prior guard/resume and audio ownership; invocation_leave returns to the retained child's parent stack entry. Root/epoch/slot validation precedes record access. Explicit loader/thread initialization establishes roots; fresh slot reuse retires the old instance. Frame indexing is (tid-1)*2048 + depth*32 within 32768 bytes for 16 slots. The invocation stack permits 63 live children (root consumes its first entry), tighter than the 64-record allocation. No guest-provided record pointer is trusted.

Admission precedes producer stack, timer consumption/event signaling and audio wait changes. DispatchMessage admission is at its MM_TIMER arm, not before all ordinary API dispatcher bookkeeping; the latter is not claimed transactional. The separate CACA000A return preflight is before dispatcher effects and the finish helper revalidates before popping/restoring. No-due/event polls release their provisional child. One-shot and periodic consumption remain unchanged. Nested DispatchMessage records restore outer guard/resume without restoring an outer audio wait. CACA003B refuses a live CACA000A owner before its pinned-stack reset; its independent callback lifetime is still outside this acceptance.

Validation on Node v24.18.1:

The independent fixture stubs resource cleanup, host services, thunk membership and branch/cache dispatch; identity guest translation remains. It closes the specific modeled-RET/separate-dispatch gap for these routes, not the complete interpreter integration gap. Canonical/full-module timer and wave-wait tests, full build, real guest translation/fault handling, browser/game/runtime tests, and CACA003B's own retained lifetime remain outstanding. Coordinator owns that qualification decision and any eventual integration; NO MAIN MERGE here.

Canonical build and full-module qualification — 2026-10-22

SCOPED ACCEPTANCE of candidate ce4ce4f95eacaedff1b3e386f3ce3ae5d85bd16c: the canonical build and the three previously unrun full-module regressions pass. This addendum follows independent source acceptance f7cc26c5433859ebf986fff30ce4a28f7224e15a; it does not repeat that source review. No producer, canonical gate, existing test, or main branch was changed.

Evidence: scratch/runs/20261022-mm-timer-canonical-qualification/ on the main host and this task branch. qualification-artifacts.tar.gz retains both shipping modules, the three instrumented test modules, logs, test fixtures and first failed attempts. Archive SHA256: 3e308c0012e996070a01c1f77d6f399e9aaf7adb2d2620b27af018a80d29e248. See RECOVERY.txt, result.json, source-manifest.json, supplemental/fixture manifests and logs/. The compressed artifact is explicitly committed, rather than relying on ignored build files surviving the box.

Source, build and loader identity

No reusable module existed in the candidate checkout, acceptance checkout or main-host build directory; the preceding reports explicitly left builds unrun. Exact candidate Git blobs were archived without Git history or the irrelevant binary corpus. The remote staging repository retained the real candidate commit, trees and API-table blob, so the append-only gate checked against actual HEAD. Final verification checked 5,625 tracked files plus the existing ignored test/binaries/notepad.exe fixture: zero SHA256 mismatches. The fixture was copied from the main host, without downloading or restoring archived assets.

All compilation and execution used /home/user/timer-qualification on assigned bx_pfv6b6wu, Linux x86-64, Node v24.18.1. Node binary SHA256: f3432a45b03b2da0d270095fdd8813dc34cbea73f5fc8b18c7a384b7cf9b333a. Dependencies were installed with npm ci --omit=dev --omit=optional --ignore-scripts --no-audit --no-fund (exit 0: pngjs@7.0.0, wabt@1.0.39). No WINE compiler/layout overrides were set.

bash tools/build.sh exited 0, every canonical gate enabled, including compiler provenance, layout checks, actual Wasm validation and compiled data overlap checks. logs/build.log is the complete successful log. Layout hash: 9a3bc9149d58209c; 324 data segments, no overlaps.

Module Bytes SHA256
Shipping build/wine-assembly.wasm 1,878,971 f644bbb26edc077239dac17b8b2cfda482aafd9eb44af4c4776d4fe153d5a1a7
Shipping compatibility module 1,881,544 9ad1d087170b755519a6b35f6dcb4fd5df3b729500a26deb3d0882f27cb691b7
Timer callback test module 1,880,090 6c8eec2c429be82d834a50bdf2a2585ba17adf4a636081b2bafb7326761bac1c
Timer thread test module 1,879,367 cd1a1505b4c61e329f855f4dba58d45c511f863d4307ea0d5fadcb768ab83e9d
Wave-wait test module 1,879,109 9e4cc591e8717ae8bf42d4af9ad0f7aefb136898fd1ed4da66e514542b1da3ff

The existing tests compile the entire src/main.watx closure through test/compile-src.js and the canonical WATX compiler, appending test-only exports to 13-exports.wat; they do not load the shipping file verbatim. The evidence observer verified this append-only transform and recorded the exact bytes passed to real WebAssembly.instantiate, matching each compiled hash above. No source-slice surrogate or replacement interpreter was used.

node evidence/full-closure-identity.cjs exited 0 (one assertion). The untouched test compile is 1,878,934 bytes, SHA256 03af1aa4d94cd3ccee5b757e439e76830a8ab89ee1308648235ab32947f9b818. Applying the same appendSection layout stamp as tools/build-compile-wat.js makes it byte-identical to the shipping module; the difference is the 37-byte wine-region-layout custom section, with no executable-section changes.

Executed regressions and limits

Commands below ran from the remote staging root. Each command exited 0; assertion counts are dynamic counts of the original Node assertions, not source line counts. Per-test .exit, .log and .identity.json files are in logs/.

Command Passing assertions
node --require ./evidence/observe.cjs test/test-mm-timer-callback.js 28
node --require ./evidence/observe.cjs test/test-mm-timer-thread.js 22
node --require ./evidence/observe.cjs test/test-wave-callback-wait.js 23
node evidence/canonical-wave-owner.cjs 26 across 3 cases

The callback suite covers event set/pulse, one-shot retirement, periodic phase, parked-wait entry/return, CACA000A completion, DispatchMessage argument/frame restoration and ordinary thread-message rejection. The thread suite covers deadlines, main-thread suppression, timer-thread callback arguments, loop ESP pinning and idle/deadline sleeps. These two suites use explicit harness setup exports; their return setup is not a Windows callback experiment.

The applicable audio regression is test/test-wave-callback-wait.js. It loads the existing PE fixture, runs real guest INC/RET20 through the full interpreter, rejects unsupported/nested injection, restores the parked wait tuple, and preserves TLS/FS. It explicitly does not qualify Worker delivery or real semaphore behavior.

The additional narrow canonical-wave-owner.cjs fixture directly reads and instantiates the unchanged shipping module (no compile or added WAT). It executes real guest INC/RET20 for normal parked wave return and two stale return cases: changed shared root and changed process epoch. Both stale cases trap; the callback remains active, the parked wait is not restored, and the 40-byte saved caller frame is not popped. Guest RET20 itself first advances ESP by 24 bytes; validation is at the CACA000A continuation boundary, not before the guest instruction's argument pop. This adds full-interpreter root/epoch evidence without claiming the entire prior source-slice adversarial matrix was rerun.

CACA003B's independent retained callback lifetime remains unqualified. The passing thread regression does not change that limitation. Compatibility Wasm was built/validated by the canonical gates but was not used for these runtime regressions. No Windows, browser, game, fleet or public-deployment acceptance is claimed. No main merge was performed.

Preserved setup and qualification failures

The first canonical attempt exited 1 because staging omitted tracked docs/dos-corpus/live/toyvm-bundle.js and toyvm-jit-bundle.js. Exact candidate blobs were supplied; the complete unchanged build then passed. The first wave-wait attempt exited 1 with ENOENT for the ignored Notepad fixture, before any assertion; supplying the existing fixture made the unchanged test pass. No canonical gate was bypassed and no production fix was needed.

The initial identity check incorrectly required equality before adding the shipping metadata stamp. The optional canonical fixture initially expected load_pe to return 1 (it returns the entry address), then incorrectly expected the guest RET20 argument pop to be rejected. Both fixture expectations were corrected to the documented API/continuation boundary; all original scripts, failed logs and exit statuses are preserved. These were evidence-harness failures, not silently discarded production failures.

The first local archive selection accidentally included encoded DOS corpus JS and produced 78,226,630 bytes, exceeding the 64MiB allocation. It was immediately deleted without transfer/use. The replacement archive is 28,009,389 bytes; allocation.json records this workflow violation and subsequent measurements. Fresh free-space admission passed throughout. SCP was denied with api_key_action_forbidden (SSH unavailable); staging/retrieval used the explicitly assigned, authorized Boat exec API, without SSH/config changes.

The assigned box remains running (Boat state idle, health ok), expiry 2026-10-22 02:53:01.742 UTC; final measured free space was 48,878,579,712 bytes. The coordinator retains shutdown ownership and the 02:43 UTC checkpoint. No worker/box creation, stop, resume or snapshot action was issued by this task.

Historical source-review resources (before canonical qualification)

The following paragraph describes only the earlier source-review task, not the canonical qualification or integration task.

Review branch: task/mm-timer-owner-release-review-oct22. Evidence: scratch/runs/20261022-mm-timer-owner-release-review/, including admission, seal verification, successful and failed gate logs, result and publication receipts. Exact non-cone manifest projection was 36,551,511 bytes plus 16,777,216 reserved bytes, below the 64 MiB total allowance; fresh admission was 8,321,327,104 bytes against 2,982,150,144 required. No 16 MiB checkout limit was applied. No workers, boxes, build, browser, dependency installation, fleet, deployment or laptop actions; no assigned box/expiry, lifecycle stays with the coordinator. Candidate and root WIP/index/config/TODOS/STATUS were not edited.

Independent qualification acceptance and integration — 2026-10-22

ACCEPT within the stated scope. Independently reviewed qualification commit a2afc1797ae782a4c53e1799ecc1dd12a7871c03, reusing accepted source review f7cc26c5433859ebf986fff30ce4a28f7224e15a for production candidate ce4ce4f95eacaedff1b3e386f3ce3ae5d85bd16c. All 65 qualification seals match both the committed and main-host evidence. The 5,623 initial manifest blobs and two supplemental tracked blobs match the exact candidate; the ignored PE fixture remains separately identified. All five archived module hashes match receipts.

Reviewed the actual observer, test transforms, compiler closure, canonical owner fixture, full-closure identity check, successful logs and preserved failed attempts. The observer delegates assertions and instantiation to the originals; the inspected test transforms only append exports and the compile helper updates all three VFS aliases. This observer is evidence for these inspected tests, not a general detector of arbitrary VFS substitution. The timer suites exercise the full compiled module with explicit harness setup and modeled RET stack advances; the wave-wait and shipping fixtures execute real guest INC/RET20. Assertion counts 28/22/23 describe those suites, not 73 independent end-to-end scenarios. The unmodified full closure equals shipping bytes after the canonical 37-byte layout stamp, so the instrumented modules are correctly distinguished from the shipping module rather than mislabeled as identical binaries.

The corrected RET20 expectation does not conceal a producer defect: the guest instruction pops its return address and five arguments before reaching the CACA000A validator. Stale root/epoch rejection must preserve the remaining 40-byte saved caller frame and active owner, without restoring the parked wait. The preserved failed expectation asked for rejection before that guest pop. One fresh replay on the same box passed all 26 assertions across normal, stale-root and stale-epoch cases using the unchanged shipping SHA256 f644bbb26edc077239dac17b8b2cfda482aafd9eb44af4c4776d4fe153d5a1a7 and sealed fixture. No build, dependency installation or packaging was repeated.

Reconciled remote main bf99a428500d47ecacd3de2e86cc47cb0dfaacef: its only changes from the production base are the three ops documents, preserved exactly. The integrated production trees remain identical to tested candidate source. Fresh manifest, fragment-balance and diff checks pass; canonical build and full-module results are reused at that exact source identity. Integration branch: task/mm-timer-qualified-integrate-oct22; receipts and publication identity: scratch/runs/20261022-mm-timer-qualified-integrate/.

This acceptance does not qualify CACA003B's independent retained lifetime, Windows/browser/game behavior, Worker semaphore delivery or compatibility-module execution. The API jump lock is unchanged. The prior 78,226,630-byte archive allocation violation remains disclosed above; this task uses individual sparse paths with a measured 41,540,061-byte projection plus 16 MiB reserve under the 64 MiB cap. No full-goal completion is claimed.

Box bx_pfv6b6wu remains running (idle/health ok, exec reachable), expiry 02:53:01.742 UTC, coordinator shutdown checkpoint 02:43 UTC. Shutdown ownership is returned to the coordinator; this task issued no lifecycle mutation.