Scoped callback lifecycle integration

Based on 8793e9f4722569602c59d523364b4bb86784d8af, preserving the current native lifecycle implementation. The first checkpoint, 622fa023d, added the primitive below. The subsequent integration connects it to the actual Worker and local callback endpoints. No native or Invisible War compatibility result is established by these source fixtures.

ProcessBoot.beginScopedLibraryYield admits yield 3/5 with the nonblocking callback gate. Busy admission does not consume the unload intent or yield. Admission captures take_dll_unload_kind once; load lookup owns a beginDllLoad provider snapshot across byte acquisition. The caller supplies bytes once with prepare, then advances one cooperative callback slice per turn. Mapping and initialization reuse completeLoadLibrarySteps, including native complete_dll_initialize, temporary references, failure rollback and mapping-pin release. FreeLibrary, FreeLibraryAndExitThread and deferred drain reuse the existing reservation/retirement generators. Results retain mapped and retired generation-bearing events for later host publication.

Scoped DllMain uses the existing register/SEH continuation and native completion path with stricter sentinel checks: an actual nontrapping halt 2 at EIP zero, restored stdcall ESP and no yield. Traps at the sentinel cannot publish LIVE. Ordinary loader compatibility behavior remains unchanged. Cooperative Sleep consumes its notification once and advances the supplied guest clock.

Cancellation is an endpoint operation before instance retirement, not a host-side recovery algorithm. It checks the same instance's native owner, releases provider pins and gate depth, rolls back unstarted image ownership, and leaves an active mapping quarantined or a detach group reserved. The numeric native owner alone does not establish endpoint generation identity. The transport now fences loader commands by exact frame/epoch and WorkerLink lifetime identity. Local commands are bound to their owning host instance. The Worker verifies that lifetime identity before admission or retirement; the transaction also verifies the native callback owner before cleanup.

Validation performed without a full build/VM:

Endpoint integration

Yields 3/5 now use scopedThreadSendLoader. Admission retains the operation before host byte lookup; stale epochs reject and nested frames defer. Mapping delivery is acknowledged before the callback driver advances the retained transaction. Retries cannot map twice or consume unload intent twice. DllMain waits, nested sends and nested loaders keep their own stack and gate depth. Abort cannot discard a captured lookup operation.

A separate publication epoch retains mapped/retired events and thunk state. Both CLI and browser use publishScopedDlls/registerMappedDll, checking native generation before resource registration and preserving replacement-generation resources on delayed retirement. Terminal unload publishes its retirement events before the driver reports yield 2; it never resumes the exited caller. Publication acknowledgement drops prepared byte buffers. A subsequent epoch evicts completed same-frame receipts; driver exit clears the resolver map.

Worker stop rejects new commands and requests retireScopedLoaders on its original lifetime identity. Retirement cancels transactions in reverse nesting order and retains its acknowledgement for duplicate requests. Lost replies get one retry; exhausted timeouts keep the Worker alive and return an error. A later explicit stop retries that identity. Lookup pin cleanup failure keeps the gate owned and can be retried after the invariant is restored. A callback or generator cancellation exception is permanently retained: a closed generator must not turn irreversible partial cleanup into successful acknowledgement. Local stop applies the same transaction cleanup before dropping frames.

Dependency API-name inspection requires guest_free, attempts it exactly once even if writes or API inspection throw, and preserves both errors if cleanup also throws. A retained scope fault prevents duplicate allocation/free.

test/test-thread-send-scoped-loader.js runs actual guest-worker.js, WorkerLink, local endpoint, host driver and ThreadManager against an inspected tiny Wasm fixture with a simulated lifecycle ABI. It does not compile the full source or execute x86. It passes nested/delayed/stale operations, missing dependencies, native-completion transition calls, FALSE/trap/partial-map quarantine, COM cleanup, all three unload intents, lost mapping/publication replies, generation-bearing publication and owner retirement. Actual Worker lost/duplicate retirement replies, timeout retention/retry, stale lifetime rejection and failed pin cleanup are covered. Worker/local irreversible generator failure remains a failure after metadata is restored; only the deliberately poisoned test fixture is forcibly disposed with its private memory.

Endpoint negative controls fail when top-frame ownership, epoch comparison, retirement acknowledgement, retained cancellation failure or inspection free is removed. Exact source is restored after each mutation. Related scoped yield/wait/frame suites, both wait reproductions, broker ownership, scheduler (56 checks), browser cache graph, test tiers and syntax/whitespace pass.

Remaining validation: run the prepared native scoped test plus the native same-module, concurrent mapping, lifecycle races, Worker message lifecycle, import graph, terminal lifecycle and process-send runtime suites listed in iw-current-lifecycle-port-20261015.md under a separately granted runtime slot. Native two-Worker scoped races and original Invisible War still require runtime evidence. No merge to main, Boat, browser, full build or native execution occurred.