修复:非委派美术来源不再误入 game-chat 谱系门
Project CI / Repository checks (pull_request) Successful in 1m6s
Project CI / Frontend tests (pull_request) Successful in 3m5s
Project CI / Backend tests (pull_request) Successful in 4m5s
Project CI / Native shell tests (pull_request) Successful in 13m56s

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:
2026-08-11 13:43:35 +00:00
parent d22cf19cb0
commit 133cd1fa3b
2 changed files with 55 additions and 0 deletions
@@ -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