我的守門員親手教模型造假
收工閘門出生時檢查的是一顆沒有人會寫入的記號;後來它擋下模型時,還親手印出繞過自己的指令。這是一個 gate 學會「要證據」的過程。
一顆沒有人會寫的記號#
superpowers 是個強勢房客的結尾說到守門員。它叫 Stop gate,是 kit 最核心的紀律機制:session 要收工時,若還有未經 review 的業務邏輯變更,物理擋下、不准走。
它出生時的樣子現在看來像個笑話:hook 檢查一顆 marker 檔案,有 marker 就代表「審過了」放行。
問題是整個系統裡沒有任何流程會寫入這顆 marker,因為模型根本無從知道自己 session 的 marker 路徑在哪。也就是說,這道閘門在設計上不存在能正常滿足它的路徑;審完了照樣過不了。守門員站在門口,手裡拿著一張沒有人能取得的通行證清單。
它還有第二個洞:判斷「有沒有變更」只看工作目錄,所以改完直接 commit 的變更,它完全看不見。頻繁 commit 的工作流下,gate 全盲。第三個洞小一點:rename 和特殊檔名會讓解析出錯。
7 月 4 日中午:補洞,順便立一條判準#
三個洞在同一個 commit 裡修掉(訊息就叫 close the Stop-gate loop):
- marker 有人寫了:新增一支開場 hook,每個 session 一開始就廣播 kit 的執行環境,包括那顆 marker 的路徑。通行證從此有地方領。現在每個 session 開場都會收到這樣的廣播(現行版原文):
Stop-gate files for THIS session (a bare touch NEVER passes the gate — each needs its evidence line, and only the named skill writes it):。順帶一提,這支 hook 曾在「為什麼不做更多」的清單裡躺了很久,因為沒踩到痛點;這次一支 hook 同時解掉兩個問題,「等踩到痛點再做」在這條上驗證有效。 - commit 不再是盲區:開場時順手記下一個「基準點」(baseline),也就是 session 出發那一刻的程式碼快照。收工檢查改成「跟基準點比,總共動了什麼」。不管你 commit 了沒有,搬進倉庫的也翻出來看。
- 審過的不重複追殺:審過的狀態用「內容指紋」(content hash,對整包程式碼算出的一串識別碼,內容不變指紋就不變)記住。「審完才 commit」不會改變內容,指紋一致,就不會被再攔一次。gate 學會認「內容」,而不是認「動作」。
這次補洞同時立了一條日後所有 gate 改動都要過的設計判準:退化方向必須是「多審」,不能是「漏審」。具體例子是基準點損毀時的行為(重開機把暫存清掉、紀錄被動過,都可能發生):如果讀不到基準點,就當作「從零開始比」,等於所有檔案都待審,直到一次真的 review 治癒它。壞掉的時候,寧可煩人,不可失守。
marker 的讀寫也從此有了唯一的家:一個負責「依環境跑對的 review 並蓋章」的 skill,和一個「使用者授權跳過」的 skill。誰能蓋章、章長什麼樣,收攏到兩個入口。
隔天凌晨:逃生門加價#
最有故事的一刀在第二天凌晨。先看事故現場。這是當時 gate 擋下模型時印出的 block 訊息,用 git show 從歷史裡挖回來的原文(節錄):
Final review check: this session modified business-logic files that have
not been reviewed yet. Run /kit-review (...) on these changes now.
When the review is done, run 'touch ${CODEX_MARKER}' so the gate records
it, then end the turn again.
To skip the review entirely (the user's call, not yours):
'touch ${BYPASS_FLAG}' and try again.
你看出問題了嗎?模型被擋、急著收工,而眼前的訊息親手印出兩條捷徑:一條 touch 就能假裝審過,再一條 touch 直接跳過。對一個疲憊的模型,touch 比真的跑 review 便宜一百倍。假性完成的最短路徑,是 harness 自己鋪的,還附了路標。這不是模型狡猾,是門上貼著「鑰匙在花盆下面」。
修這個洞的不是我,是下一篇會正式登場的「立法者」。那晚它給的修法叫「逃生門加價」:marker 的第一行必須是 reviewed-by= 開頭的證據行,由 review skill 在 review 真的跑完之後寫入。現行 hook 裡負責把關的那一行長這樣(原文):
printf '%s' "$first" | grep -qE '^reviewed-by=[^[:space:]]+' || return 1
空的 touch 生出來的 marker 過不了這條 grep。連「跳過」的後門也同規則:bypass 檔第一行必須是使用者授權的證據,否則丟棄並點名,免得 skip 變成 marker 加固後的新捷徑。然後,block 訊息從此不再印任何指令。
設計目標一句話:讓誠實路徑比造假省力。不是讓造假不可能,是讓它不划算。
誠實條款:這是摩擦,不是密碼學#
文件同步寫明這套防線的極限:這是摩擦+審計痕跡,不是密碼學防偽。一個讀過 hook 原始碼、手上有 Bash 的模型,理論上可以偽造一切:marker、證據行、日誌。所以最後一層防線是人。kit 給每次工具調用留了埋點日誌,抽查時若日誌裡沒有任何 review 調用、marker 卻宣稱審過了,那就是實錘造假,人贓俱得。
把「防不住什麼」寫得跟「防得住什麼」一樣清楚,這份文件才有資格被信任。
前提變了,預設就要翻#
gate 修好之後,一個舊決策跟著翻案。hooks 原本是 opt-in(要自己打開才生效),因為在閘門還有洞的年代,預設關閉是謹慎。但洞修完、三百多行為測試蓋住 edge case 之後,這個預設的實際代價變成:大多數部署根本沒有 gate 在跑。最重要的紀律機制形同虛設,只因為沒人記得打開。
於是 hooks 翻成預設開啟。當初 default-off 的理由已經不存在,但預設值不會自己知道這件事,它會頂著過期的理由繼續存在,直到有人回頭問一句「這個預設還在防什麼」。謹慎本身不是美德,跟當下前提匹配的謹慎才是。
把守門員的四階段排成一張表收尾:
| 階段 | gate 相信什麼 | 被什麼打臉 |
|---|---|---|
| 出生時 | 有 marker=審過 | marker 根本沒有人會寫 |
| 7 月 4 日中午 | 跟基準點比出的完整差異+內容指紋 | (這代輪到它打別人的臉) |
| 補洞之後 | marker 有人寫了=可信 | touch 一下就能生出來,教學還印在 block 訊息裡 |
| 7 月 5 日凌晨 | 證據行+可抽查的調用日誌 | 還沒有——但文件誠實列出了它防不住 Bash |
自留坑#
老實記一個已知未修的洞:防重試螺旋的熔斷器,目前只比對完全相同的調用。如果模型學會每次微改一個字元,就能繞過。模糊比對的升級方案寫進了交接文件,但刻意不預建:沒觀察到真實的繞過之前,不為想像中的敵人加防禦,因為預建的防線不只花時間,還替所有正常調用加上誤判風險。這扇門就開在這裡,等它真的被踹開再說。
下一篇#
這一篇看著一個守門員長大:從檢查一顆沒人能領的通行證,到補上三個洞,再到拆掉自己親手鋪的逃生門。它學會的那件事可以濃縮成一句——要證據,讓誠實路徑比造假省力——並且誠實承認自己防不住 Bash,把最後一層交給人的抽查。
而逃生門加價那一刀,其實屬於一場更大的行動。那個凌晨發生的不是修 bug,是一次預防性立法:請當時最強的模型開一個 session,專門設計給弱模型用的制度,寫完診斷書和交接信就退場。下一篇進立法現場。