AI 数据治理框架 A: findings/rules 两表 + D1 唯一索引重复探测器 #162
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景
AI 数据治理框架第 A 步(设计: docs/design/AI数据治理框架-20260919.md §三/§四):
建
governance_findings(探测结果)+governance_rules(判断规则沉淀)两表,并实现 D1 探测器(唯一索引重复)。
为什么需要这个框架(用户 2026-09-19 提出):
数据治理现状是「人发现坏数据 → 写脚本 → 人工判断 → 改数据」,每次都走开发流程,成本高且易误判。
C-P1-7 实证:人工扫描 5 个候选索引,4 个是误报(把「业务允许多个 NULL」当成重复)。
应由系统 AI 机制自动分析 → 给治理建议 → 分级执行。
影响范围
新增两表 + D1 探测器服务 + 单测;不改现有表结构与业务逻辑。
处理动作 / 设计要求
governance_findings表(§3.1):detector/table_name/target/severity_raw/metrics(JSONB)/samples(JSONB,脱敏≤10条)/repro_sql/status + AI 研判产物字段(verdict/verdict_reason/
suggestion/confidence/rule_id)
governance_rules表(§3.2):detector/table_name/target_pattern/match_metrics/verdict/reason/disposition/disposition_level/hit_count/source_finding_id
解析
pg_indexes.indexdef(含WHERE部分条件、函数如date(...)、COALESCE(...)),生成等价
GROUP BY ... HAVING count(*)>1,而非用indkey裸列名repro_sql(可审计、可人工复核)+ 样本行(脱敏)manual_refunds的表达式索引不得误报)+ 漏报回归(构造 NULL 列重复能被检出)验收标准 / 关闭条件
pytest backend/tests/test_governance_detector.py全过,含:manual_refunds场景(表达式索引含 date/COALESCE/部分条件)不误报(#161 误报回归)repro_sql可直接在生产库执行并复现 metrics 数字(凭证贴实测)关联
关闭凭证核验 (2026-09-20)
csctalon_migtest:alembic stamp h3i4j5k6l7m8→upgrade m3n4o5p6q7r8→ 两表 + 9/8 索引 →downgrade h3i4j5k6l7m8(剩余 governance 表 0)→ 再upgrade head幂等通过;生产alembic_version = m3n4o5p6q7r8,refunds=184987 / wallet=28852 / ai_reports=188不变manual_refunds表达式索引不误报(#161 回归)tests/test_governance_detector.py::test_d1_no_false_positive_on_partial_expr_index(同构date()/COALESCE/WHERE order_no IS NOT NULL索引 + 2 行 order_no NULL 且 device_sn/registrant 均 NULL → 断言 findings == [])::test_d1_detects_null_column_dups(device_sn NULL 两行 → 1 finding / redundant_rows=1 / gap_kind=protection_gap);另::test_d1_detects_dups_inside_partial_expr_index(部分索引覆盖内重复仍检出,repro_sql 含date(reg_dt))repro_sql可在生产库执行并复现 metricsrepro_sql跑出1 组 / 1 行== metrics1 组 / 1 行;本地开发库(未治理)跑出1240 组 / 15601 行== metrics交付物
6e610d1feat(governance): 治理框架 A —— findings/rules 两表 + D1 唯一索引重复探测器 #162git-20260920024952(upgrade.sh --git,健康检查通过)app/models/governance_finding.py/governance_rule.py、app/services/governance_detector.py、alembic/versions/m3n4o5p6q7r8_governance_findings_rules.py、tests/test_governance_detector.py实现要点
两表(设计 §3.1/§3.2)
governance_findings:只存证据,不存结论。metrics / samples(脱敏)/ repro_sql 三件套保证可审计;verdict / suggestion / confidence / rule_id 等研判字段由 #164 回填 —— 探测器与研判解耦,探测可高频跑。
governance_rules:研判结论沉淀,命中即跳过 LLM(§5.4)。auto_levelL0/L1/L2 三级,MANUAL_ONLY_LEVELS = (L2,)把「不可逆必须人工确认」这条红线写成常量(设计 §六)。D1 探测器(设计 §四)—— 两条铁律写在模块文件头,由两轮真实事故换来的:
pg_get_indexdef括号内原文(含date(x)/COALESCE),部分索引带
WHERE indpred。✘ 用indkey裸列名会误报(manual_refunds曾凭空报「3062 行重复」)。GROUP BY语义),但排除「整组全 NULL」:PG 普通唯一索引用
=比较 → 含 NULL 的行永不冲突(即不受保护),而GROUP BY把 NULL 归一组→ 报出的正是「索引拦不住」的行。但要再分两种:
ai_reports.task_id176 行、dashboards.code2 行)→ 设计如此,排除device_snNULL)→ ⚠️ 真缺口(C-P1-7 本质)脱敏:敏感列(account/acct/device_sn/order_no/name/sn…)字符串值 → 确定性 SHA256 前 8 位
(保留「组内是否相等」可判、原文不可还原);非敏感列(金额/时间/短枚举)保留原文供研判。
样本值统一过
_json_safe(datetime/Decimal 直落 JSONB 会TypeError—— 实测踩到并修掉)。落库幂等:同
(detector, table_name, target)已有 open finding 时刷新证据而非新增行(同 #166 教训:无节流的重复留痕会淹没真实变化);已 resolved/ignored 的不影响新 finding(问题复现应重新暴露)。
脚本收敛:
scripts/governance_scan_unique_dup.py改为薄封装(只调服务),消除「脚本一套判据 + 服务一套判据」的漂移风险。
测试
生产实测(脚本
/tmp/prod_verify162.py,容器内执行)本地开发库(未治理,含 #160 遗留重复)对照实测 —— 同一实现两种环境结论正确区分:
repro_sql在该库跑出1240 组 / 15601 行,与 metrics 完全一致。发现但未处理(不在本工单范围,供排期)
全量迁移链路从零不可重放:临时库从空库
alembic upgrade head在更早的迁移处失败(
constraint "uq_cs_record_business" of relation "cs_records" does not exist)——说明历史迁移链存在缺口,无法在空库一键重建 schema(生产/测试库都是增量演进过来的,未暴露)。
与本工单无关,建议另立工单核查。