Преглед изворни кода

feat(native): add prebuilt Node-API flock support

imccyu пре 2 дана
родитељ
комит
d927cbff99
93 измењених фајлова са 1900 додато и 547 уклоњено
  1. 6 0
      .agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.i18n.yaml
  2. 33 0
      .agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.md
  3. 33 0
      .agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.zh.md
  4. 2 2
      .agents/notes/implemented/feature/2026-08-31-cross-process-session-write-lease.i18n.yaml
  5. 3 3
      .agents/notes/implemented/feature/2026-08-31-cross-process-session-write-lease.md
  6. 3 3
      .agents/notes/implemented/feature/2026-08-31-cross-process-session-write-lease.zh.md
  7. 9 1
      .github/workflows/node-addon-system-release.yml
  8. 62 27
      .github/workflows/node-addon-system.yml
  9. 1 0
      .gitignore
  10. 0 2
      THIRD_PARTY_NOTICES.md
  11. 2 2
      native/README.i18n.yaml
  12. 2 2
      native/README.md
  13. 2 2
      native/README.zh.md
  14. 19 41
      native/system/AGENTS.md
  15. 2 2
      native/system/README.i18n.yaml
  16. 22 41
      native/system/README.md
  17. 22 41
      native/system/README.zh.md
  18. 12 21
      native/system/docs/architecture.md
  19. 36 0
      native/system/docs/flock-contract.md
  20. 2 2
      native/system/docs/naming.md
  21. 12 33
      native/system/docs/packaging.md
  22. 7 4
      native/system/docs/release.md
  23. 8 12
      native/system/docs/support-matrix.md
  24. 4 1
      native/system/package.json
  25. 28 0
      native/system/packages/darwin-arm64/LICENSE
  26. 6 0
      native/system/packages/darwin-arm64/README.i18n.yaml
  27. 9 0
      native/system/packages/darwin-arm64/README.md
  28. 9 0
      native/system/packages/darwin-arm64/README.zh.md
  29. 19 0
      native/system/packages/darwin-arm64/package.json
  30. 6 0
      native/system/packages/darwin-arm64/prebuilds.json
  31. 28 0
      native/system/packages/darwin-x64/LICENSE
  32. 6 0
      native/system/packages/darwin-x64/README.i18n.yaml
  33. 9 0
      native/system/packages/darwin-x64/README.md
  34. 9 0
      native/system/packages/darwin-x64/README.zh.md
  35. 19 0
      native/system/packages/darwin-x64/package.json
  36. 6 0
      native/system/packages/darwin-x64/prebuilds.json
  37. 2 2
      native/system/packages/entry/README.i18n.yaml
  38. 8 11
      native/system/packages/entry/README.md
  39. 8 11
      native/system/packages/entry/README.zh.md
  40. 9 2
      native/system/packages/entry/package.json
  41. 164 0
      native/system/packages/entry/src/flock.c
  42. 57 0
      native/system/packages/entry/src/flock.ts
  43. 2 2
      native/system/packages/linux-arm64/README.i18n.yaml
  44. 6 4
      native/system/packages/linux-arm64/README.md
  45. 6 4
      native/system/packages/linux-arm64/README.zh.md
  46. 1 1
      native/system/packages/linux-arm64/package.json
  47. 14 0
      native/system/packages/linux-arm64/prebuilds.json
  48. 2 2
      native/system/packages/linux-x64/README.i18n.yaml
  49. 6 4
      native/system/packages/linux-x64/README.md
  50. 6 4
      native/system/packages/linux-x64/README.zh.md
  51. 1 1
      native/system/packages/linux-x64/package.json
  52. 14 0
      native/system/packages/linux-x64/prebuilds.json
  53. 6 3
      native/system/scripts/assemble-prebuilds.mjs
  54. 23 0
      native/system/scripts/build-test-oracle.mjs
  55. 78 72
      native/system/scripts/build.ts
  56. 3 0
      native/system/scripts/github-matrix.mjs
  57. 60 26
      native/system/scripts/repo.mjs
  58. 4 1
      native/system/scripts/verify-entry-lib.mjs
  59. 3 3
      native/system/scripts/verify-launcher-binary.mjs
  60. 34 23
      native/system/scripts/verify-packed-install.mjs
  61. 1 1
      native/system/scripts/verify-release.mjs
  62. 12 0
      native/system/test/fixtures/flock-binding.js
  63. 6 0
      native/system/test/fixtures/flock-callback-throws.js
  64. 42 0
      native/system/test/fixtures/flock-child.js
  65. 22 0
      native/system/test/fixtures/flock-import.js
  66. 37 0
      native/system/test/fixtures/flock-inherited-child.js
  67. 19 0
      native/system/test/fixtures/flock-io-child.js
  68. 75 0
      native/system/test/fixtures/flock-oracle.c
  69. 31 0
      native/system/test/fixtures/flock-worker.js
  70. 423 0
      native/system/test/flock.test.js
  71. 10 0
      native/system/test/link-platform.mjs
  72. 138 0
      native/system/test/package-matrix.test.js
  73. 4 3
      package.json
  74. 6 6
      packages/experimental/webworker-runtime/src/module-proxies.ts
  75. 3 3
      packages/experimental/webworker-runtime/src/node/builtins.ts
  76. 0 42
      packages/experimental/webworker-runtime/src/node/external_packages/fs-ext.ts
  77. 20 0
      packages/experimental/webworker-runtime/src/node/external_packages/node-addon-system-flock.ts
  78. 6 7
      packages/experimental/webworker-runtime/src/node/external_packages/replaced-externals.ts
  79. 2 2
      packages/session/session-persistence-jsonl/README.i18n.yaml
  80. 1 0
      packages/session/session-persistence-jsonl/README.md
  81. 1 0
      packages/session/session-persistence-jsonl/README.zh.md
  82. 2 3
      packages/session/session-persistence-jsonl/package.json
  83. 4 14
      packages/session/session-persistence-jsonl/src/lease.ts
  84. 8 11
      packages/session/session-persistence-jsonl/tests/lease.spec.ts
  85. 3 0
      packages/session/session-persistence-jsonl/tsconfig.json
  86. 18 31
      pnpm-lock.yaml
  87. 0 3
      pnpm-workspace.yaml
  88. 1 0
      scripts/build.ts
  89. 3 1
      scripts/check-workspace-constraints.ts
  90. 2 1
      scripts/gen-third-party-notices.ts
  91. 21 0
      scripts/run-gates.spec.ts
  92. 3 1
      scripts/run-gates.ts
  93. 1 0
      tsconfig.base.json

+ 6 - 0
.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.i18n.yaml

@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# side as of the last confirmed-consistent state. Both languages carry equal authority;
+# after editing either side, bring the other along and re-record with:
+#   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.md
+2026-09-07-prebuilt-system-primitives.md: 23037c2d0ebc0871125cf8057459cc3930bcc57a
+2026-09-07-prebuilt-system-primitives.zh.md: 4e1e6cc061d6169078c25cf6092965edd553f7e8

+ 33 - 0
.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.md

@@ -0,0 +1,33 @@
+# Agent Note: Prebuilt system primitives
+
+Status: implemented
+
+English | [中文](2026-09-07-prebuilt-system-primitives.zh.md)
+
+## Problem
+
+The JSONL writer's `fs-ext` dependency compiled a NAN addon during consumer installation. Native compiler availability and Node module ABI changes therefore affected ordinary installs, including Node 26. The repository already maintained the Landlock launcher and its per-platform publication workflow.
+
+## Decision
+
+The independently versioned `@deepseek-ai/node-addon-system` family in [native/system](../../../../native/system/README.md) distributes the existing `landlock-run` executable and a stable Node-API v8 `system.node` addon. Platform packages select OS and CPU; Linux carries distinct glibc and musl addon files. macOS carries the addon without a Landlock executable. Neither the entry nor platform packages compile during installation.
+
+The root JavaScript entry retains Landlock's API and [CLI protocol](../../../../native/system/docs/cli-contract.md). The `./flock` entry loads its addon only when `tryLockExclusive(fd)` is called. It runs `flock(fd, LOCK_EX | LOCK_NB)` in asynchronous native work and captures errno on that worker. The caller owns the descriptor through completion and releases its lock by closing it. Missing bindings reject acquisition rather than granting an unprotected lock.
+
+The [Session write-lease decision](../feature/2026-08-31-cross-process-session-write-lease.md) continues to own acquisition timing, inode checks, close ownership, and crash semantics. Windows retains its existing koffi semaphore. The browser worker substitutes only the flock subpath; it continues to use the root Landlock JavaScript API.
+
+Source builds explicitly compile the host addon before repository tests and builds that need it. Native CI builds the complete platform payload and tests the same addon bytes across Node releases; Linux also exercises the musl payload in Alpine. Platform prepack rejects malformed or incomplete binaries, and an offline npm install rehearsal checks installed bytes and real lock behavior. Native [tests](../../../../native/system/test/flock.test.js) cover descriptor/process contention, close and crash release, independent errno values, and worker teardown.
+
+## Alternatives considered
+
+**Keep NAN and publish one build per Node ABI.** This retains a Node-major build matrix for a binding that needs only stable Node-API operations. The evaluated `fs-ext-extra-prebuilt@2.2.14` selected a Node 25 ABI 141 binary under Node 26 ABI 147; its default-install fallback also exited without building when NAN was hoisted.
+
+**Bundle fs-ext into the parent tarball.** npm normally still runs bundled dependency installation hooks. Bundling alone neither suppresses compilation nor makes one binary portable across operating systems, CPUs, libc implementations, or Node ABIs.
+
+**Replace flock with OFD/fcntl locks.** On ordinary Linux filesystems these locks do not necessarily exclude existing flock holders. A tmpfs probe admitted an OFD lock while fs-ext held flock, so this is not a behavior-preserving replacement.
+
+**Use koffi for the POSIX call.** A synchronous call changes event-loop blocking behavior; reading errno after its asynchronous callback reads the wrong thread's value. A native async-work adapter keeps the syscall result and errno together without another FFI coordination layer.
+
+## Consequences
+
+The family owns a small C binding, platform builds, and installed-artifact verification rather than an entire filesystem-extension API. Node-API removes the per-Node-major binary requirement, not OS/CPU/libc requirements. The shared native release includes both capabilities, but importing or using one does not load the other. Landlock binary semantics, Windows locking, and released Session data formats remain unchanged.

+ 33 - 0
.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.zh.md

@@ -0,0 +1,33 @@
+# Agent Note: 预编译系统原语
+
+Status: implemented
+
+[English](2026-09-07-prebuilt-system-primitives.md) | 中文
+
+## Problem
+
+JSONL 写入方依赖的 `fs-ext` 在用户安装时编译 NAN addon。因此,原生编译器是否可用以及 Node 模块 ABI 的变化会影响普通安装,包括 Node 26。仓库已经维护了 Landlock 启动器及其按平台发布的工作流。
+
+## Decision
+
+[native/system](../../../../native/system/README.zh.md) 中独立版本的 `@deepseek-ai/node-addon-system` 包族分发既有 `landlock-run` 可执行文件和使用稳定 Node-API v8 的 `system.node` addon。平台包按操作系统和 CPU 选择;Linux 分别携带 glibc 与 musl addon 文件。macOS 携带 addon,但不包含 Landlock 可执行文件。入口包和平台包都不在安装期间编译。
+
+根 JavaScript 入口保留 Landlock API 和 [CLI 协议](../../../../native/system/docs/cli-contract.md)。`./flock` 入口仅在调用 `tryLockExclusive(fd)` 时加载 addon。它在异步原生工作中执行 `flock(fd, LOCK_EX | LOCK_NB)`,并在该工作线程保存 errno。调用方在完成前持有描述符,并通过关闭它释放锁。绑定缺失时拒绝获取锁,不授予没有保护的锁。
+
+[Session 写租约决策](../feature/2026-08-31-cross-process-session-write-lease.zh.md) 继续负责获取时机、inode 校验、关闭所有权和崩溃语义。Windows 保留既有 koffi 信号量。浏览器 worker 仅替换 flock 子路径,继续使用根 Landlock JavaScript API。
+
+源码构建在需要 addon 的仓库测试与构建之前显式编译当前宿主 addon。Native CI 构建完整平台产物,并让相同 addon 字节跨 Node 版本测试;Linux 还在 Alpine 中执行 musl 产物。平台 prepack 拒绝格式错误或不完整的二进制,离线 npm 安装演练检查安装字节与真实锁行为。Native [测试](../../../../native/system/test/flock.test.js) 覆盖描述符与进程竞争、关闭和崩溃释放、独立 errno 值及 worker 清理。
+
+## Alternatives considered
+
+**保留 NAN,为每个 Node ABI 发布构建。** 这会为仅需稳定 Node-API 操作的绑定保留 Node 主版本构建矩阵。已评估的 `fs-ext-extra-prebuilt@2.2.14` 在 Node 26 ABI147 下选中 Node 25 ABI141 二进制;默认安装回退还会在 NAN 被提升安装时提前退出而不编译。
+
+**将 fs-ext 打入父包 tarball。** npm 默认仍执行 bundled 依赖的安装钩子。仅打包既不能禁止编译,也不能让一个二进制跨操作系统、CPU、libc 实现或 Node ABI 通用。
+
+**将 flock 换成 OFD/fcntl 锁。** 在普通 Linux 文件系统上,这些锁不一定排斥既有 flock 持有者。tmpfs 探针在 fs-ext 持有 flock 时仍取得 OFD 锁,因此这不是保持行为的替换。
+
+**通过 koffi 执行 POSIX 调用。** 同步调用改变事件循环的阻塞行为;在异步回调后读取 errno 会读到错误线程的值。原生 async-work 适配器把系统调用结果和 errno 保存在一起,无须另加 FFI 协调层。
+
+## Consequences
+
+包族维护小型 C 绑定、平台构建和安装产物验证,而不是整套文件系统扩展 API。Node-API 消除按 Node 主版本分发二进制的要求,但不消除操作系统、CPU 和 libc 要求。统一原生发布包含两项能力,但导入或使用其中一项不会加载另一项。Landlock 二进制语义、Windows 锁和已发布 Session 数据格式保持不变。

+ 2 - 2
.agents/notes/implemented/feature/2026-08-31-cross-process-session-write-lease.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-31-cross-process-session-write-lease.md
-2026-08-31-cross-process-session-write-lease.md: 174c5152ea62e01e30ade9a68b6786638acb8ada
-2026-08-31-cross-process-session-write-lease.zh.md: e4246f12f7ed8d8b304ca7f7514117f03f32267b
+2026-08-31-cross-process-session-write-lease.md: ef8ebe2de6b231075dee31a9184bcc0a5b323011
+2026-08-31-cross-process-session-write-lease.zh.md: 8a79b6d5327c7bd5d0ca12ac435140bc0951e993

+ 3 - 3
.agents/notes/implemented/feature/2026-08-31-cross-process-session-write-lease.md

@@ -10,7 +10,7 @@ The JSONL backend's write-handle claim excluded a second writer only inside one
 
 ## Decision
 
-`SessionWriteLease` (packages/session/session-persistence-jsonl/src/lease.ts) holds a kernel lock on `session.lock` beside the log for the whole life of a write handle: POSIX takes a non-blocking `flock(2)` through the pinned native dependency `fs-ext`, and Windows holds a named kernel semaphore (count 1) derived from the canonical lock path (`CreateSemaphoreW` in src/win32.ts beside the existing koffi bindings) — a kernel object with no filesystem footprint, destroyed with its last handle. Contention maps to `SessionAlreadyOwnedError`; the kernel releases the lock when the holder's descriptor or handle closes, including on any process death, so a crashed holder never blocks a successor and no expiry bookkeeping exists. A live but wedged holder keeps the lock until its process exits: expropriating a stalled writer was rejected because its resumed appends would tear the log, and on POSIX removing the lock file remains the explicit forfeit for that case. Because a POSIX lock names an inode rather than a path, acquisition verifies the locked inode is still the file at the lock path and retries otherwise. The lock is taken at write-open of an existing artifact and, for a created session, only right before its first materializing write — an unmaterialized session leaves no filesystem footprint, and a handle that acquired the lock keeps it through close even when materialization fails; release never removes the lock file, preserving the stable inode later lockers verify against. The browser worker deployment stubs fs-ext to immediate success: it is single-process, so the in-process write claim already excludes every writer.
+`SessionWriteLease` (packages/session/session-persistence-jsonl/src/lease.ts) holds a kernel lock on `session.lock` beside the log for the whole life of a write handle: POSIX takes a non-blocking `flock(2)` through the prebuilt `@deepseek-ai/node-addon-system/flock` binding, and Windows holds a named kernel semaphore (count 1) derived from the canonical lock path (`CreateSemaphoreW` in src/win32.ts beside the existing koffi bindings) — a kernel object with no filesystem footprint, destroyed with its last handle. Contention maps to `SessionAlreadyOwnedError`; the kernel releases the lock when the holder's descriptor or handle closes, including on any process death, so a crashed holder never blocks a successor and no expiry bookkeeping exists. A live but wedged holder keeps the lock until its process exits: expropriating a stalled writer was rejected because its resumed appends would tear the log, and on POSIX removing the lock file remains the explicit forfeit for that case. Because a POSIX lock names an inode rather than a path, acquisition verifies the locked inode is still the file at the lock path and retries otherwise. The lock is taken at write-open of an existing artifact and, for a created session, only right before its first materializing write — an unmaterialized session leaves no filesystem footprint, and a handle that acquired the lock keeps it through close even when materialization fails; release never removes the lock file, preserving the stable inode later lockers verify against. The browser worker deployment stubs the flock entry to immediate success: it is single-process, so the in-process write claim already excludes every writer.
 
 ## Alternatives considered
 
@@ -22,8 +22,8 @@ The JSONL backend's write-handle claim excluded a second writer only inside one
 
 **Windows exclusive-open sharing mode (`CreateFileW` denying `FILE_SHARE_WRITE`)** — leaves readers untouched but pins the lock file's name and directory while held: CI showed dozens of suites failing their temp-root cleanup with EBUSY because a still-open handle blocks recursive removal, and users deleting a session directory would hit the same wall. The named semaphore keeps kernel arbitration with zero filesystem footprint.
 
-**Hand-rolled ffi for POSIX too (`flock(2)` via koffi)** — avoids the node-gyp install-time build, but means owning both platform lock implementations plus their error mapping; `fs-ext` ships the POSIX code maintained and pinned, and the Windows side reuses the koffi bindings `win32.ts` already owns.
+**Hand-rolled ffi for POSIX too (`flock(2)` via koffi)** — binding selection and asynchronous errno handling are governed by the [prebuilt system primitives decision](../architecture/2026-09-07-prebuilt-system-primitives.md). The Windows side retains the koffi bindings `win32.ts` already owns.
 
 ## Consequences
 
-Cross-process exclusion costs a node-gyp-compiled native dependency (`fs-ext`, allow-listed in `pnpm-workspace.yaml` `allowBuilds`), one lock file per materialized session that release deliberately leaves in place, and the wedged-holder rule: a stuck process blocks that session's writers until it exits. It buys immediate crash recovery (no waiting period), no renewal traffic, and the removal of every takeover race the TTL design managed rather than prevented. Advisory `flock` is unreliable on some network filesystems (NFSv3); a root on such a mount degrades toward in-process-only exclusion. Deleting a live session's lock file forfeits exclusion on POSIX by design — the harness never does so; the agent-loop resume test uses it deliberately to simulate a wedged first lifecycle, and skips on Windows, where the lock is a kernel object no file operation can forfeit.
+Cross-process exclusion requires the platform's prebuilt system binding, one lock file per materialized session that release deliberately leaves in place, and the wedged-holder rule: a stuck process blocks that session's writers until it exits. It buys immediate crash recovery (no waiting period), no renewal traffic, and the removal of every takeover race the TTL design managed rather than prevented. Advisory `flock` is unreliable on some network filesystems (NFSv3); a root on such a mount degrades toward in-process-only exclusion. Deleting a live session's lock file forfeits exclusion on POSIX by design — the harness never does so; the agent-loop resume test uses it deliberately to simulate a wedged first lifecycle, and skips on Windows, where the lock is a kernel object no file operation can forfeit.

+ 3 - 3
.agents/notes/implemented/feature/2026-08-31-cross-process-session-write-lease.zh.md

@@ -10,7 +10,7 @@ JSONL 后端的写句柄认领只在单个后端实例内部排除第二个写
 
 ## Decision
 
-`SessionWriteLease`(packages/session/session-persistence-jsonl/src/lease.ts)在日志旁的 `session.lock` 上持有内核锁,贯穿写句柄的整个生命期:POSIX 经由固定版本的原生依赖 `fs-ext` 以非阻塞 `flock(2)` 加锁,Windows 持有由规范锁路径派生的命名内核信号量(计数 1,`CreateSemaphoreW`,实现在 src/win32.ts 既有 koffi 绑定旁)——零文件系统足迹的内核对象,随最后一个句柄关闭而销毁。竞争映射为 `SessionAlreadyOwnedError`;持有者的描述符或句柄关闭时内核释放锁,包括任何形式的进程死亡,因此崩溃的持有者从不阻塞后继者,也不存在任何过期簿记。活着但卡死的持有者保有锁直到其进程退出:剥夺停顿写入方的所有权被否决,因为其复活后的追加会撕坏日志;POSIX 上删除锁文件仍是该场景的显式放弃手段。由于 POSIX 锁指向 inode 而非路径,获取后会校验所锁 inode 仍是锁路径上的文件,否则重试。锁在写打开既有工件时立即获取,新建会话则仅在首次物化写入之前获取——未物化的会话不留任何文件系统足迹,已取得锁的句柄即使物化失败也保有锁直到关闭;释放从不删除锁文件,保住后续加锁者用于校验的稳定 inode。浏览器 worker 部署将 fs-ext 存根为立即成功:它是单进程部署,进程内写认领已排除所有写入方。
+`SessionWriteLease`(packages/session/session-persistence-jsonl/src/lease.ts)在日志旁的 `session.lock` 上持有内核锁,贯穿写句柄的整个生命期:POSIX 经由预编译 `@deepseek-ai/node-addon-system/flock` 绑定 以非阻塞 `flock(2)` 加锁,Windows 持有由规范锁路径派生的命名内核信号量(计数 1,`CreateSemaphoreW`,实现在 src/win32.ts 既有 koffi 绑定旁)——零文件系统足迹的内核对象,随最后一个句柄关闭而销毁。竞争映射为 `SessionAlreadyOwnedError`;持有者的描述符或句柄关闭时内核释放锁,包括任何形式的进程死亡,因此崩溃的持有者从不阻塞后继者,也不存在任何过期簿记。活着但卡死的持有者保有锁直到其进程退出:剥夺停顿写入方的所有权被否决,因为其复活后的追加会撕坏日志;POSIX 上删除锁文件仍是该场景的显式放弃手段。由于 POSIX 锁指向 inode 而非路径,获取后会校验所锁 inode 仍是锁路径上的文件,否则重试。锁在写打开既有工件时立即获取,新建会话则仅在首次物化写入之前获取——未物化的会话不留任何文件系统足迹,已取得锁的句柄即使物化失败也保有锁直到关闭;释放从不删除锁文件,保住后续加锁者用于校验的稳定 inode。浏览器 worker 部署将 flock 入口存根为立即成功:它是单进程部署,进程内写认领已排除所有写入方。
 
 ## Alternatives considered
 
@@ -22,8 +22,8 @@ JSONL 后端的写句柄认领只在单个后端实例内部排除第二个写
 
 **Windows 共享模式独占打开(`CreateFileW` 拒绝 `FILE_SHARE_WRITE`)** —— 读者不受影响,但持有期间钉住锁文件的名字与目录:CI 显示数十个套件的临时根清理因仍打开的句柄阻塞递归删除而报 EBUSY,用户删除会话目录也会撞上同一堵墙。命名信号量保住内核仲裁,且文件系统足迹为零。
 
-**POSIX 也手写 ffi(经 koffi 调 `flock(2)`)** —— 免去 node-gyp 安装期编译,但意味着自有两个平台的锁实现及其错误映射;`fs-ext` 交付了有维护、可固定版本的 POSIX 侧,Windows 侧复用 `win32.ts` 已自有的 koffi 绑定。
+**POSIX 也手写 ffi(经 koffi 调 `flock(2)`)** —— 绑定选择与异步 errno 处理由[预编译系统原语决策](../architecture/2026-09-07-prebuilt-system-primitives.zh.md)规定。Windows 侧保留 `win32.ts` 已有的 koffi 绑定。
 
 ## Consequences
 
-跨进程排他的代价是一个 node-gyp 编译的原生依赖(`fs-ext`,已在 `pnpm-workspace.yaml` 的 `allowBuilds` 列入允许)、每个物化会话一个由释放刻意留下的锁文件,以及卡死持有者规则:卡住的进程阻塞该会话的写入方直到其退出。它换来的是即时崩溃恢复(无等待期)、零续约流量,以及删除了 TTL 设计只能"管理"而非"消除"的全部接管竞态。咨询式 `flock` 在部分网络文件系统(NFSv3)上不可靠;位于此类挂载上的根目录会退化为仅进程内排他。POSIX 上删除活跃会话的锁文件按设计即放弃排他——harness 自身从不这样做;agent-loop 的 resume 测试刻意用它模拟卡死的第一个生命周期,并在 Windows 上跳过:那里的锁是任何文件操作都无法放弃的内核对象。
+跨进程排他需要对应平台的预编译系统绑定、每个物化会话一个由释放刻意留下的锁文件,以及卡死持有者规则:卡住的进程阻塞该会话的写入方直到其退出。它换来的是即时崩溃恢复(无等待期)、零续约流量,以及删除了 TTL 设计只能"管理"而非"消除"的全部接管竞态。咨询式 `flock` 在部分网络文件系统(NFSv3)上不可靠;位于此类挂载上的根目录会退化为仅进程内排他。POSIX 上删除活跃会话的锁文件按设计即放弃排他——harness 自身从不这样做;agent-loop 的 resume 测试刻意用它模拟卡死的第一个生命周期,并在 Windows 上跳过:那里的锁是任何文件操作都无法放弃的内核对象。

+ 9 - 1
.github/workflows/node-addon-system-release.yml

@@ -61,6 +61,7 @@ jobs:
         run: pnpm install --filter @deepseek-ai/node-addon-system-workspace... --frozen-lockfile
 
       - name: Install musl toolchain
+        if: runner.os == 'Linux'
         run: |
           sudo apt-get update -q
           sudo apt-get install -yq musl-tools
@@ -71,11 +72,18 @@ jobs:
       - name: Verify binary metadata
         run: node ./scripts/verify-launcher-binary.mjs ${{ matrix.dir }}
 
+      - name: Verify the built entry and flock
+        run: |
+          pnpm build:ts
+          pnpm build:test-oracle
+          pnpm test:flock
+          pnpm test:packaging
+
       - name: Upload prebuild artifact
         uses: actions/upload-artifact@v4
         with:
           name: ${{ matrix.artifact }}
-          path: native/system/${{ matrix.dir }}/bin/*
+          path: native/system/${{ matrix.dir }}/bin/**
           if-no-files-found: error
           retention-days: 7
 

+ 62 - 27
.github/workflows/node-addon-system.yml

@@ -46,11 +46,14 @@ jobs:
     runs-on: ubuntu-24.04
     outputs:
       ci: ${{ steps.matrix.outputs.ci }}
+      compatibility: ${{ steps.matrix.outputs.compatibility }}
     steps:
       - uses: actions/checkout@v4
 
       - id: matrix
-        run: echo "ci=$(node ./scripts/github-matrix.mjs ci)" >> "$GITHUB_OUTPUT"
+        run: |
+          echo "ci=$(node ./scripts/github-matrix.mjs ci)" >> "$GITHUB_OUTPUT"
+          echo "compatibility=$(node ./scripts/github-matrix.mjs compatibility)" >> "$GITHUB_OUTPUT"
 
   native:
     name: ${{ matrix.platform }}
@@ -76,6 +79,7 @@ jobs:
         run: pnpm install --filter @deepseek-ai/node-addon-system-workspace... --frozen-lockfile
 
       - name: Install musl toolchain
+        if: runner.os == 'Linux'
         run: |
           sudo apt-get update -q
           sudo apt-get install -yq musl-tools
@@ -96,6 +100,7 @@ jobs:
       # prove enforcement would be a false green, so an unenforcing kernel
       # fails the leg instead of skipping.
       - name: Launcher tests (real kernel enforcement)
+        if: runner.os == 'Linux'
         run: node ./test/launcher.test.js
         env:
           NALR_REQUIRE_LANDLOCK: 1
@@ -105,40 +110,70 @@ jobs:
           node ./scripts/pack-release.mjs .release/npm --current-platform-only
           node ./scripts/verify-packed-install.mjs .release/npm --current-platform-only
         env:
-          NALR_REQUIRE_LANDLOCK: 1
+          NALR_REQUIRE_LANDLOCK: ${{ runner.os == 'Linux' && '1' || '0' }}
 
-  darwin:
-    name: darwin (no platform package — degradation proof)
-    runs-on: macos-latest
-    steps:
-      - uses: actions/checkout@v4
+      - name: Verify platform payload rules
+        run: pnpm test:packaging
 
-      - uses: pnpm/action-setup@v4
-        with:
-          package_json_file: package.json
+      - name: Flock behavior (built addon)
+        run: |
+          pnpm build:test-oracle
+          pnpm test:flock
 
-      - uses: actions/setup-node@v4
+      - name: Upload this platform's built addon and entry
+        uses: actions/upload-artifact@v4
         with:
-          node-version: 24
-          cache: pnpm
-          cache-dependency-path: pnpm-lock.yaml
+          name: system-compat-${{ matrix.platform }}
+          path: |
+            native/system/packages/*/bin/**
+            native/system/packages/entry/lib/**
+          if-no-files-found: error
+
+      - name: Upload independent syscall test oracle
+        uses: actions/upload-artifact@v4
+        with:
+          name: system-oracle-${{ matrix.platform }}
+          path: native/system/test/bin/**
+          if-no-files-found: error
 
-      - name: Install dependencies
-        run: pnpm install --filter @deepseek-ai/node-addon-system-workspace... --frozen-lockfile
+  compatibility:
+    name: ${{ matrix.platform }} / Node ${{ matrix.node }} (same binary)
+    needs: [matrix, native]
+    strategy:
+      fail-fast: false
+      matrix:
+        include: ${{ fromJson(needs.matrix.outputs.compatibility) }}
+    runs-on: ${{ matrix.runner }}
+    steps:
+      - uses: actions/checkout@v4
 
-      - name: Build TypeScript
-        run: pnpm build:ts
+      - uses: actions/setup-node@v4
+        with:
+          node-version: ${{ matrix.node }}
 
-      - name: Typecheck
-        run: pnpm typecheck
+      - name: Download the original platform build
+        uses: actions/download-artifact@v4
+        with:
+          name: system-compat-${{ matrix.platform }}
+          path: native/system/packages
 
-      - name: Entry tests (keyless)
-        run: node ./test/entry.test.js
+      - name: Download independent syscall test oracle
+        uses: actions/download-artifact@v4
+        with:
+          name: system-oracle-${{ matrix.platform }}
+          path: native/system/test/bin
 
-      - name: Launcher tests (must self-skip cleanly)
-        run: node ./test/launcher.test.js
+      - name: Restore oracle executable permissions
+        run: find ./test/bin -type f -name flock-oracle -exec chmod +x {} +
 
-      - name: Pack rehearsal (entry only — fallback resolution + unusable probe)
+      - name: Test without rebuilding or installing dependencies
         run: |
-          node ./scripts/pack-release.mjs .release/npm --current-platform-only
-          node ./scripts/verify-packed-install.mjs .release/npm --current-platform-only
+          node ./test/link-platform.mjs
+          node --test ./test/flock.test.js ./test/package-matrix.test.js
+
+      - name: Test the same musl addon without a compiler
+        if: runner.os == 'Linux'
+        run: >-
+          docker run --rm -v "$PWD:$PWD" -w "$PWD"
+          node:${{ matrix.node }}-alpine
+          node --test ./test/flock.test.js ./test/package-matrix.test.js

+ 1 - 0
.gitignore

@@ -43,3 +43,4 @@ apps/web/dist/
 worktrees/
 .agents/worktrees/
 .typert-*/
+native/system/test/bin/

+ 0 - 2
THIRD_PARTY_NOTICES.md

@@ -71,7 +71,6 @@ External packages that a workspace package resolves at runtime. The tier covers
 | [`electron-updater`](https://github.com/electron-userland/electron-builder) | MIT |
 | [`eventsource-parser`](https://github.com/rexxars/eventsource-parser) | MIT |
 | [`fflate`](https://github.com/101arrowz/fflate) | MIT |
-| [`fs-ext`](https://github.com/baudehlo/node-fs-ext) | MIT |
 | [`immer`](https://github.com/immerjs/immer) | MIT |
 | [`ipaddr.js`](https://github.com/whitequark/ipaddr.js) | MIT |
 | [`js-yaml`](https://github.com/nodeca/js-yaml) | MIT |
@@ -154,7 +153,6 @@ External packages **directly declared** only by repository tooling, test infrast
 | [`@testing-library/react`](https://github.com/testing-library/react-testing-library) | MIT |
 | [`@types/babel__code-frame`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@types/compression`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
-| [`@types/fs-ext`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@types/js-yaml`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@types/jsdom`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@types/mime-types`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |

+ 2 - 2
native/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write native/README.md
-README.md: e962d1f5492379fe25c0546da58306ec671bd0aa
-README.zh.md: d383ff02ddec29e095a7da5c52175c96fca514f9
+README.md: 100e25dbfa9f472cb6a5a93a719eb7e1be02d10e
+README.zh.md: b1a66ac678334fd535951a7a0e4a921622b8ba51

+ 2 - 2
native/README.md

@@ -2,10 +2,10 @@
 
 English | [中文](README.zh.md)
 
-Native source and public packages maintained with DeepSeek Harness. The [`system/` workspace](system/README.md) owns the Landlock self-restrict-then-exec launcher consumed by the harness, including its architecture, three-package npm family, platform support, development workflow, and [release procedure](system/docs/release.md).
+Native source and public packages maintained with DeepSeek Harness. The [`system/` workspace](system/README.md) owns the Landlock launcher and POSIX flock binding, their platform packages, and the [release procedure](system/docs/release.md).
 
 ## Workspace and release boundary
 
 `system/` and its packages belong to the repository's root pnpm workspace and lockfile. Harness consumers use the current workspace entry package during development and CI, so a launcher contract change and its consumer update can land and be tested together.
 
-The main repository's `Node Addon System` workflow builds and tests each supported architecture. `Node Addon System Release` assembles those native artifacts, packs and verifies the three npm tarballs, then optionally publishes them under one launcher version. The entry package retains platform packages as npm optional dependencies, so npm still installs only the package matching the user's operating system and CPU.
+The main repository's `Node Addon System` workflow builds and tests each supported architecture. `Node Addon System Release` assembles those native artifacts, packs and verifies the npm tarballs, then optionally publishes them under one native version. The entry package retains platform packages as npm optional dependencies, so npm still installs only the package matching the user's operating system and CPU.

+ 2 - 2
native/README.zh.md

@@ -2,10 +2,10 @@
 
 [English](README.md) | 中文
 
-与 DeepSeek Harness 一同维护的原生源码和公开包。[`system/` workspace](system/README.zh.md) 负责 harness 使用的 Landlock 自限后执行启动器,包括其架构、由三个包组成的 npm 包家族、平台支持、开发工作流和[发布流程](system/docs/release.md)。
+与 DeepSeek Harness 一同维护的原生源码和公开包。[`system/` workspace](system/README.zh.md) 负责 Landlock 启动器、POSIX flock 绑定、平台包和[发布流程](system/docs/release.md)。
 
 ## Workspace 与发布边界
 
 `system/` 及其包属于仓库根 pnpm workspace,并共用根锁文件。开发和 CI 中的 harness 消费方直接使用当前 workspace 的入口包,因此启动器约定变更与消费方更新可以在同一个改动中落地并一起测试。
 
-主仓库的 `Node Addon System` 工作流为每个受支持架构构建并测试。`Node Addon System Release` 汇集这些原生产物,打包并验证三个 npm tarball,随后可选择以同一个启动器版本发布。入口包继续将平台包声明为 npm 可选依赖,因此 npm 仍然只会安装与用户操作系统和 CPU 匹配的包。
+主仓库的 `Node Addon System` 工作流为每个受支持架构构建并测试。`Node Addon System Release` 汇集这些原生产物,打包并验证 npm tarball,随后可选择以同一个原生包版本发布。入口包继续将平台包声明为 npm 可选依赖,因此 npm 仍然只会安装与用户操作系统和 CPU 匹配的包。

+ 19 - 41
native/system/AGENTS.md

@@ -1,50 +1,28 @@
 # AGENTS.md
 
-This directory builds `landlock-run`, a Landlock self-restrict-then-exec launcher: a small, auditable confinement binary distributed as prebuilt per-platform npm packages, plus the thin JS entry package that resolves it and implements its CLI contract. It belongs to the repository's root pnpm workspace and lockfile. The main repository owns native CI, tarball assembly, verification, and npm publication; keep package-family changes coordinated with harness consumers in the same repository.
+This workspace owns `@deepseek-ai/node-addon-system`: the Linux `landlock-run` confinement executable and the POSIX `system.node` binding. It shares the root pnpm workspace and lockfile; native packages have one independent version and release workflow.
 
-## Pre-release stance
+## Runtime rules
 
-The project is pre-1.0. Prefer the correct public API over compatibility shims: if a package name, exported field, layout, or contract detail is wrong, rename it and update all references in the same change. Do not add deprecated aliases unless a stable release already needs them.
+- Landlock's argv, exit codes, diagnostics, and fail-closed confinement are defined in [docs/cli-contract.md](docs/cli-contract.md). Do not change them when extending another system capability.
+- The launcher uses only libc, statically linked against musl. Its kernel UAPI definitions remain in the reviewed C source.
+- Node bindings use stable Node-API v8, never NAN, V8 C++ APIs, or experimental Node interfaces. Linux glibc and musl addons are distinct binaries; macOS has its own Mach-O bundle.
+- The flock binding attempts only `LOCK_EX | LOCK_NB` in asynchronous work and captures errno on that worker. The caller owns the fd through completion and releases its lock by closing it.
+- Root entry imports must not load the addon. `./flock` loads it only when called; Windows retains the Harness's existing semaphore implementation.
+- Runtime binary selection has no environment-variable overrides. `NALR_REQUIRE_LANDLOCK` is a test-only enforcement requirement.
+- There is no install-time compile fallback. Missing Landlock binaries probe unusable; missing flock bindings reject acquisition, never silently grant a lock.
 
-## Runtime safety rules
+## Layout and commands
 
-- Every tool must fail closed. If a ruleset cannot be created or the kernel does not enforce it, exit non-zero WITHOUT exec'ing the wrapped command. Never run unconfined as a fallback.
-- Runtime binaries and the entry packages take NO environment-variable overrides: which binary confines a process must never be decidable by the ambient environment. Test injection is by function parameter; the `NALR_*` prefix is for build/test orchestration only.
-- Kernel UAPI is self-defined in the C source (verbatim from the kernel headers), keeping builds independent of toolchain header vintage and making the definitions part of the audit record.
-- No libraries beyond libc, linked statically against musl. The audit surface of a tool is its C source plus the kernel's stable syscall contract.
-- The CLI contract of each tool ([docs/cli-contract.md](docs/cli-contract.md)) is the cross-repo compatibility contract: argv grammar, exit codes, and report lines change only with a version bump and a changelog entry, and consumers parse them only through the entry package.
-- There is deliberately NO install-time build fallback: a host without a matching platform package gets a nonexistent launcher path, the consumer's probe fails, and the consumer falls closed — that degradation is part of the design, not a gap to fill with node-gyp.
+`packages/entry/` owns JavaScript, types, and auditable C sources. Platform packages hold only binaries and metadata. `scripts/` owns native builds, packing, validation, and release; `test/` owns real process and lock behavior.
 
-## Repository layout
+Run `pnpm build:ts`, `pnpm build:native`, `pnpm build:test-oracle`, `pnpm typecheck`, and `pnpm test` in this directory. Linux full builds require musl-gcc; macOS uses cc. Repository tests build only their host addon through the root `build:native-system` script. The independent syscall fixture is test-only and never enters a published platform package.
 
-```text
-packages/entry/     Published entry package: JavaScript API (resolve/probe/grants) + the C source.
-packages/linux-*/   Published per-platform packages: one prebuilt static binary, no JavaScript.
-scripts/            Build, matrix derivation, prepack gates, and release orchestration.
-test/               Plain-node behavioral tests (entry API + real-kernel launcher proofs).
-docs/               Architecture, packaging, CLI contract, release, support matrix, naming.
-```
+## Packaging and verification
 
-## Commands
-
-```sh
-pnpm install
-pnpm build:ts        # entry packages → lib/
-pnpm build:native    # this Linux architecture's binaries (needs musl-tools); fails fast elsewhere
-pnpm typecheck
-pnpm test            # entry tests everywhere; launcher tests need linux + built binary
-```
-
-## Packaging invariants
-
-- The package matrix is explicit, checked-in metadata: `packages/<name>/package.json` (`os`, `cpu`), `packages/<name>/prebuilds.json` (the binaries that may exist there), and [docs/support-matrix.md](docs/support-matrix.md) stay synchronized when the matrix changes. `scripts/github-matrix.mjs` derives CI and release matrices from it; nothing else enumerates platforms.
-- Platform package names contain platform only (`-linux-x64`), never tool variants — those stay inside `prebuilds.json`. Static musl linking is why there is no libc suffix: one binary serves glibc and musl distros.
-- Platform packages ship no JavaScript; the entry package resolves them to file paths. Backends prove themselves at runtime through the functional probe, never through metadata trust.
-- Builds are native-only: each architecture compiles its own binary on its own runner (CI is the builder of record); no cross toolchain enters the repo.
-- Every tarball is gated at pack time: platform packages refuse to pack without their declared binaries present, executable, and in the right ELF architecture (`verify-launcher-binary.mjs`), entry packages without built `lib/` (`verify-entry-lib.mjs`), and the release pipeline byte-pins installed binaries against the workspace builds (`verify-packed-install.mjs`).
-- Platform tarballs are packed with `npm pack`, never `pnpm pack`: pnpm's pack path strips the executable bit (observed on 11.7.0), shipping a launcher no consumer can spawn. `pack-release.mjs` encodes the split; the rehearsal asserts executability of the installed copy so a regression fails loudly instead of masquerading as a non-enforcing kernel.
-- Generated artifacts stay out of git: `packages/*/bin/`, `packages/*/lib/`, `dist/`, `.release/`, `*.tsbuildinfo`. Ignore rules live in the ROOT `.gitignore` only — a package-nested ignore file can silently drop payload from tarballs.
-
-## Documentation
-
-User-facing docs are English. Keep the README focused on install, usage, and support status; durable design decisions belong in docs/ alongside the code, and the current implementation belongs in [docs/architecture.md](docs/architecture.md).
+- `os`/`cpu` and `prebuilds.json` are the checked-in package matrix. CI derives runners from that matrix, builds natively on each architecture, and tests identical addon bytes under several Node releases.
+- Linux packages contain `bin/landlock-run`, `bin/glibc/system.node`, and `bin/musl/system.node`. macOS packages contain `bin/system.node`. No Windows platform package is needed by these capabilities.
+- Platform prepack rejects missing, undeclared, wrong-format, wrong-architecture, and non-Node-API addon payloads. Launcher executability is checked separately.
+- Platform tarballs use npm pack to preserve executable permissions. The entry uses pnpm pack for workspace version conversion.
+- Packed-install verification checks manifests, installs local tarballs without a registry, byte-pins payloads, and exercises both the installed flock binding and Landlock's functional probe.
+- Build outputs stay ignored. Source/consumer changes and their behavior tests land together; preserve bilingual READMEs and independent native publication.

+ 2 - 2
native/system/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write native/system/README.md
-README.md: c3eeb84ea3b8853fc3da12dbd929e919191175ce
-README.zh.md: 30fa0ebd3e8c0127c63cde8e2ddb21b36fef5756
+README.md: 674e47c79726d0212ab17468d90e0a73e6c63db6
+README.zh.md: b9770453e25620add25a67d93a89abd0f41c6554

+ 22 - 41
native/system/README.md

@@ -1,60 +1,41 @@
+---
+description: "Prebuilt system primitives for Linux confinement and POSIX Session write locks."
+kind: "package-library"
+---
 # @deepseek-ai/node-addon-system
 
 English | [中文](README.zh.md)
 
-A [Landlock](https://landlock.io/) self-restrict-then-exec launcher for confining subprocesses on Linux, distributed as prebuilt per-platform npm packages plus a thin JS entry package that resolves the binary and speaks its CLI contract. Built for agent harnesses and other hosts that need to run untrusted commands under a filesystem allow-list without confining themselves.
+## Summary
 
-The tool is **`landlock-run`** — a self-restrict-then-exec [Landlock](https://landlock.io/) launcher (~300 lines of C11 over the raw kernel UAPI, statically linked against musl). It installs a Landlock ruleset on itself and `exec`s the wrapped command; the ruleset is inherited across `execve`, so the command and every process it spawns run confined while the invoking process stays unrestricted. Fail-closed: if the kernel cannot enforce, it exits without running the command.
+Use the Linux `landlock-run` executable to confine subprocesses, or the `./flock` entry to acquire a POSIX write lock. Platform packages contain the precompiled binaries; consumer installation never builds native code. Landlock policy and Session lifecycle remain with callers.
 
-## Install
+## Table of Contents
 
-```sh
-npm install @deepseek-ai/node-addon-system
-```
+- [Use](#use)
+- [Support](#support)
+- [Development](#development)
 
-Published packages use an entry package plus platform optional packages:
+## Use
 
-```text
-@deepseek-ai/node-addon-system
-@deepseek-ai/node-addon-system-linux-x64
-@deepseek-ai/node-addon-system-linux-arm64
-```
+The root entry exports `launcherPath`, `probe`, and `grantArgs` for Landlock. Its executable name, flags, and failure semantics are defined by the [CLI contract](docs/cli-contract.md).
 
-npm's `os`/`cpu` fields make installers fetch only the matching platform package. There is no install-time build fallback on purpose: on a host without a platform package the resolved path never exists, the probe reports `unusable`, and the consumer falls closed.
+The [flock behavior contract](docs/flock-contract.md) maps descriptor, process, and advisory-lock semantics to independent native tests.
 
-## Usage
+`@deepseek-ai/node-addon-system/flock` exports `tryLockExclusive(fd): Promise<void>`. Keep the descriptor open until completion. Acquisition uses nonblocking exclusive flock; contention rejects with `EAGAIN` or `EWOULDBLOCK`, and closing the final descriptor for the open file description releases the lock. See the [entry README](packages/entry/README.md).
 
-```js
-import { grantArgs, launcherPath, probe } from '@deepseek-ai/node-addon-system';
-
-const launcher = launcherPath();
-if (probe(launcher) !== 'unusable') {
-  const argv = [launcher, ...grantArgs({ readOnly: ['/'], readWrite: ['/tmp/work'] }), '--', 'bash', '-c', command];
-  // spawn argv with your process runner of choice
-}
-```
-
-The public API is intentionally small:
-
-- `launcherPath()`: absolute path of this host's launcher (existence deliberately unchecked — the probe is the availability signal).
-- `probe(launcher?, { timeoutMs? })`: functional enforcement probe — `'full' | 'partial' | 'unusable'`.
-- `grantArgs({ readOnly?, readWrite? })`: the launcher's grant argv; everything not granted is denied.
-- `LAUNCHER_BIN` and `LAUNCHER_FAILURE_EXIT` (125): contract constants. A successfully exec'd child may also return 125, so consumers need the fatal diagnostic as well as the status to attribute launcher failure.
-
-The full binary contract (argv grammar, exit codes, report lines) is pinned in [docs/cli-contract.md](docs/cli-contract.md).
+Importing either entry does not load an addon. A missing Landlock executable probes unusable; a missing flock binding rejects acquisition. Neither path compiles or silently grants unsupported behavior.
 
 ## Support
 
-linux-x64 and linux-arm64, kernel with Landlock enabled (5.13+; ABI level determines `full` vs `partial` enforcement — see [docs/support-matrix.md](docs/support-matrix.md)). Other platforms deliberately have no package: consumers run different confinement backends there.
+Linux x64/arm64 packages contain the static Landlock executable and separate glibc/musl `system.node` files. macOS x64/arm64 packages contain `system.node` only. Landlock additionally needs an enforcing Linux kernel; Windows uses the Harness's existing locking implementation. The [support matrix](docs/support-matrix.md) names builders and verification owners.
 
 ## Development
 
-```sh
-corepack enable
-pnpm install
-pnpm build:ts        # entry packages → lib/
-pnpm build:native    # this Linux architecture's binaries (apt-get install musl-tools)
-pnpm test
-```
+From this directory, `pnpm build:ts` builds the entry, `pnpm build:native` builds the host's declared native payload, and `pnpm build:test-oracle` builds an independent flock syscall fixture. Then `pnpm test` exercises entry, lock, packaging, and available kernel behavior. Linux requires musl-gcc for a complete build; macOS uses cc. The root `pnpm run build:native-system` builds only the current host addon for source tests.
+
+The [architecture](docs/architecture.md), [packaging](docs/packaging.md), and [release procedure](docs/release.md) own implementation and publication details.
+
+### Dev Note
 
-Binaries are git-ignored and built natively per architecture — locally for your own machine, by CI's per-arch runners as the builders of record. Release flow: [docs/release.md](docs/release.md).
+None.

+ 22 - 41
native/system/README.zh.md

@@ -1,60 +1,41 @@
+---
+description: "为 Linux 进程隔离与 POSIX Session 写锁提供预编译系统原语。"
+kind: "package-library"
+---
 # @deepseek-ai/node-addon-system
 
 [English](README.md) | 中文
 
-一个 [Landlock](https://landlock.io/)「先限制自身、再执行」启动器,用于在 Linux 上限制子进程。它以按平台预构建的 npm 包以及一个轻量 JS 入口包的形式发布;入口包负责解析二进制文件并遵循其 CLI(命令行界面)约定。该启动器面向需要让不可信命令在文件系统允许清单约束下运行、同时保持自身不受限制的 agent harness(智能体框架)和其他宿主。
+## Summary
 
-该工具是 **`landlock-run`**:一个「先限制自身、再执行」的 [Landlock](https://landlock.io/) 启动器(基于原始内核 UAPI 编写,约 300 行 C11,并与 musl 静态链接)。它在自身上安装 Landlock 规则集,再 `exec` 被包装的命令;该规则集会跨 `execve` 继承,因此命令及其产生的每个进程都在限制下运行,调用进程仍不受限制。它采用失败闭合:如果内核无法强制执行,则不运行命令并直接退出
+使用 Linux `landlock-run` 可执行文件限制子进程,或通过 `./flock` 入口获取 POSIX 写锁。平台包包含预编译二进制;用户安装时不会构建原生代码。Landlock 策略与 Session 生命周期仍由调用方负责
 
-## 安装
+## Table of Contents
 
-```sh
-npm install @deepseek-ai/node-addon-system
-```
+- [使用](#use)
+- [支持范围](#support)
+- [开发](#development)
 
-已发布包由一个入口包和可选平台包组成:
+## Use
 
-```text
-@deepseek-ai/node-addon-system
-@deepseek-ai/node-addon-system-linux-x64
-@deepseek-ai/node-addon-system-linux-arm64
-```
+根入口为 Landlock 导出 `launcherPath`、`probe` 和 `grantArgs`。其可执行文件名、参数和失败语义由 [CLI 约定](docs/cli-contract.md) 定义。
 
-npm 的 `os`/`cpu` 字段使安装器只拉取匹配的平台包。系统有意不提供安装时构建回退:在没有对应平台包的宿主上,解析后的路径绝不存在,探测会报告 `unusable`,消费方以失败闭合方式处理
+[flock 行为约定](docs/flock-contract.md) 将描述符、进程和咨询式锁语义对应到独立原生测试。
 
-## 用法
+`@deepseek-ai/node-addon-system/flock` 导出 `tryLockExclusive(fd): Promise<void>`。在调用完成前保持描述符打开。获取操作使用非阻塞独占 flock;竞争以 `EAGAIN` 或 `EWOULDBLOCK` 拒绝,关闭该打开文件描述的最后一个描述符即释放锁。参见[入口 README](packages/entry/README.zh.md)。
 
-```js
-import { grantArgs, launcherPath, probe } from '@deepseek-ai/node-addon-system';
+导入任一入口都不会加载 addon。Landlock 可执行文件缺失时探测为不可用;flock 绑定缺失时拒绝获取。两条路径都不会编译或静默授予不受支持的行为。
 
-const launcher = launcherPath();
-if (probe(launcher) !== 'unusable') {
-  const argv = [launcher, ...grantArgs({ readOnly: ['/'], readWrite: ['/tmp/work'] }), '--', 'bash', '-c', command];
-  // spawn argv with your process runner of choice
-}
-```
+## Support
 
-公开 API 有意保持精简:
+Linux x64/arm64 包包含静态 Landlock 可执行文件,以及分别用于 glibc/musl 的 `system.node` 文件。macOS x64/arm64 包仅包含 `system.node`。Landlock 还需要支持强制执行的 Linux 内核;Windows 使用 Harness 既有锁实现。[支持矩阵](docs/support-matrix.md) 指定构建者与验证负责人。
 
-- `launcherPath()`:当前宿主启动器的绝对路径(有意不检查是否存在;探测结果才是可用性信号)。
-- `probe(launcher?, { timeoutMs? })`:功能性强制执行探测,返回 `'full' | 'partial' | 'unusable'`。
-- `grantArgs({ readOnly?, readWrite? })`:启动器的授权 argv;未授予的一切都被拒绝。
-- `LAUNCHER_BIN` 和 `LAUNCHER_FAILURE_EXIT`(125):约定常量。成功完成 exec 的子进程也可能返回 125,因此消费方必须同时看到致命诊断和该状态,才能将结果归因为启动器失败。
+## Development
 
-完整的二进制约定(argv 语法、退出码、报告行)锁定在 [docs/cli-contract.md](docs/cli-contract.md) 中
+在本目录运行 `pnpm build:ts` 构建入口、`pnpm build:native` 构建当前宿主声明的原生产物、`pnpm build:test-oracle` 构建独立的 flock 系统调用 fixture。随后用 `pnpm test` 验证入口、锁、打包及可用的内核行为。Linux 完整构建需要 musl-gcc;macOS 使用 cc。根目录 `pnpm run build:native-system` 只构建源码测试所需的当前宿主 addon。
 
-## 支持范围
+[架构](docs/architecture.md)、[打包](docs/packaging.md)和[发布流程](docs/release.md)分别负责实现与发布细节。
 
-支持 linux-x64 和 linux-arm64,且内核已启用 Landlock(5.13+;ABI 级别决定强制执行为 `full` 还是 `partial`,详见 [docs/support-matrix.md](docs/support-matrix.md))。其他平台有意不提供对应包:消费方会在这些平台上运行其他限制后端。
+### Dev Note
 
-## 开发
-
-```sh
-corepack enable
-pnpm install
-pnpm build:ts        # entry packages → lib/
-pnpm build:native    # this Linux architecture's binaries (apt-get install musl-tools)
-pnpm test
-```
-
-二进制文件被 git 忽略,并且按架构原生构建:本地只构建当前机器的版本,CI 各架构 runner 产出的构建则作为正式发布依据。发布流程详见 [docs/release.md](docs/release.md)。
+无。

+ 12 - 21
native/system/docs/architecture.md

@@ -1,34 +1,25 @@
 # Architecture
 
-This repository owns confinement *mechanism*, not policy: consumers (agent harnesses and sandbox capabilities) decide which paths a run may read or write; this package family provides the launcher that enforces those grants and the JavaScript API that resolves and speaks to it. The packaging follows the per-platform-package model of [`node-addon-require-builtin`](https://www.npmjs.com/package/@esplus/node-addon-require-builtin) (and esbuild), adapted from Node addons to standalone static executables.
+The system package family supplies native mechanisms to Node callers: a Linux confinement executable and a POSIX file-lock binding. Consumers own sandbox policy and Session lifecycle.
 
-## Two-layer package family
+## Package family
 
-The family is one entry package plus per-platform binary packages:
+The ESM entry `@deepseek-ai/node-addon-system` and its optional platform packages share one version. Platform metadata chooses the operating system and CPU; each package's `prebuilds.json` declares the files it must contain.
 
-- **Entry package** (`@deepseek-ai/node-addon-system`): ESM JavaScript. Owns the tool's CLI contract — path resolution (`launcherPath`), the functional probe (`probe`), grant-argv construction (`grantArgs`), and the contract constants. Ships the C source in its tarball for auditability. Lists every platform package as an `optionalDependency`.
-- **Platform packages** (`@deepseek-ai/node-addon-system-linux-{x64,arm64}`): one prebuilt static binary under `bin/`, a `prebuilds.json` declaring it, and no JavaScript at all. npm's `os`/`cpu` fields select the matching one at install time; the entry package resolves it to a file path — there is nothing to import.
+The root entry owns the existing Landlock path resolution, grant argv, and functional probe. It does not load native addons. The `./flock` entry lazily loads `system.node` only when `tryLockExclusive(fd)` is called. Importing either JavaScript entry therefore works without a matching native payload.
 
-Because the CLI parser and binary are versioned together in one package family, the parser cannot fall behind that binary version. Preventing that mismatch is why the package split exists.
+## Separate mechanisms
 
-There is no shared loader package: platform packages have nothing to load. If a second tool ever needs shared JS, extract it then, not preemptively.
+`landlock-run` remains a static musl executable with the [existing CLI contract](cli-contract.md). It installs confinement on itself before exec, and refuses to exec if enforcement is unavailable. A missing launcher or unsupported kernel produces an unusable probe.
 
-## Resolution and availability
+`system.node` uses stable Node-API v8. Its flock operation follows [fs-ext's asynchronous callback model](https://github.com/baudehlo/node-fs-ext/blob/v2.1.1/fs-ext.cc): it runs `flock(fd, LOCK_EX | LOCK_NB)` in asynchronous work and records errno on that worker. The native callback receives zero or positive errno; JavaScript owns the promise and syscall error construction. Setup errors throw into that promise. Callback exceptions are reported through Node's uncaught-exception handler; unexpected Node-API failures terminate the process. A terminating environment may suppress JavaScript completion, but its cleanup waits for queued or running native work before freeing storage.
 
-`launcherPath()` resolves `@deepseek-ai/node-addon-system-<platform>-<arch>` and returns `<package>/bin/landlock-run`. When the package is not resolvable it returns a deterministic fallback path inside the entry package's own `node_modules` that simply never exists. Existence is deliberately unchecked either way: `probe()` is the single availability signal, and a missing binary probes `unusable` exactly like an unenforcing kernel. Consumers get one degradation path, not two.
+The descriptor belongs to the caller and must stay open through completion. The binding neither opens nor closes it; closing the final descriptor for its open file description releases the lock.
 
-The probe is functional — the launcher builds and enforces a real maximal ruleset in a short-lived child — because version checks would miss a kernel that has the syscalls but refuses enforcement.
+The JSONL backend retains its inode check, materialization timing, and close lifecycle. Windows uses its existing koffi semaphore and never calls this POSIX binding. The browser worker supplies a single-process replacement for the flock entry, while running the Landlock JavaScript API unchanged.
 
-## Fail-closed everywhere
+## Builds and release
 
-The launcher exits `125` without exec'ing the command on any launcher-level failure: usage error, unenforcing kernel, unopenable grant root, failed exec. Partial enforcement (an older Landlock ABI governing only a subset of accesses) is accepted, reported on stderr, and surfaced by the probe as `partial` — the consumer decides what its mode vocabulary promises at each level. Neither the binary nor the entry package reads environment variables: which binary confines a process is never decidable by the ambient environment.
+Repository builds and `build:bench` explicitly build the host addon before running consumers. Each platform builds natively on its CI runner. Landlock is static-musl; Linux addons are separately built for glibc and musl, and macOS uses a Mach-O bundle. Stable Node-API removes the Node-major build dimension, not OS, CPU, or libc differences. CI exercises identical addon bytes under Node 20, 22, 24, and 26; Linux also runs the musl addon in Alpine containers.
 
-## Build and release model
-
-Builds are native-only. `scripts/build.ts` compiles the running architecture's binaries with the distro `musl-gcc` (static: no loader or libc expectations on consumers, one binary for glibc and musl distros); CI's per-architecture runners are the builders of record, and no cross toolchain exists in the repo. Review covers the C source and the CI job that built each binary, enforced by three gates: platform prepack refuses missing/wrong-ELF binaries, entry prepack refuses unbuilt `lib/`, and the release pipeline byte-pins installed binaries against the workspace builds they were packed from.
-
-The package matrix is checked-in metadata (`prebuilds.json` + `os`/`cpu` fields); `scripts/github-matrix.mjs` derives the CI and Release matrices from it, so adding a platform extends automation without editing workflows.
-
-## Adding a platform
-
-A new platform adds one `packages/<platform>/` package (`package.json` with `os`/`cpu`, `prebuilds.json`, README, LICENSE), a runner entry in `scripts/github-matrix.mjs`, and a row in [support-matrix.md](support-matrix.md) — added only together with a native GitHub runner that builds and proves it (the no-cross-toolchain rule). Sibling launchers for other confinement mechanisms belong in their own repositories on this same template, not as second tools here.
+Platform prepack validates file formats, architecture, payload completeness, and Node-API exports. The packed-install rehearsal installs local tarballs, checks their bytes, and exercises the installed mechanisms. Missing capabilities fail explicitly; no consumer install runs a compiler. [Packaging](packaging.md) and [release](release.md) own the operational details.

+ 36 - 0
native/system/docs/flock-contract.md

@@ -0,0 +1,36 @@
+# POSIX flock behavior
+
+`tryLockExclusive(fd)` returns a promise for one `flock(fd, LOCK_EX | LOCK_NB)` attempt. The syscall runs off the JavaScript thread. The caller keeps the descriptor open through completion; the binding does not open, duplicate, or close it. It exposes neither a blocking-wait API nor a shared-lock API.
+
+## Behavior tests
+
+The [native tests](../test/flock.test.js) exercise real descriptors and independent processes. The [C oracle](../test/fixtures/flock-oracle.c) calls the operating system directly, independently of `system.node`.
+
+| Condition | Required observation |
+|---|---|
+| No conflicting lock | Acquisition resolves to void |
+| Same open file description acquires again | Acquisition succeeds without a second ownership record |
+| Separate opens of the same file | Exactly one exclusive holder; the contender rejects with EAGAIN/EWOULDBLOCK |
+| Different files | Both can be locked |
+| Independent C flock holder | The addon cannot acquire, and the C oracle cannot acquire while the addon holds the lock |
+| A shared flock holder | The addon's exclusive attempt conflicts |
+| Holder remains live | A nonblocking attempt reports contention before the holder unlocks |
+| Ordinary read/write by another process | Access is allowed: flock is advisory, not an I/O permission mechanism |
+| One unrelated descriptor closes | The actual holder keeps its lock |
+| A descriptor inherited by a child remains open | Closing the parent's descriptor does not release the shared open file description's lock |
+| Last owning descriptor closes | An already-open contender can acquire |
+| Holder process exits or is killed | Acquisition succeeds after process exit, without a stale-lock timeout |
+| Invalid descriptor | The promise rejects with EBADF and positive errno |
+| Native argument validation fails | The JavaScript entry returns a rejected promise without throwing synchronously |
+| Native completion callback | It receives zero or the request's positive errno asynchronously |
+| Native completion callback throws | The exception reaches Node's uncaught-exception handler |
+| Concurrent success/failure calls | Each completion receives its own syscall errno |
+| Worker environment terminates before or during its callback | Native work and cleanup reach completion without taking ownership of the caller's descriptor |
+
+Tests synchronize through IPC or flushed line protocols and await process exit before asserting crash recovery. They do not use fixed sleeps or a millisecond performance threshold to prove nonblocking behavior. The syscall oracle is built only for tests and never included in a published platform package.
+
+## Limits
+
+Locks belong to open file descriptions and follow the host filesystem's flock semantics. Removing or replacing a pathname does not transfer a lock to the replacement inode; the JSONL backend separately checks inode identity. Network filesystems can have different or unsupported lock semantics. Windows does not use this API and retains its existing semaphore implementation.
+
+Node-API compatibility tests reuse the same platform addon under different Node versions. They complement these syscall tests; loading a binary alone does not prove correct locking behavior.

+ 2 - 2
native/system/docs/naming.md

@@ -9,11 +9,11 @@ The public package family belongs to the `@deepseek-ai` scope and uses the `node
 @deepseek-ai/node-addon-system-<platform>
 ```
 
-Platform suffixes carry no libc component (binaries are static musl) and no variant component — variants stay inside `prebuilds.json` and binary filenames.
+Platform suffixes carry OS and CPU. Linux libc variants live inside the same platform package and are declared in `prebuilds.json`.
 
 ## Binaries
 
-The launcher executable is `landlock-run`, shipped at `bin/landlock-run` inside each platform package.
+The Linux launcher remains `bin/landlock-run`. The Node-API addon is `system.node`: `bin/glibc/system.node` and `bin/musl/system.node` on Linux, `bin/system.node` on macOS.
 
 ## Environment variables
 

+ 12 - 33
native/system/docs/packaging.md

@@ -1,45 +1,24 @@
 # Packaging
 
-The package family uses the same layout as native packages such as esbuild: one JS entry package plus platform optional packages. Unlike Node addons there is no ABI or backend division — each platform package carries exactly the static executables its `prebuilds.json` declares.
+The family publishes one ESM entry package plus OS/CPU-selected optional platform packages. All share one version; platform packages publish before the entry.
 
-## Published packages
+## Payloads
 
-```text
-@deepseek-ai/node-addon-system
-@deepseek-ai/node-addon-system-linux-x64
-@deepseek-ai/node-addon-system-linux-arm64
-```
+The entry exports the Landlock API at its root and the asynchronous lock API at `./flock`, with C sources included for auditability. Platform packages contain no JavaScript.
 
-Unsupported platforms are intentionally absent from `optionalDependencies` — see [support-matrix.md](support-matrix.md).
+- Linux: `bin/landlock-run`, `bin/glibc/system.node`, and `bin/musl/system.node`.
+- macOS: `bin/system.node`.
 
-## Package matrix
+`package.json` supplies OS/CPU metadata; `prebuilds.json` supplies tool, binary kind, path, and addon Node-API/libc metadata. CI matrices and release assembly derive from those files. Nested paths remain intact in uploaded artifacts and tarballs.
 
-The matrix is explicit in checked-in metadata:
+## Installation and use
 
-- `packages/entry/package.json` lists the platform packages as `optionalDependencies`.
-- `packages/<name>/package.json` declares `os` and `cpu`. There is no `libc` field on purpose: the binaries are statically linked against musl and run on glibc and musl distros alike.
-- `packages/<name>/prebuilds.json` declares the binaries that may exist in that package (`tool`, `kind`, `path`).
-- [support-matrix.md](support-matrix.md) explains why unsupported platform packages are not published.
+Neither entry nor platform packages have installation lifecycle scripts. The entry resolves its matching optional package when a native operation needs it. Optional means that the package manager selects a platform, not that a requested lock can succeed without its binding.
 
-`scripts/github-matrix.mjs` derives the CI and Release matrices from these files. `scripts/build.ts` builds only the current host's targets, into `packages/<name>/bin/`; it is not a matrix generator. When changing the matrix, update package metadata, `prebuilds.json`, the lockfile, and the support/release docs in the same change.
+The root Landlock API stays importable without native payloads and reports unavailable enforcement through its probe. The flock entry is also lazy at import; acquisition reports a missing or unloadable addon instead of compiling or granting an unprotected lock.
 
-## Runtime selection
+## Pack verification
 
-1. npm's `os`/`cpu` fields make installers fetch only the matching platform package.
-2. The entry package's `launcherPath()` resolves it to `<package>/bin/landlock-run`; unresolvable packages yield a deterministic, never-existing fallback path.
-3. `probe()` is the single availability signal: missing binary and unenforcing kernel are deliberately indistinguishable (`unusable`), so consumers have one fail-closed path.
+Platform tarballs use npm pack to preserve the launcher's executable bit. The entry uses pnpm pack to convert workspace dependency versions. Prepack rejects missing or undeclared payloads, invalid ELF/Mach-O architecture or type, addons without Node-API exports, and launchers without executable permission.
 
-## No install fallback
-
-The entry package has NO install script and never compiles on the consumer host. A compile fallback would require a musl toolchain everywhere and turn a clean fail-closed degradation into an environment-dependent maybe. The packed-manifest check in `verify-packed-install.mjs` enforces the absence of install lifecycle scripts.
-
-## Pack gates
-
-Platform tarballs are produced by `npm pack`, entry tarballs by `pnpm pack` — deliberately split: `pnpm pack` (observed on 11.7.0) normalizes file modes and strips the executable bit, which would ship a launcher no consumer can spawn, while platform packages have no dependencies and so need none of pnpm's workspace-protocol conversion; entry packages need that conversion and carry no executables. `scripts/pack-release.mjs` encodes the split — never hand-pack a platform package with pnpm.
-
-Both pack paths produce the exact publish bytes behind a `prepack` gate:
-
-- Platform packages: `scripts/verify-launcher-binary.mjs` — every declared binary present, executable, ELF `e_machine` matching the declared `cpu`, nothing undeclared in `bin/`.
-- Entry packages: `scripts/verify-entry-lib.mjs` — built `lib/` present.
-
-`scripts/verify-packed-install.mjs` then rehearses the consumer path from the packed tarballs: payload checks, a throwaway install, a byte-pin of the installed binary against the workspace build, an executability check on the installed copy, and a real confinement world-proof through the installed launcher. A non-executable or missing binary fails loudly here instead of masquerading as a non-enforcing kernel.
+The installed-artifact rehearsal verifies concrete dependency versions and absence of installation hooks, performs an offline npm install from local tarballs, and compares installed bytes with build outputs. It then proves flock contention/close release and probes the installed Landlock launcher; real confinement remains required on enforcing CI kernels.

+ 7 - 4
native/system/docs/release.md

@@ -4,7 +4,7 @@ Pre-1.0: treat this as a release checklist, not a stability policy.
 
 ## Versioning
 
-The launcher workspace root and its three public packages share one version. Run the bump helper from the repository root:
+The native workspace root and every platform/entry package share one version. Run the bump helper from the repository root:
 
 ```sh
 pnpm --dir native/system release:bump patch          # or minor / major / x.y.z
@@ -28,11 +28,14 @@ pnpm --dir native/system typecheck
 pnpm --dir native/system test:entry
 ```
 
-On a Linux host, also rehearse the pack path locally:
+On a supported Linux or macOS host, also rehearse the pack path locally:
 
 ```sh
 pnpm --dir native/system build:native
+pnpm --dir native/system build:test-oracle
 pnpm --dir native/system test:launcher
+pnpm --dir native/system test:flock
+pnpm --dir native/system test:packaging
 node native/system/scripts/pack-release.mjs native/system/.release/npm --current-platform-only
 node native/system/scripts/verify-packed-install.mjs native/system/.release/npm --current-platform-only
 ```
@@ -45,9 +48,9 @@ Use the main repository's `Node Addon System Release` workflow so every binary i
 2. Create and push the `node-addon-system-vX.Y.Z` tag matching the package versions.
 3. Run the same workflow from that tag with `publish=true`.
 
-The workflow publishes only from the final packed tarballs, in `publish-order.txt` order (platform packages before the entry that optionally depends on them). A current-platform rehearsal can still query npm for metadata about an incompatible optional platform package; that package cannot supply the host launcher, which comes from the matching local tarball. Publishing every platform package before the entry ensures a public entry version never points ahead of its platform packages. The workflow supports npm trusted publishing through GitHub OIDC; without it, provide an `NPM_TOKEN` secret in the `npm-publish` environment. Packages publish with `--access public`.
+The workflow publishes only from the final packed tarballs, in `publish-order.txt` order (platform packages before the entry that optionally depends on them). The current-platform rehearsal uses offline npm installation; the current entry and platform package come from local tarballs. Publishing every platform package before the entry ensures a public entry version never points ahead of its platform packages. The workflow supports npm trusted publishing through GitHub OIDC; without it, provide an `NPM_TOKEN` secret in the `npm-publish` environment. Packages publish with `--access public`.
 
-The three scoped package names must be bootstrapped with an `@deepseek-ai` organization token through the `NPM_TOKEN` fallback: npm [requires a package to exist before a trusted publisher can be configured](https://docs.npmjs.com/cli/v11/commands/npm-trust/). After the first release creates all three packages, configure each package to trust `node-addon-system-release.yml` in this repository with the `npm-publish` environment, then remove the fallback token when organization policy permits it.
+New scoped package names must be bootstrapped with an `@deepseek-ai` organization token through the `NPM_TOKEN` fallback: npm [requires a package to exist before a trusted publisher can be configured](https://docs.npmjs.com/cli/v11/commands/npm-trust/). After the first release creates the packages, configure each package to trust `node-addon-system-release.yml` in this repository with the `npm-publish` environment, then remove the fallback token when organization policy permits it.
 
 Manual local fallback (current platform's packages only) — always through `pack-release.mjs`, never `pnpm publish` directly (pnpm's pack path strips the launcher's executable bit; see [packaging.md](packaging.md)):
 

+ 8 - 12
native/system/docs/support-matrix.md

@@ -1,18 +1,14 @@
 # Support matrix
 
-## Supported
-
-| Platform package | GitHub runner (builder of record) | Notes |
+| Platform package suffix | Builder | Payload |
 |---|---|---|
-| `@deepseek-ai/node-addon-system-linux-x64` | `ubuntu-24.04` | static musl — glibc and musl distros alike |
-| `@deepseek-ai/node-addon-system-linux-arm64` | `ubuntu-24.04-arm` | static musl — glibc and musl distros alike |
-
-Enforcement additionally requires a kernel with Landlock enabled (5.13+). The negotiated ABI level decides the probe verdict: every access this build knows governed → `full`; an older ABI governing a subset → `partial` (still confined for everything it supports); Landlock absent or disabled → `unusable`, and the launcher refuses to run commands at all. The probe — not the kernel version — is the authority: a kernel built without Landlock, or with the LSM disabled, probes `unusable` regardless of its version.
+| linux-x64 | ubuntu-24.04 | static Landlock executable; glibc and musl system.node |
+| linux-arm64 | ubuntu-24.04-arm | static Landlock executable; glibc and musl system.node |
+| darwin-x64 | macos-15-intel | system.node |
+| darwin-arm64 | macos-latest | system.node |
 
-## Deliberately unsupported
+The stable Node-API v8 addon is built once per platform/libc and exercised by CI under Node 20, 22, 24, and 26. macOS builds target 11.0 or later. Linux binding selection uses the running Node process's libc; the static launcher serves both libc variants.
 
-- **darwin**: macOS consumers typically confine through `sandbox-exec`/Seatbelt, which ships with the OS — there is no binary to distribute.
-- **win32**: a Windows confinement launcher would be a different mechanism in its own repository, not a port of this one.
-- **Other Linux architectures** (riscv64, s390x, …): no native CI builder of record yet. The no-cross-toolchain rule means a platform package is added only together with a native runner that builds and proves it.
+Landlock additionally requires an enforcing Linux kernel. The functional probe determines full, partial, or unusable enforcement; kernel version alone is not an availability guarantee.
 
-A consumer on an unsupported platform resolves a nonexistent launcher path, probes `unusable`, and falls closed — the documented degradation, exercised by CI's darwin leg.
+Windows has neither a Landlock launcher nor this POSIX addon. The Harness retains its existing Windows semaphore implementation. Other CPU/OS combinations have no published platform package: Landlock probes unusable, and flock acquisition rejects. New platform support requires a native builder and installed-artifact verification.

+ 4 - 1
native/system/package.json

@@ -9,10 +9,13 @@
     "build": "pnpm build:ts",
     "build:ts": "tsc -b",
     "build:native": "tsx ./scripts/build.ts",
+    "build:test-oracle": "node ./scripts/build-test-oracle.mjs",
     "typecheck": "tsc --noEmit && tsc -b --dry",
-    "test": "node ./test/entry.test.js && node ./test/launcher.test.js",
+    "test": "node ./test/entry.test.js && node ./test/launcher.test.js && node --test ./test/flock.test.js ./test/package-matrix.test.js",
     "test:entry": "node ./test/entry.test.js",
     "test:launcher": "node ./test/launcher.test.js",
+    "test:flock": "node --test ./test/flock.test.js",
+    "test:packaging": "node --test ./test/package-matrix.test.js",
     "gha:matrix": "node ./scripts/github-matrix.mjs",
     "release:bump": "node ./scripts/bump-release.mjs",
     "release:commit": "node ./scripts/commit-release.mjs",

+ 28 - 0
native/system/packages/darwin-arm64/LICENSE

@@ -0,0 +1,28 @@
+BSD 3-Clause License
+
+Copyright (c) 2026, node-addon-landlock-run contributors
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 6 - 0
native/system/packages/darwin-arm64/README.i18n.yaml

@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# side as of the last confirmed-consistent state. Both languages carry equal authority;
+# after editing either side, bring the other along and re-record with:
+#   pnpm run verify-translation-pairing --write native/system/packages/darwin-arm64/README.md
+README.md: 71cfaacc9bc442341b3a9d57389c2cb1904fbb70
+README.zh.md: 5e54c8155009b98e3336ea612535ce08e56817ae

+ 9 - 0
native/system/packages/darwin-arm64/README.md

@@ -0,0 +1,9 @@
+---
+description: "Prebuilt system.node for macOS arm64 POSIX locks."
+kind: "package-library"
+---
+# @deepseek-ai/node-addon-system-darwin-arm64
+
+English | [中文](README.zh.md)
+
+This platform package supplies `bin/system.node`, a stable Node-API v8 addon used by `@deepseek-ai/node-addon-system/flock`. It contains no Landlock executable, JavaScript loader, or installation build script. The native workflow builds it on macOS arm64 and owns its installed-artifact validation.

+ 9 - 0
native/system/packages/darwin-arm64/README.zh.md

@@ -0,0 +1,9 @@
+---
+description: "为 macOS arm64 POSIX 锁提供预编译 system.node。"
+kind: "package-library"
+---
+# @deepseek-ai/node-addon-system-darwin-arm64
+
+[English](README.md) | 中文
+
+此平台包提供 `bin/system.node`,供 `@deepseek-ai/node-addon-system/flock` 使用的稳定 Node-API v8 addon。它不包含 Landlock 可执行文件、JavaScript 加载器或安装构建脚本。Native 工作流在 macOS arm64 上构建它,并负责验证安装后的产物。

+ 19 - 0
native/system/packages/darwin-arm64/package.json

@@ -0,0 +1,19 @@
+{
+  "name": "@deepseek-ai/node-addon-system-darwin-arm64",
+  "version": "0.1.1",
+  "description": "Prebuilt POSIX flock Node-API binding for macOS arm64",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/deepseek-harness/deepseek-harness.git",
+    "directory": "native/system/packages/darwin-arm64"
+  },
+  "os": ["darwin"],
+  "cpu": ["arm64"],
+  "files": ["README.md", "bin/", "prebuilds.json"],
+  "scripts": {
+    "prepack": "node ../../scripts/verify-launcher-binary.mjs"
+  },
+  "engines": { "node": ">=20" },
+  "license": "BSD-3-Clause",
+  "publishConfig": { "access": "public" }
+}

+ 6 - 0
native/system/packages/darwin-arm64/prebuilds.json

@@ -0,0 +1,6 @@
+{
+  "platform": "darwin-arm64",
+  "binaries": [
+    { "tool": "flock", "kind": "node-api", "napi": 8, "path": "bin/system.node" }
+  ]
+}

+ 28 - 0
native/system/packages/darwin-x64/LICENSE

@@ -0,0 +1,28 @@
+BSD 3-Clause License
+
+Copyright (c) 2026, node-addon-landlock-run contributors
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 6 - 0
native/system/packages/darwin-x64/README.i18n.yaml

@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# side as of the last confirmed-consistent state. Both languages carry equal authority;
+# after editing either side, bring the other along and re-record with:
+#   pnpm run verify-translation-pairing --write native/system/packages/darwin-x64/README.md
+README.md: 1740788eef943373591a895baeb7207635972a46
+README.zh.md: 644165743486a29e48bee7af33429ebf4f5d8fe2

+ 9 - 0
native/system/packages/darwin-x64/README.md

@@ -0,0 +1,9 @@
+---
+description: "Prebuilt system.node for macOS x64 POSIX locks."
+kind: "package-library"
+---
+# @deepseek-ai/node-addon-system-darwin-x64
+
+English | [中文](README.zh.md)
+
+This platform package supplies `bin/system.node`, a stable Node-API v8 addon used by `@deepseek-ai/node-addon-system/flock`. It contains no Landlock executable, JavaScript loader, or installation build script. The native workflow builds it on macOS x64 and owns its installed-artifact validation.

+ 9 - 0
native/system/packages/darwin-x64/README.zh.md

@@ -0,0 +1,9 @@
+---
+description: "为 macOS x64 POSIX 锁提供预编译 system.node。"
+kind: "package-library"
+---
+# @deepseek-ai/node-addon-system-darwin-x64
+
+[English](README.md) | 中文
+
+此平台包提供 `bin/system.node`,供 `@deepseek-ai/node-addon-system/flock` 使用的稳定 Node-API v8 addon。它不包含 Landlock 可执行文件、JavaScript 加载器或安装构建脚本。Native 工作流在 macOS x64 上构建它,并负责验证安装后的产物。

+ 19 - 0
native/system/packages/darwin-x64/package.json

@@ -0,0 +1,19 @@
+{
+  "name": "@deepseek-ai/node-addon-system-darwin-x64",
+  "version": "0.1.1",
+  "description": "Prebuilt POSIX flock Node-API binding for macOS x64",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/deepseek-harness/deepseek-harness.git",
+    "directory": "native/system/packages/darwin-x64"
+  },
+  "os": ["darwin"],
+  "cpu": ["x64"],
+  "files": ["README.md", "bin/", "prebuilds.json"],
+  "scripts": {
+    "prepack": "node ../../scripts/verify-launcher-binary.mjs"
+  },
+  "engines": { "node": ">=20" },
+  "license": "BSD-3-Clause",
+  "publishConfig": { "access": "public" }
+}

+ 6 - 0
native/system/packages/darwin-x64/prebuilds.json

@@ -0,0 +1,6 @@
+{
+  "platform": "darwin-x64",
+  "binaries": [
+    { "tool": "flock", "kind": "node-api", "napi": 8, "path": "bin/system.node" }
+  ]
+}

+ 2 - 2
native/system/packages/entry/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write native/system/packages/entry/README.md
-README.md: 4bf08c3845f9a627b8d6f7fffdf928c386cfa57f
-README.zh.md: c6b7d8ffa40f909f63ad02c4eb26576418932d7e
+README.md: af3f5ac4df5883d7d9b4f65d273f9d45fae874e8
+README.zh.md: 55f428568cb7b5e7a9956c842931f52e0feeaf0a

+ 8 - 11
native/system/packages/entry/README.md

@@ -1,18 +1,15 @@
+---
+description: "JavaScript entry for the prebuilt Landlock launcher and asynchronous POSIX flock."
+kind: "package-library"
+---
 # @deepseek-ai/node-addon-system
 
 English | [中文](README.zh.md)
 
-Landlock self-restrict-then-exec launcher for confining subprocesses on Linux: this entry package resolves the per-platform prebuilt binary, runs its functional enforcement probe, and builds its grant argv — consumers never spell launcher flags or parse launcher output themselves.
+The root entry exports the Landlock launcher path, enforcement probe, grant arguments, and protocol constants. Its independent `./flock` entry exports `tryLockExclusive(fd): Promise<void>`; importing either entry does not load `system.node`.
 
-```js
-import { grantArgs, launcherPath, probe } from '@deepseek-ai/node-addon-system';
+The lock operation attempts `LOCK_EX | LOCK_NB` asynchronously. Keep the caller-owned descriptor open until completion; contention rejects with `EAGAIN`/`EWOULDBLOCK`, other syscall failures also reject, and errors carry their code, positive errno, and `syscall: 'flock'`. Native setup errors reject the same promise. Closing the last descriptor for the open file description releases the lock. The binding does not open, duplicate, close, or explicitly unlock descriptors.
 
-const launcher = launcherPath();
-if (probe(launcher) !== 'unusable') {
-  const argv = [launcher, ...grantArgs({ readOnly: ['/'], readWrite: ['/tmp/work'] }), '--', 'bash', '-c', command];
-}
-```
+Optional OS/CPU platform packages carry the binaries. Linux has `bin/landlock-run` and separate `bin/glibc/system.node` / `bin/musl/system.node`; macOS has `bin/system.node`. Missing or unloadable flock bindings reject acquisition, without installation-time compilation. Landlock remains a separate executable with its existing fail-closed protocol; unsupported kernels/platforms probe unusable.
 
-The launcher installs a Landlock ruleset on itself and `exec`s the wrapped command; the ruleset is inherited across `execve`, so the whole process tree runs confined. Everything not granted is denied, and launcher failures exit `125` without running the command — fail-closed, never fail-open. The binary contract is pinned in the repo's `docs/cli-contract.md`; the C source rides this tarball (`src/main.c`) for audit.
-
-Platform packages (`os`/`cpu`-selected optional dependencies, no JavaScript inside): `@deepseek-ai/node-addon-system-linux-x64`, `@deepseek-ai/node-addon-system-linux-arm64`. On hosts without one, `launcherPath()` returns a deterministic nonexistent path and `probe()` reports `'unusable'` — there is deliberately no install-time compile fallback.
+The two C sources ship for auditability. See the workspace [architecture](../../docs/architecture.md), [support matrix](../../docs/support-matrix.md), and [CLI contract](../../docs/cli-contract.md).

+ 8 - 11
native/system/packages/entry/README.zh.md

@@ -1,18 +1,15 @@
+---
+description: "预编译 Landlock 启动器与异步 POSIX flock 的 JavaScript 入口。"
+kind: "package-library"
+---
 # @deepseek-ai/node-addon-system
 
 [English](README.md) | 中文
 
-用于在 Linux 上限制子进程的 Landlock「先限制自身、再执行」启动器:此入口包定位对应平台的预构建二进制文件,运行功能性强制执行探测,并构建其授权 argv。消费方无需自行拼写启动器标志或解析启动器输出
+根入口导出 Landlock 启动器路径、强制执行探测、授权参数和协议常量。独立的 `./flock` 入口导出 `tryLockExclusive(fd): Promise<void>`;导入任一入口都不会加载 `system.node`
 
-```js
-import { grantArgs, launcherPath, probe } from '@deepseek-ai/node-addon-system';
+锁操作异步尝试 `LOCK_EX | LOCK_NB`。在完成前保持调用方拥有的描述符打开;竞争以 `EAGAIN`/`EWOULDBLOCK` 拒绝,其他系统调用失败也会拒绝,错误携带 code、正 errno 和 `syscall: 'flock'`。原生调用准备阶段的错误也会拒绝同一个 promise。关闭该打开文件描述的最后一个描述符即释放锁。绑定不打开、复制、关闭或显式解锁描述符。
 
-const launcher = launcherPath();
-if (probe(launcher) !== 'unusable') {
-  const argv = [launcher, ...grantArgs({ readOnly: ['/'], readWrite: ['/tmp/work'] }), '--', 'bash', '-c', command];
-}
-```
+可选操作系统/CPU 平台包携带二进制。Linux 包含 `bin/landlock-run` 和分别用于两种 libc 的 `bin/glibc/system.node` / `bin/musl/system.node`;macOS 包含 `bin/system.node`。flock 绑定缺失或无法加载时拒绝获取,不在安装时编译。Landlock 仍是遵循既有失败关闭协议的独立可执行文件;不支持的内核或平台探测为不可用。
 
-启动器在自身上安装 Landlock 规则集,再 `exec` 被包装的命令;该规则集会跨 `execve` 继承,因此整个进程树都在限制下运行。未授予的一切都被拒绝;启动器失败时以 `125` 退出且不运行命令:采用失败闭合策略,绝不在失败时放行。二进制约定锁定在仓库的 `docs/cli-contract.md` 中;C 源码作为 `src/main.c` 随该 tarball 分发,便于审计。
-
-平台包(由 `os`/`cpu` 选择的可选依赖,内部不含 JavaScript):`@deepseek-ai/node-addon-system-linux-x64`、`@deepseek-ai/node-addon-system-linux-arm64`。在缺少对应包的宿主上,`launcherPath()` 返回一个固定但不存在的路径,`probe()` 报告 `'unusable'`;系统有意不提供安装时编译回退。
+两个 C 源文件随包分发以供审计。参见工作区[架构](../../docs/architecture.md)、[支持矩阵](../../docs/support-matrix.md)和 [CLI 约定](../../docs/cli-contract.md)。

+ 9 - 2
native/system/packages/entry/package.json

@@ -2,7 +2,7 @@
   "name": "@deepseek-ai/node-addon-system",
   "version": "0.1.1",
   "type": "module",
-  "description": "Landlock self-restrict-then-exec launcher for sandboxing subprocesses on Linux: per-platform prebuilt static binaries plus the JS seam that resolves, probes, and speaks their CLI contract",
+  "description": "Prebuilt system primitives: a Linux Landlock launcher and asynchronous POSIX flock through stable Node-API",
   "repository": {
     "type": "git",
     "url": "git+https://github.com/deepseek-harness/deepseek-harness.git",
@@ -15,13 +15,18 @@
       "types": "./lib/index.d.ts",
       "default": "./lib/index.js"
     },
+    "./flock": {
+      "types": "./lib/flock.d.ts",
+      "default": "./lib/flock.js"
+    },
     "./package.json": "./package.json"
   },
   "files": [
     "README.md",
     "lib/",
     "!lib/*.tsbuildinfo",
-    "src/main.c"
+    "src/main.c",
+    "src/flock.c"
   ],
   "scripts": {
     "build:js": "tsc -b",
@@ -35,6 +40,8 @@
     "access": "public"
   },
   "optionalDependencies": {
+    "@deepseek-ai/node-addon-system-darwin-arm64": "workspace:*",
+    "@deepseek-ai/node-addon-system-darwin-x64": "workspace:*",
     "@deepseek-ai/node-addon-system-linux-arm64": "workspace:*",
     "@deepseek-ai/node-addon-system-linux-x64": "workspace:*"
   }

+ 164 - 0
native/system/packages/entry/src/flock.c

@@ -0,0 +1,164 @@
+/*
+ * Node-API v8 binding for asynchronous flock(LOCK_EX | LOCK_NB).
+ * The caller owns fd through completion; this module never opens, duplicates,
+ * closes, or explicitly unlocks it. The callback receives zero or a positive
+ * errno; JavaScript owns the promise and syscall error construction.
+ */
+
+#include <node_api.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <sys/file.h>
+
+typedef struct {
+  napi_env env;
+  napi_ref callback;
+  napi_async_work work;
+  napi_async_cleanup_hook_handle cleanup;
+  int fd;
+  int error;
+  bool closing;
+} lock_request;
+
+static void check_status(napi_status status, const char *message) {
+  if (status != napi_ok) {
+    napi_fatal_error("flock", NAPI_AUTO_LENGTH, message, NAPI_AUTO_LENGTH);
+  }
+}
+
+static void release_request(lock_request *request) {
+  if (request->callback != NULL) {
+    (void)napi_delete_reference(request->env, request->callback);
+  }
+  if (request->work != NULL) {
+    (void)napi_delete_async_work(request->env, request->work);
+  }
+  if (request->cleanup != NULL) {
+    (void)napi_remove_async_cleanup_hook(request->cleanup);
+  }
+  free(request);
+}
+
+static napi_value throw_setup_error(napi_env env, napi_status status,
+                                   const char *message) {
+  if (status != napi_pending_exception) {
+    status = napi_throw_error(env, "ERR_FLOCK_ASYNC_WORK", message);
+    bool pending;
+    /* Error construction can fail with both generic_failure and a JS exception. */
+    check_status(napi_is_exception_pending(env, &pending), "Cannot inspect flock setup exception");
+    if (!pending && status != napi_pending_exception) check_status(status, message);
+  }
+  return NULL;
+}
+
+static void execute_lock(napi_env env, void *data) {
+  (void)env;
+  lock_request *request = data;
+  request->error = flock(request->fd, LOCK_EX | LOCK_NB) == 0 ? 0 : errno;
+}
+
+static void complete_lock(napi_env env, napi_status status, void *data) {
+  lock_request *request = data;
+  if (env != NULL && !request->closing) {
+    napi_value callback;
+    napi_value receiver;
+    napi_value result;
+    check_status(status, "flock async work did not complete");
+    check_status(napi_get_reference_value(env, request->callback, &callback),
+                 "Cannot retrieve flock callback");
+    check_status(napi_get_undefined(env, &receiver), "Cannot create flock receiver");
+    check_status(napi_create_int32(env, request->error, &result),
+                 "Cannot create flock result");
+    status = napi_call_function(env, receiver, callback, 1, &result, NULL);
+  } else {
+    status = napi_ok;
+  }
+  release_request(request);
+  /* Node's async-work dispatcher reports callback exceptions and handles termination. */
+  if (status != napi_pending_exception) {
+    check_status(status, "Cannot invoke flock callback");
+  }
+}
+
+static void cleanup_lock(napi_async_cleanup_hook_handle handle, void *data) {
+  (void)handle;
+  lock_request *request = data;
+  request->closing = true;
+  /*
+   * Running work cannot be cancelled. The hook keeps the environment alive
+   * until completion releases both the work and this hook; it never frees
+   * memory that the execution thread can still access.
+   */
+  (void)napi_cancel_async_work(request->env, request->work);
+}
+
+static napi_value try_lock(napi_env env, napi_callback_info info) {
+  size_t argc = 2;
+  napi_value argv[2];
+  double fd;
+  napi_status status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
+  if (status != napi_ok) {
+    return throw_setup_error(env, status, "Cannot read flock arguments");
+  }
+  if (argc < 1 || napi_get_value_double(env, argv[0], &fd) != napi_ok) {
+    (void)napi_throw_type_error(env, NULL, "fd must be a number");
+    return NULL;
+  }
+  if (!(fd >= INT_MIN && fd <= INT_MAX) || fd != (int)fd) {
+    (void)napi_throw_range_error(env, NULL, "fd must be a signed C int");
+    return NULL;
+  }
+  napi_valuetype callback_type;
+  if (argc < 2 || napi_typeof(env, argv[1], &callback_type) != napi_ok ||
+      callback_type != napi_function) {
+    (void)napi_throw_type_error(env, NULL, "callback must be a function");
+    return NULL;
+  }
+
+  lock_request *request = calloc(1, sizeof(*request));
+  if (request == NULL) {
+    (void)napi_throw_error(env, "ENOMEM", "Cannot allocate flock async work");
+    return NULL;
+  }
+  request->env = env;
+  request->fd = (int)fd;
+
+  status = napi_create_reference(env, argv[1], 1, &request->callback);
+  if (status != napi_ok) {
+    release_request(request);
+    return throw_setup_error(env, status, "Cannot retain flock callback");
+  }
+  napi_value name;
+  status = napi_create_string_utf8(env, "flock", NAPI_AUTO_LENGTH, &name);
+  if (status == napi_ok) {
+    status = napi_create_async_work(env, NULL, name, execute_lock, complete_lock,
+                                   request, &request->work);
+  }
+  if (status != napi_ok) {
+    release_request(request);
+    return throw_setup_error(env, status, "Cannot create flock async work");
+  }
+  status = napi_add_async_cleanup_hook(env, cleanup_lock, request, &request->cleanup);
+  if (status != napi_ok) {
+    release_request(request);
+    return throw_setup_error(env, status, "Cannot register flock environment cleanup");
+  }
+  status = napi_queue_async_work(env, request->work);
+  if (status != napi_ok) {
+    /* Queue failure schedules no completion callback. */
+    release_request(request);
+    return throw_setup_error(env, status, "Cannot queue flock async work");
+  }
+  return NULL;
+}
+
+NAPI_MODULE_INIT() {
+  napi_value function;
+  if (napi_create_function(env, "tryLock", NAPI_AUTO_LENGTH, try_lock,
+                           NULL, &function) != napi_ok ||
+      napi_set_named_property(env, exports, "tryLock", function) != napi_ok) {
+    return NULL;
+  }
+  return exports;
+}

+ 57 - 0
native/system/packages/entry/src/flock.ts

@@ -0,0 +1,57 @@
+/** Lazy POSIX flock entry; importing it does not load a native addon. */
+import { createRequire } from 'node:module'
+import { dirname, join } from 'node:path'
+import { getSystemErrorName } from 'node:util'
+
+interface FlockBinding {
+  tryLock(fd: number, callback: (errno: number) => void): void
+}
+
+let binding: FlockBinding | undefined
+
+function loadBinding(): FlockBinding {
+  if (binding) return binding
+  const { platform, arch } = process
+  if (platform !== 'linux' && platform !== 'darwin') {
+    throw Object.assign(new Error(`flock is not supported on ${platform}-${arch}`), {
+      code: 'ERR_FLOCK_UNSUPPORTED_PLATFORM',
+      syscall: 'flock',
+    })
+  }
+
+  let filename = 'system.node'
+  if (platform === 'linux') {
+    // Node's report types omit the libc field supplied by Linux reports.
+    const report = process.report.getReport() as { header: { glibcVersionRuntime?: string } }
+    filename = join(report.header.glibcVersionRuntime ? 'glibc' : 'musl', filename)
+  }
+  const require = createRequire(import.meta.url)
+  const manifest = require.resolve(`@deepseek-ai/node-addon-system-${platform}-${arch}/package.json`)
+  binding = require(join(dirname(manifest), 'bin', filename)) as FlockBinding
+  return binding
+}
+
+/**
+ * Attempt an exclusive, nonblocking POSIX flock on the caller's descriptor.
+ * The syscall runs in asynchronous work, so acquisition can occur after this
+ * call returns. Keep fd open until the promise settles; the binding never
+ * opens, duplicates, or closes it. Closing the locked descriptor releases the
+ * lock once all descriptors for its open file description are closed.
+ * @param fd - Open file descriptor to lock; ownership remains with the caller.
+ * @returns A promise resolving to void on acquisition. Contention rejects with
+ *   EAGAIN/EWOULDBLOCK; other syscall failures also reject. Syscall errors carry
+ *   code, positive errno, and syscall='flock'. Native setup errors, unsupported
+ *   platforms, and addon loading failures reject; importing alone does not load it.
+ */
+export async function tryLockExclusive(fd: number): Promise<void> {
+  const errno = await new Promise<number>((resolve) => {
+    loadBinding().tryLock(fd, resolve)
+  })
+  if (errno === 0) return
+  const code = getSystemErrorName(-errno)
+  throw Object.assign(new Error(`${code}: flock failed`), {
+    code,
+    errno,
+    syscall: 'flock',
+  })
+}

+ 2 - 2
native/system/packages/linux-arm64/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write native/system/packages/linux-arm64/README.md
-README.md: 11449d1043ea9913424d732af4f3c81599252a0b
-README.zh.md: d74a65aa94a840e1432ef23c43b1ad1cfe356385
+README.md: b1e97481a3f59b5b7cf85e6565cd3d078e42cd7f
+README.zh.md: 79f3b75e16570c4dff8d7249bcd75481f11cc85c

+ 6 - 4
native/system/packages/linux-arm64/README.md

@@ -1,9 +1,11 @@
+---
+description: "Prebuilt Landlock launcher and POSIX flock addons for Linux arm64."
+kind: "package-library"
+---
 # @deepseek-ai/node-addon-system-linux-arm64
 
 English | [中文](README.zh.md)
 
-Prebuilt `bin/landlock-run` Landlock launcher for linux-arm64 — a static musl binary compiled natively (no cross toolchain) from the C source shipped in [`@deepseek-ai/node-addon-system`](https://www.npmjs.com/package/@deepseek-ai/node-addon-system). npm's `os`/`cpu` fields select this package at install time; the entry package resolves it to a file path — it ships no JavaScript and is never imported.
+This platform package contains the static musl executable `bin/landlock-run` and Node-API v8 addons `bin/glibc/system.node` and `bin/musl/system.node`. The entry chooses the addon matching the running Node process's libc; the Landlock executable serves both libc systems.
 
-The binary is git-ignored and rides the npm tarball via the `files` list; the `prepack` gate refuses to pack when it is missing or has the wrong ELF architecture, and the release pipeline byte-pins the packed binary against the CI build it came from. Static musl linking means one binary for glibc and musl distros alike — hence no libc suffix in the name.
-
-Sibling: `@deepseek-ai/node-addon-system-linux-x64`.
+The package contains no JavaScript or installation build script. Platform prepack checks complete payloads, ELF architecture, Node-API exports, and launcher executability; the installed-artifact rehearsal checks bytes and executes native behavior. See the workspace [support matrix](../../docs/support-matrix.md).

+ 6 - 4
native/system/packages/linux-arm64/README.zh.md

@@ -1,9 +1,11 @@
+---
+description: "为 Linux arm64 提供预编译 Landlock 启动器和 POSIX flock addon。"
+kind: "package-library"
+---
 # @deepseek-ai/node-addon-system-linux-arm64
 
 [English](README.md) | 中文
 
-面向 linux-arm64 的预构建 `bin/landlock-run` Landlock 启动器:一个由 [`@deepseek-ai/node-addon-system`](https://www.npmjs.com/package/@deepseek-ai/node-addon-system) 包所附的 C 源码原生编译而成的静态 musl 二进制文件(不使用交叉工具链)。npm 的 `os`/`cpu` 字段在安装时选择此包;入口包将其定位到文件路径。该包不包含 JavaScript,也绝不会被导入
+此平台包包含静态 musl 可执行文件 `bin/landlock-run`,以及 Node-API v8 addon `bin/glibc/system.node` 和 `bin/musl/system.node`。入口包按运行 Node 进程的 libc 选择 addon;Landlock 可执行文件在两种 libc 系统上共用
 
-该二进制文件被 git 忽略,并通过 `files` 列表进入 npm tarball;如果文件缺失或 ELF 架构错误,`prepack` 门禁会拒绝打包,发布流水线则会按字节核验打包的二进制文件与其来源 CI 构建产物一致。静态 musl 链接使同一个二进制文件同时适用于 glibc 和 musl 发行版,因此名称中没有 libc 后缀。
-
-同级包:`@deepseek-ai/node-addon-system-linux-x64`。
+包中没有 JavaScript 或安装编译脚本。平台 prepack 检查完整产物、ELF 架构、Node-API 导出和启动器可执行权限;安装演练核对字节并执行原生行为。参见工作区[支持矩阵](../../docs/support-matrix.md)。

+ 1 - 1
native/system/packages/linux-arm64/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/node-addon-system-linux-arm64",
   "version": "0.1.1",
-  "description": "Prebuilt landlock-run Landlock launcher binary for linux-arm64 (static musl) — resolved as a file path by @deepseek-ai/node-addon-system, never imported",
+  "description": "Linux arm64 system binaries: static Landlock launcher and glibc/musl Node-API flock addons",
   "repository": {
     "type": "git",
     "url": "git+https://github.com/deepseek-harness/deepseek-harness.git",

+ 14 - 0
native/system/packages/linux-arm64/prebuilds.json

@@ -5,6 +5,20 @@
       "tool": "landlock-run",
       "kind": "static-musl",
       "path": "bin/landlock-run"
+    },
+    {
+      "tool": "flock",
+      "kind": "node-api",
+      "napi": 8,
+      "libc": "glibc",
+      "path": "bin/glibc/system.node"
+    },
+    {
+      "tool": "flock",
+      "kind": "node-api",
+      "napi": 8,
+      "libc": "musl",
+      "path": "bin/musl/system.node"
     }
   ]
 }

+ 2 - 2
native/system/packages/linux-x64/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write native/system/packages/linux-x64/README.md
-README.md: c0b85ccbecd88df943d645fdd3c5880f7d5f3000
-README.zh.md: b0e04e1be41b21089801b6d8c9156e3d73558fff
+README.md: caebb5d8a888a69a952fc89afb3edfb8ece489fc
+README.zh.md: 6ad1dcf6fe26007f76cdeb98936a94262ece4d7c

+ 6 - 4
native/system/packages/linux-x64/README.md

@@ -1,9 +1,11 @@
+---
+description: "Prebuilt Landlock launcher and POSIX flock addons for Linux x64."
+kind: "package-library"
+---
 # @deepseek-ai/node-addon-system-linux-x64
 
 English | [中文](README.zh.md)
 
-Prebuilt `bin/landlock-run` Landlock launcher for linux-x64 — a static musl binary compiled natively (no cross toolchain) from the C source shipped in [`@deepseek-ai/node-addon-system`](https://www.npmjs.com/package/@deepseek-ai/node-addon-system). npm's `os`/`cpu` fields select this package at install time; the entry package resolves it to a file path — it ships no JavaScript and is never imported.
+This platform package contains the static musl executable `bin/landlock-run` and Node-API v8 addons `bin/glibc/system.node` and `bin/musl/system.node`. The entry chooses the addon matching the running Node process's libc; the Landlock executable serves both libc systems.
 
-The binary is git-ignored and rides the npm tarball via the `files` list; the `prepack` gate refuses to pack when it is missing or has the wrong ELF architecture, and the release pipeline byte-pins the packed binary against the CI build it came from. Static musl linking means one binary for glibc and musl distros alike — hence no libc suffix in the name.
-
-Sibling: `@deepseek-ai/node-addon-system-linux-arm64`.
+The package contains no JavaScript or installation build script. Platform prepack checks complete payloads, ELF architecture, Node-API exports, and launcher executability; the installed-artifact rehearsal checks bytes and executes native behavior. See the workspace [support matrix](../../docs/support-matrix.md).

+ 6 - 4
native/system/packages/linux-x64/README.zh.md

@@ -1,9 +1,11 @@
+---
+description: "为 Linux x64 提供预编译 Landlock 启动器和 POSIX flock addon。"
+kind: "package-library"
+---
 # @deepseek-ai/node-addon-system-linux-x64
 
 [English](README.md) | 中文
 
-面向 linux-x64 的预构建 `bin/landlock-run` Landlock 启动器:一个由 [`@deepseek-ai/node-addon-system`](https://www.npmjs.com/package/@deepseek-ai/node-addon-system) 包所附的 C 源码原生编译而成的静态 musl 二进制文件(不使用交叉工具链)。npm 的 `os`/`cpu` 字段在安装时选择此包;入口包将其定位到文件路径。该包不包含 JavaScript,也绝不会被导入
+此平台包包含静态 musl 可执行文件 `bin/landlock-run`,以及 Node-API v8 addon `bin/glibc/system.node` 和 `bin/musl/system.node`。入口包按运行 Node 进程的 libc 选择 addon;Landlock 可执行文件在两种 libc 系统上共用
 
-该二进制文件被 git 忽略,并通过 `files` 列表进入 npm tarball;如果文件缺失或 ELF 架构错误,`prepack` 门禁会拒绝打包,发布流水线则会按字节核验打包的二进制文件与其来源 CI 构建产物一致。静态 musl 链接使同一个二进制文件同时适用于 glibc 和 musl 发行版,因此名称中没有 libc 后缀。
-
-同级包:`@deepseek-ai/node-addon-system-linux-arm64`。
+包中没有 JavaScript 或安装编译脚本。平台 prepack 检查完整产物、ELF 架构、Node-API 导出和启动器可执行权限;安装演练核对字节并执行原生行为。参见工作区[支持矩阵](../../docs/support-matrix.md)。

+ 1 - 1
native/system/packages/linux-x64/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/node-addon-system-linux-x64",
   "version": "0.1.1",
-  "description": "Prebuilt landlock-run Landlock launcher binary for linux-x64 (static musl) — resolved as a file path by @deepseek-ai/node-addon-system, never imported",
+  "description": "Linux x64 system binaries: static Landlock launcher and glibc/musl Node-API flock addons",
   "repository": {
     "type": "git",
     "url": "git+https://github.com/deepseek-harness/deepseek-harness.git",

+ 14 - 0
native/system/packages/linux-x64/prebuilds.json

@@ -5,6 +5,20 @@
       "tool": "landlock-run",
       "kind": "static-musl",
       "path": "bin/landlock-run"
+    },
+    {
+      "tool": "flock",
+      "kind": "node-api",
+      "napi": 8,
+      "libc": "glibc",
+      "path": "bin/glibc/system.node"
+    },
+    {
+      "tool": "flock",
+      "kind": "node-api",
+      "napi": 8,
+      "libc": "musl",
+      "path": "bin/musl/system.node"
     }
   ]
 }

+ 6 - 3
native/system/scripts/assemble-prebuilds.mjs

@@ -4,7 +4,7 @@
  * verify the result. The Release workflow's build legs upload one
  * `prebuild-<package>` artifact per platform package (its `bin/` payload);
  * this script copies each into `packages/<package>/bin/` and then checks
- * every declared binary for presence and ELF architecture.
+ * every declared binary for presence and native architecture.
  *
  * Usage: `node scripts/assemble-prebuilds.mjs <artifact-root>`.
  */
@@ -39,13 +39,16 @@ for (const artifactName of fs.readdirSync(artifactRoot)) {
   for (const file of fs.readdirSync(artifactDir)) {
     const source = path.join(artifactDir, file);
     const destination = path.join(root, 'packages', name, 'bin', file);
-    fs.copyFileSync(source, destination);
-    fs.chmodSync(destination, 0o755);
+    fs.cpSync(source, destination, { recursive: true, preserveTimestamps: true });
     console.log(`Copied ${path.relative(root, source)} -> ${path.relative(root, destination)}`);
   }
 }
 
 for (const dir of platformDirs()) {
+  const metadata = JSON.parse(fs.readFileSync(path.join(root, dir, 'prebuilds.json'), 'utf8'));
+  for (const binary of metadata.binaries) {
+    if (binary.kind === 'static-musl') fs.chmodSync(path.join(root, dir, binary.path), 0o755);
+  }
   const { name, count } = verifyPlatformBinaries(path.join(root, dir));
   console.log(`Verified ${name}: ${count} binaries`);
 }

+ 23 - 0
native/system/scripts/build-test-oracle.mjs

@@ -0,0 +1,23 @@
+/** Build the independent POSIX flock oracle used by native behavior tests. */
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { spawnSync } from 'node:child_process';
+
+const root = fileURLToPath(new URL('..', import.meta.url));
+if (process.platform !== 'linux' && process.platform !== 'darwin') {
+  throw new Error('The flock oracle is a POSIX test fixture');
+}
+const variants = process.platform === 'linux' ? ['glibc', 'musl'] : [''];
+for (const variant of variants) {
+  const compiler = variant === 'musl' ? 'musl-gcc' : 'cc';
+  const output = path.join(root, 'test/bin', variant, 'flock-oracle');
+  fs.mkdirSync(path.dirname(output), { recursive: true });
+  const args = ['-std=c11', '-O2', '-Wall', '-Wextra', '-Werror'];
+  if (process.platform === 'darwin') args.push('-mmacosx-version-min=11.0');
+  if (variant === 'musl') args.push('-static');
+  const result = spawnSync(compiler, [...args, path.join(root, 'test/fixtures/flock-oracle.c'), '-o', output], { stdio: 'inherit' });
+  if (result.error) throw result.error;
+  if (result.status !== 0) throw new Error(`${compiler} failed to build the flock oracle`);
+  console.log(`Built test oracle ${path.relative(root, output)}`);
+}

+ 78 - 72
native/system/scripts/build.ts

@@ -1,86 +1,92 @@
 /**
- * Build every native tool this host can build, into its per-platform
- * package.
- *
- * Targets are derived from the checked-in matrix: each
- * `packages/<name>/prebuilds.json` whose `platform` matches this host names
- * the binaries to produce; the TOOLS table below maps each `tool` to its C
- * source. Builds are NATIVE-ONLY — each Linux architecture compiles its own
- * binary with the distro's `musl-gcc` (static musl: runs on glibc and musl
- * distros alike, no loader or libc expectations on the consumer host), and
- * CI's per-arch runners are the builders of record. No cross toolchain
- * exists here on purpose: native runners replace it, and the audit surface
- * is the reviewed C source plus the CI job that built the binary.
- *
- * Binaries land in `packages/<name>/bin/` — git-ignored (root
- * `.gitignore`), packed into the platform package's npm tarball behind its
- * `prepack` gate (`scripts/verify-launcher-binary.mjs`).
- *
- * Run: `pnpm run build:native` (Linux with musl-gcc on PATH:
- * `apt-get install musl-tools`). Non-Linux hosts fail fast — no platform
- * package exists for them to build.
+ * Build this host's declared system binaries. Landlock is a static musl
+ * executable; flock uses stable Node-API with separate Linux libc builds.
+ * Node headers come from the Node installation running this script.
  */
 import { spawnSync } from 'node:child_process'
-import { existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs'
+import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync } from 'node:fs'
 import { basename, dirname, join, resolve } from 'node:path'
+import { parseArgs } from 'node:util'
 
-/** Each native tool's C source, keyed by the `tool` field in prebuilds.json. */
-const TOOLS: Record<string, { source: string }> = {
-  'landlock-run': { source: 'packages/entry/src/main.c' },
+const root = resolve(import.meta.dirname, '..')
+const { values } = parseArgs({ options: { 'host-addon-only': { type: 'boolean' } }, allowPositionals: false })
+const hostAddonOnly = values['host-addon-only'] === true
+const sources: Record<string, string> = {
+  'landlock-run': 'packages/entry/src/main.c',
+  flock: 'packages/entry/src/flock.c',
 }
 
-const repoRoot = resolve(import.meta.dirname, '..')
-
-if (process.platform !== 'linux') {
-  console.error(`build: native tools are built natively per Linux architecture (no cross toolchain) — nothing to build on ${process.platform}. CI's per-arch runners build and rehearse every platform package.`)
-  process.exit(1)
+interface Binary {
+  tool: string
+  kind: string
+  path: string
+  napi?: number
+  libc?: string
 }
-const hostPlatform = `linux-${process.arch}`
 
-/** This host's platform packages, from the checked-in matrix. */
-const targets: { packageDir: string; tool: string; binaryPath: string; kind: string }[] = []
-const packagesRoot = join(repoRoot, 'packages')
-for (const name of readdirSync(packagesRoot).sort()) {
-  const prebuildsFile = join(packagesRoot, name, 'prebuilds.json')
-  if (!existsSync(prebuildsFile)) continue
-  const prebuilds = JSON.parse(readFileSync(prebuildsFile, 'utf8')) as {
-    platform: string
-    binaries: { tool: string; kind: string; path: string }[]
-  }
-  if (prebuilds.platform !== hostPlatform) continue
-  for (const binary of prebuilds.binaries) {
-    targets.push({ packageDir: join(packagesRoot, name), tool: binary.tool, binaryPath: binary.path, kind: binary.kind })
-  }
-}
-if (targets.length === 0) {
-  console.error(`build: no platform package declares binaries for ${hostPlatform} — supported platforms are the packages/*/prebuilds.json "platform" values.`)
-  process.exit(1)
+if (process.platform !== 'linux' && process.platform !== 'darwin') {
+  if (hostAddonOnly) process.exit(0)
+  throw new Error('build: system binaries are built on Linux or macOS; no native target for this host')
 }
+const host = `${process.platform}-${process.arch}`
+const libc = process.platform === 'linux'
+  ? ((process.report.getReport() as { header: { glibcVersionRuntime?: string } }).header.glibcVersionRuntime ? 'glibc' : 'musl')
+  : undefined
+const headers = resolve(dirname(process.execPath), '../include/node')
+let built = 0
 
-for (const target of targets) {
-  const tool = TOOLS[target.tool]
-  if (tool === undefined) {
-    console.error(`build: prebuilds.json names unknown tool "${target.tool}" — add it to the TOOLS table in scripts/build.ts.`)
-    process.exit(1)
-  }
-  if (target.kind !== 'static-musl') {
-    console.error(`build: unknown binary kind "${target.kind}" — the only toolchain here is static musl.`)
-    process.exit(1)
-  }
-  const binary = join(target.packageDir, target.binaryPath)
-  mkdirSync(dirname(binary), { recursive: true })
+for (const name of readdirSync(join(root, 'packages')).sort()) {
+  const dir = join(root, 'packages', name)
+  const metadata = join(dir, 'prebuilds.json')
+  if (!existsSync(metadata)) continue
+  const spec = JSON.parse(readFileSync(metadata, 'utf8')) as { platform: string; binaries: Binary[] }
+  if (spec.platform !== host) continue
+
+  for (const binary of spec.binaries) {
+    if (hostAddonOnly && (binary.kind !== 'node-api' || (binary.libc !== undefined && binary.libc !== libc))) continue
+    const source = sources[binary.tool]
+    if (source === undefined) throw new Error(`build: unknown tool ${binary.tool}`)
+    const output = join(dir, binary.path)
+    mkdirSync(dirname(output), { recursive: true })
+    let compiler: string
+    let flags: string[]
+
+    if (binary.kind === 'static-musl' && process.platform === 'linux' && binary.tool === 'landlock-run') {
+      compiler = 'musl-gcc'
+      flags = ['-std=c11', '-Os', '-Wall', '-Wextra', '-Werror', '-static', '-s']
+    } else if (binary.kind === 'node-api' && binary.tool === 'flock' && binary.napi === 8) {
+      if (!existsSync(join(headers, 'node_api.h'))) {
+        throw new Error(`build: Node-API headers missing at ${headers}; use a Node installation with development headers`)
+      }
+      compiler = process.platform === 'linux' && binary.libc === 'musl' ? 'musl-gcc' : 'cc'
+      flags = ['-std=c11', '-O2', '-Wall', '-Wextra', '-Werror', '-fPIC', '-fvisibility=hidden', '-DNAPI_VERSION=8', '-I', headers]
+      if (process.platform === 'darwin') {
+        if (binary.libc !== undefined) throw new Error('build: macOS flock does not select a Linux libc')
+        flags.push('-bundle', '-undefined', 'dynamic_lookup', '-mmacosx-version-min=11.0')
+      } else {
+        if (binary.libc !== 'glibc' && binary.libc !== 'musl') {
+          throw new Error('build: Linux flock must select glibc or musl')
+        }
+        flags.push('-shared')
+      }
+    } else {
+      throw new Error(`build: unsupported ${binary.tool}/${binary.kind} target on ${host}`)
+    }
 
-  // -static against musl: self-contained, no loader/libc expectations on the
-  // consumer host. -Werror is safe to keep hard: CI pins the builder images,
-  // and a new warning on a toolchain bump deserves a look, not a pass.
-  const result = spawnSync('musl-gcc', [
-    '-std=c11', '-Os', '-Wall', '-Wextra', '-Werror', '-static', '-s',
-    '-o', binary, join(repoRoot, tool.source),
-  ], { stdio: ['ignore', 'inherit', 'inherit'] })
-  if (result.error !== undefined || result.status !== 0) {
-    console.error('build: musl-gcc failed' +
-      (result.error ? ` (${result.error.message} — is musl-tools installed?)` : ''))
-    process.exit(1)
+    mkdirSync(join(root, '.release'), { recursive: true })
+    const temporary = mkdtempSync(join(root, '.release', 'native-build-'))
+    try {
+      const pending = join(temporary, basename(output))
+      const result = spawnSync(compiler, [...flags, '-o', pending, join(root, source)], { stdio: 'inherit' })
+      if (result.error) throw result.error
+      if (result.status !== 0) throw new Error(`build: ${compiler} failed for ${binary.path}`)
+      // Readers never see a truncated addon when source checks build concurrently.
+      renameSync(pending, output)
+    } finally {
+      rmSync(temporary, { recursive: true, force: true })
+    }
+    console.log(`build: built ${basename(dir)}/${binary.path}`)
+    built++
   }
-  console.log(`build: built ${basename(target.packageDir)}/${target.binaryPath}`)
 }
+if (built === 0) throw new Error(`build: no declared binaries for ${host}`)

+ 3 - 0
native/system/scripts/github-matrix.mjs

@@ -15,6 +15,8 @@ import { platformDirs, readJson, root } from './repo.mjs';
 const RUNNERS = {
   'linux-x64': 'ubuntu-24.04',
   'linux-arm64': 'ubuntu-24.04-arm',
+  'darwin-x64': 'macos-15-intel',
+  'darwin-arm64': 'macos-latest',
 };
 
 function runnerFor(platform) {
@@ -56,6 +58,7 @@ const target = process.argv[2];
 const matrices = {
   ci: ciMatrix,
   'release-prebuild': releasePrebuildMatrix,
+  compatibility: () => ciMatrix().include.flatMap((row) => [20, 22, 24, 26].map((node) => ({ ...row, node }))),
 };
 
 if (!target || !matrices[target]) {

+ 60 - 26
native/system/scripts/repo.mjs

@@ -44,45 +44,79 @@ export function packageDirs() {
 }
 
 /**
- * Verify one platform package's binaries against its `prebuilds.json`:
- * every declared binary exists, nothing undeclared sits in `bin/`, and each
- * file's ELF `e_machine` matches the package's declared `cpu`. Throws with
- * a remediation message on the first mismatch.
+ * Verify platform metadata, complete bin/ payloads, executable permissions,
+ * and native file formats before packing. Node addons must export Node-API.
  */
 export function verifyPlatformBinaries(packageDir) {
   const manifest = readJson(path.join(packageDir, 'package.json'));
   const prebuilds = readJson(path.join(packageDir, 'prebuilds.json'));
   const cpu = manifest.cpu?.[0];
-  if (cpu === undefined || !(cpu in E_MACHINE)) {
-    throw new Error(`${manifest.name}: unsupported or missing "cpu" in package.json (expected one of: ${Object.keys(E_MACHINE).join(', ')})`);
+  const os = manifest.os?.[0];
+  if (!(cpu in E_MACHINE) || !['linux', 'darwin'].includes(os)) {
+    throw new Error(`${manifest.name}: unsupported or missing os/cpu metadata`);
+  }
+  if (prebuilds.platform !== `${os}-${cpu}`) {
+    throw new Error(`${manifest.name}: prebuild platform disagrees with package os/cpu`);
   }
 
+  const declared = new Set();
   for (const binary of prebuilds.binaries) {
+    if (typeof binary.path !== 'string' || !/^bin\/(?:[a-z0-9-]+\/)?[a-z0-9._-]+$/.test(binary.path)) {
+      throw new Error(`${manifest.name}: binary path must name a file inside bin/`);
+    }
+    if (declared.has(binary.path)) throw new Error(`${manifest.name}: duplicate binary path ${binary.path}`);
+    declared.add(binary.path);
+    const executable = binary.kind === 'static-musl' && binary.tool === 'landlock-run' && os === 'linux';
+    const addon = binary.kind === 'node-api' && binary.tool === 'flock' && binary.napi === 8;
+    if (!executable && !addon) throw new Error(`${manifest.name}: unsupported binary kind/tool/NAPI for ${binary.path}`);
+    if (addon && os === 'linux' && !['glibc', 'musl'].includes(binary.libc)) {
+      throw new Error(`${manifest.name}: Linux addon must declare glibc or musl`);
+    }
+    if (addon && os === 'darwin' && binary.libc !== undefined) {
+      throw new Error(`${manifest.name}: macOS addon must not declare a Linux libc`);
+    }
+
     const file = path.join(packageDir, binary.path);
-    if (!fs.existsSync(file)) {
-      throw new Error(`${manifest.name}: missing ${binary.path} — run \`pnpm build:native\` on a ${prebuilds.platform} host (or assemble release artifacts) before packing.`);
+    if (!fs.existsSync(file)) throw new Error(`${manifest.name}: missing ${binary.path} — build this platform before packing`);
+    if (!fs.lstatSync(file).isFile()) throw new Error(`${manifest.name}: ${binary.path} is not a regular file`);
+    if (executable) {
+      try { fs.accessSync(file, fs.constants.X_OK); }
+      catch { throw new Error(`${manifest.name}: ${binary.path} is not executable`); }
     }
-    try {
-      fs.accessSync(file, fs.constants.X_OK);
-    } catch {
-      // Only reachable when the mode was mangled somewhere between build and
-      // here (e.g. an archive step that normalized permissions) — the build
-      // itself always produces 755.
-      throw new Error(`${manifest.name}: ${binary.path} is not executable — a pack/extract step stripped the mode bit.`);
+    const data = fs.readFileSync(file);
+    if (os === 'linux') {
+      if (data.length < 64 || data.readUInt32LE(0) !== 0x464c457f || data[4] !== 2 || data[5] !== 1) {
+        throw new Error(`${manifest.name}: ${binary.path} is not a little-endian ELF64 binary`);
+      }
+      if (data.readUInt16LE(18) !== E_MACHINE[cpu]) {
+        throw new Error(`${manifest.name}: ${binary.path} has the wrong ELF architecture`);
+      }
+      if (data.readUInt16LE(16) !== (executable ? 2 : 3)) {
+        throw new Error(`${manifest.name}: ${binary.path} has the wrong ELF file type`);
+      }
+    } else {
+      const expectedCpu = cpu === 'x64' ? 0x01000007 : 0x0100000c;
+      if (data.length < 32 || data.readUInt32LE(0) !== 0xfeedfacf) {
+        throw new Error(`${manifest.name}: ${binary.path} is not a Mach-O 64-bit bundle`);
+      }
+      if (data.readUInt32LE(4) !== expectedCpu || data.readUInt32LE(12) !== 8) {
+        throw new Error(`${manifest.name}: ${binary.path} has the wrong Mach-O architecture or file type`);
+      }
     }
-    const machine = fs.readFileSync(file).readUInt16LE(18);
-    if (machine !== E_MACHINE[cpu]) {
-      throw new Error(`${manifest.name}: ${binary.path} has ELF e_machine ${machine}, expected ${E_MACHINE[cpu]} for ${cpu} — the binary was built for a different architecture.`);
+    if (addon && (!data.includes(Buffer.from('napi_register_module_v1'))
+      || !data.includes(Buffer.from('node_api_module_get_api_version_v1')))) {
+      throw new Error(`${manifest.name}: ${binary.path} does not export the Node-API entry points`);
     }
   }
 
-  const declared = prebuilds.binaries.map((binary) => path.basename(binary.path)).sort();
-  const binDir = path.join(packageDir, 'bin');
-  const actual = fs.existsSync(binDir) ? fs.readdirSync(binDir).sort() : [];
-  const extra = actual.filter((name) => !declared.includes(name));
-  if (extra.length) {
-    throw new Error(`${manifest.name}: bin/ contains files not declared in prebuilds.json: ${extra.join(', ')}`);
+  function files(dir, prefix) {
+    if (!fs.existsSync(dir)) return [];
+    return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
+      const name = prefix + '/' + entry.name;
+      return entry.isDirectory() ? files(path.join(dir, entry.name), name) : [name];
+    });
   }
-
-  return { name: manifest.name, count: prebuilds.binaries.length };
+  const extra = files(path.join(packageDir, 'bin'), 'bin').filter((name) => !declared.has(name));
+  if (extra.length) throw new Error(`${manifest.name}: undeclared bin/ files: ${extra.join(', ')}`);
+  return { name: manifest.name, count: declared.size };
 }

+ 4 - 1
native/system/scripts/verify-entry-lib.mjs

@@ -16,7 +16,10 @@ import path from 'node:path';
 const packageDir = process.cwd();
 const manifest = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8'));
 
-for (const file of ['lib/index.js', 'lib/index.d.ts']) {
+const exportedFiles = Object.values(manifest.exports)
+  .flatMap((entry) => typeof entry === 'string' ? [entry] : Object.values(entry))
+  .filter((file) => typeof file === 'string' && file.startsWith('./lib/'));
+for (const file of exportedFiles) {
   if (!fs.existsSync(path.join(packageDir, file))) {
     console.error(`verify-entry-lib: ${manifest.name} has no ${file} — run \`pnpm build:ts\` before packing.`);
     process.exit(1);

+ 3 - 3
native/system/scripts/verify-launcher-binary.mjs

@@ -7,8 +7,8 @@
  * `pnpm run build:native` would ship an EMPTY platform package — the
  * binary's absence surfacing only at runtime as a failed probe on every
  * consumer — and a binary copied across packages would advertise an
- * architecture it cannot execute. The check is presence + ELF `e_machine`
- * against the package's declared `cpu`. `verify-packed-install.mjs`
+ * architecture it cannot execute. Checks cover ELF/Mach-O format, architecture,
+ * declared payloads, and Node-API exports. `verify-packed-install.mjs`
  * separately pins the installed tarball bytes to the workspace build.
  *
  * Runs from each platform package's `prepack` hook (pnpm sets the script
@@ -23,7 +23,7 @@ const packageDir = process.argv[2] ? path.resolve(root, process.argv[2]) : proce
 
 try {
   const { name, count } = verifyPlatformBinaries(packageDir);
-  console.log(`verify-launcher-binary: ${name} — ${count} binaries present with the right ELF architecture.`);
+  console.log(`verify-launcher-binary: ${name} — ${count} binaries present with the right native format and architecture.`);
 } catch (error) {
   console.error(`verify-launcher-binary: ${error instanceof Error ? error.message : error}`);
   process.exit(1);

+ 34 - 23
native/system/scripts/verify-packed-install.mjs

@@ -4,15 +4,14 @@
  * exactly what a consumer install needs. `pnpm pack` already produced the
  * bytes `pnpm publish` would upload; this script checks the payload
  * (coverage, concrete dependency versions, NO lifecycle install scripts —
- * this family has no install fallback on purpose), unpacks the entry plus
+ * this family has no install fallback on purpose), installs the entry plus
  * THIS host's platform tarball into a throwaway consumer OUTSIDE the repo,
  * byte-pins the installed binary against the workspace build it was packed
  * from, and drives the INSTALLED entry under plain `node` — resolution,
  * probe, and a real confinement world-proof through the installed launcher.
  *
- * On non-Linux hosts (no platform package exists) it instead proves the
- * documented degradation: resolution falls back to a nonexistent path and
- * the probe reports `unusable`.
+ * On non-Linux hosts it proves that Landlock remains unavailable, while
+ * supported POSIX hosts independently exercise the flock binding.
  *
  * Usage: `node scripts/verify-packed-install.mjs [tarball-dir] [--current-platform-only]`.
  * The flag skips the all-platforms tarball-presence check for
@@ -56,7 +55,7 @@ function run(command, commandArgs, options = {}) {
   });
   if (result.error) throw result.error;
   if (result.status !== 0) {
-    process.exit(result.status ?? 1);
+    throw new Error(`${command} failed (status=${result.status}, signal=${result.signal})`);
   }
 }
 
@@ -98,19 +97,6 @@ function packageInstallDir(packageName) {
   return path.join(tempRoot, 'node_modules', ...packageName.split('/'));
 }
 
-function unpackTarball(manifest) {
-  const extractRoot = fs.mkdtempSync(path.join(tempRoot, 'extract-'));
-  run('tar', ['-xzf', tarballPath(manifest), '-C', extractRoot]);
-
-  const source = path.join(extractRoot, 'package');
-  const destination = packageInstallDir(manifest.name);
-  fs.rmSync(destination, { recursive: true, force: true });
-  fs.mkdirSync(path.dirname(destination), { recursive: true });
-  fs.renameSync(source, destination);
-  fs.rmSync(extractRoot, { recursive: true, force: true });
-  console.log(`Unpacked ${manifest.name} -> ${path.relative(tempRoot, destination)}`);
-}
-
 const manifests = packageDirs().map((dir) => ({ dir, manifest: readJson(path.join(root, dir, 'package.json')) }));
 const entryManifest = manifests.find(({ manifest }) => manifest.name === entryPackageName)?.manifest;
 if (!entryManifest) throw new Error(`missing source manifest for ${entryPackageName}`);
@@ -144,16 +130,16 @@ for (const { manifest } of expectedTarballs) {
 }
 
 // Throwaway ESM consumer, built from local tarballs only — no registry.
-const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'nalr-packed-install-'));
+const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'native-system-packed-'));
+try {
 fs.writeFileSync(
   path.join(tempRoot, 'package.json'),
-  `${JSON.stringify({ name: 'nalr-packed-install-check', version: '0.0.0', private: true, type: 'module' }, null, 2)}\n`,
+  `${JSON.stringify({ name: 'native-system-packed-check', version: '0.0.0', private: true, type: 'module', dependencies: Object.fromEntries([entryManifest, ...(currentPlatformEntry ? [currentPlatformEntry.manifest] : [])].map((manifest) => [manifest.name, `file:${tarballPath(manifest)}`])) }, null, 2)}\n`,
 );
 console.log(`Verifying packed install in ${tempRoot}`);
 
-unpackTarball(entryManifest);
+run('npm', ['install', '--offline', '--no-audit', '--no-fund', '--package-lock=false'], { cwd: tempRoot });
 if (currentPlatformEntry) {
-  unpackTarball(currentPlatformEntry.manifest);
 
   // Byte-pin: the installed binary must be the workspace build it was packed
   // from — any divergence means the tarball did not carry the built bytes.
@@ -181,6 +167,7 @@ import fs from 'node:fs';
 import os from 'node:os';
 import path from 'node:path';
 import { grantArgs, launcherPath, probe } from '@deepseek-ai/node-addon-system';
+import { tryLockExclusive } from '@deepseek-ai/node-addon-system/flock';
 
 const requireLandlock = process.env.NALR_REQUIRE_LANDLOCK === '1';
 const platformPackage = '@deepseek-ai/node-addon-system-' + process.platform + '-' + process.arch;
@@ -213,11 +200,35 @@ if (process.platform === 'linux') {
     console.log('confinement world-proof passed through the installed launcher');
   }
 } else {
-  assert.ok(!fs.existsSync(resolved), 'no platform package exists for this host — the fallback path must not exist');
+  assert.ok(!fs.existsSync(resolved), 'Landlock has no executable for this host');
   assert.equal(probe(resolved), 'unusable');
   console.log('non-linux host: fallback resolution and unusable probe verified');
 }
+
+if (process.platform === 'linux' || process.platform === 'darwin') {
+  const lockRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'native-system-flock-'));
+  const handles = [];
+  try {
+    const lock = path.join(lockRoot, 'lock');
+    const a = fs.openSync(lock, 'wx+', 0o600);
+    handles.push(a);
+    const b = fs.openSync(lock, 'r+');
+    handles.push(b);
+    await tryLockExclusive(a);
+    await assert.rejects(tryLockExclusive(b), { code: 'EAGAIN' });
+    fs.closeSync(a);
+    handles.splice(handles.indexOf(a), 1);
+    await tryLockExclusive(b);
+    console.log('installed Node-API flock: exclusion and close release verified');
+  } finally {
+    for (const fd of handles) fs.closeSync(fd);
+    fs.rmSync(lockRoot, { recursive: true, force: true });
+  }
+}
 `);
 run(process.execPath, [driver], { cwd: tempRoot });
 
 console.log('Packed install verification passed.');
+} finally {
+  fs.rmSync(tempRoot, { recursive: true, force: true });
+}

+ 1 - 1
native/system/scripts/verify-release.mjs

@@ -3,7 +3,7 @@
  * Release verification. Always: every published package carries one shared
  * version, and — when running from a tag or publishing — the
  * `node-addon-system-vX.Y.Z` tag matches it. With `--prebuilds`: every platform package's declared
- * binaries exist with the right ELF architecture (run after
+ * binaries exist with the right native format and architecture (run after
  * `assemble-prebuilds.mjs` or a local `build:native`).
  */
 

+ 12 - 0
native/system/test/fixtures/flock-binding.js

@@ -0,0 +1,12 @@
+/** Load the private callback API to test native completion independently of its Promise wrapper. */
+import { createRequire } from 'node:module';
+import { fileURLToPath } from 'node:url';
+
+/** @returns The built addon's private callback binding for this host. */
+export function loadFlockBinding() {
+  const libc = process.platform === 'linux'
+    ? `${process.report.getReport().header.glibcVersionRuntime ? 'glibc' : 'musl'}/`
+    : '';
+  const binary = new URL(`../../packages/${process.platform}-${process.arch}/bin/${libc}system.node`, import.meta.url);
+  return createRequire(import.meta.url)(fileURLToPath(binary));
+}

+ 6 - 0
native/system/test/fixtures/flock-callback-throws.js

@@ -0,0 +1,6 @@
+/** Isolate an uncaught native-callback exception from the test runner. */
+import { loadFlockBinding } from './flock-binding.js';
+
+const binding = loadFlockBinding();
+process.send({ type: 'ready' });
+binding.tryLock(-1, () => { throw new Error('flock callback failure'); });

+ 42 - 0
native/system/test/fixtures/flock-child.js

@@ -0,0 +1,42 @@
+/** IPC-controlled lock holder; acknowledgements follow settled syscalls or close. */
+import assert from 'node:assert/strict';
+import { once, on } from 'node:events';
+import { closeSync, openSync } from 'node:fs';
+import { tryLockExclusive } from '../../packages/entry/lib/flock.js';
+
+const messages = on(process, 'message');
+let fd = openSync(process.argv[2], 'a+', 0o600);
+try {
+  await send({ type: 'ready' });
+  for await (const [command] of messages) {
+    if (command === 'close') {
+      closeSync(fd);
+      fd = undefined;
+      await send({ type: 'closed' });
+      break;
+    }
+    assert.equal(command, 'tryLock');
+    let reply;
+    try {
+      await tryLockExclusive(fd);
+      reply = { type: 'locked' };
+    } catch (error) {
+      reply = { type: 'error', code: error.code, errno: error.errno, syscall: error.syscall };
+    }
+    await send(reply);
+  }
+} finally {
+  await messages.return();
+  if (fd !== undefined) closeSync(fd);
+  if (process.connected) {
+    const disconnected = once(process, 'disconnect');
+    process.disconnect();
+    await disconnected;
+  }
+}
+
+function send(message) {
+  return new Promise((resolve, reject) => {
+    process.send(message, (error) => error ? reject(error) : resolve());
+  });
+}

+ 22 - 0
native/system/test/fixtures/flock-import.js

@@ -0,0 +1,22 @@
+/** A separate process keeps unsupported-platform simulation away from other tests. */
+import assert from 'node:assert/strict';
+
+const platform = process.argv[2];
+const descriptor = Object.getOwnPropertyDescriptor(process, 'platform');
+try {
+  if (platform) Object.defineProperty(process, 'platform', { value: platform });
+  const { tryLockExclusive } = await import('../../packages/entry/lib/flock.js');
+  assert.equal(typeof tryLockExclusive, 'function');
+  if (platform || (process.platform !== 'linux' && process.platform !== 'darwin')) {
+    await assert.rejects(tryLockExclusive(-1), {
+      code: 'ERR_FLOCK_UNSUPPORTED_PLATFORM',
+      syscall: 'flock',
+    });
+  }
+} finally {
+  Object.defineProperty(process, 'platform', descriptor);
+}
+await new Promise((resolve, reject) => {
+  process.send({ type: 'ready' }, (error) => error ? reject(error) : resolve());
+});
+process.disconnect();

+ 37 - 0
native/system/test/fixtures/flock-inherited-child.js

@@ -0,0 +1,37 @@
+/** fd 4 is inherited through spawn's stdio mapping, never reopened by path. */
+import assert from 'node:assert/strict';
+import { on } from 'node:events';
+import { closeSync, fstatSync } from 'node:fs';
+import { tryLockExclusive } from '../../packages/entry/lib/flock.js';
+
+const messages = on(process, 'message');
+let fd = 4;
+try {
+  assert.ok(fstatSync(fd).isFile());
+  await send({ type: 'ready' });
+  for await (const [command] of messages) {
+    if (command === 'quit') {
+      await send({ type: 'bye' });
+      break;
+    }
+    if (command === 'close') {
+      closeSync(fd);
+      fd = undefined;
+      await send({ type: 'closed' });
+      continue;
+    }
+    assert.equal(command, 'tryLock');
+    await tryLockExclusive(fd);
+    await send({ type: 'locked' });
+  }
+} finally {
+  await messages.return();
+  if (fd !== undefined) closeSync(fd);
+  if (process.connected) process.disconnect();
+}
+
+function send(message) {
+  return new Promise((resolve, reject) => {
+    process.send(message, (error) => error ? reject(error) : resolve());
+  });
+}

+ 19 - 0
native/system/test/fixtures/flock-io-child.js

@@ -0,0 +1,19 @@
+/** Ordinary file I/O from a process that never acquires a lock. */
+import assert from 'node:assert/strict';
+import { once } from 'node:events';
+import { readFileSync, writeFileSync } from 'node:fs';
+
+const request = once(process, 'message');
+await send({ type: 'ready' });
+const [command] = await request;
+assert.equal(command, 'read-write');
+const previous = readFileSync(process.argv[2], 'utf8');
+writeFileSync(process.argv[2], 'written without acquiring a lock');
+await send({ type: 'written', previous });
+process.disconnect();
+
+function send(message) {
+  return new Promise((resolve, reject) => {
+    process.send(message, (error) => error ? reject(error) : resolve());
+  });
+}

+ 75 - 0
native/system/test/fixtures/flock-oracle.c

@@ -0,0 +1,75 @@
+/* Independent system flock(2) oracle; stdin commands produce flushed JSON lines. */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/file.h>
+#include <unistd.h>
+
+static int reply(int fd, int operation) {
+  const int result = flock(fd, operation);
+  const int error = result == 0 ? 0 : errno;
+  if (printf("{\"errno\":%d}\n", error) < 0 || fflush(stdout) == EOF) {
+    perror("flock-oracle: stdout");
+    return 1;
+  }
+  return 0;
+}
+
+int main(int argc, char **argv) {
+  int operation = LOCK_EX;
+  if (argc < 2 || argc > 3) {
+    fprintf(stderr, "usage: flock-oracle <path> [exclusive|shared]\n");
+    return 1;
+  }
+  if (argc == 3) {
+    if (strcmp(argv[2], "shared") == 0) {
+      operation = LOCK_SH;
+    } else if (strcmp(argv[2], "exclusive") != 0) {
+      fprintf(stderr, "flock-oracle: mode must be exclusive or shared\n");
+      return 1;
+    }
+  }
+
+  const int fd = open(argv[1], O_RDWR | O_CREAT, 0600);
+  if (fd == -1) {
+    perror("flock-oracle: open");
+    return 1;
+  }
+  int status = 0;
+  if (puts("{\"ready\":true}") == EOF || fflush(stdout) == EOF) {
+    perror("flock-oracle: stdout");
+    status = 1;
+    goto cleanup;
+  }
+
+  char command[3];
+  while (fgets(command, sizeof(command), stdin) != NULL) {
+    if (command[1] != '\n') {
+      fprintf(stderr, "flock-oracle: commands must be one letter followed by a newline\n");
+      status = 1;
+      break;
+    }
+    if (command[0] == 'q') break;
+    if (command[0] != 't' && command[0] != 'u') {
+      fprintf(stderr, "flock-oracle: expected t, u, or q\n");
+      status = 1;
+      break;
+    }
+    if (reply(fd, command[0] == 't' ? operation | LOCK_NB : LOCK_UN) != 0) {
+      status = 1;
+      break;
+    }
+  }
+  if (ferror(stdin)) {
+    perror("flock-oracle: stdin");
+    status = 1;
+  }
+
+cleanup:
+  if (close(fd) == -1) {
+    perror("flock-oracle: close");
+    status = 1;
+  }
+  return status;
+}

+ 31 - 0
native/system/test/fixtures/flock-worker.js

@@ -0,0 +1,31 @@
+/** Hold work before or inside its callback while the parent terminates this environment. */
+import assert from 'node:assert/strict';
+import { createHook } from 'node:async_hooks';
+import { parentPort, workerData } from 'node:worker_threads';
+import { tryLockExclusive } from '../../packages/entry/lib/flock.js';
+import { loadFlockBinding } from './flock-binding.js';
+
+let nativeWork = 0;
+const hook = createHook({
+  init(_id, type) {
+    if (type === 'flock') nativeWork++;
+  },
+});
+hook.enable();
+try {
+  if (workerData.phase === 'callback') {
+    loadFlockBinding().tryLock(workerData.fd, () => {
+      parentPort.postMessage({ type: 'callback', nativeWork });
+      Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0);
+    });
+  } else {
+    const pending = tryLockExclusive(workerData.fd);
+    assert.equal(nativeWork, 1);
+    parentPort.postMessage({ type: 'queued', nativeWork });
+    // No JS yield precedes this wait, so the native completion cannot run first.
+    Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0);
+    await pending;
+  }
+} finally {
+  hook.disable();
+}

+ 423 - 0
native/system/test/flock.test.js

@@ -0,0 +1,423 @@
+/** Kernel behavior through the built flock entry; each case owns its files and processes. */
+import assert from 'node:assert/strict';
+import { fork, spawn } from 'node:child_process';
+import { once } from 'node:events';
+import { closeSync, mkdtempSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
+import { constants, tmpdir } from 'node:os';
+import { join } from 'node:path';
+import { createInterface } from 'node:readline';
+import test from 'node:test';
+import { fileURLToPath } from 'node:url';
+import { Worker } from 'node:worker_threads';
+import { tryLockExclusive } from '../packages/entry/lib/flock.js';
+import { loadFlockBinding } from './fixtures/flock-binding.js';
+
+const posix = process.platform === 'linux' || process.platform === 'darwin';
+const timeout = 120_000;
+const nativeOnly = { timeout, skip: posix ? false : 'The flock addon requires Linux or macOS' };
+
+function resources(t) {
+  const disposers = [];
+  // Match the repository's process-e2e budget for both cases and cleanup.
+  t.after(async () => {
+    // Cleanup must await close even when the case's signal is already aborted.
+    const signal = AbortSignal.timeout(timeout);
+    const errors = [];
+    for (const dispose of disposers.reverse()) {
+      try {
+        await dispose(signal);
+      } catch (error) {
+        errors.push(error);
+      }
+    }
+    if (errors.length) throw new AggregateError(errors, 'flock test cleanup failed');
+  }, { timeout });
+  // On local Linux filesystems, flock and OFD byte-range locks are independent;
+  // network filesystems can translate between them and hide a wrong syscall.
+  const root = mkdtempSync(join(tmpdir(), 'node-addon-system-flock-'));
+  disposers.push(() => rmSync(root, { recursive: true, force: true }));
+  return {
+    file: join(root, 'lock'),
+    defer: (dispose) => disposers.push(dispose),
+    open(name = 'lock') {
+      const fd = openSync(join(root, name), 'a+', 0o600);
+      let closed = false;
+      const close = () => {
+        if (!closed) {
+          closeSync(fd);
+          closed = true;
+        }
+      };
+      disposers.push(close);
+      return { fd, close };
+    },
+  };
+}
+
+function flockError(error, codes) {
+  assert.ok(codes.includes(error.code), `Unexpected flock error: ${error.code}`);
+  assert.equal(error.errno, constants.errno[error.code]);
+  assert.ok(error.errno > 0);
+  assert.equal(error.syscall, 'flock');
+  return true;
+}
+
+const busy = (error) => flockError(error, ['EAGAIN', 'EWOULDBLOCK']);
+
+async function until(promise, signal) {
+  let abort;
+  const cancelled = new Promise((_, reject) => {
+    abort = () => reject(signal.reason);
+    if (signal.aborted) abort();
+    else signal.addEventListener('abort', abort, { once: true });
+  });
+  try {
+    return await Promise.race([promise, cancelled]);
+  } finally {
+    signal.removeEventListener('abort', abort);
+  }
+}
+
+function childEnvironment() {
+  return Object.fromEntries(Object.entries(process.env)
+    .filter(([name]) => !/KEY|SECRET|TOKEN|PASSWORD/i.test(name)));
+}
+
+function observeChild(t, scope, child) {
+  let closed = false;
+  let stderr = '';
+  let processError;
+  const done = new Promise((resolve) => child.once('close', (code, signal) => {
+    closed = true;
+    resolve({ code, signal, stderr, error: processError });
+  }));
+  scope.defer(async (signal) => {
+    if (!closed) child.kill('SIGKILL');
+    await until(done, signal);
+  });
+  child.stderr.setEncoding('utf8');
+  child.stderr.on('data', (chunk) => { stderr += chunk; });
+  child.on('error', (error) => { processError = error; });
+
+  async function response(emitter, event, send = () => Promise.resolve()) {
+    const waiting = new AbortController();
+    const signal = AbortSignal.any([t.signal, waiting.signal]);
+    try {
+      const received = Promise.race([
+        once(emitter, event, { signal }).then(([message]) => message),
+        done.then((result) => {
+          throw new Error(`flock fixture exited before replying: ${JSON.stringify(result)}`);
+        }),
+      ]);
+      const [message] = await until(Promise.all([received, send()]), signal);
+      return message;
+    } finally {
+      waiting.abort();
+    }
+  }
+
+  return { child, response, waitForExit: () => until(done, t.signal) };
+}
+
+async function childFixture(t, scope, fixture, args = [], { execArgv = [], inheritedFd } = {}) {
+  const stdio = ['ignore', 'ignore', 'pipe', 'ipc'];
+  if (inheritedFd !== undefined) stdio.push(inheritedFd);
+  const child = fork(new URL(`./fixtures/${fixture}`, import.meta.url), args, {
+    execArgv,
+    env: childEnvironment(),
+    stdio,
+  });
+  const observed = observeChild(t, scope, child);
+  const exchange = (command) => observed.response(child, 'message', () => (
+    command === undefined ? Promise.resolve() : new Promise((resolve, reject) => {
+      child.send(command, (error) => error ? reject(error) : resolve());
+    })
+  ));
+  assert.deepEqual(await exchange(), { type: 'ready' });
+  return { child, waitForExit: observed.waitForExit, exchange };
+}
+
+async function oracleFixture(t, scope, mode) {
+  const binary = process.platform === 'linux'
+    ? `./bin/${process.report.getReport().header.glibcVersionRuntime ? 'glibc' : 'musl'}/flock-oracle`
+    : './bin/flock-oracle';
+  const child = spawn(fileURLToPath(new URL(binary, import.meta.url)), [scope.file, mode], {
+    env: childEnvironment(),
+    stdio: ['pipe', 'pipe', 'pipe'],
+  });
+  const observed = observeChild(t, scope, child);
+  const lines = createInterface({ input: child.stdout });
+  child.once('close', () => lines.close());
+  let inputError;
+  child.stdin.on('error', (error) => { inputError = error; });
+  const send = (command) => until(new Promise((resolve, reject) => {
+    if (inputError) reject(inputError);
+    else child.stdin.write(`${command}\n`, (error) => error ? reject(error) : resolve());
+  }), t.signal);
+  const exchange = async (command) => JSON.parse(await observed.response(lines, 'line', () => (
+    command === undefined ? Promise.resolve() : send(command)
+  )));
+  assert.deepEqual(await exchange(), { ready: true });
+  return {
+    exchange,
+    async quit() {
+      await send('q');
+      cleanExit(await observed.waitForExit());
+    },
+  };
+}
+
+function cleanExit(result) {
+  assert.equal(result.signal, null, result.stderr);
+  assert.equal(result.error, undefined);
+  assert.equal(result.code, 0, result.stderr);
+}
+
+test('import succeeds with native addons disabled', { timeout }, async (t) => {
+  const scope = resources(t);
+  const child = await childFixture(t, scope, 'flock-import.js', [], { execArgv: ['--no-addons'] });
+  cleanExit(await child.waitForExit());
+});
+
+for (const platform of ['win32', 'freebsd']) {
+  test(`calling flock on ${platform} rejects without loading an addon`, { timeout }, async (t) => {
+    const scope = resources(t);
+    const child = await childFixture(t, scope, 'flock-import.js', [platform], { execArgv: ['--no-addons'] });
+    cleanExit(await child.waitForExit());
+  });
+}
+
+test('acquisition resolves asynchronously to void and the same fd can reacquire', nativeOnly, async (t) => {
+  const scope = resources(t);
+  const owner = scope.open();
+  const result = tryLockExclusive(owner.fd);
+  assert.ok(result instanceof Promise);
+  assert.equal(await result, undefined);
+  assert.equal(await tryLockExclusive(owner.fd), undefined);
+});
+
+test('separate opens of one file contend', nativeOnly, async (t) => {
+  const scope = resources(t);
+  const owner = scope.open();
+  const contender = scope.open();
+  await tryLockExclusive(owner.fd);
+  await assert.rejects(tryLockExclusive(contender.fd), busy);
+});
+
+test('different files can be locked concurrently', nativeOnly, async (t) => {
+  const scope = resources(t);
+  const first = scope.open('first');
+  const second = scope.open('second');
+  await Promise.all([tryLockExclusive(first.fd), tryLockExclusive(second.fd)]);
+});
+
+test('closing the locked fd allows an already-open contender to acquire', nativeOnly, async (t) => {
+  const scope = resources(t);
+  const owner = scope.open();
+  const contender = scope.open();
+  await tryLockExclusive(owner.fd);
+  await assert.rejects(tryLockExclusive(contender.fd), busy);
+  owner.close();
+  await tryLockExclusive(contender.fd);
+});
+
+test('closing another fd for the same file does not release the lock', nativeOnly, async (t) => {
+  const scope = resources(t);
+  const owner = scope.open();
+  const other = scope.open();
+  const contender = scope.open();
+  await tryLockExclusive(owner.fd);
+  other.close();
+  await assert.rejects(tryLockExclusive(contender.fd), busy);
+  owner.close();
+  await tryLockExclusive(contender.fd);
+});
+
+test('invalid fd rejects asynchronously with EBADF and positive errno', nativeOnly, async () => {
+  let result;
+  assert.doesNotThrow(() => { result = tryLockExclusive(-1); });
+  assert.ok(result instanceof Promise);
+  await assert.rejects(result, (error) => flockError(error, ['EBADF']));
+});
+
+test('native argument errors reject the JavaScript promise without throwing from the entry', nativeOnly, async () => {
+  let result;
+  assert.doesNotThrow(() => { result = tryLockExclusive(2 ** 31); });
+  assert.ok(result instanceof Promise);
+  await assert.rejects(result, { name: 'RangeError', message: 'fd must be a signed C int' });
+});
+
+test('native callbacks receive asynchronous, request-local success and errno results', nativeOnly, async (t) => {
+  const scope = resources(t);
+  const owner = scope.open();
+  const contender = scope.open();
+  await tryLockExclusive(owner.fd);
+  const binding = loadFlockBinding();
+  const results = await Promise.all([owner.fd, contender.fd, -1].map((fd) => new Promise((resolve) => {
+    let returned = false;
+    const result = binding.tryLock(fd, (errno) => {
+      assert.equal(returned, true);
+      resolve(errno);
+    });
+    assert.equal(result, undefined);
+    returned = true;
+  })));
+  assert.equal(results[0], 0);
+  assert.ok([constants.errno.EAGAIN, constants.errno.EWOULDBLOCK].includes(results[1]));
+  assert.equal(results[2], constants.errno.EBADF);
+});
+
+test('an exception in the native completion callback is reported as uncaught', nativeOnly, async (t) => {
+  const scope = resources(t);
+  const child = await childFixture(t, scope, 'flock-callback-throws.js');
+  const exit = await child.waitForExit();
+  assert.equal(exit.signal, null, exit.stderr);
+  assert.equal(exit.error, undefined);
+  assert.equal(exit.code, 1, exit.stderr);
+  assert.match(exit.stderr, /Error: flock callback failure/);
+});
+
+test('concurrent calls retain their own syscall errno', nativeOnly, async (t) => {
+  const scope = resources(t);
+  const owner = scope.open();
+  const contender = scope.open();
+  await tryLockExclusive(owner.fd);
+  await Promise.all([
+    assert.rejects(tryLockExclusive(contender.fd), busy),
+    assert.rejects(tryLockExclusive(-1), (error) => flockError(error, ['EBADF'])),
+    assert.rejects(tryLockExclusive(contender.fd), busy),
+    assert.rejects(tryLockExclusive(-1), (error) => flockError(error, ['EBADF'])),
+  ]);
+});
+
+test('two child processes exclude each other and normal close transfers ownership', nativeOnly, async (t) => {
+  const scope = resources(t);
+  const observer = scope.open();
+  const children = await Promise.all([
+    childFixture(t, scope, 'flock-child.js', [scope.file]),
+    childFixture(t, scope, 'flock-child.js', [scope.file]),
+  ]);
+  const results = await Promise.all(children.map((child) => child.exchange('tryLock')));
+  assert.equal(results.filter((result) => result.type === 'locked').length, 1);
+  assert.equal(results.filter((result) => result.type === 'error').length, 1);
+  const winnerIndex = results.findIndex((result) => result.type === 'locked');
+  const winner = children[winnerIndex];
+  const loser = children[1 - winnerIndex];
+  busy(results[1 - winnerIndex]);
+  await assert.rejects(tryLockExclusive(observer.fd), busy);
+
+  assert.deepEqual(await winner.exchange('close'), { type: 'closed' });
+  cleanExit(await winner.waitForExit());
+  assert.deepEqual(await loser.exchange('tryLock'), { type: 'locked' });
+  await assert.rejects(tryLockExclusive(observer.fd), busy);
+  assert.deepEqual(await loser.exchange('close'), { type: 'closed' });
+  cleanExit(await loser.waitForExit());
+  await tryLockExclusive(observer.fd);
+});
+
+test('SIGKILL releases a child lock after exit', nativeOnly, async (t) => {
+  const scope = resources(t);
+  const observer = scope.open();
+  const owner = await childFixture(t, scope, 'flock-child.js', [scope.file]);
+  const contender = await childFixture(t, scope, 'flock-child.js', [scope.file]);
+  assert.deepEqual(await owner.exchange('tryLock'), { type: 'locked' });
+  const rejected = await contender.exchange('tryLock');
+  assert.equal(rejected.type, 'error');
+  busy(rejected);
+  assert.equal(owner.child.kill('SIGKILL'), true);
+  const exit = await owner.waitForExit();
+  assert.equal(exit.error, undefined);
+  assert.equal(exit.signal, 'SIGKILL');
+  assert.equal(exit.code, null);
+  assert.deepEqual(await contender.exchange('tryLock'), { type: 'locked' });
+  await assert.rejects(tryLockExclusive(observer.fd), busy);
+});
+
+for (const phase of ['queued', 'callback']) {
+  test(`worker termination during ${phase} drains native work without taking ownership of the fd`, nativeOnly, async (t) => {
+    const scope = resources(t);
+    const owner = scope.open();
+    const contender = scope.open();
+    const worker = new Worker(new URL('./fixtures/flock-worker.js', import.meta.url), {
+      workerData: { fd: owner.fd, phase },
+      execArgv: [],
+    });
+    scope.defer((signal) => until(worker.terminate(), signal));
+    const exited = once(worker, 'exit');
+    const waiting = new AbortController();
+    try {
+      const [message] = await Promise.race([
+        once(worker, 'message', { signal: AbortSignal.any([t.signal, waiting.signal]) }),
+        exited.then(([code]) => { throw new Error(`flock worker exited before ${phase}: ${code}`); }),
+      ]);
+      assert.deepEqual(message, { type: phase, nativeWork: 1 });
+    } finally {
+      waiting.abort();
+    }
+    assert.equal(await until(worker.terminate(), t.signal), 1);
+    await until(exited, t.signal);
+    await tryLockExclusive(owner.fd);
+    await assert.rejects(tryLockExclusive(contender.fd), busy);
+    owner.close();
+    await tryLockExclusive(contender.fd);
+  });
+}
+
+for (const mode of ['exclusive', 'shared']) {
+  test(`an addon exclusive lock blocks an independent C ${mode} flock until its fd closes`, nativeOnly, async (t) => {
+    const scope = resources(t);
+    const owner = scope.open();
+    await tryLockExclusive(owner.fd);
+    const oracle = await oracleFixture(t, scope, mode);
+    const result = await oracle.exchange('t');
+    assert.ok([constants.errno.EAGAIN, constants.errno.EWOULDBLOCK].includes(result.errno));
+    owner.close();
+    assert.deepEqual(await oracle.exchange('t'), { errno: 0 });
+    await oracle.quit();
+  });
+
+  test(`an independent C ${mode} flock blocks the addon until explicit unlock`, nativeOnly, async (t) => {
+    const scope = resources(t);
+    const contender = scope.open();
+    const oracle = await oracleFixture(t, scope, mode);
+    assert.deepEqual(await oracle.exchange('t'), { errno: 0 });
+    await assert.rejects(tryLockExclusive(contender.fd), busy);
+    assert.deepEqual(await oracle.exchange('u'), { errno: 0 });
+    await tryLockExclusive(contender.fd);
+    await oracle.quit();
+  });
+}
+
+test('an advisory exclusive lock permits another process to read and write without locking', nativeOnly, async (t) => {
+  const scope = resources(t);
+  const owner = scope.open();
+  const contender = scope.open();
+  writeFileSync(scope.file, 'written before locking');
+  await tryLockExclusive(owner.fd);
+  await assert.rejects(tryLockExclusive(contender.fd), busy);
+  const child = await childFixture(t, scope, 'flock-io-child.js', [scope.file]);
+  assert.deepEqual(await child.exchange('read-write'), {
+    type: 'written', previous: 'written before locking',
+  });
+  cleanExit(await child.waitForExit());
+  assert.equal(readFileSync(scope.file, 'utf8'), 'written without acquiring a lock');
+  await assert.rejects(tryLockExclusive(contender.fd), busy);
+});
+
+test('an inherited fd shares the lock after parent close until the child closes its last reference', nativeOnly, async (t) => {
+  const scope = resources(t);
+  const owner = scope.open();
+  const contender = scope.open();
+  await tryLockExclusive(owner.fd);
+  const child = await childFixture(t, scope, 'flock-inherited-child.js', [], { inheritedFd: owner.fd });
+  assert.deepEqual(await child.exchange('tryLock'), { type: 'locked' });
+  owner.close();
+  await assert.rejects(tryLockExclusive(contender.fd), busy);
+  assert.deepEqual(await child.exchange('close'), { type: 'closed' });
+  await tryLockExclusive(contender.fd);
+  // The child stays alive, so its close acknowledgement—not process exit—releases the lock.
+  assert.equal(child.child.exitCode, null);
+  assert.equal(child.child.signalCode, null);
+  assert.deepEqual(await child.exchange('quit'), { type: 'bye' });
+  cleanExit(await child.waitForExit());
+});

+ 10 - 0
native/system/test/link-platform.mjs

@@ -0,0 +1,10 @@
+/** Link the downloaded platform artifact for dependency-free ABI tests. */
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const packages = fileURLToPath(new URL('../packages/', import.meta.url));
+const platform = `${process.platform}-${process.arch}`;
+const parent = path.join(packages, 'entry/node_modules/@deepseek-ai');
+fs.mkdirSync(parent, { recursive: true });
+fs.symlinkSync(path.join(packages, platform), path.join(parent, `node-addon-system-${platform}`), 'junction');

+ 138 - 0
native/system/test/package-matrix.test.js

@@ -0,0 +1,138 @@
+import assert from 'node:assert/strict';
+import fs from 'node:fs';
+import os from 'node:os';
+import path from 'node:path';
+import { test } from 'node:test';
+import { spawnSync } from 'node:child_process';
+import { fileURLToPath } from 'node:url';
+import { verifyPlatformBinaries } from '../scripts/repo.mjs';
+
+// These minimal headers exercise format rejection, not executable behavior.
+// flock.test.js and packed-install verification execute the real addon.
+function fixture(t, { platform = 'linux', arch = 'x64', kind = 'node-api' } = {}) {
+  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'system-package-'));
+  t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
+  const executable = kind === 'static-musl';
+  const binary = executable
+    ? { tool: 'landlock-run', kind, path: 'bin/landlock-run' }
+    : { tool: 'flock', kind, napi: 8, ...(platform === 'linux' ? { libc: 'glibc' } : {}), path: 'bin/system.node' };
+  const spec = { platform: `${platform}-${arch}`, binaries: [binary] };
+  const manifest = { name: 'fixture', os: [platform], cpu: [arch] };
+  const bytes = Buffer.alloc(256);
+  if (platform === 'linux') {
+    bytes.writeUInt32LE(0x464c457f, 0);
+    bytes[4] = 2;
+    bytes[5] = 1;
+    bytes.writeUInt16LE(executable ? 2 : 3, 16);
+    bytes.writeUInt16LE(arch === 'x64' ? 62 : 183, 18);
+  } else {
+    bytes.writeUInt32LE(0xfeedfacf, 0);
+    bytes.writeUInt32LE(arch === 'x64' ? 0x01000007 : 0x0100000c, 4);
+    bytes.writeUInt32LE(8, 12);
+  }
+  bytes.write('napi_register_module_v1\0node_api_module_get_api_version_v1', 64);
+  const file = path.join(dir, binary.path);
+  fs.mkdirSync(path.dirname(file), { recursive: true });
+  fs.writeFileSync(file, bytes, { mode: executable ? 0o755 : 0o644 });
+  const save = () => {
+    fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify(manifest));
+    fs.writeFileSync(path.join(dir, 'prebuilds.json'), JSON.stringify(spec));
+  };
+  save();
+  return { dir, file, bytes, binary, spec, manifest, save };
+}
+
+for (const platform of ['linux', 'darwin']) {
+  for (const arch of ['x64', 'arm64']) {
+    test(`accepts ${platform}-${arch} addon metadata and header`, (t) => {
+      assert.equal(verifyPlatformBinaries(fixture(t, { platform, arch }).dir).count, 1);
+    });
+  }
+}
+
+test('accepts the Linux static launcher format', (t) => {
+  assert.equal(verifyPlatformBinaries(fixture(t, { kind: 'static-musl' }).dir).count, 1);
+});
+
+for (const [name, change, expected] of [
+  ['unknown platform', (f) => { f.manifest.os = ['win32']; }, /os\/cpu/],
+  ['mismatched platform', (f) => { f.spec.platform = 'linux-arm64'; }, /disagrees/],
+  ['path outside bin', (f) => { f.binary.path = '../system.node'; }, /inside bin/],
+  ['duplicate binary', (f) => { f.spec.binaries.push({ ...f.binary }); }, /duplicate/],
+  ['unknown kind', (f) => { f.binary.kind = 'unknown'; }, /kind\/tool\/NAPI/],
+  ['wrong NAPI version', (f) => { f.binary.napi = 9; }, /kind\/tool\/NAPI/],
+  ['missing Linux libc', (f) => { delete f.binary.libc; }, /declare glibc or musl/],
+  ['missing payload', (f) => { fs.unlinkSync(f.file); }, /missing/],
+  ['wrong ELF architecture', (f) => { f.bytes.writeUInt16LE(183, 18); fs.writeFileSync(f.file, f.bytes); }, /ELF architecture/],
+  ['wrong ELF type', (f) => { f.bytes.writeUInt16LE(2, 16); fs.writeFileSync(f.file, f.bytes); }, /ELF file type/],
+  ['truncated ELF', (f) => { fs.writeFileSync(f.file, Buffer.alloc(8)); }, /ELF64/],
+  ['missing NAPI exports', (f) => { f.bytes.fill(0, 64); fs.writeFileSync(f.file, f.bytes); }, /Node-API entry points/],
+  ['undeclared nested file', (f) => { fs.mkdirSync(path.join(f.dir, 'bin/extra')); fs.writeFileSync(path.join(f.dir, 'bin/extra/other.node'), 'x'); }, /undeclared/],
+]) {
+  test(`rejects ${name}`, (t) => {
+    const f = fixture(t);
+    change(f);
+    f.save();
+    assert.throws(() => verifyPlatformBinaries(f.dir), expected);
+  });
+}
+
+test('rejects Linux libc metadata on macOS', (t) => {
+  const f = fixture(t, { platform: 'darwin' });
+  f.binary.libc = 'musl';
+  f.save();
+  assert.throws(() => verifyPlatformBinaries(f.dir), /must not declare/);
+});
+
+for (const [offset, value] of [[0, 0], [4, 0], [12, 2]]) {
+  test(`rejects invalid Mach-O field at ${offset}`, (t) => {
+    const f = fixture(t, { platform: 'darwin' });
+    f.bytes.writeUInt32LE(value, offset);
+    fs.writeFileSync(f.file, f.bytes);
+    assert.throws(() => verifyPlatformBinaries(f.dir), /Mach-O/);
+  });
+}
+
+test('rejects a launcher whose executable bit was lost', { skip: process.platform === 'win32' }, (t) => {
+  const f = fixture(t, { kind: 'static-musl' });
+  fs.chmodSync(f.file, 0o644);
+  assert.throws(() => verifyPlatformBinaries(f.dir), /not executable/);
+});
+
+test('rejects a symbolic-link payload', { skip: process.platform === 'win32' }, (t) => {
+  const f = fixture(t);
+  fs.renameSync(f.file, f.file + '.target');
+  fs.symlinkSync(f.file + '.target', f.file);
+  assert.throws(() => verifyPlatformBinaries(f.dir), /not a regular file/);
+});
+
+test('entry prepack rejects a missing exported flock file even when the root entry exists', (t) => {
+  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'system-entry-'));
+  t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
+  fs.mkdirSync(path.join(dir, 'lib'));
+  fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({
+    name: 'entry-fixture',
+    exports: {
+      '.': { types: './lib/index.d.ts', default: './lib/index.js' },
+      './flock': { types: './lib/flock.d.ts', default: './lib/flock.js' },
+    },
+  }));
+  for (const file of ['index.js', 'index.d.ts', 'flock.d.ts']) fs.writeFileSync(path.join(dir, 'lib', file), '');
+  const script = fileURLToPath(new URL('../scripts/verify-entry-lib.mjs', import.meta.url));
+  const options = {
+    cwd: dir,
+    encoding: 'utf8',
+    timeout: 120_000,
+    env: Object.fromEntries(Object.entries(process.env).filter(([key]) => !/KEY|TOKEN|SECRET|PASSWORD/i.test(key))),
+  };
+  const missing = spawnSync(process.execPath, [script], options);
+  assert.equal(missing.error, undefined);
+  assert.equal(missing.signal, null);
+  assert.equal(missing.status, 1);
+  assert.match(missing.stderr, /lib\/flock\.js/);
+  fs.writeFileSync(path.join(dir, 'lib/flock.js'), '');
+  const complete = spawnSync(process.execPath, [script], options);
+  assert.equal(complete.error, undefined);
+  assert.equal(complete.signal, null);
+  assert.equal(complete.status, 0, complete.stderr);
+});

+ 4 - 3
package.json

@@ -18,7 +18,7 @@
   ],
   "scripts": {
     "build": "tsx scripts/build.ts",
-    "build:bench": "npm run build:lib && tsdown --config benchmarks/tsdown.config.ts",
+    "build:bench": "npm run build:native-system && npm run build:lib && tsdown --config benchmarks/tsdown.config.ts",
     "build:official": "tsx scripts/build.ts --profile official",
     "build:lib": "pnpm run build:lib:host && pnpm run build:lib:client",
     "build:lib:host": "node --max-old-space-size=4096 ./node_modules/typescript/bin/tsc -b tsconfig.host.json && tsdown --env.DSH_BUILD_FACE host",
@@ -48,8 +48,9 @@
     "lint:fix": "npm run build:lib:host && npm run lint:fix:contracts-ready",
     "lint:fix:contracts-ready": "tsx scripts/run-oxlint.ts --config .oxlintrc.staged.json packages/typert/generator/tests/fixtures/type-model --fix && tsx scripts/run-oxlint.ts . --fix",
     "duplication": "jscpd --config .jscpd.json packages scripts",
-    "test": "vitest run",
-    "test:coverage": "vitest run --coverage",
+    "test": "pnpm run build:native-system && vitest run",
+    "test:coverage": "pnpm run build:native-system && vitest run --coverage",
+    "build:native-system": "tsx native/system/scripts/build.ts --host-addon-only",
     "test:coverage:partitioned": "tsx scripts/run-coverage-partitions.ts",
     "test:e2e": "vitest run --config vitest.e2e.config.ts",
     "test:bench": "npm run build:bench && npm run build:web && npm run test:bench:built",

+ 6 - 6
packages/experimental/webworker-runtime/src/module-proxies.ts

@@ -1,7 +1,7 @@
 /**
  * The worker bundle's module proxy table: the ONLY platform fork of the host
- * tree. Every entry replaces a Node builtin or an external npm package;
- * workspace and vendored modules are always mounted as they ship.
+ * tree. Entries replace Node builtins, external npm packages, and the native
+ * flock subpath; other workspace and vendored modules are mounted as they ship.
  *
  * The build turns these into bundler aliases, and `node/builtins.ts` turns the
  * same modules into the loader's static table — one list, two consumers.
@@ -16,9 +16,9 @@
  */
 
 /**
- * Module proxy table — the ONLY platform fork of the worker host. Every entry
- * replaces a Node builtin or an external npm package; workspace and vendored
- * modules are always mounted as-is. Keys are exact module specifiers.
+ * Module proxy table — the ONLY platform fork of the worker host. Keys are
+ * exact module specifiers; the native system package's Landlock entry stays
+ * unmodified while its flock subpath is replaced.
  */
 export const MODULE_PROXIES: Record<string, string> = {
   // VFS-backed real implementations.
@@ -65,7 +65,7 @@ export const MODULE_PROXIES: Record<string, string> = {
   'node:worker_threads': './node/builtin_modules/mock/worker_threads.ts',
   'node:sqlite': './node/builtin_modules/mock/sqlite.ts',
   // External npm replacements, named after the package each stands in for.
-  'fs-ext': './node/external_packages/fs-ext.ts',
+  '@deepseek-ai/node-addon-system/flock': './node/external_packages/node-addon-system-flock.ts',
   'koffi': './node/external_packages/koffi.ts',
   'sharp': './node/external_packages/sharp.ts',
   'node-pty': './node/external_packages/node-pty.ts',

+ 3 - 3
packages/experimental/webworker-runtime/src/node/builtins.ts

@@ -45,7 +45,7 @@ import * as nodeNet from './builtin_modules/mock/net.ts'
 import * as nodeSqlite from './builtin_modules/mock/sqlite.ts'
 import * as nodeVm from './builtin_modules/mock/vm.ts'
 import * as nodeWorkerThreads from './builtin_modules/mock/worker_threads.ts'
-import * as fsExt from './external_packages/fs-ext.ts'
+import * as systemFlock from './external_packages/node-addon-system-flock.ts'
 import * as koffi from './external_packages/koffi.ts'
 import * as nodePty from './external_packages/node-pty.ts'
 import * as piAi from './external_packages/pi-ai.ts'
@@ -84,9 +84,9 @@ const BUILTINS: Record<string, StaticModuleFactory> = {
   zlib: () => nodeZlib,
 }
 
-/** External npm packages replaced wholesale (structural not-implemented stubs and fakes). */
+/** Exact package or subpath specifiers served by worker stubs and fakes. */
 const EXTERNALS: Record<string, StaticModuleFactory> = {
-  'fs-ext': () => fsExt,
+  '@deepseek-ai/node-addon-system/flock': () => systemFlock,
   'koffi': () => koffi,
   'sharp': () => sharp,
   'node-pty': () => nodePty,

+ 0 - 42
packages/experimental/webworker-runtime/src/node/external_packages/fs-ext.ts

@@ -1,42 +0,0 @@
-/**
- * `fs-ext` stub: the kernel file-lock bridge the JSONL session backend uses
- * for cross-process write exclusion. The worker is a single-process
- * deployment whose in-process write claim already excludes every writer, so
- * both flock faces succeed immediately; every other entry is loud because
- * nothing in the worker reaches it.
- */
-import { notImplementedFail } from '../notImplementedFail.ts'
-
-const MODULE = 'fs-ext'
-
-/**
- * Asynchronous flock face; the single-process worker grants every lock.
- * @param _fd - file descriptor (unused).
- * @param _flags - lock flags (unused).
- * @param callback - completion callback, invoked with no error.
- */
-export function flock(_fd: number, _flags: unknown, callback: (error: null) => void): void {
-  queueMicrotask(() => { callback(null) })
-}
-
-/**
- * Synchronous flock face; the single-process worker grants every lock.
- */
-export function flockSync(): void {}
-
-/** Unreached in the worker; loud refusal. */
-export const fcntl = notImplementedFail(MODULE, 'fcntl')
-/** Unreached in the worker; loud refusal. */
-export const fcntlSync = notImplementedFail(MODULE, 'fcntlSync')
-/** Unreached in the worker; loud refusal. */
-export const seek = notImplementedFail(MODULE, 'seek')
-/** Unreached in the worker; loud refusal. */
-export const seekSync = notImplementedFail(MODULE, 'seekSync')
-/** Unreached in the worker; loud refusal. */
-export const statVFS = notImplementedFail(MODULE, 'statVFS')
-
-/** CommonJS interop marker: the worker loader hands `default` to default imports (see ./builtins.ts). */
-export const __esModule = true
-
-/** The fs-ext face its consumers read. */
-export default { flock, flockSync, fcntl, fcntlSync, seek, seekSync, statVFS }

+ 20 - 0
packages/experimental/webworker-runtime/src/node/external_packages/node-addon-system-flock.ts

@@ -0,0 +1,20 @@
+/**
+ * Single-process worker replacement for `@deepseek-ai/node-addon-system/flock`.
+ * The JSONL backend's in-process write claim already excludes every writer,
+ * so its kernel-lock request succeeds without acquiring another resource.
+ */
+
+/**
+ * Grant the single-process worker's exclusive-lock request immediately.
+ * @param _fd - file descriptor (unused).
+ * @returns an already-fulfilled promise.
+ */
+export function tryLockExclusive(_fd: number): Promise<void> {
+  return Promise.resolve()
+}
+
+/** CommonJS interop marker for the worker module loader. */
+export const __esModule = true
+
+/** The native flock API used by the JSONL backend. */
+export default { tryLockExclusive }

+ 6 - 7
packages/experimental/webworker-runtime/src/node/external_packages/replaced-externals.ts

@@ -1,16 +1,15 @@
 /**
- * Names of external npm packages the worker replaces wholesale. Kept in a module
- * with no imports so both consumers can read it: the runtime builtin table
- * (`./builtins.ts`) and the build-time VFS image collector, which must leave
- * these packages out of the image entirely — the loader answers them from the
- * bundle before it ever reaches `node_modules`.
+ * Exact package or subpath specifiers served from the worker bundle. Kept
+ * import-free for the runtime builtin table and the VFS image collector.
+ * Whole-package entries are omitted from the image; subpath entries leave
+ * their parent package available to other consumers.
  */
 
-/** External packages served from the worker bundle instead of the VFS. */
+/** Package or subpath specifiers served from the worker bundle instead of the VFS. */
 export const REPLACED_EXTERNAL_PACKAGES: readonly string[] = [
   '@earendil-works/pi-ai',
   '@vscode/ripgrep',
-  'fs-ext',
+  '@deepseek-ai/node-addon-system/flock',
   'koffi',
   'node-pty',
   'sharp',

+ 2 - 2
packages/session/session-persistence-jsonl/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/session/session-persistence-jsonl/README.md
-README.md: cce62bf98f1e906d8800b71e215e53b96bee8044
-README.zh.md: 6bc5f574ecd7266375039858cc9974e47618cc74
+README.md: 34edcac11195f8a15a12abb5dca05b43c2e896dc
+README.zh.md: 803df0d89bfd2d922a42e909f56306f61609e593

+ 1 - 0
packages/session/session-persistence-jsonl/README.md

@@ -155,6 +155,7 @@ These limits define when this backend is a poor fit or needs special operational
 - **Nothing deletes session files** — logs accumulate under `root` until removed externally; the seam has no deletion API.
 - **One live writer per session** — the write-handle claim excludes a second writer inside the owning backend instance, and a kernel lock (non-blocking `flock(2)` on `session.lock`; on Windows a named kernel semaphore derived from that path, with no filesystem footprint) excludes every other instance and process; the lock is taken at write-open of an existing artifact and, for a created session, only right before its first materializing write, so an unmaterialized session leaves no filesystem footprint. A crashed holder's lock dies with its process, so its session is writable again immediately, while a live-but-wedged holder blocks writers until its process exits (on POSIX, removing the lock file forfeits that exclusion; release itself never removes it). Advisory `flock` is unreliable on some network filesystems (NFSv3), and the Windows semaphore name is per login session.
 - **POSIX materialization requires hard-link support** — first append uses `link()` so same-id races fail instead of overwriting a committed log; Windows uses write-through rename without replacement.
+- **POSIX writes require the matching prebuilt system addon** — [`node-addon-system`](../../../native/system/README.md) supplies asynchronous flock without consumer-side compilation. A missing addon rejects write ownership; Windows retains its semaphore implementation.
 
 <a id="dev-note"></a>
 ### Dev Note

+ 1 - 0
packages/session/session-persistence-jsonl/README.zh.md

@@ -155,6 +155,7 @@ JSONL 存储不修改实时请求前缀。只有重建历史、当前 envelope 
 - **不删除会话文件**——日志在 `root` 下累积,直到外部移除;seam 无删除接口。
 - **每会话一个活动写入方**——写句柄认领在所属后端实例内排除第二个写入方,内核锁(`session.lock` 上的非阻塞 `flock(2)`;Windows 上为由该路径派生的命名内核信号量,零文件系统足迹)排除其他所有实例与进程;锁在写打开既有工件时立即获取,新建会话则仅在首次物化写入之前获取,因此未物化的会话不留任何文件系统足迹。崩溃持有者的锁随其进程消亡,会话立即可再写入,而活着但卡死的持有者会阻塞写入方直到其进程退出(POSIX 上删除锁文件即放弃该排他;释放本身从不删除它)。咨询式 `flock` 在部分网络文件系统(NFSv3)上不可靠,Windows 信号量名按登录会话隔离。
 - **POSIX 实体化需要硬链接支持**——第一次 append 使用 `link()`,使同 id 竞态失败而不覆盖已提交日志;Windows 使用无替换 write-through rename。
+- **POSIX 写入需要匹配的预编译系统 addon**——[`node-addon-system`](../../../native/system/README.zh.md) 提供异步 flock,无须在用户侧编译。addon 缺失时拒绝写入所有权;Windows 保留其信号量实现。
 
 <a id="dev-note"></a>
 ### 开发备注

+ 2 - 3
packages/session/session-persistence-jsonl/package.json

@@ -36,7 +36,7 @@
     "@deepseek-ai/dsh-llm": "workspace:^",
     "@deepseek-ai/dsh-session-format": "workspace:^",
     "@deepseek-ai/dsh-session-format-catalog": "workspace:^",
-    "fs-ext": "2.1.1",
+    "@deepseek-ai/node-addon-system": "workspace:^",
     "koffi": "^3.1.0",
     "@deepseek-ai/schemastery": "workspace:^"
   },
@@ -44,7 +44,6 @@
     "@deepseek-ai/dsh-session": "workspace:^",
     "@deepseek-ai/dsh-session-format-v0-to-v1": "workspace:^",
     "@deepseek-ai/dsh-session-persistence": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^",
-    "@types/fs-ext": "2.0.3"
+    "@deepseek-ai/cordis": "workspace:^"
   }
 }

+ 4 - 14
packages/session/session-persistence-jsonl/src/lease.ts

@@ -1,7 +1,7 @@
 /**
  * Cross-process write-ownership lock for one session's artifact directory,
  * held for the whole life of a write handle. The arbiter is the kernel:
- * POSIX takes a non-blocking `flock(2)` (through fs-ext) on `session.lock`
+ * POSIX takes a non-blocking `flock(2)` via native system support on `session.lock`
  * beside the log, and Windows holds a named kernel semaphore derived from
  * that path — never a file lock or handle, so readers, searches, and
  * directory removal proceed freely while the lock is held. Contention maps
@@ -22,7 +22,7 @@
  * unmaterialized session has no filesystem footprint. Release never removes
  * the POSIX lock file: every acquired lock belongs to a materialized or
  * materializing session, and the surviving file keeps the stable inode later
- * lockers verify against. The browser worker deployment stubs fs-ext to
+ * lockers verify against. The browser worker stubs the native flock entry to
  * immediate success: it is single-process, so the in-process write claim
  * already excludes every writer.
  * @module @deepseek-ai/dsh-session-persistence-jsonl/lease
@@ -31,7 +31,7 @@
 import { mkdir, open, stat } from 'node:fs/promises'
 import type { FileHandle } from 'node:fs/promises'
 import { join } from 'node:path'
-import { flock } from 'fs-ext'
+import { tryLockExclusive } from '@deepseek-ai/node-addon-system/flock'
 import { SessionAlreadyOwnedError } from '@deepseek-ai/dsh-session-persistence'
 import type { SessionId } from '@deepseek-ai/dsh-session'
 import { acquireLockHandleWin32, releaseLockHandleWin32 } from './win32.ts'
@@ -44,16 +44,6 @@ type HeldLock =
   | { readonly kind: 'posix'; readonly handle: FileHandle }
   | { readonly kind: 'win32'; readonly handle: number }
 
-/** Promise face over fs-ext's callback flock, pinned to its string-flag overload. */
-function flockAsync(fd: number, flags: 'exnb' | 'un'): Promise<void> {
-  return new Promise((resolve, reject) => {
-    flock(fd, flags, (error) => {
-      if (error) reject(error)
-      else resolve()
-    })
-  })
-}
-
 /** Whether a flock failure means another descriptor holds the lock. */
 function isLockContention(error: unknown): boolean {
   const code = (error as NodeJS.ErrnoException | null)?.code
@@ -101,7 +91,7 @@ export class SessionWriteLease {
       const handle = await open(path, 'w')
       try {
         try {
-          await flockAsync(handle.fd, 'exnb')
+          await tryLockExclusive(handle.fd)
         } catch (error: unknown) {
           if (isLockContention(error)) throw new SessionAlreadyOwnedError(id)
           throw error

+ 8 - 11
packages/session/session-persistence-jsonl/tests/lease.spec.ts

@@ -39,7 +39,7 @@ const refuse = vi.hoisted(() => ({
   lockOpen: false,
   /** Next flock call fails EACCES (a non-contention kernel refusal). */
   flock: false,
-  /** Next flock call fails EWOULDBLOCK (the Windows LockFileEx contention code). */
+  /** Next flock call fails EWOULDBLOCK. */
   flockBusy: false,
   /** Next stat of a lock file fails EACCES (unreadable path). */
   lockStat: false,
@@ -84,23 +84,20 @@ vi.mock('node:fs/promises', async (importOriginal) => {
   }
 })
 
-vi.mock('fs-ext', async (importOriginal) => {
-  const actual = await importOriginal<typeof import('fs-ext')>()
+vi.mock('@deepseek-ai/node-addon-system/flock', async (importOriginal) => {
+  const actual = await importOriginal<typeof import('@deepseek-ai/node-addon-system/flock')>()
   return {
-    ...actual,
-    flock: ((fd: number, flags: never, callback: (error: Error | null) => void) => {
+    tryLockExclusive: async (fd: number): Promise<void> => {
       if (refuse.flock) {
         refuse.flock = false
-        callback(Object.assign(new Error('EACCES: injected flock refusal'), { code: 'EACCES' }))
-        return
+        throw Object.assign(new Error('EACCES: injected flock refusal'), { code: 'EACCES' })
       }
       if (refuse.flockBusy) {
         refuse.flockBusy = false
-        callback(Object.assign(new Error('EWOULDBLOCK: injected contention'), { code: 'EWOULDBLOCK' }))
-        return
+        throw Object.assign(new Error('EWOULDBLOCK: injected contention'), { code: 'EWOULDBLOCK' })
       }
-      (actual.flock as (fd: number, flags: never, callback: (error: Error | null) => void) => void)(fd, flags, callback)
-    }) as typeof actual.flock,
+      return actual.tryLockExclusive(fd)
+    },
   }
 })
 

+ 3 - 0
packages/session/session-persistence-jsonl/tsconfig.json

@@ -8,6 +8,9 @@
     "src"
   ],
   "references": [
+    {
+      "path": "../../../native/system/packages/entry"
+    },
     {
       "path": "../../../vendor/cosmokit"
     },

+ 18 - 31
pnpm-lock.yaml

@@ -754,8 +754,18 @@ importers:
         specifier: ^6.0.3
         version: 6.0.3
 
+  native/system/packages/darwin-arm64: {}
+
+  native/system/packages/darwin-x64: {}
+
   native/system/packages/entry:
     optionalDependencies:
+      '@deepseek-ai/node-addon-system-darwin-arm64':
+        specifier: workspace:*
+        version: link:../darwin-arm64
+      '@deepseek-ai/node-addon-system-darwin-x64':
+        specifier: workspace:*
+        version: link:../darwin-x64
       '@deepseek-ai/node-addon-system-linux-arm64':
         specifier: workspace:*
         version: link:../linux-arm64
@@ -8011,12 +8021,12 @@ importers:
       '@deepseek-ai/dsh-session-format-catalog':
         specifier: workspace:^
         version: link:../session-format-catalog
+      '@deepseek-ai/node-addon-system':
+        specifier: workspace:^
+        version: link:../../../native/system/packages/entry
       '@deepseek-ai/schemastery':
         specifier: link:../../../vendor/schemastery
         version: link:../../../vendor/schemastery
-      fs-ext:
-        specifier: 2.1.1
-        version: 2.1.1
       koffi:
         specifier: ^3.1.0
         version: 3.1.1
@@ -8033,9 +8043,6 @@ importers:
       '@deepseek-ai/dsh-session-persistence':
         specifier: workspace:^
         version: link:../session-persistence
-      '@types/fs-ext':
-        specifier: 2.0.3
-        version: 2.0.3
 
   packages/session/session-projection:
     dependencies:
@@ -13848,9 +13855,6 @@ packages:
   '@types/express@5.0.6':
     resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==}
 
-  '@types/fs-ext@2.0.3':
-    resolution: {integrity: sha512-0j2F+laosJF2NTd2DVheQ5GvXo8ln9L175VwLPfbsppE33iYC+6gn6XlOQS0pGvZm2yrQ32/LRZh0As/7rCs2Q==}
-
   '@types/fs-extra@9.0.13':
     resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
 
@@ -15359,10 +15363,6 @@ packages:
   fs-constants@1.0.0:
     resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
 
-  fs-ext@2.1.1:
-    resolution: {integrity: sha512-/TrISPOFhCkbgIRWK9lzscRzwPCu0PqtCcvMc9jsHKBgZGoqA0VzhspVht5Zu8lxaXjIYIBWILHpRotYkCCcQA==}
-    engines: {node: '>= 8.0.0'}
-
   fs-extra@10.1.0:
     resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
     engines: {node: '>=12'}
@@ -16362,9 +16362,6 @@ packages:
   multistream@4.1.0:
     resolution: {integrity: sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==}
 
-  nan@2.28.0:
-    resolution: {integrity: sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==}
-
   nanoid@3.3.12:
     resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -17170,13 +17167,13 @@ packages:
   stackback@0.0.2:
     resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
 
+  standardwebhooks@1.1.1:
+    resolution: {integrity: sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==}
+
   stat-mode@1.0.0:
     resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==}
     engines: {node: '>= 6'}
 
-  standardwebhooks@1.1.1:
-    resolution: {integrity: sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==}
-
   statuses@2.0.2:
     resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
     engines: {node: '>= 0.8'}
@@ -20383,10 +20380,6 @@ snapshots:
       '@types/express-serve-static-core': 5.1.3
       '@types/serve-static': 2.2.0
 
-  '@types/fs-ext@2.0.3':
-    dependencies:
-      '@types/node': 22.20.0
-
   '@types/fs-extra@9.0.13':
     dependencies:
       '@types/node': 22.20.0
@@ -22154,10 +22147,6 @@ snapshots:
 
   fs-constants@1.0.0: {}
 
-  fs-ext@2.1.1:
-    dependencies:
-      nan: 2.28.0
-
   fs-extra@10.1.0:
     dependencies:
       graceful-fs: 4.2.11
@@ -23365,8 +23354,6 @@ snapshots:
       once: 1.4.0
       readable-stream: 3.6.2
 
-  nan@2.28.0: {}
-
   nanoid@3.3.12: {}
 
   napi-build-utils@2.0.0: {}
@@ -24290,13 +24277,13 @@ snapshots:
 
   stackback@0.0.2: {}
 
-  stat-mode@1.0.0: {}
-
   standardwebhooks@1.1.1:
     dependencies:
       '@stablelib/base64': 1.0.1
       fast-sha256: 1.3.0
 
+  stat-mode@1.0.0: {}
+
   statuses@2.0.2: {}
 
   std-env@4.1.0: {}

+ 0 - 3
pnpm-workspace.yaml

@@ -44,9 +44,6 @@ allowBuilds:
   node-addon-require-builtin: false
   # JSONL durability calls MoveFileExW with write-through publication on Windows.
   koffi: true
-  # The session write lock is flock(2) / LockFileEx; fs-ext compiles its
-  # binding with node-gyp at install.
-  fs-ext: true
   # The Python runtime deploy includes the reviewed workspace postinstall that
   # restores the executable bit on node-pty's macOS spawn helper.
   '@deepseek-ai/dsh-subprocess-local@file:packages/subprocess/subprocess-local': true

+ 1 - 0
scripts/build.ts

@@ -41,6 +41,7 @@ function main(): void {
   const buildEnvironment = clientBuildProcessEnvironment(process.env, clientEnvironment)
 
   rmSync(resolve(root, CLIENT_BUILD_RECORD_PATH), { force: true })
+  runScript('build:native-system', buildEnvironment)
   runScript('build:lib', buildEnvironment)
   runScript('build:web', buildEnvironment)
   const record = writeClientBuildRecord(root, clientEnvironment)

+ 3 - 1
scripts/check-workspace-constraints.ts

@@ -34,12 +34,14 @@ const vendoredPackages = new Set([
 ])
 const publicNativePackages = new Set([
   '@deepseek-ai/node-addon-system',
+  '@deepseek-ai/node-addon-system-darwin-arm64',
+  '@deepseek-ai/node-addon-system-darwin-x64',
   '@deepseek-ai/node-addon-system-linux-arm64',
   '@deepseek-ai/node-addon-system-linux-x64',
 ])
 /** Deliberate source payloads whose exact bytes are part of the package's audit surface. */
 const publicationSourceAllowlist: Readonly<Record<string, readonly string[]>> = {
-  '@deepseek-ai/node-addon-system': ['src/main.c'],
+  '@deepseek-ai/node-addon-system': ['src/main.c', 'src/flock.c'],
 }
 const repositoryUrl = 'git+https://github.com/deepseek-harness/deepseek-harness.git'
 /**

+ 2 - 1
scripts/gen-third-party-notices.ts

@@ -41,6 +41,8 @@ const DEV_ONLY_AREAS = [
 /** First-party public native packages: reachable at runtime but not third-party. */
 const FIRST_PARTY = new Set([
   '@deepseek-ai/node-addon-system',
+  '@deepseek-ai/node-addon-system-darwin-arm64',
+  '@deepseek-ai/node-addon-system-darwin-x64',
   '@deepseek-ai/node-addon-system-linux-arm64',
   '@deepseek-ai/node-addon-system-linux-x64',
 ])
@@ -75,7 +77,6 @@ const OVERRIDES: Record<string, { license?: string; repo?: string }> = {
   // No repository field in the published manifest.
   'node-addon-require-builtin': { repo: 'https://www.npmjs.com/package/node-addon-require-builtin' },
   // No `license` field in the published manifest; the tarball's LICENSE.txt is the MIT text.
-  'fs-ext': { license: 'MIT' },
 }
 
 /**

+ 21 - 0
scripts/run-gates.spec.ts

@@ -160,6 +160,27 @@ describe('gate graph validation', () => {
     await expect(runGates(subject, subject.length, execute)).resolves.toHaveLength(subject.length)
   })
 
+  it('builds the native addon before benchmarks through the ci-bench script chain', () => {
+    const subject = withPnpmEntrypoint(() => gatesForMode('ci-bench'))
+    const { scripts } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as {
+      scripts: Record<string, string>
+    }
+
+    expect(scripts['check:ci:bench']).toBe('tsx scripts/run-gates.ts ci-bench')
+    expect(subject).toHaveLength(1)
+    expect(subject[0]).toMatchObject({
+      id: 'bench',
+      displayCommand: 'pnpm run test:bench',
+      args: ['/private/pnpm.cjs', 'run', 'test:bench'],
+    })
+    expect(scripts['test:bench']).toBe('npm run build:bench && npm run build:web && npm run test:bench:built')
+    expect(scripts['build:bench']).toBe(
+      'npm run build:native-system && npm run build:lib && tsdown --config benchmarks/tsdown.config.ts',
+    )
+    expect(scripts['build:native-system']).toBe('tsx native/system/scripts/build.ts --host-addon-only')
+    expect(scripts['test:bench:built']).toBe('vitest run --config vitest.bench.config.ts')
+  })
+
   it('keeps the public repository link policy in the documentation gate', () => {
     const ids = withPnpmEntrypoint(() => gatesForMode('doc-sync').map(subject => subject.id))
 

+ 3 - 1
scripts/run-gates.ts

@@ -631,7 +631,8 @@ function coverageGates(): Gate[] {
       streamOutput: true,
     })
   return [
-    instrumented,
+    pnpmScript('native-system', 'build:native-system'),
+    { ...instrumented, needs: ['native-system'] },
     pnpmExec('coverage-exempt-heavy', [
       'vitest',
       'run',
@@ -640,6 +641,7 @@ function coverageGates(): Gate[] {
       ...timeouts,
     ], {
       label: 'test:coverage-exempt-heavy',
+      needs: ['native-system'],
     }),
   ]
 }

+ 1 - 0
tsconfig.base.json

@@ -38,6 +38,7 @@
       "@deepseek-ai/cordis-plugin-hmr": ["./vendor/hmr/src"],
       "@deepseek-ai/cordis-plugin-logger-console": ["./vendor/logger-console/src"],
       "@deepseek-ai/node-addon-system": ["./native/system/packages/entry/src/index.ts"],
+      "@deepseek-ai/node-addon-system/flock": ["./native/system/packages/entry/src/flock.ts"],
       "@deepseek-ai/dsh-invariants": ["./packages/runtime-diagnostics/invariants/src/index.ts"],
       "@deepseek-ai/dsh-tool-call-timeout-policy": ["./packages/guard/timeout-policy/src"],
       "@deepseek-ai/dsh-spill-policy/notice": ["./packages/spill/spill-policy/src/notice.ts"],