fix(telegram): fail closed on unverified delivery

This commit is contained in:
ogt
2026-07-14 20:49:34 +08:00
parent 180e708444
commit 849f06ad11
15 changed files with 2751 additions and 597 deletions

View File

@@ -20,6 +20,31 @@ from src.services.telegram_gateway import TelegramGateway
_CHAT_ID = "verified-chat"
_MESSAGE_ID = 77
_DESTINATION_BINDING = gateway_module._telegram_destination_binding(_CHAT_ID)
_SENT_RECEIPT = {
"ok": True,
"_awooop_delivery_status": "sent",
"_awooop_provider_send_performed": True,
"result": {"message_id": _MESSAGE_ID, "chat": {"id": _CHAT_ID}},
"_awoooi_canonical_route_receipt": {
"schema_version": "telegram_canonical_egress_receipt_v1",
"decision": "allowed",
"destination_alias": "inbound_interaction_reply_target",
"destination_binding": _DESTINATION_BINDING,
"sender_bot_alias": "tsenyang_bot",
"provider_send_performed": True,
},
gateway_module._DELIVERY_CONTEXT_KEY: {
"schema_version": "telegram_delivery_context_v1",
"method": "sendMessage",
"sender_bot_alias": "tsenyang_bot",
"destination_alias": "inbound_interaction_reply_target",
"destination_binding": _DESTINATION_BINDING,
"payload_destination_binding": _DESTINATION_BINDING,
"provider_destination_binding": _DESTINATION_BINDING,
"destination_binding_verified": True,
},
}
def _assert_verified_callback_payload(payload: dict) -> None:
@@ -174,13 +199,15 @@ async def test_llm_and_category_results_keep_verified_callback_context(
class _Redis:
async def get(self, _key: str) -> str:
return json.dumps({
"name": "inspect",
"provider": "internal",
"tool": "status",
"risk": "low",
"incident_id": "INC-1",
})
return json.dumps(
{
"name": "inspect",
"provider": "internal",
"tool": "status",
"risk": "low",
"incident_id": "INC-1",
}
)
monkeypatch.setattr(gateway_module, "get_redis", lambda: _Redis())
monkeypatch.setattr(
@@ -213,13 +240,15 @@ async def test_llm_and_category_results_keep_verified_callback_context(
monkeypatch.setattr(
callback_dispatcher,
"dispatch_action",
AsyncMock(return_value=DispatchResult(
success=True,
action="inspect",
incident_id="INC-1",
user_id=42,
result_text="result",
)),
AsyncMock(
return_value=DispatchResult(
success=True,
action="inspect",
incident_id="INC-1",
user_id=42,
result_text="result",
)
),
)
class _IncidentRepo:
@@ -264,7 +293,9 @@ async def test_write_category_callback_keeps_verified_context(
"verify_callback",
AsyncMock(return_value={"id": 42}),
)
monkeypatch.setattr(gateway, "_check_incident_state_guard", AsyncMock(return_value=None))
monkeypatch.setattr(
gateway, "_check_incident_state_guard", AsyncMock(return_value=None)
)
monkeypatch.setattr(gateway, "_answer_callback", AsyncMock())
spec = SimpleNamespace(
requires_multi_sig=False,
@@ -278,13 +309,15 @@ async def test_write_category_callback_keeps_verified_context(
monkeypatch.setattr(
callback_dispatcher,
"dispatch_action",
AsyncMock(return_value=DispatchResult(
success=True,
action="restart_safe",
incident_id="INC-1",
user_id=42,
result_text="done",
)),
AsyncMock(
return_value=DispatchResult(
success=True,
action="restart_safe",
incident_id="INC-1",
user_id=42,
result_text="done",
)
),
)
class _IncidentRepo:
@@ -335,9 +368,7 @@ async def test_approval_replies_keep_verified_callback_context(
_assert_verified_callback_payload(status_payload)
source_extra = status_payload[gateway_module._AWOOOP_SOURCE_ENVELOPE_EXTRA_KEY]
merged = gateway_module._merge_outbound_source_envelope_extra({}, source_extra)
assert merged["callback_reply"]["parent_provider_message_id"] == str(
_MESSAGE_ID
)
assert merged["callback_reply"]["parent_provider_message_id"] == str(_MESSAGE_ID)
send_request.reset_mock()
approval_id = UUID("44444444-4444-4444-4444-444444444444")
@@ -367,8 +398,9 @@ async def test_approval_replies_keep_verified_callback_context(
@pytest.mark.parametrize(
("result", "expected"),
[
({"ok": True}, True),
({"ok": True, "_awooop_delivery_status": "sent_reused"}, True),
(_SENT_RECEIPT, True),
({"ok": True}, False),
({"ok": True, "_awooop_delivery_status": "sent_reused"}, False),
({"ok": False}, False),
(
{"ok": True, "_awooop_delivery_status": "blocked_no_egress"},
@@ -414,7 +446,7 @@ async def test_approval_structured_no_send_retries_plain_on_exact_parent(
) -> None:
gateway = TelegramGateway()
attempts: list[tuple[str, dict]] = []
send_results = iter([primary_failure, {"ok": True}])
send_results = iter([primary_failure, dict(_SENT_RECEIPT)])
async def structured_primary_failure(method: str, payload: dict) -> dict:
attempts.append((method, payload))
@@ -423,7 +455,7 @@ async def test_approval_structured_no_send_retries_plain_on_exact_parent(
return {"ok": True}
record_failure = AsyncMock(return_value=None)
shared_fallback = AsyncMock(return_value={"ok": True})
shared_fallback = AsyncMock(return_value=dict(_SENT_RECEIPT))
monkeypatch.setattr(gateway, "_send_request", structured_primary_failure)
monkeypatch.setattr(gateway, "_record_callback_reply_failure", record_failure)
monkeypatch.setattr(gateway, "send_alert_notification", shared_fallback)
@@ -454,10 +486,12 @@ async def test_approval_structured_double_no_send_records_durable_failure(
) -> None:
gateway = TelegramGateway()
attempts: list[tuple[str, dict]] = []
send_results = iter([
{"ok": True, "_awooop_delivery_status": "blocked_no_egress"},
{"ok": True, "_awooop_provider_send_performed": False},
])
send_results = iter(
[
{"ok": True, "_awooop_delivery_status": "blocked_no_egress"},
{"ok": True, "_awooop_provider_send_performed": False},
]
)
async def structured_no_send(method: str, payload: dict) -> dict:
attempts.append((method, payload))
@@ -501,7 +535,7 @@ async def test_callback_card_edits_use_only_verified_inbound_identity(
action: str,
) -> None:
gateway = TelegramGateway()
send_request = AsyncMock(return_value={"ok": True})
send_request = AsyncMock(return_value=dict(_SENT_RECEIPT))
monkeypatch.setattr(gateway, "_send_request", send_request)
await gateway._update_message_after_action(
@@ -658,17 +692,20 @@ async def test_autonomous_lifecycle_threads_use_canonical_route_and_parent_recei
monkeypatch: pytest.MonkeyPatch,
) -> None:
gateway = TelegramGateway()
send_request = AsyncMock(return_value={"ok": True})
send_request = AsyncMock(return_value=dict(_SENT_RECEIPT))
monkeypatch.setattr(gateway, "_send_request", send_request)
monkeypatch.setattr(gateway_module, "get_redis", lambda: _LifecycleRedis())
assert await gateway.mark_auto_repaired("INC-1", "playbook", 10) is True
assert await gateway.append_incident_update("INC-2", "healthy") is True
assert await gateway.append_grouped_alert_digest(
incident_id="INC-3",
group_key="group-1",
digest_text="digest",
) is True
assert (
await gateway.append_grouped_alert_digest(
incident_id="INC-3",
group_key="group-1",
digest_text="digest",
)
is True
)
send_payloads = [
call.args[1]