# 判据在，但它永远不会响（2026-08-21 · INFRA-F133）

一轮闸的实施里同一个病现身 **4 次**，其中 2 次是我自己引入的、1 次是我自己漏掉的。
它不是「判据写错了」，是**判据写对了、测试也绿了，但在某条真实执行路径上它永远不会生效**。
本篇沉淀这个 pattern，加上同一轮里另外两条可迁移的东西（两侧 shell、退档纪律），
以及一条**已登记的教训为什么没拦住**的记账。

真源指针：判据与档位的真源是 [闸脚本头注释](../../../scripts/audit-claim-vs-livesource.mjs)；
enforcement 层级登记在 [`meta-rules.md` 触发器 K](../../meta-rules.md)；
本篇只记**可迁移的判据**，⛔ 不在这里维护第二份闸规格。

---

## 一 · 新 pattern：「死判据」——判据存在，但在某条真实路径上永不到达

四例，逐条都有实测，不是理论风险：

| # | 判据 | 它本该防什么 | 为什么永不生效 | 谁发现的 |
|---|---|---|---|---|
| 1 | S3「有受管目录却 fileCount==0 → 红」 | 扫描面塌 | 计划只判 staged 文件 ⇒ `fileCount==0` 是绝大多数 commit 的常态，且 `main()` 用「`targets.length===0` 提前 exit 0」在它之前就返回了 | pre-flight 自查（Ruling-1） |
| 2 | S1/S3/S4 三条 fail-closed 兜底 | 「把 canonical 删空让闸空转成假绿」 | `targets.length===0` 的提前 exit 0 排在 `failures` 判定**之前** ⇒ 最该红的那一刻（没 staged 指令型 .md 但删空了 src/canonical）闸正好绿 | dispatch 前逐行读 brief（Ruling-5） |
| 3 | S1「活源提取分母非空」 | 同上 | 实现写成 `props===0 && exports===0`，而 spec 那个「/」是**或** ⇒ 单侧清零不红；更糟：此时 S2 每条 claim 都在 `compProps===undefined` 处 continue ⇒ **report-only 的读数也一起静静归零** | 最终 whole-branch review（C1）；**我此前的故障注入漏掉了** |
| 4 | CI 侧那一步的 `--files` 名单 | master 面的全量覆盖 | `git diff origin/master...HEAD` 在 `push: master` 上恒空 ⇒ 闸恒走 skipped；叠加 pipefail 后更糟，变成**恒红** | run #1007 报红后定位；**是我自己引入的** |

**为什么它比「没有判据」更糟**：判据的存在会让人**降低人工核对**。四例里有三例的注释/文本逐字
写着「这条是为了防 X」——读者据此认为 X 已经被机械拦住了。第 3 例的注释原文就是
「防把 canonical 删空让闸空转成假绿」，而它当时只在两侧同时清零才认账。

**四例里没有一例是靠单测发现的**，而且不是测试写得不够多（这条闸最终有 40 条新单测、
全绿）。原因是同型的：

- 例 1/2 是**出口顺序**问题 —— 单测直接调 `evaluate()`，根本不经过 `main()` 的提前 return。
- 例 3 的测试是「两侧都空 → 红」，而**「全空→红」在 `&&` 和 `||` 两种实现下都通过** ⇒
  那条测试对这个 bug **完全没有鉴别力**。
- 例 4 只在 `push: master` 事件上成立，本地把 27 个 step 全跑一遍也复现不出来。

### 可迁移的判据模板

新增或修改任何一条闸判据时，逐条回答（已机械化为 [`meta-rules.md` 触发器 R](../../meta-rules.md)）：

1. **这条判据有几条真实执行路径？** pre-commit / CI `pull_request` / CI `push: master` /
   手工 CLI / 本地 shell vs runner shell —— 逐条列，⛔ 不许写「都一样」。
2. **每条路径上它的输入面是什么？会不会恒空 / 恒满？** 例 1/2/4 全是这一问漏掉的。
3. **故障注入要逐个分母单侧打。** ⛔ 只打「全部清零」不算 —— 那种注入在 `&&` 和 `||` 下
   都会绿灯通过（例 3 就是这么漏的）。
4. **若把 X 改坏，哪几条测试会变红？一条都不红就是空断言。** 这一问本轮用了四次，
   每次都换来真结论；它也是唯一能把「同义反复的永绿测试」照出来的问法。

---

## 二 · 同一段命令，两侧判定不同 —— 写闸时的默认前提

**pre-commit 是 `#!/usr/bin/env sh` + `set -e`，没有 pipefail；CI runner 的 `run:` 体是
`bash -eo pipefail`（act 系默认）。** 同一段写法在两侧判定不同：

```
FILES=$(git diff --name-only … | grep -E '…' | paste -sd, -)
```

`push: master` 上差集恒空 ⇒ `grep` 无命中退 1 ⇒ 在 pipefail 侧命令替换失败被 `set -e` 打死，
**node 一行都没跑就 exit 1**；在无 pipefail 侧一切正常。实测 `bash -e -c` EXIT=0 /
`bash -eo pipefail -c` EXIT=1。后果是 master 上**永久性假红**，连并行 session 一起挡。

**同一家族，本篇写作当天又撞了一次**：查重时用

```
paths="AGENTS.md docs/meta-rules.md …"
git grep -c "$kw" origin/master -- $paths
```

在 **zsh 下不做 word splitting** ⇒ `$paths` 成了**一个**含空格的 pathspec ⇒ 零匹配 ⇒
exit 1 无输出。bash 同一段 exit 0 且有命中。差一步就把「规则真源零命中」当成结论，
去起草三条与既有触发器重复的规则 —— 那恰好违反 [触发器 J](../../meta-rules.md) 本身。
救回来靠的是**对已知必命中的一条做自检**（`report-only` 在 meta-rules 里必然有，
因为上一轮刚写进去），而不是重读命令。

**可迁移**：

- 写进 hook / workflow 的任何管道，**两侧 shell 都要实测**，或显式 `set -euo pipefail`
  让两侧同判（本轮的修法）。
- 无匹配时**退 0** 的枚举器（`git ls-files`）优先于「过滤器 + grep」——少一个退 1 的来源。
- 诊断用的 shell 命令，**先对一条已知必命中的输入做自检**，再信它的零命中。
  零命中是最容易被当成结论的输出，因为它「看起来就像答案」。

---

## 三 · 退档比修启发式更对 —— 但要有实测才配退

这条闸的核心判据（S2：文档里的 prop 断言 × 活源）初始档实测 **86.7% 假阳性
（13/15，35 份指令型文档，逐条读原文判定）**，目标是 < 10%。处置是**退成 report-only**
（每次跑、每次印、不阻断），不是继续修启发式。

三条依据都是实测，不是推断：

1. 86.7% 远超阈值。
2. **spec 指定的「收窄档」实测更糟**：收窄到 markdown 表格同行后是「0 真 / 8 假」——
   本仓表格多是「项 \| 结论 \| 手段」这种单元格塞散文的核查清单，一行里多个组件名 +
   文件名 + 字段名；反而 `- \`Comp\`：props …` 这种定义列表才是真正「一行一个宿主」。
   **spec 当初的假设正好写反了，靠实测才翻过来。**
3. **不许用零假阳性的小样本子集宣称达标**：deflist-only 是唯一 0% 假阳性的区域，但 n=2 ——
   用 2 个样本宣称「假阳性率 < 10%」本身就是「把未核的数字写成事实」，正是这条闸要治的病；
   而且它会打掉验收凭据（三条历史 bug 回归 fixture 全是表格形态）。

**⛔ 两条封路**：

- **不许靠往豁免表塞条目实现退档** —— 13 条假阳性压绿就是
  [`2026-08-05-fault-injection-harness-can-fake-success.md`](./2026-08-05-fault-injection-harness-can-fake-success.md)
  复盘的那个病。豁免表保持 `[]`，**空着是终态不是待办**。
- **不许为了救阻塞档去修启发式**：已算过，黑名单 + 「紧邻在前的宿主」后仍约 33%。
  「是不是断言」「断言归属谁」不是词法特征 —— 同一结论在本设计里已被实测否决三次。

**反向代价才是决定性的**：86.7% 的噪声必然导致两种结局之一 —— 全仓靠豁免压绿（假绿闸），
或被人 `--no-verify` 绕过（闸形同不存在）。两者都比退档更坏。⇒ 已机械化为
[触发器 K 的退档条款](../../meta-rules.md)。

⚠️ 退档后**层级必须如实登记**：report-only 不算 L4/L5（不拦不算数），也不是靠 AI 记得
调用的 L1/L3，而是自动挂载但不拦截的独立一格。记成「L4+L5」的危害是反的：会让人以为
错断言会被机械拦住，从而**降低**人工核对。

---

## 四 · 已登记的教训为什么没拦住（这条最该记）

收尾时我判「开 PR」，被 owner 一句「为什么开 PR？我的不是应该直接进 Master 吗」推翻。
三条硬证据都在仓库里、当时该查而没查：`AGENTS.md` 硬规则 #7 明写「Owner 可直 commit
master、不建分支」；`pr-checks.yml` 的 `on:` 同时挂 `pull_request` 与 `push: branches:[master]`
（所以「值得一次真实 CI」那半理由根本不成立）；`origin/master` 近 20 个 commit 零 merge commit。

**而这件事 2026-08-07 已经登记过一次**——
[`the-gate-that-only-watches-one-direction.md`](./2026-08-07-the-gate-that-only-watches-one-direction.md) §4③
记的就是「skill 的菜单架空了仓库的标准动作」，连 owner 的原话都记着。

⇒ 值得记的不是「下次记得直推」，是**错误的形态**：`finishing-a-development-branch` 的
选项 1 要求 `cd` 主仓 + `git checkout master`，而主工作树正被并行 session 占用 ⇒ 我据此
**排除了正确选项**。但那是**技能配方的限制，不是 git 的限制**：从当前 worktree
`git push origin HEAD:master` 全程不 checkout 任何东西。

**这与第一节的死判据同族**：都是「在某条路径上并不成立的前提」被当成了普适前提。
一个发生在闸的判据里，一个发生在我的决策里。

**可迁移**：技能 / 配方给出的选项集，是**它的实现路径**的枚举，不是**事实可能性**的枚举。
被配方排除掉一个选项时，问一句「这个限制是工具的还是世界的」。

---

## 五 · 验到有效的做法（不只记教训）

本轮反复用、且换来了真结论的做法，按「下次遇到类似场景直接用」记下：

1. **「若把 X 改坏，哪几条会变红？」** 用它判测试/闸的有效性，而不是看断言长相。
   本轮四次使用，每次都有产出；也是唯一照出「同义反复永绿测试」的问法。
2. **探针形态审计**：验「区域 A 是否终止」的探针，必须是**只可能被 A 收**的形态。
   本轮因探针本身污染结论中招两次（一次是我设计的探针比 implementer 的更差），
   之后把它写成了 reviewer 的显式判据。
3. **双向变异分析**：改档位（阻塞 → report-only）这类改动最容易把真断言悄悄变成空断言 ⇒
   要求「若改回原通道会红哪几条 / 若整段删掉会红哪几条 —— **两种变异下都不红的就是空断言**」。
4. **端到端跑真实输入，不信理想化 fixture**。单测全绿而闸实际无效，本轮出现两次：
   一次是 live 用硬编码 mock，一次是**文档 fixture 用了真实语料里不存在的理想化表格形态**。
   两次都只有真实输入才照出来。
5. **subagent 的「完成/通过」一律亲验**。一个 implementer 被 OAuth 过期打断只留一行
   「1866 passing」，controller 自己重跑才算数；另一次两个 implementer 的报告互相矛盾，
   亲验判定**先说的那个错**。
6. **取真实退出码先存变量再读 `$?`**，⛔ 不经管道 —— `| tail` 会把 exit 1 吃成 0。
   本工作线踩过三次，其中一次让 `git merge` 的 CONFLICT 显示成成功。
7. **引用另一份文档时，要么原样复制不加工、要么别说「逐字」，并且必须带 commit / 日期。**
   我在一个专门防「未核断言」的计划里，自己犯了这个病：改写了原文却标「逐字」，
   而且引的那个版本已被后续 commit 整段覆盖。在有并行 session 每小时推数次的仓库里，
   「我读过」和「现在还在」是两件事。
