test(snapshot): pin the parent-only override through the assembled headless app

Review fix (ds-review-bot on #623): the ACP scenario runs at deployment
strength (the automation protocol has no session-scoped switch), so the
assembled-app path could not detect the delegation bypass itself. The new
keyless subagent-inheritance headless scenario closes that on the
semantic-checkpoint precedent: a seeded parent log carrying a real
sandbox/mode: read-only switch under a workspace-write deployment default
is resumed through the Loader-booted cli-demo app via a resume fixture
plugin and delegates through the real subagent tool; the child's real
write is denied by the real dsh-fs-sandbox fence (physical ENOENT
assertion), its persisted header carries the inherited baseline, and both
logs pin as expected outputs. Verified red: disabling the driver's capture
makes the scenario fail on the disk assertion (the child writes under the
deployment default).
This commit is contained in:
kingwl
2026-07-27 12:54:54 +08:00
parent 50d759c888
commit 68d59a5414
10 changed files with 300 additions and 5 deletions

View File

@@ -1,6 +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
2026-07-25-subagent-policy-inheritance.md: ea8e4626a1bd2ac31c2a7ce207bf9ba73e2e32fc
2026-07-25-subagent-policy-inheritance.zh.md: b9e978761af7de6ecf9a2e6ec167e2bb512c9ebf
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md
2026-07-25-subagent-policy-inheritance.md: fcc91310399dd0bbaae3acc444fbf790ae4d0b3c
2026-07-25-subagent-policy-inheritance.zh.md: d06948dc34213e87300bf46ee5f1fc43d8f48fc7

View File

@@ -34,5 +34,5 @@ A confined child that hits the wall gets the ordinary denial marker; an escalati
- A parent's tightened sandbox mode and `'never'` approval stance now bind spawn children, fork children (regardless of seed timing), and grandchildren; the delegation bypass is closed at every depth, with no first-turn timing window (veto, injection, crash). Pinned by the real-wall suite in `packages/subagent/subagent-inprocess/tests/inheritance.spec.ts` (a scripted-model child hitting the real `dsh-fs-sandbox` fence through the real `write` tool, asserted on disk state and denial markers — including the delegation-vs-late-switch race, a veto-capable prompt-submit listener, and header durability before any child turn) and the `overrideOf` contract tests in the two service suites (baseline read, seed-boundary precedence, closed-vocabulary rejection).
- The baseline rides `SessionHeader` through both persistence backends (a JSONL header-line field; SQLite `sessions` columns with `SCHEMA_VERSION` bumped to 11 — pre-release, no migration), so resume restores it like `delegationDepth`; the child may later be switched independently, its own post-seed events outranking the baseline.
- Accepted limits: a parent switch made while a child is already running does not propagate (snapshot semantics); out-of-process backends (`subagent-acp`, subprocess children) inherit nothing here — their policy belongs to the child harness's own deployment, the sandbox Agent Note's deferred phase.
- Snapshot coverage runs at deployment strength only: the recorded `subagent-sandbox-inheritance` scenario proves a delegated child confined under a read-only DEPLOYMENT policy through the assembled ACP app, but the automation-only protocol has no session-scoped switch, so a parent-ONLY override (wider default, tightened parent, denied child) is not expressible there. When picked up, the shape is the headless suite's semantic-checkpoint precedent: seed a persisted parent log carrying a real `sandbox/mode` switch, resume it through the Loader-booted cli-demo app via a resume fixture plugin, and let it delegate — a scenario-harness addition (the headless suite has no subagent+sandbox overlay yet), not a new mechanism.
- Assembled-app snapshots pin both strengths. The recorded `subagent-sandbox-inheritance` ACP scenario proves a delegated child confined under a read-only DEPLOYMENT policy (the automation-only protocol has no session-scoped switch). The keyless `subagent-inheritance` headless scenario pins the parent-ONLY override on the semantic-checkpoint precedent: a seeded parent log carrying a real `sandbox/mode: read-only` switch under a workspace-write deployment default is resumed through the Loader-booted cli-demo app via a resume fixture plugin and delegates; the child's real write is denied by the real fence, its persisted header carries the inherited baseline, and disabling the driver's capture makes the scenario fail on the physical disk assertion — the assembled-app red/green anchor for the delegation bypass.
- `dsh-subagent-inprocess` declares `dsh-sandbox-policy` and `dsh-user-approval` as peers for the `ctx.get` typing; both remain runtime-optional. `SessionHeader` gains two neutral optional string fields; `SESSION_FORMAT_VERSION` stays 0 (additive, pre-release).

View File

@@ -34,5 +34,5 @@ Status: implemented
- 父级收紧后的沙箱模式与 `'never'` 审批立场现在会约束 spawn 子 agent、fork 子 agent无论种子时机如何与孙代 agent委派旁路在每一层深度都已封死且不存在任何第一轮次的时序窗口否决、注入、崩溃。该行为由 `packages/subagent/subagent-inprocess/tests/inheritance.spec.ts` 中的真实围栏测试套件钉住(脚本化模型驱动的子 agent 通过真实 `write` 工具撞上真实的 `dsh-fs-sandbox` 围栏,按落盘状态与拒绝标记断言——其中包括委派与延迟切换之间的竞态用例、一个具备否决能力的 prompt-submit 监听器用例,以及子 agent 任何轮次开始前的会话头持久性用例),并由两个服务各自测试套件中的 `overrideOf` 契约测试钉住(基线读取、种子边界优先级、封闭词汇拒绝)。
- 基线随 `SessionHeader` 通过两个持久化后端存储JSONL 头部行字段SQLite `sessions` 表中的列,`SCHEMA_VERSION` 提升到 11——预发布阶段无迁移因此恢复时它像 `delegationDepth` 一样被还原;子 agent 之后仍可被独立切换,其自身种子之后的事件优先于基线。
- 已接受的限制:子 agent 已在运行时父级再做的切换不会传播(快照语义);进程外后端(`subagent-acp`、子进程形态的子 agent在这里不继承任何内容它们的策略归子 harness 自身的部署所有,属于沙箱 Agent Note 中延后的阶段。
- 快照覆盖只运行在部署强度上:已录制的 `subagent-sandbox-inheritance` 场景经由组装后的 ACP 应用,证明了一个被委派的子 agent 被约束在只读的部署级策略之下,但这个仅面向自动化的协议没有会话作用域的切换,因此仅父级的覆盖项(更宽的默认值、收紧的父级、被拒的子 agent在该协议上无法表达。待接手时其形态沿用 headless 套件的语义检查点先例预置一份携带真实 `sandbox/mode` 切换的已持久化父级日志,经由一个恢复用的 fixture测试前置数据插件在 Loader 启动的 cli-demo 应用中恢复它,再让它发起委派——这是一次场景 harness 的扩充headless 套件尚无 subagent+沙箱 overlay而非新机制
- 组装后应用的快照钉住两种强度。已录制的 `subagent-sandbox-inheritance` ACP 场景证明了一个被委派的子 agent 被约束在只读的部署级策略之下这个仅面向自动化的协议没有会话作用域的切换)。无密钥的 `subagent-inheritance` headless 场景则沿语义检查点先例钉住仅父级的覆盖项:在 workspace-write 的部署默认值之下,预置一份携带真实 `sandbox/mode: read-only` 切换的父级日志,经由一个恢复用的 fixture测试前置数据插件在 Loader 启动的 cli-demo 应用中恢复它并发起委派;子 agent 的真实写入被真实围栏拒绝,其持久化的会话头携带继承来的基线,而禁用驱动器的捕获会让该场景在物理落盘断言上失败——这就是委派旁路在组装后应用层面的红/绿锚点
- `dsh-subagent-inprocess``dsh-sandbox-policy``dsh-user-approval` 声明为对等依赖peer dependency以支撑 `ctx.get` 的类型;两者在运行时仍然可选。`SessionHeader` 新增两个中性的可选字符串字段;`SESSION_FORMAT_VERSION` 保持为 0仅新增字段预发布阶段