Files
awoooi/apps/api/src/services/ai_agent_autonomous_runtime_control.py
2026-06-28 11:42:46 +08:00

880 lines
32 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.
"""Current AI Agent autonomous runtime control plane.
This read model is the current directive layer. Historical P2 snapshots can
still describe earlier no-send / no-live states, but this payload states what
the product should enforce now: low, medium, and high risk routes may proceed
through controlled automation when allowlist, check-mode, verifier, rollback,
KM, and Telegram receipts are present.
"""
from __future__ import annotations
from collections.abc import Iterable, Mapping
from datetime import datetime, timezone
from typing import Any
from src.core.config import settings
from src.core.logging import get_logger
from sqlalchemy import text
from src.db.base import get_db_context
from src.services.report_generation_service import (
DAILY_REPORT_HOUR_TAIPEI,
MONTHLY_REPORT_DAY_TAIPEI,
MONTHLY_REPORT_HOUR_TAIPEI,
WEEKLY_REPORT_HOUR_TAIPEI,
WEEKLY_REPORT_WEEKDAY_TAIPEI,
)
_SCHEMA_VERSION = "ai_agent_autonomous_runtime_control_v1"
_RUNTIME_AUTHORITY = "current_owner_directive_controlled_ai_automation"
_DEPLOY_READBACK_MARKER = "p2_416_d1n_autonomous_runtime_control_prod_readback_v2"
_DEPLOY_ATTEMPT_NOTE = "cd_3673_retry_after_host_pressure_gate_fix"
_LIVE_READBACK_SCHEMA_VERSION = "ai_agent_autonomous_runtime_receipt_readback_v1"
_DEFAULT_PROJECT_ID = "awoooi"
_DEFAULT_LOOKBACK_HOURS = 24
# CD cancel-stale-cd triggers are workflow-only and must not change payloads.
_EXECUTOR_OPERATION_TYPES = (
"ansible_candidate_matched",
"ansible_check_mode_executed",
"ansible_apply_executed",
"ansible_rollback_executed",
"ansible_execution_skipped",
)
logger = get_logger(__name__)
def _allowed_risk_levels() -> list[str]:
raw = str(settings.AWOOOP_ANSIBLE_CONTROLLED_APPLY_ALLOWED_RISK_LEVELS or "")
return sorted({item.strip().lower() for item in raw.split(",") if item.strip()})
def _utc_iso(value: Any) -> str | None:
if value is None:
return None
if isinstance(value, datetime):
if value.tzinfo is None:
value = value.replace(tzinfo=timezone.utc)
return value.astimezone(timezone.utc).isoformat()
return str(value)
def _row_mapping(row: Mapping[str, Any] | Any) -> dict[str, Any]:
if isinstance(row, Mapping):
return dict(row)
mapping = getattr(row, "_mapping", None)
if mapping is not None:
return dict(mapping)
return dict(row)
def _int_value(value: Any) -> int:
try:
return int(value or 0)
except (TypeError, ValueError):
return 0
def _sanitize_latest_rows(
rows: Iterable[Mapping[str, Any] | Any],
*,
allowed_keys: tuple[str, ...],
time_keys: tuple[str, ...] = ("created_at", "collected_at", "queued_at", "sent_at"),
limit: int = 5,
) -> list[dict[str, Any]]:
clean_rows: list[dict[str, Any]] = []
for row in rows:
item = _row_mapping(row)
clean: dict[str, Any] = {}
for key in allowed_keys:
if key not in item:
continue
value = item.get(key)
clean[key] = _utc_iso(value) if key in time_keys else value
clean_rows.append(clean)
if len(clean_rows) >= limit:
break
return clean_rows
def _operation_counts(
rows: Iterable[Mapping[str, Any] | Any],
) -> dict[str, dict[str, Any]]:
counts = {
operation_type: {
"total": 0,
"recent": 0,
"by_status": {},
}
for operation_type in _EXECUTOR_OPERATION_TYPES
}
for row in rows:
item = _row_mapping(row)
operation_type = str(item.get("operation_type") or "unknown")
status = str(item.get("status") or "unknown")
bucket = counts.setdefault(
operation_type,
{
"total": 0,
"recent": 0,
"by_status": {},
},
)
total = _int_value(item.get("total"))
recent = _int_value(item.get("recent"))
bucket["total"] += total
bucket["recent"] += recent
bucket["by_status"][status] = bucket["by_status"].get(status, 0) + total
return counts
def _status_counts(
rows: Iterable[Mapping[str, Any] | Any],
*,
status_key: str,
) -> dict[str, Any]:
by_status: dict[str, int] = {}
total = 0
recent = 0
for row in rows:
item = _row_mapping(row)
status = str(item.get(status_key) or "unknown")
row_total = _int_value(item.get("total"))
by_status[status] = by_status.get(status, 0) + row_total
total += row_total
recent += _int_value(item.get("recent"))
return {
"total": total,
"recent": recent,
"by_status": by_status,
}
def _latest_flow_closure(
*,
operation_latest_rows: Iterable[Mapping[str, Any] | Any],
verifier_latest_rows: Iterable[Mapping[str, Any] | Any],
km_latest_rows: Iterable[Mapping[str, Any] | Any],
telegram_latest_rows: Iterable[Mapping[str, Any] | Any],
) -> dict[str, Any]:
operation_rows = [_row_mapping(row) for row in operation_latest_rows]
verifier_rows = [_row_mapping(row) for row in verifier_latest_rows]
km_rows = [_row_mapping(row) for row in km_latest_rows]
telegram_rows = [_row_mapping(row) for row in telegram_latest_rows]
latest_apply = next(
(
row
for row in operation_rows
if str(row.get("operation_type") or "") == "ansible_apply_executed"
),
None,
)
if latest_apply is None:
return {
"apply_op_id": None,
"incident_id": None,
"has_post_apply_verifier": False,
"has_km_writeback": False,
"has_telegram_receipt": False,
"closed": False,
"missing": [
"ansible_apply_executed",
"post_apply_verifier",
"km_writeback",
"telegram_receipt",
],
}
apply_op_id = str(latest_apply.get("op_id") or "")
incident_id = str(latest_apply.get("incident_id") or "")
km_path_type = f"ansible_apply_receipt:{apply_op_id[:8]}" if apply_op_id else ""
has_verifier = any(
str(row.get("apply_op_id") or "") == apply_op_id
for row in verifier_rows
)
has_km = any(
str(row.get("path_type") or "") == km_path_type
or (
incident_id
and str(row.get("related_incident_id") or "") == incident_id
)
for row in km_rows
)
has_telegram = any(
str(row.get("send_status") or "") == "sent"
and str(row.get("action") or "") == "controlled_apply_result"
and (
not incident_id
or str(row.get("incident_id") or "") == incident_id
)
for row in telegram_rows
)
missing = [
name
for name, present in (
("post_apply_verifier", has_verifier),
("km_writeback", has_km),
("telegram_receipt", has_telegram),
)
if not present
]
return {
"apply_op_id": apply_op_id or None,
"incident_id": incident_id or None,
"has_post_apply_verifier": has_verifier,
"has_km_writeback": has_km,
"has_telegram_receipt": has_telegram,
"closed": not missing,
"missing": missing,
}
def build_runtime_receipt_readback_from_rows(
*,
project_id: str = _DEFAULT_PROJECT_ID,
lookback_hours: int = _DEFAULT_LOOKBACK_HOURS,
db_read_status: str = "ok",
operation_count_rows: Iterable[Mapping[str, Any] | Any] = (),
operation_latest_rows: Iterable[Mapping[str, Any] | Any] = (),
verifier_count_rows: Iterable[Mapping[str, Any] | Any] = (),
verifier_latest_rows: Iterable[Mapping[str, Any] | Any] = (),
km_count_rows: Iterable[Mapping[str, Any] | Any] = (),
km_latest_rows: Iterable[Mapping[str, Any] | Any] = (),
telegram_count_rows: Iterable[Mapping[str, Any] | Any] = (),
telegram_latest_rows: Iterable[Mapping[str, Any] | Any] = (),
error_type: str | None = None,
) -> dict[str, Any]:
"""Build the live executor receipt readback from already-fetched rows."""
operation_latest = list(operation_latest_rows)
verifier_latest = list(verifier_latest_rows)
km_latest = list(km_latest_rows)
telegram_latest = list(telegram_latest_rows)
operation_summary = _operation_counts(operation_count_rows)
verifier_summary = _status_counts(
verifier_count_rows,
status_key="verification_result",
)
km_summary = _status_counts(km_count_rows, status_key="status")
telegram_summary = _status_counts(telegram_count_rows, status_key="send_status")
latest_closure = _latest_flow_closure(
operation_latest_rows=operation_latest,
verifier_latest_rows=verifier_latest,
km_latest_rows=km_latest,
telegram_latest_rows=telegram_latest,
)
apply_summary = operation_summary.get("ansible_apply_executed") or {}
readback = {
"schema_version": _LIVE_READBACK_SCHEMA_VERSION,
"project_id": project_id,
"lookback_hours": max(1, int(lookback_hours or _DEFAULT_LOOKBACK_HOURS)),
"db_read_status": db_read_status,
"writes_on_read": False,
"ansible_operations": {
"counts": operation_summary,
"latest": _sanitize_latest_rows(
operation_latest,
allowed_keys=(
"op_id",
"parent_op_id",
"operation_type",
"status",
"actor",
"incident_id",
"catalog_id",
"playbook_path",
"execution_mode",
"returncode",
"duration_ms",
"created_at",
),
),
},
"ansible_apply_executed": {
"total": _int_value(apply_summary.get("total")),
"recent": _int_value(apply_summary.get("recent")),
"by_status": apply_summary.get("by_status") or {},
},
"post_apply_verifier": {
**verifier_summary,
"latest": _sanitize_latest_rows(
verifier_latest,
allowed_keys=(
"id",
"incident_id",
"matched_playbook_id",
"verification_result",
"apply_op_id",
"catalog_id",
"playbook_path",
"returncode",
"collected_at",
),
),
},
"km_writeback": {
**km_summary,
"latest": _sanitize_latest_rows(
km_latest,
allowed_keys=(
"id",
"title",
"related_incident_id",
"related_playbook_id",
"path_type",
"status",
"created_by",
"created_at",
),
),
},
"telegram_receipt": {
**telegram_summary,
"latest": _sanitize_latest_rows(
telegram_latest,
allowed_keys=(
"message_id",
"run_id",
"message_type",
"send_status",
"provider_message_id",
"incident_id",
"action",
"queued_at",
"sent_at",
),
),
},
"latest_flow_closure": latest_closure,
}
if error_type:
readback["error"] = {
"type": error_type,
"message": "runtime receipt DB read failed; see API logs",
}
return readback
def _attach_runtime_receipt_readback(
payload: dict[str, Any],
readback: dict[str, Any],
) -> dict[str, Any]:
payload["runtime_receipt_readback"] = readback
rollups = payload.setdefault("rollups", {})
rollups.update({
"live_ansible_apply_executed_count": _int_value(
readback.get("ansible_apply_executed", {}).get("total")
),
"live_post_apply_verifier_count": _int_value(
readback.get("post_apply_verifier", {}).get("total")
),
"live_km_writeback_count": _int_value(
readback.get("km_writeback", {}).get("total")
),
"live_telegram_receipt_count": _int_value(
readback.get("telegram_receipt", {}).get("total")
),
"live_executor_latest_flow_closed_count": (
1
if (readback.get("latest_flow_closure") or {}).get("closed") is True
else 0
),
})
return payload
def build_ai_agent_autonomous_runtime_control() -> dict[str, Any]:
"""Build the current AI Agent autonomy control-plane readback."""
allowed_risks = _allowed_risk_levels()
report_cadences = [
{
"cadence": "daily",
"display_name": "日報",
"schedule": f"每日 {DAILY_REPORT_HOUR_TAIPEI:02d}:00 台北時間",
"worker": "report_generation_service.run_daily_report_loop",
"telegram_gateway_delivery_enabled": True,
"direct_bot_api_allowed": False,
"receipt_source": "daily_report_sent log + Telegram Gateway result",
},
{
"cadence": "weekly",
"display_name": "週報",
"schedule": (
f"每週五 {WEEKLY_REPORT_HOUR_TAIPEI:02d}:00 台北時間"
if WEEKLY_REPORT_WEEKDAY_TAIPEI == 4
else f"每週 weekday={WEEKLY_REPORT_WEEKDAY_TAIPEI} {WEEKLY_REPORT_HOUR_TAIPEI:02d}:00 台北時間"
),
"worker": "report_generation_service.run_weekly_report_loop",
"telegram_gateway_delivery_enabled": True,
"direct_bot_api_allowed": False,
"receipt_source": "weekly_report_sent log + Telegram Gateway result",
},
{
"cadence": "monthly",
"display_name": "月報",
"schedule": f"每月 {MONTHLY_REPORT_DAY_TAIPEI}{MONTHLY_REPORT_HOUR_TAIPEI:02d}:00 台北時間",
"worker": "report_generation_service.run_monthly_report_loop",
"telegram_gateway_delivery_enabled": True,
"direct_bot_api_allowed": False,
"receipt_source": "monthly_report_sent log + Telegram Gateway result",
},
]
executor_receipts = [
{
"operation_type": "ansible_candidate_matched",
"owner_agent": "Hermes",
"purpose": "把修復候選寫入 executor 可認領佇列",
"writes_runtime_state": False,
},
{
"operation_type": "ansible_check_mode_executed",
"owner_agent": "AwoooP Ansible check-mode worker",
"purpose": "執行 ansible-playbook --check --diff 並留下乾跑收據",
"writes_runtime_state": False,
},
{
"operation_type": "ansible_apply_executed",
"owner_agent": "AwoooP controlled apply worker",
"purpose": "check-mode 通過後,對 allowlisted low / medium / high PlayBook 受控 apply",
"writes_runtime_state": True,
},
{
"operation_type": "incident_evidence.post_execution_state",
"owner_agent": "post_apply_verifier",
"purpose": "apply 後寫入 verifier 結果與 post-execution evidence",
"writes_runtime_state": True,
},
{
"operation_type": "knowledge_entries",
"owner_agent": "Hermes",
"purpose": "把已驗證執行沉澱成 KM / PlayBook trust 候選",
"writes_runtime_state": True,
},
]
hard_blockers = [
"secret_token_private_key_cookie_session_auth_header_cleartext",
"drop_truncate_restore_prune_destructive_database_operation",
"reboot_node_drain_irreversible_firewall_or_host_lockout",
"credentialed_exploit_or_external_active_scan",
"new_paid_provider_cost_ceiling_or_provider_switch_without_replay_shadow_canary",
"force_push_delete_repo_refs_or_visibility_change",
"critical_or_break_glass_route_without_explicit_break_glass_contract",
]
legacy_overrides = [
{
"legacy_area": "report_status_board_no_live_send",
"current_effect": "overridden",
"new_behavior": "日報 / 週報 / 月報透過 Telegram Gateway 排程派送",
},
{
"legacy_area": "report_live_delivery_owner_review_required",
"current_effect": "overridden",
"new_behavior": "報告派送走低/中/高風險自動化政策critical 才 break-glass",
},
{
"legacy_area": "high_risk_owner_review_queue",
"current_effect": "overridden_for_high_non_critical",
"new_behavior": "high 風險允許 controlled applycritical / hard blocker 仍不自動",
},
{
"legacy_area": "telegram_no_send_preview_only",
"current_effect": "overridden",
"new_behavior": "用 Telegram Gateway 實送報告與 actionable receipt不直接暴露 Bot API",
},
]
payload = {
"schema_version": _SCHEMA_VERSION,
"generated_at": datetime.now(timezone.utc).isoformat(),
"program_status": {
"current_task_id": "P2-416-D1N",
"status": "current_directive_control_plane_active",
"runtime_authority": _RUNTIME_AUTHORITY,
"deploy_readback_marker": _DEPLOY_READBACK_MARKER,
"deploy_attempt_note": _DEPLOY_ATTEMPT_NOTE,
"legacy_no_send_no_live_rules_overridden": True,
"implementation_completion_percent": 88,
"status_note": (
"目前有效規則low / medium / high 風險由 AI Agent 在 allowlist、"
"Ansible check-mode、verifier、rollback、KM 與 Telegram receipt 下受控自動處理。"
),
},
"current_policy": {
"low_risk_controlled_apply_allowed": "low" in allowed_risks,
"medium_risk_controlled_apply_allowed": "medium" in allowed_risks,
"high_risk_controlled_apply_allowed": "high" in allowed_risks,
"critical_break_glass_required": True,
"owner_review_required_for_low_medium_high": False,
"direct_bot_api_allowed": False,
"telegram_gateway_required": True,
"post_apply_verifier_required": True,
"km_learning_writeback_required": True,
},
"runtime_switches": {
"ansible_check_mode_worker_enabled": bool(settings.ENABLE_AWOOOP_ANSIBLE_CHECK_MODE_WORKER),
"ansible_controlled_apply_enabled": bool(settings.ENABLE_AWOOOP_ANSIBLE_CONTROLLED_APPLY),
"ansible_controlled_apply_allowed_risk_levels": allowed_risks,
"ansible_check_mode_interval_seconds": settings.AWOOOP_ANSIBLE_CHECK_MODE_INTERVAL_SECONDS,
"ansible_check_mode_batch_limit": settings.AWOOOP_ANSIBLE_CHECK_MODE_BATCH_LIMIT,
"ansible_check_mode_timeout_seconds": settings.AWOOOP_ANSIBLE_CHECK_MODE_TIMEOUT_SECONDS,
"ansible_controlled_apply_timeout_seconds": settings.AWOOOP_ANSIBLE_CONTROLLED_APPLY_TIMEOUT_SECONDS,
},
"agent_roles": [
{
"agent_id": "openclaw",
"role": "仲裁 / hard blocker / replay-shadow-canary gate",
"current_job": "只阻擋真正 critical 與 hard blocker不再用身份保護舊架構",
},
{
"agent_id": "hermes",
"role": "報告 / Telegram digest / KM 與 PlayBook trust writeback",
"current_job": "日週月報、收據摘要與 verifier 後學習沉澱",
},
{
"agent_id": "nemotron",
"role": "市場技術雷達 / no-write replay / challenger scorecard",
"current_job": "用市場與回放數據挑戰 OpenClaw / provider / Agent 組合",
},
{
"agent_id": "awooop_ansible_worker",
"role": "executor",
"current_job": "candidate → check-mode → controlled apply → verifier → KM",
},
{
"agent_id": "telegram_ops",
"role": "Telegram Gateway receipt",
"current_job": "群組報告、actionable receipt、失敗告警不展示敏感值或未脫敏資料",
},
],
"report_delivery": {
"status": "telegram_gateway_delivery_enabled",
"cadences": report_cadences,
},
"controlled_executor": {
"status": "check_mode_then_apply_enabled"
if settings.ENABLE_AWOOOP_ANSIBLE_CONTROLLED_APPLY
else "check_mode_only_by_config",
"operation_receipts": executor_receipts,
"required_flow": [
"allowlisted_candidate",
"ansible_check_mode_success",
"controlled_apply",
"post_apply_verifier",
"auto_repair_execution_receipt",
"km_learning_writeback",
"telegram_receipt_or_alert",
],
},
"legacy_policy_overrides": legacy_overrides,
"hard_blockers": hard_blockers,
"visibility_contract": {
"frontend_displays_runtime_truth": True,
"work_window_transcript_display_allowed": False,
"prompt_body_display_allowed": False,
"internal_reasoning_display_allowed": False,
"sensitive_value_display_allowed": False,
"telegram_unredacted_payload_display_allowed": False,
"lan_topology_redaction_required": True,
},
"rollups": {
"automated_risk_tier_count": sum(1 for risk in ("low", "medium", "high") if risk in allowed_risks),
"hard_blocker_count": len(hard_blockers),
"report_cadence_enabled_count": len(report_cadences),
"telegram_gateway_delivery_enabled_count": sum(
1 for item in report_cadences if item["telegram_gateway_delivery_enabled"]
),
"direct_bot_api_allowed_count": 0,
"controlled_executor_operation_receipt_count": len(executor_receipts),
"runtime_write_receipt_type_count": sum(
1 for item in executor_receipts if item["writes_runtime_state"]
),
"legacy_policy_overridden_count": len(legacy_overrides),
},
}
_attach_runtime_receipt_readback(
payload,
build_runtime_receipt_readback_from_rows(
project_id=_DEFAULT_PROJECT_ID,
db_read_status="not_queried",
),
)
_validate_payload(payload)
return payload
async def load_ai_agent_autonomous_runtime_receipt_readback(
*,
project_id: str = _DEFAULT_PROJECT_ID,
lookback_hours: int = _DEFAULT_LOOKBACK_HOURS,
limit: int = 20,
) -> dict[str, Any]:
"""Read live executor receipts without sending messages or mutating runtime state."""
params = {
"project_id": project_id,
"lookback_hours": max(1, int(lookback_hours or _DEFAULT_LOOKBACK_HOURS)),
"limit": max(1, int(limit or 20)),
}
try:
async with get_db_context(project_id) as db:
await db.execute(text("SET LOCAL statement_timeout = '5000ms'"))
operation_counts = (
await db.execute(text(_RUNTIME_OPERATION_COUNTS_SQL), params)
).mappings().all()
operation_latest = (
await db.execute(text(_RUNTIME_OPERATION_LATEST_SQL), params)
).mappings().all()
verifier_counts = (
await db.execute(text(_RUNTIME_VERIFIER_COUNTS_SQL), params)
).mappings().all()
verifier_latest = (
await db.execute(text(_RUNTIME_VERIFIER_LATEST_SQL), params)
).mappings().all()
km_counts = (
await db.execute(text(_RUNTIME_KM_COUNTS_SQL), params)
).mappings().all()
km_latest = (
await db.execute(text(_RUNTIME_KM_LATEST_SQL), params)
).mappings().all()
telegram_counts = (
await db.execute(text(_RUNTIME_TELEGRAM_COUNTS_SQL), params)
).mappings().all()
telegram_latest = (
await db.execute(text(_RUNTIME_TELEGRAM_LATEST_SQL), params)
).mappings().all()
except Exception as exc:
logger.warning(
"ai_agent_autonomous_runtime_receipt_readback_failed",
project_id=project_id,
error_type=type(exc).__name__,
)
return build_runtime_receipt_readback_from_rows(
project_id=project_id,
lookback_hours=params["lookback_hours"],
db_read_status="unavailable",
error_type=type(exc).__name__,
)
return build_runtime_receipt_readback_from_rows(
project_id=project_id,
lookback_hours=params["lookback_hours"],
db_read_status="ok",
operation_count_rows=operation_counts,
operation_latest_rows=operation_latest,
verifier_count_rows=verifier_counts,
verifier_latest_rows=verifier_latest,
km_count_rows=km_counts,
km_latest_rows=km_latest,
telegram_count_rows=telegram_counts,
telegram_latest_rows=telegram_latest,
)
async def build_ai_agent_autonomous_runtime_control_with_live_readback(
*,
project_id: str = _DEFAULT_PROJECT_ID,
lookback_hours: int = _DEFAULT_LOOKBACK_HOURS,
) -> dict[str, Any]:
"""Build the control plane and attach live DB receipt readback."""
payload = build_ai_agent_autonomous_runtime_control()
readback = await load_ai_agent_autonomous_runtime_receipt_readback(
project_id=project_id,
lookback_hours=lookback_hours,
)
_attach_runtime_receipt_readback(payload, readback)
_validate_payload(payload)
return payload
_RUNTIME_OPERATION_COUNTS_SQL = """
SELECT
operation_type,
status,
count(*) AS total,
count(*) FILTER (
WHERE created_at >= NOW() - (:lookback_hours * INTERVAL '1 hour')
) AS recent
FROM automation_operation_log
WHERE operation_type IN (
'ansible_candidate_matched',
'ansible_check_mode_executed',
'ansible_apply_executed',
'ansible_rollback_executed',
'ansible_execution_skipped'
)
GROUP BY operation_type, status
ORDER BY operation_type, status
"""
_RUNTIME_OPERATION_LATEST_SQL = """
SELECT
op_id::text AS op_id,
parent_op_id::text AS parent_op_id,
operation_type,
status,
actor,
coalesce(incident_id::text, input ->> 'incident_id') AS incident_id,
input ->> 'catalog_id' AS catalog_id,
coalesce(input ->> 'apply_playbook_path', input ->> 'playbook_path') AS playbook_path,
input ->> 'execution_mode' AS execution_mode,
coalesce(output ->> 'returncode', dry_run_result ->> 'returncode') AS returncode,
duration_ms,
created_at
FROM automation_operation_log
WHERE operation_type IN (
'ansible_candidate_matched',
'ansible_check_mode_executed',
'ansible_apply_executed',
'ansible_rollback_executed',
'ansible_execution_skipped'
)
ORDER BY created_at DESC
LIMIT :limit
"""
_RUNTIME_VERIFIER_COUNTS_SQL = """
SELECT
coalesce(verification_result, 'missing') AS verification_result,
count(*) AS total,
count(*) FILTER (
WHERE collected_at >= NOW() - (:lookback_hours * INTERVAL '1 hour')
) AS recent
FROM incident_evidence
WHERE post_execution_state ->> 'apply_op_id' IS NOT NULL
GROUP BY coalesce(verification_result, 'missing')
ORDER BY verification_result
"""
_RUNTIME_VERIFIER_LATEST_SQL = """
SELECT
id,
incident_id,
matched_playbook_id,
coalesce(verification_result, 'missing') AS verification_result,
post_execution_state ->> 'apply_op_id' AS apply_op_id,
post_execution_state ->> 'catalog_id' AS catalog_id,
post_execution_state ->> 'playbook_path' AS playbook_path,
post_execution_state ->> 'returncode' AS returncode,
collected_at
FROM incident_evidence
WHERE post_execution_state ->> 'apply_op_id' IS NOT NULL
ORDER BY collected_at DESC
LIMIT :limit
"""
_RUNTIME_KM_COUNTS_SQL = """
SELECT
status,
count(*) AS total,
count(*) FILTER (
WHERE created_at >= NOW() - (:lookback_hours * INTERVAL '1 hour')
) AS recent
FROM knowledge_entries
WHERE project_id = :project_id
AND (
path_type LIKE 'ansible_apply_receipt:%'
OR tags::text LIKE '%ansible_controlled_apply%'
)
GROUP BY status
ORDER BY status
"""
_RUNTIME_KM_LATEST_SQL = """
SELECT
id,
title,
related_incident_id,
related_playbook_id,
path_type,
status,
created_by,
created_at
FROM knowledge_entries
WHERE project_id = :project_id
AND (
path_type LIKE 'ansible_apply_receipt:%'
OR tags::text LIKE '%ansible_controlled_apply%'
)
ORDER BY created_at DESC
LIMIT :limit
"""
_RUNTIME_TELEGRAM_COUNTS_SQL = """
SELECT
send_status,
count(*) AS total,
count(*) FILTER (
WHERE queued_at >= NOW() - (:lookback_hours * INTERVAL '1 hour')
) AS recent
FROM awooop_outbound_message
WHERE project_id = :project_id
AND channel_type = 'telegram'
AND source_envelope #>> '{callback_reply,action}' = 'controlled_apply_result'
GROUP BY send_status
ORDER BY send_status
"""
_RUNTIME_TELEGRAM_LATEST_SQL = """
SELECT
message_id::text AS message_id,
run_id::text AS run_id,
message_type,
send_status,
provider_message_id,
source_envelope #>> '{callback_reply,incident_id}' AS incident_id,
source_envelope #>> '{callback_reply,action}' AS action,
queued_at,
sent_at
FROM awooop_outbound_message
WHERE project_id = :project_id
AND channel_type = 'telegram'
AND source_envelope #>> '{callback_reply,action}' = 'controlled_apply_result'
ORDER BY queued_at DESC
LIMIT :limit
"""
def _validate_payload(payload: dict[str, Any]) -> None:
if payload.get("schema_version") != _SCHEMA_VERSION:
raise ValueError(f"schema_version must be {_SCHEMA_VERSION}")
status = payload.get("program_status") or {}
if status.get("runtime_authority") != _RUNTIME_AUTHORITY:
raise ValueError(f"runtime_authority must be {_RUNTIME_AUTHORITY}")
if status.get("deploy_readback_marker") != _DEPLOY_READBACK_MARKER:
raise ValueError(f"deploy_readback_marker must be {_DEPLOY_READBACK_MARKER}")
if status.get("deploy_attempt_note") != _DEPLOY_ATTEMPT_NOTE:
raise ValueError(f"deploy_attempt_note must be {_DEPLOY_ATTEMPT_NOTE}")
policy = payload.get("current_policy") or {}
for key in (
"low_risk_controlled_apply_allowed",
"medium_risk_controlled_apply_allowed",
"high_risk_controlled_apply_allowed",
"telegram_gateway_required",
"post_apply_verifier_required",
"km_learning_writeback_required",
):
if policy.get(key) is not True:
raise ValueError(f"current_policy.{key} must be true")
if policy.get("owner_review_required_for_low_medium_high") is not False:
raise ValueError("owner_review_required_for_low_medium_high must be false")
if policy.get("direct_bot_api_allowed") is not False:
raise ValueError("direct_bot_api_allowed must be false")
visibility = payload.get("visibility_contract") or {}
for key in (
"work_window_transcript_display_allowed",
"prompt_body_display_allowed",
"internal_reasoning_display_allowed",
"sensitive_value_display_allowed",
"telegram_unredacted_payload_display_allowed",
):
if visibility.get(key) is not False:
raise ValueError(f"visibility_contract.{key} must remain false")