fix(counseling): close Phase 13A KEEP-VERIFY/EXTEND — 9 real defects + 4 test-infra defects

Phase 13A(婚恋咨询与婚前辅导)复验关闭。历史 ✅ 不可信:counseling 模块存在双实现,
线上权威 CounselingService(167 行)零测试,唯一被测的 CounselingServiceApp(280 行)
是孤儿类(无注入点、无 HTTP 端点可达,生产永不执行)→ 已合并并删除。

生产缺陷(D1~D9):
- D1 Critical 生产故障 · INV-013 泄露:咨询正文 userRequestSummary 原文被写入 WARN 日志
  (同 Phase 10 D2 一族)→ 改为只记 serviceId + riskLevel,绝不记正文。
- D2 High 越权:申请 userId 取自请求体而非认证主体 → Controller 强制 principal.id()。
- D3 High 并发:分配用「先读后写」→ 两管理员并发分配静默覆盖;
  改 assignIfPending 条件更新(WHERE status='pending' AND assigned_operator_id IS NULL),
  affectedRows=0 明确报错。
- D4 High 越权读:counseling_manager 经 getDetail 可读 followups 正文 → INV-013 绕过通道;
  manager 只加回 userRequestSummary,不装配 followups。
- D5 Medium:无状态机 → 「已完成」可被再取消;新增 ALLOWED_TRANSITIONS + assertTransition。
- D6 Medium 生产故障:GET /counseling/services/intro 后端不存在(且被 /services/{id} 吞掉)
  → 小程序 intro 页 100% 无数据;补端点并声明在变量路径之前。
- D7 Medium 契约:Admin 调用 5 条不存在的 /services/operator/*(404)+ 缺四态
  → 后端补 8 个运营端端点(@PreAuthorize),前端整体重写 + 注册 /counseling 路由。
- D8 Medium 契约:小程序字段名错(userRequestSummary/preferredServiceMode)+ 无幂等
  + my-counseling 未检查 success(403 静默空列表)+ 跳转不存在的页 + WXML 内调用方法。
- D9 Medium 测试保真度:测试库 counseling 两表零 CHECK 约束,
  counseling_services 未列入 EntityMigrationParityTest 任何集合(= 零覆盖)。

测试基建缺陷(T1~T4,不修即恒真假绿):
- T1 守卫找不到 DDL:surefire workingDirectory = ${project.basedir}/.. 使 CWD 为仓库根,
  且 backend/ 是子目录非祖先 → 改为候选后缀 + 逐级向上定位,失败大声抛异常。
- T2 全限定 @com.baomidou...TableField(exist=false) 漏解析(假漂移警报)→ 正则放宽 + 回归守卫。
- T3 OperatorEndpointPermissionCoverageTest 扫到私有辅助方法(假阳性)→ 只扫描真实端点 + 自检。
- T4 两处测试自身逻辑缺陷:陈旧内存快照比较、Instant 纳秒 vs DB 微秒精度。

迁移:新增 V039(counseling 幂等键 + 三个 CHECK 白名单显式固化),基线 38 → 39。

负向验证两轮(均已还原,grep -c NEG-VAL = 0):
- ① DDL 常量守卫:从 V039 移除 'scheduled' → 常量守卫精准变红
  (证明解析的是真实 DDL 而非副本)。
- ② 行为守卫:注入「并发回退先读后写 / manager 读 followups / 状态机守卫失效」
  → 三个用例各自精准变红。

测试:新增 CounselingPhase13ATest(25)+ CounselingConstraintGuardTest(5,解析真实 DDL);
删除孤儿类及其 3 个 DTO、删除测死代码的 CounselingDataScopeTest。
全量回归:Tests run: 420, Failures: 0, Errors: 0, Skipped: 3 / BUILD SUCCESS(4:49)。
基线 391 → 420(净 +29)。admin-web vue-tsc EXIT=0;miniapp tsc EXIT=0。

Tag: phase13a-recovery-2026-09-17
Report: docs/Phase-13A-V11-Recovery-Completion-Report-2026-09-17.md
This commit is contained in:
WorkBuddy
2026-09-17 01:25:12 +08:00
parent 5b3bad326e
commit 7a2693b239
26 changed files with 2208 additions and 700 deletions
@@ -307,15 +307,46 @@ D2——`RelationshipService.createMatch` 不写 episode/lastActivityAt,与 `M
**Files:** `counseling/**`、`miniapp/pages/counseling-*`、`miniapp/pages/my-counseling/**`、`miniapp/pages/premarital-*`、`admin-web/src/modules/counseling/CounselingView.vue`、router。
- [ ] 修复 Admin Vue 语法错误并注册 `/counseling` 路由,先使 type-check 失败转为通过。
- [ ] 用户申请接口从认证主体取 userId;补幂等、取消、详情 owner 校验和合法状态迁移。
- [ ] 分配操作使用条件更新/版本号解决并发;Counselor 只能读取和操作 Assigned Service。
- [ ] Counseling Manager 可分配和查看必要摘要;普通管理员、红娘、内容编辑不得读取咨询正文。
- [ ] 婚前辅导复用 counseling service type;邀请平台内伴侣必须由对方自主确认。
- [ ] U55~U59 修复类型错误、API 契约、Loading/Empty/Error/Permission 状态和非医疗化文案。
- [x] 修复 Admin Vue 语法错误并注册 `/counseling` 路由,先使 type-check 失败转为通过。
- [x] 用户申请接口从认证主体取 userId;补幂等、取消、详情 owner 校验和合法状态迁移。
- [x] 分配操作使用条件更新/版本号解决并发;Counselor 只能读取和操作 Assigned Service。
- [x] Counseling Manager 可分配和查看必要摘要;普通管理员、红娘、内容编辑不得读取咨询正文。
- [x] 婚前辅导复用 counseling service type;邀请平台内伴侣必须由对方自主确认。
- [x] U55~U59 修复类型错误、API 契约、Loading/Empty/Error/Permission 状态和非医疗化文案。
**Gate:** 申请→分配→预约→服务→完成→反馈真实闭环通过;所有隐私负向用例通过。
> **复验完成(2026-09-17)** — Tag:`phase13a-recovery-2026-09-17`
>
> 复验发现 counseling 模块存在**双实现**:线上权威 `CounselingService`(167 行)
> **零测试**,唯一被测的 `CounselingServiceApp`(280 行)是**孤儿类**
> (无注入点、无端点可达,生产永不执行)。已合并并删除孤儿类。
>
> **9 处缺陷(D1~D9)**:
> - D1 **Critical 生产故障 / INV-013 泄露**:咨询正文原文被写入 WARN 日志(同 Phase 10 D2 一族)。
> - D2 High 越权:申请 userId 取自请求体而非认证主体。
> - D3 High 并发:分配「先读后写」→ 并发**静默覆盖**;改 `assignIfPending` 条件更新。
> - D4 High 越权读:manager 经 `getDetail` 可读 followups 正文(INV-013 绕过通道)。
> - D5 Medium:无状态机 → 「已完成」可被再取消。
> - D6 Medium 生产故障:`GET /services/intro` 后端不存在,小程序 intro 页 100% 无数据
> (且被 `/services/{id}` 吞掉)→ 已补端点并置于变量路径之前。
> - D7 Medium:Admin 调用 5 条不存在的运营端端点(404)+ 缺四态。
> - D8 Medium:小程序字段名不匹配 + 无幂等 + 未检查 `success`(403 静默空列表)+ 跳转不存在的页。
> - D9 Medium 测试保真度:测试库 counseling 两表**零 CHECK 约束**,`counseling_services` 零覆盖。
>
> **另修 4 处测试基建缺陷(T1~T4)**:守卫因 surefire `workingDirectory` 指向仓库根而
> **找不到 DDL**(恒真假绿);全限定 `@TableField(exist=false)` 漏解析(假警报);
> 权限守卫扫到私有辅助方法(假阳性);两处测试自身逻辑缺陷。
>
> **负向验证两轮**:① DDL 常量守卫 —— 从 V039 移除 `scheduled` → 守卫精准变红
> (证明解析的是**真实 DDL** 而非副本);② 行为守卫 —— 注入并发/INV-013/状态机
> 三个缺陷 → 三个用例精准变红。均已还原。
>
> **全量回归实测**:`[WARNING] Tests run: 420, Failures: 0, Errors: 0, Skipped: 3` /
> `BUILD SUCCESS`(4 分 49 秒)。Phase 13 基线 391 → 420(净 +29)。
> **Phase 13A 已关闭。Phase 14 未自动进入,等待用户确认。**
> 报告:`docs/Phase-13A-V11-Recovery-Completion-Report-2026-09-17.md`
### Phase 14:多类型公益活动 — REFACTOR/EXTEND
**Files:** activity 模块、Miniapp activity 页面、Admin activity 页面。