diff --git a/apps/api/src/services/alert_chain_emergency_ingress.py b/apps/api/src/services/alert_chain_emergency_ingress.py
index 0baf0f53d..245cc5cdc 100644
--- a/apps/api/src/services/alert_chain_emergency_ingress.py
+++ b/apps/api/src/services/alert_chain_emergency_ingress.py
@@ -12,8 +12,9 @@ is transport and coordination only; it never executes the Linux repair.
from __future__ import annotations
import hashlib
+import html
import json
-from collections.abc import Awaitable, Callable
+from collections.abc import Awaitable, Callable, Mapping
from datetime import UTC, datetime
from typing import Any, Literal
from uuid import NAMESPACE_URL, UUID, uuid5
@@ -252,6 +253,88 @@ def _incident_payload_for_candidate(
return payload
+def _alert_chain_candidate_queued_message(
+ *,
+ owner: Mapping[str, Any],
+) -> str:
+ """Render a compact, truthful queue receipt for the SRE war room."""
+
+ incident_id = html.escape(str(owner["incident_id"]))
+ run_id = html.escape(str(owner["run_id"]))
+ return (
+ "🚨 [ALERT-CHAIN | CRITICAL]\n"
+ "ai_automation_alert_card_v1\n\n"
+ "Alertmanager 告警鏈路中斷\n"
+ f"├ 資產:{ALERT_CHAIN_CANONICAL_ASSET_ID}\n"
+ "├ 現況:修復候選已持久化並排入受控佇列\n"
+ f"├ Incident:{incident_id}\n"
+ f"└ Run:{run_id}\n\n"
+ "AI Agent 動作\n"
+ "├ Agent99:已完成只讀旁路探測與驗證轉送\n"
+ "├ Policy:已用 deterministic typed-domain policy 選定執行器\n"
+ "├ Host Ansible:已排入 check → bounded apply → verify\n"
+ "└ LLM:未介入此緊急路由(不偽造 Ollama/Claude/Gemini 執行)\n\n"
+ "⏭ 自動後續:獨立驗證 → Incident closure "
+ "→ KM/RAG/MCP/PlayBook\n"
+ "runtime_write_gate=controlled"
+ )
+
+
+async def _send_alert_chain_candidate_queued_telegram_receipt(
+ *,
+ owner: Mapping[str, Any],
+ project_id: str,
+) -> dict[str, Any]:
+ """Send the queue receipt and reduce provider evidence to a safe receipt."""
+
+ from src.services.awooop_deeplinks import incident_truth_chain_reply_markup
+ from src.services.telegram_gateway import (
+ _telegram_send_delivery_succeeded,
+ get_telegram_gateway,
+ )
+
+ delivery = await get_telegram_gateway().send_alert_notification(
+ _alert_chain_candidate_queued_message(owner=owner),
+ product_id=project_id,
+ signal_family="incident_lifecycle",
+ severity="P1",
+ reply_markup=incident_truth_chain_reply_markup(
+ str(owner["incident_id"]),
+ project_id=project_id,
+ ),
+ )
+ provider_result = (
+ delivery.get("result")
+ if isinstance(delivery, Mapping)
+ and isinstance(delivery.get("result"), Mapping)
+ else {}
+ )
+ route_receipt = (
+ delivery.get("_awoooi_canonical_route_receipt")
+ if isinstance(delivery, Mapping)
+ and isinstance(
+ delivery.get("_awoooi_canonical_route_receipt"), Mapping
+ )
+ else {}
+ )
+ return {
+ "schema_version": "alert_chain_telegram_queue_receipt_v1",
+ "attempted": True,
+ "durable_acknowledged": _telegram_send_delivery_succeeded(delivery),
+ "delivery_status": str(
+ delivery.get("_awooop_delivery_status") or "unknown"
+ )
+ if isinstance(delivery, Mapping)
+ else "invalid_response",
+ "provider_message_id": str(provider_result.get("message_id") or ""),
+ "destination_alias": str(route_receipt.get("destination_alias") or ""),
+ "classification": "alert_chain_health",
+ "agent99_activity": "read_only_relay_completed",
+ "executor_activity": "host_ansible_candidate_queued",
+ "model_activity": "not_invoked_deterministic_policy",
+ }
+
+
async def process_alert_chain_emergency_relay(
payload: AlertChainEmergencyRelayRequest,
*,
@@ -263,6 +346,7 @@ async def process_alert_chain_emergency_relay(
),
candidate_enqueuer: Callable[..., Awaitable[dict[str, Any]]] | None = None,
event_recorder: Callable[..., Awaitable[Any]] = record_alertmanager_event,
+ telegram_notifier: Callable[..., Awaitable[dict[str, Any]]] | None = None,
) -> dict[str, Any]:
"""Persist the deterministic owner and queue the exact Ansible candidate."""
@@ -272,6 +356,8 @@ async def process_alert_chain_emergency_relay(
)
candidate_enqueuer = enqueue_ai_decision_ansible_candidate
+ if telegram_notifier is None:
+ telegram_notifier = _send_alert_chain_candidate_queued_telegram_receipt
owner = build_alert_chain_emergency_owner(payload, project_id=project_id)
approvals = approval_service or get_approval_service()
@@ -454,10 +540,77 @@ async def process_alert_chain_emergency_relay(
incident_id=owner["incident_id"],
)
+ telegram_receipt: dict[str, Any] = {
+ "schema_version": "alert_chain_telegram_queue_receipt_v1",
+ "attempted": False,
+ "durable_acknowledged": False,
+ "delivery_status": "candidate_not_queued",
+ "provider_message_id": "",
+ "destination_alias": "",
+ "classification": "alert_chain_health",
+ "agent99_activity": "read_only_relay_completed",
+ "executor_activity": (
+ "host_ansible_candidate_queued"
+ if queued
+ else "host_ansible_candidate_not_queued"
+ ),
+ "model_activity": "not_invoked_deterministic_policy",
+ }
+ if queued:
+ try:
+ notification_result = await telegram_notifier(
+ owner=owner,
+ project_id=project_id,
+ )
+ if isinstance(notification_result, Mapping):
+ delivery_fields = (
+ "attempted",
+ "durable_acknowledged",
+ "delivery_status",
+ "provider_message_id",
+ "destination_alias",
+ )
+ telegram_receipt.update(
+ {
+ key: notification_result.get(key)
+ for key in delivery_fields
+ if key in notification_result
+ }
+ )
+ except Exception as exc:
+ telegram_receipt.update(
+ {
+ "attempted": True,
+ "durable_acknowledged": False,
+ "delivery_status": f"failed:{type(exc).__name__}",
+ }
+ )
+ logger.warning(
+ "alert_chain_candidate_queued_telegram_receipt_failed",
+ error_type=type(exc).__name__,
+ incident_id=owner["incident_id"],
+ )
+
+ telegram_acknowledged = bool(
+ queued
+ and telegram_receipt.get("attempted") is True
+ and telegram_receipt.get("durable_acknowledged") is True
+ )
+ queued_blockers = [
+ "independent_post_verifier_pending",
+ "incident_closure_and_learning_writeback_pending",
+ ]
+ if queued and not telegram_acknowledged:
+ queued_blockers.insert(0, "telegram_durable_queue_receipt_missing")
+
return {
"schema_version": "alert_chain_emergency_ingress_result_v1",
"status": (
- "ansible_candidate_queued_verifier_pending"
+ (
+ "ansible_candidate_queued_verifier_pending"
+ if telegram_acknowledged
+ else "ansible_candidate_queued_telegram_receipt_pending"
+ )
if queued
else str(
enqueue_receipt.get("status")
@@ -494,13 +647,12 @@ async def process_alert_chain_emergency_relay(
for value in enqueue_receipt.get("active_blockers") or []
][:8],
},
+ "telegram_receipt": telegram_receipt,
+ "alert_to_telegram_receipt_verified": telegram_acknowledged,
"runtime_write_performed": False,
"runtime_closure_verified": False,
"active_blockers": (
- [
- "independent_post_verifier_pending",
- "incident_closure_and_learning_writeback_pending",
- ]
+ queued_blockers
if queued
else [
str(value)[:160]
@@ -510,7 +662,13 @@ async def process_alert_chain_emergency_relay(
][:8]
),
"safe_next_action": (
- "ansible_worker_check_apply_verify_writeback_same_run"
+ (
+ "ansible_worker_check_apply_verify_writeback_same_run"
+ if telegram_acknowledged
+ else (
+ "ansible_worker_continue_same_run_and_retry_telegram_receipt"
+ )
+ )
if queued
else "retry_same_relay_receipt_after_candidate_queue_blocker"
),
diff --git a/apps/api/tests/test_alert_chain_emergency_ingress.py b/apps/api/tests/test_alert_chain_emergency_ingress.py
index d1795b26e..53589099e 100644
--- a/apps/api/tests/test_alert_chain_emergency_ingress.py
+++ b/apps/api/tests/test_alert_chain_emergency_ingress.py
@@ -24,6 +24,7 @@ from src.services.alert_chain_emergency_ingress import (
ALERT_CHAIN_CATALOG_ID,
ALERT_CHAIN_EXECUTOR,
AlertChainEmergencyRelayRequest,
+ _alert_chain_candidate_queued_message,
build_alert_chain_emergency_owner,
process_alert_chain_emergency_relay,
)
@@ -254,6 +255,24 @@ def test_owner_identity_is_stable_and_binds_same_run_trace_work_item() -> None:
assert first["canonical_asset_id"] == ALERT_CHAIN_CANONICAL_ASSET_ID
+def test_queue_receipt_message_is_compact_truthful_and_actionable() -> None:
+ owner = build_alert_chain_emergency_owner(
+ AlertChainEmergencyRelayRequest.model_validate(payload())
+ )
+
+ message = _alert_chain_candidate_queued_message(owner=owner)
+
+ assert "[ALERT-CHAIN | CRITICAL]" in message
+ assert "ai_automation_alert_card_v1" in message
+ assert owner["incident_id"] in message
+ assert owner["run_id"] in message
+ assert "Agent99:已完成只讀旁路探測與驗證轉送" in message
+ assert "Host Ansible:已排入 check → bounded apply → verify" in message
+ assert "未介入此緊急路由" in message
+ assert "KM/RAG/MCP/PlayBook" in message
+ assert "建議下一步" not in message
+
+
@pytest.mark.asyncio
async def test_secondary_ingress_persists_and_queues_exact_ansible_candidate() -> None:
request = AlertChainEmergencyRelayRequest.model_validate(payload())
@@ -262,6 +281,7 @@ async def test_secondary_ingress_persists_and_queues_exact_ansible_candidate() -
incidents = FakeIncidentService()
enqueue_calls = []
events = []
+ telegram_calls = []
async def create_incident(**kwargs):
assert kwargs["canonical_incident_id"] == owner["incident_id"]
@@ -290,6 +310,18 @@ async def test_secondary_ingress_persists_and_queues_exact_ansible_candidate() -
async def record_event(**kwargs):
events.append(kwargs)
+ async def notify_telegram(**kwargs):
+ telegram_calls.append(kwargs)
+ assert kwargs["owner"] == owner
+ assert kwargs["project_id"] == "awoooi"
+ return {
+ "attempted": True,
+ "durable_acknowledged": True,
+ "delivery_status": "sent",
+ "provider_message_id": "4431",
+ "destination_alias": "awoooi_sre_war_room",
+ }
+
result = await process_alert_chain_emergency_relay(
request,
approval_service=approvals,
@@ -297,6 +329,7 @@ async def test_secondary_ingress_persists_and_queues_exact_ansible_candidate() -
incident_creator=create_incident,
candidate_enqueuer=enqueue,
event_recorder=record_event,
+ telegram_notifier=notify_telegram,
)
assert result["status"] == "ansible_candidate_queued_verifier_pending"
@@ -308,7 +341,21 @@ async def test_secondary_ingress_persists_and_queues_exact_ansible_candidate() -
assert result["executor"] == "host_ansible_executor"
assert result["runtime_write_performed"] is False
assert result["runtime_closure_verified"] is False
+ assert result["alert_to_telegram_receipt_verified"] is True
+ assert result["telegram_receipt"] == {
+ "schema_version": "alert_chain_telegram_queue_receipt_v1",
+ "attempted": True,
+ "durable_acknowledged": True,
+ "delivery_status": "sent",
+ "provider_message_id": "4431",
+ "destination_alias": "awoooi_sre_war_room",
+ "classification": "alert_chain_health",
+ "agent99_activity": "read_only_relay_completed",
+ "executor_activity": "host_ansible_candidate_queued",
+ "model_activity": "not_invoked_deterministic_policy",
+ }
assert len(enqueue_calls) == 1
+ assert len(telegram_calls) == 1
assert events[0]["stage"] == "secondary_ingress_ansible_candidate_queued"
repeated = await process_alert_chain_emergency_relay(
@@ -318,9 +365,11 @@ async def test_secondary_ingress_persists_and_queues_exact_ansible_candidate() -
incident_creator=create_incident,
candidate_enqueuer=enqueue,
event_recorder=record_event,
+ telegram_notifier=notify_telegram,
)
assert repeated["identity"] == result["identity"]
assert approvals.incremented == 1
+ assert len(telegram_calls) == 2
@pytest.mark.asyncio
@@ -348,6 +397,9 @@ async def test_secondary_ingress_keeps_persisted_candidate_partial_when_queue_bl
async def no_event(**_kwargs):
return None
+ async def must_not_notify(**_kwargs):
+ raise AssertionError("telegram must not send before exact queue ack")
+
result = await process_alert_chain_emergency_relay(
request,
approval_service=approvals,
@@ -355,14 +407,70 @@ async def test_secondary_ingress_keeps_persisted_candidate_partial_when_queue_bl
incident_creator=create_incident,
candidate_enqueuer=blocked_enqueue,
event_recorder=no_event,
+ telegram_notifier=must_not_notify,
)
assert result["candidate_persisted"] is True
assert result["candidate_queued"] is False
+ assert result["telegram_receipt"]["attempted"] is False
+ assert result["alert_to_telegram_receipt_verified"] is False
assert result["runtime_closure_verified"] is False
assert result["safe_next_action"].startswith("retry_same_relay_receipt")
+@pytest.mark.asyncio
+async def test_queued_candidate_continues_when_telegram_receipt_is_missing() -> None:
+ request = AlertChainEmergencyRelayRequest.model_validate(payload())
+ owner = build_alert_chain_emergency_owner(request)
+ approvals = FakeApprovalService()
+ incidents = FakeIncidentService()
+
+ async def create_incident(**_kwargs):
+ incidents.incident = FakeIncident(owner["incident_id"])
+ return owner["incident_id"]
+
+ async def enqueue(**_kwargs):
+ return {
+ "status": "controlled_check_mode_queued",
+ "automation_run_id": owner["run_id"],
+ "trace_id": owner["trace_id"],
+ "work_item_id": owner["work_item_id"],
+ "queued": True,
+ "side_effect_performed": False,
+ "active_blockers": [],
+ }
+
+ async def record_event(**_kwargs):
+ return None
+
+ async def notify_without_durable_ack(**_kwargs):
+ return {
+ "attempted": True,
+ "durable_acknowledged": False,
+ "delivery_status": "durable_mirror_failed",
+ }
+
+ result = await process_alert_chain_emergency_relay(
+ request,
+ approval_service=approvals,
+ incident_service=incidents,
+ incident_creator=create_incident,
+ candidate_enqueuer=enqueue,
+ event_recorder=record_event,
+ telegram_notifier=notify_without_durable_ack,
+ )
+
+ assert result["status"] == (
+ "ansible_candidate_queued_telegram_receipt_pending"
+ )
+ assert result["candidate_queued"] is True
+ assert result["alert_to_telegram_receipt_verified"] is False
+ assert "telegram_durable_queue_receipt_missing" in result["active_blockers"]
+ assert result["safe_next_action"] == (
+ "ansible_worker_continue_same_run_and_retry_telegram_receipt"
+ )
+
+
@pytest.mark.asyncio
async def test_secondary_ingress_rejects_deterministic_owner_collision() -> None:
request = AlertChainEmergencyRelayRequest.model_validate(payload())