fix(analytics): close Phase 20 KEEP-VERIFY — 20 defects (fake metric definitions, hardcoded zeros, systemic soft-delete leak, module-wide 403, fake-green tests); add MetricCatalog + unified calculator + V043 grants + 51 guard tests; full regression 648 green

口径造假:
- D1 实名人数用 user_profiles.profile_status != 'rejected' 冒充 → in_progress/need_edit 被算成已实名
- D3 Chat Started 直接数会话行数 → system/risk_notice 系统通知计入
- D4 Match 不分来源 → 活动 10 对互选被当 10 对成功引用(Gate)
- D6 Banner 曝光/点击硬编码 0 → 改返回 null + NOT_INSTRUMENTED 标记

死代码 / 占位:
- D5 活动漏斗全部硬编码 0 + 一行把"用户数"当"活动数"的死代码
- D20 getDailySummary(days) 无边界校验

系统性软删缺陷(D12):
- BaseEntity.deletedAt 无 @TableLogic 且全局无逻辑删除配置
  → selectCount 把软删数据全部计入统计且无任何报错
- 修复 45 处统计调用显式 deleted_at IS NULL(2 轮复验,首轮漏扫 5 处次级路径:
  服务用户数分域明细 / 服务站统计 / 性别与年龄分布)

结构类:
- D15 Dashboard/用户漏斗/专项页各实现一遍同名指标且互不相同 → 抽 AnalyticsMetricsCalculator 统一口径

权限(Gate ⑩):
- data_viewer 在 V001 定义却全仓从未授权(空壳角色)
- platform_admin 的 V015 扩展条件 id >= '20000000-…' 漏掉 V1.0 老权限段
  → 整个 Analytics 模块除 super_admin 外全员 403
- V043 授予两角色;刻意不授予 organization_admin(相关表无组织维度,无范围全量即越权)

假绿测试:
- 删除 AnalyticsServiceTest(5× assertTrue(true),零断言)

新增:
- 指标字典 MetricCatalog(36 指标 × 12 字段)+ MetricStatus/MetricDefinition + docs/ANALYTICS_METRICS.md
- 守卫 51 用例:MetricCatalogGuardTest(11) / AnalyticsMetricsPhase20IntegrationTest(21)
  / AnalyticsSoftDeleteGuardTest(4,结构性) / AnalyticsPermissionGrantTest(6)
  / LocalEligibilityStatusVocabularyGuardTest(9)

跨阶段修复(缺陷本体属账号生命周期模块,按"发现即修"处理):
- X1(Critical)注销写入 user_identities 状态域的 'failed' → 违反生产 V017 chk_elig_verify_status
  → 真实 PG 上 closeAccount 事务回滚、账号注销 100% 不可用(负向验证实测 DataIntegrityViolation)
- X2 checkCanWithdraw 资格拦截用 submitted/pending(该表永不可能出现)→ 死代码
- X3 测试库补 chk_elig_type/chk_elig_verify_status,暴露 ActivityPhase14Test 夹具违规

验证:
- 负向验证两轮共 7 处注入 → 7 个用例精准变红且无无关失败(一处被源码结构+运行时行为两层捕获)
- 还原后 md5sum 逐字节一致,NEG-VAL 残留 0
- 后端全量 648 tests / 0 failures / 0 errors / BUILD SUCCESS;admin-web vue-tsc EXIT=0
This commit is contained in:
WorkBuddy
2026-09-17 23:25:31 +08:00
parent ff22c0d06a
commit 22c778b111
28 changed files with 3652 additions and 359 deletions
@@ -0,0 +1,43 @@
-- V043: Phase 20 —— 打通 analytics.view 授权(修复「专设角色零权限」与「老权限段被角色扩展漏掉」)
--
-- 背景(两个叠加的授权缺陷):
--
-- 1) V001 第 149 行定义了一个**专门用于看统计**的角色:
-- ('00000000-...0009', 'data_viewer', '数据查看者', '仅查看统计数据', TRUE)
-- 但全仓迁移从未给它授予任何权限 —— 该角色的 description 写得清清楚楚,
-- 权限表里却一条都没有。用这个角色登录后台,「数据统计」菜单点了就是 403。
-- 这是「角色定义与权限授予脱节」:角色存在 ≠ 角色可用。
--
-- 2) V015 给 platform_admin 的扩展条件是 id >= '20000000-0000-0000-0000-000000000100'
-- (V1.1 新增权限的 ID 段),而 analytics.view 的 ID 是
-- '10000000-0000-0000-0000-000000000060'
-- 属于 V1.0 老权限段 —— 因此 platform_admin 也被漏掉。
--
-- 净效果:整个 Analytics 模块(15 个端点)除 super_admin 外**全员 403**,
-- 包括专门为它设计的 data_viewer。
--
-- 与 Phase 19 发现的 service_location.manage「死权限」是同族问题:
-- 权限定义了、角色定义了、两者之间从未连上。
--
-- 授权范围(最小权限原则):
-- * data_viewer —— 平台级数据查看者,职责就是看统计,必须授予;
-- * platform_admin —— 平台运营管理员,需要看全局数据做运营决策。
--
-- **刻意不授予 organization_admin**:平台级统计接口(用户/匹配/聊天/活动)
-- 所依赖的表多数没有 organization_id 维度(users / matches / messages 均无),
-- 因此无法把机构管理员的数据范围限制在"本机构"。在没有范围限制的情况下授予
-- 全局统计权限 = 越权可见全平台数据。宁可返回 403,也不给无范围的全量数据。
-- 待统计口径支持组织维度后再评估(见 docs/ANALYTICS_METRICS.md 待办)。
--
-- 低权限角色(content_editor / counselor / service_station_operator 等)**不授予**:
-- Phase 20 Gate 明确要求「低权限账号返回 403」,这正是期望行为。
INSERT INTO role_permissions (role_id, permission_id)
SELECT '00000000-0000-0000-0000-000000000009', id FROM permissions
WHERE code = 'analytics.view'
ON CONFLICT DO NOTHING;
INSERT INTO role_permissions (role_id, permission_id)
SELECT '00000000-0000-0000-0000-000000000002', id FROM permissions
WHERE code = 'analytics.view'
ON CONFLICT DO NOTHING;