Files
danyang-match/docs/Phase-22-V11-Recovery-Completion-Report-2026-09-18.md
T
WorkBuddy 6b2ff0f73e 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 会随机丢失」的事故记录与纪律)。
2026-09-18 21:58:58 +08:00

9.7 KiB
Raw Permalink Blame History

善缘丹阳 V1.1 — Phase 22 部署运维配置扩展(Feature Flag)KEEP-VERIFY 复验 Completion Report

日期:2026-09-18 | 复验纪律:历史 ✅ 不可信;逐条核对运行时路径与生产约束;发现真实缺陷即修;补测试保真度;守卫解析真实 DDL/真实 SQL;单段分支提交 + tag + bundle + 回读。 范围说明:计划文件 docs/善缘丹阳 V1.1 客户需求基线增量开发与修复计划.md 的 Phase 22 实为「Deployment / Ops 基础设施」(Docker/CI/备份/密钥),与 STATUS.md 遗留区「Phase 22 部署运维配置扩展」所指的 Feature Flag 配置扩展 是两份不同工作。本 Phase 复验的是后者(用户确认的「部署运维配置扩展」标签),即 7 个 V1.1 Feature Flag + FeatureFlagController + application.yml 默认值。计划的 Deployment/Ops 基础设施 Phase 22 为另一块更大的运维任务,需另行确认。


一、复验起点:STATUS 声明 vs 实际运行时路径

STATUS 遗留区「Phase 22 部署运维配置扩展 ✅ 已完成」逐条声明(P22-T01~T07、FeatureFlagController API、application.yml 默认配置)。KEEP-VERIFY 逐项核对真实代码路径后,结论如下:

STATUS 声明 复验实测 结论
T01 counselingEnabled flag ✅ isCounselingEnabled() 全仓无业务调用点 ❌ 死配置(暴露但未 gate)
T02 premaritalEnabled flag ✅ isPremaritalEnabled() 全仓无业务调用点 ❌ 死配置
T03 contentEnabled flag ✅ isContentEnabled() 全仓无业务调用点 ❌ 死配置
T04 serviceLocationEnabled flag ✅ isServiceLocationEnabled() 全仓无业务调用点 ❌ 死配置
T05 localEligibilityEnabled flag ✅ isLocalEligibilityEnabled() 全仓无调用点(模块开关未接) ❌ 死配置(但上传策略已接,见下)
T06 local evidence upload policy ✅ LocalEligibilityService 强制 isLocalEvidenceUploadEnabled + getLocalEvidenceMaxFiles;PhotoService 强制 getLocalEvidenceMaxFileSizeMB + getLocalEvidenceAllowedTypes ✅ 已真强制
T07 private storage for counseling attachments ✅ counselingAttachmentsPrivate / counselingAttachmentsBucket 全仓只在 FeatureFlagConfig 定义与 getAllFlags() 暴露,业务零引用(counseling 模块无附件存储代码引用) ❌ 死配置
FeatureFlagController API ✅ (GET/PUT) GET 正常;PUT 仅支持布尔 flag(反射 getMethod("set"+cap, boolean.class)),7 个非布尔 flag 更新必失败 ❌→✅ 已修复(D-22-A)
application.yml 默认配置 ✅ (15 个 V1.1 flag) backend/src/main/resources/*.yml 无 features: 块,仅 Java 字段默认值生效 ⚠️ 夸大声明(功能正常,文档纠正)

二、真实缺陷(修复)

编号 严重度 缺陷 修复
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)」,来源表述错误: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),保留 ✅,无需修复。


三、未静默扩权的遗留项(文档化,交后续)

  1. 模块开关死配置(T01–T05):counselingEnabled / premaritalEnabled / contentEnabled / serviceLocationEnabled / localEligibilityEnabled(模块级)在后端无调用点 → 运维无法通过 flag 关闭对应模块。属「配置暴露但未 gate」,需在各模块自身 Phase 接线(建议加控制器入口 @PreAuthorize/服务层 featureFlags.isXxxEnabled() 判断),不在本 Phase 静默铺开。
  2. T07 私有存储桶死配置:counselingAttachmentsPrivate / counselingAttachmentsBucket 无业务引用;咨询附件存储本就走 object-storage.bucket-private(已私有)。当前 flag 为「专用桶覆盖」旋钮,未接线,属可选增强。
  3. PUT 变更仅内存生效:FeatureFlagConfig 为单例 @Component,运行时修改不持久化,重启后回到默认值。计划/STATUS 未要求持久化,记为已知限制(如需跨重启保留,需接 DB/可刷新配置,属范围外)。
  4. 计划 Phase 22(Deployment/Ops 基础设施):Docker / CI / 备份恢复 / 密钥注入 / 回滚 等尚未纳入本 Phase,需另行确认后单独复验。

四、守卫测试(新增)

backend/src/test/java/com/danyang/match/platform/shared/controller/FeatureFlagControllerTest.java(7 用例,直接驱动控制器方法,使用隔离的 FeatureFlagConfig 实例避免污染共享单例):

  1. getAllFlagsReturnsEveryDeclaredFlag — 21 个 flag 全部暴露(size=21 + 逐项 containsKey)。
  2. updateBooleanFlagIsReflected — 布尔 flag 更新后反映到配置。
  3. updateStringFlagIsReflected — 字符串 flag(D-22-A 修复点) 更新生效。
  4. updateIntFlagIsReflected — 整数 flag(D-22-A 修复点) 更新生效。
  5. updateUnknownFlagReturnsBadRequest — 未知 flag → 400。
  6. updateMissingValueReturnsBadRequest — 缺 value → 400(D-22-B 修复点)。
  7. updateBooleanFlagWithWrongTypeReturnsBadRequestAndDoesNotSilentlyDisable — 类型不匹配 → 400 且原值不变(D-22-B 修复点)。

既有 ConfigTest.testFeatureFlagDefaults 保留(校验 Java 默认值)。


五、负向验证(守卫非假绿)

将控制器分发逻辑改回「仅布尔」(模拟 D-22-A 修复前缺陷,保留 Map<String,Object> 签名以保证可编译),重跑 FeatureFlagControllerTest:

[ERROR] Tests run: 7, Failures: 2, Errors: 0, Skipped: 0
[ERROR] FeatureFlagControllerTest.updateIntFlagIsReflected    <<< FAILURE!  expected: <true> but was: <false>
[ERROR] FeatureFlagControllerTest.updateStringFlagIsReflected <<< FAILURE!  expected: <true> but was: <false>

→ 恰好 2 个关于非布尔更新的用例精准变红,其余 5 个通过;还原修复版后全绿。证明守卫真实有效,非假绿。


六、全量回归

离线容器( danyang-match-backend-test:latest,挂载 /proj,命名卷 danyang-m2-cache,--memory=3g -e MAVEN_OPTS=-Xmx256m):

Tests run: 657, Failures: 0, Errors: 0, Skipped: 3  —  BUILD SUCCESS

(起点 650,净 +7 = 新增 FeatureFlagControllerTest 7 用例)。


七、Gate 评估

Gate 结论
配置基础设施可经 API 读写(含非布尔) ✅ 修复 D-22-A/B 后 GET/PUT 覆盖 21 个 flag(布尔/整数/字符串)
未知 flag / 缺 value / 类型错 → 明确拒绝,不静默误关 ✅ D-22-B
上传策略真强制 ✅ T06 已在 LocalEligibilityService + PhotoService 生效
模块开关真 gate 功能 ❌ 遗留(T01–T05 死配置,交各模块 Phase)
私有存储桶真生效 ❌ 遗留(T07 死配置)

结论:KEEP-VERIFY 复验关闭。修复 2 处真实代码缺陷(D-22-A High、D-22-B Medium)+ 纠正 2 处文档夸大(D-22-C/D);补 7 守卫用例;全量 657 绿;负向验证精准。4 项遗留已文档化,未静默扩权。


八、Git 物证

  • 提交:(见 git log 最新一条)
  • Tag:phase22-recovery-2026-09-18
  • Bundle:.workbuddy/backup/phase22-recovery-2026-09-18.bundle
  • 改动文件:
    • backend/src/main/java/com/danyang/match/platform/shared/controller/FeatureFlagController.java(D-22-A/B)
    • backend/src/test/java/com/danyang/match/platform/shared/controller/FeatureFlagControllerTest.java(新增守卫)
    • docs/STATUS.md、docs/JIRA_BACKLOG.md(同步复验结论)