fix(telegram): require durable callback receipts
This commit is contained in:
@@ -43,18 +43,22 @@ _STATIC_ACTIONS: dict[str, TelegramButtonActionContract] = {
|
||||
"approve": TelegramButtonActionContract(
|
||||
"approve", "nonce", "TelegramGateway.handle_callback", "high",
|
||||
"approval_control_plane", "approval_and_execution_receipt",
|
||||
state="blocked_missing_controlled_apply_contract",
|
||||
),
|
||||
"reject": TelegramButtonActionContract(
|
||||
"reject", "nonce", "TelegramGateway.handle_callback", "medium",
|
||||
"approval_control_plane", "approval_state_receipt",
|
||||
state="blocked_missing_controlled_apply_contract",
|
||||
),
|
||||
"silence": TelegramButtonActionContract(
|
||||
"silence", "nonce", "TelegramGateway._handle_silence", "medium",
|
||||
"telegram_silence_store", "telegram_callback_reply_receipt",
|
||||
state="blocked_missing_controlled_apply_contract",
|
||||
),
|
||||
"snooze": TelegramButtonActionContract(
|
||||
"snooze", "nonce", "TelegramGateway._handle_snooze", "low",
|
||||
"telegram_snooze_store", "telegram_callback_reply_receipt",
|
||||
state="blocked_missing_controlled_apply_contract",
|
||||
),
|
||||
"tune": TelegramButtonActionContract(
|
||||
"tune", "nonce", "TelegramGateway._handle_auto_tuning", "low",
|
||||
@@ -64,22 +68,27 @@ _STATIC_ACTIONS: dict[str, TelegramButtonActionContract] = {
|
||||
"log_manual_fix": TelegramButtonActionContract(
|
||||
"log_manual_fix", "nonce", "TelegramGateway.handle_callback:log_manual_fix", "low",
|
||||
"incident_timeline", "timeline_write_receipt",
|
||||
state="blocked_missing_controlled_apply_contract",
|
||||
),
|
||||
"drift_view": TelegramButtonActionContract(
|
||||
"drift_view", "nonce", "TelegramGateway._handle_drift_action", "low",
|
||||
"drift_readback", "telegram_callback_reply_receipt",
|
||||
state="blocked_missing_controlled_apply_contract",
|
||||
),
|
||||
"drift_adopt": TelegramButtonActionContract(
|
||||
"drift_adopt", "nonce", "TelegramGateway._handle_drift_action", "high",
|
||||
"drift_control_plane", "drift_post_verifier",
|
||||
state="blocked_missing_controlled_apply_contract",
|
||||
),
|
||||
"drift_revert": TelegramButtonActionContract(
|
||||
"drift_revert", "nonce", "TelegramGateway._handle_drift_action", "high",
|
||||
"drift_control_plane", "drift_post_verifier",
|
||||
state="blocked_missing_controlled_apply_contract",
|
||||
),
|
||||
"la": TelegramButtonActionContract(
|
||||
"la", "llm_short_id", "TelegramGateway._handle_llm_action_callback", "dynamic",
|
||||
"callback_dispatcher", "callback_dispatch_receipt",
|
||||
state="blocked_missing_controlled_apply_contract",
|
||||
),
|
||||
}
|
||||
|
||||
@@ -99,23 +108,16 @@ def _callback_action(callback_data: str) -> str:
|
||||
def _registry_action_contract(action: str) -> TelegramButtonActionContract | None:
|
||||
contract = _STATIC_ACTIONS.get(action)
|
||||
if contract is not None:
|
||||
return contract
|
||||
return contract if contract.state == "active" else None
|
||||
if action in _AI_ADVISORY_ACTIONS:
|
||||
return TelegramButtonActionContract(
|
||||
action,
|
||||
"advisory",
|
||||
"TelegramGateway._handle_ai_advisory_action",
|
||||
"low",
|
||||
"ai_advisory_control_plane",
|
||||
"telegram_callback_reply_receipt",
|
||||
)
|
||||
return None
|
||||
|
||||
# The YAML registry is the existing source of truth for category actions.
|
||||
# Import lazily so registry validation never creates a module cycle.
|
||||
from src.services.callback_dispatcher import get_action_spec
|
||||
|
||||
spec = get_action_spec(action)
|
||||
if spec is None:
|
||||
if spec is None or spec.callback_format != "info" or spec.risk != "low":
|
||||
return None
|
||||
return TelegramButtonActionContract(
|
||||
action=spec.name,
|
||||
@@ -166,12 +168,12 @@ def registered_info_callback_actions() -> frozenset[str]:
|
||||
names = {
|
||||
action
|
||||
for action, contract in _STATIC_ACTIONS.items()
|
||||
if contract.callback_format == "info"
|
||||
if contract.callback_format == "info" and contract.state == "active"
|
||||
}
|
||||
names.update(
|
||||
spec.name
|
||||
for spec in load_action_registry().values()
|
||||
if spec.callback_format == "info"
|
||||
if spec.callback_format == "info" and spec.risk == "low"
|
||||
)
|
||||
return frozenset(names)
|
||||
|
||||
@@ -189,6 +191,7 @@ def telegram_button_registry_snapshot() -> dict[str, object]:
|
||||
risk="low",
|
||||
executor="ai_advisory_control_plane",
|
||||
verifier="telegram_callback_reply_receipt",
|
||||
state="blocked_missing_controlled_apply_contract",
|
||||
)
|
||||
for action in sorted(_AI_ADVISORY_ACTIONS)
|
||||
)
|
||||
@@ -200,6 +203,13 @@ def telegram_button_registry_snapshot() -> dict[str, object]:
|
||||
risk=spec.risk,
|
||||
executor=f"mcp:{spec.mcp_provider}/{spec.mcp_tool}",
|
||||
verifier="callback_dispatch_receipt",
|
||||
state=(
|
||||
"active"
|
||||
if spec.callback_format == "info" and spec.risk == "low"
|
||||
else "break_glass_only"
|
||||
if spec.risk == "critical"
|
||||
else "blocked_missing_controlled_apply_contract"
|
||||
),
|
||||
)
|
||||
for spec in load_action_registry().values()
|
||||
if spec.name not in _STATIC_ACTIONS
|
||||
@@ -207,8 +217,15 @@ def telegram_button_registry_snapshot() -> dict[str, object]:
|
||||
rows = sorted((asdict(item) for item in contracts), key=lambda item: item["action"])
|
||||
return {
|
||||
"schema_version": "telegram_button_action_registry_v1",
|
||||
"registered_action_count": len(rows),
|
||||
"declared_action_count": len(rows),
|
||||
"registered_action_count": sum(
|
||||
1 for item in rows if item["state"] == "active"
|
||||
),
|
||||
"blocked_action_count": sum(
|
||||
1 for item in rows if item["state"] != "active"
|
||||
),
|
||||
"unknown_action_policy": "do_not_render",
|
||||
"write_action_policy": "fail_closed_until_controlled_apply_contract",
|
||||
"actions": rows,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user