diff --git a/apps/api/scripts/migrate_rules_to_playbooks.py b/apps/api/scripts/migrate_rules_to_playbooks.py index ba70b290..7c75352c 100644 --- a/apps/api/scripts/migrate_rules_to_playbooks.py +++ b/apps/api/scripts/migrate_rules_to_playbooks.py @@ -66,6 +66,14 @@ def parse_args() -> argparse.Namespace: default=False, help="模擬 ENABLE_RULE_MIGRATION_DRAFT=false(測試 feature flag 關閉路徑)", ) + # 2026-04-29 ogt + Claude Opus 4.7: critic Major #2 修 + # --commit 寫 prod DB 必須二次確認,誤跑會在 prod 製造 25 筆 DRAFT + parser.add_argument( + "--yes", + action="store_true", + default=False, + help="跳過 --commit 的二次確認 prompt(CI / 自動化用)", + ) return parser.parse_args() @@ -97,6 +105,16 @@ async def _run(args: argparse.Namespace) -> int: print(f"[ERROR] yaml 不存在: {yaml_path}", file=sys.stderr) return 1 + # 2026-04-29 critic Major #2 修:--commit 二次確認,--yes 跳過 + if not dry_run and not args.yes: + ans = input( + "⚠️ 即將寫入 prod DB(最多 25 筆 DRAFT Playbook)\n" + " Type 'yes' to confirm (or 'n' to abort): " + ).strip().lower() + if ans != "yes": + print("[ABORTED] 使用者取消(type 'yes' to confirm)", file=sys.stderr) + return 1 + report = await migrate_yaml_rules_to_playbooks( yaml_path=yaml_path, dry_run=dry_run, diff --git a/apps/api/src/services/rule_to_playbook_migrator.py b/apps/api/src/services/rule_to_playbook_migrator.py index 2e6cb98b..0aa1c6fc 100644 --- a/apps/api/src/services/rule_to_playbook_migrator.py +++ b/apps/api/src/services/rule_to_playbook_migrator.py @@ -18,7 +18,6 @@ W1 PR-R1 — 規則 → Playbook 遷移 """ from __future__ import annotations -import re from dataclasses import dataclass, field from pathlib import Path from typing import Any @@ -125,8 +124,13 @@ def _build_symptom_pattern(rule: dict[str, Any]) -> dict[str, Any]: # 過濾萬用符(generic_fallback 有 "*") keywords = [k for k in keywords if k != "*"][:15] + # 2026-04-29 ogt + Claude Opus 4.7: critic Major #1 修 + # 過濾 alert_names 中的 "*" wildcard(generic_fallback)— 否則進 RAG 向量化 + # 後變成「告警: *」污染語料,每筆查詢都會跟它算相似度 + raw_names = alertnames if isinstance(alertnames, list) else [alertnames] + filtered_names = [n for n in raw_names if n and n != "*"] return { - "alert_names": alertnames if isinstance(alertnames, list) else [alertnames], + "alert_names": filtered_names, "affected_services": [], "severity_range": severity_range, "keywords": keywords, @@ -159,6 +163,14 @@ def _build_repair_steps(rule: dict[str, Any]) -> list[dict[str, Any]]: "expected_result": resp.get("action_title", ""), "risk_level": risk_level, "requires_approval": risk_level == "CRITICAL" or suggested_action in ("RESTART_DEPLOYMENT", "DELETE_POD", "SCALE_DEPLOYMENT"), + # 2026-04-29 ogt + Claude Opus 4.7: critic Major #3 修 + # yaml_rule 來源的 kubectl_command 未經 SPF-2 action_parser 驗證 + # promote 流程(DRAFT → APPROVED)必須強制走 action_parser,否則危險指令直達 prod + "metadata": { + "unverified_command": True, + "needs_action_parser_review": True, + "source": "yaml_rule_migration", + }, }) else: # NO_ACTION — 記錄診斷描述為 manual step,讓 RAG 至少有症狀可查 @@ -172,8 +184,10 @@ def _build_repair_steps(rule: dict[str, Any]) -> list[dict[str, Any]]: "requires_approval": True, }) - # 追加 optimization steps(最多 3 個,step_number 從 2 開始) - for idx, opt in enumerate(resp.get("optimization", []) or [], start=2): + # 追加 optimization steps(最多 3 個,step_number 2/3/4) + # 2026-04-29 critic Minor 修:原 `if idx >= 4: break` 寫在 append 後易誤讀 + # 改用 [:3] slice 明確限制最多 3 個 + for idx, opt in enumerate((resp.get("optimization", []) or [])[:3], start=2): opt_cmd = (opt.get("command", "") or "").strip() if not opt_cmd or opt_cmd.startswith("#"): continue @@ -185,8 +199,6 @@ def _build_repair_steps(rule: dict[str, Any]) -> list[dict[str, Any]]: "risk_level": "LOW", "requires_approval": False, }) - if idx >= 4: # 最多 3 個 optimization steps - break return steps