feat(drift+target): P0.1+P0.2+P0.3 三修 — drift 分頁分類 + AI 推薦 + target 追 trace

統帥三問決議:全做;AI 推薦 0.85 門檻純顯示不自動;先查 aol 再修

## RCA: awoooi-service 失敗來源
- /api/v1/aiops/kpi 顯示過去 24h 有 1 筆 playbook_executed actor=approval_execution status=failed
- grep codebase: 無任何程式碼寫死 awoooi-service(只有歷史 comment)
- 最可能源: alert_rule_engine._extract_vars 從 labels.service 取值當 Deployment 名
- cf5050c/4f2e122(2026-04-18)已修 NEMOTRON 幻覺雙路徑;本次修第三條路徑

## 修復
### P0.3a alert_rule_engine._extract_vars
- labels.service 降級:-service 結尾先剝 suffix 視為 base name
- match_rule 回傳新增 target_source 欄位追 trace
- 下次 awoooi-service 復發可直接看來源(label.service(stripped) 等)

### P0.3c approval_execution._log_aol_started.input
- 補 parsed_target/operation/namespace 欄位
- 未來 aol 查 failed 可直接看 target,無需推敲

### P0.1 telegram_gateway._send_drift_diff_detail
- 分頁(10 項/頁)取代一次洗版 30 項
- header 3 桶分類計數: 人工高風險 / 一般修改 / K8s 自動
- 底部 ⬅️/➡️ 分頁按鈕(callback: drift_view_page:{report_id}_{page})
- security_interceptor INFO_ACTIONS 加 drift_view_page 白名單

### P0.2 drift_narrator recommendation
- LLM prompt 加 recommendation 欄位(action/confidence/reason)
- action ∈ {adopt, revert, ignore, investigate}
- 卡片頂部顯示「🎯 AI 建議: 回滾 (85%) — reason」
- LLM 失敗走 _fallback_recommendation(規則式依 intent 對應)
- 卡片 diff_summary 上限 500 → 1500 字容納推薦 + narrative + items
- 統帥指令:純顯示不自動執行(門檻 0.85 保留未來)

## 驗證
- 90 個 pytest test 全過(drift + rule_engine + approval_execution)
- 5 檔 AST syntax check 過

## 下次驗收
1. 下次 drift 觸發 → 卡片頂部有「🎯 AI 建議」
2. drift_view 按下 → 3 桶分類 header + ⬅️/➡️
3. awoooi-service 若復發 → automation_operation_log.input.parsed_target 直接查

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-04-20 04:01:30 +08:00
parent 8d40bbff2b
commit 54d60d04f5
6 changed files with 319 additions and 37 deletions

View File

@@ -1096,18 +1096,37 @@ class ApprovalExecutionService:
在 automation_operation_log 寫一筆 'pending' 紀錄,回傳 op_id 供 _log_aol_completed 更新。
失敗時 (DB 異常) 回 None,主流程繼續 — aol 寫入永不阻塞執行。
2026-04-20 P0.3: input 補 target / operation_type / namespace
失敗時 aol.input 就能直接看到 target 是什麼(追 awoooi-service 類誤判的 source trace
"""
try:
from sqlalchemy import text as _sql
from src.db.base import get_db_context
import json as _json
# 2026-04-20 P0.3: 先嘗試從 action 解析 target / op_type失敗不阻塞
_parsed_target: str | None = None
_parsed_op: str | None = None
_parsed_ns: str | None = None
try:
_parsed = parse_operation_from_action(approval.action or "")
_parsed_target = _parsed.resource_name
_parsed_op = _parsed.operation_type.value if _parsed.operation_type else None
_parsed_ns = _parsed.namespace
except Exception:
pass
input_payload = {
"approval_id": str(approval.id),
"incident_id": approval.incident_id or "",
"action": (approval.action or "")[:500],
"risk_level": getattr(approval, "risk_level", None) or "",
"requested_by": getattr(approval, "requested_by", "") or "",
# 2026-04-20 P0.3: target source trace
"parsed_target": _parsed_target or "",
"parsed_operation": _parsed_op or "",
"parsed_namespace": _parsed_ns or "",
}
async with get_db_context() as db: