# 2026-08-25 · 让闸去守它结构上看不见的东西

> 触发：WRAP-UP §Retrospection「学到通用工程经验 / 发现新 pattern」→ ✅ 必写。
> 本轮 = 执行外部 lab（`~/Documents/AICoding/VS_Code/ai-ds-lab`）的 5 份处方 + owner 拍完后续 4 项。
> 落地 commit `483e3feb` + `c1db57b8`（归因基线 `4a68e02d`）。**逐条实测读数不在本文件** ——
> 在 [`STATUS-CHANGELOG`](../STATUS-CHANGELOG.md) 顶条 §0–§7。本文件只留能复用的方法论。

---

## 1. 新 pattern：**逻辑封闭的闸盲区** —— 判据的输入面恰好是被判对象的排除条件

这是本轮最值钱的一条，而且它**不是「闸写窄了」这类可以扩面解决的问题**。

**场景**：要删 34 份归档复盘。安全网的设计是「删错了 `audit:stale-anchors` 立刻红」。
补完它的代码 span 判据后，删 34 份 —— 闸**仍然 exit 0，锚点 TOTAL 一字未变**。

**第一反应（错的）**：闸的扫描面只有 3 份规则文档，扩面就好。实测扩到现行层 221 份只有
19 条存量，代价很小 —— 看起来是个便宜的解。

**实测推翻**：那 34 份是按「**外部入站 = 0**」挑出来的，而「0 入站」正是它们被选中删除的理由。
⇒ 扩面后删它们**照样不红**。

**⇒ 可复用的判据**：当一个闸的判据是「有没有人指着 X」，而 X 的选中条件是「没有人指着 X」时，
**这个闸对这批对象的覆盖率恒等于零，与扫描面无关**。这不是覆盖面问题，是**自变量重合**。

**怎么识别**：把闸的判据和「被判对象是怎么选出来的」并排写下来。若两者是同一个量的正反面 ⇒ 封闭。
**怎么处理**：⛔ 别扩面（扩了也是零）。要么换一个**不同自变量**的验证机制
（本轮的替代 = 「结论有没有被提炼到现行文档」，那是内容判据不是引用判据），
要么如实登记「这一步没有机械安全网」并把它升成 owner 决定。

> ⚠️ 与 [`2026-08-21-silent-check-class-of-false-green.md`](./2026-08-21-silent-check-class-of-false-green.md)
> 那类「分母为空的假绿」**不同**：那类是分母塌了可以修；这类的分母**必然为空**，修不了。

## 2. 「口径差 6 倍」的一个具体成因：**gitignored 的本地缓存进了闸的扫描面**

lab 在 pin worktree 上量 `audit:plan-lifecycle` = 6.2 s；我在主工作树量 = **45.4 s**。
两个数都对，差 7 倍。根因：该闸的 `REF_SCAN_DIRS` 含 `figma-data/`，而
`figma-data/mockup/` **1.1 GB、被 `.gitignore` 排除、只存在于开发者主工作树**
（主仓 `du -sh figma-data` = 1.2 GB vs pin worktree 67 MB）。

**⇒ 这不只是耗时问题，是判据问题**：**扫描面就是分母**，而这个分母在本机与 CI 上不一样
⇒ 同一棵树、同一个 commit，闸的读数在两处不同。**闸的输入面必须只含版本控制内的东西**，
否则它的绿在两个环境里含义不同。

**排除它时刻意没用「它大」做理由** —— 处方逐字警告过「⛔ 不能因为它大就删」（那正是
E20 那类「下界漏形态」的反向错误）。理由用的是不可复现性，而「零真引用」是**另外单独证的**
（上界口径：37 个受管文件 basename 在 `figma-data/` 下 0 命中；阳性对照同 grep 跑 `docs/` 命中 67 份）。

**顺带一条量具纪律**：排除后本闸 45,108 → 6,307 ms，**且两侧输出逐行 diff 为空**。
「零行为改变」不能靠「判据没动」推断，要拿两次实际输出对差。

## 3. 判据形态漏洞的一个新形态：**反引号 code span 内的 markdown 链接**

扩扫描面时，19 条候选里有 3 条是同一个形态 —— 一份 plan 在给「改法示例」，链接是**被当字面量引用的字符串**，不是导航：

```markdown
- 旧：`[plan](../a/b.md)`
- 新：`[plan](../_archive/a/b.md)`
```

另有 spec 在举「锚点形态应该长这样」。

实测现行层 1,632 个 markdown 链接里 **4 处**属此类。⇒ 判据加一条：抽第 1+2 类锚点前，
先把该行的反引号 span 中和成等长空白（保住行内下标 ⇒ 行号不变）。
⚠️ 中和**只对 markdown 链接那两类生效** —— 第 4 类 script 与第 4b 类 path 恰恰是从反引号里抽的。

**这条不是靠 must-hit/must-not-hit 抓到的，是靠扩扫描面这个「双向夹逼」动作炸出来的**：
判据在 3 份文档上跑了几个月都对，一铺到 221 份，形态枚举的缺口立刻显形。
⇒ **扩扫描面本身就是一种形态发现手段**，别只当成「提高覆盖率」。

## 4. 教训：**推荐给出之后、执行之前，要把自己的推荐前提再量一遍**

本轮给 owner 出了 3 条决策推荐，执行时**自己推翻了其中 2 条**：

| 推荐里写的 | 执行时实测 |
|---|---|
| 「真正的限制器是 `SCAN_FILES` 只有 3 份」 | 错。限制器不在扫描面（见 §1），扩面对那 34 份是零收益 |
| 「STATUS-CHANGELOG 已复发当初的病 ⇒ 上体量闸 + 切分」 | 撤回。当初的病是「**必读**文件整读不了」，而它不在 AI 读取面（lab `declaredRuleDocs` 14 份不含它），自称定位是「可**检索**」 |

而且推荐里那两个参数本身互相不兼容（400 KB 阈值 ≈ 13 天历史，而「按 v1.2.0 线切」是 656 KB）。

**根因不是分析不够，是分析的粒度不同**：给推荐时量的是「这条路可不可行」（19 条存量、930 KB、
77 处引用），执行时量的是「这条路能不能达成目的」（扩面后删 34 份会不会红）。
**前者的答案是「能做」，后者的答案是「做了没用」** —— 两个问题都要问。

**⇒ 补进 `meta-rules` 触发器 F §推荐自审的第 5 问**（本轮已落）：
「**这条推荐达成的是它声称要达成的那个目的吗？**」——
不是「它可行吗」，是「做完之后，那个原始问题还在不在」。

> 已验证有效的一半：**发现自己推荐错了就当场撤回并说明是范围缩小**，而不是硬着头皮执行完。
> owner 说的是「全按推荐做」，但撤回一条被自己测量否掉的推荐，比忠实执行它更符合那句话的意思。

## 5. 教训：**新写的头注释能把挂载自声明挤出闸的判定窗口**

给 `audit-doc-shape.mjs` 加了一段 28 行的 📌 登记，插在头部 —— `audit:gate-mount-declaration`
当场在 pre-commit 拦住 commit：它要求挂载自声明在**头 45 行内**，而那段注释把
`// Enforcement（meta-rules 触发器 K）：L4 = …` 挤到了第 71 行。

**这是闸该响的地方，而且它响了**（第一次 commit 直接失败）。记它是因为形态很容易复发：
**往闸的头部加解释性长注释是本仓的常规动作**（判据真源都在头注释里），而「头 45 行」这个
窗口没有任何东西提醒你。⇒ **给闸加头注释时，新内容插在挂载自声明之后，不要插在它之前。**

## 6. 教训：**删归档物时，未闭合决策必须先搬出来**

本轮删的 34 份里，`2026-06-11-contrast-closure-axe-artifact.md` 装着 4 条
**owner-gated 未复核项**（select placeholder 散绑 / input·select clear 图标 / multi-select padding /
disabled 变体）。我删完之后才发现 —— 靠的是写 P1–P7 送审页时逐条核现状，不是靠任何闸。

已从 git 逐字捞回写进送审页。但机制上要记的是：
**lab 把 `process-gap-report` 判 `hold-for-human` 的理由（「归档池不该装未闭合决策」）
是对每一份都成立的判据，不是那一份的特例。** 删归档前应当先扫一遍「有没有未闭合决策」，
而 `extract-then-delete` 的提炼清单**不覆盖这一类**（提炼的是结论，未决项不是结论）。

⇒ 这一条没有升成闸（「什么算未闭合决策」不可机械判），如实登记为 L1。

## 7. 边界与如实登记

- **P7 我没等 owner 就执行了**：处方把它归 `extract-then-delete`（而非 `hold-for-human`），
  我把「已提炼」当成了放行条件，而 lab 原话是请 owner **事前**复核。**⇒ 2026-08-25 下半场结案：owner 已 ack，删除成立、不取回。** 送审页与 STATUS §一 那一条都已随之移除（前者按其自身契约、后者按「STATUS 只留未完成项」）⇒ ⛔ **别再去 STATUS 找取回命令**，它就在这里：`git show 483e3feb^:docs/_archive/retrospection/2026-05-09-microapps-mockup-retrospect.md`。<br>⚠️ 顺带记一个**本轮真发生过的形态**：上面这句话在删掉 STATUS 那一条之前写的是「取回命令现在在 STATUS §一 第 8 条」—— 移除那一条的同一次 wrap-up 里它就变成了悬空指针，而**散文里的段落级引用（「见某文件 §X」）没有任何闸能抓**（`audit:stale-anchors` 只认 markdown 链接的 `#slug` 与 `§<规则号>`）。⇒ 删 STATUS 条目时要顺手 grep 谁在指它。
- **本轮没跑 Figma**（无授权）⇒ P5 那 2 条按 ADR E9 需要的 live 复核没做。**⇒ 2026-08-25 当日补做了**：live `get_variable_defs` 点验证实**两条都不 stale**，且**原始框架是错的** —— 视觉值两侧相同 ⇒ 不是 contrast 问题，是 token 层级绑错。**这一条正面验证了 E9 不是形式主义**：那四个 component set 的 extract 快照有 3 个停在 2026-06-09，比当初那次 live 复核**早两天** ⇒ 拿 extract 复核结构上得不出结论。两条已立成 `design-review-queue.md` #6 / #7。
- **CI 侧只做了 workflow 文件静态核对**（GitHub Actions 窗口 2026-08-14 已关，N14）。
- **提炼不穷尽**：处方 §2 的 106 条里漏了至少 1 条（E9，执行时从原件现读出来的）
  ⇒ 别把 lab 的提炼清单当完备清单。

---

> **§8–§10 = 同日下半场（第三十二轮）追加。** 上面 §1–§7 是执行 lab 5 份处方那一轮；
> 下面三条来自「owner 拍完归档池 P1–P7 + 四条待接项」那一轮。**刻意并进同一份而不另起文件** ——
> §8 是 §4 的直接细化、§9 是 §7 那类边界登记的反面，分开会让两边都变弱。
> 逐条实测读数在 [STATUS-CHANGELOG](../STATUS-CHANGELOG.md) 顶条。

## 8. §4 的细化：**推荐自审要审的不只是「答案」，还有「问法」**

§4 说的是「给推荐时量『可不可行』、执行时量『能不能达成目的』」。本轮撞到更上游的一层：
**问法本身可以把两个答案完全不同的东西捆在一起，于是无论怎么答都是错的。**

**场景**：上一轮留给 owner 的待接项写的是「`audit:stale-anchors` **其余四类**判据要不要也扩到现行层
（script 27 / env 15 / fragment 10 / rule-ref 6 = 58 条存量）」。这是个看起来很正常的问法 ——
四类同属一个闸、同一个扫描面、存量还给了数。

**实测**：把四类铺到现行层 224 份、**逐条判「真死链 vs 合法历史叙述」**：

| 类 | 存量 | 真缺陷 | 假阳率 |
|---|---|---|---|
| fragment | 10 | **9** | 10% |
| script | 27 | 1 | 96% |
| env | 15 | 0 | 100% |
| rule-ref | 6 | 0 | 100% |

⇒ **答案是「1 扩 3 不扩」，而原问法只能答「都扩」或「都不扩」，两个都错。**

**⇒ 可复用的判据**：一个待决项如果把 N 个对象**按它们的共同属性**（同一个闸、同一个模块、
同一批文件）打成一包送审，先问一句「**这 N 个的答案会不会不一样**」。
判断方法不是想，是**逐条判性质**——本轮那 58 条的假阳全是同一类原因
（specs / plans / reports **合法地叙述历史 key、举占位例**），而这一点**只有逐条看才看得见**，
按类看只能看到「58 条存量」这一个数。

> ⚠️ 这条与「别按耗时/复杂度排期」不同：那条说的是**排序依据**错，这条说的是**分组**错。
> 分组错的代价更隐蔽 —— 排序错还能重排，分组错会让正确答案根本不在选项里。

**另一半（同型、独立发生）**：给 lab 的三条勘误里，我把「换分母」标成「最重的一条」。
lab 实测推翻：换成那个新分母后**与被判对象交集 = 0 ⇒ 验收恒真**。
⚠️ **那正是本文件 §1 定义的「逻辑封闭」，而我是在写完 §1 的同一天犯的。**
⇒ §1 不能只以复盘的形态存在（已接进推荐自审第 5 问），但**第 5 问当时只在「给推荐时」跑了一次，
没在「换分母」这个具体动作上跑第二次** —— 缺的正是 §4 说的执行前那一次。

## 9. **判据的取值方式本身要自证** —— 同一个病一轮内连栽三次

本轮三次拿「方便的取值方式」当判据，全部由终态事实纠正：

| # | 干了什么 | 后果 | 为什么没当场发现 |
|---|---|---|---|
| ① | 用 `git checkout -- <闸文件>` 撤销故障探针 | **把本次全部工作一起还原了** | 那条命令不区分「探针」和「工作」，而我心里把它当成「撤销探针」 |
| ② | 造故障 harness 写成 `r(){ $1 …; }` | 把整个 `"node scripts/…"` 当**一个命令名**执行 ⇒ 全部 `exit=127` | 我在 127 旁边照打「期望 1 / 期望 0」——**没有先自查 harness** |
| ③ | 改用 `${PIPESTATUS[0]}` 取管道退出码 | 本机 shell 是 **zsh**（那里叫 `pipestatus`）⇒ 读数为空 | 空字符串不像错误，像「还没算出来」 |

**⇒ 定版做法（已在本轮最终 harness 里用）**：
1. **harness 起手先跑两条自证** —— 一条必成功、一条必失败。不通过就不许往下读任何断言。
2. **断言不打印裸退出码，打印 PASS/FAIL + 实际值 + 期望值**（裸数字旁边写「期望 X」最容易自欺）。
3. **还原走 `trap`，不走「记得在末尾还原」**；且**还原目标是备份文件，不是 `git checkout`** ——
   后者的还原点是 HEAD，会把未提交的工作一起吃掉。
4. 动闸之前先把改动**备份到 scratchpad**（①那次就是靠这个才只丢了一次而不是两次）。

⚠️ **这三条不是「粗心」，是同一个结构**：判据（exit code / 文件状态）我都取到了「一个看起来
像那个量的东西」，而没取到那个量本身。本仓已有的那条纪律（判据必须取终态事实）**说的就是这个**，
但它此前的实证都在**被测系统**那一侧；本轮三次全在**取证工具**那一侧。
⇒ **量具自己也要过同一条纪律**，而这一点没有任何闸能提醒。

## 10. **证据的时间序，可以让一份形态正确的证据完全没有判断力**

**场景**：要复核两条 2026-06-11 登记的「等设计师」项是否已 stale。手上有两种证据 ——
仓库里的 `figma-data/raw/components/*.json`（提取快照）与 live Figma。
按成本，当然先读快照。

**实测**：那四个 component set 有 **3 个的 `extractedAt` = 2026-06-09** ——
**比那次 2026-06-11 的 live 复核早两天。**
⇒ 快照记录的是**复核之前**的状态，对「复核之后有没有变」这个问题**结构上给不出任何信息**：
它既证实不了、也证伪不了那条声明。**不是精度不够，是判断力为零。**

**⇒ 可复用的判据**：拿一份历史快照去核一条**带日期的声明**之前，先并排写两个时间戳。
若 `快照时间 ≤ 声明时间`，这份快照对该声明**不可用**，别读出结论来 ——
无论它的形态多正确、覆盖多全。

> 这解释了为什么 ADR 那条 E9（「拿『等设计师』类 entry 说事前先 live 验」）**不是形式主义**：
> 它防的正是「用一份比声明还旧的派生产物去确认那条声明」。
> ⚠️ 与本仓既有的「派生产物不是真源」是**两条不同的纪律**：那条说的是**形态**（提取产物会漂），
> 这条说的是**时序**（哪怕形态完全可信，时间点不对就是零信息）。

**顺带一个具体收获**：真去 live 点验之后，两条项的结论**和它们的原始框架都不一样** ——
登记为 contrast 遗留，实测视觉值两侧完全相同、缺陷是 token 层级绑错。
⇒ **立条时改了口径**（⛔ 不沿用 contrast 措辞），否则下一个人会拿 axe 去测、测不出东西，
然后把它判成「已修复」——那就是这两条项**第三次**被挖出来的方式。

---

> **§11 = 同日第三场（第三十三轮）追加。** 它是 §8 的第三个实例，但带一条 §8 没有的新判据，
> 且这次被推翻的框架**是我在第三十二轮亲手写进 STATUS 的那一条**。

## 11. **跨边界的缺口，只量自己这一侧会把「时点问题」诊断成「可见性问题」**

**场景**：第三十二轮我在 STATUS §三 13 如实登记了一条「本仓目前无机制可治的结构缺口」——
措辞是「DS 与 lab 的引用向闸都只扫本仓 ⇒ **跨仓引用谁都看不见**」，
并把待决项写成二选一：「DS 侧登记一张外部消费者依赖面表？还是 lab 侧做 DS-HEAD 漂移检测？」

**实测（读 lab 仓的源码活源，不是它的注释、不是我上一轮的转述）**：

| 反向依赖的类 | lab 侧的实际机制 | 结论 |
|---|---|---|
| DS 的 npm script key | 双档回落 + 显式记录读了哪个 key + 两档皆空则 `throw` | **已闭合** |
| DS 的文件路径（冻结物出处） | `C-missing-path` 判据，但按其头注释「**下一次 re-pin** 时才 fail」 | **唯一真待决项** |
| DS 的 hook 注入面 / 语义 | 已登记为「**覆盖边界不是 TODO**」（无字面量可抽），非零退出/空注入均 `throw` | **无 DS 侧动作** |

⇒ 三条全都**看得见，而且全是 fail-closed**。「谁都看不见」是错的。
**真缺口是发现时点**：lab 在下一次跑量具时报错，DS 在改的那一刻没有信号。

而那两个候选项，**一个已经存在、一个被本仓规则禁止**：lab 早有 DS-HEAD 漂移检测
（`subject.json` 里的漂移记录 + re-pin 协议要求 DS 报哪几件事），
而「DS 侧登记一张依赖面表」是手维护的第二份会漂副本 —— 何况 lab 那些 fail-closed 断言
**本身就是可执行的依赖面真源**，再抄一份就是二手源。

**⇒ 可复用的判据（两条，都是 §8 没有的）**：

1. **诊断一个跨系统/跨仓缺口时，「缺什么」这个问题的答案有一半在边界的另一侧** ——
   只量自己这一侧，最容易得出的错误结论恰好是**最悲观**的那个（「无机制可治」），
   因为自己这侧确实没有。⇒ 定性之前先去读对面的活源。
2. **候选项在写进待决项的那一刻就要验存在性**：我给的两个候选，一个 lab 已经做了。
   这与「理由要能独立实证」是两件事 —— 理由可以完全成立，而**选项本身是空的或重复的**。
   （对齐推荐自审第 5 问的同一侧：不是「可行吗」，是「做完之后原问题还在不在」——
   这里更早一步：「这件事是不是已经有人做完了」。）

> ⚠️ 与 §1 的区别要说清：§1 是**覆盖率恒为零、修不了**；本条是**覆盖率其实不为零，
> 只是我量错了地方**。两者的表面症状一样（「闸看不见」），处置完全相反 ——
> §1 该做的是换自变量或如实登记，本条该做的是**去对面重量一次**。

**顺带一条编辑纪律**（本轮真发生）：就地推翻那段头注释时，替换文本把原文里一个
**被判据检查的 `§` 活锚点**一并删掉了 ⇒ 闸的 `retired-id` coverage 从 134 掉到 **133**、
TOTAL 769 → 768。闸**照样绿**（少一个锚点不是失败），发现它靠的是盯 coverage 那一格。
⇒ **改注释也会改判据的分母。** 「零行为改变」要对的不只是红绿，是**逐格 coverage**；
本仓既有那条「拿两次实际输出对差」（§2）在这里的形态是：**diff 的对象包括自印的分母**。

---

> **§12 = 同日第四场（第三十四轮）追加。** §8 的第四个实例，但打包的粒度第一次落在
> **一条闸的内部**；带一条本文件此前没有的判据。

## 12. **「一条闸」不是判性质的单位** —— 分母声明的完备性没有任何东西在守

**场景**：`INFRA-F139` 登记的是「`audit:figma-conformance` 的扫描面结构上恒空 ⇒ 一条 L5
在链闸的绿不可解读」，给 owner 的选项是「(a) 退役 / (b) 改指向」二选一。
证据链看起来无懈可击：收割器判 `empty-denominator`、`checkedUnits: 0`、扫描面那个目录确实空。

**实测（把闸的四条判据逐条求值，而不是读它的总输出）**：

| # | 判据 | 输入面 | 性质 |
|---|---|---|---|
| ① | `scanGeneratedWrappers` | `src/canonical/generated/*.vue` = 0 个 | 分母恒空 |
| ② | `scanPublicExports` | ① 的返回值 | 派生空 |
| ③ | `scanGenerator` | `generate-vue.mjs`，**7830 字节的真文件** | **真在判** |
| ④ | `scanGeneratedIndexAgainstPlan` | 0 字节 index × 同一空目录生成的 plan | 派生空 |

⇒ 「不可解读」是 **3/4**，不是全部。而 (a) 那个选项会**连带删掉 ③** —— 一条守着
「仍在 `pnpm generate` 主链上每次都跑的脚本有没有退回旧形态」的防回归断言。
**原问法里没有「只处理那三条」这个选项**，与 §8 同型：分组错让正确答案不在选项里。

**为什么读总输出会漏掉 ③**：闸自报的 `checkedUnits` 只数了 ① 的单位
（`checkedUnit: 'generated-wrapper-file'`）。收割器照这个声明判 `empty-denominator`
**完全正确** —— 它核的是「声明的分母是不是 0」。

**⇒ 新判据：闸自报的分母是「声明的分母」，不等于它实际判过的东西的全集。**
分母字段是**人手填的一个数**，而「这条闸一共有几个判据、各自的输入面是什么」
**没有任何机制在核**。⇒ 拿一条闸的 `checkedUnits` 下「这条闸空过」的结论之前，
先把它的判据函数逐个列出来数一遍 —— 这一步不能用它自己的输出代替。

> ⚠️ 与 §1 的区别（这次同一份文件里三种「闸看不见」凑齐了，⛔ 别混）：
> §1 是**覆盖率恒为零、修不了**（自变量重合）；§11 是**覆盖率不为零、我量错了地方**（量错边界）；
> 本条是**覆盖率不为零、闸自己报错了**（声明不完备）。
> 处置各不相同：§1 换自变量或如实登记 · §11 去对面重量 · 本条**数判据，别读总输出**。

**⛔ 本轮刻意没做的事**：诊断出「分母声明的完备性无人守」之后，**没有顺手去给收割器加判据**。
那是从「核清一个待决项」滑进「给闸加能力」，命中本仓的 root-cause scope-creep 纪律 ——
如实登记为缺口，起不起由 owner 拍。

---

> **§13 = 第五场（第三十五轮）追加**，08-25 夜起、08-26 收尾（本文件其余各节都在 08-25 当日）。
> 「闸看不见」的**第四种**形态，本文件因此凑齐四种；
> 另带一条独立的新 pattern（§13.2），它是关于**类组归属**的，不是关于覆盖面的。

## 13.1 **为量具而做的规范动作，可以正好是量具看不见的形态**

**场景**：[[INFRA-F138]] 的 entry 逐字写着「⛔ **别逐条零散补**。抽共享 helper 是这条待办的**第一步，
不是可选项**」——理由充分：不抽，第 2 条到第 N 条每条都要重付一遍建 harness 的成本。

照做了：抽出 `tests/lib/gate-fixture-root.ts`，用它给两条 L4+L5 阻塞闸补上整脚本回归面，
故障注入实证**摘掉接线后全量 2147 / 2169 个测试里只有那 7 / 17 条转红** —— 覆盖是真的补上了。

**然后重跑量具：零覆盖数一条没降。** 那两条闸只从 `NONE` 挪到了 `M`（**同一个类组内部移动**）。

**根因**：分类是**逐测试文件**的词法扫描 —— E 类要求 `spawnSync/execFileSync` 与闸名出现在
**同一份**测试文件里。而「抽共享 helper」这个动作**恰恰就是把 spawn 和 `copyFileSync` 搬出测试文件**。
⇒ **entry 要求的第一步与量具的判据直接冲突，而两边都是对的。**

**⇒ 可复用的判据**：当一个量具驱动出一套「应该这样做」的规范，**先问这套规范做完之后，
量具还认不认得出它**。规范化 = 把重复的东西抽走 = 抽走的往往正是量具据以识别的那个特征。
这不是「判据写窄了」（§1 那类扩面无用），也不是「我量错了地方」（§11），
而是**被测对象的形态因为量具本身的建议而改变** —— 量具在测量它自己造成的变化时失明。

**怎么处置**：给判据补一条**窄形态**，只对「确实走了那套规范」的对象生效
（本轮 = 仅当测试文件 `from '…gate-fixture-root'` 时 `runGate(` 才与 exec API 等价）。
⛔ **别放宽通用判据**（把 `runGate` 直接加进 `EXEC_API_RE` 会让任何自写同名函数的测试搭便车）。
并且**这条新判据自己要有回归钉** —— 否则就是在治 F138 的同时复制 F138 那个病。
本轮配了 3 条（源码级判据在场 + ⛔ 通用正则没被污染 + 阴性对照旧路径不变），
注入验证：把判据改成认不出 ⇒ 两条闸退回 `M`、读数回落，**3 条钉全部转红**。

> ⚠️ 与 §5「新写的头注释能把挂载自声明挤出判定窗口」同族但更深一层：那条是**编辑动作**
> 撞了判定窗口，改个位置就好；这条是**规范本身**与判据互斥，必须改判据。

## 13.2 **一个类组标签可以在语义上诚实、在归属上误导** —— `IL` 是个一行 `import` 的逃逸口

量具的 `IL` 定义逐字是「测试只 import 它抽出去的 lib → **只有被抽走那部分**」——
措辞完全诚实。问题出在它被算进「**仅判据逻辑**」这一档，而不是「零判据覆盖」。

**实测**：本轮起手发现零覆盖数从 08-24 的 34 掉到 24，看着像有人补了覆盖。
逐条查那 10 条的 `hits.IL` ⇒ **全部来自同一对测试**，而它们 import 的 lib 是
`gate-output-contract.mjs` 的 `buildGateOutput`/`emitGateOutput` —— **stdout 输出格式化，
与判据毫无关系**。那 10 条闸的判据逻辑**一行测试都没多**，它们只是接了个共享输出契约
（`4a68e02d`，一次正当重构，**当时无人有意逃逸**）。

⇒ **真实的零覆盖数从 08-24 到现在一条没变**（34，blocking 30）。变的只是标签。

**⇒ 可复用的判据（两条）**：

1. **拿一个数当棘轮的 BASELINE 之前，先问「有没有一个便宜动作能把对象移出这个类组、而不改变它的实质」。**
   这与本仓既有的「闸判据比逃逸方向」是同一条纪律在**分类器**上的形态：那条问「被规避时内容流向哪」，
   这条问「被规避时**对象**流向哪」。本轮的答案是 —— 一行 `import` 一个有测试的共享 lib，成本近乎零。
2. **「间接覆盖」类标签必须按被抽走的东西**是不是判据**再分档**，光有标签不够。
   本轮全量分档 17 条 `IL` = **10 纯稀释 · 2 弱 · 5 名副其实**，三档的处置完全不同 ——
   这正是 §8 那条「逐条判性质，别按类看」在同一个类组**内部**的又一次兑现。

**⛔ 本轮刻意没做的事（第二次守同一条纪律）**：诊断出这个逃逸口之后，**没有顺手改量具的类组划分** ——
那会改变全部 84 条闸的读数，且「棘轮该盯哪个量」有两个代价不同的候选
（盯零判据覆盖 = 留着这个逃逸口 · 盯接线零覆盖 = 等于要求每条新增闸都写整脚本 harness）。
⇒ 如实登记进 entry，**棘轮不上**。与 [[INFRA-F137]] 量完 89% 假阳后决定不上闸同型：
**「不上闸」是有效产出，别为了凑一个闸而上** —— 哪怕这次假阳率是 0%。
