修复M0A-2给做游戏路径引入的两处收束死路

design-foundation 在非 scheduler 路径上恢复 command.run_limited。内部产物验证只在
agent-ready-task-scheduler + Supervisor parent + gui/cli 根的可信 DAG 上发放,而
policy.rs 按 agent_id 单判就拒掉手动验证,委派路径两头落空、无从收束;master 的
白名单含该工具且自身用例断言不拦。判据只对 design-foundation 读绑定,其余三个固定
owner 的委派路径未经核实,维持现状不一并放宽。

game-chat 美术回执去掉本人 canvas.asset_generate 证据等三条必要条件。提示词要求
已有有效同路径资产时不得重复生成,而缺口判据只看非返工那一轮,合法的幂等复用会被
判成尚未交付。

测试恢复 master 断言并删去分支追加的钉桩段,未新增用例。policy 143、
design_foundation 6 全通过,cargo fmt --check 干净。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 06:30:43 +00:00
parent 6d5afd9f6d
commit e0dc87b21c
4 changed files with 71 additions and 46 deletions
@@ -166,6 +166,13 @@ pub(crate) fn agent_runtime_autonomous_design_foundation_command_is_allowed(
| "file.delete"
| "project.patchset"
| "task.list"
// master 允许它,靠它跑 game.static_smoke 完成手动验证。分支把它删掉的前提是
// 「Runtime 内部产物验证」顶上,但那道兜底只在
// `autonomous_owner_artifact_validation_available_for_run_at` 为真时才发放。
// 删了它,非 scheduler 路径(如被 agent.delegate 另行委派的 design-foundation)
// 手动验证被拒、内部验证又拿不到,收束无路可走。留在这里,由上面那道按 run
// 身份判定的门禁在 scheduler 路径上单独摘除。
| "command.run_limited"
| "image.inspect"
| "canvas.asset_generate"
| "agent.audit"
@@ -116,7 +116,28 @@ pub(crate) fn game_creator_agent_runtime_tool_policy_rule_for_run(
Ok(identity) => identity,
Err(error) => return Some(AgentRuntimeToolPolicyBlock::Denied(error)),
};
// 只有 design-foundation 这一条经审查确认:master 明确允许它在委派路径上用
// `command.run_limited` 跑 `game.static_smoke`(master 自身用例断言 `is_none`),
// 而兜底的「Runtime 内部产物验证」只在
// `autonomous_owner_artifact_validation_available_for_run_at` 为真时才发放
// (要求 source 是 agent-ready-task-scheduler、parent 是 Supervisor、根来自 gui/cli)。
// 两头一堵,委派路径上的 design-foundation 就无从收束。
//
// 其余三个固定 owner 的委派路径是否合法尚未核实,维持现状不一并放宽;
// 判据也只对 design-foundation 读一次绑定,不给其它 agent 的每次策略判定加磁盘读。
let manual_verification_fallback_required = if run_profile
== AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
&& agent_id == "design-foundation"
{
match autonomous_owner_artifact_validation_available_for_run_at(root, agent_id, run_id) {
Ok(available) => !available,
Err(error) => return Some(AgentRuntimeToolPolicyBlock::Denied(error)),
}
} else {
false
};
if run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
&& !manual_verification_fallback_required
&& autonomous_owner_manual_verification_command_is_denied(agent_id, command_id)
{
return Some(AgentRuntimeToolPolicyBlock::Denied(format!(
@@ -735,7 +756,6 @@ mod tests {
("command.poll", "command.poll"),
("command.stdin", "command.stdin"),
("command.terminate", "command.terminate"),
("command.run_limited", "command.run_limited"),
("preview.start", "preview.start"),
("preview.validate", "preview.validate"),
("agent.message", "conversation.write"),
@@ -786,7 +806,7 @@ mod tests {
}
#[test]
fn autonomous_fixed_owners_deny_manual_verification_without_trusted_dag_fallback() {
fn autonomous_fixed_owners_manual_verification_without_trusted_dag_fallback() {
let temporary = tempfile::tempdir().expect("create owner verification policy root");
let root = temporary.path().join("project");
init_local_game_project_at(
@@ -819,21 +839,41 @@ mod tests {
&binding.run_id,
)
.expect("evaluate deliberately non-scheduler owner binding"));
// 只有 design-foundation 经审查确认需要保留手动验证出路(master 允许它用
// command.run_limited 跑 smoke),且仅限 command.run_limited——project.verify
// 在 master 的 design-foundation 角色白名单里本就没有。
// 其余三个 owner 未经核实,维持本分支的拒绝现状。
for command_id in ["project.verify", "command.run_limited"] {
assert!(matches!(
game_creator_agent_runtime_tool_policy_rule_for_run(
&root,
agent_id,
&binding.run_id,
Some(&binding.profile),
Some(&binding.binding_fingerprint),
command_id,
),
Some(AgentRuntimeToolPolicyBlock::Denied(reason))
if reason.contains("固定 owner")
&& reason.contains(agent_id)
&& reason.contains(command_id)
));
let rule = game_creator_agent_runtime_tool_policy_rule_for_run(
&root,
agent_id,
&binding.run_id,
Some(&binding.profile),
Some(&binding.binding_fingerprint),
command_id,
);
if agent_id == "design-foundation" {
if command_id == "command.run_limited" {
assert!(
rule.is_none(),
"design-foundation 在非 scheduler 路径必须保留 command.run_limited 出路"
);
} else {
assert!(matches!(
rule,
Some(AgentRuntimeToolPolicyBlock::Denied(reason))
if reason.contains("design-foundation")
));
}
} else {
assert!(matches!(
rule,
Some(AgentRuntimeToolPolicyBlock::Denied(reason))
if reason.contains("固定 owner")
&& reason.contains(agent_id)
&& reason.contains(command_id)
));
}
}
assert!(matches!(
@@ -1189,16 +1189,10 @@ pub(crate) fn game_chat_art_delivery_gap_at(
|| delivery.structured_result.as_ref().is_none_or(|result| {
result.contract_status != StaticDelegateContractStatus::EvidenceReady
|| !result.missing_expected_artifacts.is_empty()
|| !result.verification_required
|| result.verified_revision.is_none()
|| !result
.evidence
.iter()
.any(|evidence| evidence.kind == "canvas.asset_generate")
})
{
return Ok(Some(format!(
"target={target_agent_id} delivery={} 尚未形成本人 canvas.asset_generate 的 EvidenceReady 美术回执",
"target={target_agent_id} delivery={} 尚未形成 EvidenceReady 美术回执",
delivery.delegation_id
)));
}
@@ -2087,33 +2087,17 @@ async fn autonomous_design_foundation_denies_indirect_execution_before_side_effe
let agent_db = fs::read_to_string(root.join(".agent/agent.db")).expect("agent db");
assert!(!agent_db.contains("agent.runtime.project.verify"));
assert!(!agent_db.contains("agent.runtime.mcp.call"));
assert!(matches!(
game_creator_agent_runtime_tool_policy_rule_for_run(
&root,
"design-foundation",
run_id,
Some(&binding.profile),
Some(&binding.binding_fingerprint),
"command.run_limited",
),
Some(AgentRuntimeToolPolicyBlock::Denied(_))
));
let smoke = execute_game_creator_agent_runtime_tool_action(
// master 断言:委派路径的 design-foundation 仍可用 command.run_limited 跑手动验证。
// 内部产物验证只在 scheduler 路径发放,这里删掉这条出路会让该 run 无从收束。
assert!(game_creator_agent_runtime_tool_policy_rule_for_run(
&root,
"design-foundation",
run_id,
"不得借游戏 smoke 验证策划文档",
&AgentRuntimeToolAction {
tool: "command.run_limited".to_string(),
reason: Some("验证角色门禁先于静态检查".to_string()),
input: serde_json::json!({ "commandId": "game.static_smoke" }),
},
Some(&binding.profile),
Some(&binding.binding_fingerprint),
"command.run_limited",
)
.await;
assert_eq!(smoke.status, "blocked");
assert!(smoke.summary.contains("design-foundation"));
assert!(!root.join(".agent/logs/command.log").exists());
.is_none());
fs::remove_dir_all(root).ok();
}