fix(service): #123 proposal_service.py 修正 key prefix + 移除重複邏輯
ADR-046 修復: proposal_service 使用錯誤 Redis key prefix "incident:" (brain 使用 "awoooi:incidents:"),導致 R-R2 後 load/persist 失效。 變更: - _load_incident(): 委派給 IncidentEngineAdapter.get_incident() (正確 key prefix,含 brain→local 型別轉換) - _persist_incident(): Redis 部分委派給 brain DualIncidentMemory 透過 local_to_brain() 轉換後儲存 (key prefix 一致) - 移除 _record_to_incident() 重複邏輯 (已由 IncidentEngineAdapter 處理) - 移除 INCIDENT_KEY_PREFIX 常數 - 移除 get_redis() 直接依賴 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,11 +19,9 @@ Decision Proposal Service - Phase 6.4 決策輸出層
|
||||
"""
|
||||
|
||||
from datetime import UTC, datetime
|
||||
from uuid import UUID
|
||||
|
||||
import structlog
|
||||
|
||||
from src.core.redis_client import get_redis
|
||||
from src.db.base import get_db_context
|
||||
from src.db.models import IncidentRecord
|
||||
from src.models.approval import (
|
||||
@@ -42,8 +40,11 @@ from src.models.incident import (
|
||||
Severity,
|
||||
)
|
||||
from src.services.approval_db import get_approval_service
|
||||
from src.services.incident_engine import get_incident_engine
|
||||
from src.services.incident_memory import get_incident_memory
|
||||
from src.services.openclaw import get_openclaw
|
||||
from src.services.trust_engine import normalize_action_pattern, trust_engine
|
||||
from src.utils.incident_converter import local_to_brain
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
@@ -52,8 +53,6 @@ logger = structlog.get_logger(__name__)
|
||||
# Constants
|
||||
# =============================================================================
|
||||
|
||||
INCIDENT_KEY_PREFIX = "incident:"
|
||||
|
||||
# Severity → RiskLevel 對應
|
||||
SEVERITY_TO_RISK = {
|
||||
Severity.P0: ApprovalRiskLevel.CRITICAL, # P0 (critical) → CRITICAL (2 簽核)
|
||||
@@ -292,67 +291,21 @@ class ProposalService:
|
||||
|
||||
async def _load_incident(self, incident_id: str) -> Incident | None:
|
||||
"""
|
||||
載入 Incident (Redis 優先,DB 備援)
|
||||
載入 Incident (委派給 IncidentEngineAdapter)
|
||||
|
||||
ADR-046 (2026-04-01 ogt): 透過 IncidentEngineAdapter.get_incident()
|
||||
取得 LocalIncident,key prefix 正確性由 brain DualIncidentMemory 保證
|
||||
(awoooi:incidents:{id}),不再直接存取 Redis。
|
||||
"""
|
||||
redis_client = get_redis()
|
||||
key = f"{INCIDENT_KEY_PREFIX}{incident_id}"
|
||||
|
||||
# 1. 嘗試從 Redis 載入
|
||||
try:
|
||||
data = await redis_client.get(key)
|
||||
if data:
|
||||
return Incident.model_validate_json(data)
|
||||
return await get_incident_engine().get_incident(incident_id)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"redis_load_failed",
|
||||
"load_incident_failed",
|
||||
incident_id=incident_id,
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
# 2. 從 DB 載入
|
||||
try:
|
||||
async with get_db_context() as db:
|
||||
from sqlalchemy import select
|
||||
|
||||
stmt = select(IncidentRecord).where(
|
||||
IncidentRecord.incident_id == incident_id
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
record = result.scalar_one_or_none()
|
||||
|
||||
if record:
|
||||
return self._record_to_incident(record)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"db_load_failed",
|
||||
incident_id=incident_id,
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def _record_to_incident(self, record: IncidentRecord) -> Incident:
|
||||
"""將 DB Record 轉換為 Incident"""
|
||||
from src.models.incident import Signal
|
||||
|
||||
signals = [
|
||||
Signal.model_validate(s) for s in (record.signals or [])
|
||||
]
|
||||
|
||||
return Incident(
|
||||
incident_id=record.incident_id,
|
||||
status=IncidentStatus(record.status.lower()),
|
||||
severity=Severity(record.severity),
|
||||
signals=signals,
|
||||
affected_services=record.affected_services or [],
|
||||
proposal_ids=[UUID(pid) for pid in (record.proposal_ids or [])],
|
||||
decision_chain=None, # DB 不存完整決策鏈
|
||||
outcome=None, # DB 存結果狀態而非完整 outcome
|
||||
created_at=record.created_at,
|
||||
updated_at=record.updated_at,
|
||||
resolved_at=record.resolved_at,
|
||||
closed_at=record.closed_at,
|
||||
)
|
||||
return None
|
||||
|
||||
# =========================================================================
|
||||
# 輔助方法: 決定修復動作
|
||||
@@ -466,17 +419,15 @@ class ProposalService:
|
||||
async def _persist_incident(self, incident: Incident) -> None:
|
||||
"""
|
||||
更新 Incident 到 Redis + DB
|
||||
"""
|
||||
redis_client = get_redis()
|
||||
key = f"{INCIDENT_KEY_PREFIX}{incident.incident_id}"
|
||||
|
||||
# 1. 更新 Redis
|
||||
ADR-046 (2026-04-01 ogt): Redis 委派給 brain DualIncidentMemory
|
||||
確保 key prefix 一致性 (awoooi:incidents:{id})。
|
||||
DB 直接更新 status + proposal_ids (最小化變更)。
|
||||
"""
|
||||
# 1. 更新 Redis (委派給 brain DualIncidentMemory)
|
||||
try:
|
||||
await redis_client.set(
|
||||
key,
|
||||
incident.model_dump_json(),
|
||||
ex=604800, # 7 days
|
||||
)
|
||||
brain_incident = local_to_brain(incident)
|
||||
await get_incident_memory().save_incident(brain_incident)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"redis_persist_failed",
|
||||
|
||||
Reference in New Issue
Block a user