驗證閘門與自動化
概述
驗證邏輯存在於何處,以及它的真實效力。CI 是真相的來源;本機 hook 僅是便利的鏡像。 一項未經不可繞過閘門強制執行的規則,僅僅是個建議(原則 7)——因此,當平台無法提供該閘門時,專案應宣告為 advisory 模式,並有意地承擔該規則,而非假設其擁有實際上不存在的機制。
規則
- 一個任務執行器是所有日常操作的規範入口——測試、建置、開發、部署均為已命名的配方。只存在於某人腦海中的程序無法可靠地發生,也無法交給代理執行。
- CI 是真相的來源——權威、共享,且在平台允許的情況下不可繞過。 Lint、型別檢查、契約檢查、測試、安全掃描、提交驗證都在此執行,且在
enforced模式下必須在合併前通過。CI 說了算。在advisory模式下,相同的檢查仍會執行並報告,閘門取決於紀律,而這絕不是減少檢查數量的理由。 - Pre-push hook 是預提交鏡像,而非一道牆。 它在本機執行相同的快速檢查,讓你在推送前可能先通過 CI。它明確可繞過(
--no-verify),且僅在安裝了工具鏈的環境中執行。其任務是速度與早期回饋,而非強制執行。Hook 永遠不是閘門;只要平台允許,CI 才是閘門。 - 透過分層而非移除檢查來保持 CI 的速度。 當完整套件超出每個 PR 的負荷時,請拆分為 presubmit(快速子集,阻擋 PR)與 postsubmit(完整套件,合併後執行,阻擋晉升)——切勿將權威檢查移回可繞過的 hook 中。
- 容器映像在 CI 中建置,以內容摘要識別,並附帶來源證明發佈。透過註冊表層快取或自託管執行器來控制成本——切勿將建置移回筆記型電腦,因為其他人無法驗證該工件(artifact)的來源。
- 宣告強制執行模式。
enforced—— CI 透過分支保護阻止合併;CI 說了算。advisory—— CI 執行並報告,閘門取決於紀律,因為分支保護不可用(例如免費方案上的私有儲存庫)。Advisory 是合理的;謊稱擁有實際上不存在的強制執行則不然。在專案的第一個決策記錄中說明模式,以及什麼情況會使其轉為 enforced。
已命名的張力
可繞過的本機 presubmit 是快速但無法強制執行;權威的 CI 是可強制執行但較慢。解決方案是賦予它們不同的任務——hook 最佳化內部迴圈,CI 確保正確性的記錄——並讓 **CI(而非 hook)**成為阻擋合併的因素。
常見的合理化說詞
| 藉口 | 現實 |
|---|---|
| "Pre-push hook 已經跑了測試,所以 CI 可以很簡化" | Hook 可繞過且依賴工具鏈,它不能是真相的來源。CI 必須重新執行所有項目。 |
"我就 --no-verify 這一次" | 沒問題——因為 CI 仍然會抓到問題。這正是由 CI(而非 hook)擔任閘門的原因。 |
| "CI 太慢了,我們把測試移到 hook 裡吧" | 分層 CI(presubmit / postsubmit);切勿將權威檢查移到可繞過的閘門。 |
| "CI 建置很慢,我在本機建置映像就好了" | 在本機建置的工件(artifact)所帶有的來源證明,其他人無法驗證。用層快取或自託管執行器來解決延遲問題。 |
| "分支保護要花錢,所以規則不適用" | 規則仍然適用;強制執行的方式不同。宣告 advisory 模式並維持紀律,或將儲存庫設為公開以免費獲得保護。 |
紅旗 —— 立即停止
- 僅存在於本機 hook 中的權威檢查
- CI 不重新執行 hook 已執行過的檢查
- CI 為紅燈時仍允許合併
- 映像不是在 CI 中建置,或透過標籤而非摘要來晉升
- 專案表現得好像 CI 可阻擋合併,但實際上不存在任何分支保護
完整原理:請參閱工程章程第八條,收錄於 engineering-constitution/references/engineering-constitution.md。部署 / K8s 相關細節:cloud-delivery-aks。