修复:非委派美术来源不再误入 game-chat 谱系门
M0A-2 将美术写入边界统一到严格谱系判定后,缺失 Run Profile 绑定从 "视为普通美术 run"(master 语义)变成 Err 失败关闭,且发生在只读工具 豁免之前:scheduler 直启与后台来源的 art run 全部工具被拦,恢复扫描 重执行 canvas.asset_generate 也因此永远发不出外部轮询请求。 在 retry 谱系判定之后、严格谓词之前增加 source 预筛:非 agent-delegate 来源直接判 Unrestricted,交由 autonomous owner 与路径策略管辖; agent-delegate 缺绑定与 agent-delegate-retry 的失败关闭语义不变。 治愈 CI 回归 recovery_scan_resumes_accepted_generation_on_default_worker_stack 与 command_output_read_allows_same_agent_history_and_rejects_cross_agent_action_id, 新增 non_delegated_art_sources_stay_out_of_game_chat_art_gate 钉住契约。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -685,6 +685,53 @@ async fn legacy_game_chat_dynamic_art_retry_runs_are_read_only_and_fail_closed()
|
||||
assert!(!root.join("assets/forged-retry-must-not-write.txt").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_delegated_art_sources_stay_out_of_game_chat_art_gate() {
|
||||
for source in ["agent-ready-task-scheduler", "agent-background-task"] {
|
||||
let temporary = tempfile::tempdir().expect("create non-delegated art root");
|
||||
let root = temporary.path().join("project");
|
||||
init_local_game_project_at(&root, "non-delegated-art", "非委派美术来源")
|
||||
.expect("init non-delegated art project");
|
||||
let run_id = format!("non-delegated-art-{source}");
|
||||
start_game_creator_agent_runtime_task_at(
|
||||
&root,
|
||||
"art-director",
|
||||
"非委派来源不得进入 game-chat 美术谱系门",
|
||||
&run_id,
|
||||
source,
|
||||
"校验工具边界",
|
||||
vec!["非委派来源不受美术门限制".to_string()],
|
||||
)
|
||||
.expect("start non-delegated art runtime");
|
||||
for (tool, input) in [
|
||||
(
|
||||
"command.output_read",
|
||||
serde_json::json!({"actionId":"history-probe","startLine":1,"maxLines":5}),
|
||||
),
|
||||
(
|
||||
"canvas.asset_generate",
|
||||
serde_json::json!({"prompt":"恢复视觉规范图","outputPath":AGENT_RUNTIME_ART_SPEC_PATH}),
|
||||
),
|
||||
(
|
||||
"file.write",
|
||||
serde_json::json!({"path":"game/notes.txt","content":"scoped by other policies"}),
|
||||
),
|
||||
] {
|
||||
assert!(
|
||||
game_chat_delegated_art_agent_input_mutation_block(
|
||||
&root,
|
||||
"art-director",
|
||||
&run_id,
|
||||
tool,
|
||||
&input,
|
||||
)
|
||||
.is_none(),
|
||||
"{source}/{tool} 不应被 game-chat 美术门拦截"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn game_chat_dynamic_art_retry_guard_preserves_dag_art_and_non_art_delegated_retry() {
|
||||
let dag_temporary = tempfile::tempdir().expect("create full DAG art retry root");
|
||||
|
||||
@@ -146,6 +146,14 @@ fn game_chat_delegated_art_agent_mutation_scope_at(
|
||||
if game_chat_dynamic_art_retry_claims_game_chat_lineage_at(root, &task)? {
|
||||
return Ok(GameChatDelegatedArtAgentMutationScope::RetryLineageBlocked);
|
||||
}
|
||||
// Live game-chat dynamic art children only ever run with the
|
||||
// `agent-delegate` source (retry lineage is classified above).
|
||||
// Scheduler-seeded DAG tasks and plain background runs must not reach
|
||||
// the strict predicate: it fails closed on a missing run-profile
|
||||
// binding and would block even their read-only tools.
|
||||
if task.source != "agent-delegate" {
|
||||
return Ok(GameChatDelegatedArtAgentMutationScope::Unrestricted);
|
||||
}
|
||||
}
|
||||
// `assets/**` is a broad write scope. Grant it only after the same
|
||||
// canonical-output authorization used by Canvas replacement has verified
|
||||
|
||||
Reference in New Issue
Block a user