修复 GDD 修订后的审批取证链路
新增 plan Supervisor 的 AwaitingAcceptanceEvidence 阶段与无副作用 durable 状态判定 在证据不足时收窄工具面,阻止重复 agent.delegate 并保留 file.read、acceptance_update、run_status 补充一条阶段工具面回归及项目排障记录
This commit is contained in:
+14
-5
@@ -158,6 +158,9 @@ pub(crate) enum PlanRootSupervisorStage {
|
||||
GoalContract,
|
||||
/// 合同已冻结但本根 run 还没有任何委派:唯一能推进的动作是派出策划子 Agent。
|
||||
Delegate,
|
||||
/// 最新 GDD 已提交但尚未完成当前根 Run 的 Acceptance Graph 取证:只能读取
|
||||
/// `game/fast_gdd.md`、更新验收图或重放状态,不能抢先创建重复策划 delivery。
|
||||
AwaitingAcceptanceEvidence,
|
||||
/// 已有委派:取证、返工与审批相关工具全部开放。
|
||||
Delegated,
|
||||
}
|
||||
@@ -168,6 +171,9 @@ pub(crate) fn agent_runtime_plan_root_supervisor_tools_for_stage(
|
||||
match stage {
|
||||
PlanRootSupervisorStage::GoalContract => &["agent.goal_contract"],
|
||||
PlanRootSupervisorStage::Delegate => &["agent.delegate"],
|
||||
PlanRootSupervisorStage::AwaitingAcceptanceEvidence => {
|
||||
&["file.read", "agent.acceptance_update", "agent.run_status"]
|
||||
}
|
||||
// 合同已冻结且不可重写,再广告 agent.goal_contract 只会诱导一次必被拒的调用。
|
||||
PlanRootSupervisorStage::Delegated => &[
|
||||
"file.read",
|
||||
@@ -194,6 +200,7 @@ mod plan_root_stage_tests {
|
||||
let union = [
|
||||
PlanRootSupervisorStage::GoalContract,
|
||||
PlanRootSupervisorStage::Delegate,
|
||||
PlanRootSupervisorStage::AwaitingAcceptanceEvidence,
|
||||
PlanRootSupervisorStage::Delegated,
|
||||
]
|
||||
.into_iter()
|
||||
@@ -234,11 +241,13 @@ pub(crate) fn plan_root_supervisor_stage_at(
|
||||
let delegated = list_static_delegate_deliveries_at(root)?
|
||||
.into_iter()
|
||||
.any(|delivery| delivery.parent_agent_id == agent_id && delivery.parent_run_id == run_id);
|
||||
Ok(if delegated {
|
||||
PlanRootSupervisorStage::Delegated
|
||||
} else {
|
||||
PlanRootSupervisorStage::Delegate
|
||||
})
|
||||
if !delegated {
|
||||
return Ok(PlanRootSupervisorStage::Delegate);
|
||||
}
|
||||
if plan_root_supervisor_acceptance_evidence_required_at(root, agent_id, run_id)? {
|
||||
return Ok(PlanRootSupervisorStage::AwaitingAcceptanceEvidence);
|
||||
}
|
||||
Ok(PlanRootSupervisorStage::Delegated)
|
||||
}
|
||||
|
||||
pub(crate) fn agent_runtime_native_executable_tools() -> Vec<&'static str> {
|
||||
|
||||
@@ -338,6 +338,93 @@ fn latest_plan_gdd_for_root<'a>(gdds: &'a [PlanGddV1], root_run_id: &str) -> Opt
|
||||
})
|
||||
}
|
||||
|
||||
/// Return whether the plan-root Supervisor must collect the current GDD
|
||||
/// acceptance evidence before it can dispatch another planning child.
|
||||
///
|
||||
/// This is deliberately a read-only projection of the existing acceptance
|
||||
/// gate. It does not create approval pending or mutate any planning sidecar;
|
||||
/// the actual pending projection remains owned by
|
||||
/// `ensure_plan_gdd_approval_pending_after_acceptance_locked` after a successful
|
||||
/// `agent.acceptance_update`.
|
||||
pub(crate) fn plan_root_supervisor_acceptance_evidence_required_at(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
run_id: &str,
|
||||
) -> Result<bool, String> {
|
||||
if !crate::config::game_creator_planning_capability_enabled()? {
|
||||
return Ok(false);
|
||||
}
|
||||
if agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID || run_id.trim().is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
|
||||
root,
|
||||
"planning.supervisor-stage",
|
||||
)
|
||||
.map_err(|error| format!("取得 plan Supervisor 阶段判定项目锁失败:{error}"))?;
|
||||
plan_root_supervisor_acceptance_evidence_required_locked(root, run_id.trim())
|
||||
}
|
||||
|
||||
fn plan_root_supervisor_acceptance_evidence_required_locked(
|
||||
root: &Path,
|
||||
run_id: &str,
|
||||
) -> Result<bool, String> {
|
||||
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 {
|
||||
return Ok(false);
|
||||
};
|
||||
let Some(global_latest) = gdds.last() else {
|
||||
return Ok(false);
|
||||
};
|
||||
if global_latest.gdd_id != gdd.gdd_id
|
||||
|| global_latest.version != gdd.version
|
||||
|| global_latest.fingerprint != gdd.fingerprint
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
validate_plan_gdd(gdd).map_err(|error| error.to_string())?;
|
||||
|
||||
let approvals = read_plan_gdd_approvals_locked(root).map_err(|error| error.to_string())?;
|
||||
validate_plan_gdd_approvals_against_gdds(&gdds, &approvals)
|
||||
.map_err(|error| error.to_string())?;
|
||||
if read_plan_gdd_approval_for_version_locked(root, gdd.version)
|
||||
.map_err(|error| error.to_string())?
|
||||
.is_some()
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
if let Some(pending) =
|
||||
read_plan_gdd_approval_pending_locked(root).map_err(|error| error.to_string())?
|
||||
{
|
||||
if !pending_matches_gdd(&pending, gdd) {
|
||||
return Err("plan Supervisor 阶段判定发现 approval pending identity 冲突".to_string());
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let Some(session) =
|
||||
read_plan_session_with_recovery_locked(root).map_err(|error| error.to_string())?
|
||||
else {
|
||||
return Ok(false);
|
||||
};
|
||||
if !plan_gdd_session_matches_submission(&session, gdd) {
|
||||
return Ok(false);
|
||||
}
|
||||
let Some(delivery) = read_static_delegate_delivery_at(root, &gdd.delegation_id)? else {
|
||||
return Ok(false);
|
||||
};
|
||||
if delivery.status != StaticDelegateDeliveryStatus::ClaimedByParent
|
||||
|| delivery.terminal_status.as_deref() != Some("completed")
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(matches!(
|
||||
plan_fast_gdd_acceptance_status_at_locked(root, gdd)?,
|
||||
PlanFastGddAcceptanceStatus::NeedsEvidence
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_plan_gdd_approval_pending_after_acceptance_locked(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
|
||||
@@ -4721,6 +4721,29 @@ mod tests {
|
||||
cleanup_fixture(root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn m1c2a_unapproved_gdd_requires_acceptance_evidence_before_delegate() {
|
||||
let (root, _gdd, root_runtime) = acceptance_gate_fixture(true);
|
||||
|
||||
assert_eq!(
|
||||
plan_root_supervisor_stage_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
&root_runtime.run_id,
|
||||
)
|
||||
.expect("classify plan root stage"),
|
||||
PlanRootSupervisorStage::AwaitingAcceptanceEvidence
|
||||
);
|
||||
assert_eq!(
|
||||
agent_runtime_plan_root_supervisor_tools_for_stage(
|
||||
PlanRootSupervisorStage::AwaitingAcceptanceEvidence
|
||||
),
|
||||
&["file.read", "agent.acceptance_update", "agent.run_status"]
|
||||
);
|
||||
|
||||
cleanup_fixture(root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn m1c2a_failed_acceptance_requires_claim_before_repair_dispatch() {
|
||||
let (root, gdd, root_runtime) = acceptance_gate_fixture(false);
|
||||
|
||||
@@ -2256,6 +2256,7 @@ mod tests {
|
||||
for stage in [
|
||||
PlanRootSupervisorStage::GoalContract,
|
||||
PlanRootSupervisorStage::Delegate,
|
||||
PlanRootSupervisorStage::AwaitingAcceptanceEvidence,
|
||||
PlanRootSupervisorStage::Delegated,
|
||||
] {
|
||||
let mut staged = functions.clone();
|
||||
@@ -2290,6 +2291,7 @@ mod tests {
|
||||
for stage in [
|
||||
PlanRootSupervisorStage::GoalContract,
|
||||
PlanRootSupervisorStage::Delegate,
|
||||
PlanRootSupervisorStage::AwaitingAcceptanceEvidence,
|
||||
PlanRootSupervisorStage::Delegated,
|
||||
] {
|
||||
let mut staged = functions.clone();
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# 决策记录
|
||||
|
||||
## 2026-08-26 Fast GDD 修订后先取证再允许再次委派
|
||||
|
||||
- **现象**:GDD v1 经用户选择“修改”后,策划子 Agent 正确提交 v2,但 plan 根 Supervisor 的 `Delegated` 阶段仍同时广告 `agent.delegate` 与审批前置工具;模型可能在 Acceptance Graph 重新取证前重复创建修订 delivery,随后被 `PLAN_PROVIDER_USAGE_DEFERRED` 拦停。
|
||||
- **决策**:plan 根阶段增加轻量的 `AwaitingAcceptanceEvidence` 状态。当前根最新 GDD 无 approval receipt/pending、session `latestSubmittedRef` 精确指向该提交、delivery 已由根认领且 Acceptance Graph 返回 `NeedsEvidence` 时,只广告 `file.read`、`agent.acceptance_update`、`agent.run_status`;只有用户真正对最新审批卡选择修改/退回后,才恢复 `agent.delegate`。
|
||||
- **边界**:不放宽 Provider usage 门禁,不重构 delegation/repair lineage,不自动生成证据或审批 pending;审批 pending 仍只由既有 acceptance gate 在 `agent.acceptance_update` 成功后创建。
|
||||
- **验证**:新增一条阶段工具面回归,并通过 15 条 M1C-2a acceptance gate 定向测试、plan root 原生工具目录测试、`cargo check --all-targets`、格式与 diff 检查。
|
||||
|
||||
## 2026-08-24 AGC Direct 媒体能力只通过客户端语义工具开放
|
||||
|
||||
- 背景:资源页已经补齐视频、角色动画、音效和背景音乐的 create/derive 能力,但 Direct Codex 只能准备标准美术包,无法查询已登记源资源或表达新增媒体意图。直接开放 Tauri invoke 会把项目路径、revision、operation、幂等键、登录态和事务权力交给模型。
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# 踩坑与排障记录
|
||||
|
||||
## 2026-08-26 GDD 新版本提交后不能沿用“已有委派”工具面
|
||||
|
||||
- **现象**:`plan_root_supervisor_stage_at` 只按是否存在 delivery 判定 `Delegated`。用户修订产生的新 GDD 仍未完成当前根 Run 的 `file.read → agent.acceptance_update` 取证时,模型会看到 `agent.delegate`,可能重复派发同一条策划链。
|
||||
- **原因**:自然语言 playbook 已规定“证据不足先取证、用户修改后才返工”,但阶段工具白名单没有把这条 durable 状态固化。
|
||||
- **处理**:阶段判定复用现有 acceptance gate 的 GDD/session/delivery/graph identity 检查,增加无副作用的 `AwaitingAcceptanceEvidence` 阶段;`PLAN_PROVIDER_USAGE_DEFERRED` 保持 fail-closed,不通过放宽 Provider 使用量门禁解决。
|
||||
- **排查顺序**:先看最新 `gdd.vN.json`、`session.latestSubmittedRef`、delivery 是否 `ClaimedByParent`,再看 Acceptance Graph 是否 `NeedsEvidence`;若仍可见 `agent.delegate`,优先检查 plan root 阶段快照,而不是修改 acceptance gate 或 Provider 门禁。
|
||||
|
||||
## 2026-08-15 把校验往链路前面挪,改的不是严格程度而是作用域
|
||||
|
||||
- 现象:CI 全量 5 条失败,看上去毫不相干(两条 Goal 续跑停在 `needs-reconciliation`、一条交接用例断言错误文案、一条恢复用例把不可读 state 的错误抛了出来、一条 Linux-only 用例错误码对不上),实际只有 3 个根因,且三者是**同一个形状**:新增或既有的检查被放在了链路更靠前的位置,于是它的语义作用域被悄悄放大或提前,而不是「变严」。
|
||||
|
||||
Reference in New Issue
Block a user