审查发现2:验收前置门先识别 Fast GDD 再走绑定父链
ensure_plan_gdd_approval_pending_after_acceptance_locked 先用会遍历并校验整条
祖先链的 read_..._run_profile_binding 读绑定,再检查 binding.source 是否为
project-supervisor-plan。顺序反了:任何祖先绑定的毛病都抢先变成 Err,连「这根本
不是策划根」都来不及说,于是别人的坏链变成了这道门的失败。
这正是 P4(cb5af31e7)在同文件 plan_root_completion_identity_at 修过的形状,当时
漏了这个姊妹函数。而它比完成门更容易被踩到:agent.run_status 每次 status
observation 都会重跑本门(见 runtime_tools/run_status.rs 的
「Re-run the locked gate on every plan-root status observation」),那条路径上没有
任何上游守卫先把带父链的 run 挡掉,适用性完全交给门自己判。
- 识别改用 _once,只看 run 自己那条绑定记录;确认是策划根之后才走完整父链,
严格度一点没降,只是不再作用到别人身上
- 回归 broken_ancestor_binding_must_not_fail_the_acceptance_gate_for_a_non_plan_supervisor_run:
复用 P4 的构造(supervisor 的 isolated-join 子 run,删掉父绑定),断言门只能判
NotApplicable。变异验证:换回链走优先后该用例报出
Err("Agent Runtime Run Profile 父绑定缺失")
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+73
-1
@@ -302,8 +302,14 @@ pub(crate) fn ensure_plan_gdd_approval_pending_after_acceptance_locked(
|
||||
if agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID || run_id.trim().is_empty() {
|
||||
return Ok(PlanGddAcceptanceGateOutcome::NotApplicable);
|
||||
}
|
||||
// 先识别、后校验。`read_..._binding`(不带 `_once`)会遍历并校验整条祖先链,
|
||||
// 把它排在识别前面,等于让任何祖先绑定的毛病都抢先变成 Err——连「这根本不是
|
||||
// 策划根」都来不及说,于是别人的坏链变成了这道门的失败。`agent.run_status`
|
||||
// 每次都会重跑本门,带父链的非策划 supervisor run 因此会被误伤。P4
|
||||
// (`cb5af31e7`)已在同文件的 `plan_root_completion_identity_at` 修过同一形状,
|
||||
// 当时漏了这个姊妹函数。识别只看 run 自己那条绑定记录。
|
||||
let Some(binding) =
|
||||
read_game_creator_agent_runtime_run_profile_binding(root, agent_id, run_id)?
|
||||
read_game_creator_agent_runtime_run_profile_binding_once(root, agent_id, run_id.trim())?
|
||||
else {
|
||||
return Ok(PlanGddAcceptanceGateOutcome::NotApplicable);
|
||||
};
|
||||
@@ -313,6 +319,8 @@ pub(crate) fn ensure_plan_gdd_approval_pending_after_acceptance_locked(
|
||||
if binding.profile != AGENT_RUNTIME_RUN_PROFILE_STANDARD {
|
||||
return Err("Fast GDD acceptance gate 的 plan 根 Run Profile 已漂移".to_string());
|
||||
}
|
||||
// 确认是策划根之后才走完整父链:严格度一点没降,只是不再作用到别人身上。
|
||||
read_game_creator_agent_runtime_run_profile_binding(root, agent_id, run_id)?;
|
||||
let gdds = read_plan_gdd_chain_locked(root).map_err(|error| error.to_string())?;
|
||||
let Some(gdd) = latest_plan_gdd_for_root(&gdds, run_id) else {
|
||||
// A contract may be updated before the planning child has submitted a
|
||||
@@ -2167,4 +2175,68 @@ mod tests {
|
||||
);
|
||||
let _ = fs::remove_dir_all(root);
|
||||
}
|
||||
|
||||
/// 同一形状的第二处:验收前置门。它比完成门更容易被踩到——`agent.run_status`
|
||||
/// 每次 status observation 都会重跑这道门(见 `runtime_tools/run_status.rs` 的
|
||||
/// 「Re-run the locked gate on every plan-root status observation」),而那条路径
|
||||
/// 上没有任何上游守卫先把带父链的 run 挡掉,适用性完全交给门自己判。所以门必须
|
||||
/// 先说得出「这不是策划根」,才轮到校验祖先链。
|
||||
#[test]
|
||||
fn broken_ancestor_binding_must_not_fail_the_acceptance_gate_for_a_non_plan_supervisor_run() {
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"genarrative-plan-acceptance-gate-identity-{}",
|
||||
uuid::Uuid::new_v4().simple()
|
||||
));
|
||||
init_local_game_project_at(
|
||||
&root,
|
||||
"project-test-acceptance-gate",
|
||||
"acceptance gate identity",
|
||||
)
|
||||
.expect("init project");
|
||||
|
||||
let parent_run_id = "acceptance-gate-isolated-join-parent";
|
||||
let child_run_id = "acceptance-gate-isolated-join-child";
|
||||
bind_game_creator_agent_runtime_run_profile_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
parent_run_id,
|
||||
AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE,
|
||||
Some(AGENT_RUNTIME_RUN_PROFILE_STANDARD),
|
||||
None,
|
||||
)
|
||||
.expect("bind ancestor run");
|
||||
bind_game_creator_agent_runtime_run_profile_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
child_run_id,
|
||||
AGENT_RUNTIME_ISOLATED_JOIN_SOURCE,
|
||||
Some(AGENT_RUNTIME_RUN_PROFILE_STANDARD),
|
||||
Some(&AgentRuntimeTaskLink {
|
||||
parent_agent_id: Some(GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID.to_string()),
|
||||
parent_run_id: Some(parent_run_id.to_string()),
|
||||
delegation_id: None,
|
||||
}),
|
||||
)
|
||||
.expect("bind isolated-join child run");
|
||||
|
||||
fs::remove_file(game_creator_agent_runtime_run_profile_binding_path(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
parent_run_id,
|
||||
))
|
||||
.expect("remove ancestor binding");
|
||||
|
||||
let outcome = ensure_plan_gdd_approval_pending_after_acceptance_locked(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
child_run_id,
|
||||
);
|
||||
assert!(
|
||||
matches!(outcome, Ok(PlanGddAcceptanceGateOutcome::NotApplicable)),
|
||||
"isolated-join run 的 source 不是立项策划,验收前置门只能判 NotApplicable,\
|
||||
不能因为别人的祖先链坏掉就让整个 agent.run_status 失败:{:?}",
|
||||
outcome.map(|outcome| format!("{outcome:?}"))
|
||||
);
|
||||
let _ = fs::remove_dir_all(root);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user