feat(api): ADR-027 Phase 2 - 簽核/拒絕後自動同步 Incident 狀態
Router 整合點:
- POST /approvals/{id}/sign → on_approval_status_change("approved")
- POST /approvals/{id}/reject → on_approval_status_change("rejected")
- POST /approvals/bulk-approve → 批次同步
變更:
- 移除舊的 resolve_incident_after_approval() 調用
- 改用 IncidentApprovalService.on_approval_status_change()
- 同步失敗不阻斷主流程 (容錯設計)
ADR-027 進度: Phase 1-2 ✅ 完成
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,7 @@ from src.models.approval import (
|
||||
from src.services.approval_db import get_approval_service, get_timeline_service
|
||||
from src.services.approval_execution import get_execution_service
|
||||
from src.services.executor import get_executor
|
||||
from src.services.incident_approval_service import get_incident_approval_service
|
||||
from src.services.proposal_service import get_proposal_service
|
||||
|
||||
router = APIRouter(prefix="/approvals", tags=["HITL Approvals"])
|
||||
@@ -357,41 +358,26 @@ async def sign_approval(
|
||||
execution_svc = get_execution_service()
|
||||
background_tasks.add_task(execution_svc.execute_approved_action, approval)
|
||||
|
||||
# Phase 6.5: 更新關聯的 Incident 狀態為 RESOLVED
|
||||
# 🔴 關鍵: 這是審核後 Incident 狀態更新的核心邏輯
|
||||
incident_id = approval.metadata.get("incident_id") if approval.metadata else None
|
||||
logger.info(
|
||||
"sign_approval_checking_incident",
|
||||
approval_id=str(approval_id),
|
||||
has_metadata=approval.metadata is not None,
|
||||
incident_id=incident_id,
|
||||
metadata_keys=list(approval.metadata.keys()) if approval.metadata else [],
|
||||
)
|
||||
|
||||
if incident_id:
|
||||
proposal_svc = get_proposal_service()
|
||||
resolve_success = await proposal_svc.resolve_incident_after_approval(
|
||||
incident_id=incident_id,
|
||||
# Phase 6.5 + ADR-027: 更新關聯的 Incident 狀態為 RESOLVED
|
||||
# 🔴 2026-03-26: 使用 IncidentApprovalService 確保原子同步
|
||||
try:
|
||||
sync_service = get_incident_approval_service()
|
||||
await sync_service.on_approval_status_change(
|
||||
approval_id=str(approval_id),
|
||||
new_status="approved",
|
||||
)
|
||||
logger.info(
|
||||
"incident_resolved_after_sign",
|
||||
incident_id=incident_id,
|
||||
"incident_approval_synced",
|
||||
approval_id=str(approval_id),
|
||||
success=resolve_success,
|
||||
new_status="approved",
|
||||
)
|
||||
if not resolve_success:
|
||||
logger.error(
|
||||
"incident_resolve_failed",
|
||||
incident_id=incident_id,
|
||||
approval_id=str(approval_id),
|
||||
note="Incident status may not be updated correctly",
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"sign_approval_no_incident_id",
|
||||
except Exception as e:
|
||||
# 同步失敗不阻斷主流程,但記錄錯誤
|
||||
logger.error(
|
||||
"incident_approval_sync_failed",
|
||||
approval_id=str(approval_id),
|
||||
note="Approval has no incident_id in metadata, cannot update Incident status",
|
||||
error=str(e),
|
||||
note="Incident status may not be updated correctly",
|
||||
)
|
||||
|
||||
# SSE: 發布簽核/核准事件
|
||||
@@ -483,6 +469,25 @@ async def reject_approval(
|
||||
reason=request.reason,
|
||||
)
|
||||
|
||||
# ADR-027: 同步更新 Incident 狀態為 rejected
|
||||
try:
|
||||
sync_service = get_incident_approval_service()
|
||||
await sync_service.on_approval_status_change(
|
||||
approval_id=str(approval_id),
|
||||
new_status="rejected",
|
||||
)
|
||||
logger.info(
|
||||
"incident_approval_synced",
|
||||
approval_id=str(approval_id),
|
||||
new_status="rejected",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"incident_approval_sync_failed",
|
||||
approval_id=str(approval_id),
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
# SSE: 發布拒絕事件
|
||||
asyncio.create_task(_publish_approval_event("rejected", approval))
|
||||
|
||||
@@ -614,13 +619,18 @@ async def bulk_approve(
|
||||
bulk_execution_svc = get_execution_service()
|
||||
background_tasks.add_task(bulk_execution_svc.execute_approved_action, signed_approval)
|
||||
|
||||
# 更新關聯的 Incident 狀態
|
||||
incident_id = signed_approval.metadata.get("incident_id") if signed_approval.metadata else None
|
||||
if incident_id:
|
||||
proposal_svc = get_proposal_service()
|
||||
await proposal_svc.resolve_incident_after_approval(
|
||||
incident_id=incident_id,
|
||||
# ADR-027: 同步更新 Incident 狀態
|
||||
try:
|
||||
sync_service = get_incident_approval_service()
|
||||
await sync_service.on_approval_status_change(
|
||||
approval_id=approval_id_str,
|
||||
new_status="approved",
|
||||
)
|
||||
except Exception as sync_err:
|
||||
logger.error(
|
||||
"bulk_incident_sync_failed",
|
||||
approval_id=approval_id_str,
|
||||
error=str(sync_err),
|
||||
)
|
||||
|
||||
# SSE: 發布事件
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# ADR-027: Incident-Approval 同步架構
|
||||
|
||||
**狀態**: 批准
|
||||
**狀態**: 實作中 (Phase 1-2 ✅ 完成)
|
||||
**日期**: 2026-03-26 (台北時區)
|
||||
**決策者**: 統帥
|
||||
**觸發**: 活躍事件顯示 0 + Telegram 告警鏈異常 (2026-03-26 首席架構師審查)
|
||||
@@ -191,12 +191,28 @@ APPROVAL_TO_INCIDENT_STATUS = {
|
||||
|
||||
## 四階段實作計畫
|
||||
|
||||
| Phase | 內容 | 估時 | 優先級 |
|
||||
|-------|------|------|--------|
|
||||
| 1 | UnitOfWork + IncidentApprovalService | 3-4h | P0 |
|
||||
| 2 | 狀態同步 Hook + TTL 延長 | 2-3h | P0 |
|
||||
| 3 | 分散式鎖 + TTL 同步 | 2-3h | P1 |
|
||||
| 4 | 整合測試 | 2h | P1 |
|
||||
| Phase | 內容 | 估時 | 狀態 |
|
||||
|-------|------|------|------|
|
||||
| 1 | UnitOfWork + IncidentApprovalService | 3-4h | ✅ 完成 |
|
||||
| 2 | 狀態同步 Hook + TTL 延長 | 2-3h | ✅ 完成 |
|
||||
| 3 | 分散式鎖 + TTL 同步 | 2-3h | 🔲 待做 |
|
||||
| 4 | 整合測試 | 2h | 🔲 待做 |
|
||||
|
||||
### 實作完成檔案 (2026-03-26)
|
||||
|
||||
| 檔案 | 功能 | 行數 |
|
||||
|------|------|------|
|
||||
| `apps/api/src/core/constants.py` | TTL + Redis Key 常量 | 38 |
|
||||
| `apps/api/src/core/unit_of_work.py` | PostgreSQL 事務管理 | 138 |
|
||||
| `apps/api/src/services/incident_approval_service.py` | 原子同步服務 | 470 |
|
||||
|
||||
### Router 整合點
|
||||
|
||||
| 端點 | 整合方式 |
|
||||
|------|----------|
|
||||
| `POST /approvals/{id}/sign` | `on_approval_status_change("approved")` |
|
||||
| `POST /approvals/{id}/reject` | `on_approval_status_change("rejected")` |
|
||||
| `POST /approvals/bulk-approve` | `on_approval_status_change("approved")` |
|
||||
|
||||
**總估時**: 9-12h
|
||||
|
||||
|
||||
Reference in New Issue
Block a user