Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 1m34s
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>
159 lines
5.0 KiB
Python
159 lines
5.0 KiB
Python
#!/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 的二次確認 prompt(CI / 自動化用)",
|
||
)
|
||
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()
|