LockFile implementation checkpoint

Baseline: origin/main 3c791a0231267d4bd5a2db3160ad830c40a869da. Original run 20261019-madbomber-dbcs-retry stops when the KERNEL32 LockFile name lookup returns NULL. API IDs 453 and 711 already exist.

The implementation must enforce locks, not just expose the name. Proposed ownership is a stable file entry plus a separate open-file owner shared by DuplicateHandle. Independent opens, including adopted child filesystems, have separate owners. Closing the final duplicate removes its locks. Process teardown closes all disk handles before shell snapshot handoff; adoption itself must not release the still-running parent's locks.

Store unsigned offsets and lengths as BigInt. Reject ranges extending past 2^64, support ranges ending exactly there and locks beyond EOF. Unlock must match one exact original region. Conflicts report ERROR_LOCK_VIOLATION (33), missing unlock reports ERROR_NOT_LOCKED (158), invalid handles report 6. Ordinary reads/writes through a different owner fail before modifying file position, bytes, guest buffers, or invoking lazy providers. Scatter reads must preflight the whole request. Memory mappings are exempt, per Win32.

Necessary prerequisite: handles currently resolve mutable path names on each operation. Retain the opened entry instead; CREATE_ALWAYS must truncate that same entry, and a rename/replacement must not retarget an existing open. File entry references and lock state must remain out of serialized artifacts.

Conformance cases: exact/partial/adjacent overlap, repeated lock, exact unlock and wrong owner, duplicate access and final close, close-source, zero-byte ranges, offsets above 2^53 and 2^64 overflow, EOF, lazy provider noninvocation, append writes, scatter read nonmutation, rename/recreate, parent/child adoption, process teardown, and KERNEL32 export/stdio cleanup. Source/JavaScript tests run locally only; compiled guest and real Worker validation require a separate coordinator grant. No gameplay claim.

References:

Source implementation and validation

Implemented per-entry WeakMap lock tables and per-open owner tokens. DuplicateHandle shares a token only for the current process; cross-process disk transfer was previously an incorrect local alias and now fails error 6. Pipe, console, mapping and other-object duplicate dispatch stays separate. The existing process validator accepts -1 and real current-process handles. Disk inheritance is not implemented by adoptFrom; child opens have independent owners. CRT _dup carries the new argument; _dup2 remains unimplemented.

Pending host reads reserve their full range across provider yields; conflicting locks fail while that operation is outstanding. Completion, failure, close, thread retirement and process teardown release reservations. Process exit and WineAssembly.stop close disk handles and reject late opens, so a retiring Worker cannot reacquire a lock. Adoption does not close a live parent's handles. Mapped views bypass byte-range locks.

KERNEL32 names use ordinals 591/592 after the coordinator's reserved 588..590. API IDs 453/711 remain unchanged. Loader image and 281-import mirror updated.

Passed without building wasm:

An initial unfiltered lazy suite could not load four sparse dependencies (including the compiled Worker closure helper), before compilation. Two old path-replacement tests expected an open handle to retarget a new file. They now assert retained opened-entry identity and pass. The filter permits explicit source-only runs; the normal full suite still runs every case.

test/test-lockfile-handlers.js is authored and syntax-checked, not run. It covers BOOL/last-error/ESP, high offsets, DuplicateHandle and CRT _dup. Full build gates, real Worker RPC, public GetProcAddress execution, native Windows zero-byte/duplicate edge comparison, and original Mad Bomber replay remain coordinator validation work. No gameplay or runtime compatibility conclusion follows from the JavaScript checks.

Independent review corrections: unsupported cross-process duplication now passes through CLOSE_SOURCE cleanup on failure, releasing the lock only when no duplicate remains. FlushFileBuffers validates the retained opened entry, so rename no longer makes a valid writable handle fail. Focused regressions cover both final-owner and surviving-duplicate rejection, renamed flush, and closed renamed handles. No native execution was added for these corrections.

Current-main RTC reconciliation (2026-10-19)

Merged verified origin/main 462f44b2f into this isolated task branch. The clean text merge retained a stale generated import count of 282: the complete current include closure has 283 with both file locking and CMOS RTC. Regenerated the mirror using tools/gen-host-import-sigs.js and verified --check.

Focused file-byte-lock ownership/range/lifecycle/I/O tests passed; VFS 46/0, VFS adoption and CMOS host/broker/browser-import source contracts passed. Initial checks encountered absent sparse-checkout tools and the RTC test's dib module dependency; expanded tracked lib/src closure and reran successfully. These are JavaScript checks, not native, OS Worker, browser or actual Mad Bomber validation. Native handler and original-game qualification remain pending under the one-runtime scheduler. No canonical module or original binary was changed.

Surface4/7 main reconciliation and bounded qualification plan (2026-10-19)

Merged main 0ace954f93b5d7107fd1de1d61601a54609e4e5e, then the narrow debugger-arm main update 6cfc0d936b2d44b4e6de69002f0aa8f9f203f61c in the isolated task/lockfile-rebased-oct19 worktree. Regenerated dispatch, API hashes, KERNEL32 image and import mirror; regeneration introduced no additional diff. All 4590 main API rows remain identical, including the nine appended Surface rows. LockFile/UnlockFile keep API IDs 453/711 and export ordinals 591/592; the combined mirror remains 283 imports.

Source-only checks passed: file-byte-lock regression, VFS 46/0, adoption, filtered lazy VFS 47/0 (same four compiled/Worker/browser exclusions above), CMOS host/broker/browser-import contracts, API append-only and all four generator freshness gates, hash lookup, 127 fragment balances, handler ESP, logical-and normalization, Surface 78 call sites/UUID identities/fail-fast, and 2025-test manifest membership. Review retained the existing owner-token, entry-identity, failed-close-source and renamed-flush corrections. Pending scatter reads rewind to their original position before parking, so the wrapper reserves the full request again on retry. No new production defect was found in this bounded review. Native zero-byte and duplicate semantics remain an explicit conformance gap, not a result inferred from these tests.

Also verified all 140 retained original fixture file lengths and SHA-256 hashes against launch-manifest.json, and the main debugger-arm source regression (node tools/test-debugger-watch-arm.js /tmp/wa-lockfile-rebased-oct19).

The handler fixture now accepts --compat and omits fonts because it exercises only file ABI. Syntax checked; neither mode has been executed. Proposed serial qualification, requiring a fresh coordinator runtime grant:

  1. Pin this task HEAD, all source inputs, existing canonical modules and the original fixture manifest before building. Use isolated outputs only; admit each job with enough reserve to keep at least 2 GiB free, stop on floor/log cap breach, and verify source/original pins and absent process groups after each job. No broad cleanup or canonical module replacement.
  2. Run normal build gates and build in the isolated worktree (120-second hard bound). Then run node test/test-lockfile-handlers.js and the same command with --compat, separately (60 seconds each, 2 MiB logs). These test direct handlers/ESP/error values, not public name resolution or OS Workers.
  3. Run the existing lazy fixture with --filter=^two real Workers preserve I/O ownership (90 seconds, 2 MiB log) against the combined source. This exercises real Worker adapters but does not yet assert LockFile RPC itself; a dedicated six-argument fs_lock_file Worker contention/unlock and five-argument duplicate fixture is still needed before claiming Worker lock qualification. Public GetProcAddress execution also remains a required check.
  4. Only after those gates, replay original Mad Bomber using the complete argv and 140-file manifest retained in /home/user/wine-assembly/scratch/runs/20261019-madbomber-dbcs-retry/. Original root is scratch/runs/20261018-madbomber-source/original/madbomber-0.2.5/; preserve all DLL seeds, data mounts, guest C:\MADBOMBER path and original bytes. Archive SHA-256 is f21ab59154fd434f0952daf84848af47bf50262b7317f2ca1dbf7f43cb213164. Reverify manifest hashes; replace only worktree/module/output destinations and add LockFile/UnlockFile to the bounded API trace. Retain the prior 10-second guest, 15-second normal quit, 25-second hard stop, 2 MiB log limits and 2214592512-byte stop floor. Write a new run directory and result last. Success requires observing name resolution and progress beyond the former missing-LockFile stop; menu/gameplay/browser evidence remains separate.

No build, native fixture, Worker, browser or original executable was run for this reconciliation. No claim of gameplay or merge readiness follows.

Source-ready public export and Worker qualification

The pending test gaps above now have authored fixtures, still not executed:

Microsoft's LockFile contract documents exclusive nonblocking locks, second-open denial, beyond-EOF ranges and mapping exemption; it does not specify zero-length overlap behavior. Wine's Windows conformance test, test_LockFile is the explicit zero-length oracle consulted on 2026-10-19. Its unconditional checks cover successful zero-length lock/exact unlock and a left-adjacent nonzero range. Interior/right overlap denial is conditional on the detected limited_LockFile behavior of older Windows. The native fixture therefore asserts only that unconditional subset. The JavaScript regression's stronger point-overlap policy remains the candidate's chosen behavior, not a universal Win98 compatibility claim. No local native-Windows comparison has occurred.

tools/run-lockfile-native.js supersedes the earlier prose-only gate sequence: normal build (120 seconds), handler/public-export tail and compat (60 seconds each), then real Worker tail and compat (45 seconds each), serially. Every job requires free space strictly above 2852126720 bytes; monitoring stops at or below 2214592512 bytes. Logs cap at 2 MiB each, aggregate evidence plus isolated build outputs at 32 MiB, checked every 200 ms. Detached job groups are killed on deadline/cap/interruption and checked absent. A failed job stops the sequence. Explicit coordinator grant, reviewed manifest SHA-256, exact task commit and the /tmp/wine-assembly-local-runtime.lock exclusive lease are required.

Preparation pins present tracked source inputs in the task, shared host and recovery worktrees, both existing recovery canonical modules (shared-host module absence is also pinned), the complete pngjs dependency, and all 140 original Mad Bomber files. Newly built modules are pinned across later jobs. Source-only guard tests check content changes/deletion/unexpected appearance, caps/job bounds and name/ordinal/API-ID agreement. Syntax, 2026-test membership, and 283-import freshness pass; compiled tests remain pending.

Only needed missing tracked paths were expanded after runtime release: .gitignore, index.html, package metadata, fonts/Terminal.fon, fonts/substitutions.json and the two committed toyVM browser bundles. The bundles account for most of the approximately 44 MiB expansion; the other roughly 124 MiB of omitted tracked data was not copied. pngjs is resolved from the recovery worktree's pinned node_modules. No Army path-handling functions or canonical modules were edited. This controller never launches Mad Bomber; original replay remains a separate grant after native qualification.

First native allocation: build gate rejection, no native execution

The granted exact five-job controller (SHA-256 583adce4cc2a16992ff138ca132a86439ca470fadb3dc0fc708a821a9d030c6c) ran session 89820 with prepared manifest f4054a9e. Normal build stopped at the callback ownership census after 1.954 seconds, exit 1: host.js and lib/host-imports.js whole-file hashes still described main before the LockFile teardown additions. No WAT compilation or native fixture was reached. There was no guard breach; process group 2893836 is absent, lease removed, all 9912 pins unchanged, and the isolated build directory is empty and retained. Minimum sampled free space was 2996908032 bytes; a subsequent observation was 2832879616, below admission. No retry occurred.

Evidence is retained in isolated scratch/runs/20261019-lockfile-native/: five artifacts total 1717969 bytes, plus artifact-seal.json SHA-256 d1401b04563ee64ca2b76bd6b89d121dbcc6cdfecca92744b78b2f5b9905dfcf. The seal includes original manifest, 374-byte build log, build status, failure result and postflight (changed [], group/lease absent, four jobs not run).

Source review of the two changed producers found exactly one added operation each: synchronous closeAllDiskHandles before stop's asynchronous runtime retirement, and before exit's host callback/snapshot handoff. The VFS marks retirement first, rejects subsequent opens, and closes each live disk record; closeHandle removes read reservations and releases locks at final duplicate close. Neither cleanup method calls guest code or restores an invocation. Adoption retains separate open ownership and does not retire a live parent. The two census records now describe these reviewed cleanup boundaries and retain ownership=pending for the existing unrelated callback families. The production host.exit regression asserts release and rejected late opens before onExit observes the VFS, while preserving the child's independent lifetime. This is a source-only correction; fresh pins and another grant are required before any new build or native attempt. The original failed bundle is intact.

Full-path main reconciliation before the second allocation

Merged main 42aeaa128c4d44f1769764aaf8f3320e12350be3. Its production delta touches only getFullPathName and fs_get_full_path_name, disjoint from byte-lock ownership and I/O checks. The pending Army _resolvePath candidate was not included. Full-path A/W regression, file-byte-locks including production exit handoff, VFS 46/0, callback census 396 and controller source guards all pass. The two reviewed teardown census records remain unchanged.

After asserting that build was a real empty directory, preserved it by rename to scratch/runs/20261019-lockfile-build-preserved/empty-build; the adjacent preservation.json records the operation. No failed evidence was changed or deleted. A new build destination is absent, as required by the unchanged controller. A fresh source manifest and unique output directory are prepared for review; no new build/native execution is authorized by this source step.

Second allocation: normal build passed, native admission refused

Root ran the reviewed v2 controller as sole runtime, session 42448. Normal build passed all gates and compilation in 22.601 seconds, exit 0, process group 2907251 absent. Minimum sampled free space was 2361241600 bytes, above the stop floor; no guard fired. The next job's fresh disk admission refused before spawning a native test. That failed admission sample was not recorded; root's later 2960699392-byte observation is distinct and does not explain it. All four planned native tests remain not run.

The retained production modules are:

All 9913 original pins and both modules reverified unchanged; group and lease absent. Isolated scratch/runs/20261019-lockfile-native-v2/ has six sealed artifacts totaling 1723573 bytes plus artifact-seal.json (SHA-256 c31153b885307bc6574d8f380e2366782da1eaa08374c4af9851877b16161e89). Build outputs remain in place, unmodified.

tools/run-lockfile-continuation.js prepares six serial jobs without a normal rebuild: the original four instrumented handler/Worker tests, then two 30-second exact-production-module smoke tests. Instrumented handler tests compile the same source with test-only exports; Worker tests compile production import declarations. Neither is described as executing the retained production modules. New test/test-lockfile-public-binary.js verifies and directly instantiates each exact retained binary, resolves both exports by name and ordinal through actual imported APIs, executes their public thunks, and checks 64-bit contention/unlock/error/ESP. Missing unrelated host dependencies throw if reached; no file operation or transport is replaced. All six are authored and syntax checked, not run.

The continuation pins both modules and permits source differences from built commit b130b9445 only in qualification tools/tests and these notes. Its limits, per-job admission, stop floor and global lease are unchanged. The 32 MiB aggregate now includes both previous run directories, current continuation evidence and retained build outputs. Original manifests and seals are pinned. New main 509b620ef's disjoint _resolvePath slash-classification fix and its test are deliberately not merged into this already-built candidate; no claim is made for that combined source. Original Mad Bomber replay and gameplay remain separate future work after conformance, not a consequence of a passing build.

Native continuation passed (2026-10-19)

Root ran the exact reviewed continuation in session 96429, terminal 0. All six jobs passed: instrumented handler/public tail 2.915s and compat 2.762s; real Worker RPC tail 0.060s and compat 0.057s; exact production binary public API tail 0.064s and compat 0.067s. The production smoke logs identify the retained 97c67d7a/b3a9e951 hashes. No guard fired, all six process groups are absent, lease removed, and all 9930 pins reverified unchanged. Minimum sampled free space was 2948763648 bytes.

scratch/runs/20261019-lockfile-continuation/ contains 15 sealed artifacts, 1725166 bytes plus artifact-seal.json SHA-256 9d0330e39b27c466e416f5da5ec5b557cd97685d11542a42eb283945f064b951. This establishes the bounded fixture contracts, not original-game behavior. The next original Mad Bomber consumer requires its own prepared matching runtime, original 140-file manifest, retained module pins and separate grant.

Original consumer preparation

tools/run-madbomber-lockfile-consumer.js reuses the prior original launch argv: all 140 mounts, original DLL seeds, guest C:\MADBOMBER path and 10-second cooperative CLI observation. It changes only the candidate worktree/module, new output destination, adds LockFile/UnlockFile tracing, and explicitly seeds the same shared MSVCRT dependency formerly found in the recovery tree's default DLL directory. That file remains at its original physical path, 278581 bytes, SHA-256 887eb5ce93edb7192ca3e9220f07f9ca0f94db02af5862ebcbdfcb852db99fd1. No original executable, asset, DLL or guest input is modified.

All tracked font assets needed by CLI startup were expanded (about 8.5 MiB), and their bytes must match committed Git blobs. stdole2.tlb is already present. Source-only plan checks validate exact original mount/seed preservation, timeouts/paths, no --input injection, all bitmap/substitute fonts and system data dependencies. The source delta from built b130b9445 permits only the qualification tools/tests and these notes; production runtime remains unchanged.

The prepared consumer retains the 2852126720-byte fresh admission, 2214592512-byte stop floor, 2 MiB log cap and 32 MiB aggregate including all three prior run directories plus build/current output. Guest bound is 10s, normal quit at 15s and hard process-group kill at 25s. Passive PNG/snapshot requests occur at 5s and 9s. A separate grant and global lease are mandatory. The original archive, 140 files, shared CRT, source and modules are all pinned. Mad Bomber is still unregistered in candidate-corpus: resulting metadata is investigation evidence kept in these notes, not a dashboard gameplay claim. This consumer has not been launched.

Initial original allocation refused before spawn

Root's exact consumer command returned 1 at the initial fresh admission check, before creating an output directory or spawning a guest. The failed numerical sample was not recorded. A distinct joint statfs observation at 2026-10-19T02:32:17.775Z reported 2928340992 bytes for both worktree and shared root; it is not evidence of the failed sample or its cause. The refusal is preserved at scratch/runs/20261019-madbomber-lockfile-admission-refused/refusal.json. The original ef81a891 manifest is unchanged; controller bytes can be recovered from be58482fe. No original execution or automatic retry occurred.

The controller now captures free space exactly once for pre-spawn admission, including timestamp, cwd, root and the unchanged exclusive 2852126720-byte threshold in any assertion error. Successful admission records the same sample in launch.json. Source regression checks confirm one read for each decision, refusal at/below the boundary and acceptance one byte above it. Other guards, argv and runtime remain unchanged; a fresh manifest refreshes only this reviewed controller pin, with a unique original-v2 output proposed for a new allocation.

Original remote observation, 2026-10-19

Source checkpoint da120b0dc8738c83a9ef45c9e11c75a647d5a64f was cloned over HTTPS into the assigned isolated bx_rqnkhrnr directory. Exact tested 97c67d7a primary and b3a9e951 compat modules, 140 original files, original seven DLL seeds, shared CRT and pngjs were transferred via the documented Files API and hashed. Portable manifest 0a09d0a8825b592c2b200f09fc59e8c5a1327ddc09bb527ccf6aa14560f7a6a6 pins 3520 files. Controller SHA is ba54f79ac90aa2de7bce2631b0227255edf1e867a5b340ffa86689dc9297ef7d.

The first allocation cmd_xt642uv9rfab failed before spawning a guest: the fresh checkout lacked scratch/runs, so output mkdir returned ENOENT. The lease was removed, output absent, pins unchanged. This staging omission is preserved in scratch/runs/20261019-madbomber-original-remote-v1-refused/. Root explicitly authorized creating the parent and a unique v2 allocation with unchanged guards.

The actual v2 original run, cmd_teaujbskbivh, exited 1. GetProcAddress #302 now resolves LockFile to 0x00db9704; #303 resolves UnlockFile to 0x00db9b0c. There is no traced invocation of either API. The last lookup, #312 SetLocalTime, returns zero, and the log reports DLL_PROCESS_ATTACH returned FALSE. Combined stdout and stderr ordering does not independently prove exact failure causality. SetLocalTime already has a fail-fast unimplemented handler at stable API 627; its real behavior/export needs separate investigation, never a success stub. No PNG was produced before early exit: this is advancement beyond the previous LockFile export blocker, not successful startup or gameplay qualification.

The controller recorded no guard reason, minimum free 48770813952 bytes, 147846 log bytes, child PID 22907/group absent, all 3520 pins unchanged and lease absent. No retry followed the guest failure. All seven original artifacts were recovered via Files API; archive SHA-256 745c5a543beed3d5c7b11ca78b6dc70c4ec7acbb34e7a213a00f4ce66357ca6e matched the remote archive. Local review plus seven artifacts total 819678 bytes under scratch/runs/20261019-madbomber-original-remote-v2/, sealed by f472a138e9ebad8c068610dcad1c602363356622c2f4fca8923055f97b0b15f6. Original inputs and both modules remain retained locally; recovery is the portable inventory plus HTTPS checkpoint and the preserved source assets.

Static inspection identifies the next caller precisely: original UPX-packed msvcr70.dll loads at 0x761000 (preferred 0x7c000000), so traced return address 0x7b85a9 maps to original VA 0x7c0575a9. The original file's UPX1 code calls GetProcAddress at 0x7c0575a3, tests EAX at 0x7c0575a9 and branches on zero to 0x7c0575b4: popa; xor eax,eax; ret 0xc. The startup log identifies this DLL's DllMain at 0x7b8450 returning EAX=0. Together, these source bytes and the final SetLocalTime lookup establish that the missing export makes the UPX import reconstruction return FALSE. This is a separate API compatibility task.

The integration review also ran the complete lazy VFS suite: initial 49/51, with two legacy callback fixtures failing because they constructed an uninitialized ThreadManager and used obsolete unscoped clear_yield calls. Both failures reproduced against main's filesystem; thread-manager bytes were identical. Test-only b495b0486 replaces those fixtures with the real local GuestThreadHost endpoint, ThreadManager constructor, registered receiving thread and frame/epoch-owned callback completion. All original VFS assertions remain. Complete suite now 51/51 including real Workers; the same two updated fixtures pass against main's filesystem. File-byte-lock/fullpath checks pass. No production ownership or slash behavior was changed to accommodate tests.