diff --git a/.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.i18n.yaml index 643164b793..c11c54f6a9 100644 --- a/.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.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/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 +2026-09-07-prebuilt-system-primitives.md: 183cd3ea984cd779417493f0de17a770f38fc001 +2026-09-07-prebuilt-system-primitives.zh.md: f051bba92910086601aa48a877f360e276df524d diff --git a/.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.md b/.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.md index 23037c2d0e..183cd3ea98 100644 --- a/.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.md +++ b/.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.md @@ -12,9 +12,9 @@ The JSONL writer's `fs-ext` dependency compiled a NAN addon during consumer inst 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 package has no root export. The `./landlock-run` 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. +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 uses the unchanged `./landlock-run` 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. diff --git a/.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.zh.md b/.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.zh.md index 4e1e6cc061..f051bba929 100644 --- a/.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-07-prebuilt-system-primitives.zh.md @@ -12,9 +12,9 @@ JSONL 写入方依赖的 `fs-ext` 在用户安装时编译 NAN addon。因此, [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。调用方在完成前持有描述符,并通过关闭它释放锁。绑定缺失时拒绝获取锁,不授予没有保护的锁。 +包不提供根导出。`./landlock-run` 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。 +[Session 写租约决策](../feature/2026-08-31-cross-process-session-write-lease.zh.md) 继续负责获取时机、inode 校验、关闭所有权和崩溃语义。Windows 保留既有 koffi 信号量。浏览器 worker 仅替换 flock 子路径,使用未经修改的 `./landlock-run` JavaScript API。 源码构建在需要 addon 的仓库测试与构建之前显式编译当前宿主 addon。Native CI 构建完整平台产物,并让相同 addon 字节跨 Node 版本测试;Linux 还在 Alpine 中执行 musl 产物。平台 prepack 拒绝格式错误或不完整的二进制,离线 npm 安装演练检查安装字节与真实锁行为。Native [测试](../../../../native/system/test/flock.test.js) 覆盖描述符与进程竞争、关闭和崩溃释放、独立 errno 值及 worker 清理。 diff --git a/native/system/AGENTS.md b/native/system/AGENTS.md index fceeb437b7..8234f941fd 100644 --- a/native/system/AGENTS.md +++ b/native/system/AGENTS.md @@ -8,7 +8,7 @@ This workspace owns `@deepseek-ai/node-addon-system`: the Linux `landlock-run` c - 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. +- `./landlock-run` and `./flock` are independent capability exports; the package has no root export. Neither import loads 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. diff --git a/native/system/README.i18n.yaml b/native/system/README.i18n.yaml index adc328a0aa..1e1eb1cd92 100644 --- a/native/system/README.i18n.yaml +++ b/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: 674e47c79726d0212ab17468d90e0a73e6c63db6 -README.zh.md: b9770453e25620add25a67d93a89abd0f41c6554 +README.md: 5a01754cb96fabbdfd2dbf4f068f19addd7a2347 +README.zh.md: dabd00cecd05ded13a50d2efd000985afed713d4 diff --git a/native/system/README.md b/native/system/README.md index 674e47c797..5a01754cb9 100644 --- a/native/system/README.md +++ b/native/system/README.md @@ -18,7 +18,7 @@ Use the Linux `landlock-run` executable to confine subprocesses, or the `./flock ## Use -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). +`@deepseek-ai/node-addon-system/landlock-run` exports `launcherPath`, `probe`, and `grantArgs` for Landlock. Its executable name, flags, and failure semantics are defined by the [CLI contract](docs/cli-contract.md). The [flock behavior contract](docs/flock-contract.md) maps descriptor, process, and advisory-lock semantics to independent native tests. diff --git a/native/system/README.zh.md b/native/system/README.zh.md index b9770453e2..dabd00cecd 100644 --- a/native/system/README.zh.md +++ b/native/system/README.zh.md @@ -18,7 +18,7 @@ kind: "package-library" ## Use -根入口为 Landlock 导出 `launcherPath`、`probe` 和 `grantArgs`。其可执行文件名、参数和失败语义由 [CLI 约定](docs/cli-contract.md) 定义。 +`@deepseek-ai/node-addon-system/landlock-run` 为 Landlock 导出 `launcherPath`、`probe` 和 `grantArgs`。其可执行文件名、参数和失败语义由 [CLI 约定](docs/cli-contract.md) 定义。 [flock 行为约定](docs/flock-contract.md) 将描述符、进程和咨询式锁语义对应到独立原生测试。 diff --git a/native/system/docs/architecture.md b/native/system/docs/architecture.md index 1aa3df99ae..7c258752a3 100644 --- a/native/system/docs/architecture.md +++ b/native/system/docs/architecture.md @@ -4,9 +4,9 @@ The system package family supplies native mechanisms to Node callers: a Linux co ## Package family -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. +The ESM package `@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. -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. +The `./landlock-run` entry owns Landlock path resolution, grant argv, and the 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. The package exposes these capability subpaths and its manifest, without a root export. ## Separate mechanisms diff --git a/native/system/docs/packaging.md b/native/system/docs/packaging.md index 039cf76575..5ca2320612 100644 --- a/native/system/docs/packaging.md +++ b/native/system/docs/packaging.md @@ -4,7 +4,7 @@ The family publishes one ESM entry package plus OS/CPU-selected optional platfor ## Payloads -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. +The entry package exports the Landlock API at `./landlock-run` and the asynchronous lock API at `./flock`, with C sources included for auditability. There is no root export. Platform packages contain no JavaScript. - Linux: `bin/landlock-run`, `bin/glibc/system.node`, and `bin/musl/system.node`. - macOS: `bin/system.node`. @@ -15,7 +15,7 @@ The entry exports the Landlock API at its root and the asynchronous lock API at 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. -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. +The `./landlock-run` 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. ## Pack verification diff --git a/native/system/packages/entry/README.i18n.yaml b/native/system/packages/entry/README.i18n.yaml index 3626041e8e..6c10cabb1c 100644 --- a/native/system/packages/entry/README.i18n.yaml +++ b/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: af3f5ac4df5883d7d9b4f65d273f9d45fae874e8 -README.zh.md: 55f428568cb7b5e7a9956c842931f52e0feeaf0a +README.md: 8f6a4d341c8f9e4c104e5706e08d9a7d45c065e1 +README.zh.md: 152257916df0cf40511b9743d389ae753ab6c85c diff --git a/native/system/packages/entry/README.md b/native/system/packages/entry/README.md index af3f5ac4df..8f6a4d341c 100644 --- a/native/system/packages/entry/README.md +++ b/native/system/packages/entry/README.md @@ -6,7 +6,7 @@ kind: "package-library" English | [中文](README.zh.md) -The root entry exports the Landlock launcher path, enforcement probe, grant arguments, and protocol constants. Its independent `./flock` entry exports `tryLockExclusive(fd): Promise`; importing either entry does not load `system.node`. +The `./landlock-run` entry exports the Landlock launcher path, enforcement probe, grant arguments, and protocol constants. The independent `./flock` entry exports `tryLockExclusive(fd): Promise`; importing either entry does not load `system.node`. The package has no root export. 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. diff --git a/native/system/packages/entry/README.zh.md b/native/system/packages/entry/README.zh.md index 55f428568c..152257916d 100644 --- a/native/system/packages/entry/README.zh.md +++ b/native/system/packages/entry/README.zh.md @@ -6,7 +6,7 @@ kind: "package-library" [English](README.md) | 中文 -根入口导出 Landlock 启动器路径、强制执行探测、授权参数和协议常量。独立的 `./flock` 入口导出 `tryLockExclusive(fd): Promise`;导入任一入口都不会加载 `system.node`。 +`./landlock-run` 入口导出 Landlock 启动器路径、强制执行探测、授权参数和协议常量。独立的 `./flock` 入口导出 `tryLockExclusive(fd): Promise`;导入任一入口都不会加载 `system.node`。包不提供根导出。 锁操作异步尝试 `LOCK_EX | LOCK_NB`。在完成前保持调用方拥有的描述符打开;竞争以 `EAGAIN`/`EWOULDBLOCK` 拒绝,其他系统调用失败也会拒绝,错误携带 code、正 errno 和 `syscall: 'flock'`。原生调用准备阶段的错误也会拒绝同一个 promise。关闭该打开文件描述的最后一个描述符即释放锁。绑定不打开、复制、关闭或显式解锁描述符。 diff --git a/native/system/packages/entry/package.json b/native/system/packages/entry/package.json index b4c64e3675..9f46d53f01 100644 --- a/native/system/packages/entry/package.json +++ b/native/system/packages/entry/package.json @@ -8,10 +8,8 @@ "url": "git+https://github.com/deepseek-harness/deepseek-harness.git", "directory": "native/system/packages/entry" }, - "main": "lib/index.js", - "types": "lib/index.d.ts", "exports": { - ".": { + "./landlock-run": { "types": "./lib/index.d.ts", "default": "./lib/index.js" }, diff --git a/native/system/scripts/verify-packed-install.mjs b/native/system/scripts/verify-packed-install.mjs index a7c83f5033..ebd6df487d 100644 --- a/native/system/scripts/verify-packed-install.mjs +++ b/native/system/scripts/verify-packed-install.mjs @@ -166,9 +166,13 @@ import { spawnSync } from 'node:child_process'; 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 { grantArgs, launcherPath, probe } from '@deepseek-ai/node-addon-system/landlock-run'; import { tryLockExclusive } from '@deepseek-ai/node-addon-system/flock'; +await assert.rejects(import('@deepseek-ai/node-addon-system'), { + code: 'ERR_PACKAGE_PATH_NOT_EXPORTED', +}); + const requireLandlock = process.env.NALR_REQUIRE_LANDLOCK === '1'; const platformPackage = '@deepseek-ai/node-addon-system-' + process.platform + '-' + process.arch; const resolved = launcherPath(); diff --git a/native/system/test/entry.test.js b/native/system/test/entry.test.js index e000df198b..b93bc2a6eb 100644 --- a/native/system/test/entry.test.js +++ b/native/system/test/entry.test.js @@ -15,7 +15,11 @@ import { grantArgs, launcherPath, probe, -} from '@deepseek-ai/node-addon-system'; +} from '@deepseek-ai/node-addon-system/landlock-run'; + +await assert.rejects(import('@deepseek-ai/node-addon-system'), { + code: 'ERR_PACKAGE_PATH_NOT_EXPORTED', +}); // --- constants are part of the CLI contract --- assert.equal(LAUNCHER_BIN, 'landlock-run'); diff --git a/native/system/test/launcher.test.js b/native/system/test/launcher.test.js index 4043000f78..01fbc8458e 100644 --- a/native/system/test/launcher.test.js +++ b/native/system/test/launcher.test.js @@ -22,7 +22,7 @@ import { grantArgs, launcherPath, probe, -} from '@deepseek-ai/node-addon-system'; +} from '@deepseek-ai/node-addon-system/landlock-run'; const FATAL_PREFIX = 'landlock-run: '; const PARTIAL_NOTICE = 'landlock-run: partial enforcement (older Landlock ABI)'; diff --git a/native/system/test/package-matrix.test.js b/native/system/test/package-matrix.test.js index 8b4656378c..12ea4c3030 100644 --- a/native/system/test/package-matrix.test.js +++ b/native/system/test/package-matrix.test.js @@ -7,6 +7,54 @@ import { spawnSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import { verifyPlatformBinaries } from '../scripts/repo.mjs'; +test('the real Landlock subpath imports without platform packages or dlopen, while the root is unexported', { timeout: 120_000 }, (t) => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'system-landlock-entry-')); + t.after(() => fs.rmSync(dir, { recursive: true, force: true }), { timeout: 120_000 }); + const entry = fileURLToPath(new URL('../packages/entry/', import.meta.url)); + const installed = path.join(dir, 'node_modules', '@deepseek-ai', 'node-addon-system'); + fs.mkdirSync(installed, { recursive: true }); + fs.copyFileSync(path.join(entry, 'package.json'), path.join(installed, 'package.json')); + // Only the real entry payload is present; no platform package or addon is copied. + fs.cpSync(path.join(entry, 'lib'), path.join(installed, 'lib'), { recursive: true }); + const manifest = JSON.parse(fs.readFileSync(path.join(installed, 'package.json'), 'utf8')); + assert.equal(manifest.main, undefined); + assert.equal(manifest.types, undefined); + + const result = spawnSync(process.execPath, ['--no-addons', '--input-type=module', '--eval', ` + import assert from 'node:assert/strict'; + import { createRequire } from 'node:module'; + const originalDlopen = process.dlopen; + let dlopenCalls = 0; + try { + process.dlopen = () => { + dlopenCalls++; + throw new Error('Landlock import attempted dlopen'); + }; + const api = await import('@deepseek-ai/node-addon-system/landlock-run'); + assert.equal(api.LAUNCHER_BIN, 'landlock-run'); + assert.deepEqual(api.grantArgs({}), []); + assert.equal(dlopenCalls, 0); + await assert.rejects(import('@deepseek-ai/node-addon-system'), { + code: 'ERR_PACKAGE_PATH_NOT_EXPORTED', + }); + assert.throws(() => createRequire(import.meta.url).resolve('@deepseek-ai/node-addon-system'), { + code: 'ERR_PACKAGE_PATH_NOT_EXPORTED', + }); + } finally { + process.dlopen = originalDlopen; + } + `], { + cwd: dir, + encoding: 'utf8', + timeout: 120_000, + env: Object.fromEntries(Object.entries(process.env) + .filter(([key]) => !/KEY|TOKEN|SECRET|PASSWORD|^NODE_PATH$/i.test(key))), + }); + assert.equal(result.error, undefined); + assert.equal(result.signal, null, result.stderr); + assert.equal(result.status, 0, result.stderr); +}); + // 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' } = {}) { @@ -106,14 +154,14 @@ test('rejects a symbolic-link payload', { skip: process.platform === 'win32' }, 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) => { +test('entry prepack rejects a missing exported flock file even when the Landlock 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' }, + './landlock-run': { types: './lib/index.d.ts', default: './lib/index.js' }, './flock': { types: './lib/flock.d.ts', default: './lib/flock.js' }, }, })); diff --git a/packages/experimental/webworker-packer/tests/image-loadable.spec.ts b/packages/experimental/webworker-packer/tests/image-loadable.spec.ts index a7b586c10d..f1a7edafc2 100644 --- a/packages/experimental/webworker-packer/tests/image-loadable.spec.ts +++ b/packages/experimental/webworker-packer/tests/image-loadable.spec.ts @@ -36,6 +36,7 @@ const repoRoot = fileURLToPath(new URL('../../../../', import.meta.url)) /** A leaf workspace package: real build output, no dependencies to drag in. */ const SUBJECT = '@deepseek-ai/dsh-timeout' const LANDLOCK = '@deepseek-ai/node-addon-system' +const LANDLOCK_ENTRY = `${LANDLOCK}/landlock-run` const PLUGIN_INVENTORY = '@deepseek-ai/dsh-plugin-package-inventory-deepseek' const WEB_SERVER = '@deepseek-ai/dsh-host-webserver' @@ -240,7 +241,7 @@ const archive = async (): Promise => expect(result.packages.has(LANDLOCK)).toBe(true) expect(result.missing).toEqual([]) expect(Object.hasOwn(result.files, `node_modules/${LANDLOCK}/lib/index.js`)).toBe(true) - expect(createNodeBuiltins()[LANDLOCK]).toBeUndefined() + expect(createNodeBuiltins()[LANDLOCK_ENTRY]).toBeUndefined() const vfs = loadVfsImage(await inflateImage(result.image, 'the packed Landlock package'), DEFAULT_ROOT) const loader = new WorkerModuleLoader({ @@ -251,7 +252,7 @@ const archive = async (): Promise => }) setActiveVfs(vfs) setActiveModuleLoader(loader) - const landlock = loader.requireFrom(`${DEFAULT_ROOT}/workspace`)(LANDLOCK) as { + const landlock = loader.requireFrom(`${DEFAULT_ROOT}/workspace`)(LANDLOCK_ENTRY) as { LAUNCHER_BIN: string LAUNCHER_FAILURE_EXIT: number launcherPath(): string diff --git a/packages/experimental/webworker-runtime/README.i18n.yaml b/packages/experimental/webworker-runtime/README.i18n.yaml index 8c771c662b..7f82ab35d0 100644 --- a/packages/experimental/webworker-runtime/README.i18n.yaml +++ b/packages/experimental/webworker-runtime/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/experimental/webworker-runtime/README.md -README.md: 0bfac2891e14d7c6f9947c62acf968bef8220565 -README.zh.md: b31fd325c79c82d3992a62cbe4c9fb8c5506211b +README.md: df61667329deacf99c320cd4121715115403d6f5 +README.zh.md: 7faff0584764978ba30cb8f832adf25a98237500 diff --git a/packages/experimental/webworker-runtime/README.md b/packages/experimental/webworker-runtime/README.md index 0bfac2891e..df61667329 100644 --- a/packages/experimental/webworker-runtime/README.md +++ b/packages/experimental/webworker-runtime/README.md @@ -50,7 +50,7 @@ None; this package neither assembles nor sends a provider request. - **The worker composition writes plaintext session logs** (`compression: 'none'` boot patch): it carries no Zstandard codec, so exported logs are `.jsonl`, never `.jsonl.zstd`. - **`node:dns/promises`, `node:vm`, `node:net`, `node:sqlite`, `node:worker_threads` are structural stubs**: every call reports its refusal on the console and throws. Rows needing native DNS, a real process, or realm isolation cannot run here. - **Filesystem watchers observe only the mounted VFS**: image seeding is silent and the VFS has no symlinks or external writers. `persistent`, `ref()`, and `unref()` preserve the Node API but cannot control a dedicated Worker's lifetime because browsers expose no ref-counted event loop. -- **Worker confinement is a VFS boundary, not kernel Landlock**: `read-only` and `workspace-write` run the unchanged `@deepseek-ai/node-addon-system` JavaScript and launcher argv, but the process layer implements the logical `landlock-run` executable and enforces its grants on every shell filesystem request. `full` therefore covers the Worker command table and mounted VFS only; it does not claim arbitrary native-process execution or Linux kernel isolation. +- **Worker confinement is a VFS boundary, not kernel Landlock**: `read-only` and `workspace-write` run the unchanged `@deepseek-ai/node-addon-system/landlock-run` JavaScript and launcher argv, but the process layer implements the logical `landlock-run` executable and enforces its grants on every shell filesystem request. `full` therefore covers the Worker command table and mounted VFS only; it does not claim arbitrary native-process execution or Linux kernel isolation. - **The worker bundle pins a path inside `@yarnpkg/parsers`** — the build resolves the package's own `lib/shell.js` instead of its root, whose barrel also re-exports the Syml parser and so drags js-yaml into a bundle that never parses that format (around 175 kB, plus its module body at worker start). The path is derived from the package manifest, so a layout change fails the build rather than reinstating the barrel; upgrading the dependency means re-checking that the shell parser still lives there. - **The shell is not bash**: no loops, functions, `case`, job control, or process substitution — the grammar stops at pipelines, `&&`/`||`, subshells, groups, redirections, and expansion. `&` runs its command to completion in place, `sed` accepts only substitution scripts, patterns are JavaScript regular expressions, and the command table holds coreutils only (no `git`, no network tools). - **A shell process has no synchronous filesystem**: it reads and writes the host's VFS by message, because blocking on a reply would need `SharedArrayBuffer`, which requires a cross-origin isolation GitHub Pages cannot grant. Directory-walking commands therefore cost one round trip per entry, and two concurrent commands can interleave their writes. diff --git a/packages/experimental/webworker-runtime/README.zh.md b/packages/experimental/webworker-runtime/README.zh.md index b31fd325c7..7faff05847 100644 --- a/packages/experimental/webworker-runtime/README.zh.md +++ b/packages/experimental/webworker-runtime/README.zh.md @@ -50,7 +50,7 @@ kind: "package-library" - **worker 组合写明文会话日志**(`compression: 'none'` boot patch):不带 Zstandard 编解码器,导出日志是 `.jsonl`,不会是 `.jsonl.zstd`。 - **`node:dns/promises`、`node:vm`、`node:net`、`node:sqlite`、`node:worker_threads` 是结构化 stub**:每次调用在 console 报告拒绝并抛出。需要原生 DNS、真进程或真 realm 隔离的行在此无法运行。 - **文件 watcher 只能观察已挂载的 VFS**:镜像 seed 不产生事件,VFS 也没有符号链接或外部写入方。`persistent`、`ref()` 和 `unref()` 保留 Node API,但浏览器没有引用计数事件循环,因此这些接口不能控制 dedicated Worker 的生存期。 -- **Worker confinement 是 VFS 边界,不是内核 Landlock**:`read-only` 和 `workspace-write` 运行未经修改的 `@deepseek-ai/node-addon-system` JavaScript 与 launcher argv,进程层则实现逻辑 `landlock-run` 可执行文件,并在 shell 的每次文件系统请求上执行其授权。`full` 仅覆盖 Worker 命令表和已挂载 VFS,不表示能够执行任意 native 进程,也不表示 Linux 内核隔离。 +- **Worker confinement 是 VFS 边界,不是内核 Landlock**:`read-only` 和 `workspace-write` 运行未经修改的 `@deepseek-ai/node-addon-system/landlock-run` JavaScript 与 launcher argv,进程层则实现逻辑 `landlock-run` 可执行文件,并在 shell 的每次文件系统请求上执行其授权。`full` 仅覆盖 Worker 命令表和已挂载 VFS,不表示能够执行任意 native 进程,也不表示 Linux 内核隔离。 - **worker 束钉住了 `@yarnpkg/parsers` 的包内路径**——构建解析到该包自己的 `lib/shell.js` 而非包根,因为包根 barrel 还 re-export 了 Syml 解析器,会把 js-yaml 拖进一个从不解析该格式的束(约 175 kB,外加 worker 启动时的模块体求值)。该路径由包 manifest 派生,包内布局一变即构建期失败、不会静默退回 barrel;升级这个依赖时须复核 shell 解析器是否仍在那里。 - **这个 shell 不是 bash**:没有循环、函数、`case`、作业控制或进程替换——语法止步于管道、`&&`/`||`、子 shell、group、重定向与展开。`&` 会就地把命令跑完,`sed` 只接受替换脚本,模式是 JavaScript 正则,命令表只有 coreutils(没有 `git`,没有网络工具)。 - **shell 进程没有同步文件面**:它靠消息读写宿主的 VFS,因为阻塞等待回帧需要 `SharedArrayBuffer`,而那要求 GitHub Pages 给不了的跨源隔离。因此目录遍历类命令每个条目一次往返,并发的两条命令写入可以交错。 diff --git a/packages/experimental/webworker-runtime/tests/node/child-process.spec.ts b/packages/experimental/webworker-runtime/tests/node/child-process.spec.ts index 59bc7d3116..e105a94f34 100644 --- a/packages/experimental/webworker-runtime/tests/node/child-process.spec.ts +++ b/packages/experimental/webworker-runtime/tests/node/child-process.spec.ts @@ -18,7 +18,7 @@ import { setActiveVfs } from '@deepseek-ai/dsh-experimental-webworker-runtime/sr import { spawn, spawnSync } from '@deepseek-ai/dsh-experimental-webworker-runtime/src/node/builtin_modules/implemented/child_process.ts' import { LAUNCHER_FAILURE_EXIT, grantArgs, launcherPath, probe, -} from '@deepseek-ai/node-addon-system' +} from '@deepseek-ai/node-addon-system/landlock-run' import { processAlive, signalProcess } from '@deepseek-ai/dsh-experimental-webworker-runtime/src/node/process-table.ts' import { hostFileSystem } from '@deepseek-ai/dsh-experimental-webworker-runtime/src/shell/fs-access.ts' import { diff --git a/packages/experimental/webworker-runtime/tests/node/node-stubs.spec.ts b/packages/experimental/webworker-runtime/tests/node/node-stubs.spec.ts index e440625a4e..dbd3de5193 100644 --- a/packages/experimental/webworker-runtime/tests/node/node-stubs.spec.ts +++ b/packages/experimental/webworker-runtime/tests/node/node-stubs.spec.ts @@ -119,6 +119,7 @@ describe('replaced external packages', () => { it('lists the packages the loader serves from the bundle', () => { expect(REPLACED_EXTERNAL_PACKAGES).not.toContain('chokidar') expect(REPLACED_EXTERNAL_PACKAGES).not.toContain('@deepseek-ai/node-addon-system') + expect(REPLACED_EXTERNAL_PACKAGES).not.toContain('@deepseek-ai/node-addon-system/landlock-run') expect(REPLACED_EXTERNAL_PACKAGES).toContain('ws') }) diff --git a/packages/sandbox/sandbox-local/README.i18n.yaml b/packages/sandbox/sandbox-local/README.i18n.yaml index 799ac2a802..ff45f7054b 100644 --- a/packages/sandbox/sandbox-local/README.i18n.yaml +++ b/packages/sandbox/sandbox-local/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/sandbox/sandbox-local/README.md -README.md: 16deeef790f28b232dc517784ca45ecb38749c6f -README.zh.md: 4d163766ff6c8097c60cfb8376a21d2a9a441100 +README.md: 33853b68407c2d3294ad1323e47b5473419b0e12 +README.zh.md: 8d62c20c65e2d7555844a1ba81e13694c5c6a8df diff --git a/packages/sandbox/sandbox-local/README.md b/packages/sandbox/sandbox-local/README.md index 16deeef790..33853b6840 100644 --- a/packages/sandbox/sandbox-local/README.md +++ b/packages/sandbox/sandbox-local/README.md @@ -74,7 +74,7 @@ Selection is by platform first, probes second: each platform has a runner chain The bwrap profile combines a read-only host root, a fresh `/dev`, and `/proc` from a private PID namespace — commands manage their descendants but cannot see host processes, so procfs magic links cannot bypass the mounts; `workspace-write` adds an ephemeral `/tmp` and a writable workspace bind. The [private-PID note](../../../.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.md) records the boundary. -The Landlock launcher ships as an npm-distributed native addon (`@deepseek-ai/node-addon-system`) that supplies the platform launcher, functional probe, and grant vocabulary; this provider maps mode to grants only, keeping path resolution and probe parsing with the versioned binary. +The `@deepseek-ai/node-addon-system/landlock-run` API supplies the platform launcher, functional probe, and grant vocabulary; this provider maps mode to grants only, keeping path resolution and probe parsing with the versioned binary. The Seatbelt profile is allow-default with `(deny file-write*)` plus write allow-lists derived from the shared `writableRoots` helper, so exactly the mode's promised file effects are governed; every root is canonicalized because Seatbelt matches resolved paths (`/tmp` IS `/private/tmp`). diff --git a/packages/sandbox/sandbox-local/README.zh.md b/packages/sandbox/sandbox-local/README.zh.md index 4d163766ff..8d62c20c65 100644 --- a/packages/sandbox/sandbox-local/README.zh.md +++ b/packages/sandbox/sandbox-local/README.zh.md @@ -74,7 +74,7 @@ kind: "package-reference" bwrap profile 组合只读宿主根目录、全新 `/dev` 与私有 PID 命名空间中的 `/proc`——命令可管理其后代,但看不到宿主进程,因此 procfs 魔法链接无法绕过挂载;`workspace-write` 另加临时的 `/tmp` 与可写工作区绑定挂载。[私有 PID 笔记](../../../.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.zh.md)记录该边界。 -Landlock launcher 以 npm 分发的原生插件(`@deepseek-ai/node-addon-system`)提供平台 launcher、功能探测与授权词汇;此提供方只做模式到授权的映射,把路径解析与探测解析保留在带版本的 binary 中。 +`@deepseek-ai/node-addon-system/landlock-run` API 提供平台 launcher、功能探测与授权词汇;此提供方只做模式到授权的映射,把路径解析与探测解析保留在带版本的 binary 中。 Seatbelt profile 默认允许,带 `(deny file-write*)` 与来自共享 `writableRoots` 辅助函数的写入 allow-list,因此恰好管辖模式承诺的文件操作;每个根目录都经过规范化,因为 Seatbelt 匹配解析后的路径(`/tmp` 就是 `/private/tmp`)。 diff --git a/packages/sandbox/sandbox-local/src/index.ts b/packages/sandbox/sandbox-local/src/index.ts index 403d600705..9e9b45a741 100644 --- a/packages/sandbox/sandbox-local/src/index.ts +++ b/packages/sandbox/sandbox-local/src/index.ts @@ -30,7 +30,7 @@ import { LAUNCHER_FAILURE_EXIT, launcherPath as landlockLauncherPath, probe as defaultProbeLandlock, -} from '@deepseek-ai/node-addon-system' +} from '@deepseek-ai/node-addon-system/landlock-run' import { Context } from '@deepseek-ai/cordis' import z from '@deepseek-ai/schemastery' import { SandboxProvider, SandboxUnavailableError } from '@deepseek-ai/dsh-sandbox' diff --git a/packages/sandbox/sandbox-local/src/profiles.ts b/packages/sandbox/sandbox-local/src/profiles.ts index 46eb1aad79..23c10d5f82 100644 --- a/packages/sandbox/sandbox-local/src/profiles.ts +++ b/packages/sandbox/sandbox-local/src/profiles.ts @@ -4,7 +4,7 @@ * @module @deepseek-ai/dsh-sandbox-local/profiles */ -import { grantArgs as landlockGrantArgs } from '@deepseek-ai/node-addon-system' +import { grantArgs as landlockGrantArgs } from '@deepseek-ai/node-addon-system/landlock-run' import { writableRoots } from '@deepseek-ai/dsh-sandbox' import type { SandboxPolicy } from '@deepseek-ai/dsh-sandbox' diff --git a/packages/sandbox/sandbox-local/tests/landlock.e2e.ts b/packages/sandbox/sandbox-local/tests/landlock.e2e.ts index e61eeb149e..9bc81d6e8e 100644 --- a/packages/sandbox/sandbox-local/tests/landlock.e2e.ts +++ b/packages/sandbox/sandbox-local/tests/landlock.e2e.ts @@ -6,7 +6,7 @@ import { join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import type { SandboxPolicy } from '@deepseek-ai/dsh-sandbox' -import { launcherPath } from '@deepseek-ai/node-addon-system' +import { launcherPath } from '@deepseek-ai/node-addon-system/landlock-run' import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local' /** diff --git a/packages/sandbox/sandbox-local/tests/local.spec.ts b/packages/sandbox/sandbox-local/tests/local.spec.ts index fe66fd3b7a..e6d7aaa258 100644 --- a/packages/sandbox/sandbox-local/tests/local.spec.ts +++ b/packages/sandbox/sandbox-local/tests/local.spec.ts @@ -12,7 +12,7 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { LAUNCHER_FAILURE_EXIT } from '@deepseek-ai/node-addon-system' +import { LAUNCHER_FAILURE_EXIT } from '@deepseek-ai/node-addon-system/landlock-run' import { SANDBOX_UNAVAILABLE, SandboxUnavailableError } from '@deepseek-ai/dsh-sandbox' import type { SandboxPolicy } from '@deepseek-ai/dsh-sandbox' import { diff --git a/packages/sandbox/sandbox-local/tests/packed-install.e2e.ts b/packages/sandbox/sandbox-local/tests/packed-install.e2e.ts index 90bb1af29d..de6c8f1c09 100644 --- a/packages/sandbox/sandbox-local/tests/packed-install.e2e.ts +++ b/packages/sandbox/sandbox-local/tests/packed-install.e2e.ts @@ -109,7 +109,7 @@ describe.skipIf(!packable)('sandbox-local: packed-tarball distribution (publish- import { spawnSync } from 'node:child_process' import { existsSync } from 'node:fs' import { Context } from '@deepseek-ai/cordis' - import { launcherPath } from '@deepseek-ai/node-addon-system' + import { launcherPath } from '@deepseek-ai/node-addon-system/landlock-run' import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local' const ctx = new Context() await ctx.plugin(LocalSandboxProvider, {}) diff --git a/packages/shell/bash-sandbox/tests/landlock.e2e.ts b/packages/shell/bash-sandbox/tests/landlock.e2e.ts index 7ee932ba94..b0caa5e91f 100644 --- a/packages/shell/bash-sandbox/tests/landlock.e2e.ts +++ b/packages/shell/bash-sandbox/tests/landlock.e2e.ts @@ -5,7 +5,7 @@ import { homedir, tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import { launcherPath } from '@deepseek-ai/node-addon-system' +import { launcherPath } from '@deepseek-ai/node-addon-system/landlock-run' import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local' import { SandboxPolicyService } from '@deepseek-ai/dsh-sandbox-policy' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' diff --git a/packages/shell/bash-sandbox/tests/partial-landlock.spec.ts b/packages/shell/bash-sandbox/tests/partial-landlock.spec.ts index 205496eaae..10aa1ecf2c 100644 --- a/packages/shell/bash-sandbox/tests/partial-landlock.spec.ts +++ b/packages/shell/bash-sandbox/tests/partial-landlock.spec.ts @@ -10,7 +10,7 @@ import { join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' -import { LAUNCHER_FAILURE_EXIT } from '@deepseek-ai/node-addon-system' +import { LAUNCHER_FAILURE_EXIT } from '@deepseek-ai/node-addon-system/landlock-run' import { SANDBOX_UNAVAILABLE, SandboxUnavailableError } from '@deepseek-ai/dsh-sandbox' import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local' import { SandboxPolicyService } from '@deepseek-ai/dsh-sandbox-policy' diff --git a/tsconfig.base.json b/tsconfig.base.json index 39bfd46d6f..48e1f0c479 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -37,7 +37,7 @@ "@deepseek-ai/cordis-plugin-timer": ["./vendor/timer/src"], "@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/landlock-run": ["./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"],