diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index ee81f371..fcc4f11e 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -58,6 +58,9 @@ from src.services.ai_agent_deployment_layout import ( from src.services.ai_agent_proactive_operations_contract import ( load_latest_ai_agent_proactive_operations_contract, ) +from src.services.ai_agent_telegram_action_required_digest_policy import ( + load_latest_ai_agent_telegram_action_required_digest_policy, +) from src.services.ai_agent_tool_adoption_approval_package import ( load_latest_ai_agent_tool_adoption_approval_package, ) @@ -645,6 +648,35 @@ async def get_agent_tool_adoption_approval_package() -> dict[str, Any]: ) from exc +@router.get( + "/agent-telegram-action-required-digest-policy", + response_model=dict[str, Any], + summary="取得 AI Agent Telegram action-required digest policy", + description=( + "讀取最新已提交的 AI Agent Telegram action-required digest policy;" + "此端點只回傳 critical / action-required / failure-only digest 規則與 redaction 邊界," + "不送 Telegram、不寫 Telegram Gateway queue、不改 Alertmanager route / receiver、" + "不寫 AwoooP event、不觸發 workflow、不查外部掃描、不執行 runtime、不讀取 secret、" + "不回傳工作視窗對話內容。" + ), +) +async def get_agent_telegram_action_required_digest_policy() -> dict[str, Any]: + """Return the latest read-only AI Agent Telegram action-required digest policy.""" + try: + return await asyncio.to_thread(load_latest_ai_agent_telegram_action_required_digest_policy) + except FileNotFoundError as exc: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=str(exc), + ) from exc + except (json.JSONDecodeError, ValueError) as exc: + logger.error("ai_agent_telegram_action_required_digest_policy_invalid", error=str(exc)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="AI Agent Telegram action-required digest policy 無效", + ) from exc + + @router.get( "/runtime-surface-inventory", response_model=dict[str, Any], diff --git a/apps/api/src/services/ai_agent_telegram_action_required_digest_policy.py b/apps/api/src/services/ai_agent_telegram_action_required_digest_policy.py new file mode 100644 index 00000000..67f13263 --- /dev/null +++ b/apps/api/src/services/ai_agent_telegram_action_required_digest_policy.py @@ -0,0 +1,289 @@ +""" +AI Agent Telegram action-required digest policy snapshot. + +Loads the latest committed, read-only policy for deciding which AI Agent, +version, tool-adoption, scanner, and upgrade signals may become +action-required Telegram digest candidates. This module never sends Telegram +messages, writes Telegram Gateway queues, changes Alertmanager receivers, +triggers workflows, reads secrets, or exposes work-window transcripts. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) +_SNAPSHOT_PATTERN = "ai_agent_telegram_action_required_digest_policy_*.json" +_SCHEMA_VERSION = "ai_agent_telegram_action_required_digest_policy_v1" +_RUNTIME_AUTHORITY = "policy_only_no_telegram_send_or_route_change" +_TRANSCRIPT_MARKERS = { + "# In app browser", + "My request for Codex", + "Current URL:", + "AGENTS.md instructions", + "", + "批准!繼續", +} + + +def load_latest_ai_agent_telegram_action_required_digest_policy( + evaluations_dir: Path | None = None, +) -> dict[str, Any]: + """Load the newest committed AI Agent Telegram digest policy.""" + directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR + candidates = sorted(directory.glob(_SNAPSHOT_PATTERN)) + if not candidates: + raise FileNotFoundError( + f"no AI Agent Telegram action-required digest policy snapshots found in {directory}" + ) + + latest = candidates[-1] + with latest.open(encoding="utf-8") as handle: + payload = json.load(handle) + + if not isinstance(payload, dict): + raise ValueError(f"{latest}: expected JSON object") + _require_schema(payload, _SCHEMA_VERSION, str(latest)) + _require_read_only_boundaries(payload, str(latest)) + _require_rollup_consistency(payload, str(latest)) + _require_success_noise_suppression(payload, str(latest)) + _require_digest_gate_safety(payload, str(latest)) + _require_message_redaction(payload, str(latest)) + _require_no_plaintext_secret_payload_keys(payload, str(latest)) + _require_no_conversation_transcript_content(payload, str(latest)) + return payload + + +def _require_schema(payload: dict[str, Any], expected: str, label: str) -> None: + actual = payload.get("schema_version") + if actual != expected: + raise ValueError(f"{label}: expected schema_version={expected}, got {actual!r}") + + +def _require_read_only_boundaries(payload: dict[str, Any], label: str) -> None: + program_status = payload.get("program_status") or {} + if program_status.get("read_only_mode") is not True: + raise ValueError(f"{label}: program_status.read_only_mode must be true") + if program_status.get("runtime_authority") != _RUNTIME_AUTHORITY: + raise ValueError(f"{label}: runtime_authority must stay {_RUNTIME_AUTHORITY}") + + operation_boundaries = payload.get("operation_boundaries") or {} + if operation_boundaries.get("read_only_policy_allowed") is not True: + raise ValueError(f"{label}: read_only_policy_allowed must be true") + + blocked_operation_flags = { + "telegram_direct_send_allowed", + "telegram_test_message_allowed", + "telegram_gateway_queue_write_allowed", + "alertmanager_route_change_allowed", + "telegram_receiver_change_allowed", + "awooop_event_write_allowed", + "workflow_trigger_allowed", + "external_scan_allowed", + "runtime_execution_allowed", + "secret_plaintext_allowed", + "conversation_transcript_allowed", + } + allowed_operation_flags = sorted( + flag + for flag in blocked_operation_flags + if operation_boundaries.get(flag) is not False + ) + if allowed_operation_flags: + raise ValueError( + f"{label}: operation boundaries must remain false: {allowed_operation_flags}" + ) + + approval_boundaries = payload.get("approval_boundaries") or {} + allowed_approval_flags = sorted( + flag for flag, value in approval_boundaries.items() if value is not False + ) + if allowed_approval_flags: + raise ValueError( + f"{label}: approval boundaries must remain false: {allowed_approval_flags}" + ) + + +def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None: + rules = payload.get("digest_rules") or [] + channels = payload.get("digest_channels") or [] + categories = payload.get("trigger_categories") or [] + rollups = payload.get("rollups") or {} + + expected_counts = { + "rule_count": len(rules), + "channel_count": len(channels), + "trigger_category_count": len(categories), + } + mismatched = { + key: {"expected": expected, "actual": rollups.get(key)} + for key, expected in expected_counts.items() + if rollups.get(key) != expected + } + if mismatched: + raise ValueError(f"{label}: rollup counts must match payload sections: {mismatched}") + + by_decision: dict[str, int] = {} + for rule in rules: + decision = str(rule.get("decision")) + by_decision[decision] = by_decision.get(decision, 0) + 1 + if rollups.get("by_decision") != by_decision: + raise ValueError(f"{label}: rollups.by_decision must match digest rule decisions") + + expected_lists = { + "action_required_rule_ids": { + rule.get("rule_id") + for rule in rules + if rule.get("decision") == "draft_action_required_digest" + }, + "failure_escalation_rule_ids": { + rule.get("rule_id") + for rule in rules + if rule.get("decision") == "draft_failure_escalation_digest" + }, + "suppressed_success_rule_ids": { + rule.get("rule_id") + for rule in rules + if rule.get("decision") == "suppress_success_noise" + }, + } + for key, expected in expected_lists.items(): + if set(rollups.get(key) or []) != expected: + raise ValueError(f"{label}: rollups.{key} mismatch") + + zero_rollups = { + "telegram_direct_send_allowed_count", + "telegram_gateway_queue_write_allowed_count", + "route_change_allowed_count", + "conversation_transcript_allowed_count", + } + nonzero = sorted(key for key in zero_rollups if rollups.get(key) != 0) + if nonzero: + raise ValueError(f"{label}: digest safety counters must remain 0: {nonzero}") + + +def _require_success_noise_suppression(payload: dict[str, Any], label: str) -> None: + noisy_channels = [ + channel.get("channel_id") + for channel in payload.get("digest_channels") or [] + if channel.get("success_immediate_allowed") is not False + or channel.get("direct_send_allowed") is not False + ] + if noisy_channels: + raise ValueError(f"{label}: digest channels must suppress success/direct send: {noisy_channels}") + + noisy_rules = [ + rule.get("rule_id") + for rule in payload.get("digest_rules") or [] + if rule.get("signal_state") in {"success", "verified"} + and rule.get("decision") != "suppress_success_noise" + ] + if noisy_rules: + raise ValueError(f"{label}: success/verified signals must suppress digest noise: {noisy_rules}") + + summary_policy = str(payload.get("rate_limit_policy", {}).get("success_policy") or "") + if "成功" not in summary_policy or "不得" not in summary_policy or "Telegram" not in summary_policy: + raise ValueError(f"{label}: rate_limit_policy.success_policy must block success spam") + + +def _require_digest_gate_safety(payload: dict[str, Any], label: str) -> None: + unsafe_rules = [ + rule.get("rule_id") + for rule in payload.get("digest_rules") or [] + if rule.get("decision") in { + "draft_action_required_digest", + "draft_failure_escalation_digest", + } + and ( + not rule.get("required_evidence") + or rule.get("telegram_direct_send_allowed") is not False + or rule.get("requires_openclaw_review") is not True + ) + ] + if unsafe_rules: + raise ValueError(f"{label}: digest candidate rules need evidence and OpenClaw gate: {unsafe_rules}") + + immediate_non_failure = [ + rule.get("rule_id") + for rule in payload.get("digest_rules") or [] + if rule.get("decision") == "draft_failure_escalation_digest" + and rule.get("signal_state") not in {"failed", "blocked", "critical"} + ] + if immediate_non_failure: + raise ValueError(f"{label}: failure escalation digest must stay failure-only: {immediate_non_failure}") + + +def _require_message_redaction(payload: dict[str, Any], label: str) -> None: + template = payload.get("message_template_contract") or {} + required_fields = set(template.get("required_fields") or []) + required = { + "stage", + "severity", + "target_id", + "evidence_ref", + "blocked_reason", + "next_action", + "owner_agent", + "approval_gate", + } + if not required.issubset(required_fields): + raise ValueError(f"{label}: message_template_contract.required_fields missing digest context") + + forbidden_fields = set(template.get("forbidden_fields") or []) + required_forbidden = { + "secret_value", + "token", + "authorization_header", + "work_window_transcript", + "codex_user_message", + "prompt_text", + "chain_of_thought", + "session_id", + "browser_context", + } + if not required_forbidden.issubset(forbidden_fields): + raise ValueError(f"{label}: message_template_contract.forbidden_fields missing redaction boundary") + + display = payload.get("display_redaction_contract") or {} + if display.get("conversation_transcript_display_allowed") is not False: + raise ValueError(f"{label}: conversation transcript display must remain false") + if display.get("redaction_required") is not True: + raise ValueError(f"{label}: display redaction must be required") + + +def _require_no_plaintext_secret_payload_keys(value: Any, label: str, path: str = "$") -> None: + if isinstance(value, dict): + forbidden_key_fragments = { + "secret_value", + "token_plaintext", + "authorization_header", + "private_key", + "credential_value", + } + for key, nested in value.items(): + normalized_key = str(key).lower() + if any(fragment in normalized_key for fragment in forbidden_key_fragments): + raise ValueError(f"{label}: forbidden plaintext secret key at {path}.{key}") + _require_no_plaintext_secret_payload_keys(nested, label, f"{path}.{key}") + elif isinstance(value, list): + for index, nested in enumerate(value): + _require_no_plaintext_secret_payload_keys(nested, label, f"{path}[{index}]") + + +def _require_no_conversation_transcript_content(value: Any, label: str, path: str = "$") -> None: + if isinstance(value, str): + for marker in _TRANSCRIPT_MARKERS: + if marker in value: + raise ValueError( + f"{label}: forbidden work-window conversation content at {path}: {marker}" + ) + elif isinstance(value, dict): + for key, nested in value.items(): + _require_no_conversation_transcript_content(nested, label, f"{path}.{key}") + elif isinstance(value, list): + for index, nested in enumerate(value): + _require_no_conversation_transcript_content(nested, label, f"{path}[{index}]") diff --git a/apps/api/tests/test_ai_agent_proactive_operations_contract.py b/apps/api/tests/test_ai_agent_proactive_operations_contract.py index 8865d636..e766ed1d 100644 --- a/apps/api/tests/test_ai_agent_proactive_operations_contract.py +++ b/apps/api/tests/test_ai_agent_proactive_operations_contract.py @@ -13,9 +13,9 @@ def test_load_latest_ai_agent_proactive_operations_contract_reads_committed_snap data = load_latest_ai_agent_proactive_operations_contract() assert data["schema_version"] == "ai_agent_proactive_operations_contract_v1" - assert data["program_status"]["overall_completion_percent"] == 55 - assert data["program_status"]["current_task_id"] == "P2-402C" - assert data["program_status"]["next_task_id"] == "P2-402D" + assert data["program_status"]["overall_completion_percent"] == 68 + assert data["program_status"]["current_task_id"] == "P2-402D" + assert data["program_status"]["next_task_id"] == "P2-402E" assert data["program_status"]["read_only_mode"] is True assert data["program_status"]["runtime_authority"] == "contract_only_no_version_or_runtime_update" assert data["approval_boundaries"]["runtime_version_update_allowed"] is False diff --git a/apps/api/tests/test_ai_agent_proactive_operations_contract_api.py b/apps/api/tests/test_ai_agent_proactive_operations_contract_api.py index 860901cf..4174d5f0 100644 --- a/apps/api/tests/test_ai_agent_proactive_operations_contract_api.py +++ b/apps/api/tests/test_ai_agent_proactive_operations_contract_api.py @@ -16,9 +16,9 @@ def test_ai_agent_proactive_operations_contract_endpoint_returns_committed_snaps assert response.status_code == 200 data = response.json() assert data["schema_version"] == "ai_agent_proactive_operations_contract_v1" - assert data["program_status"]["overall_completion_percent"] == 55 - assert data["program_status"]["current_task_id"] == "P2-402C" - assert data["program_status"]["next_task_id"] == "P2-402D" + assert data["program_status"]["overall_completion_percent"] == 68 + assert data["program_status"]["current_task_id"] == "P2-402D" + assert data["program_status"]["next_task_id"] == "P2-402E" assert data["program_status"]["read_only_mode"] is True assert data["approval_boundaries"]["runtime_version_update_allowed"] is False assert data["approval_boundaries"]["package_upgrade_allowed"] is False diff --git a/apps/api/tests/test_ai_agent_telegram_action_required_digest_policy.py b/apps/api/tests/test_ai_agent_telegram_action_required_digest_policy.py new file mode 100644 index 00000000..b587f071 --- /dev/null +++ b/apps/api/tests/test_ai_agent_telegram_action_required_digest_policy.py @@ -0,0 +1,270 @@ +from __future__ import annotations + +import json + +import pytest + +from src.services.ai_agent_telegram_action_required_digest_policy import ( + load_latest_ai_agent_telegram_action_required_digest_policy, +) + + +def test_load_latest_ai_agent_telegram_action_required_digest_policy_reads_committed_snapshot(): + data = load_latest_ai_agent_telegram_action_required_digest_policy() + + assert data["schema_version"] == "ai_agent_telegram_action_required_digest_policy_v1" + assert data["program_status"]["overall_completion_percent"] == 68 + assert data["program_status"]["current_task_id"] == "P2-402D" + assert data["program_status"]["next_task_id"] == "P2-402E" + assert data["program_status"]["read_only_mode"] is True + assert data["program_status"]["runtime_authority"] == "policy_only_no_telegram_send_or_route_change" + assert data["operation_boundaries"]["read_only_policy_allowed"] is True + assert data["operation_boundaries"]["telegram_direct_send_allowed"] is False + assert data["operation_boundaries"]["telegram_gateway_queue_write_allowed"] is False + assert data["operation_boundaries"]["alertmanager_route_change_allowed"] is False + assert data["operation_boundaries"]["conversation_transcript_allowed"] is False + assert data["approval_boundaries"]["telegram_digest_send_approved"] is False + assert data["rollups"]["rule_count"] == len(data["digest_rules"]) == 8 + assert data["rollups"]["channel_count"] == len(data["digest_channels"]) == 3 + assert data["rollups"]["trigger_category_count"] == len(data["trigger_categories"]) == 5 + assert data["rollups"]["by_decision"]["suppress_success_noise"] == 2 + assert data["rollups"]["by_decision"]["draft_failure_escalation_digest"] == 3 + assert data["rollups"]["by_decision"]["draft_action_required_digest"] == 3 + assert data["rollups"]["telegram_direct_send_allowed_count"] == 0 + assert data["rollups"]["telegram_gateway_queue_write_allowed_count"] == 0 + assert data["rollups"]["route_change_allowed_count"] == 0 + assert data["rollups"]["conversation_transcript_allowed_count"] == 0 + assert "work_window_transcript" in data["message_template_contract"]["forbidden_fields"] + assert all( + rule["decision"] == "suppress_success_noise" + for rule in data["digest_rules"] + if rule["signal_state"] in {"success", "verified"} + ) + + +def test_ai_agent_telegram_action_required_digest_policy_blocks_direct_send(tmp_path): + snapshot = _snapshot() + snapshot["operation_boundaries"]["telegram_direct_send_allowed"] = True + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="operation boundaries"): + load_latest_ai_agent_telegram_action_required_digest_policy(tmp_path) + + +def test_ai_agent_telegram_action_required_digest_policy_rejects_rollup_mismatch(tmp_path): + snapshot = _snapshot() + snapshot["rollups"]["rule_count"] = 99 + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="rollup counts"): + load_latest_ai_agent_telegram_action_required_digest_policy(tmp_path) + + +def test_ai_agent_telegram_action_required_digest_policy_rejects_success_noise(tmp_path): + snapshot = _snapshot() + snapshot["digest_rules"][0]["decision"] = "draft_action_required_digest" + snapshot["digest_rules"][0]["requires_openclaw_review"] = True + snapshot["rollups"]["by_decision"] = { + "draft_action_required_digest": 2, + "draft_failure_escalation_digest": 1, + } + snapshot["rollups"]["action_required_rule_ids"] = [ + "repo_only_freshness_snapshot_success", + "tool_install_or_ci_change_needs_approval", + ] + snapshot["rollups"]["suppressed_success_rule_ids"] = [] + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="success/verified signals"): + load_latest_ai_agent_telegram_action_required_digest_policy(tmp_path) + + +def test_ai_agent_telegram_action_required_digest_policy_rejects_transcript_markers(tmp_path): + snapshot = _snapshot() + snapshot["message_template_contract"]["action_required_message_policy"] = ( + "禁止放入:" + "My request for Codex" + ) + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="forbidden work-window conversation content"): + load_latest_ai_agent_telegram_action_required_digest_policy(tmp_path) + + +def test_ai_agent_telegram_action_required_digest_policy_requires_redaction_fields(tmp_path): + snapshot = _snapshot() + snapshot["message_template_contract"]["forbidden_fields"].remove("work_window_transcript") + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="forbidden_fields"): + load_latest_ai_agent_telegram_action_required_digest_policy(tmp_path) + + +def _write_snapshot(tmp_path, snapshot: dict) -> None: + (tmp_path / "ai_agent_telegram_action_required_digest_policy_2026-06-11.json").write_text( + json.dumps(snapshot), + encoding="utf-8", + ) + + +def _snapshot() -> dict: + return { + "schema_version": "ai_agent_telegram_action_required_digest_policy_v1", + "generated_at": "2026-06-11T23:58:00+08:00", + "program_status": { + "overall_completion_percent": 68, + "current_priority": "P2", + "current_task_id": "P2-402D", + "next_task_id": "P2-402E", + "read_only_mode": True, + "runtime_authority": "policy_only_no_telegram_send_or_route_change", + "status_note": "測試。", + }, + "source_refs": ["docs/evaluations/example.json"], + "trigger_categories": [ + { + "category_id": "success_noise", + "owner_agent": "Hermes", + "policy_intent": "測試。", + "blocked_now": ["send"], + }, + { + "category_id": "tool_adoption_approval_required", + "owner_agent": "OpenClaw", + "policy_intent": "測試。", + "blocked_now": ["send"], + }, + ], + "digest_channels": [ + { + "channel_id": "telegram_ops_digest_draft", + "purpose": "測試。", + "success_immediate_allowed": False, + "direct_send_allowed": False, + "queue_write_allowed": False, + "requires_telegram_gateway_e2e": True, + } + ], + "digest_rules": [ + { + "rule_id": "repo_only_freshness_snapshot_success", + "category_id": "success_noise", + "signal_state": "success", + "severity": "info", + "decision": "suppress_success_noise", + "owner_agent": "Hermes", + "required_evidence": ["snapshot"], + "channels": ["telegram_ops_digest_draft"], + "requires_openclaw_review": False, + "telegram_direct_send_allowed": False, + "next_action": "測試。", + }, + { + "rule_id": "version_watch_source_failed", + "category_id": "tool_adoption_approval_required", + "signal_state": "failed", + "severity": "warning", + "decision": "draft_failure_escalation_digest", + "owner_agent": "Hermes", + "required_evidence": ["source_id"], + "channels": ["telegram_ops_digest_draft"], + "requires_openclaw_review": True, + "telegram_direct_send_allowed": False, + "next_action": "測試。", + }, + { + "rule_id": "tool_install_or_ci_change_needs_approval", + "category_id": "tool_adoption_approval_required", + "signal_state": "action_required", + "severity": "warning", + "decision": "draft_action_required_digest", + "owner_agent": "OpenClaw", + "required_evidence": ["tool_id"], + "channels": ["telegram_ops_digest_draft"], + "requires_openclaw_review": True, + "telegram_direct_send_allowed": False, + "next_action": "測試。", + }, + ], + "message_template_contract": { + "required_fields": [ + "stage", + "severity", + "target_id", + "evidence_ref", + "blocked_reason", + "next_action", + "owner_agent", + "approval_gate", + ], + "forbidden_fields": [ + "secret_value", + "token", + "authorization_header", + "work_window_transcript", + "codex_user_message", + "prompt_text", + "chain_of_thought", + "session_id", + "browser_context", + ], + "success_message_policy": "成功不得送 Telegram。", + "action_required_message_policy": "測試。", + "failure_message_policy": "測試。", + }, + "rate_limit_policy": { + "success_policy": "成功不得 Telegram 洗版。", + "digest_window_minutes": 30, + "dedupe_key_fields": ["category_id"], + "max_digest_items": 10, + "fallback_policy": "測試。", + }, + "display_redaction_contract": { + "conversation_transcript_display_allowed": False, + "redaction_required": True, + "allowed_frontend_fields": ["政策規則摘要"], + "forbidden_frontend_content": ["內部對話內容"], + }, + "agent_roles": [], + "operation_boundaries": { + "read_only_policy_allowed": True, + "telegram_direct_send_allowed": False, + "telegram_test_message_allowed": False, + "telegram_gateway_queue_write_allowed": False, + "alertmanager_route_change_allowed": False, + "telegram_receiver_change_allowed": False, + "awooop_event_write_allowed": False, + "workflow_trigger_allowed": False, + "external_scan_allowed": False, + "runtime_execution_allowed": False, + "secret_plaintext_allowed": False, + "conversation_transcript_allowed": False, + }, + "approval_boundaries": { + "telegram_digest_send_approved": False, + "telegram_gateway_queue_write_approved": False, + "telegram_route_change_approved": False, + "alertmanager_receiver_change_approved": False, + "awooop_event_write_approved": False, + "workflow_trigger_approved": False, + "runtime_execution_approved": False, + "secret_plaintext_approved": False, + }, + "rollups": { + "rule_count": 3, + "channel_count": 1, + "trigger_category_count": 2, + "by_decision": { + "suppress_success_noise": 1, + "draft_failure_escalation_digest": 1, + "draft_action_required_digest": 1, + }, + "action_required_rule_ids": ["tool_install_or_ci_change_needs_approval"], + "failure_escalation_rule_ids": ["version_watch_source_failed"], + "suppressed_success_rule_ids": ["repo_only_freshness_snapshot_success"], + "telegram_direct_send_allowed_count": 0, + "telegram_gateway_queue_write_allowed_count": 0, + "route_change_allowed_count": 0, + "conversation_transcript_allowed_count": 0, + "next_approval_task_ids": ["P2-402E"], + }, + } diff --git a/apps/api/tests/test_ai_agent_telegram_action_required_digest_policy_api.py b/apps/api/tests/test_ai_agent_telegram_action_required_digest_policy_api.py new file mode 100644 index 00000000..2ef519b6 --- /dev/null +++ b/apps/api/tests/test_ai_agent_telegram_action_required_digest_policy_api.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import json + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from src.api.v1.agents import router + + +def test_agent_telegram_action_required_digest_policy_endpoint_returns_committed_snapshot(): + app = FastAPI() + app.include_router(router, prefix="/api/v1") + client = TestClient(app) + + response = client.get("/api/v1/agents/agent-telegram-action-required-digest-policy") + + assert response.status_code == 200 + data = response.json() + assert data["schema_version"] == "ai_agent_telegram_action_required_digest_policy_v1" + assert data["program_status"]["overall_completion_percent"] == 68 + assert data["program_status"]["current_task_id"] == "P2-402D" + assert data["program_status"]["next_task_id"] == "P2-402E" + assert data["program_status"]["read_only_mode"] is True + assert data["operation_boundaries"]["telegram_direct_send_allowed"] is False + assert data["operation_boundaries"]["telegram_test_message_allowed"] is False + assert data["operation_boundaries"]["telegram_gateway_queue_write_allowed"] is False + assert data["operation_boundaries"]["alertmanager_route_change_allowed"] is False + assert data["operation_boundaries"]["telegram_receiver_change_allowed"] is False + assert data["operation_boundaries"]["conversation_transcript_allowed"] is False + assert data["approval_boundaries"]["telegram_digest_send_approved"] is False + assert data["rollups"]["rule_count"] == 8 + assert data["rollups"]["channel_count"] == 3 + assert data["rollups"]["trigger_category_count"] == 5 + assert data["rollups"]["telegram_direct_send_allowed_count"] == 0 + assert data["rollups"]["telegram_gateway_queue_write_allowed_count"] == 0 + assert data["rollups"]["route_change_allowed_count"] == 0 + assert data["rollups"]["conversation_transcript_allowed_count"] == 0 + assert any( + rule["rule_id"] == "telegram_delivery_silence_detected" + for rule in data["digest_rules"] + ) + + serialized = json.dumps(data, ensure_ascii=False) + for marker in [ + "批准!繼續", + "My request for Codex", + "Current URL:", + "# In app browser", + "AGENTS.md instructions", + ]: + assert marker not in serialized diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 9b93d71f..13518704 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,26 @@ +## 2026-06-11|P2-402D AI Agent Telegram action-required digest policy + +**背景**:P2-402C 已把 Renovate / OSV-Scanner / Trivy / Syft / Grype 的採用批准包建立完成,但所有工具安裝、CI 變更、外部查詢與 Telegram 實發都仍未開 gate。本階段接續建立 Telegram action-required digest policy,先定義哪些 AI Agent / 版本 / 工具 / 掃描 / 升級訊號可成為 critical / action-required / failure-only digest 草案,並明確壓制成功洗版。 + +**完成內容:** +- 新增 `ai_agent_telegram_action_required_digest_policy_v1` schema。 +- 新增 committed snapshot:`docs/evaluations/ai_agent_telegram_action_required_digest_policy_2026-06-11.json`。 +- 新增只讀 loader:`apps/api/src/services/ai_agent_telegram_action_required_digest_policy.py`。 +- 新增治理 API:`GET /api/v1/agents/agent-telegram-action-required-digest-policy`。 +- 新增 loader / API / direct-send boundary / success-noise suppression / rollup mismatch / transcript redaction 測試。 +- 新增分析報告:`docs/ai/AI_AGENT_TELEGRAM_ACTION_REQUIRED_DIGEST_POLICY_2026-06-11.md`。 +- 同步 `AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md`、`AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md` 與 MASTER §3.2.1c。 + +**完成度與下一步:** +- P2-402D:`100%`。 +- AI Agent 主動營運與版本生命週期整體:`68%`。 +- Current task:`P2-402D`。 +- Next task:`P2-402E` Gitea PR 草案 lane。 + +**安全邊界:** +- 本波只做 digest policy 與只讀 API。 +- 未送 Telegram、未寫 Telegram Gateway queue、未改 Alertmanager route / receiver、未寫 AwoooP event、未觸發 workflow、未查外部掃描、未執行 runtime、未讀取或輸出 secret、未顯示工作視窗對話內容。 + ## 2026-06-11|P0 Telegram 批准後執行真相鏈止血 **背景**:使用者指出每個 Telegram 告警批准後都只得到「已記錄觀察,未執行修復」,代表前一輪只補到告警送達與 recurrence 可見,尚未把 approval -> execution -> verifier -> KM / PlayBook 的執行真相鏈閉合。Production status-chain 抽查 `INC-20260611-BB1D9D` 確認舊流程為 `latest_action=OBSERVE`、`completion_status=completed_no_repair`、`repair_status=not_executed`;另一個具備 `kubectl rollout restart` 的事件雖有 execution record,但缺少 `auto_repair_executions` 與 verifier / KM 收斂證據。 diff --git a/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md b/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md index d85cc50a..856949ab 100644 --- a/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md +++ b/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md @@ -13,7 +13,7 @@ | 工具 / 服務 / 套件 AI 自動化 | 92% | P0 已完成;P1 服務 / runtime / 監控 / provider / service health / 備份 / DR / 套件與供應鏈只讀基線已完成;P1-007 失敗限定通知合約與前端 redaction 合約已完成;下一主線是 P2-004 依賴 / 供應鏈漂移監控 | 狀態分類、盤點 schema、權限矩陣、靜態盤點種子、只讀 API、UI 骨架、驗證、自動化待辦 schema / 快照 / API / 分組 UI、Backup / DR 目標盤點、準備度矩陣、備份通知政策、Backup / DR 證據 UI、復原演練批准包模板、異地 / escrow 準備度狀態、任務批准邊界、確定性進度彙總、Python 套件 / 供應鏈只讀基線、JS pnpm/npm 只讀基線、Docker build surface 只讀基線、CVE / license / drift 嚴重度政策、定期依賴漂移與外部資料來源檢查設計、依賴升級批准包模板、runtime_surface_inventory_v1 schema / snapshot / API / UI、gitea_workflow_runner_health_v1 schema / snapshot / API / UI、observability_contract_matrix_v1 schema / snapshot / API / UI、ai_provider_route_matrix_v1 schema / snapshot / API / UI、service_health_gap_matrix_v1 schema / snapshot / API / UI、service health evidence cards UI、service_health_failure_notification_policy_v1 schema / snapshot / API / UI 已完成 | | OpenClaw / Hermes / NemoTron 佈建布局 | 45% | P1-401 / P1-402 已完成;仍是只讀 layout 與治理頁顯示,不是 runtime deploy | `ai_agent_deployment_layout_v1` schema、`ai_agent_deployment_layout_2026-06-11.json`、`GET /api/v1/agents/agent-deployment-layout`、治理頁自動化盤點 UI、`AI_AGENT_DEPLOYMENT_LAYOUT_2026-06-11.md` | | OpenClaw / Hermes / NemoTron 主動溝通與學習契約 | 35% | P2-401A 已完成只讀 contract;runtime worker、DB migration、Telegram 實發、SDK / 付費服務仍未開 gate | `ai_agent_communication_learning_contract_v1` schema、`ai_agent_communication_learning_contract_2026-06-11.json`、`GET /api/v1/agents/agent-communication-learning-contract`、MASTER §3.2.1b / §3.4.3 | -| AI Agent 主動營運委派與版本生命週期 | 55% | P2-402A / P2-402B / P2-402C 已完成;已建立 repo-only 版本新鮮度快照、工具採用批准包與 API。定期排程、外部版本查詢、工具安裝、CI 變更、套件升級、主機更新、container pull、auto merge、Telegram 實發仍未開 gate | `ai_agent_proactive_operations_contract_v1`、`ai_agent_version_freshness_snapshot_v1`、`ai_agent_tool_adoption_approval_package_v1`、`GET /api/v1/agents/agent-proactive-operations-contract`、`GET /api/v1/agents/agent-version-freshness-snapshot`、`GET /api/v1/agents/agent-tool-adoption-approval-package`、MASTER §3.2.1c | +| AI Agent 主動營運委派與版本生命週期 | 68% | P2-402A / P2-402B / P2-402C / P2-402D 已完成;已建立 repo-only 版本新鮮度快照、工具採用批准包、Telegram action-required digest policy 與 API。定期排程、外部版本查詢、工具安裝、CI 變更、套件升級、主機更新、container pull、auto merge、Telegram 實發仍未開 gate | `ai_agent_proactive_operations_contract_v1`、`ai_agent_version_freshness_snapshot_v1`、`ai_agent_tool_adoption_approval_package_v1`、`ai_agent_telegram_action_required_digest_policy_v1`、`GET /api/v1/agents/agent-proactive-operations-contract`、`GET /api/v1/agents/agent-version-freshness-snapshot`、`GET /api/v1/agents/agent-tool-adoption-approval-package`、`GET /api/v1/agents/agent-telegram-action-required-digest-policy`、MASTER §3.2.1c | | 本工作清單與分析報告 | 100% | 已完成 | 本 MD 文件 | AI Agent 自動化工作包目前完成度:**92%**。本工作清單文件本身完成度:**100%**。 @@ -22,7 +22,7 @@ AI Agent 自動化工作包目前完成度:**92%**。本工作清單文件本 三 Agent 主動溝通與學習契約目前完成度:**35%**。已完成只讀 schema / snapshot / API / 測試與 MASTER 同步;下一步依優先順序推 `P2-401B` AgentSession / Redis Streams migration 與 worker gate,但在批准前仍不得啟動 runtime loop。 -AI Agent 主動營運委派與版本生命週期目前完成度:**55%**。已完成 12 類版本 domain、24 類可委派能力、5 種 cadence、8 類 MCP、4 類 RAG memory、只讀 API、`P2-402B` repo-only daily version freshness snapshot,以及 `P2-402C` Renovate / OSV-Scanner / Trivy / Syft / Grype 工具採用批准包;下一步是 `P2-402D` Telegram action-required digest policy,外部 registry / package source / host probe / 工具安裝 / CI 變更 / Telegram 實發仍需 gate。 +AI Agent 主動營運委派與版本生命週期目前完成度:**68%**。已完成 12 類版本 domain、24 類可委派能力、5 種 cadence、8 類 MCP、4 類 RAG memory、只讀 API、`P2-402B` repo-only daily version freshness snapshot、`P2-402C` Renovate / OSV-Scanner / Trivy / Syft / Grype 工具採用批准包,以及 `P2-402D` Telegram action-required digest policy;下一步是 `P2-402E` Gitea PR 草案 lane,外部 registry / package source / host probe / 工具安裝 / CI 變更 / Telegram 實發仍需 gate。 完成度計算模型: @@ -954,7 +954,7 @@ UI: | P2-402A | 完成 | 100 | Hermes + OpenClaw + Nemotron | 定義 AI Agent 主動營運委派與版本生命週期:12 類版本 domain、24 類可委派能力、MCP/RAG/Telegram policy | `ai_agent_proactive_operations_contract_v1` / snapshot / 只讀 API / MASTER 同步 | 只讀;不啟用排程、不升級、不 host update、不 pull image、不 auto merge、不發 Telegram | | P2-402B | 完成 | 100 | Hermes | 建立 repo-only daily version freshness snapshot | `ai_agent_version_freshness_snapshot_v1` / snapshot / 只讀 API / 測試;12 類 repo-only source、5 個 daily step、3 Agent role | 只讀;workflow schedule 未啟用、外部 lookup 未開、不得升級或發 Telegram | | P2-402C | 完成 | 100 | OpenClaw | 建立 Renovate / OSV-Scanner / Trivy / Syft / Grype 工具採用批准包 | `ai_agent_tool_adoption_approval_package_v1` / snapshot / 只讀 API / 測試;5 工具、5 官方來源、4 採用 lane、6 批准欄位 | 只讀;tool install、CI change、external lookup、vulnerability DB、PR、Telegram 全部未啟用 | -| P2-402D | 待辦 | 0 | OpenClaw | 建立 Telegram action-required digest policy | critical / action-required / failure-only digest | Telegram Gateway E2E | +| P2-402D | 完成 | 100 | OpenClaw | 建立 Telegram action-required digest policy | `ai_agent_telegram_action_required_digest_policy_v1` / snapshot / 只讀 API / 測試;8 條 digest rule、3 個 channel 草案、5 類 trigger category、成功降噪與 redaction policy | 只讀;Telegram send、Gateway queue write、route / receiver change、workflow、runtime 全部未啟用 | | P2-402E | 待辦 | 0 | Hermes | 設計 Gitea PR 草案 lane | grouping、automerge=false、tests、rollback、owner response | bot / branch policy approval | | P2-402F | 待辦 | 0 | OpenClaw | 建立 host OS / K3s / stateful services 版本只讀盤點 | host / K3s / DB / Redis / MinIO / Gitea 版本矩陣 | host readonly probe + maintenance window approval | | P2-402G | 待辦 | 0 | Hermes | 接入 governance UI 顯示可委派能力 | 自主等級、gate、owner、Telegram policy | frontend UI change approval | diff --git a/docs/ai/AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md b/docs/ai/AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md index e15539ef..348dcee5 100644 --- a/docs/ai/AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md +++ b/docs/ai/AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md @@ -1,7 +1,7 @@ # AI Agent 主動營運委派與版本生命週期分析報告 > 日期:2026-06-11(台北時間) -> 文件定位:P2-402A / P2-402B / P2-402C 只讀契約摘要。權威細節以 MASTER §3.2.1c、`ai_agent_proactive_operations_contract_v1`、`ai_agent_version_freshness_snapshot_v1` 與 `ai_agent_tool_adoption_approval_package_v1` 為準。 +> 文件定位:P2-402A / P2-402B / P2-402C / P2-402D 只讀契約摘要。權威細節以 MASTER §3.2.1c、`ai_agent_proactive_operations_contract_v1`、`ai_agent_version_freshness_snapshot_v1`、`ai_agent_tool_adoption_approval_package_v1` 與 `ai_agent_telegram_action_required_digest_policy_v1` 為準。 ## 1. 本波完成度 @@ -10,7 +10,8 @@ | 主動營運委派契約 | 100% | 已完成 schema / snapshot / API / 測試 | | Repo-only 版本新鮮度快照 | 100% | P2-402B 已完成 schema / committed snapshot / API / 測試 | | 工具採用批准包 | 100% | P2-402C 已完成 Renovate / OSV-Scanner / Trivy / Syft / Grype schema / committed snapshot / API / 測試 | -| 整體主動營運與版本生命週期 | 55% | 已完成架構、repo-only freshness 與工具採用批准包;runtime 排程、工具安裝、CI 變更與更新仍未開 gate | +| Telegram action-required digest policy | 100% | P2-402D 已完成 critical / action-required / failure-only digest schema / committed snapshot / API / 測試 | +| 整體主動營運與版本生命週期 | 68% | 已完成架構、repo-only freshness、工具採用批准包與 Telegram digest policy;runtime 排程、工具安裝、CI 變更與更新仍未開 gate | ## 2. 可交給 AI Agent 的工作分類 @@ -38,6 +39,9 @@ | `docs/schemas/ai_agent_tool_adoption_approval_package_v1.schema.json` | 工具採用批准包 schema;工具安裝、CI 變更、外部查詢、漏洞 DB、PR、Telegram 全部預設 false | | `docs/evaluations/ai_agent_tool_adoption_approval_package_2026-06-11.json` | Renovate / OSV-Scanner / Trivy / Syft / Grype 官方來源、採用 lane、批准欄位、rollback plan | | `GET /api/v1/agents/agent-tool-adoption-approval-package` | 只讀 API;不安裝工具、不改 CI、不查外部、不建 PR、不發 Telegram | +| `docs/schemas/ai_agent_telegram_action_required_digest_policy_v1.schema.json` | Telegram action-required digest policy schema;direct send、queue write、route / receiver change、workflow、transcript 全部預設 false | +| `docs/evaluations/ai_agent_telegram_action_required_digest_policy_2026-06-11.json` | 8 條 digest rule、3 個 channel 草案、5 類 trigger category、成功降噪、redaction、fallback policy | +| `GET /api/v1/agents/agent-telegram-action-required-digest-policy` | 只讀 API;不送 Telegram、不寫 queue、不改 route / receiver、不觸發 workflow | ## 4. P2-402B Repo-only 版本新鮮度快照 @@ -64,17 +68,26 @@ P2-402C 的重點是把「哪些工具可被採用、採用前要填哪些批准欄位、風險與 rollback 如何定義」固定成資料契約。它不是工具導入本身;工具安裝、CI workflow、外部 registry / vulnerability DB、Gitea PR、Telegram 實發都維持 blocked。 -## 6. 下一步優先順序 +## 6. P2-402D Telegram Action-Required Digest Policy + +| 類別 | 決策 | Telegram 邊界 | +|---|---|---| +| 成功 / 已驗證 / 快照完成 | `suppress_success_noise` | 不即時送 Telegram,只進每日 KM / LOGBOOK 摘要草案 | +| 版本來源失敗 / Telegram silence / critical CVE 或 EOL | `draft_failure_escalation_digest` | 只產 failure escalation digest 草案;需 OpenClaw review,不能直發 | +| 工具安裝 / CI 變更 / scanner disagreement / no executable repair candidate | `draft_action_required_digest` | 只產 action-required digest 草案;未批准不得寫 Gateway queue | + +本波把「哪些事情值得通知」和「哪些成功訊息必須壓掉」固定成資料契約。Telegram Gateway queue write、direct send、route / receiver change、AwoooP event write、workflow trigger、runtime execution 仍全部為 false。 + +## 7. 下一步優先順序 | ID | 優先 | 任務 | 關卡 | |---|---|---|---| -| P2-402C | 0 | Renovate / OSV-Scanner / Trivy / Syft / Grype 採用批准包 | 已完成,只讀;tool install / CI change 未啟用 | -| P2-402D | 1 | Telegram action-required digest policy | Telegram Gateway E2E | -| P2-402E | 2 | Gitea PR 草案 lane | bot / branch policy approval | -| P2-402F | 3 | host OS / K3s / stateful services 版本只讀盤點 | host probe / maintenance approval | -| P2-402G | 4 | governance UI 顯示可委派能力 | frontend UI approval | +| P2-402D | 0 | Telegram action-required digest policy | 已完成,只讀;Telegram send / queue write 未啟用 | +| P2-402E | 1 | Gitea PR 草案 lane | bot / branch policy approval | +| P2-402F | 2 | host OS / K3s / stateful services 版本只讀盤點 | host probe / maintenance approval | +| P2-402G | 3 | governance UI 顯示可委派能力 | frontend UI approval | -## 7. 仍維持 false 的安全邊界 +## 8. 仍維持 false 的安全邊界 - `runtime_version_update_allowed=false` - `package_upgrade_allowed=false` diff --git a/docs/ai/AI_AGENT_TELEGRAM_ACTION_REQUIRED_DIGEST_POLICY_2026-06-11.md b/docs/ai/AI_AGENT_TELEGRAM_ACTION_REQUIRED_DIGEST_POLICY_2026-06-11.md new file mode 100644 index 00000000..62a96165 --- /dev/null +++ b/docs/ai/AI_AGENT_TELEGRAM_ACTION_REQUIRED_DIGEST_POLICY_2026-06-11.md @@ -0,0 +1,55 @@ +# AI Agent Telegram Action-Required Digest Policy 工作報告 + +> 日期:2026-06-11(台北時間) +> 狀態:P2-402D 已完成;整體 AI Agent 主動營運與版本生命週期完成度 68%。 +> 邊界:本文件與對應 API 只提供 Telegram digest policy,不送 Telegram、不寫 Telegram Gateway queue、不改 Alertmanager route / receiver、不寫 AwoooP event、不觸發 workflow、不執行 runtime、不讀取 secret、不顯示工作視窗對話內容。 + +## 1. 本波完成項目 + +| 項目 | 狀態 | 產出 | +|---|---|---| +| Telegram digest policy schema | 完成 | `docs/schemas/ai_agent_telegram_action_required_digest_policy_v1.schema.json` | +| Telegram digest policy snapshot | 完成 | `docs/evaluations/ai_agent_telegram_action_required_digest_policy_2026-06-11.json` | +| API loader | 完成 | `apps/api/src/services/ai_agent_telegram_action_required_digest_policy.py` | +| 只讀 API | 完成 | `GET /api/v1/agents/agent-telegram-action-required-digest-policy` | +| 測試 | 完成 | loader / API / direct-send boundary / success-noise suppression / rollup mismatch / transcript redaction | +| 主契約同步 | 完成 | `ai_agent_proactive_operations_contract_v1` 完成度 68%,current `P2-402D`,next `P2-402E` | + +## 2. Digest 規則摘要 + +| 決策 | 規則數 | 用途 | 邊界 | +|---|---:|---|---| +| `suppress_success_noise` | 2 | repo-only freshness 成功、工具批准包完成等成功訊號只進每日摘要 | 不即時 Telegram、不寫 queue | +| `draft_failure_escalation_digest` | 3 | version watch source failed、critical CVE / EOL、Telegram delivery silence | 需 OpenClaw review;只產草案 | +| `draft_action_required_digest` | 3 | 工具安裝 / CI 變更需批准、scanner disagreement、approval 後沒有可執行修復候選 | 需 evidence ref、owner、approval gate | + +## 3. Agent 分工 + +| Agent | 本波可做 | 本波不可做 | +|---|---|---| +| Hermes | 彙整 repo-only freshness、source failure、工具採用 evidence refs 與每日摘要草案 | 直接發 Telegram、寫 Gateway queue、觸發 workflow | +| OpenClaw | 仲裁 critical / action-required / failure-only digest 草案,確認 blast radius、rollback 與 approval gate | 自行批准發送、改 route / receiver、把成功訊息升級為告警 | +| NemoTron | 針對 scanner disagreement、AI Agent / model / prompt / tool-call risk 產生離線 replay note | 呼叫付費模型、新增 SDK、開 shadow / canary | + +## 4. 仍維持 false 的邊界 + +- `telegram_direct_send_allowed=false` +- `telegram_test_message_allowed=false` +- `telegram_gateway_queue_write_allowed=false` +- `alertmanager_route_change_allowed=false` +- `telegram_receiver_change_allowed=false` +- `awooop_event_write_allowed=false` +- `workflow_trigger_allowed=false` +- `external_scan_allowed=false` +- `runtime_execution_allowed=false` +- `secret_plaintext_allowed=false` +- `conversation_transcript_allowed=false` + +## 5. 下一步代辦 + +| 優先 | ID | 工作 | 完成標準 | +|---:|---|---|---| +| 1 | P2-402E | Gitea PR 草案 lane | Renovate grouping、automerge=false、測試要求、rollback、owner response、bot / branch policy approval | +| 2 | P2-402F | host OS / K3s / stateful services 版本只讀盤點 | 主機、K3s、PostgreSQL、Redis、MinIO、Harbor、Gitea 只讀版本矩陣 | +| 3 | P2-402G | Governance UI 顯示可委派能力 | 前端顯示 autonomy level、gate、owner、Telegram policy,無執行按鈕 | +| 4 | 候選池 | Telegram Gateway E2E 批准包 | queue write / send / callback / fallback path 的測試與 rollout gate | diff --git a/docs/ai/AI_AGENT_TOOL_ADOPTION_APPROVAL_PACKAGE_2026-06-11.md b/docs/ai/AI_AGENT_TOOL_ADOPTION_APPROVAL_PACKAGE_2026-06-11.md index 4992607d..4679947f 100644 --- a/docs/ai/AI_AGENT_TOOL_ADOPTION_APPROVAL_PACKAGE_2026-06-11.md +++ b/docs/ai/AI_AGENT_TOOL_ADOPTION_APPROVAL_PACKAGE_2026-06-11.md @@ -37,7 +37,7 @@ | 優先 | ID | 工作 | 完成標準 | 目前狀態 | |---:|---|---|---|---| -| 1 | P2-402D | Telegram action-required digest policy | 定義 critical / action-required / failure-only,禁止成功洗版,接 Telegram Gateway E2E gate | 待辦 | +| 1 | P2-402D | Telegram action-required digest policy | 定義 critical / action-required / failure-only,禁止成功洗版,接 Telegram Gateway E2E gate | 完成 | | 2 | P2-402E | Gitea PR 草案 lane | Renovate grouping、automerge=false、測試要求、rollback、owner response | 待辦 | | 3 | P2-402F | host OS / K3s / stateful services 版本只讀盤點 | 主機、K3s、PostgreSQL、Redis、MinIO、Harbor、Gitea 只讀版本矩陣 | 待辦 | | 4 | P2-402G | Governance UI 顯示可委派能力 | 前端顯示 autonomy level、gate、owner、Telegram policy,無執行按鈕 | 待辦 | diff --git a/docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json b/docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json index 2bca560c..e85b5535 100644 --- a/docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json +++ b/docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json @@ -2,13 +2,13 @@ "schema_version": "ai_agent_proactive_operations_contract_v1", "generated_at": "2026-06-11T21:30:00+08:00", "program_status": { - "overall_completion_percent": 55, + "overall_completion_percent": 68, "current_priority": "P2", - "current_task_id": "P2-402C", - "next_task_id": "P2-402D", + "current_task_id": "P2-402D", + "next_task_id": "P2-402E", "read_only_mode": true, "runtime_authority": "contract_only_no_version_or_runtime_update", - "status_note": "P2-402C 已建立 Renovate / OSV-Scanner / Trivy / Syft / Grype 工具採用批准包、schema、snapshot、只讀 API 與測試;本波仍不安裝工具、不改 CI、不查外部 registry、不下載漏洞 DB、不建立 PR、不發 Telegram。" + "status_note": "P2-402D 已建立 Telegram action-required digest policy、schema、snapshot、只讀 API 與測試;本波仍不送 Telegram、不寫 Gateway queue、不改 Alertmanager route / receiver、不觸發 workflow、不執行 runtime。" }, "external_source_evidence": [ { @@ -628,16 +628,16 @@ "completion_percent": 100, "owner_agent": "OpenClaw", "summary": "建立 Renovate / OSV-Scanner / Trivy / Syft / Grype 工具採用批准包、官方來源 evidence、採用 lane、批准欄位、schema、snapshot、只讀 API 與測試。", - "next_gate": "P2-402D_telegram_action_required_digest_policy" + "next_gate": "P2-402D_completed" }, { "task_id": "P2-402D", "priority": "P2", - "status": "planned", - "completion_percent": 0, + "status": "done", + "completion_percent": 100, "owner_agent": "OpenClaw", - "summary": "建立 Telegram action-required digest policy,只通知 critical / action-required,不發成功洗版。", - "next_gate": "telegram_gateway_e2e_required" + "summary": "建立 Telegram action-required digest policy、schema、snapshot、只讀 API 與測試;定義 critical / action-required / failure-only digest 草案、成功降噪、redaction 與 fallback gap 邊界。", + "next_gate": "P2-402E_gitea_pr_draft_lane" }, { "task_id": "P2-402E", diff --git a/docs/evaluations/ai_agent_telegram_action_required_digest_policy_2026-06-11.json b/docs/evaluations/ai_agent_telegram_action_required_digest_policy_2026-06-11.json new file mode 100644 index 00000000..b39eb903 --- /dev/null +++ b/docs/evaluations/ai_agent_telegram_action_required_digest_policy_2026-06-11.json @@ -0,0 +1,405 @@ +{ + "schema_version": "ai_agent_telegram_action_required_digest_policy_v1", + "generated_at": "2026-06-11T23:58:00+08:00", + "program_status": { + "overall_completion_percent": 68, + "current_priority": "P2", + "current_task_id": "P2-402D", + "next_task_id": "P2-402E", + "read_only_mode": true, + "runtime_authority": "policy_only_no_telegram_send_or_route_change", + "status_note": "P2-402D 建立 Telegram action-required digest policy;本波只定義 critical / action-required / failure-only digest 規則,不送 Telegram、不寫 Gateway queue、不改 Alertmanager route、不改 receiver、不觸發 workflow。" + }, + "source_refs": [ + "docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json", + "docs/evaluations/ai_agent_version_freshness_snapshot_2026-06-11.json", + "docs/evaluations/ai_agent_tool_adoption_approval_package_2026-06-11.json", + "docs/evaluations/service_health_failure_notification_policy_2026-06-05.json", + "docs/evaluations/backup_notification_policy_2026-06-04.json", + "docs/LOGBOOK.md" + ], + "trigger_categories": [ + { + "category_id": "success_noise", + "owner_agent": "Hermes", + "policy_intent": "成功、完成、已驗證、只讀快照更新不得即時送 Telegram;只能留在 committed evidence 或日摘要。", + "blocked_now": [ + "成功巡檢即時 Telegram", + "成功掃描即時 Telegram", + "只讀 snapshot 完成即時 Telegram" + ] + }, + { + "category_id": "version_watch_failure", + "owner_agent": "Hermes", + "policy_intent": "版本來源連續失敗、資料過期、watch source 失聯時產生 action-required digest 草案。", + "blocked_now": [ + "直接寫 Telegram Gateway queue", + "直接送 Bot", + "觸發外部 registry lookup" + ] + }, + { + "category_id": "critical_security_or_eol", + "owner_agent": "OpenClaw", + "policy_intent": "critical CVE、EOL approaching、production incompatibility risk 才可進 failure escalation digest 草案。", + "blocked_now": [ + "下載 vulnerability DB", + "掃描 live image", + "升級套件或 image" + ] + }, + { + "category_id": "tool_adoption_approval_required", + "owner_agent": "OpenClaw", + "policy_intent": "Renovate / OSV-Scanner / Trivy / Syft / Grype 採用需要人類批准時,僅產 action-required digest 草案。", + "blocked_now": [ + "安裝工具", + "改 CI workflow", + "建立 Gitea PR" + ] + }, + { + "category_id": "telegram_delivery_silence", + "owner_agent": "OpenClaw", + "policy_intent": "Telegram delivery silence、fallback gap、callback failure 屬於告警必到風險,進 failure escalation digest 草案。", + "blocked_now": [ + "直接發測試訊息", + "改 receiver", + "改 Alertmanager route" + ] + } + ], + "digest_channels": [ + { + "channel_id": "telegram_ops_digest_draft", + "purpose": "Telegram action-required / failure-only digest 草案;P2-402D 只定義政策,不送出。", + "success_immediate_allowed": false, + "direct_send_allowed": false, + "queue_write_allowed": false, + "requires_telegram_gateway_e2e": true + }, + { + "channel_id": "awooop_action_queue_draft", + "purpose": "AwoooP action-required queue 草案;本波不寫 event、不建立 operator task。", + "success_immediate_allowed": false, + "direct_send_allowed": false, + "queue_write_allowed": false, + "requires_telegram_gateway_e2e": false + }, + { + "channel_id": "daily_km_digest_only", + "purpose": "成功與資訊性訊號只能進每日 KM / LOGBOOK 摘要草案。", + "success_immediate_allowed": false, + "direct_send_allowed": false, + "queue_write_allowed": false, + "requires_telegram_gateway_e2e": false + } + ], + "digest_rules": [ + { + "rule_id": "repo_only_freshness_snapshot_success", + "category_id": "success_noise", + "signal_state": "success", + "severity": "info", + "decision": "suppress_success_noise", + "owner_agent": "Hermes", + "required_evidence": [ + "committed snapshot ref", + "rollup counters" + ], + "channels": [ + "daily_km_digest_only" + ], + "requires_openclaw_review": false, + "telegram_direct_send_allowed": false, + "next_action": "只寫 LOGBOOK / KM 摘要草案,不送 Telegram。" + }, + { + "rule_id": "tool_adoption_package_completed_success", + "category_id": "success_noise", + "signal_state": "verified", + "severity": "info", + "decision": "suppress_success_noise", + "owner_agent": "Hermes", + "required_evidence": [ + "schema ref", + "API verification ref" + ], + "channels": [ + "daily_km_digest_only" + ], + "requires_openclaw_review": false, + "telegram_direct_send_allowed": false, + "next_action": "保留 committed evidence,不發成功洗版。" + }, + { + "rule_id": "version_watch_source_failed", + "category_id": "version_watch_failure", + "signal_state": "failed", + "severity": "warning", + "decision": "draft_failure_escalation_digest", + "owner_agent": "Hermes", + "required_evidence": [ + "source_id", + "last_success_at", + "failure_count", + "fallback_source" + ], + "channels": [ + "telegram_ops_digest_draft", + "awooop_action_queue_draft" + ], + "requires_openclaw_review": true, + "telegram_direct_send_allowed": false, + "next_action": "OpenClaw 確認是否升級為 action-required Telegram digest。" + }, + { + "rule_id": "critical_cve_or_eol_requires_owner", + "category_id": "critical_security_or_eol", + "signal_state": "critical", + "severity": "critical", + "decision": "draft_failure_escalation_digest", + "owner_agent": "OpenClaw", + "required_evidence": [ + "official advisory ref", + "affected component", + "blast radius", + "rollback plan" + ], + "channels": [ + "telegram_ops_digest_draft", + "awooop_action_queue_draft" + ], + "requires_openclaw_review": true, + "telegram_direct_send_allowed": false, + "next_action": "產生 owner packet 與批准包;未批准不得升級。" + }, + { + "rule_id": "telegram_delivery_silence_detected", + "category_id": "telegram_delivery_silence", + "signal_state": "failed", + "severity": "critical", + "decision": "draft_failure_escalation_digest", + "owner_agent": "OpenClaw", + "required_evidence": [ + "delivery route", + "last_successful_send", + "fallback path", + "failed callback evidence" + ], + "channels": [ + "telegram_ops_digest_draft", + "awooop_action_queue_draft" + ], + "requires_openclaw_review": true, + "telegram_direct_send_allowed": false, + "next_action": "保留告警必到 fallback 檢查;本波不直接送測試訊息。" + }, + { + "rule_id": "tool_install_or_ci_change_needs_approval", + "category_id": "tool_adoption_approval_required", + "signal_state": "action_required", + "severity": "warning", + "decision": "draft_action_required_digest", + "owner_agent": "OpenClaw", + "required_evidence": [ + "tool_id", + "approval_requirements", + "secret_storage_plan", + "rollback_plan" + ], + "channels": [ + "telegram_ops_digest_draft", + "awooop_action_queue_draft" + ], + "requires_openclaw_review": true, + "telegram_direct_send_allowed": false, + "next_action": "要求 operator approval id;未批准不得安裝工具或改 CI。" + }, + { + "rule_id": "scanner_disagreement_needs_review", + "category_id": "tool_adoption_approval_required", + "signal_state": "action_required", + "severity": "warning", + "decision": "draft_action_required_digest", + "owner_agent": "NemoTron", + "required_evidence": [ + "scanner_a_ref", + "scanner_b_ref", + "disagreement_summary", + "affected package or image" + ], + "channels": [ + "telegram_ops_digest_draft" + ], + "requires_openclaw_review": true, + "telegram_direct_send_allowed": false, + "next_action": "NemoTron 產生離線 replay / assumption note,OpenClaw 仲裁是否 action-required。" + }, + { + "rule_id": "no_executable_repair_candidate_after_approval", + "category_id": "telegram_delivery_silence", + "signal_state": "blocked", + "severity": "warning", + "decision": "draft_action_required_digest", + "owner_agent": "OpenClaw", + "required_evidence": [ + "incident_id", + "approval_id", + "latest_action", + "repair_candidate_gap" + ], + "channels": [ + "telegram_ops_digest_draft", + "awooop_action_queue_draft" + ], + "requires_openclaw_review": true, + "telegram_direct_send_allowed": false, + "next_action": "標記 no-action / non-executable,避免誤導 operator 以為已執行修復。" + } + ], + "message_template_contract": { + "required_fields": [ + "stage", + "severity", + "target_id", + "evidence_ref", + "blocked_reason", + "next_action", + "owner_agent", + "approval_gate" + ], + "forbidden_fields": [ + "secret_value", + "token", + "authorization_header", + "work_window_transcript", + "codex_user_message", + "prompt_text", + "chain_of_thought", + "session_id", + "browser_context" + ], + "success_message_policy": "成功、完成、已驗證、只讀快照更新不得即時送 Telegram / AwoooP;只能進每日摘要或 LOGBOOK。", + "action_required_message_policy": "只允許輸出 evidence ref、blocked reason、next action、owner agent、approval gate;本波不送出。", + "failure_message_policy": "只有 failed / blocked / critical 且具備 evidence ref 與 OpenClaw review gate 時,才可成為 failure escalation digest 草案。" + }, + "rate_limit_policy": { + "success_policy": "成功巡檢、成功掃描、成功快照不得即時 Telegram 洗版。", + "digest_window_minutes": 30, + "dedupe_key_fields": [ + "category_id", + "target_id", + "evidence_ref", + "owner_agent" + ], + "max_digest_items": 10, + "fallback_policy": "若 Telegram Gateway 或 callback 失敗,保留 AwoooP / LOGBOOK evidence refs 與 fallback gap,不直接重送 Bot。" + }, + "display_redaction_contract": { + "conversation_transcript_display_allowed": false, + "redaction_required": true, + "allowed_frontend_fields": [ + "政策規則摘要", + "已提交證據參照", + "blocked reason", + "next action", + "owner agent", + "approval gate" + ], + "forbidden_frontend_content": [ + "內部對話內容", + "Codex / 使用者訊息逐字稿", + "提示詞 / 思考鏈", + "工作階段識別碼 / 瀏覽器脈絡", + "機密 / 權杖 / 授權標頭" + ] + }, + "agent_roles": [ + { + "agent_id": "hermes", + "digest_responsibility": "彙整 repo-only freshness、source failure、工具採用 evidence refs 與每日摘要草案。", + "blocked_now": [ + "直接發 Telegram", + "寫 Telegram Gateway queue", + "觸發 workflow" + ] + }, + { + "agent_id": "openclaw", + "digest_responsibility": "仲裁 critical / action-required / failure-only digest 草案,確認 blast radius、rollback 與 approval gate。", + "blocked_now": [ + "自行批准 Telegram 發送", + "改 route / receiver", + "把成功訊息升級為告警" + ] + }, + { + "agent_id": "nemotron", + "digest_responsibility": "針對 scanner disagreement、AI Agent / model / prompt / tool-call risk 產生離線 replay note。", + "blocked_now": [ + "呼叫付費模型", + "新增 SDK", + "開 shadow / canary" + ] + } + ], + "operation_boundaries": { + "read_only_policy_allowed": true, + "telegram_direct_send_allowed": false, + "telegram_test_message_allowed": false, + "telegram_gateway_queue_write_allowed": false, + "alertmanager_route_change_allowed": false, + "telegram_receiver_change_allowed": false, + "awooop_event_write_allowed": false, + "workflow_trigger_allowed": false, + "external_scan_allowed": false, + "runtime_execution_allowed": false, + "secret_plaintext_allowed": false, + "conversation_transcript_allowed": false + }, + "approval_boundaries": { + "telegram_digest_send_approved": false, + "telegram_gateway_queue_write_approved": false, + "telegram_route_change_approved": false, + "alertmanager_receiver_change_approved": false, + "awooop_event_write_approved": false, + "workflow_trigger_approved": false, + "runtime_execution_approved": false, + "secret_plaintext_approved": false + }, + "rollups": { + "rule_count": 8, + "channel_count": 3, + "trigger_category_count": 5, + "by_decision": { + "suppress_success_noise": 2, + "draft_failure_escalation_digest": 3, + "draft_action_required_digest": 3 + }, + "action_required_rule_ids": [ + "tool_install_or_ci_change_needs_approval", + "scanner_disagreement_needs_review", + "no_executable_repair_candidate_after_approval" + ], + "failure_escalation_rule_ids": [ + "version_watch_source_failed", + "critical_cve_or_eol_requires_owner", + "telegram_delivery_silence_detected" + ], + "suppressed_success_rule_ids": [ + "repo_only_freshness_snapshot_success", + "tool_adoption_package_completed_success" + ], + "telegram_direct_send_allowed_count": 0, + "telegram_gateway_queue_write_allowed_count": 0, + "route_change_allowed_count": 0, + "conversation_transcript_allowed_count": 0, + "next_approval_task_ids": [ + "P2-402E", + "P2-402F", + "P2-402G" + ] + } +} diff --git a/docs/schemas/ai_agent_telegram_action_required_digest_policy_v1.schema.json b/docs/schemas/ai_agent_telegram_action_required_digest_policy_v1.schema.json new file mode 100644 index 00000000..745e9403 --- /dev/null +++ b/docs/schemas/ai_agent_telegram_action_required_digest_policy_v1.schema.json @@ -0,0 +1,503 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://awoooi.wooo.work/schemas/ai_agent_telegram_action_required_digest_policy_v1.schema.json", + "title": "AI Agent Telegram Action Required Digest Policy v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "generated_at", + "program_status", + "source_refs", + "trigger_categories", + "digest_channels", + "digest_rules", + "message_template_contract", + "rate_limit_policy", + "display_redaction_contract", + "agent_roles", + "operation_boundaries", + "approval_boundaries", + "rollups" + ], + "properties": { + "schema_version": { + "const": "ai_agent_telegram_action_required_digest_policy_v1" + }, + "generated_at": { + "type": "string" + }, + "program_status": { + "type": "object", + "additionalProperties": false, + "required": [ + "overall_completion_percent", + "current_priority", + "current_task_id", + "next_task_id", + "read_only_mode", + "runtime_authority", + "status_note" + ], + "properties": { + "overall_completion_percent": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "current_priority": { + "type": "string" + }, + "current_task_id": { + "type": "string" + }, + "next_task_id": { + "type": "string" + }, + "read_only_mode": { + "const": true + }, + "runtime_authority": { + "const": "policy_only_no_telegram_send_or_route_change" + }, + "status_note": { + "type": "string" + } + } + }, + "source_refs": { + "type": "array", + "items": { + "type": "string" + } + }, + "trigger_categories": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "category_id", + "owner_agent", + "policy_intent", + "blocked_now" + ], + "properties": { + "category_id": { + "type": "string" + }, + "owner_agent": { + "type": "string" + }, + "policy_intent": { + "type": "string" + }, + "blocked_now": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "digest_channels": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "channel_id", + "purpose", + "success_immediate_allowed", + "direct_send_allowed", + "queue_write_allowed", + "requires_telegram_gateway_e2e" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "success_immediate_allowed": { + "const": false + }, + "direct_send_allowed": { + "const": false + }, + "queue_write_allowed": { + "const": false + }, + "requires_telegram_gateway_e2e": { + "type": "boolean" + } + } + } + }, + "digest_rules": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "rule_id", + "category_id", + "signal_state", + "severity", + "decision", + "owner_agent", + "required_evidence", + "channels", + "requires_openclaw_review", + "telegram_direct_send_allowed", + "next_action" + ], + "properties": { + "rule_id": { + "type": "string" + }, + "category_id": { + "type": "string" + }, + "signal_state": { + "type": "string" + }, + "severity": { + "type": "string" + }, + "decision": { + "enum": [ + "suppress_success_noise", + "draft_action_required_digest", + "draft_failure_escalation_digest", + "hold_for_owner_packet" + ] + }, + "owner_agent": { + "type": "string" + }, + "required_evidence": { + "type": "array", + "items": { + "type": "string" + } + }, + "channels": { + "type": "array", + "items": { + "type": "string" + } + }, + "requires_openclaw_review": { + "type": "boolean" + }, + "telegram_direct_send_allowed": { + "const": false + }, + "next_action": { + "type": "string" + } + } + } + }, + "message_template_contract": { + "type": "object", + "additionalProperties": false, + "required": [ + "required_fields", + "forbidden_fields", + "success_message_policy", + "action_required_message_policy", + "failure_message_policy" + ], + "properties": { + "required_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "forbidden_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "success_message_policy": { + "type": "string" + }, + "action_required_message_policy": { + "type": "string" + }, + "failure_message_policy": { + "type": "string" + } + } + }, + "rate_limit_policy": { + "type": "object", + "additionalProperties": false, + "required": [ + "success_policy", + "digest_window_minutes", + "dedupe_key_fields", + "max_digest_items", + "fallback_policy" + ], + "properties": { + "success_policy": { + "type": "string" + }, + "digest_window_minutes": { + "type": "integer", + "minimum": 1 + }, + "dedupe_key_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "max_digest_items": { + "type": "integer", + "minimum": 1 + }, + "fallback_policy": { + "type": "string" + } + } + }, + "display_redaction_contract": { + "type": "object", + "additionalProperties": false, + "required": [ + "conversation_transcript_display_allowed", + "redaction_required", + "allowed_frontend_fields", + "forbidden_frontend_content" + ], + "properties": { + "conversation_transcript_display_allowed": { + "const": false + }, + "redaction_required": { + "const": true + }, + "allowed_frontend_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "forbidden_frontend_content": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "agent_roles": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "agent_id", + "digest_responsibility", + "blocked_now" + ], + "properties": { + "agent_id": { + "type": "string" + }, + "digest_responsibility": { + "type": "string" + }, + "blocked_now": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "operation_boundaries": { + "type": "object", + "additionalProperties": false, + "required": [ + "read_only_policy_allowed", + "telegram_direct_send_allowed", + "telegram_test_message_allowed", + "telegram_gateway_queue_write_allowed", + "alertmanager_route_change_allowed", + "telegram_receiver_change_allowed", + "awooop_event_write_allowed", + "workflow_trigger_allowed", + "external_scan_allowed", + "runtime_execution_allowed", + "secret_plaintext_allowed", + "conversation_transcript_allowed" + ], + "properties": { + "read_only_policy_allowed": { + "const": true + }, + "telegram_direct_send_allowed": { + "const": false + }, + "telegram_test_message_allowed": { + "const": false + }, + "telegram_gateway_queue_write_allowed": { + "const": false + }, + "alertmanager_route_change_allowed": { + "const": false + }, + "telegram_receiver_change_allowed": { + "const": false + }, + "awooop_event_write_allowed": { + "const": false + }, + "workflow_trigger_allowed": { + "const": false + }, + "external_scan_allowed": { + "const": false + }, + "runtime_execution_allowed": { + "const": false + }, + "secret_plaintext_allowed": { + "const": false + }, + "conversation_transcript_allowed": { + "const": false + } + } + }, + "approval_boundaries": { + "type": "object", + "additionalProperties": false, + "required": [ + "telegram_digest_send_approved", + "telegram_gateway_queue_write_approved", + "telegram_route_change_approved", + "alertmanager_receiver_change_approved", + "awooop_event_write_approved", + "workflow_trigger_approved", + "runtime_execution_approved", + "secret_plaintext_approved" + ], + "properties": { + "telegram_digest_send_approved": { + "const": false + }, + "telegram_gateway_queue_write_approved": { + "const": false + }, + "telegram_route_change_approved": { + "const": false + }, + "alertmanager_receiver_change_approved": { + "const": false + }, + "awooop_event_write_approved": { + "const": false + }, + "workflow_trigger_approved": { + "const": false + }, + "runtime_execution_approved": { + "const": false + }, + "secret_plaintext_approved": { + "const": false + } + } + }, + "rollups": { + "type": "object", + "additionalProperties": false, + "required": [ + "rule_count", + "channel_count", + "trigger_category_count", + "by_decision", + "action_required_rule_ids", + "failure_escalation_rule_ids", + "suppressed_success_rule_ids", + "telegram_direct_send_allowed_count", + "telegram_gateway_queue_write_allowed_count", + "route_change_allowed_count", + "conversation_transcript_allowed_count", + "next_approval_task_ids" + ], + "properties": { + "rule_count": { + "type": "integer", + "minimum": 0 + }, + "channel_count": { + "type": "integer", + "minimum": 0 + }, + "trigger_category_count": { + "type": "integer", + "minimum": 0 + }, + "by_decision": { + "type": "object", + "additionalProperties": { + "type": "integer", + "minimum": 0 + } + }, + "action_required_rule_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "failure_escalation_rule_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "suppressed_success_rule_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "telegram_direct_send_allowed_count": { + "const": 0 + }, + "telegram_gateway_queue_write_allowed_count": { + "const": 0 + }, + "route_change_allowed_count": { + "const": 0 + }, + "conversation_transcript_allowed_count": { + "const": 0 + }, + "next_approval_task_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } +} diff --git a/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md b/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md index 1c8d98f6..81e9b8eb 100644 --- a/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md +++ b/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md @@ -670,7 +670,7 @@ Repo / registry / release notes / K8s / host / observability / backup evidence | 檔案 / API | 用途 | |---|---| | `docs/schemas/ai_agent_proactive_operations_contract_v1.schema.json` | 主動營運委派、版本生命週期、MCP、RAG、Telegram policy、approval boundary 契約 | -| `docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json` | 12 類版本 domain、24 類可委派能力、5 種 cadence、8 類 MCP、4 類 RAG memory;完成度 `55%` | +| `docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json` | 12 類版本 domain、24 類可委派能力、5 種 cadence、8 類 MCP、4 類 RAG memory;完成度 `68%` | | `apps/api/src/services/ai_agent_proactive_operations_contract.py` | 只讀 loader;強制 runtime update / package upgrade / host upgrade / workflow schedule / auto merge / Telegram direct send 全部 false | | `GET /api/v1/agents/agent-proactive-operations-contract` | 治理 API;只回傳 committed snapshot,不啟用排程、不升級、不呼叫付費服務 | | `docs/schemas/ai_agent_version_freshness_snapshot_v1.schema.json` | P2-402B repo-only 版本新鮮度 schema;鎖定 schedule / external lookup / upgrade / Telegram / PR / host probe 全部 false | @@ -679,13 +679,16 @@ Repo / registry / release notes / K8s / host / observability / backup evidence | `docs/schemas/ai_agent_tool_adoption_approval_package_v1.schema.json` | P2-402C 工具採用批准包 schema;鎖定 tool install / CI change / external lookup / vulnerability DB / PR / Telegram 全部 false | | `docs/evaluations/ai_agent_tool_adoption_approval_package_2026-06-11.json` | P2-402C committed snapshot:Renovate / OSV-Scanner / Trivy / Syft / Grype 官方來源、採用 lane、批准欄位與 rollback gate | | `GET /api/v1/agents/agent-tool-adoption-approval-package` | 只讀 API;只回傳工具採用批准包,不安裝工具、不改 CI、不查外部、不建 PR、不發 Telegram | +| `docs/schemas/ai_agent_telegram_action_required_digest_policy_v1.schema.json` | P2-402D Telegram action-required digest policy schema;鎖定 direct send / Gateway queue write / route change / receiver change / workflow / transcript 全部 false | +| `docs/evaluations/ai_agent_telegram_action_required_digest_policy_2026-06-11.json` | P2-402D committed snapshot:8 條 digest rule、3 個 channel 草案、5 類 trigger category、成功降噪、failure-only 與 redaction policy | +| `GET /api/v1/agents/agent-telegram-action-required-digest-policy` | 只讀 API;只回傳 digest policy,不送 Telegram、不寫 queue、不改 route / receiver、不觸發 workflow | **採用順序:** 1. 先做 repo-only daily freshness:manifest / lockfile / Dockerfile / K8s YAML / runbook / snapshot。✅ P2-402B 已建立 committed snapshot/API;每日排程仍未授權。 2. 再評估 external primary source weekly watch:Renovate、OSV-Scanner、Trivy、Syft、Grype、Kubernetes skew policy、Docker Scout。✅ P2-402C 已建立工具採用批准包;工具安裝、CI 變更、外部查詢仍未授權。 -3. 建立 Telegram action-required digest policy,先定義 critical / action-required / failure-only 通知門檻,禁止成功洗版。下一步 P2-402D。 -4. 再進 Gitea PR 草案 lane:grouping、automerge=false、tests、rollback、owner response。 +3. 建立 Telegram action-required digest policy,先定義 critical / action-required / failure-only 通知門檻,禁止成功洗版。✅ P2-402D 已建立 policy snapshot/API;Telegram 實發與 queue write 仍未授權。 +4. 再進 Gitea PR 草案 lane:grouping、automerge=false、tests、rollback、owner response。下一步 P2-402E。 5. 最後才進人工批准後的 dry-run / smoke / canary / production rollout。 #### 3.2.2 核心缺口與災難場景 @@ -1323,6 +1326,7 @@ Repo / registry / release notes / K8s / host / observability / backup evidence | 主動營運委派與版本生命週期契約 | `docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json` + `GET /api/v1/agents/agent-proactive-operations-contract` | 先固定 12 類版本 domain、24 類可委派能力、MCP/RAG/Telegram 邊界;不啟用排程、不自動升版 | L4×D2 / L7×D4 / L6×D6 | | Repo-only 版本新鮮度快照 | `docs/evaluations/ai_agent_version_freshness_snapshot_2026-06-11.json` + `GET /api/v1/agents/agent-version-freshness-snapshot` | P2-402B:讓 Hermes 可每日產生 repo-only freshness evidence packet;目前只讀 API,不啟用 schedule、不查外部、不升級 | L4×D2 / L7×D4 / L6×D6 | | 工具採用批准包 | `docs/evaluations/ai_agent_tool_adoption_approval_package_2026-06-11.json` + `GET /api/v1/agents/agent-tool-adoption-approval-package` | P2-402C:讓 OpenClaw / Hermes / NemoTron 用官方來源評估 Renovate / OSV-Scanner / Trivy / Syft / Grype;目前只讀 API,不安裝工具、不改 CI、不查外部、不建 PR、不發 Telegram | L4×D2 / L7×D4 / L6×D6 | +| Telegram action-required digest policy | `docs/evaluations/ai_agent_telegram_action_required_digest_policy_2026-06-11.json` + `GET /api/v1/agents/agent-telegram-action-required-digest-policy` | P2-402D:讓 OpenClaw / Hermes / NemoTron 先定義 critical / action-required / failure-only digest 與成功降噪;目前只讀 API,不送 Telegram、不寫 queue、不改 route / receiver | L4×D2 / L7×D4 / L6×D6 | **退出條件(量化)** @@ -1706,6 +1710,12 @@ Phase 6 完成後 - 更新 `ai_agent_proactive_operations_contract_2026-06-11.json`:整體完成度 `55%`,current task `P2-402C`,next task `P2-402D`。 - 本波仍不安裝工具、不改 CI、不查外部 registry、不下載 vulnerability DB、不升級套件、不寫 lockfile、不 build/pull image、不建立 Gitea PR、不 auto merge、不發 Telegram;P2-402D 才建立 Telegram action-required digest policy。 +### 2026-06-11 23:58 (台北) — §3.2 / §5 — 完成 P2-402D Telegram action-required digest policy — 回應統帥要求告警搭配 Telegram Bot 但先禁止直發與成功洗版 + +- 新增 `ai_agent_telegram_action_required_digest_policy_v1` schema / committed snapshot / loader / API / 測試,定義 8 條 digest rule、3 個 channel 草案、5 類 trigger category、message redaction、dedupe / rate-limit 與 fallback policy。 +- 更新 `ai_agent_proactive_operations_contract_2026-06-11.json`:整體完成度 `68%`,current task `P2-402D`,next task `P2-402E`。 +- 本波仍不送 Telegram、不寫 Telegram Gateway queue、不改 Alertmanager route / receiver、不寫 AwoooP event、不觸發 workflow、不查外部掃描、不執行 runtime、不讀取或輸出 secret、不回傳工作視窗對話內容;P2-402E 才設計 Gitea PR 草案 lane。 + ### 2026-04-15 (台北) — 全檔 — 建立 v2 骨架,§0/§1 完成 — 統帥批准「單 MASTER + 4 道閘門」結構 - 從 v1(plans/2026-04-15-MASTER-ai-autonomous-flywheel.md)繼承核心發現