fix(critic-hotfix): 4 修補 critic BLOCKER + HIGH(CD 阻塞 + 飛輪空轉)
Some checks are pending
CD Pipeline / build-and-deploy (push) Has started running

Critic 全面審查 6 個 commit 後抓出:

CD 阻塞修復:
- test_ai_router_failover_integration.py: 3 個 test 改用 patch.object 直接
  mock _select_provider_and_model 強制初始 OLLAMA。原 IntentType.UNKNOWN mock
  在 router 內仍被 reclassify 成 DIAGNOSE → openclaw_nemo,failover 不觸發。
  → 5/5 PASSED

BLOCKER B1 — Gitea Telegram 通知永遠發不出去:
- apps/api/src/api/v1/gitea_webhook.py:399
  redis = await get_redis()  →  redis = get_redis()
  原 await 會 raise TypeError 被外層 except 吞 → Task C PR merged + workflow_run
  failure 通知全部失效(CI 綠燈是假象,test 只驗 HTTP 202 不驗實際送達)

BLOCKER B2 — P1.3+P1.4 學習鏈閉環空轉(兩處同 bug):
- apps/api/src/api/v1/webhooks.py:261
- apps/api/src/services/approval_execution.py:771(pre-existing)
  EvidenceSnapshot.get_latest_snapshot(...) 是 module-level async function
  不是 classmethod → AttributeError 被 except 吞成 warning
  → 飛輪閉環假性接通實際空跑(feature flag default off 暫時免爆)

HIGH H3 — main.py lifespan 順序競爭:
- apps/api/src/main.py: configure_alerter() 移到 _recovery_svc.start() 之前
  原順序:start() 觸發 immediate-check → 可能呼叫 alert_recovery,但 alerter
  尚未注入 Redis → dedup fail-open,重複告警風險。

HIGH H1 — Gemini quota dedup 跨日吞告警:
- apps/api/src/services/failover_alerter.py:89
  dedup key 加 :{YYYY-MM-DD} 後綴,每日獨立 dedup window
  原昨 22:00 觸發,今 21:30 再觸發時 dedup 還沒過期會被吞掉

Tests: 14 passed (failover_alerter + ai_router_failover_integration + lifespan_wiring)

延後 follow-up:
- H2: proactive_inspector memory metric 改名 + baseline 清理
- H4: probe_success NaN fallback
- M1-M4 / S1-S2: 見 critic 報告

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-04-26 20:39:53 +08:00
parent dcf2750b2b
commit 32affaffeb
6 changed files with 52 additions and 64 deletions

View File

@@ -751,7 +751,9 @@ class ApprovalExecutionService:
try:
from src.services.incident_service import get_incident_service
from src.services.post_execution_verifier import get_post_execution_verifier
from src.services.evidence_snapshot import EvidenceSnapshot
# 2026-04-26 critic-B2 hotfix by Claude Opus 4.7
# get_latest_snapshot 是 module-level async function不是 EvidenceSnapshot classmethod
from src.services.evidence_snapshot import get_latest_snapshot
incident_svc = get_incident_service()
# 2026-04-25 修復 L1IncidentService 沒有 get_incident() 方法
@@ -768,7 +770,7 @@ class ApprovalExecutionService:
return
# 取最新 EvidenceSnapshot若 Phase 1 flag 有啟動才會有)
snapshot = await EvidenceSnapshot.get_latest_snapshot(approval.incident_id)
snapshot = await get_latest_snapshot(approval.incident_id)
verifier = get_post_execution_verifier()
verification_result = await verifier.verify(

View File

@@ -85,15 +85,18 @@ class FailoverAlerter:
logger.info("recovery_alert_sent", from_provider=from_provider)
async def alert_gemini_quota_exceeded(self, event: dict[str, Any]) -> None:
"""Gemini 每日上限觸發,降級到 188 CPU 備援 — 24h dedup"""
dedup_key = "alert:gemini_quota_exceeded"
"""Gemini 每日上限觸發,降級到 188 CPU 備援 — 24h dedup(每日重置)"""
# 2026-04-26 critic-H1 hotfix by Claude Opus 4.7 — dedup key 加日期後綴
# 原 key 常數 + 24h TTL 會吞跨日告警(昨 22:00 觸發,今 21:30 再觸發時 dedup 還沒過期)
# 修法key 加 YYYY-MM-DD每日獨立 dedup windowTTL 縮短到 8h 足夠當日內防重複
date_str = datetime.now(TAIPEI_TZ).date().isoformat()
dedup_key = f"alert:gemini_quota_exceeded:{date_str}"
if not await self._check_dedup(dedup_key, ttl=QUOTA_DEDUP_TTL_SEC):
logger.debug("quota_alert_dedup_skipped")
logger.debug("quota_alert_dedup_skipped", date=date_str)
return
quota = event.get("quota", "?")
current_count = event.get("current_count", "?")
date_str = datetime.now(TAIPEI_TZ).date().isoformat()
msg = (
f"*Gemini 每日配額耗盡*\n\n"