From c127cf2f993cabe1c012119d6e492f8727357305 Mon Sep 17 00:00:00 2001 From: Linghong Date: Wed, 2 Sep 2026 11:02:19 +0000 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90Issue225=E9=98=B6=E6=AE=B56?= =?UTF-8?q?=E9=AA=8C=E6=94=B6=E4=B8=8E=E4=BA=A4=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新Issue225实施方案与分阶段验收计划为阶段0至6完成 补充最终门禁、48个定向测试证据与tracking_event落点说明 固化Issue225交付评论和Issue226客户端交接及后续风险记录 --- ...€‘Issue225-AGC主站请求标记埋点统计-2026-09-02.md | 329 +++++++++++++++ ...e225-AGC主站请求标记埋点分阶段验收-2026-09-02.md | 391 ++++++++++++++++++ ...】Issue225阶段5定向测试与安全边界-2026-09-02.md | 97 +++++ ...】Issue225阶段6最终门禁与交接收口-2026-09-02.md | 144 +++++++ 4 files changed, 961 insertions(+) create mode 100644 local-docs/【实施方案】Issue225-AGC主站请求标记埋点统计-2026-09-02.md create mode 100644 local-docs/【实施计划】Issue225-AGC主站请求标记埋点分阶段验收-2026-09-02.md create mode 100644 local-docs/【阶段验收】Issue225阶段5定向测试与安全边界-2026-09-02.md create mode 100644 local-docs/【阶段验收】Issue225阶段6最终门禁与交接收口-2026-09-02.md diff --git a/local-docs/【实施方案】Issue225-AGC主站请求标记埋点统计-2026-09-02.md b/local-docs/【实施方案】Issue225-AGC主站请求标记埋点统计-2026-09-02.md new file mode 100644 index 000000000..5694c7db5 --- /dev/null +++ b/local-docs/【实施方案】Issue225-AGC主站请求标记埋点统计-2026-09-02.md @@ -0,0 +1,329 @@ +# Issue #225:AGC 主站请求标记埋点统计实施方案 + +更新时间:2026-09-02 +关联 Issue: + +- `#225 添加客户端埋点统计`:本文全部实施范围 +- `#226 添加客户端特殊标识`:客户端侧已完成;本文只接收其固定交接契约,不回改客户端 + +当前状态:阶段 0~6 已完成。主站生产代码和定向测试已按阶段提交;阶段 6 仅补充最终门禁、交接与验收文档。未修改 SpacetimeDB schema、OpenAPI 或后台页面。 + +## 1. 一句话交付结果 + +主站 `api-server` 能识别 AGC 发来的 `X-Genarrative-Client: agc`,并在现有成功路由埋点写入 `tracking_event.metadata_json` 的 `client: "agc"`,同时按真实登录用户或 External API Key 的 `owner_user_id` 归属,后台可以通过现有 tracking 事件查询看到这类请求;不改变鉴权、计费、幂等、响应和现有统计口径。 + +## 2. Issue 边界 + +### 2.1 本次只做 #225 + +本次主改动限定在主站后端: + +- `server-rs/crates/api-server/src/app.rs` 的 tracking middleware 入口。 +- `server-rs/crates/api-server/src/tracking.rs` 的标记识别、主体归属、metadata 合并和 route tracking 覆盖。 +- 必要的 `api-server` 定向测试、External v1 路由测试和后台 tracking 读取验证。 +- 方案、验收、交接文档。 + +### 2.2 本次不做 #226 的回改 + +不修改: + +- AGC TS 的 `fetchClientHttp` 标记注入。 +- AGC Rust 主站 Client factory、请求终结器和 redirect policy。 +- AGC 主站/第三方请求边界、timeout、认证、幂等和请求体语义。 + +`#226` 已冻结并合入的客户端契约直接作为本 Issue 的输入。 + +### 2.3 明确不做项 + +- 不新增 `tracking_event` 列、索引、migration 或生成 bindings;第一阶段复用已有 `metadata_json`。 +- 不新增平行的 `agc_client_request` 事件、平行表或平行统计口径。 +- 不改变 `/api/external/v1` 的路由、HTTP 方法、DTO、状态码、鉴权或异步语义,因此不改 OpenAPI 契约。 +- 不把 `generationInputs.source`、User-Agent、请求体中的 owner 字段当作客户端来源或主体。 +- 不记录 access token、External API Key 明文、Cookie、签名 URL、项目绝对路径或其他秘密。 +- 不把 OSS 上传、签名 URL 实际下载、LLM/Codex Provider、受控搜索、loopback、更新下载和任意外部网页请求当成主站业务 tracking。 +- 不默认把失败响应改造成新的 tracking 事实;先保持现有“成功响应才写 route tracking”语义。 +- 不默认新增后台筛选控件;后台先复用现有 tracking 原始事件查询展示 `metadata_json`。 + +## 3. 当前实现基线与缺口 + +### 3.1 现有 tracking 流程 + +当前全局链路位于: + +- `server-rs/crates/api-server/src/app.rs` +- `server-rs/crates/api-server/src/tracking.rs` + +流程为: + +```text +请求进入 + → tracking middleware 保存 method/path + → next.run(request) + → 从 response extensions 读取认证主体 + → 仅对成功响应解析 RouteTrackingSpec + → 生成 TrackingEventDraft.metadata + → 本机 tracking outbox + → SpacetimeDB tracking_event / tracking_daily_stat +``` + +现有 `AuthenticatedAccessToken` 和 `ExternalApiPrincipal` 都会在认证 middleware 成功后写入 response extensions;但 tracking middleware 目前只消费前者。 + +### 3.2 当前缺口 + +- tracking middleware 尚未读取 `X-Genarrative-Client`。 +- route metadata 尚未写入 `client`。 +- External API Key 的 `ExternalApiPrincipal.owner_user_id` 尚未接入 route tracking 归属。 +- `/api/external/v1/*` 没有完整的 route tracking spec。 +- 账号态实际被 AGC 使用的 `/api/editor`、`/api/assets`、`/api/runtime` 路径也有未覆盖项。 + +## 4. 冻结的 #226 交接契约 + +### 4.1 Header 识别 + +```http +X-Genarrative-Client: agc +``` + +规则: + +- Header 名按 HTTP 规则大小写不敏感。 +- 值去除首尾空白后,精确等于小写 `agc` 才认定为 AGC。 +- 缺失、空值、`AGC`、其他未知值均按“未标记”处理。 +- 未标记请求不被拒绝,也不改变业务行为。 +- Header 只用于来源审计和统计,不参与鉴权、权限、计费、幂等或账号归属。 + +### 4.2 metadata 形态 + +第一阶段复用已有 `tracking_event.metadata_json`,在原对象上追加固定键: + +```json +{ + "route": "/api/editor/images/generations", + "method": "POST", + "status": 202, + "operation": "generateExternalEditorImage", + "client": "agc" +} +``` + +约定: + +- JSON key 固定为 `client`,值固定为 `agc`。 +- 有效标记时追加 `client`;未标记时不写 `client`,不写空字符串或 `null`。 +- 已有 `route`、`method`、`status`、`operation` 以及资产类嵌套 metadata 必须保留。 +- `route` 使用主站实际收到的 method/path;External v1 不得只记录客户端账本中的内部映射路径。 + +### 4.3 认证主体归属 + +| 请求类型 | `user_id` | `owner_user_id` | `scope_kind/scope_id` | 说明 | +|---|---|---|---|---| +| 登录账号态 | 沿用 `AuthenticatedAccessToken.claims().user_id()` | 沿用现有行为,通常与 user_id 相同 | 沿用 route spec;User scope 使用真实用户 | Header 不能覆盖真实认证主体 | +| External API Key 态 | 不伪造登录用户,可为空 | `ExternalApiPrincipal.owner_user_id()` | User scope 使用 owner_user_id | 归属 API Key 所属账号 | +| 无认证的公开/站点请求 | 为空 | 为空 | 沿用 Site/公开 route spec | 只在已有公开 route spec 时记录 | + +第一阶段不把 API Key 明文写入 metadata。`key_id` 是安全的内部标识,但只有在后续明确需要按单个 Key 统计或审计时才增加 `externalApiKeyId`,不作为本期客户端交接前提。 + +### 4.4 成功与失败语义 + +本期先保留当前 route tracking 的成功响应语义: + +- 2xx 成功响应按 route spec 写入 tracking。 +- 3xx、4xx、5xx 和 transport failure 不因为 AGC 标记而自动新增 route 事件。 +- 生成提交、异步轮询、重试请求使用同一标记;是否落库仍由现有 route tracking 规则决定。 + +如果产品后续要求统计失败调用,应另立失败事件的 event key、幂等键、容量和报表口径,不在本期隐式扩展。 + +## 5. 推荐实现:扩展现有 route tracking + +本期采用方案一:在现有 route tracking 上追加来源和主体信息,不创建通用 AGC 请求事件。 + +```text +请求 Header + → tracking middleware 在 next.run 前白名单解析 marker + → next.run(request) + → 从 response extensions 读取 AuthenticatedAccessToken / ExternalApiPrincipal + → 按实际 method + path 解析 RouteTrackingSpec + → 计算 user/owner/scope + → 在现有 metadata 对象追加 client=agc + → 复用现有 outbox、tracking_event、tracking_daily_stat + → 现有后台 tracking 查询读取 metadata_json +``` + +### 5.1 为什么不新增通用 AGC 事件 + +- 不会与现有 route event 重复计数。 +- 不会改变 `tracking_daily_stat` 的 event key 和历史统计口径。 +- 仍能看到实际 path、method、status、operation 和 client 的组合。 +- 继续复用现有 outbox、SpacetimeDB procedure 和后台原始事件查询。 +- 新增或确认 AGC 路由时只需补齐 route spec 和测试,不需要引入第二套事件系统。 + +代价是 route coverage 必须显式审计;因此阶段 3 将以 AGC 客户端调用清单和 External v1 router/OpenAPI 交叉核对,禁止“标记已经发送但主站没有 route spec”的漏项。 + +### 5.2 实现边界 + +推荐保持最小抽象: + +- `app.rs` 在消费 request 之前解析 marker,并从 response extensions 取两类主体。 +- `tracking.rs` 提供小型白名单解析函数和主体归属逻辑。 +- `build_route_tracking_metadata` 只在有效 marker 时追加 `client`,不重写既有字段。 +- `record_route_tracking_event_after_success` 继续负责 route spec、outbox 和失败日志策略。 +- 不把 marker 放进 `RequestContext`,除非阶段 1 证明同一请求的其他统一 tracking 入口确实需要它;避免扩大公共上下文结构。 + +## 6. Route 覆盖范围 + +### 6.1 必须覆盖的账号态路径 + +按 AGC 当前扫描清单,至少核对并覆盖: + +- `/api/auth/*`:当前登录用户查询、refresh、登录入口、发送验证码、手机号登录、logout 等实际调用。 +- `/api/profile/*`:dashboard、recharge center、recharge order/confirm、wallet ledger、API Key 管理。 +- `/api/assets/*`:direct-upload-tickets、objects/confirm、read-url、read-bytes 以及 AGC 实际使用的资产操作。 +- `/api/editor/*`:projects、assets/library、assets/folders、项目 resources、图片/编辑/去背景/图集/角色动画/视频/音效/BGM 生成和轮询相关内部路径。 +- `/api/runtime/external-generation/jobs/{operationId}`:账号态生成任务轮询。 + +已有 route spec 的 event key 和 scope 语义保持不变;新增项使用稳定、可读、与路径/operation 对应的 event key。 + +### 6.2 必须覆盖的 External API Key 业务路径 + +按 `server-rs/crates/api-server/src/modules/external_api.rs` 和实际 AGC 使用情况核对: + +- `/api/external/v1/assets/direct-upload-tickets` +- `/api/external/v1/assets/objects/confirm` +- `/api/external/v1/assets/read-url` +- `/api/external/v1/editor/projects` 及项目读取/资源登记相关路径 +- `/api/external/v1/editor/assets/library`、folders、asset CRUD 中实际被 AGC 使用的路径 +- `/api/external/v1/editor/images/generations` +- `/api/external/v1/editor/images/edits` +- `/api/external/v1/editor/images/background-removals` +- `/api/external/v1/editor/icon-spritesheets/generations` +- `/api/external/v1/editor/character-animations/generations` +- `/api/external/v1/editor/videos/generations` +- `/api/external/v1/editor/audios/sound-effects/generations` +- `/api/external/v1/editor/audios/background-music/generations` +- `/api/external/v1/generations/{operationId}` + +External v1 路由的 tracking 必须记录外部实际 path,不把它改写成 `/api/editor`、`/api/assets` 或 `/api/runtime`。 + +### 6.3 明确排除的 External v1 路径 + +以下是公开发现或集成协议入口,不属于普通 AGC 主站业务调用,本期不新增普通 route tracking spec: + +- `GET /api/external/v1/openapi.json` +- `GET /api/external/v1/agent-integration.json` +- `GET /api/external/v1/skill/SKILL.md` +- `GET /api/external/v1/skill.zip` +- `POST /api/external/v1/mcp` + +如果未来 AGC 明确接入远程 MCP/Skill,再单独定义集成流量的事件语义,不把它们隐式混入普通业务统计。 + +## 7. 数据库与后台承接 + +### 7.1 数据库 + +沿用现有链路: + +```text +TrackingEventDraft.metadata + → RuntimeTrackingEventInput.metadata_json + → tracking outbox + → record_tracking_event procedure + → tracking_event.metadata_json +``` + +不增加字段、不改 migration、不重新生成 bindings。现有 JSON object 校验足以容纳 `client` 键;`tracking_daily_stat` 继续按原 event key/scope/day 聚合,不按 client 另建统计表。 + +### 7.2 后台 + +现有后台 tracking 查询已经返回 `metadata_json`,第一阶段只做数据可见性验证: + +- 管理员能在原始事件详情中看到 `client: "agc"`。 +- 原有 event key、scope、user/owner、日期查询不回归。 +- 不新增客户端筛选器、导出列或新的后台路由。 + +如果后续需要高频按 client 筛选,再评估新增结构化列和索引;那将是独立 schema 变更,不应在本期偷偷引入。 + +## 8. 安全与兼容性约束 + +- 来源 Header 是可伪造的审计标签,不能当作安全边界。 +- 主体必须来自已验证的 `AuthenticatedAccessToken` 或 `ExternalApiPrincipal`,不能信任请求体 owner、客户端账本或 Header 中的身份。 +- 不记录 Bearer token、API Key 原文、Cookie、签名 URL 或请求体中的敏感字段。 +- Header 缺失/未知时继续走原有业务和 tracking 逻辑,只是不追加 `client`。 +- metadata 合并必须保留既有资产嵌套字段,不得用新对象覆盖旧 metadata。 +- outbox 入队、SpacetimeDB 写入失败继续只记录 warning,不阻断主业务响应。 +- 不修改 route normalization、dynamic id 归一化、event id 幂等或 daily stat 聚合规则。 + +## 9. 定向测试方案 + +### 9.1 Marker 解析 + +表驱动测试覆盖: + +- `X-Genarrative-Client: agc` → `Some("agc")`。 +- Header 名大小写变化 → 仍识别。 +- 缺失、空值、首尾空白、`AGC`、未知值 → 未标记。 + +### 9.2 Metadata 与主体归属 + +- 有效 marker 的账号态成功路由:metadata 含 `client: "agc"`,user_id/owner_user_id 保持真实用户。 +- 有效 marker 的 External v1 成功路由:metadata 含 `client: "agc"`,owner_user_id 等于 `ExternalApiPrincipal.owner_user_id()`,scope_id 不落到 `anonymous`。 +- 未标记请求:metadata 不含 `client`。 +- 请求体伪造 owner 或 marker 不改变主体归属。 +- metadata 原有 route/method/status/operation 和资产嵌套字段仍存在。 + +### 9.3 Route 覆盖 + +- 当前 AGC 调用清单中的账号态路径全部能解析到 route spec。 +- 当前 AGC 使用的 External v1 业务路径全部能解析到 route spec。 +- `/api/external/v1` 发现、Skill 和 MCP 路径不被普通业务 spec 误收录。 +- dynamic project/asset/operation ID 仍按现有 normalize 规则归一化。 + +### 9.4 持久化与后台读取 + +- 构造 tracking event input 后,`metadata_json` 是合法 JSON object 且含 `client`。 +- outbox 入队/回退直写路径不丢失 `client`。 +- tracking_event 读回及后台 tracking API 解析不丢失 metadata。 +- 现有 daily stat、event id 幂等和失败不阻断语义保持不变。 + +### 9.5 状态码语义 + +至少保留一组回归: + +- 2xx 成功响应写入 route tracking。 +- 4xx/5xx 不因为 marker 自动新增成功 route event。 +- 认证失败不伪造 ExternalApiPrincipal 或用户归属。 + +## 10. 实施顺序 + +1. 阶段 0:确认基线、交接契约、路径边界和不做项。 +2. 阶段 1:加入 marker 白名单解析,并将解析结果传入现有 route tracking。 +3. 阶段 2:接入 `AuthenticatedAccessToken` / `ExternalApiPrincipal`,完成 owner/user/scope 归属和 metadata 合并。 +4. 阶段 3:按 AGC 调用清单补齐账号态与 External v1 业务 route spec,明确排除发现/MCP。 +5. 阶段 4:验证 outbox、SpacetimeDB `tracking_event` 和现有后台原始查询,无 schema 变更。 +6. 阶段 5:完成 marker、主体、route 覆盖、状态码、安全和持久化定向测试。 +7. 阶段 6:执行最终门禁,更新交接材料并把结果回传 Issue #225;不要求 #226 回改。 + +## 11. 完成判据 + +只有同时满足以下条件,#225 才算完成: + +- 主站精确识别 `X-Genarrative-Client: agc`,未知/缺失值不拒绝请求。 +- 有效标记进入现有 route tracking metadata,写入 `client: "agc"`。 +- 已有 route、method、status、operation、资产 metadata 和 event key 语义不丢失。 +- 登录账号态按真实用户归属,External API Key 态按 `owner_user_id` 归属。 +- 当前 AGC 实际使用的账号态和 External v1 业务路径均有 tracking spec。 +- External v1 discovery/Skill/MCP、OSS、签名下载、Provider、搜索、loopback、更新下载不被误纳入普通 AGC 业务统计。 +- outbox、SpacetimeDB 写入、daily stat、幂等和失败不阻断语义无回归。 +- 后台现有 tracking 查询可以看到 `metadata_json.client`,无需新增 schema 或页面。 +- 定向测试和必要的格式/编码/空白检查通过。 + +## 12. 评审与提交节奏建议 + +建议按阶段分组提交或至少在一个 PR 中保持以下逻辑顺序: + +1. 阶段 0 文档与契约冻结。 +2. marker 解析、metadata 合并和账号态主体接入。 +3. External API Key 主体接入与 External v1 route spec。 +4. 持久化/后台读取验证和定向测试。 +5. 最终文档、交接评论和门禁记录。 + +每个阶段先通过自身验收,再进入下一阶段;不需要等待 #226 再次修改,也不需要先新增数据库字段。 diff --git a/local-docs/【实施计划】Issue225-AGC主站请求标记埋点分阶段验收-2026-09-02.md b/local-docs/【实施计划】Issue225-AGC主站请求标记埋点分阶段验收-2026-09-02.md new file mode 100644 index 000000000..e6484b71f --- /dev/null +++ b/local-docs/【实施计划】Issue225-AGC主站请求标记埋点分阶段验收-2026-09-02.md @@ -0,0 +1,391 @@ +# Issue #225:AGC 主站请求标记埋点分阶段实施与验收计划 + +更新时间:2026-09-02 +关联 Issue: + +- `#225 添加客户端埋点统计`:本计划全部实施范围 +- `#226 添加客户端特殊标识`:客户端已完成;仅作为固定交接输入 + +当前状态:阶段 0、阶段 1、阶段 2、阶段 3、阶段 4、阶段 5、阶段 6 已完成。阶段 1~5 的实现已提交:`2296f79fd`、`2a23ba657`、`01159a269`、`c75c61521`、`c5170669e`;日登录埋点构造器回归修复为 `0a06b4988`。阶段 6 仅补充最终验收、交接和门禁文档,不新增生产代码。 + +## 1. 交付目标 + +主站识别有效的: + +```http +X-Genarrative-Client: agc +``` + +并在已有 route tracking 的 `tracking_event.metadata_json` 中记录: + +```json +{ + "client": "agc" +} +``` + +同时按真实认证主体归属:登录账号态使用 `AuthenticatedAccessToken` 的用户,External API Key 态使用 `ExternalApiPrincipal.owner_user_id()`;不新增平行事件体系,不改鉴权、计费、幂等和 API 契约。 + +## 2. 固定不做项 + +本计划不修改: + +- AGC 客户端 Header 注入和 Rust Client factory。 +- `tracking_event` schema、migration、bindings 或索引。 +- `/api/external/v1` OpenAPI、DTO、HTTP 方法、状态码和鉴权。 +- 失败请求的全新 tracking 事实。 +- 公开 discovery、Skill、MCP、OSS、签名下载、Provider、搜索、loopback 和更新下载的普通 AGC 业务统计。 +- 后台新筛选器或报表页面;第一阶段只复用原始 metadata 查询。 + +## 3. 阶段总览 + +```text +阶段 0 现状基线与交接契约冻结(已完成) + ↓ +阶段 1 Marker 解析与 tracking 入口接入 + ↓ +阶段 2 认证主体归属与 metadata 合并 + ↓ +阶段 3 账号态 / External v1 route coverage + ↓ +阶段 4 tracking_event、outbox 与后台读取验证 + ↓ +阶段 5 定向测试、安全边界与语义回归 + ↓ +阶段 6 最终门禁与 #226 交接收口 +``` + +每阶段先通过本阶段验收,再进入下一阶段。阶段 1~6 均只改主站或相关测试/文档,不要求 #226 回改。 + +## 4. 阶段 0:现状基线与交接契约冻结 + +### 4.1 工作内容 + +- 确认当前分支、HEAD、工作树和 `origin/master` 关系。 +- 核对 `app.rs` tracking middleware、`tracking.rs` route spec、outbox 和 `tracking_event` 写入链路。 +- 核对 `AuthenticatedAccessToken` 和 `ExternalApiPrincipal` 的 response extension 传播。 +- 读取 AGC 调用清单和 External v1 router,冻结正向/排除路径。 +- 固定 Header、metadata、未知值行为、主体归属和成功响应语义。 + +### 4.2 阶段边界 + +- 不修改生产代码。 +- 不新增 schema 或后台代码。 +- 不把 Issue226 的客户端实现重新打开。 + +### 4.3 验收标准 + +- [x] 当前基线和代码缺口已记录。 +- [x] `X-Genarrative-Client: agc` 的识别规则已固定。 +- [x] `metadata_json.client = "agc"` 的落库形态已固定。 +- [x] 账号态与 External API Key 态的主体归属已固定。 +- [x] External v1 discovery/Skill/MCP 与第三方边界已列明。 +- [x] 未修改主站生产代码、schema、OpenAPI 或后台。 + +### 4.4 阶段产物 + +- [【阶段验收】Issue225阶段0现状基线与边界冻结-2026-09-02.md](C:/projects/narrative/Genarrative/local-docs/【阶段验收】Issue225阶段0现状基线与边界冻结-2026-09-02.md) +- 本实施方案。 +- 本分阶段验收计划。 + +## 5. 阶段 1:Marker 解析与 tracking 入口接入 + +### 5.1 工作内容 + +修改范围限定在 `api-server` tracking 入口及定向测试: + +- 在 `record_api_tracking_after_success` 的 `next.run(request)` 之前读取请求 Header。 +- 增加白名单解析:首尾空白可清理,值必须精确为小写 `agc`。 +- 将解析结果传入现有 `record_route_tracking_event_after_success`。 +- 不把原始未知值、完整 Header、token 或请求体写入日志/metadata。 + +### 5.2 阶段验收 + +定向单元测试必须证明: + +- 有效 `agc` 能被识别。 +- Header 名大小写变化仍能识别。 +- 缺失、空值、`AGC`、未知值均按未标记处理。 +- 未标记请求仍走原有 route tracking 逻辑,不被拒绝。 +- 认证、响应状态和现有 request context 行为不变。 + +### 5.3 阶段完成判据 + +- [x] marker 只在统一 tracking 入口解析一次。 +- [x] 业务 handler 无需逐个读取 Header。 +- [x] 尚未接入主体归属和 External v1 route 扩展之外,不发生无关改动。 + +### 5.4 阶段 1 实现与验证记录 + +- `app.rs` 在 `next.run(request)` 前调用统一 marker 解析函数,并把解析结果传入 route tracking。 +- `tracking.rs` 只接受 Header 名 `X-Genarrative-Client`(HTTP 名称大小写不敏感)和值 `trim` 后精确等于小写 `agc` 的请求。 +- 解析结果以内部 `TrackingClientMarker::Agc` 保存到 `TrackingEventDraft`,本阶段不改变 metadata 内容;metadata 合并由阶段 2 完成。 +- 未标记、未知值和无效 Header 值均得到 `None`,不会写入日志,也不会拒绝请求。 +- 定向测试:`tracking::tests::tracking_client_marker_accepts_only_trimmed_lowercase_agc` 通过。 +- `cargo fmt --manifest-path server-rs/crates/api-server/Cargo.toml`、`git diff --check` 通过。 +- 当前未运行完整 `api-server` 测试套件;现有编译过程已通过,完整测试按阶段 5/CI 统一执行。 + +## 6. 阶段 2:认证主体归属与 metadata 合并 + +### 6.1 工作内容 + +- 从最终 response extensions 读取 `AuthenticatedAccessToken`。 +- 同时读取 `ExternalApiPrincipal`。 +- 账号态沿用现有 user_id/owner_user_id 语义。 +- External API Key 态设置 `owner_user_id = principal.owner_user_id()`,User scope 的 scope_id 使用 owner,避免回退到 `anonymous`。 +- 在 `build_route_tracking_metadata` 原有 JSON object 上追加 `client: "agc"`,保留原字段和资产嵌套 metadata。 +- 第一阶段不写 API Key 明文;不新增 `externalApiKeyId`,除非评审明确要求单 Key 统计。 + +### 6.2 阶段验收 + +- 账号态成功请求的 user_id 与现有 access token 用户一致。 +- External v1 成功请求的 owner_user_id 与 `ExternalApiPrincipal.owner_user_id()` 一致。 +- Header 不能覆盖、伪造或替换认证主体。 +- 有效 marker 时 metadata 含 `client: "agc"`;无效/缺失时不含该键。 +- `route`、`method`、`status`、`operation` 和已有嵌套字段均保留。 +- token、API Key 明文和签名 URL 不进入 metadata 或日志。 + +### 6.3 阶段完成判据 + +- [x] route tracking draft 在两类主体下都能生成正确的结构化归属。 +- [x] metadata 合并逻辑有独立测试,不能只依赖端到端偶然覆盖。 +- [x] 不改变 event id、outbox 和 daily stat 逻辑。 + +### 6.4 阶段 2 实现与验证记录 + +- `app.rs` 从最终 response extensions 同时读取 `AuthenticatedAccessToken` 和 `ExternalApiPrincipal`。 +- `tracking.rs` 优先使用已验证的 External API Key owner;External API Key 请求不伪造 `user_id`,User scope 的 `scope_id` 使用 `owner_user_id`。 +- 账号态继续使用 access token claims 的 user id,同时写入 `user_id` 和 `owner_user_id`,保持原有语义。 +- `build_route_tracking_metadata` 只在有效 marker 时追加 `client: "agc"`,保留 route、method、status、operation 和 asset 嵌套 metadata;无 marker 不写 client。 +- 定向测试覆盖账号态主体、External API Key owner 优先级、User scope owner 和 metadata 字段保留。 +- 当前未修改 External v1 route spec、schema、OpenAPI、outbox 或后台页面。 + +## 7. 阶段 3:账号态与 External v1 Route Coverage + +### 7.1 工作内容 + +以 `local-docs/【扫描清单】AGC客户端主站调用与可标记点-2026-09-01.md` 和 `modules/external_api.rs` 为输入,补齐显式 route spec: + +- 账号态 `/api/auth/*`、`/api/profile/*`、`/api/assets/*`、`/api/editor/*`、`/api/runtime/external-generation/jobs/{id}` 的实际 AGC 路径。 +- External API Key 态 `/api/external/v1/assets/*`、`/editor/*`、`/generations/{id}` 的实际业务路径。 +- 生成提交、轮询、项目读取/资源登记、素材库、上传凭证、对象确认、换签读取等当前已确认调用。 +- 每个新增 spec 固定 event key、module key、scope kind 和动态路径归一化方式。 + +同时明确不加入: + +- `/api/external/v1/openapi.json`。 +- `/api/external/v1/agent-integration.json`。 +- `/api/external/v1/skill/SKILL.md`、`skill.zip`。 +- `/api/external/v1/mcp`。 + +### 7.2 阶段验收 + +- AGC 当前实际调用清单中的每个 method + path 都能解析到 spec。 +- External v1 记录实际外部 path,不被映射为内部 `/api/editor` 等路径。 +- 发现/MCP 路径不会误进入普通业务 route tracking。 +- project/asset/operation 动态 ID 仍按现有规则归一化。 +- 原有 route spec 的 event key、scope 和统计口径不变。 + +### 7.3 阶段完成判据 + +- [x] 有一张可审计的“AGC 调用清单 → route spec → event key”矩阵。 +- [x] 新增路径均有 resolver 测试;未确认的 OpenAPI 潜在路径单独记录,不混入已实现范围。 +- [x] 没有用 catch-all `agc_client_request` 取代显式 route tracking。 + +### 7.4 阶段 3 实现与验证记录 + +- `tracking.rs` 补齐当前 AGC 实际使用的账号态 `/api/auth`、`/api/profile`、`/api/assets`、`/api/editor`、`/api/runtime/external-generation/jobs/{id}` 路径。 +- `tracking.rs` 补齐当前 AGC 实际使用的 External v1 资产、项目、素材库、生成提交和任务轮询路径;External v1 使用独立的实际外部 path 进入 metadata,不映射回账号态内部路径。 +- 上传票据和对象确认已有详细资产事件,route spec 以 `handled_by_existing_event` 标记为复用现有事件,统一 route tracking 不再重复写入;tracking middleware 将有效 marker 放入 request extensions,由共享资产 handler 将 `client: "agc"` 合并到既有资产 metadata,并补齐实际 route/method/status/operation。 +- 共享资产事件沿用真实主体:账号态写入 user/owner;External API Key 态只写 `owner_user_id`,不伪造登录 `user_id`,User scope 使用 owner。 +- 扩展动态路径归一化的静态段白名单,使 project、generation、asset 和 external v1 路径按既有 `{id}` 规则正确匹配;未引入 catch-all AGC 事件。 +- discovery、agent-integration、Skill、skill.zip 和 MCP 路径没有业务 route spec。 + +已执行定向验证: + +```text +cargo fmt --manifest-path server-rs/crates/api-server/Cargo.toml -- --check +cargo check --locked --manifest-path server-rs/Cargo.toml -p api-server +cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server tracking::tests:: -- --nocapture +cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server assets::tests::asset_tracking_metadata_receives_only_valid_agc_marker -- --nocapture +git diff --check +``` + +结果:tracking 定向测试 13 个通过,资产 marker/route metadata 与 External owner 归属测试 2 个通过,api-server 编译检查通过。 + +## 8. 阶段 4:tracking_event、outbox 与后台读取验证 + +### 8.1 工作内容 + +- 验证 `TrackingEventDraft.metadata` 经 `build_tracking_event_input` 后仍是合法 JSON object。 +- 验证本机 tracking outbox 入队和 SpacetimeDB 回退直写都保留 `client`。 +- 验证既有 `tracking_event` 行和 `tracking_daily_stat` 聚合未改变。 +- 验证现有后台 tracking API 返回 `metadata_json` 中的 `client`。 +- 不新增 schema/migration/bindings,不修改后台页面。 + +### 8.2 阶段验收 + +- AGC 成功事件可在 tracking_event 读回 `client: "agc"`。 +- unmarked 事件不会被补写 `client`。 +- owner_user_id、user_id、scope_id、event_key 和 occurred_at 不丢失。 +- 后台原始事件查询和已有 event key 查询不回归。 +- `npm run check:spacetime-schema` 不因本阶段产生 schema 变更;若未改 schema,可记录为无需运行。 + +### 8.3 阶段完成判据 + +- 至少有一条账号态和一条 External Key 态从 tracking draft 到后台读取的完整证据。 +- 已确认第一阶段不需要新增数据库字段。 + +### 8.4 阶段 4 实现与验证记录 + +- `api-server` tracking 测试分别构造账号态和 External API Key 态输入,验证 `TrackingEventDraft → build_tracking_event_input` 后 metadata 仍是合法 JSON object,`client: "agc"`、route、status、event key、module、scope 和 user/owner 字段均保留;未标记输入不补写 `client`。 +- tracking outbox 测试使用 External v1 AGC 事件完成 NDJSON 入队和读取 round-trip,验证 `metadata_json.client`、实际 External v1 route、`owner_user_id` 和“不伪造 `user_id`”均保留。 +- `spacetime-client` active mapper 测试验证送往生成绑定的 `RuntimeTrackingEventInput` 不丢失 `client`、External owner、scope、event key 和 module;module-runtime 的统一输入校验测试验证 metadata 必须是 JSON object,标记对象可正常通过。由于本地未启动 SpacetimeDB,未把确定性 input/mapper 验证扩大解释为真实远端库 E2E。 +- 后台 tracking SQL response parser 测试分别覆盖账号态与 External API Key 态,并增加 draft/input → SQL row → parser 组合回归,验证管理员现有原始事件读取能保留 `metadata_json.client`、实际 route 和 user/owner 归属。 +- 未修改 tracking schema、migration、bindings、outbox 失败回退策略、daily stat 聚合逻辑或后台页面;SpacetimeDB 的实际入库仍复用既有 `record_tracking_event` / `record_tracking_events` procedure,未新增字段。 + +已执行定向验证: + +```text +cargo fmt --manifest-path server-rs/Cargo.toml --all -- --check +cargo test --locked --manifest-path server-rs/Cargo.toml -p module-runtime tracking_input_ -- --nocapture +cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server tracking::tests:: -- --nocapture +cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server tracking_outbox::tests:: -- --nocapture +cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server admin::tests::parse_admin_tracking_events_sql_response_ -- --nocapture +cargo test --locked --manifest-path server-rs/Cargo.toml -p spacetime-client tracking_input_mapper_preserves_agc_metadata_and_external_owner -- --nocapture +``` + +结果:module-runtime 2 个、api-server tracking 15 个、tracking outbox 8 个、后台 readback parser 5 个、spacetime-client mapper 1 个定向测试全部通过;格式检查通过。未运行完整工作区测试和 `npm run check:spacetime-schema`,因为本阶段未修改 schema。 + +## 9. 阶段 5:定向测试、安全边界与语义回归 + +### 9.1 工作内容 + +补齐并运行与改动直接相关的测试: + +- marker 解析表驱动测试。 +- route metadata 合并测试。 +- 账号态主体归属测试。 +- ExternalApiPrincipal owner 归属测试。 +- External v1 route resolver 覆盖测试。 +- discovery/MCP 排除测试。 +- 2xx 记录、4xx/5xx 不新增成功 route 事件测试。 +- outbox/SpacetimeDB input 和后台 tracking readback 测试。 + +CI 已覆盖且与本改动无直接关系的全量测试可交给 CI;本阶段仍必须运行能直接证明本 Issue 契约的 targeted tests。 + +### 9.2 阶段验收 + +- 有效 marker 正向场景全部通过。 +- 缺失/空值/未知 marker 负向场景全部通过。 +- 登录账号态和 External Key 态主体归属均通过。 +- 未发生鉴权、状态码、幂等、请求体或异步轮询回归。 +- 测试输出不包含 token、API Key 明文、Cookie、签名 URL 或本地私密路径。 + +### 9.3 阶段完成判据 + +- 测试矩阵覆盖“标记/未标记 × 账号态/External Key 态 × 已覆盖/排除路由”。 +- 所有失败都能定位到 marker、主体、route、持久化或后台读取中的具体层。 + +### 9.4 阶段 5 实现与验证记录 + +- `tracking.rs` 抽出 `should_record_route_tracking` 判定,明确只有 2xx 成功响应且不是已有详细资产事件的路由才进入统一成功 route tracking;4xx/5xx 和上传确认等复用事件均不新增重复成功事件。 +- 新增 2xx、4xx/5xx 状态矩阵测试,覆盖 `OK / CREATED / ACCEPTED / NO_CONTENT` 与认证、权限、客户端、服务端失败状态。 +- 新增 `app.rs` 路由回归:带有效 `X-Genarrative-Client: agc` 的未认证业务请求仍返回 `401`,marker 不绕过鉴权,也不改变失败状态。 +- 新增 route metadata 安全边界测试,确认统一 route metadata 只包含既有 route/method/status/operation 和可选 `client`/资产字段,不出现 authorization、token、API Key、Cookie、签名 URL 或 request body 字段。 +- 阶段 4 的账号态、External API Key、outbox、后台 readback 和 mapper 测试全部复跑;SpacetimeDB 既有 event-id 幂等回归也通过。未修改鉴权、请求体、异步轮询、event id、daily stat 或失败回退实现。 + +已执行定向验证: + +```text +cargo fmt --manifest-path server-rs/Cargo.toml --all -- --check +cargo test --locked --manifest-path server-rs/Cargo.toml -p module-runtime tracking_input_ -- --nocapture +cargo test --locked --manifest-path server-rs/Cargo.toml -p spacetime-client tracking_input_mapper_preserves_agc_metadata_and_external_owner -- --nocapture +cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server tracking::tests:: -- --nocapture +cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server app::tests::agc_marker_does_not_bypass_authentication_or_change_failure_status -- --nocapture +cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server assets::tests::asset_tracking_metadata_receives_only_valid_agc_marker -- --nocapture +cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server assets::tests::external_asset_tracking_keeps_owner_without_forging_user -- --nocapture +cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server tracking_outbox::tests:: -- --nocapture +cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server admin::tests::parse_admin_tracking_events_sql_response_ -- --nocapture +cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server admin::tests::tracking_inputs_round_trip_to_admin_readback_for_both_subjects -- --nocapture +cargo test --locked --manifest-path server-rs/Cargo.toml -p spacetime-module duplicate_tracking_event_ids_are_treated_as_idempotent_replays -- --nocapture +``` + +结果:module-runtime 2 个、spacetime-client 1 个、api-server tracking 18 个、app 鉴权回归 1 个、资产 marker/owner 2 个、tracking outbox 8 个、后台 readback 5 个、spacetime-module 幂等 1 个定向测试全部通过;格式检查通过。未运行与本 Issue 无直接关系的完整工作区测试,按约定交给 CI。 + +## 10. 阶段 6:最终门禁与 #226 交接收口 + +### 10.1 工作内容 + +- 汇总阶段 1~5 的测试和 readback 证据。 +- 更新本方案、验收记录和 Issue #225 评论草案。 +- 复核 #226 交接契约不需要客户端回改。 +- 检查 diff、编码、格式、敏感信息和无关文件。 + +### 10.2 最终门禁 + +按实际改动范围运行: + +- `cargo fmt --manifest-path server-rs/Cargo.toml -- --check`。 +- `cargo test --locked -p api-server` 的 tracking/External v1 定向测试。 +- 必要时 `cargo check --locked -p api-server`。 +- `npm run check:encoding`。 +- `git diff --check`。 +- `git status --short`,确认没有构建产物、日志、凭据或无关文件。 + +未修改 schema 时不运行 schema 生成;若实现阶段意外需要 schema,必须停在阶段 4 重新确认迁移范围,不能顺手修改。 + +### 10.3 阶段完成判据 + +- 主站可以在现有 tracking_event metadata 中看到 `client: "agc"`。 +- 两类认证主体归属正确。 +- 当前 AGC 实际业务路径无漏记,发现/MCP/第三方边界无误记。 +- 后台现有查询可读,无需新 schema 或新页面。 +- 所有必要 targeted tests、格式/编码/空白检查通过。 +- 向 #225 交付固定 Header、metadata、主体归属、路由覆盖和验证证据;不要求 #226 回改。 + +### 10.4 阶段 6 实际执行与结果 + +阶段 6 以当前分支 `feat/agc_call_rec`、`HEAD=5aa38d9f3` 为基线;该提交已合并最新 `origin/master`(`8932f0b27`)。本阶段未修改 `server-rs`、SpacetimeDB schema、OpenAPI 或后台页面,只更新验收和交接文档。 + +最终门禁结果: + +| 验收项 | 命令/证据 | 结果 | +|---|---|---| +| api-server 编译 | `cargo check --locked --manifest-path server-rs/Cargo.toml -p api-server` | 通过;仅有仓库既有 dead-code warning | +| api-server tracking/资产/后台/outbox 定向测试 | `cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server tracking -- --nocapture` | 通过,43 tests passed | +| marker 不绕过鉴权回归 | `cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server app::tests::agc_marker_does_not_bypass_authentication_or_change_failure_status -- --nocapture` | 通过,1 test passed | +| module-runtime tracking input | `cargo test --locked --manifest-path server-rs/Cargo.toml -p module-runtime tracking_input_ -- --nocapture` | 通过,2 tests passed | +| spacetime-client mapper | `cargo test --locked --manifest-path server-rs/Cargo.toml -p spacetime-client tracking_input_mapper_preserves_agc_metadata_and_external_owner -- --nocapture` | 通过,1 test passed | +| SpacetimeDB event-id 幂等 | `cargo test --locked --manifest-path server-rs/Cargo.toml -p spacetime-module duplicate_tracking_event_ids_are_treated_as_idempotent_replays -- --nocapture` | 通过,1 test passed | +| Rust 格式 | `cargo fmt --manifest-path server-rs/Cargo.toml --all -- --check` | 通过 | +| 中文编码 | `npm run check:encoding` | 通过,5662 个文件 | +| Diff 空白与分支基线 | `git diff --check`、`git merge-base --is-ancestor origin/master HEAD` | 通过 | + +上述测试合计 48 个与本 Issue 直接相关的测试通过。完整工作区测试按用户约定交由 CI;本阶段未启动真实 SpacetimeDB,也未执行发布环境线上写入,因此最终证据是本地确定性 input/mapper、outbox、后台 parser 和 route tracking 回归,不把本地测试表述为线上 E2E。 + +### 10.5 阶段 6 交接结论 + +- 主站只对白名单值 `X-Genarrative-Client: agc` 追加 `metadata_json.client = "agc"`;缺失、空值、`AGC` 和未知值按未标记处理,不拒绝请求。 +- 账号态沿用真实 access token 用户;External API Key 态使用 `ExternalApiPrincipal.owner_user_id()`,不伪造 `user_id`。 +- 当前 AGC 实际账号态和 External v1 业务路径均有显式 route spec;动态 ID 继续归一化,External v1 保留实际外部 path。 +- OSS、签名下载、Provider、受控搜索、loopback、更新下载、公开 discovery/Skill/MCP 不进入普通 AGC 业务 route tracking。 +- 最终记录仍落在现有 SpacetimeDB `tracking_event.metadata_json`,经过本机 `server-rs/.data/tracking-outbox/` 临时缓冲后由既有 tracking procedure 写入;不新增 schema、索引、后台页面或独立事件体系。 +- `#226` 的客户端标记注入和 origin-safe redirect 契约已满足本 Issue 输入要求,不需要回改 #226 设计。 +- 另有一个独立于 #225 的客户端后续项:自定义 origin 使用显式默认端口或大写主机名时,`clientHttp.ts` 的字符串与 `URL.origin` 比较可能误判为跨 origin;它只会拒绝合法请求,不会泄漏 marker,应单独在 #226 跟踪。 + +阶段 6 详细验收记录与可直接粘贴到 Issue #225 的交付评论见: + +- [【阶段验收】Issue225阶段6最终门禁与交接收口-2026-09-02.md](C:/projects/narrative/Genarrative/local-docs/【阶段验收】Issue225阶段6最终门禁与交接收口-2026-09-02.md) + +## 11. 建议提交分组 + +建议按以下逻辑分组提交,便于逐阶段验收: + +1. 阶段 1:marker 解析与 tracking 入口。 +2. 阶段 2:主体归属与 metadata 合并。 +3. 阶段 3:route spec 与 External v1 覆盖。 +4. 阶段 4~5:持久化/后台验证、定向测试和安全回归。 +5. 阶段 6:验收记录、Issue 交接评论和最终门禁。 + +如仓库要求单提交,也应在 PR 描述中按上述五组列出,确保每组都有独立验收标准。 diff --git a/local-docs/【阶段验收】Issue225阶段5定向测试与安全边界-2026-09-02.md b/local-docs/【阶段验收】Issue225阶段5定向测试与安全边界-2026-09-02.md new file mode 100644 index 000000000..ce603b55f --- /dev/null +++ b/local-docs/【阶段验收】Issue225阶段5定向测试与安全边界-2026-09-02.md @@ -0,0 +1,97 @@ +# Issue225 阶段 5:定向测试与安全边界 + +更新时间:2026-09-02 +关联分支:`feat/agc_call_rec` + +## 1. 阶段交付结果 + +本阶段补齐 Issue #225 直接相关的状态码、鉴权、安全 metadata 和幂等回归,确认 `X-Genarrative-Client: agc` 只是来源审计标签,不改变认证、权限、计费、请求体、异步轮询或成功事件语义。 + +## 2. 新增回归 + +### 2.1 成功状态语义 + +`tracking.rs` 新增 `should_record_route_tracking` 判定测试: + +- `200 / 201 / 202 / 204` 的显式业务 route 可以进入成功 tracking; +- `400 / 401 / 403 / 404 / 500 / 502` 即使带 AGC marker,也不会新增成功 route event; +- 已由详细资产事件处理的上传票据和对象确认不重复生成统一 route event。 + +### 2.2 鉴权边界 + +`app.rs` 通过真实 router 发起未认证的: + +```text +GET /api/editor/projects +X-Genarrative-Client: agc +``` + +响应仍为 `401 Unauthorized`。marker 不会获得权限,也不会改变失败状态。 + +### 2.3 metadata 安全边界 + +route metadata 测试确认不会写入: + +```text +authorization +accessToken +token +apiKey +cookie +signature +signedUrl +requestBody +``` + +有效 marker 只产生固定的 `client: "agc"`,并保留既有 route/method/status/operation 与资产嵌套字段。 + +### 2.4 既有链路复回归 + +阶段 4 的以下路径在阶段 5 再次顺序执行: + +- `TrackingEventDraft → RuntimeTrackingEventInput`; +- tracking outbox NDJSON round-trip; +- `spacetime-client` mapper; +- module-runtime metadata object 校验; +- 后台 tracking SQL response parser; +- 账号态 / External API Key owner 归属; +- SpacetimeDB tracking event-id 幂等回归。 + +## 3. 验收结果 + +- [x] marker 有效值、缺失值、空值、大小写和未知值测试通过。 +- [x] 账号态 `user_id = owner_user_id` 归属测试通过。 +- [x] External API Key 态只写真实 `owner_user_id`,不伪造 `user_id`,scope 使用 owner。 +- [x] 当前账号态和 External v1 业务 route coverage 测试通过。 +- [x] discovery、Skill、MCP 排除测试通过。 +- [x] 2xx 成功记录和 4xx/5xx 不新增成功 route event 测试通过。 +- [x] AGC marker 不绕过鉴权、不改变失败状态。 +- [x] route metadata 不包含 token、API Key、Cookie、签名 URL 或请求体字段。 +- [x] outbox、mapper、runtime input 和后台 readback 回归通过。 +- [x] event-id 幂等回归通过。 + +## 4. 定向验证结果 + +| 测试范围 | 通过数 | +|---|---:| +| `module-runtime` tracking input | 2 | +| `spacetime-client` tracking mapper | 1 | +| `api-server` tracking | 18 | +| `api-server` app 鉴权回归 | 1 | +| `api-server` 资产 marker / owner | 2 | +| `api-server` tracking outbox | 8 | +| `api-server` 后台 readback | 5 | +| `spacetime-module` event-id 幂等 | 1 | +| 合计 | 38 | + +另外通过: + +- `cargo fmt --manifest-path server-rs/Cargo.toml --all -- --check` +- `git diff --check` +- `npm run check:encoding` + +未运行与本 Issue 无直接关系的完整工作区测试,按约定交给 CI。没有启动真实 SpacetimeDB,因此没有把确定性 procedure input/mapper 和 admin parser 回归扩大解释为远端数据库 E2E。 + +## 5. 后续阶段 + +阶段 6 汇总阶段 1~5 的证据,复核 #226 交接契约、执行最终门禁并准备 Issue #225 交付说明。阶段 5 代码已提交为 `c5170669e`;日登录埋点构造器回归修复补充提交为 `0a06b4988`。 diff --git a/local-docs/【阶段验收】Issue225阶段6最终门禁与交接收口-2026-09-02.md b/local-docs/【阶段验收】Issue225阶段6最终门禁与交接收口-2026-09-02.md new file mode 100644 index 000000000..2b586b3be --- /dev/null +++ b/local-docs/【阶段验收】Issue225阶段6最终门禁与交接收口-2026-09-02.md @@ -0,0 +1,144 @@ +# Issue225 阶段6:最终门禁与 #226 交接收口 + +更新时间:`2026-09-02` +实施范围:`#225 添加客户端埋点统计` +交接输入:`#226 添加客户端特殊标识` + +执行结论:阶段 6 通过,Issue #225 的主站接收、tracking metadata、主体归属、路由覆盖、持久化/后台读取验证和安全语义回归已收口。本阶段没有新增生产功能,没有修改 #226 客户端实现,不需要 #226 回改设计。 + +## 1. 阶段边界 + +本阶段只完成: + +1. 汇总阶段 1~5 的实现提交、定向测试和 readback 证据。 +2. 复核 `#226` 交接的 Header、origin、认证和第三方边界。 +3. 执行与本 Issue 直接相关的最终编译、测试、格式、编码和空白门禁。 +4. 更新实施方案、分阶段计划和 Issue #225 交付评论草案。 + +本阶段明确不做: + +- 不修改 `AGC` 客户端的 `fetchClientHttp`、Rust Client factory、请求终结器或 redirect policy。 +- 不新增 `tracking_event` 列、索引、migration、生成 bindings 或新的统计表。 +- 不修改 `/api/external/v1` 的 OpenAPI、DTO、HTTP 方法、状态码、鉴权或异步语义。 +- 不新增后台筛选器、报表页面或真实发布环境线上写入。 + +## 2. 当前仓库基线 + +阶段 6 开始时仓库状态: + +| 项目 | 结果 | +|---|---| +| 分支 | `feat/agc_call_rec` | +| HEAD | `5aa38d9f3`(已合并最新 `origin/master`) | +| `origin/master` | `8932f0b27` | +| 工作树 | 开始阶段 6 时干净;本记录及计划/方案更新属于本阶段待提交文档变更 | + +阶段 1~5 的实现提交: + +| 阶段 | 提交 | +|---|---| +| 阶段 1:marker 解析与 tracking 入口 | `2296f79fd` | +| 阶段 2:主体归属与 metadata | `2a23ba657` | +| 阶段 3:路由覆盖 | `01159a269` | +| 阶段 4:持久化与后台读取 | `c75c61521` | +| 阶段 5:安全与语义回归 | `c5170669e` | +| 日登录埋点构造器回归修复 | `0a06b4988` | + +## 3. 最终交付行为 + +### 3.1 Marker 与 metadata + +主站识别: + +```http +X-Genarrative-Client: agc +``` + +规则: + +- Header 名按 HTTP 规则大小写不敏感。 +- Header 值去除首尾空白后,必须精确等于小写 `agc`。 +- 缺失、空值、`AGC` 或未知值按未标记处理,不拒绝请求。 +- 有效标记只追加到已有成功 route tracking 的 `tracking_event.metadata_json`: + +```json +{ + "route": "/api/editor/projects", + "method": "GET", + "status": 200, + "operation": "listEditorProjects", + "client": "agc" +} +``` + +不写入 Header 原文、token、API Key、Cookie、签名 URL、请求体或项目绝对路径。 + +### 3.2 主体归属 + +- 登录账号态:使用已验证 access token 的真实用户,保留既有 `user_id`、`owner_user_id` 和 scope 语义。 +- External API Key 态:使用 `ExternalApiPrincipal.owner_user_id()`;不伪造登录 `user_id`,User scope 的 `scope_id` 使用 owner。 +- Header 只是来源审计标签,不能绕过认证、权限、计费或替换主体。 + +### 3.3 路由覆盖与排除 + +已覆盖的实际业务路径: + +- 账号态:`/api/auth/*`、`/api/profile/*`、`/api/assets/*`、`/api/editor/*`、`/api/runtime/*`。 +- External API Key 态:`/api/external/v1/*` 的当前资产、项目、素材库、生成提交和任务轮询业务路径。 +- 动态 project/generation/asset ID 继续按显式静态段规则归一化;External v1 metadata 保留实际外部 path。 +- 上传票据和对象确认沿用已有详细资产事件,不重复生成普通 route event,但会保留有效 AGC metadata。 + +明确排除: + +- OSS multipart 上传、签名 URL/OSS 媒体下载。 +- LLM/Codex Provider、AGC 受控搜索、loopback 工具桥。 +- 更新清单、更新包下载和任意外部网页请求。 +- `/api/external/v1/openapi.json`、`agent-integration.json`、Skill 文档/压缩包和 MCP 入口。 + +### 3.4 落库与读取位置 + +AGC 调用记录最终落在主站 SpacetimeDB 的 `tracking_event` 表,标识位于 `tracking_event.metadata_json.client`。普通 route tracking 默认先进入 api-server 本机: + +```text +server-rs/.data/tracking-outbox/active.ndjson +server-rs/.data/tracking-outbox/sealed-*.ndjson +``` + +worker 使用既有 `record_tracking_events_and_return` 批量 procedure 写入 SpacetimeDB;outbox 不可用时沿用同步 `record_tracking_event_and_return` 回退。后台 `GET /admin/api/tracking/events` 读取同一 `tracking_event` 的 `metadata_json`,当前没有单独的 `client=agc` 查询参数,需要从返回 JSON 中识别 `"client": "agc"`。 + +## 4. 最终门禁证据 + +| 验收项 | 命令 | 结果 | +|---|---|---| +| api-server 编译 | `cargo check --locked --manifest-path server-rs/Cargo.toml -p api-server` | 通过;仅仓库既有 warning | +| api-server tracking/资产/后台/outbox 定向测试 | `cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server tracking -- --nocapture` | 43 passed,0 failed | +| marker 不绕过鉴权回归 | `cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server app::tests::agc_marker_does_not_bypass_authentication_or_change_failure_status -- --nocapture` | 1 passed,0 failed | +| module-runtime tracking input | `cargo test --locked --manifest-path server-rs/Cargo.toml -p module-runtime tracking_input_ -- --nocapture` | 2 passed,0 failed | +| spacetime-client mapper | `cargo test --locked --manifest-path server-rs/Cargo.toml -p spacetime-client tracking_input_mapper_preserves_agc_metadata_and_external_owner -- --nocapture` | 1 passed,0 failed | +| SpacetimeDB event-id 幂等 | `cargo test --locked --manifest-path server-rs/Cargo.toml -p spacetime-module duplicate_tracking_event_ids_are_treated_as_idempotent_replays -- --nocapture` | 1 passed,0 failed | +| Rust 格式 | `cargo fmt --manifest-path server-rs/Cargo.toml --all -- --check` | 通过 | +| 中文编码 | `npm run check:encoding` | 通过,5662 个文件 | +| Diff 空白/分支基线 | `git diff --check`、`git merge-base --is-ancestor origin/master HEAD` | 通过 | + +与本 Issue 直接相关的本轮定向测试合计 48 个通过。完整工作区测试不在本地重复运行,按约定交给 CI;本地未启动真实 SpacetimeDB,因此没有把 input/mapper/readback parser 测试表述为线上 E2E。 + +## 5. #226 交接复核 + +`#226` 已提供并冻结以下输入: + +- 发往当前 Genarrative 主站 origin 的业务请求带 `X-Genarrative-Client: agc`。 +- OSS、签名下载、Provider、受控搜索、loopback、更新下载和外部网页请求不带该标记。 +- 同源重定向继续允许;跨 origin 重定向被阻断,避免标记泄漏到第三方 origin。 +- 请求级同名 Header 不能伪造最终值,主站业务请求最终仍为 `agc`。 + +主站 #225 已按上述契约消费 Header;不存在要求 #226 重新设计或回改的接口缺口。 + +补充记录一个不属于 #225 的客户端后续项:`clientHttp.ts` 当前把自定义服务器地址的规范化字符串直接与 `URL.origin` 比较;显式默认端口(例如 `https://example.com:443`)或主机名大小写可能导致合法自定义 origin 被误判为跨 origin。该问题表现为请求被客户端拒绝,不会造成 AGC 标记泄漏,也不影响当前 release/dev 默认地址;应作为 #226 的独立客户端修复跟踪,不能在 #225 中偷偷回改客户端设计。 + +## 6. 可直接粘贴到 Issue #225 的交付评论 + +> `#225` 主站侧已完成并收口:统一识别 `X-Genarrative-Client: agc`,并在现有成功 route tracking 的 `tracking_event.metadata_json` 中追加 `client: "agc"`。账号态按已验证 access token 的真实用户归属,External API Key 态按 `ExternalApiPrincipal.owner_user_id()` 归属,不伪造 `user_id`,不记录 token/API Key/Cookie/签名 URL。当前 AGC 实际使用的账号态 `/api/auth/*`、`/api/profile/*`、`/api/assets/*`、`/api/editor/*`、`/api/runtime/*` 与 External v1 业务路径均有显式 route spec;动态 ID 继续归一化,External v1 保留实际外部 path。OSS/签名下载、Provider、受控搜索、loopback、更新下载、公开 discovery/Skill/MCP 不进入普通 AGC 业务统计。记录最终落在现有 `tracking_event.metadata_json.client`,复用既有 outbox、SpacetimeDB procedure、daily stat、event-id 幂等和后台 `GET /admin/api/tracking/events` 读取,不新增 schema、页面或独立事件体系。阶段 1~5 的定向实现和回归已提交,阶段 6 最终门禁通过:api-server tracking/资产/后台/outbox 43 个、鉴权回归 1 个、module-runtime 2 个、spacetime-client 1 个、spacetime-module 1 个定向测试全部通过,Rust 编译/格式、编码和 diff 检查通过;完整工作区测试按 CI 执行。`#226` 客户端标记、origin-safe redirect 和第三方边界契约无需回改。` + +## 7. 后续发布前事项 + +本 Issue 代码和本地确定性验证已完成;发布或联调时由主站环境补做一次真实链路 smoke:使用已部署的 AGC 客户端请求主站业务接口,确认 Header 被接收、`tracking_event.metadata_json.client` 可由后台原始查询读回。该 smoke 是环境验证,不改变本 PR 的设计或代码范围。