From b4fc28fbbbbea4a6403d468beff85f07ea49339e Mon Sep 17 00:00:00 2001 From: Linghong Date: Wed, 2 Sep 2026 03:25:23 +0000 Subject: [PATCH] =?UTF-8?q?=E9=98=B6=E6=AE=B55=EF=BC=9A=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E6=80=81=E4=B8=8E=20External=20=E6=80=81?= =?UTF-8?q?=E8=AF=AD=E4=B9=89=E5=9B=9E=E5=BD=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增账号态主站路径、标记、Bearer、幂等键和异步轮询回归断言。 新增 External Key 态自定义 apiBaseUrl 与 External v1 路径请求捕获测试。 锁定 generationInputs.source、单次提交和结果未知不重试语义。 更新 Issue 226 实施方案与分阶段验收计划,记录阶段 5 完成。 --- .../src-tauri/src/tests/project.rs | 88 +++++++++++++++++++ ...Issue226-AGC客户端主站请求统一标记-2026-09-01.md | 10 ++- ...26-AGC客户端主站请求标记分阶段验收-2026-09-01.md | 10 ++- 3 files changed, 106 insertions(+), 2 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs index 610d85061..33759f02d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs @@ -993,6 +993,37 @@ async fn background_agent_runtime_can_generate_platform_art_asset() { .iter() .find(|request| request.starts_with("POST /api/editor/icon-spritesheets/generations ")) .expect("canvas generation request"); + let request_header = |request: &str, expected_name: &str| { + request.lines().find_map(|line| { + let (name, value) = line.split_once(':')?; + name.eq_ignore_ascii_case(expected_name) + .then(|| value.trim().to_string()) + }) + }; + let main_site_requests = canvas_requests + .iter() + .filter(|request| { + request.starts_with("GET /api/editor/") + || request.starts_with("POST /api/editor/") + || request.starts_with("GET /api/assets/") + || request.starts_with("POST /api/assets/") + || request.starts_with("GET /api/runtime/") + }) + .collect::>(); + assert!(!main_site_requests.is_empty()); + assert!(main_site_requests.iter().all(|request| { + request + .to_ascii_lowercase() + .contains("x-genarrative-client: agc") + })); + assert_eq!( + request_header(generation_request, "authorization"), + Some("Bearer editor-runtime-key".to_string()) + ); + let generation_idempotency_key = + request_header(generation_request, "idempotency-key").expect("generation idempotency key"); + assert!(uuid::Uuid::parse_str(&generation_idempotency_key).is_ok()); + assert!(generation_request.contains(r#""source":"ai-game-creator-client""#)); assert_eq!( canvas_requests .iter() @@ -1011,6 +1042,14 @@ async fn background_agent_runtime_can_generate_platform_art_asset() { 3, "fixture should exercise queued, running, and completed states" ); + assert!(canvas_requests + .iter() + .filter(|request| request.starts_with("GET /api/runtime/external-generation/jobs/")) + .all(|request| { + request + .to_ascii_lowercase() + .contains("x-genarrative-client: agc") + })); for expected in [ r#""referenceId":"resource-icon-spec""#, r#""iconDescriptions":"#, @@ -2121,6 +2160,55 @@ async fn sync_canvas_project_assets_downloads_external_resources() { fs::remove_dir_all(root).ok(); } +#[tokio::test] +async fn sync_canvas_project_assets_with_developer_key_uses_external_route_and_marker() { + let _platform_session = crate::platform_session::clear_test_platform_session(); + let root = unique_project_path(); + let (request_sender, request_receiver) = mpsc::channel(); + let base_url = spawn_mock_external_canvas_api_server_with_capture(3, Some(request_sender)); + let api_key = "tnr_sk_phase5_fixture"; + let result = crate::assets::with_external_editor_api_credentials( + crate::assets::external_editor_api_credentials_for_test( + base_url.clone(), + api_key.to_string(), + ), + sync_canvas_project_assets_at( + &root, + "canvas-project-1", + Some(base_url.clone()), + Some(api_key.to_string()), + ), + ) + .await + .expect("developer key canvas project sync"); + + assert_eq!(result.canvas_project_id, "canvas-project-1"); + let requests = request_receiver.try_iter().collect::>(); + assert_eq!(requests.len(), 3); + let project_request = requests + .iter() + .find(|request| request.starts_with("GET /api/external/v1/editor/projects/")) + .expect("External v1 project request"); + let read_url_request = requests + .iter() + .find(|request| request.starts_with("GET /api/external/v1/assets/read-url?")) + .expect("External v1 read URL request"); + for request in [project_request, read_url_request] { + let normalized = request.to_ascii_lowercase(); + assert!(normalized.contains("x-genarrative-client: agc")); + assert!(normalized.contains("authorization: bearer tnr_sk_phase5_fixture")); + } + let signed_download = requests + .iter() + .find(|request| request.starts_with("GET /signed/")) + .expect("signed media download request"); + assert!(!signed_download + .to_ascii_lowercase() + .contains("x-genarrative-client:")); + + fs::remove_dir_all(root).ok(); +} + #[tokio::test] async fn generate_platform_art_asset_downloads_and_registers_external_image() { let root = unique_project_path(); diff --git a/local-docs/【实施方案】Issue226-AGC客户端主站请求统一标记-2026-09-01.md b/local-docs/【实施方案】Issue226-AGC客户端主站请求统一标记-2026-09-01.md index 12458a7fa..bc08957d7 100644 --- a/local-docs/【实施方案】Issue226-AGC客户端主站请求统一标记-2026-09-01.md +++ b/local-docs/【实施方案】Issue226-AGC客户端主站请求统一标记-2026-09-01.md @@ -6,7 +6,7 @@ - `#226 添加客户端特殊标识`:本方案实际实施范围 - `#225 添加客户端埋点统计`:主站接收、落库和后台统计,本文只冻结交接契约,不在本次实施 -状态:阶段 4 已完成,待阶段 5 实施
+状态:阶段 5 已完成,待阶段 6 实施
本方案包含 #226 客户端代码修改,不包含 #225 主站接收、落库和后台统计代码 ## 1. 一句话交付结果 @@ -245,6 +245,14 @@ let client = agc_main_site_client_builder() - 阶段 4 验证结果:`omits_agc_marker` 负向测试 4 个通过;Provider 代理测试 1 个通过;阶段 3 的画板同步请求捕获测试继续作为签名 URL 媒体下载无标记证据。 - 未修改主站 API、`ExternalEditorBindingAccess` 路由映射、认证语义、OSS/Provider/搜索/loopback/更新的网络安全策略或 #225 代码。 +### 6.7 阶段 5 实施记录 + +- 账号态回归使用真实平台会话 fixture,验证 `/api/editor`、`/api/assets` 和 `/api/runtime` 实际路径均携带 `X-Genarrative-Client: agc`。 +- 账号态生成请求继续携带 Bearer、UUID Idempotency-Key 和原有 `generationInputs.source = ai-game-creator-client`;排队、运行中和完成状态仍按原语义轮询,提交只发生一次。 +- External Key 态回归清除平台会话,使用 `tnr_sk_phase5_fixture` 和本地自定义 `apiBaseUrl`,验证 `/api/external/v1/editor/projects/*` 与 `/api/external/v1/assets/read-url` 携带相同标记和 API Key,签名媒体下载不带标记。 +- External v1 endpoint、账号态路由映射、请求体、认证、幂等、异步轮询和结果未知语义均未改变。 +- 阶段 5 验证:`background_agent_runtime_can_generate_platform_art_asset`、`sync_canvas_project_assets_with_developer_key_uses_external_route_and_marker` 和 `generation_submit_response_loss_is_not_retried` 均通过;Rust fmt、编码检查和 `git diff --check` 通过。 + ## 7. 调用语义和现有路由边界 本次不改变 `ExternalEditorBindingAccess` 的职责: diff --git a/local-docs/【实施计划】Issue226-AGC客户端主站请求标记分阶段验收-2026-09-01.md b/local-docs/【实施计划】Issue226-AGC客户端主站请求标记分阶段验收-2026-09-01.md index e7eb31f6d..e855e3619 100644 --- a/local-docs/【实施计划】Issue226-AGC客户端主站请求标记分阶段验收-2026-09-01.md +++ b/local-docs/【实施计划】Issue226-AGC客户端主站请求标记分阶段验收-2026-09-01.md @@ -6,7 +6,7 @@ - `#226 添加客户端特殊标识`:本计划全部实施范围 - `#225 添加客户端埋点统计`:只接收交接契约,不在本计划实现 -当前状态:阶段 4 已完成;阶段 5 尚未开始。阶段 0 的证据与验收记录见 +当前状态:阶段 5 已完成;阶段 6 尚未开始。阶段 0 的证据与验收记录见 [【阶段验收】Issue226阶段0现状基线与契约冻结-2026-09-01.md](C:/projects/narrative/Genarrative/local-docs/【阶段验收】Issue226阶段0现状基线与契约冻结-2026-09-01.md)。 ## 1. 交付目标 @@ -323,6 +323,14 @@ fn agc_main_site_client_builder() -> reqwest::ClientBuilder; - 没有发生 token、API Key、Cookie、签名 URL 或项目绝对路径泄漏到日志/测试输出。 - 没有因为 Header 增加而改变业务状态码或重试策略。 +### 9.4 阶段 5 实施记录 + +- 账号态生成回归:`background_agent_runtime_can_generate_platform_art_asset` 捕获并检查 `/api/editor`、`/api/assets`、`/api/runtime` 请求,确认统一标记、Bearer、UUID Idempotency-Key、单次提交和三阶段轮询;同时锁定 `generationInputs.source = ai-game-creator-client`。 +- External Key 态与自定义服务器回归:`sync_canvas_project_assets_with_developer_key_uses_external_route_and_marker` 在无平台会话下使用 `tnr_sk_phase5_fixture` 和自定义本地 `apiBaseUrl`,确认 External v1 项目/换签请求带标记,签名媒体下载不带标记。 +- 结果未知语义回归:`generation_submit_response_loss_is_not_retried` 通过,确认提交响应丢失时保留原 Idempotency-Key 且不重复提交。 +- 账号态画板同步测试继续覆盖主站项目请求、`assets/read-url` 和签名下载的正负边界。 +- 阶段 5 定向测试均通过;阶段 6 只剩 #225 交接材料复核与最终门禁,不在本阶段修改主站代码。 + ## 10. 阶段 6:#225 交接与最终门禁 ### 10.1 交给 #225 的固定契约