diff --git a/CLAUDE.md b/CLAUDE.md index f8458446..2cfc961b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,6 +60,7 @@ | API 整合 | `feedback_api_response_verification.md` | | 構建部署 | `feedback_build_from_git_only.md` | | **測試** | `feedback_no_mock_testing.md` 🔴🔴 禁止 Mock | +| **API 路徑** | `feedback_api_path_naming.md` 🔴 修改需同步前端 | ## Skills 載入 diff --git a/apps/api/src/api/v1/approvals.py b/apps/api/src/api/v1/approvals.py index ab5aafb7..b6f87c73 100644 --- a/apps/api/src/api/v1/approvals.py +++ b/apps/api/src/api/v1/approvals.py @@ -631,10 +631,19 @@ async def sign_approval( background_tasks.add_task(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() - await proposal_svc.resolve_incident_after_approval( + resolve_success = await proposal_svc.resolve_incident_after_approval( incident_id=incident_id, approval_id=str(approval_id), ) @@ -642,6 +651,20 @@ async def sign_approval( "incident_resolved_after_sign", incident_id=incident_id, approval_id=str(approval_id), + success=resolve_success, + ) + 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", + approval_id=str(approval_id), + note="Approval has no incident_id in metadata, cannot update Incident status", ) return SignResponse( diff --git a/apps/api/src/services/proposal_service.py b/apps/api/src/services/proposal_service.py index 490b89ff..ec92b3a5 100644 --- a/apps/api/src/services/proposal_service.py +++ b/apps/api/src/services/proposal_service.py @@ -553,6 +553,7 @@ class ProposalService: incident = Incident.model_validate_json(data) old_status = incident.status.value incident.status = IncidentStatus.RESOLVED + incident.resolved_at = datetime.now(UTC) incident.updated_at = datetime.now(UTC) if incident.decision: incident.decision.state = "completed" @@ -564,6 +565,15 @@ class ProposalService: old_status=old_status, new_status="resolved", ) + else: + # 🔴 關鍵修復: Redis 沒有 Incident 時,從 DB 讀取並更新 + logger.warning( + "resolve_incident_redis_miss", + incident_id=incident_id, + note="Incident not found in Redis, will update DB only", + ) + # 仍然標記為成功,讓 DB 更新繼續執行 + redis_ok = True except Exception as e: logger.exception( "resolve_incident_redis_error", @@ -572,6 +582,7 @@ class ProposalService: ) # 2. 更新 DB (如果存在) + now = datetime.now(UTC) try: async with get_db_context() as db: stmt = select(IncidentRecord).where( @@ -581,12 +592,15 @@ class ProposalService: record = result.scalar_one_or_none() if record: record.status = "resolved" - record.updated_at = datetime.now(UTC) + record.resolved_at = now + record.updated_at = now + # 🔴 關鍵: 確保 commit 成功 await db.commit() db_ok = True logger.info( "resolve_incident_db_updated", incident_id=incident_id, + resolved_at=now.isoformat(), ) else: # DB 沒有記錄但 Redis 有 - 這是可接受的狀態 diff --git a/docs/HARD_RULES.md b/docs/HARD_RULES.md index 059d90ed..84d1249c 100644 --- a/docs/HARD_RULES.md +++ b/docs/HARD_RULES.md @@ -15,6 +15,7 @@ | 架構 | 刪除 OpenClaw | OpenClaw 是核心 | [→ OpenClaw](#openclaw) | | Git | `--force` | 正常 push | [→ Git Safety](#git-safety) | | **測試** | **Mock 測試** | **真實 DB/服務** | [→ No Mock Testing](#no-mock-testing) | +| **API** | **單獨改路徑** | **前後端同步** | [→ API Path Naming](#api-path-naming) | --- @@ -147,6 +148,25 @@ git revert --- +## API Path Naming + +**Memory:** `~/.claude/projects/-Users-ogt-awoooi/memory/feedback_api_path_naming.md` + +```python +# ❌ 禁止 - 單獨修改後端路徑 +@router.get("/ai-performance") # 改成 /incidents/ai-performance +# 但前端仍調用 /ai-performance → 404 + +# ✅ 正確 - 前後端同步修改 +# 1. 後端: @router.get("/incidents/ai-performance") +# 2. 前端: await fetch('/api/v1/stats/incidents/ai-performance') +# 3. 測試: curl 驗證 +``` + +**原因:** 路徑變更是破壞性變更,必須同時更新前後端。 + +--- + ## No Mock Testing **Memory:** `~/.claude/projects/-Users-ogt-awoooi/memory/feedback_no_mock_testing.md`