# 只报第一条的闸 —— 以及三种「取证装置自己造出的缺陷」

> 2026-08-20 · 落地线第十至十三轮的复盘。触发条件 = WRAP-UP §Retrospection「学到通用工程经验 / 发现新 pattern」。
> ⛔ 本文件不复述状态与数字（那些在 `STATUS-CHANGELOG` 当日三条与各 entry / 各闸头注释里，闸每次运行自印）。
> 本文件只留**可迁移的判断规则**。

---

## 1. 「1 failed」不等于「只有 1 处坏」—— 断言型闸的短路语义

**事实**：`pnpm test:visual` 在 master 上报 `34 passed / 1 failed`，失败项是 `table` 页的
`table-figma-members-light.png`（1092 px）。这条输出被上一轮如实记进 entry、送到 owner 面前审批，
owner 批「重拍那一张」。重拍后闸**又红**，点名同一页的第二张（1160 px）。逐张放行后量出真实面
是 **7 张**（同页全部 light 段），dark 零变化。

**根因不是新缺陷，是判据的读法**：`expect(...).toHaveScreenshot()` 是**抛异常**的断言，
一失败就中断该 test 后续所有 segment。于是「1 failed」的准确语义是
**「第一个失败的是它」**，而不是「只有它失败」。同类形态遍地都是：`set -e` 的 shell 闸、
`assert` 链、任何 fail-fast 的循环体。

**规则（可迁移）**：
- 拿闸的失败**条数**去估工作量 / 定审批范围之前，先问一句「这个闸失败后还会继续量吗」。
- 会短路的闸，**先把它跑到绿再数**，或者用「逐条放行、逐轮取数」的办法穷举
（本轮就是这么量出 7 张的：每轮把已确认的那张换成新值再跑，下一轮报出的就是下一张）。
- ⛔ 别把短路闸的输出当**完整清单**送审 —— 那等于让 owner 在不完整的事实上签核，
而签核一旦给出，AI 又不能自行扩面，于是必须回头再问一次。**这一轮就付了这个来回。**

## 2. 收窄范围不是保守，它是发现问题的手段

`--update-snapshots -g <page>` 的 scope 是**整页**：第一次重拍它一次重写了 7 张 PNG。
当时的处置是「owner 只批了 1 张 ⇒ 还原其余 6 张」。**正是这个收窄让闸把第 2 张暴露出来** ——
若当时图省事把 7 张一起提交，本轮会以「全绿、任务完成」收工，而 owner 只看过 1 张。

**规则**：把「批准面」和「工具的最小操作粒度」分开看。工具按页动，签核按图给 ⇒
**多出来的那部分必须显式还原**，不是「反正都是同源改动」。同源与否是**结论**，不是前提；
本轮它恰好为真（7 张全是同一处 token），但那是量完才知道的。

## 3. 三种「取证装置自己造出的缺陷」，本轮各撞一次

| 形态 | 表象 | 真相 | 怎么识别 |
|---|---|---|---|
| playwright `webServer.cwd` 默认取**配置文件所在目录** | 两个 scratch config 首跑都 `Timed out waiting 120000ms from config.webServer`，看起来像闸失败 | config 放在 `scratch/` 下 ⇒ vite 服务的是 `scratch/`（无 index.html）⇒ readiness 永不满足 | 手工起一次服务：实测 **565 ms ready / HTTP 200** + `lsof -a -p <pid> -d cwd -Fn` 核服务方 ⇒ 与「120s 起不来」直接矛盾 |
| 闸的分母依赖某个 worktree 没有的东西 | `audit:typecheck-scope` 在旧 worktree 报 2 条 `TS2307` | `react-pilot/node_modules` 缺失（那些 devDep 只在 react-pilot 自己的 lockfile 里） | 拆掉依赖跑一遍 = CI 仿真；**假红与真红的区别是错误类型**（模块找不到 vs 类型不符） |
| 用错的工具量作用面 | `tsc` 报 112 个文件在根 typecheck 面内 | `tsc` 认不了 `.vue` ⇒ 只经 `.vue` 可达的 81 个文件被漏算；`vue-tsc` 报 193 | 换成项目真正在用的那个 runner 再量；差值本身就是答案 |

**共同规则**：**在把「闸红了」当结论之前，先证明装置是好的。** 判据 =
拿一条**与失败信息直接矛盾**的独立观测（手工 curl / lsof / 换 runner 重量），
而不是重跑一次同一条命令。⛔ 尤其别把装置故障读成「修法无效」—— 那是方向相反的错论。

## 4. 一个正面样本：shrink-only 豁免表两天内自己完成了一次闭环

2026-08-18 给 `react-pilot/tsconfig.json` 的存量错误上具名带日期豁免时写了一句
「表空着是终态、修完由闸自己宣布」。08-20 owner 签核、5 处失效 `@ts-expect-error` 删掉之后，
`audit:typecheck-scope` **自己报 `[S6-stale-error-exemption] …（addedAt 2026-08-18）已不再命中 —— 请删掉该行`**，
两行照它点的名删掉。

**规则**：新建豁免表时，**把「不再命中就判红」写成判据**，别写成注释里的自律条款。
区别在于：前者会在正确的时刻主动找上门，后者要求未来某个人恰好记得回来清。
（对照本轮另一处：`--update-snapshots` 的整页语义**没有**任何机制提醒，所以它只能靠这份复盘。）

## 5. 顺带记一条排序纪律的实证

本轮三件签核项的推荐顺序是「先批那件零渲染改动的（纯注释删除）」，理由不是它省事，
而是它**解锁一层更勤的保护**（harness 进根 `tsconfig` 后由 pre-commit 的**无条件** `vue-tsc` 守，
不再只由条件闸守）。⇒ 排序自变量仍是可靠性，不是省事程度；只是这次两者恰好同向。
⚠️ 而它的**顺序**是硬的：先删注释、再扩 include ——反过来会让那条无条件 typecheck 当场变红，
把红潜伏到 master（第九轮刚为「只在切 tag 时跑的闸红了会潜伏 6 天」付过代价）。
