补齐 M1C-1 漏掉的 agent_db 守恒断言,并让审批投影恢复区分瞬时错误
M1C-1 给 agent_db 新增了 planning 决策预留车道并从普通追加额度里扣除,但 ordinary_append_soft_limit_preserves_action_receipt_record_slots 仍断言两车道的守恒 律,差额恰好是新车道的 2_097_280 字节;记录额度同样少算 128 条。master 没有这个常量, 所以 master CI 绿而本分支必红。断言补到三车道,记录额度改为 999_680。 审批投影恢复的收敛此前不分错误性质:`.agent/project.lock` 正被占用这类瞬时错误也会 把策划根 run 永久标成 needs-reconciliation——比收敛出现之前的强传播更糟。现在先过 static_delegate_parent_wake_error_is_transient(复用委派唤醒的既有判据,避免两处分类 漂移),瞬时错误照旧上抛,由调用方转成 recovery_pending 下一轮重试;只有持久不一致 才收敛到那个 run。新增回归覆盖这条分支。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -822,9 +822,17 @@ fn durable_process_session_recovery_exists_at(root: &Path) -> bool {
|
||||
/// Fast GDD approval 投影恢复失败时,把 fail-closed 收敛到受影响的那个 run。
|
||||
///
|
||||
/// 返回 `Ok(true)` 表示已经把策划根 Supervisor 标成 needs-reconciliation,调用方可以
|
||||
/// 继续扫描其余 Agent;`Ok(false)` 表示当前没有可归属的 run,无法精确收敛,调用方必须
|
||||
/// 把原错误照旧上抛,保持全局 fail-closed。
|
||||
/// 继续扫描其余 Agent;`Ok(false)` 表示不该、或无法精确收敛,调用方必须把原错误照旧
|
||||
/// 上抛,保持全局 fail-closed。
|
||||
fn contain_plan_gdd_approval_recovery_failure_at(root: &Path, error: &str) -> Result<bool, String> {
|
||||
// 瞬时错误绝不能收敛成 needs-reconciliation。最常见的就是 `.agent/project.lock`
|
||||
// 正被另一个写操作占用——什么都没坏,下一轮扫描重试即可;把它标成人工核对等于
|
||||
// 用一次转瞬即逝的锁争用永久停掉策划根 run,比原来的强传播更糟。这里照旧上抛,
|
||||
// 调用方把它变成 recovery_pending 并在下一轮重试,与本函数出现之前的行为一致。
|
||||
// 判据复用委派唤醒那条既有的瞬时特征串,避免两处各写一套导致分类漂移。
|
||||
if static_delegate_parent_wake_error_is_transient(error) {
|
||||
return Ok(false);
|
||||
}
|
||||
let Some(_runtime_lock) = try_acquire_game_creator_agent_runtime_task_lock(
|
||||
root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
@@ -1967,4 +1975,54 @@ mod plan_gdd_approval_wait_recovery_tests {
|
||||
.count();
|
||||
assert_eq!(audit_count, 1);
|
||||
}
|
||||
|
||||
/// 瞬时错误不能被收敛成 needs-reconciliation。
|
||||
///
|
||||
/// `.agent/project.lock` 正被另一个写操作占用时什么都没坏,下一轮扫描重试即可;
|
||||
/// 把它标成人工核对,等于用一次转瞬即逝的锁争用永久停掉策划根 run——那比这条
|
||||
/// 收敛出现之前的强传播还糟。此时必须照旧上抛,由调用方转成 recovery_pending。
|
||||
#[test]
|
||||
fn transient_plan_gdd_approval_recovery_failure_is_retried_instead_of_reconciled() {
|
||||
let temporary =
|
||||
crate::tests::canonical_test_tempdir("plan-gdd-approval-recovery-transient-");
|
||||
let root = temporary.path();
|
||||
init_local_game_project_at(root, "plan-gdd-approval-transient", "审批投影恢复瞬时失败")
|
||||
.expect("init project");
|
||||
let runtime = start_game_creator_agent_runtime_task_at(
|
||||
root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
"推进立项策划",
|
||||
"plan-gdd-approval-transient-run",
|
||||
"agent-ready-task-scheduler",
|
||||
"准备推进立项策划",
|
||||
vec!["推进立项策划".to_string()],
|
||||
)
|
||||
.expect("start plan-root supervisor runtime");
|
||||
|
||||
let held = acquire_project_write_lock(root, "test.hold-project-write-lock")
|
||||
.expect("hold the project write lock");
|
||||
let error = resume_game_creator_agent_background_tasks_at(root)
|
||||
.expect_err("锁争用属于瞬时错误,必须照旧上抛而不是就地收敛");
|
||||
assert!(
|
||||
error.contains("恢复 GDD approval 投影失败"),
|
||||
"unexpected error: {error}"
|
||||
);
|
||||
drop(held);
|
||||
|
||||
let contained = read_game_creator_agent_runtime_at(root, &runtime.agent_id)
|
||||
.expect("read supervisor runtime after transient failure");
|
||||
assert_ne!(
|
||||
contained.state.phase, "needs-reconciliation",
|
||||
"瞬时锁争用不得把策划根 run 永久停掉"
|
||||
);
|
||||
assert!(contained.state.error.is_none());
|
||||
assert!(!read_agent_db_records_bounded(root, 1024 * 1024)
|
||||
.expect("read approval recovery audit")
|
||||
.0
|
||||
.iter()
|
||||
.any(
|
||||
|record| record.get("recordType").and_then(|value| value.as_str())
|
||||
== Some("agent.runtime.plan.gdd.approval_recovery.needs_reconciliation")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2465,13 +2465,19 @@ fn agent_db_capacity_reserves_terminal_receipt_space_without_rotation() {
|
||||
|
||||
#[test]
|
||||
fn ordinary_append_soft_limit_preserves_action_receipt_record_slots() {
|
||||
// 守恒律必须覆盖全部预留车道。M1C-1 新增了 planning 决策车道
|
||||
// (`AGENT_DB_PLAN_GDD_DECISION_RESERVE_BYTES`,(16 KiB+1)×128)并从
|
||||
// `AGENT_DB_MAX_ORDINARY_APPEND_BYTES` 里扣掉,这条断言却还停在两车道,
|
||||
// 差额恰好是新车道的 2_097_280 字节。少一条车道就意味着这条断言不再能证明
|
||||
// 「普通追加不会吃掉任何终态预留」——它才是这个测试存在的理由。
|
||||
assert_eq!(
|
||||
AGENT_DB_MAX_ORDINARY_APPEND_BYTES
|
||||
+ AGENT_DB_LIFECYCLE_TERMINAL_RESERVE_BYTES
|
||||
+ AGENT_DB_TERMINAL_RESERVE_BYTES,
|
||||
+ AGENT_DB_TERMINAL_RESERVE_BYTES
|
||||
+ AGENT_DB_PLAN_GDD_DECISION_RESERVE_BYTES,
|
||||
AGENT_DB_MAX_ACTION_RECEIPT_SCAN_BYTES
|
||||
);
|
||||
assert_eq!(AGENT_DB_MAX_ORDINARY_APPEND_RECORDS, 999_808);
|
||||
assert_eq!(AGENT_DB_MAX_ORDINARY_APPEND_RECORDS, 999_680);
|
||||
assert_eq!(
|
||||
AGENT_DB_MAX_SCAN_RECORDS
|
||||
- usize::try_from(AGENT_DB_TERMINAL_RESERVE_RECORDS)
|
||||
|
||||
Reference in New Issue
Block a user