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:
WorkBuddy
2026-09-18 21:58:58 +08:00
parent f63e579a8d
commit 6b2ff0f73e
5 changed files with 87 additions and 10 deletions
+1 -1
View File
@@ -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 对外发布、调用审计与配额、脱敏边界 |
@@ -30,7 +30,8 @@ STATUS 遗留区「Phase 22 部署运维配置扩展 ✅ 已完成」逐条声
| D-22-A | High | `FeatureFlagController.updateFlag` 用反射 `getMethod("set"+cap, boolean.class)` 只支持布尔 flag。7 个非布尔 flag(`livenessProvider`、`localEvidenceAllowedTypes`、`counselingAttachmentsBucket`、`localEvidenceMaxFiles`、`localEvidenceMaxFileSizeMB`、`localEvidenceRetentionDays`)经 API 更新必抛 `NoSuchMethodException` → 返回错误,运维无法经接口修改这些配置。 | 改为按字段类型分派:`boolean` → `setXxx(boolean)`、`int` → `setXxx(int)`、`String` → `setXxx(String)`;未知类型返回明确错误。 |
| D-22-B | Medium | `updateFlag` 用 `body.getOrDefault("value", false)`:缺 `value` 时静默置 `false`(可能误关功能);非布尔值转型 `((Boolean) value)` 直接崩。 | 改为:缺 `value` → 400;`value` 按字段类型校验后转型;类型不匹配返回 400 且**不**改变原值。 |
| D-22-C | Doc/Accuracy | STATUS 将 T01–T05、T07 标 ✅,但对应 flag 从未被业务读取(死配置)。 | 不静默扩权改写各模块;在报告中如实降级为「配置暴露但未强制」,列为遗留项交各模块 Phase 处理。 |
| D-22-D | Doc/Accuracy | STATUS 称「application.yml 默认配置(15 个 flag)」,但 yml 无 `features:` 块。 | 在报告/STATUS 中纠正:默认值来自 `FeatureFlagConfig` 的 Java 字段默认值,非 yml。功能不受影响。 |
| D-22-D | Doc/Accuracy | STATUS 称「application.yml 默认配置(15 个 flag)」,**来源**表述错误:features 不在基础 `application.yml`。⚠️ **本行原判据「`*.yml` 无 `features:` 块」于 2026-09-18 二次修正中被证伪** —— `application-dev.yml` 与 `application-docker.yml` 均含该块且实际生效,原「默认值仅来自 Java 字段默认」的纠正本身不成立。 | 二次纠正为:features 由 **profile yml(dev / docker)**提供并实际生效;基础 `application.yml` 不提供。功能不受影响。 |
| D-22-E | Doc/Accuracy(新发现,2026-09-18 二次修正) | `AppController#getConfig`(`GET /app/config`)返回一份**硬编码**的 `features` map(`activityEnabled` / `matchmakerEnabled` / `imageChatEnabled` / `appealEnabled` / `checkinEnabled` 恒为 `true`),与 `FeatureFlagConfig` **构成双源**;且键名 `imageChatEnabled` 与后端 `chatImageEnabled` **不一致**,值也可能与实际配置相反(如 `appealEnabled` 实际默认 `false`)。 | 不静默改契约(小程序端依赖此响应)。如实登记为遗留,交小程序契约/PWA 模块统一为单一来源(建议改读 `FeatureFlagConfig#getAllFlags()`)。 |
> 注:T06 上传策略经实测已真强制(`LocalEligibilityService` + `PhotoService`),保留 ✅,无需修复。
@@ -10,8 +10,11 @@
- 「E2E **16/16 PASS**」→ **推翻**。Phase 23 复验发现原用例在 import 期即崩,且断言大量只比对
**常量字符串**;E2E 在 CI 上**从未真正通过过**。Phase 23B 已重建 E2E 基建(真栈 + conftest + seed 账号)。
- 「Feature Flags(15 项)PASS」→ **推翻**。Phase 22 证实 4 份 `application*.yml` **没有 `features:` 块**,
15 个 flag 实为 Java 字段默认、且业务代码**从不读取**(死配置)。
- 「Feature Flags(15 项)PASS」→ **推翻**。Phase 22 复验证实 10 个模块级 flag(T01–T05 及同类)全仓
**零业务调用点**,运维无法经开关关闭对应模块(死配置);另 `AppController#getConfig` 返回**硬编码**
第二份 features,与 `FeatureFlagConfig` 构成双源(D-22-E)。注:Phase 22 初判「4 份 `application*.yml`
没有 `features:` 块」**已被证伪**(2026-09-18 二次修正)—— `application-dev.yml` / `application-docker.yml`
均有该块且实际生效,仅基础 `application.yml` 不提供。
- 部署端口表(Admin 8086 等)→ **取代**。管理端已收敛至 `/admin/` 子路径,端口 13520(dev)/ 13521(staging)。
- 3 项「已知限制」→ 不再由本报告跟踪,以 `docs/JIRA_BACKLOG.md`、`REMAINING_WORK.md` 为准。
+6 -4
View File
@@ -484,7 +484,7 @@ MinIO: localhost:9003 (凭证由本地环境变量提供)
| T01–T05 模块开关 | 复验发现 `isCounselingEnabled` / `isPremaritalEnabled` / `isContentEnabled` / `isServiceLocationEnabled` / `isLocalEligibilityEnabled`(模块级)全仓无业务调用点 → **死配置**(暴露但未 gate);不静默扩权,留交各模块 Phase 接线 |
| T06 上传策略 | 实测已在 `LocalEligibilityService`(启用开关 + 数量上限)+ `PhotoService`(大小 + 类型)**真强制** ✅ |
| T07 私有存储桶 | `counselingAttachmentsPrivate` / `Bucket` 全仓零引用 → **死配置**(存储本就走 `object-storage.bucket-private`);可选增强,遗留 |
| yml 声明夸大 | STATUS 称「application.yml 默认配置(15 flag)」,实际 yml 无 `features:` 块,默认值来自 Java 字段默认值;功能不受影响,文档纠正 |
| yml 声明夸大 | STATUS 称「application.yml 默认配置(15 flag)」——**来源表述错误**(features **不在**基础 `application.yml`),但**内容本身属实**:`application-dev.yml`(`spring.profiles.active: dev`,默认激活)与 `application-docker.yml`(compose 设 `SPRING_PROFILES_ACTIVE: docker`)**均有 `features:` 块且实际生效**(硬证据:dev 的 `chat-image-enabled: true` **覆盖**了 Java 字段默认 `false`)。原复验「4 份 yml 无 `features:` 块 / 默认值仅来自 Java 字段默认」为**误判**,已于 2026-09-18 二次修正;功能不受影响 |
| 守卫测试 | 新增 `FeatureFlagControllerTest`(7 用例:21 键全暴露 / 布尔·字符串·整数更新生效 / 未知 flag·缺 value·类型错→400);负向验证(仅布尔缺陷版)恰 2 用例精准变红,还原全绿 |
| 全量回归 | **657 tests / 0 failures / 0 errors / 3 skipped(起点 650,净 +7)** |
| 报告 | `docs/Phase-22-V11-Recovery-Completion-Report-2026-09-18.md` |
@@ -496,8 +496,10 @@ MinIO: localhost:9003 (凭证由本地环境变量提供)
> ⚠️ 此处原有**两块 2026-09-09 期的历史声明**(自称「application.yml 默认配置 ✅(15 个 V1.1 flag)/
> application-dev、docker.yml 配置 ✅/T01–T05、T07 ✅」),已被 2026-09-18 的 Phase 22 复验**整体推翻**:
> `grep -rn "features:" backend/src/main/resources/*.yml` 证明 4 份 yml **没有 `features:` 块**,
> 15 个 flag 实为 Java 字段默认值、且业务代码**从不读取**(死配置)。
> ① 原文所称 `features:` 块**确实存在且生效**,但**不在**基础 `application.yml`,而在 `application-dev.yml` /
> `application-docker.yml`(两 profile 均实际生效)—— 复验初判「4 份 yml 无 `features:` 块」**为误判**,
> 已于 2026-09-18 二次修正;② T01–T05 模块开关与 T07 私有桶**确实全仓零业务调用点**(死配置),此结论成立。
> ③ 另发现 `AppController#getConfig` 返回**硬编码** features(第二来源),见 Phase 22 报告 D-22-E。
> **唯一有效结论见上方「Phase 22 — 部署运维配置扩展(Feature Flag)✅(KEEP-VERIFY 复验关闭,2026-09-18)」。**
> 原文可从 `git log --all -- docs/STATUS.md` 取回。
@@ -684,7 +686,7 @@ OpenAPI 对外发布、调用审计与配额、分页幂等覆盖、脱敏边界
| P26-T03 Seed Service Types | ✅ (CHECK约束已定义) |
| P26-T04 创建万善公园服务站 | ✅ (V027) |
| P26-T05 初始化Banner | ✅ (V027) |
| P26-T06 初始化Feature Flags | ⚠️ **不成立**(2026-09-18 Phase 22 复验:4 份 `application*.yml` 无 `features:` 块;15 个 flag 实为 Java 字段默认且业务不读取) |
| P26-T06 初始化Feature Flags | ⚠️ **来源纠错 + 部分成立**(2026-09-18 二次修正):`features:` 块实际存在于 `application-dev.yml` / `application-docker.yml`(**不在**基础 `application.yml`)且实际生效(dev 的 `chat-image-enabled: true` 覆盖 Java 默认 `false`)—— 原「4 份 yml 无 features 块」为**误判**;但 10 个模块级 flag(T01–T05 及同类)全仓**零业务调用点**,运维**无法**经开关关闭对应模块(死配置) |
| P26-T07 初始化新增Roles/Permissions | ✅ (V015已覆盖) |
| P26-T08~T13 Smoke tests | ⏳ 上线前手动验证 |
+73 -2
View File
@@ -170,13 +170,84 @@ naive 扫描得 **29 处**,其中 **24 处合法**(出现在「已删除对
**仍需处理(不属文档治理范围,需决策/另开一轮)**:
1. **Phase 24(UAT)启动需用户确认** —— 后端 669 全绿、UI 门禁 14/14,但 Phase 23 的 E2E 入 CI 仍受
「活栈供给方式」基础设施决策阻塞(见收尾计划批 2)。
1. **Phase 24(UAT)启动需用户确认** —— 后端 669 全绿、UI 门禁 14/14、**E2E 已在本机真实执行(49 passed)**;
「活栈供给方式」决策已于 2026-09-18 落地为 Compose in CI(见收尾计划 Task 2.3),仅余「在 GitHub Actions
上跑一次绿」需在有 Docker Hub 出口的环境完成。
2. **`Phase-V11-Deployment-Completion-Report.md` 路径仍在(内容已是废止声明)** —— 若后续确要真正移除,
须连同 `Phase-01` / `Phase-13A` 两处引用一并处理,并遵守本机删除红线(优先原地重写)。
**执行纪律**:删除类改动仍应分 3 个 commit(①删除 ②归档+头注 ③状态同步+计划+报告)+ 每 commit 后 `git tag`(**单段名**,分支名含 `/` 会被实时清理)+ `git bundle create .workbuddy/backup/<phase>-recovery-<date>.bundle --all`,**并且必须**用 `git bundle list-heads <file> | grep refs/heads/main` 确认指向的是最新提交(否则等于没有安全网,见 D7 补记)。
---
## 九、遗留清偿第二轮(2026-09-18 夜)—— 活栈供给决策 + features 失实二次修正
### 9.1 活栈供给方式:已决策(Compose in CI)
**决策**:CI 的 E2E 活栈在 runner 内用 `docker compose` 起全栈(postgres + redis + minio + backend)。
**备选(未采纳)**:service containers + runner 上 `mvn package` 后 `java -jar` —— 需为后端另写一套 host
网络配置,等于制造**第二条与本地不一致的路径**,且增加一份与生产无关的分歧。
**本机实测(与 CI 同一序列),2026-09-18**:
| 环节 | 结果 |
|---|---|
| 中间件 healthcheck | postgres / redis / minio **全 Healthy**(`pg_isready` / `redis-cli -a … ping` / `mc ready local`) |
| backend 就绪 | `GET /api/v1/health/live` → **200** |
| seed | 幂等可执行(`INSERT 0 0`;`pgcrypto` 已存在跳过) |
| E2E | **`pytest tests/e2e` → 49 passed in 9.31s** |
> 本机限制(如实记录):Docker Hub 不可达(`auth.docker.io` Bad Gateway)且本地无 temurin 基础镜像 →
> **未复现 `--build`**;改用本地既有 `danyang-match-backend:latest`(2026-09-09 构建)为底座,挂载**当日
> 16:24 构建的 jar**,使被验证的**代码版本与 CI 一致**。若不换 jar,就是拿 09-09 的旧代码验 09-18 的用例,
> 结论无效(该镜像 `Created` 为 `2026-09-09T04:02Z`,已实测确认)。
**顺带修掉的 6 个 CI 真实缺陷**:① minio 滚动 tag(pin `RELEASE.2025-09-07T16-13-09Z`);② `up` 未显式列出
minio(而其 healthcheck 是 backend 启动前置);③ `up --build` 耦合构建与起栈、失败日志混杂;④ 就绪窗口 5min
偏紧(首次构建后仍需 Spring 启动 + Flyway 46 迁移);⑤ **缺 `down -v` 前置清卷** —— seed 用
`ON CONFLICT (username) DO NOTHING`,残留卷会让口令停留在上一轮 → 401 且**伪装成业务失败**(本机复用旧库时
已实际踩到,须手工 `UPDATE` 密码才能跑);⑥ job 无 timeout(默认 360 分钟)→ 加 `timeout-minutes: 30`。
**被实测否定的一个假设**:`pytest -v` 会从仓库根误收集 `tests/*_test.py`(其在 import 期依赖 `psycopg2`
与 `E2E_DB_PASSWORD`,会成 collection error)—— **不成立**:`pytest.ini` 的 `testpaths = tests/e2e` 已限定,
实测从根收集正是 49 个用例且无 error。仍显式写成 `pytest tests/e2e -v` 作为兜底。
### 9.2 features 断言失实:二次修正(5 处文档)
**背景**:本轮自查发现 Phase 22 复验报告 D-22-D 的判据「`backend/src/main/resources/*.yml` 无 `features:` 块」
**是错的**,而上一个收尾轮又把该错误结论照抄进了 `STATUS.md`(P26-T06 行)—— 属**二次传播**。
**实测事实(可复核)**:
| 文件 | 是否有 `features:` 块 | 依据 |
|---|---|---|
| `application.yml`(基础) | ❌ 无 | `grep -rn "^features:" backend/src/main/resources/` 只命中 dev / docker |
| `application-dev.yml` | ✅ 有(21 键) | 自 `63cbbff` 起;且 `spring.profiles.active: dev` → 它是**默认激活**的 profile |
| `application-docker.yml` | ✅ 有 | 自 `730dddd`(Phase 23B)起;compose 设 `SPRING_PROFILES_ACTIVE: docker` |
**硬证据**:`application-dev.yml` 的 `chat-image-enabled: true` **覆盖**了 `FeatureFlagConfig` 的 Java 字段
默认 `false` —— 若真如原判据所称「仅 Java 字段默认值生效」,该值不可能为 `true`。
**修正落点(5 处)**:`STATUS.md`(P26-T06 行 + Phase 22 段 + 历史声明块)、`Phase-22-…-Report.md`(表格行 + D-22-D)、
`JIRA_BACKLOG.md`、`Phase-V11-Deployment-Completion-Report.md`。
**仍然成立的部分**: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`)。不静默改契约,如实登记遗留。
### 9.3 操作事故:同一文件并行 Edit 会丢失改动
**现象**:对同一文件在一条消息里发起多个 Edit,**会随机丢失**其中若干处(读-改-写竞争)。
本轮实测:`STATUS.md` 3 处**丢 1 处**、`ci.yml` 3 处**丢 2 处**、`Phase-22-…-Report.md` 2 处**侥幸全中**。
每处都回报「Successfully edited」—— **成功回执不可作为落盘证据**。
**纪律**:① 同一文件的多处改动**必须串行**(一次一个 Edit,等结果返回);
② 任何批量改动后**必须回读确认每一处都在**(本轮即靠回读发现 `timeout-minutes` 与 `pytest tests/e2e -v` 丢失)。
---
*报告生成: 2026-09-18 — 对应方案任务五;处置明细见 `docs/superpowers/plans/2026-09-18-phase23-closeout-and-uat-readiness.md`*