From a0ba1890ad84ed2836946605dd64fb6130c68e7e Mon Sep 17 00:00:00 2001 From: ogt Date: Sat, 11 Jul 2026 19:14:48 +0800 Subject: [PATCH] fix(agent): keep verified closures moving --- .../awooop_ansible_check_mode_service.py | 14 ++- .../tests/test_ansible_verified_closure.py | 101 ++++++++++++++++++ 2 files changed, 107 insertions(+), 8 deletions(-) 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 00a1485d6..6eda82042 100644 --- a/apps/api/src/services/awooop_ansible_check_mode_service.py +++ b/apps/api/src/services/awooop_ansible_check_mode_service.py @@ -3805,13 +3805,11 @@ async def backfill_missing_auto_repair_execution_receipts_once( projection.get("verified") or 0 ) projection_error = str(projection.get("error") or "")[:500] - if stats["retry_terminal_projection_scanned"] or projection_error: - stats["scanned"] = stats["retry_terminal_projection_scanned"] - stats["incident_closure_written"] = stats[ - "retry_terminal_projection_written" - ] - stats["error"] = projection_error or None - return stats + stats["scanned"] = stats["retry_terminal_projection_scanned"] + stats["incident_closure_written"] = stats[ + "retry_terminal_projection_written" + ] + stats["error"] = projection_error or None async with get_db_context(project_id) as db: await db.execute(text("SET LOCAL statement_timeout = '5000ms'")) result = await db.execute( @@ -3965,7 +3963,7 @@ async def backfill_missing_auto_repair_execution_receipts_once( }, ) rows = [dict(row) for row in result.mappings().all()] - stats["scanned"] = len(rows) + stats["scanned"] += len(rows) for row in rows: reconstructed = _claim_from_apply_operation_row(row) if reconstructed is None: diff --git a/apps/api/tests/test_ansible_verified_closure.py b/apps/api/tests/test_ansible_verified_closure.py index 7471b28b3..3308af252 100644 --- a/apps/api/tests/test_ansible_verified_closure.py +++ b/apps/api/tests/test_ansible_verified_closure.py @@ -880,6 +880,107 @@ async def test_retry_terminal_projection_backfill_writes_and_verifies_no_apply( telegram_readback.assert_awaited_once() +@pytest.mark.asyncio +async def test_retry_projection_does_not_starve_verified_success_closure( + monkeypatch: pytest.MonkeyPatch, +) -> None: + class _RowsResult(_MappingResult): + def __init__(self, rows: list[dict]) -> None: + super().__init__() + self._rows = rows + + def all(self) -> list[dict]: + return self._rows + + apply_row = { + "op_id": "00000000-0000-0000-0000-000000000104", + "status": "success", + "verifier_ready": True, + } + db = _SequenceDB(_MappingResult(), _RowsResult([apply_row])) + + @asynccontextmanager + async def fake_db_context(_project_id: str): + yield db + + monkeypatch.setattr(service, "get_db_context", fake_db_context) + monkeypatch.setattr( + service, + "backfill_missing_retry_terminal_projections_once", + AsyncMock( + return_value={ + "scanned": 1, + "written": 0, + "retry_receipt_written": 0, + "telegram_receipt_acknowledged": 1, + "incident_receipt_written": 0, + "lifecycle_written": 0, + "verified": 0, + "runtime_apply_executed": False, + "error": None, + } + ), + ) + monkeypatch.setattr( + service, + "_claim_from_apply_operation_row", + lambda _row: (_claim(), _verified_result()), + ) + monkeypatch.setattr( + service, + "_record_post_apply_verifier_and_learning", + AsyncMock( + return_value={ + "verification": True, + "verification_passed": True, + "verification_result": "success", + "learning": True, + "trust_learning": True, + "rag_writeback": True, + "runtime_stage_receipts": [], + } + ), + ) + monkeypatch.setattr( + service, + "_record_auto_repair_execution_receipt", + AsyncMock(return_value=True), + ) + monkeypatch.setattr( + service, + "_record_timeline_projection_receipt", + AsyncMock(return_value=None), + ) + monkeypatch.setattr( + service, + "_record_runtime_stage_receipts", + AsyncMock(return_value=True), + ) + closure = AsyncMock( + return_value={ + "status": "controlled_apply_closed", + "closed": True, + "telegram_receipt_acknowledged": True, + "runtime_apply_executed": False, + } + ) + monkeypatch.setattr( + service, + "_reconcile_verified_apply_closure_projections", + closure, + ) + + result = await service.backfill_missing_auto_repair_execution_receipts_once( + limit=1 + ) + + assert result["retry_terminal_projection_scanned"] == 1 + assert result["scanned"] == 2 + assert result["incident_closure_written"] == 1 + assert result["telegram_receipt_acknowledged"] == 1 + closure.assert_awaited_once() + + @pytest.mark.asyncio async def test_broker_prioritizes_retry_terminal_projection_backfill( monkeypatch: pytest.MonkeyPatch,