docs: M2 第三波收口——报告 22~27 入档挂导航
CI / docs-build (push) Successful in 58s

- 22~26 Flutter 接入五单报告(T2-11~14 + 白名单扩充,flutter 测试 64→272)
- 27 第三波收口总表:demo 数据消亡、四态纪律、埋点端到端贯通、DEBT-1 偿还
- 三次 compose 实测无契约偏差;波内 agent 中断续跑事故记录在案

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-08 14:10:36 +08:00
parent 222990e587
commit b81c050e03
7 changed files with 768 additions and 0 deletions
@@ -0,0 +1,81 @@
# 24 · 事件白名单 v2 扩充(T2-17 后端半边)
> 依据:`06-experiment-tracking-plan.md` §1.4/§1.5(事件字典 v2 增量)、§5.2page_viewed 转正稿)、§6.4(值级巡检);ADR-013health_record_action 移除,dev@58576f8
>
> 交付:`patbond-api` dev@`64c9b72``patbond-user` 模块 analytics 包,3 文件,+216/9
## 1. 结论速览
| 项 | 结果 |
| --- | --- |
| 新增白名单事件 | 10 个(pet 域 3 + health_record 域 7),props 键集与 06 号 §1.5 可直抄块逐条一致 |
| page_viewed 转正核对 | **一致,零修正**:现行白名单已是 `Set.of("pageName", "referrer")`,与 v2 正稿键集相同;仅更新注释标注正稿地位与 pageName 枚举(含 §1.6 修订的 `pet_form` |
| health_record_action | 保持移除(ADR-013),新增集成测试锁定其仍被 `unknown_event_name` 拒绝 |
| 测试数 | 182 → **191**(+9:字典边界 5 + 接收端集成 4),`mvnw clean test` 全绿 |
| 契约变更 | **无需**`openapi.yaml` 的 events 契约对事件名开放(字符串 + 后端字典校验),本次未触碰 |
## 2. 新增事件与 props 对照(vs 06 号 §1.4/§1.5
`EventDictionary.java``patbond-user/src/main/java/com/patbond/patbond/user/analytics/``WHITELIST` 增量,逐条对照字典 v2
### 2.1 pet 域(3 事件)
| 事件名 | 白名单 props | 与 06 号 §1.5 |
| --- | --- | --- |
| `pet_create_started` | `entryPoint` | 一致 |
| `pet_create_succeeded` | `durationMs``species``petIndex` | 一致 |
| `pet_create_failed` | `failureReason``errorCode``httpStatus``attemptSeq` | 一致 |
### 2.2 health_record 域(7 事件)
| 事件名 | 白名单 props | 与 06 号 §1.5 |
| --- | --- | --- |
| `health_record_create_started` | `recordType``entryPoint` | 一致 |
| `health_record_create_succeeded` | `recordType``durationMs``photoCount` | 一致 |
| `health_record_create_failed` | `recordType``failureReason``errorCode``httpStatus``attemptSeq` | 一致 |
| `health_record_viewed` | `recordType``source` | 一致 |
| `health_record_edit_succeeded` | `recordType``fieldCount` | 一致 |
| `health_record_edit_failed` | `recordType``failureReason``errorCode``httpStatus`(无 `attemptSeq`,正稿如此) | 一致 |
| `health_record_deleted` | `recordType` | 一致 |
### 2.3 page_viewed 转正核对
现行条目 `Map.entry("page_viewed", Set.of("pageName", "referrer"))` 与 v2 正稿(§5.2)键集**完全一致,无需修正**。差异只在语义层:v2 要求 pageName 为编译期枚举(`login/register/home/profile/pet_list/pet_detail/pet_form/record_form/record_detail`)——这是客户端约束(T2-17 Flutter 半边)+ §6.4 值级巡检的职责,后端键级白名单结构不承载值枚举(见 §3)。已将枚举全集写入 `EventDictionary` 类注释作字典说明。
## 3. 枚举值的校验边界(设计决策,沿用现行架构)
当前 `EventDictionary` 是**键级白名单**(白名单外键剥离、红线键拒绝、未知事件名拒绝),不做值级枚举校验。v2 的 `recordType``weight/vaccine/health_event/reminder`)、失败枚举(含 `permission_denied/conflict/not_found`)、`pageName` 枚举维持同一分层:
1. **客户端编译期枚举**是第一道约束(06 号 §5.2 明确 pageName 为「编译期枚举」;recordType 同理);
2. **接收端只校验键**——枚举外的值(如 `recordType: "grooming"`**过 ingest 不拒绝**,由 §6.4 值级巡检 SQL 兜底发现。06 号 §1.5 的「可直抄」Java 块本身就是纯键集,本实现与其逐字一致,未擅自加严接收契约(加严会使客户端枚举漂移时整条事件丢失,与 §5.2 第 4 条「宁可不上报、不要报错名」的防洪水思路相悖)。
此边界已用集成测试 `enumOutRecordTypeValuePassesIngestForOfflinePatrol` 显式锁定为文档化行为,避免后人误当漏洞「修复」。
## 4. 测试增量(182 → 191,全绿)
`JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test`:总计 191failures 0errors 0。
**`EventDictionaryTest`3 → 8+5**
| 测试 | 边界 |
| --- | --- |
| `v2PetDomainEventsMatchDictionary` | pet 域 3 事件 props 键集 `containsExactlyInAnyOrder` 全矩阵 |
| `v2HealthRecordCreateFunnelMatchesDictionary` | 创建漏斗 3 事件键集全矩阵 |
| `v2HealthRecordLifecycleEventsMatchDictionary` | viewed/edit/deleted 4 事件键集(含锁定 edit_failed 无 attemptSeq |
| `pageViewedFormalizedPropsAreExactlyPageNameAndReferrer` | 正稿键集恰为 pageName+referrer |
| `deliberatelyAbsentEventsStayUnknown` | §1.4 刻意不设的 `pet_viewed`/`health_record_edit_started`/`health_record_delete_failed` 保持 unknown |
**`AnalyticsIntegrationTest`7 → 11+4**
| 测试 | 边界 |
| --- | --- |
| `acceptsV2HealthRecordFunnelEvent` | v2 事件(合法 recordType)端到端 accepted 且落库 |
| `stripsPropsOutsideV2Whitelist` | v2 事件白名单外键(内容型 `recordTitle`)被剥离,`recordType` 保留 |
| `enumOutRecordTypeValuePassesIngestForOfflinePatrol` | 枚举外 recordType 值过 ingest(§3 决策的锁定) |
| `retiredHealthRecordActionStaysRejected` | 废弃事件带 v2 同名 props 上报仍整条 rejected`unknown_event_name` |
## 5. 未尽事项
- `entryPoint` 枚举(`profile_empty_state/pet_list/post_register_guide` 等)06 号标注「待 UI 定稿收敛」——键已入白名单,枚举收敛属 Flutter 半边与 UI 定稿,后端无阻塞。
- `pet_create_failed.failureReason``pet_limit_reached` 待拍板(无上限则删)——纯值级枚举,不影响本次键级白名单。
- T2-17 Flutter 半边(细分事件挂接、pageName 编译期枚举、RouteObserver)不在本工单范围。