diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs index 2edc17e9d..bcb641444 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs @@ -107,6 +107,11 @@ pub(crate) const AGENT_RUNTIME_ISOLATED_JOIN_SOURCE: &str = "agent-isolated-join pub(crate) const AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE: &str = "project-supervisor-gui"; pub(crate) const AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE: &str = "project-supervisor-cli"; pub(crate) const AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE: &str = "project-supervisor-game-chat"; +pub(crate) const AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE: &str = "project-supervisor-plan"; +pub(crate) const AGENT_RUNTIME_PLAN_AUTONOMOUS_PROFILE_UNSUPPORTED_KIND: &str = + "plan-autonomous-profile-unsupported"; +pub(crate) const AGENT_RUNTIME_PLAN_ROOT_STEER_UNSUPPORTED_KIND: &str = + "plan-root-steer-unsupported"; pub(super) const GAME_CHAT_FIXED_TASK_GRAPH_STALLED_ERROR: &str = "game-chat 首版固定任务图无法继续推进,拒绝回退到普通 Provider 协作波"; @@ -116,8 +121,36 @@ pub(crate) fn agent_runtime_supervisor_source_is_trusted(source: &str) -> bool { AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE | AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE | AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE + | AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE ) } + +pub(crate) fn agent_runtime_supervisor_source_is_plan(source: &str) -> bool { + source.trim() == AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE +} + +pub(crate) fn reject_supervisor_plan_autonomous_profile( + source: &str, + run_profile: &str, +) -> Result<(), String> { + if agent_runtime_supervisor_source_is_plan(source) + && run_profile.trim() == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD + { + return Err(format!( + "立项策划根 Run 必须使用 standard 档,不能搭配 autonomous-game-build(kind={AGENT_RUNTIME_PLAN_AUTONOMOUS_PROFILE_UNSUPPORTED_KIND})" + )); + } + Ok(()) +} + +pub(crate) fn reject_supervisor_plan_root_steer(source: &str) -> Result<(), String> { + if agent_runtime_supervisor_source_is_plan(source) { + return Err(format!( + "立项策划根 Run 不接受 steer 替换;请在本轮问询中回答,或通过审批卡修改 / 退回(kind={AGENT_RUNTIME_PLAN_ROOT_STEER_UNSUPPORTED_KIND})" + )); + } + Ok(()) +} pub(super) const AGENT_RUNTIME_RUN_PROFILE_BINDING_SCHEMA_VERSION: &str = "game-creator-run-profile-binding.v1"; pub(super) const AGENT_RUNTIME_AUTONOMOUS_COMPLETION_CONTRACT_SCHEMA_VERSION: &str = diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/task_start.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/task_start.rs index ee3839f0c..75d72e1b8 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/task_start.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/task_start.rs @@ -126,7 +126,8 @@ pub(crate) fn start_game_creator_supervisor_background_task_for_session_at( if !agent_runtime_supervisor_source_is_trusted(source) { return Err("Project Supervisor 提交 source 不受信任".to_string()); } - normalize_agent_runtime_run_profile(Some(run_profile))?; + let run_profile = normalize_agent_runtime_run_profile(Some(run_profile))?; + reject_supervisor_plan_autonomous_profile(source, &run_profile)?; start_game_creator_agent_background_task_with_source_at( root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, @@ -134,7 +135,7 @@ pub(crate) fn start_game_creator_supervisor_background_task_for_session_at( task, run_id, source, - Some(run_profile), + Some(run_profile.as_str()), ) .map(|(mut result, accepted_run_id)| { result.accepted_run_id = Some(accepted_run_id); diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs index a51f871e0..e9209ce15 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs @@ -56,7 +56,7 @@ fn autonomous_fixture_with_source( } #[test] -fn autonomous_supervisor_source_allowlist_includes_game_chat_only() { +fn autonomous_supervisor_source_allowlist_includes_game_chat_and_plan() { assert!(agent_runtime_supervisor_source_is_trusted( AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE )); @@ -66,9 +66,126 @@ fn autonomous_supervisor_source_allowlist_includes_game_chat_only() { assert!(agent_runtime_supervisor_source_is_trusted( AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE )); + assert!(agent_runtime_supervisor_source_is_trusted( + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE + )); assert!(!agent_runtime_supervisor_source_is_trusted( "project-supervisor-forged" )); + assert!(!agent_runtime_supervisor_source_is_trusted( + "project-supervisor-plan-chat" + )); +} + +#[test] +fn plan_source_rejects_autonomous_profile_and_accepts_standard() { + reject_supervisor_plan_autonomous_profile( + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + AGENT_RUNTIME_RUN_PROFILE_STANDARD, + ) + .expect("plan + standard 应放行"); + let error = reject_supervisor_plan_autonomous_profile( + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD, + ) + .expect_err("plan + autonomous 应拒绝"); + assert!( + error.contains(AGENT_RUNTIME_PLAN_AUTONOMOUS_PROFILE_UNSUPPORTED_KIND), + "{error}" + ); + reject_supervisor_plan_autonomous_profile( + AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE, + AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD, + ) + .expect("gui + autonomous 不受 plan 组合门影响"); +} + +#[test] +fn plan_root_steer_is_rejected_inside_and_outside_trusted_matcher() { + assert!(agent_runtime_supervisor_source_is_trusted( + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE + )); + let in_matcher = reject_supervisor_plan_root_steer(AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE) + .expect_err("plan 在 matcher 内也必须拒绝 steer"); + assert!( + in_matcher.contains(AGENT_RUNTIME_PLAN_ROOT_STEER_UNSUPPORTED_KIND), + "{in_matcher}" + ); + + assert!(!agent_runtime_supervisor_source_is_trusted( + "project-supervisor-plan-chat" + )); + reject_supervisor_plan_root_steer("project-supervisor-plan-chat") + .expect("已作废的 plan-chat 字面不是现行 plan source,独立否决不得误伤"); + reject_supervisor_plan_root_steer(AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE) + .expect("gui 不受 plan steer 独立否决"); + reject_supervisor_plan_root_steer("project-supervisor-forged") + .expect("不可信非 plan source 不走 plan steer 错误,留给 trusted matcher"); +} + +#[test] +fn plan_supervisor_start_rejects_autonomous_and_allows_standard() { + let temporary = crate::tests::canonical_test_tempdir("plan-source-start-"); + let root = temporary.path().join("project"); + init_local_game_project_at(&root, "plan-source-start", "立项策划启动门").expect("init"); + let _runtime_lock = try_acquire_game_creator_agent_runtime_task_lock( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + ) + .expect("lock") + .expect("lock available"); + + let autonomous_error = start_game_creator_supervisor_background_task_for_session_at( + &root, + None, + "做一份 Fast GDD", + "plan-autonomous-forbidden", + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD, + ) + .expect_err("plan + autonomous 启动必须拒绝"); + assert!( + autonomous_error.contains(AGENT_RUNTIME_PLAN_AUTONOMOUS_PROFILE_UNSUPPORTED_KIND), + "{autonomous_error}" + ); + + let started = start_game_creator_supervisor_background_task_for_session_at( + &root, + None, + "做一份 Fast GDD", + "plan-standard-allowed", + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + AGENT_RUNTIME_RUN_PROFILE_STANDARD, + ) + .expect("plan + standard 应能启动"); + assert_eq!( + started.accepted_run_id.as_deref(), + Some("plan-standard-allowed") + ); + let queued = read_latest_game_creator_agent_runtime_task_by_run_id( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + "plan-standard-allowed", + ) + .expect("read queued plan task") + .expect("queued plan task exists"); + assert_eq!(queued.source, AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE); + assert_eq!(queued.run_profile, AGENT_RUNTIME_RUN_PROFILE_STANDARD); + + let steer_error = steer_game_creator_agent_runtime_task_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + &queued.session_id, + "plan-standard-allowed", + "steer-plan-forbidden", + "改成另一套玩法", + "test", + ) + .expect_err("plan 根 run 的 steer 必须拒绝"); + assert!( + steer_error.contains(AGENT_RUNTIME_PLAN_ROOT_STEER_UNSUPPORTED_KIND), + "{steer_error}" + ); } #[test] diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/steering.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/steering.rs index 003285533..6ec369a4e 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/steering.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/steering.rs @@ -760,10 +760,13 @@ fn goal_contract_root_steer_task_at( if task.session_id != session_id || task.parent_agent_id.is_some() || task.parent_run_id.is_some() - || !agent_runtime_supervisor_source_is_trusted(&task.source) { return Ok(None); } + reject_supervisor_plan_root_steer(&task.source)?; + if !agent_runtime_supervisor_source_is_trusted(&task.source) { + return Ok(None); + } let Some(binding) = read_game_creator_agent_runtime_run_profile_binding(root, agent_id, run_id)? else { @@ -1152,6 +1155,11 @@ pub(crate) fn steer_game_creator_agent_runtime_task_for_profile_at( if accepted_via.is_empty() { return Err("追加指令缺少 acceptedVia".to_string()); } + if let Some(task) = + read_latest_game_creator_agent_runtime_task_by_run_id(root, &agent_id, run_id)? + { + reject_supervisor_plan_root_steer(&task.source)?; + } if let Some(root_task) = goal_contract_root_steer_task_at(root, &agent_id, session_id, run_id)? { return transition_goal_contract_root_steer_at( @@ -1174,6 +1182,7 @@ pub(crate) fn steer_game_creator_agent_runtime_task_for_profile_at( if state.run_id != run_id || state.session_id != session_id { return Err("追加指令与当前 Agent 的 session/run 身份不匹配".to_string()); } + reject_supervisor_plan_root_steer(&state.source)?; if let Some(expected_run_profile) = expected_run_profile { let expected_run_profile = normalize_agent_runtime_run_profile(Some(expected_run_profile))?; let (persisted_run_profile, persisted_binding_fingerprint) = diff --git a/apps/ai-game-creator-shell/src-tauri/src/commands.rs b/apps/ai-game-creator-shell/src-tauri/src/commands.rs index 498616bd4..484ca92df 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/commands.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/commands.rs @@ -698,6 +698,7 @@ pub(crate) fn start_game_creator_supervisor_runtime_task( if !agent_runtime_supervisor_source_is_trusted(source) { return Err("Project Supervisor 提交 source 不受信任".to_string()); } + reject_supervisor_plan_autonomous_profile(source, run_profile)?; start_game_creator_supervisor_background_task_for_session_at( root, session_id.as_deref(), @@ -873,6 +874,7 @@ pub(crate) async fn steer_game_creator_agent_runtime_task( .map(str::trim) .filter(|value| !value.is_empty()) { + reject_supervisor_plan_root_steer(source)?; if !agent_runtime_supervisor_source_is_trusted(source) { return Err("Project Supervisor steer source 不受信任".to_string()); } diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 63846702b..575d7978f 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -1,5 +1,17 @@ # 决策记录 +## 2026-08-13 M1A-1:`project-supervisor-plan` 进可信 matcher,steer 独立否决 + +- 落地:新增 `AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE = "project-supervisor-plan"` 并加入 `agent_runtime_supervisor_source_is_trusted`。启动路径 `start_game_creator_supervisor_background_task_for_session_at` / Tauri command 接受该 source,`standard` 放行,`plan + autonomous-game-build` 返回 `kind=plan-autonomous-profile-unsupported`。旧字面 `project-supervisor-plan-chat` 仍不在 matcher 内。 +- steer:独立函数 `reject_supervisor_plan_root_steer` 只认精确 plan source,**不咨询 matcher**。Tauri command 在 trusted 检查之前调用;`steer_game_creator_agent_runtime_task_for_profile_at` 读到 task/runtime source 即拒;`goal_contract_root_steer_task_at` 同样先拒再走 trusted。typed 错误 `kind=plan-root-steer-unsupported`。回归覆盖「plan 在 matcher 内仍拒」与「否决不依赖 matcher、不误伤 gui/forged/已作废 plan-chat」。 +- 消费点复核(`rg agent_runtime_supervisor_source_is_trusted`,测试除外): + - **适用(plan 进 matcher 后语义正确)**:`commands.rs` 启动门、`task_start.rs` 启动门、`goal_contract.rs` 的 `validate_goal_contract_record` / `create_game_creator_agent_runtime_goal_contract_at`、`acceptance_graph.rs` 的 `update_game_creator_agent_runtime_acceptance_graph_at` / `goal_contract_acceptance_completion_blocker_at_locked`、`provider_request_builders.rs` 的 `root_control_authority`、`run_configuration.rs` 根 binding 写入(autonomous 组合另由启动门拒绝)。 + - **不适用 → 独立否决**:`commands.rs` `steer_game_creator_agent_runtime_task`、`steering.rs` `goal_contract_root_steer_task_at`(及 `steer_..._for_profile_at` 入口)。不得用「不进 matcher」实现。 + - **不适用且已被 profile 挡住,本包不改函数**:`task_start.rs` `current_autonomous_game_build_root_task_at`(先要求 `run_profile == autonomous-game-build`);`lifecycle_control.rs` `resolve_game_creator_agent_runtime_retry_configuration_at`(autonomous 分支才读 trusted source,standard 走 `agent-background-task`);`project_gates.rs` `ensure_current_autonomous_ready_child_mutation_at_locked`(`profile != autonomous` 即 `Ok(())`);`autonomous_completion.rs` 的 `autonomous_game_build_root_run_active_at` / `validate_autonomous_completion_contract` / `ensure_autonomous_completion_contract_for_task_at` 均先看 autonomous profile;`failed_terminal_autonomous_root_contract_before_task_at` 由后者以及 `autonomous_effective_root_task_at` 调用,后者先要求已存在完成合同(完成合同只由 autonomous 根写入)。 +- 本包不做:工具面、brief、`plan.submit_gdd`、planning sidecar、审批、前端入口。 +- 回归:`autonomous_supervisor_source_allowlist_includes_game_chat_and_plan`、`plan_source_rejects_autonomous_profile_and_accepts_standard`、`plan_root_steer_is_rejected_inside_and_outside_trusted_matcher`、`plan_supervisor_start_rejects_autonomous_and_allows_standard`。 +- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 23.8 节 `M1A-1`。 + ## 2026-08-13 checkpoint handoff 私有持久化随 D10 作废;M1 入口前置决策归零 - 处置:原待裁决项「checkpoint handoff 私有持久化」**无需裁决,随 D10 一并作废**。它待的是 `plan-decision-checkpoint` 这份 Provider 响应的专用 handoff schema / path / requestSlot / ledger 排序语义;该请求 kind 是 D10「Runtime 直投」的组成部分(策划节点持续存活于同一 run,用户回答后在同一 run 内再发一次专用请求形成设计解释)。D11 下策划子 Agent 以终态信封退出来提问、该 run 随即结束,解释与下一步由 continuation 子 Agent 的第一个普通 tool-plan turn 完成,专用请求 kind 不存在,其专用 handoff 也就不需要。 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 3e15a278c..ef3eb6305 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -16,7 +16,7 @@ - 陷阱:这些判据**全都不看 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 卡在完成门,不是启动失败,排查方向容易跑偏。 - 更坏的一半:把新 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 参与者」两条判据,而不是继续复用同一个函数。立项策划的对应裁决见《【技术方案】立项策划Agent(Fast GDD)-2026-08-10》第 23.1 节,尚未冻结。 +- 处理:新增 trusted source 前,先逐个确认这些调用对新 source 的语义是否成立,尤其是 Goal Contract 创建、验收图完成门与 steer 三处,再单独确认不看 source 的入口门;需要区分时,应当拆出「可信入口」与「Goal Contract 参与者」两条判据,而不是继续复用同一个函数。立项策划已按第 23.1 节裁决进 matcher 并参与 Goal Contract;steer 用独立于 matcher 的显式否决(`reject_supervisor_plan_root_steer`),不得用「不进 matcher」实现。复核结论见 decision-log 2026-08-13 `M1A-1` 条。 - 相关:`requiredEvidence` 只接受 `tool:` 且必须命中 `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 加"新鲜度门控"或身份字段的两个陷阱 diff --git a/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md b/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md index 860edba81..d04027ff6 100644 --- a/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md +++ b/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md @@ -1693,10 +1693,10 @@ M0 文档 PR 本身最低验证:Markdown 结构与三张 Mermaid 图可解析 | 主题 | 当前证据 | M1/M2 要点 | | --- | --- | --- | | **`project-planning` 身份登记(已落地)** | `apps/ai-game-creator-shell/src-tauri/prompts/runtime/manifest.json`(`agentCatalog.planning`)、`build_support/runtime_prompt_bundle.rs`、`src/agent/runtime_adapter.rs`、`src/agent/prompt.rs`、`src/agent/generation/pass_artifacts.rs`、`src/agent/runtime_driver/task_start.rs`、`src/agent/runtime_tools/task_ops.rs`、`src/agent/runtime_tools/delegation.rs` | **2026-08-13 已合入**。登记为与 `supervisor` 平级、不进 `groups` 的独立条目,`build.rs`/种子 DAG/`new_game_creation_app_seed_tasks()` 一行未动。同批修掉「非 supervisor 即专业组成员」二分假设的四个受害点:角色身份合成(blocking,不修则委派第一轮即硬失败)、内存路径解析、恢复枚举漏收、`task.create` 静默兜底成 Design 组;并把 `project-planning` 排除出 `agent.spawn_isolated` 的合法模板集。回归见 `project_planning_is_a_delegatable_identity_outside_the_seed_dag` | -| Supervisor trusted source | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs:107-120` | 当前只有 gui/cli/game-chat。**2026-08-13 已裁决**:M1 新增 `project-supervisor-plan` 常量并**加入该 matcher**,做方案链路正常参与 Goal Contract 协议(见第 23.1 节) | -| trusted matcher 消费者 | 2026-08-12 复核为 10 个非测试文件、18 处调用:`commands.rs`(2)、`agent/runtime_actions/project_gates.rs`、`agent/runtime_protocol/acceptance_graph.rs`(2)、`agent/runtime_protocol/goal_contract.rs`(2)、`agent/runtime_protocol/run_configuration.rs`、`agent/runtime_protocol/steering.rs`、`agent/runtime_driver/lifecycle_control.rs`、`agent/runtime_driver/task_start.rs`(2)、`agent/runtime_actions/provider_request_builders.rs`、`agent/runtime_protocol/autonomous_completion.rs`(5) | 2026-08-10 记录的两个消费者已过期。`agent_runtime_supervisor_source_is_trusted` 现在同时是 run 启动门、steer 门、Goal Contract 创建权限、验收图完成门与根控制面工具授权的共同判据,**都不看 Run Profile**。M1 只拆 autonomous-only matcher 已不够。**2026-08-13 裁决:plan source 进该 matcher**,原「裁决前不得合入依赖 plan source 可信身份的 M1 代码」硬门解除(见第 23.1 节)。**但落地前必须逐点复核这些消费点对 plan 语义是否正确**,不适用者单独收窄,不能靠 matcher 一刀切;其中 steer 门已被单独否决,须实现为独立于本 matcher 的显式拒绝。2026-08-13 复核调用数已增至 19 处,实施时以当时源码为准 | +| Supervisor trusted source | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs`(`AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE` + matcher) | **2026-08-13 `M1A-1` 已落地**:matcher 现为 gui/cli/game-chat/plan。`plan + autonomous-game-build` 由独立组合门拒绝(`kind=plan-autonomous-profile-unsupported`)。做方案链路正常参与 Goal Contract 协议(见第 23.1 节) | +| trusted matcher 消费者 | 2026-08-12 复核为 10 个非测试文件、18 处调用:`commands.rs`(2)、`agent/runtime_actions/project_gates.rs`、`agent/runtime_protocol/acceptance_graph.rs`(2)、`agent/runtime_protocol/goal_contract.rs`(2)、`agent/runtime_protocol/run_configuration.rs`、`agent/runtime_protocol/steering.rs`、`agent/runtime_driver/lifecycle_control.rs`、`agent/runtime_driver/task_start.rs`(2)、`agent/runtime_actions/provider_request_builders.rs`、`agent/runtime_protocol/autonomous_completion.rs`(5) | 2026-08-10 记录的两个消费者已过期。`agent_runtime_supervisor_source_is_trusted` 现在同时是 run 启动门、steer 门、Goal Contract 创建权限、验收图完成门与根控制面工具授权的共同判据,**都不看 Run Profile**。M1 只拆 autonomous-only matcher 已不够。**2026-08-13 裁决:plan source 进该 matcher**;**`M1A-1` 已逐点复核并落地**:适用者保留 matcher、steer 独立否决(`kind=plan-root-steer-unsupported`)、autonomous 消费点已由 profile 挡住本包不改函数。复核表见 decision-log 2026-08-13 `M1A-1` 条。2026-08-13 复核调用数已增至 19 处,以当时源码为准 | | Run Profile | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_adapter.rs:48-75`、`apps/ai-game-creator-shell/src-tauri/src/main.rs:1243-1244` | 复用 `standard`,不新增 profile | -| Supervisor start 校验 | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/task_start.rs:114-134`、`apps/ai-game-creator-shell/src-tauri/src/commands.rs:497-529` | 增加 top-level plan source/profile 组合门 | +| Supervisor start 校验 | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/task_start.rs`、`apps/ai-game-creator-shell/src-tauri/src/commands.rs` | **`M1A-1` 已落地**:plan 必须 `standard`;`plan + autonomous-game-build` 拒绝 | | durable run binding | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/run_configuration.rs:72-125,226-374` | source/profile/root/parent CAS 与恢复必须贯穿 | | 前端提交链 | `apps/ai-game-creator-shell/src/features/agent-runtime/model.ts:844-899`、`apps/ai-game-creator-shell/src/App.tsx:5743-5800` | 已有 source/profile DTO;后端仍须重验 | | Prompt Bundle | `apps/ai-game-creator-shell/src-tauri/prompts/runtime/manifest.json:30-74`、`apps/ai-game-creator-shell/src-tauri/build_support/runtime_prompt_bundle.rs:73-85,867-925` | **2026-08-13 改写**:`supervisorPlanChat` / `SupervisorPlanChat` 随 D6 作废,**不新增 composition 也不新增编译期 source kind**(见第 4.2 节)。实际改动是 `agentCatalog` 新增与 `supervisor` 平级、不进 `groups` 的 `planning` 条目——**已于 2026-08-13 落地**,含结构体、`validate_agent_catalog`、`render_agent_catalog` 三处 | @@ -1878,7 +1878,7 @@ M0 完成不表示任何策划功能已上线。M1 的功能实现仍未开始 | 三 | `project-planning` 的 agentCatalog 登记 | **已完成**(机制冻结见第 3.1 节;**代码亦已落地**,2026-08-13:manifest、prompt bundle、runtime adapter、`prompt.rs` 角色合成分支及四处 needs_change 全部合入) | | 四 | `M0A-3` 批二:拓扑与工具面部分 | **已完成**(2026-08-13),拆解见下 | | 四之余 | schema 与 golden vector 收口 | **已完成**(2026-08-13),拆解见下 | -| 五 | M1 本体:策划闭环功能实现 | 未开始;合入门见下 | +| 五 | M1 本体:策划闭环功能实现 | **`M1A-1` 已落地**(source + matcher + steer 独立否决);其余 PR 未开始。合入门见第 23.8 节 | 批二在 2026-08-13 拆成两半,因为其中一半在 M1 代码存在之前**做不完**: @@ -1900,7 +1900,7 @@ M0 完成不表示任何策划功能已上线。M1 的功能实现仍未开始 *三、待执行项*(已有结论,只差落地,不阻塞开工决策): -- `agent_runtime_supervisor_source_is_trusted` 的约 19 处生产消费点逐点复核,确认对 plan 语义正确,不适用者单独收窄;其中 steer 门须实现为独立于该 matcher 的显式否决。 +- ~~`agent_runtime_supervisor_source_is_trusted` 的约 19 处生产消费点逐点复核,确认对 plan 语义正确,不适用者单独收窄;其中 steer 门须实现为独立于该 matcher 的显式否决。~~——**2026-08-13 `M1A-1` 已落地**。复核结论见 decision-log 同日条。 - ~~第 4.3 节记录的「Supervisor 自行发起提问目前只有 Prompt 兜底、缺机制约束」~~——**2026-08-13 裁决:M1 不做成机制约束,维持 Prompt 兜底。** 理由:本链路上 Supervisor 是自家 Prompt 驱动的受控角色,不是外部输入;真正的失效后果(问题被改写、答案转述失真)属于产出质量问题,由用户在审批卡上兜底,不是安全边界被突破。为它单独接一道等价于 `static_delegate_clarification_pending_matches_delivery_at` 的校验,成本落在 `standard` 全链路上,与收益不成比例。 **本裁决的纪律**:第 4.3 节与第 5.2 节现有的如实记录**必须原样保留**,不得因为「已裁决」就改写成「已保证」或删掉——它记的是事实(standard 下该校验不触发),事实没变。M1 的相关回归也不得断言「Supervisor 无法自行提问」。 @@ -1954,7 +1954,7 @@ M0 完成不表示任何策划功能已上线。M1 的功能实现仍未开始 | PR | 主题 | 依赖 | 合入门禁 | | --- | --- | --- | --- | -| `M1A-1` | source 常量 `project-supervisor-plan` + 进可信 matcher + steer 独立否决 | — | 全部可信 source 消费点逐点复核结论进 PR 描述;steer 在「plan source 在 matcher 内」与「不在」两种构造下均被拒 | +| `M1A-1` | source 常量 `project-supervisor-plan` + 进可信 matcher + steer 独立否决 | — | **已落地**。消费点复核见 decision-log 2026-08-13 `M1A-1` 条;steer `kind=plan-root-steer-unsupported`,独立于 matcher | | `M1A-2` | 两层工具面 + `project-planning` 角色 brief | `M1A-1` | 两层工具面快照;`user.input_request` 在广告层不出现且执行层仍拒(第 19 节第 2 条两半) | | `M1B-1` | `.agent/planning/` 存储层、strict schema、typed 指纹、版本链;含 `.agent/planning/**` 只挡写判据 | `M1A-2` | **第 9.1 节 golden vector 逐字节相等且指纹相等**(先于其它测试);create-only 与等前缀不可变 | | `M1B-2` | `plan.submit_gdd` 原生工具与提交点 | `M1B-1` | 全部拒绝分支;提交点前后强杀恢复;同 submissionId replay 不产生 vN+1 |