docs: 二次修正 features 断言失实(5 处)—— 原判据「*.yml 无 features 块」被证伪
Phase 22 复验报告 D-22-D 的判据「backend/src/main/resources/*.yml 无 features: 块」是错的,
且被上一轮收尾照抄进 STATUS.md(P26-T06 行),属二次传播。
实测事实:
- application.yml(基础) → 无 features 块
- application-dev.yml(默认激活 profile,自 63cbbff)→ 有,21 键
- application-docker.yml(自 730dddd / Phase 23B) → 有
硬证据:dev 的 chat-image-enabled: true 覆盖了 FeatureFlagConfig 的 Java 字段默认 false ——
若「仅 Java 字段默认值生效」成立,该值不可能为 true。
仍然成立的部分:T01~T05 模块开关与 T07 私有桶确实全仓零业务调用点(实测 12 个 flag 的
调用点计数全为 0),「死配置」结论正确;失实的只是来源(非 application.yml、非「仅 Java 默认值」)。
新发现 D-22-E:AppController#getConfig(GET /app/config)返回一份硬编码 features map
(imageChatEnabled 恒 true,键名与后端 chatImageEnabled 不一致)→ 与 FeatureFlagConfig 双源,
前端读到的开关可能与实际配置相反(如 appealEnabled 实际默认 false)。不静默改契约,登记为遗留。
修正落点:STATUS.md(P26-T06 行 / Phase 22 段 / 历史声明块)、Phase-22 报告(表格行 + D-22-D +
新增 D-22-E)、JIRA_BACKLOG.md、Phase-V11-Deployment-Completion-Report.md、
治理报告新增第九节(含「同一文件并行 Edit 会随机丢失」的事故记录与纪律)。
This commit is contained in:
@@ -179,7 +179,7 @@
|
||||
| Phase 19 统一运营后台 + 用户端公益服务页面复验 | 已完成(2026-09-17) | 见 `Phase-19-V11-Recovery-Completion-Report-2026-09-17.md`;修复 **25 处真实缺陷**(**D20 服务站公告能力整条不可达:Service + V042 表都在、全仓零端点**;**D21 服务站 `create/update` 越权写:类级 `service_location.view` 兜底,`service_location.manage` 是从未使用的死权限**;D22 该控制器未纳入权限覆盖守卫;**D2/D3 Banner 校验 500 + 软删 Banner 仍上首页**;**D5/D6 Content 发布死路 + 分类停用不可恢复**;D10~D14 ServiceProgram 审批死路/草稿泄露/成员数恒 0/退出未实现/撞唯一键;D9/D23 测试库缺 `content_categories` 与 `service_location_announcements` → 零覆盖;D18 预约 IDOR;D24 迁移基线未跟进 V042);补 V042 公告表 + 运营端五端点 + 用户端只读端点 + 后台公告管理 UI + 小程序 U50 公告区块;新增 3 组守卫(24+5+5 用例)并把服务站运营端收入权限覆盖扫描;负向验证 3 注入 → 4 用例精准变红;后端 **602 tests 全绿**;admin-web `vue-tsc`/`vite build` 双绿;小程序 `tsc` EXIT=0;ui-tests 14/14;**Phase 20 尚未获准** |
|
||||
| Phase 20 Analytics 数据统计与管理驾驶舱复验 | 已完成(2026-09-17) | 见 `Phase-20-V11-Recovery-Completion-Report-2026-09-17.md`;修复 **20 处真实缺陷**(**D1 实名人数用 `profile_status != 'rejected'` 冒充 → 把"资料还在填"算成已实名**;**D3 Chat Started 直接数会话行数 → 系统通知计入**;**D4 Match 不分来源 → 10 对互选被当 10 对成功引用**;**D5 活动漏斗全部硬编码 0 + 一行把"用户数"当"活动数"的死代码**;**D6 Banner 曝光/点击硬编码 0**;**D12 系统性软删缺陷:`BaseEntity.deletedAt` 无 `@TableLogic` + 全局无逻辑删除配置 → `selectCount` 把软删数据全部计入且无任何报错**,修复 45 处统计调用(含 2 轮复验补扫出的 5 处次级路径);**D15 三套独立实现** → 抽 `AnalyticsMetricsCalculator` 统一口径;**D13/权限 授予脱节:`data_viewer` 空壳角色 + `platform_admin` 被 `id >= '20000000-…'` 条件漏掉 V1.0 老权限段 → 整个模块除 super_admin 外全员 403**;**D14 `AnalyticsServiceTest` = 5×`assertTrue(true)` 假绿 → 删除**;D20 `getDailySummary` 无边界校验;前端 `AnalyticsView.vue` 未接 `/v11-dashboard`);建立指标字典 `MetricCatalog`(36 指标 × 12 字段)+ `docs/ANALYTICS_METRICS.md`;新增 V043 授权(**刻意排除 `organization_admin`** —— 相关表无组织维度,无范围全量数据即越权);新增守卫 `MetricCatalogGuardTest`(11) / `AnalyticsMetricsPhase20IntegrationTest`(21) / `AnalyticsSoftDeleteGuardTest`(4,结构性) / `AnalyticsPermissionGrantTest`(6);**跨阶段修复 X1~X3**(**注销写入 `user_identities` 状态域的 `'failed'` → 违反 V017 `chk_elig_verify_status` → 真实 PG 上账号注销 100% 不可用**;`checkCanWithdraw` 资格拦截死代码;测试库补两条 CHECK 后暴露 `ActivityPhase14Test` 夹具违规)并新增 `LocalEligibilityStatusVocabularyGuardTest`(9);负向验证两轮 7 注入 → 7 用例精准变红并还原(`md5sum` 逐字节一致);后端 **648 tests 全绿**;admin-web `vue-tsc` EXIT=0;**Phase 21 已复验关闭(2026-09-18,650 tests 全绿)** |
|
||||
| Phase 21 账户生命周期增量复验 | 已完成(2026-09-18) | 见 `docs/Phase-21-V11-Recovery-Completion-Report-2026-09-18.md`;KEEP-VERIFY 复验发现 STATUS 标✅ 但含 **6 处真实缺陷 + 假绿测试**:① `AuthService.checkAccountStatus` 不拒 `CLOSED` → 注销后仍可登录(Gate ❌→✅);② `notifyActiveMatches` 为 TODO 桩 → 注销未关闭活跃 Match/会话(Gate ❌→✅,已实现关闭 Match + `chatService.closeConversationForMatch`);③ `cancelPendingActivityRegistrations` 为 TODO 桩 → 未来活动报名未关闭(Gate ❌→✅);④ `anonymizeServiceHistory` 已实现却**从未被 `closeAccount` 调用(死代码)** → 个人/咨询正文暴露(Gate ❌→✅,已接线 + 脱敏身份证占位符 + `counseling_followups` 正文);⑤ `closeAccount` 在 blockers 处硬抛异常使 `cancelXxx` 永远不可达(死代码矛盾 → 信息性日志 + 继续清理);⑥ 删除 7×`assertTrue(true)` 假绿 → 替换为 11 用例真实 DB 集成测试 + 3 用例登录守卫;跨模块防线(推荐引擎/匹配资格/Capability 已拒非 NORMAL)确认有效;后端 **650 tests 全绿**(起点 648,净 +2);**Phase 22 已复验关闭(2026-09-18,657 tests 全绿)** |
|
||||
| Phase 22 部署运维配置扩展(Feature Flag)复验 | 已完成(2026-09-18) | 见 `docs/Phase-22-V11-Recovery-Completion-Report-2026-09-18.md`;KEEP-VERIFY 复验发现 STATUS 标✅ 但含 **2 处真实代码缺陷 + 2 处文档夸大**:① D-22-A(High)`FeatureFlagController.updateFlag` 仅支持布尔 flag(反射 `getMethod("set"+cap, boolean.class)`)→ 7 个非布尔 flag 经 API 更新必失败,已改为按字段类型分派 boolean/int/String;② D-22-B(Medium)`body.getOrDefault("value", false)` 缺 value 静默置 false + 非布尔转型崩,已改为缺 value/类型错→400 且不改原值;T01–T05 模块开关(`isCounselingEnabled` 等)全仓无业务调用点 → 死配置(暴露未 gate,遗留交各模块 Phase);T06 上传策略实测已在 `LocalEligibilityService`+`PhotoService` 真强制 ✅;T07 私有桶零引用 → 死配置(遗留);yml 无 `features:` 块、默认值实为 Java 字段默认(文档纠正);新增 `FeatureFlagControllerTest`(7 用例)+ 负向验证(仅布尔缺陷版恰 2 用例变红);后端 **657 tests 全绿**(起点 650,净 +7);**Phase 23 已复验(2026-09-18,部分关闭)** |
|
||||
| Phase 22 部署运维配置扩展(Feature Flag)复验 | 已完成(2026-09-18) | 见 `docs/Phase-22-V11-Recovery-Completion-Report-2026-09-18.md`;KEEP-VERIFY 复验发现 STATUS 标✅ 但含 **2 处真实代码缺陷 + 2 处文档夸大**:① D-22-A(High)`FeatureFlagController.updateFlag` 仅支持布尔 flag(反射 `getMethod("set"+cap, boolean.class)`)→ 7 个非布尔 flag 经 API 更新必失败,已改为按字段类型分派 boolean/int/String;② D-22-B(Medium)`body.getOrDefault("value", false)` 缺 value 静默置 false + 非布尔转型崩,已改为缺 value/类型错→400 且不改原值;T01–T05 模块开关(`isCounselingEnabled` 等)全仓无业务调用点 → 死配置(暴露未 gate,遗留交各模块 Phase);T06 上传策略实测已在 `LocalEligibilityService`+`PhotoService` 真强制 ✅;T07 私有桶零引用 → 死配置(遗留);「`features:` 块不存在」为**误判**(2026-09-18 二次修正:`application-dev.yml`/`application-docker.yml` 均有该块且实际生效,仅基础 `application.yml` 无);另 `AppController#getConfig` 硬编码第二份 features(D-22-E 遗留);新增 `FeatureFlagControllerTest`(7 用例)+ 负向验证(仅布尔缺陷版恰 2 用例变红);后端 **657 tests 全绿**(起点 650,净 +7);**Phase 23 已复验(2026-09-18,部分关闭)** |
|
||||
| Phase 23 可信系统测试重建(REFACTOR)复验 | ⚠️ 部分关闭(2026-09-18) | 见 `docs/Phase-23-V11-Recovery-Completion-Report-2026-09-18.md`;KEEP-VERIFY 复验发现 STATUS 标✅ 但含 **5 处真实缺陷**:① **D-23-A(Critical)** `V11InvariantsTest` 原 14 个用例全为常量字符串断言 —— INV-016「Banner 目标类型」实际断言**文章状态**常量(跑错域)、INV-017「首页 8 入口」是**对自己刚声明的字面量数组做 `length==4` 的重言式**(从未调用首页服务)、INV-018/019/020 用预约/文章/账号状态常量冒充权限与数据范围验证;已重写为 12 个用例,含 **4 个真实服务行为守卫**(INV-013 走真实 `CounselingService.getDetail` 断言 Matchmaker 读到 `userRequestSummary==null` 并以 assigned Counselor 能读正文作反证;INV-016 反射读生产 `ALLOWED_TARGET_TYPES` 断言不含外链;INV-017 真实 `getHome` 断言 8 入口编码与顺序固定;INV-019 断言 `content_editor` 读到 null)+ `assertBehaviorTestExists` 用 `Class.forName` 把「行为验证见 XxxTest」变成可执行契约;② **D-23-B(High)** `tests/e2e/` **44 个 `pass` 空壳**(4 个文件 100% 为空壳,另 2 个混合),违反 Gate「不允许 skip/pass 空壳」→ 已移除并改为诚实占位(保留完整场景清单);③ **D-23-C(High)** pytest E2E 套件**整体不可执行**:全仓无 `conftest.py`,约 15 个 fixture(`api_client`/`verified_user`/`banner_factory`…)不存在,唯一可执行的 `run_v11_tests.py`(16 用例)需活栈 + `E2E_PASSWORD`;④ **D-23-D(High,遗留未修)** CI 未串联 E2E(计划明确要求)→ 需先决定 CI 活栈供给方式,未实施;⑤ **D-23-E(Medium)** CI `migration` job 注释称「验证 SQL 语法」实为只查文件非空(真实语法校验在 `MigrationValidationTest`,随 backend job 跑)→ 改为真实序列校验(命名/非空/版本号唯一/无缺号,43 个文件 1~43 连续唯一)。**新增闸门**:`scripts/check-no-empty-tests.py`(AST 扫描阻断 `pass` 空壳与无条件 `skip`,已双向验证)+ CI `test-shell-guard` job。**证据**:负向注入 3 处生产缺陷 → 恰好 `inv013`/`inv016`/`inv017` 3 个用例精准变红、其余 9 绿、零无关失败,`md5sum -c` 还原一致;`655 tests / 通过 652 / 失败 0 / 错误 0 / 跳过 3`(起点 657,净 −2 因 14 空转用例合并为 12;3 个跳过均有显式原因,非空壳)。**遗留**:pytest fixture 体系缺失、INV-011 缺显式守卫(E2E 入 CI 与 fixture 体系已由 Phase 23B 补齐)。**结论:不宣布「失败为零可进入 UAT」**;**Phase 23B(E2E 测试基建)已于 2026-09-18 完成并关闭** |
|
||||
| Phase 23B E2E 测试基建重建 | 已完成(2026-09-18) | 补齐 Phase 23 遗留 D-23-C(E2E 整体不可执行)与 D-23-D(CI 未串联 E2E)。**交付**:`tests/e2e/conftest.py`(ApiClient / `operator_token` / `new_user` / 栈探活)、`pytest.ini`、`tests/e2e/requirements.txt`、`tests/e2e/sql/seed_e2e_operators.sql`(迁移里**没有任何 operator 种子**,全新库无账号可登录 → 6 个 e2e 账号 + 角色绑定,幂等,口令由 `psql -v pwd` 注入)、CI job `e2e`(起真实栈→等就绪→seed→pytest→失败阻断→清理)、8 个 E2E 文件改造(消除 import 期 `KeyError`,替换恒真/名不副实断言)。**核心设计**:栈不可达时 `pytest.exit(4)` **而非 skip**(skip 会把未执行伪装成通过)。**验证**:本机真实活栈 **33 passed**;栈不可达/缺口令均 exit 4;后端 **656 tests / 0 failures / 0 errors / 3 skipped**(基线 655,+1 为新增构建产物守卫,已负向验证:注入重复 `V042` → 恰 1 用例变红)。**本轮暴露并修复 6 处真实生产缺陷**(H2 零覆盖):D-23B-A 构建产物残留重复迁移版本号 042 → 应用起不来;D-23B-B 非 UUID path → 500 + 数据库异常外泄;D-23B-C 6 个 E2E 文件 import 期崩溃 → 从未执行;D-23B-E `super_admin` 仅 18/48 权限、`matchmaker` 0 权限(matchmaker 部分列遗留,需产品定义);D-23B-I 非法枚举参数 → 500(应 BAD_REQUEST);D-23B-J `uuid = character varying` → JDBC 加 `stringtype=unspecified`。**另有一处判断被证伪后撤回**:初查以为 `service_appointment.register` 权限丢失,核实后端点实际用 `service.assisted_registration`(V029 已 seed),未据此改动任何代码。详见 `docs/Phase-23B-V11-E2E-Infrastructure-Completion-Report-2026-09-18.md` |
|
||||
| HTTP 语义检修(失败响应携带真实 HTTP 状态码) | 已完成(2026-09-18) | 见 `docs/HTTP语义检修报告-2026-09-18.md`;起因:Phase 23B 收尾时质疑「HTTP 200 + body.code 这套约定,开放第三方还守得住吗」→ 审计确认**当时就已经是断的**。**5 处发现**:105 处 / 30 个 Controller 直返 `ApiResponse.error()` 不抛异常、不带 `@ResponseStatus` → **HTTP 恒为 200**;小程序 `api.js` 的 401/403/429 分支**永远走不到**(token 过期回 403 → 不触发 `onSessionExpired` → **用户卡死无法重登录**);`SecurityConfig` 未配 `authenticationEntryPoint` → 未认证回 403 且 body 连 `code` 都没有;`CorsConfig` 默认 `*` + `allowCredentials(true)` 而注释称"默认只允许 localhost";全量测试仅 4 文件 / 15 处断言触及状态码。**修法**:`ApiResponseCode` 新增 `httpStatus` 字段(**唯一真相源**,新增码不填就编译不过)+ 新增 `ApiResponseStatusAdvice`(`ResponseBodyAdvice`,只补 2xx、只改状态行、body 一字不动)→ **105 处 Controller 零改动获得正确状态**;补 401/403 入口点并统一 body;CORS 默认改为拒绝跨域。**同日续做(身份类型与路径规则收敛)**:兜底规则由 `authenticated()` 收紧为 `hasRole("USER")`(`@AuthenticationPrincipal UserPrincipal` 遇 `OperatorPrincipal` 类型不匹配返回 **null** → 全仓 **244 处**使用、**仅 48 处**判空 → 196 处潜在 NPE→500 在认证层一次性消解);显式枚举 9 个运营前缀(`/**/operator/**` 是**非法 PathPattern**,写出后整个过滤器链 500,实测 E2E 由 37 passed 直落 33 failed);新增 `SecurityPathCoverageTest` 机械守卫(负向验证:摘掉 `/content/operator/**` → 恰 1 用例变红并列出受影响的 6 个端点)。**验证**:E2E **40 passed**;后端 **664 / 0 failures / 0 errors / 3 skipped**(首轮 662/3 failures 经判定**均为"测试断言了旧的错误行为"**,改断言而非改回实现,且保留各自的实质性安全断言)。遗留(属范围扩张,需产品决策):第三方凭据机制、限流是否真实生效、OpenAPI 对外发布、调用审计与配额、脱敏边界 |
|
||||
|
||||
Reference in New Issue
Block a user