立项策划:落地 M1C-0 合入复核结论,三条 M1C-1 前置与一条哨兵订正
Project CI / Repository checks (pull_request) Failing after 13s
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled

生产代码零改动(delegation.rs 三个 hunk 全在 mod tests 内),本次只补回归与文档。

复核确认「今天行为零变化」成立且可证:contract_status 生产赋值点只有两处且都从
客观事实派生;StaticDelegateClaimRecord 全仓库唯一构造点是运行时构造,不是 agent
提交的 JSON;durable sidecar 在 .agent/runtime/** 下被 file_ops 与 filesystem 各
三处拒写;前端无 contractStatus 消费者。lineage 三个 fail-closed 出口全部返回
(u32::MAX, u32::MAX),新分支的哨兵检查接得住。

三条 M1C-1 前置写进技术方案第 23.7、23.8 节与 decision-log:
① UserRevisionRequested 不进 repair_required_count / user_input_required_count
   任一 barrier,写入方落地即意味着 Supervisor run 可在用户修订未派出时完成;
② validate_static_delegate_structured_result 对该变体只有否定约束,缺正向一致性
   分支,UserRevisionRequested + failed + 缺产物 能通过校验落盘;
③ 前向兼容失败粒度是整个子系统——list_static_delegate_deliveries_at 逐条 ? 上抛,
   一条解析失败即整个目录枚举失败。bump schema version 救不了(版本校验在 parse 之后)。

文档订正:STATIC_DELEGATE_LINEAGE_MAX_HOPS = 32 限的是链上节点数不是跳数
(判据排在入链之前),真实跳数上限 31,第 23.7 节原写 32 跳已订正。

测试:
- static_delegate_user_revision_preserves_existing_clarification_round 名不副实,
  它把 UserRevisionRequested 放在目标位置,而计数循环只遍历父节点集合,新分支
  从未被执行。改名为 ..._parent_hop_preserves_depth_and_clarification_round,补
  一跳真正以用户修订为父的续跑并加反证;原断言留作对照组并注明性质。
- 新增 concurrent_user_revision_dispatch_creates_exactly_one_delivery,父节点为
  UserRevisionRequested 且 depth 已为 1,两侧同时钉住并发下恰好放行一条。
- user_revision_continuation_... 补兄弟检查断言(depth 门对用户修订失效后,它是
  该路径上唯一剩下的扇出约束)。

变异测试:摘掉 counters 分支 4 条变红(含新增反证 left (2,0) / right (1,1));
摘掉 gate 分支 3 条变红(并发用例 left 0 / right 1)。两处守卫各自有回归覆盖。

验证:定向 41 passed / 0 failed;npm run check:encoding 通过(5374 files);
git diff --check 干净。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 05:17:39 +00:00
parent 8c4acaaad5
commit 1d4d77ef3d
4 changed files with 265 additions and 9 deletions
@@ -2725,7 +2725,7 @@ mod tests {
}
#[test]
fn static_delegate_user_revision_preserves_existing_clarification_round() {
fn static_delegate_user_revision_parent_hop_preserves_depth_and_clarification_round() {
let mut quality_root = new_static_delegate_delivery(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
"m1c0-counter-session",
@@ -2774,11 +2774,49 @@ mod tests {
user_revision_result.contract_status = StaticDelegateContractStatus::UserRevisionRequested;
user_revision.structured_result = Some(user_revision_result);
let deliveries = vec![quality_root, clarification, user_revision];
let mut deliveries = vec![quality_root, clarification, user_revision];
// 对照组,不是主张:计数循环只遍历 chain[..len-1](父节点集合),目标节点自身的
// status 从不参与判定。所以「目标是 UserRevisionRequested」这条断言与新分支无关,
// 单靠它证明不了用户修订分类。
assert_eq!(
static_delegate_lineage_counters(&deliveries, "m1c0-counter-d2"),
(1, 1),
"用户修订必须继承此前的 depth=1 与 clarification_round=1"
"目标自身是用户修订时,两个计数只由其父链决定"
);
// 真正打到新分支的是「父节点为 UserRevisionRequested」的下一跳。
let continuation = new_static_delegate_delivery_with_contract(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
"m1c0-counter-session",
"m1c0-counter-run",
"m1c0-counter-d3-action",
"m1c0-counter-d3",
"design-director",
"m1c0-counter-d3-session",
"m1c0-counter-d3-run",
&[],
&[],
Some("m1c0-counter-d2"),
);
deliveries.push(continuation);
assert_eq!(
static_delegate_lineage_counters(&deliveries, "m1c0-counter-d3"),
(1, 1),
"父节点是用户修订时,depth 与 clarification_round 都必须原样继承"
);
// 反证:同一条链只把父节点改回普通质量返工,上一条必须变成 (2, 0)。缺了这条,
// 上一条断言在新分支被删掉后依然成立(走 else 支得到 (2, 0) 才会失败),
// 但没有对照就看不出它究竟钉住了什么。
deliveries[2]
.structured_result
.as_mut()
.expect("d2 structured result")
.contract_status = StaticDelegateContractStatus::NeedsRepair;
assert_eq!(
static_delegate_lineage_counters(&deliveries, "m1c0-counter-d3"),
(2, 0),
"父节点不是用户修订时必须回到质量返工分类"
);
}
@@ -3801,8 +3801,7 @@ fn alternating_repair_and_clarification_hop_blocks_second_repair_at_depth_two()
}
#[test]
fn static_delegate_user_revision_requested_continuation_passes_real_delegate_gate_after_depth_one()
{
fn user_revision_continuation_passes_real_gate_at_depth_one_but_stays_single_child() {
// 这里手工把 delivery 状态切到 UserRevisionRequested,是对 M1C-1 未来审批写入的
// durable fixture;委派本身仍走真实 observe_agent_runtime_agent_delegate 生产路径。
let root = unique_project_path();
@@ -3942,6 +3941,211 @@ fn static_delegate_user_revision_requested_continuation_passes_real_delegate_gat
Some(d1_id.as_str())
);
// 新分支放行 depth 门之后,函数末尾的「同一静态委派最多允许一轮返工」兄弟检查必须
// 仍然生效。这条是用户修订路径唯一剩下的扇出约束:depth 门对它不再适用,若兄弟检查
// 也漏掉该状态,一个 UserRevisionRequested 父节点就能挂任意多个并存子委派。
let d2b_observation = dispatch_static_delegate_plain_repair(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
run_id,
target_agent_id,
"对同一父节点再发一次用户修订",
&acceptance_criteria,
&expected_artifacts,
&d1_id,
"user-revision-real-gate-d2b-revision-action",
);
assert_eq!(
d2b_observation.status, "failed",
"用户修订不得绕过兄弟检查扇出第二条并存子委派:{d2b_observation:?}"
);
assert!(
d2b_observation.summary.contains("最多允许一轮返工"),
"拒绝理由必须是兄弟检查而不是别的门:{d2b_observation:?}"
);
drop(target_lock);
fs::remove_dir_all(root).ok();
}
/// 与 `project_supervisor_concurrent_repair_dispatch_creates_exactly_one_delivery` 同构,
/// 但父节点是 `UserRevisionRequested` 且链上 depth 已经是 1。两件事一起钉:
/// ① 并发下用户修订仍然只放行一条(兄弟检查在锁内有效,不因新分支失效);
/// ② 恰好放行「一条」而不是「零条」——新分支被删掉时两条都会被 depth 门拒,本用例变红。
#[test]
fn concurrent_user_revision_dispatch_creates_exactly_one_delivery() {
let root = unique_project_path();
init_local_game_project_at(
&root,
"project-concurrent-user-revision",
"用户修订并发派发测试",
)
.expect("project init");
let run_id = "project-supervisor-concurrent-user-revision-run";
let state = start_game_creator_agent_runtime_task_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
"并发发起同一用户修订",
run_id,
"agent-chat",
"等待用户修订",
vec!["只允许一条用户修订续跑".to_string()],
)
.expect("start concurrent user revision parent");
let acceptance_criteria = vec!["必须交付策划产物".to_string()];
let expected_artifacts = vec!["game/concurrent-user-revision.md".to_string()];
let target_agent_id = "design-director";
// 车道锁必须在任何真实派发之前拿:下面 D1 走的是真实 observe 路径,会占用
// 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 = "concurrent-user-revision-d0";
create_dispatched_static_delegate_delivery(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&state.session_id,
run_id,
"concurrent-user-revision-d0-action",
d0_id,
target_agent_id,
"concurrent-user-revision-d0-session",
"concurrent-user-revision-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,
"concurrent-user-revision-d0-session",
"concurrent-user-revision-d0-run",
d0_id,
&expected_artifacts,
"concurrent-user-revision-d0-claim",
);
// 先用一次普通质量返工把链上 depth 顶到 1,之后才轮到新分支承重。
let d1_action = "concurrent-user-revision-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,
"concurrent-user-revision-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");
let start = Arc::new(Barrier::new(3));
let mut workers = Vec::new();
for action_id in [
"concurrent-user-revision-action-a",
"concurrent-user-revision-action-b",
] {
let worker_root = root.clone();
let worker_start = Arc::clone(&start);
let worker_criteria = acceptance_criteria.clone();
let worker_artifacts = expected_artifacts.clone();
let worker_original = d1_id.clone();
workers.push(std::thread::spawn(move || {
worker_start.wait();
let observation = observe_agent_runtime_agent_delegate(
&worker_root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
run_id,
Some(action_id),
&serde_json::json!({
"agentId": "design-director",
"task": "按用户审批修改方案",
"acceptanceCriteria": worker_criteria,
"expectedArtifacts": worker_artifacts,
"repairOfDelegationId": worker_original,
"runId": null
}),
);
(action_id, observation)
}));
}
start.wait();
let results = workers
.into_iter()
.map(|worker| worker.join().expect("join user revision worker"))
.collect::<Vec<_>>();
assert_eq!(
results
.iter()
.filter(|(_, observation)| observation.status == "ok")
.count(),
1,
"并发用户修订必须恰好放行一条:{results:?}"
);
assert!(
results.iter().any(|(_, observation)| {
observation.status == "failed" && observation.summary.contains("最多允许一轮返工")
}),
"被拒的那条必须止于兄弟检查:{results:?}"
);
let persisted = results
.iter()
.filter_map(|(action_id, _)| {
read_static_delegate_delivery_at(
&root,
&agent_runtime_delegation_id(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
run_id,
target_agent_id,
action_id,
),
)
.expect("read competing user revision delivery")
})
.collect::<Vec<_>>();
assert_eq!(persisted.len(), 1, "只能落盘一条用户修订续跑");
assert_eq!(
persisted[0].repair_of_delegation_id.as_deref(),
Some(d1_id.as_str())
);
drop(target_lock);
fs::remove_dir_all(root).ok();
}
@@ -1,5 +1,18 @@
# 决策记录
## 2026-08-14 M1C-0 合入复核:三条 M1C-1 前置、一条文档订正、一条已排除假设
合入 `M1C-0`(见本文件同日条)后对该包做整组复核。**结论:包本身可合,「今天行为零变化」成立且可证**;但新变体在 `M1C-1` 写入之日会同时点亮三个默认值,而这三个默认值都不是裁决出来的,是「给用 `==` 比较(而非穷尽 `match`)的 durable enum 加变体,编译器不报,新变体静默落到作者没想过的那一侧」的结果——与本文件「修复 M1A-2 引入的回归」条同源,是同一失败模式的第二次出现。(复核完成后 `M1B-2` 已合入;其对 `delegation.rs` 的改动是 rustfmt 换行、无语义变化,本条结论不受影响。)
- **不可达性已实证,不是靠注释。** `contract_status` 的生产赋值点只有两处(终态回执构造与 `build_static_delegate_structured_result_at`),都从客观事实派生(终态、缺失产物、verification、问题数),模型选不了;`StaticDelegateClaimRecord` 全仓库**唯一**构造点在 `delegation.rs` 的 claim 准备路径,是运行时构造而非 agent 提交的 JSON,所以「claim payload 自带 `contractStatus`」这条路不存在;durable sidecar 位于 `.agent/runtime/delegation-deliveries/`,被 `is_agent_runtime_private_control_path` 覆盖,`file_ops.rs``project/filesystem.rs` 各三处拒绝写入;前端无 `contractStatus` 消费者。哨兵侧同样成立:`static_delegate_lineage_counters` 的三个 fail-closed 出口(成环、超长、上游缺失)**全部**返回 `(u32::MAX, u32::MAX)`,两个分量都是 MAX,新分支的 `== u32::MAX` 检查全接得住;合法累加上限 31,不会误触。
- **前置一:新变体不进任何 barrier。** `repair_required_count` 的判据是 `== NeedsRepair || (== NeedsUserInput && 有澄清答案)``user_input_required_count` 要求 `== NeedsUserInput``UserRevisionRequested` 两边都不落,因此 `barrier.is_clear()` 可能为真——`M1C-1` 写入方一落地就意味着 **Supervisor run 可以在用户修订尚未派出时完成**。同源的还有 `swarm_cli/terminal_classification.rs``static_delegate_delivery_has_repairable_contract``is_none_or(|r| == NeedsRepair)` → 新变体判为不可返工,影响面小但同样是默认选的)。`M1C-1` 须显式裁决补或不补。
- **前置二:`validate_static_delegate_structured_result` 缺正向一致性分支。** `EvidenceReady` 有(终态/产物/verification 对账),`NeedsUserInput` 有(问题数 + 指纹),`UserRevisionRequested` 只继承了「不得携带 `user_input_questions`」这条否定约束,于是 `UserRevisionRequested + terminal_status="failed" + 缺产物` 能通过校验落盘。`M1C-0` 新增的 real-gate 回归恰好依赖这一点才能构造 fixture。
- **前置三:前向兼容失败的粒度是整个子系统,不是单条记录。** `list_static_delegate_deliveries_at` 对每条 sidecar 用 `?` 上抛,一条解析失败即整个目录枚举失败,barrier、lineage、返工校验、run status 投影同时不可用。`M1C-0` 有意不 bump `STATIC_DELEGATE_DELIVERY_SCHEMA_VERSION` 且把「未知状态 serde 直接报错」记为安全属性——该性质成立,但**粒度当时没写**。注意 bump 版本号救不了:版本号校验排在 serde parse 之后。三条均已写进技术方案第 23.7 节「落地约束」与第 23.8 节 `M1C-1` 行。
- **文档订正:`STATIC_DELEGATE_LINEAGE_MAX_HOPS = 32` 限的是链上节点数,不是跳数。** 判据 `chain.len() >= MAX_HOPS` 排在入链之前,故链最多 32 个节点 = **31 跳**。第 23.7 节原写「真实上限就是 32 跳」,已订正为 31。产品阈值 16 远低于两者,不受影响;`M1C-0` 自己的测试注释(「32 条 delivery(31 跳)」)一直是对的。之所以要订正:`M1C-0` 之前 `depth=1` 才是主刹车,用户修订路径上现在只剩这个哨兵,写错的数字从此是承重的。
- **已排除的假设,后续不要重走。** 怀疑过「派一个子委派 → 抑制 → 再派一个」可绕开兄弟检查、在用户修订父节点下无限扩宽度。打不通:`suppress_static_delegate_delivery_at``ClaimedByParent` 直接原样返回、拒绝抑制,且生产侧唯一调用者是 `suppress_static_delegate_deliveries_for_parent_terminal_at`(父 run 终态清理),彼时同一 `parent_run_id` 已不能再派委派。另确认新分支**没有**跳过兄弟检查:if/else-if 链在前、兄弟检查在后且无提前返回,扇出仍是一条。
- **测试侧本次一并处置。**`static_delegate_user_revision_preserves_existing_clarification_round` 名不副实:计数循环只遍历 `chain[..len-1]`(父节点集合),目标自身 status 从不参与判定,而该用例把 `UserRevisionRequested` 放在了**目标**位置,新分支根本没被执行。已改名为 `static_delegate_user_revision_parent_hop_preserves_depth_and_clarification_round`,补一跳真正以用户修订为父的续跑,并加反证(同一条链只把该父节点改回 `NeedsRepair`,结果必须变成 `(2, 0)`);原断言保留为对照组并注明其性质。② `user_revision_continuation_passes_real_gate_at_depth_one_but_stays_single_child`(原 `static_delegate_user_revision_requested_continuation_...`)补兄弟检查断言。③ 新增 `concurrent_user_revision_dispatch_creates_exactly_one_delivery`,与既有 `project_supervisor_concurrent_repair_dispatch_creates_exactly_one_delivery` 同构但父节点为 `UserRevisionRequested` 且链上 depth 已为 1,两侧同时钉住:并发下恰好放行一条,且不是零条(新分支被删则两条都会被 depth 门拒,用例变红)。
- 教训(与 `M1A-2` 回归条合看):**给用 `==`/`!=` 比较的 durable enum 加变体,等于在每一个比较点上替作者做了一次没人复核的裁决。** 这类 PR 即使「无写入方、行为零变化」也必须逐个枚举比较点并写下每处落点,否则这些默认值会在写入方落地的那个 PR 里一次性生效,而那个 PR 的复核者只会看它自己的 diff。
## 2026-08-14 M1B-2 实现合同收口:Provider binding、结构化注入与提交恢复边界
- **状态与基线**`M1B-1` 已通过门禁并合入,作为 `.agent/planning/` storage 基线;`M1B-2` 工作包已通过本包门禁并以 `27c3eb847` 合入本分支。当前包接 `plan.submit_gdd`、exact planning Provider 请求身份、提交点和恢复;`gdd-approval` planning pending、审批等待、receipt、审批命令与 UI 继续属于 `M1C-1` 及之后。本状态只表示 M1B-2 工作包完成,不表示完整产品可交付。
@@ -1979,17 +1979,18 @@ M0 完成不表示完整策划闭环已经上线。`M1A-1``M1A-4`、`M1B-1`
| 硬边界 | 值 | 性质 |
| --- | --- | --- |
| `STATIC_DELEGATE_LINEAGE_MAX_HOPS` | 32 | 链上推断每次走完整条链,超限 fail closed 返回 `(u32::MAX, u32::MAX)`;防环/防越界兜底,不是可调参数 |
| `STATIC_DELEGATE_LINEAGE_MAX_HOPS` | 32 | 链上推断每次走完整条链,超限 fail closed 返回 `(u32::MAX, u32::MAX)`;防环/防越界兜底,不是可调参数。**该常量限的是链上节点数,不是跳数**:判据 `chain.len() >= MAX_HOPS` 排在入链之前,故链最多 32 个节点 = **31 跳**2026-08-14 订正,此前本节把它写成 32 跳) |
| 单 lineage 版本数(第 8.8 节) | 128 | 版本链只增不改 |
只要修订挂在同一条链上,真实上限就是 **32**。让修订不挂链(`repair_of=None` 另起)可绕开,但会丢失谱系关联并重置澄清预算,等于绕开整套约束,不采纳。产品阈值定为 **16 次修订且为软提示**:远低于机制边界、又高到用户实际碰不到;超过时不拒绝,提示「已修订 16 次,建议退回重做重新梳理需求」——每次修订都是一次完整 Provider 调用加一个新版本,改到十几次仍不满意时问题多半在需求本身,此时应走退回重做(新 lineage、重新问询)而非无限微调。
只要修订挂在同一条链上,真实上限就是 **31**。让修订不挂链(`repair_of=None` 另起)可绕开,但会丢失谱系关联并重置澄清预算,等于绕开整套约束,不采纳。产品阈值定为 **16 次修订且为软提示**:远低于机制边界、又高到用户实际碰不到;超过时不拒绝,提示「已修订 16 次,建议退回重做重新梳理需求」——每次修订都是一次完整 Provider 调用加一个新版本,改到十几次仍不满意时问题多半在需求本身,此时应走退回重做(新 lineage、重新问询)而非无限微调。
**落地约束**
- 拆包(2026-08-13 按第 23.8 节更正):**变体与分类分支在 `M1C-0`**——该 PR 无写入方、是惰性路径、行为零变化,便于把「做游戏链路返工额度未被误放宽」这条关键回归单独证明;**写入方与 receipt 在 `M1C-1` 同进同退**——只置 status 不写 receipt(或反之)会让链路计数与审批事实脱节。这样拆全程不产生半状态:`UserRevisionRequested` 直到 `M1C-1` 才可能被写出。
- `StaticDelegateContractStatus` 是 durable schema 的一部分。新增变体属**前向兼容**问题(旧代码读不了新记录),非后向兼容问题:只有做方案链路会写它,做游戏链路既有记录不受影响。反序列化必须 fail closed,**不得静默降级为 `NeedsRepair`**——那会让用户修订被误计成返工,正是本裁决要消除的行为。
- **2026-08-14 补,`M1C-0` 合入复核)前向兼容失败的粒度是整个子系统,不是单条记录。** `list_static_delegate_deliveries_at` 对每条 sidecar 用 `?` 上抛,一条解析失败即整个目录枚举失败,barrier 计算、lineage 重放、返工校验与 run status 投影同时不可用。因此 `M1C-1` 写出该状态后,旧版本 App(回滚、或多机版本不一致)打开同一项目会**整个静态委派面不可用**。注意 bump `STATIC_DELEGATE_DELIVERY_SCHEMA_VERSION` 救不了这一条:版本号校验排在 serde parse 之后,parse 已先失败——要处置只能改枚举粒度(单条损坏跳过并告警)。**`M1C-1` 上线前必须二选一并记录裁决:给枚举加单条容错,或明确接受回滚锁死项目。**
- 本裁决改的是 master 已发布的静态委派机制,须在 `decision-log.md` 补 dated 记录,写明 `WP1` 的 depth≤1 结论未被推翻。
- 回归必须覆盖:用户修订跳不增 `depth` 也不重置 `round`;连续多次修订均可通过;做游戏链路的返工仍在 `depth=1` 被拒;无该变体的历史记录分类行为不变;32 跳边界仍 fail closed。
- 回归必须覆盖:用户修订跳不增 `depth` 也不重置 `round`;连续多次修订均可通过;做游戏链路的返工仍在 `depth=1` 被拒;无该变体的历史记录分类行为不变;链长边界仍 fail closed;**用户修订父节点下「同一静态委派最多允许一轮返工」的兄弟检查仍然生效(含并发)**——depth 门对用户修订不再适用后,兄弟检查是该路径上唯一剩下的扇出约束
### 23.8 M1 的 PR 结构与合入门禁(2026-08-13
@@ -2004,7 +2005,7 @@ M0 完成不表示完整策划闭环已经上线。`M1A-1``M1A-4`、`M1B-1`
| `M1B-1` | `.agent/planning/` 存储层、strict schema、typed 指纹、版本链;含 `.agent/planning/**` 只挡写判据 | `M1A-2` | **2026-08-14 已通过门禁并合入本分支**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 的预审批投影;M1C-1 接入 receipt 后必须重建 `approved/revise/reject/superseded` 状态。create-only 与等前缀不可变 |
| `M1B-2` | `plan.submit_gdd` 原生工具、exact planning Provider 请求绑定与 GDD 提交点 | `M1B-1` | **工作包已通过本包门禁并以 `27c3eb847` 合入本分支**。实现合同:四类 request kind 全部写 v3 lifecycle、required binding 与同一 dedicated structured-injection user message;只有 `tool-plan` 可生成 sole-submit v4 batch;提交点后只修复 index/Markdown/session successor、终止策划子 run,并保留 generic v5 standalone pending + v4 batch anchors,不创建 `gdd-approval` planning pending/审批等待。定向 Rust、`cargo check --offline`、格式、编码与 diff 门禁均已通过;完整审批链路与产品可交付仍留给后续工作包 |
| `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-1` | `gdd-approval` pending、审批命令、receiptreceipt 写入上述 status | `M1B-2``M1C-0` | 三动作全通;版本+指纹竞态防护;两窗口并发;**连续多次修订均可通过且 `repair_depth` 不变**。**另须先裁决 `M1C-0` 合入复核留下的三条**(详见第 23.7 节「落地约束」与 `decision-log.md` 同名条):① `UserRevisionRequested` 目前不进 `repair_required_count` / `user_input_required_count` 任一 barrier,写入方一落地就意味着 Supervisor run 可在用户修订尚未派出时完成——要么补 barrier,要么明确裁决不补;② `validate_static_delegate_structured_result` 对该变体只有「不得携带 user_input_questions」这条否定约束,没有正向一致性分支,须补齐终态/产物口径;③ 前向兼容失败粒度为整个子系统,须按第 23.7 节二选一 |
| `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 重放幂等不增加轮次;答案绑定冲突被拒 |
| `M1D-1` | 前端 hydrate 与 GDD 审批卡 | `M1C-2b` | 前端只经 `hydrate_game_creator_plan_gdd_state` 读权威状态,不在页面侧合成批准事实 |