diff --git a/apps/api/src/services/channel_event_dossier_service.py b/apps/api/src/services/channel_event_dossier_service.py index 8cf7856d..84d38404 100644 --- a/apps/api/src/services/channel_event_dossier_service.py +++ b/apps/api/src/services/channel_event_dossier_service.py @@ -39,6 +39,16 @@ def _as_dict(value: Any) -> dict[str, Any]: return value if isinstance(value, dict) else {} +def _json_safe(value: Any) -> Any: + if isinstance(value, dict): + return {str(key): _json_safe(item) for key, item in value.items()} + if isinstance(value, (list, tuple, set)): + return [_json_safe(item) for item in value] + if value is None or isinstance(value, (str, int, float, bool)): + return value + return str(value) + + def _compact_ref_count(source_refs: dict[str, Any]) -> int: total = 0 for value in source_refs.values(): @@ -905,7 +915,7 @@ async def _record_recurrence_work_item_dry_run_history( actor="awooop_recurrence_work_item_service", action_detail=f"recurrence_work_item_dry_run:{payload.get('mode')}"[:200], success=allowed, - context=context, + context=_json_safe(context), ) if record is not None: history["alert_operation_id"] = getattr(record, "id", None) @@ -978,7 +988,7 @@ async def _record_recurrence_work_item_handoff_history( f"recurrence_work_item_handoff:{payload.get('handoff_kind')}" )[:200], success=allowed, - context=context, + context=_json_safe(context), ) if record is not None: history["alert_operation_id"] = getattr(record, "id", None) diff --git a/apps/api/tests/test_channel_event_dossier_service.py b/apps/api/tests/test_channel_event_dossier_service.py index 32de9e2c..d1c8c084 100644 --- a/apps/api/tests/test_channel_event_dossier_service.py +++ b/apps/api/tests/test_channel_event_dossier_service.py @@ -606,6 +606,7 @@ async def test_source_correlation_dry_run_history_records_without_incident( "sentry:upstream_canary:" "awoooi-canary-codex-t115-production" ), + "latest_run_id": UUID("11111111-1111-4111-8111-111111111111"), }, "ticket_preview": {"would_create": False}, "read_model_route": { @@ -629,6 +630,10 @@ async def test_source_correlation_dry_run_history_records_without_incident( assert appended[0]["incident_id"] is None assert appended[0]["actor"] == "awooop_recurrence_work_item_service" assert appended[0]["context"]["work_item_id"].startswith("source-evidence:") + assert ( + appended[0]["context"]["current_state_summary"]["latest_run_id"] + == "11111111-1111-4111-8111-111111111111" + ) def test_build_recurrence_work_item_handoff_records_ticket_proposal_contract() -> None: diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index eb21f745..445dfd17 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -14,6 +14,7 @@ - `reason=provider_native_evidence_unlinked` - 不寫入 Incident / AutoRepair / Ticket,只提供 preview / dry-run / handoff read model。 - Source review dry-run / handoff 沒有 Incident 時仍會寫入 `alert_operation_log`(`incident_id=null`),避免 operator 看到 `missing_incident_id` 誤判為沒有 DB audit;timeline 仍只在有 Incident 時寫入。 +- Recurrence work item audit context 會先做 JSON-safe 轉換,避免 UUID / datetime 這類 DB row 型別讓 `alert_operation_log.context` 寫入失敗。 - `/api/v1/platform/events/dossier/recurrence` summary 新增 `source_correlation_review_group_total`。 - AwoooP Runs 前端「重複告警關聯」新增「來源待審」指標,卡片顯示事件 stage,讓 operator 可看見 provider-native evidence 已進 AwoooP 但仍需配對審核。 - AwoooP Work Items 同步顯示 source review count、stage、provider event id、Sentry / SignOz refs,避免從 Runs 點進工作項後掉成 unknown。 @@ -47,7 +48,8 @@ python -m ruff check src/services/channel_event_dossier_service.py src/api/v1/pl - 前端 AI 自動化管理介面同步:99.99%(Runs / Work Items recurrence panel 已同步來源待審)。 - 完整 AI 自動化管理產品化:99.65% → 99.68%。 - 第一輪部署:Gitea Actions 1952 Code Review success;1951 CD success;production recurrence schema 已出現 `source_correlation_review_group_total` / `latest_stage` / `stage_counts`。 -- 剩餘:source review audit 小修再推 Gitea main,等待第二輪 CI/CD、production dry-run history 驗證。 +- 第二輪部署:Gitea Actions 1954 Code Review success;1953 CD in progress;production dry-run 已進入新分支但 alert_operation_log context 需 JSON-safe follow-up。 +- 剩餘:JSON-safe audit follow-up 再推 Gitea main,等待第三輪 CI/CD、production dry-run history 驗證。 ## 2026-05-20|T115 Provider-native upstream canary 接入