From 77d55ddedc5d29e739a400e5ffe7663366e578cb Mon Sep 17 00:00:00 2001 From: ogt Date: Wed, 15 Jul 2026 18:27:03 +0800 Subject: [PATCH] fix(agent99): persist canonical verifier run identity --- .../awooop_ansible_check_mode_service.py | 1 + ...est_ansible_incident_ledger_churn_guard.py | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/apps/api/src/services/awooop_ansible_check_mode_service.py b/apps/api/src/services/awooop_ansible_check_mode_service.py index 5e555f5d4..697266001 100644 --- a/apps/api/src/services/awooop_ansible_check_mode_service.py +++ b/apps/api/src/services/awooop_ansible_check_mode_service.py @@ -5760,6 +5760,7 @@ async def _record_post_apply_verifier_and_learning( result, verifier_receipt, ) + automation_run_id = _automation_run_id_for_claim(claim) post_state = { **verifier_receipt, "automation_run_id": automation_run_id, diff --git a/apps/api/tests/test_ansible_incident_ledger_churn_guard.py b/apps/api/tests/test_ansible_incident_ledger_churn_guard.py index fbbdc8e11..e49d73361 100644 --- a/apps/api/tests/test_ansible_incident_ledger_churn_guard.py +++ b/apps/api/tests/test_ansible_incident_ledger_churn_guard.py @@ -41,6 +41,14 @@ class _RowsResult: return self._rows +class _ScalarResult: + def __init__(self, value) -> None: + self._value = value + + def scalar(self): + return self._value + + class _SequenceDB: def __init__(self, *results) -> None: self._results = list(results) @@ -208,6 +216,64 @@ def test_backfill_accepts_only_same_run_successful_durable_verifier() -> None: ) +@pytest.mark.asyncio +async def test_post_apply_verifier_persists_canonical_same_run_identity( + monkeypatch: pytest.MonkeyPatch, +) -> None: + claim = _wazuh_claim() + apply_op_id = "00000000-0000-0000-0000-000000000203" + receipt = _durable_verifier_receipt(claim, apply_op_id=apply_op_id) + verifier_db = _SequenceDB(_ScalarResult("evidence-id")) + context_calls = 0 + + @asynccontextmanager + async def fake_db_context(_project_id: str): + nonlocal context_calls + context_calls += 1 + if context_calls == 1: + yield verifier_db + return + raise RuntimeError("KM persistence is outside this focused receipt test") + + monkeypatch.setattr(service, "get_db_context", fake_db_context) + monkeypatch.setattr( + service, + "_resolve_ansible_post_verifier_receipt", + AsyncMock(return_value=(receipt, True)), + ) + monkeypatch.setattr( + service, + "_record_apply_post_verifier_terminal", + AsyncMock(return_value=True), + ) + monkeypatch.setattr( + service, + "_record_learning_writeback_receipt", + AsyncMock(return_value=None), + ) + + result = await service._record_post_apply_verifier_and_learning( + claim, + service.AnsibleRunResult( + returncode=0, + stdout="", + stderr="", + duration_ms=5, + ), + apply_op_id=apply_op_id, + project_id="default", + durable_verifier_receipt=receipt, + ) + + post_state = json.loads(verifier_db.parameters[0]["post_execution_state"]) + assert post_state["automation_run_id"] == claim.input_payload["automation_run_id"] + assert post_state["trace_id"] == claim.input_payload["trace_id"] + assert post_state["run_id"] == claim.input_payload["run_id"] + assert result["verification"] is True + assert result["verification_passed"] is True + assert result["durable_verifier_receipt_reused"] is True + + @pytest.mark.asyncio async def test_backfill_repairs_orphan_incident_and_reuses_verifier( monkeypatch: pytest.MonkeyPatch,