fix(p0.4): Playbook 學習鏈三道修復 — partial index + race防護 + 手動路徑接線

ADR-092 P0.4 Playbook EWMA 學習閉環的 DB / Repository / Service 三層修補。

DB 層 (db-expert-fix by Engineer-B):
- ApprovalRecord.matched_playbook_id 移除 index=True,改 __table_args__ partial index
  (WHERE matched_playbook_id IS NOT NULL) — 多數列 NULL,full index 浪費空間
- adr092_p1_learning_chain_rollback.sql: 純 ROLLBACK SQL(DBA 手動執行)

Repository 層:
- playbook_repository.py: SELECT FOR UPDATE 防 lost update
  避免並發 EWMA 更新覆蓋彼此

Service 層 (P0.4 修復):
- proposal_service.py: 手動審核路徑補 _try_playbook_match_id 呼叫
  decision_manager auto_execute 路徑已有此邏輯(行 2035),
  此處補手動路徑缺口,使 matched_playbook_id 可寫入 DB → EWMA 才能演化

測試:
- test_playbook_repository_race_condition.py: 3 cases SELECT FOR UPDATE 防 race
  正確阻擋並發 EWMA 更新(pass)

Note: migration SQL 待 DBA 手動執行(feedback_dev_prod_separation.md),
      不執行 alembic upgrade(statu 文件禁忌條款)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-04-26 20:19:46 +08:00
parent 55c6b4e2d9
commit e96055eef9
5 changed files with 310 additions and 19 deletions

View File

@@ -246,6 +246,12 @@ class ProposalService:
metadata["signoz_correlation"] = llm_proposal.get("signoz_correlation", "")
metadata["optimization_suggestions"] = llm_proposal.get("optimization_suggestions", [])
# 2026-04-25 P0.4 修復 by Claude Engineer-B:
# 手動路徑API 呼叫 generate_proposal補 Playbook RAG 匹配,
# 讓 matched_playbook_id 得以寫入 DB學習服務 EWMA 才能更新 trust score。
# decision_manager auto_execute 路徑已有此邏輯(行 2035此處補手動路徑缺口。
matched_pb_id: str | None = await self._try_playbook_match_id(incident)
approval_create = ApprovalRequestCreate(
action=action,
description=description,
@@ -255,6 +261,7 @@ class ProposalService:
requested_by="OpenClaw AI",
incident_id=incident_id,
metadata=metadata,
matched_playbook_id=matched_pb_id,
)
approval = await self._approval_service.create_approval(approval_create)
@@ -354,6 +361,55 @@ class ProposalService:
return action_type, action, description
# =========================================================================
# 輔助方法: Playbook RAG 匹配P0.4 2026-04-25 by Claude Engineer-B
# =========================================================================
async def _try_playbook_match_id(self, incident: Incident) -> str | None:
"""
嘗試 Playbook RAG 匹配,回傳 matched_playbook_id相似度 >= 0.85 才填)。
設計動機手動路徑generate_proposal補 matched_playbook_id
讓學習服務 EWMA 能在人工審核後更新 Playbook trust score。
邏輯與 decision_manager._try_playbook_match 相同,但只回傳 ID 不改 action。
失敗時靜默返回 None不阻塞主流程
"""
PLAYBOOK_SIMILARITY_THRESHOLD = 0.85
try:
from src.models.playbook import SymptomPattern
from src.services.playbook_service import get_playbook_service
alert_names = [s.alert_name for s in incident.signals] if incident.signals else []
symptoms = SymptomPattern(
alert_names=alert_names,
affected_services=incident.affected_services or [],
severity_range=[incident.severity.value] if incident.severity else ["P2"],
)
recommendations = await get_playbook_service().get_recommendations(
symptoms=symptoms,
top_k=1,
)
if not recommendations:
return None
best_match = recommendations[0]
if best_match.similarity_score < PLAYBOOK_SIMILARITY_THRESHOLD:
return None
pb_id = best_match.playbook.playbook_id
logger.info(
"proposal_playbook_matched",
incident_id=incident.incident_id,
playbook_id=pb_id,
similarity=best_match.similarity_score,
)
return pb_id
except Exception as e:
logger.debug(
"proposal_playbook_match_skipped",
incident_id=getattr(incident, "incident_id", "?"),
error=str(e),
)
return None
# =========================================================================
# 輔助方法: 建立 BlastRadius
# =========================================================================