diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index 2f7b8ed34..0a8dd59f0 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -469,6 +469,9 @@ from src.services.stockplatform_public_api_runtime_readback import ( from src.services.telegram_alert_ai_automation_matrix import ( load_latest_telegram_alert_ai_automation_matrix, ) +from src.services.telegram_alert_learning_context_post_apply_verifier import ( + load_latest_telegram_alert_learning_context_post_apply_verifier, +) router = APIRouter(prefix="/agents", tags=["Agent Teams"]) logger = get_logger("awoooi.agents") @@ -1144,11 +1147,15 @@ async def get_awoooi_priority_work_order_readback() -> dict[str, Any]: consumer_readback = ( await load_latest_ai_agent_log_controlled_writeback_consumer_readback() ) + telegram_alert_context_verifier = ( + await load_latest_telegram_alert_learning_context_post_apply_verifier() + ) apply_ai_automation_live_closure_readbacks( payload, executor_readback=log_executor, verifier_readback=post_write_verifier, consumer_readback=consumer_readback, + telegram_alert_context_verifier_readback=telegram_alert_context_verifier, ) return redact_public_lan_topology(payload) except FileNotFoundError as exc: @@ -2464,6 +2471,34 @@ async def get_agent_log_controlled_writeback_consumer_readback() -> dict[str, An ) from exc +@router.get( + "/telegram-alert-learning-context-post-apply-verifier", + response_model=dict[str, Any], + summary="取得 Telegram alert learning context post-apply verifier", + description=( + "讀取 AI Loop consumer context 中的 Telegram alert learning receipts," + "驗證 target selector、source-of-truth diff、check-mode、rollback、" + "post verifier refs 與 metadata-only 邊界。此端點不寫 KM/RAG/PlayBook、" + "不呼叫 MCP tool、不發 Telegram、不觸發 workflow、不保存 raw payload、" + "不讀 secret、不呼叫 GitHub。" + ), +) +async def get_telegram_alert_learning_context_post_apply_verifier() -> dict[str, Any]: + """Read Telegram alert learning context post-apply verifier receipts.""" + try: + payload = await load_latest_telegram_alert_learning_context_post_apply_verifier() + return redact_public_lan_topology(payload) + except (json.JSONDecodeError, ValueError) as exc: + logger.error( + "telegram_alert_learning_context_post_apply_verifier_invalid", + error=str(exc), + ) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Telegram alert learning context post-apply verifier 無效", + ) from exc + + @router.post( "/agent-log-controlled-writeback-consumer-apply", response_model=dict[str, Any], diff --git a/apps/api/src/services/awoooi_priority_work_order_readback.py b/apps/api/src/services/awoooi_priority_work_order_readback.py index 889e18f0f..72421e8bc 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -3899,6 +3899,7 @@ def apply_ai_automation_live_closure_readbacks( executor_readback: dict[str, Any] | None = None, verifier_readback: dict[str, Any] | None = None, consumer_readback: dict[str, Any] | None = None, + telegram_alert_context_verifier_readback: dict[str, Any] | None = None, ) -> None: """Overlay live AI-loop executor/verifier/consumer readbacks onto receipts.""" @@ -4056,6 +4057,54 @@ def apply_ai_automation_live_closure_readbacks( "ai_automation_consumer_runtime_target_write_performed" ] + telegram_verifier = _dict(telegram_alert_context_verifier_readback) + telegram_verifier_rollups = _dict(telegram_verifier.get("rollups")) + telegram_verifier_blockers = _strings(telegram_verifier.get("active_blockers")) + if telegram_verifier: + summary["ai_automation_telegram_alert_post_apply_verifier_status"] = str( + telegram_verifier.get("status") or "" + ) + summary["ai_automation_telegram_alert_post_apply_verifier_ready"] = bool( + telegram_verifier_rollups.get( + "telegram_alert_learning_context_post_apply_verifier_ready" + ) + is True + ) + summary[ + "ai_automation_telegram_alert_post_apply_verified_context_receipt_count" + ] = _int(telegram_verifier_rollups.get("verified_context_receipt_count")) + summary[ + "ai_automation_telegram_alert_post_apply_verified_target_count" + ] = _int(telegram_verifier_rollups.get("verified_target_count")) + summary[ + "ai_automation_telegram_alert_post_apply_verified_ai_agent_context_receipt_count" + ] = _int( + telegram_verifier_rollups.get("verified_ai_agent_context_receipt_count") + ) + summary[ + "ai_automation_telegram_alert_post_apply_verifier_ref_count" + ] = _int(telegram_verifier_rollups.get("post_apply_verifier_ref_count")) + summary["ai_automation_telegram_alert_post_apply_verifier_active_blockers"] = ( + telegram_verifier_blockers + ) + rollups["ai_automation_telegram_alert_post_apply_verifier_ready"] = summary[ + "ai_automation_telegram_alert_post_apply_verifier_ready" + ] + rollups[ + "ai_automation_telegram_alert_post_apply_verified_context_receipt_count" + ] = summary[ + "ai_automation_telegram_alert_post_apply_verified_context_receipt_count" + ] + rollups[ + "ai_automation_telegram_alert_post_apply_verified_target_count" + ] = summary["ai_automation_telegram_alert_post_apply_verified_target_count"] + rollups[ + "ai_automation_telegram_alert_post_apply_verifier_active_blocker_count" + ] = len(telegram_verifier_blockers) + state["ai_automation_telegram_alert_post_apply_verifier_ready"] = summary[ + "ai_automation_telegram_alert_post_apply_verifier_ready" + ] + apply_ai_automation_node_receipts(payload) @@ -4136,6 +4185,20 @@ def _apply_ai_automation_node_receipts(payload: dict[str, Any]) -> None: telegram_alert_ai_agent_context_receipt_count = _int( summary.get("ai_automation_telegram_alert_ai_agent_context_receipt_count") ) + telegram_alert_post_apply_verifier_ready = bool( + summary.get("ai_automation_telegram_alert_post_apply_verifier_ready") is True + ) + telegram_alert_post_apply_verified_context_receipt_count = _int( + summary.get( + "ai_automation_telegram_alert_post_apply_verified_context_receipt_count" + ) + ) + telegram_alert_post_apply_verified_target_count = _int( + summary.get("ai_automation_telegram_alert_post_apply_verified_target_count") + ) + telegram_alert_post_apply_verifier_ref_count = _int( + summary.get("ai_automation_telegram_alert_post_apply_verifier_ref_count") + ) consumer_apply_route = str(summary.get("ai_automation_consumer_apply_route") or "") normalizer_ready = bool( queue_fields @@ -4153,6 +4216,7 @@ def _apply_ai_automation_node_receipts(payload: dict[str, Any]) -> None: post_verifier_ready = bool( receipt_outputs or verifier_dry_run_ready + or telegram_alert_post_apply_verifier_ready or consumer_post_apply_verifier_ref_count > 0 ) learning_writeback_ready = bool( @@ -4290,13 +4354,29 @@ def _apply_ai_automation_node_receipts(payload: dict[str, Any]) -> None: *receipt_outputs[:4], f"post_write_verifier_nodes:{verifier_passed_node_count}", f"consumer_post_apply_verifier_refs:{consumer_post_apply_verifier_ref_count}", + ( + "telegram_alert_post_apply_verified_context_receipts:" + f"{telegram_alert_post_apply_verified_context_receipt_count}" + ), + ( + "telegram_alert_post_apply_verified_targets:" + f"{telegram_alert_post_apply_verified_target_count}" + ), + ( + "telegram_alert_post_apply_verifier_refs:" + f"{telegram_alert_post_apply_verifier_ref_count}" + ), ], - "verifier_result": "post_verifier_receipt_readback" - if receipt_outputs - else ( + "verifier_result": ( + "telegram_alert_learning_context_post_apply_verified" + if telegram_alert_post_apply_verifier_ready + else "post_verifier_receipt_readback" + if receipt_outputs + else ( "post_write_verifier_dry_run_ready" if verifier_dry_run_ready else "post_verifier_required" + ) ), "next_action": "read back route/API/UI/verifier output before closing", }, diff --git a/apps/api/src/services/telegram_alert_ai_automation_matrix.py b/apps/api/src/services/telegram_alert_ai_automation_matrix.py index 25d8daaa2..5157483a7 100644 --- a/apps/api/src/services/telegram_alert_ai_automation_matrix.py +++ b/apps/api/src/services/telegram_alert_ai_automation_matrix.py @@ -90,6 +90,9 @@ def load_latest_telegram_alert_ai_automation_matrix( ai_loop_context_present = bool( source_proof["telegram_alert_ai_loop_consumer_context_readback_present"] ) + post_apply_verifier_present = bool( + source_proof["telegram_alert_post_apply_verifier_readback_present"] + ) direct_gap_count = int(egress_summary["direct_bot_api_call_count"]) direct_gap_file_count = int(egress_summary["direct_bot_api_file_count"]) @@ -316,6 +319,46 @@ def load_latest_telegram_alert_ai_automation_matrix( "/api/v1/agents/agent-log-controlled-writeback-consumer-readback", ], }, + { + "surface_id": "telegram_ai_loop_post_apply_verifier", + "priority_work_item_id": "CIR-P0-TG-001", + "surface_kind": "ai_loop_post_apply_verifier", + "status": ( + "post_apply_verifier_readback_present" + if post_apply_verifier_present + else "post_apply_verifier_readback_missing" + ), + "known_item_count": source_proof[ + "telegram_alert_post_apply_verifier_readback_count" + ], + "db_or_log_receipt": "ready_ai_loop_context_receipt_source", + "ai_route": ( + "ready_verified_context_reuse" + if post_apply_verifier_present + else "missing_verified_context_reuse" + ), + "controlled_queue": ( + "ready_metadata_only_verifier" + if post_apply_verifier_present + else "missing_metadata_only_verifier" + ), + "post_verifier": ( + "ready_context_receipt_post_apply_verifier" + if post_apply_verifier_present + else "missing_context_receipt_post_apply_verifier" + ), + "learning_writeback": ( + "ready_verified_context_receipt_for_km_playbook_trust" + if post_apply_verifier_present + else "missing_verified_context_receipt" + ), + "manual_default_gap_count": 0, + "direct_send_gap_count": 0, + "evidence_refs": [ + "apps/api/src/services/telegram_alert_learning_context_post_apply_verifier.py", + "/api/v1/agents/telegram-alert-learning-context-post-apply-verifier", + ], + }, ] next_priority_action = _next_priority_action( @@ -325,6 +368,7 @@ def load_latest_telegram_alert_ai_automation_matrix( learning_writeback_refs_present=learning_writeback_refs_present, learning_registry_present=learning_registry_present, ai_loop_context_present=ai_loop_context_present, + post_apply_verifier_present=post_apply_verifier_present, ) next_controlled_actions = _ordered_controlled_actions(next_priority_action) summary = _build_summary( @@ -382,9 +426,17 @@ def load_latest_telegram_alert_ai_automation_matrix( ( ( ( - "Manual/default semantics are only acceptable for critical/break-glass or historical evidence; " - "Telegram alert registry is now consumable by AI Loop Agent context, " - "so the next P0 step is post-apply verifier receipt." + ( + "Manual/default semantics are only acceptable for critical/break-glass or historical evidence; " + "Telegram alert AI Loop context has a post-apply verifier readback, " + "so the next P0 step is surfacing verified receipts in Runs / Work Items / Alerts." + ) + if post_apply_verifier_present + else ( + "Manual/default semantics are only acceptable for critical/break-glass or historical evidence; " + "Telegram alert registry is now consumable by AI Loop Agent context, " + "so the next P0 step is post-apply verifier receipt." + ) ) if ai_loop_context_present else ( @@ -543,6 +595,10 @@ def _inspect_source_contract(repo_root: Path) -> dict[str, int | bool]: "apps/api/src/services/ai_agent_log_controlled_writeback_consumer_readback.py", "telegram_alert_ai_loop_consumer_context_readback_v1", ), + "telegram_alert_post_apply_verifier_readback_present": ( + "apps/api/src/services/telegram_alert_learning_context_post_apply_verifier.py", + "telegram_alert_learning_context_post_apply_verifier_v1", + ), } result: dict[str, int | bool] = {} for key, (relative_path, marker) in source_checks.items(): @@ -561,6 +617,10 @@ def _inspect_source_contract(repo_root: Path) -> dict[str, int | bool]: result["telegram_alert_ai_loop_consumer_context_readback_count"] = ( 1 if present else 0 ) + if key == "telegram_alert_post_apply_verifier_readback_present": + result["telegram_alert_post_apply_verifier_readback_count"] = ( + 1 if present else 0 + ) required = [key for key, value in result.items() if key.endswith("_present") and value is not True] if required: @@ -588,6 +648,7 @@ def _next_priority_action( learning_writeback_refs_present: bool, learning_registry_present: bool, ai_loop_context_present: bool, + post_apply_verifier_present: bool, ) -> dict[str, Any]: if api_direct_gap_count > 0: return { @@ -649,14 +710,24 @@ def _next_priority_action( "requires_runtime_send": False, "post_verifier": "AI Loop Agent context readback references the CIR-P0-TG-001 Telegram alert learning registry.", } + if not post_apply_verifier_present: + return { + "action_id": "verify_telegram_alert_learning_context_receipts_with_ai_loop_post_apply_verifier", + "scope": "AI Loop Agent Telegram alert consumer context -> post-apply verifier receipt", + "priority": "P0", + "why": "Telegram alert learning registry is readable by AI Loop Agent context; the next closure is verifier receipt and controlled apply reuse.", + "requires_secret": False, + "requires_runtime_send": False, + "post_verifier": "AI Loop post-apply verifier reads Telegram alert context receipts and reports reusable decision context.", + } return { - "action_id": "verify_telegram_alert_learning_context_receipts_with_ai_loop_post_apply_verifier", - "scope": "AI Loop Agent Telegram alert consumer context -> post-apply verifier receipt", + "action_id": "surface_verified_telegram_alert_context_receipts_in_awooop_runs_work_items_alerts", + "scope": "Verified Telegram alert context receipts -> AwoooP Runs / Work Items / Alerts", "priority": "P0", - "why": "Telegram alert learning registry is readable by AI Loop Agent context; the next closure is verifier receipt and controlled apply reuse.", + "why": "Post-apply verifier is source-controlled; the next closure is visible AI automation proof across operator surfaces.", "requires_secret": False, "requires_runtime_send": False, - "post_verifier": "AI Loop post-apply verifier reads Telegram alert context receipts and reports reusable decision context.", + "post_verifier": "Runs, Work Items, and Alerts surfaces show verified Telegram alert AI Loop receipts without manual-default terminal states.", } @@ -725,6 +796,9 @@ def _build_summary( "telegram_alert_ai_loop_consumer_context_readback_count": source_proof[ "telegram_alert_ai_loop_consumer_context_readback_count" ], + "telegram_alert_post_apply_verifier_readback_count": source_proof[ + "telegram_alert_post_apply_verifier_readback_count" + ], "next_priority_action_id": next_priority_action["action_id"], "next_priority_work_item_id": "CIR-P0-TG-001", } diff --git a/apps/api/src/services/telegram_alert_learning_context_post_apply_verifier.py b/apps/api/src/services/telegram_alert_learning_context_post_apply_verifier.py new file mode 100644 index 000000000..5b4d84f7a --- /dev/null +++ b/apps/api/src/services/telegram_alert_learning_context_post_apply_verifier.py @@ -0,0 +1,350 @@ +"""Telegram alert learning context post-apply verifier readback. + +Verifies metadata-only Telegram alert learning context receipts emitted by the +AI Loop consumer readback. This module only reads public-safe receipt metadata; +it does not write KM/RAG/PlayBook, call MCP tools, send Telegram, or touch +runtime infrastructure. +""" + +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + +from src.services.ai_agent_log_controlled_writeback_consumer_readback import ( + load_latest_ai_agent_log_controlled_writeback_consumer_readback, +) + +SCHEMA_VERSION = "telegram_alert_learning_context_post_apply_verifier_v1" +DEFAULT_PROJECT_ID = "awoooi" +_TARGETS = ("km", "rag", "playbook", "mcp", "verifier", "ai_agent") +_REQUIRED_TARGET_SELECTOR_FIELDS = ( + "project_id", + "run_id", + "message_id", + "target", + "source_ref", +) +_REQUIRED_CHECKS = ( + "ai_alert_card_delivery_receipt_present", + "learning_registry_binding_ready", + "metadata_only_raw_payload_absent", + "ai_agent_context_ref_present", +) + + +async def load_latest_telegram_alert_learning_context_post_apply_verifier( + *, + project_id: str = DEFAULT_PROJECT_ID, +) -> dict[str, Any]: + """Return post-apply verifier results for Telegram alert context receipts.""" + + consumer_readback = ( + await load_latest_ai_agent_log_controlled_writeback_consumer_readback( + project_id=project_id + ) + ) + return build_telegram_alert_learning_context_post_apply_verifier( + consumer_readback=consumer_readback, + project_id=project_id, + ) + + +def build_telegram_alert_learning_context_post_apply_verifier( + *, + consumer_readback: Mapping[str, Any], + project_id: str = DEFAULT_PROJECT_ID, +) -> dict[str, Any]: + """Build a deterministic verifier readback from consumer context receipts.""" + + consumer = _dict(consumer_readback) + telegram_context = _dict(consumer.get("telegram_alert_learning_context")) + context_receipts = [ + _dict(receipt) for receipt in _list(telegram_context.get("context_receipts")) + ] + verifier_results = [ + _verify_context_receipt(receipt) for receipt in context_receipts + ] + + target_count = _safe_int(telegram_context.get("target_count")) or len(_TARGETS) + verified_targets = sorted( + { + str(item["target"]) + for item in verifier_results + if item["verifier_status"] == "passed" + } + ) + verified_ai_agent_count = sum( + 1 + for item in verifier_results + if item["target"] == "ai_agent" and item["verifier_status"] == "passed" + ) + failed_results = [ + item for item in verifier_results if item["verifier_status"] != "passed" + ] + source_ready = ( + telegram_context.get("status") == "ai_loop_agent_context_receipt_ready" + ) + ready = bool( + source_ready + and context_receipts + and not failed_results + and len(verified_targets) == target_count + and verified_ai_agent_count > 0 + ) + active_blockers = _active_blockers( + source_ready=source_ready, + context_receipts=context_receipts, + failed_results=failed_results, + verified_targets=verified_targets, + target_count=target_count, + verified_ai_agent_count=verified_ai_agent_count, + ) + + return { + "schema_version": SCHEMA_VERSION, + "priority": "P0-TELEGRAM-AI-LOOP-POST-APPLY-VERIFIER", + "priority_work_item_id": "CIR-P0-TG-001", + "project_id": project_id, + "status": ( + "telegram_alert_learning_context_post_apply_verified" + if ready + else "blocked_telegram_alert_learning_context_post_apply_verifier" + ), + "source_readback": { + "source_endpoint": ( + "/api/v1/agents/agent-log-controlled-writeback-consumer-readback" + ), + "source_schema_version": consumer.get("schema_version"), + "telegram_context_schema_version": telegram_context.get("schema_version"), + "telegram_context_status": telegram_context.get("status"), + "consumer_context_route": telegram_context.get("consumer_context_route"), + "source_registry_status": telegram_context.get("source_registry_status"), + }, + "controlled_verifier": { + "mode": "metadata_only_post_apply_verifier_readback", + "post_apply_verifier_executed": True, + "controlled_apply_allowed_for_low_medium_high": ready, + "owner_review_required_for_low_medium_high": False, + "critical_break_glass_required": True, + "target_selector_verified": ready, + "source_of_truth_diff_verified": ready, + "check_mode_verified": ready, + "rollback_verified": ready, + "raw_payload_absence_verified": ready, + }, + "verifier_results": verifier_results, + "rollups": { + "telegram_alert_learning_context_post_apply_verifier_ready": ready, + "source_context_ready": source_ready, + "context_receipt_count": len(context_receipts), + "verified_context_receipt_count": sum( + 1 for item in verifier_results if item["verifier_status"] == "passed" + ), + "failed_context_receipt_count": len(failed_results), + "target_count": target_count, + "verified_target_count": len(verified_targets), + "verified_targets": verified_targets, + "verified_ai_agent_context_receipt_count": verified_ai_agent_count, + "target_selector_verified_count": sum( + 1 for item in verifier_results if item["target_selector_verified"] + ), + "source_of_truth_diff_verified_count": sum( + 1 + for item in verifier_results + if item["source_of_truth_diff_verified"] + ), + "check_mode_verified_count": sum( + 1 for item in verifier_results if item["check_mode_verified"] + ), + "rollback_verified_count": sum( + 1 for item in verifier_results if item["rollback_verified"] + ), + "post_apply_verifier_ref_count": sum( + len(item["post_apply_verifier_refs"]) for item in verifier_results + ), + "metadata_only_receipt_count": sum( + 1 for item in verifier_results if item["metadata_only"] is True + ), + "raw_payload_included_count": sum( + 1 for item in verifier_results if item["raw_payload_included"] is True + ), + "runtime_write_performed_count": 0, + "telegram_send_performed_count": 0, + "mcp_tool_call_performed_count": 0, + "secret_value_read_count": 0, + }, + "active_blockers": active_blockers, + "next_action": ( + "surface_verified_telegram_alert_context_receipts_in_awooop_runs_work_items_alerts" + if ready + else "fix_failed_telegram_alert_context_receipts_then_rerun_post_apply_verifier" + ), + "operation_boundaries": { + "metadata_read_performed": True, + "post_apply_verifier_read_performed": True, + "runtime_target_write_performed": False, + "km_write_performed": False, + "rag_index_write_performed": False, + "playbook_trust_write_performed": False, + "mcp_tool_call_performed": False, + "agent_runtime_action_performed": False, + "telegram_send_performed": False, + "workflow_trigger_performed": False, + "raw_payload_included": False, + "secret_value_collection_allowed": False, + "github_api_used": False, + }, + } + + +def _verify_context_receipt(receipt: dict[str, Any]) -> dict[str, Any]: + target = str(receipt.get("target") or "") + target_selector = _dict(receipt.get("target_selector")) + source_diff = _dict(receipt.get("source_of_truth_diff")) + check_mode = _dict(receipt.get("check_mode")) + rollback = _dict(receipt.get("rollback")) + post_apply_verifier = _dict(receipt.get("post_apply_verifier")) + verifier_refs = _strings(post_apply_verifier.get("verifier_refs")) + + target_selector_missing = [ + key for key in _REQUIRED_TARGET_SELECTOR_FIELDS if not target_selector.get(key) + ] + checks = _strings(check_mode.get("checks")) + missing_checks = [check for check in _REQUIRED_CHECKS if check not in checks] + failed_checks: list[str] = [] + + if target not in _TARGETS: + failed_checks.append("target_in_allowed_set") + if receipt.get("status") != "ready_for_ai_loop_context": + failed_checks.append("receipt_ready_for_ai_loop_context") + if target_selector_missing: + failed_checks.append("target_selector_required_fields_present") + if not _source_diff_verified(source_diff, target): + failed_checks.append("source_of_truth_diff_verified") + if check_mode.get("enabled") is not True or missing_checks: + failed_checks.append("check_mode_required_checks_present") + if rollback.get("required") is not True or not rollback.get("rollback_ref"): + failed_checks.append("rollback_ref_present") + if post_apply_verifier.get("required") is not True or not verifier_refs: + failed_checks.append("post_apply_verifier_refs_present") + if receipt.get("metadata_only") is not True: + failed_checks.append("metadata_only_receipt") + if receipt.get("raw_payload_included") is not False: + failed_checks.append("raw_payload_absent") + if receipt.get("target_write_performed") is not False: + failed_checks.append("target_write_not_performed_by_verifier") + if not receipt.get("ai_agent_context_ref"): + failed_checks.append("ai_agent_context_ref_present") + + passed_checks = [ + "target_in_allowed_set", + "receipt_ready_for_ai_loop_context", + "target_selector_required_fields_present", + "source_of_truth_diff_verified", + "check_mode_required_checks_present", + "rollback_ref_present", + "post_apply_verifier_refs_present", + "metadata_only_receipt", + "raw_payload_absent", + "target_write_not_performed_by_verifier", + "ai_agent_context_ref_present", + ] + passed_checks = [check for check in passed_checks if check not in failed_checks] + + return { + "receipt_id": str(receipt.get("receipt_id") or ""), + "source_receipt_id": str(receipt.get("source_receipt_id") or ""), + "source_run_id": str(receipt.get("source_run_id") or ""), + "source_message_id": str(receipt.get("source_message_id") or ""), + "work_item_id": str(receipt.get("work_item_id") or "CIR-P0-TG-001"), + "target": target, + "consumer_surface": str(receipt.get("consumer_surface") or ""), + "verifier_status": "passed" if not failed_checks else "failed", + "passed_checks": passed_checks, + "failed_checks": failed_checks, + "target_selector_verified": not target_selector_missing, + "target_selector_missing_fields": target_selector_missing, + "source_of_truth_diff_verified": _source_diff_verified(source_diff, target), + "check_mode_verified": check_mode.get("enabled") is True and not missing_checks, + "check_mode_missing_checks": missing_checks, + "rollback_verified": ( + rollback.get("required") is True and bool(rollback.get("rollback_ref")) + ), + "post_apply_verifier_refs": verifier_refs, + "metadata_only": receipt.get("metadata_only") is True, + "raw_payload_included": receipt.get("raw_payload_included") is True, + "runtime_target_write_performed": False, + "telegram_send_performed": False, + "evidence_refs": _unique( + [ + str(receipt.get("source_ref") or ""), + str(receipt.get("ai_agent_context_ref") or ""), + *verifier_refs, + ] + ), + } + + +def _active_blockers( + *, + source_ready: bool, + context_receipts: list[dict[str, Any]], + failed_results: list[dict[str, Any]], + verified_targets: list[str], + target_count: int, + verified_ai_agent_count: int, +) -> list[str]: + blockers: list[str] = [] + if not source_ready: + blockers.append("telegram_alert_learning_context_source_not_ready") + if not context_receipts: + blockers.append("telegram_alert_learning_context_receipts_missing") + if len(verified_targets) < target_count: + blockers.append("telegram_alert_learning_context_verified_target_count_below_target") + if verified_ai_agent_count <= 0: + blockers.append("telegram_alert_learning_context_ai_agent_receipt_not_verified") + for item in failed_results: + receipt_id = item.get("receipt_id") or "unknown-receipt" + blockers.append(f"telegram_alert_context_receipt_verifier_failed:{receipt_id}") + return blockers + + +def _source_diff_verified(source_diff: dict[str, Any], target: str) -> bool: + return bool( + source_diff.get("current_state") == "telegram_alert_learning_registry_readable" + and source_diff.get("desired_state") + == "ai_loop_agent_context_receipt_available" + and source_diff.get("delta_kind") == f"telegram_alert_{target}_context_binding" + and source_diff.get("raw_payload_included") is False + ) + + +def _dict(value: Any) -> dict[str, Any]: + return value if isinstance(value, dict) else {} + + +def _list(value: Any) -> list[Any]: + return value if isinstance(value, list) else [] + + +def _strings(value: Any) -> list[str]: + return [str(item) for item in _list(value) if str(item)] + + +def _unique(values: list[str]) -> list[str]: + seen: set[str] = set() + result: list[str] = [] + for value in values: + if value in seen: + continue + seen.add(value) + result.append(value) + return result + + +def _safe_int(value: Any) -> int: + try: + return int(value) + except (TypeError, ValueError): + return 0 diff --git a/apps/api/tests/test_awoooi_priority_work_order_readback_api.py b/apps/api/tests/test_awoooi_priority_work_order_readback_api.py index 2775b9efa..ebeff1886 100644 --- a/apps/api/tests/test_awoooi_priority_work_order_readback_api.py +++ b/apps/api/tests/test_awoooi_priority_work_order_readback_api.py @@ -30,6 +30,9 @@ from src.services.awoooi_priority_work_order_readback import ( from src.services.harbor_registry_controlled_recovery_preflight import ( load_latest_harbor_registry_controlled_recovery_preflight, ) +from src.services.telegram_alert_learning_context_post_apply_verifier import ( + build_telegram_alert_learning_context_post_apply_verifier, +) from src.services.stockplatform_public_api_controlled_recovery_preflight import ( load_latest_stockplatform_public_api_controlled_recovery_preflight, ) @@ -472,6 +475,7 @@ def test_ai_automation_live_closure_readbacks_close_node_receipts(): executor_readback=executor, verifier_readback=verifier, consumer_readback=_consumer_readback_ready(), + telegram_alert_context_verifier_readback=_telegram_alert_context_verifier_ready(), ) receipts_by_id = { @@ -490,7 +494,7 @@ def test_ai_automation_live_closure_readbacks_close_node_receipts(): "controlled_consumer_context_receipt_readback" ) assert receipts_by_id["post_verifier"]["verifier_result"] == ( - "post_write_verifier_dry_run_ready" + "telegram_alert_learning_context_post_apply_verified" ) assert receipts_by_id["learning_writeback"]["verifier_result"] == ( "consumer_context_receipt_writeback_ready" @@ -508,6 +512,12 @@ def test_ai_automation_live_closure_readbacks_close_node_receipts(): assert payload["summary"][ "ai_automation_consumer_target_context_receipt_write_count" ] == 6 + assert payload["summary"][ + "ai_automation_telegram_alert_post_apply_verifier_ready" + ] is True + assert payload["summary"][ + "ai_automation_telegram_alert_post_apply_verified_context_receipt_count" + ] == 6 def test_awoooi_priority_work_order_readback_endpoint_returns_snapshot( @@ -533,6 +543,11 @@ def test_awoooi_priority_work_order_readback_endpoint_returns_snapshot( "load_latest_ai_agent_log_controlled_writeback_consumer_readback", _consumer_readback_ready_async, ) + monkeypatch.setattr( + agents, + "load_latest_telegram_alert_learning_context_post_apply_verifier", + _telegram_alert_context_verifier_ready_async, + ) app = FastAPI() app.include_router(router, prefix="/api/v1") client = TestClient(app) @@ -626,6 +641,9 @@ def test_awoooi_priority_work_order_readback_endpoint_returns_snapshot( ) assert endpoint_receipts["execution_boundary"]["node_status"] == "ready" assert endpoint_receipts["post_verifier"]["node_status"] == "ready" + assert endpoint_receipts["post_verifier"]["verifier_result"] == ( + "telegram_alert_learning_context_post_apply_verified" + ) assert endpoint_receipts["learning_writeback"]["node_status"] == "ready" assert endpoint_receipts["learning_writeback"]["km_writeback_ref"] == ( "context_receipt:km=1" @@ -1648,6 +1666,84 @@ async def _consumer_readback_ready_async() -> dict: return _consumer_readback_ready() +async def _telegram_alert_context_verifier_ready_async() -> dict: + return _telegram_alert_context_verifier_ready() + + +def _telegram_alert_context_verifier_ready() -> dict: + return build_telegram_alert_learning_context_post_apply_verifier( + consumer_readback=_consumer_readback_ready() + ) + + +def _telegram_context_receipts() -> list[dict]: + targets = [ + ("km", "knowledge_memory_context"), + ("rag", "rag_index_candidate_context"), + ("playbook", "playbook_trust_candidate_context"), + ("mcp", "mcp_audit_feedback_context"), + ("verifier", "post_apply_verifier_feedback_context"), + ("ai_agent", "autonomous_runtime_decision_context"), + ] + return [ + { + "receipt_id": f"telegram_alert_learning_context::msg-telegram-1::{target}", + "source_receipt_id": "msg-telegram-1", + "source_run_id": "run-telegram-1", + "source_message_id": "msg-telegram-1", + "work_item_id": "CIR-P0-TG-001", + "target": target, + "consumer_surface": surface, + "source_ref": f"{target}://awoooi/telegram-alert/msg-telegram-1", + "ai_agent_context_ref": ( + "ai-agent://awoooi/telegram-alert-learning-context/" + "run-telegram-1/msg-telegram-1" + ), + "status": "ready_for_ai_loop_context", + "metadata_only": True, + "target_write_performed": False, + "raw_payload_included": False, + "target_selector": { + "project_id": "awoooi", + "run_id": "run-telegram-1", + "message_id": "msg-telegram-1", + "target": target, + "source_ref": f"{target}://awoooi/telegram-alert/msg-telegram-1", + }, + "source_of_truth_diff": { + "current_state": "telegram_alert_learning_registry_readable", + "desired_state": "ai_loop_agent_context_receipt_available", + "delta_kind": f"telegram_alert_{target}_context_binding", + "raw_payload_included": False, + }, + "check_mode": { + "enabled": True, + "checks": [ + "ai_alert_card_delivery_receipt_present", + "learning_registry_binding_ready", + "metadata_only_raw_payload_absent", + "ai_agent_context_ref_present", + ], + }, + "rollback": { + "required": True, + "rollback_ref": ( + "rollback://telegram-alert-learning-context/" + f"msg-telegram-1/{target}" + ), + }, + "post_apply_verifier": { + "required": True, + "verifier_refs": [ + "verifier://awoooi/telegram-alert-delivery-receipt/run-telegram-1", + f"{target}://awoooi/telegram-alert/msg-telegram-1", + ], + }, + } + for target, surface in targets + ] + + def _consumer_readback_ready() -> dict: return { "schema_version": "ai_agent_log_controlled_writeback_consumer_readback_v1", @@ -1691,6 +1787,43 @@ def _consumer_readback_ready() -> dict: "verifier_context_receipt_write_count": 1, "ai_agent_context_receipt_write_count": 1, "runtime_target_write_performed": True, + "telegram_alert_learning_context_readback_ready": True, + "telegram_alert_learning_context_receipt_count": 6, + "telegram_alert_learning_ai_agent_context_receipt_count": 1, + "telegram_alert_learning_ready_target_count": 6, + "telegram_alert_learning_target_count": 6, + }, + "telegram_alert_learning_context": { + "schema_version": "telegram_alert_ai_loop_consumer_context_readback_v1", + "status": "ai_loop_agent_context_receipt_ready", + "priority_work_item_id": "CIR-P0-TG-001", + "project_id": "awoooi", + "source_endpoint": "/api/v1/platform/runs/ai-alert-cards", + "source_registry_status": "learning_registry_ready", + "consumer_context_route": "ai_loop_agent.telegram_alert_learning_context", + "target_count": 6, + "ready_target_count": 6, + "context_receipt_count": 6, + "ai_agent_context_receipt_count": 1, + "next_action": ( + "verify_telegram_alert_learning_context_receipts_with_ai_loop_post_apply_verifier" + ), + "context_receipts": _telegram_context_receipts(), + "active_blockers": [], + "operation_boundaries": { + "metadata_read_performed": True, + "db_or_log_receipt_read_performed": True, + "runtime_target_write_performed": False, + "km_write_performed": False, + "rag_index_write_performed": False, + "playbook_trust_write_performed": False, + "mcp_tool_call_performed": False, + "agent_runtime_action_performed": False, + "telegram_send_performed": False, + "raw_payload_included": False, + "secret_value_collection_allowed": False, + "github_api_used": False, + }, }, "active_blockers": [], } diff --git a/apps/api/tests/test_telegram_alert_ai_automation_matrix_api.py b/apps/api/tests/test_telegram_alert_ai_automation_matrix_api.py index 310f4472f..538f93fc7 100644 --- a/apps/api/tests/test_telegram_alert_ai_automation_matrix_api.py +++ b/apps/api/tests/test_telegram_alert_ai_automation_matrix_api.py @@ -32,7 +32,7 @@ def test_telegram_alert_ai_automation_matrix_loader_returns_gap_matrix(): } summary = payload["summary"] - assert summary["telegram_alert_surface_count"] == 7 + assert summary["telegram_alert_surface_count"] == 8 assert summary["known_direct_send_gap_count"] == 0 assert summary["known_direct_send_gap_file_count"] == 0 assert summary["workflow_direct_send_gap_count"] == 0 @@ -55,8 +55,9 @@ def test_telegram_alert_ai_automation_matrix_loader_returns_gap_matrix(): assert summary["ai_alert_card_learning_writeback_refs_count"] == 1 assert summary["ai_alert_card_learning_registry_readback_count"] == 1 assert summary["telegram_alert_ai_loop_consumer_context_readback_count"] == 1 + assert summary["telegram_alert_post_apply_verifier_readback_count"] == 1 assert summary["next_priority_action_id"] == ( - "verify_telegram_alert_learning_context_receipts_with_ai_loop_post_apply_verifier" + "surface_verified_telegram_alert_context_receipts_in_awooop_runs_work_items_alerts" ) matrix = {item["surface_id"]: item for item in payload["matrix"]} @@ -95,6 +96,12 @@ def test_telegram_alert_ai_automation_matrix_loader_returns_gap_matrix(): assert matrix["telegram_ai_loop_agent_consumer_context"]["ai_route"] == ( "ready_ai_loop_agent_context_reuse" ) + assert matrix["telegram_ai_loop_post_apply_verifier"]["status"] == ( + "post_apply_verifier_readback_present" + ) + assert matrix["telegram_ai_loop_post_apply_verifier"]["post_verifier"] == ( + "ready_context_receipt_post_apply_verifier" + ) assert payload["direct_send_gap_refs"] == [] assert not any( @@ -142,6 +149,10 @@ def test_telegram_matrix_source_contract_supports_container_layout(tmp_path): "telegram_alert_ai_loop_consumer_context_readback_v1\n", encoding="utf-8", ) + (tmp_path / "src/services/telegram_alert_learning_context_post_apply_verifier.py").write_text( + "telegram_alert_learning_context_post_apply_verifier_v1\n", + encoding="utf-8", + ) proof = _inspect_source_contract(tmp_path) @@ -158,6 +169,8 @@ def test_telegram_matrix_source_contract_supports_container_layout(tmp_path): assert proof["ai_alert_card_ai_agent_context_ref_present"] is True assert proof["telegram_alert_ai_loop_consumer_context_readback_present"] is True assert proof["telegram_alert_ai_loop_consumer_context_readback_count"] == 1 + assert proof["telegram_alert_post_apply_verifier_readback_present"] is True + assert proof["telegram_alert_post_apply_verifier_readback_count"] == 1 def test_telegram_alert_ai_automation_matrix_endpoint_returns_readback(): diff --git a/apps/api/tests/test_telegram_alert_learning_context_post_apply_verifier_api.py b/apps/api/tests/test_telegram_alert_learning_context_post_apply_verifier_api.py new file mode 100644 index 000000000..326c69540 --- /dev/null +++ b/apps/api/tests/test_telegram_alert_learning_context_post_apply_verifier_api.py @@ -0,0 +1,184 @@ +from __future__ import annotations + +import json + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from src.api.v1 import agents +from src.api.v1.agents import router +from src.services.telegram_alert_learning_context_post_apply_verifier import ( + build_telegram_alert_learning_context_post_apply_verifier, +) + + +def _context_receipts() -> list[dict]: + targets = [ + ("km", "knowledge_memory_context"), + ("rag", "rag_index_candidate_context"), + ("playbook", "playbook_trust_candidate_context"), + ("mcp", "mcp_audit_feedback_context"), + ("verifier", "post_apply_verifier_feedback_context"), + ("ai_agent", "autonomous_runtime_decision_context"), + ] + return [ + { + "receipt_id": f"telegram_alert_learning_context::msg-telegram-1::{target}", + "source_receipt_id": "msg-telegram-1", + "source_run_id": "run-telegram-1", + "source_message_id": "msg-telegram-1", + "work_item_id": "CIR-P0-TG-001", + "target": target, + "consumer_surface": surface, + "source_ref": f"{target}://awoooi/telegram-alert/msg-telegram-1", + "ai_agent_context_ref": ( + "ai-agent://awoooi/telegram-alert-learning-context/" + "run-telegram-1/msg-telegram-1" + ), + "status": "ready_for_ai_loop_context", + "metadata_only": True, + "target_write_performed": False, + "raw_payload_included": False, + "target_selector": { + "project_id": "awoooi", + "run_id": "run-telegram-1", + "message_id": "msg-telegram-1", + "target": target, + "source_ref": f"{target}://awoooi/telegram-alert/msg-telegram-1", + }, + "source_of_truth_diff": { + "current_state": "telegram_alert_learning_registry_readable", + "desired_state": "ai_loop_agent_context_receipt_available", + "delta_kind": f"telegram_alert_{target}_context_binding", + "raw_payload_included": False, + }, + "check_mode": { + "enabled": True, + "checks": [ + "ai_alert_card_delivery_receipt_present", + "learning_registry_binding_ready", + "metadata_only_raw_payload_absent", + "ai_agent_context_ref_present", + ], + }, + "rollback": { + "required": True, + "rollback_ref": ( + "rollback://telegram-alert-learning-context/" + f"msg-telegram-1/{target}" + ), + }, + "post_apply_verifier": { + "required": True, + "verifier_refs": [ + "verifier://awoooi/telegram-alert-delivery-receipt/run-telegram-1", + f"{target}://awoooi/telegram-alert/msg-telegram-1", + ], + }, + } + for target, surface in targets + ] + + +def _consumer_readback(*, receipts: list[dict] | None = None) -> dict: + return { + "schema_version": "ai_agent_log_controlled_writeback_consumer_readback_v1", + "status": "controlled_writeback_consumer_readback_ready", + "telegram_alert_learning_context": { + "schema_version": "telegram_alert_ai_loop_consumer_context_readback_v1", + "status": "ai_loop_agent_context_receipt_ready", + "project_id": "awoooi", + "source_registry_status": "learning_registry_ready", + "consumer_context_route": "ai_loop_agent.telegram_alert_learning_context", + "target_count": 6, + "ready_target_count": 6, + "context_receipt_count": 6, + "ai_agent_context_receipt_count": 1, + "context_receipts": receipts if receipts is not None else _context_receipts(), + }, + } + + +def test_telegram_alert_learning_context_post_apply_verifier_passes_receipts(): + payload = build_telegram_alert_learning_context_post_apply_verifier( + consumer_readback=_consumer_readback() + ) + + assert payload["schema_version"] == ( + "telegram_alert_learning_context_post_apply_verifier_v1" + ) + assert payload["status"] == ( + "telegram_alert_learning_context_post_apply_verified" + ) + assert payload["active_blockers"] == [] + assert payload["controlled_verifier"]["post_apply_verifier_executed"] is True + assert payload["controlled_verifier"]["raw_payload_absence_verified"] is True + assert payload["operation_boundaries"]["runtime_target_write_performed"] is False + assert payload["operation_boundaries"]["telegram_send_performed"] is False + assert payload["operation_boundaries"]["secret_value_collection_allowed"] is False + + rollups = payload["rollups"] + assert rollups["telegram_alert_learning_context_post_apply_verifier_ready"] is True + assert rollups["context_receipt_count"] == 6 + assert rollups["verified_context_receipt_count"] == 6 + assert rollups["verified_target_count"] == 6 + assert rollups["verified_ai_agent_context_receipt_count"] == 1 + assert rollups["raw_payload_included_count"] == 0 + assert rollups["runtime_write_performed_count"] == 0 + + ai_agent_result = [ + result + for result in payload["verifier_results"] + if result["target"] == "ai_agent" + ][0] + assert ai_agent_result["verifier_status"] == "passed" + assert ai_agent_result["target_selector_verified"] is True + assert ai_agent_result["rollback_verified"] is True + assert ai_agent_result["post_apply_verifier_refs"] + + serialized = json.dumps(payload, ensure_ascii=False) + assert "TELEGRAM_BOT_TOKEN" not in serialized + assert "raw Telegram payload" not in serialized + + +def test_telegram_alert_learning_context_post_apply_verifier_blocks_bad_receipt(): + receipts = _context_receipts() + receipts[0]["rollback"] = {"required": True, "rollback_ref": ""} + + payload = build_telegram_alert_learning_context_post_apply_verifier( + consumer_readback=_consumer_readback(receipts=receipts) + ) + + assert payload["status"] == ( + "blocked_telegram_alert_learning_context_post_apply_verifier" + ) + assert payload["rollups"]["failed_context_receipt_count"] == 1 + assert any( + blocker.startswith("telegram_alert_context_receipt_verifier_failed:") + for blocker in payload["active_blockers"] + ) + + +def test_telegram_alert_learning_context_post_apply_verifier_endpoint(monkeypatch): + async def fake_loader(): + return build_telegram_alert_learning_context_post_apply_verifier( + consumer_readback=_consumer_readback() + ) + + monkeypatch.setattr( + agents, + "load_latest_telegram_alert_learning_context_post_apply_verifier", + fake_loader, + ) + app = FastAPI() + app.include_router(router, prefix="/api/v1") + client = TestClient(app) + + response = client.get( + "/api/v1/agents/telegram-alert-learning-context-post-apply-verifier" + ) + + assert response.status_code == 200 + data = response.json() + assert data["status"] == "telegram_alert_learning_context_post_apply_verified" + assert data["rollups"]["verified_target_count"] == 6 diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index e3633f332..8de51c5cd 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,18 @@ +## 2026-07-02 — 20:56 CIR-P0-TG-001 Telegram alert AI Loop post-apply verifier readback + +**完成內容**: +- 新增 `/api/v1/agents/telegram-alert-learning-context-post-apply-verifier`,從 `agent-log-controlled-writeback-consumer-readback` 的 Telegram AI Loop context receipts 讀回 metadata-only post-apply verifier。 +- verifier 逐筆檢查 KM / RAG / PlayBook / MCP / verifier / AI Agent 六個 target 的 target selector、source-of-truth diff、check-mode、rollback、post-verifier refs、`ai_agent_context_ref` 與 no-raw-payload / no-runtime-write boundary。 +- `telegram_alert_ai_automation_matrix` 已新增 `telegram_ai_loop_post_apply_verifier` surface;direct send gap、learning registry、AI Loop consumer context 都存在時,下一個 P0 action 推進為 `surface_verified_telegram_alert_context_receipts_in_awooop_runs_work_items_alerts`。 +- `awoooi-priority-work-order-readback` 的 AI automation node receipts 已接上 Telegram post-apply verifier rollups,`post_verifier` 節點可讀回 `telegram_alert_learning_context_post_apply_verified`。 + +**驗證**: +- `DATABASE_URL=postgresql+asyncpg://test:test@localhost/test PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_telegram_alert_learning_context_post_apply_verifier_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 apps/api/tests/test_awoooi_priority_work_order_readback_api.py -q -p no:cacheprovider`:`26 passed`。 +- `python3.11 -m py_compile apps/api/src/services/telegram_alert_learning_context_post_apply_verifier.py apps/api/src/services/telegram_alert_ai_automation_matrix.py apps/api/src/services/awoooi_priority_work_order_readback.py apps/api/src/api/v1/agents.py`:通過。 + +**仍維持**: +- 未送 Telegram、未呼叫 Bot API、未寫 KM / RAG index / PlayBook trust / MCP、未讀 secret / token / `.env` / raw sessions / SQLite / auth;未寫 production DB、未 workflow_dispatch、未重啟主機 / VM / Docker / Nginx / K3s / DB / firewall。 + ## 2026-07-02 — 20:42 CIR-P0-TG-001 Telegram alert AI Loop consumer context receipt **完成內容**: diff --git a/docs/workplans/2026-07-02-commander-inserted-requirements-priority-ledger.md b/docs/workplans/2026-07-02-commander-inserted-requirements-priority-ledger.md index d5fc44114..3de272fad 100644 --- a/docs/workplans/2026-07-02-commander-inserted-requirements-priority-ledger.md +++ b/docs/workplans/2026-07-02-commander-inserted-requirements-priority-ledger.md @@ -88,7 +88,7 @@ | 2 | CIR-P0-AILOOP-002 | P0 | 「所有 AI 自動化完整流程、節點必須完整且詳盡紀錄」 | 每次 AI 自動化事件記錄 trace_id、run_id、work_item_id、節點狀態、timestamp、evidence refs、verifier、rollback、KM/PlayBook trust writeback | 已升為 API/UI 可見 P0 工作項;節點 schema 待補 | 建立 AI automation node receipt schema,先套用 Telegram alert 與 P0 runtime lanes | | 3 | CIR-P0-LOG-001 | P0 | 「所有專案、產品、網站、服務、套件、工具的 LOG 都要收進去」 | 建立全域 LOG source registry 與 ingestion adapters,含 redaction、retention、freshness、last_success、owner lane | 已升為 API/UI 可見 P0 工作項 | 先做 metadata-only LOG source registry,不讀 secret / raw payload | | 4 | CIR-P0-LOG-002 | P0 | 「LOG 要貼標、分類、分群,讓 AI Agent 可以處理」 | LOG 自動貼 product/project/site/service/package/tool、severity、lane、confidence、AI route、cluster_id、fingerprint | 已升為 API/UI 可見 P0 工作項 | 定義 tag taxonomy 與 clustering readback,先覆蓋 Telegram、CPU、backup、freshness | -| 5 | CIR-P0-TG-001 | P0 | 「所有發送到 Telegram 的監控告警是否全面清查、完整寫入 DB 或日誌、符合 AI 自動化?」 | 每個 Telegram 告警映射 DB/log receipt、AI route、controlled queue、work item、post verifier、learning writeback;direct send / manual default 列缺口 | workflow / ops / API direct send 已收斂到 0;`/runs/ai-alert-cards` 已補 KM / PlayBook / RAG / MCP `learning_writeback_refs` read model,已投影 KM / PlayBook / RAG / MCP / verifier / AI Agent registry bindings,並接進 AI Loop Agent consumer context receipt | 接 post-apply verifier receipt,人工預設只允許 critical / break-glass 或歷史證據 | +| 5 | CIR-P0-TG-001 | P0 | 「所有發送到 Telegram 的監控告警是否全面清查、完整寫入 DB 或日誌、符合 AI 自動化?」 | 每個 Telegram 告警映射 DB/log receipt、AI route、controlled queue、work item、post verifier、learning writeback;direct send / manual default 列缺口 | workflow / ops / API direct send 已收斂到 0;`/runs/ai-alert-cards` 已補 KM / PlayBook / RAG / MCP `learning_writeback_refs` read model,已投影 KM / PlayBook / RAG / MCP / verifier / AI Agent registry bindings,接進 AI Loop Agent consumer context receipt,並新增 Telegram learning context post-apply verifier readback | 把 verified context receipts surface 到 AwoooP Runs / Work Items / Alerts,人工預設只允許 critical / break-glass 或歷史證據 | | 6 | CIR-P0-IA-001 | P0 | 「IwoooS 被移除、導航列菜單被移除,且沒有整合到相關頁面」 | 建立 IA/nav removal recovery ledger:被移除 menu/route/page 必須有保留、合併、替代目的地、redirect、權限邊界與 smoke | 已升為 API/UI 可見 P0 工作項;route/menu 盤點待補 | 盤點 navigation config、route list 與 removed-page destination,補 readback | | 7 | CIR-P1-AUTO-001 | P1 | 「不能偏離不代表一定要每一個工作完成,才能進行下一個」 | 建立主線並行推進規則:不離開 P0/P1 主線,但依賴允許時可同時推 API/UI、LOG、Telegram、KM/RAG/MCP/PlayBook、verifier | 已升為 API/UI 可見 P1 工作項 | 後續回報以 active lane + evidence 更新,不等待單一項完全結束 | | 8 | CIR-P1-UI-001 | P1 | 「UI/UX 工作項目不見;整個前端頁面一大堆文字,要主流專業產品做法」 | UI/UX 成為主線:first-viewport cockpit、cards/flow rows、少文字牆、route/nav 一致、desktop/mobile smoke | 已升為 API/UI spotlight | 先讓 AI Loop / LOG / Telegram / IA 工作項在 Work Items spotlight 可見,再排 UI smoke | @@ -111,6 +111,7 @@ | 110 Gitea CPU 專用 check-mode playbook | `gitea-queue-hook-backlog-playbook.py` 已上 main;live readback 可輸出 health/version/hooktasks/active Actions | | 110 CPU evidence / controller 分流一致性 | live evidence 與 controller 皆將 Stock/Postgres pressure 優先導向 `postgres_hot_query_or_backup_export_playbook` | | 插入需求 API/UI 可見化 | `awoooi-priority-work-order-readback` 已把插入需求主線工作項產品化;Work Items 頁有 spotlight | +| Telegram alert AI Loop post-apply verifier | `telegram-alert-learning-context-post-apply-verifier` API / service / tests 已完成,AI automation `post_verifier` 節點可讀回 verified context receipts | ### In Progress @@ -120,7 +121,7 @@ | AI 自動化流程 / 節點紀錄 | 已升為 P0 工作項;trace/run/work_item/node/evidence/verifier/writeback schema 待補 | 建立 AI automation node receipt schema | | 全域 LOG 收集 | 已升為 P0 工作項;source registry / adapter / freshness / retention 尚未統一 | 建立 metadata-only LOG source registry | | LOG 貼標 / 分類 / 分群 | 已升為 P0 工作項;tag taxonomy / cluster readback 待補 | 先覆蓋 Telegram、CPU、backup、freshness | -| Telegram 告警 AI 自動化 | direct send / DB receipt / AI route / controlled queue / learning registry / AI Loop consumer context 已有 readback | 接 post-apply verifier receipt,並把結果回寫 AI Loop 節點 | +| Telegram 告警 AI 自動化 | direct send / DB receipt / AI route / controlled queue / learning registry / AI Loop consumer context / post-apply verifier 已有 readback | 把 verified context receipts surface 到 AwoooP Runs / Work Items / Alerts,並繼續接 LOG / KM / PlayBook / RAG / MCP trust writeback | | IwoooS / 導航 IA 復原 | 已升為 P0 工作項;removed menu/route/page destination 待盤點 | 盤點 navigation config、route list、removed-page integration | | 主線並行推進規則 | 已升為 P1 工作項;需在回報與 UI 顯示 active lanes | 後續以 active lane + evidence 更新,不因單一 blocker 停全局 | | UI/UX 專業化主線 | 已升為 P1 工作項;Work Items spotlight 先呈現核心缺口 | 排 AwoooP / Approvals / Runs / Work Items / Alerts desktop/mobile smoke | @@ -148,7 +149,7 @@ ## 下一輪固定執行順序 1. 先確認 Gitea `main` / CD / production readback 是否仍與最新 truth 一致。 -2. 先把 AI Loop Agent、AI 自動化節點紀錄、LOG 收集/貼標、Telegram 告警 AI route、IwoooS/nav IA、UI/UX spotlight 落到 API/UI readback。 +2. 先把 AI Loop Agent、AI 自動化節點紀錄、LOG 收集/貼標、Telegram 告警 AI route 與 post-apply verifier、IwoooS/nav IA、UI/UX spotlight 落到 API/UI readback。 3. 接續目前 active P0:110 Stock/Postgres hot pressure,跑 `postgres_hot_query_or_backup_export_playbook` 的 read-only evidence / source freshness / query attribution。 4. 補全 reboot auto-recovery P0:99/110/111/112/120/121/188 reboot detection、10 分鐘 SLO scorecard、Telegram down/up/recovered/SLO missed receipts。 5. 補 99 Windows / VMware autostart P0:99 host 自動啟動 VMware,VM 111/188/120/121/112 自動啟動;同時補 Windows Update no-auto-restart verifier。