Files
awoooi/apps/api/scripts/migrate_rules_to_playbooks.py
Your Name 8d24f15183
Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 1m34s
fix(critic-review): PR-R1 4 Major 修 — wildcard 過濾 + 二次確認 + unverified 旗標
critic PR review 681b5ac9 揭示 4 Major 問題(無 Critical),全部修復。

## Major #1 — generic_fallback wildcard 污染 RAG 語料
位置:rule_to_playbook_migrator.py:128 `_build_symptom_pattern`

問題:generic_fallback 規則的 `alert_names=["*"]` 會原樣寫入 PlaybookRecord,
進 playbook_rag 向量化文字「告警: *」變成普通 token,每筆查詢都會跟它算相似度
→ RAG top-k 可能回 fallback DRAFT 誤導推薦。

修法:在 `_build_symptom_pattern` 過濾 `["*"]`(與 keywords 一致對待)。

## Major #2 — CLI --commit 無二次確認
位置:scripts/migrate_rules_to_playbooks.py

問題:`--commit` 直接寫 prod DB 25 筆 DRAFT,誤跑無法回頭。

修法:
- 加 `--yes` flag(CI / 自動化用)
- 沒帶 `--yes` 時 stdin prompt: "Type 'yes' to confirm"

## Major #3 — yaml_rule kubectl_command 未過 SPF-2 action_parser
位置:rule_to_playbook_migrator.py:153 `_build_repair_steps`

問題:DRAFT 不會自動 promote(門檻 0.9),但人工 review 路徑無安全攔截器。
若有人 UI 一鍵 promote → 含 {target} placeholder 的危險指令直接到 prod。

修法:在 step dict 加 metadata:
- unverified_command: True
- needs_action_parser_review: True
- source: "yaml_rule_migration"
(promote 流程須強制走 action_parser,由 SPF-2 落地時實作)

## Minor 修
- 刪除 dead import `import re`(未使用)
- `enumerate([:3], start=2)` 取代 `if idx >= 4: break`(邊界寫法易誤讀)

## 驗證
- 23 個 PR-R1 測試全綠(修法不破壞既有行為)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 10:56:32 +08:00

159 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
migrate_rules_to_playbooks.py — 規則 → Playbook 遷移 CLI
=========================================================
將 alert_rules.yaml 中的 25 條規則遷移為 DRAFT Playbook讓飛輪 RAG 有資料可查。
用法:
# 預設 dry-run只印計畫不寫 DB
python scripts/migrate_rules_to_playbooks.py
# 指定 yaml 路徑
python scripts/migrate_rules_to_playbooks.py --yaml-path /path/to/alert_rules.yaml
# 真實寫入 DB
python scripts/migrate_rules_to_playbooks.py --commit
# 完整選項
python scripts/migrate_rules_to_playbooks.py --yaml-path alert_rules.yaml --commit
W1 PR-R1 — 規則 → Playbook 遷移
2026-04-28 ogt + Claude Sonnet 4.6
"""
from __future__ import annotations
import argparse
import asyncio
import os
import sys
from pathlib import Path
# 確保 apps/api/src 在 import path 中(從 scripts/ 執行時)
_SCRIPT_DIR = Path(__file__).parent
_API_ROOT = _SCRIPT_DIR.parent
sys.path.insert(0, str(_API_ROOT))
# 預設 yaml 路徑:相對 scripts/ 的上一層apps/api/alert_rules.yaml
_DEFAULT_YAML_PATH = _API_ROOT / "alert_rules.yaml"
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="將 alert_rules.yaml 遷移為 DRAFT Playbook飛輪 RAG 冷啟動)",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
範例:
python scripts/migrate_rules_to_playbooks.py # dry-run預設
python scripts/migrate_rules_to_playbooks.py --commit # 真實寫入
python scripts/migrate_rules_to_playbooks.py --yaml-path alert_rules.yaml --commit
""",
)
parser.add_argument(
"--yaml-path",
type=Path,
default=_DEFAULT_YAML_PATH,
help=f"alert_rules.yaml 路徑(預設: {_DEFAULT_YAML_PATH}",
)
parser.add_argument(
"--commit",
action="store_true",
default=False,
help="真實寫入 DB預設 dry-run僅印計畫",
)
parser.add_argument(
"--disable-flag",
action="store_true",
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 的二次確認 promptCI / 自動化用)",
)
return parser.parse_args()
async def _run(args: argparse.Namespace) -> int:
"""
非同步主流程
Returns:
exit code (0=成功, 1=有錯誤)
"""
from src.services.rule_to_playbook_migrator import migrate_yaml_rules_to_playbooks
yaml_path: Path = args.yaml_path
dry_run: bool = not args.commit
enable_migration: bool = not args.disable_flag
# 讀取 feature flag環境變數優先CLI flag 次之)
env_flag = os.environ.get("ENABLE_RULE_MIGRATION_DRAFT", "").lower()
if env_flag == "false":
enable_migration = False
print(f"\n{'[DRY-RUN] ' if dry_run else ''}規則 → Playbook 遷移")
print(f" yaml_path: {yaml_path}")
print(f" enable_migration: {enable_migration}")
print(f" dry_run: {dry_run}")
print()
if not yaml_path.exists():
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,
enable_migration=enable_migration,
)
# 輸出報告
print("=" * 60)
print(report.summary())
print("=" * 60)
if report.created_names:
action = "待建立" if dry_run else "已建立"
print(f"\n{action} ({len(report.created_names)} 條):")
for name in report.created_names:
print(f" + {name}")
if report.skipped_names:
print(f"\n已跳過(已存在)({len(report.skipped_names)} 條):")
for name in report.skipped_names:
print(f" ~ {name}")
if report.errors:
print(f"\n[ERROR] 失敗 ({len(report.errors)} 條):", file=sys.stderr)
for err in report.errors:
print(f" ! {err}", file=sys.stderr)
if dry_run and report.created > 0:
print(f"\n提示: 加 --commit 參數執行實際寫入(將建立 {report.created} 條 DRAFT Playbook")
return 1 if report.failed > 0 else 0
def main() -> None:
args = parse_args()
exit_code = asyncio.run(_run(args))
sys.exit(exit_code)
if __name__ == "__main__":
main()