From ec7e45d538283cd4605f45e000395fb5dd8cdeee Mon Sep 17 00:00:00 2001 From: OG T Date: Tue, 24 Mar 2026 10:39:22 +0800 Subject: [PATCH] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=BE=A9=20Incident-Appro?= =?UTF-8?q?val=20=E7=8B=80=E6=85=8B=E5=90=8C=E6=AD=A5=20BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔴 P0 核心功能修復: 問題: 審核後頁面重整,Y/n 按鈕重複出現 根因: resolve_incident_after_approval 在 Redis 缺失時靜默跳過 修復: 1. proposal_service.py - 處理 Redis 缺失情況 2. approvals.py - 添加詳細日誌追蹤 3. 設定 resolved_at 時間戳 防禦性增強: - 日誌記錄 metadata 內容 - 記錄 resolve 成功/失敗狀態 - 警告無 incident_id 的情況 長期規範: - 新增 feedback_incident_approval_sync.md 記憶 - 更新 HARD_RULES.md API 路徑規範 Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 1 + apps/api/src/api/v1/approvals.py | 25 ++++++++++++++++++++++- apps/api/src/services/proposal_service.py | 16 ++++++++++++++- docs/HARD_RULES.md | 20 ++++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) 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`