From e1ac2847bdbef7585a7c4339b8c03f493f006dbf Mon Sep 17 00:00:00 2001 From: ogt Date: Fri, 17 Jul 2026 10:59:01 +0800 Subject: [PATCH] fix(telegram): finalize legacy pending receipt routes --- apps/api/src/services/telegram_gateway.py | 70 ++++++++++++++----- .../tests/test_ansible_verified_closure.py | 45 ++++++++++++ 2 files changed, 96 insertions(+), 19 deletions(-) diff --git a/apps/api/src/services/telegram_gateway.py b/apps/api/src/services/telegram_gateway.py index 3e31688f2..3a31f6ec4 100644 --- a/apps/api/src/services/telegram_gateway.py +++ b/apps/api/src/services/telegram_gateway.py @@ -6546,27 +6546,57 @@ class TelegramGateway: send_error = NULL, sent_at = coalesce(sent_at, NOW()), triggered_by_state = :triggered_by_state, - source_envelope = jsonb_set( - jsonb_set( + source_envelope = CASE + WHEN :expected_destination_binding = '' THEN jsonb_set( jsonb_set( - source_envelope, - '{callback_reply,status}', - to_jsonb('callback_reply_sent'::text), + jsonb_set( + jsonb_set( + source_envelope, + '{callback_reply,status}', + to_jsonb('callback_reply_sent'::text), + true + ), + '{notification_policy,disposition}', + to_jsonb('sent'::text), + true + ), + '{canonical_route_receipt,durable_receipt_persisted}', + 'true'::jsonb, true ), - '{notification_policy,disposition}', - to_jsonb('sent'::text), + '{canonical_route_receipt,durable_receipt_status}', + to_jsonb('persisted'::text), true - ), - '{canonical_route_receipt,durable_receipt_persisted}', - 'true'::jsonb, - true - ), - '{canonical_route_receipt,durable_receipt_status}', - to_jsonb('persisted'::text), - true - ) + ) + ELSE + jsonb_set( + jsonb_set( + jsonb_set( + jsonb_set( + jsonb_set( + source_envelope, + '{callback_reply,status}', + to_jsonb('callback_reply_sent'::text), + true + ), + '{notification_policy,disposition}', + to_jsonb('sent'::text), + true + ), + '{canonical_route_receipt,durable_receipt_persisted}', + 'true'::jsonb, + true + ), + '{canonical_route_receipt,durable_receipt_status}', + to_jsonb('persisted'::text), + true + ), + '{canonical_route_receipt,destination_binding}', + to_jsonb(:expected_destination_binding::text), + true + ) + END WHERE project_id = :project_id AND message_id = CAST(:message_id AS uuid) AND run_id = CAST(:run_id AS uuid) @@ -6578,9 +6608,11 @@ class TelegramGateway: ) AND ( :expected_destination_binding = '' - OR source_envelope #>> - '{canonical_route_receipt,destination_binding}' - = :expected_destination_binding + OR COALESCE( + source_envelope #>> + '{canonical_route_receipt,destination_binding}', + '' + ) IN ('', :expected_destination_binding) ) RETURNING send_status, provider_message_id """), diff --git a/apps/api/tests/test_ansible_verified_closure.py b/apps/api/tests/test_ansible_verified_closure.py index 0f690761b..852447fcf 100644 --- a/apps/api/tests/test_ansible_verified_closure.py +++ b/apps/api/tests/test_ansible_verified_closure.py @@ -883,6 +883,51 @@ async def test_durable_reconcile_finalizes_exact_pending_row_without_provider_se assert "UPDATE awooop_outbound_message" in db.statements[2] +@pytest.mark.asyncio +async def test_controlled_result_finalize_backfills_missing_destination_binding( + monkeypatch: pytest.MonkeyPatch, +) -> None: + db = _SequenceDB( + _MappingResult(), + _MappingResult(row={"send_status": "sent", "provider_message_id": "456"}), + ) + + @asynccontextmanager + async def fake_get_db_context(_project_id): + yield db + + monkeypatch.setattr("src.db.base.get_db_context", fake_get_db_context) + gateway = TelegramGateway() + identity = { + "project_id": "awoooi", + "automation_run_id": "agent99-lifecycle-missing-binding", + "incident_id": "INC-20260717-WAZUHACK", + "apply_op_id": "00000000-0000-0000-0000-000000000104", + "delivery_kind": "controlled_apply_result", + } + + finalized = await gateway._finalize_controlled_apply_result_outbound( + identity=identity, + reservation={ + "message_id": "00000000-0000-0000-0000-000000000202", + "run_id": str( + telegram_gateway_module._controlled_apply_result_delivery_run_id( + identity + ) + ), + }, + provider_message_id="456", + expected_destination_binding="a" * 64, + ) + + assert finalized is True + update_sql = db.statements[1] + assert "{canonical_route_receipt,destination_binding}" in update_sql + assert "COALESCE(" in update_sql + assert "IN ('', :expected_destination_binding)" in update_sql + assert db.parameters[1]["expected_destination_binding"] == "a" * 64 + + @pytest.mark.asyncio async def test_durable_reconcile_rejects_provider_message_identity_mismatch( monkeypatch: pytest.MonkeyPatch,