# 我软链进来的那个 `dist` 造了一个不存在的缺陷 —— 兼「两个 agent 打架时谁都别信」

> 2026-08-14，backlog 清零第 5–6 批 session。本文只记**可复用的三条**；两批的完整叙述在 [STATUS-CHANGELOG](../STATUS-CHANGELOG.md) 当日两段，方法论判据在 [tracker §待核可关](./design-spec-canonical-alignment-tracker.md)，此处不复述。

---

## 一、`dist` 软链进一个「别人正在重建」的仓库 = 取证环境造假缺陷（第三例，已可定性为 pattern）

### 事实

多 session 并行写同一仓库，按纪律起手开 worktree。worktree 的已知三坑之一是「没有构建产物」，所以我照惯例把三样都软链回主仓：

```
node_modules -> ../../../node_modules
dist-wc      -> ../../../dist-wc
dist         -> ../../../dist
```

随后跑 `audit:composition-exports`，得 **FAIL**：`dist/composition/composition.json 与 docs/internal/component-affordances.json 生成结果不一致`。

而同一条命令，subagent 二十分钟前跑的结果是 **PASS / exit 0**。

### 查明

三个数字就定死了：

| 取样点 | md5 |
|---|---|
| worktree 里那份 `component-affordances.json`（已提交） | `c5ac64f7…` |
| 主仓 `HEAD` 里那份 | `c5ac64f7…` ← **与 worktree 相同** |
| 主仓**工作树**里那份 | `f8ad9666…` ← **不同** |

加上 `dist/composition/composition.json` 的 mtime = **11:28**，而我建软链是 **11:20**。

⇒ 并行线在这八分钟里拿他们**未提交**的 affordances 重建了 `dist/`。于是那个闸做的比较变成了「**另一条 session 的半成品产物** vs **我这边的已提交源码**」—— 一个没有任何意义的比较。**agent 的 PASS 在它跑的时刻是对的，是状态在我们之间变了。**

### 教训（这条要带进下一轮）

**worktree 的三个软链里，只有 `dist` 有这个问题。**

- `node_modules` —— 依赖内容与源码无对比关系，安全。
- `dist-wc` —— 只被「能不能 import `@tvu/wc`」用到，不参与任何源码↔产物一致性判据，安全。
- **`dist` —— 至少三个闸把它当「消费方拿到的那份」去对比源码**：`audit:composition-exports` · `audit:page-recipes` 的 S4 · `audit:exports-types-contract`。软链到一个活的、正在被别人重建的目录，等于把别人的中间态喂进判据。

⇒ **并行态下开 worktree 时，`dist` 要么别软链（让 S4 那类判据走它自己的 SKIPPED 降级路径），要么软链了就别信任何「源码 ↔ dist」型闸的结果。**

### 为什么它算第三例

同型前两例都在仓库里留过档：`2026-08-05-fault-injection-harness-can-fake-success.md`（scratchpad 无 `node_modules` → playwright config 根本没加载，注入无效却读成"闸没红"）· memory `harness-config-can-fabricate-defects`（`python3 -m http.server` 不送 charset → 中文乱码 → 量出不存在的溢出）。

三例的共同形态：**判据本身没问题，是我为了取证临时搭的那层东西改变了被测对象。**

⇒ 已经足够定性：**自己搭的取证环境是被测系统的一部分，异常结果先怀疑它，再怀疑判据。** 这次的具体判据 = **拿到异常结果时，先问「这个输入面此刻由谁在写」**。

---

## 二、两个 agent 给出互斥结论时，别选一个信 —— 去查它们各自指的活源

### 事实

两位只读核查员核同一件事（组合契约随包发是哪次 ship），给出互斥的 commit：

- 核 F63 的说：`64028b27`，且**它没碰 `package.json`**
- 核 F60 的说：`369017fd`，两条通道**同在一次 ship**

我去查了两个 commit 的 `--stat`。**两个都对**：`369017fd` 里有 `figma-sync/generate-composition-exports.mjs` + `audit-composition-exports.mjs` + `package.json`（= npm 出口那条）；`64028b27` 只碰导出脚本 + STATUS + backlog（= bundle 那条），确实没碰 `package.json`。

**互斥是转述层造出来的**：它们说的是同一份 json 的两个分发面，而 backlog 里两条 entry 的状态行**逐字撞车**（都写「组合契约随包发」），才诱发了「这两条重复登记吧」的怀疑 —— 那个怀疑还是**我在 prompt 里写给它们的假设**，两位各自独立把它推翻了。

### 教训

- **两份报告打架 ≠ 其中一份错。** 先假设它们在说不同的东西，去查各自指的活源。
- **同名不同物是 backlog 的常见病**：两条 entry 用同一句话描述两个交付面，几周后没人分得清。修法不是合并 entry，是**把词还原到各自的通道**（「随包发」专指 npm exports，bundle 那条改说「进 bundle」）。

---

## 三、六批下来，agent 自身的 7 处错**全部**在「理由」不在「结论」

这不是巧合，值得写成判据。七处逐条：

| # | 结论 | 它给的理由 |
|---|---|---|
| 1–4 | 对 | 前四批各一处（含一处阴性对照实跑与其期望相反） |
| 5 | 对（操作面确实零转述） | 说「全仓零转述」—— 实测某 plan 文件逐字有 |
| 6 | 对（散文取号纪律会发退役号） | 说「同一 bug 也在 `scripts/new-backlog.mjs`」—— 实测分配器扫三份 SoT，会发 129 不是 127 |
| 7 | 对（两条通道） | 两位给出互斥 commit（见上，实为都对） |

**为什么会这样**：结论要么能被一条命令证否、要么不能；而**理由是"为什么"，天然没有对应的命令**，于是它成了幻觉的低阻力出口。

⇒ **落地纪律（六批实测有效，别省）**：要求每个 subagent 交「一条决定性命令 + 一个阴性对照」，主线**两条都实跑**，并对阴性对照再加一层**致败探针**（同目录、同口径、换成必然命中的串），证明它不是路径打错造成的假阴。这一层本轮救了两次：一次证明「grep 无输出」是真·无第二家而非口径太窄，一次拦住了把 `INFRA-F55` 误判成悬挂指针（它没有 `### ` heading，但 backlog 里有一整段关闭 note）。
