# 三次「前提悄悄不成立」——验证动作本身翻车的三种方向（2026-08-17）

> 一个 session（`INFRA-F116` 落地线第三轮）里连撞三次：**命令跑了、输出读了、结论是反的**。
> 三次都不是"数字算错"，而是**我以为成立的那个前提，在那个环境里根本不成立**。
> 触发登记：WRAP-UP §Retrospection 触发判断「学到通用工程经验 / 发现新 pattern」。
> 现场证据与逐条数字在 [STATUS-CHANGELOG](../STATUS-CHANGELOG.md) 2026-08-17 落地线**第三轮 + 第四轮**两条。
> ⛔ **`INFRA-F116` 这个号已于 2026-08-17 第四轮整条删档，别去 backlog 找**：接线线闭合后它留着的
> 只有「差集现算 + 接线」那条规程，而那条规程已升成闸 `pnpm audit:gate-ci-parity` ——
> **算法真源现在是 `scripts/audit-gate-ci-parity.mjs` 的头注释**，且闸每次运行会自印一遍。
>
> 与 [`2026-08-12-three-ways-a-measurement-lies.md`](./2026-08-12-three-ways-a-measurement-lies.md) 是姊妹篇：
> 那篇讲**测量口径**骗人（数字真、结论错），本篇讲**验证前提**骗人（命令真、结论错）。

---

## 三次

| # | 我做的验证 | 观察到的 | 前提错在哪 | 真结果 |
|---|---|---|---|---|
| 1 | 给 workflow 新 step 的 `set -e` 跑截停探针 | 「没截停、六条全跑、EXIT=0」 | 取证 shell 是 **zsh**，而 CI 的 `run: \|` 是 **bash** | `bash -c` 重跑：**EXIT=1，后五条一条没跑** |
| 2 | 给新 `--all` 模式的 fail-closed 写单测，断 `exit 1` | 故障注入「git 失败静默 `return []`」**照样全绿** | 这个程序有**两条** fail-closed 路径，`exit 1` 是它们**共用**的信号 | 判据换成路径独有的 stderr 后，同一注入即红 |
| 3 | 单测里把 cwd 设成非 git 目录，验「git 不可用 ⇒ 抛错」 | `pnpm test` 绿，`git commit` **红** | git 钩子往环境里注 `GIT_DIR` / `GIT_INDEX_FILE`，子进程继承后 git 在**任何 cwd 下都能成功** | 剥掉全部 `GIT_*` 后，两种环境下都绿；故障注入仍能翻红 |

三种方向：**① 取证器不是目标器**（shell 方言）· **② 判据分辨率不够**（一个信号对应多条路径）· **③ 运行环境被上游注入改写**（hook 环境变量）。

①③ 同源，②独立。

---

## 可迁移的三条

### A. 「我在这跑通了」要先问「我这个『这』和目标那个『那』差在哪」

第 1、3 次都是这一条。差异点是可枚举的，值得当清单过：

- **shell 方言** —— CI 的 `run: |` 是 bash（`set -e` 语义、分词行为都不同）。zsh 还有个老坑：`for g in $VAR` 不分词，把整串当一个 token（本轮又踩一次，量出「三批闸全红」的假警报）。
- **上游注入的环境变量** —— git hook 注 `GIT_*`；CI 注 `CI=true`/`GITHUB_*`；husky 注它自己的。凡子进程调外部命令，先问这组变量会不会改掉前提。
- **文件系统形态** —— worktree 里没有 `dist/`、`node_modules` 是软链、`.env` 可能缺（本轮为做 CI 真实性核，**刻意拆掉 `.env` 与 `dist-wc` 软链重跑**，才敢说「零凭据依赖」）。
- **Node 版本 / 可用二进制**。

⇒ 判据是**在目标环境的形态下重跑**，不是重读脚本。本轮的做法：workflow `run` 体一律 `bash -c '…'` 原样跑；涉及 hook 的单测在**注了 `GIT_DIR` 的模拟环境**下再跑一遍。

### B. 断 exit code 之前，先数「这个 exit 1 有几种走法」

第 2 次。程序里但凡有 ≥2 条 fail-closed 路径，`exit 1` 就不再是判据——它是所有路径的公共出口。故障注入把 A 路径改坏，程序落进 B 路径，观测结果一模一样，测试全绿。

⇒ 找每条路径**独有**的信号：特定 stderr 措辞、写没写某个产物、某个计数被打印出来。本轮的判据变成「stderr 必须含 git 自己的 `fatal:`，**且不得含**「命中 0 份」那句」——一正一负两个断言把两条路径钉开。

这与 [`2026-08-05-fault-injection-harness-can-fake-success.md`](./2026-08-05-fault-injection-harness-can-fake-success.md) 是镜像：那篇讲失败信号会被**造故障失败**污染，本条讲失败信号会被**另一条正确的失败路径**污染。

### C. 接闸上 CI，改的不只是 workflow —— 被接脚本的自声明当场就 stale

不是验证坑，是本轮成本第二高的一件，一并记。

本轮接的 9 条闸里，**3 条的头注释逐字写着「⛔ 不在 L5」/「加进 prepublishOnly 纯噪音」**——接线那一刻全部变成假声明。而这个仓库刚花了一整条 entry（原 INFRA-F126）治「闸的自我陈述与实际不符」。

⇒ **把「改被接脚本的头注释」当接线动作的一部分，不是收尾。** 沉默态的那几条补 D1 自声明后，`audit:gate-mount-declaration` 的 shrink-only 判据当场报 FAIL 点名要求删豁免行（表 51 → 46）——**机制按设计工作，红是对的**，别把它读成新缺陷。

---

## 与既有规则的关系

| 已有的 | 本轮补的 |
|---|---|
| memory `harness-config-can-fabricate-defects`（响应头 / 编码造假缺陷） | 扩到 **shell 方言 + hook 注入的环境变量**——同族第二个面 |
| memory `regression-pass-needs-fault-proof`（断言 PASS 可能是空过） | 补 **B**：多条 fail-closed 路径共用 exit code 时，只断 exit code 等于没断 |
| memory `verification-criterion-must-be-terminal-state`（判据要取终态事实） | 本轮三次都是它的实例：`bash` 才是终态 shell、路径独有信号才是终态事实、hook 环境才是终态环境 |
| 原 INFRA-F126「闸的自我陈述与实际不符」 | 补一条**新触发路径**：不是年久失修漂的，是**接线动作自己当场制造的** |

⛔ **本文不重复 F116 的数字**（差集 / 条数 / 分母都是现算量，见 entry 与各闸自印）。
