From 025829434d1324fce1b4c27f62972570bdecf8cd Mon Sep 17 00:00:00 2001 From: ogt Date: Tue, 14 Jul 2026 17:00:20 +0800 Subject: [PATCH] fix(agent99): compact callback incident ids --- .../services/agent99_completion_callback.py | 19 +++++- .../test_agent99_completion_callback_api.py | 67 +++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/apps/api/src/services/agent99_completion_callback.py b/apps/api/src/services/agent99_completion_callback.py index 38073b612..4a771e36e 100644 --- a/apps/api/src/services/agent99_completion_callback.py +++ b/apps/api/src/services/agent99_completion_callback.py @@ -2,6 +2,7 @@ from __future__ import annotations +from hashlib import sha256 from typing import Any from sqlalchemy import text @@ -16,6 +17,18 @@ from src.services.channel_hub import ( record_external_alert_event, ) +_OPERATION_INCIDENT_ID_MAX_LENGTH = 30 + + +def _operation_incident_id(alert_id: str | None) -> str | None: + """Keep external alert identity while fitting the immutable event schema.""" + if not alert_id: + return None + if len(alert_id) <= _OPERATION_INCIDENT_ID_MAX_LENGTH: + return alert_id + digest = sha256(alert_id.encode("utf-8")).hexdigest().upper()[:21] + return f"INC-AG99-{digest}" + async def _load_callback_readback( *, @@ -73,6 +86,7 @@ def _operation_context(payload: Agent99CompletionCallbackRequest) -> dict[str, A "trace_id": payload.trace_id, "work_item_id": payload.work_item_id, "alert_id": payload.alert_id, + "operation_incident_id": _operation_incident_id(payload.alert_id), "correlation_key": payload.correlation_key, "source": payload.source, "mode": payload.mode, @@ -133,6 +147,7 @@ async def record_agent99_completion_callback( else "warning" ) context = _operation_context(payload) + operation_incident_id = _operation_incident_id(payload.alert_id) event_id = await record_external_alert_event( project_id=payload.project_id, provider="agent99", @@ -166,7 +181,7 @@ async def record_agent99_completion_callback( if int((before or {}).get("execution_count") or 0) == 0: await repository.append( "EXECUTION_COMPLETED", - incident_id=payload.alert_id, + incident_id=operation_incident_id, actor="agent99_completion_callback", action_detail=f"{payload.mode}:{payload.outcome_state}", success=payload.verifier_passed, @@ -175,7 +190,7 @@ async def record_agent99_completion_callback( if terminal_resolved and int((before or {}).get("resolved_count") or 0) == 0: await repository.append( "RESOLVED", - incident_id=payload.alert_id, + incident_id=operation_incident_id, actor="agent99_completion_callback", action_detail=f"{payload.mode}:verified_resolved", success=True, diff --git a/apps/api/tests/test_agent99_completion_callback_api.py b/apps/api/tests/test_agent99_completion_callback_api.py index cb69f58a6..ab1f2d2ba 100644 --- a/apps/api/tests/test_agent99_completion_callback_api.py +++ b/apps/api/tests/test_agent99_completion_callback_api.py @@ -2,6 +2,7 @@ from __future__ import annotations # ruff: noqa: E402 import os +from hashlib import sha256 from types import SimpleNamespace from uuid import UUID @@ -83,6 +84,24 @@ def test_completion_payload_rejects_secret_fields_and_full_paths() -> None: ) +def test_completion_operation_incident_id_preserves_canonical_ids() -> None: + assert callback_service._operation_incident_id(None) is None + assert ( + callback_service._operation_incident_id("INC-20260714-A1B2C3") + == "INC-20260714-A1B2C3" + ) + + +def test_completion_operation_incident_id_compacts_external_ids() -> None: + external_id = "awoooi-agent99-ae36a454-c9eb-5f2d-9e35-be852b3eac9b" + expected = f"INC-AG99-{sha256(external_id.encode('utf-8')).hexdigest().upper()[:21]}" + + compact = callback_service._operation_incident_id(external_id) + + assert compact == expected + assert len(compact) == 30 + + def test_completion_callback_rejects_wrong_token(monkeypatch) -> None: monkeypatch.setattr(settings, "AGENT99_SRE_ALERT_RELAY_TOKEN", "expected") @@ -198,6 +217,54 @@ async def test_completion_service_writes_event_run_and_operation_receipts(monkey assert repository.events[0][1]["context"]["raw_log_stored"] is False +@pytest.mark.asyncio +async def test_completion_service_compacts_long_alert_id_for_operation_log( + monkeypatch, +) -> None: + external_alert_id = "awoooi-agent99-ae36a454-c9eb-5f2d-9e35-be852b3eac9b" + request = Agent99CompletionCallbackRequest.model_validate( + {**payload(), "alert_id": external_alert_id} + ) + event_id = UUID("3c9e8ee7-fb21-5420-afae-ef928f75a58c") + repository = FakeOperationRepository() + readbacks = iter( + [ + None, + { + "event_id": str(event_id), + "run_id": "5e5b0080-e8c9-53a7-872b-0fc1ce80f11f", + "operation_count": 2, + "execution_count": 1, + "resolved_count": 1, + }, + ] + ) + + async def fake_readback(**_kwargs): + return next(readbacks) + + async def fake_record_event(**_kwargs): + return event_id + + monkeypatch.setattr(callback_service, "_load_callback_readback", fake_readback) + monkeypatch.setattr(callback_service, "record_external_alert_event", fake_record_event) + monkeypatch.setattr( + callback_service, + "get_alert_operation_log_repository", + lambda: repository, + ) + + receipt = await callback_service.record_agent99_completion_callback(request) + + compact_id = callback_service._operation_incident_id(external_alert_id) + assert receipt["durable_readback"] is True + assert compact_id is not None + assert len(compact_id) == 30 + assert {event[1]["incident_id"] for event in repository.events} == {compact_id} + assert repository.events[0][1]["context"]["alert_id"] == external_alert_id + assert repository.events[0][1]["context"]["operation_incident_id"] == compact_id + + @pytest.mark.asyncio async def test_completion_service_is_idempotent(monkeypatch) -> None: request = Agent99CompletionCallbackRequest.model_validate(payload())