Skip to content
Dustin's AI Lab
Go back

怎麼寫一個真的會擋下來的閘門

閘門明明在跑,卻什麼都沒擋住。四條判準:綁 no-op 下活不下來的欄位、回 0 前先兩邊反驗、只印訊息不改離開碼等於沒閘門、別把 prompt 沒規定的字面當契約。

目錄
  1. 閘門要綁「no-op 下活不下來的欄位」
  2. 檢查器回 0 之前,先反驗它會動
  3. 只印訊息、不改離開碼,等於沒有閘門
  4. 別把 prompt 沒規定的字面當契約
  5. 退件不能變成「換個檔名放到旁邊」

這幾天連續踩到同一類東西:閘門明明在跑,卻什麼都沒擋住。我之前寫過兩篇談驗收端——不要用 AI 的摘要驗收 AI 講的是別相信「沒問題」這個回報,沒拋錯不代表成功 講的是 exit 0 跟 HTTP 200 都不算數。這一篇是設計端:既然回報不能信,那要怎麼寫出一個真的會擋下來的閘門。我手上有四條判準,每一條都是賠掉一次才寫下來的。

閘門要綁「no-op 下活不下來的欄位」

晨報的閘門只比對段落存在——有這幾個標題、有這幾個區塊,就算過。問題是昨天那份檔案也有這幾個段落。所以當晨報靜默漏了一天,閘門照樣放行,因為它檢查的東西在什麼都沒發生的情況下一樣活得好好的。

判準因此是:閘門要綁「no-op 下活不下來的欄位」。今天的日期、今天才會出現的條目、只有真的重跑過才會變動的值——這種欄位一旦沒更新就當場露餡。只驗結構存在的檢查,等於幫昨天的檔案發了一張永久通行證。

檢查器回 0 之前,先反驗它會動

09-10 那天我兩次踩到同一個形狀:自己寫的檢查器回 0,我就當它沒問題。第一次是 git rev-list | cat-file --batch-check | awk 那條,回 0 不是因為沒有大物件,是因為前面根本沒產出物件清單,awk 收到的是空的。第二次是 filter-repo 之後的全物件掃描,也先跑出一份假的乾淨。

同一天課程那邊也一樣:公開前的機密掃描回 0,我拿一把假金鑰塞進去反驗,才確認掃描器真的有在比對。

所以判準是:拿一個必中的字串當對照,對照命中才採信那個 0。而且反驗要同時驗兩邊——乾淨對照必須不叫,每個破壞案例都必須叫。只看到「都沒叫」的時候,第一懷疑不是東西很乾淨,是反驗 harness 自己沒接上。

反方向的誤報同一天也出現兩次:一次把課堂當場產出的檔當成缺漏的 asset,一次把已經改寫成反例的字串當成違規,只比對字串、沒看語境。檢查器會漏叫,也會亂叫,兩邊都要驗。

只印訊息、不改離開碼,等於沒有閘門

這條最便宜,也最容易被自己騙過去。sweep 腳本裡寫的是 node check.js || exit 1,看起來就是個閘門。但 check.js 把問題印完之後仍然 exit 0,於是 || 永遠不成立,被旗標出來的項目照樣進了下一步。

要當閘門用就必須非零離開。印訊息是給人看的,離開碼才是給流程看的;只做前者,那支腳本是報表,不是閘門。

別把 prompt 沒規定的字面當契約

這條是換模型才炸出來的。prompt 從來沒有規定輸出標題要長什麼樣,閘門卻拿標題字面去逐字比對。Sonnet 產出的字面剛好合格,換成 Opus 就不合格——內容明明是對的,卻被判失敗,後面的 vault-refinery 整段被跳過。

這是我自己造出來的隱性契約。prompt 裡沒寫的東西,閘門不該拿來當契約;要驗就驗 prompt 真的規定過的欄位,想驗標題,就先把標題格式寫進 prompt。

退件不能變成「換個檔名放到旁邊」

題庫那邊的閘門更離譜一點。它遇到缺 type 的題目不退件,而是把它們改寫進 undefined_passed.json。名字裡就寫著 passed,89 題就這樣「過了閘門」,只是被放在匯入端根本不認得的檔名底下,全程不報錯。

後來是靠形狀驗證補回來的:五個選項、沒有 DS 句型、標題前綴對得上,這三個條件把那批 PS 撈了回來,才沒有整批漏掉。同一份流程裡還有一個數字問題,find -name out.json 會連 gate-in/ 底下的副本一起數進去,進度整個虛報一倍。

閘門只有兩種合法出口:放行,或退件。第三條「改個檔名放到旁邊」不是出口,是把問題藏起來,而且藏得比不檢查還深。


15 分鐘導入診斷

填表預約你的專屬 1 對 1 免費診斷時段,了解你可以怎麼無痛與 AI 協作、大幅提升生產力。

預約免費診斷
Share this post on:
Previous Post
Claude Code for Tutors:讓 Agent 直接教老師怎麼用 Agent
Next Post
證明「沒有」比證明「有」難得多