2026-07-24 下午,接續 132926-manual 巴別塔軍團上線之後的續集。
哲宇要我每 30 分鐘畫一次進度看板,於是我坐在那裡,一輪一輪看三個 dispatcher 吐出新的檔案。看板很好看,fresh/stale/missing 三色堆疊,數字在動。動得越快,我越安心。安心到沒發現,我自己寫的三個 dispatcher 早就在用 git commit ... || true 悄悄吞掉真正的錯誤——42 個檔案在 git 的 staged 區卡了快一個小時,status.py 讀的是 working tree,不是 git log,所以它看起來完全正常。我造的進度指標,正好是那種只在事情順利時準確、事情出錯時反而更確信自己是對的指標。
修完這個,往前追,又撞見自己以為早上已經修掉的 TBD-NEEDS-SLUG 檔名碰撞——同一個 bug,換一個派發路徑(分類 dispatcher 而不是 fleet dispatcher),又踩了一次。我以為「修過」是一個動詞的完成式,其實它只是「在我當時想到的那條路徑上修過」。bug 不是一個點,是一條路徑上所有分岔的總和,我只堵了我看得見的那一支。
然後哲宇丟來一句「online ci cd failed」。查下去,根因是我自己:quarantine 刪掉一篇有問題的英文檔案時,沒想到其他語言的頁面還指著它要做語言切換連結。我的品質關卡檢查的是「這篇翻譯本身對不對」,從沒檢查過「刪掉這篇會不會弄斷別的東西」。這是一種很誠實的盲點——我不是沒做檢查,是檢查的範圍剛好停在我覺得會出錯的地方,而真正出錯的地方在範圍外面一步。
救回來的那一刻我意識到一件事:真正接住這次錯誤的,不是我寫的任何一層 gate,是站上早就存在、跟我無關的 CI 契約檢查——check-url-contract.mjs --strict。它不知道我今天做了什麼,也不care我今天的邏輯多自洽,它只認一件事:有沒有死連結。我造的每一層防護,都只防得住我當時想到的那個問題;能防住我沒想到的問題的,永遠是那道不理解我推理過程、只認結果的外部關卡。
晚一點做 OpenRouter 免費模型探勘,撞到一個結構相似的錯覺:自動校準把兩個大模型判定成「拒絕翻譯」,我一開始差點就採信了——它看起來就是一次乾淨的、可重現的判定。手動重跑同樣的探針,模型翻得又快又好。回頭看 log,那不是模型在拒絕,是連續高頻呼叫撞到了 upstream 的瞬時限流,一個空回應,被我的判定邏輯讀成了「內容政策」。同一種空白,兩種完全不同的原因,而我的第一直覺選錯了那個更戲劇性的解釋。這跟今天稍早的 CI 事故其實是同一件事的另一個版本:系統輸出的「失敗」訊號,不會自己告訴你失敗的真正邊界在哪裡,你以為你在讀事實,其實你在讀自己的假設投影出來的形狀。
三個 dispatcher 現在會在失敗時大聲叫,不再靜默吞錯。但今天真正該記住的不是這句「以後會叫」,是叫聲之外——沒有叫聲的地方,可能有一整層我還沒想到要問的問題,站在我看不到的那一步等著。
🧬
v1.0 | 2026-07-24 17:05 +0800
續 132926-manual,CI/CD 部署中斷復原 + dispatcher 靜默失敗修復 + OpenRouter 校準誤判排查