文档:补全 M1 Goal Contract 死锁的第二道门与方案 D

复核发现原表述只覆盖了完成门。实际卡在两道互相独立的门:

- 入口门 validate_root_goal_contract_control_plan_at 位于通用
  tool-plan 解析路径,判据只有 agent_id 是 project-supervisor、
  binding 的 root 是自己、存在 run profile binding,既不看 source
  也不看 Run Profile。合同不存在时强制本轮恰好一个
  agent.goal_contract 动作且无 plan/plan_update/response。
- 出口门 goal_contract_acceptance_completion_blocker_at_locked
  判据含可信 source。

因此原方案 A「plan source 不进可信集合」只解除出口门,入口门照卡,
表中代价已更正。新增方案 D「plan root 改用独立 agentId」——三处
判据的共同项只有 agent_id == project-supervisor,这是唯一一次性
解耦的做法,代价是推翻第 4.1 节已冻结的 agentId。

同时记录冲突根源:现行是 deny-list 工具模型,可信 root Supervisor
默认持有 agent.goal_contract,两道门对现役 Agent 是照着做;plan
source 是首个 allow-list 模型的 source,前提对它不成立。第 4.1 节
原三分 matcher 方案随之标注为不足。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 05:24:31 +00:00
parent fe4c35e918
commit b6899619f0
3 changed files with 21 additions and 7 deletions
@@ -9,7 +9,7 @@
- M2 裁决一:确定性收束与 Runtime 内部产物验证**都不构成验收证据**。`design-director` 确定性化后不产生 Provider 回执,M0-3 的内部 owner 验证同样不产生;而 passed 节点必须引用真实成功动作回执且 `requiredEvidence` 须命中 `agent_runtime_acceptance_evidence_tools()`。涉及 GDD 落地的验收标准要么由根 Supervisor 用允许的证据工具自行取证,要么不写成 required 节点。**不得**为使确定性节点可验收而把内部验证工具暴露给 Provider——那会推翻 M0-3 已冻结的 owner 验证边界。
- M2 裁决二:planning baseline 不是「一次冻结永久有效」。steer 替换协议会终止旧根树并另起 replacement root run,该 run 必须在自己的锁/CAS 边界内重新冻结与被替换根**完全相同**的 `approvedGddRef`,即使期间已有更新的 approved 版本也不换稿;无法证明同一 ref 时失败关闭,不得降级为 `mode=direct-build`
- M2 附带证据:Goal Contract 落在 `.agent/runtime/goal-contracts/{rootAgent}/{rootRun}.json`,按 root run 分文件并绑定 binding fingerprint 与 source SHA-256。这是「自带 root/run 身份」的正面先例,D4 关于 `approvedGddRef` 是否进 manifest 的决策应参照此形态,而非项目级单例。
- 保留待裁决:**plan source 与 Goal Contract 协议的关系**M1 入口前置,与 checkpoint handoff 私有持久化决策并列)。触发事实是 `agent_runtime_supervisor_source_is_trusted` 已成为 run 启动、steer、Goal Contract 创建权限、根控制面工具授权与验收图完成门的共同判据且全部不看 Run Profileplan source 一旦进入该集合,未冻结 Goal Contract 即完成门永久 blocked,而四项 exact allowlist 无解除手段。三方案与代价见技术方案第 23.1 节。裁决冻结前,依赖 plan source 可信身份的 M1 代码不得合入。陷阱细节见 pitfalls 2026-08-12「往 trusted supervisor source 里加新 source」条。
- 保留待裁决:**plan source 与 Goal Contract 协议的关系**M1 入口前置,与 checkpoint handoff 私有持久化决策并列)。根源是两套工具模型不兼容:现行 deny-list 模型下 `allowed_tools` 直接等于全量执行工具目录,可信 root Supervisor 默认持有 `agent.goal_contract`,两道门对现役 gui/cli/game-chat 是「照着做」;plan source 是首个 allow-list 模型的 source,不持有该工具。卡点是两道**互相独立**的门:入口门 `validate_root_goal_contract_control_plan_at` 在通用 tool-plan 解析路径上强制「合同不存在时本轮必须是唯一的 `agent.goal_contract` 动作」,判据既不看 source 也不看 Run Profile;出口门 `goal_contract_acceptance_completion_blocker_at_locked` 判据含可信 source,未冻结合同即完成门永久 blocked。四方案与代价见技术方案第 23.1 节,其中「plan root 改用独立 `agentId`」是唯一一次性解耦三处判据的方案,但会推翻第 4.1 节已冻结的 `agentId=project-supervisor`。裁决冻结前,依赖 plan source 可信身份的 M1 代码不得合入。陷阱细节见 pitfalls 2026-08-12「往 trusted supervisor source 里加新 source」条。
## 2026-08-12 manifest 不是 game-chat 轮次身份的权威源(M0B-2 挂起项收敛)
@@ -4,7 +4,9 @@
- 现象:`agent_runtime_supervisor_source_is_trusted``apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs:113`)读起来像一个「谁能启动 Project Supervisor」的入口白名单,实际早已是多条互不相干的授权判据的共同开关。2026-08-11 合入动态目标验收图后,它的非测试消费者从 2 个文件涨到 10 个文件 18 处调用:run 启动(`commands.rs:698`)、steer`commands.rs:876``steering.rs:763`)、Goal Contract 创建权限(`goal_contract.rs:496`)、根控制面工具是否被剥离(`provider_request_builders.rs:134``root_control_authority`)、验收图完成门(`acceptance_graph.rs:595`)、run configuration、lifecycle_control、task_start、project_gates、autonomous_completion。
- 陷阱:这些判据**全都不看 Run Profile**。`goal_contract_acceptance_completion_blocker_at_locked``apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/acceptance_graph.rs:568-611`)只要求「`agent_id``project-supervisor` + binding 是无 parent 的 root + source 可信」,未冻结 Goal Contract 就返回 `blocked`,并被 `main_loop.rs:213``main_loop.rs:1803``finalization.rs:398` 消费。因此给一个**用途完全不同**的新 source(例如立项策划的 plan chat)加进白名单,会让它的根 Run 立刻背上「必须先调 `agent.goal_contract`」的义务;如果该 source 的工具面按 exact allowlist 设计、不含这个工具,根 Run 就永远无法完成——而且症状是 run 卡在完成门,不是启动失败,排查方向容易跑偏。
- 处理:新增 trusted source 前,先逐个确认这 18 处调用对新 source 的语义是否成立,尤其是 Goal Contract 创建、验收图完成门与 steer 三处;需要区分时,应当拆出「可信入口」与「Goal Contract 参与者」两条判据,而不是继续复用同一个函数。立项策划的对应裁决见《【技术方案】立项策划AgentFast GDD-2026-08-10》第 23.1 节,尚未冻结
- 更坏的一半:把新 source 排除出白名单**并不能**脱身。同一协议还有一道入口门 `validate_root_goal_contract_control_plan_at``apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/autonomous_policy.rs:171`),由 `provider_tool_plan.rs:434` 在通用 tool-plan 解析路径上无条件调用,判据只有「`agent_id``project-supervisor` + binding 的 root 是自己 + 存在 run profile binding」——**连 source 都不看**。合同不存在时它强制本轮恰好一个 `agent.goal_contract` 动作且 `plan_update`/legacy plan/`response` 全为空,于是「第一轮先问用户一个问题」或「第一轮先回复」的 Agent 会被直接判协议错误。三处判据里只有 `agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID` 是共同项,改 `agent_id` 是唯一能一次性解耦的做法
- 为什么现役 Agent 没事:工具面是 **deny-list** 模型。`agent_runtime_tool_policy_snapshot_at``tool_policy_snapshot.rs:130-178`)的 `allowed_tools` 直接等于全量 `agent_runtime_executable_tools()``agent.goal_contract` 天然在内;`standard` profile 在 `agent_runtime_tool_policy_snapshot_for_run_at:198` 提前返回、不做裁剪;只有 `!root_control_authority && goal_contract_participant` 的后代才在 `provider_request_builders.rs` 被剔除。所以 gui/cli/game-chat 根 Supervisor 默认就握着这个工具,两道门对它们是「照着做」而不是「过不去」。**按 exact allowlist 设计工具面的新 source 才会撞上**,而 allow-list 正是更安全的那个方向——这条陷阱专门惩罚更严格的设计。
- 处理:新增 trusted source 前,先逐个确认这 18 处调用对新 source 的语义是否成立,尤其是 Goal Contract 创建、验收图完成门与 steer 三处,再单独确认不看 source 的入口门;需要区分时,应当拆出「可信入口」与「Goal Contract 参与者」两条判据,而不是继续复用同一个函数。立项策划的对应裁决见《【技术方案】立项策划AgentFast GDD-2026-08-10》第 23.1 节,尚未冻结。
- 相关:`requiredEvidence` 只接受 `tool:<Runtime 工具名>` 且必须命中 `agent_runtime_acceptance_evidence_tools()``apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/tool_policy_snapshot.rs:74-96`,当前 18 项)。确定性收束的任务和 Runtime 内部产物验证都不产生 Provider 回执,因此无法为验收节点提供证据——不要指望「让 Runtime 自己验一下」能满足验收图。
## 2026-08-12 给 manifest 加"新鲜度门控"或身份字段的两个陷阱
@@ -123,6 +123,8 @@ flowchart TD
M1 不能把新 source 直接塞进一个被 autonomous 语义复用的总 matcher。source predicate 必须拆成三种语义:top-level Supervisor trusted matcher 接受 gui/cli/game-chat/planautonomous-build trusted matcher仍只接受现役 gui/cli/game-chatplan binding matcher只接受 top-level `project-supervisor + standard + project-supervisor-plan-chat`。现有 run configuration、completion gate 和恢复调用点逐个改用正确 predicate,确保 `plan + autonomous-game-build` 永远非法。前端已有 `runProfile + source` 提交链只能作为请求;后端必须重新验证,不能信任页面选择。
2026-08-12 复核:上述三分法在 2026-08-11 动态目标验收图合入后**已不足**。原文把 plan 归入「top-level Supervisor trusted matcher」,而该 matcher`agent_runtime_supervisor_source_is_trusted`)如今同时是 Goal Contract 创建权限、根控制面工具授权与验收图完成门的判据,plan 一进入即被当作 Goal Contract 参与者。至少需要第四种语义「Goal Contract 参与者」,且它与 top-level trusted 的关系必须显式冻结,不能靠默认相等。此外 Goal Contract 的入口门只按 `agentId=project-supervisor` + root binding 判定,与 source predicate 无关,因此拆 matcher 本身解决不了它。完整处置见第 23.1 节待裁决项;该裁决可能反过来影响本节已冻结的 `agentId=project-supervisor`(方案 D)。
所有 plan 例外必须共用单一 `is_exact_supervisor_plan_run_at(root, agentId, runId)`:只有 durable run-profile binding 已通过 project/fingerprint 校验,且 `agentId=project-supervisor``source=project-supervisor-plan-chat``profile=standard`、parent/delegation 均为空、`rootAgentId=agentId``rootRunId=runId`,并与 task/runtime 以及存在时的 planning pending、尚存 batch 的 source/profile/binding fingerprint 逐项相等时才返回 true。缺失必需 binding、损坏或漂移不得获得 plan 例外,不能只比较内存中的 `runtime.source`
普通 standard retry 当前会改写为通用 background source。M1 必须在 generic standard fallback 前增加 exact plan root 分支,但不扩大现役 retry 的破坏面:只有 Provider 故障的旧 plan task 已由现有生命周期收束为终态、没有 provider/action/planning pending ledger,且原 task 与已验证 root binding 逐项相等、无 parent/delegation 时,retry 才以同一 gddId/session 创建新 run,继续使用 `project-supervisor-plan-chat + standard`,并在项目锁内写 session revision+1 successor;不得由 retry helper 主动终结 running/waiting run,不得新建 gddId,也不得降级为 `agent-background-task`。任一身份校验失败直接拒绝 retry。已经进入 `gdd-approval` 等待的 run 禁止走 generic retry,只能恢复并续跑 receipt 所绑定的精确原 run。
@@ -1366,7 +1368,7 @@ Canvas 是条件外部能力,不是 M0-3 四个固定 owner 的通用前置:
2026-08-12 裁决补充:manifest 不是 game-chat 轮次身份的权威源。`GameCreationAppManifest` / `GameCreationAppTaskState` 在 TS 与 Rust 双侧都不含 run/root/agent 身份字段,且 manifest 是项目级单例文件并跨轮复用同一份,因此任何任务状态都无法从数据本身归属到具体轮次。轮次结论的权威事实只有 Runtime lineage`agentId + sessionId + runId + source + parentAgentId + parentRunId`);manifest 只能作为 lineage 判定通过后的补充信号,不得单独裁定当前轮结论,也不得单独解锁阶段归档。阶段归档快照的全部写入点都必须校验被归档 root 仍是当前 root,终态会话同步中的异步捕获同样适用——该读取若在下一轮接管后才 resolve,会把掺入新轮活动的清单冻结成旧轮快照;跳过捕获不会饿死归档,因为开下一轮前的阻塞门在快照未冻结时直接失败要求重试。本阶段明确不为 manifest 或其投影新增 `statusRunId` / `statusSource` 等身份字段:补字段必须同时覆盖 `update_manifest_task_status_at` 与无秩序守卫的 `set_task_status` 两条写入路径,只改前者会让后者原样保留上一次写入的旧身份印记,制造“校验通过”的假象而比不校验更危险。
2026-08-12 记录一处尚未裁决的不变量冲突。本节第 2 条(plan source 的工具广告与执行双门只有四项)与 2026-08-11 合入的动态目标验收图存在结构性冲突`goal_contract_acceptance_completion_blocker_at_locked` 只按「`agent_id``project-supervisor` + binding 是无 parent 的 root + source 可信」三条判定,**不看 Run Profile**同一判据决定 Goal Contract 的创建权限和 `agent.goal_contract` / `agent.acceptance_update` 是否被剥离。M1 一旦按第 22 节把 plan source 加入可信集合,plan root 就同时满足三条,未提交 Goal Contract 即完成门永久 blocked,而四项 exact allowlist 里没有解除该 blocker 的工具。处置方案见第 23.1 节的待裁决项;在裁决冻结前,本节第 2 条按「设计意图」保留,不得据此认为现行代码已经满足它。
2026-08-12 记录一处尚未裁决的不变量冲突。本节第 2 条(plan source 的工具广告与执行双门只有四项)与 2026-08-11 合入的动态目标验收图存在结构性冲突。现行工具面是 deny-list 模型,可信 root Supervisor 默认持有 `agent.goal_contract`;而 plan source 按 exact allowlist 设计,不持有该工具。由此卡在两道独立的门:入口门 `validate_root_goal_contract_control_plan_at` 在通用 tool-plan 解析路径上强制「合同不存在时本轮必须是唯一的 `agent.goal_contract` 动作、且无 plan/plan_update/response」,判据**既不看 source 也不看 Run Profile**;出口门 `goal_contract_acceptance_completion_blocker_at_locked` 在 root project-supervisor + 可信 source 且合同不存在时返回 `blocked`,卡住完成、finalization 与恢复。同一判据决定 Goal Contract 的创建权限和 `agent.goal_contract` / `agent.acceptance_update` 是否被剥离。四个处置方案与代价见第 23.1 节的待裁决项;在裁决冻结前,本节第 2 条按「设计意图」保留,不得据此认为现行代码已经满足它。
## 20. rollout、停用与回滚
@@ -1463,13 +1465,23 @@ M0 文档 PR 本身最低验证:Markdown 结构与三张 Mermaid 图可解析
M0A-1 是 M1 详细设计与技术 spike 的输入,不是功能交付。M1 可以与 M0-3 并行准备;但 checkpoint handoff 私有持久化决策未冻结前,对应 checkpoint 代码不得合入,M0-3 的 dated 决策、Prompt、source/tool policy 与测试未全部合入前,依赖 Fast GDD source/tool policy 的 M1 代码也不得合入主线。M0-3 采用 Runtime 内部 owner 验证,不为未来 plan source 暴露 command、smoke 或替代验证工具。M0-4 不阻塞 M1/M2,仅阻塞 M3-4 和“M0 全部完成”;不得因为并行关系把工作包标签误报为阶段完成。
2026-08-12 新增第二项 M1 合入前置决策:**plan source 与 Goal Contract 协议的关系**。触发事实见第 19 节与第 22 节 trusted matcher 行——`agent_runtime_supervisor_source_is_trusted` 现在同时决定 run 启动、steer、Goal Contract 创建权限、根控制面工具授权与验收图完成门,且全部不看 Run Profile。三个候选方案与已知代价:
2026-08-12 新增第二项 M1 合入前置决策:**plan source 与 Goal Contract 协议的关系**。
冲突的根源不是某处判据写错,而是两套工具模型不兼容。现行是 **deny-list 模型**`agent_runtime_tool_policy_snapshot_at``allowed_tools` 直接等于全量 `agent_runtime_executable_tools()``agent.goal_contract` 天然在内,`standard` profile 在 `agent_runtime_tool_policy_snapshot_for_run_at` 里更是提前返回、不做任何裁剪;`provider_request_builders.rs` 只对 `!root_control_authority && goal_contract_participant` 的后代做剔除。因此现役可信 root Supervisorgui / cli / game-chat**默认就持有**这个工具,两道门对它们是「照着做」。而 M1 是第一个 **allow-list 模型**的 source(第 4.3 节 exact allowlist),Goal Contract 协议隐含的「root Supervisor 一定持有 `agent.goal_contract`」前提对它不成立。
具体卡在两道**互相独立**的门,判据不同,必须分别处置:
- **入口门** `validate_root_goal_contract_control_plan_at``agent/runtime_actions/autonomous_policy.rs:171`,由 `provider_tool_plan.rs:434` 在通用 tool-plan 解析路径上无条件调用)。判据只有「`agent_id``project-supervisor` + binding 的 root 是自己 + 存在 run profile binding」,**既不看 source 也不看 Run Profile**。合同不存在时强制本轮恰好一个 `agent.goal_contract` 动作,且 `plan_update` 为空、legacy plan 为空、`response` 为空——plan run 第一轮无论发决策卡还是回复都被拒。
- **出口门** `goal_contract_acceptance_completion_blocker_at_locked``agent/runtime_protocol/acceptance_graph.rs:568`)。判据是 root project-supervisor + **可信 source**,合同不存在即 `blocked`,卡住普通完成、finalization 与恢复。
四个候选方案与已知代价:
| 方案 | 做法 | 已知代价 |
| --- | --- | --- |
| A. plan source 不进可信集合 | 为 plan 另开平行的启动/steer 门 | `commands.rs` 的启动与 steer 两处都要改从此存在两套 source 信任判据,后续新增可信消费者容易只接一套 |
| B. 进可信集合但按 profile 豁免 | 在 Goal Contract 创建、验收图完成门、steer 三处`standard + plan source` 豁免 | 豁免点分散且会继续增加;必须同时冻结「可信 root ≠ Goal Contract 参与者」这条新不变量,否则下一个消费者默认把 plan 当参与者 |
| C. plan 接纳 Goal Contract 协议 | 把 `agent.goal_contract` 纳入 plan 工具面 | 与第 4.3 节 exact allowlist、第 19 节第 2 条和第 24 节「plan source 无……」直接冲突;且 GDD 与 Goal Contract 语义大面积重叠,等于让同一轮策划冻结两份目标事实 |
| A. plan source 不进可信集合 | 为 plan 另开平行的启动/steer 门 | **只解除出口门,入口门照卡**——入口门不看 source。仍须额外改 `validate_root_goal_contract_control_plan_at` 或让 plan 完全绕开通用 tool-plan 解析路径(但该 validator 位于 `parse``and_then` 里,是最通用的位置)。另外 `commands.rs` 的启动与 steer 两处都要改从此存在两套 source 信任判据,后续新增可信消费者容易只接一套 |
| B. 进可信集合但按 profile 豁免 | 在入口门、Goal Contract 创建、出口门、steer **四处**`standard + plan source` 豁免 | 豁免点分散且会继续增加;必须同时冻结「可信 root ≠ Goal Contract 参与者」这条新不变量,否则下一个消费者默认把 plan 当参与者 |
| C. plan 接纳 Goal Contract 协议 | 把 `agent.goal_contract` 纳入 plan 工具面 | 两道门都自然满足,但与第 4.3 节 exact allowlist、第 19 节第 2 条和第 24 节「plan source 无……」直接冲突;且 GDD 与 Goal Contract 语义大面积重叠,等于让同一轮策划冻结两份目标事实,还要额外裁决二者的权威关系 |
| D. plan root 改用独立 `agentId` | 不再复用 `project-supervisor` | 入口门、创建权限与出口门三处**同时**自动不适用(三者都要求 `agent_id == project-supervisor`),是唯一一次性解耦的方案;但直接推翻第 4.1 节已冻结的 `agentId=project-supervisor`,牵动顶层通道假设、前端 hydrate、run lineage 与「每项目最多一个 active plan run」的判定口径,须先重开第 4.1 节 |
裁决冻结前,依赖 plan source 可信身份的 M1 代码不得合入主线;该决策与 checkpoint handoff 私有持久化决策并列,均属 M1 入口前置。此外需一并确认:plan run 是否允许 steer——`steering.rs` 的 steer 门同样只按可信 source 判定,而 steer 替换协议会终止旧根并另起 replacement root run,与第 5.1 节的 session CAS、`roundsUsed``activeQuestion` 语义存在未验证的交互,第 14 节恢复矩阵目前没有这条路径。