# 派发面验证(B1–B7) 对象:`@local/dsh-codex-bridge`,commit `27aac52`。方法:FIXTURE(验证矩阵中的 FIX),全部离线执行,不消耗真实模型 turn,不读写真实 Codex home。 测试入口:`node --test tests/dispatch.test.mjs`(32 个测试:30 个 FIX + 2 个 CODE 静态审计,全部通过,约 7.5s)。 ## 方法 - **加载真实插件**:`import { apply } from '../index.js'`,在假 Host context(`tools.register` / `effect` / `get` / `subprocess`)上执行 `apply`,捕获六个工具定义后直接调用 `execute(args, { agent: {} })`。 - **假 subprocess seam**(`tests/support/fake-seam.mjs`)复刻插件实际消费的契约: `spawn({ argv, cwd, stdio: { stdin: { data }, stdout: { maxBytes, spill: { maxBytes } }, stderr: { maxBytes } }, graceMs, env })` → `{ done: Promise<{ exitCode, signal }>, collected: { stdout: { readFrom }, stderr: { readFrom } }, terminate() }`。 collect 模式保留每个流最后 `maxBytes` 字节,丢弃前部字节时 `lossy=true`;stdout 溢出且配置 spill 时写出 `spillPath`;`terminate()` 先发 SIGTERM、`graceMs` 后仍存活则 SIGKILL。 - **假 codex 可执行文件**(`tests/fixtures/fake-codex.mjs`,另有 `.cmd` / POSIX shim 供手工使用):Node 脚本,按 `FAKE_CODEX_*` 环境变量产生 `thread.started` / `item.completed` / `turn.completed` / `error` / `turn.failed` / 非零退出 / hang / 超大 stdout 等行为;并把 argv、cwd、env、stdin 原始字节与 SHA-256 写入 capture 文件。Windows 无法用 CreateProcess 直接执行 shebang 脚本,假 seam 用 node 启动该脚本;插件 argv 契约在记录的 spawn spec 上断言,不因该翻译而弱化。 - **临时 CODEX_HOME 夹具**(`tests/support/fixture.mjs`):`session_index.jsonl`、`sessions/.../rollout-*.jsonl`(含 `session_meta.cwd`)、`thread-writer-locks`、`automations`、`goals_1.sqlite`(node:sqlite)。每个测试独立目录,位于 %TEMP%,测试结束删除,不接触真实 ${CODEX_HOME}。 - **凭据泄漏探针**:测试进程设置 `OPENAI_API_KEY` / `GITHUB_TOKEN` / `CODEX_API_KEY` / `AWS_SECRET_ACCESS_KEY`,断言 spawn env 与子进程实际 env 中均不存在这些键。 > **修复状态**:验证时点代码为 `27aac52`,B1 的 prompt `trim()` 限制与 B5 的 usage 回退限制已在提交 `4fdc340` 修复并加回归测试(见文末「修复记录」)。 ## 结果摘要 | 项 | 结论 | 要点 | |---|---|---| | B1 新线程 argv/stdin/env | PASS-WITH-LIMITS | argv 形状、Unicode/多行 stdin 字节、env 白名单与凭据隔离全部符合;prompt 首尾空白被 `trim()` | | B2 异步与轮询 | PASS | `waitMs=0` 立即 running+runId;列表/详情/轮询到 completed;`waitMs>0` 阻塞;`maxWaitMs` 封顶 | | B3 resume | PASS-WITH-LIMITS | argv、rollout-meta cwd、线程解析、单写者保护符合;外部写者真实拒绝形态离线不可复现 | | B4 失败路径 | PASS | 非零退出 / 协议 error / turn.failed / spawn 抛错 / done reject / 未知线程 / 未知 runId / 空 message 均为明确失败 | | B5 codex_run 语义 | PASS-WITH-LIMITS | limit clamp、target、usage、preview、finalMessage 裁剪、lossy 标记符合;真实 CLI 的 usage 字段差异见缺陷 3 | | B6 dispose/终止 | PASS | 运行中子进程被终止一次、run 变 failed、后续 send 被拒;已完成 run 不受影响 | | B7 liveness nudge | PASS-WITH-LIMITS | allowNudge 门禁、固定消息、blocked/paused 不 nudge、异常捕获符合;turn 失败语义为 nudged+failed | | CODE 静态审计 | PASS | `lib/runner.mjs` env 白名单无凭据形状名、不展开 `process.env`;buildArgv 模板与验证一致 | ## B1 新线程派发 argv/stdin/env — PASS-WITH-LIMITS 证据(`B1-new-thread` 等): - **argv**:` exec --json --skip-git-repo-check -C -c approval_policy=never -`,与矩阵一致。可选 `model` / `approvalPolicy` / `sandboxMode` 依次追加为 `-m `、`-c approval_policy=<...>`、`-c sandbox_mode=<...>`,位置在结尾提示标记 `-` 之前;未传 `cwd` 时使用 `config.defaultCwd`。 - **stdin**:子进程侧 capture 证明收到的原始 stdin 字节与发送内容逐字节一致(UTF-8;中文、emoji、tab、多行均保留;字节数与 SHA-256 与测试期望一致),而不是只经插件内存推断。 - **env**:spawn spec 与子进程 env 都只含白名单键(`PATH`、`SystemRoot`、`TEMP` 等)以及对照夹具显式声明的键;四个凭据标记键均不存在;`CODEX_HOME` 指向夹具 home,`NO_COLOR=1`、`TERM=dumb`。 - **CODE**:`lib/runner.mjs` 的 `ENV_KEYS` 是显式数组(29 项),全文没有 `...process.env`,白名单内没有 KEY/TOKEN/SECRET/PASSWORD/CREDENTIAL 形状的名字;`CODEX_HOME` 为显式赋值。 - **限制**:`index.js` 在派发前执行 `message.trim()`,因此 prompt 的首尾空白不会原样到达 stdin;内部换行与 Unicode 不变。矩阵“prompt 原样传入”按内容成立、按首尾空白不成立,行为由 `B1 prompt fidelity` 测试固定。建议 README 注明,或改为用 trim 判空、传原串。 ## B2 异步与轮询 — PASS - `waitMs=0`:子进程夹具延迟 500ms 时,`codex_send` 37ms 返回 `status: running` + runId;`codex_run` 无参列表可见该 run(target `(new)`、prompt 预览);详情为 running、`target: (new)`、`final message: (none yet)`。 - 轮询到 `completed` 后详情含 `target: (new) -> `、`usage`、final message。 - `waitMs>0`:子进程延迟 100ms 时阻塞到 completed 才返回(耗时 ≥ 延迟)。 - `maxWaitMs` 封顶:`config.maxWaitMs=1000`、请求 `waitMs=5000`、子进程睡眠 2500ms → 1053ms 返回 `status: running`(未获得结果,但按封顶返回,提示继续轮询)。 - 无 run 时列表返回 `no bridge runs in this Host session yet`。 - **限制**:跨 DSH 会话的 Host 级 run 可见性属于 LIVE/C2(见 `docs/verification.md`),FIX 只覆盖同一插件实例。 ## B3 resume 续聊 — PASS-WITH-LIMITS - **argv**:` exec resume --json --skip-git-repo-check -c approval_policy=never -`;resume 不带 `-C`(与源码一致),cwd 由子进程工作目录承载。 - **cwd**:未传 `cwd` 时取线程 rollout 首行 `session_meta.payload.cwd`;显式 `cwd` 可覆盖。 - **线程解析**:名称、`@名称`、id 前缀都解析到同一线程 id;解析结果出现在 argv 倒数第二个位置。 - **单写者**:同线程已有本插件 running turn 时再次 `codex_send` 返回 `refused: thread