From 598f9719f4e27b6d5e37478fb20a85214956fd0d Mon Sep 17 00:00:00 2001 From: Hypatia May Date: Thu, 6 Aug 2026 10:23:26 +0800 Subject: [PATCH 1/6] refactor(landlock-run): unify workspace release (review round 1) --- .../feature/2026-07-06-sandbox.i18n.yaml | 4 +- .../implemented/feature/2026-07-06-sandbox.md | 12 +- .../feature/2026-07-06-sandbox.zh.md | 12 +- ...6-in-repository-landlock-release.i18n.yaml | 6 + ...26-08-06-in-repository-landlock-release.md | 42 +++ ...08-06-in-repository-landlock-release.zh.md | 42 +++ .github/workflows/landlock-run-release.yml | 170 +++++++++ .github/workflows/landlock-run.yml | 42 ++- .github/workflows/sandbox.yml | 27 +- THIRD_PARTY_NOTICES.md | 4 +- knip.json | 1 + native/README.i18n.yaml | 4 +- native/README.md | 10 +- native/README.zh.md | 10 +- native/landlock-run/AGENTS.md | 2 +- native/landlock-run/docs/release.md | 36 +- native/landlock-run/pnpm-lock.yaml | 345 ------------------ native/landlock-run/pnpm-workspace.yaml | 8 - native/landlock-run/scripts/bump-release.mjs | 11 +- .../landlock-run/scripts/commit-release.mjs | 10 +- native/landlock-run/scripts/repo.mjs | 4 +- .../landlock-run/scripts/verify-release.mjs | 16 +- package.json | 2 + packages/bash/bash-sandbox/package.json | 2 +- packages/bash/bash-sandbox/tsconfig.json | 3 + .../examples/agent-spine-demo/package.json | 2 +- .../examples/agent-spine-demo/tsconfig.json | 3 + packages/sandbox/sandbox-local/package.json | 2 +- .../sandbox-local/tests/landlock.e2e.ts | 2 +- .../sandbox-local/tests/packed-install.e2e.ts | 35 +- packages/sandbox/sandbox-local/tsconfig.json | 3 + pnpm-lock.yaml | 103 +++--- pnpm-workspace.yaml | 11 +- scripts/check-workspace-constraints.ts | 32 +- scripts/clean.spec.ts | 16 +- scripts/clean.ts | 9 +- scripts/gen-third-party-notices.spec.ts | 4 +- scripts/gen-third-party-notices.ts | 16 +- tsconfig.base.json | 1 + tsconfig.host.json | 1 + 40 files changed, 535 insertions(+), 530 deletions(-) create mode 100644 .agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.i18n.yaml create mode 100644 .agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.md create mode 100644 .agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.zh.md create mode 100644 .github/workflows/landlock-run-release.yml delete mode 100644 native/landlock-run/pnpm-lock.yaml delete mode 100644 native/landlock-run/pnpm-workspace.yaml diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml b/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml index 7f15a55333..5f8dfa4e65 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.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-07-06-sandbox.md -2026-07-06-sandbox.md: aed5ac1ceb02130ce97a8c83c0f77869fdc32146 -2026-07-06-sandbox.zh.md: db95b1a5b7a7cae1e0fcdd8deba9dcb6ad020a67 +2026-07-06-sandbox.md: 69a3f1bd181bc06d9a176fa45b1e091991cfa682 +2026-07-06-sandbox.zh.md: eeca55b61da24df215f7a9b7ba8dbf9ab2387f20 diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.md b/.agents/notes/implemented/feature/2026-07-06-sandbox.md index aed5ac1ceb..69a3f1bd18 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.md +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.md @@ -64,7 +64,7 @@ Left open, for the phase that needs them: whether network restriction arrives as The launcher is a ~300-line C program (plain C11 over the raw Landlock UAPI — no libraries beyond a statically linked musl, so the audit surface is that one file plus the kernel's stable syscall contract): `--ro ` / `--rw ` grants, `--`, the wrapped argv; it installs the ruleset on itself and `exec`s (rulesets are inherited across `execve`, and it sets `no_new_privs` before restricting); `--probe` enforces a maximal ruleset in a short-lived child and exits 0 only when the kernel actually enforces; every launcher failure exits 125 without running the child and prints a fatal `landlock-run:` line. A successfully exec'd child may also return 125, so status alone is not launcher evidence. An older ABI prints the exact `landlock-run: partial enforcement (older Landlock ABI)` notice before it executes the child, so that line is not fatal evidence. -The Landlock launcher source and package workspace live at `native/landlock-run`, next to the harness consumers. The standalone [`node-addon-landlock-run`](https://github.com/deepseek-harness/node-addon-landlock-run) repository is the release mirror used to pack and publish the npm package family; `native/README.md` owns the export procedure. Platform binaries are selected by npm, and the entry package owns path resolution, probing, CLI flags, the fatal prefix, and the partial-enforcement notice while the harness maps sandbox modes to grants. Versioning the entry point with its binaries keeps probe parsing and launch syntax aligned. +The Landlock launcher source and package family live at `native/landlock-run`, next to the harness consumers and inside the root pnpm workspace. The [in-repository Landlock release decision](../process/2026-08-06-in-repository-landlock-release.md) owns the shared lockfile, native build, pack rehearsal, and npm publication boundary. Platform binaries are selected by npm, and the entry package owns path resolution, probing, CLI flags, the fatal prefix, and the partial-enforcement notice while the harness maps sandbox modes to grants. Versioning the entry point with its binaries keeps probe parsing and launch syntax aligned. Backend profiles share the mode contract but differ in necessary host grants. Landlock and Seatbelt allow only `/dev/null` in read-only mode; workspace-write also permits their required host temp roots. Each wrap carries backend-specific denial signatures. Landlock reports partial enforcement on older ABIs that cannot govern every operation, while successful bwrap and Seatbelt profiles report full enforcement. @@ -118,7 +118,7 @@ fs/web/todo execute in-process, so their sandbox semantics are policy at their s ### Testing - **Unit:** pin platform selection and profiles, direct provider-argv handoff, spawn-level failures with invalid-workdir controls, missing/non-executable/missing-interpreter evidence, malformed-runner negative controls, confined `BASH_ENV` ordering, structured runner classification (including partial-Landlock notice-only child outcomes, gated fatal evidence, child exits 126/127, and foreground/background parity), per-call mode/root resolution, per-process facts, escalation validation and outcomes, permission preset folding and write-through, and runtime-context ordering and materialization. -- **Keyless real-runner:** exercise bwrap, Landlock, and Seatbelt against real filesystem effects at provider and bash-consumer layers; one real Cordis context concurrently drives two project sessions through shipped bash and fs tools, proving own-root success and sibling-root denial. Packed-install coverage proves the registry launcher remains executable. CI rejects a silent all-skip. +- **Keyless real-runner:** exercise bwrap, Landlock, and Seatbelt against real filesystem effects at provider and bash-consumer layers; one real Cordis context concurrently drives two project sessions through shipped bash and fs tools, proving own-root success and sibling-root denial. Packed-install coverage installs the current checkout's native tarballs and proves the launcher remains executable and byte-identical. CI rejects a silent all-skip. - **With-key:** start the real ACP composition in read-only mode, let a model-driven bash write hit the runner's denial marker, then drive the bridge answerer and disk effect through granted and rejected workspace-write retries; unavailable credentials or runners self-skip. - **Snapshot:** pin the atomic current-policy context and both scripted approval branches. A real ACP example scenario places its session under the user home while the deployment fallback points at `/tmp`, then pins both the workspace-write runtime-context message and a successful deployment-selected mutation; this distinguishes session-root resolution from the process fallback without depending on runner-specific denial text. A POSIX fake partial-Landlock provider pins direct bash `false` as an ordinary child result and a missing provider executable as foreground/background infrastructure failure through the assembled app. Other snapshots start unconfined so unrelated fixtures remain platform-independent. @@ -134,9 +134,9 @@ Each phase gets its full design when picked up, validated against the code at th - **Command-string heuristic preflight** — rejected: cannot understand expansion/subprocesses/symlinks; the strict attempt (run it, let the kernel decide) is the only trustworthy denial signal. - **Functionally probe even a platform's sole backend** — rejected: probing arbitrates between candidates; with one there is nothing to decide, and probe cost taxes the first confined command of every session (prohibitive for heavy future backends). The runner's own exec-time fail-closed refusal plus structured `runnerFailureRules` classification carries the safety property instead. -- **Commit the built launcher binaries** — rejected: a binary in a diff is unreviewable and churns history; reviewed source + native CI builds + the launcher repo's byte-pinned publish rehearsal keep bytes out of every tree. +- **Commit the built launcher binaries** — rejected: a binary in a diff is unreviewable and churns history; reviewed source + native CI builds + the main repository's byte-pinned publish rehearsal keep bytes out of every tree. - **Compile the launcher on install** — rejected: pushes a C toolchain onto every consumer; a fallback that exists only where a compiler happens to be is not a fallback. -- **Cross-compile both architectures from one builder** — rejected: requires carrying a pinned cross toolchain (rustup targets, zig, or a container image) solely to rebuild two ~70 KB binaries; per-architecture native runners already exist and each builds its own platform package (the `node-addon-require-builtin` model, the launcher repo's own pipeline). +- **Cross-compile both architectures from one builder** — rejected: requires carrying a pinned cross toolchain (rustup targets, zig, or a container image) solely to rebuild two ~70 KB binaries; per-architecture native runners already exist and each builds its own platform package (the `node-addon-require-builtin` model, retained by the main repository's native pipeline). - **No fallback (bwrap or fail closed)** — rejected: concentrates failure on the hosts a sandbox matters most, degrading to `danger-full-access` by resignation. - **Keep the mechanism inside `dsh-bash-sandbox`** — rejected: blocks the existing second consumer, makes future phases read mode out of a bash plugin's config, and cannot express escalation. - **Config-fixed mode on the provider** — rejected: one mode per process; cannot serve concurrent consumers with different policies nor the one-shot widened retry. @@ -175,7 +175,7 @@ Costs and accepted limits: - **The Seatbelt rung leans on Apple's deprecated-but-shipped `sandbox-exec` CLI.** As darwin's sole candidate it is selected without probing, so a future removal under a usable workdir surfaces as a runner-attributable spawn failure and an executable refusal through its fatal signature — both become `SANDBOX_UNAVAILABLE`, and the command never runs; fail closed, never open. - **Landlock confinement is only as complete as the running kernel's ABI.** Reported as `enforcement: 'partial'` rather than refused — the deliberate trade that keeps the fallback available on older-kernel hosts. - **Runner attribution uses an in-band protocol.** Exit status plus stderr cannot cryptographically identify the writer, so a confined child can mimic a fatal runner line and status to cause an availability/diagnostic false attribution. The conjunction and exact notice exclusion reduce accidental matches; this is not a sandbox bypass because the child is already confined. -- **The launcher arrives as a registry dependency.** Trusted through its own repository's release pipeline (reviewed C source, native CI builders, byte-pinned publish rehearsal) plus this repo's version pin — the real-kernel e2e legs are what vouch for behavior through the installed bytes. +- **The launcher is a workspace dependency in source and an npm dependency after publication.** The main repository tests reviewed C source, native CI builds, and byte-pinned local tarballs together before publishing the same package family; the real-kernel e2e legs vouch for behavior through those installed bytes. - **The model may over-ask.** Escalating without denial grounding, or picking `danger-full-access` where `workspace-write` suffices: the description steers and the enum forces the ladder, but the human prompt is the actual gate; the `approval/asked` reasons make over-asking auditable, and a `prepend` policy answerer can auto-reject patterns a deployment never wants. - **The advertised target set is static while the effective mode is per-session** (schemas are registry-global) — a session already at the widest mode is still offered the fields. Harmless by construction: the strict-wider check at execution, not the enum, is the safety boundary — a non-widening request fails with its own text and never prompts anyone. - **A granted escalation is not a working sandbox.** An unavailable backend still fails closed even for a granted escalation to a confining mode — at `confine()` when the platform has no chain or every probe fails, through the spawn channel when the selected executable cannot start, or through a structured rule when a started runner refuses — while a granted `danger-full-access` run never touches the provider at all: there the grant, not the probe, is the authority. @@ -187,7 +187,7 @@ Costs and accepted limits: - **A command came back with `[sandbox: file access denied under read-only mode]` — did it fail?** It RAN, and the kernel refused a file effect: the denial is a result fact orthogonal to exit code. The teaching forbids retrying around it; the one sanctioned move is the same command retried once with an escalation request. - **How is a BROKEN sandbox told apart from a failing command?** Any provider-argv spawn rejection proves the confined launch never started, but it identifies a broken runner only when the caller-owned workdir is usable and Node reports attributable `ENOENT` or `EACCES` for that argv[0]. A bare `syscall: 'spawn'` without an exact error path and all other rejections remain ordinary command-start errors. After a process starts, runner failure outranks denial only when one `runnerFailureRules` entry matches both its optional exit-code gate and a fatal stderr line after exact informational exclusions. Foreground failures throw structured `SANDBOX_UNAVAILABLE` with spawn or matched-line detail; an asynchronously rejected or settled background task stamps `sandbox.runnerFailed` and renders its own marker. A `SubprocessService` that synchronously throws the same provenanced `ENOENT`/`EACCES` shape makes background start throw the structured error; other synchronous errors propagate unchanged. A Landlock partial-enforcement notice plus an ordinary child failure remains a command result. - **What happens on a platform with no backend — Windows today?** `confine()` throws the fail-closed `SANDBOX_UNAVAILABLE` and the command never spawns; `win32` is a reserved EMPTY chain, pinned by test to fail closed identically until a Windows runner fills it (§ Deferred phases). -- **`bwrap` is installed on my host but unusable (disabled unprivileged userns, an LSM denying `mount`) — what happens?** The chain probe is functional — it builds and enforces a real profile rather than checking `--version` — so a present-but-unusable `bwrap` fails its probe, selection falls to the registry-installed Landlock launcher, and the verdict is cached for the provider's lifetime. +- **`bwrap` is installed on my host but unusable (disabled unprivileged userns, an LSM denying `mount`) — what happens?** The chain probe is functional — it builds and enforces a real profile rather than checking `--version` — so a present-but-unusable `bwrap` fails its probe, selection falls to the packaged Landlock launcher, and the verdict is cached for the provider's lifetime. - **Does the sandbox restrict network or process visibility?** No — `SandboxMode` claims FILE effects only; the bwrap profile deliberately does not unshare pid, and no backend claims network. Whether network restriction becomes its own knob is left open in § The seam. - **Which tools actually run confined?** OS subprocesses through `ctx.bash` — the bash tools, and hook commands transitively — plus the filesystem tools (`read`/`write`/`edit`) through the sandboxed `ctx.fs` provider (the [cross-family fs sandbox RFC](2026-07-14-cross-family-fs-sandbox.md)): bash confines via the OS runner, fs via an in-process path fence, both keying off the same `ctx.sandboxPolicy` mode. web/todo stay in-process and unfenced (web's only effect is network, outside the file-effect mode vocabulary). - **Does a granted escalation persist?** No. The grant is consumed by the exact foreground or background call that asked; every neighboring call keeps its own effective mode. A later background denial surfaces through `task_output` and may ground a new exact-command retry. diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md b/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md index db95b1a5b7..eeca55b61d 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md @@ -64,7 +64,7 @@ OS 子进程约束适用于 bash 执行器(包括钩子命令),后续还 launcher 是一个约 300 行的 C 程序(纯 C11,直接使用 Landlock UAPI——除静态链接的 musl 外无其他库,因此审计面仅为该文件加内核的稳定 syscall 契约):`--ro ` / `--rw ` 授权,`--`,被包装的 argv;它为自身安装规则集并执行 `exec`(规则集跨 `execve` 继承,且它在限制前设置 `no_new_privs`);`--probe` 在一个短生命周期子进程中强制最大规则集,仅当内核确实强制时才以 0 退出;所有 launcher 失败都会以 125 退出且不运行子进程,并打印一行致命的 `landlock-run:` 诊断。成功完成 exec 的子进程也可能返回 125,因此仅凭退出状态不能作为 launcher 失败的证据。较旧的 ABI 会在执行子进程之前打印精确的 `landlock-run: partial enforcement (older Landlock ABI)` 通知,因此该行不是致命证据。 -Landlock launcher 源码和包工作区位于 `native/landlock-run`,与 harness 消费方同仓。独立的 [`node-addon-landlock-run`](https://github.com/deepseek-harness/node-addon-landlock-run) 仓库是用于打包并发布 npm 包族的发布镜像;导出流程归 `native/README.md` 所有。平台二进制由 npm 选择,入口包拥有路径解析、探测、CLI 参数、致命前缀和部分强制执行通知,而 harness 将沙箱模式映射为授权。将入口点与其二进制一起版本化,使探测解析和启动语法保持对齐。 +Landlock launcher 源码和包家族位于 `native/landlock-run`,与 harness 消费方同仓,并属于根 pnpm workspace。[仓库内 Landlock 发布决策](../process/2026-08-06-in-repository-landlock-release.md)负责共享锁文件、原生构建、打包演练和 npm 发布边界。平台二进制由 npm 选择,入口包拥有路径解析、探测、CLI 参数、致命前缀和部分强制执行通知,而 harness 将沙箱模式映射为授权。将入口点与其二进制一起版本化,使探测解析和启动语法保持对齐。 后端 profile 共享模式契约但在必要的主机授权上有所不同。Landlock 和 Seatbelt 在 read-only 模式下仅允许 `/dev/null`;workspace-write 还允许各自所需的主机临时目录根。每次包装携带后端特定的拒绝签名。Landlock 在较旧的 ABI 无法管控所有操作时报告 partial enforcement,而成功的 bwrap 和 Seatbelt profile 报告 full enforcement。 @@ -118,7 +118,7 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自 seam 层 ### 测试 - **单元测试:** 固定平台选择和 profile、直接交接提供方返回的 argv、带有无效 workdir 对照的 spawn 层失败、runner 缺失/不可执行/解释器缺失证据、格式错误 runner 阴性对照、受约束的 `BASH_ENV` 求值顺序、结构化 runner 分类(包括只有部分强制执行通知的子进程结果、带门控的致命证据、子进程退出码 126/127,以及前台/后台一致性)、按调用的模式/根目录解析、按进程事实、升级验证和结果、权限 preset fold 和写入透传,以及运行时上下文排序与具体化。 -- **Keyless 真实 runner:** 在提供方和 bash 消费方层面对 bwrap、Landlock 和 Seatbelt 执行真实文件系统效果测试;一个真实 Cordis 上下文通过已交付的 bash 和 fs 工具并发驱动两个项目会话,证明在自身根目录写入成功、在兄弟根目录写入被拒绝。打包安装测试证明注册表 launcher 保持可执行。CI 拒绝静默全跳过。 +- **Keyless 真实 runner:** 在提供方和 bash 消费方层面对 bwrap、Landlock 和 Seatbelt 执行真实文件系统效果测试;一个真实 Cordis 上下文通过已交付的 bash 和 fs 工具并发驱动两个项目会话,证明在自身根目录写入成功、在兄弟根目录写入被拒绝。打包安装测试会安装当前 checkout 的原生 tarball,并证明 launcher 保持可执行且字节完全一致。CI 拒绝静默全跳过。 - **With-key:** 以只读模式启动真实 ACP 组合,让模型驱动的 bash 写入命中 runner 的拒绝标记,再通过已授权与被拒绝的 workspace-write 重试驱动 bridge 应答器和磁盘效果;不可用的凭证或 runner 自动跳过。 - **快照:** 固定原子化的当前策略上下文和两个脚本化的 approval 分支。一个真实 ACP 示例场景把会话放在用户主目录下,同时让部署后备根目录指向 `/tmp`,然后固定 workspace-write 运行时上下文消息与一次成功的、由部署选定的变更;这能区分会话根目录解析与进程后备值,而不依赖 runner 特定的拒绝文本。一个模拟 Landlock 部分强制执行行为的 POSIX 提供方会在组装后的应用中固定直接执行 bash `false` 时仍得到普通子进程结果,并固定提供方可执行文件缺失时在前台/后台均为基础设施失败。其他快照以无约束启动,使无关 fixture(测试前置数据)保持平台无关。 @@ -134,9 +134,9 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自 seam 层 - **命令字符串启发式预检**:否决。无法理解展开/子进程/符号链接;严格尝试(运行它,让内核决定)是唯一可信的拒绝信号。 - **即使平台仅有一个后端也功能性探测**:否决。探测用于在候选者之间仲裁;只有一个时无需决策,且探测开销对每个会话的首次约束命令征税(对未来重量级后端而言代价过高)。runner 自身执行时的失败关闭拒绝加结构化 `runnerFailureRules` 分类承载了安全属性。 -- **提交构建好的 launcher 二进制**:否决。diff 中的二进制不可审查且膨胀历史;经审查的源码 + 原生 CI 构建 + launcher 仓库的字节固定发布演练使二进制远离所有代码树。 +- **提交构建好的 launcher 二进制**:否决。diff 中的二进制不可审查且膨胀历史;经审查的源码 + 原生 CI 构建 + 主仓库的字节固定发布演练使二进制远离所有代码树。 - **安装时编译 launcher**:否决。将 C 工具链强加给每个消费方;仅在碰巧有编译器时才存在的备选不是备选。 -- **从一个构建器交叉编译两种架构**:否决。仅为重建两个约 70 KB 的二进制就需要携带一个固定的交叉工具链(rustup targets、zig 或容器镜像);每架构的原生 runner 已存在,各自构建自己的平台包(`node-addon-require-builtin` 模式,launcher 仓库自己的流水线)。 +- **从一个构建器交叉编译两种架构**:否决。仅为重建两个约 70 KB 的二进制就需要携带一个固定的交叉工具链(rustup targets、zig 或容器镜像);每架构的原生 runner 已存在,各自构建自己的平台包(`node-addon-require-builtin` 模式,由主仓库的原生流水线保留)。 - **无备选(bwrap 或失败关闭)**:否决。将失败集中在沙箱最重要的主机上,最终因放弃而降级到 `danger-full-access`。 - **将机制保留在 `dsh-bash-sandbox` 内部**:否决。阻塞既有的第二个消费方,使未来阶段从一个 bash 插件的配置中读取模式,且无法表达升级。 - **提供方上的配置固定模式**:否决。每进程一个模式;无法服务具有不同策略的并发消费方,也无法表达一次性放宽重试。 @@ -175,7 +175,7 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自 seam 层 - **Seatbelt 层级依赖 Apple 已弃用但仍交付的 `sandbox-exec` CLI。** 作为 darwin 的唯一候选,它无需探测即被选中,因此在 workdir 可用时,未来移除会表现为可归因于 runner 的 spawn 失败,可执行文件拒绝则通过其致命签名体现——两者都会变为 `SANDBOX_UNAVAILABLE`,且命令绝不会运行;失败关闭,绝不开放。 - **Landlock 约束的完整度取决于运行内核的 ABI。** 报告为 `enforcement: 'partial'` 而非拒绝——这是有意的权衡,使备选在旧内核主机上仍可用。 - **Runner 归因使用带内协议。** 退出状态与 stderr 无法以密码学方式识别写入者,因此受限子进程可以模仿 runner 的致命诊断行和状态,造成可用性或诊断误归因。多项证据的合取与精确通知排除减少了意外匹配;这不是沙箱绕过,因为子进程已经受到限制。 -- **launcher 作为注册表依赖到达。** 通过其自身仓库的发布流水线(经审查的 C 源码、原生 CI 构建器、字节固定的发布演练)加上本仓库的版本固定获得信任——真实内核 e2e 测试环节会验证安装产物的实际行为。 +- **launcher 在源码中是 workspace 依赖,发布后是 npm 依赖。** 主仓库会在发布同一个包家族之前,一起测试经审查的 C 源码、原生 CI 构建和字节固定的本地 tarball;真实内核 e2e 测试环节会验证这些安装字节的实际行为。 - **模型可能过度请求。** 在没有拒绝依据的情况下升级,或在 `workspace-write` 足够时选择 `danger-full-access`:描述引导且枚举强制阶梯,但人的提示词是实际门控;`approval/asked` 原因使过度请求可审计,且 `prepend` 策略应答器可以自动拒绝部署永远不想要的模式。 - **公布的目标集是静态的,而有效模式是按会话的**(schema 是注册表全局的)——已处于最宽模式的会话仍被提供这些字段。构造上无害:执行时的严格放宽检查(而非枚举)是安全边界——非放宽请求以自身文本失败且不提示任何人。 - **授权的升级不等于可工作的沙箱。** 不可用的后端即使对授权升级到约束模式也仍然失败关闭——平台没有链或所有探测失败时在 `confine()` 阶段失败,所选可执行文件无法启动时通过 spawn 通道失败,已启动的 runner 拒绝时则通过结构化规则失败——而授权的 `danger-full-access` 运行根本不触及提供方:此时授权(而非探测)是权威。 @@ -187,7 +187,7 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自 seam 层 - **一个命令返回了 `[sandbox: file access denied under read-only mode]`——它失败了吗?** 它运行了,内核拒绝了一个文件操作:拒绝是与退出码正交的结果事实。相关指令禁止通过绕过限制来重试;唯一被认可的动作是以升级请求重试同一命令一次。 - **如何区分损坏的沙箱与失败的命令?** 提供方 argv 的任何 spawn 拒绝都能证明受限启动从未开始,但只有在调用方拥有的 workdir 可用,且 Node 为该 argv[0] 报告可归因的 `ENOENT` 或 `EACCES` 时,才能据此判定 runner 损坏。没有精确错误路径的裸 `syscall: 'spawn'` 和其他所有拒绝仍是普通的命令启动错误。进程启动后,只有当 `runnerFailureRules` 中某一条目同时匹配其可选退出码门控,以及排除整行精确信息性行后的一行致命 stderr 诊断时,runner 失败才会优先于拒绝。前台失败会抛出结构化的 `SANDBOX_UNAVAILABLE`,并附带 spawn 错误或匹配行作为详细信息;遭异步拒绝或已结算的后台任务则盖章 `sandbox.runnerFailed` 并渲染自己的标记。如果 `SubprocessService` 同步抛出同样带有来源信息的 `ENOENT`/`EACCES` 形态,后台启动会抛出该结构化错误;其他同步错误原样传播。Landlock 部分强制执行通知加上普通子进程失败时,仍返回命令结果。 - **在没有后端的平台上会发生什么——今天的 Windows?** `confine()` 抛出失败关闭的 `SANDBOX_UNAVAILABLE`,命令永不 spawn;`win32` 是保留的空链,由测试固定为同样失败关闭,直到 Windows runner 填充它(§ 延迟阶段)。 -- **`bwrap` 已安装在我的主机上但不可用(禁用了非特权 userns、LSM 拒绝 `mount`)——会发生什么?** 链探测是功能性的——它构建并强制一个真实 profile 而非检查 `--version`——因此存在但不可用的 `bwrap` 探测失败,选择落到注册表安装的 Landlock launcher,结论在提供方生命周期内缓存。 +- **`bwrap` 已安装在我的主机上但不可用(禁用了非特权 userns、LSM 拒绝 `mount`)——会发生什么?** 链探测是功能性的——它构建并强制一个真实 profile 而非检查 `--version`——因此存在但不可用的 `bwrap` 探测失败,选择落到已打包的 Landlock launcher,结论在提供方生命周期内缓存。 - **沙箱限制网络或进程可见性吗?** 不——`SandboxMode` 仅声称文件操作;bwrap profile 刻意不 unshare pid,没有后端声称网络。网络限制是否成为自己的旋钮留在 § seam 中开放。 - **哪些工具实际在约束下运行?** 通过 `ctx.bash` 的 OS 子进程——bash 工具及传递性的钩子命令——再加上通过沙箱化 `ctx.fs` 提供方运行的文件系统工具(`read`/`write`/`edit`,见[跨工具族 fs 沙箱 Agent Note](2026-07-14-cross-family-fs-sandbox.md)):bash 通过 OS runner 约束,fs 通过进程内路径围栏约束,二者都以同一个 `ctx.sandboxPolicy` 模式为键。web/todo 仍在进程内且不受限制(web 的唯一效果是网络,不在文件效果模式词汇内)。 - **授权的升级会持久化吗?** 不会。授权由发起请求的确切前台或后台调用消费;每个相邻调用保留自己的有效模式。后续的后台拒绝通过 `task_output` 呈现,并且可以作为一次新的精确命令重试的依据。 diff --git a/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.i18n.yaml b/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.i18n.yaml new file mode 100644 index 0000000000..3ce0e0d5e1 --- /dev/null +++ b/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.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/process/2026-08-06-in-repository-landlock-release.md +2026-08-06-in-repository-landlock-release.md: f682078250adde8d56a4270e9d01ce4b1cd1bee9 +2026-08-06-in-repository-landlock-release.zh.md: 4950d80d87afd18c5605f4f5bca56b8d85564fc2 diff --git a/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.md b/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.md new file mode 100644 index 0000000000..f682078250 --- /dev/null +++ b/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.md @@ -0,0 +1,42 @@ +# Agent Note: In-repository Landlock release + +Status: implemented + +English | [中文](2026-08-06-in-repository-landlock-release.zh.md) + +## Problem + +The `node-addon-landlock-run` source already lives beside its DeepSeek Harness consumers under `native/landlock-run`, but it previously kept a separate pnpm workspace and lockfile and depended on a standalone repository for npm publication. Harness packages consumed a fixed registry version, so one pull request could change the launcher contract and its consumer without testing those changes together. The source repository's native workflow could rehearse the package, but it did not publish the artifact it tested. + +The mirror also duplicated release coordination: export the source, update another lockfile, run another release workflow, publish the native family, then return to this repository to bump registry dependencies. That split made source-to-binary provenance, rollback, and security-fix coordination harder without changing what npm users actually needed. + +The consolidation must preserve platform selection. The public distribution is deliberately one JavaScript entry package plus separate Linux x64 and arm64 binary packages; merging repository ownership does not imply putting every binary into one tarball or publishing every DeepSeek Harness package at the launcher version. + +## Decision + +`native/landlock-run` and `native/landlock-run/packages/*` belong to the repository's root pnpm workspace and use the root `pnpm-lock.yaml`. Harness consumers declare `node-addon-landlock-run` with `workspace:*`, so development, type checking, builds, and pull-request tests resolve the entry package from the same checkout. The root TypeScript project graph builds that entry package before consumers, and the repository cleaner owns its direct `lib/` output. + +The public npm boundary remains three packages with one launcher-family version: `node-addon-landlock-run`, `node-addon-landlock-run-linux-x64`, and `node-addon-landlock-run-linux-arm64`. The entry package retains both platform packages as `optionalDependencies`; their `os` and `cpu` manifest fields let npm install only the compatible package. Repository constraints allow public publication only for those three names, require `publishConfig.access: public`, and require their versions to match the private launcher workspace root. Other repository workspaces remain private under the existing constraint. + +The main repository owns both native CI and publication. `Landlock Run` runs for relevant pull requests and `master` pushes and builds each platform on its matching native runner. The manually dispatched `Landlock Run Release` workflow builds both platform binaries, transfers them as workflow artifacts, assembles and verifies the complete package family, packs immutable npm tarballs, installs and exercises those tarballs, and only then permits the protected publish job. Platform tarballs publish before the entry tarball that optionally depends on them. Publication uses `landlock-run-vX.Y.Z` tags so launcher releases cannot collide with other release families in the monorepo; prereleases use the npm `next` dist-tag. + +The sandbox packed-install rehearsal no longer permits the npm registry to supply the launcher. It packs the current checkout's entry and matching native package alongside the harness dependency closure, installs those local tarballs into an external plain-Node consumer, and proves that the installed launcher is executable, byte-identical to the native build, and the correct ELF architecture before testing confinement or fail-closed behavior. + +## Alternatives considered + +- **Keep the standalone repository as a release mirror** — rejected because it preserves the split lockfiles, source export, stale-registry test window, and cross-repository release sequence after the source of record has already moved here. +- **Publish one npm package containing every platform binary** — rejected because users would download binaries they cannot run and npm could no longer use package-level `os`/`cpu` filtering. Repository ownership and npm package layout are separate choices. +- **Give the launcher the root DeepSeek Harness version and publish the complete monorepo recursively** — rejected because this change owns one three-package public family, not the independent `@deepseek-ai/*` baseline. The [artifact-first npm baseline proposal](../../proposed/process/2026-08-04-artifact-first-npm-baseline-publication.md) explicitly keeps native workspaces outside its target set. +- **Cross-compile both binaries in one release job** — rejected because the checked-in package matrix already assigns each architecture a native GitHub runner and avoids adding a cross-toolchain trust surface. + +## Consequences + +Launcher protocol, TypeScript entry code, native source, harness consumption, and publish-path tests can change in one pull request and resolve from one lockfile. A release tag now identifies the source, consumer integration, build instructions, and tarballs tested by the main repository. The standalone mirror is no longer part of the release path and can be archived after the first successful in-repository publication. + +npm consumers keep the same install command and package names. A supported Linux host downloads the entry package and its matching architecture package; the other architecture package is skipped. An unsupported host receives no platform binary and follows the existing deterministic fail-closed probe path. + +The implementation touches more files than a dependency-line edit because the repository must also own workspace constraints, TypeScript build order, cleanup, CI triggers, release tags, lockfile generation, packed-install provenance, release documentation, and generated notices. The behavioral boundary stays narrow: it changes only the Landlock package family and its three direct workspace consumers, not the version or publication state of other DeepSeek Harness packages. + +The main repository's `npm-publish` environment must authorize npm trusted publishing or provide `NPM_TOKEN`; moving workflow code cannot configure those external settings. npm still publishes packages sequentially and offers no cross-package transaction, so a failed publish can leave a partial version. Because npm rejects an already-published name and version, an operator must inspect the registry and publish only the missing tarballs rather than rerunning the workflow unchanged. Linux x64 and arm64 runners remain the authoritative binary and real-kernel checks; a macOS checkout can verify the entry package and unsupported-platform behavior but cannot replace those jobs. + +This note supersedes only the release-mirror and registry-pinned source-development statements in the [sandbox Agent Note](../feature/2026-07-06-sandbox.md); that note continues to own sandbox behavior, runner selection, and enforcement semantics. diff --git a/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.zh.md b/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.zh.md new file mode 100644 index 0000000000..4950d80d87 --- /dev/null +++ b/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.zh.md @@ -0,0 +1,42 @@ +# Agent Note: 仓库内 Landlock 发布 + +Status: implemented + +[English](2026-08-06-in-repository-landlock-release.md) | 中文 + +## 问题 + +`node-addon-landlock-run` 源码已经与其 DeepSeek Harness 消费方一同位于 `native/landlock-run` 下,但此前仍保留独立的 pnpm workspace 和锁文件,并依赖一个独立仓库发布到 npm。Harness 包使用 npm 注册表中的固定版本,因此同一个 PR(Pull Request)可以同时修改启动器契约及其消费方,却无法一起测试这些改动。源码仓库的原生工作流可以演练打包流程,但不会发布它实际测试过的产物。 + +发布镜像还造成重复的发布协调工作:导出源码、更新另一份锁文件、运行另一套发布工作流、发布原生包家族,然后回到本仓库更新注册表依赖。npm 用户的实际需求并未改变,这种拆分却增加了从源码到二进制的溯源、回滚和安全修复协调难度。 + +此次整合必须保留平台选择机制。公开分发有意采用一个 JavaScript 入口包,并为 Linux x64 和 arm64 分别提供二进制包;合并仓库归属并不意味着要把所有二进制文件放进同一个 tarball,也不意味着要按照启动器版本发布所有 DeepSeek Harness 包。 + +## 决策 + +`native/landlock-run` 和 `native/landlock-run/packages/*` 属于仓库根 pnpm workspace,并使用根 `pnpm-lock.yaml`。Harness 消费方将 `node-addon-landlock-run` 声明为 `workspace:*`,因此开发、类型检查、构建和 PR 测试都会从同一个 checkout 解析入口包。根 TypeScript 项目图会先构建该入口包,再构建消费方;仓库清理器负责清理其直接生成的 `lib/` 输出目录。 + +公开 npm 分发边界仍由 3 个包组成,它们共用一个启动器包家族版本:`node-addon-landlock-run`、`node-addon-landlock-run-linux-x64` 和 `node-addon-landlock-run-linux-arm64`。入口包继续通过 `optionalDependencies` 声明两个平台包;它们在 manifest(元数据清单)中的 `os` 和 `cpu` 字段让 npm 只安装兼容的包。仓库约束只允许公开发布这 3 个包名,要求设置 `publishConfig.access: public`,并要求其版本与私有启动器 workspace 根包一致。仓库中的其他 workspace 仍受现有约束保护,保持私有状态。 + +主仓库同时负责原生 CI 和发布。`Landlock Run` 会为相关 PR 和 `master` 推送运行,并在各自匹配的原生 runner 上构建每个平台包。手动触发的 `Landlock Run Release` 工作流会构建两个平台的二进制文件,将其作为工作流产物传递,组装并验证完整的包家族,打包出内容不可变的 npm tarball,安装并实际运行这些 tarball,之后才允许受保护的发布作业执行。发布顺序是平台 tarball 在前,最后发布将它们列为可选依赖的入口 tarball。发布使用 `landlock-run-vX.Y.Z` tag,避免启动器版本与 monorepo 中其他发布家族发生冲突;预发布版本使用 npm 的 `next` dist-tag。 + +沙箱打包安装演练不再允许 npm 注册表提供启动器。它会将当前 checkout 的入口包、匹配的原生包和 harness 依赖闭包一起打包,把这些本地 tarball 安装到仓库外部的纯 Node 消费方中,并在测试约束效果或失败闭合行为之前,证明所安装的启动器可执行、与原生构建产物字节完全一致,且具有正确的 ELF 架构。 + +## 曾考虑的替代方案 + +- **保留独立仓库作为发布镜像**:不予采纳,因为在权威源码已经迁入本仓库后,这仍会保留拆分的锁文件、源码导出、测试使用陈旧注册表版本的时间窗,以及跨仓库发布序列。 +- **发布一个包含所有平台二进制文件的 npm 包**:不予采纳,因为用户会下载无法在其主机上运行的二进制文件,而且 npm 无法再利用包级 `os`/`cpu` 筛选。仓库归属与 npm 包布局是两个彼此独立的选择。 +- **让启动器使用 DeepSeek Harness 根版本,并递归发布整个 monorepo**:不予采纳,因为本次改动负责的是一个由 3 个包组成的公开包家族,而不是独立的 `@deepseek-ai/*` 基线。[产物优先的 npm 基线提案](../../proposed/process/2026-08-04-artifact-first-npm-baseline-publication.md)明确将原生 workspace 排除在其目标集合之外。 +- **在一个发布作业中交叉编译两个二进制文件**:不予采纳,因为仓库内已提交的包矩阵已经为每种架构分配了原生 GitHub runner,无需再把交叉工具链纳入信任边界。 + +## 后果 + +同一个 PR 可以同时修改启动器协议、TypeScript 入口代码、原生源码、harness 消费方式和发布路径测试,并从同一份锁文件解析这些内容。发布 tag 现在标识源码、消费方集成、构建指令,以及主仓库测试过的 tarball。第一次成功从本仓库发布后,独立镜像便不再属于发布路径,可以归档。 + +npm 消费方继续使用相同的安装命令和包名。受支持的 Linux 主机会下载入口包及与其架构匹配的包,并跳过另一架构的包。不受支持的主机不会收到平台二进制文件,并继续沿用现有的确定性失败闭合探测路径。 + +实现涉及的文件比只修改一行依赖更多,因为仓库还必须负责 workspace 约束、TypeScript 构建顺序、清理、CI 触发条件、发布 tag、锁文件生成、打包安装来源证明、发布文档和生成的第三方声明。行为边界仍然很窄:此次改动只影响 Landlock 包家族及其 3 个直接 workspace 消费方,不改变其他 DeepSeek Harness 包的版本或发布状态。 + +主仓库的 `npm-publish` 环境必须授权 npm trusted publishing,或提供 `NPM_TOKEN`;只迁移工作流代码无法配置这些外部设置。npm 仍会按顺序发布各个包,且不提供跨包事务,因此发布失败可能留下只完成了一部分的版本。由于 npm 会拒绝已经发布的同名同版本包,操作人员必须检查注册表并只发布缺失的 tarball,而不能原样重新运行工作流。Linux x64 和 arm64 runner 仍提供权威的二进制构建与真实内核检查;macOS checkout 可以验证入口包和不受支持平台上的行为,但不能取代这些作业。 + +本说明仅取代[沙箱 Agent Note](../feature/2026-07-06-sandbox.md)中有关发布镜像和开发源码时依赖注册表固定版本的表述;该 Agent Note 仍负责沙箱行为、runner 选择和强制执行语义。 diff --git a/.github/workflows/landlock-run-release.yml b/.github/workflows/landlock-run-release.yml new file mode 100644 index 0000000000..dca6c9eed1 --- /dev/null +++ b/.github/workflows/landlock-run-release.yml @@ -0,0 +1,170 @@ +# Build and publish the node-addon-landlock-run package family from the +# harness source of record. Rehearsal and publication consume the same packed +# tarballs; each native binary is built on its matching architecture. +name: Landlock Run Release + +on: + workflow_dispatch: + inputs: + publish: + description: Publish packed tarballs to npm. Must run from a landlock-run-v* tag. + required: true + type: boolean + default: false + +permissions: + contents: read + +concurrency: + # Stable/prerelease dist-tags are shared registry state; serialize release + # runs so two versions cannot race the final tag assignment. + group: ${{ github.workflow }} + cancel-in-progress: false + +defaults: + run: + working-directory: native/landlock-run + +jobs: + matrix: + name: Matrix + runs-on: ubuntu-24.04 + outputs: + prebuilds: ${{ steps.matrix.outputs.prebuilds }} + steps: + - uses: actions/checkout@v4 + + - id: matrix + run: echo "prebuilds=$(node ./scripts/github-matrix.mjs release-prebuild)" >> "$GITHUB_OUTPUT" + + build-prebuilds: + name: ${{ matrix.package }} + needs: matrix + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrix.outputs.prebuilds) }} + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + package_json_file: package.json + + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + + - name: Install dependencies + run: pnpm install --filter node-addon-landlock-run-workspace... --frozen-lockfile + + - name: Install musl toolchain + run: | + sudo apt-get update -q + sudo apt-get install -yq musl-tools + + - name: Build native binaries + run: pnpm build:native + + - name: Verify binary metadata + run: node ./scripts/verify-launcher-binary.mjs ${{ matrix.dir }} + + - name: Upload prebuild artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: native/landlock-run/${{ matrix.dir }}/bin/* + if-no-files-found: error + retention-days: 7 + + pack: + name: Pack npm tarballs + needs: build-prebuilds + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + package_json_file: package.json + + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + + - name: Install dependencies + run: pnpm install --filter node-addon-landlock-run-workspace... --frozen-lockfile + + - name: Build TypeScript + run: pnpm build:ts + + - name: Verify release version + run: node ./scripts/verify-release.mjs + env: + RELEASE_PUBLISH: ${{ inputs.publish }} + + - name: Download prebuild artifacts + uses: actions/download-artifact@v4 + with: + pattern: prebuild-* + path: native/landlock-run/.release/prebuild-artifacts + + - name: Assemble and verify prebuilds + run: node ./scripts/assemble-prebuilds.mjs .release/prebuild-artifacts + + - name: Verify release payload + run: node ./scripts/verify-release.mjs --prebuilds + env: + RELEASE_PUBLISH: ${{ inputs.publish }} + + - name: Pack release tarballs + run: node ./scripts/pack-release.mjs dist/npm + + - name: Verify packed install + run: node ./scripts/verify-packed-install.mjs dist/npm + env: + NALR_REQUIRE_LANDLOCK: 1 + + - name: Upload npm tarballs + uses: actions/upload-artifact@v4 + with: + name: npm-tarballs + path: native/landlock-run/dist/npm/* + if-no-files-found: error + retention-days: 7 + + publish: + name: Publish to npm + if: inputs.publish + needs: pack + runs-on: ubuntu-24.04 + environment: npm-publish + permissions: + contents: read + id-token: write + steps: + - uses: actions/setup-node@v4 + with: + node-version: 24 + registry-url: https://registry.npmjs.org + + - name: Download npm tarballs + uses: actions/download-artifact@v4 + with: + name: npm-tarballs + path: native/landlock-run/dist/npm + + - name: Publish tarballs + run: | + version="${GITHUB_REF#refs/tags/landlock-run-v}" + tag_args=() + case "$version" in *-*) tag_args=(--tag next);; esac + while IFS= read -r tarball; do + npm publish "dist/npm/${tarball}" --access public "${tag_args[@]}" + done < dist/npm/publish-order.txt + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/landlock-run.yml b/.github/workflows/landlock-run.yml index dad9638761..391e1eeae1 100644 --- a/.github/workflows/landlock-run.yml +++ b/.github/workflows/landlock-run.yml @@ -1,15 +1,27 @@ -# Manually-dispatched CI for the landlock-run source of record -# (native/landlock-run). A separate workflow from ci.yml on purpose: the -# subtree is a self-contained pnpm workspace with its own gates, exercised on -# demand — per-architecture native legs (build + behavioral tests + pack -# rehearsal on real kernels) plus one darwin leg proving the documented -# degradation on hosts without a platform package. Legs derive from the -# subtree's checked-in package matrix (scripts/github-matrix.mjs). Packing -# for npm happens in the release mirror (node-addon-landlock-run) after an -# export — see native/README.md; this workflow never packs for release. +# CI for the landlock-run packages under native/landlock-run. A separate +# workflow from ci.yml keeps the native OS/architecture matrix independent of +# the harness Node matrix. Release assembly and publication use the companion +# Landlock Run Release workflow. name: Landlock Run on: + pull_request: + paths: + - '.github/workflows/landlock-run.yml' + - '.github/workflows/landlock-run-release.yml' + - 'native/landlock-run/**' + - 'package.json' + - 'pnpm-lock.yaml' + - 'pnpm-workspace.yaml' + push: + branches: [master] + paths: + - '.github/workflows/landlock-run.yml' + - '.github/workflows/landlock-run-release.yml' + - 'native/landlock-run/**' + - 'package.json' + - 'pnpm-lock.yaml' + - 'pnpm-workspace.yaml' workflow_dispatch: concurrency: @@ -52,16 +64,16 @@ jobs: - uses: pnpm/action-setup@v4 with: - package_json_file: native/landlock-run/package.json + package_json_file: package.json - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm - cache-dependency-path: native/landlock-run/pnpm-lock.yaml + cache-dependency-path: pnpm-lock.yaml - name: Install dependencies - run: pnpm install --frozen-lockfile + run: pnpm install --filter node-addon-landlock-run-workspace... --frozen-lockfile - name: Install musl toolchain run: | @@ -103,16 +115,16 @@ jobs: - uses: pnpm/action-setup@v4 with: - package_json_file: native/landlock-run/package.json + package_json_file: package.json - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm - cache-dependency-path: native/landlock-run/pnpm-lock.yaml + cache-dependency-path: pnpm-lock.yaml - name: Install dependencies - run: pnpm install --frozen-lockfile + run: pnpm install --filter node-addon-landlock-run-workspace... --frozen-lockfile - name: Build TypeScript run: pnpm build:ts diff --git a/.github/workflows/sandbox.yml b/.github/workflows/sandbox.yml index 939ca2f6ab..2dfaf0e175 100644 --- a/.github/workflows/sandbox.yml +++ b/.github/workflows/sandbox.yml @@ -3,9 +3,8 @@ # .agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.md. # A separate workflow from ci.yml because the axis is different — these jobs # fan out over OS×runner (kernel capabilities), not node versions. The Landlock -# launcher arrives from the registry with `pnpm install` (the npm package family -# `node-addon-landlock-run`, built and released from its own repository), so -# these legs exercise the true consumer path — nothing is compiled here. +# launcher is built from native/landlock-run on each Landlock leg and installed +# from the same tarballs the main-repository release workflow publishes. name: Sandbox on: @@ -30,7 +29,7 @@ jobs: # an OS×runner matrix — bwrap and Landlock on Linux (separate legs: the # Landlock files force the bwrap rung off, so each leg proves exactly one # rung; Landlock twice, once per architecture, each confining through the - # registry-installed launcher), Seatbelt on macOS (sandbox-exec ships with + # locally built launcher), Seatbelt on macOS (sandbox-exec ships with # the OS). One node # version only: kernel confinement does not vary by node, and ci.yml's # node matrix already covers the node axis. @@ -80,6 +79,14 @@ jobs: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 \ || echo "apparmor userns knob absent — the functional probe decides" + - name: Build Landlock launcher for this architecture + if: matrix.runner == 'landlock' + run: | + sudo apt-get update -q + sudo apt-get install -yq musl-tools + pnpm --dir native/landlock-run run build:ts + pnpm --dir native/landlock-run run build:native + # The unit suite runs on ubuntu in `checks`; this is the one darwin leg # in the workflow, so run it here too — the platform-dependent unit # expectations (Seatbelt path canonicalization: /tmp IS /private/tmp) @@ -105,14 +112,10 @@ jobs: # the very platform that exists to prove it) is a failure, not a pass. echo "$out" | grep -qE 'Test Files[[:space:]]+2 passed \(2\)' - # Publish-path rehearsal, Landlock legs only (the pack gates need built - # lib/). The e2e packs the workspace closure, installs the tarballs - # into a throwaway consumer — npm pulling `node-addon-landlock-run` - # and its platform package from the registry, the true consumer path — - # and confines through the INSTALLED launcher, asserting it executable - # apart (a mode-stripped binary must not masquerade as a non-enforcing - # kernel). Same no-silent-skip guard as above. - - name: Build packages (lib/ for the pack rehearsal) + # Publish-path rehearsal, Landlock legs only. Build the launcher on its + # native architecture, then install the local native and harness tarballs + # together so registry state cannot mask source/package drift. + - name: Build packages for the pack rehearsal if: matrix.runner == 'landlock' run: pnpm run build diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 7d2e257ea9..aa73dba8d9 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -170,6 +170,6 @@ Direct dependencies of the `pyproject.toml` manifests, plus `uv` as the developm | --- | --- | --- | | [`@yao-pkg/pkg`](https://github.com/yao-pkg/pkg) | MIT | invoked by `scripts/build-exe-for-python-sdk.ts` to assemble the single-file SDK runtime executable | -## First-party sibling releases +## First-party native packages -`node-addon-landlock-run` (and its platform packages) is released from a DeepSeek Harness sibling repository under BSD 3-Clause. It is listed here for completeness; it is first-party, not third-party. +`node-addon-landlock-run` (and its platform packages) is built and released from this repository under BSD 3-Clause. It is listed here for completeness; it is first-party, not third-party. diff --git a/knip.json b/knip.json index dfb8058d7c..60853c6517 100644 --- a/knip.json +++ b/knip.json @@ -5,6 +5,7 @@ ], "ignoreBinaries": [ "bwrap", + "musl-gcc", "python3", "sandbox-exec", "taskkill" diff --git a/native/README.i18n.yaml b/native/README.i18n.yaml index a55273be29..31a86ebe73 100644 --- a/native/README.i18n.yaml +++ b/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: a79d9ca5747d4c4fbfa50745b3eece07b96aea58 -README.zh.md: 708430c0a087e5a9da1dae6fd078cb477db65d9b +README.md: 51c8da7b57df15e65b8e431ee18ce6cebb89d54b +README.zh.md: baf530157c6731893a66aea301c6dd962592defc diff --git a/native/README.md b/native/README.md index a79d9ca574..51c8da7b57 100644 --- a/native/README.md +++ b/native/README.md @@ -2,12 +2,10 @@ English | [中文](README.zh.md) -Source of record for `node-addon-landlock-run`, the Landlock self-restrict-then-exec launcher consumed by the harness. The [`landlock-run/` workspace](landlock-run/README.md) owns its architecture, package family, platform support, development workflow, and release procedure. The standalone repository is a release mirror. +Native source and public packages maintained with DeepSeek Harness. The [`landlock-run/` workspace](landlock-run/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](landlock-run/docs/release.md). -## Release mirror +## Workspace and release boundary -| Directory | Mirror repo | Last exported release | Commit | -|---|---|---|---| -| `landlock-run/` | https://github.com/deepseek-harness/node-addon-landlock-run | `v0.0.1` | `614f7fd7dc11e6eaceefba9e7ff1fbe28b51ba22` | +`landlock-run/` 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 subtree is a self-contained pnpm workspace and is not part of the harness workspace. The [launcher release reference](landlock-run/docs/release.md) owns the export and publication workflow. The mirror must not diverge: port any direct mirror hotfix back here before the next export. +The main repository's `Landlock Run` workflow builds and tests each supported architecture. `Landlock Run 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. diff --git a/native/README.zh.md b/native/README.zh.md index 708430c0a0..baf530157c 100644 --- a/native/README.zh.md +++ b/native/README.zh.md @@ -2,12 +2,10 @@ [English](README.md) | 中文 -`node-addon-landlock-run` 的真源;它是供 harness 使用、先施加 Landlock 自限再执行命令的启动器。[`landlock-run/` workspace](landlock-run/README.md)负责其架构、包家族、平台支持、开发工作流和发布流程。独立仓库是发布镜像。 +与 DeepSeek Harness 一同维护的原生源码和公开包。[`landlock-run/` workspace](landlock-run/README.md)负责 harness 使用的 Landlock 自限后执行启动器,包括其架构、由三个包组成的 npm 包家族、平台支持、开发工作流和[发布流程](landlock-run/docs/release.md)。 -## 发布镜像 +## Workspace 与发布边界 -| 目录 | 镜像仓库 | 最近导出的版本 | Commit | -|---|---|---|---| -| `landlock-run/` | https://github.com/deepseek-harness/node-addon-landlock-run | `v0.0.1` | `614f7fd7dc11e6eaceefba9e7ff1fbe28b51ba22` | +`landlock-run/` 及其包属于仓库根 pnpm workspace,并共用根锁文件。开发和 CI 中的 harness 消费方直接使用当前 workspace 的入口包,因此启动器契约变更与消费方更新可以在同一个改动中落地并一起测试。 -该子树是自包含的 pnpm workspace,不属于 harness workspace。[启动器发布参考](landlock-run/docs/release.md)负责导出和发布工作流。镜像不得发生分歧:下次导出前,必须把任何直接施加于镜像的热修复移植回此处。 +主仓库的 `Landlock Run` 工作流为每个受支持架构构建并测试。`Landlock Run Release` 汇集这些原生产物,打包并验证三个 npm tarball,随后可选择以同一个启动器版本发布。入口包继续将平台包声明为 npm 可选依赖,因此 npm 仍然只会安装与用户操作系统和 CPU 匹配的包。 diff --git a/native/landlock-run/AGENTS.md b/native/landlock-run/AGENTS.md index 31e12e177c..6742ff81d4 100644 --- a/native/landlock-run/AGENTS.md +++ b/native/landlock-run/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md -This workspace 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 speaks its CLI contract. The source of record is the `deepseek-harness` repository's `native/landlock-run/`; the `node-addon-landlock-run` repository is the release mirror this tree is exported to for packing and publishing (procedure: `native/README.md` in the harness repo). Make changes in the source of record, never only in the mirror. +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 speaks 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. ## Pre-release stance diff --git a/native/landlock-run/docs/release.md b/native/landlock-run/docs/release.md index e1ea65c411..a95cffd47f 100644 --- a/native/landlock-run/docs/release.md +++ b/native/landlock-run/docs/release.md @@ -4,45 +4,45 @@ Pre-1.0: treat this as a release checklist, not a stability policy. ## Versioning -One version across every package in the repo. Use the bump helper: +The launcher workspace root and its three public packages share one version. Run the bump helper from the repository root: ```sh -pnpm release:bump patch # or minor / major / x.y.z +pnpm --dir native/landlock-run release:bump patch # or minor / major / x.y.z ``` -It updates the root and every `packages/*` manifest, refreshes the lockfile (`--ignore-scripts --lockfile-only`), and runs `release:verify`. Explicit versions accept full semver including prereleases (`pnpm release:bump 0.0.0-test.0`); the publish workflow puts prerelease versions under the `next` dist-tag, so `latest` never points at a test build. Keep `workspace:*` dependencies in source; pnpm converts them to concrete versions during pack. +It updates `native/landlock-run/package.json` and every `native/landlock-run/packages/*` manifest, refreshes the repository root lockfile (`--ignore-scripts --lockfile-only`), and runs `release:verify`. Explicit versions accept full semver including prereleases (`pnpm --dir native/landlock-run release:bump 0.0.0-test.0`); the publish workflow puts prerelease versions under the `next` dist-tag, so `latest` never points at a test build. Keep `workspace:*` dependencies in source; pnpm converts them to concrete versions during pack. -Version bumps are normal source changes: open a release PR (or commit) with the manifests and lockfile, merge it, then create the matching `vX.Y.Z` tag from that commit. The publish workflow validates that the tag matches every package version. +Version bumps are normal source changes: open a release PR (or commit) with the launcher manifests and root lockfile, merge it, then create the matching `landlock-run-vX.Y.Z` tag from that commit. The namespace avoids colliding with release tags for other package families in the repository. The publish workflow validates that the tag matches every launcher package version. ```sh -pnpm release:commit patch # bump + stage + commit in one command -git tag v0.0.2 +pnpm --dir native/landlock-run release:commit patch # bump + stage + commit in one command +git tag landlock-run-v0.0.2 ``` ## Preflight ```sh pnpm install --frozen-lockfile -pnpm build:ts -pnpm typecheck -pnpm test:entry +pnpm --dir native/landlock-run build:ts +pnpm --dir native/landlock-run typecheck +pnpm --dir native/landlock-run test:entry ``` On a Linux host, also rehearse the pack path locally: ```sh -pnpm build:native -pnpm test:launcher -node ./scripts/pack-release.mjs .release/npm --current-platform-only -node ./scripts/verify-packed-install.mjs .release/npm --current-platform-only +pnpm --dir native/landlock-run build:native +pnpm --dir native/landlock-run test:launcher +node native/landlock-run/scripts/pack-release.mjs native/landlock-run/.release/npm --current-platform-only +node native/landlock-run/scripts/verify-packed-install.mjs native/landlock-run/.release/npm --current-platform-only ``` ## Publish -Use the `Release` workflow so every binary is built on its matching native runner: +Use the main repository's `Landlock Run Release` workflow so every binary is built on its matching native runner: 1. Run it with `publish=false` (from the release commit) to build all platform binaries, assemble and verify the payloads, pack the tarballs in publish order, rehearse the packed install, and upload the `npm-tarballs` artifact for inspection. -2. Create and push the `vX.Y.Z` tag matching the package versions. +2. Create and push the `landlock-run-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). It supports npm trusted publishing through GitHub OIDC; without it, provide an `NPM_TOKEN` secret in the `npm-publish` environment. Packages publish with `--access public`. @@ -50,9 +50,9 @@ The workflow publishes only from the final packed tarballs, in `publish-order.tx 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)): ```sh -node ./scripts/pack-release.mjs dist/npm --current-platform-only -node ./scripts/verify-packed-install.mjs dist/npm --current-platform-only -while IFS= read -r tarball; do npm publish "dist/npm/${tarball}" --access public; done < dist/npm/publish-order.txt +node native/landlock-run/scripts/pack-release.mjs native/landlock-run/dist/npm --current-platform-only +node native/landlock-run/scripts/verify-packed-install.mjs native/landlock-run/dist/npm --current-platform-only +while IFS= read -r tarball; do npm publish "native/landlock-run/dist/npm/${tarball}" --access public; done < native/landlock-run/dist/npm/publish-order.txt ``` Do not commit `.npmrc` files with tokens or registry overrides. diff --git a/native/landlock-run/pnpm-lock.yaml b/native/landlock-run/pnpm-lock.yaml deleted file mode 100644 index dd309fb046..0000000000 --- a/native/landlock-run/pnpm-lock.yaml +++ /dev/null @@ -1,345 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - devDependencies: - '@types/node': - specifier: ^26.0.1 - version: 26.0.1 - node-addon-landlock-run: - specifier: workspace:* - version: link:packages/entry - tsx: - specifier: ^4.20.6 - version: 4.23.0 - typescript: - specifier: ^6.0.3 - version: 6.0.3 - - packages/entry: - optionalDependencies: - node-addon-landlock-run-linux-arm64: - specifier: workspace:* - version: link:../linux-arm64 - node-addon-landlock-run-linux-x64: - specifier: workspace:* - version: link:../linux-x64 - - packages/linux-arm64: {} - - packages/linux-x64: {} - -packages: - - '@esbuild/aix-ppc64@0.28.1': - resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.28.1': - resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.28.1': - resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.28.1': - resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.28.1': - resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.28.1': - resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.28.1': - resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.28.1': - resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.28.1': - resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.28.1': - resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.28.1': - resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.28.1': - resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.28.1': - resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.28.1': - resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.28.1': - resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.28.1': - resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.28.1': - resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.28.1': - resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.28.1': - resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.28.1': - resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.28.1': - resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.28.1': - resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.28.1': - resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.28.1': - resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.28.1': - resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.28.1': - resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@types/node@26.0.1': - resolution: {integrity: sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==} - - esbuild@0.28.1: - resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} - engines: {node: '>=18'} - hasBin: true - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - tsx@4.23.0: - resolution: {integrity: sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==} - engines: {node: '>=18.0.0'} - hasBin: true - - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} - hasBin: true - - undici-types@8.3.0: - resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - -snapshots: - - '@esbuild/aix-ppc64@0.28.1': - optional: true - - '@esbuild/android-arm64@0.28.1': - optional: true - - '@esbuild/android-arm@0.28.1': - optional: true - - '@esbuild/android-x64@0.28.1': - optional: true - - '@esbuild/darwin-arm64@0.28.1': - optional: true - - '@esbuild/darwin-x64@0.28.1': - optional: true - - '@esbuild/freebsd-arm64@0.28.1': - optional: true - - '@esbuild/freebsd-x64@0.28.1': - optional: true - - '@esbuild/linux-arm64@0.28.1': - optional: true - - '@esbuild/linux-arm@0.28.1': - optional: true - - '@esbuild/linux-ia32@0.28.1': - optional: true - - '@esbuild/linux-loong64@0.28.1': - optional: true - - '@esbuild/linux-mips64el@0.28.1': - optional: true - - '@esbuild/linux-ppc64@0.28.1': - optional: true - - '@esbuild/linux-riscv64@0.28.1': - optional: true - - '@esbuild/linux-s390x@0.28.1': - optional: true - - '@esbuild/linux-x64@0.28.1': - optional: true - - '@esbuild/netbsd-arm64@0.28.1': - optional: true - - '@esbuild/netbsd-x64@0.28.1': - optional: true - - '@esbuild/openbsd-arm64@0.28.1': - optional: true - - '@esbuild/openbsd-x64@0.28.1': - optional: true - - '@esbuild/openharmony-arm64@0.28.1': - optional: true - - '@esbuild/sunos-x64@0.28.1': - optional: true - - '@esbuild/win32-arm64@0.28.1': - optional: true - - '@esbuild/win32-ia32@0.28.1': - optional: true - - '@esbuild/win32-x64@0.28.1': - optional: true - - '@types/node@26.0.1': - dependencies: - undici-types: 8.3.0 - - esbuild@0.28.1: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.1 - '@esbuild/android-arm': 0.28.1 - '@esbuild/android-arm64': 0.28.1 - '@esbuild/android-x64': 0.28.1 - '@esbuild/darwin-arm64': 0.28.1 - '@esbuild/darwin-x64': 0.28.1 - '@esbuild/freebsd-arm64': 0.28.1 - '@esbuild/freebsd-x64': 0.28.1 - '@esbuild/linux-arm': 0.28.1 - '@esbuild/linux-arm64': 0.28.1 - '@esbuild/linux-ia32': 0.28.1 - '@esbuild/linux-loong64': 0.28.1 - '@esbuild/linux-mips64el': 0.28.1 - '@esbuild/linux-ppc64': 0.28.1 - '@esbuild/linux-riscv64': 0.28.1 - '@esbuild/linux-s390x': 0.28.1 - '@esbuild/linux-x64': 0.28.1 - '@esbuild/netbsd-arm64': 0.28.1 - '@esbuild/netbsd-x64': 0.28.1 - '@esbuild/openbsd-arm64': 0.28.1 - '@esbuild/openbsd-x64': 0.28.1 - '@esbuild/openharmony-arm64': 0.28.1 - '@esbuild/sunos-x64': 0.28.1 - '@esbuild/win32-arm64': 0.28.1 - '@esbuild/win32-ia32': 0.28.1 - '@esbuild/win32-x64': 0.28.1 - - fsevents@2.3.3: - optional: true - - tsx@4.23.0: - dependencies: - esbuild: 0.28.1 - optionalDependencies: - fsevents: 2.3.3 - - typescript@6.0.3: {} - - undici-types@8.3.0: {} diff --git a/native/landlock-run/pnpm-workspace.yaml b/native/landlock-run/pnpm-workspace.yaml deleted file mode 100644 index 22299bfea0..0000000000 --- a/native/landlock-run/pnpm-workspace.yaml +++ /dev/null @@ -1,8 +0,0 @@ -packages: - - packages/* - -# pnpm 10+ blocks any dependency shipping an install/build script until it is -# explicitly reviewed here. Deny by default; esbuild (tsx's bundled native -# binary) genuinely needs its script. -allowBuilds: - esbuild: true diff --git a/native/landlock-run/scripts/bump-release.mjs b/native/landlock-run/scripts/bump-release.mjs index 29a7777379..55033eed09 100644 --- a/native/landlock-run/scripts/bump-release.mjs +++ b/native/landlock-run/scripts/bump-release.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node /** - * Bump every package (workspace root + packages/*) to one version, refresh - * the lockfile, and verify. Usage: `pnpm release:bump `. + * Bump the launcher workspace root and packages/* to one version, refresh the + * repository lockfile, and verify. Usage: `pnpm release:bump `. */ import fs from 'node:fs'; @@ -11,14 +11,15 @@ import { packageDirs, readJson, root } from './repo.mjs'; const bump = process.argv[2]; const releaseTypes = new Set(['major', 'minor', 'patch']); +const repositoryRoot = path.resolve(root, '../..'); function writeJson(file, value) { fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`); } -function run(command, args) { +function run(command, args, cwd = root) { const result = spawnSync(command, args, { - cwd: root, + cwd, stdio: 'inherit', env: { ...process.env, CI: 'true' }, }); @@ -84,7 +85,7 @@ for (const file of files) { console.log(`${file}: ${targetVersion}`); } -run('pnpm', ['install', '--ignore-scripts', '--lockfile-only']); +run('pnpm', ['install', '--ignore-scripts', '--lockfile-only'], repositoryRoot); run('node', ['./scripts/verify-release.mjs']); console.log(`Release version bumped to ${targetVersion}`); diff --git a/native/landlock-run/scripts/commit-release.mjs b/native/landlock-run/scripts/commit-release.mjs index b7bf3e513b..1b1c78bce5 100644 --- a/native/landlock-run/scripts/commit-release.mjs +++ b/native/landlock-run/scripts/commit-release.mjs @@ -1,8 +1,8 @@ #!/usr/bin/env node /** * Bump, stage, and commit a release in one command: - * `pnpm release:commit `. The tag stays manual — - * create it from the merged release commit. + * `pnpm release:commit `. The namespaced tag stays + * manual — create it from the merged release commit. */ import path from 'node:path'; @@ -35,8 +35,8 @@ run('git', [ 'add', 'package.json', 'packages/*/package.json', - 'pnpm-lock.yaml', + '../../pnpm-lock.yaml', ]); -run('git', ['commit', '-m', `release: ${version}`]); +run('git', ['commit', '-m', `release(landlock-run): ${version}`]); -console.log(`Committed release ${version}. Create the tag manually: git tag v${version}`); +console.log(`Committed release ${version}. Create the tag manually: git tag landlock-run-v${version}`); diff --git a/native/landlock-run/scripts/repo.mjs b/native/landlock-run/scripts/repo.mjs index 8032d3da37..db5ffaf28d 100644 --- a/native/landlock-run/scripts/repo.mjs +++ b/native/landlock-run/scripts/repo.mjs @@ -12,10 +12,10 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; export const root = fileURLToPath(new URL('..', import.meta.url)); -export const packagesRoot = path.join(root, 'packages'); +const packagesRoot = path.join(root, 'packages'); /** ELF `e_machine` (offset 18, little-endian) per platform-package `cpu` value. */ -export const E_MACHINE = { x64: 62, arm64: 183 }; +const E_MACHINE = { x64: 62, arm64: 183 }; export function readJson(file) { return JSON.parse(fs.readFileSync(file, 'utf8')); diff --git a/native/landlock-run/scripts/verify-release.mjs b/native/landlock-run/scripts/verify-release.mjs index e812b34a14..ff3917b4b5 100644 --- a/native/landlock-run/scripts/verify-release.mjs +++ b/native/landlock-run/scripts/verify-release.mjs @@ -1,8 +1,8 @@ #!/usr/bin/env node /** * Release verification. Always: every published package carries one shared - * version, and — when running from a tag or publishing — the `vX.Y.Z` tag - * matches it. With `--prebuilds`: every platform package's declared + * version, and — when running from a tag or publishing — the + * `landlock-run-vX.Y.Z` tag matches it. With `--prebuilds`: every platform package's declared * binaries exist with the right ELF architecture (run after * `assemble-prebuilds.mjs` or a local `build:native`). */ @@ -10,6 +10,8 @@ import path from 'node:path'; import { packageDirs, platformDirs, readJson, root, verifyPlatformBinaries } from './repo.mjs'; +const TAG_PREFIX = 'refs/tags/landlock-run-v'; + function verifyVersions() { const packages = packageDirs().map((dir) => ({ dir, @@ -26,13 +28,13 @@ function verifyVersions() { const version = packages[0].manifest.version; const ref = process.env.GITHUB_REF || ''; const publish = process.env.RELEASE_PUBLISH === 'true'; - if (publish && !ref.startsWith('refs/tags/v')) { - throw new Error('publishing requires running the workflow from a v* tag'); + if (publish && !ref.startsWith(TAG_PREFIX)) { + throw new Error('publishing requires running the workflow from a landlock-run-v* tag'); } - if (ref.startsWith('refs/tags/v')) { - const tagVersion = ref.slice('refs/tags/v'.length); + if (ref.startsWith(TAG_PREFIX)) { + const tagVersion = ref.slice(TAG_PREFIX.length); if (tagVersion !== version) { - throw new Error(`tag/version mismatch: tag v${tagVersion}, packages ${version}`); + throw new Error(`tag/version mismatch: tag landlock-run-v${tagVersion}, packages ${version}`); } } diff --git a/package.json b/package.json index 7bd84db93a..8340580e6d 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "workspaces": [ "vendor/*", "packages/*/*", + "native/landlock-run", + "native/landlock-run/packages/*", "apps/*", "website" ], diff --git a/packages/bash/bash-sandbox/package.json b/packages/bash/bash-sandbox/package.json index e3d3c3deb3..6a29c3f6c8 100644 --- a/packages/bash/bash-sandbox/package.json +++ b/packages/bash/bash-sandbox/package.json @@ -41,6 +41,6 @@ "@deepseek-ai/dsh-sandbox-local": "workspace:^", "@deepseek-ai/dsh-sandbox-policy": "workspace:^", "cordis": "^4.0.0-rc.7", - "node-addon-landlock-run": "0.0.0-test.0" + "node-addon-landlock-run": "workspace:*" } } diff --git a/packages/bash/bash-sandbox/tsconfig.json b/packages/bash/bash-sandbox/tsconfig.json index fcd79e0296..7a7d67f0cb 100644 --- a/packages/bash/bash-sandbox/tsconfig.json +++ b/packages/bash/bash-sandbox/tsconfig.json @@ -14,6 +14,9 @@ { "path": "../../../vendor/cordis" }, + { + "path": "../../../native/landlock-run/packages/entry" + }, { "path": "../../util/brand" }, diff --git a/packages/examples/agent-spine-demo/package.json b/packages/examples/agent-spine-demo/package.json index a4b18b9c0c..b9f817d6e5 100644 --- a/packages/examples/agent-spine-demo/package.json +++ b/packages/examples/agent-spine-demo/package.json @@ -84,7 +84,7 @@ "@deepseek-ai/dsh-tool-tasks": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", "@deepseek-ai/dsh-workspace-context": "workspace:^", - "node-addon-landlock-run": "0.0.0-test.0", + "node-addon-landlock-run": "workspace:*", "cordis": "^4.0.0-rc.7" }, "dependencies": { diff --git a/packages/examples/agent-spine-demo/tsconfig.json b/packages/examples/agent-spine-demo/tsconfig.json index 6a0091a6f6..f245e9f4d0 100644 --- a/packages/examples/agent-spine-demo/tsconfig.json +++ b/packages/examples/agent-spine-demo/tsconfig.json @@ -17,6 +17,9 @@ { "path": "../../../vendor/schemastery" }, + { + "path": "../../../native/landlock-run/packages/entry" + }, { "path": "../../llm/llm" }, diff --git a/packages/sandbox/sandbox-local/package.json b/packages/sandbox/sandbox-local/package.json index 2683ab3654..f7004c4d5c 100644 --- a/packages/sandbox/sandbox-local/package.json +++ b/packages/sandbox/sandbox-local/package.json @@ -31,7 +31,7 @@ "cordis": "^4.0.0-rc.7" }, "dependencies": { - "node-addon-landlock-run": "0.0.0-test.0", + "node-addon-landlock-run": "workspace:*", "schemastery": "^3.18.0" }, "devDependencies": { diff --git a/packages/sandbox/sandbox-local/tests/landlock.e2e.ts b/packages/sandbox/sandbox-local/tests/landlock.e2e.ts index f5ecbc67f9..6e2faecc6b 100644 --- a/packages/sandbox/sandbox-local/tests/landlock.e2e.ts +++ b/packages/sandbox/sandbox-local/tests/landlock.e2e.ts @@ -10,7 +10,7 @@ import { launcherPath } from 'node-addon-landlock-run' import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local' /** - * Keyless backend integration through `confine()` and the registry `landlock-run` launcher, with + * Keyless backend integration through `confine()` and the workspace `landlock-run` launcher, with * bwrap forced off. Tests assert real world effects; consumer coverage lives in dsh-bash-sandbox. * Skips when the platform package or enforcing kernel is unavailable. HOME-based workspaces avoid * Landlock's wholesale `/tmp` grant, so workspace-write proves the workspace-root grant itself. diff --git a/packages/sandbox/sandbox-local/tests/packed-install.e2e.ts b/packages/sandbox/sandbox-local/tests/packed-install.e2e.ts index a032e1add7..caf0a32c68 100644 --- a/packages/sandbox/sandbox-local/tests/packed-install.e2e.ts +++ b/packages/sandbox/sandbox-local/tests/packed-install.e2e.ts @@ -7,20 +7,22 @@ import { fileURLToPath } from 'node:url' import { afterAll, beforeAll, describe, expect, it } from 'vitest' /** - * Keyless publish-path rehearsal. It packs the package and workspace peers, installs those exact - * tarballs in an external plain-Node consumer, and lets npm resolve the registry Landlock launcher - * plus its platform package. No tsx, path mapping, or workspace resolution can hide missing files, - * dependency errors, or lost executable modes. + * Keyless publish-path rehearsal. It packs the provider, its workspace peers, and the current + * repository's Landlock entry/platform packages, then installs those exact tarballs in an external + * plain-Node consumer. No registry copy, tsx, path mapping, or workspace resolution can hide + * missing files, dependency errors, or lost executable modes. * * The installed launcher must match the host architecture, remain executable, and either confine a * real process with bwrap disabled or fail closed on a non-enforcing kernel. Skips off Linux or - * before `pnpm run build`; launcher byte provenance belongs to its upstream release pipeline. + * before the harness and native packages are built. */ const packageDir = fileURLToPath(new URL('..', import.meta.url)) const repoRoot = fileURLToPath(new URL('../../../..', import.meta.url)) +const nativeDir = join(repoRoot, 'native/landlock-run') +const sourceLauncher = join(nativeDir, 'packages', `linux-${process.arch}`, 'bin', 'landlock-run') -/** The closure the consumer needs: the package and its transitive `@deepseek-ai` peers; the launcher family arrives from the registry. */ +/** The harness closure the consumer needs; native tarballs are packed through their mode-preserving release script. */ const WORKSPACE_CLOSURE = [ 'packages/sandbox/sandbox-local', 'packages/sandbox/sandbox', @@ -36,6 +38,8 @@ const E_MACHINE = { x64: 62, arm64: 183 }[process.arch as 'x64' | 'arm64'] const packable = process.platform === 'linux' && E_MACHINE !== undefined && existsSync(join(packageDir, 'lib', 'index.js')) + && existsSync(join(nativeDir, 'packages/entry/lib/index.js')) + && existsSync(sourceLauncher) let consumerDir = '' let workDir = '' @@ -57,7 +61,20 @@ describe.skipIf(!packable)('sandbox-local: packed-tarball distribution (publish- consumerDir = mkdtempSync(join(tmpdir(), 'dsh-packed-consumer-')) workDir = mkdtempSync(join(tmpdir(), 'dsh-packed-work-')) - // Pack each closure member with the exact bytes publish would upload. + const nativePackDest = join(packDest, 'native') + const nativePack = spawnSync('node', ['./scripts/pack-release.mjs', nativePackDest, '--current-platform-only'], { + cwd: nativeDir, + encoding: 'utf8', + timeout: 120_000, + }) + expect(nativePack.status, `native pack failed:\n${nativePack.stdout}\n${nativePack.stderr}`).toBe(0) + + const nativeTarballs = readFileSync(join(nativePackDest, 'publish-order.txt'), 'utf8') + .trim() + .split('\n') + .map(tarball => join(nativePackDest, tarball)) + + // Pack each harness closure member with the exact bytes publish would upload. const tarballs: string[] = [] for (const pkg of WORKSPACE_CLOSURE) { const pack = spawnSync('pnpm', ['pack', '--pack-destination', packDest], { @@ -69,6 +86,7 @@ describe.skipIf(!packable)('sandbox-local: packed-tarball distribution (publish- const lines = pack.stdout.trim().split('\n') tarballs.push(lines[lines.length - 1] as string) } + tarballs.push(...nativeTarballs) // Peer ranges resolve to the tarballs; Cordis is pinned to their peer range. Do not omit optional // dependencies because the launcher selects its OS/CPU package through one. @@ -124,12 +142,13 @@ describe.skipIf(!packable)('sandbox-local: packed-tarball distribution (publish- await Promise.all([consumerDir, workDir].filter(Boolean).map(dir => rm(dir, { recursive: true, force: true }))) }) - it('installs the registry launcher for this host: present, EXECUTABLE, right ELF arch', () => { + it('installs this checkout\'s launcher for the host: present, executable, byte-identical, and right ELF arch', () => { const installed = join(consumerDir, 'node_modules', `node-addon-landlock-run-linux-${process.arch}`, 'bin', 'landlock-run') expect(existsSync(installed), 'platform package missing from the installed tree').toBe(true) // A tarball or extraction step that strips the mode bit would leave the // probe failing exactly like a non-enforcing kernel — assert it apart. expect(() => { accessSync(installed, constants.X_OK) }, 'installed launcher is not executable').not.toThrow() + expect(readFileSync(installed), 'installed launcher bytes').toEqual(readFileSync(sourceLauncher)) expect(readFileSync(installed).readUInt16LE(18), 'ELF e_machine').toBe(E_MACHINE) }) diff --git a/packages/sandbox/sandbox-local/tsconfig.json b/packages/sandbox/sandbox-local/tsconfig.json index 608f0e9568..7a41ffc5fd 100644 --- a/packages/sandbox/sandbox-local/tsconfig.json +++ b/packages/sandbox/sandbox-local/tsconfig.json @@ -17,6 +17,9 @@ { "path": "../../../vendor/schemastery" }, + { + "path": "../../../native/landlock-run/packages/entry" + }, { "path": "../../llm/llm" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 01a5a2f64c..feedce51c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -851,6 +851,34 @@ importers: specifier: workspace:* version: link:../packages/context/workspace-context + native/landlock-run: + devDependencies: + '@types/node': + specifier: ^26.0.1 + version: 26.1.2 + node-addon-landlock-run: + specifier: workspace:* + version: link:packages/entry + tsx: + specifier: ^4.20.6 + version: 4.22.4 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + + native/landlock-run/packages/entry: + optionalDependencies: + node-addon-landlock-run-linux-arm64: + specifier: workspace:* + version: link:../linux-arm64 + node-addon-landlock-run-linux-x64: + specifier: workspace:* + version: link:../linux-x64 + + native/landlock-run/packages/linux-arm64: {} + + native/landlock-run/packages/linux-x64: {} + packages/acp/acp: dependencies: '@agentclientprotocol/sdk': @@ -986,8 +1014,8 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis node-addon-landlock-run: - specifier: 0.0.0-test.0 - version: 0.0.0-test.0 + specifier: workspace:* + version: link:../../../native/landlock-run/packages/entry packages/bash/pwsh-local: dependencies: @@ -1306,7 +1334,7 @@ importers: version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) vitest: specifier: ^4.1.8 - version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) devDependencies: '@deepseek-ai/dsh-client-runtime': specifier: workspace:^ @@ -2939,8 +2967,8 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis node-addon-landlock-run: - specifier: 0.0.0-test.0 - version: 0.0.0-test.0 + specifier: workspace:* + version: link:../../../native/landlock-run/packages/entry packages/examples/cli-demo: devDependencies: @@ -4219,8 +4247,8 @@ importers: packages/sandbox/sandbox-local: dependencies: node-addon-landlock-run: - specifier: 0.0.0-test.0 - version: 0.0.0-test.0 + specifier: workspace:* + version: link:../../../native/landlock-run/packages/entry schemastery: specifier: ^3.18.0 version: link:../../../vendor/schemastery @@ -5464,7 +5492,7 @@ importers: version: link:../loader-smoke vitest: specifier: ^4.1.8 - version: 4.1.8(@opentelemetry/api@1.9.0)(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 4.1.8(@opentelemetry/api@1.9.0)(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) devDependencies: '@deepseek-ai/dsh-invariants': specifier: workspace:^ @@ -9113,6 +9141,9 @@ packages: '@types/node@25.9.3': resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==} + '@types/node@26.1.2': + resolution: {integrity: sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==} + '@types/picomatch@3.0.2': resolution: {integrity: sha512-n0i8TD3UDB7paoMMxA3Y65vUncFJXjcUf7lQY7YyKGl6031FNjfsLs6pdLFCy2GNFxItPJG8GvvpbZc2skH7WA==} @@ -11011,22 +11042,6 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-addon-landlock-run-linux-arm64@0.0.0-test.0: - resolution: {integrity: sha512-oJsXcC33qKl9mWYx0n9YPJ2pUAoY39PoIX0Gx4lDrSCTEvENFrEaODAsQYNY+eEGpn9YMN7E+FOftvea3/1FqQ==} - engines: {node: '>=20'} - cpu: [arm64] - os: [linux] - - node-addon-landlock-run-linux-x64@0.0.0-test.0: - resolution: {integrity: sha512-eXvdfnH/UV55MTZzroKvM3CD68SP5OlCsuth908YOcJOnn0LPD5KJjmBz6ToDlBYjF52NNK62+g7TvmUWjbKWQ==} - engines: {node: '>=20'} - cpu: [x64] - os: [linux] - - node-addon-landlock-run@0.0.0-test.0: - resolution: {integrity: sha512-c5qopltRonjW6+VinXYMp4FVi9Sxf8eQEb/9G82EksQQ+JC4CDprv+ko5URWmtyZ3wD4GFdeRTyw1AG5yCwJhQ==} - engines: {node: '>=20'} - node-addon-native-custom-loader@0.1.4: resolution: {integrity: sha512-DreegO6EoC1JHWYBv3j8Miwp2Zl/CyBeNyoeyCbnEdjyYFEulR4Gcb3wj9fXF7KMDY0ZJ5MWwHcXP8GVNyScnA==} engines: {node: '>=20'} @@ -11815,6 +11830,9 @@ packages: undici-types@7.24.6: resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + undici@7.28.0: resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} engines: {node: '>=20.18.1'} @@ -14189,6 +14207,10 @@ snapshots: dependencies: undici-types: 7.24.6 + '@types/node@26.1.2': + dependencies: + undici-types: 8.3.0 + '@types/picomatch@3.0.2': {} '@types/prop-types@15.7.15': {} @@ -14344,13 +14366,13 @@ snapshots: optionalDependencies: vite: 8.0.16(@types/node@22.20.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0) - '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))': + '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.8 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0) '@vitest/pretty-format@4.1.8': dependencies: @@ -16476,17 +16498,6 @@ snapshots: node-addon-api@7.1.1: {} - node-addon-landlock-run-linux-arm64@0.0.0-test.0: - optional: true - - node-addon-landlock-run-linux-x64@0.0.0-test.0: - optional: true - - node-addon-landlock-run@0.0.0-test.0: - optionalDependencies: - node-addon-landlock-run-linux-arm64: 0.0.0-test.0 - node-addon-landlock-run-linux-x64: 0.0.0-test.0 - node-addon-native-custom-loader@0.1.4: {} node-addon-require-builtin-darwin-arm64@0.1.4: @@ -17398,6 +17409,8 @@ snapshots: undici-types@7.24.6: {} + undici-types@8.3.0: {} + undici@7.28.0: {} unicorn-magic@0.3.0: {} @@ -17533,7 +17546,7 @@ snapshots: tsx: 4.22.4 yaml: 2.9.0 - vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0): + vite@8.0.16(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -17541,7 +17554,7 @@ snapshots: rolldown: 1.0.3 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 25.9.3 + '@types/node': 26.1.2 esbuild: 0.28.1 fsevents: 2.3.3 jiti: 2.7.0 @@ -17635,10 +17648,10 @@ snapshots: transitivePeerDependencies: - msw - vitest@4.1.8(@opentelemetry/api@1.9.0)(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)): + vitest@4.1.8(@opentelemetry/api@1.9.0)(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.8 '@vitest/runner': 4.1.8 '@vitest/snapshot': 4.1.8 @@ -17655,7 +17668,7 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 @@ -17695,10 +17708,10 @@ snapshots: transitivePeerDependencies: - msw - vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)): + vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.8 '@vitest/runner': 4.1.8 '@vitest/snapshot': 4.1.8 @@ -17715,7 +17728,7 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index fec185b114..66510d89ec 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,10 @@ packages: - vendor/* - packages/*/* + # The Landlock launcher is developed with its harness consumers but keeps + # its native build and publication scripts under native/landlock-run. + - native/landlock-run + - native/landlock-run/packages/* # Product assemblies over the package tier; apps/cli owns the `dsh` bin. - apps/* - website @@ -46,14 +50,7 @@ allowBuilds: # restores the executable bit on node-pty's macOS spawn helper. '@deepseek-ai/dsh-pty-local@file:packages/pty/pty-local': true -# The Landlock launcher family is our own sibling-repo release, consumed -# fresh (hours old at each coordinated bump) — the release-age quarantine -# would block every such bump, so the family is exempted BY NAME, not by -# pinned version. minimumReleaseAgeExclude: - - node-addon-landlock-run - - node-addon-landlock-run-linux-arm64 - - node-addon-landlock-run-linux-x64 # Cordis release candidates are source-vendored and pinned in vendor/README.md # during the same-day sync that updates package manifests and the lockfile. - '@cordisjs/plugin-loader@1.0.0-rc.5' diff --git a/scripts/check-workspace-constraints.ts b/scripts/check-workspace-constraints.ts index 6bd613c2ea..32e2096aa9 100644 --- a/scripts/check-workspace-constraints.ts +++ b/scripts/check-workspace-constraints.ts @@ -15,6 +15,8 @@ const root = resolve(import.meta.dirname, '..') const workspaceGlobs = [ { dir: 'vendor', depth: 1 }, { dir: 'packages', depth: 2 }, + { dir: 'native', depth: 1 }, + { dir: 'native/landlock-run/packages', depth: 1 }, { dir: 'apps', depth: 1 }, ] as const const vendoredPackages = new Set([ @@ -28,6 +30,11 @@ const vendoredPackages = new Set([ '@cordisjs/plugin-hmr', '@cordisjs/plugin-logger-console', ]) +const publicLandlockPackages = new Set([ + 'node-addon-landlock-run', + 'node-addon-landlock-run-linux-arm64', + 'node-addon-landlock-run-linux-x64', +]) const localArtifactDirs = new Set(['node_modules']) const appPackageFiles: Readonly> = { @@ -55,6 +62,7 @@ interface PackageManifest { | undefined > files?: string[] + publishConfig?: { access?: string } peerDependencies?: Record devDependencies?: Record } @@ -71,6 +79,8 @@ function readJson(path: string): PackageManifest { const rootManifest = readJson(join(root, 'package.json')) const repositoryVersion = rootManifest.version +const landlockWorkspaceManifest = readJson(join(root, 'native/landlock-run/package.json')) +const landlockVersion = landlockWorkspaceManifest.version /** Repo-relative dirs holding a package.json, walked to the configured depth. */ function packageDirs(base: string, depth: number): string[] { @@ -161,8 +171,19 @@ function usesEmittedTreeDefaults(manifest: PackageManifest): boolean { function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] { const errors: string[] = [] const label = manifest.name ?? dir + const isLandlockPackageDir = dir.startsWith('native/landlock-run/packages/') + const isPublicLandlockPackage = isLandlockPackageDir + && manifest.name !== undefined + && publicLandlockPackages.has(manifest.name) - if (manifest.private !== true) { + if (isPublicLandlockPackage) { + if (manifest.private === true) { + errors.push(`${label}: published Landlock package must not set "private": true`) + } + if (manifest.publishConfig?.access !== 'public') { + errors.push(`${label}: published Landlock package must set publishConfig.access to "public"`) + } + } else if (manifest.private !== true) { errors.push(`${label}: package.json must set "private": true`) } @@ -187,6 +208,15 @@ function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] { } } + if (isLandlockPackageDir) { + if (!isPublicLandlockPackage) { + errors.push(`${label}: unexpected package in the public Landlock package family`) + } + if (manifest.version !== landlockVersion) { + errors.push(`${label}: package.json version must match Landlock workspace version ${landlockVersion ?? '(missing)'}`) + } + } + if (dir.startsWith('packages/') && manifest.name?.startsWith('@deepseek-ai/dsh-')) { const peer = manifest.peerDependencies?.cordis const dev = manifest.devDependencies?.cordis diff --git a/scripts/clean.spec.ts b/scripts/clean.spec.ts index 0a46764d9a..aada0667ef 100644 --- a/scripts/clean.spec.ts +++ b/scripts/clean.spec.ts @@ -18,10 +18,10 @@ function write(path: string, content = ''): void { writeFileSync(path, content) } -function addProject(root: string, path: string): void { +function addProject(root: string, path: string, outDir = 'lib/types'): void { write(join(root, 'tsconfig.json'), JSON.stringify({ files: [], references: [{ path }] })) write(join(root, path, 'tsconfig.json'), JSON.stringify({ - compilerOptions: { composite: true, outDir: 'lib/types' }, + compilerOptions: { composite: true, outDir }, include: ['src'], })) write(join(root, path, 'src/index.ts'), 'export {}\n') @@ -60,6 +60,18 @@ describe('RepositoryCleaner', () => { expect(existsSync(join(root, 'products/shell/lib'))).toBe(true) }) + it('removes the native Landlock entry output that emits directly to lib', async () => { + const root = fixture() + const entry = 'native/landlock-run/packages/entry' + addProject(root, entry, 'lib') + write(join(root, entry, 'lib/index.js')) + + await new RepositoryCleaner(root).clean() + + expect(existsSync(join(root, entry, 'lib'))).toBe(false) + expect(existsSync(join(root, entry, 'src/index.ts'))).toBe(true) + }) + it('refuses project outputs reached through a symlink outside the repository', async () => { const root = fixture() const externalProject = fixture() diff --git a/scripts/clean.ts b/scripts/clean.ts index fff158c458..1224fe8420 100644 --- a/scripts/clean.ts +++ b/scripts/clean.ts @@ -114,6 +114,7 @@ export class RepositoryCleaner { const outputs = new Set() const pending = [join(this.root, 'tsconfig.json')] const visited = new Set() + const nativeEntryOutput = join(this.root, 'native/landlock-run/packages/entry/lib') while (pending.length > 0) { const nextConfigPath = pending.pop() @@ -125,10 +126,14 @@ export class RepositoryCleaner { const parsed = parseConfig(configPath) if (parsed.options.outDir !== undefined) { const typesDirectory = resolve(parsed.options.outDir) - if (basename(typesDirectory) !== 'types') { + const outputDirectory = basename(typesDirectory) === 'types' + ? dirname(typesDirectory) + : typesDirectory === nativeEntryOutput + ? typesDirectory + : undefined + if (outputDirectory === undefined) { throw new Error(`clean: expected TypeScript outDir to end in /types: ${repositoryPath(this.root, typesDirectory)}`) } - const outputDirectory = dirname(typesDirectory) this.assertRepositoryTarget(outputDirectory) outputs.add(outputDirectory) } diff --git a/scripts/gen-third-party-notices.spec.ts b/scripts/gen-third-party-notices.spec.ts index f31cca6879..6db1f6ea8d 100644 --- a/scripts/gen-third-party-notices.spec.ts +++ b/scripts/gen-third-party-notices.spec.ts @@ -247,13 +247,13 @@ describe('isPermissive', () => { describe('manifestPatterns', () => { it('derives globs from the declared members, so a new member area is read', () => { - expect(manifestPatterns(['packages/*/*', 'tools/*'], ['packages/*'])).toEqual([ + expect(manifestPatterns(['packages/*/*', 'tools/*', 'native/landlock-run', 'native/landlock-run/packages/*'])).toEqual([ 'package.json', 'packages/*/*/package.json', 'tools/*/package.json', - 'examples/*/package.json', 'native/landlock-run/package.json', 'native/landlock-run/packages/*/package.json', + 'examples/*/package.json', ]) }) }) diff --git a/scripts/gen-third-party-notices.ts b/scripts/gen-third-party-notices.ts index 0d41953e4b..d7e0c26a5f 100644 --- a/scripts/gen-third-party-notices.ts +++ b/scripts/gen-third-party-notices.ts @@ -39,10 +39,7 @@ const DEV_ONLY_AREAS = [ 'native/', ] as const -/** - * First-party packages released from sibling repositories under the project's - * own license: reachable from workspace manifests but not third-party. - */ +/** First-party public native packages: reachable at runtime but not third-party. */ const FIRST_PARTY = new Set([ 'node-addon-landlock-run', 'node-addon-landlock-run-linux-arm64', @@ -119,16 +116,13 @@ function readManifest(rel: string): Manifest { * here, so a new member area (`tools/*`) is read the day it is declared. * @returns one glob per manifest-bearing location, repository-relative. */ -export function manifestPatterns(rootMembers: readonly string[], nativeMembers: readonly string[]): string[] { +export function manifestPatterns(rootMembers: readonly string[]): string[] { return [ 'package.json', ...rootMembers.map(member => `${member}/package.json`), // The demo leaves join the workspace through `examples/package.json`, so // their own manifests are members of nothing and no glob above reaches them. 'examples/*/package.json', - // `native/landlock-run` is a nested workspace with its own lock file. - 'native/landlock-run/package.json', - ...nativeMembers.map(member => `native/landlock-run/${member}/package.json`), ] } @@ -149,7 +143,7 @@ function workspaceMembers(rel: string): string[] { * would silently push dev-area manifests into the runtime tier. */ function loadWorkspaceManifests(): { manifests: Map; names: Set } { - const patterns = manifestPatterns(workspaceMembers('pnpm-workspace.yaml'), workspaceMembers('native/landlock-run/pnpm-workspace.yaml')) + const patterns = manifestPatterns(workspaceMembers('pnpm-workspace.yaml')) const manifests = new Map() const names = new Set() for (const pattern of patterns) { @@ -591,9 +585,9 @@ ${python.map(dep => `| [\`${dep.name}\`](${dep.repo}) | ${dep.license} | ${dep.r | --- | --- | --- | ${BUILD_TIME_TOOLS.map(tool => `| [\`${tool.name}\`](${tool.repo}) | ${tool.license} | ${tool.role} |`).join('\n')} -## First-party sibling releases +## First-party native packages -\`node-addon-landlock-run\` (and its platform packages) is released from a DeepSeek Harness sibling repository under BSD 3-Clause. It is listed here for completeness; it is first-party, not third-party. +\`node-addon-landlock-run\` (and its platform packages) is built and released from this repository under BSD 3-Clause. It is listed here for completeness; it is first-party, not third-party. ` } diff --git a/tsconfig.base.json b/tsconfig.base.json index 9ba9ba5d84..634464cb9b 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -38,6 +38,7 @@ "@cordisjs/plugin-timer": ["./vendor/timer/src"], "@cordisjs/plugin-hmr": ["./vendor/hmr/src"], "@cordisjs/plugin-logger-console": ["./vendor/logger-console/src"], + "node-addon-landlock-run": ["./native/landlock-run/packages/entry/src/index.ts"], "@deepseek-ai/dsh-invariants": ["./packages/support/invariants/src/index.ts"], "@deepseek-ai/dsh-typert-registry": ["./packages/typert/registry/src/index.ts"], "@deepseek-ai/dsh-typert-loader": ["./packages/typert/loader/src/index.ts"], diff --git a/tsconfig.host.json b/tsconfig.host.json index c13d480a46..f6b125339a 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -156,6 +156,7 @@ { "path": "./packages/bash/bash-env" }, { "path": "./packages/bash/pwsh-local" }, { "path": "./packages/bash/tool-pwsh" }, + { "path": "./native/landlock-run/packages/entry" }, { "path": "./packages/sandbox/sandbox" }, { "path": "./packages/sandbox/sandbox-local" }, { "path": "./packages/sandbox/sandbox-policy" }, From d3aa337c26806d14e45faf1319bb3d2ceada6cd5 Mon Sep 17 00:00:00 2001 From: Hypatia May Date: Thu, 6 Aug 2026 10:52:46 +0800 Subject: [PATCH 2/6] fix(landlock-run): close release integration gaps (review round 2) --- ...2026-07-27-dependabot-version-updates.i18n.yaml | 4 ++-- .../2026-07-27-dependabot-version-updates.md | 10 +++++----- .../2026-07-27-dependabot-version-updates.zh.md | 10 +++++----- ...6-07-30-generated-third-party-notices.i18n.yaml | 4 ++-- .../2026-07-30-generated-third-party-notices.md | 2 +- .../2026-07-30-generated-third-party-notices.zh.md | 2 +- .github/dependabot.yml | 14 -------------- THIRD_PARTY_NOTICES.md | 2 +- native/landlock-run/packages/entry/package.json | 5 +++++ .../landlock-run/packages/linux-arm64/package.json | 5 +++++ .../landlock-run/packages/linux-x64/package.json | 5 +++++ scripts/check-workspace-constraints.ts | 12 ++++++++++-- scripts/gen-third-party-notices.ts | 8 ++++---- 13 files changed, 46 insertions(+), 37 deletions(-) diff --git a/.agents/notes/implemented/process/2026-07-27-dependabot-version-updates.i18n.yaml b/.agents/notes/implemented/process/2026-07-27-dependabot-version-updates.i18n.yaml index 07c742c518..316c31771e 100644 --- a/.agents/notes/implemented/process/2026-07-27-dependabot-version-updates.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-27-dependabot-version-updates.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/process/2026-07-27-dependabot-version-updates.md -2026-07-27-dependabot-version-updates.md: 725649652c5b91ba4897d03b548b9aa5c3694c21 -2026-07-27-dependabot-version-updates.zh.md: 6400ba8ed94bf138fcece90e5d7ff82886d33ed1 +2026-07-27-dependabot-version-updates.md: 5d42563788d9f1e72da65c8e9750d6b1ecba06a5 +2026-07-27-dependabot-version-updates.zh.md: 4847059944e7e35de5719a6cbfd3d5b133467ccb diff --git a/.agents/notes/implemented/process/2026-07-27-dependabot-version-updates.md b/.agents/notes/implemented/process/2026-07-27-dependabot-version-updates.md index 725649652c..5d42563788 100644 --- a/.agents/notes/implemented/process/2026-07-27-dependabot-version-updates.md +++ b/.agents/notes/implemented/process/2026-07-27-dependabot-version-updates.md @@ -6,23 +6,23 @@ English | [中文](2026-07-27-dependabot-version-updates.zh.md) ## Problem -Maintained registry and GitHub Actions dependencies need a regular update path. Adopting every release immediately increases exposure to compromised releases and early regressions, while leaving updates entirely manual lets dependency drift accumulate. Vendored Cordis sources and independently locked workspaces also cannot be treated as one undifferentiated package tree. +Maintained registry and GitHub Actions dependencies need a regular update path. Adopting every release immediately increases exposure to compromised releases and early regressions, while leaving updates entirely manual lets dependency drift accumulate. Vendored Cordis sources cannot be treated like registry dependencies, and workspaces sharing one lockfile must be updated through the same package tree. ## Decision -The default branch carries [`.github/dependabot.yml`](../../../../.github/dependabot.yml) with weekly version-update checks for the root pnpm workspace, the independently locked `native/landlock-run` pnpm workspace, the `python/sdk` uv project, and GitHub Actions. Every entry sets `cooldown.default-days` to `30`, so a version release becomes eligible only after it is at least 30 days old and is proposed on the next weekly check. +The default branch carries [`.github/dependabot.yml`](../../../../.github/dependabot.yml) with weekly version-update checks for the root pnpm workspace, including `native/landlock-run`, the `python/sdk` uv project, and GitHub Actions. Every entry sets `cooldown.default-days` to `30`, so a version release becomes eligible only after it is at least 30 days old and is proposed on the next weekly check. The [in-repository Landlock release decision](2026-08-06-in-repository-landlock-release.md) owns the shared-workspace boundary. -The root pnpm version-update scan excludes `vendor/**`, whose source and manifests move only through the [vendoring procedure](../../../../vendor/README.md), and `native/landlock-run/**`, which its dedicated entry owns. GitHub applies `exclude-paths` only to version updates; a security pull request that touches a vendored manifest is replaced through the vendoring procedure instead of being merged as generated. Dependabot pull requests receive the repository's `cleanup` kind and `area/infra` area labels, run the normal pull-request checks, and remain subject to maintainer review; this automation does not merge them. +The root pnpm version-update scan excludes `vendor/**`, whose source and manifests move only through the [vendoring procedure](../../../../vendor/README.md). GitHub applies `exclude-paths` only to version updates; a security pull request that touches a vendored manifest is replaced through the vendoring procedure instead of being merged as generated. Dependabot pull requests receive the repository's `cleanup` kind and `area/infra` area labels, run the normal pull-request checks, and remain subject to maintainer review; this automation does not merge them. Repository settings enable dependency vulnerability alerts and Dependabot security updates. GitHub does not apply version-update cooldowns to those security updates, so security fixes remain eligible immediately. A generated pnpm security pull request can still fail the repository's lockfile release-age verification when dependency resolution selects unrelated fresh transitive versions; that pull request waits or is narrowed instead of weakening the policy. The repository's coordinated fresh-release exceptions are not copied into Dependabot's cooldown exclusions: automated version updates use the uniform 30-day wait, while an explicitly reviewed manual update can still follow its owning release procedure. -The pnpm entries keep both workspaces on their pinned pnpm 11 instead of introducing an automation-only downgrade. The current Dependabot updater installs the version requested by `packageManager` and reads both workspaces' lockfile format `9.0`; the provider-run update job remains the integration check. +The pnpm entry keeps the unified workspace on its pinned pnpm 11 instead of introducing an automation-only downgrade. The current Dependabot updater installs the version requested by the root `packageManager` and reads the root lockfile format `9.0`; the provider-run update job remains the integration check. ## Alternatives considered - **Immediate version updates.** Rejected because they remove the requested release-age quarantine and make the project an early consumer of every upstream release. - **Automatic merging after CI.** Rejected because dependency changes can alter runtime, build, and release behavior; the normal review decision remains part of accepting an update. -- **One recursive npm scan.** Rejected because it could admit vendored manifests or conflate the root and native lockfiles. Explicit exclusions and a dedicated native entry preserve their ownership boundaries. +- **A separate native npm scan.** Rejected because the Landlock manifests belong to the root workspace and lockfile; splitting their update would recreate an ownership boundary the package manager no longer has. The root scan excludes only vendored manifests. - **Renovate or a scheduled agent.** Both can propose aged updates, but Dependabot is the requested service and the repository's CI already recognizes its pull requests as an untrusted dependency source. - **Cooldown exemptions for coordinated fresh releases.** Rejected for the automated path because those releases require an explicit synchronization or model-catalog decision rather than a generic update proposal. diff --git a/.agents/notes/implemented/process/2026-07-27-dependabot-version-updates.zh.md b/.agents/notes/implemented/process/2026-07-27-dependabot-version-updates.zh.md index 6400ba8ed9..4847059944 100644 --- a/.agents/notes/implemented/process/2026-07-27-dependabot-version-updates.zh.md +++ b/.agents/notes/implemented/process/2026-07-27-dependabot-version-updates.zh.md @@ -6,23 +6,23 @@ Status: implemented ## 问题 -来自包注册表的依赖与 GitHub Actions 依赖都需要定期更新机制。每个新版本一经发布便立即采用,会增加受到遭入侵的版本和早期回归影响的风险;但完全依靠手动更新,又会导致依赖版本差距持续扩大。以源码形式纳入仓库的 Cordis 与各自维护独立锁文件的工作区,也不能不加区分地视为同一棵包树。 +来自包注册表的依赖与 GitHub Actions 依赖都需要定期更新机制。每个新版本一经发布便立即采用,会增加受到遭入侵的版本和早期回归影响的风险;但完全依靠手动更新,又会导致依赖版本差距持续扩大。以源码形式纳入仓库的 Cordis 不能当作注册表依赖处理,而共用一份锁文件的工作区必须通过同一棵包树更新。 ## 决策 -默认分支包含 [`.github/dependabot.yml`](../../../../.github/dependabot.yml),其中为根 pnpm 工作区、独立维护锁文件的 `native/landlock-run` pnpm 工作区、`python/sdk` uv 项目和 GitHub Actions 配置了每周一次的版本更新检查。每个更新项都将 `cooldown.default-days` 设为 `30`,因此某个版本只有在发布至少 30 天后才符合更新条件,并会在下一次每周检查时生成更新提案。 +默认分支包含 [`.github/dependabot.yml`](../../../../.github/dependabot.yml),其中为包含 `native/landlock-run` 的根 pnpm 工作区、`python/sdk` uv 项目和 GitHub Actions 配置了每周一次的版本更新检查。每个更新项都将 `cooldown.default-days` 设为 `30`,因此某个版本只有在发布至少 30 天后才符合更新条件,并会在下一次每周检查时生成更新提案。[仓库内 Landlock 发布决策](2026-08-06-in-repository-landlock-release.md)负责共享工作区边界。 -根 pnpm 工作区的版本更新扫描排除 `vendor/**`,其中的源码和 manifest(元数据清单)只能通过 [vendoring 流程](../../../../vendor/README.md)变更;扫描还排除由专用更新项负责的 `native/landlock-run/**`。GitHub 仅将 `exclude-paths` 用于版本更新;如果安全更新 PR(Pull Request)涉及随源码纳入仓库的 manifest,则改由 vendoring 流程处理,以替代自动生成的 PR,而不会将其原样合并。Dependabot PR 会获得仓库的 `cleanup` 类型标签和 `area/infra` 区域标签,运行常规 PR 检查,并且仍须由维护者评审;该自动化不会合并这些 PR。 +根 pnpm 工作区的版本更新扫描排除 `vendor/**`,其中的源码和 manifest(元数据清单)只能通过 [vendoring 流程](../../../../vendor/README.md)变更。GitHub 仅将 `exclude-paths` 用于版本更新;如果安全更新 PR(Pull Request)涉及随源码纳入仓库的 manifest,则改由 vendoring 流程处理,以替代自动生成的 PR,而不会将其原样合并。Dependabot PR 会获得仓库的 `cleanup` 类型标签和 `area/infra` 区域标签,运行常规 PR 检查,并且仍须由维护者评审;该自动化不会合并这些 PR。 仓库设置已启用依赖项漏洞警报和 Dependabot 安全更新。GitHub 不会对这些安全更新应用版本更新冷却期,因此安全修复仍可立即进入更新流程。如果依赖解析还选中了其他刚发布的传递依赖,pnpm 安全更新 PR 仍可能无法通过仓库的锁文件发布时长校验;此类 PR 应等待隔离期结束或缩小更新范围,不得因此放宽政策。仓库为协调刚发布版本而设置的例外,不会纳入 Dependabot 的冷却期排除项:自动版本更新统一等待 30 天;经过明确评审的手动更新仍可遵循相应的发布流程。 -pnpm 更新项让两个工作区继续使用已固定的 pnpm 11,不会仅为了自动化而降级版本。当前 Dependabot 更新器会安装 `packageManager` 指定的版本,并读取两个工作区使用的 `9.0` 锁文件格式;由提供方运行的更新任务仍作为集成检查。 +pnpm 更新项让统一工作区继续使用已固定的 pnpm 11,不会仅为了自动化而降级版本。当前 Dependabot 更新器会安装根 `packageManager` 指定的版本,并读取根锁文件的 `9.0` 格式;由提供方运行的更新任务仍作为集成检查。 ## 考虑过的替代方案 - **立即进行版本更新。** 不采用,因为这会取消所要求的版本发布后隔离期,使项目在每个上游版本的发布初期就采用该版本。 - **CI 通过后自动合并。** 不采用,因为依赖变更可能改变运行时、构建和发布行为;是否接受更新仍须经过常规评审决策。 -- **使用一次递归 npm 扫描。** 不采用,因为它可能将随源码纳入仓库的 manifest 纳入更新范围,或混淆根工作区与 native 工作区的锁文件。显式排除项和专用 native 更新项可维持各自的归属边界。 +- **为 native 配置独立的 npm 扫描。** 不采用,因为 Landlock manifest 属于根工作区和根锁文件;拆分更新会重建一个包管理器已不存在的归属边界。根扫描仅排除随源码纳入的 manifest。 - **Renovate 或定期运行的 agent(智能体)。** 二者都能为发布已满一定时长的版本提出更新,但所要求的服务是 Dependabot,而且仓库 CI 已将其 PR 视为不可信的依赖来源。 - **为需协调的刚发布版本设置冷却期豁免。** 自动化路径不采用,因为此类版本需要明确的同步决策或模型目录决策,不能由通用更新提案代替。 diff --git a/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.i18n.yaml b/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.i18n.yaml index d65dae2802..afe8cdba57 100644 --- a/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.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/process/2026-07-30-generated-third-party-notices.md -2026-07-30-generated-third-party-notices.md: e480954d29d5dc09ef8ecd4069059a1f0c8b1043 -2026-07-30-generated-third-party-notices.zh.md: 78ba7250e797c57048078d1b4f62b7a9a5d9d561 +2026-07-30-generated-third-party-notices.md: 6a95953bc551cb38ca1d9aaa51a2041deadd1b08 +2026-07-30-generated-third-party-notices.zh.md: 9d48d3b39de76d5f4fbc1e2e9593a933b08583c8 diff --git a/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.md b/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.md index e480954d29..6a95953bc5 100644 --- a/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.md +++ b/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.md @@ -24,7 +24,7 @@ The file discloses **direct** dependencies only. The complete npm closure with p The runtime tier deliberately covers **every mountable plugin**, not just what the CLI, Web UI, and Python runtime load by default. `scripts/install.sh` installs the repository itself, so a user's `cordis.yml` can mount any plugin package; `@modelcontextprotocol/sdk` and the OpenTelemetry packages reach real users even though no default assembly imports them. Under-disclosure is the costly direction for a legal notice. -The manifest set is derived from the `packages:` members each `pnpm-workspace.yaml` declares — the root one and the nested Landlock workspace's — so a new member area is read the day it is declared rather than the day someone remembers to extend a list. License and repository metadata come from the installed pnpm stores, both the root one and the Landlock workspace's, so the generator requires an installed tree and fails loud when a package resolves to neither, rather than emitting an empty cell. `OVERRIDES` carries the packages whose published manifest cannot answer — Rust-built npm bins that omit `license`, and the `modelcontextprotocol/servers` packages whose repository is mid MIT→Apache-2.0 relicensing, so their effective terms are per-contribution. A runtime dependency whose license is not on the permissive list is a hard error: shipping copyleft is a distribution decision, not something a regenerated table may absorb silently. Vendored packages are cross-checked against `vendor/README.md` and rejected if any is not MIT, and `pnpm-workspace.yaml`'s `patchedDependencies` are listed under the runtime table because pnpm applies those patches at install time — shipped artifacts carry modified copies of `@earendil-works/pi-tui` and `node-pty`, and the patch files are the record of what changed. +The manifest set is derived from the `packages:` members the root `pnpm-workspace.yaml` declares, including the Landlock workspace and its public packages, so a new member area is read the day it is declared rather than the day someone remembers to extend a list. License and repository metadata come from the root workspace's installed pnpm store and package-local link farms, so the generator requires an installed tree and fails loud when a package resolves to neither, rather than emitting an empty cell. `OVERRIDES` carries the packages whose published manifest cannot answer — Rust-built npm bins that omit `license`, and the `modelcontextprotocol/servers` packages whose repository is mid MIT→Apache-2.0 relicensing, so their effective terms are per-contribution. A runtime dependency whose license is not on the permissive list is a hard error: shipping copyleft is a distribution decision, not something a regenerated table may absorb silently. Vendored packages are cross-checked against `vendor/README.md` and rejected if any is not MIT, and `pnpm-workspace.yaml`'s `patchedDependencies` are listed under the runtime table because pnpm applies those patches at install time — shipped artifacts carry modified copies of `@earendil-works/pi-tui` and `node-pty`, and the patch files are the record of what changed. ## Testing diff --git a/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.zh.md b/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.zh.md index 78ba7250e7..9d48d3b39d 100644 --- a/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.zh.md +++ b/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.zh.md @@ -24,7 +24,7 @@ Status: implemented 运行时层刻意覆盖**所有可挂载的插件**,而不止 CLI、Web UI 与 Python 运行时默认加载的那些。`scripts/install.sh` 安装的就是仓库本身,用户的 `cordis.yml` 可以挂载任何插件包;`@modelcontextprotocol/sdk` 与 OpenTelemetry 系列即使没有任何默认装配引入,也会触达真实用户。对法务披露而言,披露不足才是代价更高的那个方向。 -清单集合由两个 `pnpm-workspace.yaml`——根工作区与嵌套的 Landlock 工作区——各自声明的 `packages:` 成员派生,因此新增成员区域在声明当天就会被读取,而不必等谁想起来去补一份列表。许可证与仓库地址取自已安装的 pnpm store,根 store 与 Landlock 工作区的 store 都会查;某个包两处都解析不到时直接失败,而不是留下空单元格。`OVERRIDES` 收录已发布清单答不上来的包:用 Rust 构建、发布时省略 `license` 字段的 npm 可执行包,以及 `modelcontextprotocol/servers` 系列——该仓库正处在 MIT 向 Apache-2.0 的重新许可过程中,实际条款按贡献逐条而定。运行时依赖的许可证若不在宽松清单内即为硬失败:交付 copyleft 是一项分发决策,不该被一次重新生成悄悄吸收。被源码收编的包会与 `vendor/README.md` 交叉核对,出现非 MIT 即报错;`pnpm-workspace.yaml` 的 `patchedDependencies` 列在运行时表格之后,因为 pnpm 在安装期就会打上这些补丁——交付产物携带的是改动过的 `@earendil-works/pi-tui` 与 `node-pty`,补丁文件本身就是改动的完整记录。 +清单集合由根 `pnpm-workspace.yaml` 声明的 `packages:` 成员派生,其中包括 Landlock 工作区及其公开包,因此新增成员区域在声明当天就会被读取,而不必等谁想起来去补一份列表。许可证与仓库地址取自根工作区已安装的 pnpm store 和包本地链接场;某个包两处都解析不到时直接失败,而不是留下空单元格。`OVERRIDES` 收录已发布清单答不上来的包:用 Rust 构建、发布时省略 `license` 字段的 npm 可执行包,以及 `modelcontextprotocol/servers` 系列——该仓库正处在 MIT 向 Apache-2.0 的重新许可过程中,实际条款按贡献逐条而定。运行时依赖的许可证若不在宽松清单内即为硬失败:交付 copyleft 是一项分发决策,不该被一次重新生成悄悄吸收。被源码收编的包会与 `vendor/README.md` 交叉核对,出现非 MIT 即报错;`pnpm-workspace.yaml` 的 `patchedDependencies` 列在运行时表格之后,因为 pnpm 在安装期就会打上这些补丁——交付产物携带的是改动过的 `@earendil-works/pi-tui` 与 `node-pty`,补丁文件本身就是改动的完整记录。 ## Testing diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 81052d7c08..524d7912e2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,20 +6,6 @@ updates: exclude-paths: # Vendored Cordis sources follow vendor/README.md instead of registry updates. - "vendor/**" - # This independently locked pnpm workspace has its own update entry below. - - "native/landlock-run/**" - schedule: - interval: "cron" - cronjob: "0 4 * * *" - timezone: "Asia/Shanghai" - cooldown: - default-days: 30 - labels: - - "cleanup" - - "area/infra" - - - package-ecosystem: "npm" - directory: "/native/landlock-run" schedule: interval: "cron" cronjob: "0 4 * * *" diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index aa73dba8d9..6ff3f6f8ea 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -7,7 +7,7 @@ DeepSeek Harness is licensed under [BSD 3-Clause](LICENSE). It depends on the th This file lists **direct** dependencies declared by the workspace. It is generated from the workspace manifests by `scripts/gen-third-party-notices.ts`: a pre-commit hook regenerates it whenever a staged file changes one of its inputs, and `scripts/gen-third-party-notices.spec.ts` asserts in the test lane that the committed bytes match. Deleting a manifest runs no hook, so that case is caught by the assertion instead. Run `pnpm run verify-third-party-notices` for the standalone check. -The complete npm transitive closure, with exact pinned versions, is recorded in [`pnpm-lock.yaml`](pnpm-lock.yaml) — inspect it with `pnpm licenses list`. The Python closure is recorded in [`python/sdk/uv.lock`](python/sdk/uv.lock), and the Landlock launcher workspace keeps its own in [`native/landlock-run/pnpm-lock.yaml`](native/landlock-run/pnpm-lock.yaml). +The complete npm transitive closure, including the Landlock launcher workspace, is recorded with exact pinned versions in [`pnpm-lock.yaml`](pnpm-lock.yaml) — inspect it with `pnpm licenses list`. The Python closure is recorded separately in [`python/sdk/uv.lock`](python/sdk/uv.lock). ## Vendored source (`vendor/`) diff --git a/native/landlock-run/packages/entry/package.json b/native/landlock-run/packages/entry/package.json index f05e81f06b..56345b2847 100644 --- a/native/landlock-run/packages/entry/package.json +++ b/native/landlock-run/packages/entry/package.json @@ -3,6 +3,11 @@ "version": "0.0.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", + "repository": { + "type": "git", + "url": "git+https://github.com/deepseek-harness/deepseek-harness.git", + "directory": "native/landlock-run/packages/entry" + }, "main": "lib/index.js", "types": "lib/index.d.ts", "exports": { diff --git a/native/landlock-run/packages/linux-arm64/package.json b/native/landlock-run/packages/linux-arm64/package.json index 0067f77c8b..af5467cead 100644 --- a/native/landlock-run/packages/linux-arm64/package.json +++ b/native/landlock-run/packages/linux-arm64/package.json @@ -2,6 +2,11 @@ "name": "node-addon-landlock-run-linux-arm64", "version": "0.0.1", "description": "Prebuilt landlock-run Landlock launcher binary for linux-arm64 (static musl) — resolved as a file path by node-addon-landlock-run, never imported", + "repository": { + "type": "git", + "url": "git+https://github.com/deepseek-harness/deepseek-harness.git", + "directory": "native/landlock-run/packages/linux-arm64" + }, "os": [ "linux" ], diff --git a/native/landlock-run/packages/linux-x64/package.json b/native/landlock-run/packages/linux-x64/package.json index 8ea60b636c..375d05332a 100644 --- a/native/landlock-run/packages/linux-x64/package.json +++ b/native/landlock-run/packages/linux-x64/package.json @@ -2,6 +2,11 @@ "name": "node-addon-landlock-run-linux-x64", "version": "0.0.1", "description": "Prebuilt landlock-run Landlock launcher binary for linux-x64 (static musl) — resolved as a file path by node-addon-landlock-run, never imported", + "repository": { + "type": "git", + "url": "git+https://github.com/deepseek-harness/deepseek-harness.git", + "directory": "native/landlock-run/packages/linux-x64" + }, "os": [ "linux" ], diff --git a/scripts/check-workspace-constraints.ts b/scripts/check-workspace-constraints.ts index 32e2096aa9..0a446d77f8 100644 --- a/scripts/check-workspace-constraints.ts +++ b/scripts/check-workspace-constraints.ts @@ -35,6 +35,7 @@ const publicLandlockPackages = new Set([ 'node-addon-landlock-run-linux-arm64', 'node-addon-landlock-run-linux-x64', ]) +const repositoryUrl = 'git+https://github.com/deepseek-harness/deepseek-harness.git' const localArtifactDirs = new Set(['node_modules']) const appPackageFiles: Readonly> = { @@ -63,6 +64,7 @@ interface PackageManifest { > files?: string[] publishConfig?: { access?: string } + repository?: { type?: string; url?: string; directory?: string } peerDependencies?: Record devDependencies?: Record } @@ -89,12 +91,12 @@ function packageDirs(base: string, depth: number): string[] { .filter(entry => entry.isDirectory()) .filter(entry => !localArtifactDirs.has(entry.name)) .filter(entry => existsSync(join(root, base, entry.name, 'package.json'))) - .map(entry => join(base, entry.name)) + .map(entry => `${base}/${entry.name}`) } return readdirSync(join(root, base), { withFileTypes: true }) .filter(entry => entry.isDirectory()) .filter(entry => !localArtifactDirs.has(entry.name)) - .flatMap(group => packageDirs(join(base, group.name), depth - 1)) + .flatMap(group => packageDirs(`${base}/${group.name}`, depth - 1)) } function workspaceManifests(): WorkspaceManifest[] { @@ -183,6 +185,12 @@ function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] { if (manifest.publishConfig?.access !== 'public') { errors.push(`${label}: published Landlock package must set publishConfig.access to "public"`) } + const expectedDirectory = dir + if (manifest.repository?.type !== 'git' + || manifest.repository.url !== repositoryUrl + || manifest.repository.directory !== expectedDirectory) { + errors.push(`${label}: published Landlock package repository must use ${repositoryUrl} with directory ${expectedDirectory} for trusted publishing`) + } } else if (manifest.private !== true) { errors.push(`${label}: package.json must set "private": true`) } diff --git a/scripts/gen-third-party-notices.ts b/scripts/gen-third-party-notices.ts index d7e0c26a5f..e56cce670a 100644 --- a/scripts/gen-third-party-notices.ts +++ b/scripts/gen-third-party-notices.ts @@ -191,8 +191,8 @@ export function virtualManifest(virtual: string, name: string): VirtualManifest function installedMetadata(name: string): { license: string; repo: string } { const override = OVERRIDES[name] let manifest: (Manifest & { license?: string; repository?: string | { url?: string }; homepage?: string }) | undefined - // The nested Landlock workspace installs into its own store, so a package - // only that workspace depends on is unreachable from the root one. + // Workspace-local link farms can expose a dependency that is not linked at + // the repository root; both are backed by the root workspace's lockfile. for (const store of ['node_modules', 'native/landlock-run/node_modules']) { const direct = resolve(root, store, name, 'package.json') if (existsSync(direct)) { @@ -208,7 +208,7 @@ function installedMetadata(name: string): { license: string; repo: string } { const rawRepo = typeof manifest?.repository === 'string' ? manifest.repository : manifest?.repository?.url ?? manifest?.homepage const repo = override?.repo ?? normalizeRepo(rawRepo) if (license === undefined || repo === undefined) { - throw new Error(`gen-third-party-notices: cannot resolve ${license === undefined ? 'license' : 'repository'} for ${name}; run \`pnpm install\` (or, for a Landlock-only dependency, \`pnpm --dir native/landlock-run install\`), or add an OVERRIDES entry.`) + throw new Error(`gen-third-party-notices: cannot resolve ${license === undefined ? 'license' : 'repository'} for ${name}; run \`pnpm install\`, or add an OVERRIDES entry.`) } return { license, repo } } @@ -544,7 +544,7 @@ DeepSeek Harness is licensed under [BSD 3-Clause](LICENSE). It depends on the th This file lists **direct** dependencies declared by the workspace. It is generated from the workspace manifests by \`scripts/gen-third-party-notices.ts\`: a pre-commit hook regenerates it whenever a staged file changes one of its inputs, and \`scripts/gen-third-party-notices.spec.ts\` asserts in the test lane that the committed bytes match. Deleting a manifest runs no hook, so that case is caught by the assertion instead. Run \`pnpm run verify-third-party-notices\` for the standalone check. -The complete npm transitive closure, with exact pinned versions, is recorded in [\`pnpm-lock.yaml\`](pnpm-lock.yaml) — inspect it with \`pnpm licenses list\`. The Python closure is recorded in [\`python/sdk/uv.lock\`](python/sdk/uv.lock), and the Landlock launcher workspace keeps its own in [\`native/landlock-run/pnpm-lock.yaml\`](native/landlock-run/pnpm-lock.yaml). +The complete npm transitive closure, including the Landlock launcher workspace, is recorded with exact pinned versions in [\`pnpm-lock.yaml\`](pnpm-lock.yaml) — inspect it with \`pnpm licenses list\`. The Python closure is recorded separately in [\`python/sdk/uv.lock\`](python/sdk/uv.lock). ## Vendored source (\`vendor/\`) From 32864c026cbfc7a36d875d1bee87b8c28bba0a78 Mon Sep 17 00:00:00 2001 From: Hypatia May Date: Thu, 6 Aug 2026 11:11:57 +0800 Subject: [PATCH 3/6] fix(clean): remove native build state (review round 3) --- scripts/clean.spec.ts | 4 +++- scripts/clean.ts | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/clean.spec.ts b/scripts/clean.spec.ts index aada0667ef..c724453283 100644 --- a/scripts/clean.spec.ts +++ b/scripts/clean.spec.ts @@ -60,16 +60,18 @@ describe('RepositoryCleaner', () => { expect(existsSync(join(root, 'products/shell/lib'))).toBe(true) }) - it('removes the native Landlock entry output that emits directly to lib', async () => { + it('removes the native Landlock entry output and solution build info', async () => { const root = fixture() const entry = 'native/landlock-run/packages/entry' addProject(root, entry, 'lib') write(join(root, entry, 'lib/index.js')) + write(join(root, 'native/landlock-run/tsconfig.tsbuildinfo')) await new RepositoryCleaner(root).clean() expect(existsSync(join(root, entry, 'lib'))).toBe(false) expect(existsSync(join(root, entry, 'src/index.ts'))).toBe(true) + expect(existsSync(join(root, 'native/landlock-run/tsconfig.tsbuildinfo'))).toBe(false) }) it('refuses project outputs reached through a symlink outside the repository', async () => { diff --git a/scripts/clean.ts b/scripts/clean.ts index 1224fe8420..68e4ff4e71 100644 --- a/scripts/clean.ts +++ b/scripts/clean.ts @@ -72,6 +72,11 @@ export class RepositoryCleaner { for (const entry of await readdir(this.root, { withFileTypes: true })) { if (entry.isFile() && entry.name.endsWith('.tsbuildinfo')) targets.add(join(this.root, entry.name)) } + await this.addIfPresent( + targets, + join(this.root, 'native/landlock-run/tsconfig.tsbuildinfo'), + canonicalRoot, + ) // The root project-reference graph is the source of truth for live build targets. // Each emitting project declares lib/types as outDir; its parent lib also owns From 10c1d77a4f3842812293d138a4e447356149ab5b Mon Sep 17 00:00:00 2001 From: Hypatia May Date: Thu, 6 Aug 2026 13:50:48 +0800 Subject: [PATCH 4/6] fix(landlock-run): address release review feedback --- .../implemented/feature/2026-07-06-sandbox.i18n.yaml | 4 ++-- .../notes/implemented/feature/2026-07-06-sandbox.md | 2 +- .../notes/implemented/feature/2026-07-06-sandbox.zh.md | 2 +- .github/workflows/landlock-run-release.yml | 10 ++++++++-- native/landlock-run/docs/release.md | 2 +- packages/bash/bash-sandbox/tests/landlock.e2e.ts | 6 +++--- .../sandbox/sandbox-local/tests/packed-install.e2e.ts | 6 ++++-- 7 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml b/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml index 5f8dfa4e65..b927bf9f72 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.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-07-06-sandbox.md -2026-07-06-sandbox.md: 69a3f1bd181bc06d9a176fa45b1e091991cfa682 -2026-07-06-sandbox.zh.md: eeca55b61da24df215f7a9b7ba8dbf9ab2387f20 +2026-07-06-sandbox.md: de00453eace87ef89e7e05bfe20e1ff956ee4d19 +2026-07-06-sandbox.zh.md: db84e9b3872fb5807720c75310c9ee58f2b9fdb7 diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.md b/.agents/notes/implemented/feature/2026-07-06-sandbox.md index 69a3f1bd18..de00453eac 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.md +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.md @@ -128,7 +128,7 @@ Each phase gets its full design when picked up, validated against the code at th - **Second consumer** — `subagent-acp` optionally confines child agents (per-call policy; unconfined default — a child agent must write its own persistence). - **More environments** — an environment-coherent capability group example (e.g. bash+fs against one container). -- **Windows chain** — `PLATFORM_CHAINS.win32` is reserved and empty (fail-closed); filling it means a confinement runner from the AppContainer/restricted-token family, shipped from its own repository on the `node-addon-landlock-run` template, plus its profile dialect, denial signatures, and runner-failure rules. Wrapping the third-party landstrip runner instead was [considered and rejected](../../rejected/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md) — not battle-tested enough for a security invariant. +- **Windows chain** — `PLATFORM_CHAINS.win32` is reserved and empty (fail-closed); filling it means a confinement runner from the AppContainer/restricted-token family, shipped from the main repository under `native/` following the `node-addon-landlock-run` template, plus its profile dialect, denial signatures, and runner-failure rules. Wrapping the third-party landstrip runner instead was [considered and rejected](../../rejected/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md) — not battle-tested enough for a security invariant. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md b/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md index eeca55b61d..db84e9b387 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md @@ -128,7 +128,7 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自 seam 层 - **第二个消费方**——`subagent-acp` 可选地约束子 agent(按调用策略;默认无约束——子 agent 必须写入自己的持久化)。 - **更多环境**——环境一致的能力组示例(如 bash+fs 对一个容器)。 -- **Windows 链**——`PLATFORM_CHAINS.win32` 保留为空(失败关闭);填充它意味着来自 AppContainer/restricted-token 家族的约束 runner,从其自己的仓库按 `node-addon-landlock-run` 模板交付,加上其 profile 方言、拒绝签名和 runner 失败规则。改为包装第三方 landstrip runner 的方案[经考虑后已驳回](../../rejected/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md)——它所经受的实战检验还不足以承载安全不变式。 +- **Windows 链**——`PLATFORM_CHAINS.win32` 保留为空(失败关闭);填充它意味着来自 AppContainer/restricted-token 家族的约束 runner,由主仓库在 `native/` 下按 `node-addon-landlock-run` 模板交付,加上其 profile 方言、拒绝签名和 runner 失败规则。改为包装第三方 landstrip runner 的方案[经考虑后已驳回](../../rejected/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md)——它所经受的实战检验还不足以承载安全不变式。 ## 曾考虑的替代方案 diff --git a/.github/workflows/landlock-run-release.yml b/.github/workflows/landlock-run-release.yml index dca6c9eed1..c69ebbe5ee 100644 --- a/.github/workflows/landlock-run-release.yml +++ b/.github/workflows/landlock-run-release.yml @@ -158,6 +158,14 @@ jobs: name: npm-tarballs path: native/landlock-run/dist/npm + - name: Configure npm token fallback + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + if [[ -n "$NPM_TOKEN" ]]; then + echo "NODE_AUTH_TOKEN=$NPM_TOKEN" >> "$GITHUB_ENV" + fi + - name: Publish tarballs run: | version="${GITHUB_REF#refs/tags/landlock-run-v}" @@ -166,5 +174,3 @@ jobs: while IFS= read -r tarball; do npm publish "dist/npm/${tarball}" --access public "${tag_args[@]}" done < dist/npm/publish-order.txt - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/native/landlock-run/docs/release.md b/native/landlock-run/docs/release.md index a95cffd47f..353a489c2c 100644 --- a/native/landlock-run/docs/release.md +++ b/native/landlock-run/docs/release.md @@ -45,7 +45,7 @@ Use the main repository's `Landlock Run Release` workflow so every binary is bui 2. Create and push the `landlock-run-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). It 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). 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`. 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)): diff --git a/packages/bash/bash-sandbox/tests/landlock.e2e.ts b/packages/bash/bash-sandbox/tests/landlock.e2e.ts index 0c5cfbe563..7579292fe1 100644 --- a/packages/bash/bash-sandbox/tests/landlock.e2e.ts +++ b/packages/bash/bash-sandbox/tests/landlock.e2e.ts @@ -13,14 +13,14 @@ import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local' /** * KEYLESS consumer-integration proof: the REAL `LocalSandboxProvider` (bwrap - * rung forced off, so the npm-distributed `landlock-run` confines) underneath the + * rung forced off, so the workspace `landlock-run` launcher confines) underneath the * REAL `SandboxBashExecutor`, driven through the executor's public run/start * paths. Verifies the WORLD (files exist or don't) plus the stamped result * facts; the backend-only confinement proofs live with * `@deepseek-ai/dsh-sandbox-local`. * - * Self-skips when the running kernel does not enforce Landlock; the - * launcher binary itself arrives with `pnpm install` (`node-addon-landlock-run`). + * Self-skips when the running kernel does not enforce Landlock. CI builds the launcher from + * `native/landlock-run` before running this file. */ const probe = spawnSync(launcherPath(), ['--probe'], { timeout: 5_000, encoding: 'utf8' }) diff --git a/packages/sandbox/sandbox-local/tests/packed-install.e2e.ts b/packages/sandbox/sandbox-local/tests/packed-install.e2e.ts index caf0a32c68..612751e6da 100644 --- a/packages/sandbox/sandbox-local/tests/packed-install.e2e.ts +++ b/packages/sandbox/sandbox-local/tests/packed-install.e2e.ts @@ -9,8 +9,10 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest' /** * Keyless publish-path rehearsal. It packs the provider, its workspace peers, and the current * repository's Landlock entry/platform packages, then installs those exact tarballs in an external - * plain-Node consumer. No registry copy, tsx, path mapping, or workspace resolution can hide - * missing files, dependency errors, or lost executable modes. + * plain-Node consumer. The host launcher comes from the exact local tarballs, so no registry copy, + * tsx, path mapping, or workspace resolution can hide missing files, dependency errors, or lost + * executable modes. npm may still query registry metadata for an incompatible optional platform + * package that cannot supply the host launcher. * * The installed launcher must match the host architecture, remain executable, and either confine a * real process with bwrap disabled or fail closed on a non-enforcing kernel. Skips off Linux or From 22c70870742bd69590863c769a5beee684bf8e77 Mon Sep 17 00:00:00 2001 From: Hypatia May Date: Thu, 6 Aug 2026 14:41:17 +0800 Subject: [PATCH 5/6] fix(landlock-run): publish under deepseek scope --- .../feature/2026-07-06-sandbox.i18n.yaml | 4 ++-- .../implemented/feature/2026-07-06-sandbox.md | 2 +- .../feature/2026-07-06-sandbox.zh.md | 2 +- ...2-win32-in-process-folder-dialog.i18n.yaml | 4 ++-- ...26-08-02-win32-in-process-folder-dialog.md | 2 +- ...08-02-win32-in-process-folder-dialog.zh.md | 2 +- ...6-in-repository-landlock-release.i18n.yaml | 4 ++-- ...26-08-06-in-repository-landlock-release.md | 14 ++++++----- ...08-06-in-repository-landlock-release.zh.md | 14 ++++++----- .github/workflows/landlock-run-release.yml | 6 ++--- .github/workflows/landlock-run.yml | 4 ++-- AGENTS.md | 2 +- THIRD_PARTY_NOTICES.md | 2 +- native/landlock-run/README.i18n.yaml | 4 ++-- native/landlock-run/README.md | 12 +++++----- native/landlock-run/README.zh.md | 12 +++++----- native/landlock-run/docs/architecture.md | 6 ++--- native/landlock-run/docs/naming.md | 6 ++--- native/landlock-run/docs/packaging.md | 6 ++--- native/landlock-run/docs/release.md | 2 ++ native/landlock-run/docs/support-matrix.md | 4 ++-- native/landlock-run/package.json | 4 ++-- .../packages/entry/README.i18n.yaml | 4 ++-- native/landlock-run/packages/entry/README.md | 6 ++--- .../landlock-run/packages/entry/README.zh.md | 6 ++--- .../landlock-run/packages/entry/package.json | 6 ++--- .../landlock-run/packages/entry/src/index.ts | 4 ++-- native/landlock-run/packages/entry/src/main.c | 2 +- .../packages/linux-arm64/README.i18n.yaml | 4 ++-- .../packages/linux-arm64/README.md | 6 ++--- .../packages/linux-arm64/README.zh.md | 6 ++--- .../packages/linux-arm64/package.json | 4 ++-- .../packages/linux-x64/README.i18n.yaml | 4 ++-- .../landlock-run/packages/linux-x64/README.md | 6 ++--- .../packages/linux-x64/README.zh.md | 6 ++--- .../packages/linux-x64/package.json | 4 ++-- .../scripts/verify-packed-install.mjs | 6 ++--- native/landlock-run/test/entry.test.js | 4 ++-- native/landlock-run/test/launcher.test.js | 2 +- packages/bash/bash-sandbox/package.json | 2 +- .../bash/bash-sandbox/tests/landlock.e2e.ts | 2 +- .../tests/partial-landlock.spec.ts | 2 +- .../examples/agent-spine-demo/package.json | 2 +- .../tests/multi-project-sandbox.e2e.ts | 2 +- .../sandbox/sandbox-local/README.i18n.yaml | 4 ++-- packages/sandbox/sandbox-local/README.md | 2 +- packages/sandbox/sandbox-local/README.zh.md | 2 +- packages/sandbox/sandbox-local/package.json | 2 +- packages/sandbox/sandbox-local/src/index.ts | 2 +- .../sandbox/sandbox-local/src/profiles.ts | 2 +- .../sandbox-local/tests/landlock.e2e.ts | 2 +- .../sandbox/sandbox-local/tests/local.spec.ts | 2 +- .../sandbox-local/tests/packed-install.e2e.ts | 7 +++--- pnpm-lock.yaml | 24 +++++++++---------- scripts/check-workspace-constraints.ts | 13 ++++++---- scripts/gen-third-party-notices.ts | 8 +++---- tsconfig.base.json | 2 +- 57 files changed, 146 insertions(+), 134 deletions(-) diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml b/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml index b927bf9f72..7294f47357 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.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-07-06-sandbox.md -2026-07-06-sandbox.md: de00453eace87ef89e7e05bfe20e1ff956ee4d19 -2026-07-06-sandbox.zh.md: db84e9b3872fb5807720c75310c9ee58f2b9fdb7 +2026-07-06-sandbox.md: 583b388815cd9b2b9cf94ce393839169ce3ffac3 +2026-07-06-sandbox.zh.md: e435b671a42ca5c3ea4f6800bf91d6e006da35d3 diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.md b/.agents/notes/implemented/feature/2026-07-06-sandbox.md index de00453eac..583b388815 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.md +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.md @@ -128,7 +128,7 @@ Each phase gets its full design when picked up, validated against the code at th - **Second consumer** — `subagent-acp` optionally confines child agents (per-call policy; unconfined default — a child agent must write its own persistence). - **More environments** — an environment-coherent capability group example (e.g. bash+fs against one container). -- **Windows chain** — `PLATFORM_CHAINS.win32` is reserved and empty (fail-closed); filling it means a confinement runner from the AppContainer/restricted-token family, shipped from the main repository under `native/` following the `node-addon-landlock-run` template, plus its profile dialect, denial signatures, and runner-failure rules. Wrapping the third-party landstrip runner instead was [considered and rejected](../../rejected/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md) — not battle-tested enough for a security invariant. +- **Windows chain** — `PLATFORM_CHAINS.win32` is reserved and empty (fail-closed); filling it means a confinement runner from the AppContainer/restricted-token family, shipped from the main repository under `native/` following the `@deepseek-ai/node-addon-landlock-run` template, plus its profile dialect, denial signatures, and runner-failure rules. Wrapping the third-party landstrip runner instead was [considered and rejected](../../rejected/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md) — not battle-tested enough for a security invariant. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md b/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md index db84e9b387..e435b671a4 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md @@ -128,7 +128,7 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自 seam 层 - **第二个消费方**——`subagent-acp` 可选地约束子 agent(按调用策略;默认无约束——子 agent 必须写入自己的持久化)。 - **更多环境**——环境一致的能力组示例(如 bash+fs 对一个容器)。 -- **Windows 链**——`PLATFORM_CHAINS.win32` 保留为空(失败关闭);填充它意味着来自 AppContainer/restricted-token 家族的约束 runner,由主仓库在 `native/` 下按 `node-addon-landlock-run` 模板交付,加上其 profile 方言、拒绝签名和 runner 失败规则。改为包装第三方 landstrip runner 的方案[经考虑后已驳回](../../rejected/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md)——它所经受的实战检验还不足以承载安全不变式。 +- **Windows 链**——`PLATFORM_CHAINS.win32` 保留为空(失败关闭);填充它意味着来自 AppContainer/restricted-token 家族的约束 runner,由主仓库在 `native/` 下按 `@deepseek-ai/node-addon-landlock-run` 模板交付,加上其 profile 方言、拒绝签名和 runner 失败规则。改为包装第三方 landstrip runner 的方案[经考虑后已驳回](../../rejected/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md)——它所经受的实战检验还不足以承载安全不变式。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/feature/2026-08-02-win32-in-process-folder-dialog.i18n.yaml b/.agents/notes/implemented/feature/2026-08-02-win32-in-process-folder-dialog.i18n.yaml index 2ec7925a3e..b98c2bee56 100644 --- a/.agents/notes/implemented/feature/2026-08-02-win32-in-process-folder-dialog.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-02-win32-in-process-folder-dialog.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-02-win32-in-process-folder-dialog.md -2026-08-02-win32-in-process-folder-dialog.md: 91a1ed0d7b1c1938a5e038ce36f1ca90bf3c9e82 -2026-08-02-win32-in-process-folder-dialog.zh.md: 6b90dc1c5fa0042b3e2bcbea8ed554f1f0ea2acf +2026-08-02-win32-in-process-folder-dialog.md: 5389293605169ce5ca269a127de5f609b8b7dd11 +2026-08-02-win32-in-process-folder-dialog.zh.md: ef81bc1f65b2859de1eb60f74f79e4869da4746b diff --git a/.agents/notes/implemented/feature/2026-08-02-win32-in-process-folder-dialog.md b/.agents/notes/implemented/feature/2026-08-02-win32-in-process-folder-dialog.md index 91a1ed0d7b..5389293605 100644 --- a/.agents/notes/implemented/feature/2026-08-02-win32-in-process-folder-dialog.md +++ b/.agents/notes/implemented/feature/2026-08-02-win32-in-process-folder-dialog.md @@ -14,7 +14,7 @@ The Windows directory picker's primary tier was a spawned PowerShell script arou ## Alternatives considered -- **A prebuilt native helper (`native/` family like `node-addon-landlock-run`).** Rejected: a mirror repository, an npm package family, MSVC provisioning, and a release handoff — all to ship ~150 lines of C the repository cannot exercise on CI (no real-Windows lane); koffi delivers the same COM surface with zero new supply chain. +- **A prebuilt native helper (`native/` family like `@deepseek-ai/node-addon-landlock-run`).** Rejected: another npm package family, MSVC provisioning, and a Windows build/release lane — all to ship ~150 lines of C the repository cannot currently exercise on CI (no real-Windows lane); koffi delivers the same COM surface with zero new supply chain. - **An N-API in-process addon.** Rejected for the same CI/toolchain reasons plus owned C++ for STA threading and message pumping that a child process + koffi express in TypeScript. - **Keep PowerShell primary and probe versions.** Rejected: the picker stays hostage to shell packaging (6 vs 7, Store aliases, profiles), and 5.1's legacy dialog remains the floor wherever pwsh is absent; the fallback-trigger widening alone was accepted into the fallback tier instead. - **Blocking the main thread for the modal call.** Rejected outright: the web host must keep serving RPC while the dialog is open. diff --git a/.agents/notes/implemented/feature/2026-08-02-win32-in-process-folder-dialog.zh.md b/.agents/notes/implemented/feature/2026-08-02-win32-in-process-folder-dialog.zh.md index 6b90dc1c5f..ef81bc1f65 100644 --- a/.agents/notes/implemented/feature/2026-08-02-win32-in-process-folder-dialog.zh.md +++ b/.agents/notes/implemented/feature/2026-08-02-win32-in-process-folder-dialog.zh.md @@ -14,7 +14,7 @@ Windows 目录选择器的主层此前是围绕 WinForms `FolderBrowserDialog` ## 考虑过的替代方案 -- **预编译原生助手(`native/` 家族,如 `node-addon-landlock-run`)。** 否决:镜像仓库、npm 包家族、MSVC 供给和发布交接——只为交付约 150 行 CI 无法执行的 C(没有真 Windows 通道);koffi 以零新增供应链提供同一 COM 面。 +- **预编译原生助手(`native/` 家族,如 `@deepseek-ai/node-addon-landlock-run`)。** 否决:再增加一个 npm 包家族、MSVC 供给和 Windows 构建/发布通道——只为交付约 150 行目前无法在 CI 中执行的 C(现有 CI 没有真 Windows 通道);koffi 以零新增供应链提供同一 COM 面。 - **N-API 进程内插件。** 否决:同样的 CI/工具链原因,另加需要自有 C++ 处理 STA 线程与消息泵,而子进程 + koffi 用 TypeScript 就能表达。 - **保留 PowerShell 为主层并探测版本。** 否决:选择器仍被 shell 打包形态挟持(6 与 7、Store 别名、profile),且没有 pwsh 的机器地板仍是 5.1 的旧版对话框;仅把回退触发条件的拓宽吸收进回退层。 - **在主线程上阻塞模态调用。** 直接否决:对话框打开期间 web 宿主必须继续服务 RPC。 diff --git a/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.i18n.yaml b/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.i18n.yaml index 3ce0e0d5e1..2a4a389174 100644 --- a/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.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/process/2026-08-06-in-repository-landlock-release.md -2026-08-06-in-repository-landlock-release.md: f682078250adde8d56a4270e9d01ce4b1cd1bee9 -2026-08-06-in-repository-landlock-release.zh.md: 4950d80d87afd18c5605f4f5bca56b8d85564fc2 +2026-08-06-in-repository-landlock-release.md: 3ae9e9c3c50a1d0202a345e419cb2b7079e29ffa +2026-08-06-in-repository-landlock-release.zh.md: 9f2233f6ae95620d7221f83648b5dc9b4cf8c7d2 diff --git a/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.md b/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.md index f682078250..3ae9e9c3c5 100644 --- a/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.md +++ b/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.md @@ -6,17 +6,19 @@ English | [中文](2026-08-06-in-repository-landlock-release.zh.md) ## Problem -The `node-addon-landlock-run` source already lives beside its DeepSeek Harness consumers under `native/landlock-run`, but it previously kept a separate pnpm workspace and lockfile and depended on a standalone repository for npm publication. Harness packages consumed a fixed registry version, so one pull request could change the launcher contract and its consumer without testing those changes together. The source repository's native workflow could rehearse the package, but it did not publish the artifact it tested. +The `@deepseek-ai/node-addon-landlock-run` source already lives beside its DeepSeek Harness consumers under `native/landlock-run`, but it previously kept a separate pnpm workspace and lockfile and depended on a standalone repository for npm publication. Harness packages consumed a fixed registry version, so one pull request could change the launcher contract and its consumer without testing those changes together. The source repository's native workflow could rehearse the package, but it did not publish the artifact it tested. The mirror also duplicated release coordination: export the source, update another lockfile, run another release workflow, publish the native family, then return to this repository to bump registry dependencies. That split made source-to-binary provenance, rollback, and security-fix coordination harder without changing what npm users actually needed. +The existing unscoped npm names are owned by the standalone publisher account rather than the `@deepseek-ai` organization. Moving only the workflow would therefore leave publication dependent on a personal credential outside the repository's release ownership. + The consolidation must preserve platform selection. The public distribution is deliberately one JavaScript entry package plus separate Linux x64 and arm64 binary packages; merging repository ownership does not imply putting every binary into one tarball or publishing every DeepSeek Harness package at the launcher version. ## Decision -`native/landlock-run` and `native/landlock-run/packages/*` belong to the repository's root pnpm workspace and use the root `pnpm-lock.yaml`. Harness consumers declare `node-addon-landlock-run` with `workspace:*`, so development, type checking, builds, and pull-request tests resolve the entry package from the same checkout. The root TypeScript project graph builds that entry package before consumers, and the repository cleaner owns its direct `lib/` output. +`native/landlock-run` and `native/landlock-run/packages/*` belong to the repository's root pnpm workspace and use the root `pnpm-lock.yaml`. Harness consumers declare `@deepseek-ai/node-addon-landlock-run` with `workspace:*`, so development, type checking, builds, and pull-request tests resolve the entry package from the same checkout. The root TypeScript project graph builds that entry package before consumers, and the repository cleaner owns its direct `lib/` output. -The public npm boundary remains three packages with one launcher-family version: `node-addon-landlock-run`, `node-addon-landlock-run-linux-x64`, and `node-addon-landlock-run-linux-arm64`. The entry package retains both platform packages as `optionalDependencies`; their `os` and `cpu` manifest fields let npm install only the compatible package. Repository constraints allow public publication only for those three names, require `publishConfig.access: public`, and require their versions to match the private launcher workspace root. Other repository workspaces remain private under the existing constraint. +The public npm boundary is three organization-owned packages with one launcher-family version: `@deepseek-ai/node-addon-landlock-run`, `@deepseek-ai/node-addon-landlock-run-linux-x64`, and `@deepseek-ai/node-addon-landlock-run-linux-arm64`. The entry package retains both platform packages as `optionalDependencies`; their `os` and `cpu` manifest fields let npm install only the compatible package. Repository constraints allow public publication only for those three names, require `publishConfig.access: public`, and require their versions to match the private launcher workspace root. The former unscoped names are not release targets of this repository; other repository workspaces remain private under the existing constraint. The main repository owns both native CI and publication. `Landlock Run` runs for relevant pull requests and `master` pushes and builds each platform on its matching native runner. The manually dispatched `Landlock Run Release` workflow builds both platform binaries, transfers them as workflow artifacts, assembles and verifies the complete package family, packs immutable npm tarballs, installs and exercises those tarballs, and only then permits the protected publish job. Platform tarballs publish before the entry tarball that optionally depends on them. Publication uses `landlock-run-vX.Y.Z` tags so launcher releases cannot collide with other release families in the monorepo; prereleases use the npm `next` dist-tag. @@ -26,17 +28,17 @@ The sandbox packed-install rehearsal no longer permits the npm registry to suppl - **Keep the standalone repository as a release mirror** — rejected because it preserves the split lockfiles, source export, stale-registry test window, and cross-repository release sequence after the source of record has already moved here. - **Publish one npm package containing every platform binary** — rejected because users would download binaries they cannot run and npm could no longer use package-level `os`/`cpu` filtering. Repository ownership and npm package layout are separate choices. -- **Give the launcher the root DeepSeek Harness version and publish the complete monorepo recursively** — rejected because this change owns one three-package public family, not the independent `@deepseek-ai/*` baseline. The [artifact-first npm baseline proposal](../../proposed/process/2026-08-04-artifact-first-npm-baseline-publication.md) explicitly keeps native workspaces outside its target set. +- **Give the launcher the root DeepSeek Harness version and publish the complete monorepo recursively** — rejected because this change owns one three-package public family, not the independent `@deepseek-ai/dsh-*` baseline. The [artifact-first npm baseline proposal](../../proposed/process/2026-08-04-artifact-first-npm-baseline-publication.md) explicitly keeps native workspaces outside its target set. - **Cross-compile both binaries in one release job** — rejected because the checked-in package matrix already assigns each architecture a native GitHub runner and avoids adding a cross-toolchain trust surface. ## Consequences Launcher protocol, TypeScript entry code, native source, harness consumption, and publish-path tests can change in one pull request and resolve from one lockfile. A release tag now identifies the source, consumer integration, build instructions, and tarballs tested by the main repository. The standalone mirror is no longer part of the release path and can be archived after the first successful in-repository publication. -npm consumers keep the same install command and package names. A supported Linux host downloads the entry package and its matching architecture package; the other architecture package is skipped. An unsupported host receives no platform binary and follows the existing deterministic fail-closed probe path. +npm consumers install `@deepseek-ai/node-addon-landlock-run`; the old unscoped package names are not silently redirected. A supported Linux host downloads the scoped entry package and its matching architecture package; the other architecture package is skipped. An unsupported host receives no platform binary and follows the existing deterministic fail-closed probe path. The implementation touches more files than a dependency-line edit because the repository must also own workspace constraints, TypeScript build order, cleanup, CI triggers, release tags, lockfile generation, packed-install provenance, release documentation, and generated notices. The behavioral boundary stays narrow: it changes only the Landlock package family and its three direct workspace consumers, not the version or publication state of other DeepSeek Harness packages. -The main repository's `npm-publish` environment must authorize npm trusted publishing or provide `NPM_TOKEN`; moving workflow code cannot configure those external settings. npm still publishes packages sequentially and offers no cross-package transaction, so a failed publish can leave a partial version. Because npm rejects an already-published name and version, an operator must inspect the registry and publish only the missing tarballs rather than rerunning the workflow unchanged. Linux x64 and arm64 runners remain the authoritative binary and real-kernel checks; a macOS checkout can verify the entry package and unsupported-platform behavior but cannot replace those jobs. +The first scoped release must use an `@deepseek-ai` organization token through the `npm-publish` environment's `NPM_TOKEN`, because npm cannot configure trusted publishing until a package exists. After bootstrap, all three packages must authorize this repository's release workflow before the fallback token can be removed. npm still publishes packages sequentially and offers no cross-package transaction, so a failed publish can leave a partial version. Because npm rejects an already-published name and version, an operator must inspect the registry and publish only the missing tarballs rather than rerunning the workflow unchanged. Linux x64 and arm64 runners remain the authoritative binary and real-kernel checks; a macOS checkout can verify the entry package and unsupported-platform behavior but cannot replace those jobs. This note supersedes only the release-mirror and registry-pinned source-development statements in the [sandbox Agent Note](../feature/2026-07-06-sandbox.md); that note continues to own sandbox behavior, runner selection, and enforcement semantics. diff --git a/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.zh.md b/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.zh.md index 4950d80d87..9f2233f6ae 100644 --- a/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.zh.md +++ b/.agents/notes/implemented/process/2026-08-06-in-repository-landlock-release.zh.md @@ -6,17 +6,19 @@ Status: implemented ## 问题 -`node-addon-landlock-run` 源码已经与其 DeepSeek Harness 消费方一同位于 `native/landlock-run` 下,但此前仍保留独立的 pnpm workspace 和锁文件,并依赖一个独立仓库发布到 npm。Harness 包使用 npm 注册表中的固定版本,因此同一个 PR(Pull Request)可以同时修改启动器契约及其消费方,却无法一起测试这些改动。源码仓库的原生工作流可以演练打包流程,但不会发布它实际测试过的产物。 +`@deepseek-ai/node-addon-landlock-run` 源码已经与其 DeepSeek Harness 消费方一同位于 `native/landlock-run` 下,但此前仍保留独立的 pnpm workspace 和锁文件,并依赖一个独立仓库发布到 npm。Harness 包使用 npm 注册表中的固定版本,因此同一个 PR(Pull Request)可以同时修改启动器契约及其消费方,却无法一起测试这些改动。源码仓库的原生工作流可以演练打包流程,但不会发布它实际测试过的产物。 发布镜像还造成重复的发布协调工作:导出源码、更新另一份锁文件、运行另一套发布工作流、发布原生包家族,然后回到本仓库更新注册表依赖。npm 用户的实际需求并未改变,这种拆分却增加了从源码到二进制的溯源、回滚和安全修复协调难度。 +现有的非 scoped npm 包名归独立发布账号所有,而不属于 `@deepseek-ai` 组织。因此,仅迁移工作流仍会让发布依赖仓库发布归属之外的个人凭证。 + 此次整合必须保留平台选择机制。公开分发有意采用一个 JavaScript 入口包,并为 Linux x64 和 arm64 分别提供二进制包;合并仓库归属并不意味着要把所有二进制文件放进同一个 tarball,也不意味着要按照启动器版本发布所有 DeepSeek Harness 包。 ## 决策 -`native/landlock-run` 和 `native/landlock-run/packages/*` 属于仓库根 pnpm workspace,并使用根 `pnpm-lock.yaml`。Harness 消费方将 `node-addon-landlock-run` 声明为 `workspace:*`,因此开发、类型检查、构建和 PR 测试都会从同一个 checkout 解析入口包。根 TypeScript 项目图会先构建该入口包,再构建消费方;仓库清理器负责清理其直接生成的 `lib/` 输出目录。 +`native/landlock-run` 和 `native/landlock-run/packages/*` 属于仓库根 pnpm workspace,并使用根 `pnpm-lock.yaml`。Harness 消费方将 `@deepseek-ai/node-addon-landlock-run` 声明为 `workspace:*`,因此开发、类型检查、构建和 PR 测试都会从同一个 checkout 解析入口包。根 TypeScript 项目图会先构建该入口包,再构建消费方;仓库清理器负责清理其直接生成的 `lib/` 输出目录。 -公开 npm 分发边界仍由 3 个包组成,它们共用一个启动器包家族版本:`node-addon-landlock-run`、`node-addon-landlock-run-linux-x64` 和 `node-addon-landlock-run-linux-arm64`。入口包继续通过 `optionalDependencies` 声明两个平台包;它们在 manifest(元数据清单)中的 `os` 和 `cpu` 字段让 npm 只安装兼容的包。仓库约束只允许公开发布这 3 个包名,要求设置 `publishConfig.access: public`,并要求其版本与私有启动器 workspace 根包一致。仓库中的其他 workspace 仍受现有约束保护,保持私有状态。 +公开 npm 分发边界由 3 个归组织所有的包组成,它们共用一个启动器包家族版本:`@deepseek-ai/node-addon-landlock-run`、`@deepseek-ai/node-addon-landlock-run-linux-x64` 和 `@deepseek-ai/node-addon-landlock-run-linux-arm64`。入口包继续通过 `optionalDependencies` 声明两个平台包;它们在 manifest(元数据清单)中的 `os` 和 `cpu` 字段让 npm 只安装兼容的包。仓库约束只允许公开发布这 3 个包名,要求设置 `publishConfig.access: public`,并要求其版本与私有启动器 workspace 根包一致。原先的非 scoped 包名不属于本仓库的发布目标;仓库中的其他 workspace 仍受现有约束保护,保持私有状态。 主仓库同时负责原生 CI 和发布。`Landlock Run` 会为相关 PR 和 `master` 推送运行,并在各自匹配的原生 runner 上构建每个平台包。手动触发的 `Landlock Run Release` 工作流会构建两个平台的二进制文件,将其作为工作流产物传递,组装并验证完整的包家族,打包出内容不可变的 npm tarball,安装并实际运行这些 tarball,之后才允许受保护的发布作业执行。发布顺序是平台 tarball 在前,最后发布将它们列为可选依赖的入口 tarball。发布使用 `landlock-run-vX.Y.Z` tag,避免启动器版本与 monorepo 中其他发布家族发生冲突;预发布版本使用 npm 的 `next` dist-tag。 @@ -26,17 +28,17 @@ Status: implemented - **保留独立仓库作为发布镜像**:不予采纳,因为在权威源码已经迁入本仓库后,这仍会保留拆分的锁文件、源码导出、测试使用陈旧注册表版本的时间窗,以及跨仓库发布序列。 - **发布一个包含所有平台二进制文件的 npm 包**:不予采纳,因为用户会下载无法在其主机上运行的二进制文件,而且 npm 无法再利用包级 `os`/`cpu` 筛选。仓库归属与 npm 包布局是两个彼此独立的选择。 -- **让启动器使用 DeepSeek Harness 根版本,并递归发布整个 monorepo**:不予采纳,因为本次改动负责的是一个由 3 个包组成的公开包家族,而不是独立的 `@deepseek-ai/*` 基线。[产物优先的 npm 基线提案](../../proposed/process/2026-08-04-artifact-first-npm-baseline-publication.md)明确将原生 workspace 排除在其目标集合之外。 +- **让启动器使用 DeepSeek Harness 根版本,并递归发布整个 monorepo**:不予采纳,因为本次改动负责的是一个由 3 个包组成的公开包家族,而不是独立的 `@deepseek-ai/dsh-*` 基线。[产物优先的 npm 基线提案](../../proposed/process/2026-08-04-artifact-first-npm-baseline-publication.md)明确将原生 workspace 排除在其目标集合之外。 - **在一个发布作业中交叉编译两个二进制文件**:不予采纳,因为仓库内已提交的包矩阵已经为每种架构分配了原生 GitHub runner,无需再把交叉工具链纳入信任边界。 ## 后果 同一个 PR 可以同时修改启动器协议、TypeScript 入口代码、原生源码、harness 消费方式和发布路径测试,并从同一份锁文件解析这些内容。发布 tag 现在标识源码、消费方集成、构建指令,以及主仓库测试过的 tarball。第一次成功从本仓库发布后,独立镜像便不再属于发布路径,可以归档。 -npm 消费方继续使用相同的安装命令和包名。受支持的 Linux 主机会下载入口包及与其架构匹配的包,并跳过另一架构的包。不受支持的主机不会收到平台二进制文件,并继续沿用现有的确定性失败闭合探测路径。 +npm 消费方改为安装 `@deepseek-ai/node-addon-landlock-run`;原先的非 scoped 包名不会被静默重定向。受支持的 Linux 主机会下载 scoped 入口包及与其架构匹配的包,并跳过另一架构的包。不受支持的主机不会收到平台二进制文件,并继续沿用现有的确定性失败闭合探测路径。 实现涉及的文件比只修改一行依赖更多,因为仓库还必须负责 workspace 约束、TypeScript 构建顺序、清理、CI 触发条件、发布 tag、锁文件生成、打包安装来源证明、发布文档和生成的第三方声明。行为边界仍然很窄:此次改动只影响 Landlock 包家族及其 3 个直接 workspace 消费方,不改变其他 DeepSeek Harness 包的版本或发布状态。 -主仓库的 `npm-publish` 环境必须授权 npm trusted publishing,或提供 `NPM_TOKEN`;只迁移工作流代码无法配置这些外部设置。npm 仍会按顺序发布各个包,且不提供跨包事务,因此发布失败可能留下只完成了一部分的版本。由于 npm 会拒绝已经发布的同名同版本包,操作人员必须检查注册表并只发布缺失的 tarball,而不能原样重新运行工作流。Linux x64 和 arm64 runner 仍提供权威的二进制构建与真实内核检查;macOS checkout 可以验证入口包和不受支持平台上的行为,但不能取代这些作业。 +第一次发布 scoped 包时,必须通过 `npm-publish` 环境的 `NPM_TOKEN` 使用 `@deepseek-ai` 组织 token,因为 npm 只有在包已经存在后才能配置 trusted publishing。完成 bootstrap 后,必须让 3 个包都授权本仓库的发布工作流,才能移除后备 token。npm 仍会按顺序发布各个包,且不提供跨包事务,因此发布失败可能留下只完成了一部分的版本。由于 npm 会拒绝已经发布的同名同版本包,操作人员必须检查注册表并只发布缺失的 tarball,而不能原样重新运行工作流。Linux x64 和 arm64 runner 仍提供权威的二进制构建与真实内核检查;macOS checkout 可以验证入口包和不受支持平台上的行为,但不能取代这些作业。 本说明仅取代[沙箱 Agent Note](../feature/2026-07-06-sandbox.md)中有关发布镜像和开发源码时依赖注册表固定版本的表述;该 Agent Note 仍负责沙箱行为、runner 选择和强制执行语义。 diff --git a/.github/workflows/landlock-run-release.yml b/.github/workflows/landlock-run-release.yml index c69ebbe5ee..7d78e98bc6 100644 --- a/.github/workflows/landlock-run-release.yml +++ b/.github/workflows/landlock-run-release.yml @@ -1,4 +1,4 @@ -# Build and publish the node-addon-landlock-run package family from the +# Build and publish the @deepseek-ai/node-addon-landlock-run package family from the # harness source of record. Rehearsal and publication consume the same packed # tarballs; each native binary is built on its matching architecture. name: Landlock Run Release @@ -58,7 +58,7 @@ jobs: cache-dependency-path: pnpm-lock.yaml - name: Install dependencies - run: pnpm install --filter node-addon-landlock-run-workspace... --frozen-lockfile + run: pnpm install --filter @deepseek-ai/node-addon-landlock-run-workspace... --frozen-lockfile - name: Install musl toolchain run: | @@ -97,7 +97,7 @@ jobs: cache-dependency-path: pnpm-lock.yaml - name: Install dependencies - run: pnpm install --filter node-addon-landlock-run-workspace... --frozen-lockfile + run: pnpm install --filter @deepseek-ai/node-addon-landlock-run-workspace... --frozen-lockfile - name: Build TypeScript run: pnpm build:ts diff --git a/.github/workflows/landlock-run.yml b/.github/workflows/landlock-run.yml index 391e1eeae1..6379c8cdc1 100644 --- a/.github/workflows/landlock-run.yml +++ b/.github/workflows/landlock-run.yml @@ -73,7 +73,7 @@ jobs: cache-dependency-path: pnpm-lock.yaml - name: Install dependencies - run: pnpm install --filter node-addon-landlock-run-workspace... --frozen-lockfile + run: pnpm install --filter @deepseek-ai/node-addon-landlock-run-workspace... --frozen-lockfile - name: Install musl toolchain run: | @@ -124,7 +124,7 @@ jobs: cache-dependency-path: pnpm-lock.yaml - name: Install dependencies - run: pnpm install --filter node-addon-landlock-run-workspace... --frozen-lockfile + run: pnpm install --filter @deepseek-ai/node-addon-landlock-run-workspace... --frozen-lockfile - name: Build TypeScript run: pnpm build:ts diff --git a/AGENTS.md b/AGENTS.md index a42f39084a..79d35f97fe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,7 @@ packages/ @deepseek-ai/dsh- workspaces at packages/// support/ dev/test infrastructure util/ zero-dependency utilities python/ Python SDK and bundled runtime (see python/README.md) -native/ node-addon-landlock-run source of record (see native/README.md) +native/ @deepseek-ai/node-addon-landlock-run source of record (see native/README.md) examples/ Runnable cordis.yml leaves over packages/examples bundles (see examples/AGENTS.md) .agents/ Agent workflows and Agent Notes (`notes/`) docs/ architecture, generated catalogs, postmortems, cookbook (see docs/AGENTS.md) diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 6ff3f6f8ea..e6907730fe 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -172,4 +172,4 @@ Direct dependencies of the `pyproject.toml` manifests, plus `uv` as the developm ## First-party native packages -`node-addon-landlock-run` (and its platform packages) is built and released from this repository under BSD 3-Clause. It is listed here for completeness; it is first-party, not third-party. +`@deepseek-ai/node-addon-landlock-run` (and its platform packages) is built and released from this repository under BSD 3-Clause. It is listed here for completeness; it is first-party, not third-party. diff --git a/native/landlock-run/README.i18n.yaml b/native/landlock-run/README.i18n.yaml index bdcf985216..c204d571cf 100644 --- a/native/landlock-run/README.i18n.yaml +++ b/native/landlock-run/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/landlock-run/README.md -README.md: 19cc18830b90609f648cfb2ce1ee509ad9fe381b -README.zh.md: 5d3c1c2cd692bb87d5a759a0a6f9628a3f065863 +README.md: fcb8e8249e6728d925fd08c938a780b155d3d4ac +README.zh.md: 8206ab8074d8d80fb9b11cff436bbd10e6dc34dd diff --git a/native/landlock-run/README.md b/native/landlock-run/README.md index 19cc18830b..fcb8e8249e 100644 --- a/native/landlock-run/README.md +++ b/native/landlock-run/README.md @@ -1,4 +1,4 @@ -# node-addon-landlock-run +# @deepseek-ai/node-addon-landlock-run English | [中文](README.zh.md) @@ -9,15 +9,15 @@ The first tool is **`landlock-run`** — a self-restrict-then-exec [Landlock](ht ## Install ```sh -npm install node-addon-landlock-run +npm install @deepseek-ai/node-addon-landlock-run ``` Published packages use an entry package plus platform optional packages: ```text -node-addon-landlock-run -node-addon-landlock-run-linux-x64 -node-addon-landlock-run-linux-arm64 +@deepseek-ai/node-addon-landlock-run +@deepseek-ai/node-addon-landlock-run-linux-x64 +@deepseek-ai/node-addon-landlock-run-linux-arm64 ``` 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. @@ -25,7 +25,7 @@ npm's `os`/`cpu` fields make installers fetch only the matching platform package ## Usage ```js -import { grantArgs, launcherPath, probe } from 'node-addon-landlock-run'; +import { grantArgs, launcherPath, probe } from '@deepseek-ai/node-addon-landlock-run'; const launcher = launcherPath(); if (probe(launcher) !== 'unusable') { diff --git a/native/landlock-run/README.zh.md b/native/landlock-run/README.zh.md index 5d3c1c2cd6..8206ab8074 100644 --- a/native/landlock-run/README.zh.md +++ b/native/landlock-run/README.zh.md @@ -1,4 +1,4 @@ -# node-addon-landlock-run +# @deepseek-ai/node-addon-landlock-run [English](README.md) | 中文 @@ -9,15 +9,15 @@ ## 安装 ```sh -npm install node-addon-landlock-run +npm install @deepseek-ai/node-addon-landlock-run ``` 已发布包由一个入口包和可选平台包组成: ```text -node-addon-landlock-run -node-addon-landlock-run-linux-x64 -node-addon-landlock-run-linux-arm64 +@deepseek-ai/node-addon-landlock-run +@deepseek-ai/node-addon-landlock-run-linux-x64 +@deepseek-ai/node-addon-landlock-run-linux-arm64 ``` npm 的 `os`/`cpu` 字段使安装器只拉取匹配的平台包。系统有意不提供安装时构建回退:在没有对应平台包的宿主上,解析后的路径绝不存在,探测会报告 `unusable`,消费方以失败闭合方式处理。 @@ -25,7 +25,7 @@ npm 的 `os`/`cpu` 字段使安装器只拉取匹配的平台包。系统有意 ## 用法 ```js -import { grantArgs, launcherPath, probe } from 'node-addon-landlock-run'; +import { grantArgs, launcherPath, probe } from '@deepseek-ai/node-addon-landlock-run'; const launcher = launcherPath(); if (probe(launcher) !== 'unusable') { diff --git a/native/landlock-run/docs/architecture.md b/native/landlock-run/docs/architecture.md index e6974f4e50..b462b40635 100644 --- a/native/landlock-run/docs/architecture.md +++ b/native/landlock-run/docs/architecture.md @@ -6,8 +6,8 @@ This repository owns confinement *mechanism*, not policy: consumers (agent harne The family is one entry package plus per-platform binary packages: -- **Entry package** (`node-addon-landlock-run`): 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** (`node-addon-landlock-run-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. +- **Entry package** (`@deepseek-ai/node-addon-landlock-run`): 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-landlock-run-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. Because the contract parser and the binary version together in one family, probe-parsing drift against the binary is structurally impossible — the failure mode the split exists to prevent. @@ -15,7 +15,7 @@ There is no shared loader package: platform packages have nothing to load. If a ## Resolution and availability -`launcherPath()` resolves `node-addon-landlock-run--` and returns `/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. +`launcherPath()` resolves `@deepseek-ai/node-addon-landlock-run--` and returns `/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 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. diff --git a/native/landlock-run/docs/naming.md b/native/landlock-run/docs/naming.md index 9de9f0b95f..a1f2665654 100644 --- a/native/landlock-run/docs/naming.md +++ b/native/landlock-run/docs/naming.md @@ -2,11 +2,11 @@ ## npm packages -The public package family is unscoped, using the `node-addon-landlock-run` package prefix; platform packages append platform information only: +The public package family belongs to the `@deepseek-ai` scope and uses the `node-addon-landlock-run` package prefix; platform packages append platform information only: ```text -node-addon-landlock-run -node-addon-landlock-run- +@deepseek-ai/node-addon-landlock-run +@deepseek-ai/node-addon-landlock-run- ``` Platform suffixes carry no libc component (binaries are static musl) and no variant component — variants stay inside `prebuilds.json` and binary filenames. diff --git a/native/landlock-run/docs/packaging.md b/native/landlock-run/docs/packaging.md index 9a1be47b2a..ec459eb655 100644 --- a/native/landlock-run/docs/packaging.md +++ b/native/landlock-run/docs/packaging.md @@ -5,9 +5,9 @@ The package family uses the same broad shape as native packages such as esbuild: ## Published packages ```text -node-addon-landlock-run -node-addon-landlock-run-linux-x64 -node-addon-landlock-run-linux-arm64 +@deepseek-ai/node-addon-landlock-run +@deepseek-ai/node-addon-landlock-run-linux-x64 +@deepseek-ai/node-addon-landlock-run-linux-arm64 ``` Unsupported platforms are intentionally absent from `optionalDependencies` — see [support-matrix.md](support-matrix.md). diff --git a/native/landlock-run/docs/release.md b/native/landlock-run/docs/release.md index 353a489c2c..d5eec50b6e 100644 --- a/native/landlock-run/docs/release.md +++ b/native/landlock-run/docs/release.md @@ -47,6 +47,8 @@ Use the main repository's `Landlock Run Release` workflow so every binary is bui 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 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 `landlock-run-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)): ```sh diff --git a/native/landlock-run/docs/support-matrix.md b/native/landlock-run/docs/support-matrix.md index 96d02b3cf6..e60ad201c1 100644 --- a/native/landlock-run/docs/support-matrix.md +++ b/native/landlock-run/docs/support-matrix.md @@ -4,8 +4,8 @@ | Platform package | GitHub runner (builder of record) | Notes | |---|---|---| -| `node-addon-landlock-run-linux-x64` | `ubuntu-24.04` | static musl — glibc and musl distros alike | -| `node-addon-landlock-run-linux-arm64` | `ubuntu-24.04-arm` | static musl — glibc and musl distros alike | +| `@deepseek-ai/node-addon-landlock-run-linux-x64` | `ubuntu-24.04` | static musl — glibc and musl distros alike | +| `@deepseek-ai/node-addon-landlock-run-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. diff --git a/native/landlock-run/package.json b/native/landlock-run/package.json index 6fe3f7eff9..0fb9b3ddfb 100644 --- a/native/landlock-run/package.json +++ b/native/landlock-run/package.json @@ -1,5 +1,5 @@ { - "name": "node-addon-landlock-run-workspace", + "name": "@deepseek-ai/node-addon-landlock-run-workspace", "version": "0.0.1", "private": true, "type": "module", @@ -22,7 +22,7 @@ "release:verify-packed-install": "node ./scripts/verify-packed-install.mjs" }, "devDependencies": { - "node-addon-landlock-run": "workspace:*", + "@deepseek-ai/node-addon-landlock-run": "workspace:*", "@types/node": "^26.0.1", "tsx": "^4.20.6", "typescript": "^6.0.3" diff --git a/native/landlock-run/packages/entry/README.i18n.yaml b/native/landlock-run/packages/entry/README.i18n.yaml index 47d33e0d70..7c67f49670 100644 --- a/native/landlock-run/packages/entry/README.i18n.yaml +++ b/native/landlock-run/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/landlock-run/packages/entry/README.md -README.md: e402cdfe71c4eb81b977a21955fe3fff6bf55fd3 -README.zh.md: e4fcd33a256b51c815cdd1c6771be328bc46f138 +README.md: fff722428c5d213d9fcce0ee87a1d48cdc189884 +README.zh.md: f462fbe3cb0cb8d1d83b4d6b1d8e2f61e88ff69c diff --git a/native/landlock-run/packages/entry/README.md b/native/landlock-run/packages/entry/README.md index e402cdfe71..fff722428c 100644 --- a/native/landlock-run/packages/entry/README.md +++ b/native/landlock-run/packages/entry/README.md @@ -1,11 +1,11 @@ -# node-addon-landlock-run +# @deepseek-ai/node-addon-landlock-run 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. ```js -import { grantArgs, launcherPath, probe } from 'node-addon-landlock-run'; +import { grantArgs, launcherPath, probe } from '@deepseek-ai/node-addon-landlock-run'; const launcher = launcherPath(); if (probe(launcher) !== 'unusable') { @@ -15,4 +15,4 @@ if (probe(launcher) !== '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): `node-addon-landlock-run-linux-x64`, `node-addon-landlock-run-linux-arm64`. On hosts without one, `launcherPath()` returns a deterministic nonexistent path and `probe()` reports `'unusable'` — there is deliberately no install-time compile fallback. +Platform packages (`os`/`cpu`-selected optional dependencies, no JavaScript inside): `@deepseek-ai/node-addon-landlock-run-linux-x64`, `@deepseek-ai/node-addon-landlock-run-linux-arm64`. On hosts without one, `launcherPath()` returns a deterministic nonexistent path and `probe()` reports `'unusable'` — there is deliberately no install-time compile fallback. diff --git a/native/landlock-run/packages/entry/README.zh.md b/native/landlock-run/packages/entry/README.zh.md index e4fcd33a25..f462fbe3cb 100644 --- a/native/landlock-run/packages/entry/README.zh.md +++ b/native/landlock-run/packages/entry/README.zh.md @@ -1,11 +1,11 @@ -# node-addon-landlock-run +# @deepseek-ai/node-addon-landlock-run [English](README.md) | 中文 用于在 Linux 上限制子进程的 Landlock「先限制自身、再执行」启动器:此入口包定位对应平台的预构建二进制文件,运行功能性强制执行探测,并构建其授权 argv。消费方无需自行拼写启动器标志或解析启动器输出。 ```js -import { grantArgs, launcherPath, probe } from 'node-addon-landlock-run'; +import { grantArgs, launcherPath, probe } from '@deepseek-ai/node-addon-landlock-run'; const launcher = launcherPath(); if (probe(launcher) !== 'unusable') { @@ -15,4 +15,4 @@ if (probe(launcher) !== 'unusable') { 启动器在自身上安装 Landlock 规则集,再 `exec` 被包装的命令;该规则集会跨 `execve` 继承,因此整个进程树都在限制下运行。未授予的一切都被拒绝;启动器失败时以 `125` 退出且不运行命令:采用失败闭合策略,绝不在失败时放行。二进制契约锁定在仓库的 `docs/cli-contract.md` 中;C 源码作为 `src/main.c` 随该 tarball 分发,便于审计。 -平台包(由 `os`/`cpu` 选择的可选依赖,内部不含 JavaScript):`node-addon-landlock-run-linux-x64`、`node-addon-landlock-run-linux-arm64`。在缺少对应包的宿主上,`launcherPath()` 返回一个固定但不存在的路径,`probe()` 报告 `'unusable'`;系统有意不提供安装时编译回退。 +平台包(由 `os`/`cpu` 选择的可选依赖,内部不含 JavaScript):`@deepseek-ai/node-addon-landlock-run-linux-x64`、`@deepseek-ai/node-addon-landlock-run-linux-arm64`。在缺少对应包的宿主上,`launcherPath()` 返回一个固定但不存在的路径,`probe()` 报告 `'unusable'`;系统有意不提供安装时编译回退。 diff --git a/native/landlock-run/packages/entry/package.json b/native/landlock-run/packages/entry/package.json index 56345b2847..1614df5ad2 100644 --- a/native/landlock-run/packages/entry/package.json +++ b/native/landlock-run/packages/entry/package.json @@ -1,5 +1,5 @@ { - "name": "node-addon-landlock-run", + "name": "@deepseek-ai/node-addon-landlock-run", "version": "0.0.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", @@ -35,7 +35,7 @@ "access": "public" }, "optionalDependencies": { - "node-addon-landlock-run-linux-arm64": "workspace:*", - "node-addon-landlock-run-linux-x64": "workspace:*" + "@deepseek-ai/node-addon-landlock-run-linux-arm64": "workspace:*", + "@deepseek-ai/node-addon-landlock-run-linux-x64": "workspace:*" } } diff --git a/native/landlock-run/packages/entry/src/index.ts b/native/landlock-run/packages/entry/src/index.ts index 7a4349a5ca..ec909f928a 100644 --- a/native/landlock-run/packages/entry/src/index.ts +++ b/native/landlock-run/packages/entry/src/index.ts @@ -53,7 +53,7 @@ export interface LauncherGrants { /** * Path of the launcher binary for this host: resolved from the per-platform - * npm package `node-addon-landlock-run--` (npm's + * npm package `@deepseek-ai/node-addon-landlock-run--` (npm's * `os`/`cpu` fields make installers fetch only the matching one). When the * package is not resolvable — a platform without one, or an install that * skipped the optional dependency — the returned fallback path points inside @@ -69,7 +69,7 @@ export interface LauncherGrants { export function launcherPath( resolvePackageJson: (specifier: string) => string = createRequire(import.meta.url).resolve, ): string { - const platformPackage = `node-addon-landlock-run-${process.platform}-${process.arch}` + const platformPackage = `@deepseek-ai/node-addon-landlock-run-${process.platform}-${process.arch}` try { return join(dirname(resolvePackageJson(`${platformPackage}/package.json`)), 'bin', LAUNCHER_BIN) } catch { diff --git a/native/landlock-run/packages/entry/src/main.c b/native/landlock-run/packages/entry/src/main.c index af3c2eb3f0..e4e1f5c17e 100644 --- a/native/landlock-run/packages/entry/src/main.c +++ b/native/landlock-run/packages/entry/src/main.c @@ -31,7 +31,7 @@ * linked statically), so the whole audit surface is this file plus the * kernel's stable syscall contract. Built natively per architecture by * `scripts/build.ts` into the per-platform npm packages - * (`node-addon-landlock-run-linux-{x64,arm64}`); the argv grammar, + * (`@deepseek-ai/node-addon-landlock-run-linux-{x64,arm64}`); the argv grammar, * exit codes, and report lines are pinned in `docs/cli-contract.md`. */ diff --git a/native/landlock-run/packages/linux-arm64/README.i18n.yaml b/native/landlock-run/packages/linux-arm64/README.i18n.yaml index f7e057193c..fc5c8f9b11 100644 --- a/native/landlock-run/packages/linux-arm64/README.i18n.yaml +++ b/native/landlock-run/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/landlock-run/packages/linux-arm64/README.md -README.md: e5117988cf0bae2227edaa041700c2f75753899c -README.zh.md: e502b0239b5ed862af579b21e36b8c47d7d6107e +README.md: dfcc9e97dc1393a42ff4b89ac009cdfd31e1497b +README.zh.md: 350044e92f1d0247222cc16c82f03588ed0154c9 diff --git a/native/landlock-run/packages/linux-arm64/README.md b/native/landlock-run/packages/linux-arm64/README.md index e5117988cf..dfcc9e97dc 100644 --- a/native/landlock-run/packages/linux-arm64/README.md +++ b/native/landlock-run/packages/linux-arm64/README.md @@ -1,9 +1,9 @@ -# node-addon-landlock-run-linux-arm64 +# @deepseek-ai/node-addon-landlock-run-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 [`node-addon-landlock-run`](https://www.npmjs.com/package/node-addon-landlock-run). 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. +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-landlock-run`](https://www.npmjs.com/package/@deepseek-ai/node-addon-landlock-run). 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. 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: `node-addon-landlock-run-linux-x64`. +Sibling: `@deepseek-ai/node-addon-landlock-run-linux-x64`. diff --git a/native/landlock-run/packages/linux-arm64/README.zh.md b/native/landlock-run/packages/linux-arm64/README.zh.md index e502b0239b..350044e92f 100644 --- a/native/landlock-run/packages/linux-arm64/README.zh.md +++ b/native/landlock-run/packages/linux-arm64/README.zh.md @@ -1,9 +1,9 @@ -# node-addon-landlock-run-linux-arm64 +# @deepseek-ai/node-addon-landlock-run-linux-arm64 [English](README.md) | 中文 -面向 linux-arm64 的预构建 `bin/landlock-run` Landlock 启动器:一个由 [`node-addon-landlock-run`](https://www.npmjs.com/package/node-addon-landlock-run) 包所附的 C 源码原生编译而成的静态 musl 二进制文件(不使用交叉工具链)。npm 的 `os`/`cpu` 字段在安装时选择此包;入口包将其定位到文件路径。该包不包含 JavaScript,也绝不会被导入。 +面向 linux-arm64 的预构建 `bin/landlock-run` Landlock 启动器:一个由 [`@deepseek-ai/node-addon-landlock-run`](https://www.npmjs.com/package/@deepseek-ai/node-addon-landlock-run) 包所附的 C 源码原生编译而成的静态 musl 二进制文件(不使用交叉工具链)。npm 的 `os`/`cpu` 字段在安装时选择此包;入口包将其定位到文件路径。该包不包含 JavaScript,也绝不会被导入。 该二进制文件被 git 忽略,并通过 `files` 列表进入 npm tarball;如果文件缺失或 ELF 架构错误,`prepack` 门禁会拒绝打包,发布流水线则会按字节核验打包的二进制文件与其来源 CI 构建产物一致。静态 musl 链接使同一个二进制文件同时适用于 glibc 和 musl 发行版,因此名称中没有 libc 后缀。 -同级包:`node-addon-landlock-run-linux-x64`。 +同级包:`@deepseek-ai/node-addon-landlock-run-linux-x64`。 diff --git a/native/landlock-run/packages/linux-arm64/package.json b/native/landlock-run/packages/linux-arm64/package.json index af5467cead..14190e4765 100644 --- a/native/landlock-run/packages/linux-arm64/package.json +++ b/native/landlock-run/packages/linux-arm64/package.json @@ -1,7 +1,7 @@ { - "name": "node-addon-landlock-run-linux-arm64", + "name": "@deepseek-ai/node-addon-landlock-run-linux-arm64", "version": "0.0.1", - "description": "Prebuilt landlock-run Landlock launcher binary for linux-arm64 (static musl) — resolved as a file path by node-addon-landlock-run, never imported", + "description": "Prebuilt landlock-run Landlock launcher binary for linux-arm64 (static musl) — resolved as a file path by @deepseek-ai/node-addon-landlock-run, never imported", "repository": { "type": "git", "url": "git+https://github.com/deepseek-harness/deepseek-harness.git", diff --git a/native/landlock-run/packages/linux-x64/README.i18n.yaml b/native/landlock-run/packages/linux-x64/README.i18n.yaml index 7050c110ef..cb0022b138 100644 --- a/native/landlock-run/packages/linux-x64/README.i18n.yaml +++ b/native/landlock-run/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/landlock-run/packages/linux-x64/README.md -README.md: 68b5dfc9b6f437a387c3792ee047a1f11630aca0 -README.zh.md: 3b9578a7eb78dfc05977795ca521cf3a881e9f1a +README.md: d08cc0c4abbc74f64c5d1075dea796427211bd8f +README.zh.md: ed6839aa6230b16b82c67a716fc0a4128e5a977c diff --git a/native/landlock-run/packages/linux-x64/README.md b/native/landlock-run/packages/linux-x64/README.md index 68b5dfc9b6..d08cc0c4ab 100644 --- a/native/landlock-run/packages/linux-x64/README.md +++ b/native/landlock-run/packages/linux-x64/README.md @@ -1,9 +1,9 @@ -# node-addon-landlock-run-linux-x64 +# @deepseek-ai/node-addon-landlock-run-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 [`node-addon-landlock-run`](https://www.npmjs.com/package/node-addon-landlock-run). 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. +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-landlock-run`](https://www.npmjs.com/package/@deepseek-ai/node-addon-landlock-run). 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. 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: `node-addon-landlock-run-linux-arm64`. +Sibling: `@deepseek-ai/node-addon-landlock-run-linux-arm64`. diff --git a/native/landlock-run/packages/linux-x64/README.zh.md b/native/landlock-run/packages/linux-x64/README.zh.md index 3b9578a7eb..ed6839aa62 100644 --- a/native/landlock-run/packages/linux-x64/README.zh.md +++ b/native/landlock-run/packages/linux-x64/README.zh.md @@ -1,9 +1,9 @@ -# node-addon-landlock-run-linux-x64 +# @deepseek-ai/node-addon-landlock-run-linux-x64 [English](README.md) | 中文 -面向 linux-x64 的预构建 `bin/landlock-run` Landlock 启动器:一个由 [`node-addon-landlock-run`](https://www.npmjs.com/package/node-addon-landlock-run) 包所附的 C 源码原生编译而成的静态 musl 二进制文件(不使用交叉工具链)。npm 的 `os`/`cpu` 字段在安装时选择此包;入口包将其定位到文件路径。该包不包含 JavaScript,也绝不会被导入。 +面向 linux-x64 的预构建 `bin/landlock-run` Landlock 启动器:一个由 [`@deepseek-ai/node-addon-landlock-run`](https://www.npmjs.com/package/@deepseek-ai/node-addon-landlock-run) 包所附的 C 源码原生编译而成的静态 musl 二进制文件(不使用交叉工具链)。npm 的 `os`/`cpu` 字段在安装时选择此包;入口包将其定位到文件路径。该包不包含 JavaScript,也绝不会被导入。 该二进制文件被 git 忽略,并通过 `files` 列表进入 npm tarball;如果文件缺失或 ELF 架构错误,`prepack` 门禁会拒绝打包,发布流水线则会按字节核验打包的二进制文件与其来源 CI 构建产物一致。静态 musl 链接使同一个二进制文件同时适用于 glibc 和 musl 发行版,因此名称中没有 libc 后缀。 -同级包:`node-addon-landlock-run-linux-arm64`。 +同级包:`@deepseek-ai/node-addon-landlock-run-linux-arm64`。 diff --git a/native/landlock-run/packages/linux-x64/package.json b/native/landlock-run/packages/linux-x64/package.json index 375d05332a..43c092d17b 100644 --- a/native/landlock-run/packages/linux-x64/package.json +++ b/native/landlock-run/packages/linux-x64/package.json @@ -1,7 +1,7 @@ { - "name": "node-addon-landlock-run-linux-x64", + "name": "@deepseek-ai/node-addon-landlock-run-linux-x64", "version": "0.0.1", - "description": "Prebuilt landlock-run Landlock launcher binary for linux-x64 (static musl) — resolved as a file path by node-addon-landlock-run, never imported", + "description": "Prebuilt landlock-run Landlock launcher binary for linux-x64 (static musl) — resolved as a file path by @deepseek-ai/node-addon-landlock-run, never imported", "repository": { "type": "git", "url": "git+https://github.com/deepseek-harness/deepseek-harness.git", diff --git a/native/landlock-run/scripts/verify-packed-install.mjs b/native/landlock-run/scripts/verify-packed-install.mjs index 60f225a9d2..928fff50f7 100644 --- a/native/landlock-run/scripts/verify-packed-install.mjs +++ b/native/landlock-run/scripts/verify-packed-install.mjs @@ -31,7 +31,7 @@ import { entryDirs, packageDirs, platformDirs, readJson, root } from './repo.mjs const args = process.argv.slice(2); const currentPlatformOnly = args.includes('--current-platform-only'); const tarballDir = path.resolve(args.find((arg) => !arg.startsWith('--')) || path.join(root, 'dist', 'npm')); -const entryPackageName = 'node-addon-landlock-run'; +const entryPackageName = '@deepseek-ai/node-addon-landlock-run'; function tarballName(manifest) { if (manifest.name.startsWith('@')) { @@ -180,10 +180,10 @@ 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 'node-addon-landlock-run'; +import { grantArgs, launcherPath, probe } from '@deepseek-ai/node-addon-landlock-run'; const requireLandlock = process.env.NALR_REQUIRE_LANDLOCK === '1'; -const platformPackage = 'node-addon-landlock-run-' + process.platform + '-' + process.arch; +const platformPackage = '@deepseek-ai/node-addon-landlock-run-' + process.platform + '-' + process.arch; const resolved = launcherPath(); assert.ok(path.isAbsolute(resolved), 'launcherPath must be absolute'); assert.ok(resolved.includes(path.join(...platformPackage.split('/'))), 'launcherPath must point into the platform package: ' + resolved); diff --git a/native/landlock-run/test/entry.test.js b/native/landlock-run/test/entry.test.js index 2e2cfe8f17..2b535559a2 100644 --- a/native/landlock-run/test/entry.test.js +++ b/native/landlock-run/test/entry.test.js @@ -15,7 +15,7 @@ import { grantArgs, launcherPath, probe, -} from 'node-addon-landlock-run'; +} from '@deepseek-ai/node-addon-landlock-run'; // --- constants are part of the CLI contract --- assert.equal(LAUNCHER_BIN, 'landlock-run'); @@ -31,7 +31,7 @@ assert.deepEqual( assert.deepEqual(grantArgs({ readWrite: ['/a'], readOnly: ['/b'] }), ['--ro', '/b', '--rw', '/a']); // --- launcherPath: resolves the platform package next to its package.json --- -const platformPackage = `node-addon-landlock-run-${process.platform}-${process.arch}`; +const platformPackage = `@deepseek-ai/node-addon-landlock-run-${process.platform}-${process.arch}`; const resolvedViaSeam = launcherPath((specifier) => { assert.equal(specifier, `${platformPackage}/package.json`); return path.join('/fake-install', specifier); diff --git a/native/landlock-run/test/launcher.test.js b/native/landlock-run/test/launcher.test.js index 55385d2156..a78501cd4a 100644 --- a/native/landlock-run/test/launcher.test.js +++ b/native/landlock-run/test/launcher.test.js @@ -22,7 +22,7 @@ import { grantArgs, launcherPath, probe, -} from 'node-addon-landlock-run'; +} from '@deepseek-ai/node-addon-landlock-run'; const FATAL_PREFIX = 'landlock-run: '; const PARTIAL_NOTICE = 'landlock-run: partial enforcement (older Landlock ABI)'; diff --git a/packages/bash/bash-sandbox/package.json b/packages/bash/bash-sandbox/package.json index 6a29c3f6c8..a771acb1d3 100644 --- a/packages/bash/bash-sandbox/package.json +++ b/packages/bash/bash-sandbox/package.json @@ -41,6 +41,6 @@ "@deepseek-ai/dsh-sandbox-local": "workspace:^", "@deepseek-ai/dsh-sandbox-policy": "workspace:^", "cordis": "^4.0.0-rc.7", - "node-addon-landlock-run": "workspace:*" + "@deepseek-ai/node-addon-landlock-run": "workspace:*" } } diff --git a/packages/bash/bash-sandbox/tests/landlock.e2e.ts b/packages/bash/bash-sandbox/tests/landlock.e2e.ts index 7579292fe1..7255ee43c9 100644 --- a/packages/bash/bash-sandbox/tests/landlock.e2e.ts +++ b/packages/bash/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 'cordis' -import { launcherPath } from 'node-addon-landlock-run' +import { launcherPath } from '@deepseek-ai/node-addon-landlock-run' import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local' import { SandboxPolicyService } from '@deepseek-ai/dsh-sandbox-policy' import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox' diff --git a/packages/bash/bash-sandbox/tests/partial-landlock.spec.ts b/packages/bash/bash-sandbox/tests/partial-landlock.spec.ts index 23546e5d92..b578716c43 100644 --- a/packages/bash/bash-sandbox/tests/partial-landlock.spec.ts +++ b/packages/bash/bash-sandbox/tests/partial-landlock.spec.ts @@ -9,7 +9,7 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import { Context } from 'cordis' -import { LAUNCHER_FAILURE_EXIT } from 'node-addon-landlock-run' +import { LAUNCHER_FAILURE_EXIT } from '@deepseek-ai/node-addon-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/packages/examples/agent-spine-demo/package.json b/packages/examples/agent-spine-demo/package.json index b9f817d6e5..e6c6ce351d 100644 --- a/packages/examples/agent-spine-demo/package.json +++ b/packages/examples/agent-spine-demo/package.json @@ -84,7 +84,7 @@ "@deepseek-ai/dsh-tool-tasks": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", "@deepseek-ai/dsh-workspace-context": "workspace:^", - "node-addon-landlock-run": "workspace:*", + "@deepseek-ai/node-addon-landlock-run": "workspace:*", "cordis": "^4.0.0-rc.7" }, "dependencies": { diff --git a/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts b/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts index 46f3f56dc3..53722aa7e5 100644 --- a/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts +++ b/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts @@ -15,7 +15,7 @@ import SandboxPolicyService from '@deepseek-ai/dsh-sandbox-policy' import { SessionId } from '@deepseek-ai/dsh-session' import * as ToolFs from '@deepseek-ai/dsh-tool-fs' import type { ToolResult } from '@deepseek-ai/dsh-tools' -import { launcherPath } from 'node-addon-landlock-run' +import { launcherPath } from '@deepseek-ai/node-addon-landlock-run' import * as agentSpine from '../src/index.ts' const bwrapUsable = spawnSync('bwrap', [ diff --git a/packages/sandbox/sandbox-local/README.i18n.yaml b/packages/sandbox/sandbox-local/README.i18n.yaml index 43fb941975..cbc1e9ad55 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: f6a1cc2b3e454e0670a564151d41182ec515bdcf -README.zh.md: 18b66af350932fc8d5c4f184d0e7fa049f910250 +README.md: 23d3a32451c105c71c0a7399ed051288b70753f3 +README.zh.md: 165a6fc88a9fdd219c3ddb016cdf415504556d8c diff --git a/packages/sandbox/sandbox-local/README.md b/packages/sandbox/sandbox-local/README.md index f6a1cc2b3e..23d3a32451 100644 --- a/packages/sandbox/sandbox-local/README.md +++ b/packages/sandbox/sandbox-local/README.md @@ -12,7 +12,7 @@ Policy is per call; the provider stores only the mechanism and cached runner ver The Seatbelt profile is allow-default with `(deny file-write*)` plus write allow-lists, so exactly the mode's promised file effects are governed: `read-only` grants the `/dev/null` literal alone; `workspace-write` adds the workspace root, `/tmp`, and the per-user darwin temp dir (`os.tmpdir()` — the platform's real temp area for mkstemp-family tools), every root canonicalized because Seatbelt matches resolved paths (`/tmp` IS `/private/tmp`). Apple marks the `sandbox-exec` CLI deprecated but ships it on every macOS; the functional probe is what fails closed if that ever changes. -[`node-addon-landlock-run`](https://www.npmjs.com/package/node-addon-landlock-run) supplies the platform launcher, functional probe, and CLI argument vocabulary. This provider owns only mode-to-grant mapping and runner selection. Keeping path resolution and probe parsing with the versioned binary prevents contract drift. +[`@deepseek-ai/node-addon-landlock-run`](https://www.npmjs.com/package/@deepseek-ai/node-addon-landlock-run) supplies the platform launcher, functional probe, and CLI argument vocabulary. This provider owns only mode-to-grant mapping and runner selection. Keeping path resolution and probe parsing with the versioned binary prevents contract drift. ```yaml - id: sandbox diff --git a/packages/sandbox/sandbox-local/README.zh.md b/packages/sandbox/sandbox-local/README.zh.md index 18b66af350..165a6fc88a 100644 --- a/packages/sandbox/sandbox-local/README.zh.md +++ b/packages/sandbox/sandbox-local/README.zh.md @@ -12,7 +12,7 @@ Seatbelt profile 默认允许,但带 `(deny file-write*)` 和写入 allow-list,因此恰好约束相应模式承诺的文件操作:`read-only` 只授予 `/dev/null` 字面路径;`workspace-write` 另加工作区根目录、`/tmp` 和逐用户 darwin 临时目录(`os.tmpdir()`,即平台供 mkstemp 家族工具使用的真实临时区域)。每个根目录都经过规范化,因为 Seatbelt 匹配解析后的路径(`/tmp` 就是 `/private/tmp`)。Apple 将 `sandbox-exec` CLI(命令行界面)标为 deprecated,但所有 macOS 系统仍会提供它;若情况发生变化,功能探测会使执行被拒绝。 -[`node-addon-landlock-run`](https://www.npmjs.com/package/node-addon-landlock-run)提供平台 launcher、功能探测和 CLI 参数词汇。该提供方只负责模式到授权的映射与 runner 选择。把路径解析和探测解析保留在带版本的 binary 中,可防止契约漂移。 +[`@deepseek-ai/node-addon-landlock-run`](https://www.npmjs.com/package/@deepseek-ai/node-addon-landlock-run)提供平台 launcher、功能探测和 CLI 参数词汇。该提供方只负责模式到授权的映射与 runner 选择。把路径解析和探测解析保留在带版本的 binary 中,可防止契约漂移。 ```yaml - id: sandbox diff --git a/packages/sandbox/sandbox-local/package.json b/packages/sandbox/sandbox-local/package.json index f7004c4d5c..eace2ef08c 100644 --- a/packages/sandbox/sandbox-local/package.json +++ b/packages/sandbox/sandbox-local/package.json @@ -31,7 +31,7 @@ "cordis": "^4.0.0-rc.7" }, "dependencies": { - "node-addon-landlock-run": "workspace:*", + "@deepseek-ai/node-addon-landlock-run": "workspace:*", "schemastery": "^3.18.0" }, "devDependencies": { diff --git a/packages/sandbox/sandbox-local/src/index.ts b/packages/sandbox/sandbox-local/src/index.ts index 64e92d9bf2..7e9405d14b 100644 --- a/packages/sandbox/sandbox-local/src/index.ts +++ b/packages/sandbox/sandbox-local/src/index.ts @@ -12,7 +12,7 @@ import { LAUNCHER_FAILURE_EXIT, launcherPath as landlockLauncherPath, probe as defaultProbeLandlock, -} from 'node-addon-landlock-run' +} from '@deepseek-ai/node-addon-landlock-run' import { Context } from 'cordis' import z from 'schemastery' import { assertNever } from '@deepseek-ai/dsh-llm' diff --git a/packages/sandbox/sandbox-local/src/profiles.ts b/packages/sandbox/sandbox-local/src/profiles.ts index cee0f00852..5b76390319 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 'node-addon-landlock-run' +import { grantArgs as landlockGrantArgs } from '@deepseek-ai/node-addon-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 6e2faecc6b..ff4947a4ca 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 'cordis' import type { SandboxPolicy } from '@deepseek-ai/dsh-sandbox' -import { launcherPath } from 'node-addon-landlock-run' +import { launcherPath } from '@deepseek-ai/node-addon-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 74d4c2a8a1..b1b8b2c4a8 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 { describe, expect, it, vi } from 'vitest' import { Context } from 'cordis' -import { LAUNCHER_FAILURE_EXIT } from 'node-addon-landlock-run' +import { LAUNCHER_FAILURE_EXIT } from '@deepseek-ai/node-addon-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 612751e6da..5f4fa5a3bb 100644 --- a/packages/sandbox/sandbox-local/tests/packed-install.e2e.ts +++ b/packages/sandbox/sandbox-local/tests/packed-install.e2e.ts @@ -23,6 +23,7 @@ const packageDir = fileURLToPath(new URL('..', import.meta.url)) const repoRoot = fileURLToPath(new URL('../../../..', import.meta.url)) const nativeDir = join(repoRoot, 'native/landlock-run') const sourceLauncher = join(nativeDir, 'packages', `linux-${process.arch}`, 'bin', 'landlock-run') +const platformPackageName = `@deepseek-ai/node-addon-landlock-run-linux-${process.arch}` /** The harness closure the consumer needs; native tarballs are packed through their mode-preserving release script. */ const WORKSPACE_CLOSURE = [ @@ -108,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 'cordis' - import { launcherPath } from 'node-addon-landlock-run' + import { launcherPath } from '@deepseek-ai/node-addon-landlock-run' import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local' const ctx = new Context() await ctx.plugin(LocalSandboxProvider, {}) @@ -145,7 +146,7 @@ describe.skipIf(!packable)('sandbox-local: packed-tarball distribution (publish- }) it('installs this checkout\'s launcher for the host: present, executable, byte-identical, and right ELF arch', () => { - const installed = join(consumerDir, 'node_modules', `node-addon-landlock-run-linux-${process.arch}`, 'bin', 'landlock-run') + const installed = join(consumerDir, 'node_modules', ...platformPackageName.split('/'), 'bin', 'landlock-run') expect(existsSync(installed), 'platform package missing from the installed tree').toBe(true) // A tarball or extraction step that strips the mode bit would leave the // probe failing exactly like a non-enforcing kernel — assert it apart. @@ -156,7 +157,7 @@ describe.skipIf(!packable)('sandbox-local: packed-tarball distribution (publish- it('the installed provider resolves the launcher INSIDE the consumer node_modules platform package', () => { expect(verdict.launcher) - .toBe(join(consumerDir, 'node_modules', `node-addon-landlock-run-linux-${process.arch}`, 'bin', 'landlock-run')) + .toBe(join(consumerDir, 'node_modules', ...platformPackageName.split('/'), 'bin', 'landlock-run')) }) it('confines through the installed launcher (enforcing kernel) or fails closed (non-enforcing) — never unconfined', async () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef10b7c2d7..a93b757791 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -853,12 +853,12 @@ importers: native/landlock-run: devDependencies: + '@deepseek-ai/node-addon-landlock-run': + specifier: workspace:* + version: link:packages/entry '@types/node': specifier: ^26.0.1 version: 26.1.2 - node-addon-landlock-run: - specifier: workspace:* - version: link:packages/entry tsx: specifier: ^4.20.6 version: 4.22.4 @@ -868,10 +868,10 @@ importers: native/landlock-run/packages/entry: optionalDependencies: - node-addon-landlock-run-linux-arm64: + '@deepseek-ai/node-addon-landlock-run-linux-arm64': specifier: workspace:* version: link:../linux-arm64 - node-addon-landlock-run-linux-x64: + '@deepseek-ai/node-addon-landlock-run-linux-x64': specifier: workspace:* version: link:../linux-x64 @@ -1010,12 +1010,12 @@ importers: '@deepseek-ai/dsh-subprocess-local': specifier: workspace:^ version: link:../../subprocess/subprocess-local + '@deepseek-ai/node-addon-landlock-run': + specifier: workspace:* + version: link:../../../native/landlock-run/packages/entry cordis: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - node-addon-landlock-run: - specifier: workspace:* - version: link:../../../native/landlock-run/packages/entry packages/bash/pwsh-local: dependencies: @@ -2969,12 +2969,12 @@ importers: '@deepseek-ai/dsh-workspace-context': specifier: workspace:^ version: link:../../context/workspace-context + '@deepseek-ai/node-addon-landlock-run': + specifier: workspace:* + version: link:../../../native/landlock-run/packages/entry cordis: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - node-addon-landlock-run: - specifier: workspace:* - version: link:../../../native/landlock-run/packages/entry packages/examples/cli-demo: devDependencies: @@ -4255,7 +4255,7 @@ importers: packages/sandbox/sandbox-local: dependencies: - node-addon-landlock-run: + '@deepseek-ai/node-addon-landlock-run': specifier: workspace:* version: link:../../../native/landlock-run/packages/entry schemastery: diff --git a/scripts/check-workspace-constraints.ts b/scripts/check-workspace-constraints.ts index 0a446d77f8..dabd0f7805 100644 --- a/scripts/check-workspace-constraints.ts +++ b/scripts/check-workspace-constraints.ts @@ -31,10 +31,14 @@ const vendoredPackages = new Set([ '@cordisjs/plugin-logger-console', ]) const publicLandlockPackages = new Set([ - 'node-addon-landlock-run', - 'node-addon-landlock-run-linux-arm64', - 'node-addon-landlock-run-linux-x64', + '@deepseek-ai/node-addon-landlock-run', + '@deepseek-ai/node-addon-landlock-run-linux-arm64', + '@deepseek-ai/node-addon-landlock-run-linux-x64', ]) +/** Deliberate source payloads whose exact bytes are part of the package's audit surface. */ +const publicationSourceAllowlist: Readonly> = { + '@deepseek-ai/node-addon-landlock-run': ['src/main.c'], +} const repositoryUrl = 'git+https://github.com/deepseek-harness/deepseek-harness.git' const localArtifactDirs = new Set(['node_modules']) @@ -200,8 +204,9 @@ function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] { } if (manifest.name?.startsWith('@deepseek-ai/')) { + const allowedSources = publicationSourceAllowlist[manifest.name] ?? [] for (const file of manifest.files ?? []) { - if (isForbiddenPublicationFile(file)) { + if (isForbiddenPublicationFile(file) && !allowedSources.includes(file)) { errors.push(`${label}: package.json files must not publish ${JSON.stringify(file)}`) } } diff --git a/scripts/gen-third-party-notices.ts b/scripts/gen-third-party-notices.ts index e56cce670a..04ea0cf8eb 100644 --- a/scripts/gen-third-party-notices.ts +++ b/scripts/gen-third-party-notices.ts @@ -41,9 +41,9 @@ const DEV_ONLY_AREAS = [ /** First-party public native packages: reachable at runtime but not third-party. */ const FIRST_PARTY = new Set([ - 'node-addon-landlock-run', - 'node-addon-landlock-run-linux-arm64', - 'node-addon-landlock-run-linux-x64', + '@deepseek-ai/node-addon-landlock-run', + '@deepseek-ai/node-addon-landlock-run-linux-arm64', + '@deepseek-ai/node-addon-landlock-run-linux-x64', ]) /** @@ -587,7 +587,7 @@ ${BUILD_TIME_TOOLS.map(tool => `| [\`${tool.name}\`](${tool.repo}) | ${tool.lice ## First-party native packages -\`node-addon-landlock-run\` (and its platform packages) is built and released from this repository under BSD 3-Clause. It is listed here for completeness; it is first-party, not third-party. +\`@deepseek-ai/node-addon-landlock-run\` (and its platform packages) is built and released from this repository under BSD 3-Clause. It is listed here for completeness; it is first-party, not third-party. ` } diff --git a/tsconfig.base.json b/tsconfig.base.json index 634464cb9b..2965a1f794 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -38,7 +38,7 @@ "@cordisjs/plugin-timer": ["./vendor/timer/src"], "@cordisjs/plugin-hmr": ["./vendor/hmr/src"], "@cordisjs/plugin-logger-console": ["./vendor/logger-console/src"], - "node-addon-landlock-run": ["./native/landlock-run/packages/entry/src/index.ts"], + "@deepseek-ai/node-addon-landlock-run": ["./native/landlock-run/packages/entry/src/index.ts"], "@deepseek-ai/dsh-invariants": ["./packages/support/invariants/src/index.ts"], "@deepseek-ai/dsh-typert-registry": ["./packages/typert/registry/src/index.ts"], "@deepseek-ai/dsh-typert-loader": ["./packages/typert/loader/src/index.ts"], From 1627728eff0acd837c7b5cf0a63043f3668779ee Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sat, 8 Aug 2026 16:15:00 +0800 Subject: [PATCH 6/6] fix: allow audited publishing repository links --- .../verify-public-repository-links.spec.ts | 28 +++++++++++++++++++ scripts/verify-public-repository-links.ts | 21 ++++++++++---- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/scripts/verify-public-repository-links.spec.ts b/scripts/verify-public-repository-links.spec.ts index ec12f38427..372018d7cf 100644 --- a/scripts/verify-public-repository-links.spec.ts +++ b/scripts/verify-public-repository-links.spec.ts @@ -29,4 +29,32 @@ describe('public repository link policy', () => { { file: 'subject.md', line: 8 }, ]) }) + + it('allows only the exact audited trusted-publishing repository declarations', () => { + const internalOwner = ['deepseek', 'harness'].join('-') + const internalRepository = [internalOwner, internalOwner].join('/') + const repositoryUrl = `git+https://github.com/${internalRepository}.git` + const manifestLine = ` "url": "${repositoryUrl}",` + const constraintLine = `const repositoryUrl = '${repositoryUrl}'` + const allowedDeclarations = [ + ['native/landlock-run/packages/entry/package.json', manifestLine], + ['native/landlock-run/packages/linux-arm64/package.json', manifestLine], + ['native/landlock-run/packages/linux-x64/package.json', manifestLine], + ['scripts/check-workspace-constraints.ts', constraintLine], + ] as const + + for (const [file, source] of allowedDeclarations) { + expect(findInternalRepositoryReferences(file, source)).toEqual([]) + } + + const wrongFile = 'native/landlock-run/package.json' + expect(findInternalRepositoryReferences(wrongFile, manifestLine)).toEqual([{ file: wrongFile, line: 1 }]) + + const manifestFile = 'native/landlock-run/packages/entry/package.json' + const wrongField = ` "homepage": "${repositoryUrl}",` + expect(findInternalRepositoryReferences(manifestFile, wrongField)).toEqual([{ file: manifestFile, line: 1 }]) + + const encodedLine = manifestLine.replace('github.com/', 'github.com\\/') + expect(findInternalRepositoryReferences(manifestFile, encodedLine)).toEqual([{ file: manifestFile, line: 1 }]) + }) }) diff --git a/scripts/verify-public-repository-links.ts b/scripts/verify-public-repository-links.ts index 6d1e537733..e612e6641f 100644 --- a/scripts/verify-public-repository-links.ts +++ b/scripts/verify-public-repository-links.ts @@ -1,4 +1,4 @@ -/** Reject tracked files that expose the internal repository identity. */ +/** Reject tracked files that expose the internal repository identity outside audited publishing declarations. */ import { execFileSync } from 'node:child_process' import { existsSync, lstatSync, readFileSync, readlinkSync } from 'node:fs' @@ -9,6 +9,15 @@ const root = resolve(import.meta.dirname, '..') const internalOwner = ['deepseek', 'harness'].join('-') const internalRepository = [internalOwner, internalOwner].join('/') const internalIssueShorthand = `${internalOwner}#` +const trustedPublishingRepositoryUrl = `git+https://github.com/${internalRepository}.git` + +/** Exact declarations that intentionally expose the source repository for trusted publishing. */ +const allowedInternalRepositoryLineByFile: Readonly> = { + 'native/landlock-run/packages/entry/package.json': `"url": "${trustedPublishingRepositoryUrl}",`, + 'native/landlock-run/packages/linux-arm64/package.json': `"url": "${trustedPublishingRepositoryUrl}",`, + 'native/landlock-run/packages/linux-x64/package.json': `"url": "${trustedPublishingRepositoryUrl}",`, + 'scripts/check-workspace-constraints.ts': `const repositoryUrl = '${trustedPublishingRepositoryUrl}'`, +} const namedReferenceCharacters: Readonly> = { hyphen: '-', @@ -40,7 +49,7 @@ export interface InternalRepositoryReference { } /** - * Locate internal-repository references in one text file. + * Locate unaudited internal-repository references in one text file. * @param file - Repository-relative path used in diagnostics. * @param source - Text to inspect. * @returns every matching source line. @@ -49,7 +58,9 @@ export function findInternalRepositoryReferences(file: string, source: string): const references: InternalRepositoryReference[] = [] for (const [index, line] of source.split('\n').entries()) { const canonicalLine = canonicalReferenceText(line) - if (canonicalLine.includes(internalRepository) || canonicalLine.includes(internalIssueShorthand)) { + const isAllowedPublishingDeclaration = line.trim() === allowedInternalRepositoryLineByFile[file] + if (!isAllowedPublishingDeclaration + && (canonicalLine.includes(internalRepository) || canonicalLine.includes(internalIssueShorthand))) { references.push({ file, line: index + 1 }) } } @@ -81,9 +92,9 @@ const isMain = invokedPath !== undefined && import.meta.url === pathToFileURL(re if (isMain) { const references = scanRepository(root) if (references.length === 0) { - console.log('verify-public-repository-links: tracked files expose no internal repository identity.') + console.log('verify-public-repository-links: tracked files expose no unexpected internal repository identity.') } else { - console.error('verify-public-repository-links: internal repository references found:') + console.error('verify-public-repository-links: unexpected internal repository references found:') for (const reference of references) console.error(` ${reference.file}:${String(reference.line)}`) process.exitCode = 1 }