立项策划:合入 M1C-0 用户修订 lineage 分类
Project CI / Repository checks (pull_request) Successful in 1m25s
Project CI / Frontend tests (pull_request) Successful in 2m53s
Project CI / Backend tests (pull_request) Successful in 3m55s
Project CI / Native shell tests (pull_request) Failing after 10m40s

M1C-0 在隔离 worktree 上以 09c7d7af8(M1A-2 收口)为基线开发,缺 M1A-4、
M1A 残余收口、M1A-2 回归修复与 M1B-1 共六个提交,故走 merge 而非 fast-forward。

代码零冲突:本包改顶层 src-tauri/src/delegation.rs,M1A-4 改
src-tauri/src/agent/runtime_tools/delegation.rs,同名不同文件;
tests/collaboration/static_deliveries.rs 两侧各自追加测试,自动合并。

两处文档状态句冲突,按「原分支事实优先」解决:保留 M1A-4 与 M1B-1 的已落地
事实,删去本包基线上「.agent/planning 存储仍未实现」「M1B-1 及之后仍未开始」
两句已被 M1B-1 推翻的表述;顺带把状态句和第 23.6 节第五行遗漏的 M1A-4 补回。
decision-log 的 M1C-0 条补一段合入说明,并订正其关联行里自指的 M1C-0 为 M1C-1。

合入后验证:cargo check --offline --all-targets 通过,npm run check:encoding
通过(5373 files),git diff --check 干净。定向测试与逐项复核另行跟进。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 07:10:02 +00:00
5 changed files with 477 additions and 10 deletions
@@ -37,6 +37,7 @@ pub(crate) enum StaticDelegateContractStatus {
EvidenceReady,
NeedsRepair,
NeedsUserInput,
UserRevisionRequested,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
@@ -1090,7 +1091,7 @@ pub(crate) fn game_chat_art_delivery_gap_at(
/// 下一跳(若存在)应当被归类为澄清 continuation,而不是质量返工。
/// `validate_static_delegate_repair_request_at` 与
/// `validate_static_delegate_clarification_continuation_at` 共用这一个判据源,
/// 避免两处口径漂移。
/// 避免两处口径漂移;用户修订状态另由下方判据单独识别
fn static_delegate_original_is_awaiting_clarification(
delivery: &StaticDelegateDeliveryRecord,
) -> bool {
@@ -1099,12 +1100,26 @@ fn static_delegate_original_is_awaiting_clarification(
})
}
/// 唯一权威判据:某条 delivery 是否由用户审批的「修改」动作标记为待修订。
///
/// 该状态只由后续审批工作包写入;本包只让 lineage 重放认识它,不能自行生成或
/// 把其它状态静默映射成它。
fn static_delegate_original_is_user_revision_requested(
delivery: &StaticDelegateDeliveryRecord,
) -> bool {
delivery.structured_result.as_ref().is_some_and(|result| {
result.contract_status == StaticDelegateContractStatus::UserRevisionRequested
})
}
/// 沿 repair_of_delegation_id 反向重放整条链,现算目标 delivery 的
/// (repair_depth, clarification_round)。两个维度都是运行时派生值,故意不落盘:
/// - R3链根repair_of_delegation_id 为 None):depth = 0round = 0。
/// - R1澄清跳父节点处于 awaiting-clarification):round = parent.round + 1
/// - 链根repair_of_delegation_id 为 None):depth = 0round = 0。
/// - 澄清跳父节点处于 awaiting-clarification):round = parent.round + 1
/// depth 原样继承(不重置),否则可以插一次澄清洗掉返工深度,变成无限返工。
/// - R2(返工跳:父节点不处于 awaiting-clarification):depth = parent.depth + 1
/// - 用户修订跳(父节点为 UserRevisionRequested):depth/round 都原样继承
/// 因为该跳由用户显式触发,不属于 runaway-agent 质量返工。
/// - 质量返工跳(父节点不处于上述两种状态):depth = parent.depth + 1
/// round 重置为 0,因为返工后策划节点重新开工,不能因为返工吃掉预设的澄清轮次预算。
///
/// 防环 / 防越界:反向重放阶段一旦遇到重复 id、缺失的上游节点,或跳数超出
@@ -1140,6 +1155,8 @@ pub(crate) fn static_delegate_lineage_counters(
for parent in &chain[..chain.len().saturating_sub(1)] {
if static_delegate_original_is_awaiting_clarification(*parent) {
round += 1;
} else if static_delegate_original_is_user_revision_requested(*parent) {
// 用户明确触发的修订不是 runaway-agent 返工;保留两个运行时派生计数。
} else {
depth += 1;
round = 0;
@@ -1215,6 +1232,12 @@ pub(crate) fn validate_static_delegate_repair_request_at(
if original_clarification_round >= clarification_round_limit {
return Err("静态委派澄清轮次已达上限".to_string());
}
} else if static_delegate_original_is_user_revision_requested(original) {
// 用户修订不消耗 repair_depth/clarification_round,但链上重放的 fail-closed
// 哨兵仍不能被绕过;否则损坏或成环的 durable lineage 可能被误放行。
if original_repair_depth == u32::MAX || original_clarification_round == u32::MAX {
return Err("静态委派谱系计数无效,拒绝继续".to_string());
}
} else if original_repair_depth >= 1 {
return Err("静态委派返工深度最多为 1".to_string());
}
@@ -2575,4 +2598,288 @@ mod tests {
fs::remove_dir_all(root).ok();
}
fn claimed_static_delegate_for_lineage_test(
parent_run_id: &str,
delegation_id: &str,
repair_of_delegation_id: Option<&str>,
contract_status: StaticDelegateContractStatus,
) -> StaticDelegateDeliveryRecord {
let mut delivery = new_static_delegate_delivery_with_contract(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
"m1c0-lineage-parent-session",
parent_run_id,
&format!("{delegation_id}-action"),
delegation_id,
"design-director",
&format!("{delegation_id}-session"),
&format!("{delegation_id}-run"),
&[],
&[],
repair_of_delegation_id,
);
let mut result = StaticDelegateStructuredResult::default();
result.contract_status = contract_status;
delivery.status = StaticDelegateDeliveryStatus::ClaimedByParent;
delivery.terminal_status = Some("completed".to_string());
delivery.result_summary = Some("M1C-0 lineage test".to_string());
delivery.structured_result = Some(result);
delivery.claimed_by_action_id = Some(format!("{delegation_id}-claim-action"));
delivery
}
#[test]
fn static_delegate_user_revision_preserves_counters_and_bypasses_depth_gate() {
let root = std::env::temp_dir().join(format!(
"genarrative-static-user-revision-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("system time after unix epoch")
.as_nanos()
));
init_local_game_project_at(&root, "m1c0-user-revision", "M1C-0 用户修订分类测试")
.expect("project init");
let parent_run_id = "m1c0-user-revision-parent-run";
let root_delivery = claimed_static_delegate_for_lineage_test(
parent_run_id,
"m1c0-user-revision-d0",
None,
StaticDelegateContractStatus::NeedsRepair,
);
write_static_delegate_delivery_at(&root, &root_delivery).expect("write root delivery");
// 先证明做游戏链路的普通返工仍然受 depth=1 门限制。
let mut first_revision = claimed_static_delegate_for_lineage_test(
parent_run_id,
"m1c0-user-revision-d1",
Some(&root_delivery.delegation_id),
StaticDelegateContractStatus::NeedsRepair,
);
write_static_delegate_delivery_at(&root, &first_revision)
.expect("write first repair delivery");
let ordinary_repair_error = validate_static_delegate_repair_request_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
parent_run_id,
"m1c0-user-revision-d2-ordinary",
"design-director",
&[],
&[],
Some(&first_revision.delegation_id),
)
.expect_err("ordinary repair at depth=1 must remain blocked");
assert!(ordinary_repair_error.contains("深度最多为 1"));
// 同一节点被用户审批标记为修订后,第一次用户修订不应再被当作质量返工。
first_revision
.structured_result
.as_mut()
.expect("first revision structured result")
.contract_status = StaticDelegateContractStatus::UserRevisionRequested;
write_static_delegate_delivery_at(&root, &first_revision)
.expect("rewrite first delivery as user revision");
let deliveries = list_static_delegate_deliveries_at(&root).expect("list deliveries");
assert_eq!(
static_delegate_lineage_counters(&deliveries, &first_revision.delegation_id),
(1, 0),
"用户修订跳的父节点已有一次普通返工,两个计数仍应保持 (1,0)"
);
validate_static_delegate_repair_request_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
parent_run_id,
"m1c0-user-revision-d2",
"design-director",
&[],
&[],
Some(&first_revision.delegation_id),
)
.expect("user revision must bypass the depth=1 quality-repair gate");
// 连续第二次用户修订仍挂在同一 lineage 上,不能把任一计数重新解释成返工。
let second_revision = claimed_static_delegate_for_lineage_test(
parent_run_id,
"m1c0-user-revision-d2",
Some(&first_revision.delegation_id),
StaticDelegateContractStatus::UserRevisionRequested,
);
write_static_delegate_delivery_at(&root, &second_revision)
.expect("write second user revision delivery");
let deliveries = list_static_delegate_deliveries_at(&root).expect("list deliveries");
assert_eq!(
static_delegate_lineage_counters(&deliveries, &second_revision.delegation_id),
(1, 0),
"连续用户修订不能增加 repair_depth,也不能重置 clarification_round"
);
validate_static_delegate_repair_request_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
parent_run_id,
"m1c0-user-revision-d3",
"design-director",
&[],
&[],
Some(&second_revision.delegation_id),
)
.expect("a second consecutive user revision must remain admissible");
fs::remove_dir_all(root).ok();
}
#[test]
fn static_delegate_user_revision_preserves_existing_clarification_round() {
let mut quality_root = new_static_delegate_delivery(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
"m1c0-counter-session",
"m1c0-counter-run",
"m1c0-counter-d0-action",
"m1c0-counter-d0",
"design-director",
"m1c0-counter-d0-session",
"m1c0-counter-d0-run",
);
let mut quality_result = StaticDelegateStructuredResult::default();
quality_result.contract_status = StaticDelegateContractStatus::NeedsRepair;
quality_root.structured_result = Some(quality_result);
let mut clarification = new_static_delegate_delivery_with_contract(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
"m1c0-counter-session",
"m1c0-counter-run",
"m1c0-counter-d1-action",
"m1c0-counter-d1",
"design-director",
"m1c0-counter-d1-session",
"m1c0-counter-d1-run",
&[],
&[],
Some("m1c0-counter-d0"),
);
let mut clarification_result = StaticDelegateStructuredResult::default();
clarification_result.contract_status = StaticDelegateContractStatus::NeedsUserInput;
clarification.structured_result = Some(clarification_result);
let mut user_revision = new_static_delegate_delivery_with_contract(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
"m1c0-counter-session",
"m1c0-counter-run",
"m1c0-counter-d2-action",
"m1c0-counter-d2",
"design-director",
"m1c0-counter-d2-session",
"m1c0-counter-d2-run",
&[],
&[],
Some("m1c0-counter-d1"),
);
let mut user_revision_result = StaticDelegateStructuredResult::default();
user_revision_result.contract_status = StaticDelegateContractStatus::UserRevisionRequested;
user_revision.structured_result = Some(user_revision_result);
let deliveries = vec![quality_root, clarification, user_revision];
assert_eq!(
static_delegate_lineage_counters(&deliveries, "m1c0-counter-d2"),
(1, 1),
"用户修订必须继承此前的 depth=1 与 clarification_round=1"
);
}
#[test]
fn static_delegate_lineage_boundary_and_status_deserialization_fail_closed() {
let mut deliveries = Vec::new();
let mut ids = Vec::new();
for index in 0..33 {
ids.push(format!("m1c0-lineage-boundary-{index}"));
}
for index in 0..33 {
let parent = (index > 0).then(|| ids[index - 1].as_str());
let mut delivery = new_static_delegate_delivery(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
"m1c0-boundary-session",
"m1c0-boundary-run",
&format!("{}-action", ids[index]),
&ids[index],
"design-director",
&format!("{}-session", ids[index]),
&format!("{}-run", ids[index]),
);
delivery.repair_of_delegation_id = parent.map(str::to_string);
let mut result = StaticDelegateStructuredResult::default();
result.contract_status = StaticDelegateContractStatus::UserRevisionRequested;
delivery.structured_result = Some(result);
deliveries.push(delivery);
}
assert_eq!(
static_delegate_lineage_counters(&deliveries[..32], &ids[31]),
(0, 0),
"32 条 delivery31 跳)仍应在安全阀内"
);
assert_eq!(
static_delegate_lineage_counters(&deliveries, &ids[32]),
(u32::MAX, u32::MAX),
"超过 32-hop 安全阀必须 fail closed"
);
let encoded = serde_json::to_value(StaticDelegateContractStatus::UserRevisionRequested)
.expect("serialize user revision status");
assert_eq!(encoded, serde_json::json!("user-revision-requested"));
assert_eq!(
serde_json::from_value::<StaticDelegateContractStatus>(encoded)
.expect("round-trip user revision status"),
StaticDelegateContractStatus::UserRevisionRequested
);
let unknown = serde_json::from_value::<StaticDelegateContractStatus>(serde_json::json!(
"future-contract-status"
))
.expect_err("unknown durable contract status must fail closed");
assert!(unknown.to_string().contains("unknown variant"));
// 额外钉住 durable sidecar 读取入口:未知状态不能在 JSON sidecar 层被吞掉或
// 降级成 Default(NeedsRepair)。
let root = std::env::temp_dir().join(format!(
"genarrative-static-unknown-status-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("system time after unix epoch")
.as_nanos()
));
init_local_game_project_at(&root, "m1c0-unknown-status", "未知静态委派状态解析测试")
.expect("project init");
let mut record = new_static_delegate_delivery(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
"m1c0-unknown-status-session",
"m1c0-unknown-status-run",
"m1c0-unknown-status-action",
"m1c0-unknown-status-delivery",
"design-director",
"m1c0-unknown-status-target-session",
"m1c0-unknown-status-target-run",
);
record.status = StaticDelegateDeliveryStatus::Ready;
record.terminal_status = Some("completed".to_string());
record.result_summary = Some("unknown status fixture".to_string());
let mut result = StaticDelegateStructuredResult::default();
result.contract_status = StaticDelegateContractStatus::UserRevisionRequested;
record.structured_result = Some(result);
write_static_delegate_delivery_at(&root, &record).expect("write known status fixture");
let delivery_path = root
.join(STATIC_DELEGATE_DELIVERY_DIR)
.join(format!("{}.json", record.delegation_id));
let mut raw = serde_json::to_value(&record).expect("serialize status fixture");
raw["structuredResult"]["contractStatus"] = serde_json::json!("future-contract-status");
fs::write(
&delivery_path,
serde_json::to_vec(&raw).expect("serialize unknown status fixture"),
)
.expect("overwrite unknown status fixture");
let sidecar_error = read_static_delegate_delivery_at(&root, &record.delegation_id)
.expect_err("unknown durable status must fail at sidecar read");
assert!(
sidecar_error.contains("解析") && sidecar_error.contains("静态委派 delivery"),
"unexpected sidecar parse error: {sidecar_error}"
);
fs::remove_dir_all(root).ok();
}
}
@@ -3796,6 +3796,151 @@ fn alternating_repair_and_clarification_hop_blocks_second_repair_at_depth_two()
fs::remove_dir_all(root).ok();
}
#[test]
fn static_delegate_user_revision_requested_continuation_passes_real_delegate_gate_after_depth_one() {
// 这里手工把 delivery 状态切到 UserRevisionRequested,是对 M1C-1 未来审批写入的
// durable fixture;委派本身仍走真实 observe_agent_runtime_agent_delegate 生产路径。
let root = unique_project_path();
init_local_game_project_at(
&root,
"project-user-revision-real-gate",
"用户修订真实委派门测试",
)
.expect("project init");
let run_id = "project-supervisor-user-revision-real-gate-run";
let state = start_game_creator_agent_runtime_task_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
"验收策划子 Agent 并处理用户修订",
run_id,
"agent-chat",
"等待策划回执",
vec!["完成用户修订后的方案".to_string()],
)
.expect("start supervisor run");
let acceptance_criteria = vec!["必须交付策划产物".to_string()];
let expected_artifacts = vec!["game/user-revision-real-gate.md".to_string()];
let target_agent_id = "design-director";
let target_lock = try_acquire_game_creator_agent_runtime_task_lock(&root, target_agent_id)
.expect("acquire target lane")
.expect("target lane available");
let d0_id = "user-revision-real-gate-d0";
create_dispatched_static_delegate_delivery(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&state.session_id,
run_id,
"user-revision-real-gate-d0-action",
d0_id,
target_agent_id,
"user-revision-real-gate-d0-session",
"user-revision-real-gate-d0-run",
&acceptance_criteria,
&expected_artifacts,
None,
);
mark_and_claim_static_delegate_needs_repair(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
run_id,
target_agent_id,
"user-revision-real-gate-d0-session",
"user-revision-real-gate-d0-run",
d0_id,
&expected_artifacts,
"user-revision-real-gate-d0-claim",
);
let d1_action = "user-revision-real-gate-d1-repair-action";
let d1_id = agent_runtime_delegation_id(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
run_id,
target_agent_id,
d1_action,
);
let d1_observation = dispatch_static_delegate_plain_repair(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
run_id,
target_agent_id,
"补齐用户修订前的策划产物",
&acceptance_criteria,
&expected_artifacts,
d0_id,
d1_action,
);
assert_eq!(
d1_observation.status, "ok",
"首次质量返工必须放行:{d1_observation:?}"
);
let d1_delivery = read_static_delegate_delivery_at(&root, &d1_id)
.expect("read D1")
.expect("D1 exists");
mark_and_claim_static_delegate_needs_repair(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
run_id,
target_agent_id,
&d1_delivery.target_session_id,
&d1_delivery.target_run_id,
&d1_id,
&expected_artifacts,
"user-revision-real-gate-d1-claim",
);
let mut d1_user_revision = read_static_delegate_delivery_at(&root, &d1_id)
.expect("read claimed D1")
.expect("claimed D1 exists");
d1_user_revision
.structured_result
.as_mut()
.expect("D1 structured result")
.contract_status = StaticDelegateContractStatus::UserRevisionRequested;
write_static_delegate_delivery_at(&root, &d1_user_revision)
.expect("persist simulated approval revision status");
// D1 的链上 depth 已经是 1;只有 UserRevisionRequested 分支能让这次真实委派继续。
let d2_action = "user-revision-real-gate-d2-revision-action";
let d2_id = agent_runtime_delegation_id(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
run_id,
target_agent_id,
d2_action,
);
let d2_observation = dispatch_static_delegate_plain_repair(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
run_id,
target_agent_id,
"按用户审批修改方案",
&acceptance_criteria,
&expected_artifacts,
&d1_id,
d2_action,
);
assert_eq!(
d2_observation.status, "ok",
"UserRevisionRequested 不能被 depth=1 质量返工门误拒:{d2_observation:?}"
);
let d2_delivery = read_static_delegate_delivery_at(&root, &d2_id)
.expect("read D2")
.expect("D2 exists");
let deliveries = list_static_delegate_deliveries_at(&root).expect("list deliveries");
assert_eq!(
static_delegate_lineage_counters(&deliveries, &d2_id),
(1, 0),
"用户修订续跑应保留已有 depth=1 且不重置此前 round"
);
assert_eq!(
d2_delivery.repair_of_delegation_id.as_deref(),
Some(d1_id.as_str())
);
drop(target_lock);
fs::remove_dir_all(root).ok();
}
#[test]
fn quality_rework_resets_clarification_round_budget() {
let root = unique_project_path();
@@ -1,5 +1,14 @@
# 决策记录
## 2026-08-14 M1C-0:用户修订状态进入静态委派 lineage 分类
- 落地:`StaticDelegateContractStatus` 新增 `UserRevisionRequested`serde durable 值为 `user-revision-requested`)。`static_delegate_lineage_counters` 现在按三类传播:`NeedsUserInput` 只增加 `clarification_round``UserRevisionRequested` 原样继承 `repair_depth``clarification_round`;其它状态继续按质量返工增加 `repair_depth` 并重置 `clarification_round`
- 门禁:父 delivery 为 `UserRevisionRequested` 时,后续同链续跑不再被 `repair_depth=1` 的质量返工门误拒,因此连续用户修订可以继续通过;链上 `(u32::MAX, u32::MAX)` fail-closed 哨兵仍拒绝,不得借用户修订分支绕过 32-hop、成环或缺失上游保护。普通做游戏链路没有该状态,`repair_depth≤1` 结论未被推翻。
- 范围:本包没有任何审批状态写入方、`gdd-approval` pending、receipt 或前端状态;`UserRevisionRequested` 仍由后续 `M1C-1` 的审批命令与 receipt 同步写入。本包不 bump `STATIC_DELEGATE_DELIVERY_SCHEMA_VERSION`,未知 durable status 由 serde 直接报错,禁止静默降级为 `NeedsRepair`
- 回归:新增连续用户修订、保留已有澄清轮次、普通 depth=1 拒绝、32-hop fail-closed、`user-revision-requested` serde round-trip 与未知 variant fail-closed 覆盖;无新状态的历史记录继续按原质量返工分类。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 23.7、23.8 节;后续审批写入依赖 `M1B-2``M1C-1`
- 合入说明:本包在隔离 worktree 上以 `09c7d7af8``M1A-2` 收口)为基线开发,未包含 `M1A-4`、M1A 残余收口、`M1A-2` 回归修复与 `M1B-1`。合回时代码零冲突(本包改的是顶层 `src-tauri/src/delegation.rs``M1A-4` 改的是 `src-tauri/src/agent/runtime_tools/delegation.rs`,同名不同文件),仅两份文档的状态句冲突:合并时以原分支为准保留 `M1A-4` / `M1B-1` 的已落地事实,删去本包基线上「`.agent/planning` 存储仍未实现」「`M1B-1` 及之后仍未开始」两句已被 `M1B-1` 推翻的表述。
## 2026-08-14 修复 M1A-2 引入的回归:未知工具名不是身份违规
- 症状:`background_agent_runtime_persists_receipts_for_rejected_actions` 在 feature 分支恒失败(3/3),`master` 通过。首个 tool-plan 请求能收到,动作被拒绝后**第二次 Provider follow-up 请求不再发出**,测试等待超时。**不是已知的 mock-LLM 本机 flake**,是确定性回归。
@@ -30,6 +39,7 @@
2. **plan 之外的 source 多了一个失败面。** 两个 observe 函数新增的 task journal 读取,其 `Err` 分支位于弱判据之前,对 `project-supervisor-gui/cli/game-chat` 同样生效。正常状态不触发(仅 journal I/O 损坏等异常)。保留的依据是:读不到 task 就无法判断这条 run 是不是 plan 根,此时 fail closed 是正确取向;改成「读失败就放行」会引入一个新的 fail-open。**这是对第 19 节「做游戏链路逐字不变」的一处有意例外,仅限异常路径。**
3. **上下文层回归是弱断言。** `plan_root_supervisor_prompt_drops_intro_and_visual_contract_sections` 用「不含某几条独有短句」断言,不是对照组那种字节级 `assert_eq`。已实测非永真。已知漏报场景:若将来 `$visualContract` / `supervisorIntro` 被替换成措辞不同但仍暗示专业组扇出的新文本,这条不会报警。**执行层的 `A1`/`A2` 是该场景的唯一保障**——这也是本包把硬门排在裁段之前的原因。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 4.3、22、23.8、24 节。
## 2026-08-14 M1B-1planning storage 基础与只挡写隔离完成(待合入)
- 范围:在 `M1A-2` 的 planning 子 Agent 工具边界之上,先落地 Runtime-owned `.agent/planning/**` 的 typed storage 基础,不注册、不广告、不执行 `plan.submit_gdd`(该工具仍属于 `M1B-2`)。当前实现位于隔离 worktree 的 `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs`,由 `runtime_protocol.rs` 注册。
@@ -1060,7 +1060,7 @@ static delivery 在 V1.16 的 parent/target Agent、Session、run、action、del
```text
structuredResult = {
contractStatus: evidence-ready | needs-repair,
contractStatus: evidence-ready | needs-repair | needs-user-input | user-revision-requested,
artifacts: [{ path, sha256 }],
missingExpectedArtifacts: [path],
verificationRequired: boolean,
@@ -1070,6 +1070,11 @@ structuredResult = {
}
```
其中 `needs-user-input` 是静态委派澄清中转状态,`user-revision-requested` 是 Fast GDD
审批扩展预留的用户修订状态;后者不由通用 Runtime 自动生成,且不得被反序列化失败静默
降级为 `needs-repair`。用户修订的 lineage 计数语义与 M1C-0/M1C-1 边界以
`【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 23.7 节为准。
`artifacts``missingExpectedArtifacts` 必须恰好分割全部 `expectedArtifacts`,路径保持合同中的精确相对路径;存在的普通文件在形成终态回执时重新计算 SHA-256。`evidence``error` 只允许有界、已清洗的安全摘要、项目内相对路径和哈希,不得保存凭据、绝对路径、私有 observation 正文或 Provider payload。旧空合同 delivery 可以继续没有 `structuredResult`,且不得为了升级格式改写原 sidecar;新带合同 delivery 进入 ready/claimed 时必须有完整 `structuredResult`
### 客观门禁与语义验收
@@ -1,9 +1,9 @@
# 立项策划 AgentFast GDD)技术方案
- 日期:2026-08-10
- 状态:2026-08-12 **M0 代码工作包全部完成**`M0A-2``M0B-1``M0B-2` 已合入 M0 集成分支并通过各自门禁,见第 23.4 节);同日 **D6 作废、拓扑改变**`M0A-1` 交付的文档基线随之失效,需以工作包 `M0A-3` 修订,**修订完成前 M0 不计完整完成**(见第 1.1 节)。2026-08-13 **D9 二次作废、D10 作废,由 D11 取代**:立项策划节点改为 Project Supervisor 通过 `agent.delegate` 发起的静态委派子 Agent,问询复用 PR #165 中转链路(见第 1.1 节「D11 新拓扑」);D11 依赖 WP1(静态委派澄清轮次与返工深度拆分)为强制前置,**该前置已于 2026-08-13 落地并合入**`WP1` 生产代码 + `WP2` 回归,完成状态与门禁见第 23.5 节),澄清轮次上限现为 3game-chat source 仍为 1)。随后 `M1A-1``M1A-2``M1A-3` 已分别落地:`M1A-2` 仅收口两层工具面、`project-planning` role brief 注入和 fail-closed 拒绝边界。**2026-08-14 M1B-1 已在隔离 worktree 完成并通过本包验收门禁,已提交于隔离分支 `f453c2ca2`,待合回原分支**:已落地 `.agent/planning` storage module、strict schema/typed 指纹/canonical parser、GDD 版本链、session 原子恢复、Runtime 写入身份及只挡写门禁;golden vector 与 11 个定向 storage 测试通过,writer/index/recovery 及全仓库门禁已完成。`plan.submit_gdd`、审批闭环和 UI 仍未实现,见第 23.6、23.8 节。后续执行计划见第 23.6 节。
- 状态:2026-08-12 **M0 代码工作包全部完成**`M0A-2``M0B-1``M0B-2` 已合入 M0 集成分支并通过各自门禁,见第 23.4 节);同日 **D6 作废、拓扑改变**`M0A-1` 交付的文档基线随之失效,需以工作包 `M0A-3` 修订,**修订完成前 M0 不计完整完成**(见第 1.1 节)。2026-08-13 **D9 二次作废、D10 作废,由 D11 取代**:立项策划节点改为 Project Supervisor 通过 `agent.delegate` 发起的静态委派子 Agent,问询复用 PR #165 中转链路(见第 1.1 节「D11 新拓扑」);D11 依赖 WP1(静态委派澄清轮次与返工深度拆分)为强制前置,**该前置已于 2026-08-13 落地并合入**`WP1` 生产代码 + `WP2` 回归,完成状态与门禁见第 23.5 节),澄清轮次上限现为 3game-chat source 仍为 1)。随后 `M1A-1``M1A-2``M1A-3``M1A-4` 已分别落地:`M1A-2` 仅收口两层工具面、`project-planning` role brief 注入和 fail-closed 拒绝边界`M1A-4` 收窄 plan 根 run 的子 Agent 创建面。**2026-08-14 M1B-1 已在隔离 worktree 完成并通过本包验收门禁,已提交于隔离分支 `f453c2ca2`,待合回原分支**:已落地 `.agent/planning` storage module、strict schema/typed 指纹/canonical parser、GDD 版本链、session 原子恢复、Runtime 写入身份及只挡写门禁;golden vector 与 11 个定向 storage 测试通过,writer/index/recovery 及全仓库门禁已完成。**2026-08-14 `M1C-0` 亦已在隔离 worktree 完成并合回本分支**:仅新增用户修订状态及 lineage 分类,不包含审批写入方。`plan.submit_gdd`、审批闭环和 UI 仍未实现,见第 23.6、23.8 节。后续执行计划见第 23.6 节。
- 适用范围:AI 游戏创作独立 App、Project Supervisor、Agent Runtime、本地项目策划 sidecar 与后续完整构建准入
- 当前实现边界:本文件是后续详细设计与实现的仓库内阶段基线;M0 工作包冻结 Fast GDD 合同并修复现有 owner 验证、game-chat retry 与前端投影边界,`M1A-1``M1A-3` 已提供 plan source、两层工具面角色 brief 的 Runtime 基础`M1B-1` 当前 worktree 已提供 storage 基础与写入隔离,但尚未宣称最终合入完成;立项策划入口、`plan.submit_gdd` 提交、审批 UI 或构建绑定仍不可用
- 当前实现边界:本文件是后续详细设计与实现的仓库内阶段基线;M0 工作包冻结 Fast GDD 合同并修复现有 owner 验证、game-chat retry 与前端投影边界,`M1A-1``M1A-4` 已提供 plan source、两层工具面角色 brief 与子 Agent 创建面收窄的 Runtime 基础,`M1C-0` 已提供用户修订 lineage 分类`M1B-1` 当前 worktree 已提供 storage 基础与写入隔离,但尚未宣称最终合入完成;立项策划入口、`plan.submit_gdd` 提交、审批 UI 或构建绑定仍不可用
## 1. 背景与目标
@@ -1872,7 +1872,7 @@ M0 完成不表示完整策划闭环已经上线。`M1A-1`、`M1A-2`、`M1A-3`
**完成状态**:上述门禁全部通过,`WP1` 生产代码与 `WP2` 回归已合入本分支。已知遗留:`project_supervisor_concurrent_repair_dispatch_creates_exactly_one_delivery` 是 Barrier 同步的并发用例,在本机 Windows 上间歇失败;经与改造前基线对照(同一用例各跑 6 次,改造前后同为 1 次失败)确认为既有抖动,不是本工作包引入,不作为回归处理。
### 23.6 后续执行计划(2026-08-13 起)
### 23.6 后续执行计划(2026-08-13 起2026-08-14 状态更新
本节记录 D11 之后的执行顺序与各步状态,避免「设计结论持续更新、但做到哪了无人维护」。
@@ -1883,7 +1883,7 @@ M0 完成不表示完整策划闭环已经上线。`M1A-1`、`M1A-2`、`M1A-3`
| 三 | `project-planning` 的 agentCatalog 登记 | **已完成**(机制冻结见第 3.1 节;**代码亦已落地**2026-08-13manifest、prompt bundle、runtime adapter、`prompt.rs` 角色合成分支及四处 needs_change 全部合入) |
| 四 | `M0A-3` 批二:拓扑与工具面部分 | **已完成**2026-08-13),拆解见下 |
| 四之余 | schema 与 golden vector 收口 | **已完成**2026-08-13),拆解见下 |
| 五 | M1 本体:策划闭环功能实现 | **`M1A-1``M1A-2``M1A-3` 已落地**`M1B-1` 已于 2026-08-14 在隔离 worktree 完成并通过本包验收门禁,已提交于隔离分支 `f453c2ca2`,待合回原分支:storage 基础、strict schema、typed 指纹、版本链、session 原子恢复、只挡写门禁及 writer/index/recovery 验证均已完成,golden vector 与 11 个定向 storage 测试通过。`M1A-2` 只交付工具面、brief 注入和拒绝边界,不包含 GDD 提交/审批;`M1B-2` 及之后未开始。合入门见第 23.8 节 |
| 五 | M1 本体:策划闭环功能实现 | **`M1A-1``M1A-2``M1A-3``M1A-4``M1C-0` 已落地**`M1B-1` 已于 2026-08-14 在隔离 worktree 完成并通过本包验收门禁,已提交于隔离分支 `f453c2ca2`,待合回原分支:storage 基础、strict schema、typed 指纹、版本链、session 原子恢复、只挡写门禁及 writer/index/recovery 验证均已完成,golden vector 与 11 个定向 storage 测试通过。`M1A-2` 只交付工具面、brief 注入和拒绝边界,`M1C-0` 只交付用户修订 lineage 分类,均不包含 GDD 提交/审批;`M1B-2` 及之后未开始。合入门见第 23.8 节 |
批二在 2026-08-13 拆成两半,因为其中一半在 M1 代码存在之前**做不完**:
@@ -1965,7 +1965,7 @@ M0 完成不表示完整策划闭环已经上线。`M1A-1`、`M1A-2`、`M1A-3`
| `M1A-4` | plan 根 run 的子 Agent 创建面收窄:`agent.delegate` 目标必须是 `project-planning``agent.spawn_isolated` 一律拒;plan source 下不拼 `supervisorIntro``$visualContract` | `M1A-2` | 执行层与上下文层**都要做且不可互相替代**(同第 19 节第 2 条纪律);`agent.spawn_isolated` 是第二条造子 Agent 的通道,只堵 `agent.delegate` 视为未完成;强判据 `Err` 必须落进拒绝分支而非当作「不是 plan 根」;`project-supervisor-gui/cli` 的委派与 spawn 行为正常路径不变。**必须早于 `M1D-2``M1E`** |
| `M1B-1` | `.agent/planning/` 存储层、strict schema、typed 指纹、版本链;含 `.agent/planning/**` 只挡写判据 | `M1A-2` | **2026-08-14 已在隔离 worktree 完成并通过本包验收门禁,已提交于隔离分支 `f453c2ca2`,待合回原分支**GDD / index / session strict DTO、canonical bytes、duplicate-key 与后缀门、typed 指纹、连续版本链、session 原子写入/恢复、Runtime writer identity 及 `game/fast_gdd.md` / `.agent/planning/**` 通用写入拒绝;第 9.1 节 golden vector3857 bytes)与 11 个定向 storage 测试通过,writer 目标 schema 重验、index 权威对账与锁内 index recovery(缺失/损坏/陈旧从严格 GDD 链重建)、恢复故障矩阵、`cargo check --offline``npm run check:encoding``git diff --check` 均通过。当前无 approval receipt schema,多版本 `statusCache` 只是无 receipt 的预审批投影(最新版本 `ready_for_approval`、旧版本 `superseded`);M1C-1 接入 receipt 后必须重建 `approved/revise/reject/superseded` 状态。`plan.submit_gdd`、审批闭环仍留给 `M1B-2` 及后续包;create-only 与等前缀不可变 |
| `M1B-2` | `plan.submit_gdd` 原生工具与提交点 | `M1B-1` | 全部拒绝分支;提交点前后强杀恢复;同 submissionId replay 不产生 vN+1 |
| `M1C-0` | 新增 `StaticDelegateContractStatus::UserRevisionRequested` 与分类分支 | `M1A-1` | **本 PR 无写入方、是惰性路径,行为零变化**;做游戏链路返工仍在 `depth=1` 被拒;无该变体的历史记录分类不变 |
| `M1C-0` | 新增 `StaticDelegateContractStatus::UserRevisionRequested` 与分类分支 | `M1A-1` | **已落地**:无审批写入方、是惰性路径;用户修订跳不增 `repair_depth` 也不重置 `clarification_round`,连续修订可通过;做游戏链路返工仍在 `depth=1` 被拒;历史记录与未知状态均保持 fail-closed |
| `M1C-1` | `gdd-approval` pending、审批命令、receiptreceipt 写入上述 status | `M1B-2``M1C-0` | 三动作全通;版本+指纹竞态防护;两窗口并发;**连续多次修订均可通过且 `repair_depth` 不变** |
| `M1C-2a` | Goal Contract 接线:turn 1 冻结、固定验收图、审批前置门取证 | `M1C-1``M1A-3` | turn 1 只能是一个 `agent.goal_contract``acceptanceNodes` 不接受自定义;**第 13.0 节审批前置门:取证未通过时不出现审批卡、而是产生返工委派**(取证顺序是协议时序问题,归本包而非 `M1C-1` |
| `M1C-2b` | 澄清中转接线、轮次派生、预算注入 | `M1C-2a` | 3 轮上限;continuation 重放幂等不增加轮次;答案绑定冲突被拒 |