From cc5868b657a5c123faa01f6f6d90d88c693f5515 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 3 Jul 2026 09:31:05 +0800 Subject: [PATCH] fix(awooop): surface telegram alert automation coverage --- ...gram_alert_monitoring_coverage_readback.py | 402 ++++++++++++++++++ ..._alert_monitoring_coverage_readback_api.py | 107 +++++ apps/web/messages/en.json | 6 + apps/web/messages/zh-TW.json | 6 + .../autonomous-runtime-receipt-panel.tsx | 214 +++++++++- docs/LOGBOOK.md | 21 + 6 files changed, 751 insertions(+), 5 deletions(-) diff --git a/apps/api/src/services/telegram_alert_monitoring_coverage_readback.py b/apps/api/src/services/telegram_alert_monitoring_coverage_readback.py index caf9b7842..fc0523c95 100644 --- a/apps/api/src/services/telegram_alert_monitoring_coverage_readback.py +++ b/apps/api/src/services/telegram_alert_monitoring_coverage_readback.py @@ -46,6 +46,28 @@ _ALERT_LOG_EVENT_TYPES = ( "EXECUTION_STARTED", "EXECUTION_COMPLETED", ) +_REQUIRED_TAG_DIMENSIONS = ( + ( + "project", + "project_id", + "bind every alert/log receipt to the project source of truth", + ), + ( + "product", + "product_id", + "separate AWOOOI/AwoooP/IwoooS and adjacent product automation lanes", + ), + ("website", "website_id", "cluster public website and route evidence"), + ("service", "service_name", "cluster runtime service, pod, job, or daemon signals"), + ("package", "package_name", "cluster package, dependency, and version-radar signals"), + ("tool", "tool_id", "cluster MCP, script, exporter, and connector evidence"), + ("log", "log_source", "preserve metadata-only log source identity"), + ("alert", "alertname", "bind alert fingerprints, severity, and recurrence state"), + ("playbook", "playbook_id", "route candidates into controlled apply playbooks"), + ("rag", "rag_context_ref", "make receipts retrievable by RAG without raw payloads"), + ("mcp", "mcp_evidence_ref", "bind tool evidence without bypassing the MCP registry"), + ("schedule", "schedule_id", "cluster cron, worker, and maintenance-window signals"), +) logger = get_logger(__name__) @@ -137,6 +159,12 @@ def build_telegram_alert_monitoring_coverage_readback( ai_alert_ready_total = _int( ai_alert_summary.get("learning_writeback_ready_total") ) + runtime_event_type_counts = { + str(key): _int(value) + for key, value in _dict( + runtime_log_readback.get("event_type_counts_7d") + ).items() + } coverage_matrix = _coverage_matrix( project_id=project_id, @@ -165,6 +193,38 @@ def build_telegram_alert_monitoring_coverage_readback( ai_alert_ready_total=ai_alert_ready_total, ) ready = not active_blockers + monitoring_asset_rollups = _monitoring_asset_rollups( + monitoring_inventory, + monitoring_readback_plan, + ) + alert_receipt_pipeline = _alert_receipt_pipeline( + monitoring_surface_count=monitoring_surface_count, + live_evidence_received_count=live_evidence_received_count, + monitoring_live_gap_count=monitoring_live_gap_count, + direct_gap_count=direct_gap_count, + runtime_db_ok=runtime_db_ok, + runtime_summary=runtime_summary, + ai_alert_db_ok=ai_alert_db_ok, + ai_alert_total=ai_alert_total, + ai_alert_ready_total=ai_alert_ready_total, + verifier_ready=verifier_ready, + source_ready=source_ready, + ) + work_item_progress = _work_item_progress( + monitoring_live_gap_count=monitoring_live_gap_count, + direct_gap_count=direct_gap_count, + runtime_db_ok=runtime_db_ok, + runtime_summary=runtime_summary, + ai_alert_db_ok=ai_alert_db_ok, + ai_alert_total=ai_alert_total, + ai_alert_ready_total=ai_alert_ready_total, + verifier_ready=verifier_ready, + source_ready=source_ready, + ) + ai_controlled_gap_queue = _ai_controlled_gap_queue( + active_blockers=active_blockers, + monitoring_asset_rollups=monitoring_asset_rollups, + ) return { "schema_version": SCHEMA_VERSION, @@ -178,6 +238,14 @@ def build_telegram_alert_monitoring_coverage_readback( ), "mode": "metadata_only_db_log_ai_context_readback", "operator_answer": { + "all_telegram_monitoring_alerts_fully_audited": ready, + "all_telegram_monitoring_alert_surfaces_have_db_or_log_receipt": ( + monitoring_live_gap_count == 0 and runtime_db_ok and ai_alert_db_ok + ), + "all_telegram_monitoring_alerts_ai_agent_automation_ready": ( + ready and ai_alert_total > 0 and ai_alert_ready_total >= ai_alert_total + ), + "ai_controlled_gap_queue_present": bool(ai_controlled_gap_queue), "all_known_telegram_egress_routes_controlled": direct_gap_count == 0, "all_known_telegram_egress_routes_have_db_or_log_receipt": ( direct_gap_count == 0 and ai_alert_db_ok and runtime_db_ok @@ -248,8 +316,27 @@ def build_telegram_alert_monitoring_coverage_readback( "source_contract_ready": source_ready, "runtime_db_readback_ok": runtime_db_ok, "ai_alert_card_db_readback_ok": ai_alert_db_ok, + "runtime_event_type_with_receipts_count": len(runtime_event_type_counts), + "alert_receipt_pipeline_stage_count": len(alert_receipt_pipeline), + "alert_receipt_pipeline_ready_count": sum( + 1 for stage in alert_receipt_pipeline if stage["ready"] + ), + "ai_controlled_gap_queue_count": len(ai_controlled_gap_queue), + "required_tag_dimension_count": len(_REQUIRED_TAG_DIMENSIONS), + "monitoring_asset_config_kind_count": len( + monitoring_asset_rollups["by_config_kind"] + ), + "telegram_monitoring_audit_completion_percent": work_item_progress[ + "completion_percent" + ], "full_coverage_ready": ready, }, + "required_tag_dimensions": _required_tag_dimensions(), + "monitoring_asset_rollups": monitoring_asset_rollups, + "runtime_event_type_counts_7d": runtime_event_type_counts, + "alert_receipt_pipeline": alert_receipt_pipeline, + "work_item_progress": work_item_progress, + "ai_controlled_gap_queue": ai_controlled_gap_queue, "coverage_matrix": coverage_matrix, "active_blockers": active_blockers, "next_controlled_actions": _next_controlled_actions(active_blockers), @@ -435,6 +522,240 @@ async def _load_runtime_log_readback_once(*, project_id: str) -> dict[str, Any]: } +def _required_tag_dimensions() -> list[dict[str, str]]: + return [ + { + "dimension": dimension, + "canonical_field": canonical_field, + "why": why, + } + for dimension, canonical_field, why in _REQUIRED_TAG_DIMENSIONS + ] + + +def _monitoring_asset_rollups( + monitoring_inventory: Mapping[str, Any], + monitoring_readback_plan: Mapping[str, Any], +) -> dict[str, Any]: + surfaces = _list_of_dicts(monitoring_inventory.get("observability_surfaces")) + readback_candidates = _list_of_dicts( + monitoring_readback_plan.get("readback_candidates") + ) + inventory_source = readback_candidates or surfaces + accepted_live_receipts = sum( + 1 + for item in inventory_source + if bool(item.get("post_incident_readback_accepted")) + or bool(item.get("live_evidence_received")) + ) + live_receipt_gap_samples = [ + _monitoring_gap_sample(item) + for item in inventory_source + if bool(item.get("requires_live_evidence")) + and not bool(item.get("post_incident_readback_accepted")) + and not bool(item.get("live_evidence_received")) + ][:12] + return { + "surface_count": len(surfaces) or len(inventory_source), + "readback_candidate_count": len(readback_candidates), + "accepted_live_receipt_count": accepted_live_receipts, + "live_receipt_gap_count": max( + len(inventory_source) - accepted_live_receipts, + 0, + ), + "write_capable_surface_count": sum( + 1 for item in inventory_source if bool(item.get("write_capable_surface")) + ), + "requires_live_evidence_count": sum( + 1 for item in inventory_source if bool(item.get("requires_live_evidence")) + ), + "by_config_kind": _count_by_key(inventory_source, "config_kind"), + "by_control_tier": _count_by_key(inventory_source, "control_tier"), + "by_status": _count_by_key(inventory_source, "status"), + "by_reviewer_outcome": _count_by_key(inventory_source, "reviewer_outcome"), + "by_expected_scope_prefix": _count_by_expected_scope_prefix(inventory_source), + "live_receipt_gap_samples": live_receipt_gap_samples, + } + + +def _monitoring_gap_sample(item: Mapping[str, Any]) -> dict[str, Any]: + return { + "surface_id": str(item.get("surface_id") or ""), + "label": str(item.get("label") or ""), + "config_kind": str(item.get("config_kind") or "unknown"), + "control_tier": str(item.get("control_tier") or "unknown"), + "expected_scope": str(item.get("expected_scope") or ""), + "repo_source_path": str( + item.get("repo_source_path") or item.get("source_path") or "" + ), + "status": str(item.get("status") or "waiting_live_receipt"), + "reviewer_outcome": str( + item.get("reviewer_outcome") or "waiting_post_incident_readback" + ), + "requires_live_evidence": bool(item.get("requires_live_evidence")), + "write_capable_surface": bool(item.get("write_capable_surface")), + "missing_receipt_fields": [ + key + for key in ( + "monitoring_incident_or_change_ref", + "receiver_receipt_readback_ref", + "notification_delivery_metadata_ref", + "alert_chain_health_readback_ref", + "postcheck_readback_ref", + "recurrence_guard_ref", + ) + if not item.get(key) + ], + } + + +def _alert_receipt_pipeline( + *, + monitoring_surface_count: int, + live_evidence_received_count: int, + monitoring_live_gap_count: int, + direct_gap_count: int, + runtime_db_ok: bool, + runtime_summary: Mapping[str, Any], + ai_alert_db_ok: bool, + ai_alert_total: int, + ai_alert_ready_total: int, + verifier_ready: bool, + source_ready: bool, +) -> list[dict[str, Any]]: + runtime_total = _int(runtime_summary.get("alert_operation_log_total_7d")) + outbound_total = _int(runtime_summary.get("outbound_telegram_total_7d")) + return [ + { + "stage_id": "monitoring_inventory_live_receipts", + "ready": monitoring_live_gap_count == 0, + "receipt_count": min(live_evidence_received_count, monitoring_surface_count), + "gap_count": monitoring_live_gap_count, + "next_action": "ingest_monitoring_surface_live_receipts_to_db_log", + }, + { + "stage_id": "telegram_egress_gateway_control", + "ready": direct_gap_count == 0, + "receipt_count": 0, + "gap_count": direct_gap_count, + "next_action": "keep_all_telegram_alerts_on_gateway_receipt_path", + }, + { + "stage_id": "alert_operation_log_runtime_readback", + "ready": runtime_db_ok and runtime_total > 0, + "receipt_count": runtime_total, + "gap_count": 0 if runtime_db_ok and runtime_total > 0 else 1, + "next_action": "append_alert_lifecycle_events_with_metadata_tags", + }, + { + "stage_id": "awooop_outbound_telegram_ai_alert_card_mirror", + "ready": ai_alert_db_ok and ai_alert_total > 0 and outbound_total >= 0, + "receipt_count": ai_alert_total, + "gap_count": 0 if ai_alert_db_ok and ai_alert_total > 0 else 1, + "next_action": "mirror_telegram_cards_with_ai_automation_metadata", + }, + { + "stage_id": "km_rag_mcp_playbook_ai_agent_learning_refs", + "ready": ai_alert_total > 0 and ai_alert_ready_total >= ai_alert_total, + "receipt_count": ai_alert_ready_total, + "gap_count": max(ai_alert_total - ai_alert_ready_total, 0) + if ai_alert_total > 0 + else 1, + "next_action": "write_metadata_receipts_to_km_rag_mcp_playbook_context", + }, + { + "stage_id": "ai_loop_post_apply_verifier", + "ready": verifier_ready, + "receipt_count": 1 if verifier_ready else 0, + "gap_count": 0 if verifier_ready else 1, + "next_action": "verify_context_receipts_before_calling_ai_automation_ready", + }, + { + "stage_id": "source_contract_and_operator_surface", + "ready": source_ready, + "receipt_count": 1 if source_ready else 0, + "gap_count": 0 if source_ready else 1, + "next_action": "surface_receipts_in_runs_work_items_alerts", + }, + ] + + +def _work_item_progress( + *, + monitoring_live_gap_count: int, + direct_gap_count: int, + runtime_db_ok: bool, + runtime_summary: Mapping[str, Any], + ai_alert_db_ok: bool, + ai_alert_total: int, + ai_alert_ready_total: int, + verifier_ready: bool, + source_ready: bool, +) -> dict[str, Any]: + runtime_total = _int(runtime_summary.get("alert_operation_log_total_7d")) + checks = [ + ("source_contract_ready", source_ready), + ("direct_telegram_send_gap_closed", direct_gap_count == 0), + ("runtime_alert_operation_log_readback_ready", runtime_db_ok), + ("runtime_alert_operation_log_has_recent_events", runtime_total > 0), + ("ai_alert_card_delivery_readback_ready", ai_alert_db_ok), + ("ai_alert_card_delivery_has_receipts", ai_alert_total > 0), + ( + "ai_alert_card_learning_writeback_refs_ready", + ai_alert_total > 0 and ai_alert_ready_total >= ai_alert_total, + ), + ("ai_loop_post_apply_verifier_ready", verifier_ready), + ("monitoring_live_receipt_coverage_complete", monitoring_live_gap_count == 0), + ] + completed = sum(1 for _, passed in checks if passed) + return { + "work_item_id": "CIR-P0-TG-001", + "priority": "P0", + "completed_check_count": completed, + "total_check_count": len(checks), + "completion_percent": round(completed * 100 / len(checks), 1), + "checks": [ + { + "check_id": check_id, + "status": "completed" if passed else "in_progress", + } + for check_id, passed in checks + ], + } + + +def _ai_controlled_gap_queue( + *, + active_blockers: list[str], + monitoring_asset_rollups: Mapping[str, Any], +) -> list[dict[str, Any]]: + gap_samples = _list_of_dicts(monitoring_asset_rollups.get("live_receipt_gap_samples")) + queue: list[dict[str, Any]] = [] + for index, blocker in enumerate(active_blockers, start=1): + queue.append({ + "work_item_id": f"CIR-P0-TG-001-{index:02d}", + "parent_work_item_id": "CIR-P0-TG-001", + "priority": "P0" if index <= 4 else "P1", + "blocker": blocker, + "status": "queued_ai_controlled_apply", + "owner_review_required_for_low_medium_high": False, + "critical_break_glass_required": True, + "target_selector": _gap_target_selector(blocker), + "controlled_next_action": _gap_next_action(blocker), + "post_verifier": ( + "/api/v1/agents/telegram-alert-monitoring-coverage-readback" + ), + "sample_surfaces": gap_samples[:3] if blocker.startswith("monitoring_") else [], + "operation_boundaries": { + "requires_secret": False, + "requires_runtime_send": False, + "stores_raw_payload": False, + "destructive_action": False, + }, + }) + return queue + + def _coverage_matrix( *, project_id: str, @@ -781,6 +1102,64 @@ def _source_contract_blockers(source_contract: Mapping[str, Any]) -> list[str]: ] +def _gap_target_selector(blocker: str) -> dict[str, Any]: + if blocker.startswith("monitoring_live_receipt_gap"): + return { + "target_surface": "monitoring_inventory", + "target_tables": ["alert_operation_log", "awooop_outbound_message"], + "target_contexts": ["km", "rag", "playbook", "mcp", "ai_agent"], + } + if blocker.startswith("telegram_direct_send_gap"): + return { + "target_surface": "telegram_egress", + "target_tables": ["awooop_outbound_message"], + "target_contexts": ["telegram_gateway"], + } + if blocker.startswith("awooop_ai_alert_card"): + return { + "target_surface": "awooop_ai_alert_card_delivery", + "target_tables": ["awooop_outbound_message"], + "target_contexts": ["km", "rag", "playbook", "mcp", "verifier", "ai_agent"], + } + if blocker.startswith("runtime_alert_operation_log"): + return { + "target_surface": "alert_operation_log", + "target_tables": ["alert_operation_log"], + "target_contexts": ["alert_lifecycle", "ai_lane"], + } + if blocker.startswith("source_contract_missing"): + return { + "target_surface": "source_contract", + "target_tables": [], + "target_contexts": ["runs", "work_items", "alerts"], + } + return { + "target_surface": "telegram_monitoring_ai_automation", + "target_tables": ["alert_operation_log", "awooop_outbound_message"], + "target_contexts": ["km", "rag", "playbook", "mcp", "verifier", "ai_agent"], + } + + +def _gap_next_action(blocker: str) -> str: + if blocker.startswith("monitoring_live_receipt_gap"): + return "classify_monitoring_surfaces_and_ingest_metadata_live_receipts" + if blocker.startswith("telegram_direct_send_gap"): + return "migrate_direct_sender_to_telegram_gateway_receipt_path" + if blocker == "runtime_alert_operation_log_db_readback_unavailable": + return "retry_runtime_alert_operation_log_readback_with_bounded_timeout" + if blocker == "awooop_ai_alert_card_delivery_db_readback_unavailable": + return "retry_awooop_ai_alert_card_delivery_readback" + if blocker == "awooop_ai_alert_card_delivery_receipts_missing": + return "bind_telegram_alert_cards_to_awooop_outbound_message_mirror" + if blocker == "awooop_ai_alert_card_learning_writeback_refs_missing": + return "populate_km_rag_mcp_playbook_ai_agent_learning_refs" + if blocker == "telegram_alert_ai_loop_post_apply_verifier_not_ready": + return "run_ai_loop_context_post_apply_verifier_readback" + if blocker.startswith("source_contract_missing"): + return "restore_operator_visible_source_contract_marker" + return "continue_ai_controlled_gap_closure" + + def _load_required_json(path: Path) -> dict[str, Any]: if not path.exists(): raise FileNotFoundError(f"required monitoring coverage source missing: {path}") @@ -841,6 +1220,29 @@ def _read_source_contract_text(repo_root: Path, relative_path: str) -> str: return "" +def _list_of_dicts(value: Any) -> list[dict[str, Any]]: + if not isinstance(value, list): + return [] + return [dict(item) for item in value if isinstance(item, Mapping)] + + +def _count_by_key(items: list[dict[str, Any]], key: str) -> dict[str, int]: + counts: dict[str, int] = {} + for item in items: + value = str(item.get(key) or "unknown") + counts[value] = counts.get(value, 0) + 1 + return dict(sorted(counts.items())) + + +def _count_by_expected_scope_prefix(items: list[dict[str, Any]]) -> dict[str, int]: + counts: dict[str, int] = {} + for item in items: + expected_scope = str(item.get("expected_scope") or "unknown") + prefix = expected_scope.split("_", 1)[0] or "unknown" + counts[prefix] = counts.get(prefix, 0) + 1 + return dict(sorted(counts.items())) + + def _dict(value: Any) -> dict[str, Any]: return dict(value) if isinstance(value, Mapping) else {} diff --git a/apps/api/tests/test_telegram_alert_monitoring_coverage_readback_api.py b/apps/api/tests/test_telegram_alert_monitoring_coverage_readback_api.py index 1b8962ce6..e6c0abdeb 100644 --- a/apps/api/tests/test_telegram_alert_monitoring_coverage_readback_api.py +++ b/apps/api/tests/test_telegram_alert_monitoring_coverage_readback_api.py @@ -39,12 +39,60 @@ def _payload() -> dict: "alert_rule_surface_count": 13, "telegram_surface_count": 3, }, + "observability_surfaces": [ + { + "surface_id": "prometheus_k8s_base_config", + "label": "K8s Prometheus base config", + "config_kind": "prometheus_config", + "control_tier": "C1", + "expected_scope": "k8s_monitoring_prometheus_base", + "requires_live_evidence": True, + "live_evidence_received": False, + }, + { + "surface_id": "telegram_gateway_config", + "label": "Telegram Gateway config", + "config_kind": "telegram_gateway", + "control_tier": "C1", + "expected_scope": "telegram_gateway", + "requires_live_evidence": True, + "live_evidence_received": False, + }, + ], }, monitoring_readback_plan={ "schema_version": "monitoring_post_incident_readback_plan_v1", "summary": { "readback_candidate_count": 60, }, + "readback_candidates": [ + { + "surface_id": "prometheus_k8s_base_config", + "label": "K8s Prometheus base config", + "config_kind": "prometheus_config", + "control_tier": "C1", + "expected_scope": "k8s_monitoring_prometheus_base", + "repo_source_path": "k8s/monitoring/prometheus.yml", + "requires_live_evidence": True, + "write_capable_surface": False, + "post_incident_readback_accepted": False, + "status": "waiting_post_incident_readback", + "reviewer_outcome": "waiting_post_incident_readback", + }, + { + "surface_id": "telegram_gateway_config", + "label": "Telegram Gateway config", + "config_kind": "telegram_gateway", + "control_tier": "C1", + "expected_scope": "telegram_gateway", + "repo_source_path": "apps/api/src/services/telegram_gateway.py", + "requires_live_evidence": True, + "write_capable_surface": False, + "post_incident_readback_accepted": False, + "status": "waiting_post_incident_readback", + "reviewer_outcome": "waiting_post_incident_readback", + }, + ], }, telegram_matrix={ "schema_version": "telegram_alert_ai_automation_matrix_v1", @@ -84,6 +132,13 @@ def _payload() -> dict: "telegram_sent_event_count_7d": 8, "km_converted_event_count_7d": 4, "playbook_draft_event_count_7d": 3, + "outbound_telegram_total_7d": 8, + }, + "event_type_counts_7d": { + "ALERT_RECEIVED": 15, + "TELEGRAM_SENT": 8, + "KM_CONVERTED": 4, + "PLAYBOOK_DRAFT_CREATED": 3, }, }, source_contract=_source_contract(), @@ -120,6 +175,23 @@ def test_telegram_alert_monitoring_coverage_readback_surfaces_live_gaps(): is False ) assert payload["operator_answer"]["manual_default_terminal_state_allowed"] is False + assert ( + payload["operator_answer"]["all_telegram_monitoring_alerts_fully_audited"] + is False + ) + assert ( + payload["operator_answer"][ + "all_telegram_monitoring_alert_surfaces_have_db_or_log_receipt" + ] + is False + ) + assert ( + payload["operator_answer"][ + "all_telegram_monitoring_alerts_ai_agent_automation_ready" + ] + is False + ) + assert payload["operator_answer"]["ai_controlled_gap_queue_present"] is True summary = payload["summary"] assert summary["monitoring_surface_count"] == 60 @@ -130,8 +202,43 @@ def test_telegram_alert_monitoring_coverage_readback_surfaces_live_gaps(): assert summary["verified_target_count"] == 6 assert summary["runtime_telegram_sent_event_count_7d"] == 8 assert summary["source_contract_ready"] is True + assert summary["runtime_event_type_with_receipts_count"] == 4 + assert summary["alert_receipt_pipeline_stage_count"] == 7 + assert summary["alert_receipt_pipeline_ready_count"] == 6 + assert summary["ai_controlled_gap_queue_count"] == 1 + assert summary["required_tag_dimension_count"] == 12 + assert summary["monitoring_asset_config_kind_count"] == 2 + assert summary["telegram_monitoring_audit_completion_percent"] == 88.9 assert "monitoring_live_receipt_gap:60" in payload["active_blockers"] + tag_dimensions = {item["dimension"] for item in payload["required_tag_dimensions"]} + assert {"project", "product", "website", "service", "package", "tool"} <= tag_dimensions + assert {"log", "alert", "playbook", "rag", "mcp", "schedule"} <= tag_dimensions + + asset_rollups = payload["monitoring_asset_rollups"] + assert asset_rollups["by_config_kind"] == { + "prometheus_config": 1, + "telegram_gateway": 1, + } + assert asset_rollups["live_receipt_gap_count"] == 2 + assert asset_rollups["live_receipt_gap_samples"][0]["surface_id"] == ( + "prometheus_k8s_base_config" + ) + + pipeline = {stage["stage_id"]: stage for stage in payload["alert_receipt_pipeline"]} + assert pipeline["monitoring_inventory_live_receipts"]["ready"] is False + assert pipeline["alert_operation_log_runtime_readback"]["receipt_count"] == 41 + assert pipeline["km_rag_mcp_playbook_ai_agent_learning_refs"]["ready"] is True + assert payload["runtime_event_type_counts_7d"]["ALERT_RECEIVED"] == 15 + + progress = payload["work_item_progress"] + assert progress["work_item_id"] == "CIR-P0-TG-001" + assert progress["completed_check_count"] == 8 + assert progress["total_check_count"] == 9 + assert payload["ai_controlled_gap_queue"][0]["controlled_next_action"] == ( + "classify_monitoring_surfaces_and_ingest_metadata_live_receipts" + ) + matrix = {item["surface_id"]: item for item in payload["coverage_matrix"]} assert matrix["telegram_gateway_outbound_mirror"]["gap_count"] == 0 assert matrix["telegram_alert_ai_loop_context_verifier"]["status"] == ( diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 3f3396d8b..9678117b7 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -11964,6 +11964,10 @@ "subtitle": "Monitoring alerts aligned to DB/log receipts, AI routes, controlled queue, and KM/RAG/MCP/PlayBook context.", "status": "Coverage status", "statusDetail": "coverage endpoint readback", + "completion": "Overall progress", + "completionDetail": "pipeline {ready}/{total}", + "tags": "Tag dimensions", + "tagsDetail": "asset kinds {kinds}", "direct": "Direct gaps", "directDetail": "gateway controlled: {ready}", "monitoring": "Live receipts", @@ -11975,6 +11979,8 @@ "logs": "7d alert log", "logsDetail": "TG {telegram} / KM {km} / PB {playbook}", "blockers": "Active blockers", + "queue": "AI controlled queue", + "pipeline": "Receipt pipeline", "noBlockers": "No active blocker" }, "recent": "近 24h {count}", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 11f920bd2..297412c2f 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -11964,6 +11964,10 @@ "subtitle": "監控告警對齊 DB/log receipt、AI route、controlled queue 與 KM/RAG/MCP/PlayBook context。", "status": "Coverage status", "statusDetail": "coverage endpoint readback", + "completion": "整體完成度", + "completionDetail": "流水線 {ready}/{total}", + "tags": "貼標維度", + "tagsDetail": "資產類型 {kinds}", "direct": "Direct gaps", "directDetail": "gateway controlled:{ready}", "monitoring": "Live receipts", @@ -11975,6 +11979,8 @@ "logs": "7d alert log", "logsDetail": "TG {telegram} / KM {km} / PB {playbook}", "blockers": "Active blockers", + "queue": "AI controlled queue", + "pipeline": "收據流水線", "noBlockers": "無 active blocker" }, "recent": "近 24h {count}", diff --git a/apps/web/src/components/awooop/autonomous-runtime-receipt-panel.tsx b/apps/web/src/components/awooop/autonomous-runtime-receipt-panel.tsx index 2809b7af8..3abd9d095 100644 --- a/apps/web/src/components/awooop/autonomous-runtime-receipt-panel.tsx +++ b/apps/web/src/components/awooop/autonomous-runtime-receipt-panel.tsx @@ -284,6 +284,10 @@ type TelegramMonitoringCoveragePayload = { status?: string | null; active_blockers?: string[] | null; operator_answer?: { + all_telegram_monitoring_alerts_fully_audited?: boolean | null; + all_telegram_monitoring_alert_surfaces_have_db_or_log_receipt?: boolean | null; + all_telegram_monitoring_alerts_ai_agent_automation_ready?: boolean | null; + ai_controlled_gap_queue_present?: boolean | null; all_known_telegram_egress_routes_controlled?: boolean | null; all_known_telegram_egress_routes_have_db_or_log_receipt?: boolean | null; all_verified_ai_alert_context_receipts_reusable_by_ai_agent?: boolean | null; @@ -309,8 +313,53 @@ type TelegramMonitoringCoveragePayload = { source_contract_ready?: boolean | null; runtime_db_readback_ok?: boolean | null; ai_alert_card_db_readback_ok?: boolean | null; + runtime_event_type_with_receipts_count?: number | null; + alert_receipt_pipeline_stage_count?: number | null; + alert_receipt_pipeline_ready_count?: number | null; + ai_controlled_gap_queue_count?: number | null; + required_tag_dimension_count?: number | null; + monitoring_asset_config_kind_count?: number | null; + telegram_monitoring_audit_completion_percent?: number | null; full_coverage_ready?: boolean | null; } | null; + required_tag_dimensions?: Array<{ + dimension?: string | null; + canonical_field?: string | null; + why?: string | null; + }> | null; + monitoring_asset_rollups?: { + by_config_kind?: Record | null; + live_receipt_gap_samples?: Array<{ + surface_id?: string | null; + label?: string | null; + config_kind?: string | null; + status?: string | null; + }> | null; + } | null; + runtime_event_type_counts_7d?: Record | null; + alert_receipt_pipeline?: Array<{ + stage_id?: string | null; + ready?: boolean | null; + receipt_count?: number | null; + gap_count?: number | null; + next_action?: string | null; + }> | null; + work_item_progress?: { + completion_percent?: number | null; + completed_check_count?: number | null; + total_check_count?: number | null; + checks?: Array<{ + check_id?: string | null; + status?: string | null; + }> | null; + } | null; + ai_controlled_gap_queue?: Array<{ + work_item_id?: string | null; + priority?: string | null; + blocker?: string | null; + status?: string | null; + controlled_next_action?: string | null; + }> | null; operation_boundaries?: { telegram_send_performed?: boolean | null; bot_api_call_performed?: boolean | null; @@ -550,6 +599,12 @@ export function AutonomousRuntimeReceiptPanel({ const telegramCoverageSummary = telegramCoveragePayload?.summary ?? {}; const telegramCoverageAnswers = telegramCoveragePayload?.operator_answer ?? {}; const telegramCoverageBlockers = telegramCoveragePayload?.active_blockers ?? []; + const telegramCoverageProgress = telegramCoveragePayload?.work_item_progress; + const telegramCoveragePipeline = telegramCoveragePayload?.alert_receipt_pipeline ?? []; + const telegramCoverageGapQueue = telegramCoveragePayload?.ai_controlled_gap_queue ?? []; + const telegramCoverageTags = telegramCoveragePayload?.required_tag_dimensions ?? []; + const telegramCoverageAssetKinds = telegramCoveragePayload?.monitoring_asset_rollups?.by_config_kind ?? {}; + const telegramCoverageAssetKindNames = Object.keys(telegramCoverageAssetKinds).slice(0, 5); const consumerReady = consumerRollups.controlled_consumer_readback_ready === true || consumerPayload?.status === "controlled_writeback_consumer_readback_ready"; const telegramPostApplyVerifierReady = @@ -1139,6 +1194,51 @@ export function AutonomousRuntimeReceiptPanel({ ? "neutral" as Tone : telegramMonitoringCoverageReady ? "ok" as Tone : "warn" as Tone, }, + { + key: "completion", + label: t("monitoringCoverage.completion"), + value: !hasTelegramCoverageReadback + ? "--" + : `${numberValue( + telegramCoverageProgress?.completion_percent + ?? telegramCoverageSummary.telegram_monitoring_audit_completion_percent + ?? 0 + )}%`, + detail: t("monitoringCoverage.completionDetail", { + ready: readbackNumber( + telegramCoverageSummary.alert_receipt_pipeline_ready_count, + hasTelegramCoverageReadback + ), + total: readbackNumber( + telegramCoverageSummary.alert_receipt_pipeline_stage_count, + hasTelegramCoverageReadback + ), + }), + icon: Gauge, + tone: !hasTelegramCoverageReadback + ? "neutral" as Tone + : telegramMonitoringCoverageReady ? "ok" as Tone : "warn" as Tone, + }, + { + key: "tags", + label: t("monitoringCoverage.tags"), + value: readbackNumber( + telegramCoverageSummary.required_tag_dimension_count, + hasTelegramCoverageReadback + ), + detail: t("monitoringCoverage.tagsDetail", { + kinds: readbackNumber( + telegramCoverageSummary.monitoring_asset_config_kind_count, + hasTelegramCoverageReadback + ), + }), + icon: SlidersHorizontal, + tone: !hasTelegramCoverageReadback + ? "neutral" as Tone + : toNumber(telegramCoverageSummary.required_tag_dimension_count) > 0 + ? "ok" as Tone + : "warn" as Tone, + }, { key: "direct", label: t("monitoringCoverage.direct"), @@ -1245,17 +1345,24 @@ export function AutonomousRuntimeReceiptPanel({ }, { key: "blockers", - label: t("monitoringCoverage.blockers"), - value: readbackNumber(telegramCoverageBlockers.length, hasTelegramCoverageReadback), + label: t("monitoringCoverage.queue"), + value: readbackNumber( + telegramCoverageSummary.ai_controlled_gap_queue_count + ?? telegramCoverageGapQueue.length + ?? telegramCoverageBlockers.length, + hasTelegramCoverageReadback + ), detail: !hasTelegramCoverageReadback ? t("states.loading") + : telegramCoverageGapQueue.length > 0 + ? telegramCoverageGapQueue[0]?.controlled_next_action ?? telegramCoverageGapQueue[0]?.blocker ?? "--" : telegramCoverageBlockers.length > 0 - ? telegramCoverageBlockers[0] - : t("monitoringCoverage.noBlockers"), + ? telegramCoverageBlockers[0] + : t("monitoringCoverage.noBlockers"), icon: TriangleAlert, tone: !hasTelegramCoverageReadback ? "neutral" as Tone - : telegramCoverageBlockers.length > 0 ? "warn" as Tone : "ok" as Tone, + : telegramCoverageBlockers.length > 0 || telegramCoverageGapQueue.length > 0 ? "warn" as Tone : "ok" as Tone, }, ]; const visibleWorkItems = orderedWorkItems.filter((item) => matchesWorkFilter(item, workFilter)); @@ -1498,6 +1605,103 @@ export function AutonomousRuntimeReceiptPanel({ ); })} + {mode === "full" ? ( +
+
+
+

+ {t("monitoringCoverage.pipeline")} +

+ + {readbackRatio( + telegramCoverageSummary.alert_receipt_pipeline_ready_count, + telegramCoverageSummary.alert_receipt_pipeline_stage_count, + hasTelegramCoverageReadback + )} + +
+
+ {telegramCoveragePipeline.slice(0, 7).map((stage) => ( +
+
+ {stage.stage_id ?? "--"} +
+ + {readbackNumber(stage.gap_count, hasTelegramCoverageReadback)} + +
+ ))} +
+
+
+
+

+ {t("monitoringCoverage.queue")} +

+ + {readbackNumber(telegramCoverageGapQueue.length, hasTelegramCoverageReadback)} + +
+
+ {telegramCoverageGapQueue.length > 0 ? ( + telegramCoverageGapQueue.slice(0, 4).map((item) => ( +
+
+ + {item.controlled_next_action ?? item.blocker ?? "--"} + + + {item.priority ?? "--"} + +
+ + {item.work_item_id ?? item.status ?? "--"} + +
+ )) + ) : ( +
+ {t("monitoringCoverage.noBlockers")} +
+ )} +
+
+
+
+

+ {t("monitoringCoverage.tags")} +

+ + {readbackNumber(telegramCoverageTags.length, hasTelegramCoverageReadback)} + +
+
+ {telegramCoverageTags.slice(0, 12).map((tag) => ( + + {tag.dimension} + + ))} +
+

+ {telegramCoverageAssetKindNames.length > 0 + ? telegramCoverageAssetKindNames.join(" / ") + : t("states.loading")} +

+
+
+ ) : null} diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 93ae2f2a4..77f7bc8b7 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -53989,3 +53989,24 @@ production browser smoke: **下一步**: - 跑 web build、UI density、runner pressure guard 與 Knowledge Base desktop / mobile smoke;通過後 commit / push 到 Gitea main,等 deploy marker 再驗證正式頁面顯示新的資產分類與主知識 API fail-soft。 + +## 2026-07-03 — Telegram 監控告警 AI automation coverage 可視化 + +**完成內容**: +- `/api/v1/agents/telegram-alert-monitoring-coverage-readback` 從單純 gap summary 升級為可操作 readback:新增 required tag dimensions(project / product / website / service / package / tool / log / alert / playbook / rag / mcp / schedule)、monitoring asset rollups、runtime event type counts、alert receipt pipeline、work item progress 與 AI controlled gap queue。 +- operator answer 現在可直接回答:是否完成 Telegram 監控告警全量清查、是否都有 DB/log receipt、是否已可被 AI Agent automation 使用、是否仍有 AI controlled gap queue。 +- AwoooP 共用 `AutonomousRuntimeReceiptPanel` 已接入新欄位;完整模式顯示整體完成度、收據流水線、AI controlled queue 與貼標維度,Runs / Work Items / Alerts / Approvals 共用同一 readback surface。 +- 保持 metadata-only 邊界:沒有送 Telegram、沒有呼叫 Bot API、沒有改 receiver、沒有保存 raw payload、沒有讀 secret、沒有 workflow dispatch。 + +**本地驗證結果**: +- `DATABASE_URL=postgresql+asyncpg://test:test@localhost/test PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_telegram_alert_monitoring_coverage_readback_api.py apps/api/tests/test_telegram_alert_ai_automation_matrix_api.py apps/api/tests/test_ai_agent_log_controlled_writeback_consumer_readback_api.py -q -p no:cacheprovider`:`11 passed`。 +- `pnpm --dir apps/web typecheck`、`NEXT_PUBLIC_API_URL=https://awoooi.wooo.work pnpm --dir apps/web build`、i18n JSON parse、`pnpm --dir apps/web audit:ui-density`、`python3.11 ops/runner/guard-gitea-runner-pressure.py --root .`、`git diff --check`:通過。 +- UI density guard 仍指出既有大型債務:`:locale/iwooos`、`:locale/awooop/work-items`、`:locale/awooop/runs` 仍是下一批 UI/UX 主線壓縮對象。 + +**仍維持**: +- 沒有讀 secret / token / `.env` / raw sessions / SQLite / auth。 +- 沒有使用 GitHub / gh / GitHub API / GitHub Actions。 +- 沒有重啟主機,沒有 Docker / Nginx / K3s / DB / firewall restart,沒有 workflow_dispatch,沒有 DROP / TRUNCATE / restore / prune,沒有送 Telegram 測試訊息。 + +**下一步**: +- commit / push 到 Gitea main;等 deploy marker 後讀回 `/api/v1/agents/telegram-alert-monitoring-coverage-readback`,並以 Playwright smoke 驗證 `/zh-TW/awooop`、Runs、Work Items、Alerts、Approvals 顯示 completion / pipeline / queue / tag dimensions。