应用部分 10. 保护指标免受古德哈特效应:守护指标与紧急模式
状态:建议。 用配对守护指标(guard 指标)和阻塞性紧急模式保护 KPI 是 Google SRE Book 中描述的成熟实践。具体阈值(silent_p0、manual_review_floor、audit_trace_coverage)和 validation.md v1.1 格式是建议性框架,大多数团队都会对其进行适配。
对于学习路径而言,只需运行 examples/goodhart-validator/ 并观察良好的 MTTR 如何被 silent_p0 的增长所阻塞即可。指标网络、trace 字段和阈值校准属于完整生产轨道。如果下方出现名称「红色按钮」,请将其视为正式紧急模式的简短标签。
在第一卷的第 9 部分中,一项检查对应一个指标已经足够:「发布后反馈可见」「余额不会为负」。在生产场景中,对 cdn_error_budget_burn 使用相同逻辑已显不足。来自第 11 部分的代理与非缺陷日志仪表板在发布后呈现出矛盾的画面,而单一指标成为了陷阱。在本节中,我们将其扩展为配对守护指标网络——即「KPI + 备份指标」组合,其中第二个指标可防止为优化第一个指标而造成隐性损害。防范此类典型操纵的目录在第 20 部分 SDD 反模式中进行了系统化整理。
阅读前
- 来自第一卷的依据:第 9 部分教导验证事实而非令人信服的散文;第 20 部分展示了流程如何开始保护错误目标。
- 本地学习案例:
cdn_error_budget_burn,因为改进的 MTTR 可以被silent_p0的增长所阻塞。 - 在
capstone/中的足迹:一个目标指标、一个 guard 指标以及一个为high_memory_usage阻塞的示例。
- 第一遍的关键术语:guard 指标和紧急模式(「红色按钮」)。其余的——
silent_p0、manual_review_floor、audit_trace_coverage、edge_drift、trace 字段、指标网络——仅供参考,仅当capstone/goodhart-note.md的一行需要时再查阅。 - 可推迟的内容:指标网络、trace 字段、drift 校准和完整的紧急模式。
目标
到本节结束时,您将构建一个 validation.md,它能预先捕捉古德哈特陷阱,并防止 LLM 事件管道以牺牲 triage 退化为代价来改进报告 KPI。
主要收益是:将指标分为可管理目标和不可侵犯的质量不变量。然后为它们设置可验证的阈值、trace 中的证据以及 CI 中的阻塞。
此处「诱饵指标」是指作为信号有用、但在与质量不变量分开进行优化时变得危险的 KPI。KPI(关键绩效指标)是团队希望通过发布改进的关键指标。
这种方法延续了 SDD 循环:规范、检查标准和迭代在实施变更前固定,而不是在得到漂亮结果后调整(GitHub Spec Kit Quickstart)。
「当度量变成目标时,它就不再是一个好的度量」这一效应被经典地称为古德哈特定律(Wikipedia: Goodhart's law)。Google SRE 对 SLO 的定义直接基于这种谨慎(SRE Book: Service Level Objectives)。
最小学习场景
学习案例
来自book/part-11-second-feature-phase.md的学习代理日志中呈现的生产事件 cdn_error_budget_burn。一次发布将 MTTR 从 660s 改进到 290s,形式上看是成功。但 silent_p0 从 0.02 跃升至 0.18,manual_review_rate 从 0.18 下降到 0.12。目标是看到 CI 网关如何捕捉到此变化并阻塞合并,尽管 MTTR 显示「绿色」。
准备
book2/examples/goodhart-validator/specs/validation.yaml— 不变量和红色按钮检查。
book2/examples/goodhart-validator/fixtures/baseline_metrics.json— 基准(MTTR 660s,silent_p0 0.02)。book2/examples/goodhart-validator/fixtures/new_metrics_good.json— 无盲点的改进。book2/examples/goodhart-validator/fixtures/new_metrics_bad.json— 「MTTR 盲点」(290s,silent_p0 0.18)。book2/examples/goodhart-validator/fixtures/new_metrics_drift.json— 边相关性上的漂移。book2/examples/goodhart-validator/scripts/run_validation.py、compare_drift.py、ci_gate.py。
步骤
cd book2/examples/goodhart-validator。预期:您位于示例目录中,没有额外依赖。- 「好」运行:
python3 scripts/run_validation.py --validation specs/validation.yaml --metrics fixtures/new_metrics_good.json。*预期:返回码 0,状态PASS,所有三个不变量OK。* - 「MTTR 盲点」运行:
python3 scripts/run_validation.py --validation specs/validation.yaml --metrics fixtures/new_metrics_bad.json。*预期:返回码 1,red_button_mttr_blindness触发,manual_review_floor和silent_p0_cap标记为 FAIL。*
差: 仅看 MTTR——发布更快,似乎「更好」。
好: 使用不变量运行验证——在 silent_p0=0.18 时的「更快」会被自动阻塞。
- 针对 drift 装置运行漂移比较:
python3 scripts/compare_drift.py --baseline fixtures/baseline_metrics.json --new fixtures/new_metrics_drift.json。*预期:edge_drift > 0.12,返回码 1。* - 对照:相同的
compare_drift.py对好指标运行。*预期:edge_drift <= 0.12,返回码 0。* - 完整 CI 网关:
python3 scripts/ci_gate.py --validation specs/validation.yaml --baseline fixtures/baseline_metrics.json --new fixtures/new_metrics_bad.json。*预期:返回码 1,reasons中列出具体违反的不变量,而非通用的FAIL。* - 将运行结果记录为简短的 anti-Goodhart 注释:目标指标得到改进,但
silent_p0_cap和manual_review_floor阻塞了发布。预期:下一次具有更快 MTTR 的 PR,验证器不是「绿色对比旧基准」而是与 good/bad/drift 装置进行比较。
如果您已安装 Qwen Code 并需要供审查使用的解释,请执行一个单独的可选步骤:
qwen -p "读取 @fixtures/new_metrics_bad.json 和 @specs/validation.yaml。即使 MTTR=290s,哪些不变量不能绕过?不要修改文件。" --approval-mode plan
此类输出作为说明有用,但不能替代 run_validation.py、compare_drift.py 和 ci_gate.py。
检验事实
步骤 2 返回码为 0,步骤 3 和 4 返回码为 1,并具体指出违反的不变量。步骤 6 在复合网关中显示相同行为。如果 CI 网关放行 new_metrics_bad.json,则验证器配置已被削弱——silent_p0_cap 或 manual_review_floor 阈值已被移动。
它如何进入 capstone/
将一个目标指标、一个 guard 指标和一个被阻塞的示例转移到 capstone/goodhart-note.md。如果主要计分案例是 high_memory_usage,请将此运行记录为同一回路的 anti-Goodhart 风险:不能以 silent_p0、手动审计或 5xx 为代价改进 memory 或 MTTR。如果指标网络未被重新计算,请不要全部转移;对于学习最小值,只需展示改进的 KPI 在没有保护性不变量的情况下无法通过。
最小片段:
target_metric: "MTTR <= 5m"
guard_metric: "silent_p0 <= 0.05 and manual_review_rate >= 0.15"
blocked_example: "new_metrics_bad.json"
reason: "MTTR improved, but silent_p0 and manual_review_floor fail"
可审查的足迹
脚本 run_validation.py、compare_drift.py 和 ci_gate.py 将结果写入 stdout,不创建单独的 out/ 目录。对于学习路径,请将结果转移到 capstone/goodhart-note.md:目标指标、guard 指标、被阻塞的示例和原因。
如果您在项目中保存 outputs/goodhart.last-run.txt,它应该是审查的可读附件,而非空标记。在 SDD 中,事实是可复现的命令或可读工件,而非提交本身的存在。
关键思想
首先确定哪些指标保持为质量不变量,哪些成为优化目标并因此容易被操纵。不变量不能通过直接施压来「改进」:它描述了系统最低可接受状态。不变量示例:
- 审计完整性;
- 手动检查比例;
silent_p0上限(即未经升级而关闭的「静默」严重事件的比率)。
优化目标则相反,可以降低或提高,但只能在保护走廊内进行。MTTR 作为恢复速度指标有用,但作为模型或团队的唯一奖励是危险的。
在 validation.md 中明确这一区别。MTTR<=5m 可以是目标。而 manual_review_rate>=15%、silent_p0<=5% 和 audit_trace_coverage==100% 保留为入场条件。
差:
> 达到 MTTR 低于 5 分钟。
问题:没有守护指标的裸目标,直接通向 silent_p0。
好:
> MTTR <= 5m AND silent_p0 <= 5% AND manual_review_rate >= 15% AND audit_trace_coverage == 100% — 违反任何条件 = CI_BLOCK。
当指标成为现实的替代品时,古德哈特陷阱就会出现。系统开始优化度量方式而非 triage 质量。如果 MTTR 被孤立检查,模型会学会更快关闭事件、减少升级比例并避免冗长调查——这正是拖累平均恢复时间的原因。
在图表上,这看起来像胜利:MTTR 降至 5 分钟或更低。但在操作回路中,这可能意味着相反。复杂的 P0 并没有消失,而是变得不可见,因为它们被错误地归类为误报、低紧急性或「自愈」事件。
「MTTR 5 分钟」陷阱对罕见严重事件尤其危险,因为关闭速度与调查完整性相互竞争。用数字看是这样的:
- 重放中 300 个事件的基准:MTTR 11:00,升级比例 14%,
silent_p02%; - 新优化版本:MTTR 4:50,升级 6%,
silent_p018%。
形式上 KPI 有所改进。但系统开始更频繁地遗漏关键事件,不进行手动检查和升级。阻塞此类发布:它将风险从可见报告转移到未来的重复事件、事后分析回归和责任链丢失。
validation.md 中的抗体是防止优化重新定义质量含义的形式条件。最小集是三条规则,必须同时检查:
| 规则 | 保护内容 | 边界 |
|---|---|---|
manual_review_floor | 手动验证决策的比例 | 不低于 15% |
| silent_p0_cap | 未经升级关闭的「静默」P0 比例 | 不高于 5% | | audit_trace_required | 决策足迹的完整性(prompt、diff、来源) | 100%,无例外 |
单独来看,这些规则会留下漏洞。高可追溯性无法补偿 silent_p0 的增长。如果无法恢复提示、差异和决策来源,则手动检查无用。配置「红色按钮」使其不是对单一不良数字触发,而是对保护回路的违反触发。
选择什么作为目标,什么作为保护
并非所有 KPI 都需要相同的保护。手动 triage 操作和自动修复具有不同的风险水平,因此最小不变量集也不同。核心规则是:操作越危险,与目标 KPI 配对的守护指标就越多。
| 决策类型 | 改进内容 | 必须配对的内容 |
|---|---|---|
| 手动 triage 操作 | MTTR | 决策足迹已完整保存 |
| 无操作的自动分类 | 分类的速度和准确性 | 无静默 P0;决策足迹已保存 |
| 自动升级 | 升级延迟 | 无静默 P0;无错误升级 |
| 无状态的自动修复 | MTTR | 无静默 P0;有手动检查;完整审计足迹 | | 有状态的自动修复(DB、缓存) | MTTR | 加上经确认的备份 | | 新策略发布 | 重放上的准确性 | 无「边界漂移」;完整审计足迹 |
完整的英文指标名称(silent_p0、manual_review_floor、audit_trace_coverage、false_escalation_rate、edge_drift、postmortem_gap、backup_verified)及其阈值和公式已移至附录 D。此处重要的是规则而非名称表:「改进什么」的每一行必须有一到两个来自同一风险领域的守护指标。
对于危险操作(最后三行),还应包括「红色按钮」——一个阻塞网关,没有第 3 章的评审团无法绕过。对于手动和观察性操作(前三行),软警告已足够。
该表的目的不是将其变成教条,而是帮助您看到遗漏的内容。如果在「有状态的自动修复」行中没有备份检查,这是重写 validation.md 的信号,而非「优化 MTTR」的信号。
> [conceptual interface] — validation.md 的结构,请根据您的足迹文件进行调整。
#### validation.md v1.1 的最小结构
version: 1.1
invariants:
- name: manual_review_floor
expression: "manual_review_rate >= 0.15"
- name: silent_p0_cap
expression: "silent_p0 <= 0.05"
- name: audit_trace_required
expression: "audit_trace_coverage == 1.0"
checks:
- name: red_button_mttr_blindness
when: "MTTR <= 5m"
assert: "manual_review_rate >= 0.15 and silent_p0 <= 0.05 and audit_trace_coverage == 1.0"
fail: "CI_BLOCK"
带有 artifact_inputs、network_consistency 和通过 COUNT(events_with(...)) 表达 audit_trace_required 的精确表达的完整形式位于 [examples/goodhart-validator/specs/validation.yaml](examples/goodhart-validator/specs/validation.yaml)。
下一层保护是规范中直接检测隐藏偏差。将 triage 行为变化视为 KPI 不变时的回归。此处回归是指聚合中不可见的决策分布变化。
原因:危害并不总是出现在顶层数字中。MTTR 可能保持不变,升级比例可能看起来正常,但模型开始在 auto_close、manual_review 和 defer 之间以不同方式分配边缘案例。
因此在 validation.md 中,不仅比较聚合,还要比较行为模式:
- severity 转移矩阵;
- 关闭原因分布;
- 重新打开的事件比例;
- 事后标记的延迟;
manual_review_rate与silent_p0之间关系的变化。
如果 drift_budget(与基准的可允许偏差走廊)超出,则即使 KPI 显示「绿色」也要阻塞构建。这意味着系统已经改变了决策模式。
要看到主要陷阱,三个指标和一个守护就足够了:
flowchart LR
MTTR[MTTR]
silent_p0[silent_p0]
manual_review_rate[manual_review_rate]
audit_trace_coverage[audit_trace_coverage]
silent_p0 -->|不诚实地拉低 MTTR| MTTR
manual_review_rate -->|诚实地拉高 MTTR| MTTR
audit_trace_coverage -->|限制 silent_p0| silent_p0读法:如果允许「静默」P0 未经升级关闭,可以人为改进 MTTR。守护 audit_trace_coverage 禁止无足迹关闭,manual_review_rate 保持手动检查比例。包含其他指标(escalation_rate、postmortem_regression)的完整图景在附录 D 中;那里还有形式阈值和关系。
将检查绑定到 Qwen 日志、决策和差异链——否则无法在生产中部署而不会丢失上下文。每事件的最小足迹组成:trace_id(链)、prompt_hash(提示哈希)、decision(选择内容)、policy_version + diff_id(哪个版本及哪个变更引入)以及 postmortem_label(复盘确认的内容)。带有 agent、raw_alert_excerpt、reasoning_delta 和 review_outcome 的完整字段集属于完整轨道,并在 [examples/templates/validation.md](examples/templates/validation.md) 中收集。
这五个字段允许在阻塞后回答工程问题:规范的哪个版本改变了行为,哪个提示促使模型自动关闭,哪个差异引入了新启发式。没有此链接,validation.md 仍是声明;有了它,它成为可复现的审计工件。
将指标设计为依赖网络而非独立计数器集合。这正是 network_consistency:一个指标的变化不应与相关指标相矛盾。共同重新计算 MTTR、silent_p0、manual_review_rate、escalation_rate、postmortem_regression、rollback_rate 和 audit_gap(见上图)。一个数字的局部改进通常在另一个数字上产生债务。实际标准是边一致性:如果 MTTR 下降,但同时手动检查下降且延迟确认的 P0 比例上升,则将系统标记为有风险。这将 CI 从「通过/不通过 KPI」检查转变为 triage 行为稳健性检查。
> [conceptual interface] — scripts/metrics/network_recompute.py 显示指标网络局部重新计算的形式;教材仓库中没有现成的 CLI。具有 anti-Goodhart 检查的 CI 网关的可运行模拟是 python3 examples/goodhart-validator/scripts/run_validation.py 和 ci_gate.py(见下方「检验事实」)。
#### spec 变更后更新指标网络和验证 CI-gate
python3 scripts/metrics/network_recompute.py \
--spec specs/incident-spec.md \
--replay data/replay_*.jsonl \
--out .artifacts/metric_network.json
python3 scripts/metrics/ci_gate.py \
--artifact validation.md \
--metric-network .artifacts/metric_network.json \
--traces .artifacts/qwen_trace.ndjson
CONTROL: CI_GATE = PASS if (edge_drift <= 0.12 && silent_p0 <= 0.05 && manual_review_rate >= 0.15 && audit_trace_coverage == 1.0) else CI_BLOCK
完整轨道:阈值校准
silent_p0、manual_review_rate、edge_drift、audit_trace_coverage 的「低/默认/高」表、同时「危险」削弱两个保护的练习以及完整指标依赖网络位于附录 D,第 D.4 节。在第一遍中,只需看到糟糕的发布被 guard 指标阻塞即可。
示例与应用
示例:团队希望证明新的 triage 管道已准备好进行更激进的自动关闭。首先使用目标优化 MTTR<=5m 运行重放。然后通过 red_button_mttr_blindness 检查相同的事件集。
如果结果看起来像 MTTR=4:50、silent_p0=18%、manual_review_rate=12%,则阻塞发布。原因不是糟糕的速度,而是保护不变量的违反。这是一个重要区别:目标已达成,但质量合同已被破坏。
> [conceptual interface] — scripts/metrics/simulate.py 和 validate_red_button.py 显示紧急模式检查的形式;教材仓库中没有现成的 CLI。在学习装置上检查相同不变量的可运行模拟是 python3 examples/goodhart-validator/scripts/run_validation.py(参见 examples/goodhart-validator/README.md)。
#### 在重放上运行红色按钮的示例
python3 scripts/metrics/simulate.py \
--scenario data/replay_300.jsonl \
--goal "MTTR<=5m" \
--spec specs/incident-spec.md
python3 scripts/metrics/validate_red_button.py \
--artifact validation.md \
--mode red_button \
--assert "silent_p0<=5% && manual_review_rate>=15% && audit_trace_coverage==1.0"
CONTROL: red_button = BLOCKED (MTTR=4:50, silent_p0=18%, manual_review_rate=12%)
第二个示例——错误地将 40 个 P0 作为「误报」自动关闭。在事后分析之前,指标看起来很干净:事件快速关闭,升级减少,队列不增长。
与标签核对后发现另一情况。五个事件是真实的严重故障。它们本应增加 silent_p0、escalation_regret 和 postmortem_regression。
在 validation.md 中将这种情况记录为 triage 的预测性失败。不要等到生产中的用户损害。使用重放和事后真相作为阻塞的早期信号。
实践中将 validation.md 存储在规范旁边,并且仅通过与 triage 规则相同的审查流程进行更新。每次变更的 CI 都会重建指标网络、运行重放、检查足迹完整性并比较行为与基准。阈值变更——例如将允许的 silent_p0 从 5% 提升到 7%——应通过第 3 部分中的 mutable 规则进行,作为风险合同变更,而非 YAML 的技术性编辑。此屏障保护系统免受在便利报告压力下逐渐侵蚀不变量。
总结
诱饵指标的危险不在于它们是假的。它们在成为唯一优化目标之前是有用的。
可靠的 validation.md 解决五项任务:
- 将目标与不变量分开;
- 在
silent_p0增长时阻塞 MTTR 改进; - 要求最小手动验证;
- 检查 triage 行为漂移;
- 保留 Qwen 日志、决策和差异的证据链。
> [runnable] — 本章的最小冒烟运行位于 [examples/goodhart-validator/](examples/goodhart-validator/README.md)。
cd book2/examples/goodhart-validator
python3 scripts/run_validation.py \
--validation specs/validation.yaml \
--metrics fixtures/new_metrics_good.json
python3 scripts/ci_gate.py \
--validation specs/validation.yaml \
--baseline fixtures/baseline_metrics.json \
--new fixtures/new_metrics_good.json
红色按钮场景的预期失败示例使用 fixtures/new_metrics_bad.json:run_validation.py 和 ci_gate.py 应以返回码 1 结束,因为 manual_review_floor 和 silent_p0_cap 被违反。
在下一章中,此保护回路将连接到实际监控和部署 API。
工件与准备标准
| 工件 | 准备完成条件 |
|---|---|
book2/examples/goodhart-validator 的本地运行 | 优化目标与不可侵犯不变量分离 |
| 三个装置:good / bad / drift | new_metrics_good.json 通过,new_metrics_bad.json 因具体原因被阻塞,new_metrics_drift.json 被 compare_drift.py 阻塞 | | 由于守护指标导致的阻塞示例 | MTTR 改进,但发布因 silent_p0 或 manual_review_floor 被阻塞 | | capstone/goodhart-note.md 中的记录 | 三行:目标指标、配对 guard 指标、阻塞条件 |
完整轨道添加了带有目标指标和质量不变量的 validation.md、.artifacts/metric_network.json 或可运行的指标网络模拟、重放装置以及带有 edge_drift、silent_p0 和 audit_trace_coverage 的 CI 网关报告。在以下情况下视为准备完成:紧急模式在 silent_p0 增长时阻塞 MTTR<5m;足迹字段链接提示、差异、决策和事后标签;阈值变更形式化为风险合同变更而非 YAML 装饰。
实践
cd book2/examples/goodhart-validator && python3 scripts/run_validation.py --validation specs/validation.yaml --metrics fixtures/new_metrics_good.json --json— *预期:码 0,JSON 中"status": "PASS",所有三个不变量manual_review_floor、silent_p0_cap、audit_trace_required具有"ok": true。*python3 scripts/run_validation.py --validation specs/validation.yaml --metrics fixtures/new_metrics_bad.json --json— *预期:码 1,JSON 中"status": "CI_BLOCK",检查red_button_mttr_blindness触发并具有"fail": "CI_BLOCK"和violated_invariants: [manual_review_floor, silent_p0_cap]。同样python3 scripts/compare_drift.py --baseline fixtures/baseline_metrics.json --new fixtures/new_metrics_drift.json --threshold 0.12返回码 1 并打印edge_drift=0.18 threshold=0.12 -> FAIL。*- 将三行转移到
capstone/goodhart-note.md:目标指标(MTTR)、guard 指标(silent_p0或audit_trace_coverage)、红色按钮规则。*预期:下一次尝试合并具有改进的 MTTR 但audit_trace_coverage下降的 PR 会被自动阻塞。*
检查问题
- 为什么 MTTR 作为唯一优化目标是危险的?
- 质量不变量与 KPI 有什么区别?
- 调查古德哈特回归需要哪些足迹字段?
- 发布后 MTTR 下降了 30%,但
silent_p0从 4% 增长到 12%。您会接受发布、回滚,还是要求额外诊断?您具体会检查什么?