Files
awoooi/apps/api/src/services/alert_chain_emergency_ingress.py

676 lines
25 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Authenticated reduced-envelope ingress for Alertmanager delivery failures.
The primary Alertmanager webhook cannot be the only producer of its own repair
candidate: when that delivery path is broken, calling it again is a catch-22.
Agent99 therefore polls the exact host-110 Alertmanager API without credentials,
reduces only the exact firing alert, then sends this authenticated no-secret
envelope to AWOOOI. This service binds one deterministic approval/Incident/run
identity and queues the exact host-110 Alertmanager Ansible candidate. Agent99
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, Mapping
from datetime import UTC, datetime
from typing import Any, Literal
from uuid import NAMESPACE_URL, UUID, uuid5
from pydantic import BaseModel, ConfigDict, Field, field_validator
from src.core.logging import get_logger
from src.models.approval import (
ApprovalRequestCreate,
BlastRadius,
DataImpact,
DryRunCheck,
RiskLevel,
)
from src.services.approval_db import get_approval_service
from src.services.channel_hub import record_alertmanager_event
from src.services.incident_service import (
create_incident_for_approval,
get_incident_service,
)
logger = get_logger("awoooi.alert_chain_emergency_ingress")
ALERT_CHAIN_EMERGENCY_SCHEMA_VERSION = (
"agent99_alertmanager_emergency_relay_v1"
)
ALERT_CHAIN_EMERGENCY_OWNER_SCHEMA_VERSION = (
"alert_chain_emergency_ingress_owner_v1"
)
ALERT_CHAIN_ALERTNAME = "AlertChainBroken_Alertmanager"
ALERT_CHAIN_CATALOG_ID = "ansible:110-alertmanager-delivery-recovery"
ALERT_CHAIN_CANONICAL_ASSET_ID = "container:host_110:alertmanager"
ALERT_CHAIN_EXECUTOR = "host_ansible_executor"
ALERT_CHAIN_AGENT99_ROLE = "relay_coordination_only"
ALERT_CHAIN_APPROVAL_FINGERPRINT_DOMAIN = (
"awoooi.alert_chain_emergency.approval.v1"
)
class AlertChainEmergencyRelayRequest(BaseModel):
"""Bounded relay envelope; arbitrary labels, annotations and logs are forbidden."""
model_config = ConfigDict(extra="forbid")
schema_version: Literal["agent99_alertmanager_emergency_relay_v1"]
relay_receipt_id: str = Field(
pattern=r"^agent99-alertchain-[0-9a-f]{64}$",
max_length=83,
)
source_host: Literal["192.168.0.110"]
source_url: Literal["http://192.168.0.110:9093/api/v2/alerts"]
source_fingerprint: str = Field(
pattern=r"^[0-9a-f]{8,64}$",
max_length=64,
)
source_started_at: datetime
alertname: Literal["AlertChainBroken_Alertmanager"]
status: Literal["firing"]
severity: Literal["critical"]
target_resource: Literal["alertmanager"]
namespace: Literal["monitoring"]
agent99_role: Literal["relay_coordination_only"]
executor: Literal["host_ansible_executor"]
catalog_id: Literal["ansible:110-alertmanager-delivery-recovery"]
runtime_write_performed: Literal[False]
raw_payload_stored: Literal[False]
@field_validator("source_started_at")
@classmethod
def require_timezone(cls, value: datetime) -> datetime:
if value.tzinfo is None:
raise ValueError("source_started_at_timezone_required")
return value.astimezone(UTC)
def _canonical_json(value: Any) -> str:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
)
def build_alert_chain_emergency_owner(
payload: AlertChainEmergencyRelayRequest,
*,
project_id: str = "awoooi",
) -> dict[str, Any]:
"""Build deterministic IDs for one exact Alertmanager firing episode."""
project = str(project_id or "awoooi").strip() or "awoooi"
occurrence = {
"source_host": payload.source_host,
"source_url": payload.source_url,
"source_fingerprint": payload.source_fingerprint,
"source_started_at": payload.source_started_at.isoformat(),
"relay_receipt_id": payload.relay_receipt_id,
}
owner_identity = {
"schema_version": ALERT_CHAIN_EMERGENCY_OWNER_SCHEMA_VERSION,
"project_id": project,
"alertname": ALERT_CHAIN_ALERTNAME,
"canonical_asset_id": ALERT_CHAIN_CANONICAL_ASSET_ID,
"source_occurrence": occurrence,
}
owner_key = _canonical_json(owner_identity)
digest = hashlib.sha256(owner_key.encode("utf-8")).hexdigest()
run_id = str(uuid5(NAMESPACE_URL, f"{owner_key}:run"))
return {
**owner_identity,
"owner_identity": owner_identity,
"owner_key_sha256": digest,
"approval_id": str(uuid5(NAMESPACE_URL, f"{owner_key}:approval")),
"incident_id": f"INC-ACR-{digest[:16].upper()}",
"run_id": run_id,
"trace_id": run_id,
"work_item_id": f"alertchain:{project}:{digest[:32]}",
"source_receipt_ref": payload.relay_receipt_id,
"source_fingerprint": payload.source_fingerprint,
"source_started_at": payload.source_started_at.isoformat(),
"agent99_role": ALERT_CHAIN_AGENT99_ROLE,
"executor": ALERT_CHAIN_EXECUTOR,
"catalog_id": ALERT_CHAIN_CATALOG_ID,
"canonical_asset_id": ALERT_CHAIN_CANONICAL_ASSET_ID,
"runtime_write_performed": False,
"runtime_closure_verified": False,
}
def _approval_storage_fingerprint(owner: dict[str, Any]) -> str:
payload = (
f"{ALERT_CHAIN_APPROVAL_FINGERPRINT_DOMAIN}\x1f"
f"{_canonical_json(owner['owner_identity'])}"
)
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
def _approval_matches_owner(approval: Any, owner: dict[str, Any]) -> bool:
metadata = getattr(approval, "metadata", None)
return bool(
str(getattr(approval, "id", "") or "") == owner["approval_id"]
and isinstance(metadata, dict)
and metadata.get("schema_version")
== ALERT_CHAIN_EMERGENCY_OWNER_SCHEMA_VERSION
and metadata.get("owner_identity") == owner["owner_identity"]
and metadata.get("owner_key_sha256") == owner["owner_key_sha256"]
and metadata.get("run_id") == owner["run_id"]
and metadata.get("trace_id") == owner["trace_id"]
and metadata.get("work_item_id") == owner["work_item_id"]
and metadata.get("agent99_role") == ALERT_CHAIN_AGENT99_ROLE
and metadata.get("executor") == ALERT_CHAIN_EXECUTOR
and metadata.get("catalog_id") == ALERT_CHAIN_CATALOG_ID
and metadata.get("runtime_write_performed") is False
)
def _approval_request(owner: dict[str, Any]) -> ApprovalRequestCreate:
return ApprovalRequestCreate(
action=(
"ANSIBLE_CONTROLLED_CANDIDATE "
"ansible:110-alertmanager-delivery-recovery"
),
description=(
"Authenticated Agent99 poll relay created the exact "
"Alertmanager delivery recovery candidate. Agent99 remains relay-only; "
"the Linux executor is the bounded host Ansible broker."
),
risk_level=RiskLevel.MEDIUM,
blast_radius=BlastRadius(
affected_pods=0,
estimated_downtime="0-30s",
related_services=["alertmanager"],
data_impact=DataImpact.WRITE,
),
dry_run_checks=[
DryRunCheck(
name="exact-host read-only poll ingress",
passed=True,
message="unauthenticated first hop; authenticated reduced outbound",
),
DryRunCheck(
name="typed executor boundary",
passed=True,
message=(
"Agent99 relay-only; host_ansible_executor owns Linux apply"
),
),
DryRunCheck(
name="no-secret envelope",
passed=True,
message="raw labels, annotations, logs and credentials rejected",
),
],
requested_by="Agent99 Alertmanager emergency relay",
incident_id=owner["incident_id"],
metadata={
**owner,
"preallocated_approval_id": owner["approval_id"],
"owner_policy": "global_product_governance_v2",
"candidate_only": True,
"controlled_apply_allowed": True,
},
)
def _incident_payload_for_candidate(
incident: Any,
*,
owner: dict[str, Any],
) -> dict[str, Any]:
if hasattr(incident, "model_dump"):
payload = incident.model_dump(mode="json")
elif isinstance(incident, dict):
payload = dict(incident)
else:
payload = {}
payload.update(
{
"incident_id": owner["incident_id"],
"project_id": owner["project_id"],
"alertname": ALERT_CHAIN_ALERTNAME,
"alert_category": "alert_chain_health",
"notification_type": "TYPE-3",
"trace_id": owner["trace_id"],
"run_id": owner["run_id"],
"work_item_id": owner["work_item_id"],
"source_receipt_ref": owner["source_receipt_ref"],
"asset_scope_aliases": [
ALERT_CHAIN_CANONICAL_ASSET_ID,
"host_110",
"alertmanager",
],
}
)
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 (
"🚨 <b>[ALERT-CHAIN | CRITICAL]</b>\n"
"<code>ai_automation_alert_card_v1</code>\n\n"
"<b>Alertmanager 告警鏈路中斷</b>\n"
f"├ 資產:<code>{ALERT_CHAIN_CANONICAL_ASSET_ID}</code>\n"
"├ 現況:修復候選已持久化並排入受控佇列\n"
f"├ Incident<code>{incident_id}</code>\n"
f"└ Run<code>{run_id}</code>\n\n"
"<b>AI Agent 動作</b>\n"
"├ Agent99已完成只讀旁路探測與驗證轉送\n"
"├ Policy已用 deterministic typed-domain policy 選定執行器\n"
"├ Host Ansible已排入 check → bounded apply → verify\n"
"└ LLM未介入此緊急路由不偽造 Ollama/Claude/Gemini 執行)\n\n"
"⏭ <b>自動後續</b>:獨立驗證 → Incident closure "
"→ KM/RAG/MCP/PlayBook\n"
"<code>runtime_write_gate=controlled</code>"
)
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,
*,
project_id: str = "awoooi",
approval_service: Any | None = None,
incident_service: Any | None = None,
incident_creator: Callable[..., Awaitable[str]] = (
create_incident_for_approval
),
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."""
if candidate_enqueuer is None:
from src.jobs.awooop_ansible_candidate_backfill_job import (
enqueue_ai_decision_ansible_candidate,
)
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()
incidents = incident_service or get_incident_service()
approval_uuid = UUID(owner["approval_id"])
approval = await approvals.get_approval(approval_uuid)
created = False
if approval is not None and not _approval_matches_owner(approval, owner):
return {
"schema_version": "alert_chain_emergency_ingress_result_v1",
"status": "durable_owner_identity_mismatch_no_candidate",
"candidate_persisted": False,
"candidate_queued": False,
"runtime_write_performed": False,
"runtime_closure_verified": False,
}
if approval is None:
try:
approval = await approvals.create_approval_with_fingerprint(
request=_approval_request(owner),
fingerprint=_approval_storage_fingerprint(owner),
)
created = True
except Exception:
approval = await approvals.get_approval(approval_uuid)
if approval is None:
raise
else:
incremented = await approvals.increment_hit_count(approval_uuid)
if incremented is not None:
approval = incremented
if not _approval_matches_owner(approval, owner):
return {
"schema_version": "alert_chain_emergency_ingress_result_v1",
"status": "durable_owner_readback_mismatch_no_candidate",
"candidate_persisted": False,
"candidate_queued": False,
"runtime_write_performed": False,
"runtime_closure_verified": False,
}
canonical_incident = await incidents.get_for_readback(
owner["incident_id"],
project_id=project_id,
)
if canonical_incident is None:
await incident_creator(
approval_id=owner["approval_id"],
risk_level="medium",
target_resource="alertmanager",
namespace="monitoring",
alert_type="custom",
message=(
"Alertmanager main webhook delivery failure received by the "
"authenticated Agent99 secondary machine ingress"
),
source="alertmanager",
alertname=ALERT_CHAIN_ALERTNAME,
alert_labels={
"alertname": ALERT_CHAIN_ALERTNAME,
"severity": "critical",
"component": "alertmanager",
"host": "110",
"alert_category": "alert_chain_health",
"fingerprint": payload.source_fingerprint,
"relay_receipt_id": payload.relay_receipt_id,
"canonical_asset_id": ALERT_CHAIN_CANONICAL_ASSET_ID,
},
notification_type="TYPE-3",
alert_category="alert_chain_health",
canonical_incident_id=owner["incident_id"],
)
await approvals.update_incident_id(
approval_uuid,
owner["incident_id"],
)
canonical_incident = await incidents.get_for_readback(
owner["incident_id"],
project_id=project_id,
)
durable_incident = bool(
canonical_incident is not None
and getattr(canonical_incident, "persisted_to_pg", False) is True
)
bound_approval = await approvals.get_approval(approval_uuid)
durable_binding = bool(
bound_approval is not None
and _approval_matches_owner(bound_approval, owner)
and str(getattr(bound_approval, "incident_id", "") or "")
== owner["incident_id"]
)
if not durable_incident or not durable_binding:
return {
"schema_version": "alert_chain_emergency_ingress_result_v1",
"status": "durable_incident_binding_missing_no_candidate",
"candidate_persisted": False,
"candidate_queued": False,
"identity": {
key: owner[key]
for key in ("run_id", "trace_id", "work_item_id")
},
"runtime_write_performed": False,
"runtime_closure_verified": False,
}
candidate_incident = _incident_payload_for_candidate(
canonical_incident,
owner=owner,
)
enqueue_receipt = await candidate_enqueuer(
incident=candidate_incident,
proposal_data={
"source": "alert_webhook_controlled_router",
"risk_level": "medium",
"action": "enqueue_allowlisted_ansible_check_mode",
"alert_type": "custom",
"alertname": ALERT_CHAIN_ALERTNAME,
"namespace": "monitoring",
"approval_id": owner["approval_id"],
"execution_priority": 20,
"source_fingerprint": payload.source_fingerprint,
"source_occurrence_id": payload.relay_receipt_id,
"source_received_at": payload.source_started_at.isoformat(),
"source_recurrence_verified": True,
"source_recurrence_source": "agent99_secondary_machine_ingress",
"source_recurrence_anchor": payload.relay_receipt_id,
"source_recurrence_durable_readback_verified": True,
"source_canonical_asset_id": ALERT_CHAIN_CANONICAL_ASSET_ID,
},
project_id=project_id,
automation_run_id=owner["run_id"],
)
queued = bool(
enqueue_receipt.get("queued") is True
and str(enqueue_receipt.get("automation_run_id") or "")
== owner["run_id"]
and str(enqueue_receipt.get("trace_id") or "")
== owner["trace_id"]
and str(enqueue_receipt.get("work_item_id") or "")
== owner["work_item_id"]
)
try:
await event_recorder(
project_id=project_id,
alert_id=payload.relay_receipt_id,
alertname=ALERT_CHAIN_ALERTNAME,
severity="critical",
namespace="monitoring",
target_resource="alertmanager",
fingerprint=payload.source_fingerprint,
stage=(
"secondary_ingress_ansible_candidate_queued"
if queued
else "secondary_ingress_candidate_queue_blocked"
),
notification_type="TYPE-3",
alert_category="alert_chain_health",
incident_id=owner["incident_id"],
approval_id=owner["approval_id"],
repeat_count=int(getattr(bound_approval, "hit_count", 1) or 1),
labels={
"alertname": ALERT_CHAIN_ALERTNAME,
"component": "alertmanager",
"canonical_asset_id": ALERT_CHAIN_CANONICAL_ASSET_ID,
"fingerprint": payload.source_fingerprint,
},
annotations={
"relay_role": ALERT_CHAIN_AGENT99_ROLE,
"executor": ALERT_CHAIN_EXECUTOR,
},
)
except Exception as exc:
logger.warning(
"alert_chain_secondary_ingress_event_record_failed",
error_type=type(exc).__name__,
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"
if telegram_acknowledged
else "ansible_candidate_queued_telegram_receipt_pending"
)
if queued
else str(
enqueue_receipt.get("status")
or "ansible_candidate_queue_not_acknowledged"
)
),
"candidate_owner_created": created,
"candidate_persisted": True,
"candidate_queued": queued,
"identity": {
key: owner[key]
for key in (
"approval_id",
"incident_id",
"run_id",
"trace_id",
"work_item_id",
"source_receipt_ref",
)
},
"agent99_role": ALERT_CHAIN_AGENT99_ROLE,
"executor": ALERT_CHAIN_EXECUTOR,
"catalog_id": ALERT_CHAIN_CATALOG_ID,
"canonical_asset_id": ALERT_CHAIN_CANONICAL_ASSET_ID,
"enqueue_receipt": {
"status": enqueue_receipt.get("status"),
"automation_run_id": enqueue_receipt.get("automation_run_id"),
"queued": enqueue_receipt.get("queued") is True,
"side_effect_performed": bool(
enqueue_receipt.get("side_effect_performed")
),
"active_blockers": [
str(value)[:160]
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": (
queued_blockers
if queued
else [
str(value)[:160]
for value in enqueue_receipt.get("active_blockers") or [
"ansible_candidate_queue_not_acknowledged"
]
][:8]
),
"safe_next_action": (
(
"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"
),
}