9.0 KiB
9.0 KiB
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_statusCHECK、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.sqlbackend/src/test/java/com/danyang/match/platform/activity_mutual/ActivityMutualConsentServiceTest.java
修改文件
activity_mutual/service/ActivityMutualConsentService.java(重写confirmConnection走confirmActivityInterest;exactly-once 赢家建 Match+会话并bindMatch;删除死代码hasActiveMatch;修复 INV-006getMyConsentStatus)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 复用语义)
六、遗留(供后续阶段)
- P16-V11-T13 超时处理未接线:V041 已加
expires_at列与拒绝逻辑所需字段,但"超时后自动置 expire"的定时/惰性触发尚未实现,留待后续 Phase。 - 互选超时维度测试:
ActivityMutualConsentServiceTest.expiredConsentRejectsConfirm已就"带expires_at且已过期的记录拒绝确认"给出守卫,但无"到期自动推进状态"的端到端验证。 - 双向活动意向入口一致性:小程序
activity-mutual-result双端状态机已就绪,但双确认成功后的"跳转到既有会话"仍需真机联调确认(后端契约已通过conversations/from-match/{id}验证)。
Phase 16 已关闭。Phase 17(举报与风控)未自动进入,等待用户确认。