# Curation Pipeline

> AI 輔助資料策展的 staging 閘門紀律——代理只寫 staging、唯讀的 validator 檢查、人核准，然後才有東西進正本。當你要建立或檢討一條「由 LLM 代理供稿到資料庫、知識庫或策展資料集」的流水線、要決定哪個代理可以寫哪裡、或是一份策展資料集長出了重複、無來源的列或安靜的缺口時使用。

- Skill: `jerryliu0103/curation-pipeline-2` (Agent Skill)
- Install (CLI): `npx skillmds@latest add jerryliu0103/curation-pipeline-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jerryliu0103/curation-pipeline-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: jerryliu0103 (https://skillmd.com/u/jerryliu0103)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/jerryliu0103/curation-pipeline-2

---


# staging 閘門

一條規則撐起整個東西：

> **代理不寫正本。代理寫 staging。由人把 staging 搬進正本。**

這支 skill 裡其他所有東西，都是因為這條規則而存在，或是為了防止有人安靜地繞過它。

## 為什麼不乾脆讓代理直接寫？

**因為你沒辦法審查已經發生的事。** 一個直接寫入的代理只給你兩個爛選項：信任它，或是
事後稽核資料庫——而事後稽核難得多，因為到那時候，壞的列跟好的列長得一模一樣。

**staging 檔案就是那份可供審查的東西。** 它是「代理加了 40 筆」與「這裡有 40 筆，
其中 3 筆需要你看一下，理由如下」之間的差別。

## 四個階段

```
   蒐集              檢查              核准             寫入
  ──────            ──────            ──────           ──────
  curator     →    validator     →     人        →      人
  （只寫            （唯讀，由        （讀差異）      （執行 SQL）
   staging）         hook 強制）
```

1. **蒐集**——`curator` 寫 `data/staging/<batch>.json`。絕不寫正本。
2. **檢查**——`validator` 讀 staging、對正本下唯讀查詢，回報通過／待複核／退回。
   **什麼都不寫，包括 staging。**
3. **核准**——人讀那份報告。退回的回到第 1 步。
4. **寫入**——人執行寫入。**這是唯一有寫入權的步驟。**

第 3、4 步是人，這就是重點。**你把它們自動化掉，就等於重建了這支 skill 要防的那件事**，
而 staging 檔案變成了裝飾品。

## 用結構強制，不要用拜託

**一條違反了也不會報錯的規則，會被違反，而且你不會知道。**

具體案例：某個專案寫著「代理絕不可碰某個資料源」，這條規則跑了一個月；查核時發現五個
代理定義檔全都在做那件事。沒有人粗心。**違反時沒有任何東西會失敗**，所以完全沒有任何
形式的回饋。

三層，可靠度由低到高：

| 層 | 機制 | 強度 |
|---|---|---|
| 提示 | 在代理檔裡寫「不要寫入正本」 | 最弱，只是建議 |
| 工具集 | frontmatter 的 `disallowedTools: Write, Edit` | 拿掉能力 |
| Hook | `PreToolUse` 擋掉指令 | 接住從 Bash 漏出去的 |

**三層都要用。** 工具集那層管不到 `Bash`，而 `Bash` 正是資料庫被寫入的方式。
那就是 hook 存在的理由。

本 plugin 附了 `hooks/readonly-guard.py`（擋非唯讀 SQL）與
`hooks/canonical-store-guard.py`（碰到錯誤資料源時先問）。用
`templates/settings.json.example` 接起來。

**對有正當用途的路徑，用 `ask` 不要用 `deny`。** 把人真的需要的指令擋死，只會讓人繞
過去，然後你就完全失去可見度。你要的是「不會不小心走進去」，不是「不准走到這裡」。

## 每個代理只給剛好夠用的角色

| 代理 | 讀 | 寫 | 由什麼強制 |
|---|---|---|---|
| `curator` | 網路、檔案 | 只有 staging | 根本沒有資料庫工具 |
| `validator` | staging、正本 | 什麼都不寫 | `disallowedTools` ＋ hook |
| `reader` | 正本 | 什麼都不寫 | `disallowedTools` ＋ hook |
| `mirror-writer` | 正本 | 只有鏡像 | 設計上就是單向 |
| `principle-extractor` | 檔案 | 只有 staging | 不能上網、不碰資料庫 |

這種收斂不是官僚。**出事的時候，角色越窄，可能的肇因清單就越短。**

## 批次寫入前先試跑

拿一份可拋棄的正本副本，在一個會回捲的交易裡跑這批資料。它會抓出語法錯、約束違反，
以及——最有價值的——**什麼都沒配對到的 JOIN**。

**關於試跑，有兩件不明顯的事：**

1. **失敗的 `BEGIN` 會把你的試跑安靜地降級成真的寫入。** 交易如果從來沒開始，每個敘述
   都會逐句自動提交，而結尾的 `ROLLBACK` 沒有東西可以回捲。**abort-on-error 要下在
   命令列上**，不要靠寫在腳本檔裡的指示詞——文字在經過幾層 shell 與產生器時跳脫字元
   會被吃掉，而**一個被吃掉的指示詞不會報錯，它只是什麼都不做**。
2. **輸出裡印出 `ROLLBACK` 不代表任何東西被回捲了。** 那個字一定會印。
   **試跑完要回頭查正本，確認真的什麼都沒寫進去。** 如果你看到「目前沒有進行中的交易」
   這種警告，那不是雜訊——**那是流水線在告訴你這次是真的寫入**。

**驗收試跑要比對數量，不是看有沒有出錯。** 什麼都沒配對到的 JOIN 不會拋出任何東西。
見 `silent-failure-hunting` skill。

## 是累積，不是補完

策展資料庫永遠不會「做完」。兩個會讓人意外的後果：

- **「這一筆已經有資料了」不是跳過它的理由。** 第二個來源不會覆蓋第一個，它是另一列。
  **來源是累積的。**
- **因此要分清楚兩種欄位**，它們的行為不同：

  | 種類 | 例子 | 規則 |
  |---|---|---|
  | 單值屬性 | 分類、產地、型別 | 來源會競爭。空的就補，只有更好的來源可以覆蓋。 |
  | 敘述性內容 | 描述、註記 | 多來源各自成列並存。永遠不會「補滿」。 |
  | 量測值 | 評分、強度 | 並存，**而且要記下量測條件**。 |

  最後一列是最多人做錯的。**兩個看起來互相矛盾的來源可能根本沒有矛盾——它們可能是在
  不同條件下量的。** 把條件記下來，否則你會去「解決」一個從來不存在的矛盾。

## 你專案的 CLAUDE.md 該放什麼

不是這支 skill——連結過來就好。CLAUDE.md 只放**漏讀就會造成損害**的東西：

- 哪一份是正本，其他的各是什麼
- staging 在哪裡，以及不可以寫別的地方
- 連到正本的確切指令，以及哪些指令是陷阱
- **這個專案裡已經發生過的事故**

骨架在 `templates/CLAUDE.md.template`。

## 相關

- `silent-failure-hunting`——不會產生錯誤訊息的那些失敗
- `provenance-and-dedup`——來源座標、雙向核對，以及刻意丟棄清單

