diff --git a/apps/api/src/jobs/agent99_controlled_dispatch_reconciler_job.py b/apps/api/src/jobs/agent99_controlled_dispatch_reconciler_job.py index 2b4350478..d0df500d9 100644 --- a/apps/api/src/jobs/agent99_controlled_dispatch_reconciler_job.py +++ b/apps/api/src/jobs/agent99_controlled_dispatch_reconciler_job.py @@ -832,7 +832,11 @@ async def _ensure_telegram_closure_receipt( if backup_readback else f"Agent99 {mode or 'controlled executor'}" ), - verifier_name="agent99_outcome_independent_verifier", + verifier_name=( + "backup_restore_readback_verifier" + if backup_readback + else "agent99_outcome_independent_verifier" + ), received_status="passed", check_status="passed", apply_status=( diff --git a/apps/api/tests/test_agent99_controlled_dispatch_reconciler_job.py b/apps/api/tests/test_agent99_controlled_dispatch_reconciler_job.py index cbb5b6861..ea7f31f54 100644 --- a/apps/api/tests/test_agent99_controlled_dispatch_reconciler_job.py +++ b/apps/api/tests/test_agent99_controlled_dispatch_reconciler_job.py @@ -73,6 +73,45 @@ async def test_recipient_visible_closure_is_recovered_and_durable( assert "KM/RAG/MCP/PlayBook" in payload.verification +@pytest.mark.asyncio +async def test_backup_recipient_visible_closure_names_typed_verifier( + monkeypatch, +) -> None: + identity = build_agent99_dispatch_identity( + project_id="awoooi", + incident_id="INC-20260719-BACKUP-CLOSED", + source_fingerprint="backup-closure-fingerprint", + route_id="agent99:backup_health:BackupCheck", + ) + delivered = [] + + async def deliver(payload): # type: ignore[no-untyped-def] + delivered.append(payload) + return { + "ok": True, + "durable_outbound_acknowledged": True, + "destination_binding_verified": True, + "provider_message_id": "9921", + } + + monkeypatch.setattr(job, "deliver_agent99_telegram_lifecycle", deliver) + + receipt = await job._ensure_telegram_closure_receipt( + identity, + mode="BackupCheck", + ) + + assert receipt == "telegram_outbound:9921:durable_closure_ack" + assert len(delivered) == 1 + payload = delivered[0] + assert payload.lifecycle == "recovered" + assert payload.executor_name == "Agent99 BackupCheck (read-only)" + assert payload.verifier_name == "backup_restore_readback_verifier" + assert payload.apply_status == "not_applicable" + assert payload.closure_status == "passed" + assert "KM/RAG/MCP/PlayBook/DR" in payload.verification + + def test_agent99_playbook_identity_is_project_scoped() -> None: other = build_agent99_dispatch_identity( project_id="other-project", diff --git a/docs/operations/sre-k3s-controlled-automation-work-items.snapshot.json b/docs/operations/sre-k3s-controlled-automation-work-items.snapshot.json index aca534b90..5c3f681d5 100644 --- a/docs/operations/sre-k3s-controlled-automation-work-items.snapshot.json +++ b/docs/operations/sre-k3s-controlled-automation-work-items.snapshot.json @@ -820,6 +820,7 @@ "exit_condition": "closure requires all learning acknowledgements on the same run", "confirmed_truth": [ "source and focused tests bind typed verifier outcomes to same-run Telegram, KM, RAG, MCP, PlayBook and backup DR scorecard acknowledgements", + "Backup/restore recipient-visible recovered cards name Agent99 BackupCheck as the read-only executor and backup_restore_readback_verifier as the typed independent verifier; apply remains not_applicable and the final durable closure receipt is required", "BackupCheck learning assets are reconciled idempotently as read-only DR evidence rather than controlled repair" ], "runtime_gaps": [