Kaynağa Gözat

refactor(sandbox): defer native protocol exports (round 3)

Hypatia May 2 ay önce
ebeveyn
işleme
f91aedd074

+ 1 - 1
docs/config-catalog.md

@@ -1037,7 +1037,7 @@ export interface Config {
 }
 }
 ```
 ```
 
 
-Source: [`packages/sandbox/sandbox-local/src/index.ts:25`](../packages/sandbox/sandbox-local/src/index.ts)
+Source: [`packages/sandbox/sandbox-local/src/index.ts:24`](../packages/sandbox/sandbox-local/src/index.ts)
 
 
 ## `@deepseek-ai/dsh-sandbox-policy`
 ## `@deepseek-ai/dsh-sandbox-policy`
 
 

+ 6 - 9
examples/acp-agent/tests/fixtures/partial-landlock-sandbox.ts

@@ -1,10 +1,7 @@
 import type { ConfinedArgv, SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
 import type { ConfinedArgv, SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
 import { SandboxProvider } from '@deepseek-ai/dsh-sandbox'
 import { SandboxProvider } from '@deepseek-ai/dsh-sandbox'
-import {
-  LAUNCHER_FAILURE_EXIT,
-  LAUNCHER_FATAL_PREFIX,
-  PARTIAL_ENFORCEMENT_NOTICE,
-} from 'node-addon-landlock-run'
+
+const NOTICE = 'landlock-run: partial enforcement (older Landlock ABI)'
 
 
 /** Snapshot-only provider that reproduces an older-ABI Landlock launch. */
 /** Snapshot-only provider that reproduces an older-ABI Landlock launch. */
 export default class PartialLandlockSandboxProvider extends SandboxProvider {
 export default class PartialLandlockSandboxProvider extends SandboxProvider {
@@ -13,16 +10,16 @@ export default class PartialLandlockSandboxProvider extends SandboxProvider {
       argv: [
       argv: [
         'bash',
         'bash',
         '-c',
         '-c',
-        `printf '%s\\n' '${PARTIAL_ENFORCEMENT_NOTICE}' >&2; exec "$@"`,
+        `printf '%s\\n' '${NOTICE}' >&2; exec "$@"`,
         'partial-landlock-run',
         'partial-landlock-run',
         ...argv,
         ...argv,
       ],
       ],
       enforcement: 'partial',
       enforcement: 'partial',
       denialSignatures: ['permission denied'],
       denialSignatures: ['permission denied'],
       runnerFailureRules: [{
       runnerFailureRules: [{
-        allowedExitCodes: [LAUNCHER_FAILURE_EXIT],
-        fatalSignatures: [LAUNCHER_FATAL_PREFIX],
-        informationalLines: [PARTIAL_ENFORCEMENT_NOTICE],
+        allowedExitCodes: [125],
+        fatalSignatures: ['landlock-run: '],
+        informationalLines: [NOTICE],
       }],
       }],
     }
     }
   }
   }

+ 1 - 2
examples/package.json

@@ -100,7 +100,6 @@
     "@deepseek-ai/dsh-web": "workspace:*",
     "@deepseek-ai/dsh-web": "workspace:*",
     "@deepseek-ai/dsh-web-fetch-local": "workspace:*",
     "@deepseek-ai/dsh-web-fetch-local": "workspace:*",
     "@deepseek-ai/dsh-workflow-workerthread": "workspace:*",
     "@deepseek-ai/dsh-workflow-workerthread": "workspace:*",
-    "@deepseek-ai/dsh-workspace-context": "workspace:*",
-    "node-addon-landlock-run": "0.0.0-test.0"
+    "@deepseek-ai/dsh-workspace-context": "workspace:*"
   }
   }
 }
 }

+ 2 - 2
native/landlock-run/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # 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:
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write native/landlock-run/README.md
 #   pnpm run verify-translation-pairing --write native/landlock-run/README.md
-README.md: 483b14df7cb91fc6486f69efc1ddc560ab635ed5
-README.zh.md: a239889ec68a8d56bb901b17feaac7f7acbf66fa
+README.md: 19cc18830b90609f648cfb2ce1ee509ad9fe381b
+README.zh.md: c93c33b843e3afc15b1890ce14edf436e051592c

+ 1 - 1
native/landlock-run/README.md

@@ -39,7 +39,7 @@ The public API is intentionally small:
 - `launcherPath()`: absolute path of this host's launcher (existence deliberately unchecked — the probe is the availability signal).
 - `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'`.
 - `probe(launcher?, { timeoutMs? })`: functional enforcement probe — `'full' | 'partial' | 'unusable'`.
 - `grantArgs({ readOnly?, readWrite? })`: the launcher's grant argv; everything not granted is denied.
 - `grantArgs({ readOnly?, readWrite? })`: the launcher's grant argv; everything not granted is denied.
-- `LAUNCHER_BIN`, `LAUNCHER_FAILURE_EXIT` (125), `LAUNCHER_FATAL_PREFIX`, and `PARTIAL_ENFORCEMENT_NOTICE`: contract constants. Every launcher failure uses 125, but a successfully exec'd child may also return 125, so failure attribution requires the fatal prefix too.
+- `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).
 The full binary contract (argv grammar, exit codes, report lines) is pinned in [docs/cli-contract.md](docs/cli-contract.md).
 
 

+ 1 - 1
native/landlock-run/README.zh.md

@@ -39,7 +39,7 @@ if (probe(launcher) !== 'unusable') {
 - `launcherPath()`:当前宿主启动器的绝对路径(有意不检查是否存在;探测结果才是可用性信号)。
 - `launcherPath()`:当前宿主启动器的绝对路径(有意不检查是否存在;探测结果才是可用性信号)。
 - `probe(launcher?, { timeoutMs? })`:功能性强制执行探测,返回 `'full' | 'partial' | 'unusable'`。
 - `probe(launcher?, { timeoutMs? })`:功能性强制执行探测,返回 `'full' | 'partial' | 'unusable'`。
 - `grantArgs({ readOnly?, readWrite? })`:启动器的授权 argv;未授予的一切都被拒绝。
 - `grantArgs({ readOnly?, readWrite? })`:启动器的授权 argv;未授予的一切都被拒绝。
-- `LAUNCHER_BIN`、`LAUNCHER_FAILURE_EXIT`(125)、`LAUNCHER_FATAL_PREFIX` 和 `PARTIAL_ENFORCEMENT_NOTICE`:契约常量。所有 launcher 失败的退出码都是 125,但成功完成 exec 的子进程也可能返回 125,因此还必须有致命前缀,才能将结果归因为 launcher 失败。
+- `LAUNCHER_BIN` 和 `LAUNCHER_FAILURE_EXIT`(125):契约常量。成功完成 exec 的子进程也可能返回 125,因此消费者必须同时看到致命诊断和该状态,才能将结果归因为 launcher 失败。
 
 
 完整的二进制契约(argv 语法、退出码、报告行)锁定在 [docs/cli-contract.md](docs/cli-contract.md) 中。
 完整的二进制契约(argv 语法、退出码、报告行)锁定在 [docs/cli-contract.md](docs/cli-contract.md) 中。
 
 

+ 4 - 4
native/landlock-run/docs/cli-contract.md

@@ -1,6 +1,6 @@
 # CLI contract: landlock-run
 # CLI contract: landlock-run
 
 
-This file pins the launcher's externally observable behavior — the cross-repo compatibility surface between the binaries and every consumer. Consumers interact with it only through the entry package (`launcherPath`/`probe`/`grantArgs` and its protocol constants); changing anything below requires a version bump for the whole package family and a note in the release notes.
+This file pins the launcher's externally observable behavior — the cross-repo compatibility surface between the binaries and every consumer. Consumers interact with it through the entry package (`launcherPath`/`probe`/`grantArgs`) and the launcher protocol; changing anything below requires a version bump for the whole package family and a note in the release notes.
 
 
 ## Invocation grammar
 ## Invocation grammar
 
 
@@ -20,14 +20,14 @@ landlock-run --probe
 ## Exit codes
 ## Exit codes
 
 
 - `125` (`LAUNCHER_FAILURE_EXIT`): every launcher-level failure — usage error, kernel that cannot enforce Landlock, unopenable grant root, failed `exec`. The wrapped command was NOT run.
 - `125` (`LAUNCHER_FAILURE_EXIT`): every launcher-level failure — usage error, kernel that cannot enforce Landlock, unopenable grant root, failed `exec`. The wrapped command was NOT run.
-- After a successful `exec`, every child status is passed through unchanged, including 125. Consumers therefore require both status 125 and a `LAUNCHER_FATAL_PREFIX` line to attribute launcher failure.
+- After a successful `exec`, every child status is passed through unchanged, including 125. Consumers therefore require both status 125 and a `landlock-run: ` fatal line to attribute launcher failure.
 - `--probe`: `0` when the kernel enforces (fully or partially), `125` otherwise.
 - `--probe`: `0` when the kernel enforces (fully or partially), `125` otherwise.
 
 
 ## Report lines
 ## Report lines
 
 
 - Probe success prints exactly one stdout line: `landlock: fully enforced` or `landlock: partially enforced (older ABI)`. The entry package's `probe()` maps these to `full`/`partial`; a non-zero probe exit maps to `unusable`.
 - Probe success prints exactly one stdout line: `landlock: fully enforced` or `landlock: partially enforced (older ABI)`. The entry package's `probe()` maps these to `full`/`partial`; a non-zero probe exit maps to `unusable`.
-- A confined run under a partial-ABI kernel prints one stderr line `landlock-run: partial enforcement (older Landlock ABI)` (`PARTIAL_ENFORCEMENT_NOTICE`) and proceeds — still confined for everything the kernel supports.
-- Every fatal error prints one stderr line prefixed `landlock-run: ` (`LAUNCHER_FATAL_PREFIX`) before exiting `125`.
+- A confined run under a partial-ABI kernel prints one stderr line `landlock-run: partial enforcement (older Landlock ABI)` and proceeds — still confined for everything the kernel supports.
+- Every fatal error prints one stderr line prefixed `landlock-run: ` before exiting `125`.
 
 
 ## Confinement semantics
 ## Confinement semantics
 
 

+ 3 - 9
native/landlock-run/packages/entry/src/index.ts

@@ -21,18 +21,12 @@ import { fileURLToPath } from 'node:url'
 /** The launcher binary's file name inside each platform package's `bin/`. */
 /** The launcher binary's file name inside each platform package's `bin/`. */
 export const LAUNCHER_BIN = 'landlock-run'
 export const LAUNCHER_BIN = 'landlock-run'
 
 
-/** Prefix on every launcher-owned fatal stderr line. */
-export const LAUNCHER_FATAL_PREFIX = 'landlock-run: '
-
-/** Informational stderr line emitted before child execution under partial enforcement. */
-export const PARTIAL_ENFORCEMENT_NOTICE = 'landlock-run: partial enforcement (older Landlock ABI)'
-
 /**
 /**
  * The exit code for every launcher-level failure (usage error, unenforcing
  * The exit code for every launcher-level failure (usage error, unenforcing
  * kernel, unopenable grant root, failed exec). After a successful `exec`, the
  * kernel, unopenable grant root, failed exec). After a successful `exec`, the
- * wrapped command may also return 125, so consumers require a matching
- * {@link LAUNCHER_FATAL_PREFIX} diagnostic to attribute launcher failure.
- * Part of the CLI contract.
+ * wrapped command may also return 125, so consumers also require a matching
+ * launcher-owned fatal diagnostic to attribute launcher failure. Part of the
+ * CLI contract.
  */
  */
 export const LAUNCHER_FAILURE_EXIT = 125
 export const LAUNCHER_FAILURE_EXIT = 125
 
 

+ 0 - 4
native/landlock-run/test/entry.test.js

@@ -12,8 +12,6 @@ import path from 'node:path';
 import {
 import {
   LAUNCHER_BIN,
   LAUNCHER_BIN,
   LAUNCHER_FAILURE_EXIT,
   LAUNCHER_FAILURE_EXIT,
-  LAUNCHER_FATAL_PREFIX,
-  PARTIAL_ENFORCEMENT_NOTICE,
   grantArgs,
   grantArgs,
   launcherPath,
   launcherPath,
   probe,
   probe,
@@ -22,8 +20,6 @@ import {
 // --- constants are part of the CLI contract ---
 // --- constants are part of the CLI contract ---
 assert.equal(LAUNCHER_BIN, 'landlock-run');
 assert.equal(LAUNCHER_BIN, 'landlock-run');
 assert.equal(LAUNCHER_FAILURE_EXIT, 125);
 assert.equal(LAUNCHER_FAILURE_EXIT, 125);
-assert.equal(LAUNCHER_FATAL_PREFIX, 'landlock-run: ');
-assert.equal(PARTIAL_ENFORCEMENT_NOTICE, 'landlock-run: partial enforcement (older Landlock ABI)');
 
 
 // --- grantArgs: flag spelling, ordering, and empty grants ---
 // --- grantArgs: flag spelling, ordering, and empty grants ---
 assert.deepEqual(grantArgs({}), []);
 assert.deepEqual(grantArgs({}), []);

+ 5 - 5
native/landlock-run/test/launcher.test.js

@@ -19,13 +19,13 @@ import path from 'node:path';
 import { spawnSync } from 'node:child_process';
 import { spawnSync } from 'node:child_process';
 import {
 import {
   LAUNCHER_FAILURE_EXIT,
   LAUNCHER_FAILURE_EXIT,
-  LAUNCHER_FATAL_PREFIX,
-  PARTIAL_ENFORCEMENT_NOTICE,
   grantArgs,
   grantArgs,
   launcherPath,
   launcherPath,
   probe,
   probe,
 } from 'node-addon-landlock-run';
 } from 'node-addon-landlock-run';
 
 
+const FATAL_PREFIX = 'landlock-run: ';
+const PARTIAL_NOTICE = 'landlock-run: partial enforcement (older Landlock ABI)';
 const requireLandlock = process.env.NALR_REQUIRE_LANDLOCK === '1';
 const requireLandlock = process.env.NALR_REQUIRE_LANDLOCK === '1';
 
 
 if (process.platform !== 'linux') {
 if (process.platform !== 'linux') {
@@ -45,7 +45,7 @@ const run = (args, options = {}) => spawnSync(launcher, args, { encoding: 'utf8'
 {
 {
   const noCommand = run([]);
   const noCommand = run([]);
   assert.equal(noCommand.status, LAUNCHER_FAILURE_EXIT);
   assert.equal(noCommand.status, LAUNCHER_FAILURE_EXIT);
-  assert.ok(noCommand.stderr.startsWith(LAUNCHER_FATAL_PREFIX));
+  assert.ok(noCommand.stderr.startsWith(FATAL_PREFIX));
   assert.match(noCommand.stderr, /usage error: missing `-- <argv>\.\.\.` command/);
   assert.match(noCommand.stderr, /usage error: missing `-- <argv>\.\.\.` command/);
 
 
   const unknownFlag = run(['--bogus', '--', 'true']);
   const unknownFlag = run(['--bogus', '--', 'true']);
@@ -78,7 +78,7 @@ if (enforcement === 'unusable') {
   console.log('launcher.test: SKIP enforcement half — kernel does not enforce Landlock');
   console.log('launcher.test: SKIP enforcement half — kernel does not enforce Landlock');
   process.exit(0);
   process.exit(0);
 }
 }
-const expectedNotice = enforcement === 'partial' ? `${PARTIAL_ENFORCEMENT_NOTICE}\n` : '';
+const expectedNotice = enforcement === 'partial' ? `${PARTIAL_NOTICE}\n` : '';
 {
 {
   const probeRun = run(['--probe']);
   const probeRun = run(['--probe']);
   assert.equal(probeRun.status, 0);
   assert.equal(probeRun.status, 0);
@@ -129,7 +129,7 @@ const expectedNotice = enforcement === 'partial' ? `${PARTIAL_ENFORCEMENT_NOTICE
   const marker = path.join(os.tmpdir(), `nalr-should-not-exist-${process.pid}`);
   const marker = path.join(os.tmpdir(), `nalr-should-not-exist-${process.pid}`);
   const badGrant = run(['--ro', '/no/such/grant/root', '--', '/bin/sh', '-c', `echo x > ${marker}`]);
   const badGrant = run(['--ro', '/no/such/grant/root', '--', '/bin/sh', '-c', `echo x > ${marker}`]);
   assert.equal(badGrant.status, LAUNCHER_FAILURE_EXIT);
   assert.equal(badGrant.status, LAUNCHER_FAILURE_EXIT);
-  assert.ok(badGrant.stderr.startsWith(LAUNCHER_FATAL_PREFIX));
+  assert.ok(badGrant.stderr.startsWith(FATAL_PREFIX));
   assert.match(badGrant.stderr, /cannot open rule path/);
   assert.match(badGrant.stderr, /cannot open rule path/);
   assert.ok(!fs.existsSync(marker), 'the command must never run when the launcher fails');
   assert.ok(!fs.existsSync(marker), 'the command must never run when the launcher fails');
 }
 }

+ 14 - 16
packages/bash/bash-sandbox/tests/partial-landlock.spec.ts

@@ -9,18 +9,16 @@ import { tmpdir } from 'node:os'
 import { join } from 'node:path'
 import { join } from 'node:path'
 import { afterEach, describe, expect, it } from 'vitest'
 import { afterEach, describe, expect, it } from 'vitest'
 import { Context } from 'cordis'
 import { Context } from 'cordis'
-import {
-  LAUNCHER_FAILURE_EXIT,
-  LAUNCHER_FATAL_PREFIX,
-  PARTIAL_ENFORCEMENT_NOTICE,
-} from 'node-addon-landlock-run'
+import { LAUNCHER_FAILURE_EXIT } from 'node-addon-landlock-run'
 import { SANDBOX_UNAVAILABLE } from '@deepseek-ai/dsh-sandbox'
 import { SANDBOX_UNAVAILABLE } from '@deepseek-ai/dsh-sandbox'
 import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local'
 import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local'
 import { SandboxPolicyService } from '@deepseek-ai/dsh-sandbox-policy'
 import { SandboxPolicyService } from '@deepseek-ai/dsh-sandbox-policy'
 import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox'
 import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox'
 import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
 import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
 
 
-const FATAL = `${LAUNCHER_FATAL_PREFIX}landlock ruleset error: Invalid argument`
+const NOTICE = 'landlock-run: partial enforcement (older Landlock ABI)'
+const FATAL_PREFIX = 'landlock-run: '
+const FATAL = `${FATAL_PREFIX}landlock ruleset error: Invalid argument`
 
 
 const contexts: Context[] = []
 const contexts: Context[] = []
 const tempDirs: string[] = []
 const tempDirs: string[] = []
@@ -41,10 +39,10 @@ while [ "$#" -gt 0 ]; do
   case "$1" in
   case "$1" in
     --ro|--rw) shift 2 ;;
     --ro|--rw) shift 2 ;;
     --) shift; break ;;
     --) shift; break ;;
-    *) printf '%s\\n' '${LAUNCHER_FATAL_PREFIX}usage error: unexpected fake argument' >&2; exit ${LAUNCHER_FAILURE_EXIT} ;;
+    *) printf '%s\\n' '${FATAL_PREFIX}usage error: unexpected fake argument' >&2; exit ${LAUNCHER_FAILURE_EXIT} ;;
   esac
   esac
 done
 done
-printf '%s\\n' '${PARTIAL_ENFORCEMENT_NOTICE}' >&2
+printf '%s\\n' '${NOTICE}' >&2
 ${fatalBranch}exec "$@"
 ${fatalBranch}exec "$@"
 `, { mode: 0o755 })
 `, { mode: 0o755 })
   return launcher
   return launcher
@@ -113,7 +111,7 @@ describe('partial Landlock runner-failure classification', () => {
       const bash = await setup()
       const bash = await setup()
       const result = await bash.run(bash.resolve({ command: `exit ${exitCode}` }))
       const result = await bash.run(bash.resolve({ command: `exit ${exitCode}` }))
       expect(result.exitCode).toBe(exitCode)
       expect(result.exitCode).toBe(exitCode)
-      expect(result.stderr.text).toBe(`${PARTIAL_ENFORCEMENT_NOTICE}\n`)
+      expect(result.stderr.text).toBe(`${NOTICE}\n`)
       expect(result.sandbox).toEqual({ mode: 'read-only', denied: false, enforcement: 'partial' })
       expect(result.sandbox).toEqual({ mode: 'read-only', denied: false, enforcement: 'partial' })
     },
     },
   )
   )
@@ -122,7 +120,7 @@ describe('partial Landlock runner-failure classification', () => {
     const bash = await setup()
     const bash = await setup()
     const result = await bash.run(bash.resolve({ command: `exit ${exitCode}` }))
     const result = await bash.run(bash.resolve({ command: `exit ${exitCode}` }))
     expect(result.exitCode).toBe(exitCode)
     expect(result.exitCode).toBe(exitCode)
-    expect(result.stderr.text).toBe(`${PARTIAL_ENFORCEMENT_NOTICE}\n`)
+    expect(result.stderr.text).toBe(`${NOTICE}\n`)
     expect(result.sandbox).toEqual({ mode: 'read-only', denied: false, enforcement: 'partial' })
     expect(result.sandbox).toEqual({ mode: 'read-only', denied: false, enforcement: 'partial' })
   })
   })
 
 
@@ -130,7 +128,7 @@ describe('partial Landlock runner-failure classification', () => {
     const bash = await setup(exitCode)
     const bash = await setup(exitCode)
     const result = await bash.run(bash.resolve({ command: 'true' }))
     const result = await bash.run(bash.resolve({ command: 'true' }))
     expect(result.exitCode).toBe(exitCode)
     expect(result.exitCode).toBe(exitCode)
-    expect(result.stderr.text).toBe(`${PARTIAL_ENFORCEMENT_NOTICE}\n${FATAL}\n`)
+    expect(result.stderr.text).toBe(`${NOTICE}\n${FATAL}\n`)
     expect(result.sandbox).toEqual({ mode: 'read-only', denied: false, enforcement: 'partial' })
     expect(result.sandbox).toEqual({ mode: 'read-only', denied: false, enforcement: 'partial' })
   })
   })
 
 
@@ -140,13 +138,13 @@ describe('partial Landlock runner-failure classification', () => {
     expect(error).toMatchObject({ name: 'SandboxUnavailableError', code: SANDBOX_UNAVAILABLE })
     expect(error).toMatchObject({ name: 'SandboxUnavailableError', code: SANDBOX_UNAVAILABLE })
     expect(error).toBeInstanceOf(Error)
     expect(error).toBeInstanceOf(Error)
     expect((error as Error).message).toContain(`Runner failure: ${FATAL}`)
     expect((error as Error).message).toContain(`Runner failure: ${FATAL}`)
-    expect((error as Error).message).not.toContain(PARTIAL_ENFORCEMENT_NOTICE)
+    expect((error as Error).message).not.toContain(NOTICE)
   })
   })
 
 
   it('classifies a notice plus child Permission denied as a denial, not runner failure', async () => {
   it('classifies a notice plus child Permission denied as a denial, not runner failure', async () => {
     const bash = await setup()
     const bash = await setup()
     const result = await bash.run(bash.resolve({ command: 'printf "%s\\n" "child: Permission denied" >&2; exit 1' }))
     const result = await bash.run(bash.resolve({ command: 'printf "%s\\n" "child: Permission denied" >&2; exit 1' }))
-    expect(result.stderr.text).toBe(`${PARTIAL_ENFORCEMENT_NOTICE}\nchild: Permission denied\n`)
+    expect(result.stderr.text).toBe(`${NOTICE}\nchild: Permission denied\n`)
     expect(result.sandbox).toEqual({ mode: 'read-only', denied: true, enforcement: 'partial' })
     expect(result.sandbox).toEqual({ mode: 'read-only', denied: true, enforcement: 'partial' })
   })
   })
 
 
@@ -156,7 +154,7 @@ describe('partial Landlock runner-failure classification', () => {
       const task = bash.start(bash.resolve({ command }))
       const task = bash.start(bash.resolve({ command }))
       await task.done
       await task.done
       expect(task.sandbox).toEqual({ mode: 'read-only', denied: false, enforcement: 'partial' })
       expect(task.sandbox).toEqual({ mode: 'read-only', denied: false, enforcement: 'partial' })
-      expect(task.readOutput().delta).toContain(PARTIAL_ENFORCEMENT_NOTICE)
+      expect(task.readOutput().delta).toContain(NOTICE)
     }
     }
   })
   })
 
 
@@ -165,7 +163,7 @@ describe('partial Landlock runner-failure classification', () => {
     const task = bash.start(bash.resolve({ command: 'printf "%s\\n" "child: Permission denied" >&2; exit 1' }))
     const task = bash.start(bash.resolve({ command: 'printf "%s\\n" "child: Permission denied" >&2; exit 1' }))
     await task.done
     await task.done
     expect(task.sandbox).toEqual({ mode: 'read-only', denied: true, enforcement: 'partial' })
     expect(task.sandbox).toEqual({ mode: 'read-only', denied: true, enforcement: 'partial' })
-    expect(task.readOutput().delta).toContain(PARTIAL_ENFORCEMENT_NOTICE)
+    expect(task.readOutput().delta).toContain(NOTICE)
   })
   })
 
 
   it('makes a background fatal line outrank denial text after the notice', async () => {
   it('makes a background fatal line outrank denial text after the notice', async () => {
@@ -179,7 +177,7 @@ describe('partial Landlock runner-failure classification', () => {
       runnerFailed: true,
       runnerFailed: true,
     })
     })
     const output = task.readOutput().delta
     const output = task.readOutput().delta
-    expect(output).toContain(PARTIAL_ENFORCEMENT_NOTICE)
+    expect(output).toContain(NOTICE)
     expect(output).toContain(FATAL)
     expect(output).toContain(FATAL)
   })
   })
 })
 })

+ 3 - 4
packages/sandbox/sandbox-local/src/index.ts

@@ -8,9 +8,8 @@
 
 
 import { spawnSync } from 'node:child_process'
 import { spawnSync } from 'node:child_process'
 import {
 import {
-  LAUNCHER_FATAL_PREFIX,
+  LAUNCHER_BIN,
   LAUNCHER_FAILURE_EXIT,
   LAUNCHER_FAILURE_EXIT,
-  PARTIAL_ENFORCEMENT_NOTICE,
   launcherPath as landlockLauncherPath,
   launcherPath as landlockLauncherPath,
   probe as defaultProbeLandlock,
   probe as defaultProbeLandlock,
 } from 'node-addon-landlock-run'
 } from 'node-addon-landlock-run'
@@ -157,8 +156,8 @@ const RUNNER_FAILURE_RULES = {
   bwrap: [{ fatalSignatures: ['bwrap: '] }],
   bwrap: [{ fatalSignatures: ['bwrap: '] }],
   landlock: [{
   landlock: [{
     allowedExitCodes: [LAUNCHER_FAILURE_EXIT],
     allowedExitCodes: [LAUNCHER_FAILURE_EXIT],
-    fatalSignatures: [LAUNCHER_FATAL_PREFIX],
-    informationalLines: [PARTIAL_ENFORCEMENT_NOTICE],
+    fatalSignatures: [`${LAUNCHER_BIN}: `],
+    informationalLines: [`${LAUNCHER_BIN}: partial enforcement (older Landlock ABI)`],
   }],
   }],
   seatbelt: [{ fatalSignatures: ['sandbox-exec: '] }],
   seatbelt: [{ fatalSignatures: ['sandbox-exec: '] }],
 } as const satisfies Record<SelectedRunner['runner'], readonly RunnerFailureRule[]>
 } as const satisfies Record<SelectedRunner['runner'], readonly RunnerFailureRule[]>

+ 3 - 7
packages/sandbox/sandbox-local/tests/local.spec.ts

@@ -12,11 +12,7 @@ import { tmpdir } from 'node:os'
 import { join } from 'node:path'
 import { join } from 'node:path'
 import { describe, expect, it, vi } from 'vitest'
 import { describe, expect, it, vi } from 'vitest'
 import { Context } from 'cordis'
 import { Context } from 'cordis'
-import {
-  LAUNCHER_FAILURE_EXIT,
-  LAUNCHER_FATAL_PREFIX,
-  PARTIAL_ENFORCEMENT_NOTICE,
-} from 'node-addon-landlock-run'
+import { LAUNCHER_FAILURE_EXIT } from 'node-addon-landlock-run'
 import { SANDBOX_UNAVAILABLE, SandboxUnavailableError } from '@deepseek-ai/dsh-sandbox'
 import { SANDBOX_UNAVAILABLE, SandboxUnavailableError } from '@deepseek-ai/dsh-sandbox'
 import type { SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
 import type { SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
 import {
 import {
@@ -179,8 +175,8 @@ describe('the platform chains', () => {
       denialSignatures: ['permission denied'],
       denialSignatures: ['permission denied'],
       runnerFailureRules: [{
       runnerFailureRules: [{
         allowedExitCodes: [LAUNCHER_FAILURE_EXIT],
         allowedExitCodes: [LAUNCHER_FAILURE_EXIT],
-        fatalSignatures: [LAUNCHER_FATAL_PREFIX],
-        informationalLines: [PARTIAL_ENFORCEMENT_NOTICE],
+        fatalSignatures: ['landlock-run: '],
+        informationalLines: ['landlock-run: partial enforcement (older Landlock ABI)'],
       }],
       }],
     })
     })
     expect(probeLandlock).toHaveBeenCalledWith(launcher)
     expect(probeLandlock).toHaveBeenCalledWith(launcher)

+ 0 - 4
pnpm-lock.yaml

@@ -848,10 +848,6 @@ importers:
       '@deepseek-ai/dsh-workspace-context':
       '@deepseek-ai/dsh-workspace-context':
         specifier: workspace:*
         specifier: workspace:*
         version: link:../packages/context/workspace-context
         version: link:../packages/context/workspace-context
-      node-addon-landlock-run:
-        specifier: 0.0.0-test.0
-        version: 0.0.0-test.0
-
   packages/acp/acp:
   packages/acp/acp:
     dependencies:
     dependencies:
       '@agentclientprotocol/sdk':
       '@agentclientprotocol/sdk':