diff --git a/apps/api/src/services/auto_approve.py b/apps/api/src/services/auto_approve.py index d15fce86..fa4ade65 100644 --- a/apps/api/src/services/auto_approve.py +++ b/apps/api/src/services/auto_approve.py @@ -50,6 +50,7 @@ class AutoApproveReason(str, Enum): CRITICAL_OPERATION = "critical_operation" # 關鍵操作 LOW_TRUST = "low_trust" # 信任不足 NO_PLAYBOOK = "no_playbook" # 無匹配 Playbook + NO_EXECUTABLE_ACTION = "no_executable_action" # action 為自然語言,無法執行 LOW_SUCCESS_RATE = "low_success_rate" # Playbook 成功率不足 INSUFFICIENT_HISTORY = "insufficient_history" # 執行歷史不足 @@ -294,13 +295,17 @@ class AutoApprovePolicy: # 根因:Solver 經 OpenClaw Nemo 路徑輸出「重啟 Crash Looping Pod」等自然語言 # action 非空 → 條件 1c 通過 → auto_approved=True # 但 kubectl_command 為空 → 實際無法執行 → incident 卡在 investigating - # 修復:action 必須含 kubectl 關鍵字才可自動執行,否則降級人工審核 + # 修復:直接讀 proposal_data["action"] 原始值(非 fallback 後的 action 變數) + # 避免「action 空 → fallback 成 kubectl_command → action 含 kubectl → 誤放行」 + # Code Review 2026-04-17: P0-1 修正 action fallback 語意混淆 + # P1-2 改用 NO_EXECUTABLE_ACTION(避免污染 KM 飛輪學習資料) + _raw_action = proposal_data.get("action", "") or "" _kubectl_cmd = proposal_data.get("kubectl_command", "") or "" - _has_kubectl = "kubectl" in action.lower() or "kubectl" in _kubectl_cmd.lower() + _has_kubectl = "kubectl" in _raw_action.lower() or "kubectl" in _kubectl_cmd.lower() if not _has_kubectl: return self._reject( - reason=AutoApproveReason.NO_PLAYBOOK, - detail=f"Action '{action[:60]}' is natural language — no kubectl command, requires human review", + reason=AutoApproveReason.NO_EXECUTABLE_ACTION, + detail=f"Action '{_raw_action[:60] or _kubectl_cmd[:60]}' is natural language — no kubectl command, requires human review", risk_level=risk_level, trust_score=trust_score, confidence=confidence, diff --git a/apps/api/src/services/decision_manager.py b/apps/api/src/services/decision_manager.py index 729c6ca2..bc213274 100644 --- a/apps/api/src/services/decision_manager.py +++ b/apps/api/src/services/decision_manager.py @@ -381,7 +381,7 @@ async def _push_decision_to_telegram( alertname=_alertname, threat_level=_threat_level, resource=target[:60], - threat_behavior=reasoning[:150] if reasoning else description[:150], + threat_behavior=_smt(_parse_debate_summary(reasoning).get("diagnosis") or reasoning, 150) if reasoning else description[:150], ) elif _alert_category == "business": # TYPE-6B:業務/FinOps 資訊告警 — 發到 SRE 群組(無審核按鈕)(ADR-075 Step-5) diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index a4f6031e..272caab6 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -6,6 +6,148 @@ --- +## 📍 2026-04-17 晚 — P1+P2 安全熱修 + 第一次授權執行歷史里程碑 🏁 + +### 第一次 [✅批准] 歷史時刻 + +- **INC-20260417-43E98A**:混沌注入 `KubePodCrashLooping` → TYPE-3 卡片呈現符合預期 +- [✅批准][❌拒絕] 置頂 ✅,AI 診斷只顯示診斷摘要 ✅,action 為 `kubectl get pods -n awoooi-prod` ✅ +- 統帥按下 [✅批准] → `approval_execution.py` 接收 → 原卡片下方 reply「✅ 執行成功/失敗」 +- KM 沉澱:`_write_execution_result_to_km()` 自動寫入 `INCIDENT_CASE`(含 alertname/category/action) + +### P1 安全熱修 — Commit `93205ce` + +| 問題 | 根因 | 修復 | +|------|------|------| +| 自然語言 action 通過 auto_approve | 條件 1c 只判斷 action 是否為空,未驗證格式 | 新增條件 1d:action 必須含 `kubectl` 關鍵字,否則 `NO_PLAYBOOK` 拒絕,降人工審核 | +| Solver Nemo 格式路徑輸出自然語言 | `action_title` 不含 kubectl 仍被轉為 CandidateAction | `_extract_candidates()`:`action_title` 不含 kubectl → `return []` → 觸發 `_degraded_plan` | +| 降級動作為 `"restart_pod"` 等自然語言 | `_default_action_for_category` 返回非 kubectl 字串 | 改為真實 `kubectl get/top/exec` 調查指令(唯讀,無副作用) | + +### 架構現況(2026-04-17 晚) + +| 層級 | 狀態 | 說明 | +|------|------|------| +| L1 監控/告警 | ✅ 生產運行 | Prometheus + Alertmanager | +| L2 AI 診斷 | ✅ 生產運行 | 5-Agent Debate,confidence/blast_radius 計算 | +| L3 條件自動執行 | ✅ 首次驗證 | kubectl 格式 + blast_radius 評分 → 人工或自動 | +| L4 自動放行(高信任) | ⚠️ 架構就緒 | trust_score 邏輯存在;min_trust_score=0(pod重啟會歸零)| +| L5 自主學習飛輪 | ⚠️ 架構就緒 | `_write_execution_result_to_km` 寫入,未 7 天驗證 | + +### 已驗證事實修正 + +- **卡片不會 in-place edit**:執行結果以 `reply_to_message_id` 送新訊息到原告警下方 +- **KM 沉澱是真的**:`approval_execution.py:676` `create_entry` 確實執行 +- **AI 仲裁 20%** = Solver 走降級路徑,`confidence=0.2` 是設計值(降級動作應給低分) + +--- + +## 📍 2026-04-17 下午三 — 混沌演習 + Telegram UI 第三波修復(BUG-C)🎯 + +### 混沌演習結果(Alertmanager API 注入法) + +- 注入:`KubePodCrashLooping` → `INC-20260417-C6D1D6` 建立 ✅ +- AI Debate 完成(confidence=0.9, risk=low)✅ +- 揭露新 BUG:TYPE-3 root_cause 仍含 debate_summary 全文 + K8s 按鈕蓋台 approve/reject + +### 修復 Commit `f421e65` + +| 問題 | 根因 | 修復 | +|------|------|------| +| TYPE-3 卡片 AI 診斷欄顯示完整 debate_summary | `root_cause=_smt(reasoning, 500)` 未解析 | `_parse_debate_summary` 只取 `diagnosis` + `_smt 300` | +| K8s 動態按鈕蓋台,看不到批准/拒絕 | `requires_human_approval` 條件未滿足時跳過 approve/reject | `_build_inline_keyboard` 重構:[✅批准][❌拒絕] 永遠第一行,K8s 按鈕置後 | + +**副作用清理**:移除 `requires_human_approval` 參數(`_build_inline_keyboard` + `send_approval_card` + 呼叫端),邏輯簡化為無條件置頂。 + +--- + +## 📍 2026-04-17 下午二 — Telegram UI 第二波修復(BUG-A + BUG-B)📊 + +### 系統盤點(System Audit) + +完成 6 TYPE 全分類盤點:TYPE-1/2/3/4/4D/8M + +- TYPE-2/3/4:✅ TelegramMessage 結構化模板,正常 +- TYPE-8M:✅ 已修復(第一波 6baa2e9) +- TYPE-1:⚠️ BUG-A — `message=reasoning[:200]` 傾倒完整 debate_summary +- TYPE-4D:⚠️ BUG-B — `diff_summary=description[:500]` 傾倒 AI 輸出的 JSON 原文 + +### 修復 Commit `418d735` + +| 問題 | 根因 | 修復 | +|------|------|------| +| TYPE-1 純資訊通知顯示 "診斷...;方案...;安全審查..." 全文 | `reasoning[:200]` 未解析 debate_summary | `_parse_debate_summary(reasoning)` 只取 `diagnosis` + `_smt` 截斷 200 字 | +| TYPE-4D Config Drift 顯示 `{"action_title":"...","description":"..."}` | `description[:500]` 傳入未解析的 LLM JSON | JSON Catcher:`json.loads` 成功 → 格式化「📝建議操作/📖說明/⏪回滾方案」;失敗 → 平滑降級純文字 | + +**修改範圍**:僅 `decision_manager.py` 路由準備段(+23行/-2行),`telegram_gateway.py` 模板層零改動。 + +--- + +## 📍 2026-04-17 下午 — Telegram UI 三連修(顧問戰報分析)🎯 + +### 顧問診斷兩張截圖 + +**截圖一(好消息)**:Solver 成功輸出 `kubectl delete pod awoooi-api -n awoooi-prod`(blast_radius=25), +Trust Score 未達 0.8 門檻 → 系統正確降級為 ACTION REQUIRED — Trust Engine 正常運作。 + +**截圖二(真問題)**:TYPE-8M 卡片三欄重複 + 幽靈截斷 + 死卡(無批准/拒絕按鈕) + +### 修復 Commit `6baa2e9` + +| 問題 | 根因 | 修復 | +|------|------|------| +| 批准/拒絕按鈕消失(死卡) | `_build_inline_keyboard` 有動態按鈕時跳過 approve/reject | 新增 `requires_human_approval` 參數,True 時強制插入批准/拒絕行 | +| TYPE-8M 三欄重複渲染 | `diagnosis`/`system_impact`/`probable_cause` 全取 `reasoning[:100]` | 新增 `_parse_debate_summary()` 拆分各組件 | +| 幽靈截斷「質疑:無(通」 | 粗暴 `[:N]` 在括號中間切斷 | 新增 `_smart_truncate()` 在句子邊界截斷 | + +驗證:`verify_telegram_ui.py` 全部通過,Run 927 部署中(13:58 台北)。 + +--- + +## 📍 2026-04-17 — Phase 5 燃料修復 + 生產 Bug 三連修 🔧 + +### 背景 +顧問(統帥)從 Telegram 截圖診斷出 4 個生產問題: +CI/CD 失敗、API 短暫離線、drift 研判原因空白、Telegram 截斷幽靈復發 + +### 根本原因 + 修復 Commits + +| Commit | 問題 | 根因 | 狀態 | +|--------|------|------|------| +| `e0bfcc7` | Phase 5 blast_radius fill rate = 0% | Solver prompt 範例為 `restart_service:xxx` 自訂格式 → LLM 輸出自然語言 → auto_approve Cond 1c 拒絕 → blast_radius_calculator 從未被呼叫 | ✅ 已部署 `0ab92c2` | +| `5dae610` | CD pipeline rebase 衝突 | `git rebase` 無 `-X theirs` → kustomization.yaml 衝突未解 → push rejected | ✅ 已部署 `0ab92c2` | +| `58d9c06` | drift_narrator 研判原因空白 | `_generate_narrative()` 直接呼叫 `192.168.0.111:11434`(dead Ollama)→ httpx 拋 exception → 整個 narrate_and_notify() 跳出 → DB 從未寫入 | ✅ 已部署 `0ab92c2` | +| `0ab92c2` | Telegram 截斷幽靈 "質疑:無(通" | `root_cause=reasoning[:300]` 裁切在 300 字 | ✅ 已部署 | + +### 修復技術摘要 + +**Solver prompt 修復(e0bfcc7)**: +- 舊:`action 範例 = "restart_service:awoooi-api"` → LLM 模仿輸出自然語言 +- 新:明確要求 kubectl 命令 + 正確範例 `kubectl rollout restart deployment/awoooi-api -n awoooi-prod` +- 影響:auto_approve Cond 1c 恢復,_auto_execute() 路徑打通,blast_radius_calculator 開始運作 + +**drift_narrator 修復(58d9c06)**: +- 舊:`httpx.AsyncClient → POST 192.168.0.111:11434/api/generate` (Dead IP) +- 新:`get_openclaw().call(prompt)` — 走 AI Router,自動 fallback +- 與 drift_interpreter.py 同樣修法(d952435) + +### 生產驗證(2026-04-17 13:38 台北) + +| 指標 | 狀態 | +|------|------| +| Run 926 部署 | ✅ success,image `0ab92c20...` | +| API 在線 | ✅ HTTP 200 | +| Solver kubectl 格式 | ⏳ 等下一個告警觸發 | +| blast_radius_score 記錄 | ⏳ 等新 incident | +| drift_narrator 研判原因 | ⏳ 等 14:00 cronjob 觸發 | +| Telegram 截斷修復 | ⏳ 等長 reasoning 的 incident | + +### GitOps Token 修復(本 Session 早期) +- Gitea Issue `write:issue` scope 缺失 → 403 +- 修復:docker exec gitea → generate-access-token → patch K8s Secret +- Phase 5 GitOps PR 功能:`AIOPS_P5_GITOPS_PR=false`(configmap,可按需啟用) + +--- + ## 📍 2026-04-16 — E2E 全節點驗證 + 生產 bug 連環修復 ### 問題背景