From c4a77a1e519007b26bf0db86ae537ccf9fc8737b Mon Sep 17 00:00:00 2001 From: ogt Date: Sat, 11 Jul 2026 15:01:31 +0800 Subject: [PATCH] fix(agent): persist automated incident outcome truth --- .../awooop_ansible_check_mode_service.py | 16 ++++++++++++++++ apps/api/tests/test_ansible_verified_closure.py | 9 +++++++++ 2 files changed, 25 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 6742c9eca..de29d0e59 100644 --- a/apps/api/src/services/awooop_ansible_check_mode_service.py +++ b/apps/api/src/services/awooop_ansible_check_mode_service.py @@ -2475,6 +2475,15 @@ async def _record_incident_terminal_disposition( CAST(:terminal_payload AS jsonb), true ) + || CASE + WHEN :success_terminal THEN jsonb_build_object( + 'proposal_executed', + true, + 'execution_success', + true + ) + ELSE '{}'::jsonb + END AS json ), resolved_at = CASE @@ -2512,6 +2521,11 @@ async def _record_incident_terminal_disposition( readback = outcome.get("automation_terminal") if not isinstance(readback, Mapping): return None + if success_terminal and not ( + outcome.get("proposal_executed") is True + and outcome.get("execution_success") is True + ): + return None if ( readback.get("automation_run_id") != automation_run_id or readback.get("apply_op_id") != apply_op_id @@ -2522,6 +2536,8 @@ async def _record_incident_terminal_disposition( **terminal_payload, "incident_id": claim.incident_id, "incident_status": str(row.get("incident_status") or ""), + "proposal_executed": outcome.get("proposal_executed") is True, + "execution_success": outcome.get("execution_success") is True, "incident_row_version": ( row["updated_at"].isoformat() if row.get("updated_at") is not None diff --git a/apps/api/tests/test_ansible_verified_closure.py b/apps/api/tests/test_ansible_verified_closure.py index f4b94727a..91488d411 100644 --- a/apps/api/tests/test_ansible_verified_closure.py +++ b/apps/api/tests/test_ansible_verified_closure.py @@ -66,6 +66,15 @@ def test_incident_terminal_writer_normalizes_legacy_non_object_outcome() -> None assert "'{automation_terminal}'" in source +def test_incident_terminal_persists_automated_outcome_truth() -> None: + source = inspect.getsource(service._record_incident_terminal_disposition) + + assert "'proposal_executed'" in source + assert "'execution_success'" in source + assert 'outcome.get("proposal_executed") is True' in source + assert 'outcome.get("execution_success") is True' in source + + @pytest.mark.asyncio async def test_closure_refuses_to_resolve_when_any_receipt_is_missing( monkeypatch: pytest.MonkeyPatch,