# ADR-040: 全域自動修復熔斷機制 # ADR-040: Global Auto-repair Governance Strategy **狀態**: 已批准 **日期**: 2026-03-30 01:10 (台北時間) **決策者**: 統帥 + Antigravity (首席架構師) **觸發事件**: 沙盤推演發現跨資源 Poison Pill 可導致 AI 骨牌修復死循環 --- ## 問題陳述 ### 場景:跨資源修復骨牌效應 ``` 流量激增 → AI 執行 scale_up (擴容 API Pod) → API Pod 數量增加,連線池耗盡 PostgreSQL → AI 收到 DB 告警,執行 restart_api_pod (釋放 DB 連線) → API 重啟導致 502 激增 → AI 再次執行 scale_up → 死循環,資源榨乾 ``` **核心問題**:現有 `max_repairs_per_resource: 3` 只限單一資源,無法防止骨牌效應。 --- ## 決策:雙層全域保護機制 ### 機制一:全域修復冷卻期(Global Action Cooldown) 當系統整體在過去 15 分鐘內自動修復超過 **5 次**(不論對象),強制凍結所有 Auto-Repair,轉為 `AWAITING_APPROVAL`。 ### 機制二:StatefulSet 硬禁令(Stateful Service Blacklist) 有狀態服務(PostgreSQL、Redis、ClickHouse、MinIO 等)**永遠不允許**自動重啟,強制人工介入。 --- ## 實作規範 ### 全域計數器(Redis 實作) ```python # apps/api/src/services/global_repair_cooldown.py """ 全域修復熔斷機制 ================ ADR-039:防止跨資源循環修復 設計原則: - Redis TTL 滑動窗口(15 分鐘) - 失敗降級:Redis 故障時保守跳過自動修復,強制人工確認 """ import structlog from src.core.redis_client import get_redis logger = structlog.get_logger(__name__) GLOBAL_COOLDOWN_KEY = "global:auto_repair:count" GLOBAL_COOLDOWN_TTL = 900 # 15 分鐘窗口 GLOBAL_COOLDOWN_THRESHOLD = 5 # 超過 5 次強制凍結 STATEFUL_SERVICE_BLACKLIST = frozenset({ "postgres", "postgresql", "awoooi-postgres", "redis", "awoooi-redis", "redis-stack", "clickhouse", "signoz-clickhouse", "elasticsearch", "etcd", "minio", "awoooi-minio", }) async def check_global_repair_cooldown( incident_id: str, affected_services: list[str], ) -> tuple[bool, str]: """ 檢查是否允許自動修復 Returns: (can_repair: bool, reason: str) """ redis = get_redis() # === 硬禁令:有狀態服務黑名單 === for service in affected_services: if any(bl in service.lower() for bl in STATEFUL_SERVICE_BLACKLIST): reason = f"服務 {service} 為有狀態服務,禁止自動重啟,請統帥手動介入" logger.warning( "stateful_service_blocked", service=service, incident_id=incident_id, ) return False, reason # === 全域冷卻期:Redis 計數 === try: count_raw = await redis.get(GLOBAL_COOLDOWN_KEY) current_count = int(count_raw) if count_raw else 0 if current_count >= GLOBAL_COOLDOWN_THRESHOLD: reason = ( f"系統在過去 15 分鐘內已自動修復 {current_count} 次," f"超出安全閾值 {GLOBAL_COOLDOWN_THRESHOLD}," "強制轉為人工審核模式" ) logger.warning( "global_repair_cooldown_active", current_count=current_count, threshold=GLOBAL_COOLDOWN_THRESHOLD, incident_id=incident_id, ) return False, reason return True, "允許自動修復" except Exception as e: # Redis 故障 → 保守策略:禁止自動修復 logger.error( "global_repair_cooldown_redis_error", error=str(e), fallback="blocking_auto_repair_for_safety", ) return False, f"Redis 連線異常,保守禁止自動修復(原因:{e})" async def record_global_repair_action() -> None: """ 記錄一次全域修復動作 使用 INCR + EXPIRE 實現滑動窗口計數 注意:INCR 是原子操作,多個 Worker 並發安全 """ try: redis = get_redis() count = await redis.incr(GLOBAL_COOLDOWN_KEY) # 只在第一次設定 TTL(避免頻繁重設導致窗口延長) if count == 1: await redis.expire(GLOBAL_COOLDOWN_KEY, GLOBAL_COOLDOWN_TTL) logger.info( "global_repair_action_recorded", count=count, threshold=GLOBAL_COOLDOWN_THRESHOLD, ) except Exception as e: # Redis 故障:靜默失敗 logger.warning("global_repair_record_failed", error=str(e)) ``` ### 整合到 auto_repair_service.py ```python # auto_repair_service.py - evaluate_auto_repair() 加入前置檢查 from src.services.global_repair_cooldown import check_global_repair_cooldown async def evaluate_auto_repair(self, incident: Incident) -> AutoRepairDecision: # === 最優先:全域熔斷檢查(在所有其他邏輯之前)=== can_repair, cooldown_reason = await check_global_repair_cooldown( incident_id=incident.incident_id, affected_services=incident.affected_services or [], ) if not can_repair: return AutoRepairDecision( can_auto_repair=False, reason=cooldown_reason, blocked_by="GLOBAL_GUARDRAIL", ) # === 現有邏輯:Severity 檢查 === if incident.severity and incident.severity.value in ["P0", "P1"]: ... # ... 後續現有邏輯 ... ``` ```python # auto_repair_service.py - execute_auto_repair() 執行後記錄 async def execute_auto_repair(self, incident, playbook) -> AutoRepairResult: # ... 現有執行邏輯 ... # === 執行成功後,記錄全域計數 === if result.success: from src.services.global_repair_cooldown import record_global_repair_action await record_global_repair_action() return result ``` --- ## 全域熔斷狀態視覺化(未來) ``` Dashboard 首頁 → AI 自治指標面板 → 顯示: 「⚠️ 系統保護模式:過去 15 分鐘修復 5 次,暫停自動修復」 → 統帥點擊解除 → POST /api/v1/repair/cooldown/reset(Tier 1 操作) ``` --- ## 閾值設計依據 | 參數 | 值 | 理由 | |------|-----|------| | `GLOBAL_COOLDOWN_THRESHOLD` | 5 | 正常運作時每天 < 5 次修復;5 次以上代表異常模式 | | `GLOBAL_COOLDOWN_TTL` | 900s(15 分鐘)| 大多數骨牌效應在 10 分鐘內完成;15 分鐘提供緩衝 | --- ## 模組化合規驗證 | 項目 | 說明 | 合規狀態 | |------|------|---------| | 層次 | `services/` 獨立服務層 | ✅ 合規 | | 依賴 | 只依賴 `core/redis_client` | ✅ 合規 | | Redis 降級 | 故障時保守處理(禁止自動修復)| ✅ 合規 | | 原子操作 | 使用 INCR(Redis 原生原子性)| ✅ 合規 | --- ## 驗收標準 | 項目 | 通過條件 | |------|---------| | 有狀態服務保護 | PostgreSQL/Redis 的 Incident 永遠返回 `GLOBAL_GUARDRAIL` | | 全域計數 | 第 6 次修復請求返回 `can_auto_repair=False` | | Redis 降級 | Redis 故障時 `check_global_repair_cooldown` 返回 `(False, "Redis 連線異常...")` | | Dashboard 可見 | (未來)保護模式顯示在 AI 自治指標面板 | --- ## 相關文件 - `docs/proposals/ARCHITECTURAL_RISK_WAR_GAME.md`:風險沙盤推演 - `apps/api/src/services/auto_repair_service.py`:自動修復服務 - ADR-028:Failure Auto-Repair Loop - ADR-030:Intelligent Auto-Remediation - ADR-038:OpenClaw Concurrency Governance(Semaphore + Circuit Breaker)