Files
danyang-match/docs/Phase-21-V11-Recovery-Completion-Report-2026-09-18.md
WorkBuddy 0580da0c62 fix(account-lifecycle): close Phase 21 KEEP-VERIFY — 6 real defects + fake-green tests
KEEP-VERIFY re-audit of account lifecycle (EXTEND): STATUS marked ✅ but
contained hard Gate violations and dead code.

Real defects fixed:
- D-21-A: AuthService.checkAccountStatus did not reject CLOSED → closed
  account could still log in (Gate ❌→✅); added CLOSED branch before
  BANNED/SUSPENDED.
- D-21-B: notifyActiveMatches was a TODO stub → active Matches/conversations
  not closed on withdrawal (Gate ❌→✅); now finds active matches, sets
  UNMATCHED + closes conversation via chatService.closeConversationForMatch.
- D-21-C: cancelPendingActivityRegistrations was a TODO stub → future
  activity registrations not cancelled (Gate ❌→✅); now cancels pending/approved.
- D-21-D: anonymizeServiceHistory existed but was never wired into
  closeAccount → personal identity + counseling text leaked (Gate ❌→✅);
  wired in, anonymizes id_number/real_name with placeholder, plus
  counseling_followups content.
- D-21-E: closeAccount hard-threw on blockers → all cancelXxx unreachable
  (dead code contradiction); changed to informational log + continue cleanup.
- D-21-F: 7× assertTrue(true) fake-green tests → replaced with 11 real DB
  integration tests + 3 login-guard tests (Redis-decoupled via
  ReflectionTestUtils).

Cross-module defenses (RecommendationEngine/MatchEligibility/Capability
reject non-NORMAL) confirmed effective. Remaining (no silent scope creep):
matchmaker in-progress interception still hardcodes 0 (no table), data
retention delete flow is a product decision, privacy-settings endpoint stub.

Full regression: 650 tests / 0 failures / 0 errors / 3 skipped (baseline 648, +2).
2026-09-18 10:12:34 +08:00

8.3 KiB
Raw Permalink Blame History

Phase 21(账户生命周期)KEEP-VERIFY 复验完成报告 — 2026-09-18

结论:CLOSED(Gate 全项通过) —— 但这是复验后的结论,不是 STATUS.md 原有「✅」所声称的结论。 原有「✅」只是历史进度声明;KEEP-VERIFY 审计发现该模块同时存在 Gate 硬违反、死代码/桩、以及一处会让"关闭未来服务"逻辑永远不可达的设计矛盾。


一、复验起点:为什么「已完成」不可信

STATUS.md 早已把 Phase 21 标为「✅ 已完成」,P21-T01~T05 全部打勾。但 KEEP-VERIFY 实测发现:

  • Gate「注销后不能登录」被违反 —— AuthService.checkAccountStatus 只拒绝 BANNED/SUSPENDED,不拒绝 CLOSED,注销后仍能登录。
  • Gate「注销后不能聊天」被违反 —— notifyActiveMatches 是 TODO 桩(只写日志),注销后 Match 仍是 active、对方不被通知。
  • Gate「关闭未来服务」被违反 —— cancelPendingActivityRegistrations 是 TODO 桩,活动报名不取消。
  • Gate「不反向暴露个人和咨询正文」被违反 —— anonymizeServiceHistory(P21-T05 标的"✅")实现了却从未被 closeAccount 调用(死代码);且 sanitizeUserData 仅脱敏手机号,身份证号脱敏是 TODO,注销后身份证明文(密文)永久留存。
  • 一处设计矛盾(死代码根因) —— closeAccount 在 checkCanWithdraw 返回 canWithdraw=false 时直接抛异常阻断,而 checkCanWithdraw 检查的恰恰就是 cancelXxx 那些待取消的状态(预约/咨询/项目/报名/资格)。结论:下方"关闭未来服务"的清理逻辑永远不可达,且给用户注销设了"需先手动取消所有业务"的障碍——与 AccountLifecycleService 注释「注销必须在线便捷、不设置障碍」直接冲突。
  • 测试假绿 —— AccountLifecycleServiceTest 由 7 个 assertTrue(true) 组成,零断言,却让"Phase 21 已完成"看起来成立。

代码存在 ≠ 功能正确,更 ≠ Gate 通过。


二、修复的真实缺陷(高价值项)

编号 严重度 缺陷 修复
D-21-A 🔴 CRITICAL(Gate) AuthService.checkAccountStatus 不拒绝 CLOSED → 注销后仍能登录 增加 CLOSED 拒绝分支(与 BANNED/SUSPENDED 同级)
D-21-B 🟠 HIGH(Gate 暴露个人/咨询正文 + 死代码) anonymizeServiceHistory 已实现但未接线;身份证脱敏是 TODO closeAccount 接线 anonymizeServiceHistory;sanitizeUserData 增加 user_identities 身份证/姓名脱敏(列 NOT NULL,覆盖为占位符 ***ACCOUNT_CLOSED***);anonymizeServiceHistory 扩展脱敏 counseling_followups.content(INV-013 保护的咨询正文)
D-21-C 🟠 HIGH(Gate 关闭未来服务) cancelPendingActivityRegistrations 是 TODO 桩 实现:全量取消用户 pending/approved 活动报名(用 selectList 规避既有 findByUserId 的 LIMIT 20)
D-21-D 🟠 HIGH(Gate 注销后不能聊天) notifyActiveMatches 是 TODO 桩 实现:查出用户 active/getting_to_know Match → 置 unmatched 并复用 ChatService.closeConversationForMatch 触发 conversation.closed 通知对方
D-21-E 🟡 MEDIUM(死代码矛盾) closeAccount 在 blockers 处硬抛异常,使 cancelXxx/invalidateLocalEligibility 永远不可达;且给用户注销设障碍 改为"信息性日志 + 继续清理"(尊重「注销不设置障碍」+ 计划 520「关闭未来服务」);checkCanWithdraw 仍作为 GET 端点向用户/运营展示待处理业务
D-21-F 🟡 MEDIUM(假绿测试) 7×assertTrue(true) 零断言 删除,替换为 11 用例真实 DB 集成测试 + 3 用例登录守卫

设计修正说明(D-21-E)

原 closeAccount 在 checkCanWithdraw 失败即 throw。这导致"关闭未来服务"的清理逻辑在结构上不可达(典型的"写了清理代码却永远跑不到"死代码),并且与类注释「注销必须在线便捷、不设置障碍」冲突。本轮改为:先调 checkCanWithdraw 仅作信息性日志,随后继续执行取消/退出/失效/关闭 Match/脱敏。这是一次有意为之的行为修正,理由:

  1. AGENTS「注销必须在线便捷、不设置障碍」——不能要求用户先手动取消所有业务才能注销;
  2. 计划 520「注销事务关闭未来服务」——清理必须由注销流程自己完成;
  3. 让原已写好却不可达的 cancelXxx 逻辑真正生效。

⚠️ 此修正改变了"注销在有进行中业务时是否阻断"的行为。原有行为是硬阻断,现已改为"提示 + 自动清理"。若产品方认为某些业务(如 in_progress 咨询)应先经用户确认再注销,属于产品决策,需另行评估——本轮按"不设置障碍 + 自动关闭"实现并出报告。


三、跨模块验证(既有,确认无误)

  • 推荐引擎 RecommendationEngineService:329:非 accountStatus = NORMAL 用户(含 CLOSED)被排除 → 注销后不再进入推荐池。✅(既有)
  • 匹配资格 MatchEligibilityService:149:目标用户非 NORMAL 则不可匹配 → 注销后不可被匹配。✅(既有)
  • Capability CapabilityService:46,67:NORMAL 才可展示能力入口。✅(既有)

这三处与 D-21-A/D-21-D 共同构成 Gate「注销后不能登录/推荐/聊天」的完整防线。


四、验证证据

项 结果
后端全量回归 650 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS(起点 648,净 +2)
新增守卫 AccountLifecycleServiceTest(11 用例:checkCanWithdraw 各拦截项 + closeAccount 全链路 + 关闭 Match)+ AuthServiceAccountClosedGuardTest(3 用例:CLOSED 拒登 / NORMAL 不误伤 / BANNED 仍拒)
负向验证(NEG-VAL) 3 处注入 → 3 用例精准变红、零无关失败:① 移除 checkAccountStatus 的 CLOSED 分支 → closedAccountCannotLogin 变红(经 ReflectionTestUtils 直接调私有方法,与 Redis 解耦,避免 CI 有 Redis 时守卫失效);② 移除 Match 置 unmatched → closeAccountClosesActiveMatch 变红;③ 移除身份证脱敏 → closeAccountProceedsAndCleansUpActiveServices 变红。还原后 md5sum 逐字节一致、残留 0
Git commit + tag phase21-recovery-2026-09-18 + bundle(回读通过),工作区干净

五、遗留 / 待确认(未静默扩权)

  1. 红娘服务进行中拦截 —— checkCanWithdraw 的 activeMatchmakerServices 仍硬编码 0。根因:当前数据模型没有 matchmaker_services 表(全仓搜索为空),该拦截项在设计上无法落地。本轮未凭空建表(属扩权),标注为遗留 TODO,建议产品确认红娘服务的存储模型后再补。
  2. 数据保留"配置 + 人工审核"删除流程 —— 计划 521 要求"数据保留期限未确认的敏感材料采用配置和人工审核,不擅自永久保存或删除"。本轮仅做到"脱敏保留"(PII 已脱敏),可配置保留期限 + 人工审核删除流程属产品决策,未实现,建议单列任务。
  3. 隐私设置接口 —— getPrivacySettings/updatePrivacySettings 仍是 TODO 桩(计划未将其列入 Phase 21 强制项),返回假数据/不更新,属遗留。

六、改动清单

核心修复(生产)

  • auth/service/AuthService.java —— checkAccountStatus 增加 CLOSED 拒绝。
  • account/service/AccountLifecycleService.java —— 注入 MatchMapper/ChatService/UserIdentityMapper/CounselingFollowupMapper;实现 notifyActiveMatches/cancelPendingActivityRegistrations;接线并扩展 anonymizeServiceHistory;扩展 sanitizeUserData 身份证脱敏;closeAccount 取消 blockers 硬阻断改为信息性日志+继续清理。

测试(保真度)

  • account/AccountLifecycleServiceTest.java —— 由 7×assertTrue(true) 重写为 11 用例真实集成测试。
  • auth/AuthServiceAccountClosedGuardTest.java —— 新增 3 用例(Redis 解耦)。

文档

  • docs/Phase-21-V11-Recovery-Completion-Report-2026-09-18.md(本文件)
  • docs/STATUS.md / docs/JIRA_BACKLOG.md —— 同步复验结论与真实缺陷。