# POSTMORTEMS.md

> 事故記錄。**永久保留，只寫可證偽的具體事故。**
>
> 🚨 **第一句不可以是「我下次會小心」** —— 那句話不可證偽，所以它擋不住第二次。
> 每一篇都要回答三個問題：**症狀是什麼／結構原因是什麼／現在誰接住它。**
>
> 第三個問題最重要。如果答案是「我會記得」，這篇事故就還沒寫完。

---

## 每篇的格式

```markdown
## §N <一行標題：症狀，不是原因>

**日期**：YYYY-MM-DD
**症狀**：<我／使用者看到的是什麼。注意：通常是「畫面全對」>
**表面原因**：<第一眼以為的>
**結構原因**：<真正的。這一段是整篇的價值所在>
**現在誰接住它**：<hook / 守衛 / 規則正文 / 誠實承認「沒有人」>
**代價**：<花了多少時間、影響多久>
```

---

## 範例一（機器接住了）

## §1 上雲當天一天四個 bug，全部是「本機綠、正式環境紅」

**日期**：2026-08-29
**症狀**：本機全套測試綠燈，換到雲端資料庫，一天之內四個從來沒出現過的錯誤。

**表面原因**：SQLite 跟 PostgreSQL 有差異。

**結構原因**：**本機的綠燈在回答一個不同的問題。**
本機綠只證明「邏輯與回歸沒壞」，它**從來沒有**證明過「schema、型別、外鍵在正式環境成立」——
但兩者在終端機上印出來的東西一模一樣，所以我一直把後者當成已經驗過了。

其中一個 bug 更尖銳：它躲過了本機那次驗證，**因為本機庫有 225 個使用者**，
而那個 bug 只在**空庫**發生 —— 也就是**上線那天的形狀**。

**現在誰接住它**：
- `tools/pg_check.py`（守衛）：同一份 model 在兩個 dialect 翻成的 DDL 比對。
- `tools/deploy_gate.py`（L4）：部署前必須對真的 PostgreSQL 跑一輪（約 35 分鐘）。
- `CLAUDE.md` 規則：**驗證要在兩種資料狀態下各跑一次：空庫與有資料。**
  ⇒ 推論：**「再跑一次看有沒有過」不是驗證。** 要問「我剛才是在哪一種狀態下驗的」。

**代價**：一整天。

---

## 範例二（機器接不住，誠實記著）

## §2 一支新守衛用五種方式報假綠燈

**日期**：2026-08-30
**症狀**：守衛印 PASS，但它什麼都沒驗到。**五次，五種不同的方式。**

1. 前置條件不滿足時 `return` ⇒ 整個方向跳過，照樣 exit 0
2. 走過 0 個檔案也算 PASS
3. 靜態掃描回答不了「輸出的那一刻有沒有做對」
4. 等外部回應的迴圈卡住 ⇒ 看起來跟「還在跑」一模一樣
5. 指令接了 `| tail` ⇒ exit code 變成 tail 的

**表面原因**：守衛寫得不夠仔細。

**結構原因**：**綠燈沒有內容物。**
「PASS」這個字本身不帶任何資訊 —— 它可能表示「驗了 148 個，全過」，
也可能表示「一個 `return` 就跳出去了」。而這兩者在終端機上長得一模一樣。

**現在誰接住它**：
- `CLAUDE.md`〈綠燈的可信度〉十條判準（**這一節是整份規則檔價值密度最高的地方**）
- 每支守衛的 PASS 必須附覆蓋率數字
- 每支守衛配一支 `*_drill.py` 證明它會紅
- 第 5 種由 hook 接住（`pipe-to-head-tail`）

**代價**：三次假綠燈之後才發現，中間做的驗證全部要重做。

---

## 範例三（規則本身失效 —— 最值得寫的一類）

## §3「一列一行」這條規則被違反三次

**日期**：2026-08-22 / 08-31 / 09-02
**症狀**：排程表長出續行，於是「三秒看完剩餘工時」這個唯一用途失效。

**表面原因**：忘了規則。

**結構原因**：**自動載入 ≠ 自動遵守。**
這條規則白紙黑字寫在規則檔裡，三次都不是不知道它。
真正的原因是：**一條規則躺在 700 行裡，注意力份額趨近於零。**

而且我前兩次的反應都是錯的 —— 我把規則寫得更醒目（加粗、加 🚨、搬到最前面）。
**三次都沒用**，因為問題不在「寫得不夠清楚」。

**現在誰接住它**：`tools/spec_check.py` 檢查 ⑦（數每個工作包佔幾列）。
加了守衛之後**再也沒發生過**。

**代價**：三次重讀整張表。真正的代價是前兩次修錯了地方。

**⇒ 由此得出的判準（已寫進規則檔）**：
> **同一條規則被違反第二次，就不要再改規則的寫法了，去問「機器怎麼接住它」。**

---

## 索引

| § | 標題 | 現在誰接住它 |
|---|---|---|
| §1 | 上雲當天一天四個 bug | `pg_check.py` ＋ `deploy_gate.py` ＋ 規則 |
| §2 | 守衛用五種方式報假綠燈 | 〈綠燈的可信度〉十條 ＋ 演練層 |
| §3 | 「一列一行」被違反三次 | `spec_check.py` 檢查 ⑦ |
| §N | `<...>` | **沒有人**（誠實記著，見下） |

---

## ⚠️ 關於「沒有人接住它」

有些事故確實沒有機器接得住 —— 例如「需求本身就錯」。

**這種要照實寫「沒有人」**，不要為了讓表格好看而硬塞一個接不住的守衛進去。
一個假的答案會讓你以為那一格已經安全了，而那正是下一次事故的形狀。
