fix(appeal): close Phase 18 KEEP-VERIFY — 8 defects (zero-coverage appeals table + 6 fake-green tests; operator-notification FK crash; appealType CHECK; escalated dead-end; processAppeal race; soft-delete filters; hardcoded resolved; evidence contract); add AppealPhase18IntegrationTest (15); full regression 494 green

This commit is contained in:
WorkBuddy
2026-09-17 21:07:25 +08:00
parent 307c9efb36
commit 3f60611a74
13 changed files with 661 additions and 151 deletions
+19 -9
View File
@@ -19,7 +19,10 @@
>
> **2026-09-17 Phase 17 复验关闭:** EPIC-P17-V11 已完成。复验发现风控/举报整条真实 DB 路径因测试库缺 `reports`/`risk_events`/`risk_actions` 三表而**零覆盖**,`ReportServiceTest` 全是 `assertTrue(true)` 假绿;且敏感访问(查看他人风险画像/举报)此前**无审计**(违反 AGENTS.md #8 默认拒绝 + 查看需审计);另修复审计缺 `ip`/`userAgent`、`findByUserId` 未过滤软删、人工标记无审计,以及 `javax`→`jakarta` 隐藏编译缺陷与 `risk_events` 漏 `updated_at` 列。共修复 **6 处真实缺陷**,补齐三表并新增 `ReportAndRiskPhase17IntegrationTest`(7 例),负向验证证明守卫有牙。全量回归 **488 tests 全绿**。
> 证据:`docs/Phase-17-V11-Recovery-Completion-Report-2026-09-17.md`。
> **Phase 18 需用户确认后启动。**
>
> **2026-09-17 Phase 18 复验关闭:** EPIC-P18-V11 已完成。复验发现申诉整条真实 DB 路径因测试库缺 `appeals` 表而**零覆盖**,`AppealServiceTest` 9 例中 6 例是 `assertTrue(true)` 假绿(覆盖的恰是 Gate 四条不变量);且 `escalateAppeal` 向 `operatorId` 发用户通知会撞 `notifications.user_id → users(id)` FK(operators 与 users 不同表)→ PG 上升级 100% 失败;`appealType` 未校验生产 `chk_appeal_type` 白名单。另修复 escalated 死胡同、`processAppeal` 先读后写并发竞态、三处查询漏软删过滤、硬编码 `resolved`、证据字段契约。共修复 **8 处真实缺陷**(含 1 处必崩级测试盲区、2 处「只在 PG 上炸」),补 `appeals` 表并新增 `AppealPhase18IntegrationTest`(15 例)、删除假绿 9 例,负向验证证明 Gate 守卫有牙。全量回归 **494 tests 全绿**;admin-web `vue-tsc` EXITCODE=0。
> 证据:`docs/Phase-18-V11-Recovery-Completion-Report-2026-09-17.md`。
> **Phase 19 需用户确认后启动。**
> **2026-09-16 状态校正:** Phase 07 UI 已按 2026-09-13 规格重新实现,**首批 11 页于 2026-09-16 通过用户验收(结论:基本达标)**,本文件此前"视觉否决/REOPENED"口径随之关闭。
>
@@ -141,6 +144,7 @@
| Phase 15 活动报名审核签到复验 | 已完成(2026-09-17) | 见 `Phase-15-V11-Recovery-Completion-Report-2026-09-17.md`;修复 **16 处缺陷**(**D1 签到码一生成即作废**:`generateCheckinToken` 把 token 写 `"used"` 而扫码端见 `"used"` 即拒 → 扫码 100% 失败;**D9 取消/拒绝后永远无法再报名**:撞生产 `UNIQUE(activity_id,user_id)`,测试库缺该约束故零覆盖 → 改复用记录;D2 重复签到竞态、D3 签到无数据范围、D4 手工签到无校验、D5 统计 NPE+语义错、D6 非法 token 500、D7 签名非恒定时间比较、D10 后台审核列表无 pending、D11 报名窗口不生效、D12 取消无审计、D13 审核/取消非原子、D14 容量分组不参与扣减)+ **2 处前端契约缺陷**(admin-web 无签到工作台、小程序无取消报名入口);新增 V040 `activity_staff_assignments` 落实 INV-020 活动数据范围;**3 处测试保真度缺陷**(测试库缺 UNIQUE+2 CHECK、3 条 `assertTrue(true)` 假绿、表未入必选集合);新增 `ActivityPhase15Test`(21),`CheckinServiceTest` 假绿改真实守卫(5),约束守卫 +2,迁移基线 39→40;一轮负向验证 3 处注入 → 8 条用例精准变红并还原;admin-web `vue-tsc` 0 error + `vite build` 通过;后端 **474 tests 全绿**;**Phase 16 尚未获准** |
| Phase 16 活动互选二阶段同意复验 | 已完成(2026-09-17) | 见 `Phase-16-V11-Recovery-Completion-Report-2026-09-17.md`;修复 **7 处缺陷 + 1 处隐藏编译缺陷 + 1 处测试保真度缺陷**(**D1 双确认后根本不建 Match/会话(Gate 缺陷·INV-014/015)**、**D3 Phase 15 提交态引用未定义 `reopenEpisode` 致整个模块不编译("474 绿"为假象)**、D4 INV-006 单向意向泄露、D5 复用/新建 Match 后缺会话);统一双实现到 `RelationshipService.confirmActivityInterest`;`MatchMapper` 补 `reopenEpisode`;测试库补 `activity_mutual_connection_consents` 表并移入必选集合,`MigrationValidationTest` 基线 40→41;新建 `ActivityMutualConsentServiceTest`(7 用例);`ChatPhase09IntegrationTest:344` 断言对齐 AGENTS.md #20 复用语义;新增 V041 `expires_at`+`match_id`;负向验证 2 用例精准变红并还原;后端 **481 tests 全绿**;**Phase 17 尚未获准** |
| Phase 17 举报与风控复验 | 已完成(2026-09-17) | 见 `Phase-17-V11-Recovery-Completion-Report-2026-09-17.md`;修复 **6 处真实缺陷**(**D1(Critical,必崩级盲区)测试库缺 `reports`/`risk_events`/`risk_actions` 三表 → 风控/举报真实 DB 路径零覆盖,`ReportServiceTest` 全 `assertTrue(true)` 假绿**;**D3(High)敏感访问(查看他人风险画像/举报)无审计(违反 AGENTS.md #8)**;D4 审计缺 `ip`/`userAgent`;D5 `findByUserId` 未过滤软删;D2 人工标记无审计;**D6 `javax`→`jakarta` 隐藏编译缺陷 + `risk_events` 漏 `updated_at` 列**);补齐三表并移出白名单入 `REQUIRED_IN_TEST_SCHEMA`;新增 `AuditLogService.recordSensitiveAccess` 并接线三端点;新增 `ReportAndRiskPhase17IntegrationTest`(7 用例);负向验证(注释 getReportDetail 审计 → D3 守卫精准变红并还原);后端 **488 tests 全绿**;**Phase 18 尚未获准** |
| Phase 18 申诉复验 | 已完成(2026-09-17) | 见 `Phase-18-V11-Recovery-Completion-Report-2026-09-17.md`;修复 **8 处真实缺陷**(**D1(Critical,必崩级盲区)测试库缺 `appeals` 表 + `AppealServiceTest` 9 例中 6 例 `assertTrue(true)` 假绿 → 申诉真实 DB 路径零覆盖**;**D2(High,只在 PG 上炸)`escalateAppeal` 向 `operatorId` 发用户通知撞 `notifications.user_id → users(id)` FK → 升级事务整体回滚**;**D3(High,只在 PG 上炸)`appealType` 未校验生产 `chk_appeal_type` 白名单**;D4 escalated 死胡同(前后端均无法再决策);D5 `processAppeal` 先读后写并发竞态;D6 三处查询漏 `deleted_at` 过滤;D7 硬编码 `resolved`;D8 证据字段契约 `evidenceFileIds`);补 `appeals` 表并移出白名单入 `REQUIRED_IN_TEST_SCHEMA`;新增原子认领 `markReviewingIfProcessable`;新增 `AppealPhase18IntegrationTest`(15 用例),删除假绿 `AppealServiceTest`(9 用例);负向验证(误伤其他处置 → Gate 用例精准变红并还原);后端 **494 tests 全绿**;admin-web `vue-tsc` EXITCODE=0;**Phase 19 尚未获准** |
| UI-CLEANUP-20260913 | 已完成 | 废弃 UI 代码、冲突规范、旧计划和无效验收证据已清理;`ui-document-baseline-contracts` 锁定唯一视觉入口 |
| UI-ADMIN-20260913 | 已完成(待桌面端目视复验) | 管理端全局框架与工作台已按规格交付;18 modules + 22 routes,vue-tsc 与 vite build 全绿 |
| UI-MINIAPP-20260913 | 已完成 | 小程序 11 个核心页面已重构;11/11 页 TS-only,2026-09-16 通过用户验收 |
@@ -180,7 +184,7 @@
| EPIC-P15-V11 | P15 | 活动签到回归测试 | P1 |
| EPIC-P16-V11 | P16 | 活动互选二阶段同意重构 | P0 | ✅
| EPIC-P17-V11 | P17 | 公益服务风险来源扩展 | P0 | ✅
| EPIC-P18-V11 | P18 | 申诉保持 | P1 |
| EPIC-P18-V11 | P18 | 申诉保持 | P1 | ✅
| EPIC-P19-V11 | P19 | 善缘丹阳综合运营后台 | P1 |
| EPIC-P20-V11 | P20 | 公益项目数据统计扩展 | P1 |
| EPIC-P21-V11 | P21 | 账户生命周期增量 | P1 |
@@ -507,16 +511,22 @@
- 证据:`docs/Phase-17-V11-Recovery-Completion-Report-2026-09-17.md`
---
## EPIC-P18-V11: 申诉保持 ✅ 已验证(V1.0基础保持不变)
## EPIC-P18-V11: 申诉保持 ✅ 已完成(2026-09-17 KEEP-VERIFY 复验关闭)
### Verification
- V013 appeals 表结构完整(ban_appeal / suspend_appeal)
- AppealService: FeatureFlag 控制 + 幂等性 + 可申诉限制(仅 ban/suspend)
### Verification(2026-09-17 复验后校正)
- V013 `appeals` 生产 DDL 完整(`chk_appeal_status` / `chk_appeal_type`);但**测试库此前完全缺该表**(只在 parity 可选白名单里 = 无人测),已补入 `schema-h2.sql` 并移入 `REQUIRED_IN_TEST_SCHEMA`。
- AppealService: FeatureFlag 控制 + 幂等性 + 可申诉限制(仅 ban/suspend)——复验后补 `appealType` 白名单校验与处置类型一致性。
- AppealController: 用户端 account-status / submit / my
- AppealOperatorController: 后台端 pending / detail / process / stats
- Miniapp: appeal 页面已注册
- 申诉通过 → 自动解除对应 risk_action(status→revoked)
- 申诉被拒 → 维持原处置
- Miniapp: appeal 页面已注册(证据字段名 `evidenceFileIds` 已与后端 DTO 对齐)
- 申诉通过 → 自动解除**对应** risk_action(`status → resolved`,只解除目标处置;其他仍有效处置不受影响)。
⚠️ 旧注"status→revoked"**不实**:`Constants.RISK_ACTION_STATUS_REVOKED` 从无使用点,且不在生产 `chk_risk_action_status`(active/resolved/expired)白名单内;实现使用 `resolved`。
- 申诉被拒 → 维持原处置(`risk_action` 不变更)
### 2026-09-17 KEEP-VERIFY 复验结论
- 修复 **8 处真实缺陷**:D1 零覆盖(缺 `appeals` 表 + 6 处 `assertTrue(true)` 假绿);**D2 `escalateAppeal` 向 operator 发用户通知撞 `notifications.user_id → users(id)` FK → PG 上升级 100% 失败**;**D3 `appealType` 未校验 `chk_appeal_type`**;D4 escalated 死胡同;D5 `processAppeal` 先读后写并发竞态;D6 三处查询漏软删过滤;D7 硬编码 `resolved`;D8 证据字段契约。
- 新增 `AppealPhase18IntegrationTest`(15 例),删除假绿 `AppealServiceTest`(9 例);负向验证证明 Gate 守卫有牙;后端 **494 tests 全绿**;admin-web `vue-tsc` EXITCODE=0。
- 证据:`docs/Phase-18-V11-Recovery-Completion-Report-2026-09-17.md`
### 新增文件
- V025__extend_risk_sources.sql — 扩展 risk_events.event_type CHECK + risk_actions.action_type