# 为什么会发生这么多意外？—— 组合、接缝、与日落法

> 类型：反思型 / 立场型案例文档
> 日期：2026-06-05（V37.9.117）
> 触发：用户在一天五版（V37.9.112-116）意外频发后问 —— "请深刻反思为什么会发生这么多意外情况？仅仅因为系统变的复杂？还是其他的原因？"
> 结论：确立**日落法（Sunset Law, MR-22）** 为项目北极星 —— 未来发展以降低系统复杂度为目标。

---

## TL;DR

意外频发的真因**不是"系统复杂"（部件难懂），是"系统组合"（简单正确的部件，其交互面积超线性增长，超过测试覆盖能力）**。

- **复杂关乎部件，意外关乎接缝。**
- 治理验证"代码符合规则"，验证不了"规则符合现实 / 部署符合假设"。87 不变式全在仓库内层验证仓库自己；事故住在**仓库与真实 Mac Mini 之间的接缝**里 —— 而接缝恰好没有测试覆盖（测试在 dev 跑）。
- **每加一层防御，就多一个事故面**（原则 #8 在规模上的演出）。convergence 框架为关闭"声明态-运行时态"缺口而建，却用 machine_sync 改生产成了新事故类 —— 药参与病。
- 答案往往**不是"再加一层治理"，而是"缩小接缝"**：减少一物多形、缩小 dev-production 差距、让观察者只读。

---

## 一、不是"复杂"，是"组合"

今天没有一个出问题的部件是难懂的：

| 部件 | 规模 |
|------|------|
| symlink（`~/ontology` → repo） | 5 行配置 |
| `abspath(__file__)` | 一个函数调用 |
| auto_deploy registry `entry: auto_deploy.sh` | 一行裸名 |
| V37.9.58 切关 dry-run 默认 | 一个布尔反转 |

每一个单独看都**简单、正确**。意外发生在它们的**组合**里：

```
symlink + abspath + 用 ~/ontology 路径调用        = governance 30min 挂死   (三者缺一不爆)
machine_sync + 裸名 entry + 格式错配 + 审计不 dry-run = auto_deploy 三次复发   (四者缺一不爆)
```

真正的规律：**系统每加一个正确的部件，可能的「交互组合」数量是超线性增长的，而测试只能覆盖我们「想得到」的组合。** 4063 个测试不是不够多，而是它们覆盖的是"部件的正确性"，覆盖不了"未曾设想的组合"。

> 这就是项目反复命名的「六条件组合爆炸」。意外不是从任何单个新东西来的 —— 是从**旧东西的新组合**来的。

---

## 二、五层根因

### ① 一物多形（Representation Drift）

同一逻辑事物有多个物理表示，这些表示会漂移，**bug 住在表示之间的缝里**：

- `auto_deploy.sh`：repo 里一份 + FILE_MAP 部署到 `$HOME` 一份 = 两个文件；crontab 里 `~/openclaw-model-bridge/auto_deploy.sh`（repo 路径）vs `_format_cron_line` 生成的 `~/auto_deploy.sh`（HOME 裸名）= 两条路径。
- `~/ontology` symlink vs repo 路径 = 同一份代码两条到达路径，`abspath` 在两条路径下解析出不同 repo_root。

### ② 地图 ≠ 领土（Map-Territory Gap）

测试是系统的**地图**，事故全发生在地图没画的**领土**：

- 4063 测试全在 dev 跑（地图）；事故全在 production 接缝：真 crontab、真 symlink、真 macOS BSD 工具（timeout/stat/awk）、真网络（Bluesky 403）、真时间（07:00 cron）。
- **dev mock 判 `missing=空`，Mac Mini 真 apply** —— 我在 dev 复现不出来 auto_deploy 重加。
- 测试恰好省略了引发生产事故的那些东西。**全绿是地图，事故在领土。**

### ③ 观察者改被观察者（Observer-Observed Coupling）

当观察者没和被观察者隔离，**观察就变成了修改**：

- 07:00 governance **审计** 在 real-apply **改** crontab（MR-9）。
- 我的**诊断命令**（governance via symlink）本身是 30min **挂死源** —— 观察的行为制造了问题，而且我一度把它误判为 07:00 cron 问题。
- 历史：test_movespeed 写真 incidents、test_convergence 真改 crontab。

这不是复杂度，是一个**范畴错误** —— 把"审计"当成了"自愈"。审计本该只读。（→ MR-23）

### ④ 不完整修复伪装成完整（Incomplete-Fix Trap）

一个让症状消失、又通过测试的修复，会被误当成根治：

- V37.9.114 修了一个 glob check，**通过全部测试和治理**，但根在 `_resolve_project_root`（V37.9.115 才修到）。
- V37.9.106/111「删了 line 54」三次，从没找根 —— "删了拉倒"。

**测试是按"修复者的理解"写的 —— 而修复者的理解恰好是不完整的那个东西。** 反向验证（sabotage）守得住"某一条断言"，守不住"理解的完整性"。

### ⑤ 潜伏条件累积（Latent-Condition Accumulation）

局部正确的旧决策，组合成新事故：

- symlink（V37.9.12.1，几周前）+ 切关 dry-run 默认（V37.9.58，几周前）—— 都是当时充分测试的、局部正确的决策。
- 它们各自躺成**潜伏条件**，直到第二个条件组合上来才爆。每个决策局部对，全局**加组合面积**。

> 所以"今天意外多"，部分是因为今天 V37.9.115/116 在认真核查 —— **认真验证这个动作本身会把潜伏债翻出来**。没人看的那天"没意外"，债还在躺着。

---

## 三、元层：为什么有了这么多治理还防不住？

87 不变式、23 元规则、4063 测试，一座庞大工事。但：

> **治理系统验证的是「代码符不符合规则」，验证不了「规则符不符合现实」「部署符不符合假设」。它在仓库内层验证仓库自己。事故住在仓库和真实 Mac Mini 之间的「接缝」里 —— 而接缝恰好没有测试覆盖。**

更深一层 —— **每加一层防御，就多一个事故面**（原则 #8「每加一层保险 = 多一个故障源」在规模上的演出）：

```
convergence 框架  ← 为关闭"声明态-运行时态"缺口而建（兑现 MR-17）
       │
       └─ 用 machine_sync 改生产来关闭缺口
              │
              └─ 自己变成新事故类（07:00 审计 real-apply 改 crontab）
                     │
                     └─ 药参与了病
```

项目其实**早就识别过这个模式**（V37.9.18 的 MR-17 候选写明"声明态→运行时态同步靠人记忆就是结构盲区"），并造了 convergence 框架去治。**识别了、造了框架，还是被同根咬。** 这说明：**靠"把意外转成规则"无法穷尽组合面 —— 因为规则本身也是新部件、新接缝。**

---

## 四、平衡视角：很多"意外"是免疫系统在开火

不能只往坏处看。今天：

- 12:30 watchdog **抓到** auto_deploy 重复并告警
- INV-CRON-004 **标出** 重复
- 反向验证 **抓到** 我那个不完整的守卫
- 原子实验 **证明** 了根因（删除后 0 → gov 后 1）
- dev 测试 **拦下** exec 作用域 NameError（推送前）

**很多"意外"其实是免疫系统在把潜伏问题暴露出来。** 一个"零意外"的系统，可能只是免疫系统瞎了（对比潜伏 60 天的 MOVESPEED 静默失败 / 复发三次没找根的 auto_deploy）。真正危险的从来不是"意外多"，是"意外被静默吞掉"（MR-4），或"反复删症状不找根"。今天恰恰是把后者纠正了。

---

## 五、我的责任（诚实记录）

- 今天两次 30min 挂死，**直接触发是我用错调用路径**（`~/ontology` symlink 而非 cron 同款的仓库相对路径）。
- 我一度把 symlink 挂死**归因成 07:00 cron 问题**，差点去改 cron。原则 #28（理解再动手）就是为对抗这个而立的，而我部分违反了。
- **操作者（我）也是事故源之一。** 用户的回应是"我们一起成长" —— 这份诚实记录是兑现。

---

## 六、前进方向：日落法（Sunset Law）

确立为项目北极星。**降低复杂度优先于增加功能。**（→ MR-22 + 原则 #34）

不是"再加一层治理"（那违背原则 #8 + 本反思第三节），而是**缩小接缝**：

| 方向 | 具体 |
|------|------|
| **减少一物多形** | 同一逻辑事物只留一个物理表示（一条路径，而非 repo + HOME 两条） |
| **缩小 dev-production 接缝** | 让一部分测试真在 Mac Mini 环境跑（地图覆盖领土的关键缝） |
| **让观察者只读** | 审计只检测不改变；自愈是 operator 显式动作（V37.9.116 刚做的 → MR-23） |
| **加之前先退役** | 每加一层（脚本/不变式/MR/框架/表示/路径）前先问"能否退役一个等价的旧的" |

> 本次反思**践行了日落法本身**：立 MR-22/MR-23 **刻意不铸新不变式**，复用既有 `test_v37_9_116` + `test_convergence` 隔离守卫执行。元规则数 21→23，不变式数 87 不变。

---

## 关联

- **元规则**：MR-22（sunset-over-accretion 日落法）、MR-23（audit-observes-never-mutates 审计只读，泛化 MR-9）
- **原则**：#8（做减法不做加法，MR-22 的战术前身）、#28（理解再动手）、#34（日落法北极星，本反思立）
- **lineage 案例**：V37.9.116（auto_deploy 双行根治）、V37.9.115（abspath→realpath）、V37.9.113（test_convergence 隔离）、V37.9.110（test_movespeed 测试污染）、MR-15（deployment-layout）、MR-17（declared→runtime convergence）

**一句话**：这个系统大到一定程度后，「零意外」不是目标也不可能；目标是「快速、数据驱动的根因诊断 + 持续退役复杂度」。今天每个意外都用证据钉死了根，没有一个是盲改 —— 这是健康的。下一阶段，让系统**变小**。
