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:
OG T
2026-03-26 19:44:59 +08:00
parent 42bf6a8729
commit fb03430469
2 changed files with 68 additions and 42 deletions

View File

@@ -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: 發布事件