docs: 幂等键检修报告;勘误 API_INVENTORY 与 HTTP 报告;同步 STATUS/JIRA

新增 docs/幂等键检修报告-2026-09-18.md:记录双向断裂(后端零处读取该头、
前端 Date.now 每次生成新键)、五处审计发现、机制设计与五个取舍
(先占位后执行 / 重放 / 失败释放占位 / 孤儿占位接管 / 作用域含 actor_id)、
列类型不变量扫描、E2E 与守门证据、三处同形状问题的连带修复。

同轮勘误:

- docs/HTTP语义检修报告-2026-09-18.md:其中「Idempotency-Key 已支持」一句
  在写下时不成立(后端当时零处读取),已显式标注勘误并保留原文,
  以免「结论先于核实」被当作既成事实继续引用。
- docs/api/API_INVENTORY.md:原记载 POST /activities/{id}/interests
  「提交活动互选」,后端从未实现(Phase 16 已并入 /feedback 的
  interestTargetUserIds),按真实端点重列并附勘误说明。

docs/STATUS.md:迁移范围更新为 V001~V046;回归行更新为 669 / 0 / 0 / 3;
新增「幂等键检修」章节;补登 HTTP 语义检修章节在两份文档中的缺失条目。
docs/JIRA_BACKLOG.md:补登 HTTP 语义检修与幂等键检修两行
(此前均未登记)。
This commit is contained in:
WorkBuddy
2026-09-18 16:37:47 +08:00
parent af5412646b
commit 2766e53fe2
5 changed files with 453 additions and 5 deletions
+56 -2
View File
@@ -21,8 +21,8 @@
| 阶段 2 管理端 Vue 适配 | 已完成 | 18 modules + 22 routes + vue-tsc 全绿 + vite build 11.68s |
| 阶段 3 CapabilityService 独立化 | 已完成 | New `capability` 模块:CapabilityService(getCapabilities + 公共判断方法)+ CapabilityController(GET /api/v1/capabilities);HomeApplicationService 完全委托 |
| 阶段 4 双端总验收 | **已完成**(2026-09-14) | 三端构建全绿 + 13/13 契约全绿 + 源码健康检查通过;验收报告见 docs/superpowers/plans/2026-09-14-phase4-acceptance-report.md |
| Flyway 迁移 | V001~V043 | V028~V043 已纳入 STATUS;V037 为 Phase 12 新增(实体列与生产迁移对齐,23 表 50 列);V038 为 Phase 13 新增(`matchmaker_recommendations.status` + `matchmaker_services.service_type` 的 CHECK 白名单扩展);V039 为 Phase 13A 新增(counseling 幂等键 + 三个 CHECK 白名单显式固化);V042 为 Phase 19 新增(`service_location_announcements` 服务站临时公告表);V043 为 Phase 20 新增(授予 `data_viewer` / `platform_admin` 的 `analytics.view`) |
| Backend 全量回归 | **已清偿**(2026-09-17,Phase 20) | 离线容器(`danyang-match-backend-test`)`mvn -o test` 实测:**648 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS**(约 6.5 分);含 `CanonicalRouteContractTest`、`ChatContractJsonTest`、`MeRouteContractTest`、`DiscoverBlockInvisibilityIntegrationTest`、`ChatPhase09IntegrationTest`、`ChatWebSocketHandlerTest`、`PhotoUploadValidationTest`、`NotificationPhase11IntegrationTest`、`NotificationTemplateTypeGuardTest`、`NotificationPrivacyCopyTest`、`EntityMigrationParityTest`、`RelationshipProgressPhase12Test`、`HomePremaritalGuidancePhase12Test`、`MatchmakerReferralPhase13Test`、`MatchmakerServiceTest`、`ReportAndRiskPhase17IntegrationTest`、`AppealPhase18IntegrationTest`(Phase 18 新增 15 例:申诉真实落库+审计、仅 ban/suspend 可申诉、幂等、**Gate A 通过只解除目标处置**、**Gate B 拒绝维持原处置**、原子认领唯一赢家、二次处理被拒、escalate 后可再决策、appealType 白名单/一致性/缺省推导、证据字段映射、软删过滤、Feature Flag 关闭拦截)、`MetricCatalogGuardTest`(11)、`AnalyticsMetricsPhase20IntegrationTest`(21)、`AnalyticsSoftDeleteGuardTest`(4)、`AnalyticsPermissionGrantTest`(6)、`LocalEligibilityStatusVocabularyGuardTest`(9) |
| Flyway 迁移 | V001~V046 | V028~V046 已纳入 STATUS;V037 为 Phase 12 新增(实体列与生产迁移对齐,23 表 50 列);V038 为 Phase 13 新增(`matchmaker_recommendations.status` + `matchmaker_services.service_type` 的 CHECK 白名单扩展);V039 为 Phase 13A 新增(counseling 幂等键 + 三个 CHECK 白名单显式固化);V042 为 Phase 19 新增(`service_location_announcements` 服务站临时公告表);V043 为 Phase 20 新增(授予 `data_viewer` / `platform_admin` 的 `analytics.view`);V045 为幂等键检修新增(`idempotency_records` 通用幂等表 + 唯一索引 `uk_idempotency_scope`);V046 修正其 `actor_id` 列类型(VARCHAR → uuid) |
| Backend 全量回归 | **已清偿**(最新:2026-09-18 **669 / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS**) | 离线容器(`danyang-match-backend-test`)`mvn -o test` 实测;**2026-09-18 幂等键检修订正后为 669**(HTTP 语义检修 664 → 幂等检修 +5:`GlobalExceptionHandlerTest` 3→6、新增 `IdempotencyDeclarationGuardTest` 2)。历史:2026-09-17 Phase 20 为 648;含 `CanonicalRouteContractTest`、`ChatContractJsonTest`、`MeRouteContractTest`、`DiscoverBlockInvisibilityIntegrationTest`、`ChatPhase09IntegrationTest`、`ChatWebSocketHandlerTest`、`PhotoUploadValidationTest`、`NotificationPhase11IntegrationTest`、`NotificationTemplateTypeGuardTest`、`NotificationPrivacyCopyTest`、`EntityMigrationParityTest`、`RelationshipProgressPhase12Test`、`HomePremaritalGuidancePhase12Test`、`MatchmakerReferralPhase13Test`、`MatchmakerServiceTest`、`ReportAndRiskPhase17IntegrationTest`、`AppealPhase18IntegrationTest`(Phase 18 新增 15 例:申诉真实落库+审计、仅 ban/suspend 可申诉、幂等、**Gate A 通过只解除目标处置**、**Gate B 拒绝维持原处置**、原子认领唯一赢家、二次处理被拒、escalate 后可再决策、appealType 白名单/一致性/缺省推导、证据字段映射、软删过滤、Feature Flag 关闭拦截)、`MetricCatalogGuardTest`(11)、`AnalyticsMetricsPhase20IntegrationTest`(21)、`AnalyticsSoftDeleteGuardTest`(4)、`AnalyticsPermissionGrantTest`(6)、`LocalEligibilityStatusVocabularyGuardTest`(9)、`ApiResponseCodeHttpStatusTest`(6)、`SecurityPathCoverageTest`(2)、`IdempotencyDeclarationGuardTest`(2) |
| 路由/字段契约收敛 | **已完成**(2026-09-15) | 字段:会话 `target*` + `sentAt` + `relationStatus`(commit `687a156`);路径:采用 2026-09-14 canonical(`/likes` `/passes` `/blocks` `/matches`,缘分保留 `/relations/fate/*`),旧路径与 `success` 桩实现已移除;裁决由 `CanonicalRouteContractTest` 锁定 |
| `/me/*` 路径族 | **已完成**(2026-09-16) | 小程序按 REST 规范(§16 / §22-§40 / §80 / §83-§84)调用 `/me/*`,后端此前**无 `/me` 基路径**致 17 个调用 404。新增 `MeController` 委托既有 service 暴露规范路径,旧路径全部保留,小程序三条历史写法(`/me/privacy-settings`、`/me/photos/{id}/main`、`/me/appeals`)一并兼容;由 `MeRouteContractTest` 锁定 |
| Admin Web | **验证通过** | 2026-09-14 阶段 4:vue-tsc ✅、vite build ✅ 11.68s;18 modules 齐全;壳层+工作台通过参考图 QA |
@@ -726,6 +726,60 @@ OpenAPI 对外发布、调用审计与配额、分页幂等覆盖、脱敏边界
详见 `docs/HTTP语义检修报告-2026-09-18.md`。
### 幂等键检修 — `Idempotency-Key` 从「声明」到「兑现」(2026-09-18)✅ 已关闭
起因:HTTP 语义检修收尾时我写下「`Idempotency-Key` 已支持」,随后核对**推翻了自己的结论**——
后端当时**零处**读取该头。
| 发现 | 说明 |
|---|---|
| 声明与实现彻底失联 | 规范第八章要求 7 类端点支持幂等键;前端 25 处调用点(8 service + 2 TS 页面)发送该头,**后端 0 处读取** |
| **前端那一侧也是坏的** | 原写法 `idempotencyKey: \`like:${Date.now()}\`` —— `Date.now()` 每次调用都变,**就算后端实现了也永远命中不了**。互补的两半,谁也不起作用 |
| 唯一真幂等的端点走的不是 header | 只有 counseling 申请靠 body 字段 `idempotencyKey`(V039 `uk_counseling_idem`) |
| 两道兜底双双落 500 | `DuplicateKeyException`(并发重复提交的最后防线)与 30 处 `IllegalStateException`(业务状态冲突)均无映射 → 500;而 500 语义是"可重试",**调用方于是重试,制造更多重复** |
| **一个错误的安全假设** | 上轮 `D-23B-J` 判定 `stringtype=unspecified` 是"一处解决全局"。本轮 V045 的 `actor_id VARCHAR` 在**同样开着该参数**的 docker profile 下依然抛 `character varying = uuid` → 该参数覆盖不到经类型处理器显式指定类型的参数。**真正的不变量是"列类型必须是 uuid"** |
修法:**一处实现、零业务代码改动、逐端点声明** —— `V045` 建表(`idempotency_records` + 唯一索引
`uk_idempotency_scope`)、`V046` 修正列类型、`IdempotencyInterceptor`(先占位后执行/重放/409)、
`IdempotencyResponseCacheFilter`(缓存响应体供逐字节重放)、`@Idempotent` 声明 30 处、
异常处理器两处新映射(`IllegalStateException` / `DuplicateKeyException` → 409,
且**刻意不映射**其父类 `DataIntegrityViolationException`,因后者含 CHECK 违反应由 500 暴露)。
四个关键设计:**先占位后执行**(靠唯一索引让并发只有一个赢家,不用 SELECT-then-INSERT)、
**失败释放占位**(否则一次瞬时故障把用户永久卡在 409)、**孤儿占位可接管**(进程中断不留死键)、
**作用域含 `actor_id`**(同键不同用户互不干扰)。
前端键改为"**一次未成功的逻辑操作内复用同一把键,成功才释放**",25 处 `Date.now()` 写法全部替换(残留 0)。
验证:E2E **49 passed**(含 `tests/e2e/idempotency_test.py` 7 用例:重放逐字节相同、同键只产生一条记录、
作用域隔离、**5 线程并发同键业务恰好落库一次**、无键重复 Like 不得 500);
后端全量 **669 / 0 / 0 / 3**(首轮 664/1 failure 为有意设计的迁移基线守卫,已按流程抬基线至 46);
小程序 `tsc --noEmit` EXIT=0;UI 契约门控 **14/14**。
**列类型不变量全库扫描**:所有 `*_id VARCHAR` 列中,`actor_id` 是唯一违规(已修);
`client_message_id` / `provider_request_id` 为外部 id、`resource_id` / `source_id` 在 Java 侧**零处作查询条件**
(已 grep 确认)→ 不存在第二处同类隐患。
**新增机械守卫** `IdempotencyDeclarationGuardTest`(2 用例,经注入变异负向验证,各自精确报出行号):
规范第八章 7 类端点族 + Phase 16 互选链路必须声明 `@Idempotent`;`@Idempotent` 后必须紧跟 `@PostMapping`。
同时**修正一处假绿**:`GlobalExceptionHandlerTest` 原 3 条断言只测 DTO、**一次都没进过处理器**。
**同轮清掉三处同形状问题**(均属"一处声明、另一处不兑现"的复发):
1. **补 `CheckinController` 手工签到的 `@Idempotent`** —— 第 146 章"扫码两次"场景的同类漏标;
2. **CI 上的 E2E 从未真正通过过**(本轮活栈复跑暴露):迁移里**没有任何 operator 种子**、
`DemoDataSeeder` 也不建运营账号,`conftest.admin_token` 却写死 `username="admin"` ——
那是某台开发机手工创建的遗留账号,本机有它所以本地一直绿,CI 全新库没有 → fixture 必然 401。
已改用 seed 创建的 `e2e_admin`(绑 super_admin),并新增 `tests/e2e/fixture_account_guard_test.py`
机械拦住"fixture 只准用 seed 账号"(含"解析失败即失败"断言,防守卫自身静默失效);
3. **`POST /activities/{id}/interests` 契约漂移**:`API_INVENTORY` 记载该端点、前端按此实现,
但后端**从未实现**(Phase 16 已把表达意向合并进 `/activities/{id}/feedback` 的 `interestTargetUserIds`)
→ 调用必然 404。已更正 `API_INVENTORY` 并按真实端点重列,删除前端死代码链
(`activityService.submitInterests` + `activityStore.submitInterests`,全仓确认无页面调用方),
各留注释说明删除原因与恢复条件。**若产品确认需要独立端点,应先定义语义并补后端实现。**
详见 `docs/幂等键检修报告-2026-09-18.md`。
### Phase 24 — UAT增量 ✅ 已就绪(测试数据支持)
| 任务 | 状态 |
|------|------|