修复Issue225仅记录AGC请求
Project CI / Repository checks (pull_request) Successful in 3m4s
Project CI / Frontend tests (pull_request) Successful in 4m16s
Project CI / Backend tests (pull_request) Successful in 7m2s
Project CI / Native shell tests (pull_request) Successful in 14m30s

新增AGC-only route tracking门禁

保持既有route和手工资产事件统计语义

清理TrackingEventDraft冗余marker状态

补充定向测试并同步Issue225文档
This commit is contained in:
2026-09-02 13:47:06 +00:00
parent 1aba9c843c
commit d1b2aea4a4
6 changed files with 230 additions and 73 deletions
@@ -10,7 +10,7 @@
## 1. 一句话交付结果
主站 `api-server` 能识别 AGC 发来的 `X-Genarrative-Client: agc`,并在现有成功路由埋点写入 `tracking_event.metadata_json``client: "agc"`,同时按真实登录用户或 External API Key 的 `owner_user_id` 归属,后台可以通过现有 tracking 事件查询看到这类请求;不改变鉴权、计费、幂等响应和现有统计口径
主站 `api-server` 能识别 AGC 发来的 `X-Genarrative-Client: agc`,并在本期新增的 AGC 专用成功路由埋点写入 `tracking_event.metadata_json``client: "agc"`,同时按真实登录用户或 External API Key 的 `owner_user_id` 归属,后台可以通过现有 tracking 事件查询看到这类请求;既有 route tracking 和手工资产事件保持原有全客户端统计口径,不改变鉴权、计费、幂等响应语义
## 2. Issue 边界
@@ -165,10 +165,13 @@ X-Genarrative-Client: agc
- `app.rs` 在消费 request 之前解析 marker,并从 response extensions 取两类主体。
- `tracking.rs` 提供小型白名单解析函数和主体归属逻辑。
- `RouteTrackingSpec` 明确区分既有全客户端 route 与本期新增的 AGC-only route;新增 AGC route 只有在 marker 有效时才允许落库。
- `build_route_tracking_metadata` 只在有效 marker 时追加 `client`,不重写既有字段。
- `record_route_tracking_event_after_success` 继续负责 route spec、outbox 和失败日志策略。
- 不把 marker 放进 `RequestContext`,除非阶段 1 证明同一请求的其他统一 tracking 入口确实需要它;避免扩大公共上下文结构。
既有 route spec 默认保持全客户端语义;本期新增、仅为 AGC 调用清单补齐的 route spec 使用 AGC-only 策略。已有 `handled_by_existing_event` 的详细资产事件不改记录范围,只在有效 marker 时追加 `client`
## 6. Route 覆盖范围
### 6.1 必须覆盖的账号态路径
@@ -270,21 +273,28 @@ TrackingEventDraft.metadata
- 请求体伪造 owner 或 marker 不改变主体归属。
- metadata 原有 route/method/status/operation 和资产嵌套字段仍存在。
### 9.3 Route 覆盖
### 9.3 AGC-only 记录门禁
- 本期新增的账号态和 External v1 AGC route:带有效 marker 的 2xx 响应才记录。
- 同一批新增 route:缺失、非法或未知 marker 时不记录,也不拒绝业务请求。
- 既有 route spec 和已有详细资产事件保持原有全客户端记录语义。
- 4xx/5xx 即使带有效 marker 也不新增成功 route event。
### 9.4 Route 覆盖
- 当前 AGC 调用清单中的账号态路径全部能解析到 route spec。
- 当前 AGC 使用的 External v1 业务路径全部能解析到 route spec。
- `/api/external/v1` 发现、Skill 和 MCP 路径不被普通业务 spec 误收录。
- dynamic project/asset/operation ID 仍按现有 normalize 规则归一化。
### 9.4 持久化与后台读取
### 9.5 持久化与后台读取
- 构造 tracking event input 后,`metadata_json` 是合法 JSON object 且含 `client`
- outbox 入队/回退直写路径不丢失 `client`
- tracking_event 读回及后台 tracking API 解析不丢失 metadata。
- 现有 daily stat、event id 幂等和失败不阻断语义保持不变。
### 9.5 状态码语义
### 9.6 状态码语义
至少保留一组回归:
@@ -311,6 +321,7 @@ TrackingEventDraft.metadata
- 已有 route、method、status、operation、资产 metadata 和 event key 语义不丢失。
- 登录账号态按真实用户归属,External API Key 态按 `owner_user_id` 归属。
- 当前 AGC 实际使用的账号态和 External v1 业务路径均有 tracking spec。
- 本期新增的 AGC route spec 未携带有效 marker 时不产生 route tracking event;既有 route spec 和手工资产事件保持原有全客户端统计语义。
- External v1 discovery/Skill/MCP、OSS、签名下载、Provider、搜索、loopback、更新下载不被误纳入普通 AGC 业务统计。
- outbox、SpacetimeDB 写入、daily stat、幂等和失败不阻断语义无回归。
- 后台现有 tracking 查询可以看到 `metadata_json.client`,无需新增 schema 或页面。
@@ -171,6 +171,8 @@ X-Genarrative-Client: agc
- 生成提交、轮询、项目读取/资源登记、素材库、上传凭证、对象确认、换签读取等当前已确认调用。
- 每个新增 spec 固定 event key、module key、scope kind 和动态路径归一化方式。
本阶段新增的 route spec 统一使用 AGC-only 记录策略:只有带有效 marker 的 2xx 请求才写入;既有 route spec 保持原有全客户端语义,上传票据/对象确认等 `handled_by_existing_event` 路径继续由手工资产事件负责。
同时明确不加入:
- `/api/external/v1/openapi.json`
@@ -185,6 +187,7 @@ X-Genarrative-Client: agc
- 发现/MCP 路径不会误进入普通业务 route tracking。
- project/asset/operation 动态 ID 仍按现有规则归一化。
- 原有 route spec 的 event key、scope 和统计口径不变。
- 新增 AGC route 在未携带有效 marker 时不产生 route tracking event,且不影响业务响应。
### 7.3 阶段完成判据
@@ -289,8 +292,9 @@ CI 已覆盖且与本改动无直接关系的全量测试可交给 CI;本阶
### 9.4 阶段 5 实现与验证记录
- `tracking.rs` 抽出 `should_record_route_tracking` 判定,明确只有 2xx 成功响应且不是已有详细资产事件的路由才进入统一成功 route tracking4xx/5xx 和上传确认等复用事件均不新增重复成功事件。
- `tracking.rs` 抽出 `should_record_route_tracking` 判定,明确新增 AGC-only route 只有在带有效 marker 的 2xx 响应下才进入统一成功 route tracking既有全客户端 route 保持原语义,4xx/5xx 和上传确认等复用事件均不新增重复成功事件。
- 新增 2xx、4xx/5xx 状态矩阵测试,覆盖 `OK / CREATED / ACCEPTED / NO_CONTENT` 与认证、权限、客户端、服务端失败状态。
- 新增 AGC-only route 正反向门禁测试:有效 marker 记录,缺失或无效 marker 不记录;既有 route 和手工资产事件策略保持不变。
- 新增 `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 或失败回退实现。
@@ -367,6 +371,7 @@ cargo test --locked --manifest-path server-rs/Cargo.toml -p spacetime-module dup
### 10.5 阶段 6 交接结论
- 主站只对白名单值 `X-Genarrative-Client: agc` 追加 `metadata_json.client = "agc"`;缺失、空值、`AGC` 和未知值按未标记处理,不拒绝请求。
- 本期新增的 AGC route spec 只有在带有效 marker 的 2xx 响应下才记录;既有 route spec 与手工资产事件保持原有全客户端统计语义。
- 账号态沿用真实 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。
@@ -17,7 +17,15 @@
- `400 / 401 / 403 / 404 / 500 / 502` 即使带 AGC marker,也不会新增成功 route event
- 已由详细资产事件处理的上传票据和对象确认不重复生成统一 route event。
### 2.2 鉴权边界
### 2.2 AGC-only 路由门禁
本期新增、仅为 AGC 调用清单补齐的账号态和 External v1 route spec 均要求有效 `X-Genarrative-Client: agc`
- 带有效 marker 的 2xx 响应才写入 route tracking
- 缺失、非法、未知或大小写不符合契约的 marker 不写入这些新增 route event,也不拒绝业务请求;
- 既有 route spec 和已有详细资产事件保持原有全客户端记录语义。
### 2.3 鉴权边界
`app.rs` 通过真实 router 发起未认证的:
@@ -28,7 +36,7 @@ X-Genarrative-Client: agc
响应仍为 `401 Unauthorized`。marker 不会获得权限,也不会改变失败状态。
### 2.3 metadata 安全边界
### 2.4 metadata 安全边界
route metadata 测试确认不会写入:
@@ -45,7 +53,7 @@ requestBody
有效 marker 只产生固定的 `client: "agc"`,并保留既有 route/method/status/operation 与资产嵌套字段。
### 2.4 既有链路复回归
### 2.5 既有链路复回归
阶段 4 的以下路径在阶段 5 再次顺序执行:
@@ -73,6 +73,8 @@ X-Genarrative-Client: agc
不写入 Header 原文、token、API Key、Cookie、签名 URL、请求体或项目绝对路径。
本期新增的 AGC route spec 只有在请求带有效 marker 时才记录;既有 route spec 与手工资产事件沿用原有全客户端记录策略。
### 3.2 主体归属
- 登录账号态:使用已验证 access token 的真实用户,保留既有 `user_id``owner_user_id` 和 scope 语义。
@@ -137,7 +139,7 @@ worker 使用既有 `record_tracking_events_and_return` 批量 procedure 写入
## 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 和第三方边界契约无需回改。`
> `#225` 主站侧已完成并收口:统一识别 `X-Genarrative-Client: agc`本期新增的 AGC-only 成功 route 只有在带有效 marker 时才写入 `tracking_event.metadata_json.client = "agc"`;既有 route tracking 和手工资产事件保持原有全客户端统计语义。账号态按已验证 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 业务统计。记录最终复用既有 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. 后续发布前事项
@@ -655,7 +655,6 @@ fn build_asset_tracking_event_draft(
draft.scope_id = owner_user_id.trim().to_string();
draft.user_id = tracking_user_id.map(|user_id| user_id.trim().to_string());
draft.owner_user_id = Some(owner_user_id.trim().to_string());
draft.client_marker = client_marker;
let metadata = apply_asset_tracking_client_marker(metadata, client_marker);
draft.metadata = if client_marker.is_some() {
apply_asset_tracking_route_metadata(metadata, request_context)
File diff suppressed because it is too large Load Diff