修复:封闭 game-chat 美术重试写入边界
在 successor durable 写入前类型化拒绝动态美术 child 通用 retry 将遗留 retry-source 美术 run 收敛为只读并阻断全部 mutation 补齐终态零副作用、工具矩阵及 DAG/非美术 retry 非回归测试 同步技术方案、决策记录与踩坑说明
This commit is contained in:
@@ -772,6 +772,23 @@ pub(crate) fn resolve_game_creator_agent_runtime_retry_configuration_at(
|
||||
Ok((run_profile, source))
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(in crate::agent) enum AgentRuntimeRetryErrorKind {
|
||||
GameChatDynamicArtRetryUnsupported,
|
||||
}
|
||||
|
||||
impl AgentRuntimeRetryErrorKind {
|
||||
pub(in crate::agent) const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::GameChatDynamicArtRetryUnsupported => "game-chat-dynamic-art-retry-unsupported",
|
||||
}
|
||||
}
|
||||
|
||||
fn wire_error(self, message: &str) -> String {
|
||||
format!("kind={} {message}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn retry_game_creator_agent_runtime_task_at(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
@@ -787,6 +804,13 @@ pub(crate) fn retry_game_creator_agent_runtime_task_at(
|
||||
let task =
|
||||
read_latest_game_creator_agent_runtime_task_by_run_id(root, &agent_id, &target_run_id)?
|
||||
.ok_or_else(|| format!("未找到 Agent Runtime 任务:{target_run_id}"))?;
|
||||
if game_chat_dynamic_art_child_structural_identity_at(root, &task)? {
|
||||
return Err(
|
||||
AgentRuntimeRetryErrorKind::GameChatDynamicArtRetryUnsupported.wire_error(
|
||||
"game-chat 动态美术 child 不支持通用 Agent Runtime retry;请回到当前 code-prototype,先完成 asset.list 审计,再按真实缺口建立新的 durable 美术委派",
|
||||
),
|
||||
);
|
||||
}
|
||||
if task.goal_id.is_some() {
|
||||
return Err("持久 Goal 任务不能使用普通 retry;请清理后创建新 Goal".to_string());
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,27 +1,161 @@
|
||||
use super::*;
|
||||
|
||||
fn game_chat_delegated_art_agent_is_assets_only_at(
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum GameChatDelegatedArtAgentMutationScope {
|
||||
Unrestricted,
|
||||
AssetsOnly,
|
||||
RetryLineageBlocked,
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn game_chat_dynamic_art_child_structural_identity_at(
|
||||
root: &Path,
|
||||
task: &AgentRuntimeTaskRecord,
|
||||
) -> Result<bool, String> {
|
||||
if !matches!(task.agent_id.as_str(), "art-director" | "art-asset-plan")
|
||||
|| task.parent_agent_id.as_deref() != Some("code-prototype")
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
let Some(parent_run_id) = task
|
||||
.parent_run_id
|
||||
.as_deref()
|
||||
.filter(|value| !value.is_empty())
|
||||
else {
|
||||
return Ok(false);
|
||||
};
|
||||
let Some(child_binding) =
|
||||
read_game_creator_agent_runtime_run_profile_binding(root, &task.agent_id, &task.run_id)?
|
||||
else {
|
||||
return Ok(false);
|
||||
};
|
||||
let Some(parent) = read_latest_game_creator_agent_runtime_task_by_run_id(
|
||||
root,
|
||||
"code-prototype",
|
||||
parent_run_id,
|
||||
)?
|
||||
else {
|
||||
return Ok(false);
|
||||
};
|
||||
let Some(parent_binding) =
|
||||
read_game_creator_agent_runtime_run_profile_binding(root, "code-prototype", parent_run_id)?
|
||||
else {
|
||||
return Ok(false);
|
||||
};
|
||||
let Some(root_binding) = read_game_creator_agent_runtime_run_profile_binding(
|
||||
root,
|
||||
&child_binding.root_agent_id,
|
||||
&child_binding.root_run_id,
|
||||
)?
|
||||
else {
|
||||
return Ok(false);
|
||||
};
|
||||
let Some(root_task) = read_latest_game_creator_agent_runtime_task_by_run_id(
|
||||
root,
|
||||
&root_binding.agent_id,
|
||||
&root_binding.run_id,
|
||||
)?
|
||||
else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
Ok(child_binding.agent_id == task.agent_id
|
||||
&& child_binding.run_id == task.run_id
|
||||
&& child_binding.source == task.source
|
||||
&& child_binding.profile == task.run_profile
|
||||
&& child_binding.binding_fingerprint == task.run_profile_binding_fingerprint
|
||||
&& child_binding.parent_agent_id == task.parent_agent_id
|
||||
&& child_binding.parent_run_id == task.parent_run_id
|
||||
&& child_binding.profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||
&& child_binding.root_agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|
||||
&& parent.agent_id == "code-prototype"
|
||||
&& parent.run_id == parent_run_id
|
||||
&& parent.source == "agent-ready-task-scheduler"
|
||||
&& parent.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||
&& parent.parent_agent_id.as_deref() == Some(GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID)
|
||||
&& parent.parent_run_id.as_deref() == Some(child_binding.root_run_id.as_str())
|
||||
&& parent.delegation_id.is_none()
|
||||
&& parent_binding.agent_id == parent.agent_id
|
||||
&& parent_binding.run_id == parent.run_id
|
||||
&& parent_binding.source == parent.source
|
||||
&& parent_binding.profile == parent.run_profile
|
||||
&& parent_binding.binding_fingerprint == parent.run_profile_binding_fingerprint
|
||||
&& parent_binding.parent_agent_id == parent.parent_agent_id
|
||||
&& parent_binding.parent_run_id == parent.parent_run_id
|
||||
&& parent_binding.project_id == child_binding.project_id
|
||||
&& parent_binding.root_agent_id == child_binding.root_agent_id
|
||||
&& parent_binding.root_run_id == child_binding.root_run_id
|
||||
&& child_binding.parent_binding_fingerprint.as_deref()
|
||||
== Some(parent_binding.binding_fingerprint.as_str())
|
||||
&& root_binding.project_id == child_binding.project_id
|
||||
&& root_binding.agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|
||||
&& root_binding.run_id == child_binding.root_run_id
|
||||
&& root_binding.source == AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE
|
||||
&& root_binding.profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||
&& root_binding.root_agent_id == root_binding.agent_id
|
||||
&& root_binding.root_run_id == root_binding.run_id
|
||||
&& root_binding.parent_agent_id.is_none()
|
||||
&& root_binding.parent_run_id.is_none()
|
||||
&& parent_binding.parent_binding_fingerprint.as_deref()
|
||||
== Some(root_binding.binding_fingerprint.as_str())
|
||||
&& root_task.agent_id == root_binding.agent_id
|
||||
&& root_task.run_id == root_binding.run_id
|
||||
&& root_task.source == root_binding.source
|
||||
&& root_task.run_profile == root_binding.profile
|
||||
&& root_task.run_profile_binding_fingerprint == root_binding.binding_fingerprint
|
||||
&& root_task.parent_agent_id.is_none()
|
||||
&& root_task.parent_run_id.is_none()
|
||||
&& root_task.delegation_id.is_none())
|
||||
}
|
||||
|
||||
fn game_chat_delegated_art_agent_mutation_scope_at(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
run_id: &str,
|
||||
) -> Result<bool, String> {
|
||||
) -> Result<GameChatDelegatedArtAgentMutationScope, String> {
|
||||
let canonical_output = match agent_id {
|
||||
"art-director" => AGENT_RUNTIME_ART_SPEC_PATH,
|
||||
"art-asset-plan" => AGENT_RUNTIME_ART_SPRITESHEET_PATH,
|
||||
_ => return Ok(false),
|
||||
_ => return Ok(GameChatDelegatedArtAgentMutationScope::Unrestricted),
|
||||
};
|
||||
if let Some(task) =
|
||||
read_latest_game_creator_agent_runtime_task_by_run_id(root, agent_id, run_id)?
|
||||
{
|
||||
if task.source == "agent-delegate-retry"
|
||||
&& game_chat_dynamic_art_child_structural_identity_at(root, &task)?
|
||||
{
|
||||
return Ok(GameChatDelegatedArtAgentMutationScope::RetryLineageBlocked);
|
||||
}
|
||||
}
|
||||
// `assets/**` is a broad write scope. Grant it only after the same
|
||||
// canonical-output authorization used by Canvas replacement has verified
|
||||
// the live root, main parent, child binding, durable delivery and audited
|
||||
// generate-missing route.
|
||||
game_chat_fast_path_scheduled_art_contract_repair_is_authorized_at(
|
||||
root,
|
||||
agent_id,
|
||||
run_id,
|
||||
canonical_output,
|
||||
Ok(
|
||||
if game_chat_fast_path_scheduled_art_contract_repair_is_authorized_at(
|
||||
root,
|
||||
agent_id,
|
||||
run_id,
|
||||
canonical_output,
|
||||
)? {
|
||||
GameChatDelegatedArtAgentMutationScope::AssetsOnly
|
||||
} else {
|
||||
GameChatDelegatedArtAgentMutationScope::Unrestricted
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn game_chat_dynamic_art_retry_mutation_block(tool: &str) -> AgentRuntimeToolObservation {
|
||||
AgentRuntimeToolObservation {
|
||||
tool: tool.to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: "game-chat 动态美术 child 不支持通用 retry 写入".to_string(),
|
||||
detail: Some(
|
||||
"必须回到当前 code-prototype 先完成 asset.list 审计,再按真实缺口建立新的 durable 美术委派"
|
||||
.to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn game_chat_art_child_tool_is_read_only(tool: &str) -> bool {
|
||||
matches!(
|
||||
tool,
|
||||
@@ -48,17 +182,26 @@ pub(in crate::agent) fn game_chat_delegated_art_agent_project_path_mutation_bloc
|
||||
tool: &str,
|
||||
path: &str,
|
||||
) -> Option<AgentRuntimeToolObservation> {
|
||||
match game_chat_delegated_art_agent_is_assets_only_at(root, agent_id, run_id) {
|
||||
Ok(false) => None,
|
||||
Ok(true) if path == "assets" || path.starts_with("assets/") => None,
|
||||
Ok(true) => Some(AgentRuntimeToolObservation {
|
||||
tool: tool.to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: "game-chat 临时美术 Agent 只能修改 assets/**".to_string(),
|
||||
detail: Some(format!(
|
||||
"path={path} · allowed=assets/** · parentAgentId=code-prototype"
|
||||
)),
|
||||
}),
|
||||
match game_chat_delegated_art_agent_mutation_scope_at(root, agent_id, run_id) {
|
||||
Ok(GameChatDelegatedArtAgentMutationScope::Unrestricted) => None,
|
||||
Ok(GameChatDelegatedArtAgentMutationScope::RetryLineageBlocked) => {
|
||||
Some(game_chat_dynamic_art_retry_mutation_block(tool))
|
||||
}
|
||||
Ok(GameChatDelegatedArtAgentMutationScope::AssetsOnly)
|
||||
if path == "assets" || path.starts_with("assets/") =>
|
||||
{
|
||||
None
|
||||
}
|
||||
Ok(GameChatDelegatedArtAgentMutationScope::AssetsOnly) => {
|
||||
Some(AgentRuntimeToolObservation {
|
||||
tool: tool.to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: "game-chat 临时美术 Agent 只能修改 assets/**".to_string(),
|
||||
detail: Some(format!(
|
||||
"path={path} · allowed=assets/** · parentAgentId=code-prototype"
|
||||
)),
|
||||
})
|
||||
}
|
||||
Err(error) => Some(AgentRuntimeToolObservation {
|
||||
tool: tool.to_string(),
|
||||
status: "blocked".to_string(),
|
||||
@@ -100,21 +243,28 @@ pub(in crate::agent) fn game_chat_delegated_art_agent_input_mutation_block(
|
||||
});
|
||||
}
|
||||
}
|
||||
let assets_only = match game_chat_delegated_art_agent_is_assets_only_at(root, agent_id, run_id)
|
||||
{
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
return Some(AgentRuntimeToolObservation {
|
||||
tool: tool.to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: "无法校验 game-chat 临时美术 Agent 的写入边界".to_string(),
|
||||
detail: Some(sanitize_agent_runtime_text(&error, 240)),
|
||||
});
|
||||
}
|
||||
};
|
||||
if !assets_only || game_chat_art_child_tool_is_read_only(tool) {
|
||||
let mutation_scope =
|
||||
match game_chat_delegated_art_agent_mutation_scope_at(root, agent_id, run_id) {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
return Some(AgentRuntimeToolObservation {
|
||||
tool: tool.to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: "无法校验 game-chat 临时美术 Agent 的写入边界".to_string(),
|
||||
detail: Some(sanitize_agent_runtime_text(&error, 240)),
|
||||
});
|
||||
}
|
||||
};
|
||||
if game_chat_art_child_tool_is_read_only(tool) {
|
||||
return None;
|
||||
}
|
||||
match mutation_scope {
|
||||
GameChatDelegatedArtAgentMutationScope::Unrestricted => return None,
|
||||
GameChatDelegatedArtAgentMutationScope::RetryLineageBlocked => {
|
||||
return Some(game_chat_dynamic_art_retry_mutation_block(tool));
|
||||
}
|
||||
GameChatDelegatedArtAgentMutationScope::AssetsOnly => {}
|
||||
}
|
||||
let mut block_path = |path: &str| {
|
||||
game_chat_delegated_art_agent_project_path_mutation_block(
|
||||
root, agent_id, run_id, tool, path,
|
||||
@@ -169,9 +319,16 @@ pub(in crate::agent) fn agent_role_project_path_mutation_block(
|
||||
}
|
||||
}
|
||||
Ok(false) => {
|
||||
match game_chat_delegated_art_agent_is_assets_only_at(root, agent_id, run_id) {
|
||||
Ok(true) if path == "assets" || path.starts_with("assets/") => return None,
|
||||
Ok(true) => {
|
||||
match game_chat_delegated_art_agent_mutation_scope_at(root, agent_id, run_id) {
|
||||
Ok(GameChatDelegatedArtAgentMutationScope::RetryLineageBlocked) => {
|
||||
return Some(game_chat_dynamic_art_retry_mutation_block(tool));
|
||||
}
|
||||
Ok(GameChatDelegatedArtAgentMutationScope::AssetsOnly)
|
||||
if path == "assets" || path.starts_with("assets/") =>
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Ok(GameChatDelegatedArtAgentMutationScope::AssetsOnly) => {
|
||||
return Some(AgentRuntimeToolObservation {
|
||||
tool: tool.to_string(),
|
||||
status: "blocked".to_string(),
|
||||
@@ -181,7 +338,7 @@ pub(in crate::agent) fn agent_role_project_path_mutation_block(
|
||||
)),
|
||||
});
|
||||
}
|
||||
Ok(false) => {}
|
||||
Ok(GameChatDelegatedArtAgentMutationScope::Unrestricted) => {}
|
||||
Err(error) => {
|
||||
return Some(AgentRuntimeToolObservation {
|
||||
tool: tool.to_string(),
|
||||
|
||||
Reference in New Issue
Block a user