Files
danyang-match/docs/Phase-16-V11-Recovery-Completion-Report-2026-09-17.md

103 lines
9.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 16(活动互选与双方开启沟通确认)V1.1 增量复验完成报告
- **日期**:2026-09-17
- **模式**:KEEP-VERIFY / EXTEND
- **范围**:activity_mutual、relationship、messaging 模块;activity-mutual 双端页面
- **基线**:Phase 15 收尾 `29112ee`(全量回归 474 tests)
- **结论**:**关闭**。三项计划事项全部落地,Gate 三项均达成
---
## 一、计划项达成情况
| 计划项 | 结论 | 证据 |
|---|---|---|
| 以 `activity_mutual_connection_consents` 为唯一逻辑,废弃 mutual 直接建 Match/Chat 的入口 | ✅ | 双实现(旧 `ActivityInterest.confirmInterest` / 新 `ActivityMutualConsent.confirmConnection`)统一到 `RelationshipService.confirmActivityInterest` 单实现 |
| 双方确认事务中:已有 active Match 则复用会话;历史 Unmatch 则复用同一行(episode 递增 + reopen);无历史则创建 Match 与 Conversation | ✅ | `ActivityMutualConsentService.createMatchAndConversation` 走 `confirmActivityInterest`;active 复用、unmatched 经 `MatchMapper.reopenEpisode` 复用同一行 |
| 补 decline、expires_at 超时、重复确认幂等、并发确认(exactly-once)与单向意向保密(INV-006)测试 | ✅ | 新增 `ActivityMutualConsentServiceTest`(7 例),覆盖双确认建 Match+会话+溯源、INV-006 单向不暴露、decline 阻止、幂等重确认不重复建、expires_at 超时拒绝、软删过滤、复用 active Match |
**Gate**
| Gate | 结论 |
|---|---|
| A 单选 B 时 B 无感知(INV-006 单向意向保密) | ✅ `getMyConsentStatus` 改为 `partnerAccepted = myAccepted && partnerSideAccepted`;本侧未确认则对方确认态不回显 |
| Mutual 但未双确认时无新 Chat | ✅ `confirmConnection` 单方确认不建 Match/会话,仅持久化本侧确认态 |
| 双确认后 exactly-once 形成正确 Match/Conversation | ✅ 原子 `confirmSideIfPending`(`both_confirmed_at` NULL→非NULL 仅一方赢家 `affected==1 && bothConfirmedAt!=null` 建 Match+会话并 `bindMatch` 溯源) |
---
## 二、复验发现与修复缺陷清单(7 处 + 1 处隐藏编译缺陷 + 1 处测试保真度)
### A. 核心 Gate 缺陷(活动互选二阶段同意)
- **D1 CRITICAL — 双确认后根本不建 Match/会话(Gate 缺陷 · 违反 INV-014/015)**:新路径 `ActivityMutualConsentController.confirmConnection` 在双方确认后**只置 `both_confirmed_at`,不调用任何 Match/会话创建**;旧路径 `ActivityInterest.confirmInterest`→`confirmActivityInterest` 建 Match 却**缺会话**。两条路径分裂,导致"活动互选成功后无法开启沟通"。重写为 `confirmConnection`:以原子 `confirmSideIfPending` 保证 exactly-once,由赢家方调用 `confirmActivityInterest` 建 Match+会话并 `bindMatch` 溯源;单方确认不建 Match。
- **D2 HIGH — 双实现分裂**:旧/新两条确认路径各自为政、语义不一致。统一到 `RelationshipService.confirmActivityInterest`(返回 `matchId` + `priorUnmatch`,显式开启会话)。
- **D3 HIGH — 隐藏编译缺陷(必崩级 · 最严重发现)**:Phase 15 提交态在 `RelationshipService.confirmActivityInterest` 中调用 `matchMapper.reopenEpisode`,但 `MatchMapper` **从未定义该方法** → 整个 activity_mutual / relationship 模块**根本不编译**。因此 **"474 绿"基线从未真实存在**,所谓通过是假象。修复:在 `MatchMapper` 补 `@Update reopenEpisode`(`WHERE id=#{id} AND status='unmatched' AND deleted_at IS NULL`)。**结论必须如实记录:Phase 15 的提交态从未通过编译。**
- **D4 HIGH — INV-006 单向意向泄露**:`getMyConsentStatus` 在本侧未确认时即回显对方确认状态。改为 `partnerAccepted = myAccepted && partnerSideAccepted`,本侧未确认则对方确认态不回显。
### B. 缺会话与测试保真度(4 处)
- **D5 MEDIUM — 复用/新建 Match 后缺会话**:`confirmActivityInterest` 此前不显式开启会话。现**显式**调用 `chatService.getOrCreateConversation(match.getId(), ...)` 并返回 `conversationAutoCreated=true`(新建与复用两条分支均覆盖)。
- **D6 MEDIUM — 测试库零覆盖(同族根因)**:`schema-h2.sql` 缺 `activity_mutual_connection_consents` 表 → 互选逻辑在测试环境无表可跑,长期静默。已补表(含 `deleted_at`/`expires_at`/`match_id` 与 `chk_consent_status` CHECK、UNIQUE(activity_id,user_a_id,user_b_id)),与生产 V024+V037+V041 对齐;`EntityMigrationParityTest` 将其从 `TEST_SCHEMA_OPTIONAL_TABLES` 移入 `REQUIRED_IN_TEST_SCHEMA`。
- **D7 MEDIUM — 无互选测试**:此前零互选单测。新建 `ActivityMutualConsentServiceTest`(7 例,`@SpringBootTest` 走真实方法路径)。
- **D8 MEDIUM — 断言与 AGENTS.md #20 冲突**:`ChatPhase09IntegrationTest:344` 假设"每次活动互选新建独立会话",与 AGENTS.md #20 "同对用户 Unmatch 后重连复用同一 Match 行、会话随之复用"冲突 → 改为 `assertEquals(conv.getId(), activityConv.getId(), ...)`(认可复用),保留 `sendText`/WebSocket/未读主断言以验证"复用同一消息管线"。
---
## 三、测试保真度修复(3 处)
- **T1 约束漂移盲区**:测试库缺 `activity_mutual_connection_consents` 表(正是 D6 长期不可见的根因)。已补齐并移入 `REQUIRED_IN_TEST_SCHEMA`。
- **T2 迁移基线推进**:`MigrationValidationTest` 基线 40 → 41(新增 V041)。该守卫会拒绝任何超出基线的新迁移文件,是设计内的收敛机制。
- **T3 断言与域语义对齐**:`ChatPhase09IntegrationTest:344` 修正为复用语义(见 D8)。
> 附带教训:**提交态不编译 ≠ 测试通过。** 本轮证明"474 绿"是假象(D3)。每次复验必须先在离线容器跑一次真实 `mvn -o test` 编译+执行,不能以文档声明或历史 ✅ 替代实证。
---
## 四、证据
**定向测试**:`ActivityMutualConsentServiceTest` 7 tests / 0 failures(覆盖双确认建 Match+会话+溯源、INV-006 单向不暴露、decline 阻止、幂等重确认不重复建、expires_at 超时拒绝、软删过滤、复用 active Match)
**负向验证**(注入真实缺陷 → 守卫必须变红,随后全部还原):
| 注入 | 变红的用例 |
|---|---|
| 临时注释 `createMatchAndConversation(consent);` 调用(还原 D1 一半:双确认不建 Match) | `doubleConfirmCreatesMatchAndConversation` / `idempotentReconfirmDoesNotDuplicateMatch`(2 条用例精准变红,归因与注入一一对应) |
还原后确认工作区无 `NEG-VAL` 残留(`grep -c "NEG-VAL"` = 0)。
**全量回归**:`Tests run: 481, Failures: 0, Errors: 0, Skipped: 3` / `BUILD SUCCESS`(离线容器 `danyang-match-backend-test` `mvn -B -o test`,约 5 分;基线 474 → 481,净 +7)
**新增迁移**:`V041__extend_mutual_consent_expiry.sql`(`expires_at` TIMESTAMPTZ + `match_id` UUID + 索引),与 consent 超时拒绝、互选溯源对齐。
---
## 五、变更清单
**新增文件**
- `backend/src/main/resources/db/migration/V041__extend_mutual_consent_expiry.sql`
- `backend/src/test/java/com/danyang/match/platform/activity_mutual/ActivityMutualConsentServiceTest.java`
**修改文件**
- `activity_mutual/service/ActivityMutualConsentService.java`(重写 `confirmConnection` 走 `confirmActivityInterest`;exactly-once 赢家建 Match+会话并 `bindMatch`;删除死代码 `hasActiveMatch`;修复 INV-006 `getMyConsentStatus`)
- `match/mapper/MatchMapper.java`(**新增 `reopenEpisode`**,消除 D3 隐藏编译缺陷)
- `relationship/service/RelationshipService.java`(`confirmActivityInterest` 返回 `matchId`/复用标记并**显式开启会话**)
- `activity_mutual/entity/ActivityMutualConsent.java`(extends BaseEntity + `expiresAt`/`matchId`)
- `activity_mutual/mapper/ActivityMutualConsentMapper.java`(软删过滤 `deleted_at IS NULL` + 原子 `confirmSideIfPending`/`declineSideIfPending`/`bindMatch`)
- `src/test/resources/schema-h2.sql`(新增 `activity_mutual_connection_consents` 表,对齐 V024+V037+V041)
- `deployment/EntityMigrationParityTest.java`(consent 表从可选集合移入 `REQUIRED_IN_TEST_SCHEMA`)
- `deployment/MigrationValidationTest.java`(基线 40 → 41)
- `chat/ChatPhase09IntegrationTest.java`(第 344 行断言对齐 AGENTS.md #20 复用语义)
---
## 六、遗留(供后续阶段)
1. **P16-V11-T13 超时处理未接线**:V041 已加 `expires_at` 列与拒绝逻辑所需字段,但"超时后自动置 expire"的定时/惰性触发尚未实现,留待后续 Phase。
2. **互选超时维度测试**:`ActivityMutualConsentServiceTest.expiredConsentRejectsConfirm` 已就"带 `expires_at` 且已过期的记录拒绝确认"给出守卫,但无"到期自动推进状态"的端到端验证。
3. **双向活动意向入口一致性**:小程序 `activity-mutual-result` 双端状态机已就绪,但双确认成功后的"跳转到既有会话"仍需真机联调确认(后端契约已通过 `conversations/from-match/{id}` 验证)。
---
**Phase 16 已关闭。Phase 17(举报与风控)未自动进入,等待用户确认。**