From 52894d2e8aff2b9628b04434b5dbeda8c7b5b2a8 Mon Sep 17 00:00:00 2001 From: Linghong Date: Fri, 28 Aug 2026 07:46:32 +0000 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E7=B4=A7=E7=AD=96=E5=88=92=20continua?= =?UTF-8?q?tion=20=E6=B8=B8=E6=A0=87=E9=98=B2=E6=AD=A2=E6=97=A7=20delivery?= =?UTF-8?q?=20=E8=B6=8A=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 planning session latestDelegationId 直接后继校验,阻止旧 delivery 另起分支 保留用户修订周期内质量返工的 user_revision 语义并更新提交门注释 补充 continuation 边界回归与 Fast GDD 技术决策文档 --- .../runtime_protocol/planning_coordinator.rs | 71 +++++++++++++++++-- .../agent/runtime_protocol/planning_submit.rs | 7 +- .../shared-memory/decision-log.md | 8 +++ ...方案】立项策划Agent(Fast GDD)-2026-08-10.md | 4 +- 4 files changed, 79 insertions(+), 11 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_coordinator.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_coordinator.rs index 20d56b640..e47c24f67 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_coordinator.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_coordinator.rs @@ -25,6 +25,25 @@ fn plan_coordinator_error(kind: &str, detail: impl AsRef) -> String { format!("{kind}: {}", detail.as_ref()) } +fn validate_plan_continuation_parent<'a>( + latest_delegation_id: &str, + delivery: &'a StaticDelegateDeliveryRecord, +) -> Result<&'a str, String> { + let original_id = delivery.repair_of_delegation_id.as_deref().ok_or_else(|| { + plan_coordinator_error( + "PLAN_ACTIVE_RUN_EXISTS", + "已有 planning session 时不能创建第二条根 delegation", + ) + })?; + if latest_delegation_id != original_id { + return Err(plan_coordinator_error( + "PLAN_NEEDS_RECONCILIATION", + "planning continuation 必须直接继承当前 session 的 latest delegation", + )); + } + Ok(original_id) +} + fn plan_session_successor_base(previous: &PlanSessionV1) -> Result { let mut next = previous.clone(); next.session_revision = previous.session_revision.checked_add(1).ok_or_else(|| { @@ -230,6 +249,45 @@ mod option_label_delimiter_tests { } } +#[cfg(test)] +mod planning_continuation_parent_tests { + use super::*; + + fn delivery(repair_of_delegation_id: Option<&str>) -> StaticDelegateDeliveryRecord { + new_static_delegate_delivery_with_contract( + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + "supervisor-session", + "supervisor-run", + "delegate-action", + "current-delivery", + GAME_CREATOR_PROJECT_PLANNING_AGENT_ID, + "planning-session", + "planning-run", + &[], + &[], + repair_of_delegation_id, + ) + } + + #[test] + fn continuation_must_extend_the_session_cursor() { + let continuation = delivery(Some("older-delivery")); + let error = validate_plan_continuation_parent("current-delivery", &continuation) + .expect_err("older delivery must not become the current planning branch"); + assert!(error.contains("latest delegation")); + } + + #[test] + fn continuation_accepts_the_current_session_cursor() { + let continuation = delivery(Some("current-delivery")); + assert_eq!( + validate_plan_continuation_parent("current-delivery", &continuation) + .expect("current delivery is a valid continuation"), + "current-delivery" + ); + } +} + fn plan_option_label_matches_answer(label: &str, normalized_answer: &str) -> bool { label == normalized_answer } @@ -584,12 +642,13 @@ pub(crate) fn ensure_plan_session_for_planning_child_task_at_locked( { return Ok(true); } - let original_id = delivery.repair_of_delegation_id.as_deref().ok_or_else(|| { - plan_coordinator_error( - "PLAN_ACTIVE_RUN_EXISTS", - "已有 planning session 时不能创建第二条根 delegation", - ) - })?; + // `latest_delegation_id` is the planning session's single continuation + // cursor. A new child must extend that cursor directly; otherwise a + // Supervisor can select an older claimed delivery and make an unrelated + // branch look like the current session. Keep this check here, after the + // exact-task replay fast path above, so replaying an already projected + // child remains idempotent. + let original_id = validate_plan_continuation_parent(&previous.latest_delegation_id, &delivery)?; let deliveries = list_static_delegate_deliveries_at(root)?; if static_delegate_lineage_contains_unknown_contract_status( &deliveries, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_submit.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_submit.rs index 1cc9e8430..dfb10dbc6 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_submit.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_submit.rs @@ -1189,9 +1189,10 @@ fn validate_current_session_cas( Ok(()) } -/// `user_revision` 只证明审批意见,不证明澄清。首次 collecting、澄清续跑和提交前 -/// 质量返工的 session 都没有 revise/reject `lastDecisionRef`;合法续跑会把该引用 -/// 带到新的 collecting successor 上。replay 不走这里。 +/// `user_revision` 只证明审批意见,不证明澄清。首次 collecting、澄清续跑和没有待处理 +/// 用户修订的普通质量返工 session 都没有 revise/reject `lastDecisionRef`;用户修订周期 +/// 内的 continuation(包括其质量返工)会把该引用带到新的 collecting successor 上。 +/// continuation 的直接父边由 planning coordinator 校验,replay 不走这里。 fn validate_user_revision_requires_approval_decision( session: &PlanSessionV1, input: &PlanSubmitGddInputV1, diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 277195298..491f52a40 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -24,6 +24,14 @@ - 验证方式:首次 collecting 带 invented-confirmation 必须拒绝且不落 GDD;reject continuation 再交 `user_revision` 的 v2 仍成功。 - 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`。 +## 2026-08-28 planning continuation 必须沿当前 delivery 游标推进 + +- 背景:`lastDecisionRef.action=revise/reject` 在用户修订后的质量返工中必须继续有效,但仅凭该历史指针无法证明当前 `agent.delegate` 选择的是本次 planning session 的当前分支。 +- 决策:不新增用户修订授权字段,也不在 `plan.submit_gdd` 重复遍历 approval receipt/GDD lineage。已有 planning session 创建新 child 时,`repairOfDelegationId` 必须直接等于旧 session 的 `latestDelegationId`;不一致即在 Provider 启动前以 `PLAN_NEEDS_RECONCILIATION` 拒绝。合法用户修订及其后质量返工继续保留 `lastDecisionRef`,成功提交新的 GDD 后仍由 submit successor 清理该指针。 +- 影响范围:`planning_coordinator.rs` continuation 投影门;Fast GDD 技术方案第 8.2 节和提交步骤;不改变静态委派通用返工合同或 `PlanSessionV1` schema。 +- 验证方式:新增当前游标 continuation 正向/旧 delivery 负向回归;CI 继续验证首次伪造 `user_revision` 拒绝、用户修订后质量返工提交成功及现有澄清/返工 lineage。 +- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_coordinator.rs`。 + ## 2026-08-27 退款 emergency spool 容量溢出保持可恢复 ## 2026-08-27 退款 emergency spool 容量溢出保持可恢复 diff --git a/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md b/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md index 2717788e1..b48a05c30 100644 --- a/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md +++ b/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md @@ -663,7 +663,7 @@ Provider 只能提交设计内容,不能提交或覆盖任何 Runtime 身份 该 input 及所有嵌套类型都使用 `deny_unknown_fields`;`game.platformFacts`、任意 `basis`、`projectId/gddId/version/submissionId/approvalRequestId`、action/run/session identity、时间与任何 fingerprint 一旦出现在 Provider input 中即返回 `PLAN_INVALID_REQUEST`。Runtime 在发出本轮 Provider request 前把当前 `sessionRevision/sessionFingerprint` 绑定进内部执行上下文,在项目锁内验证该 CAS 后,才把 project、GDD、版本、durable action、source/profile、session/run、时间、固定 `platformFacts`、全部 `basis:null` 与 fingerprint 注入 `plan-gdd.v1`。字段数量和文本限制按第 8.3 节对应 durable 字段执行。 -input 是当前 GDD 的完整快照,不要求与 source session 的 `decisionsSummary` 和 `prototypeValidationItems` 逐项相等。审批修订可用 `user_revision + round=0` 修改、删除或新增决定;未涉及内容由 Agent 以当前 GDD 为基线保持不变。Runtime 仍校验决定结构、原型项双射、`initial-request` 首项、身份和 CAS。payload 出现 `answerSource=user_revision` 时,当前 session 的 `lastDecisionRef.action` 必须是 `revise` 或 `reject`;首次提交、澄清续跑和普通质量返工返回 `PLAN_INVALID_REQUEST`。该闸只作用于新版本 create,同 `submissionId` replay 不重判。唯一固定的 `confirmed + user_freeform + round=0` 是 Runtime 创建的 `initial-request`。 +input 是当前 GDD 的完整快照,不要求与 source session 的 `decisionsSummary` 和 `prototypeValidationItems` 逐项相等。审批修订可用 `user_revision + round=0` 修改、删除或新增决定;未涉及内容由 Agent 以当前 GDD 为基线保持不变。Runtime 仍校验决定结构、原型项双射、`initial-request` 首项、身份和 CAS。payload 出现 `answerSource=user_revision` 时,当前 session 的 `lastDecisionRef.action` 必须是 `revise` 或 `reject`;首次提交、澄清续跑和没有待处理用户修订的普通质量返工返回 `PLAN_INVALID_REQUEST`。用户修订周期内的质量返工可以继续携带 `user_revision`,但其 planning child 必须直接继承当前 session 的 `latestDelegationId`,不得从旧 delivery 另起分支。该闸只作用于新版本 create,同 `submissionId` replay 不重判。唯一固定的 `confirmed + user_freeform + round=0` 是 Runtime 创建的 `initial-request`。 ### 8.3 `plan-gdd.v1` @@ -1161,7 +1161,7 @@ GDD handler 只能从已验证 batch binding 复制 `sourceSessionRevision/sourc main loop 不能把 submit 当成普通 action dispatch:在 durable action identity 建立后、生成普通 command ID 或进入 action executor 前,必须进入 `plan.submit_gdd` 专用分支。该分支重验 exact plan identity,执行下列提交与投影。**(2026-08-14 按 M1B-2 实现边界收口)** 本包只负责校验、定版、写不可变 GDD、重建 index、渲染 `game/fast_gdd.md`、安装 session successor 并终止策划子 run;**不创建 `.agent/planning/pending.json` / `gdd-approval` planning pending,不创建审批卡,也不把 Supervisor 或策划子 run 投影为审批等待**。`gdd-approval` pending 与 Supervisor 等待态属于 `M1C-1`,还要受第 13.0 节 `M1C-2a` 验收取证门约束。原 submit 在进入专用分支前已经建立的 generic `game-creator-pending-action.v5` standalone pending 与 `game-creator-provider-action-batch.v4` action batch 必须原样保留,作为后续 receipt/terminal observation 的同 action 恢复锚点;GDD create 成功不等于该 action 已 observed。 1. 解析第 8.2 节 strict input;在项目锁内重读 project identity、策划子 run 与委派根身份、Provider request 所绑定的 session CAS、canonical GDD 链及原 submit 的 generic v5 standalone pending / v4 batch anchors。不信任 Provider payload 中不存在也不允许出现的版本、时间、平台事实或身份;M1B-2 不读取或创建尚未实现的 approval receipt / planning pending。 -2. 验证文本上限、轮次、决定状态和 prototype item 一一对应;`decisions` 按本次完整 GDD 快照校验,不与旧 session 内容逐项比较。`round=0` 的非首项决定只能是 `answerSource=default`(默认建议)或 `answerSource=user_revision`(审批修改),分别对应允许的状态集合。`user_revision` 还要求当前 session 已有 `lastDecisionRef.action ∈ {revise, reject}`;没有该引用时不得把未确认项标成审批修改。Runtime 注入固定 platformFacts 和所有 `basis:null`,以当前 durable actionId/裸 action fingerprint 作为 submission identity。 +2. 验证文本上限、轮次、决定状态和 prototype item 一一对应;`decisions` 按本次完整 GDD 快照校验,不与旧 session 内容逐项比较。`round=0` 的非首项决定只能是 `answerSource=default`(默认建议)或 `answerSource=user_revision`(审批修改),分别对应允许的状态集合。`user_revision` 还要求当前 session 已有 `lastDecisionRef.action ∈ {revise, reject}`;没有该引用时不得把未确认项标成审批修改。已有 session 创建新的 planning child 时,`repairOfDelegationId` 必须精确等于旧 session 的 `latestDelegationId`;这条 continuation 游标约束在 Provider 请求前生效,防止旧 delivery 重新成为当前分支。Runtime 注入固定 platformFacts 和所有 `basis:null`,以当前 durable actionId/裸 action fingerprint 作为 submission identity。 3. M1B-2 尚无 receipt writer:只要已有任一 GDD,新的不同 submissionId 就返回 `PLAN_PENDING_GDD_EXISTS`;同 submissionId 只允许按历史 binding replay。`M1C-1` 接入有效 approve/revise/reject receipt 后,才把边界扩为“最新版本已有 receipt 才允许下一版本”。 4. 当前 M1B-2 的首次版本固定为 1;未来版本仍只能取最后一个连续有效版本加一,范围 1~128,不允许缺号或扫描任意文件补号。 5. 新提交由 Runtime 生成并冻结 `approvalRequestId/createdAtUtc`,填充全部 durable identity、source session binding 和时间,计算 GDD fingerprint,以第 10.1 节算法 create-only 发布 `gdd.v{N}.json`。同 submissionId replay 必须先找到并严格读取既有 GDD,复用其中 Runtime 生成的版本、request/time 与 identity 后再比较,不能用新时间制造假冲突。