修正 icon 全局放行后的两处判据:删掉死分支并更新旧断言
- 删除 `ensure_editor_image_edit_source_kind_allowed_for_request` 里 `AGC && assetKind=="icon" → Ok` 的例外分支:`icon` 已进静态图类型表被全局放行,该分支恒不改变判定结果 - 连带删除因此不再被引用的 `is_game_creator_resource_editor_generation` 与其定向用例(同一 scope 无人调用);AGC 来源别名口径仍由 `EditorGenerationQueueResultContext::from_job` 的 queue consumer 判定承担并有既有用例覆盖 - 两处调用点改为直接调 `ensure_editor_image_edit_source_kind_allowed`,删掉没有行为的 `_for_request` 包装层与 `generation_inputs` 形参 - 旧用例 `game_creator_resource_editor_bypasses_quick_edit_whitelist_only_for_its_source` 改名 `quick_edit_source_kind_policy_ignores_generation_source`,改为断言来源类型判据与 generation source 无关 - 修正 master 侧旧断言 `editor_image_edit_target_binding_prefers_object_id_and_fails_closed`:反例 effective_asset_kind 从 `icon` 换成仍被拒的 `character-animation`,断言保持 Err,守卫意图(目标图层不能借壳成允许来源)不变
This commit is contained in:
@@ -4163,37 +4163,6 @@ pub(crate) fn ensure_editor_image_edit_source_kind_allowed(
|
||||
)
|
||||
}
|
||||
|
||||
fn is_game_creator_resource_editor_generation(generation_inputs: Option<&Value>) -> bool {
|
||||
generation_inputs
|
||||
.and_then(|value| value.pointer("/source"))
|
||||
.and_then(Value::as_str)
|
||||
.is_some_and(|source| {
|
||||
// 中文注释:AGC 客户端在发送前会把 source 改写成 `ai-game-creator-client`,
|
||||
// 两个 source 是同一个 consumer 的别名(见 `from_job` 的 queue consumer 判定),
|
||||
// 这里必须同口径,否则客户端实际发出的请求永远走不到 AGC 分支。
|
||||
matches!(
|
||||
source.trim(),
|
||||
GAME_CREATOR_RESOURCE_EDITOR_SOURCE | GAME_CREATOR_CLIENT_GENERATION_SOURCE
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_editor_image_edit_source_kind_allowed_for_request(
|
||||
asset_kind: Option<&str>,
|
||||
media_type: Option<&str>,
|
||||
generation_inputs: Option<&Value>,
|
||||
) -> Result<(), AppError> {
|
||||
let asset_kind = asset_kind.map(str::trim).filter(|value| !value.is_empty());
|
||||
let media_type = media_type.map(str::trim).filter(|value| !value.is_empty());
|
||||
if is_game_creator_resource_editor_generation(generation_inputs)
|
||||
&& asset_kind == Some("icon")
|
||||
&& matches!(media_type, None | Some("image"))
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
ensure_editor_image_edit_source_kind_allowed(asset_kind, media_type)
|
||||
}
|
||||
|
||||
pub(crate) const EDITOR_IMAGE_EDIT_QUEUE_PAYLOAD_VERSION: u32 = 1;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
@@ -4379,10 +4348,9 @@ fn ensure_editor_image_edit_target_matches_source(
|
||||
})),
|
||||
);
|
||||
}
|
||||
ensure_editor_image_edit_source_kind_allowed_for_request(
|
||||
ensure_editor_image_edit_source_kind_allowed(
|
||||
target.effective_asset_kind.as_deref(),
|
||||
Some(target.media_type.as_str()),
|
||||
generation_inputs,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4524,11 +4492,7 @@ async fn resolve_editor_image_edit_source(
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
else {
|
||||
ensure_editor_image_edit_source_kind_allowed_for_request(
|
||||
source_asset_kind.as_deref(),
|
||||
Some("image"),
|
||||
payload.generation_inputs.as_ref(),
|
||||
)?;
|
||||
ensure_editor_image_edit_source_kind_allowed(source_asset_kind.as_deref(), Some("image"))?;
|
||||
return Ok(EditorImageEditResolvedSource {
|
||||
reference_kind,
|
||||
source_reference_id: source_reference_id.to_string(),
|
||||
@@ -20759,96 +20723,29 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn game_creator_resource_editor_bypasses_quick_edit_whitelist_only_for_its_source() {
|
||||
let generation_inputs = json!({ "source": GAME_CREATOR_RESOURCE_EDITOR_SOURCE });
|
||||
fn quick_edit_source_kind_policy_ignores_generation_source() {
|
||||
assert!(
|
||||
ensure_editor_image_edit_source_kind_allowed_for_request(
|
||||
Some("icon"),
|
||||
Some("image"),
|
||||
Some(&generation_inputs),
|
||||
)
|
||||
.is_ok()
|
||||
ensure_editor_image_edit_source_kind_allowed(Some("icon"), Some("image")).is_ok(),
|
||||
"icon 是共享契约里的静态图 canonical 类型,与 generationInputs.source 无关"
|
||||
);
|
||||
assert!(
|
||||
ensure_editor_image_edit_source_kind_allowed_for_request(
|
||||
Some("icon"),
|
||||
Some("image"),
|
||||
Some(&json!({ "source": "editor-agent" })),
|
||||
)
|
||||
.is_ok(),
|
||||
"icon 已是共享契约里的静态图 canonical 类型,与 generationInputs.source 无关"
|
||||
ensure_editor_image_edit_source_kind_allowed(Some("art-spritesheet"), Some("image"))
|
||||
.is_ok()
|
||||
);
|
||||
assert!(ensure_editor_image_edit_source_kind_allowed(Some("icon"), Some("video")).is_err());
|
||||
assert!(
|
||||
ensure_editor_image_edit_source_kind_allowed_for_request(
|
||||
Some("icon"),
|
||||
Some("video"),
|
||||
Some(&generation_inputs),
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
ensure_editor_image_edit_source_kind_allowed_for_request(
|
||||
Some("future-kind"),
|
||||
Some("image"),
|
||||
Some(&generation_inputs),
|
||||
)
|
||||
.is_err()
|
||||
ensure_editor_image_edit_source_kind_allowed(Some("future-kind"), Some("image"))
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
/// AGC 客户端发送前把 `generationInputs.source` 改写成
|
||||
/// `ai-game-creator-client`(`resource_editor.rs` 的 `submit_resource_edit_remote`),
|
||||
/// 服务端必须按队列 consumer 的同口径把它认成同一个 AGC consumer,否则 AGC 分支是死代码。
|
||||
#[test]
|
||||
fn game_creator_client_source_is_recognized_as_game_creator_resource_editor() {
|
||||
assert!(is_game_creator_resource_editor_generation(Some(&json!({
|
||||
"source": GAME_CREATOR_CLIENT_GENERATION_SOURCE
|
||||
}))));
|
||||
assert!(is_game_creator_resource_editor_generation(Some(&json!({
|
||||
"source": GAME_CREATOR_RESOURCE_EDITOR_SOURCE
|
||||
}))));
|
||||
assert!(
|
||||
is_game_creator_resource_editor_generation(Some(&json!({
|
||||
"source": " game-creator-resource-editor "
|
||||
}))),
|
||||
"别名判定沿用 trim 口径"
|
||||
);
|
||||
assert!(!is_game_creator_resource_editor_generation(Some(&json!({
|
||||
"source": "editor-agent"
|
||||
}))));
|
||||
assert!(!is_game_creator_resource_editor_generation(Some(
|
||||
&json!({})
|
||||
)));
|
||||
assert!(!is_game_creator_resource_editor_generation(None));
|
||||
|
||||
// 端到端同口径:AGC 客户端实际发出的 source 必须与 `from_job` 的 consumer 判定一致。
|
||||
// 快速编辑端点把 `generationInputs` 对象本身当判定输入,所以这里按 `/generationInputs` 取。
|
||||
let mut queued_job = atomic_editor_generation_job_fixture();
|
||||
queued_job.dedupe_key =
|
||||
"game-creator-client-generation:editor_image_generation:fingerprint".to_string();
|
||||
queued_job.request_payload_json = json!({
|
||||
"generationInputs": { "source": GAME_CREATOR_CLIENT_GENERATION_SOURCE }
|
||||
})
|
||||
.to_string();
|
||||
assert_eq!(
|
||||
EditorGenerationQueueResultContext::from_job(&queued_job).consumer,
|
||||
EditorGenerationQueueConsumer::GameCreatorResourceEditor
|
||||
);
|
||||
let queued_payload: Value = serde_json::from_str(queued_job.request_payload_json.as_str())
|
||||
.expect("fixture payload should be JSON");
|
||||
assert!(is_game_creator_resource_editor_generation(
|
||||
queued_payload.pointer("/generationInputs")
|
||||
));
|
||||
}
|
||||
|
||||
/// 实测 400×5 的回归用例:AGC「图片快速编辑」发出的请求,source 已被改写成
|
||||
/// `ai-game-creator-client`,来源资源的 assetKind 是本地 manifest 原始类型
|
||||
/// `art-spritesheet` / `ui`(mediaType=image)。这条路径必须放行;
|
||||
/// 实测 400×5 的回归用例:AGC「图片快速编辑」发出的请求,来源资源的 assetKind 是本地
|
||||
/// manifest 原始类型 `art-spritesheet` / `ui`(mediaType=image)。这条路径必须放行;
|
||||
/// 非静态媒体(video/audio/image-sequence)必须继续拒绝。
|
||||
#[test]
|
||||
fn game_creator_client_quick_edit_accepts_local_manifest_static_image_kinds() {
|
||||
// 与客户端 `submit_resource_edit_remote` 发送的 JSON 同形:请求体的
|
||||
// `generationInputs` 就是这里传入的判定输入。
|
||||
// 与客户端 `submit_resource_edit_remote` 发送的 JSON 同形;放行判据只看来源资源的
|
||||
// 权威 assetKind 与 mediaType,请求体里的 `generationInputs.source` 不参与。
|
||||
let agc_payload: Value = serde_json::from_str(
|
||||
r#"{
|
||||
"prompt": "把这张图改成夜间配色",
|
||||
@@ -20860,9 +20757,13 @@ mod tests {
|
||||
}"#,
|
||||
)
|
||||
.expect("AGC quick edit payload should be JSON");
|
||||
let generation_inputs = agc_payload
|
||||
.pointer("/generationInputs")
|
||||
.expect("AGC payload should carry generationInputs");
|
||||
assert_eq!(
|
||||
agc_payload
|
||||
.pointer("/generationInputs/assetKind")
|
||||
.and_then(Value::as_str),
|
||||
Some("art-spritesheet"),
|
||||
"回归夹具必须保持 AGC 客户端实际发送的请求形状"
|
||||
);
|
||||
|
||||
for (asset_kind, media_type) in [
|
||||
("art-spritesheet", "image"),
|
||||
@@ -20873,12 +20774,8 @@ mod tests {
|
||||
("icon", "image"),
|
||||
] {
|
||||
assert!(
|
||||
ensure_editor_image_edit_source_kind_allowed_for_request(
|
||||
Some(asset_kind),
|
||||
Some(media_type),
|
||||
Some(generation_inputs),
|
||||
)
|
||||
.is_ok(),
|
||||
ensure_editor_image_edit_source_kind_allowed(Some(asset_kind), Some(media_type))
|
||||
.is_ok(),
|
||||
"AGC 快速编辑必须放行 {asset_kind}/{media_type}"
|
||||
);
|
||||
}
|
||||
@@ -20893,24 +20790,17 @@ mod tests {
|
||||
("future-kind", "image"),
|
||||
] {
|
||||
assert!(
|
||||
ensure_editor_image_edit_source_kind_allowed_for_request(
|
||||
Some(asset_kind),
|
||||
Some(media_type),
|
||||
Some(generation_inputs),
|
||||
)
|
||||
.is_err(),
|
||||
ensure_editor_image_edit_source_kind_allowed(Some(asset_kind), Some(media_type))
|
||||
.is_err(),
|
||||
"非静态来源必须继续拒绝:{asset_kind}/{media_type}"
|
||||
);
|
||||
}
|
||||
|
||||
// 400 错误体必须带上真实原因,客户端才能诊断(对应 AGC 的
|
||||
// `editor_api_rejection_reason` 取值顺序修正)。
|
||||
let error = ensure_editor_image_edit_source_kind_allowed_for_request(
|
||||
Some("future-kind"),
|
||||
Some("image"),
|
||||
Some(generation_inputs),
|
||||
)
|
||||
.expect_err("未知类型必须拒绝");
|
||||
let error =
|
||||
ensure_editor_image_edit_source_kind_allowed(Some("future-kind"), Some("image"))
|
||||
.expect_err("未知类型必须拒绝");
|
||||
assert_eq!(error.status_code(), StatusCode::BAD_REQUEST);
|
||||
assert_eq!(
|
||||
error.details().and_then(|details| details.get("provider")),
|
||||
@@ -21035,7 +20925,9 @@ mod tests {
|
||||
}
|
||||
|
||||
let mut forbidden_override = target.clone();
|
||||
forbidden_override.effective_asset_kind = Some("icon".to_string());
|
||||
// 用仍然被静态图表拒绝的类型,守卫意图不变:目标图层不能借壳成允许来源。
|
||||
// `icon` 已是共享契约里的静态图 canonical 类型,不能再当反例。
|
||||
forbidden_override.effective_asset_kind = Some("character-animation".to_string());
|
||||
assert!(
|
||||
ensure_editor_image_edit_target_matches_source(
|
||||
"resource-source",
|
||||
|
||||
Reference in New Issue
Block a user