Day 21 的 Human Approval Gate 確認「這份核准屬於這次變更,而且現在仍然有效」。但發布或部署之後,系統還是可能出現異常:指標漂移、錯誤率上升、資料損壞,或實際使用者和監控看到的結果不同。
這時候最關鍵的問題不是「趕快rollback」,而是「回滾到哪一個 candidate、根據什麼 evidence、由誰決定、要不要停止進一步損害」。今天加入 Rollback Gate,把回滾前的判斷也變成唯讀、可回讀、可稽核的 evidence。
想像你把一個結帳流程推到 production:
但上線 20 分鐘後,退款成功率從 99.97% 掉到 99.80%,而且 payment webhook 連續 timeout。這時候團隊會本能地想 rollback,但如果沒先固定 evidence,很容易發生:
Rollback Gate 要回答的是:現在是否具備足夠證據支持這次 rollback,以及目標 candidate 是否真的是 last known good。
很多人把 rollback 想成「回到上一個版本」。實際上它更像是:
rollback_eligible、blocked_evidence、blocked_target,還是 blocked_stop_loss。flowchart TD
A[Trigger evidence abnormal] --> B[Fix rollback intent]
B --> C{Candidate identity match?}
C -->|否| B1[BLOCKED_TARGET]
C -->|是| D{Stop-loss / window valid?}
D -->|否| B2[BLOCKED_STOP_LOSS]
D -->|是| E[ROLLBACK_ELIGIBLE]
E --> H[Release owner executes approved action]
圖 1|Rollback Gate 檢查回滾目標、觸發證據與 stop-loss 條件,只輸出 rollback eligible 的證據。
Rollback intent 至少要包含:
run_id:這次異常觀察的執行批次。source_candidate_id:目前 serving 且被判定異常的 candidate。target_candidate_id:準備 rollback 回去的 candidate。source_commit、input_digest、environment_id、target。trigger_evidence:哪些 observation 觸發這次 rollback 判斷。只要 observation 和 intent 的 identity 有任何一个欄位不同,就回報 *_mismatch。不要因為「大概都是 production」就把另一份 run 的 rollback 判斷套過來。
Rollback target 不能是「某個舊 commit」或「看起來比較穩的版本」。Intent 必須明確宣告:
target_candidate_id 對應的是哪一個 candidate。last_known_good_evidence 的名稱與 digest。Gate 會比對 observation 裡的 candidate state 和 digest。如果 target candidate 已經不是 intent 宣告的 last known good,或者 serving state 已經改變,就停止在 BLOCKED_TARGET。
常見錯誤:
Rollback 通常伴隨 stop-loss:一旦觸發,就要避免繼續擴大傷害。Intent 至少要寫清楚:
stop_loss_condition:例如「錯誤率連續 3 個 window 超標」或「特定 queue depth 超過上限」。observation_window_min_seconds:要觀察多久才能確認異常不是短暫抖動。max_rollback_wait_seconds:超過多久即使還在觀察,也必須進入下一個決策點。如果 observation window 還不够、stop-loss 條件還沒明確觸發,就輸出 BLOCKED_STOP_LOSS,不讓團隊在恐慌中 rollback。
Rollback 不是只憑感覺。Intent 要預先宣告觸發 rollback 需要哪些 evidence,例如:
{
"trigger_evidence": [
{"name": "payment_failure_rate", "state": "above_threshold", "window_minutes": 20},
{"name": "webhook_timeout_rate", "state": "above_threshold", "window_minutes": 20}
]
}
Gate 會檢查每份 evidence 是否存在、state 是否符合、digest 是否與 intent 一致。任何一項缺失、state 錯誤或 digest 不同,都不能進入 ROLLBACK_ELIGIBLE。
Rollback Gate 的輸出不是 true/false,而是帶有 deterministic reason code 的報告:
ROLLBACK_ELIGIBLE:目標 candidate、evidence、stop-loss 都符合。BLOCKED_IDENTITY:source/target/run/environment 有漂移。BLOCKED_TARGET:target candidate 不是 last known good。BLOCKED_EVIDENCE:trigger evidence 缺少、state 錯誤或 digest 不一致。BLOCKED_STOP_LOSS:stop-loss 條件未明確觸發,或 window 還不够。BLOCKED_POLICY:同一筆 change 已經觸發過最大 rollback 次數,或 requester 無權決定 rollback。這些 reason code 讓 release owner 知道下一步是補齊 evidence、切換 target candidate、延長觀察,還是直接交給 incident commander 處理。
example-rollback-gate/ 是一個 Python 唯讀範例,只讀取 intent 與 observation,不修改系統狀態:
cd day22/example-rollback-gate
python3 -m unittest -v
python3 -m py_compile rollback_gate.py test_rollback_gate.py
python3 rollback_gate.py fixtures/intent.json fixtures/observation.json
成功時輸出:
{
"allowed": true,
"state": "rollback_eligible",
"reasons": []
}
範例刻意保持三個界線:
從 context 新鮮度開始,我們逐層把「AI 可以猜」的空間壓到最小:
flowchart LR
C[Context] --> D[Change evidence]
D --> R[Release candidate]
R --> S[Runtime stability]
S --> U[User-facing SLO]
U --> A[Human approval]
A --> X[Executed action]
X --> T{Abnormal trigger?}
T -->|否| G[Good state recorded]
T -->|是| RB[Rollback Gate]
RB --> H2[Human decides rollback target]
圖 2|證據鏈走到授權執行之後,仍然保留 rollback evidence 的出口,但每一次狀態改變都需要新的 intent 與新的 evidence。
Rollback Gate 的核心不是阻止 rollback,而是阻止「慌張中的 rollback」。它要求團隊在異常發生前就寫明:
請記住三句話:
deployed 不等於 safe to keep;異常時需要新的 rollback intent。rollback_eligible 只代表 rollback evidence 成立;真正回滾仍由人類決定並負責。