diff --git a/apps/api/src/services/platform_operator_service.py b/apps/api/src/services/platform_operator_service.py index a4cd1d90..92573af4 100644 --- a/apps/api/src/services/platform_operator_service.py +++ b/apps/api/src/services/platform_operator_service.py @@ -401,7 +401,7 @@ async def list_callback_replies( total = count_result.scalar_one() rows_result = await db.execute(list_sql, params) rows = list(rows_result.mappings().all()) - summary = await _fetch_callback_reply_audit_summary( + audit_summary = await _fetch_callback_reply_audit_summary( db, project_id=project_id or "awoooi", ) @@ -446,22 +446,22 @@ async def list_callback_replies( item["awooop_status_chain"] = chain summary_cache_key = (item_project_id, incident_id) - summary = km_completion_summary_cache.get(summary_cache_key) - if summary is None: - summary = await _fetch_km_stale_completion_summary_for_incident( + km_summary = km_completion_summary_cache.get(summary_cache_key) + if km_summary is None: + km_summary = await _fetch_km_stale_completion_summary_for_incident( project_id=item_project_id, incident_id=incident_id, queue_cache=km_completion_queue_cache, ) - km_completion_summary_cache[summary_cache_key] = summary - item["km_stale_completion_summary"] = summary + km_completion_summary_cache[summary_cache_key] = km_summary + item["km_stale_completion_summary"] = km_summary return { "items": items, "total": total, "page": page, "per_page": per_page, - "summary": summary, + "summary": audit_summary, } diff --git a/apps/api/tests/test_awooop_operator_timeline_labels.py b/apps/api/tests/test_awooop_operator_timeline_labels.py index 1c44f895..4b6dbe9f 100644 --- a/apps/api/tests/test_awooop_operator_timeline_labels.py +++ b/apps/api/tests/test_awooop_operator_timeline_labels.py @@ -1,4 +1,5 @@ import asyncio +import inspect from datetime import datetime from decimal import Decimal from types import SimpleNamespace @@ -706,6 +707,15 @@ def test_list_callback_replies_response_preserves_callback_evidence() -> None: assert dumped["summary"]["snapshot_status"] == "not_captured" +def test_list_callback_replies_keeps_audit_summary_separate_from_km_summary() -> None: + source = inspect.getsource(platform_operator_service.list_callback_replies) + + assert "audit_summary = await _fetch_callback_reply_audit_summary" in source + assert '"summary": audit_summary' in source + assert "km_summary = km_completion_summary_cache.get" in source + assert 'item["km_stale_completion_summary"] = km_summary' in source + + def test_callback_reply_audit_summary_marks_missing_snapshots() -> None: summary = _callback_reply_audit_summary_from_row( { diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 64aaa7cd..e58e8553 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -20268,6 +20268,24 @@ pnpm --dir apps/web lint -- --file 'src/app/[locale]/awooop/runs/page.tsx' NEXT_PUBLIC_API_URL=https://awoooi.wooo.work pnpm --dir apps/web run build ``` +**T183b production smoke hotfix**: + +- 首次 production smoke `/api/v1/platform/runs/callback-replies?project_id=awoooi&per_page=2` 回傳 HTTP 500。 +- root cause:`list_callback_replies()` 的 API 頂層 coverage `summary` 被每筆 callback event 的 KM completion `summary` 區域變數覆蓋,FastAPI response validation 收到 `km_stale_owner_review_completion_callback_summary_v1`,因此不符合 `telegram_callback_reply_audit_summary_v1` schema。 +- 修正: + - API 頂層 coverage 改名為 `audit_summary`。 + - 每筆事件的 KM completion summary 改名為 `km_summary`。 + - response 固定回傳 `"summary": audit_summary`。 + - 新增 regression test 防止 audit summary / KM summary 再次 shadowing。 +- hotfix local validation: + +```text +python3 -m py_compile apps/api/src/services/platform_operator_service.py apps/api/tests/test_awooop_operator_timeline_labels.py +git diff --check +PYTHONPATH=. DATABASE_URL='postgresql+asyncpg://test:test@localhost/test' /Users/ogt/.pyenv/shims/pytest tests/test_awooop_operator_timeline_labels.py -q + 52 passed in 0.80s +``` + **目前整體進度**: - AwoooP 告警可觀測鏈:約 99.52%。