收尾两批并行开发的文档回写与任务包

- Sprint 执行计划 v1.1:§1.1 补第一批交付实据;T9 SPIKE 结论回写 Sprint 1 表
  (T-P0-16 通过 2.8ms;T-P0-12 有条件;T-P0-10 原型 6/6 + 真实照片重验前置);
  Sprint 2 表标注 T-P2-01~06 交付;新增 §9 两批交付记录
- 开发任务拆解 v1.5:交付回写 + 新风险登记(RTMPose 域差异、BiRefNet 异步化)
- 新增 开发任务包/:三批多 agent 并行的协作规程与 14 份任务契约
- 新增 前置开通清单-云资源与资质.md
- 清理已用毕的评审报告(文档评审 2026-08-13、项目整体评审 2026-08-14),
  README 中相应引用改为留痕表述
This commit is contained in:
xsl
2026-08-14 21:07:20 +08:00
parent 2934954eb2
commit e511b6a66f
22 changed files with 1407 additions and 461 deletions
+84
View File
@@ -0,0 +1,84 @@
# T3 · 客户端埋点批量上报
> 对应任务:`T-P1-10`(埋点事件字典/上报通道的服务端部分)| 需求:`FR-SYS-06`、架构 §9.3 | 分支:`task/t3-events` | worktree:`/home/xsl/meida_backend-t3`
> 先读《README.md(协作规程)》,遵守全部纪律。
## 1. 目标
实现 `POST /events/batch`:客户端批量上报埋点事件,落 `ob_event` 表。这是 V1 全链路埋点(FR-SYS-06)与后续三张看板(TD-24)的数据入口。
## 2. 已就绪的契约(协调提交已提供)
### 表(迁移 `0004` 已建,ORM 已加,**你不需要碰 models.py / migrations**)
`ob_event`(架构 §6.2):
- `event_id` bigint PK(雪花)、`user_id` bigint NOT NULL、`event_name` varchar(64)、`props` JSONB、`client_ts` timestamptz NULL、`server_ts` timestamptz NOT NULL default now()、
- 索引:`(event_name, server_ts)`、`(user_id, server_ts)`。
- 架构原文"按月分区"——**V1 先普通表**(与 ob_ai_call 同样处理),代码注释写明"分区在数据量上来后再做,先不预支复杂度"。
### 配置(`config.py` 已加字段)
```python
events_max_batch: int = 50 # env: EVENTS_MAX_BATCH
events_props_max_bytes: int = 8192 # env: EVENTS_PROPS_MAX_BYTES
```
### 错误码(`errors.py` 已加)
- `EVENTS_BATCH_REJECTED = 40020`:400「埋点数据格式有误」,hint=null(对客户端静默降级即可,不打扰用户)。
### 骨架(协调提交已建,直接填充)
- `packages/analytics/__init__.py`、`packages/analytics/README.md`、`packages/analytics/service.py`(空骨架)
- `apps/api/routers/events.py`(空骨架 router,**已注册进 main.py**,prefix `/events`)
- `apps/api/deps.py` 已有 `AnalyticsServiceDep`(构造 `AnalyticsService(session)`)——**deps.py 禁改,签名如不满足需要,用路由函数内自建服务对象的方式,不改 deps**。
### 分层位置
`packages.analytics` 被协调提交放进了 import-linter 的"共享能力层"(与 identity/billing/task 同层)——它只能 import `packages.shared`/`packages.domain`,**不得** import 其它业务包或 llm。
## 3. 实现要点
允许改动:`packages/analytics/**`、`apps/api/routers/events.py`、测试文件。
1. **`packages/analytics/schemas.py`**(新建):
- `EventIn`:`event_name: str`(`pattern=r"^[a-z][a-z0-9_.]{1,63}$"`)、`props: dict[str, Any] = {}`、`client_ts: datetime | None = None`;
- `EventBatchRequest`:`events: list[EventIn]`(`min_length=1`)、
- `EventBatchResponse`:`accepted: int`。
- **事件字典(event dictionary)**:V1 不做服务端枚举校验(客户端事件名集合由 TD-23 埋点字典维护,尚未定稿)——用正则约束形状即可,代码注释写明"字典校验等 TD-23 定稿后再收紧"。
2. **`packages/analytics/service.py`**:
- `AnalyticsService(session)`:`async def record(user_id: int, batch: list[EventIn]) -> int`;
- 校验(fail-close,任一不过 → 整批拒绝 `AppError(EVENTS_BATCH_REJECTED)`,**不部分落库**):批量 ≤ `events_max_batch`;每条 props 序列化后 ≤ `events_props_max_bytes`(`len(json.dumps(props, ensure_ascii=False).encode())`);`client_ts` 若给出:不晚于 server_now+5min、不早于 7 天前;
- 组装 `ObEvent(event_id=next_id(), user_id=..., server_ts=datetime.now(UTC))` 逐条 add,`flush` 一次,返回条数;(`from packages.domain.ids import next_id`,参考 identity/service.py 用法。)
- **不含 PII 清洗**:props 里若有 `phone`/`token`/`authorization` 键 → 直接删除该键并 `logger.warning("events_pii_dropped", ...)`(NFR-SEC-03 日志脱敏的同族要求)。
3. **`apps/api/routers/events.py`**:
- `POST /batch`,`user_id: CurrentUserId`,`analytics: AnalyticsServiceDep`(若 Dep 不可用则函数内 `AnalyticsService(session)` with `SessionDep`);
- `ok(EventBatchResponse(...))`;服务层抛的 `AppError` 自然走统一异常处理,不需要额外 catch。
## 4. 测试要求
- 单测 `tests/unit/test_events_validation.py`(纯校验逻辑,不连库——把校验拆成模块级纯函数 `validate_batch(events, *, max_batch, max_bytes, now) -> list[ObEvent-like]` 便于无 DB 测试):
- 合法批量通过;超批量拒;props 超字节拒;event_name 正则(大写/数字开头/超长拒);client_ts 未来>5min 拒、7 天前拒;PII 键被剔除。
- 集成测试 `tests/integration/test_events.py`(新文件,允许跑):dev-token → POST /v1/events/batch 两条事件 → 200 accepted=2;查库两行 `ob_event`;再 POST 非法批(event_name 大写)→ 40020 且**库里没有半条**。
## 5. 禁改清单(通用禁改之外)
`packages/llm/**`、`packages/billing/**`、`packages/identity/**`、`packages/storage/**`、`packages/agent/**`、`apps/api/routers/{agent,auth,quota,tasks,uploads}.py`、`tests/integration/test_vertical_slice.py`。
## 6. 验收标准
1. `POST /api/v1/events/batch`(及 `/v1` 别名)可用,认证必需(无 token → 40100);
2. 整批原子:要么全落、要么全拒;
3. 校验规则全部有测试覆盖;
4. 全部检查绿(mypy 范围同 T2 列表)。
## 7. 提交信息
```
feat(analytics): POST /events/batch with fail-close validation
ob_event 落库通道:形状校验 + props 字节上限 + client_ts 合理性 +
PII 键剔除,整批原子(FR-SYS-06 服务端部分)。
偏差说明:<如有写在这里,无则写"无">
```