Files
awoooi/docs/adr/ADR-046-incident-type-unification.md
OG T a94bb57d8b feat(types): ADR-046 IncidentConverter + IncidentEngineAdapter
實作 ADR-046 Option B: IncidentConverter 轉換層,解決
BrainIncident (lewooogo-brain) 與 LocalIncident (apps/api) 型別邊界問題。

變更:
- 新增 src/utils/incident_converter.py
  - brain_to_local(): BrainIncident → LocalIncident
  - local_to_brain(): LocalIncident → BrainIncident
  - ESCALATED → MITIGATING 映射 (brain 無 ESCALATED)
- incident_engine.py: 新增 IncidentEngineAdapter 包裝層
  - process_signal() / get_incident() 輸出轉換為 LocalIncident
  - get_incident_engine() 返回 IncidentEngineAdapter
- incident_memory.py: 加入 brain_to_local import,更新 _record_to_incident 說明
- ADR-046: 標記三個轉換點全部完成

解鎖: #123 proposal_service.py 清理 (下一步)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 22:47:54 +08:00

4.2 KiB
Raw Blame History

ADR-046: 跨套件 Incident 型別統一治理

項目 內容
狀態 決策完成 (Option B 採用)
日期 2026-04-01
決策者 首席架構師 + 統帥
觸發 Phase R-R2.1 架構審查 P2-01

背景

Phase R-R2 完成後,系統中同時存在兩種 Incident 型別:

型別 來源 欄位
src.models.incident.Incident 本地 (apps/api) decision_chain, outcome, ttl_days, vectorized, persisted_to_pg
lewooogo_brain.interfaces.incident_processor.Incident (BrainIncident) lewooogo-brain 套件 精簡欄位,無 decision_chain 等

問題根源

IncidentDbAdapter._record_to_incident() 返回 BrainIncident,但靜態型別宣告為本地 IncidentIncidentMemoryAdapterload_incident() 通過 brain.DualIncidentMemory 返回 BrainIncident。 部分 Service 層(如 proposal_service.pyincident_service.py)期望本地 Incident 型別, 導致 AttributeError 風險或靜態分析誤報。

三種方案

Option A本地 Incident 繼承 BrainIncident

# src/models/incident.py
from lewooogo_brain.interfaces.incident_processor import Incident as BrainIncident

class Incident(BrainIncident):
    """AWOOOI 本地擴展 Incident"""
    decision_chain: AIDecisionChain | None = None
    ttl_days: int = 30
    vectorized: bool = False
    persisted_to_pg: bool = False
  • 型別統一,靜態分析通過
  • ⚠️ 強依賴 lewooogo-brain 套件版本
  • ⚠️ BrainIncident 變更會影響本地

Option B建立 IncidentConverter 轉換層

# src/utils/incident_converter.py
def brain_to_local(brain_incident: BrainIncident) -> Incident: ...
def local_to_brain(local_incident: Incident) -> BrainIncident: ...
  • 兩者保持獨立,變更不互相影響
  • 轉換點明確,可審計
  • ⚠️ 每次跨邊界需顯式轉換,增加樣板代碼

Option C逐步廢棄本地 Incident,全面切換至 BrainIncident

  • 最終架構最簡潔
  • 🔴 需要修改所有使用本地 Incident 的程式碼30+ 處)
  • 🔴 proposal_service、incident_service 等需要大規模重構

建議

推薦 Option BIncidentConverter 轉換層),理由:

  1. 套件邊界清晰,符合 leWOOOgo 積木化原則
  2. 遷移風險最低,可漸進式採用
  3. 若未來棄用 lewooogo-brain只需修改 Converter

短期緩解(已實施):

  • _record_to_incident 返回型別標注為 AnyP0-01 修復)
  • 不阻擋現有業務邏輯

決策結果 (2026-04-01 首席架構師審查)

採用 Option BIncidentConverter 轉換層

首席架構師審查評分 72/100 (條件通過),確認 Option B 為最佳方案。

緊急修復已完成 (P2-01)

  • signal_worker.py:402persisted_to_pg 改為 getattr(incident, "persisted_to_pg", False)

已實作 (Phase R-R3 Sprint - 2026-04-01)

  • 建立 src/utils/incident_converter.py
    • brain_to_local(BrainIncident) -> Incident
    • local_to_brain(Incident) -> BrainIncident
  • IncidentDbAdapter._record_to_incident 返回型別說明更新(仍返回 BrainIncident供 brain 內部使用)
  • IncidentEngineAdapter 包裝層建立(incident_engine.py
    • process_signal() → brain_to_local 轉換
    • get_incident() → brain_to_local 轉換
    • update_status() → 直接委派
  • get_incident_engine() 返回 IncidentEngineAdapter(輸出為 LocalIncident
  • #123 proposal_service.py 清理 → 依賴此 Converter下一步執行
  • 移除 USE_NEW_ENGINE config 項 (已標記為失效Phase R-R4 後清理)

三個跨邊界轉換點

位置 方向 狀態
IncidentDbAdapter._record_to_incident() DB→BrainIncident (brain 內部) 邊界清晰
IncidentEngineAdapter.process_signal() BrainIncident→LocalIncident 已實作
IncidentEngineAdapter.get_incident() BrainIncident→LocalIncident 已實作

相關文件

  • ADR-024: API 分層架構 (Phase 16 絞殺者模式)
  • ADR-003: leWOOOgo Module Architecture
  • feedback_lewooogo_modular_enforcement.md
  • Phase R-R2.1 架構審查報告 (commit d17b67c)