# 2026-08-03（session Q）· 非零的分母不等于测到了东西

> 本篇只留**通用经验**。S6/S7 的判据、注入结果与数字在 backlog [[INFRA-F87]] 残余② 与
> `scripts/smoke-peer-floor.mjs` 文件头（§S6 的缘起）；扫描面为什么不改成 `dist/` 写在
> `scripts/audit-framework-api-floor.mjs` 文件头（§盲区现在各自归谁管）。
> 交付：`a5539e98`（S6/S7 + SoT 订正 + pre-commit 补挂）。
> 承接 [`2026-07-31-probes-that-cannot-see-their-own-blind-spot`](./2026-07-31-probes-that-cannot-see-their-own-blind-spot.md)
> —— 同一主题的下一个实例，而且这次**盲区就在前一轮为消除盲区而加的那条判据里**。

---

## 1. 「分母 ≠ 0」是个有轴的判据，而写的时候没人说出那根轴

前一轮（session P）把冒烟闸从 1 个出口面扩到 3 个，同时把 S4 从「合计分母」改成「**按出口面各判一次**」，理由写得很清楚：合计不为 0 会把某一面在空转这件事盖住。**这个改动是对的，而且正是为了防空转。**

然后本轮实测发现：三个面里的 `./web-components` **根本不消费 consumer 的 peer Vue**（Vue 被内联打包进 `dist-wc`）。装 `vue@3.5.0`、装 `vue@2`、还是压根不装，它跑的都是自己那份 3.5.32 —— 那一面**结构上不可能因为 peer 的原因变红**。

而 S4 对此毫无反应：它问的是「分母是不是 0」，这一面分母是 **37**，非零。

> **S4 检测的是「测了 0 个东西」，没检测「测的这 37 个东西与被测契约有没有关系」。**

两者听起来是同一件事，其实是两根不同的轴。写 S4 时脑子里的空转形态是「循环跑空了」，于是判据就只覆盖了那一种。

**可复用的判断**：给闸加「我到底测到东西没有」这类元判据时，把**轴**明确写出来 —— 是「数量 > 0」，还是「这些样本确实受被测变量影响」？前者便宜、后者才是真问题。检验方法很直接：**把被测变量改成任意荒谬的值，看这一面会不会红**。改不动 = 这一面与它无关。本轮那个决定性探针就是这个形状：在一个 `vue` 完全不可解析的空目录里 import 产物 —— wc 面照样注册 37 个元素全通，主产物当场 `Cannot find package 'vue'`。

---

## 2. 发现某个部件名不副实时，默认动作不是删掉它，是把声明变成受检事实

最省事的修法是把 `./web-components` 从 SURFACES 里摘掉：闸的声明立刻变诚实，代码也更少。

但那样会让**反向变化无声发生** —— 哪天有人给 `vite.web-components.config.ts` 加上 `external: ['vue']`（完全合理的一个改动），那一面就悄悄**进入**了 peer 下界契约，而闸里已经没有它了，没有任何人会知道。

所以留下它 + 加一条双向判据：每个面声明 `consumesPeer`，与产物机械核对，声称消费却零 specifier = 空转、声称不消费却有 specifier = 契约面变了。`consumesPeer: false` 的面照跑（它仍有「产物在 Node 里能求值」的冒烟价值），只是输出里逐面标出「此面的绿与下界无关」，不再算进契约覆盖面。

**可复用的判断**：删除能消除**当前**的错误陈述，受检断言能同时消除**未来**的错误陈述。当被删的东西处在一条会演化的边界上（构建配置、导出面、依赖形态），优先把「它现在是什么」写成判据而不是把它拿走。判断依据是问一句：**「这个东西将来可能变成相反的样子吗？变了会有人发现吗？」**

---

## 3. 探针与被测对象形态不一致时，失败原因会指向错误的方向

第一版探针我自己搭 DOM shim，顺手写了 `globalThis.window = globalThis`。结果撞在 `document.getElementById is not a function` 上 —— 因为定义了 `window` 就**激活了产物里浏览器专属的样式注入路径**，而闸自己那份最小 shim 不定义 `window`，根本走不到那里。

危险在于这个报错**看起来像在说「它需要 DOM，所以大概也需要 peer」**，方向正好相反。我当时已经在手搓 shim 打地鼠，很容易顺着「它环境要求高」这条错误直觉走下去。

改成**原样复用闸自己那份 shim**之后，一次拿到干净结论。

**可复用的判断**：验证某条闸的行为时，探针的环境要与那条闸**逐字同形**，不要自己重搭一个"差不多的"。差异会以报错的形式出现，而报错内容往往指向探针的缺陷、不是被测对象的性质 —— 这与 meta-rules 触发器（「命令本身有缺陷 → 把命令缺陷当成数据 bug」）是同一个病，只是发生在环境层而不是命令层。

同一形态本轮还中过一次：一组 `perl -0pi -e` 注入因转义写错而**语法失败、根本没改到文件**，而我差点把随后的 "59 passed" 当成「这条测试是空过」的证据。是因为顺手打印了注入点内容才发现。**注入后必须先确认注入真的发生了**，再去读结果。

---

## 4. 自己 30 分钟前给的推荐，同样是需要被测量的预测

起手测量后我给出的推荐是「把静态表的扫描面从 `src/` 扩到 `dist/`」，理由成立且具体：编译器宏与模板语法会被自家构建下沉成运行时 helper，`src` 26 个符号到 `dist` 变 43 个，扫 `dist` 就顺手闭合了「编译器宏」「src 外代码」两类盲区。

真去量了之后自己否掉了：扫 `dist` 会引入 **26 个表外符号**（全是编译器 helper），把该闸「unknown = 0、分母 100% 覆盖」这个性质直接摧毁，还要为 26 条逐个找版本出处；**而换来的东西冒烟闸已经用更强的方式给了** —— 它在真下界上对全部 43 个符号做 ESM 链接校验，是经验证据，强于查表。entry 原本那句「真要闭合只有按下界跑冒烟」是对的。

上一篇复盘的第 4 条讲的是「**entry 里**的候选解是上一轮的推断，动手前先量」。本轮把它推进一格：**我自己刚说出口的推荐也是推断**，而且因为刚说过、理由还热着，反而更不容易主动去证伪。

**可复用的判断**：推荐一旦被写下来就获得了不该有的分量。落地前把它当成别人写的 entry 对待，至少量一个能证伪它的客观数（这次是「新增多少表外符号」）。⛔ 结论已写进脚本头，标明「别再改扫描面」，省得下一个人（或下一个我）重走一遍。

---

## 5. 归因错了不代表问题不真 —— 但错的归因会写进 commit message

撞上 `audit:status-consistency` 红（STATUS 写 26、backlog 实为 27）时，我把成因归给「并行线插进 CANONICAL-F93」，**并写进了 commit message**。那是照抄接手 prompt 里的一句转述，没查 `git log`。

收尾核实：`2e01743c`（上一轮收尾）当时 backlog Active = 26、STATUS 写 26，**完全自洽**；真正造成漂移的是 `bb8043d5`（并行线，**14:43:18，本 session 进行中**）加了 `CANONICAL-F94` 却没同步计数镜像。**不是上一轮、也不是 F93。**

memory `entry-restatement-is-secondhand` 早就写着「转述含因果时先读活源」。这次的难堪之处在于：**要读的活源只是一条 `git log`**，成本几乎为零，而我把一句现成的说法直接搬进了一个会长期留存、还会被别人当史料读的地方。commit 已推双远端且并行 session 可能已拉取 → 不改写历史，订正记在 STATUS-CHANGELOG。

**但问题本身是真的**：`audit:status-consistency` 此前**只挂 `prepublishOnly`**，于是「往 backlog `## Active` 加一条却没更计数镜像」在提交那一刻无人拦，要等 CI 才红 —— 那时 master 已经红了。这不是谁收尾马虎，是**这条闸的 enforcement 层级缺了一层**。本轮补挂 L4（37ms、纯本地读文件），并造故障验过它拦得住。

**可复用的判断**：把「谁造成的」和「机制缺什么」分开写。前者需要活源、错了要订正；后者往往与前者无关 —— 本轮即使归因全对，缺的那层 pre-commit 依然缺。**归因不确定时，先写机制缺口那半句，它不依赖归因成立。**

---

## 6. 本轮做对的（同等重要，别只囤教训）

- **起手第一步照 pickup 的要求实跑闸、看它自印的覆盖面声明**，而不是照 entry 的旧描述办。整条线索就是从那句自印声明与实测对不上开始的。
- **阴阳对照写全**：每个阴性结论都配一个阳性对照（零 vue 环境下 wc 通过 ↔ 主产物 `Cannot find package 'vue'`；`vuex` 不误命中 ↔ `vue` 命中；版权注释不命中 ↔ 同一文本加一行真 import 就命中）。单看阴性结论无法区分「真的没有」和「我的检测坏了」。
- **反向验证新单测非空过**：5 组，逐条破坏它该测的东西看是否真红（判据换成全文 grep → 3 条红是最有价值的一组，它直接证明了「只匹配 specifier 位置」这个设计不是装饰）。
- **端到端验真产物**：真给 wc 配置加 `external: ['vue']` 重新构建到临时目录，确认检测的是产物本身而不是那个标志位 —— 否则 S6 只是在核对我自己写的两个布尔值。
- **判据形态照抄本仓已有先例**，没发明新机制：双向 fail closed（照 S2/S3）· report-only + 逐条点名 + 计数（照 owner 2026-07-31 给 S5 unknown 定的那一档）· 闸自印覆盖面（照 `audit:framework-api-floor`）· pre-commit 条件 gate 的写法（照相邻两条）。
- **所有注入都 `cmp` 逐字节复原**，包括一次往 `dist/chart.js` 里塞代码的注入；仓库 `dist-wc` 全程未被探针改动（端到端那次构建输出到 scratchpad）。
- **残余没做完的部分如实标成「判断」而不是「遗漏」**：option/prop 形态与同名 API 新签名仍开着，并写清为什么两条闸都盖不住（静默行为错误、不抛异常）、当前存量实测多少、为什么现在不做（下界 3.5.0 近最新、Vue 3.6 未发布）、什么时候自然会被触发（闸自带的 2026-10-28 复核到期日）。
