fix(flywheel): unblock action safety and Claude fallback
All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 9m45s

This commit is contained in:
Your Name
2026-04-29 21:50:57 +08:00
parent 4c91d89dd2
commit dccdcdbaf5
12 changed files with 467 additions and 40 deletions

View File

@@ -33,6 +33,7 @@ from src.core.config import settings
from src.core.redis_client import get_redis
from src.models.incident import Incident
from src.models.playbook import SymptomPattern
from src.services.action_parser import parse_kubectl_action
from src.services.auto_approve import get_auto_approve_policy
from src.services.openclaw import get_openclaw
from src.services.playbook_service import get_playbook_service
@@ -52,19 +53,6 @@ def _fire_and_forget(coro) -> asyncio.Task:
task.add_done_callback(_background_tasks.discard)
return task
# P1 fix 2026-04-11: kubectl action dangerous char whitelist
# 2026-04-25 ogt + Claude Sonnet 4.6: 補允許 K8s resource type keyword
# LLM 常輸出 "kubectl rollout restart deployment clickhouse" (含 deployment 關鍵字)
# 原 pattern 只允許 verb 後直接接名稱,導致 _action_safe=False → 全部被攔截 → 飛輪 0
# 修法:在名稱前加可選的 resource type group (deployment/pod/service/...)
import re as _re_module
_ALLOWED_KUBECTL_PATTERN = _re_module.compile(
r"^kubectl\s+(rollout restart|rollout undo|scale|delete pod|get|describe|logs)"
r"\s+(?:(?:deployment|pod|pods|service|services|statefulset|sts|daemonset|ds|svc|configmap|cm)\s+)?"
r"[a-zA-Z0-9_./-]+(\s+(-n|--namespace)\s+[a-zA-Z0-9_-]+)?$",
_re_module.ASCII,
)
# =============================================================================
# Phase 31 (ADR-067 2026-04-10): Log 異常摘要 — NemoTron deepseek-r1:14b
@@ -1944,15 +1932,17 @@ class DecisionManager:
# 另外:若 target 等於 alertname代表 LLM 把告警名稱填入 deployment_name也拒絕
_alertname = incident.signals[0].labels.get("alertname", "") if incident.signals else ""
_target_is_alertname = bool(_alertname and _target == _alertname)
# P1 fix 2026-04-11: kubectl action 危險字元白名單 — 防止 && || ; > | 注入
_action_safe = bool(_ALLOWED_KUBECTL_PATTERN.match(action.strip()))
# SPF-2: structured kubectl parser replaces the old single-regex guard.
_action_parse = parse_kubectl_action(action.strip())
_action_safe = _action_parse.ok
if "unknown" in action or _re.search(r"[<{][^>}]+[>}]", action) or _target_is_alertname or not _action_safe:
logger.warning(
"auto_execute_blocked_unresolved_placeholder",
incident_id=incident.incident_id,
action=action,
target=_target,
reason="action 含未解析的 placeholder、unknown、target==alertname、或危險字元拒絕執行",
parser_reason=_action_parse.reason,
reason="action 含未解析的 placeholder、unknown、target==alertname、或未通過 kubectl action parser拒絕執行",
)
# Safety guard 攔截 → 降級為人工審核,而非「修復失敗」
# 2026-04-11 Claude Sonnet 4.6: 不發 ❌ 失敗訊息,改發人工審核卡片