feat(agents): route high risk through controlled automation
Some checks failed
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / tests (push) Failing after 1m8s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-26 18:52:19 +08:00
parent 717f0edd33
commit e0a86b6254
65 changed files with 733 additions and 516 deletions

View File

@@ -7,7 +7,7 @@ Phase 18: Failure Auto-Repair Loop (2026-03-31 統帥批准)
- 監聽 AuditLog 失敗事件
- AI 分析失敗原因
- 評估風險等級
- 執行自動修復 (LOW 風險) 或請求人工授權 (MEDIUM/CRITICAL)
- LOW / MEDIUM / HIGH 由 AI Agent 受控自動修復CRITICAL / break-glass 才硬封鎖
設計原則:
- 實作 IFailureWatcher Protocol
@@ -53,9 +53,13 @@ RISK_LEVELS = {
"operations": ["restart_pod", "restart_deployment", "clear_cache"],
},
"MEDIUM": {
"auto_repair": False,
"auto_repair": True,
"operations": ["scale_deployment", "rollback", "update_config"],
},
"HIGH": {
"auto_repair": True,
"operations": ["controlled_rollback", "controlled_config_repair", "controlled_service_repair"],
},
"CRITICAL": {
"auto_repair": False,
"operations": ["delete_pvc", "drop_database", "network_policy"],
@@ -108,8 +112,8 @@ class FailureWatcherService(IFailureWatcher):
1. 收到失敗事件 (from Redis Stream or direct call)
2. AI 分析失敗原因 (OpenClaw)
3. 評估風險等級
4. LOW 自動修復 → 揭露通知
5. MEDIUM/CRITICAL → Telegram + 前端等待授權
4. LOW / MEDIUM / HIGH → 受控自動修復 → 揭露通知
5. CRITICAL / hard blocker → Telegram + 前端 break-glass blocked 狀態
"""
def __init__(self) -> None:
@@ -183,11 +187,11 @@ class FailureWatcherService(IFailureWatcher):
target_resource=target_resource,
reason=global_reason,
)
# 強制升級為 CRITICAL必須人工授權
# 全域熔斷代表 repair storm 風險,仍維持 break-glass blocked。
risk_level = "CRITICAL"
result["risk_level"] = "CRITICAL"
result["next_action"] = "blocked_by_global_cooldown"
await self._request_human_approval(
await self._queue_ai_repair_followup(
audit_log_id=audit_log_id,
analysis=analysis,
reason=global_reason,
@@ -201,7 +205,7 @@ class FailureWatcherService(IFailureWatcher):
)
if not can_auto_repair:
# 超過冷卻期限制,升級為 MEDIUM
# 超過單資源冷卻期限制,改由 AI 受控重試佇列處理,不再要求人工接手。
logger.info(
"repair_cooldown_escalate",
audit_log_id=audit_log_id,
@@ -211,7 +215,7 @@ class FailureWatcherService(IFailureWatcher):
risk_level = "MEDIUM"
result["risk_level"] = "MEDIUM"
if risk_level == "LOW" and RISK_LEVELS["LOW"]["auto_repair"]:
if risk_level != "CRITICAL" and RISK_LEVELS.get(risk_level, {}).get("auto_repair"):
# 自動修復 (Phase 18.3: 傳入完整 failure_data)
success, repair_result = await self.execute_auto_repair(
audit_log_id=audit_log_id,
@@ -242,22 +246,22 @@ class FailureWatcherService(IFailureWatcher):
auto=True,
)
else:
# 升級為 MEDIUM請求人工授權
# 失敗後排入 AI controlled retry / rollback不再直接轉人工。
result["risk_level"] = "MEDIUM"
await self._request_human_approval(
await self._queue_ai_repair_followup(
audit_log_id=audit_log_id,
analysis=analysis,
reason="自動修復失敗,需人工介入",
reason="AI 受控自動修復失敗,已排入 rollback / transport / PlayBook 修復重試",
)
result["next_action"] = "await_approval"
result["next_action"] = "ai_retry_queued"
else:
# MEDIUM/CRITICAL: 請求人工授權
await self._request_human_approval(
# CRITICAL: break-glass / hard blocker不執行寫入。
await self._queue_ai_repair_followup(
audit_log_id=audit_log_id,
analysis=analysis,
reason=f"風險等級 {risk_level},需人工審核",
reason=f"風險等級 {risk_level} 命中 break-glass guardrailAI 已封鎖寫入並保留證據",
)
result["next_action"] = "await_approval"
result["next_action"] = "break_glass_blocked"
logger.info(
"failure_watcher_processed",
@@ -505,9 +509,9 @@ class FailureWatcherService(IFailureWatcher):
"NETWORK_ERROR": "檢查網路連線,驗證 DNS 解析",
"PERMISSION_DENIED": "檢查 RBAC 權限配置",
"RESOURCE_ERROR": "增加資源配額或清理資源",
"UNKNOWN": "需人工分析日誌",
"UNKNOWN": "AI 深度診斷:補抓 timeline / log / metric / PlayBook 相似案例",
}
return suggestions.get(classification, "需人工分析")
return suggestions.get(classification, "AI 深度診斷")
async def _check_repair_cooldown(
self,
@@ -709,7 +713,7 @@ class FailureWatcherService(IFailureWatcher):
error=str(e),
)
async def _request_human_approval(
async def _queue_ai_repair_followup(
self,
audit_log_id: str,
analysis: dict,
@@ -720,10 +724,11 @@ class FailureWatcherService(IFailureWatcher):
# 推送到 Redis (前端 WebSocket 訂閱)
redis = get_redis()
repair_request = {
"type": "repair_request",
"type": "ai_controlled_repair_followup",
"audit_log_id": audit_log_id,
"analysis": analysis,
"reason": reason,
"controlled_retry": True,
"created_at": datetime.now(UTC).isoformat(),
}
await redis.publish(
@@ -736,18 +741,18 @@ class FailureWatcherService(IFailureWatcher):
tg = get_telegram_gateway()
message = (
f"🔧 <b>修復請求</b>\n\n"
f"🤖 <b>AI 受控修復後續</b>\n\n"
f"├ 📋 AuditLog: <code>{audit_log_id[:8]}...</code>\n"
f"├ 📊 分類: {analysis.get('classification', 'UNKNOWN')}\n"
f"├ ⚠️ 風險: {analysis.get('risk_level', 'MEDIUM')}\n"
f"├ 🔍 原因: {analysis.get('root_cause', reason)}\n"
f"└ 💡 建議: {analysis.get('suggested_repair', '需人工分析')}\n\n"
f"請在 Dashboard 授權或使用 /repair {audit_log_id[:8]}"
f"└ 💡 下一步: {analysis.get('suggested_repair', 'AI 深度診斷')}\n\n"
f"狀態:<code>ai_retry_or_break_glass_recorded</code>"
)
await tg.send_alert_notification(message)
logger.info(
"repair_request_sent",
"ai_controlled_repair_followup_sent",
audit_log_id=audit_log_id,
risk_level=analysis.get("risk_level"),
)