From b36f4b97fed848dc9e50ca2e1441c5a93178df4c Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 18 Jun 2026 19:00:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai):=20=E6=96=B0=E5=A2=9E=20P2-408=20?= =?UTF-8?q?=E4=B8=AD=E4=BD=8E=E9=A2=A8=E9=9A=AA=E7=99=BD=E5=90=8D=E5=96=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/api/v1/agents.py | 33 ++ .../ai_agent_low_medium_risk_whitelist.py | 427 ++++++++++++++ ...test_ai_agent_low_medium_risk_whitelist.py | 142 +++++ ..._ai_agent_low_medium_risk_whitelist_api.py | 58 ++ apps/web/messages/en.json | 49 ++ apps/web/messages/zh-TW.json | 49 ++ .../tabs/automation-inventory-tab.tsx | 197 ++++++- apps/web/src/lib/api-client.ts | 205 +++++++ docs/LOGBOOK.md | 25 + ...AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md | 9 +- ..._low_medium_risk_whitelist_2026-06-18.json | 527 ++++++++++++++++++ ...t_low_medium_risk_whitelist_v1.schema.json | 228 ++++++++ ...-04-15-MASTER-ai-autonomous-flywheel-v2.md | 15 + 13 files changed, 1959 insertions(+), 5 deletions(-) create mode 100644 apps/api/src/services/ai_agent_low_medium_risk_whitelist.py create mode 100644 apps/api/tests/test_ai_agent_low_medium_risk_whitelist.py create mode 100644 apps/api/tests/test_ai_agent_low_medium_risk_whitelist_api.py create mode 100644 docs/evaluations/ai_agent_low_medium_risk_whitelist_2026-06-18.json create mode 100644 docs/schemas/ai_agent_low_medium_risk_whitelist_v1.schema.json diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index 106ce34b3..4a5a259a6 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -94,6 +94,9 @@ from src.services.ai_agent_receipt_readback_owner_review import ( from src.services.ai_agent_report_no_write_analysis_runtime import ( load_latest_ai_agent_report_no_write_analysis_runtime, ) +from src.services.ai_agent_low_medium_risk_whitelist import ( + load_latest_ai_agent_low_medium_risk_whitelist, +) from src.services.host_runaway_aiops_loop_readiness import ( load_latest_host_runaway_aiops_loop_readiness, ) @@ -866,6 +869,36 @@ async def get_agent_report_no_write_analysis_runtime() -> dict[str, Any]: ) from exc +@router.get( + "/agent-low-medium-risk-whitelist", + response_model=dict[str, Any], + summary="取得 P2-408 AI Agent 中低風險自動處理白名單", + description=( + "讀取最新已提交的 P2-408 AI Agent 中 / 低風險候選白名單快照;此端點只呈現 " + "low / medium action policy、dry-run verifier、rollback proof、audit reason 與高風險分流。" + "它不啟動 auto worker、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、" + "不寫 receipt production target、不寫 production、不讀 secret、不呼叫付費 API、" + "不改主機、不執行 kubectl 或不可逆操作。" + ), +) +async def get_agent_low_medium_risk_whitelist() -> dict[str, Any]: + """回傳最新 P2-408 low / medium risk whitelist 只讀快照。""" + try: + payload = await asyncio.to_thread(load_latest_ai_agent_low_medium_risk_whitelist) + return redact_public_lan_topology(payload) + 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_low_medium_risk_whitelist_invalid", error=str(exc)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="P2-408 AI Agent 中低風險自動處理白名單快照無效", + ) from exc + + @router.get( "/agent-host-runaway-aiops-loop-readiness", response_model=dict[str, Any], diff --git a/apps/api/src/services/ai_agent_low_medium_risk_whitelist.py b/apps/api/src/services/ai_agent_low_medium_risk_whitelist.py new file mode 100644 index 000000000..8cbe0277b --- /dev/null +++ b/apps/api/src/services/ai_agent_low_medium_risk_whitelist.py @@ -0,0 +1,427 @@ +""" +P2-408 AI Agent low / medium risk whitelist snapshot. + +Loads the latest committed whitelist candidate snapshot that turns P2-407 +no-write report analysis into reviewable low / medium risk candidates. This +module intentionally does not run an auto worker, send Telegram, write a +Gateway queue, write delivery receipts, read secrets, call paid APIs, mutate +hosts, run kubectl, or write production state. +""" + +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_low_medium_risk_whitelist_*.json" +_SCHEMA_VERSION = "ai_agent_low_medium_risk_whitelist_v1" +_RUNTIME_AUTHORITY = "low_medium_risk_whitelist_no_live_execution_committed_snapshot" +_EXPECTED_CURRENT_TASK = "P2-408" +_EXPECTED_NEXT_TASK = "P2-409" +_EXPECTED_CANONICAL_ROOM = "AwoooI SRE 戰情室" +_EXPECTED_CANONICAL_ROOM_ENV = "SRE_GROUP_CHAT_ID" +_EXPECTED_SOURCE_SCHEMAS = { + "ai_agent_report_no_write_analysis_runtime_v1", + "ai_agent_operation_permission_model_v1", + "ai_agent_candidate_operation_dry_run_evidence_v1", + "ai_agent_report_automation_review_v1", + "dependency_supply_chain_drift_monitor_v1", +} +_TRUE_TRUTH_FLAGS = { + "p2_407_no_write_analysis_loaded", + "operation_permission_model_loaded", + "candidate_dry_run_evidence_loaded", + "report_policy_review_loaded", + "dependency_drift_loaded", + "low_risk_candidates_ready", + "medium_risk_candidates_ready", + "dry_run_verifier_required", + "rollback_proof_required", + "audit_reason_required", + "high_risk_redirect_ready", +} +_FALSE_TRUTH_FLAGS = { + "auto_worker_enabled", + "low_risk_live_execution_enabled", + "medium_risk_live_execution_enabled", + "gateway_queue_write_enabled", + "telegram_send_enabled", + "bot_api_call_enabled", + "receipt_production_write_enabled", + "production_write_enabled", + "secret_read_enabled", + "paid_api_call_enabled", + "host_write_enabled", + "kubectl_action_enabled", + "destructive_operation_enabled", + "openclaw_replacement_allowed", +} +_ZERO_TRUTH_COUNTS = { + "auto_worker_run_count_24h", + "low_risk_execution_count_24h", + "medium_risk_execution_count_24h", + "gateway_queue_write_count_24h", + "telegram_send_count_24h", + "bot_api_call_count_24h", + "receipt_production_write_count_24h", + "production_write_count_24h", + "secret_read_count_24h", + "paid_api_call_count_24h", + "host_write_count_24h", + "kubectl_action_count_24h", + "destructive_operation_count_24h", +} +_TRUE_BOUNDARY_FLAGS = { + "read_only_whitelist_allowed", + "dry_run_verifier_preview_allowed", + "rollback_proof_preview_allowed", + "audit_reason_template_allowed", +} +_FALSE_BOUNDARY_FLAGS = { + "auto_worker_enabled", + "low_risk_live_execution_enabled", + "medium_risk_live_execution_enabled", + "gateway_queue_write_enabled", + "telegram_send_enabled", + "bot_api_call_enabled", + "receipt_production_write_enabled", + "production_write_enabled", + "secret_read_enabled", + "paid_api_call_enabled", + "host_write_enabled", + "kubectl_action_enabled", + "destructive_operation_enabled", + "openclaw_replacement_allowed", +} +_ZERO_ROLLUP_FIELDS = { + "auto_worker_run_count", + "low_risk_execution_count", + "medium_risk_execution_count", + "gateway_queue_write_count", + "telegram_send_count", + "bot_api_call_count", + "receipt_production_write_count", + "production_write_count", + "secret_read_count", + "paid_api_call_count", + "host_write_count", + "kubectl_action_count", + "destructive_operation_count", +} +_FORBIDDEN_PUBLIC_TERMS = { + "批准!繼續", + "In app browser", + "My request for Codex", + "chain_of_thought", + "chain-of-thought", + "private reasoning text", + "authorization_header", + "authorization header value", + "telegram token value", + "raw prompt", + "raw_payload", +} + + +def load_latest_ai_agent_low_medium_risk_whitelist( + evaluations_dir: Path | None = None, +) -> dict[str, Any]: + """Load the newest committed P2-408 low / medium risk whitelist snapshot.""" + directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR + candidates = sorted(directory.glob(_SNAPSHOT_PATTERN)) + if not candidates: + raise FileNotFoundError(f"no AI Agent low / medium risk whitelist 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") + + label = str(latest) + _require_schema(payload, label) + _require_sources(payload, label) + _require_whitelist_truth(payload, label) + _require_candidates(payload, label) + _require_verifiers(payload, label) + _require_rollback_proofs(payload, label) + _require_audit_templates(payload, label) + _require_high_risk_redirects(payload, label) + _require_owner_gates(payload, label) + _require_boundaries(payload, label) + _require_rollups(payload, label) + _require_no_forbidden_public_terms(payload, label) + return payload + + +def _require_schema(payload: dict[str, Any], label: str) -> None: + if payload.get("schema_version") != _SCHEMA_VERSION: + raise ValueError(f"{label}: expected schema_version={_SCHEMA_VERSION}") + status = payload.get("program_status") or {} + expected = { + "overall_completion_percent": 100, + "current_priority": "P2", + "current_task_id": _EXPECTED_CURRENT_TASK, + "next_task_id": _EXPECTED_NEXT_TASK, + "read_only_mode": True, + "runtime_authority": _RUNTIME_AUTHORITY, + } + mismatches = _mismatches(status, expected) + if mismatches: + raise ValueError(f"{label}: program_status mismatch: {mismatches}") + if not status.get("status_note"): + raise ValueError(f"{label}: program_status.status_note is required") + + +def _require_sources(payload: dict[str, Any], label: str) -> None: + if not payload.get("source_refs"): + raise ValueError(f"{label}: source_refs must not be empty") + sources = payload.get("source_readbacks") or [] + schemas = {item.get("source_schema_version") for item in sources} + missing = sorted(_EXPECTED_SOURCE_SCHEMAS - schemas) + if missing: + raise ValueError(f"{label}: missing source schemas: {missing}") + for item in sources: + readback_id = item.get("readback_id") or "" + for field in ("source_ref", "endpoint", "owner_agent", "status", "key_readback", "next_action"): + if not item.get(field): + raise ValueError(f"{label}: source readback {readback_id} missing {field}") + + +def _require_whitelist_truth(payload: dict[str, Any], label: str) -> None: + truth = payload.get("whitelist_truth") or {} + missing_true = sorted(flag for flag in _TRUE_TRUTH_FLAGS if truth.get(flag) is not True) + if missing_true: + raise ValueError(f"{label}: whitelist truth flags must remain true: {missing_true}") + unsafe_false = sorted(flag for flag in _FALSE_TRUTH_FLAGS if truth.get(flag) is not False) + if unsafe_false: + raise ValueError(f"{label}: whitelist truth flags must remain false: {unsafe_false}") + non_zero = sorted(field for field in _ZERO_TRUTH_COUNTS if truth.get(field) != 0) + if non_zero: + raise ValueError(f"{label}: whitelist live counts must remain zero: {non_zero}") + if not truth.get("truth_note"): + raise ValueError(f"{label}: whitelist_truth.truth_note is required") + + +def _require_candidates(payload: dict[str, Any], label: str) -> None: + candidates = payload.get("whitelist_candidates") or [] + if len(candidates) < 1: + raise ValueError(f"{label}: whitelist_candidates must not be empty") + risk_tiers = {item.get("risk_tier") for item in candidates} + if not {"low", "medium"}.issubset(risk_tiers): + raise ValueError(f"{label}: whitelist_candidates must include low and medium candidates") + for item in candidates: + candidate_id = item.get("candidate_id") or "" + if item.get("risk_tier") not in {"low", "medium"}: + raise ValueError(f"{label}: whitelist candidate {candidate_id} must be low or medium") + if item.get("owner_approval_required_for_live_execution") is not True: + raise ValueError(f"{label}: whitelist candidate {candidate_id} must require owner approval before live execution") + for flag in ("live_execution_allowed", "production_write_allowed"): + if item.get(flag) is not False: + raise ValueError(f"{label}: whitelist candidate {candidate_id}.{flag} must remain false") + if item.get("side_effect_count") != 0: + raise ValueError(f"{label}: whitelist candidate {candidate_id} side_effect_count must remain zero") + for field in ( + "allowed_no_write_outputs", + "required_evidence", + "dry_run_verifier_id", + "rollback_proof_id", + "audit_reason_template_id", + "blocked_runtime_actions", + "next_gate", + ): + if not item.get(field): + raise ValueError(f"{label}: whitelist candidate {candidate_id} missing {field}") + + +def _require_verifiers(payload: dict[str, Any], label: str) -> None: + verifiers = payload.get("dry_run_verifiers") or [] + verifier_ids = {item.get("verifier_id") for item in verifiers} + referenced_ids = {item.get("dry_run_verifier_id") for item in payload.get("whitelist_candidates") or []} + missing = sorted(referenced_ids - verifier_ids) + if missing: + raise ValueError(f"{label}: missing dry-run verifiers referenced by candidates: {missing}") + for item in verifiers: + verifier_id = item.get("verifier_id") or "" + for flag in ("live_readback_allowed", "production_write_allowed"): + if item.get(flag) is not False: + raise ValueError(f"{label}: dry-run verifier {verifier_id}.{flag} must remain false") + if not item.get("required_inputs") or not item.get("pass_condition"): + raise ValueError(f"{label}: dry-run verifier {verifier_id} missing inputs or pass condition") + + +def _require_rollback_proofs(payload: dict[str, Any], label: str) -> None: + proofs = payload.get("rollback_proofs") or [] + proof_ids = {item.get("rollback_proof_id") for item in proofs} + referenced_ids = {item.get("rollback_proof_id") for item in payload.get("whitelist_candidates") or []} + missing = sorted(referenced_ids - proof_ids) + if missing: + raise ValueError(f"{label}: missing rollback proofs referenced by candidates: {missing}") + for item in proofs: + proof_id = item.get("rollback_proof_id") or "" + if item.get("rollback_command_allowed") is not False: + raise ValueError(f"{label}: rollback proof {proof_id}.rollback_command_allowed must remain false") + if item.get("required_before_live_execution") is not True: + raise ValueError(f"{label}: rollback proof {proof_id} must be required before live execution") + if not item.get("rollback_scope"): + raise ValueError(f"{label}: rollback proof {proof_id} missing rollback_scope") + + +def _require_audit_templates(payload: dict[str, Any], label: str) -> None: + templates = payload.get("audit_reason_templates") or [] + template_ids = {item.get("template_id") for item in templates} + referenced_ids = {item.get("audit_reason_template_id") for item in payload.get("whitelist_candidates") or []} + missing = sorted(referenced_ids - template_ids) + if missing: + raise ValueError(f"{label}: missing audit reason templates referenced by candidates: {missing}") + for item in templates: + template_id = item.get("template_id") or "" + if item.get("risk_tier") not in {"low", "medium"}: + raise ValueError(f"{label}: audit template {template_id} must be low or medium") + if item.get("sensitive_payload_allowed") is not False: + raise ValueError(f"{label}: audit template {template_id}.sensitive_payload_allowed must remain false") + if not item.get("required_fields") or not item.get("example_reason"): + raise ValueError(f"{label}: audit template {template_id} missing required fields or example reason") + + +def _require_high_risk_redirects(payload: dict[str, Any], label: str) -> None: + redirects = payload.get("high_risk_redirects") or [] + if len(redirects) < 1: + raise ValueError(f"{label}: high_risk_redirects must not be empty") + for item in redirects: + redirect_id = item.get("redirect_id") or "" + if item.get("risk_tier") not in {"high", "critical"}: + raise ValueError(f"{label}: redirect {redirect_id} must be high or critical") + if item.get("redirect_to") != "P2-409 Owner Review Queue": + raise ValueError(f"{label}: redirect {redirect_id} must point to P2-409 Owner Review Queue") + if not item.get("blocked_runtime_actions") or not item.get("reason"): + raise ValueError(f"{label}: redirect {redirect_id} missing blocked actions or reason") + + +def _require_owner_gates(payload: dict[str, Any], label: str) -> None: + gates = payload.get("owner_review_gates") or [] + if len(gates) < 1: + raise ValueError(f"{label}: owner_review_gates must not be empty") + for gate in gates: + gate_id = gate.get("gate_id") or "" + if gate.get("status") not in {"owner_review_required", "blocked_by_runtime_gate", "draft_ready"}: + raise ValueError(f"{label}: owner gate {gate_id} status is invalid") + for field in ("required_fields", "acceptance_checks", "blocked_runtime_actions"): + if not gate.get(field): + raise ValueError(f"{label}: owner gate {gate_id} missing {field}") + + +def _require_boundaries(payload: dict[str, Any], label: str) -> None: + boundaries = payload.get("activation_boundaries") or {} + missing_true = sorted(flag for flag in _TRUE_BOUNDARY_FLAGS if boundaries.get(flag) is not True) + if missing_true: + raise ValueError(f"{label}: activation boundaries must remain true: {missing_true}") + unsafe_false = sorted(flag for flag in _FALSE_BOUNDARY_FLAGS if boundaries.get(flag) is not False) + if unsafe_false: + raise ValueError(f"{label}: activation boundaries must remain false: {unsafe_false}") + + telegram = payload.get("telegram_policy") or {} + expected_telegram = { + "canonical_room": _EXPECTED_CANONICAL_ROOM, + "canonical_room_env": _EXPECTED_CANONICAL_ROOM_ENV, + "gateway_queue_write_allowed": False, + "direct_bot_api_allowed": False, + "telegram_send_allowed": False, + "receipt_write_allowed": False, + } + mismatches = _mismatches(telegram, expected_telegram) + if mismatches: + raise ValueError(f"{label}: telegram_policy mismatch: {mismatches}") + + redaction = payload.get("display_redaction_contract") or {} + if redaction.get("redaction_required") is not True: + raise ValueError(f"{label}: display redaction must remain required") + for flag in ( + "unsafe_payload_display_allowed", + "private_reasoning_display_allowed", + "secret_value_display_allowed", + "work_window_transcript_display_allowed", + ): + if redaction.get(flag) is not False: + raise ValueError(f"{label}: display redaction flag {flag} must remain false") + + +def _require_rollups(payload: dict[str, Any], label: str) -> None: + rollups = payload.get("rollups") or {} + candidates = payload.get("whitelist_candidates") or [] + verifiers = payload.get("dry_run_verifiers") or [] + rollback_proofs = payload.get("rollback_proofs") or [] + audit_templates = payload.get("audit_reason_templates") or [] + redirects = payload.get("high_risk_redirects") or [] + gates = payload.get("owner_review_gates") or [] + sources = payload.get("source_readbacks") or [] + blocked_actions = { + *( + action + for candidate in candidates + for action in (candidate.get("blocked_runtime_actions") or []) + ), + *( + action + for redirect in redirects + for action in (redirect.get("blocked_runtime_actions") or []) + ), + *( + action + for gate in gates + for action in (gate.get("blocked_runtime_actions") or []) + ), + } + blocked_actions.discard(None) + expected = { + "source_readback_count": len(sources), + "whitelist_candidate_count": len(candidates), + "low_risk_candidate_count": sum(1 for item in candidates if item.get("risk_tier") == "low"), + "medium_risk_candidate_count": sum(1 for item in candidates if item.get("risk_tier") == "medium"), + "candidate_only_count": len(candidates), + "dry_run_verifier_count": len(verifiers), + "rollback_proof_count": len(rollback_proofs), + "audit_reason_template_count": len(audit_templates), + "high_risk_redirect_count": len(redirects), + "owner_review_gate_count": len(gates), + "live_execution_approval_required_count": sum( + 1 for item in candidates if item.get("owner_approval_required_for_live_execution") is True + ), + "blocked_runtime_action_count": len(blocked_actions), + } + mismatches = { + key: {"expected": value, "actual": rollups.get(key)} + for key, value in expected.items() + if rollups.get(key) != value + } + if mismatches: + raise ValueError(f"{label}: rollup counts must match payload sections: {mismatches}") + + non_zero = sorted(field for field in _ZERO_ROLLUP_FIELDS if rollups.get(field) != 0) + if non_zero: + raise ValueError(f"{label}: live rollup counts must remain zero: {non_zero}") + + +def _require_no_forbidden_public_terms(payload: dict[str, Any], label: str) -> None: + public_text = json.dumps(payload, ensure_ascii=False) + lower_public_text = public_text.lower() + leaked_terms = sorted( + term + for term in _FORBIDDEN_PUBLIC_TERMS + if (term.lower() if term.isascii() else term) in lower_public_text + ) + if leaked_terms: + raise ValueError(f"{label}: forbidden public terms present: {leaked_terms}") + + +def _mismatches(actual: dict[str, Any], expected: dict[str, Any]) -> dict[str, dict[str, Any]]: + return { + key: {"expected": expected_value, "actual": actual.get(key)} + for key, expected_value in expected.items() + if actual.get(key) != expected_value + } diff --git a/apps/api/tests/test_ai_agent_low_medium_risk_whitelist.py b/apps/api/tests/test_ai_agent_low_medium_risk_whitelist.py new file mode 100644 index 000000000..29c6a5e83 --- /dev/null +++ b/apps/api/tests/test_ai_agent_low_medium_risk_whitelist.py @@ -0,0 +1,142 @@ +from __future__ import annotations + +import copy +import json +from pathlib import Path + +import pytest + +from src.services.ai_agent_low_medium_risk_whitelist import ( + load_latest_ai_agent_low_medium_risk_whitelist, +) + +_REPO_ROOT = Path(__file__).resolve().parents[3] +_COMMITTED_SNAPSHOT = ( + _REPO_ROOT + / "docs" + / "evaluations" + / "ai_agent_low_medium_risk_whitelist_2026-06-18.json" +) + + +def test_load_latest_ai_agent_low_medium_risk_whitelist_reads_newest_file(tmp_path): + older = _snapshot(generated_at="2026-06-17T00:00:00+08:00") + newer = _snapshot(generated_at="2026-06-18T18:42:00+08:00") + (tmp_path / "ai_agent_low_medium_risk_whitelist_2026-06-17.json").write_text( + json.dumps(older), + encoding="utf-8", + ) + (tmp_path / "ai_agent_low_medium_risk_whitelist_2026-06-18.json").write_text( + json.dumps(newer), + encoding="utf-8", + ) + + loaded = load_latest_ai_agent_low_medium_risk_whitelist(tmp_path) + + assert loaded["generated_at"] == "2026-06-18T18:42:00+08:00" + assert loaded["program_status"]["current_task_id"] == "P2-408" + assert loaded["program_status"]["next_task_id"] == "P2-409" + assert loaded["program_status"]["read_only_mode"] is True + assert loaded["rollups"]["low_risk_candidate_count"] == 3 + assert loaded["rollups"]["medium_risk_candidate_count"] == 3 + assert loaded["rollups"]["auto_worker_run_count"] == 0 + + +def test_ai_agent_low_medium_risk_whitelist_requires_read_only_mode(tmp_path): + snapshot = _snapshot() + snapshot["program_status"]["read_only_mode"] = False + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="program_status"): + load_latest_ai_agent_low_medium_risk_whitelist(tmp_path) + + +def test_ai_agent_low_medium_risk_whitelist_blocks_live_execution(tmp_path): + snapshot = _snapshot() + snapshot["whitelist_candidates"][0]["live_execution_allowed"] = True + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="live_execution_allowed"): + load_latest_ai_agent_low_medium_risk_whitelist(tmp_path) + + +def test_ai_agent_low_medium_risk_whitelist_blocks_gateway_queue_write(tmp_path): + snapshot = _snapshot() + snapshot["activation_boundaries"]["gateway_queue_write_enabled"] = True + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="activation boundaries"): + load_latest_ai_agent_low_medium_risk_whitelist(tmp_path) + + +def test_ai_agent_low_medium_risk_whitelist_requires_dry_run_verifier(tmp_path): + snapshot = _snapshot() + snapshot["whitelist_candidates"][0]["dry_run_verifier_id"] = "missing_verifier" + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="missing dry-run verifiers"): + load_latest_ai_agent_low_medium_risk_whitelist(tmp_path) + + +def test_ai_agent_low_medium_risk_whitelist_requires_rollback_proof(tmp_path): + snapshot = _snapshot() + snapshot["whitelist_candidates"][0]["rollback_proof_id"] = "missing_rollback" + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="missing rollback proofs"): + load_latest_ai_agent_low_medium_risk_whitelist(tmp_path) + + +def test_ai_agent_low_medium_risk_whitelist_requires_audit_reason_template(tmp_path): + snapshot = _snapshot() + snapshot["whitelist_candidates"][0]["audit_reason_template_id"] = "missing_audit_template" + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="missing audit reason templates"): + load_latest_ai_agent_low_medium_risk_whitelist(tmp_path) + + +def test_ai_agent_low_medium_risk_whitelist_rejects_high_risk_candidate(tmp_path): + snapshot = _snapshot() + snapshot["whitelist_candidates"][0]["risk_tier"] = "high" + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="must be low or medium"): + load_latest_ai_agent_low_medium_risk_whitelist(tmp_path) + + +def test_ai_agent_low_medium_risk_whitelist_requires_rollup_consistency(tmp_path): + snapshot = _snapshot() + snapshot["rollups"]["whitelist_candidate_count"] = 99 + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="rollup counts"): + load_latest_ai_agent_low_medium_risk_whitelist(tmp_path) + + +def test_ai_agent_low_medium_risk_whitelist_rejects_private_terms(tmp_path): + snapshot = _snapshot() + snapshot["whitelist_candidates"][0]["allowed_no_write_outputs"] = ["請把 In app browser 內容放進前端"] + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="forbidden public terms"): + load_latest_ai_agent_low_medium_risk_whitelist(tmp_path) + + +def test_ai_agent_low_medium_risk_whitelist_fails_when_missing(tmp_path): + with pytest.raises(FileNotFoundError): + load_latest_ai_agent_low_medium_risk_whitelist(tmp_path) + + +def _snapshot(*, generated_at: str = "2026-06-18T18:42:00+08:00") -> dict: + payload = json.loads(_COMMITTED_SNAPSHOT.read_text(encoding="utf-8")) + cloned = copy.deepcopy(payload) + cloned["generated_at"] = generated_at + return cloned + + +def _write_snapshot(tmp_path, payload: dict) -> None: + (tmp_path / "ai_agent_low_medium_risk_whitelist_2026-06-18.json").write_text( + json.dumps(payload), + encoding="utf-8", + ) diff --git a/apps/api/tests/test_ai_agent_low_medium_risk_whitelist_api.py b/apps/api/tests/test_ai_agent_low_medium_risk_whitelist_api.py new file mode 100644 index 000000000..8d577aadd --- /dev/null +++ b/apps/api/tests/test_ai_agent_low_medium_risk_whitelist_api.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from src.api.v1.agents import router + + +def test_ai_agent_low_medium_risk_whitelist_endpoint_returns_committed_snapshot(): + app = FastAPI() + app.include_router(router, prefix="/api/v1") + client = TestClient(app) + + response = client.get("/api/v1/agents/agent-low-medium-risk-whitelist") + + assert response.status_code == 200 + data = response.json() + assert data["schema_version"] == "ai_agent_low_medium_risk_whitelist_v1" + assert data["program_status"]["current_task_id"] == "P2-408" + assert data["program_status"]["next_task_id"] == "P2-409" + assert data["program_status"]["read_only_mode"] is True + assert ( + data["program_status"]["runtime_authority"] + == "low_medium_risk_whitelist_no_live_execution_committed_snapshot" + ) + assert data["rollups"]["source_readback_count"] == len(data["source_readbacks"]) == 5 + assert data["rollups"]["whitelist_candidate_count"] == len(data["whitelist_candidates"]) == 6 + assert data["rollups"]["low_risk_candidate_count"] == 3 + assert data["rollups"]["medium_risk_candidate_count"] == 3 + assert data["rollups"]["dry_run_verifier_count"] == len(data["dry_run_verifiers"]) == 5 + assert data["rollups"]["rollback_proof_count"] == len(data["rollback_proofs"]) == 5 + assert data["rollups"]["audit_reason_template_count"] == len(data["audit_reason_templates"]) == 6 + assert data["rollups"]["high_risk_redirect_count"] == len(data["high_risk_redirects"]) == 3 + assert data["rollups"]["owner_review_gate_count"] == len(data["owner_review_gates"]) == 3 + assert data["rollups"]["live_execution_approval_required_count"] == 6 + assert data["rollups"]["blocked_runtime_action_count"] == 27 + assert data["rollups"]["auto_worker_run_count"] == 0 + assert data["rollups"]["low_risk_execution_count"] == 0 + assert data["rollups"]["medium_risk_execution_count"] == 0 + assert data["rollups"]["gateway_queue_write_count"] == 0 + assert data["rollups"]["telegram_send_count"] == 0 + assert data["rollups"]["bot_api_call_count"] == 0 + assert data["rollups"]["receipt_production_write_count"] == 0 + assert data["rollups"]["production_write_count"] == 0 + assert data["rollups"]["secret_read_count"] == 0 + assert data["rollups"]["paid_api_call_count"] == 0 + assert data["rollups"]["host_write_count"] == 0 + assert data["rollups"]["kubectl_action_count"] == 0 + assert data["rollups"]["destructive_operation_count"] == 0 + assert data["telegram_policy"]["canonical_room"] == "AwoooI SRE 戰情室" + assert data["telegram_policy"]["canonical_room_env"] == "SRE_GROUP_CHAT_ID" + assert data["telegram_policy"]["telegram_send_allowed"] is False + assert data["telegram_policy"]["gateway_queue_write_allowed"] is False + assert data["telegram_policy"]["direct_bot_api_allowed"] is False + assert data["activation_boundaries"]["auto_worker_enabled"] is False + assert data["activation_boundaries"]["low_risk_live_execution_enabled"] is False + assert data["activation_boundaries"]["medium_risk_live_execution_enabled"] is False + assert data["activation_boundaries"]["openclaw_replacement_allowed"] is False diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 1984781d7..4801fd06c 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -3878,6 +3878,55 @@ "critical": "critical" } }, + "lowMediumWhitelist": { + "title": "P2-408 中 / 低風險白名單", + "subtitle": "{current} → {next};候選 {candidates};阻擋中的 runtime 操作 {blocked}。", + "badges": { + "mode": "no-live whitelist", + "room": "戰情室 {room}", + "live": "live total {count}" + }, + "metrics": { + "overall": "完成度", + "candidates": "白名單候選", + "low": "低風險", + "medium": "中風險", + "verifiers": "Verifier", + "rollback": "Rollback proof", + "audit": "Audit reason", + "highRedirects": "高風險分流", + "live": "Live total" + }, + "sections": { + "candidates": "中 / 低風險候選", + "ownerGates": "Owner gates", + "verifiers": "Dry-run verifier", + "truth": "Whitelist truth" + }, + "labels": { + "verifier": "verifier {value}", + "rollback": "rollback {value}", + "gateDetail": "owner {owner} · 欄位 {fields} · 驗收 {checks}", + "generated": "generated {generated}", + "env": "env {value}", + "redaction": "redaction {value}" + }, + "agents": { + "openclaw": "OpenClaw", + "hermes": "Hermes", + "nemotron": "NemoTron", + "sre": "SRE" + }, + "riskTiers": { + "low": "low", + "medium": "medium" + }, + "statuses": { + "candidate_only_no_live_execution": "候選 only", + "dry_run_ready_no_live_execution": "dry-run ready", + "owner_review_required_no_live_execution": "owner review" + } + }, "hostRunawayAiops": { "title": "P3-009 Host Runaway AIOps 閉環", "subtitle": "{current} → {next};閉環階段 {stages};阻擋 runtime 操作 {blocked}。", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 1984781d7..4801fd06c 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -3878,6 +3878,55 @@ "critical": "critical" } }, + "lowMediumWhitelist": { + "title": "P2-408 中 / 低風險白名單", + "subtitle": "{current} → {next};候選 {candidates};阻擋中的 runtime 操作 {blocked}。", + "badges": { + "mode": "no-live whitelist", + "room": "戰情室 {room}", + "live": "live total {count}" + }, + "metrics": { + "overall": "完成度", + "candidates": "白名單候選", + "low": "低風險", + "medium": "中風險", + "verifiers": "Verifier", + "rollback": "Rollback proof", + "audit": "Audit reason", + "highRedirects": "高風險分流", + "live": "Live total" + }, + "sections": { + "candidates": "中 / 低風險候選", + "ownerGates": "Owner gates", + "verifiers": "Dry-run verifier", + "truth": "Whitelist truth" + }, + "labels": { + "verifier": "verifier {value}", + "rollback": "rollback {value}", + "gateDetail": "owner {owner} · 欄位 {fields} · 驗收 {checks}", + "generated": "generated {generated}", + "env": "env {value}", + "redaction": "redaction {value}" + }, + "agents": { + "openclaw": "OpenClaw", + "hermes": "Hermes", + "nemotron": "NemoTron", + "sre": "SRE" + }, + "riskTiers": { + "low": "low", + "medium": "medium" + }, + "statuses": { + "candidate_only_no_live_execution": "候選 only", + "dry_run_ready_no_live_execution": "dry-run ready", + "owner_review_required_no_live_execution": "owner review" + } + }, "hostRunawayAiops": { "title": "P3-009 Host Runaway AIOps 閉環", "subtitle": "{current} → {next};閉環階段 {stages};阻擋 runtime 操作 {blocked}。", diff --git a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx index c94ce8073..642c5d849 100644 --- a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx +++ b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx @@ -48,6 +48,7 @@ import { type AiAgentProfessionalTaskExpansionSnapshot, type AiAgentReceiptReadbackOwnerReviewSnapshot, type AiAgentReportNoWriteAnalysisRuntimeSnapshot, + type AiAgentLowMediumRiskWhitelistSnapshot, type HostRunawayAiopsLoopReadinessSnapshot, type AiAgentCandidateOperationDryRunEvidenceSnapshot, type AiAgentCriticReviewerResultCaptureSnapshot, @@ -721,6 +722,7 @@ export function AutomationInventoryTab() { const [professionalTaskExpansion, setProfessionalTaskExpansion] = useState(null) const [receiptReadbackOwnerReview, setReceiptReadbackOwnerReview] = useState(null) const [reportNoWriteAnalysisRuntime, setReportNoWriteAnalysisRuntime] = useState(null) + const [lowMediumRiskWhitelist, setLowMediumRiskWhitelist] = useState(null) const [hostRunawayAiops, setHostRunawayAiops] = useState(null) const [proactiveOperations, setProactiveOperations] = useState(null) const [interactionLearningProof, setInteractionLearningProof] = useState(null) @@ -814,6 +816,7 @@ export function AutomationInventoryTab() { apiClient.getAiAgentProfessionalTaskExpansion(), apiClient.getAiAgentReceiptReadbackOwnerReview(), apiClient.getAiAgentReportNoWriteAnalysisRuntime(), + apiClient.getAiAgentLowMediumRiskWhitelist(), apiClient.getHostRunawayAiopsLoopReadiness(), apiClient.getAiAgentProactiveOperationsContract(), apiClient.getAiAgentInteractionLearningProof(), @@ -900,6 +903,7 @@ export function AutomationInventoryTab() { professionalTaskExpansionResult, receiptReadbackOwnerReviewResult, reportNoWriteAnalysisRuntimeResult, + lowMediumRiskWhitelistResult, hostRunawayAiopsResult, proactiveOperationsResult, interactionLearningProofResult, @@ -983,6 +987,7 @@ export function AutomationInventoryTab() { setProfessionalTaskExpansion(professionalTaskExpansionResult.status === 'fulfilled' ? professionalTaskExpansionResult.value : null) setReceiptReadbackOwnerReview(receiptReadbackOwnerReviewResult.status === 'fulfilled' ? receiptReadbackOwnerReviewResult.value : null) setReportNoWriteAnalysisRuntime(reportNoWriteAnalysisRuntimeResult.status === 'fulfilled' ? reportNoWriteAnalysisRuntimeResult.value : null) + setLowMediumRiskWhitelist(lowMediumRiskWhitelistResult.status === 'fulfilled' ? lowMediumRiskWhitelistResult.value : null) setHostRunawayAiops(hostRunawayAiopsResult.status === 'fulfilled' ? hostRunawayAiopsResult.value : null) setProactiveOperations(proactiveOperationsResult.status === 'fulfilled' ? proactiveOperationsResult.value : null) setInteractionLearningProof(interactionLearningProofResult.status === 'fulfilled' ? interactionLearningProofResult.value : null) @@ -1068,6 +1073,7 @@ export function AutomationInventoryTab() { professionalTaskExpansionResult, receiptReadbackOwnerReviewResult, reportNoWriteAnalysisRuntimeResult, + lowMediumRiskWhitelistResult, hostRunawayAiopsResult, proactiveOperationsResult, interactionLearningProofResult, @@ -1586,6 +1592,40 @@ export function AutomationInventoryTab() { .slice(0, 4) }, [reportNoWriteAnalysisRuntime]) + const visibleLowMediumWhitelistCandidates = useMemo(() => { + if (!lowMediumRiskWhitelist) return [] + const riskPriority = { medium: 0, low: 1 } as Record + const statusPriority = { + owner_review_required_no_live_execution: 0, + dry_run_ready_no_live_execution: 1, + candidate_only_no_live_execution: 2, + } as Record + return [...lowMediumRiskWhitelist.whitelist_candidates] + .sort((a, b) => { + const leftRisk = riskPriority[a.risk_tier] ?? 2 + const rightRisk = riskPriority[b.risk_tier] ?? 2 + if (leftRisk !== rightRisk) return leftRisk - rightRisk + const leftStatus = statusPriority[a.whitelist_status] ?? 3 + const rightStatus = statusPriority[b.whitelist_status] ?? 3 + if (leftStatus !== rightStatus) return leftStatus - rightStatus + return a.candidate_id.localeCompare(b.candidate_id) + }) + .slice(0, 6) + }, [lowMediumRiskWhitelist]) + + const visibleLowMediumWhitelistGates = useMemo(() => { + if (!lowMediumRiskWhitelist) return [] + const statusPriority = { owner_review_required: 0, blocked_by_runtime_gate: 1, draft_ready: 2 } as Record + return [...lowMediumRiskWhitelist.owner_review_gates] + .sort((a, b) => { + const leftStatus = statusPriority[a.status] ?? 3 + const rightStatus = statusPriority[b.status] ?? 3 + if (leftStatus !== rightStatus) return leftStatus - rightStatus + return a.gate_id.localeCompare(b.gate_id) + }) + .slice(0, 4) + }, [lowMediumRiskWhitelist]) + const visibleReportRuntimeLanes = useMemo(() => { if (!reportRuntimeReadiness) return [] const priority = { blocked_by_runtime_gate: 0, ready_for_owner_review: 1 } as Record @@ -2518,7 +2558,7 @@ export function AutomationInventoryTab() { ) } - if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !warRoom || !professionalTaskExpansion || !receiptReadbackOwnerReview || !reportNoWriteAnalysisRuntime || !hostRunawayAiops || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportStatusBoard || !reportRuntimeReadiness || !reportRuntimeDryRun || !reportRuntimeFixtureReadback || !runtimeWorkerShadowGate || !operationPermissionModel || !candidateOperationDryRunEvidence || !taskResultAuditTrail || !matchedPlaybookLearningGap || !criticReviewerResultCapture || !ownerApprovedResultCaptureDryRun || !ownerApprovedResultCaptureReadback || !runtimeReadbackApprovalPackage || !runtimeReadbackImplementationReview || !reportLiveDeliveryApprovalPackage || !runtimeReadbackFixtureApproval || !runtimeReadbackPromotionGate || !ownerApprovedFixturePromotionGate || !canonicalRuntimeReadbackOwnerAcceptance || !failureReceiptNoSendReplay || !reviewerQueueNoWriteReadback || !resultCaptureNoWriteReadback || !resultCapturePromotionApprovalGate || !ownerApprovedResultCapturePromotionDryRun || !resultCaptureWriteGateReview || !resultCaptureWriterImplementationReview || !resultCaptureWriterDryRunFixture || !resultCaptureWriterDryRunReadback || !resultCaptureOwnerPromotionReview || !resultCaptureOwnerApprovedExecutionRehearsal || !resultCaptureOwnerAcceptanceMaintenanceGate || !resultCaptureOwnerAcceptanceReadbackPreflightHold || !resultCaptureOwnerApprovedPreflightReleasePackage || !resultCaptureOwnerApprovedReleaseReadinessReadback || !resultCaptureOwnerReleaseApprovalGate || !resultCapturePostReleaseVerifierRollbackGate || !resultCaptureFinalReleaseCandidateReadback || !resultCaptureReleaseAuthorizationHold || !resultCaptureReleaseAuthorizationReadbackGate || !resultCaptureReleaseVerifierPreflightGate || !resultCaptureReleaseVerifierOwnerReviewPacket || !resultCaptureReleaseDecisionHold || !resultCaptureReleaseDecisionReadback || !resultCaptureReleaseDecisionNextHandoff || !resultCaptureReleaseDecisionInputPrep || !resultCaptureReleaseDecisionOwnerResponsePreflight || !resultCaptureReleaseDecisionOwnerResponseReadback || !resultCaptureReleaseDecisionOwnerResponseAcceptanceGate || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !dependencySupplyChainDriftMonitor || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { + if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !warRoom || !professionalTaskExpansion || !receiptReadbackOwnerReview || !reportNoWriteAnalysisRuntime || !lowMediumRiskWhitelist || !hostRunawayAiops || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportStatusBoard || !reportRuntimeReadiness || !reportRuntimeDryRun || !reportRuntimeFixtureReadback || !runtimeWorkerShadowGate || !operationPermissionModel || !candidateOperationDryRunEvidence || !taskResultAuditTrail || !matchedPlaybookLearningGap || !criticReviewerResultCapture || !ownerApprovedResultCaptureDryRun || !ownerApprovedResultCaptureReadback || !runtimeReadbackApprovalPackage || !runtimeReadbackImplementationReview || !reportLiveDeliveryApprovalPackage || !runtimeReadbackFixtureApproval || !runtimeReadbackPromotionGate || !ownerApprovedFixturePromotionGate || !canonicalRuntimeReadbackOwnerAcceptance || !failureReceiptNoSendReplay || !reviewerQueueNoWriteReadback || !resultCaptureNoWriteReadback || !resultCapturePromotionApprovalGate || !ownerApprovedResultCapturePromotionDryRun || !resultCaptureWriteGateReview || !resultCaptureWriterImplementationReview || !resultCaptureWriterDryRunFixture || !resultCaptureWriterDryRunReadback || !resultCaptureOwnerPromotionReview || !resultCaptureOwnerApprovedExecutionRehearsal || !resultCaptureOwnerAcceptanceMaintenanceGate || !resultCaptureOwnerAcceptanceReadbackPreflightHold || !resultCaptureOwnerApprovedPreflightReleasePackage || !resultCaptureOwnerApprovedReleaseReadinessReadback || !resultCaptureOwnerReleaseApprovalGate || !resultCapturePostReleaseVerifierRollbackGate || !resultCaptureFinalReleaseCandidateReadback || !resultCaptureReleaseAuthorizationHold || !resultCaptureReleaseAuthorizationReadbackGate || !resultCaptureReleaseVerifierPreflightGate || !resultCaptureReleaseVerifierOwnerReviewPacket || !resultCaptureReleaseDecisionHold || !resultCaptureReleaseDecisionReadback || !resultCaptureReleaseDecisionNextHandoff || !resultCaptureReleaseDecisionInputPrep || !resultCaptureReleaseDecisionOwnerResponsePreflight || !resultCaptureReleaseDecisionOwnerResponseReadback || !resultCaptureReleaseDecisionOwnerResponseAcceptanceGate || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !dependencySupplyChainDriftMonitor || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { return (
@@ -2784,6 +2824,30 @@ export function AutomationInventoryTab() { + reportNoWriteAnalysisRuntime.rollups.host_write_count + reportNoWriteAnalysisRuntime.rollups.kubectl_action_count ) + const lowMediumWhitelistOverall = lowMediumRiskWhitelist.program_status.overall_completion_percent + const lowMediumWhitelistCandidates = lowMediumRiskWhitelist.rollups.whitelist_candidate_count + const lowMediumWhitelistLow = lowMediumRiskWhitelist.rollups.low_risk_candidate_count + const lowMediumWhitelistMedium = lowMediumRiskWhitelist.rollups.medium_risk_candidate_count + const lowMediumWhitelistVerifiers = lowMediumRiskWhitelist.rollups.dry_run_verifier_count + const lowMediumWhitelistRollback = lowMediumRiskWhitelist.rollups.rollback_proof_count + const lowMediumWhitelistAuditReasons = lowMediumRiskWhitelist.rollups.audit_reason_template_count + const lowMediumWhitelistHighRedirects = lowMediumRiskWhitelist.rollups.high_risk_redirect_count + const lowMediumWhitelistBlocked = lowMediumRiskWhitelist.rollups.blocked_runtime_action_count + const lowMediumWhitelistLiveTotal = ( + lowMediumRiskWhitelist.rollups.auto_worker_run_count + + lowMediumRiskWhitelist.rollups.low_risk_execution_count + + lowMediumRiskWhitelist.rollups.medium_risk_execution_count + + lowMediumRiskWhitelist.rollups.gateway_queue_write_count + + lowMediumRiskWhitelist.rollups.telegram_send_count + + lowMediumRiskWhitelist.rollups.bot_api_call_count + + lowMediumRiskWhitelist.rollups.receipt_production_write_count + + lowMediumRiskWhitelist.rollups.production_write_count + + lowMediumRiskWhitelist.rollups.secret_read_count + + lowMediumRiskWhitelist.rollups.paid_api_call_count + + lowMediumRiskWhitelist.rollups.host_write_count + + lowMediumRiskWhitelist.rollups.kubectl_action_count + + lowMediumRiskWhitelist.rollups.destructive_operation_count + ) const hostRunawayOverall = hostRunawayAiops.program_status.overall_completion_percent const hostRunawayStages = hostRunawayAiops.rollups.loop_stage_count const hostRunawayAlertLanes = hostRunawayAiops.rollups.alert_lane_count @@ -4824,6 +4888,137 @@ export function AutomationInventoryTab() {
+ +
+
+
+
+ +
+
+ + {t('lowMediumWhitelist.title')} + + + {t('lowMediumWhitelist.subtitle', { + current: lowMediumRiskWhitelist.program_status.current_task_id, + next: lowMediumRiskWhitelist.program_status.next_task_id, + candidates: lowMediumWhitelistCandidates, + blocked: lowMediumWhitelistBlocked, + })} + +
+
+
+ + + +
+
+ +
+ } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> +
+ +
+
+ {t('lowMediumWhitelist.sections.candidates')} + {visibleLowMediumWhitelistCandidates.map(candidate => { + const tone = candidate.risk_tier === 'medium' ? 'warn' : 'ok' + return ( +
+
+ + {redactPublicText(candidate.display_name)} + + +
+ + {redactPublicText(candidate.allowed_no_write_outputs.join(' / '))} + +
+ + + + +
+
+ ) + })} +
+ +
+
+ {t('lowMediumWhitelist.sections.ownerGates')} +
+ {visibleLowMediumWhitelistGates.map(gate => ( + + ))} +
+
+ +
+ {t('lowMediumWhitelist.sections.verifiers')} +
+ {lowMediumRiskWhitelist.dry_run_verifiers.slice(0, 3).map(verifier => ( + + ))} +
+
+ +
+ {t('lowMediumWhitelist.sections.truth')} + + {redactPublicText(lowMediumRiskWhitelist.whitelist_truth.truth_note)} + +
+ + + +
+
+
+
+
+
+
diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index ea65aa121..432e69b9c 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -349,6 +349,11 @@ export const apiClient = { return handleResponse(res) }, + async getAiAgentLowMediumRiskWhitelist() { + const res = await fetch(`${API_BASE_URL}/agents/agent-low-medium-risk-whitelist`) + return handleResponse(res) + }, + async getHostRunawayAiopsLoopReadiness() { const res = await fetch(`${API_BASE_URL}/agents/agent-host-runaway-aiops-loop-readiness`) return handleResponse(res) @@ -3349,6 +3354,206 @@ export interface AiAgentReportNoWriteAnalysisRuntimeSnapshot { }> } +export interface AiAgentLowMediumRiskWhitelistSnapshot { + schema_version: 'ai_agent_low_medium_risk_whitelist_v1' + generated_at: string + program_status: { + overall_completion_percent: number + current_priority: 'P0' | 'P1' | 'P2' | 'P3' + current_task_id: 'P2-408' + next_task_id: 'P2-409' + read_only_mode: true + runtime_authority: 'low_medium_risk_whitelist_no_live_execution_committed_snapshot' + status_note: string + } + source_refs: string[] + source_readbacks: Array<{ + readback_id: string + source_schema_version: string + source_ref: string + endpoint: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' | 'sre' + status: string + key_readback: string + next_action: string + }> + whitelist_truth: { + p2_407_no_write_analysis_loaded: true + operation_permission_model_loaded: true + candidate_dry_run_evidence_loaded: true + report_policy_review_loaded: true + dependency_drift_loaded: true + low_risk_candidates_ready: true + medium_risk_candidates_ready: true + dry_run_verifier_required: true + rollback_proof_required: true + audit_reason_required: true + high_risk_redirect_ready: true + auto_worker_enabled: false + low_risk_live_execution_enabled: false + medium_risk_live_execution_enabled: false + gateway_queue_write_enabled: false + telegram_send_enabled: false + bot_api_call_enabled: false + receipt_production_write_enabled: false + production_write_enabled: false + secret_read_enabled: false + paid_api_call_enabled: false + host_write_enabled: false + kubectl_action_enabled: false + destructive_operation_enabled: false + openclaw_replacement_allowed: false + auto_worker_run_count_24h: number + low_risk_execution_count_24h: number + medium_risk_execution_count_24h: number + gateway_queue_write_count_24h: number + telegram_send_count_24h: number + bot_api_call_count_24h: number + receipt_production_write_count_24h: number + production_write_count_24h: number + secret_read_count_24h: number + paid_api_call_count_24h: number + host_write_count_24h: number + kubectl_action_count_24h: number + destructive_operation_count_24h: number + truth_note: string + } + whitelist_candidates: Array<{ + candidate_id: string + display_name: string + risk_tier: 'low' | 'medium' + owner_agent: 'openclaw' | 'hermes' | 'nemotron' | 'sre' + whitelist_status: 'candidate_only_no_live_execution' | 'dry_run_ready_no_live_execution' | 'owner_review_required_no_live_execution' + allowed_no_write_outputs: string[] + required_evidence: string[] + dry_run_verifier_id: string + rollback_proof_id: string + audit_reason_template_id: string + owner_approval_required_for_live_execution: true + live_execution_allowed: false + production_write_allowed: false + side_effect_count: number + blocked_runtime_actions: string[] + next_gate: string + }> + dry_run_verifiers: Array<{ + verifier_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' | 'sre' + verifier_status: 'ready' | 'owner_review_required' + required_inputs: string[] + pass_condition: string + live_readback_allowed: false + production_write_allowed: false + }> + rollback_proofs: Array<{ + rollback_proof_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' | 'sre' + rollback_scope: string + proof_status: 'noop_ready' | 'draft_ready' | 'owner_review_required' + rollback_command_allowed: false + required_before_live_execution: true + }> + audit_reason_templates: Array<{ + template_id: string + display_name: string + risk_tier: 'low' | 'medium' + required_fields: string[] + example_reason: string + sensitive_payload_allowed: false + }> + high_risk_redirects: Array<{ + redirect_id: string + display_name: string + risk_tier: 'high' | 'critical' + owner_agent: 'openclaw' | 'hermes' | 'nemotron' | 'sre' + redirect_to: 'P2-409 Owner Review Queue' + blocked_runtime_actions: string[] + reason: string + }> + owner_review_gates: Array<{ + gate_id: string + display_name: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' | 'sre' + status: 'owner_review_required' | 'blocked_by_runtime_gate' | 'draft_ready' + required_fields: string[] + acceptance_checks: string[] + blocked_runtime_actions: string[] + }> + activation_boundaries: { + read_only_whitelist_allowed: true + dry_run_verifier_preview_allowed: true + rollback_proof_preview_allowed: true + audit_reason_template_allowed: true + auto_worker_enabled: false + low_risk_live_execution_enabled: false + medium_risk_live_execution_enabled: false + gateway_queue_write_enabled: false + telegram_send_enabled: false + bot_api_call_enabled: false + receipt_production_write_enabled: false + production_write_enabled: false + secret_read_enabled: false + paid_api_call_enabled: false + host_write_enabled: false + kubectl_action_enabled: false + destructive_operation_enabled: false + openclaw_replacement_allowed: false + } + telegram_policy: { + canonical_room: 'AwoooI SRE 戰情室' + canonical_room_env: 'SRE_GROUP_CHAT_ID' + gateway_queue_write_allowed: false + direct_bot_api_allowed: false + telegram_send_allowed: false + receipt_write_allowed: false + policy_note: string + } + display_redaction_contract: { + redaction_required: true + unsafe_payload_display_allowed: false + private_reasoning_display_allowed: false + secret_value_display_allowed: false + work_window_transcript_display_allowed: false + allowed_display_fields: string[] + blocked_display_fields: string[] + } + rollups: { + source_readback_count: number + whitelist_candidate_count: number + low_risk_candidate_count: number + medium_risk_candidate_count: number + candidate_only_count: number + dry_run_verifier_count: number + rollback_proof_count: number + audit_reason_template_count: number + high_risk_redirect_count: number + owner_review_gate_count: number + live_execution_approval_required_count: number + blocked_runtime_action_count: number + auto_worker_run_count: number + low_risk_execution_count: number + medium_risk_execution_count: number + gateway_queue_write_count: number + telegram_send_count: number + bot_api_call_count: number + receipt_production_write_count: number + production_write_count: number + secret_read_count: number + paid_api_call_count: number + host_write_count: number + kubectl_action_count: number + destructive_operation_count: number + } + next_actions: Array<{ + task_id: string + priority: 'P0' | 'P1' | 'P2' | 'P3' + summary: string + gate: string + }> +} + export interface AiAgentReportAutomationReviewSnapshot { schema_version: 'ai_agent_report_automation_review_v1' generated_at: string diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 3a13cb059..502e87c93 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -58,7 +58,32 @@ - 本輪沒有送 Telegram、沒有改 Bot token、沒有改 chat routing、沒有碰 Nginx / Docker / firewall / K8s runtime。 - `.gitea/workflows` 與可能存在於主機上的 cron / 外部 bot direct Bot API 路徑仍是旁路風險;需要納入下一段「通知出口配置控管」,不可把 API 端修補誤解成全域已完全收斂。 - IwoooS 整體仍維持 `64%`,active runtime gate 仍 `0`;runtime write / kill process / restart / reload / active scan 仍全部 `false`。 +## 2026-06-18|P2-408 中 / 低風險自動處理白名單本地完成 +**背景**:P2-407 已把日報 / 週報 / 月報、P2-406B receipt owner review、P2-004 dependency drift 與 P2-403J 報表真相收斂成 AI no-write 分析建議。P2-408 的目標是把建議轉成低 / 中風險候選白名單、dry-run verifier、rollback proof 與 audit reason,同時把高風險、Telegram / Gateway / host / kubectl / production write 全部分流到 P2-409 Owner Review Queue;本段不得啟動 auto worker、不得 live execution、不得送 Telegram 或寫 production。 + +**完成內容**: +- 新增 `ai_agent_low_medium_risk_whitelist_v1` schema 與 committed snapshot:`docs/evaluations/ai_agent_low_medium_risk_whitelist_2026-06-18.json`。 +- 新增 `apps/api/src/services/ai_agent_low_medium_risk_whitelist.py` 與 `GET /api/v1/agents/agent-low-medium-risk-whitelist`。 +- 新增 service / API regression tests:`apps/api/tests/test_ai_agent_low_medium_risk_whitelist.py`、`apps/api/tests/test_ai_agent_low_medium_risk_whitelist_api.py`。 +- Governance `automation-inventory` 新增 P2-408 卡片,顯示 `6` 筆 whitelist candidate、`3` low、`3` medium、`5` 個 dry-run verifier、`5` 個 rollback proof、`6` 個 audit reason、`3` 類 high-risk redirect、`3` 個 owner gate、`27` 個 blocked runtime action 與 live total `0`。 +- OpenClaw 負責風險裁決與高風險分流;Hermes 負責報表缺口、digest 草稿與 audit reason;NemoTron 負責依賴 / 版本 proposal;SRE 負責 failure-only digest、Gateway preview 與 host / kubectl 邊界。 + +**驗證**: +- `python3 -m py_compile apps/api/src/services/ai_agent_low_medium_risk_whitelist.py apps/api/src/api/v1/agents.py`:通過。 +- `DATABASE_URL=postgresql://test:test@localhost:5432/test pytest apps/api/tests/test_ai_agent_low_medium_risk_whitelist.py apps/api/tests/test_ai_agent_low_medium_risk_whitelist_api.py -q`:`12 passed`。 +- JSON parse:新 schema、新 snapshot、`apps/web/messages/zh-TW.json`、`apps/web/messages/en.json` 通過。 +- i18n parity:`zh 12846 / en 12846 / missing 0`。 +- `git diff --check`:通過。 +- `pnpm --filter @awoooi/web typecheck`:此 worktree 缺 `apps/web/node_modules` / `tsc`,未能執行;正式驗證需由 Gitea clean env code-review / CD 補足。 + +**完成度同步**: +- P2-408 本地完成度:`100%`。 +- P2-408 正式部署 / production API / browser smoke:`0%`,待推版與正式驗證。 +- 中低風險自動化:`30% -> 45%`;白名單與 verifier / rollback / audit reason 已完成,但 auto worker 與 live execution 仍 `0%`。 +- 下一個有效動作:推版 P2-408,完成 production readback;之後進 P2-409 高風險 Owner Review Queue。 + +**邊界**:本段沒有啟動 auto worker、沒有低 / 中風險 live execution、沒有送 Telegram、沒有寫 Gateway queue、沒有呼叫 Bot API、沒有寫 receipt production target、沒有寫 production、沒有讀 secret、沒有呼叫 paid API、沒有 host write、沒有 kubectl action、沒有 destructive operation,也沒有替換 OpenClaw。 ## 2026-06-18|AI Agent 週報全 0 改為資料缺口與下一步 **背景**:統帥指出 Telegram 週報顯示告警、AI 提案、執行、成本、部署全部為 `0`,這不是可用報表,而是資料鏈路可能斷掉卻被包裝成健康。本段先修最危險的誤導:週報資料源失效或 Git 活動讀取失敗時,不得再把 `0` 當成正常事實;Telegram 報表必須直接顯示資料缺口與下一步。 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 99e5c08e4..5b8a57a03 100644 --- a/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md +++ b/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md @@ -13,12 +13,12 @@ | 項目 | 目前完成度 | 本次判讀 | 下一個有效動作 | |---|---:|---|---| | 本工作清單細化 | 100% | 已把所有工作流拆成 P0 / P1 / P2 / P3 | 同步 LOGBOOK 與 MASTER §8 | -| AgentOps 治理與可觀測基礎 | 79% | 已有 schema / snapshot / API / UI / gate;P2-407 已正式把日報 / 週報 / 月報、P2-406B receipt owner review、P2-004 drift monitor 與 P2-403J 報表真相串成 no-write 分析草稿,且 Approvals / Runs / Alerts / Telegram 告警卡已補 KM / PlayBook / 腳本 / 排程 / Verifier 資產沉澱矩陣;runtime 真正執行仍低 | P2-408 中 / 低風險白名單;補 Runs mobile smoke;Observability / Tenants / Knowledge Base 接同一資產沉澱總帳 | +| AgentOps 治理與可觀測基礎 | 83% | 已有 schema / snapshot / API / UI / gate;P2-407 已正式把日報 / 週報 / 月報、P2-406B receipt owner review、P2-004 drift monitor 與 P2-403J 報表真相串成 no-write 分析草稿;P2-408 已本地建立中 / 低風險白名單、dry-run verifier、rollback proof 與 audit reason;runtime 真正執行仍低 | P2-408 正式驗證;補 Runs mobile smoke;Observability / Tenants / Knowledge Base 接同一資產沉澱總帳 | | OpenClaw / Hermes / NemoTron 佈建布局 | 45% | 目前是只讀 layout 與治理頁可視化,不是主機上 live agent worker | 建立 runtime agent registry 與 AgentSession ledger | | Agent 主動溝通 / 接手 / 學習 | 設計證據 100%,runtime 35% | 互動證據、War Room 與 readback gate 已齊;Event Bus、RAG writeback、PlayBook trust 寫入仍未開 | 先做 no-write event stream,再做 Owner-approved writeback | | 日報 / 週報 / 月報 | 可視化 100%,no-write 分析 100%,實發 0% | 報表批准包、P2-406B owner review、P2-407 no-write analyst 與治理頁已可見;Telegram 實發、receipt production write、AI analysis live runtime 仍為 0 | P2-408 白名單與 P2-406F no-send scheduler | | Telegram Bot / TG 群組 | 契約 46%,實發 0% | no-send preview、dry-run、owner review gate、P2-406B receipt readback owner review 與正式告警卡 `KM / PlayBook / 腳本 / 排程 / Verifier` 資產沉澱顯示已完成;live send / Bot API / Gateway queue 未批准 | P2-406D no-send envelope ledger、P2-406E failure-only digest route;收到合格 owner approval 後才評估 P2-406C one-message canary | -| 中低風險自動化 | 30% | 政策方向已明確,但實際 auto worker 未開 | 建立 low / medium whitelist、dry-run verifier、rollback proof | +| 中低風險自動化 | 45% | P2-408 已本地完成 6 筆候選白名單、5 個 dry-run verifier、5 個 rollback proof、6 個 audit reason 與 3 類高風險分流;實際 auto worker 仍未開 | 正式部署 P2-408,之後接 P2-409 Owner Review Queue | | 高風險審核 | 68% | Owner Gate、拒收規則與 P2-406B receipt owner review 已多層化;仍缺真實 owner response accepted ledger | P2-145 / P2-406C 類 gate 繼續只讀讀回 | | 市場主流 Agent 追蹤 | 55% | 已有市場治理頁與 weekly watch;需擴充成固定外部來源評分與回放 | P2-412 週期性 market watch + scorecard | | 版本生命週期自動化 | 45% | repo-only snapshot 與採用批准包已完成;安裝、升級、PR creation、host update 仍未開 | P2-413 版本情報與 no-write upgrade proposal | @@ -66,7 +66,7 @@ | 5 | P2-406F | P0 | 日報 / 週報 / 月報 no-send scheduler | Hermes | 待辦 | 產生日 / 週 / 月三種報表 snapshot;不實發 | | 6 | P2-406G | P0 | 單一 Telegram canary live send | Telegram + OpenClaw | Owner Gate 阻擋 | 需明確 owner approval、maintenance window、rollback、receipt owner;否則不得執行 | | 7 | P2-407 | P0 | AI 報表自動分析 no-write runtime | Hermes + NemoTron | 正式驗證完成 | `ai_agent_report_no_write_analysis_runtime_v1` schema / snapshot / API / tests / governance UI 已正式部署;deploy marker `42c08ece`;production API 回 current `P2-407`、next `P2-408`、completion `100`;desktop / mobile smoke 可見 OpenClaw / Hermes / NemoTron、AwoooI SRE 戰情室與 `live total 0`;live AI runtime / Telegram / Gateway / Bot API / receipt production write / production write / secret read / paid API / host write / kubectl 仍為 0 | -| 8 | P2-408 | P0 | 中 / 低風險自動處理白名單 | OpenClaw + SRE | 待辦 | low / medium action policy、dry-run verifier、rollback proof、audit reason | +| 8 | P2-408 | P0 | 中 / 低風險自動處理白名單 | OpenClaw + SRE | 本地完成,待正式驗證 | `ai_agent_low_medium_risk_whitelist_v1` schema / snapshot / API / tests / governance UI 已完成;6 筆候選、3 low、3 medium、5 個 dry-run verifier、5 個 rollback proof、6 個 audit reason、3 類 high-risk redirect、27 個 blocked runtime action;auto worker / Telegram / Gateway / Bot API / production write / secret read / paid API / host write / kubectl 仍為 0 | | 9 | P2-409 | P0 | 高風險 Owner Review Queue | OpenClaw | 待辦 | 高風險 action 全部 pause,產生 approval packet 與拒收規則 | | 10 | P2-410 | P0 | Agent action audit ledger | Hermes + Security | 待辦 | 每次判斷、交接、建議、拒收、執行結果都有 immutable event | | 11 | P2-411 | P1 | Agent Event Bus 與 handoff protocol | OpenClaw + Hermes + NemoTron | 待辦 | agent_message schema、handoff state、retry、dedup、trace_id | @@ -148,7 +148,7 @@ |---|---:|---|---| | Agent 市場治理 | 72% | 進行中 | `agent_market_governance_snapshot_v1`、API、UI 分頁、每週觀察流程 | | Nemotron 實際整合應用 | 30% | 完整回放前仍被關卡擋下 | `blocked_needs_evidence`,下一關是 `refresh_source_evidence_then_5_record_smoke_only` | -| 工具 / 服務 / 套件 AI 自動化 | 97% | P2-004 依賴 / 供應鏈漂移監控讀回已完成,且已納入 P2-406B receipt readback owner review 與 P2-407 no-write analysis;Approvals / Runs / Alerts / Telegram 告警卡已能看到 KM / PlayBook / 腳本 / 排程 / Verifier 的沉澱狀態;下一主線是 P2-408 中 / 低風險白名單與 Observability / Tenants / Knowledge Base 同款總帳 | 新增 `dependency_supply_chain_drift_monitor_v1`、`ai_agent_receipt_readback_owner_review_v1`、`ai_agent_report_no_write_analysis_runtime_v1` schema / snapshot / API / tests / governance UI 卡片;9 個 drift candidate、5 個 P2-407 source readback、6 筆 draft recommendation、3 個 owner gate;Approvals / Runs / Alerts / Telegram formatter production evidence 已驗證;仍不得外部 CVE / license / registry / Agent market lookup、不得升級、不得寫 lockfile、不得 Docker build、不得 Telegram 實發 | +| 工具 / 服務 / 套件 AI 自動化 | 98% | P2-004 依賴 / 供應鏈漂移監控讀回已完成,且已納入 P2-406B receipt readback owner review、P2-407 no-write analysis 與 P2-408 中 / 低風險白名單;Approvals / Runs / Alerts / Telegram 告警卡已能看到 KM / PlayBook / 腳本 / 排程 / Verifier 的沉澱狀態;下一主線是 P2-408 正式驗證與 Observability / Tenants / Knowledge Base 同款總帳 | 新增 `dependency_supply_chain_drift_monitor_v1`、`ai_agent_receipt_readback_owner_review_v1`、`ai_agent_report_no_write_analysis_runtime_v1`、`ai_agent_low_medium_risk_whitelist_v1` schema / snapshot / API / tests / governance UI 卡片;9 個 drift candidate、5 個 P2-407 source readback、6 筆 draft recommendation、6 筆 P2-408 whitelist candidate、5 個 verifier、5 個 rollback proof;仍不得外部 CVE / license / registry / Agent market lookup、不得升級、不得寫 lockfile、不得 Docker build、不得 Telegram 實發 | | 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 主動溝通、學習與成長證據 | 100% | P2-401A 到 P2-144 已完成只讀證據面、runtime / report / result-capture gates、no-write readback、promotion review、writer implementation review、writer dry-run fixture、writer dry-run readback、owner promotion execution gate、owner-approved execution rehearsal、owner acceptance / maintenance window gate、owner acceptance readback / preflight hold、owner-approved preflight release package、owner-approved release readiness readback、owner release approval gate、post-release verifier / rollback gate、final release candidate readback、release authorization hold / readback gate、release verifier preflight / owner review packet、release decision hold / readback、release decision next handoff、release decision input prep、12-Agent War Room、owner response 預檢與 owner response 回讀;P2-141 基線與 S4.9 owner release packet 補強皆已正式驗證,P2-142 12-Agent War Room 已完成 production readback 與 desktop / mobile smoke,P2-143 owner response 預檢已完成 production readback 與 in-app browser smoke,P2-144 owner response 回讀已完成 production API readback 與 desktop / mobile smoke。runtime worker、DB migration、production Redis consumer group、canonical runtime readback、live query、runtime score、result capture write、Telegram 實發、delivery receipt E2E、live report delivery、reviewer queue write、Gateway queue write、AI analysis runtime、中低風險 auto worker、KM / LOGBOOK / audit DB / timeline / PlayBook trust 寫入、SDK / 付費服務仍未開 gate | `ai_agent_result_capture_release_decision_owner_response_readback_v1`、`GET /api/v1/agents/agent-result-capture-release-decision-owner-response-readback`、`docs/evaluations/ai_agent_result_capture_release_decision_owner_response_readback_2026-06-14.json`、feature commit `8795f100`、deploy marker `ac938037`、Gitea code-review `2965` / CD `2964` success、5 個回覆讀回 lane、18 個 owner 必填欄位、6 個 readback validation check、6 個 rejection guard、5 個 operator action、等待外部回覆 `5`、未收件 lane `5`、正式寫入 / 發送 `0`;P2-142 feature commit `5de4b3f3`、deploy marker `1a2c9e36`、Gitea CD run `4232` success、production API readback、desktop / mobile in-app browser smoke;P2-143 feature commit `755b0a8d`、deploy marker `667d6329`、Gitea code-review `2961` / CD `2960` success、production API readback、desktop / mobile in-app browser smoke;MASTER §3.2.1b / §3.2.1d / §3.4.3 | | AI Agent 主動營運委派與版本生命週期 | 100% | P2-402A / P2-402B / P2-402C / P2-402D / P2-402E / P2-402F / P2-402G 已完成;已建立 repo-only 版本新鮮度快照、工具採用批准包、Telegram action-required digest policy、Gitea PR 草案 lane、host / K3s / stateful 版本只讀盤點、API 與 governance UI。定期排程、外部版本查詢、工具安裝、CI 變更、套件升級、主機更新、container pull、實際 PR creation、auto merge、Telegram 實發、SSH、kubectl、重啟仍未開 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`、`ai_agent_gitea_pr_draft_lane_v1`、`ai_agent_host_stateful_version_inventory_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`、`GET /api/v1/agents/agent-gitea-pr-draft-lane`、`GET /api/v1/agents/agent-host-stateful-version-inventory`、`/zh-TW/governance?tab=automation-inventory`、MASTER §3.2.1c | @@ -156,6 +156,7 @@ | AI Agent 專業任務擴展與 Telegram Runtime Bridge | 99% | P2-405F 已完成只讀契約、API service guard、治理頁 P2-405F owner review gate、9 個 owner 必填欄位、9 個 acceptance check、8 個 rejection reason、6 個 reviewer action、8 個 receipt readback check,且 redaction / i18n production closeout 已完成;P2-405E 已正式驗證 dry-run delivery rehearsal;P2-406A 已把 P2-111 日報 / 週報 / 月報實發批准包、AwoooI SRE 戰情室 route、TG Bot / Gateway / receipt / AI analysis 邊界拉到治理頁前段主看板;前端公開 payload sanitizer 已改成純繁中安全標籤,避免 `redacted_*` 替換後仍殘留工作視窗 / raw / private / authorization 類敏感 key;redaction gate lookup 已正規化 `已遮罩機密欄位 -> redacted_secret_value`,正式頁 console `MISSING_MESSAGE=0`;24 類專業任務、8 個領域、5 段 Telegram bridge、6 種訊息類型、MCP/RAG stack、日報 / 週報 / 月報 / action-required 報告契約已固定;owner review received / accepted、Telegram 實發、Gateway queue、Bot API、delivery receipt production write、secret read、paid API、host write、kubectl action 仍全部關閉 | `ai_agent_professional_task_expansion_v1`、`docs/evaluations/ai_agent_professional_task_expansion_2026-06-18_1430_p2_405f.json`、`docs/evaluations/ai_agent_professional_task_expansion_2026-06-18_1200_p2_405e.json`、`GET /api/v1/agents/agent-professional-task-expansion`、`GET /api/v1/agents/agent-report-live-delivery-approval-package`、`/zh-TW/governance?tab=automation-inventory`、feature commit `2500496f`、P2-405E deploy marker `f5be4cb8`、P2-405F redaction / i18n fix commit `795ed91f`、final deploy marker `e9cf0c35`、Gitea code-review `3098` success、final CD `3103` success、production API readback current `P2-405F` / next `P2-406B` / completion `99%`、desktop `1440x1100` / mobile `390x844` browser smoke `MISSING_MESSAGE=0`、console / page / HTTP error `0`、水平溢位 `0`、危險操作控制 `0`、P2-405F local API regression `26 passed`、Web typecheck、Web production build、公開 sanitizer 輸出掃描、`docs/ai/AI_AGENT_PROFESSIONAL_TASK_EXPANSION_2026-06-15.md`、需批准任務 `19`、no-send preview `6`、dedup key `6`、receipt expectation `6`、canary package `1`、canary send approval packet `1`、delivery gate `1`、dry-run rehearsal `1`、owner review gate `1`、P2-111 delivery approval packet `5`、route gate `4`、no-send receipt `4`、owner review received / accepted `0 / 0`、live delivery approved / attempt allowed `0 / 0`、preview / canary / delivery / rehearsal / owner review live write `0`;下一步 P2-406B receipt readback owner review,仍不得實發 | | P2-406B Receipt readback owner review | 100%(正式驗證完成) | 已把日報 / 週報 / 月報、P2-405F owner gate、Telegram receipt approval package、P2-004 drift monitor 與 P2-403J 報表真相整合成只讀 owner review,並完成 production API / desktop / mobile browser smoke | `ai_agent_receipt_readback_owner_review_v1` schema、`docs/evaluations/ai_agent_receipt_readback_owner_review_2026-06-18.json`、`GET /api/v1/agents/agent-receipt-readback-owner-review`、governance `automation-inventory` P2-406B 卡片;feature commit `649552a1`、deploy marker `2d278568`;6 個 source readback、3 個報告節奏、6 個 owner gate、8 個 receipt check、9 個 drift candidate、5 個 report truth blocker、17 個 runtime false boundary;本地 API/service regression `9 passed`;production API assert OK;desktop `1280x720` / mobile `390x844` 可見 P2-406B、AwoooI SRE 戰情室、OpenClaw / Hermes / NemoTron、`SRE_GROUP_CHAT_ID`;console error `0`、水平溢位 `0`、工作視窗片語命中 `0`;Telegram send / Gateway queue / Bot API / receipt production write / production write / secret read / paid API / host write / kubectl action 全部 `0 / false` | | P2-407 AI 報表 no-write 分析 runtime | 100%(正式驗證完成) | 已把日報 / 週報 / 月報、P2-406B receipt owner review、P2-004 drift monitor 與 P2-403J 報表真相收斂成只讀 AI 分析建議草稿,並完成 production API / desktop / mobile browser smoke;下一步是 P2-408 中 / 低風險自動處理白名單 | `ai_agent_report_no_write_analysis_runtime_v1` schema、`docs/evaluations/ai_agent_report_no_write_analysis_runtime_2026-06-18.json`、`GET /api/v1/agents/agent-report-no-write-analysis-runtime`、governance `automation-inventory` P2-407 卡片;feature commit `8548892f`、CD 修正 / 部署錨點 `adcf22cd` / `fc6c01ee` / `84ca8423` / `27143fb0`、deploy marker `42c08ece`、Gitea CD `#3177` success;5 個 source readback、3 份 report input、3 個 Agent analysis pass、6 筆 draft recommendation、4 個 draft artifact、3 個 owner review gate、9 個 blocked runtime action、2 筆需批准建議;production API assert PASS;desktop `1280x720` / mobile `390x844` 可見 P2-407、OpenClaw / Hermes / NemoTron、AwoooI SRE 戰情室、`live total 0`;console error `0`、水平溢位 `0`、工作視窗片語命中 `0`;Telegram send / Gateway queue / Bot API / receipt production write / production write / secret read / paid API / host write / kubectl action 全部 `0 / false` | +| P2-408 中 / 低風險自動處理白名單 | 100%(本地完成,待正式驗證) | 已把 P2-407 no-write 分析建議轉成中 / 低風險候選白名單、dry-run verifier、rollback proof、audit reason 與高風險分流;下一步是正式部署與 P2-409 Owner Review Queue | `ai_agent_low_medium_risk_whitelist_v1` schema、`docs/evaluations/ai_agent_low_medium_risk_whitelist_2026-06-18.json`、`GET /api/v1/agents/agent-low-medium-risk-whitelist`、governance `automation-inventory` P2-408 卡片;6 筆 whitelist candidate、3 low、3 medium、5 個 dry-run verifier、5 個 rollback proof、6 個 audit reason、3 類 high-risk redirect、3 個 owner gate、27 個 blocked runtime action;本地 API/service regression `12 passed`;JSON parse、i18n parity `12846 / 12846`、Python compile、diff check 通過;web typecheck 因本 worktree 缺 `apps/web/node_modules` / `tsc` 未執行;auto worker / low risk execution / medium risk execution / Telegram send / Gateway queue / Bot API / receipt production write / production write / secret read / paid API / host write / kubectl / destructive operation 全部 `0 / false` | | Owner response 預檢與拒收邊界 | 100% | P2-143 已完成正式部署與 production readback;承接 P2-141 input prep 與 P2-142 War Room,只建立 owner / verifier / rollback / maintenance / live-apply 五類外部回覆的 intake 預檢、必填欄位與拒收規則;正式 owner response 尚未收到、未接受、未寫入 | `ai_agent_result_capture_release_decision_owner_response_preflight_v1`、`GET /api/v1/agents/agent-result-capture-release-decision-owner-response-preflight`、feature commit `755b0a8d`、deploy marker `667d6329`、Gitea code-review `2961` / CD `2960` success、5 個 response intake lane、18 個 required owner field、6 個 validation check、6 個 rejection guard、5 個 operator action;owner response received / accepted / redacted payload / reviewer queue / Gateway / Telegram / Bot API / production write / secret read / destructive operation 全為 `0` | | Owner response 回讀狀態 | 100% | P2-144 已完成正式部署與 production readback;承接 P2-143 preflight,只讀回五類外部回覆仍未收到、未接受、未拒絕、未保存 | `ai_agent_result_capture_release_decision_owner_response_readback_v1`、`GET /api/v1/agents/agent-result-capture-release-decision-owner-response-readback`、feature commit `8795f100`、deploy marker `ac938037`、Gitea code-review `2965` / CD `2964` success、5 個 response readback lane、18 個 required owner field、6 個 readback validation check、6 個 readback rejection guard、5 個 operator action、waiting external response `5`、no external response received `5`;owner response received / accepted / redacted payload / reviewer queue / Gateway / Telegram / Bot API / production write / secret read / destructive operation 全為 `0` | | 本工作清單與分析報告 | 100% | 已完成 | 本 MD 文件 | diff --git a/docs/evaluations/ai_agent_low_medium_risk_whitelist_2026-06-18.json b/docs/evaluations/ai_agent_low_medium_risk_whitelist_2026-06-18.json new file mode 100644 index 000000000..c61fbd91e --- /dev/null +++ b/docs/evaluations/ai_agent_low_medium_risk_whitelist_2026-06-18.json @@ -0,0 +1,527 @@ +{ + "schema_version": "ai_agent_low_medium_risk_whitelist_v1", + "generated_at": "2026-06-18T18:42:00+08:00", + "program_status": { + "overall_completion_percent": 100, + "current_priority": "P2", + "current_task_id": "P2-408", + "next_task_id": "P2-409", + "read_only_mode": true, + "runtime_authority": "low_medium_risk_whitelist_no_live_execution_committed_snapshot", + "status_note": "P2-408 將 P2-407 no-write 分析建議轉成中 / 低風險候選白名單、dry-run verifier、rollback proof 與 audit reason;只建立 committed snapshot、API 與治理頁證據,不啟動 auto worker、不寫 Gateway queue、不送 Telegram、不寫 production。" + }, + "source_refs": [ + "docs/evaluations/ai_agent_report_no_write_analysis_runtime_2026-06-18.json", + "docs/evaluations/ai_agent_operation_permission_model_2026-06-12.json", + "docs/evaluations/ai_agent_candidate_operation_dry_run_evidence_2026-06-12.json", + "docs/evaluations/ai_agent_report_automation_review_2026-06-12.json", + "docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json" + ], + "source_readbacks": [ + { + "readback_id": "p2_407_no_write_analysis", + "source_schema_version": "ai_agent_report_no_write_analysis_runtime_v1", + "source_ref": "docs/evaluations/ai_agent_report_no_write_analysis_runtime_2026-06-18.json", + "endpoint": "GET /api/v1/agents/agent-report-no-write-analysis-runtime", + "owner_agent": "openclaw", + "status": "loaded", + "key_readback": "6 筆 draft recommendation、2 筆需批准建議與 9 個 blocked runtime action 已讀回。", + "next_action": "把 recommendation 拆成 low / medium candidate 與 high-risk redirect。" + }, + { + "readback_id": "p2_101_permission_model", + "source_schema_version": "ai_agent_operation_permission_model_v1", + "source_ref": "docs/evaluations/ai_agent_operation_permission_model_2026-06-12.json", + "endpoint": "GET /api/v1/agents/agent-operation-permission-model", + "owner_agent": "openclaw", + "status": "loaded", + "key_readback": "observe_only、no_write_replay_allowed、proposal_only 與 human_approval_required lanes 已存在。", + "next_action": "只允許白名單候選進 no-write / dry-run lane,不啟動 live execution lane。" + }, + { + "readback_id": "p2_102_dry_run_evidence", + "source_schema_version": "ai_agent_candidate_operation_dry_run_evidence_v1", + "source_ref": "docs/evaluations/ai_agent_candidate_operation_dry_run_evidence_2026-06-12.json", + "endpoint": "GET /api/v1/agents/agent-candidate-operation-dry-run-evidence", + "owner_agent": "sre", + "status": "loaded", + "key_readback": "候選操作已具備 dry-run evidence、side-effect counter、verifier plan 與 rollback / no-op plan。", + "next_action": "P2-408 將 verifier plan 映射成低 / 中風險候選的必要前置條件。" + }, + { + "readback_id": "p2_403j_report_automation_review", + "source_schema_version": "ai_agent_report_automation_review_v1", + "source_ref": "docs/evaluations/ai_agent_report_automation_review_2026-06-12.json", + "endpoint": "GET /api/v1/agents/agent-report-automation-review", + "owner_agent": "hermes", + "status": "loaded", + "key_readback": "日報 / 週報 / 月報與高 / 中 / 低風險 policy review 已有可處置分流。", + "next_action": "低風險只能產生工作項草稿;中風險需 verifier、rollback proof 與 owner review。" + }, + { + "readback_id": "p2_004_dependency_drift", + "source_schema_version": "dependency_supply_chain_drift_monitor_v1", + "source_ref": "docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json", + "endpoint": "GET /api/v1/agents/dependency-supply-chain-drift-monitor", + "owner_agent": "nemotron", + "status": "loaded", + "key_readback": "依賴 / 供應鏈漂移只能進 upgrade proposal,不得寫 lockfile、Docker build 或外部 registry lookup。", + "next_action": "把 dependency upgrade proposal 納入 medium candidate,等待 P2-409 / owner gate。" + } + ], + "whitelist_truth": { + "p2_407_no_write_analysis_loaded": true, + "operation_permission_model_loaded": true, + "candidate_dry_run_evidence_loaded": true, + "report_policy_review_loaded": true, + "dependency_drift_loaded": true, + "low_risk_candidates_ready": true, + "medium_risk_candidates_ready": true, + "dry_run_verifier_required": true, + "rollback_proof_required": true, + "audit_reason_required": true, + "high_risk_redirect_ready": true, + "auto_worker_enabled": false, + "low_risk_live_execution_enabled": false, + "medium_risk_live_execution_enabled": false, + "gateway_queue_write_enabled": false, + "telegram_send_enabled": false, + "bot_api_call_enabled": false, + "receipt_production_write_enabled": false, + "production_write_enabled": false, + "secret_read_enabled": false, + "paid_api_call_enabled": false, + "host_write_enabled": false, + "kubectl_action_enabled": false, + "destructive_operation_enabled": false, + "openclaw_replacement_allowed": false, + "auto_worker_run_count_24h": 0, + "low_risk_execution_count_24h": 0, + "medium_risk_execution_count_24h": 0, + "gateway_queue_write_count_24h": 0, + "telegram_send_count_24h": 0, + "bot_api_call_count_24h": 0, + "receipt_production_write_count_24h": 0, + "production_write_count_24h": 0, + "secret_read_count_24h": 0, + "paid_api_call_count_24h": 0, + "host_write_count_24h": 0, + "kubectl_action_count_24h": 0, + "destructive_operation_count_24h": 0, + "truth_note": "P2-408 只建立中 / 低風險候選白名單與必要 verifier / rollback / audit reason;真正自動處理仍需後續 owner-approved runtime gate。" + }, + "whitelist_candidates": [ + { + "candidate_id": "low_readonly_evidence_refresh", + "display_name": "只讀證據 freshness refresh", + "risk_tier": "low", + "owner_agent": "hermes", + "whitelist_status": "dry_run_ready_no_live_execution", + "allowed_no_write_outputs": ["freshness 摘要", "缺口清單", "治理頁狀態更新草稿"], + "required_evidence": ["source_ref", "generated_at", "redaction status"], + "dry_run_verifier_id": "verifier_redacted_evidence_hash", + "rollback_proof_id": "rollback_noop_readback", + "audit_reason_template_id": "audit_readonly_refresh", + "owner_approval_required_for_live_execution": true, + "live_execution_allowed": false, + "production_write_allowed": false, + "side_effect_count": 0, + "blocked_runtime_actions": ["production write", "secret read", "host write"], + "next_gate": "operator evidence review" + }, + { + "candidate_id": "low_report_gap_work_item_draft", + "display_name": "報表資料缺口 work item 草稿", + "risk_tier": "low", + "owner_agent": "hermes", + "whitelist_status": "candidate_only_no_live_execution", + "allowed_no_write_outputs": ["work item 草稿", "缺口摘要", "owner 欄位清單"], + "required_evidence": ["report_truth_ref", "gap reason", "no-write proof"], + "dry_run_verifier_id": "verifier_report_gap_no_write", + "rollback_proof_id": "rollback_noop_work_item_draft", + "audit_reason_template_id": "audit_report_gap_draft", + "owner_approval_required_for_live_execution": true, + "live_execution_allowed": false, + "production_write_allowed": false, + "side_effect_count": 0, + "blocked_runtime_actions": ["Gateway queue write", "Telegram send", "Bot API call"], + "next_gate": "P2-410 audit ledger" + }, + { + "candidate_id": "low_failure_only_digest_draft", + "display_name": "failure-only digest 草稿", + "risk_tier": "low", + "owner_agent": "sre", + "whitelist_status": "candidate_only_no_live_execution", + "allowed_no_write_outputs": ["digest preview", "dedupe key", "mute 建議草稿"], + "required_evidence": ["failure signal ref", "dedupe key", "route policy ref"], + "dry_run_verifier_id": "verifier_failure_only_digest_preview", + "rollback_proof_id": "rollback_digest_preview_drop", + "audit_reason_template_id": "audit_failure_digest_draft", + "owner_approval_required_for_live_execution": true, + "live_execution_allowed": false, + "production_write_allowed": false, + "side_effect_count": 0, + "blocked_runtime_actions": ["lockfile write", "package upgrade", "Docker build"], + "next_gate": "P2-406E failure-only Telegram digest route" + }, + { + "candidate_id": "medium_sre_digest_queue_preview", + "display_name": "SRE 戰情室 queue preview", + "risk_tier": "medium", + "owner_agent": "hermes", + "whitelist_status": "owner_review_required_no_live_execution", + "allowed_no_write_outputs": ["Gateway queue preview", "receipt expectation", "dedupe proof"], + "required_evidence": ["canonical room env", "redacted payload", "receipt owner"], + "dry_run_verifier_id": "verifier_failure_only_digest_preview", + "rollback_proof_id": "rollback_digest_preview_drop", + "audit_reason_template_id": "audit_queue_preview", + "owner_approval_required_for_live_execution": true, + "live_execution_allowed": false, + "production_write_allowed": false, + "side_effect_count": 0, + "blocked_runtime_actions": ["Alertmanager route write", "receiver change", "silence write"], + "next_gate": "P2-409 Owner Review Queue" + }, + { + "candidate_id": "medium_dependency_upgrade_proposal", + "display_name": "依賴升級 proposal", + "risk_tier": "medium", + "owner_agent": "nemotron", + "whitelist_status": "owner_review_required_no_live_execution", + "allowed_no_write_outputs": ["upgrade proposal", "risk diff", "rollback note"], + "required_evidence": ["dependency drift ref", "version diff", "no lockfile write proof"], + "dry_run_verifier_id": "verifier_dependency_upgrade_no_write", + "rollback_proof_id": "rollback_upgrade_proposal_drop", + "audit_reason_template_id": "audit_dependency_upgrade_proposal", + "owner_approval_required_for_live_execution": true, + "live_execution_allowed": false, + "production_write_allowed": false, + "side_effect_count": 0, + "blocked_runtime_actions": ["kubectl action", "ArgoCD sync", "rollout restart"], + "next_gate": "P2-413 version lifecycle proposal" + }, + { + "candidate_id": "medium_config_drift_owner_packet", + "display_name": "配置漂移 owner packet", + "risk_tier": "medium", + "owner_agent": "openclaw", + "whitelist_status": "owner_review_required_no_live_execution", + "allowed_no_write_outputs": ["owner packet", "affected scope", "rollback owner 草稿"], + "required_evidence": ["config source ref", "drift summary", "rollback owner"], + "dry_run_verifier_id": "verifier_config_drift_owner_packet", + "rollback_proof_id": "rollback_config_packet_drop", + "audit_reason_template_id": "audit_config_drift_packet", + "owner_approval_required_for_live_execution": true, + "live_execution_allowed": false, + "production_write_allowed": false, + "side_effect_count": 0, + "blocked_runtime_actions": ["KM write", "PlayBook trust write", "timeline write"], + "next_gate": "P2-409 Owner Review Queue" + } + ], + "dry_run_verifiers": [ + { + "verifier_id": "verifier_redacted_evidence_hash", + "display_name": "Redacted evidence hash verifier", + "owner_agent": "hermes", + "verifier_status": "ready", + "required_inputs": ["source_ref", "generated_at", "redaction status"], + "pass_condition": "輸出 hash 與 committed snapshot 欄位一致,且 side-effect count 為 0。", + "live_readback_allowed": false, + "production_write_allowed": false + }, + { + "verifier_id": "verifier_report_gap_no_write", + "display_name": "Report gap no-write verifier", + "owner_agent": "hermes", + "verifier_status": "ready", + "required_inputs": ["report truth ref", "gap classification", "work item draft"], + "pass_condition": "只產草稿,不寫 work item DB、不寫 KM、不送 Telegram。", + "live_readback_allowed": false, + "production_write_allowed": false + }, + { + "verifier_id": "verifier_failure_only_digest_preview", + "display_name": "Failure-only digest preview verifier", + "owner_agent": "sre", + "verifier_status": "owner_review_required", + "required_inputs": ["failure signal ref", "dedupe key", "canonical room env"], + "pass_condition": "preview 具備 route / mute / rollback 欄位,且 Gateway queue write 為 0。", + "live_readback_allowed": false, + "production_write_allowed": false + }, + { + "verifier_id": "verifier_dependency_upgrade_no_write", + "display_name": "Dependency upgrade proposal verifier", + "owner_agent": "nemotron", + "verifier_status": "owner_review_required", + "required_inputs": ["dependency drift ref", "version diff", "rollback note"], + "pass_condition": "不得寫 lockfile、不得 Docker build、不得 external registry lookup。", + "live_readback_allowed": false, + "production_write_allowed": false + }, + { + "verifier_id": "verifier_config_drift_owner_packet", + "display_name": "Config drift owner packet verifier", + "owner_agent": "openclaw", + "verifier_status": "owner_review_required", + "required_inputs": ["source-of-truth ref", "affected scope", "rollback owner"], + "pass_condition": "owner packet 欄位完整,但不 reload、不套用、不改 public route。", + "live_readback_allowed": false, + "production_write_allowed": false + } + ], + "rollback_proofs": [ + { + "rollback_proof_id": "rollback_noop_readback", + "display_name": "只讀 readback no-op proof", + "owner_agent": "hermes", + "rollback_scope": "只讀 freshness refresh 沒有外部副作用;rollback 為丟棄草稿。", + "proof_status": "noop_ready", + "rollback_command_allowed": false, + "required_before_live_execution": true + }, + { + "rollback_proof_id": "rollback_noop_work_item_draft", + "display_name": "work item 草稿 no-op proof", + "owner_agent": "hermes", + "rollback_scope": "草稿未寫 DB;rollback 為關閉草稿候選。", + "proof_status": "noop_ready", + "rollback_command_allowed": false, + "required_before_live_execution": true + }, + { + "rollback_proof_id": "rollback_digest_preview_drop", + "display_name": "digest preview 丟棄 proof", + "owner_agent": "sre", + "rollback_scope": "preview 未進 queue;rollback 為丟棄 payload 與 dedupe key 草稿。", + "proof_status": "draft_ready", + "rollback_command_allowed": false, + "required_before_live_execution": true + }, + { + "rollback_proof_id": "rollback_upgrade_proposal_drop", + "display_name": "upgrade proposal 丟棄 proof", + "owner_agent": "nemotron", + "rollback_scope": "未改 lockfile / image / package;rollback 為標記 proposal rejected。", + "proof_status": "draft_ready", + "rollback_command_allowed": false, + "required_before_live_execution": true + }, + { + "rollback_proof_id": "rollback_config_packet_drop", + "display_name": "配置 owner packet 丟棄 proof", + "owner_agent": "openclaw", + "rollback_scope": "未改 source-of-truth 或 live config;rollback 為關閉 owner packet 草稿。", + "proof_status": "owner_review_required", + "rollback_command_allowed": false, + "required_before_live_execution": true + } + ], + "audit_reason_templates": [ + { + "template_id": "audit_readonly_refresh", + "display_name": "只讀刷新原因", + "risk_tier": "low", + "required_fields": ["source_ref", "freshness_delta", "operator_visible_result"], + "example_reason": "更新 freshness 判讀,未讀 secret、未寫 production。", + "sensitive_payload_allowed": false + }, + { + "template_id": "audit_report_gap_draft", + "display_name": "報表缺口草稿原因", + "risk_tier": "low", + "required_fields": ["report_id", "gap_reason", "next_owner"], + "example_reason": "週報全零需轉成資料缺口候選,不建立正式 work item。", + "sensitive_payload_allowed": false + }, + { + "template_id": "audit_failure_digest_draft", + "display_name": "failure-only digest 草稿原因", + "risk_tier": "low", + "required_fields": ["failure_signal", "dedupe_key", "mute_window"], + "example_reason": "只產 failure-only digest 草稿,send count 保持 0。", + "sensitive_payload_allowed": false + }, + { + "template_id": "audit_queue_preview", + "display_name": "queue preview 原因", + "risk_tier": "medium", + "required_fields": ["canonical_room_env", "receipt_owner", "redaction_check"], + "example_reason": "建立 SRE 戰情室 queue preview,等待 owner gate,不寫 queue。", + "sensitive_payload_allowed": false + }, + { + "template_id": "audit_dependency_upgrade_proposal", + "display_name": "依賴升級 proposal 原因", + "risk_tier": "medium", + "required_fields": ["dependency_ref", "version_delta", "rollback_note"], + "example_reason": "產生升級候選,不寫 lockfile、不 build image。", + "sensitive_payload_allowed": false + }, + { + "template_id": "audit_config_drift_packet", + "display_name": "配置漂移 owner packet 原因", + "risk_tier": "medium", + "required_fields": ["source_of_truth_ref", "affected_scope", "rollback_owner"], + "example_reason": "配置漂移需 owner packet;不 reload、不改 route。", + "sensitive_payload_allowed": false + } + ], + "high_risk_redirects": [ + { + "redirect_id": "high_security_response", + "display_name": "資安 / secret / firewall 類動作", + "risk_tier": "high", + "owner_agent": "openclaw", + "redirect_to": "P2-409 Owner Review Queue", + "blocked_runtime_actions": ["secret rotation", "firewall change", "Wazuh active response"], + "reason": "任何 secret、firewall、active response 都不是中低風險白名單,必須進 owner queue。" + }, + { + "redirect_id": "critical_model_or_cost_change", + "display_name": "模型替換 / 費用 / provider 類動作", + "risk_tier": "critical", + "owner_agent": "openclaw", + "redirect_to": "P2-409 Owner Review Queue", + "blocked_runtime_actions": ["OpenClaw replacement", "AI provider switch", "paid API expansion"], + "reason": "OpenClaw 角色調整、provider 切換與付費 API 擴量必須走市場數據、費用與 ADR gate。" + }, + { + "redirect_id": "high_data_or_config_apply", + "display_name": "資料 / DB / production config 套用", + "risk_tier": "high", + "owner_agent": "sre", + "redirect_to": "P2-409 Owner Review Queue", + "blocked_runtime_actions": ["restore apply", "DB migration", "production config reload"], + "reason": "資料回復、migration 與 production config reload 需要 maintenance window、rollback owner 與 post-check。" + } + ], + "owner_review_gates": [ + { + "gate_id": "medium_live_execution_gate", + "display_name": "中風險 live execution gate", + "owner_agent": "openclaw", + "status": "owner_review_required", + "required_fields": ["owner role", "decision reason", "dry-run verifier id", "rollback proof id", "audit reason"], + "acceptance_checks": ["side_effect_count=0", "rollback proof exists", "post-check owner assigned"], + "blocked_runtime_actions": ["Gateway queue write", "Telegram send", "production write"] + }, + { + "gate_id": "telegram_gateway_write_gate", + "display_name": "Telegram / Gateway 寫入 gate", + "owner_agent": "hermes", + "status": "blocked_by_runtime_gate", + "required_fields": ["canonical room env", "dedupe key", "receipt owner", "redaction proof"], + "acceptance_checks": ["Bot API false", "Gateway queue false", "send false"], + "blocked_runtime_actions": ["Bot API call", "receiver change", "silence write"] + }, + { + "gate_id": "config_or_host_write_gate", + "display_name": "配置 / host / kubectl gate", + "owner_agent": "sre", + "status": "blocked_by_runtime_gate", + "required_fields": ["source-of-truth ref", "maintenance window", "rollback owner", "post-check"], + "acceptance_checks": ["no secret value", "no host write", "no kubectl action"], + "blocked_runtime_actions": ["host write", "kubectl action", "production config reload"] + } + ], + "activation_boundaries": { + "read_only_whitelist_allowed": true, + "dry_run_verifier_preview_allowed": true, + "rollback_proof_preview_allowed": true, + "audit_reason_template_allowed": true, + "auto_worker_enabled": false, + "low_risk_live_execution_enabled": false, + "medium_risk_live_execution_enabled": false, + "gateway_queue_write_enabled": false, + "telegram_send_enabled": false, + "bot_api_call_enabled": false, + "receipt_production_write_enabled": false, + "production_write_enabled": false, + "secret_read_enabled": false, + "paid_api_call_enabled": false, + "host_write_enabled": false, + "kubectl_action_enabled": false, + "destructive_operation_enabled": false, + "openclaw_replacement_allowed": false + }, + "telegram_policy": { + "canonical_room": "AwoooI SRE 戰情室", + "canonical_room_env": "SRE_GROUP_CHAT_ID", + "gateway_queue_write_allowed": false, + "direct_bot_api_allowed": false, + "telegram_send_allowed": false, + "receipt_write_allowed": false, + "policy_note": "P2-408 只建立白名單候選與 preview;Telegram 只作為目標政策讀回,不發送、不排隊。" + }, + "display_redaction_contract": { + "redaction_required": true, + "unsafe_payload_display_allowed": false, + "private_reasoning_display_allowed": false, + "secret_value_display_allowed": false, + "work_window_transcript_display_allowed": false, + "allowed_display_fields": [ + "candidate_id", + "display_name", + "risk_tier", + "owner_agent", + "whitelist_status", + "dry_run_verifier_id", + "rollback_proof_id", + "audit_reason_template_id", + "blocked_runtime_actions", + "rollups" + ], + "blocked_display_fields": [ + "raw payload", + "private reasoning", + "secret value", + "authorization header", + "browser context", + "session transcript" + ] + }, + "rollups": { + "source_readback_count": 5, + "whitelist_candidate_count": 6, + "low_risk_candidate_count": 3, + "medium_risk_candidate_count": 3, + "candidate_only_count": 6, + "dry_run_verifier_count": 5, + "rollback_proof_count": 5, + "audit_reason_template_count": 6, + "high_risk_redirect_count": 3, + "owner_review_gate_count": 3, + "live_execution_approval_required_count": 6, + "blocked_runtime_action_count": 27, + "auto_worker_run_count": 0, + "low_risk_execution_count": 0, + "medium_risk_execution_count": 0, + "gateway_queue_write_count": 0, + "telegram_send_count": 0, + "bot_api_call_count": 0, + "receipt_production_write_count": 0, + "production_write_count": 0, + "secret_read_count": 0, + "paid_api_call_count": 0, + "host_write_count": 0, + "kubectl_action_count": 0, + "destructive_operation_count": 0 + }, + "next_actions": [ + { + "task_id": "P2-409", + "priority": "P0", + "summary": "把 high / critical 風險、medium live execution 與任何 Telegram / Gateway / host / kubectl / production write 轉進 Owner Review Queue。", + "gate": "Owner Review Queue 只接遮罩後 payload、明確 owner decision、rollback owner 與 post-check,不接受口頭批准。" + }, + { + "task_id": "P2-410", + "priority": "P0", + "summary": "建立 Agent action audit ledger,讓每次候選、拒收、交接、dry-run 與 owner decision 都有 immutable event。", + "gate": "仍不寫 production action;ledger write 需先定義 schema、redaction 與 reviewer gate。" + } + ] +} diff --git a/docs/schemas/ai_agent_low_medium_risk_whitelist_v1.schema.json b/docs/schemas/ai_agent_low_medium_risk_whitelist_v1.schema.json new file mode 100644 index 000000000..f50030339 --- /dev/null +++ b/docs/schemas/ai_agent_low_medium_risk_whitelist_v1.schema.json @@ -0,0 +1,228 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:awoooi:ai-agent-low-medium-risk-whitelist-v1", + "title": "AWOOOI AI Agent low and medium risk whitelist v1", + "description": "P2-408 將 P2-407 no-write 分析建議轉成中 / 低風險候選白名單、dry-run verifier、rollback proof 與 audit reason。此 schema 只允許 committed snapshot / governance UI 呈現,不授權 live auto worker、Gateway queue 寫入、Telegram 實發、Bot API、receipt production write、production write、secret 讀取、付費 API、host write、kubectl 或不可逆操作。", + "type": "object", + "required": [ + "schema_version", + "generated_at", + "program_status", + "source_refs", + "source_readbacks", + "whitelist_truth", + "whitelist_candidates", + "dry_run_verifiers", + "rollback_proofs", + "audit_reason_templates", + "high_risk_redirects", + "owner_review_gates", + "activation_boundaries", + "telegram_policy", + "display_redaction_contract", + "rollups", + "next_actions" + ], + "properties": { + "schema_version": { "type": "string", "const": "ai_agent_low_medium_risk_whitelist_v1" }, + "generated_at": { "type": "string", "minLength": 1 }, + "program_status": { + "type": "object", + "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", "enum": ["P0", "P1", "P2", "P3"] }, + "current_task_id": { "type": "string", "const": "P2-408" }, + "next_task_id": { "type": "string", "const": "P2-409" }, + "read_only_mode": { "type": "boolean", "const": true }, + "runtime_authority": { "type": "string", "const": "low_medium_risk_whitelist_no_live_execution_committed_snapshot" }, + "status_note": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + }, + "source_refs": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }, + "source_readbacks": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/source_readback" } }, + "whitelist_truth": { "type": "object", "additionalProperties": { "type": ["boolean", "integer", "string"] } }, + "whitelist_candidates": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/whitelist_candidate" } }, + "dry_run_verifiers": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/dry_run_verifier" } }, + "rollback_proofs": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/rollback_proof" } }, + "audit_reason_templates": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/audit_reason_template" } }, + "high_risk_redirects": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/high_risk_redirect" } }, + "owner_review_gates": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/owner_review_gate" } }, + "activation_boundaries": { "type": "object", "additionalProperties": { "type": "boolean" } }, + "telegram_policy": { + "type": "object", + "required": [ + "canonical_room", + "canonical_room_env", + "gateway_queue_write_allowed", + "direct_bot_api_allowed", + "telegram_send_allowed", + "receipt_write_allowed" + ], + "properties": { + "canonical_room": { "type": "string", "const": "AwoooI SRE 戰情室" }, + "canonical_room_env": { "type": "string", "const": "SRE_GROUP_CHAT_ID" }, + "gateway_queue_write_allowed": { "type": "boolean", "const": false }, + "direct_bot_api_allowed": { "type": "boolean", "const": false }, + "telegram_send_allowed": { "type": "boolean", "const": false }, + "receipt_write_allowed": { "type": "boolean", "const": false } + }, + "additionalProperties": true + }, + "display_redaction_contract": { + "type": "object", + "required": [ + "redaction_required", + "unsafe_payload_display_allowed", + "private_reasoning_display_allowed", + "secret_value_display_allowed", + "work_window_transcript_display_allowed", + "allowed_display_fields", + "blocked_display_fields" + ], + "properties": { + "redaction_required": { "type": "boolean", "const": true }, + "unsafe_payload_display_allowed": { "type": "boolean", "const": false }, + "private_reasoning_display_allowed": { "type": "boolean", "const": false }, + "secret_value_display_allowed": { "type": "boolean", "const": false }, + "work_window_transcript_display_allowed": { "type": "boolean", "const": false }, + "allowed_display_fields": { "type": "array", "items": { "type": "string" } }, + "blocked_display_fields": { "type": "array", "items": { "type": "string" } } + }, + "additionalProperties": false + }, + "rollups": { "type": "object", "additionalProperties": { "type": ["integer", "array"] } }, + "next_actions": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/next_action" } } + }, + "additionalProperties": false, + "$defs": { + "source_readback": { + "type": "object", + "required": ["readback_id", "source_schema_version", "source_ref", "endpoint", "owner_agent", "status", "key_readback", "next_action"], + "properties": { + "readback_id": { "type": "string", "minLength": 1 }, + "source_schema_version": { "type": "string", "minLength": 1 }, + "source_ref": { "type": "string", "minLength": 1 }, + "endpoint": { "type": "string", "minLength": 1 }, + "owner_agent": { "enum": ["openclaw", "hermes", "nemotron", "sre"] }, + "status": { "type": "string", "minLength": 1 }, + "key_readback": { "type": "string", "minLength": 1 }, + "next_action": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + }, + "whitelist_candidate": { + "type": "object", + "required": ["candidate_id", "display_name", "risk_tier", "owner_agent", "whitelist_status", "allowed_no_write_outputs", "required_evidence", "dry_run_verifier_id", "rollback_proof_id", "audit_reason_template_id", "owner_approval_required_for_live_execution", "live_execution_allowed", "production_write_allowed", "side_effect_count", "blocked_runtime_actions", "next_gate"], + "properties": { + "candidate_id": { "type": "string", "minLength": 1 }, + "display_name": { "type": "string", "minLength": 1 }, + "risk_tier": { "enum": ["low", "medium"] }, + "owner_agent": { "enum": ["openclaw", "hermes", "nemotron", "sre"] }, + "whitelist_status": { "enum": ["candidate_only_no_live_execution", "dry_run_ready_no_live_execution", "owner_review_required_no_live_execution"] }, + "allowed_no_write_outputs": { "type": "array", "minItems": 1, "items": { "type": "string" } }, + "required_evidence": { "type": "array", "minItems": 1, "items": { "type": "string" } }, + "dry_run_verifier_id": { "type": "string", "minLength": 1 }, + "rollback_proof_id": { "type": "string", "minLength": 1 }, + "audit_reason_template_id": { "type": "string", "minLength": 1 }, + "owner_approval_required_for_live_execution": { "type": "boolean", "const": true }, + "live_execution_allowed": { "type": "boolean", "const": false }, + "production_write_allowed": { "type": "boolean", "const": false }, + "side_effect_count": { "type": "integer", "const": 0 }, + "blocked_runtime_actions": { "type": "array", "minItems": 1, "items": { "type": "string" } }, + "next_gate": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + }, + "dry_run_verifier": { + "type": "object", + "required": ["verifier_id", "display_name", "owner_agent", "verifier_status", "required_inputs", "pass_condition", "live_readback_allowed", "production_write_allowed"], + "properties": { + "verifier_id": { "type": "string", "minLength": 1 }, + "display_name": { "type": "string", "minLength": 1 }, + "owner_agent": { "enum": ["openclaw", "hermes", "nemotron", "sre"] }, + "verifier_status": { "enum": ["ready", "owner_review_required"] }, + "required_inputs": { "type": "array", "minItems": 1, "items": { "type": "string" } }, + "pass_condition": { "type": "string", "minLength": 1 }, + "live_readback_allowed": { "type": "boolean", "const": false }, + "production_write_allowed": { "type": "boolean", "const": false } + }, + "additionalProperties": false + }, + "rollback_proof": { + "type": "object", + "required": ["rollback_proof_id", "display_name", "owner_agent", "rollback_scope", "proof_status", "rollback_command_allowed", "required_before_live_execution"], + "properties": { + "rollback_proof_id": { "type": "string", "minLength": 1 }, + "display_name": { "type": "string", "minLength": 1 }, + "owner_agent": { "enum": ["openclaw", "hermes", "nemotron", "sre"] }, + "rollback_scope": { "type": "string", "minLength": 1 }, + "proof_status": { "enum": ["noop_ready", "draft_ready", "owner_review_required"] }, + "rollback_command_allowed": { "type": "boolean", "const": false }, + "required_before_live_execution": { "type": "boolean", "const": true } + }, + "additionalProperties": false + }, + "audit_reason_template": { + "type": "object", + "required": ["template_id", "display_name", "risk_tier", "required_fields", "example_reason", "sensitive_payload_allowed"], + "properties": { + "template_id": { "type": "string", "minLength": 1 }, + "display_name": { "type": "string", "minLength": 1 }, + "risk_tier": { "enum": ["low", "medium"] }, + "required_fields": { "type": "array", "minItems": 1, "items": { "type": "string" } }, + "example_reason": { "type": "string", "minLength": 1 }, + "sensitive_payload_allowed": { "type": "boolean", "const": false } + }, + "additionalProperties": false + }, + "high_risk_redirect": { + "type": "object", + "required": ["redirect_id", "display_name", "risk_tier", "owner_agent", "redirect_to", "blocked_runtime_actions", "reason"], + "properties": { + "redirect_id": { "type": "string", "minLength": 1 }, + "display_name": { "type": "string", "minLength": 1 }, + "risk_tier": { "enum": ["high", "critical"] }, + "owner_agent": { "enum": ["openclaw", "hermes", "nemotron", "sre"] }, + "redirect_to": { "type": "string", "const": "P2-409 Owner Review Queue" }, + "blocked_runtime_actions": { "type": "array", "minItems": 1, "items": { "type": "string" } }, + "reason": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + }, + "owner_review_gate": { + "type": "object", + "required": ["gate_id", "display_name", "owner_agent", "status", "required_fields", "acceptance_checks", "blocked_runtime_actions"], + "properties": { + "gate_id": { "type": "string", "minLength": 1 }, + "display_name": { "type": "string", "minLength": 1 }, + "owner_agent": { "enum": ["openclaw", "hermes", "nemotron", "sre"] }, + "status": { "enum": ["owner_review_required", "blocked_by_runtime_gate", "draft_ready"] }, + "required_fields": { "type": "array", "minItems": 1, "items": { "type": "string" } }, + "acceptance_checks": { "type": "array", "minItems": 1, "items": { "type": "string" } }, + "blocked_runtime_actions": { "type": "array", "minItems": 1, "items": { "type": "string" } } + }, + "additionalProperties": false + }, + "next_action": { + "type": "object", + "required": ["task_id", "priority", "summary", "gate"], + "properties": { + "task_id": { "type": "string", "minLength": 1 }, + "priority": { "enum": ["P0", "P1", "P2", "P3"] }, + "summary": { "type": "string", "minLength": 1 }, + "gate": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + } + } +} 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 82658cd75..2cccc5d78 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 @@ -5122,6 +5122,21 @@ Trigger commit `f5cd37b7` 與 deploy marker `0ba92357` 已把 governance UI 的 **裁決:** P2-407 是 committed snapshot / API / governance UI 的 no-write 分析基線,不是 live AI runtime、report scheduler、Telegram send、Gateway queue write、Bot API call、receipt production write、production optimization、secret read、paid API、host write、kubectl action、destructive operation 或 OpenClaw 替換。下一步是 `P2-408`:中 / 低風險自動處理白名單、dry-run verifier、rollback proof 與高風險 Owner Review Queue。 +### 2026-06-18 18:42 (台北) — §8 / P2-408 — 新增中 / 低風險自動處理白名單 — 把 no-write 建議轉成 verifier / rollback / audit reason + +**觸發**:P2-407 已把日報 / 週報 / 月報、receipt owner review、依賴漂移與報表真相收斂成 AI 分析草稿;下一步必須明確判定哪些低 / 中風險候選可進 dry-run / proposal lane,哪些高風險必須轉 Owner Review Queue,同時不得啟動任何 live auto worker。 + +**已推進:** +- 新增 `docs/schemas/ai_agent_low_medium_risk_whitelist_v1.schema.json` 與 `docs/evaluations/ai_agent_low_medium_risk_whitelist_2026-06-18.json`。 +- 新增 `apps/api/src/services/ai_agent_low_medium_risk_whitelist.py` 與 `GET /api/v1/agents/agent-low-medium-risk-whitelist`。 +- `/zh-TW/governance?tab=automation-inventory` 新增 P2-408 卡片,顯示候選白名單、low / medium 分流、dry-run verifier、rollback proof、audit reason、高風險分流、owner gate 與 live total。 +- P2-408 固定 `6` 筆 whitelist candidate、`3` low、`3` medium、`5` 個 dry-run verifier、`5` 個 rollback proof、`6` 個 audit reason、`3` 類 high-risk redirect、`3` 個 owner gate 與 `27` 個 blocked runtime action。 +- OpenClaw 負責風險裁決與高風險分流;Hermes 負責報表缺口、digest 草稿與 audit reason;NemoTron 負責依賴 / 版本 proposal;SRE 負責 failure-only digest、Gateway preview 與 host / kubectl 邊界。 +- 本地驗證:新 schema / snapshot / i18n JSON parse 通過;`python3 -m py_compile apps/api/src/services/ai_agent_low_medium_risk_whitelist.py apps/api/src/api/v1/agents.py` 通過;目標 service / API tests `12 passed`;i18n parity `zh 12846 / en 12846`;`git diff --check` 通過。 +- 本 worktree 缺 `apps/web/node_modules` / `tsc`,因此 `pnpm --filter @awoooi/web typecheck` 未能執行;正式部署需以 Gitea clean env code-review / CD 補足。 + +**裁決:** P2-408 是 committed snapshot / API / governance UI 的 no-live whitelist 基線,不是 auto worker、low / medium live execution、Telegram send、Gateway queue write、Bot API call、receipt production write、production write、secret read、paid API、host write、kubectl action、destructive operation 或 OpenClaw 替換。下一步是 `P2-409`:高風險 Owner Review Queue,將 high / critical、medium live execution、Telegram / Gateway / host / kubectl / production write 全部 pause 成 approval packet 與拒收規則。 + ### 2026-06-18 14:20 (台北) — §8 / Host CPU AIOps — 新增 110 runaway process 監控 / 告警 / PlayBook / gated remediation **觸發**:110 CPU 滿載已確認是跨專案 stockPlatform headless Chrome smoke 遺留 5 組 orphan process group,精準 SIGTERM 後 `REMAINING_AFTER_TERM=0`;後續 load 仍高則是 AWOOOI / VibeWork / 2026 World Cup Gitea Actions build/test。這證明泛用 `HostHighCpuLoad` 不足以支撐 AI 自動化產品,必須能把 orphan process、合法 CI load、Docker/Sentry/Harbor 事故分開。