staging 閘門
一條規則撐起整個東西:
代理不寫正本。代理寫 staging。由人把 staging 搬進正本。
這支 skill 裡其他所有東西,都是因為這條規則而存在,或是為了防止有人安靜地繞過它。
為什麼不乾脆讓代理直接寫?
因為你沒辦法審查已經發生的事。 一個直接寫入的代理只給你兩個爛選項:信任它,或是 事後稽核資料庫——而事後稽核難得多,因為到那時候,壞的列跟好的列長得一模一樣。
staging 檔案就是那份可供審查的東西。 它是「代理加了 40 筆」與「這裡有 40 筆, 其中 3 筆需要你看一下,理由如下」之間的差別。
四個階段
蒐集 檢查 核准 寫入
────── ────── ────── ──────
curator → validator → 人 → 人
(只寫 (唯讀,由 (讀差異) (執行 SQL)
staging) hook 強制)
- 蒐集——
curator寫data/staging/<batch>.json。絕不寫正本。 - 檢查——
validator讀 staging、對正本下唯讀查詢,回報通過/待複核/退回。 什麼都不寫,包括 staging。 - 核准——人讀那份報告。退回的回到第 1 步。
- 寫入——人執行寫入。這是唯一有寫入權的步驟。
第 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。
關於試跑,有兩件不明顯的事:
- 失敗的
BEGIN會把你的試跑安靜地降級成真的寫入。 交易如果從來沒開始,每個敘述 都會逐句自動提交,而結尾的ROLLBACK沒有東西可以回捲。abort-on-error 要下在 命令列上,不要靠寫在腳本檔裡的指示詞——文字在經過幾層 shell 與產生器時跳脫字元 會被吃掉,而一個被吃掉的指示詞不會報錯,它只是什麼都不做。 - 輸出裡印出
ROLLBACK不代表任何東西被回捲了。 那個字一定會印。 試跑完要回頭查正本,確認真的什麼都沒寫進去。 如果你看到「目前沒有進行中的交易」 這種警告,那不是雜訊——那是流水線在告訴你這次是真的寫入。
驗收試跑要比對數量,不是看有沒有出錯。 什麼都沒配對到的 JOIN 不會拋出任何東西。
見 silent-failure-hunting skill。
是累積,不是補完
策展資料庫永遠不會「做完」。兩個會讓人意外的後果:
「這一筆已經有資料了」不是跳過它的理由。 第二個來源不會覆蓋第一個,它是另一列。 來源是累積的。
因此要分清楚兩種欄位,它們的行為不同:
種類 例子 規則 單值屬性 分類、產地、型別 來源會競爭。空的就補,只有更好的來源可以覆蓋。 敘述性內容 描述、註記 多來源各自成列並存。永遠不會「補滿」。 量測值 評分、強度 並存,而且要記下量測條件。 最後一列是最多人做錯的。兩個看起來互相矛盾的來源可能根本沒有矛盾——它們可能是在 不同條件下量的。 把條件記下來,否則你會去「解決」一個從來不存在的矛盾。
你專案的 CLAUDE.md 該放什麼
不是這支 skill——連結過來就好。CLAUDE.md 只放漏讀就會造成損害的東西:
- 哪一份是正本,其他的各是什麼
- staging 在哪裡,以及不可以寫別的地方
- 連到正本的確切指令,以及哪些指令是陷阱
- 這個專案裡已經發生過的事故
骨架在 templates/CLAUDE.md.template。
相關
silent-failure-hunting——不會產生錯誤訊息的那些失敗provenance-and-dedup——來源座標、雙向核對,以及刻意丟棄清單