V1.0.1 配置与规则纠偏:照片上限配置化+强校验 / 活体开关闭环 / 推荐阈值快照+规则版本化 / governance 三表+漂移审计

根治三类工程治理问题(技术默认值≠产品规则、配置存在但未闭环、文档代码漂移):
- PROFILE-001:profile.photo.maxCount(默认6未确认)+ ProfilePolicy + 服务器端 FOR UPDATE
  并发强校验(PHOTO_LIMIT_EXCEEDED,拒绝先于 S3 不留孤儿)+ 前端 mergeAppConfig 嵌套键修复
- CFG-001/IDENTITY-001:IdentityPolicy + GET /verification/liveness-status 真实判定点
  (默认关不阻断,开则 enhancedVerified 变化,消除假开关)+ 删死 seed livenessVerification.enabled
- MATCH-001:阈值60定为技术默认(消解 PENDING 矛盾)+ V050 recommendation_records.threshold_snapshot
  规则版本化 + 删死重复配置类 MatchConfig
- DOC-001:新增 docs/governance/(config-register/open-questions/decision-register/
  hardcoded-rule-audit/config-drift-audit)
- TEST-001:新增守卫 PhotoLimitGuardTest(8)/IdentityLivenessGuardTest(5)/
  RecommendationThresholdSnapshotGuardTest(4),均经负向注入可红

全量回归 733/0/0/3(基线717+17新增-1删死测试);UI 契约门禁 18/18;STATUS 升 rc.21
This commit is contained in:
WorkBuddy
2026-09-21 02:05:15 +08:00
parent b60c1ffb71
commit 438b38d121
37 changed files with 1033 additions and 101 deletions
+42
View File
@@ -0,0 +1,42 @@
# 配置漂移审计 — config-drift-audit.md
> **版本**:V1.0.1 配置与规则纠偏 · **日期**:2026-09-20
> **判定原则**:同一业务含义只允许**一套 canonical key**;不得出现「seed 写一套、真实系统读另一套」「前端写一套、后端又一套」「文档说一套、代码做一套」。
---
## 一、`system_configs` 是**死表**(0 个 Java 读取点)
- `V001__init_base_tables.sql:131` 建表,`:213-217` seed 了 `match_rule_version / daily_recommendation_limit / match_score_threshold='60' / recommendation_cooldown_days`。
- `demo-seed-final.sql` 又 seed 一批 flag 名(`localEligibility.evidencePolicy`、`counseling.enabled`、`premarital.enabled`、`serviceLocation.enabled`、`content.enabled`、`localEligibility.enabled`、`banner.maxActiveCount`)。
- **全仓 `backend/src/main/java` 无任何 `SystemConfig` 实体 / Mapper / 读取**(仅 `EntityMigrationParityTest` 把它列入 H2 白名单)。
- **后果**:真实系统读的是 `MatchRuleConfigProperties`(yml `match.*`)+ `FeatureFlagConfig`(`features.*` / `feature_flag_overrides`),**不是** `system_configs`。于是 `match_score_threshold='60'` 在死表里躺著,与活键 `match.default-score-threshold` 各写一份 → **seed 与 migration 漂移、seed 与代码漂移**。
- **处置**:本轮删除 `demo-seed-final.sql` 的死 key `livenessVerification.enabled`(与活键 `livenessEnabled` 同义双 key)。其余 `system_configs` 死行**未删**(表整体退役需单独迁移裁定,避免越界)——登记为未来项:整体退役 `system_configs` 或把它接成真实配置源。
## 二、`match_rule_configs` 是**未接线的规则版本化雏形**(死表)
- `V006` 建 `match_rule_configs`(version/status/10 权重/`minimum_score DEFAULT 60`),`MatchRuleConfigMapper.findActive/findByVersion` **无任何调用方**。
- 真实规则版本化本轮走轻量路线:`recommendation_records.rule_version` + 新列 `threshold_snapshot`(V050)+ `match.rule-version`。
- **处置**:登记为未来项 —— 要么把 `match_rule_configs` 接成「规则版本唯一读取源」(配合 open-questions 的 10 维权重配置化),要么删除该死表。本轮未动(避免越界)。
## 三、本轮已消除的 key/类漂移
| 漂移 | 处置 |
|------|------|
| seed `livenessVerification.enabled` vs 活键 `livenessEnabled`(双 key 同义) | 已删 seed 行;canonical `identity.liveness.enabled` 映射到唯一实现键 `livenessEnabled`(见 config-register)。 |
| `shared/config/MatchConfig` 与 `match/config/MatchRuleConfigProperties` 同前缀 `match` 双 bean(前者 0 业务引用) | 已删死类 `MatchConfig`,统一读 `MatchRuleConfigProperties`。 |
| 前端 `mergeAppConfig` 平铺合并 → 服务端嵌套 `businessLimits.maxProfilePhotos` 等**从不生效**(前端 6 写死、后端也不强校验) | 已修前端显式节-键映射 + 后端 `ProfilePolicy` 强校验 + `/app/config` 读 `ProfilePolicy`。前后端现共用同一配置源。 |
| 活体开关「假开关」(`livenessEnabled` 无业务强制点,0 读取点) | 已建 `IdentityPolicy` + `GET /verification/liveness-status` 真实判定点,开关开则 `enhancedVerified` 变化(FF-LIVENESS-002)。 |
## 四、剩余待登记漂移(未来项,勿本轮扩大)
| 项 | 说明 |
|----|------|
| `AppController` 剩余硬编码(dailyRecommendationLimit / maxInterestTags / selfIntroMaxLength / recommendationCooldownDays / reviewConfig / features) | 与 `MatchRuleConfigProperties` / 各配置源各写一份;待统一为「读配置源 + 经 `/app/config` 下发」(见 hardcoded-rule-audit 第二节)。 |
| 前端 `BUSINESS_LIMITS` 本地默认值 vs 服务端 | `maxProfilePhotos` 已打通;`DAILY_RECOMMENDATION_LIMIT / MAX_INTEREST_TAGS` 等仍为本地 fallback,服务端值经 `/app/config` 下发后应全部以服务端为准(前端只作失败兜底)。 |
| 打分公式占位(0.6/0.4、80/60、恒 50.0) | 非漂移,属 V1 占位未产品化;正式化时随规则版本一起配置化。 |
---
*结论:本轮消除了文档点名的 4 类漂移(liveness 双 key、MatchConfig 重复、前后端照片上限失同步、活体假开关)。
`system_configs` 死表、`match_rule_configs` 死表、AppController 剩余硬编码为已登记的未来项。*