# 复盘 2026-08-21 —— 「不发行」这一类假绿，和一条我编出来的依赖

> 触发条件（`WRAP-UP.md` §Retrospection 触发判断）命中两条：**踩了反模式** + **发现新 pattern**。
> ⛔ **本文件只写可迁移的部分**。本轮的经过 / 数字 / 取证过程在
> [`STATUS-CHANGELOG`](../STATUS-CHANGELOG.md) 顶条（2026-08-21），判据真源在
> [`scripts/f129-rootradius-silent-gap-probe.mjs`](../../../scripts/f129-rootradius-silent-gap-probe.mjs)
> 头注释 —— 这里刻意不复述，复述一份就是第二处会漂的副本。

---

## 一 · 新 pattern：「不发行」是一整类假绿，不是 rootRadius 一个 bug

本轮的病灶一句话：比较器在 `expected === null` 时 **early-return**，于是那条 check
**连行都不发出**。它的危害不在「比错了」，而在**它在每一个统计口上都不存在**：

| 你去哪里找它 | 看到什么 |
|---|---|
| report 的 `checks[]` | 没有这一行 |
| 失败清单 / `fail` 计数 | 不在里面（它没失败） |
| `passRate` | 不影响（它也没通过） |
| 豁免表 | **不在**——因为它从来没红过，没人需要豁免它 |
| 闸的退出码 | 绿 |

⇒ **「没有人报告它」被读成「它没问题」。** 这一类缺陷的唯一发现方式是**从分母那边倒着数**：
拿输入集（manifest）里「本该产生 check 的条数」去减 report 里「实际存在的行数」，差值就是它。

**可迁移的判据模板**（下次遇到任何比较器 / 校验器都能套）：

1. 找到那个 `if (<某侧缺值>) return` —— 它就是静默的入口。
2. 用**输入集**算出该条件成立的条数 = 静默 check 的**人口**。
3. **归因必须自证，不能断言**：证明「条件成立 ⇔ report 里该行缺失」**双向逐一对应、0 反例**。
   只验一个方向会把「别的原因造成的缺行」一起算进来。
4. 再**注入故障**证明第 3 步那个自证真的会红（删一行 / 改一个值），否则它可能是恒真的。
5. 人口 ≠ 缺口：还要分出「另一侧有值」（真缺口）与「两侧都没值」（零风险）。
   ⛔ 别把人口当缺口报 —— 本轮 284 的人口里只有 34 是真缺口，250 是零风险。

**⛔ 两条封路（本轮已付判断成本）**：
- ⛔ **别给缺失侧补一个默认值当 expected**（如给 `null` 补 `0`）。那把「真源没说」改写成
  「真源说是 0」= **编值**，且从此永久绿。
- ⛔ **别顺手去改那个 early-return**。它是判据本体：改了就得回答「缺值时该发出什么行、
  怎么表达『验不了』而不伪装成『通过』」，而它的作用面往往含统计函数、闸的计数、字段集派生。
  正确顺序 = **先量 + 先把人口上闸，再单独立项改判据**。

**这一类的闸能守到哪、守不到哪（本轮实测的分界）**：**人口**可以离线算 ⇒ 挂得进 hook/CI；
**「其中几条真是缺口」**需要真渲染（浏览器）⇒ 在本仓结构上挂不进 CI（Gitea runner 非特权装不了
chromium，这也是 render-gate 本身不进 CI 的原因）。⇒ 上闸时**必须把这条边界写进闸的自印输出**，
否则下一个人会把「闸绿」读成「那 34 条也被守着」。

---

## 二 · 反模式：我在推荐里编了一条「依赖」

出推荐时我写：「先做 ①，因为它解锁 ② 的可信度 —— padding 修完靠什么证明没再静默？靠 ①」。
**活源一查即证伪**：`rootPadding` 有 **128 条失败 check + 11 行豁免**，它**红着**、有闸盯着，
与 rootRadius 的静默毫无关系。那条因果**不存在**，是我为了让排序显得有结构而编出来的。

**为什么值得单独记**：这不是算错数，是**给排序编了一个前置条件**。它比错数字危险 ——
数字错了下一个人重算就发现，而「A 解锁 B」听起来是架构判断，**没有任何闸会检查它**，
会被下一轮当既有结论继承。

**同族**：memory `recommendation-self-audit`（推荐自审 4 问）· `entry-restatement-is-secondhand`
（转述是二手源）。本轮给它补一条**执行细节**：

> 自审第 1 问「理由经得起反驳吗（尤其依赖/前置是真的还是编的）」，**必须对活源查，不能对自己
> 上一句话查**。具体动作 = 把每一条形如「A 解锁 B / B 卡在 A / 做完 A 才能 B」的话，
> 单独拎出来跑一条命令去证伪它。本轮那条只花了一个 `node -e` 数 failing check 就倒了。

**还有一半连带后果**：撤掉那条假依赖之后，**排序本身就变了** —— 我原来排 #1 的方案
（改两条链共用的比较器）作用面远大于另一个（只把离线那半上棘轮闸），而后者我**根本没列出来**。
⇒ 自审第 3 问「有没有更稳健的选项被压脚注」这次不是形式主义：**假依赖会掩盖真选项**，
因为编出来的前置条件正好把那个更稳的选项排除掉了。

---

## 三 · 一条小的、但会反复咬人的

**闸落地第一次跑就抓到了我自己的错**：棘轮 baseline 的 key 我写了 `switch`，而判据用的是
`codeComponent` = `Switch`。`codeComponent` ↔ `figmaName` 在本仓是**会分叉**的两个字段
（`Switch`↔`switch` · `Tooltip`↔`Tooltips` · `Button`↔`Button/url link`），
`f129-rootfill-candidate-triage.ts` 的头注释早就写着这个坑，我还是踩了。

两点：① 这顺带成了那道闸**非空过**的旁证（它抓到的第一个东西是真的）
② **新建闸的第一次运行要当成「对闸自己的测试」来读**，别看见红就先怀疑数据。
