Merge pull request 'fix/#663回归问题' (#665) from feat/support-more-node into master
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 6m28s
Project CI / AI game creator shell Rust crates (push) Successful in 4m11s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 7m51s
Project CI / Frontend tests (push) Successful in 3m14s
Project CI / Backend tests (push) Successful in 7m50s
Project CI / AI game creator shell web tests (push) Failing after 4m22s
Project CI / Repository checks (push) Successful in 7m51s
Project CI / Native shell tests (push) Successful in 10m9s

Reviewed-on: #665
This commit was merged in pull request #665.
This commit is contained in:
2026-10-07 17:54:36 +08:00
9 changed files with 280 additions and 128 deletions
@@ -9,13 +9,13 @@
## 目标
开发构建的 Linux 命令沙箱能原生解析并复用宿主 fnm / nvm 托管的 Node 安装:宿主发现与沙箱只读挂载使用同一套窄叶校验;`.nvmrc` / `.node-version` pin 权威、`engines.node` 仅为偏好;npm 以受信任 `node <npm-cli.js>` 形态启动且不丢失 `npm install` 的联网判定。不再要求用户改系统 Node、把宿主 shim 指向 `/usr/bin`,或把托管目录软链进系统路径。
开发构建的 Linux 命令沙箱能原生解析并复用宿主 fnm / nvm 托管的 Node 安装:宿主发现与沙箱只读挂载使用同一套窄叶校验;`.nvmrc` / `.node-version`(nvm / fnm 都读)命中已安装版本时优先、未安装时继续回退,`engines.node` 仅为偏好;npm 以受信任 `node <npm-cli.js>` 形态启动且不丢失 `npm install` 的联网判定。不再要求用户改系统 Node、把宿主 shim 指向 `/usr/bin`,或把托管目录软链进系统路径。
## 范围
- 移除把 `node` / `npm` / `npx` 指向 `/usr/bin/*` 的宿主 shim 依赖,开发构建默认解析托管安装。
- 枚举 fnm `node-versions/<version>/installation`(含 `aliases/default`)与 nvm `versions/node/<version>`,按窄叶规则校验完整安装前缀。
- 版本选择:`.nvmrc` / `.node-version` pin > PATH 可解析的可用 Node > 版本管理器回退链(`engines.node` 最高匹配 > 活动版本 > 默认别名 > 已安装最高版本)。
- 版本选择:`.nvmrc` / `.node-version` pin > PATH 可解析的可用 Node > 版本管理器回退链(`engines.node` 最高匹配 > 默认别名 > 已安装最高版本)。活动版本不单独查询:激活时它已在 `PATH` 里,回退链不再重复一份可能失效的副本(原 `FNM_MULTISHELL_PATH` / `NVM_BIN` 分支已删除)。
- Linux 命令沙箱把通过校验的完整安装前缀只读挂载,并以 `node <npm-cli.js>` 启动 npm;`npm install` 保持联网判定。
- 沙箱内联环境不继承 fnm multishell 等临时版本管理器变量。
@@ -35,7 +35,7 @@
## 验收标准
- [x] 开发构建在没有 `/usr/bin` shim 的情况下从 fnm / nvm 找到 Node,并在沙箱内运行出真实 `node --version` 与 `npm --version`。
- [x] `.nvmrc` / `.node-version` 指定的版本未安装时命令失败关闭,且不回退到其它已安装版本;`engines.node` 不匹配时不阻塞。
- [x] `.nvmrc` / `.node-version`(nvm / fnm 都读)指定的版本未安装时命令不阻塞,继续回退到 PATH / `engines` 偏好 / 默认别名 / 已安装最高版本;`engines.node` 不匹配时同样不阻塞。
- [x] 不支持的 pin 写法(如 `iojs`、`>20`、`<=20`)按未 pin 处理并回退,而不是误判为「已理解但未命中」。
- [x] 只有通过窄叶校验的完整安装前缀会被只读挂载;HOME、管理器根、`aliases`、宽泛目录、不完整前缀和逃逸 symlink 全部失败关闭。
- [x] npm 在 Linux 上以受信任 `node <npm-cli.js> ...` 启动,`npm install` 仍被判定为联网命令,普通 `npm run` 仍离线。
@@ -4,11 +4,11 @@
- 背景:开发构建里 AGC 让命令沙箱执行 `npm run build` / `npm install` 时,宿主 Node 由 fnm 托管,`node` / `npm` 实际是随 shell 会话变化的 fnm multishell 目录里的 shim;bwrap `--tmpfs /run` 会抹掉该路径,而只按单文件挂载 `<前缀>/bin/npm`(它软链到 `lib/node_modules/npm/bin/npm-cli.js`)会因 `Cannot find module '../lib/cli.js'` 失败。此前把宿主 `node` / `npm` / `npx` shim 指到 `/usr/bin/*` 是错误取舍:系统 Node 26 默认启用实验性 Web Storage,会顶掉 vitest 0.34 jsdom 的 localStorage,使 AGC 测试套件在 HEAD 即失败(见 `pitfalls.md` 2026-10-03 条)。
- 决策(宿主发现与沙箱挂载共用窄叶校验):新增 `validate_node_installation_prefix`,canonicalize 后拒绝 `/home`、`/root`、`/tmp`、`/var`、`/etc`、`/proc`、`/dev`、`/run`、`/sys`、`/boot`、`/srv` 根、HOME 及其祖先和相对路径,并要求前缀同时含 `bin/node` 与 `lib/node_modules/npm/bin/npm-cli.js`(bundle 形态为 `<前缀>/node` + `node_modules/npm/bin/npm-cli.js`)。宿主版本枚举与 Linux 沙箱只读挂载都调用它,避免两处信任口径漂移。
- 决策(版本选择):`.nvmrc` / `.node-version` 是权威 pin,能理解但未安装时返回 `node-version-pinned-not-installed` 失败关闭;`package.json` `engines.node` 只是偏好,永不阻塞;不支持的写法(`iojs`、`||`、部分 `>` / `<=`、hyphen range、prerelease)按未 pin 回退。整体顺序为 pin 命中 > PATH 可解析的可用 Node > 版本管理器回退链(`engines` 最高匹配 > 活动版本 > 默认别名 > 已安装最高版本),只实现文档化比较子集(精确三元组、major、`>=` / `>` / `<=` / `<`、`^`、`~`、`x` / `*`、`lts/*`);`v22.23.3` 这类带 `v` 的 `.nvmrc` 必须先剥前缀。nvm 的 `alias/default` 常只写主版本号(如 `22`),必须按同一 pin 子集在已安装版本里选最高匹配,不能当成完整三元组 `(22,0,0)`。
- 决策(沙箱内启动形态):Linux npm 改为 `node <npm-cli.js>`,因为单文件挂载 npm 软链必然丢 `../lib/cli.js`;只读挂载整棵已验证的安装前缀(不是整个 HOME、`FNM_DIR` 或 `NVM_DIR`)。`npm install` 的联网判定跟随真实启动形态(`node` + `npm-cli.js` + `install`),不因包装变化丢 `--share-net`。
- 决策(版本选择):`.nvmrc` / `.node-version`(nvm / fnm 都读)只改变搜索顺序、不是硬性要求:命中已安装版本就优先用它,能理解但未安装时按未 pin 继续回退,不再返回 `node-version-pinned-not-installed`;`package.json` `engines.node` 同样是偏好,永不阻塞;不支持的写法(`iojs`、`||`、部分 `>` / `<=`、hyphen range、prerelease)按未 pin 回退。整体顺序为 pin 命中 > PATH 可解析的可用 Node > 版本管理器回退链(`engines` 最高匹配 > 默认别名 > 已安装最高版本);活动版本不单独查询(`FNM_MULTISHELL_PATH` / `NVM_BIN`),因为激活时它已在 `PATH` 里,回退链无需重复一份可能失效的副本;只实现文档化比较子集(精确三元组、major、`>=` / `>` / `<=` / `<`、`^`、`~`、`x` / `*`;`lts/*` 与 `lts/<codename>` 都需要 LTS 行映射,当前按未 pin 回退而不猜);`v22.23.3` 这类带 `v` 的 `.nvmrc` 必须先剥前缀。nvm 的 `alias/default` 常只写主版本号(如 `22`),必须按同一 pin 子集在已安装版本里选最高匹配,不能当成完整三元组 `(22,0,0)`。
- 决策(沙箱内启动形态与联网放行):Linux npm 改为 `node <npm-cli.js>`,因为单文件挂载 npm 软链必然丢 `../lib/cli.js`;只读挂载整棵已验证的安装前缀(不是整个 HOME、`FNM_DIR` 或 `NVM_DIR`)。`npm install` 的联网放行只认解析层给出的可信 `(node, npm_cli)` 精确匹配(executable 是对应 node、首个参数是对应 `npm-cli.js`、子命令是 `install`),不再按 basename 判定:项目里同名的 `npm-cli.js` 可写,只加「路径必须绝对」也能绕过;拿不到可信对时一律不放网。
- 决策(范围与非目标):托管版本管理器发现只在 `debug_assertions` / development 生效,发布构建继续只认随包 bundle;不把 fnm / nvm CLI 做成沙箱内工具;Windows 不变;不新增 fnm / nvm 之外的版本管理器。
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/environment_check.rs`(窄叶校验、托管安装枚举、pin / engines 解析、选择与回退)、`command_sandbox.rs`(Node 工具链挂载收集与合并、`command_sandbox_requests_npm_install`、`FNM_MULTISHELL_PATH` 清理)、`command_exec.rs`(Linux npm `node_launcher`、`project_command_actual_target`、非 Node 程序 PATH 前置工具链 bin)、`process_session_bridge.rs`(`ProcessSessionLaunchPlan::from_launch` 改用实际目标)。
- 验证方式:`environment_check` 24 passed、`command_sandbox` 14 passed、`command_exec` 19 passed、`process_session` 27 passed(均 `--test-threads=1`);`GENARRATIVE_COMMAND_SANDBOX_REAL_TEST=1` 真机 bwrap 内 fnm v22.23.3 的 `node --version` 与 npm 10.9.9 通过;另装 nvm v0.40.8 + Node v22.23.3,`command_sandbox_real_linux_opt_in_runs_nvm_installation_prefix` 证明真实 nvm 前缀可在 bwrap 内跑 node / npm,`real_node_npm_environment_versions`(仅 `NVM_DIR` + 空 PATH + 临时 HOME)证明托管解析确实选中 nvm;`cargo fmt --check`、`npm run check:encoding`、`git diff --check` 通过。验证后 fnm 仍是宿主默认,nvm 未写入任何 shell profile。
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/environment_check.rs`(窄叶校验、托管安装枚举、pin / engines 解析、选择与回退)、`command_sandbox.rs`(Node 工具链挂载收集与合并、`prepare_command_sandbox_launch` 透传可信 `(node, npm_cli)`、`LinuxSandboxPlan::npm_install_network`、`FNM_MULTISHELL_PATH` 清理)、`command_exec.rs`(Linux npm `node_launcher`、`project_command_actual_target`、`prepare_project_command_launch_spec` 把可信 launcher 交给沙箱、非 Node 程序 PATH 前置工具链 bin)、`process_session_bridge.rs`(`ProcessSessionLaunchPlan::from_launch` 改用实际目标)。
- 验证方式:`environment_check` 25 passed、`command_sandbox` 15 passed、`command_exec` 19 passed、`process_session` 27 passed(均 `--test-threads=1`);`npm_install_network_detection_requires_the_trusted_node_launcher` 覆盖「项目同名 `npm-cli.js` 不放网、无可信 launcher 不放网、executable 不匹配不放网」,`namespace_arguments_share_net_only_when_trusted` 覆盖 `--share-net` 开关,`npm_command_targets_node_plus_npm_cli_on_linux` 断言解析层可信 `(node, npm_cli)` 与实际目标逐字一致;`GENARRATIVE_COMMAND_SANDBOX_REAL_TEST=1` 真机 bwrap 内 fnm v22.23.3 的 `node --version` 与 npm 10.9.9 通过;另装 nvm v0.40.8 + Node v22.23.3,`command_sandbox_real_linux_opt_in_runs_nvm_installation_prefix` 证明真实 nvm 前缀可在 bwrap 内跑 node / npm,`real_node_npm_environment_versions`(仅 `NVM_DIR` + 空 PATH + 临时 HOME)证明托管解析确实选中 nvm;`cargo fmt --check`、`npm run check:encoding`、`git diff --check` 通过。验证后 fnm 仍是宿主默认,nvm 未写入任何 shell profile。
- 边界:nvm 已在验证主机安装(v0.40.8 + Node v22.23.3)并跑通真实前缀与仅 nvm 解析;CI 仍由临时目录夹具覆盖 fnm / nvm 布局,真实安装路径测试保持 opt-in。真实验收前不宣称发布构建也支持 fnm / nvm。
## 2026-10-06 小红书导出 validate/pack:Chrome 61 能力按硬性 ERROR 拦下,pack 自带白名单不再共享
@@ -17,8 +17,9 @@
- **根因 2(`--tmpfs /run` 抹掉活动版本)**:fnm 的活动 `PATH` 项是 `/run/user/<uid>/fnm_multishells/<pid>/bin`;sandbox 的 `--tmpfs /run` 会清空该目录,sandbox 内解析到的 `node` 随之失效或退回系统版本。不要依赖宿主 `PATH` 原样进入沙箱:canonicalize 路径,并把活动版本管理器变量(如 `FNM_MULTISHELL_PATH`)从 sandbox 环境里剔除。
- **根因 3(错误取舍会打穿测试)**:把宿主 `node` / `npm` / `npx` shim 指到 `/usr/bin/*` 能让沙箱借用系统 Node,但在本机系统 Node 26 上会默认启用实验性 Web Storage,顶掉 vitest 0.34 jsdom 的 localStorage,AGC 测试在 HEAD 即红(见下方 2026-10-03「AGC 测试不在任何 tsconfig 里」条的环境提示)。正确方向是原生支持托管安装,而不是改宿主 shim。
- **补充(nvm default 别名是主版本号)**:`nvm alias default 22` 写进 `$NVM_DIR/alias/default` 的内容是 `22`,不是完整三元组。若按精确 `(22,0,0)` 去匹配 `versions/node/v22.23.3` 会永远落空,默认别名形同不存在;必须用与 `.nvmrc` 相同的比较子集在已安装版本里选最高匹配。
- **现行口径**:宿主发现与沙箱挂载共用 `validate_node_installation_prefix`;`.nvmrc` / `.node-version` 权威、`engines.node` 偏好;Linux npm 以 `node <npm-cli.js>` 启动并保留 `npm install` 联网判定。契约见技术方案 V1.11.2。
- **验证**:`GENARRATIVE_COMMAND_SANDBOX_REAL_TEST=1` 跑 `command_sandbox_real_linux_opt_in_runs_host_node_and_npm_cli`,在真实 fnm v22 前缀下 bwrap 内 `node --version` 与 `node <npm-cli.js> --version` 均通过;单元用例覆盖 pin / engines / 不支持写法 / 宽叶与逃逸前缀 / 联网判定。
- **根因 4(联网放行只看 basename 会被同名文件冒充)**:`npm install` 是唯一允许联网的入口,最初只按「首个参数 basename 是 `npm-cli.js` 且第二个参数是 `install`」放行。Linux 上 `command.exec` 不对 `node` 的脚本参数做路径校验,项目根又可写,于是在项目里放一个自写的 `npm-cli.js` 再 `node 项目/npm-cli.js install` 就能拿到 `--share-net`。修法是让联网放行与解析层给出的可信 `(node, npm_cli)` 精确比对,拿不到可信对时一律不放网;只加「路径必须绝对」不够,绝对路径的项目内文件照样绕过。
- **现行口径**:宿主发现与沙箱挂载共用 `validate_node_installation_prefix`;`.nvmrc` / `.node-version`(nvm / fnm 都读)命中已安装版本时优先、未安装时继续回退,`engines.node` 同为偏好;Linux npm 以 `node <npm-cli.js>` 启动,`npm install` 联网放行只认解析层给出的可信 `(node, npm_cli)` 精确匹配。契约见技术方案 V1.11.2。
- **验证**:`GENARRATIVE_COMMAND_SANDBOX_REAL_TEST=1` 跑 `command_sandbox_real_linux_opt_in_runs_host_node_and_npm_cli`,在真实 fnm v22 前缀下 bwrap 内 `node --version` 与 `node <npm-cli.js> --version` 均通过;单元用例覆盖 pin / engines / 不支持写法 / 宽叶与逃逸前缀 / 可信 launcher 联网放行。
## 2026-10-07 cargo 目标目录里被"刷新 mtime"的陈旧 shared-contracts 会让编译报源文件里明明存在的字段缺失
@@ -621,9 +621,9 @@ Runner-kill E2E 不再以 latest task 或单个 process record 推断整体恢
开发构建(`debug_assertions`)下,AGC 生成和验证 Web 项目所用的 Node 往往由 fnm / nvm 等版本管理器托管:安装前缀位于用户目录下,活动 `PATH` 指向随 shell 会话变化的临时目录(如 fnm 的 multishell)。V1.11.2 让开发构建的命令沙箱原生解析并只读复用这些托管安装,不再要求用户改系统 Node、把宿主 shim 指到 `/usr/bin`,或把托管目录软链进系统路径。发布构建继续只认随包 bundle,不新增托管版本管理器探测,也不改变 bundle 缺失时的失败口径;Windows 行为不变。
- 版本来源是机器上可枚举的托管安装:fnm 的 `node-versions/<version>/installation`(含 `aliases/default` 指向的默认别名)与 nvm 的 `versions/node/<version>`。宿主发现和沙箱只读挂载共用同一套窄叶校验,不允许两处信任口径漂移。
- 解析优先级为:`.nvmrc` / `.node-version` 的权威 pin 命中 > 宿主 `PATH` 能解析出的可用 Node > 版本管理器回退链(`package.json` `engines.node` 偏好中的最高匹配 > 当前活动版本 > 默认别名 > 已安装最高版本)。`.nvmrc` / `.node-version` 能理解但未安装时必须失败关闭(`node-version-pinned-not-installed`),不得静默回退;`engines.node` 只是偏好,任何情况下都不阻塞。只实现文档化的比较子集(精确三元组、major、`>=` / `>` / `<=` / `<`、`^`、`~`、`x` / `*` 通配、`lts/*`);不支持或无法解析的写法按「未 pin」处理并回退。
- 解析优先级为:`.nvmrc` / `.node-version`(nvm / fnm 都读)命中已安装版本 > 宿主 `PATH` 能解析出的可用 Node > 版本管理器回退链(`package.json` `engines.node` 偏好中的最高匹配 > 默认别名 > 已安装最高版本)。不单独查询活动版本(`FNM_MULTISHELL_PATH` / `NVM_BIN`):激活时它已经在 `PATH` 里,回退链重复一份可能失效的副本只会带来误导。`.nvmrc` / `.node-version` 只改变搜索顺序,不是硬性要求:能理解但未安装时按「未 pin」继续回退,不返回 `node-version-pinned-not-installed`、不阻塞命令;`engines.node` 同样只是偏好,任何情况下都不阻塞。只实现文档化的比较子集(精确三元组、major、`>=` / `>` / `<=` / `<`、`^`、`~`、`x` / `*` 通配);`lts/*` 与 `lts/<codename>` 都需要 LTS 行映射,当前与其它不支持写法一样按「未 pin」处理并回退,不声称匹配任意版本。
- 只读挂载只允许通过窄叶校验的完整安装前缀(同时含 `bin/node` 与 npm 的 `npm-cli.js`)。HOME、`FNM_DIR` / `NVM_DIR` 根、`aliases` 目录、宽泛用户目录、不完整前缀,以及 canonicalize 后逃逸出受控前缀的 symlink 全部拒绝并失败关闭;不得为了兼容而挂载整个用户 HOME 或版本管理器数据目录。
- Linux 上 npm 不再直接执行 npm shim,而是以受信任的 `node <npm-cli.js> ...` 启动;`npm install` 的联网判定必须跟随这条真实启动形态,不能因为包装方式变化而丢失联网或反向放开。
- Linux 上 npm 不再直接执行 npm shim,而是以受信任的 `node <npm-cli.js> ...` 启动;`npm install` 的联网放行必须与解析层给出的可信 `(node, npm_cli)` 精确一致(executable 是对应的 node、首个参数是对应的 `npm-cli.js`、子命令是 `install`),不能只匹配 basename——项目根可写,项目里同名的 `npm-cli.js` 会骗到 `--share-net`;拿不到可信对时一律不放网。
- 沙箱内联环境不继承宿主活动版本管理器的临时变量(如 fnm multishell 路径);版本管理器 CLI(`fnm` / `nvm`)本身不需要在沙箱内可用。
## V1.12 受控本地 Git 提交
@@ -865,7 +865,7 @@ Agent 可见的系统指令、工具与参数说明、恢复指引和上下文
2026-07-14 V1.11.1 最终真实验收:发布 AppData 的真实 `gpt-5.5` `process-session` 形成 41 条 task、75 条 event、63 条 Agent DB 和 8 条 receipt,唯一 start、3 poll、唯一 stdin / terminate、3 次 cursor 推进及唯一 terminal / completed / assistant全部通过;Runner kill套件形成 13 条 task、19 条 event、19 条 Agent DB,真实 SIGKILL后项目 cwd进程清零、新 boot保持同 run / session并只形成 1 条 reconciliation。两套的 reconnect、重放、重复 action / message / receipt、公共进程正文、密钥和诱饵泄漏均为 0,disposable项目均自动清理;V1.11.1 持久进程链路据此完成验收。
2026-10-07 V1.11.2 切片:Linux 命令沙箱原生支持 fnm / nvm 托管的 Node。开发构建(`debug_assertions`)先从 `.nvmrc` / `.node-version` 的权威 pin、再按 `package.json` `engines.node` 偏好、活动版本、版本管理器默认别名和已安装最高版本选择托管安装;`.nvmrc` / `.node-version` 能理解但未安装时失败关闭,`engines.node` 和无法解析的写法不阻塞。宿主发现与沙箱只读挂载共用同一窄叶校验,只挂载完整安装前缀(含 `bin/node` 与 npm 的 `npm-cli.js`),拒绝 HOME、管理器根、`aliases`、宽泛目录、不完整前缀和逃逸 symlink。Linux npm 以 `node <npm-cli.js>` 启动,`npm install` 的联网判定跟随该真实形态;sandbox 环境剔除 fnm multishell 变量。发布构建仍只认随包 bundle,Windows 不变。定向 Rust 测试与真实 bwrap 内 fnm v22 的 `node` / npm 运行已通过;真实 nvm 端到端待补。
2026-10-07 V1.11.2 切片:Linux 命令沙箱原生支持 fnm / nvm 托管的 Node。开发构建(`debug_assertions`)按 `.nvmrc` / `.node-version`(nvm / fnm 都读)命中、`package.json` `engines.node` 偏好、版本管理器默认别名和已安装最高版本的顺序选择托管安装;`.nvmrc` / `.node-version` 未安装时继续回退、不阻塞,`engines.node` 和无法解析的写法同样不阻塞。宿主发现与沙箱只读挂载共用同一窄叶校验,只挂载完整安装前缀(含 `bin/node` 与 npm 的 `npm-cli.js`),拒绝 HOME、管理器根、`aliases`、宽泛目录、不完整前缀和逃逸 symlink。Linux npm 以 `node <npm-cli.js>` 启动,`npm install` 的联网判定跟随该真实形态;sandbox 环境剔除 fnm multishell 变量。发布构建仍只认随包 bundle,Windows 不变。定向 Rust 测试与真实 bwrap 内 fnm v22 的 `node` / npm 运行已通过;真实 nvm 端到端待补。
2026-07-14 起,同一文档的“V1.12 受控本地 Git 提交”补齐单 Agent 的修改、验证、审阅、本地提交闭环。新增且只新增 `project.git_commit`,输入绑定 `message / paths / expectedHead / expectedSnapshotFingerprint`,最多提交 12 个显式安全路径;它是不可降为 `auto` 的强制确认工具,legacy 空策略也继续要求确认,项目策略仍可显式拒绝。动态隔离 child 无条件禁用该工具,最终提交由父 Agent 统一发起。当前 run 必须在当前非零 project revision 上已有 passed verification gate。`git.inspect` 签发的 `commitSnapshotFingerprint` 绑定 HEAD、附着分支、规范化安全状态和全部安全变更文件内容;`.agent` 等控制面正常落盘不制造跨动作漂移,安全源码、HEAD、分支、revision 或 gate 任一变化仍失败关闭。提交只支持标准仓库根和本地附着分支,要求真实 index 没有 staged 内容,并用临时 index、真实 `index.lock`、`commit-tree` 和带 expected old HEAD 的 `update-ref HEAD` 精确前移 ref,同步 HEAD / branch reflog后跨平台原子安装 index;未选改动保持未暂存。它不开放 remote、分支切换、merge / rebase、reset、stash、tag、submodule 或 worktree 写操作,也不能通过 `command.exec` 绕过 `.git` 只读沙箱。成功 observation、Agent DB 审计和 terminal receipt 只保留 parent / commit SHA、分支、安全路径、message SHA-256 和剩余变更计数;ref 前移后的不确定错误或审计失败进入 `needs-reconciliation`,已知 commit 的审计失败仍在 fallback receipt 保存 commit SHA,恢复不得重放提交。