fix(telegram): require durable callback receipts

This commit is contained in:
ogt
2026-07-16 23:49:23 +08:00
parent cb7c775d12
commit 84a3fa17a9
4 changed files with 186 additions and 34 deletions

View File

@@ -59,22 +59,56 @@ def test_callback_ingress_defaults_to_unverified_fail_closed() -> None:
assert status["fail_closed_reason"] == "callback_ingress_not_verified"
def test_callback_ingress_receipt_has_24_hour_freshness_contract() -> None:
def test_callback_ingress_receipt_has_five_minute_freshness_contract() -> None:
gateway = _ConfiguredGateway()
assert CALLBACK_INGRESS_RECEIPT_MAX_AGE_SECONDS == 24 * 60 * 60
assert CALLBACK_INGRESS_RECEIPT_MAX_AGE_SECONDS == 5 * 60
gateway._last_callback_ingress_source = "canonical_webhook"
gateway._last_callback_ingress_receipt_at = datetime.now(UTC) - timedelta(hours=23)
gateway._last_callback_ingress_receipt_durable = True
gateway._last_callback_ingress_receipt_at = datetime.now(UTC) - timedelta(minutes=4)
fresh = gateway.callback_ingress_status()
assert fresh["receipt_fresh"] is True
assert fresh["interactive_buttons_ready"] is True
gateway._last_callback_ingress_receipt_at = datetime.now(UTC) - timedelta(hours=25)
gateway._last_callback_ingress_receipt_at = datetime.now(UTC) - timedelta(minutes=6)
stale = gateway.callback_ingress_status()
assert stale["stale"] is True
assert stale["interactive_buttons_ready"] is False
class _SharedReceiptRedis:
def __init__(self) -> None:
self.value: str | None = None
async def setex(self, _key: str, _ttl: int, value: str) -> None:
self.value = value
async def get(self, _key: str) -> str | None:
return self.value
@pytest.mark.asyncio
async def test_callback_ingress_receipt_is_shared_across_gateway_processes(
monkeypatch,
) -> None:
redis = _SharedReceiptRedis()
monkeypatch.setattr(telegram_gateway_module, "get_redis", lambda: redis)
writer = _ConfiguredGateway()
reader = _ConfiguredGateway()
await writer.record_callback_ingress_receipt(
source="canonical_webhook",
update_id=88,
)
assert await reader.refresh_callback_ingress_receipt() is True
status = reader.callback_ingress_status()
assert status["receipt_durable"] is True
assert status["receipt_fresh"] is True
assert status["interactive_buttons_ready"] is True
assert reader._last_update_id == 88
def test_polling_flag_without_live_task_is_stale_not_ready() -> None:
gateway = _ConfiguredGateway()
gateway._polling_active = True
@@ -154,23 +188,31 @@ def test_ready_markup_still_removes_unknown_ghost_callbacks() -> None:
assert all("scale:" not in str(button) for button in rendered)
def test_machine_registry_excludes_ghosts_and_describes_shadow_tune() -> None:
def test_machine_registry_excludes_ghosts_and_fails_closed_write_actions() -> None:
assert callback_action_is_registered("detail:INC-20260716-TEST") is True
assert callback_action_is_registered("view:ERR:1:nonce") is False
assert callback_action_is_registered("scale:RES:1:nonce") is False
tune = resolve_callback_action_contract("tune:APR-1:1:nonce")
assert tune is not None
assert tune.state == "shadow_candidate_only"
assert resolve_callback_action_contract("tune:APR-1:1:nonce") is None
assert resolve_callback_action_contract("approve:APR-1:1:nonce") is None
snapshot = telegram_button_registry_snapshot()
action_names = {row["action"] for row in snapshot["actions"]}
assert snapshot["schema_version"] == "telegram_button_action_registry_v1"
assert snapshot["unknown_action_policy"] == "do_not_render"
assert snapshot["write_action_policy"] == (
"fail_closed_until_controlled_apply_contract"
)
assert "view" not in action_names
assert "scale" not in action_names
actions = {row["action"]: row for row in snapshot["actions"]}
assert actions["tune"]["state"] == "shadow_candidate_only"
assert actions["approve"]["state"] == (
"blocked_missing_controlled_apply_contract"
)
assert actions["secops_block_ip"]["state"] == "break_glass_only"
def test_every_yaml_action_has_a_renderable_registry_contract() -> None:
def test_only_low_risk_read_only_yaml_actions_are_renderable() -> None:
registry = load_action_registry()
assert registry
@@ -181,9 +223,12 @@ def test_every_yaml_action_has_a_renderable_registry_contract() -> None:
else f"{action}:INC-20260716-TEST"
)
contract = resolve_callback_action_contract(callback_data)
assert contract is not None, action
assert contract.handler == "callback_dispatcher.dispatch_action"
assert contract.executor.startswith("mcp:")
if spec.callback_format == "info" and spec.risk == "low":
assert contract is not None, action
assert contract.handler == "callback_dispatcher.dispatch_action"
assert contract.executor.startswith("mcp:")
else:
assert contract is None, action
def test_security_parser_accepts_every_registered_info_action() -> None: