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:
OG T
2026-04-01 09:11:57 +08:00
parent a94bb57d8b
commit 44840f5e73

View File

@@ -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()
取得 LocalIncidentkey 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",