Callback ownership prerequisite: source inventory

2026-10-18. Implementation checkpoints for the CRTDLL nonlocal-jump prerequisite in crt-setjmp.md. The first adds an enforceable source inventory and mutation tests. The second authors private token state and synchronous hooks, described below. Neither implements _setjmp or longjmp. The original Circus Linux failure is unchanged. A later validation grant covered the normal build and focused native tests below; no game or browser was run.

node tools/callback-ownership-census.js checks the maintained inventory in tools/callback-ownership-census.json. tools/build.sh runs the check after fragment validation. The initial inventory contains 363 records:

WAT records pin normalized AST bodies, including operand order, exports, boundary calls and EIP target expressions. Macro and top-level EIP writes are included. Changing a callback target while keeping the same depth/write count fails. Adding, deleting, or duplicating a record fails. Changing pending to covered also fails: no evidence of ownership coverage exists yet.

The categories are conservative triage, not per-site ownership certification. In particular, mixed-dispatch includes callback entry, completion, yielding, termination and ordinary redirects. The 64 writes in win32_dispatch_inner remain one body pin with 64 separately recorded target expressions; the CACA continuations still need individual retained-context lifetime review.

The host scan finds ordinary dot calls, bracket-property strings, destructured aliases and direct-call exported wrappers. It does not prove exhaustive JS coverage: computed property names, dynamically synthesized code, aliases without a literal export name in the scanned file, and indirect WAT function table edges need a separate audit. Production scanning covers root JS and recursive lib/, plus src/*.wat and src/*.watx; CLI tools/test harnesses outside those paths are not host coverage. Every unknown/new source boundary that the scanner does detect is rejected instead of silently accepted.

The gate also holds a temporary prerequisite review lock on adding the two nonlocal API rows/handlers. This is deliberately not runtime safety and must be removed/replaced as part of the actual ownership implementation with its conformance tests. It prevents a first-return-only stub or _setjmp3 alias from accidentally presenting this inventory as completed jump support.

Maintaining the inventory

Run the gate after changing a boundary. Inspect the changed function's entire context lifetime or the changed host file, then update only its corresponding record. --dump emits entries marked UNREVIEWED; it is not a bless/record command. Comments and formatting in WAT do not change its AST digest, while JS files are deliberately conservatively pinned byte-for-byte. Changes to shared host imports, API dispatch, or CRT callback functions may require coordination with other task branches before updating their records.

Source-only validation performed: mutation tests, current-tree census gate, JavaScript syntax and git diff --check. The tests exercise target replacement, unknown/stale/duplicate entries, false coverage assertions, nested comments, strings, macro/top-level writes, exported wrapper reachability, bracket and destructured host calls, reordered host restoration, and nonlocal API aliases. These tests validate the checker, not emulator execution or invocation safety.

Remaining implementation acceptance

The token implementation below has focused native validation. Instrument and test all remaining context producers beyond synchronous nested execution: retained CACA contexts and chained callbacks, JS COM, cross-thread SendMessage, timer/wave/APC entry and completion, and abort/exception/destruction paths. Ordinary guest calls and run-budget slices must preserve identity. Returned callbacks must restore the parent's identity; equal-depth sibling callbacks must receive different identities. Reject stale process/thread-slot owners, SEH and unsupported callback state before any mutation. Never recycle IDs on exhaustion. Depth alone and guest-stack CACA scanning remain insufficient.

Only then implement the saved-buffer registry and real pair, including the per-call dispatcher disposition that bypasses nonvolatile register restore only after successful validation. Execute actual x86 through decoded CALL and direct thunk dispatch, covering nonvolatile registers, exact ESP/EIP, zero to one and negative return values, multiple buffers, threads, SEH, forged/changed buffers and no partial mutation on rejection. The broader design remains in crt-setjmp.md; this inventory does not supersede or complete it.

Private token state and synchronous hooks

The delimited CALLBACK INVOCATION OWNERSHIP block in 04-cache.wat now owns an 8,464-byte allocated private region. A shared unsigned 64-bit CAS allocator issues nonzero identities without wrap; its all-ones value is terminal. Neither process reset nor thread-slot reuse clears that allocator. The shared process epoch and fresh thread root make a retired instance fail validation before its next token operation. Each of sixteen slots (main plus fifteen workers, matching the current scheduler) holds a root owner and a 64-entry invocation stack.

PE and standalone NE process loaders establish a new epoch/root; init_thread establishes a fresh root in the existing epoch. A pristine standalone instance can bootstrap a vacant slot for helper tests; it cannot steal an occupied slot. The instance captures its epoch, root, and thread ID separately from sync_msg_depth. A synchronous enter allocates a fresh child; leave checks the expected top identity and restores the parent by popping. The production sync helper supplies its current child to this pop; retained callback contexts still need their own explicit expected-token storage in later milestones. A trap does not silently unwind or clear tokens. Explicit process/thread replacement retires them. Ordinary run-budget slices never change these identities.

The existing sync_depth_enter/leave pair calls these hooks before changing its depth/spill bookkeeping. This covers that helper family, including its cross-thread send bracket, but does not certify the producer's earlier argument pushes or any uninstrumented continuation. Token stack capacity is 63 nested children; overflow/underflow, wrong-thread access, stale process/slot owners and token mismatches trap. Process loads and slot reuse retain the existing host requirement that old execution is quiescent: this patch does not make a loader or thread reinitialization transaction safe against a concurrently executing old instance. The allocator itself uses atomic CAS for worker concurrency.

test-callback-invocation-tokens.js compiles the actual token block and actual sync helpers in a tiny shared-memory module. Authored assertions cover nested and sibling identities, expected-token mismatch, independent depth, multiple instances, thread-ID mismatch, occupied-slot bootstrap rejection, slot and same-memory process reuse, aborted child retirement, stack limits, allocator exhaustion, and copied before/after state on rejection. Existing NE helper fixtures now include the real token block; the arena fixture uses a real paired enter instead of fabricating depth for its final leave. These compiled regressions passed under the subsequent grant.

The census now has 374 records. Its reviewed delta is only the ten private primitive functions, two sync helpers, PE/NE loader hooks, and init_thread. It also detects bare writes to private invocation globals, so a new reset site cannot disappear from the inventory. The temporary nonlocal API review lock remains. Deferred CACA, JS COM, timer/wave/APC and all failure paths still need their explicit ownership implementation and actual guest conformance.

Source-only validation: fragment balance, manifest, region declarations and owners, regenerated region-map mirror, JS syntax, census mutation tests and current-source gate, and diff checks. A separate token fragment was avoided because 127 existing fragments plus main.watx already consume the compiler's 128 source-location slots; the token block stays in the existing cache fragment.

Granted native validation

The normal gated build and the following tests passed on source checkpoint 2fdafb67600bb2afdefaeb896b44ac2c5abc513c (merged current main's descriptor prerequisite). The resulting candidate is 1,867,397 bytes, SHA-256 e8be19c496d4bf44a4925045976b4ca1d3cfe6d3126c15751ee839096a996dfd. The canonical artifact remained 2f73127d24220248885e32c886538d236a68b04ae6ce7dec7b5fb8a012182166.

The first two build attempts stopped for absent sparse-checkout source files (test/run.js, then compiler provenance tests); a first NE module test stopped for its absent image fixture. Their failed receipts/logs are retained. Restoring only those exact dependencies allowed the unmodified normal gates and tests to pass; no gate was bypassed. All child process groups were absent at exit, no disk/time/log guard fired, and canonical SHA-256 checks stayed identical.

Evidence is retained under the task worktree's scratch/runs/20261018-callback-token-validation/: command-specific JSON receipts/logs, guard.js, and artifacts.json. Admission required strictly more than 2 GiB + 128 MiB free; the running stop threshold was 2 GiB + 64 MiB. The minimum sampled successful-test free space was 2,286,010,368 bytes. The guard also bounded each process group to 180 seconds and logs to 8 MiB. Sampling does not establish a continuous filesystem minimum between checks.

source-sparse-patterns.txt in that evidence directory records the exact build checkout closure before narrowing. To reproduce, restore those patterns with git sparse-checkout set --no-cone --stdin, then run bash tools/build.sh and the five tests above under the same admission/stop guards. Preserve the ignored candidate/compat wasm and logs separately: Git only backs up source and this recipe. The completed task checkout was narrowed again to its changed files to recover disk for the next worker.

Allocator concurrency was subsequently exercised by distinct Node OS workers using the extracted production routines and shared memory; see the bounded concurrency qualification. Remaining limits: process/slot resets still require host quiescence. Deferred/JS/timer/wave/APC ownership, matched retained expected tokens, actual dual-dispatch x86 jumps, SEH and saved-buffer validation remain outstanding. The API review lock stays.