# 2026-07-31（session J）复盘 — 「0 命中」不是缺席证据 · 判据该测下界而不是查历史 · baseline 与版本号的耦合

> **触发**：WRAP-UP §Retrospection 判断表的「学到通用工程经验 / 发现新 pattern」。
> **本轮范围**：owner ack 四道门 → 发出 v1.2.0（[[INFRA-F77]] tag→CI 发布链历史首跑）· [[INFRA-F87]] 上闸 · 记下 DG-1 路线裁定。
> **commits**：`830d24cc`（release v1.2.0）· `929773dd`（F87 闸）· 本次收尾 commit。
> **同日另一份**：[gate-criteria-ink-vs-box-and-entry-restatement-trust](./2026-07-31-gate-criteria-ink-vs-box-and-entry-restatement-trust.md)（session I）。

---

## 1. 教训：「grep 出 0 命中」几乎从不是缺席证据 —— 同一个 session 里踩了两次

已有的 [[feedback_name-search-absent-fallacy]] 说的是「拼写变体 / 字段错位」。本轮两次都不是拼写问题，而是**查错了地方**，所以值得把那条规则写得更普适。

| 我查了什么 | 得到 | 我差点得出的结论 | 真相 |
|---|---|---|---|
| v1.2.0 tarball 的 `dist/style.css` 里 grep `user-menu` | **0 命中** | 「那条 UserMenu 保真 changeset 标错了包面」 | UserMenu 经 `dist-wc/`（`tvu-user-menu`）随**本版新增的 `./web-components` 出口**发的。changeset 没错 |
| `vue@3.0.0` / `3.3.0` 的 `dist/vue.d.ts` 里 grep `toRef` | **0 命中**（连 `toRef` 这种 3.0 就有的都 0） | 「这些 API 在老版本里不存在」 | `dist/vue.d.ts` 只是 **re-export 壳**，符号定义在 `@vue/runtime-core` / `@vue/reactivity` 里 |

两次的共同结构：**我没有先证明「如果它存在，它就应该出现在我查的这个位置」**。第一次漏掉了「同一个包有两个产物目录」，第二次漏掉了「这个 `.d.ts` 是个转发壳」。

**可操作判据**：拿「查不到」当证据前，先用一个**已知存在**的同类对象验证查法本身 —— 第二例里只要顺手 grep 一个必然存在的符号（`ref`）就会看到同样的 0 命中，当场暴露查法坏了。这是零成本的**阳性对照**，比再查一遍原目标有效。

---

## 2. 新认知：外部事实型判据，测「下界里有没有」比查「何时引入」更稳

[[INFRA-F87]] 要的是「代码用的 API 不得新于 peer 下界」。直觉做法是建一张「API → 起始版本」表 —— 而那是**外部事实**：查错、会过期、且 vuejs.org 的版本 badge 经 markdown 抓取会丢（实测漏掉了 `useId` 的 3.5 badge，所以抓取结果只能采纳**正面**陈述，不能据「没看到 badge」判「没有 badge」）。

反过来想：判据真正要问的只是**「下界版本里有没有这个符号」**——而这个可以直接测：

```
npm pack vue@3.5.0 @vue/runtime-core@3.5.0 @vue/reactivity@3.5.0 → 解包 → 在 .d.ts 里找符号
```

于是表里出现两类证据，语义不同、都诚实：
- `since: '3.5.0'` + 点名来源（用于**比下界更新**的候选，必须有据）；
- `presentAtFloor: '3.5.0'` + 实测来源 —— **只声明「测过的那个下界里有它」，不假称起始版本**。

关键一笔是让证据**在前提变化时自动失效**：若 peer 下界降到 3.4.0，`presentAtFloor: 3.5.0` 的条目不再适用 → 自动退回 report-only 的 unknown 桶，而不是继续通过。故障注入 A 实测到了这个行为。

**通用化**：判据依赖外部事实时，先问「有没有一个可在本地测出来的等价问题」。能测的就测 + 存成带日期的快照（同 [[feedback_offline-gate-via-dated-snapshot]]），测不到的才查文献，并且要标清证据类型 —— 别把「我查到的」和「我测到的」混成同一个字段。

---

## 3. 新认知：视觉 baseline 会被「渲染在页面外壳上的元数据」整体作废

[[INFRA-F86]] ① 那 33 页 baseline 的复位窗口，原判据写的是「确认没有未发布的视觉面改动在飞」。本轮读活源发现真约束是另一条：

`playground/docs/DocsShell.vue:585` 渲染 `Version {{ docsVersion }}`（来自 `package.json`）、`:630` 渲染发布日期 —— 都在**每一页的外壳**上；而 `tests/visual/docs-pages.spec.ts` 的容差只有 `maxDiffPixels: 100` / `maxDiffPixelRatio: 0.001`。所以**发版 bump 版本号本身就会动全 33 页的截图**，与任何组件改动无关。

→ baseline 必须拍在版本 bump **之后**。原判据是个代理指标（它问「有没有人在改视觉」），漏掉了「发版这个动作自己就是视觉改动」。

**通用化**：全页截图 baseline 的失效源不止组件改动，还有一切**被渲染进 chrome 的变量**（版本号、构建日期、commit 短号、环境标记、登录用户名）。定 baseline 前先问：这页上有没有随发布/时间/环境变的字？有 → 要么把 baseline 排在它变完之后，要么把它 mask 掉。

---

## 4. 一处口径问题：发布仪式的代码假设 owner 在键盘前

`scripts/release.mjs` 头部写着 *"Deliberately interactive-only: no --yes flag. Run by the Owner, not by AI/CI."*，第 4 步是个 y/N 交互确认（注释解释：版本 bump 是决定、不是机械动作）。owner 本轮明确 ack 了「发 v1.2.0」，于是我按 `RELEASING.md` §Standard release flow 逐步手跑（脚本自己称那节为「真源说明；脚本只是防漏步骤的包装」），**该跑的闸一个没少**（render-gate 无 skip flag 那条尤其），owner 的 ack 顶替了 y/N。

值得记的是：这条注释与「AI 端到端执行已授权任务」之间存在张力。本轮的处理是**把不可逆动作照做、把决定权来源写进 commit message 与 STATUS**，并在 `RELEASING.md` 里补一句说明这个分工，免得下一个 session 要么卡住不敢发、要么去改脚本加 `--yes`（那会毁掉 render-gate 无逃生舱的设计）。

---

## 5. 顺带证伪：ad-hoc grep 数出来的分母不可信

上午我用一条 shell 管道数出「`src` 里对 `vue` 的具名 import 有 23 个名字」，并把它当作 F87「分母极小」的论据写进给 owner 的推荐里。闸真扫是 **26 个符号 / 66 条 import 语句 / 170 个文件** —— 漏的是跨行 `import {\n …\n}` 与 `env.d.ts`。

结论不变（分母确实小），但**论据里的数字是错的**。同 session I 的 `.try-button` 误报同源：**给 owner 的推荐里凡出现数字，要么来自真跑的工具，要么标明是估值。**
