fix(api): 修復 Incident-Approval 狀態同步 BUG

🔴 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 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-24 10:39:22 +08:00
parent 6e644d4fd0
commit ec7e45d538
4 changed files with 60 additions and 2 deletions

View File

@@ -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 載入

View File

@@ -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(

View File

@@ -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 有 - 這是可接受的狀態

View File

@@ -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`