From 7c8bb3645bdf1fa5ac1aaa7041c237fce8c19c0e Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 12 Jun 2026 15:04:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(governance):=20=E6=96=B0=E5=A2=9E=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E9=A1=9E=E5=88=A5=E6=AC=8A=E9=99=90=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/api/v1/agents.py | 31 + .../ai_agent_operation_permission_model.py | 313 ++++++++++ ...est_ai_agent_operation_permission_model.py | 130 +++++ ...ai_agent_operation_permission_model_api.py | 38 ++ apps/web/messages/en.json | 63 ++ apps/web/messages/zh-TW.json | 63 ++ .../tabs/automation-inventory-tab.tsx | 241 +++++++- apps/web/src/lib/api-client.ts | 146 +++++ docs/LOGBOOK.md | 28 + ...AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md | 6 +- ...T_INTERACTION_LEARNING_PROOF_2026-06-11.md | 20 +- ...operation_permission_model_2026-06-12.json | 543 ++++++++++++++++++ ..._operation_permission_model_v1.schema.json | 370 ++++++++++++ ...-04-15-MASTER-ai-autonomous-flywheel-v2.md | 12 +- 14 files changed, 1994 insertions(+), 10 deletions(-) create mode 100644 apps/api/src/services/ai_agent_operation_permission_model.py create mode 100644 apps/api/tests/test_ai_agent_operation_permission_model.py create mode 100644 apps/api/tests/test_ai_agent_operation_permission_model_api.py create mode 100644 docs/evaluations/ai_agent_operation_permission_model_2026-06-12.json create mode 100644 docs/schemas/ai_agent_operation_permission_model_v1.schema.json diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index dda80e14..6fa67929 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -76,6 +76,9 @@ from src.services.ai_agent_owner_approved_fixture_dry_run import ( from src.services.ai_agent_owner_approved_learning_dry_run import ( load_latest_ai_agent_owner_approved_learning_dry_run, ) +from src.services.ai_agent_operation_permission_model import ( + load_latest_ai_agent_operation_permission_model, +) from src.services.ai_agent_post_write_verifier_package import ( load_latest_ai_agent_post_write_verifier_package, ) @@ -1035,6 +1038,34 @@ async def get_agent_runtime_worker_shadow_gate() -> dict[str, Any]: ) from exc +@router.get( + "/agent-operation-permission-model", + response_model=dict[str, Any], + summary="取得 AI Agent 操作類別權限模型", + description=( + "讀取最新已提交的 P2-101 操作類別權限模型;此端點只回傳 permission lane、" + "operation category、Agent responsibility、gate transition 與 operator template," + "不啟動 runtime worker、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、" + "不寫讀報回執、不執行 verifier live readback、不寫 production target、不讀 secret。" + ), +) +async def get_agent_operation_permission_model() -> dict[str, Any]: + """Return the latest read-only AI Agent operation permission model.""" + try: + return await asyncio.to_thread(load_latest_ai_agent_operation_permission_model) + 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_operation_permission_model_invalid", error=str(exc)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="AI Agent 操作類別權限模型無效", + ) from exc + + @router.get( "/agent-owner-approved-fixture-dry-run", response_model=dict[str, Any], diff --git a/apps/api/src/services/ai_agent_operation_permission_model.py b/apps/api/src/services/ai_agent_operation_permission_model.py new file mode 100644 index 00000000..22d428ac --- /dev/null +++ b/apps/api/src/services/ai_agent_operation_permission_model.py @@ -0,0 +1,313 @@ +""" +AI Agent operation permission model snapshot. + +Loads the latest committed P2-101 operation category permission model. +This module validates repo-committed evidence only; it never enables runtime +workers, writes Gateway queues, sends Telegram messages, reads secrets, or +writes production targets. +""" + +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_operation_permission_model_*.json" +_SCHEMA_VERSION = "ai_agent_operation_permission_model_v1" +_RUNTIME_AUTHORITY = "operation_permission_model_only_no_live_execution_or_send" + + +def load_latest_ai_agent_operation_permission_model( + evaluations_dir: Path | None = None, +) -> dict[str, Any]: + """Load the newest committed AI Agent operation permission model.""" + directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR + candidates = sorted(directory.glob(_SNAPSHOT_PATTERN)) + if not candidates: + raise FileNotFoundError(f"no AI Agent operation permission model snapshots found in {directory}") + + latest = candidates[-1] + with latest.open(encoding="utf-8") as handle: + payload = json.load(handle) + + if not isinstance(payload, dict): + raise ValueError(f"{latest}: expected JSON object") + _require_schema(payload, str(latest)) + _require_no_live_boundaries(payload, str(latest)) + _require_permission_lanes(payload, str(latest)) + _require_operation_categories(payload, str(latest)) + _require_agent_roles(payload, str(latest)) + _require_gate_transitions(payload, str(latest)) + _require_operator_templates(payload, str(latest)) + _require_redaction_contract(payload, str(latest)) + _require_rollup_consistency(payload, str(latest)) + 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 {} + if status.get("read_only_mode") is not True: + raise ValueError(f"{label}: program_status.read_only_mode must be true") + if status.get("runtime_authority") != _RUNTIME_AUTHORITY: + raise ValueError(f"{label}: runtime_authority must remain {_RUNTIME_AUTHORITY}") + if status.get("current_task_id") != "P2-101": + raise ValueError(f"{label}: current_task_id must be P2-101") + if status.get("next_task_id") != "P2-102": + raise ValueError(f"{label}: next_task_id must be P2-102") + + +def _require_no_live_boundaries(payload: dict[str, Any], label: str) -> None: + truth = payload.get("operation_permission_truth") or {} + required_true = { + "permission_model_ready", + "operation_category_matrix_ready", + "risk_tier_mapping_ready", + "agent_responsibility_mapping_ready", + "approval_gate_mapping_ready", + "manual_sop_lane_ready", + "p2_404_shadow_gate_handoff_ready", + } + missing = sorted(field for field in required_true if truth.get(field) is not True) + if missing: + raise ValueError(f"{label}: permission readiness flags must remain true: {missing}") + + required_false = { + "runtime_execution_enabled", + "gateway_queue_write_enabled", + "telegram_send_enabled", + "telegram_bot_api_call_enabled", + "delivery_receipt_write_enabled", + "ai_runtime_worker_enabled", + "medium_low_auto_worker_enabled", + "post_action_verifier_live_readback_enabled", + "production_write_enabled", + "secret_value_read_enabled", + "paid_provider_call_enabled", + "host_or_cluster_command_enabled", + "destructive_operation_enabled", + "work_window_transcript_display_allowed", + } + unsafe = sorted(field for field in required_false if truth.get(field) is not False) + if unsafe: + raise ValueError(f"{label}: live execution/send/write flags must remain false: {unsafe}") + + zero_counts = { + "runtime_execution_count_24h", + "gateway_queue_write_count_24h", + "telegram_send_count_24h", + "telegram_bot_api_call_count_24h", + "delivery_receipt_write_count_24h", + "ai_runtime_worker_run_count_24h", + "medium_low_auto_execution_count_24h", + "post_action_verifier_live_readback_count_24h", + "production_write_count_24h", + "secret_value_read_count_24h", + "paid_provider_call_count_24h", + "host_or_cluster_command_count_24h", + "destructive_operation_count_24h", + } + non_zero = sorted(field for field in zero_counts if truth.get(field) != 0) + if non_zero: + raise ValueError(f"{label}: live execution/send/write counts must remain zero: {non_zero}") + + +def _require_permission_lanes(payload: dict[str, Any], label: str) -> None: + lanes = payload.get("permission_lanes") or [] + lane_ids = {lane.get("lane_id") for lane in lanes} + required = { + "observe_only", + "no_write_replay_allowed", + "proposal_only", + "human_approval_required", + "explicitly_blocked", + } + if lane_ids != required: + raise ValueError(f"{label}: permission lanes must match {sorted(required)}") + for lane in lanes: + lane_id = lane.get("lane_id") + if lane.get("live_execution_allowed") is not False: + raise ValueError(f"{label}: lane {lane_id} live_execution_allowed must remain false") + if lane.get("production_write_allowed") is not False: + raise ValueError(f"{label}: lane {lane_id} production_write_allowed must remain false") + + +def _require_operation_categories(payload: dict[str, Any], label: str) -> None: + categories = payload.get("operation_categories") or [] + category_ids = {category.get("category_id") for category in categories} + required = { + "observe_inventory_read", + "diagnose_correlate_evidence", + "report_digest_queue_candidate", + "shadow_no_write_replay", + "manual_sop_draft", + "repair_candidate_proposal", + "low_risk_noop_execution", + "medium_risk_repair_execution", + "post_action_verifier_live_readback", + "telegram_gateway_queue_write", + "production_config_or_data_write", + "secret_or_paid_provider_access", + "destructive_host_or_cluster_action", + } + if category_ids != required: + raise ValueError(f"{label}: operation categories must match {sorted(required)}") + + for category in categories: + category_id = category.get("category_id") + if category.get("queue_write_allowed") is not False: + raise ValueError(f"{label}: category {category_id} queue_write_allowed must remain false") + if category.get("telegram_send_allowed") is not False: + raise ValueError(f"{label}: category {category_id} telegram_send_allowed must remain false") + if category.get("production_write_allowed") is not False: + raise ValueError(f"{label}: category {category_id} production_write_allowed must remain false") + if category.get("secret_value_read_allowed") is not False: + raise ValueError(f"{label}: category {category_id} secret_value_read_allowed must remain false") + if category.get("destructive_action_allowed") is not False: + raise ValueError(f"{label}: category {category_id} destructive_action_allowed must remain false") + if category.get("live_execution_allowed") is not False: + raise ValueError(f"{label}: category {category_id} live_execution_allowed must remain false") + if not _is_redacted_sha256(category.get("evidence_hash")): + raise ValueError(f"{label}: category {category_id} must expose a redacted sha256 evidence_hash") + + +def _require_agent_roles(payload: dict[str, Any], label: str) -> None: + roles = payload.get("agent_permission_roles") or [] + agents = {role.get("agent_id") for role in roles} + if agents != {"openclaw", "hermes", "nemotron"}: + raise ValueError(f"{label}: permission roles must include OpenClaw, Hermes, and NemoTron") + for role in roles: + if role.get("live_action_count_24h") != 0: + raise ValueError(f"{label}: agent {role.get('agent_id')} live_action_count_24h must remain zero") + if role.get("self_approval_allowed") is not False: + raise ValueError(f"{label}: agent {role.get('agent_id')} self_approval_allowed must remain false") + + +def _require_gate_transitions(payload: dict[str, Any], label: str) -> None: + gates = payload.get("gate_transitions") or [] + gate_ids = {gate.get("gate_id") for gate in gates} + required = { + "p2_101_permission_review_gate", + "p2_102_dry_run_evidence_gate", + "gateway_queue_write_permission_gate", + "telegram_send_permission_gate", + "medium_low_auto_worker_permission_gate", + "post_action_verifier_live_gate", + "production_write_permission_gate", + "secret_or_paid_provider_gate", + } + if gate_ids != required: + raise ValueError(f"{label}: gate transitions must match {sorted(required)}") + for gate in gates: + gate_id = gate.get("gate_id") + if gate.get("opens_live_execution") is not False: + raise ValueError(f"{label}: gate {gate_id} opens_live_execution must remain false") + if gate.get("current_status") not in {"ready_for_review", "blocked_until_evidence", "blocked_by_policy"}: + raise ValueError(f"{label}: gate {gate_id} current_status is invalid") + + +def _require_operator_templates(payload: dict[str, Any], label: str) -> None: + templates = payload.get("operator_decision_templates") or [] + template_ids = {template.get("template_id") for template in templates} + required = { + "evidence_collect_next_step", + "manual_sop_next_step", + "repair_proposal_next_step", + "queue_candidate_next_step", + "rollback_or_fix_next_step", + } + if template_ids != required: + raise ValueError(f"{label}: operator templates must match {sorted(required)}") + for template in templates: + if template.get("creates_runtime_action") is not False: + raise ValueError(f"{label}: template {template.get('template_id')} creates_runtime_action must remain false") + if template.get("requires_human_review") is not True: + raise ValueError(f"{label}: template {template.get('template_id')} requires_human_review must remain true") + + +def _require_redaction_contract(payload: dict[str, Any], label: str) -> None: + contract = payload.get("display_redaction_contract") or {} + required_false = { + "raw_prompt_display_allowed", + "private_reasoning_display_allowed", + "secret_value_display_allowed", + "raw_telegram_payload_display_allowed", + "work_window_transcript_display_allowed", + } + if contract.get("redaction_required") is not True: + raise ValueError(f"{label}: display redaction must remain required") + unsafe = sorted(field for field in required_false if contract.get(field) is not False) + if unsafe: + raise ValueError(f"{label}: display redaction fields must remain false: {unsafe}") + + +def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None: + rollups = payload.get("rollups") or {} + truth = payload.get("operation_permission_truth") or {} + lanes = payload.get("permission_lanes") or [] + categories = payload.get("operation_categories") or [] + roles = payload.get("agent_permission_roles") or [] + gates = payload.get("gate_transitions") or [] + templates = payload.get("operator_decision_templates") or [] + + expected = { + "permission_lane_count": len(lanes), + "operation_category_count": len(categories), + "observe_only_category_count": sum(1 for item in categories if item.get("permission_lane") == "observe_only"), + "no_write_replay_allowed_category_count": sum(1 for item in categories if item.get("permission_lane") == "no_write_replay_allowed"), + "proposal_only_category_count": sum(1 for item in categories if item.get("permission_lane") == "proposal_only"), + "human_approval_required_category_count": sum(1 for item in categories if item.get("permission_lane") == "human_approval_required"), + "explicitly_blocked_category_count": sum(1 for item in categories if item.get("permission_lane") == "explicitly_blocked"), + "agent_role_count": len(roles), + "gate_transition_count": len(gates), + "operator_decision_template_count": len(templates), + } + mismatches = sorted(field for field, value in expected.items() if rollups.get(field) != value) + if mismatches: + raise ValueError(f"{label}: rollup counts must match source arrays: {mismatches}") + + approval_category_ids = sorted( + item.get("category_id") for item in categories if item.get("permission_lane") == "human_approval_required" + ) + if sorted(rollups.get("human_approval_required_category_ids") or []) != approval_category_ids: + raise ValueError(f"{label}: human_approval_required_category_ids must match categories") + + blocked_category_ids = sorted( + item.get("category_id") for item in categories if item.get("permission_lane") == "explicitly_blocked" + ) + if sorted(rollups.get("explicitly_blocked_category_ids") or []) != blocked_category_ids: + raise ValueError(f"{label}: explicitly_blocked_category_ids must match categories") + + zero_pairs = { + "runtime_execution_count": truth.get("runtime_execution_count_24h"), + "gateway_queue_write_count": truth.get("gateway_queue_write_count_24h"), + "telegram_send_count": truth.get("telegram_send_count_24h"), + "telegram_bot_api_call_count": truth.get("telegram_bot_api_call_count_24h"), + "delivery_receipt_write_count": truth.get("delivery_receipt_write_count_24h"), + "ai_runtime_worker_run_count": truth.get("ai_runtime_worker_run_count_24h"), + "medium_low_auto_execution_count": truth.get("medium_low_auto_execution_count_24h"), + "post_action_verifier_live_readback_count": truth.get("post_action_verifier_live_readback_count_24h"), + "production_write_count": truth.get("production_write_count_24h"), + "secret_value_read_count": truth.get("secret_value_read_count_24h"), + "paid_provider_call_count": truth.get("paid_provider_call_count_24h"), + "host_or_cluster_command_count": truth.get("host_or_cluster_command_count_24h"), + "destructive_operation_count": truth.get("destructive_operation_count_24h"), + } + non_zero = sorted(field for field, value in zero_pairs.items() if rollups.get(field) != 0 or value != 0) + if non_zero: + raise ValueError(f"{label}: rollup live counts must remain zero: {non_zero}") + + +def _is_redacted_sha256(value: Any) -> bool: + if not isinstance(value, str): + return False + prefix = "sha256:" + if not value.startswith(prefix): + return False + digest = value[len(prefix) :] + return len(digest) == 64 and all(char in "0123456789abcdef" for char in digest) diff --git a/apps/api/tests/test_ai_agent_operation_permission_model.py b/apps/api/tests/test_ai_agent_operation_permission_model.py new file mode 100644 index 00000000..4659669f --- /dev/null +++ b/apps/api/tests/test_ai_agent_operation_permission_model.py @@ -0,0 +1,130 @@ +import copy +import json + +import pytest + +from src.services.ai_agent_operation_permission_model import ( + load_latest_ai_agent_operation_permission_model, +) + + +def _write_snapshot(tmp_path, payload): + path = tmp_path / "ai_agent_operation_permission_model_2026-06-12.json" + path.write_text(json.dumps(payload), encoding="utf-8") + return path + + +def test_load_latest_ai_agent_operation_permission_model(): + data = load_latest_ai_agent_operation_permission_model() + + assert data["schema_version"] == "ai_agent_operation_permission_model_v1" + assert data["program_status"]["current_task_id"] == "P2-101" + assert data["program_status"]["next_task_id"] == "P2-102" + assert data["program_status"]["overall_completion_percent"] == 97 + assert data["operation_permission_truth"]["permission_model_ready"] is True + assert data["operation_permission_truth"]["operation_category_matrix_ready"] is True + assert data["operation_permission_truth"]["runtime_execution_enabled"] is False + assert data["operation_permission_truth"]["gateway_queue_write_enabled"] is False + assert data["operation_permission_truth"]["telegram_send_enabled"] is False + assert data["operation_permission_truth"]["telegram_bot_api_call_enabled"] is False + assert data["operation_permission_truth"]["ai_runtime_worker_enabled"] is False + assert data["operation_permission_truth"]["medium_low_auto_worker_enabled"] is False + assert data["operation_permission_truth"]["production_write_enabled"] is False + assert data["operation_permission_truth"]["secret_value_read_enabled"] is False + assert data["operation_permission_truth"]["destructive_operation_enabled"] is False + assert data["rollups"]["permission_lane_count"] == 5 + assert data["rollups"]["operation_category_count"] == 13 + assert data["rollups"]["observe_only_category_count"] == 2 + assert data["rollups"]["no_write_replay_allowed_category_count"] == 2 + assert data["rollups"]["proposal_only_category_count"] == 2 + assert data["rollups"]["human_approval_required_category_count"] == 4 + assert data["rollups"]["explicitly_blocked_category_count"] == 3 + assert data["rollups"]["agent_role_count"] == 3 + assert data["rollups"]["gate_transition_count"] == 8 + assert data["rollups"]["operator_decision_template_count"] == 5 + assert data["rollups"]["runtime_execution_count"] == 0 + assert data["rollups"]["gateway_queue_write_count"] == 0 + assert data["rollups"]["telegram_send_count"] == 0 + assert data["rollups"]["production_write_count"] == 0 + assert data["rollups"]["destructive_operation_count"] == 0 + + +def test_rejects_runtime_execution_enabled(tmp_path): + data = load_latest_ai_agent_operation_permission_model() + bad = copy.deepcopy(data) + bad["operation_permission_truth"]["runtime_execution_enabled"] = True + _write_snapshot(tmp_path, bad) + + with pytest.raises(ValueError, match="live execution/send/write flags"): + load_latest_ai_agent_operation_permission_model(tmp_path) + + +def test_rejects_gateway_queue_write_count(tmp_path): + data = load_latest_ai_agent_operation_permission_model() + bad = copy.deepcopy(data) + bad["operation_permission_truth"]["gateway_queue_write_count_24h"] = 1 + bad["rollups"]["gateway_queue_write_count"] = 1 + _write_snapshot(tmp_path, bad) + + with pytest.raises(ValueError, match="live execution/send/write counts"): + load_latest_ai_agent_operation_permission_model(tmp_path) + + +def test_rejects_lane_live_execution(tmp_path): + data = load_latest_ai_agent_operation_permission_model() + bad = copy.deepcopy(data) + bad["permission_lanes"][0]["live_execution_allowed"] = True + _write_snapshot(tmp_path, bad) + + with pytest.raises(ValueError, match="live_execution_allowed"): + load_latest_ai_agent_operation_permission_model(tmp_path) + + +def test_rejects_category_telegram_send(tmp_path): + data = load_latest_ai_agent_operation_permission_model() + bad = copy.deepcopy(data) + bad["operation_categories"][0]["telegram_send_allowed"] = True + _write_snapshot(tmp_path, bad) + + with pytest.raises(ValueError, match="telegram_send_allowed"): + load_latest_ai_agent_operation_permission_model(tmp_path) + + +def test_rejects_agent_self_approval(tmp_path): + data = load_latest_ai_agent_operation_permission_model() + bad = copy.deepcopy(data) + bad["agent_permission_roles"][0]["self_approval_allowed"] = True + _write_snapshot(tmp_path, bad) + + with pytest.raises(ValueError, match="self_approval_allowed"): + load_latest_ai_agent_operation_permission_model(tmp_path) + + +def test_rejects_gate_opening_live_execution(tmp_path): + data = load_latest_ai_agent_operation_permission_model() + bad = copy.deepcopy(data) + bad["gate_transitions"][0]["opens_live_execution"] = True + _write_snapshot(tmp_path, bad) + + with pytest.raises(ValueError, match="opens_live_execution"): + load_latest_ai_agent_operation_permission_model(tmp_path) + + +def test_rejects_template_runtime_action(tmp_path): + data = load_latest_ai_agent_operation_permission_model() + bad = copy.deepcopy(data) + bad["operator_decision_templates"][0]["creates_runtime_action"] = True + _write_snapshot(tmp_path, bad) + + with pytest.raises(ValueError, match="creates_runtime_action"): + load_latest_ai_agent_operation_permission_model(tmp_path) + + +def test_rejects_rollup_mismatch(tmp_path): + data = load_latest_ai_agent_operation_permission_model() + bad = copy.deepcopy(data) + bad["rollups"]["operation_category_count"] = 999 + _write_snapshot(tmp_path, bad) + + with pytest.raises(ValueError, match="rollup counts"): + load_latest_ai_agent_operation_permission_model(tmp_path) diff --git a/apps/api/tests/test_ai_agent_operation_permission_model_api.py b/apps/api/tests/test_ai_agent_operation_permission_model_api.py new file mode 100644 index 00000000..5e4fedc2 --- /dev/null +++ b/apps/api/tests/test_ai_agent_operation_permission_model_api.py @@ -0,0 +1,38 @@ +from fastapi.testclient import TestClient + +from src.main import app + + +def test_get_ai_agent_operation_permission_model_api(): + client = TestClient(app) + response = client.get("/api/v1/agents/agent-operation-permission-model") + + assert response.status_code == 200 + data = response.json() + assert data["schema_version"] == "ai_agent_operation_permission_model_v1" + assert data["program_status"]["current_task_id"] == "P2-101" + assert data["program_status"]["next_task_id"] == "P2-102" + assert data["program_status"]["overall_completion_percent"] == 97 + assert data["operation_permission_truth"]["permission_model_ready"] is True + assert data["operation_permission_truth"]["runtime_execution_enabled"] is False + assert data["operation_permission_truth"]["gateway_queue_write_enabled"] is False + assert data["operation_permission_truth"]["telegram_send_enabled"] is False + assert data["operation_permission_truth"]["telegram_bot_api_call_enabled"] is False + assert data["operation_permission_truth"]["ai_runtime_worker_enabled"] is False + assert data["operation_permission_truth"]["medium_low_auto_worker_enabled"] is False + assert data["operation_permission_truth"]["production_write_enabled"] is False + assert data["operation_permission_truth"]["secret_value_read_enabled"] is False + assert data["operation_permission_truth"]["destructive_operation_enabled"] is False + assert data["rollups"]["permission_lane_count"] == 5 + assert data["rollups"]["operation_category_count"] == 13 + assert data["rollups"]["human_approval_required_category_count"] == 4 + assert data["rollups"]["explicitly_blocked_category_count"] == 3 + assert data["rollups"]["agent_role_count"] == 3 + assert data["rollups"]["gate_transition_count"] == 8 + assert data["rollups"]["operator_decision_template_count"] == 5 + assert data["rollups"]["runtime_execution_count"] == 0 + assert data["rollups"]["gateway_queue_write_count"] == 0 + assert data["rollups"]["telegram_send_count"] == 0 + assert data["rollups"]["production_write_count"] == 0 + assert data["rollups"]["secret_value_read_count"] == 0 + assert data["rollups"]["destructive_operation_count"] == 0 diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index f79c32de..d59f5526 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -4373,6 +4373,69 @@ "high": "高風險", "critical": "關鍵阻擋" } + }, + "operationPermissionModel": { + "title": "P2-101 操作類別權限模型", + "source": "{generated} · {current} → {next}", + "truthTitle": "權限模型真相", + "boundaryTitle": "runtime 邊界", + "boundarySummary": "目前 runtime {runtime}、Gateway queue write {queue}、Telegram send {send}、production write {write}、secret read {secret};本段只定義 lane,不開執行。", + "metrics": { + "overall": "P2-101 進度", + "categories": "操作類別", + "observeOnly": "只讀", + "noWrite": "no-write", + "proposals": "提案", + "humanApproval": "需人工", + "blocked": "明確阻擋", + "gates": "關卡", + "runtimeRuns": "runtime", + "queueWrites": "queue writes", + "telegramSends": "TG sends", + "productionWrites": "prod writes", + "secretReads": "secret reads", + "destructive": "破壞性" + }, + "flags": { + "modelReady": "model ready: {value}", + "matrixReady": "matrix ready: {value}", + "agentMap": "agent map: {value}", + "p2Handoff": "P2-404 handoff: {value}", + "runtime": "runtime enabled: {value}", + "queueWrite": "queue write: {value}", + "send": "send: {value}", + "productionWrite": "prod write: {value}", + "secret": "secret value: {value}", + "destructive": "destructive: {value}", + "liveExecution": "live execution: {value}", + "opensLive": "opens live: {value}", + "runtimeAction": "runtime action: {value}" + }, + "labels": { + "nextGate": "next gate: {value}", + "requiredEvidence": "evidence: {value}", + "blockedActions": "blocked: {value}", + "liveCount": "live {count}", + "reviewRequired": "人工審查: {value}" + }, + "lanes": { + "observe_only": "只讀觀察", + "no_write_replay_allowed": "no-write replay", + "proposal_only": "提案 / SOP", + "human_approval_required": "需人工批准", + "explicitly_blocked": "明確阻擋" + }, + "statuses": { + "ready_for_review": "可審查", + "blocked_until_evidence": "等證據", + "blocked_by_policy": "政策阻擋" + }, + "riskTiers": { + "low": "低風險", + "medium": "中風險", + "high": "高風險", + "critical": "關鍵阻擋" + } } } }, diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index f79c32de..d59f5526 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -4373,6 +4373,69 @@ "high": "高風險", "critical": "關鍵阻擋" } + }, + "operationPermissionModel": { + "title": "P2-101 操作類別權限模型", + "source": "{generated} · {current} → {next}", + "truthTitle": "權限模型真相", + "boundaryTitle": "runtime 邊界", + "boundarySummary": "目前 runtime {runtime}、Gateway queue write {queue}、Telegram send {send}、production write {write}、secret read {secret};本段只定義 lane,不開執行。", + "metrics": { + "overall": "P2-101 進度", + "categories": "操作類別", + "observeOnly": "只讀", + "noWrite": "no-write", + "proposals": "提案", + "humanApproval": "需人工", + "blocked": "明確阻擋", + "gates": "關卡", + "runtimeRuns": "runtime", + "queueWrites": "queue writes", + "telegramSends": "TG sends", + "productionWrites": "prod writes", + "secretReads": "secret reads", + "destructive": "破壞性" + }, + "flags": { + "modelReady": "model ready: {value}", + "matrixReady": "matrix ready: {value}", + "agentMap": "agent map: {value}", + "p2Handoff": "P2-404 handoff: {value}", + "runtime": "runtime enabled: {value}", + "queueWrite": "queue write: {value}", + "send": "send: {value}", + "productionWrite": "prod write: {value}", + "secret": "secret value: {value}", + "destructive": "destructive: {value}", + "liveExecution": "live execution: {value}", + "opensLive": "opens live: {value}", + "runtimeAction": "runtime action: {value}" + }, + "labels": { + "nextGate": "next gate: {value}", + "requiredEvidence": "evidence: {value}", + "blockedActions": "blocked: {value}", + "liveCount": "live {count}", + "reviewRequired": "人工審查: {value}" + }, + "lanes": { + "observe_only": "只讀觀察", + "no_write_replay_allowed": "no-write replay", + "proposal_only": "提案 / SOP", + "human_approval_required": "需人工批准", + "explicitly_blocked": "明確阻擋" + }, + "statuses": { + "ready_for_review": "可審查", + "blocked_until_evidence": "等證據", + "blocked_by_policy": "政策阻擋" + }, + "riskTiers": { + "low": "低風險", + "medium": "中風險", + "high": "高風險", + "critical": "關鍵阻擋" + } } } }, 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 cd7d6569..dcc88a03 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 @@ -42,6 +42,7 @@ import { type AiAgentLiveReadModelGateSnapshot, type AiAgentOwnerApprovedFixtureDryRunSnapshot, type AiAgentOwnerApprovedLearningDryRunSnapshot, + type AiAgentOperationPermissionModelSnapshot, type AiAgentPostWriteVerifierPackageSnapshot, type AiAgentProactiveOperationsContractSnapshot, type AiAgentRedisDryRunGateSnapshot, @@ -346,6 +347,7 @@ export function AutomationInventoryTab() { const [reportRuntimeDryRun, setReportRuntimeDryRun] = useState(null) const [reportRuntimeFixtureReadback, setReportRuntimeFixtureReadback] = useState(null) const [runtimeWorkerShadowGate, setRuntimeWorkerShadowGate] = useState(null) + const [operationPermissionModel, setOperationPermissionModel] = useState(null) const [reportTruthActionabilityReview, setReportTruthActionabilityReview] = useState(null) const [ownerDryRunPackage, setOwnerDryRunPackage] = useState(null) const [hostStatefulInventory, setHostStatefulInventory] = useState(null) @@ -383,6 +385,7 @@ export function AutomationInventoryTab() { apiClient.getAiAgentReportRuntimeDryRun(), apiClient.getAiAgentReportRuntimeFixtureReadback(), apiClient.getAiAgentRuntimeWorkerShadowGate(), + apiClient.getAiAgentOperationPermissionModel(), apiClient.getAiAgentReportTruthActionabilityReview(), apiClient.getAiAgentOwnerApprovedFixtureDryRun(), apiClient.getAiAgentHostStatefulVersionInventory(), @@ -419,6 +422,7 @@ export function AutomationInventoryTab() { reportRuntimeDryRunResult, reportRuntimeFixtureReadbackResult, runtimeWorkerShadowGateResult, + operationPermissionModelResult, reportTruthActionabilityReviewResult, ownerDryRunPackageResult, hostStatefulInventoryResult, @@ -452,6 +456,7 @@ export function AutomationInventoryTab() { setReportRuntimeDryRun(reportRuntimeDryRunResult.status === 'fulfilled' ? reportRuntimeDryRunResult.value : null) setReportRuntimeFixtureReadback(reportRuntimeFixtureReadbackResult.status === 'fulfilled' ? reportRuntimeFixtureReadbackResult.value : null) setRuntimeWorkerShadowGate(runtimeWorkerShadowGateResult.status === 'fulfilled' ? runtimeWorkerShadowGateResult.value : null) + setOperationPermissionModel(operationPermissionModelResult.status === 'fulfilled' ? operationPermissionModelResult.value : null) setReportTruthActionabilityReview(reportTruthActionabilityReviewResult.status === 'fulfilled' ? reportTruthActionabilityReviewResult.value : null) setOwnerDryRunPackage(ownerDryRunPackageResult.status === 'fulfilled' ? ownerDryRunPackageResult.value : null) setHostStatefulInventory(hostStatefulInventoryResult.status === 'fulfilled' ? hostStatefulInventoryResult.value : null) @@ -483,6 +488,7 @@ export function AutomationInventoryTab() { reportRuntimeDryRunResult, reportRuntimeFixtureReadbackResult, runtimeWorkerShadowGateResult, + operationPermissionModelResult, reportTruthActionabilityReviewResult, ownerDryRunPackageResult, hostStatefulInventoryResult, @@ -1047,6 +1053,42 @@ export function AutomationInventoryTab() { .slice(0, 6) }, [runtimeWorkerShadowGate]) + const visibleOperationPermissionCategories = useMemo(() => { + if (!operationPermissionModel) return [] + const lanePriority = { + explicitly_blocked: 0, + human_approval_required: 1, + proposal_only: 2, + no_write_replay_allowed: 3, + observe_only: 4, + } as Record + const riskPriority = { critical: 0, high: 1, medium: 2, low: 3 } as Record + return [...operationPermissionModel.operation_categories] + .sort((a, b) => { + const leftLane = lanePriority[a.permission_lane] ?? 5 + const rightLane = lanePriority[b.permission_lane] ?? 5 + if (leftLane !== rightLane) return leftLane - rightLane + const leftRisk = riskPriority[a.risk_tier] ?? 4 + const rightRisk = riskPriority[b.risk_tier] ?? 4 + if (leftRisk !== rightRisk) return leftRisk - rightRisk + return a.category_id.localeCompare(b.category_id) + }) + .slice(0, 8) + }, [operationPermissionModel]) + + const visibleOperationPermissionGates = useMemo(() => { + if (!operationPermissionModel) return [] + const statusPriority = { blocked_by_policy: 0, blocked_until_evidence: 1, ready_for_review: 2 } as Record + return [...operationPermissionModel.gate_transitions] + .sort((a, b) => { + const left = statusPriority[a.current_status] ?? 3 + const right = statusPriority[b.current_status] ?? 3 + if (left !== right) return left - right + return a.gate_id.localeCompare(b.gate_id) + }) + .slice(0, 8) + }, [operationPermissionModel]) + const visibleReportTruthFindings = useMemo(() => { if (!reportTruthActionabilityReview) return [] const priority = { critical: 0, high: 1, medium: 2, low: 3 } as Record @@ -1266,7 +1308,7 @@ export function AutomationInventoryTab() { ) } - if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportRuntimeReadiness || !reportRuntimeDryRun || !reportRuntimeFixtureReadback || !runtimeWorkerShadowGate || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { + if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportRuntimeReadiness || !reportRuntimeDryRun || !reportRuntimeFixtureReadback || !runtimeWorkerShadowGate || !operationPermissionModel || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { return (
@@ -1460,6 +1502,20 @@ export function AutomationInventoryTab() { const runtimeShadowQueueWrites = runtimeWorkerShadowGate.rollups.gateway_queue_write_count const runtimeShadowSends = runtimeWorkerShadowGate.rollups.telegram_send_count const runtimeShadowProductionWrites = runtimeWorkerShadowGate.rollups.production_write_count + const operationPermissionOverall = operationPermissionModel.program_status.overall_completion_percent + const operationPermissionCategories = operationPermissionModel.rollups.operation_category_count + const operationPermissionObserveOnly = operationPermissionModel.rollups.observe_only_category_count + const operationPermissionNoWrite = operationPermissionModel.rollups.no_write_replay_allowed_category_count + const operationPermissionProposalOnly = operationPermissionModel.rollups.proposal_only_category_count + const operationPermissionHumanApproval = operationPermissionModel.rollups.human_approval_required_category_count + const operationPermissionBlocked = operationPermissionModel.rollups.explicitly_blocked_category_count + const operationPermissionGates = operationPermissionModel.rollups.gate_transition_count + const operationPermissionRuntimeRuns = operationPermissionModel.rollups.runtime_execution_count + const operationPermissionQueueWrites = operationPermissionModel.rollups.gateway_queue_write_count + const operationPermissionSends = operationPermissionModel.rollups.telegram_send_count + const operationPermissionProductionWrites = operationPermissionModel.rollups.production_write_count + const operationPermissionSecretReads = operationPermissionModel.rollups.secret_value_read_count + const operationPermissionDestructive = operationPermissionModel.rollups.destructive_operation_count const reportTruthOverall = reportTruthActionabilityReview.program_status.overall_completion_percent const reportTruthFindings = reportTruthActionabilityReview.rollups.zero_signal_finding_count const reportTruthCritical = reportTruthActionabilityReview.rollups.critical_finding_count @@ -2891,6 +2947,189 @@ export function AutomationInventoryTab() {
+
+
+
+ + + {t('operationPermissionModel.title')} + +
+ +
+ +
+ } /> + } /> + } /> + } /> + } /> + 0 ? 'danger' : 'ok'} icon={} /> + 0 ? 'danger' : 'ok'} icon={} /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> +
+ +
+
+ {t('operationPermissionModel.truthTitle')} + + {operationPermissionModel.operation_permission_truth.truth_note} + +
+ + + + +
+
+ +
+ {t('operationPermissionModel.boundaryTitle')} + + {t('operationPermissionModel.boundarySummary', { + runtime: operationPermissionRuntimeRuns, + queue: operationPermissionQueueWrites, + send: operationPermissionSends, + write: operationPermissionProductionWrites, + secret: operationPermissionSecretReads, + })} + +
+ + + + + + +
+
+
+ +
+ {operationPermissionModel.permission_lanes.map(lane => ( +
+
+ + {lane.display_name} + + +
+ + {lane.summary} + +
+ {lane.allowed_outputs.slice(0, 3).map(item => ( + + ))} +
+ +
+ ))} +
+ +
+ {visibleOperationPermissionCategories.map(category => ( +
+
+ + {category.display_name} + + +
+
+ + + +
+ + {t('operationPermissionModel.labels.requiredEvidence', { value: category.required_evidence.slice(0, 3).join(' / ') })} + + + {t('operationPermissionModel.labels.blockedActions', { value: category.blocked_actions.slice(0, 3).join(' / ') })} + + +
+ ))} +
+ +
+ {operationPermissionModel.agent_permission_roles.map(role => ( +
+
+ + {role.display_name} + + +
+ + {role.permission_responsibility} + +
+ {role.allowed_lanes.slice(0, 3).map(item => ( + + ))} + {role.blocked_now.slice(0, 2).map(item => ( + + ))} +
+
+ ))} +
+ +
+ {visibleOperationPermissionGates.map(gate => ( +
+
+ + {gate.display_name} + + +
+ + {gate.required_before} + + + {gate.next_safe_step} + + +
+ ))} +
+ +
+ {operationPermissionModel.operator_decision_templates.map(template => ( +
+
+ + {template.display_name} + + +
+ + {template.when_to_use} + + + {template.human_instruction} + + +
+ ))} +
+
+
diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index d5bad7b4..075a84fe 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -347,6 +347,11 @@ export const apiClient = { return handleResponse(res) }, + async getAiAgentOperationPermissionModel() { + const res = await fetch(`${API_BASE_URL}/agents/agent-operation-permission-model`) + return handleResponse(res) + }, + async getAiAgentOwnerApprovedFixtureDryRun() { const res = await fetch(`${API_BASE_URL}/agents/agent-owner-approved-fixture-dry-run`) return handleResponse(res) @@ -2602,6 +2607,147 @@ export interface AiAgentRuntimeWorkerShadowGateSnapshot { } } +export interface AiAgentOperationPermissionModelSnapshot { + schema_version: 'ai_agent_operation_permission_model_v1' + generated_at: string + program_status: { + overall_completion_percent: number + current_priority: 'P0' | 'P1' | 'P2' | 'P3' + current_task_id: 'P2-101' + next_task_id: 'P2-102' + read_only_mode: true + runtime_authority: 'operation_permission_model_only_no_live_execution_or_send' + status_note: string + } + source_refs: string[] + operation_permission_truth: { + permission_model_ready: true + operation_category_matrix_ready: true + risk_tier_mapping_ready: true + agent_responsibility_mapping_ready: true + approval_gate_mapping_ready: true + manual_sop_lane_ready: true + p2_404_shadow_gate_handoff_ready: true + runtime_execution_enabled: false + gateway_queue_write_enabled: false + telegram_send_enabled: false + telegram_bot_api_call_enabled: false + delivery_receipt_write_enabled: false + ai_runtime_worker_enabled: false + medium_low_auto_worker_enabled: false + post_action_verifier_live_readback_enabled: false + production_write_enabled: false + secret_value_read_enabled: false + paid_provider_call_enabled: false + host_or_cluster_command_enabled: false + destructive_operation_enabled: false + work_window_transcript_display_allowed: false + runtime_execution_count_24h: number + gateway_queue_write_count_24h: number + telegram_send_count_24h: number + telegram_bot_api_call_count_24h: number + delivery_receipt_write_count_24h: number + ai_runtime_worker_run_count_24h: number + medium_low_auto_execution_count_24h: number + post_action_verifier_live_readback_count_24h: number + production_write_count_24h: number + secret_value_read_count_24h: number + paid_provider_call_count_24h: number + host_or_cluster_command_count_24h: number + destructive_operation_count_24h: number + truth_note: string + } + permission_lanes: Array<{ + lane_id: 'observe_only' | 'no_write_replay_allowed' | 'proposal_only' | 'human_approval_required' | 'explicitly_blocked' + display_name: string + summary: string + allowed_outputs: string[] + required_gate_before_promotion: string + live_execution_allowed: false + production_write_allowed: false + }> + operation_categories: Array<{ + category_id: string + display_name: string + risk_tier: 'low' | 'medium' | 'high' | 'critical' + permission_lane: 'observe_only' | 'no_write_replay_allowed' | 'proposal_only' | 'human_approval_required' | 'explicitly_blocked' + primary_agent: 'openclaw' | 'hermes' | 'nemotron' + allowed_outputs: string[] + blocked_actions: string[] + required_evidence: string[] + next_gate: string + queue_write_allowed: false + telegram_send_allowed: false + production_write_allowed: false + secret_value_read_allowed: false + destructive_action_allowed: false + live_execution_allowed: false + evidence_hash: string + }> + agent_permission_roles: Array<{ + agent_id: 'openclaw' | 'hermes' | 'nemotron' + display_name: string + permission_responsibility: string + allowed_lanes: string[] + blocked_now: string[] + self_approval_allowed: false + live_action_count_24h: number + }> + gate_transitions: Array<{ + gate_id: string + display_name: string + current_status: 'ready_for_review' | 'blocked_until_evidence' | 'blocked_by_policy' + required_before: string + next_safe_step: string + opens_live_execution: false + }> + operator_decision_templates: Array<{ + template_id: string + display_name: string + when_to_use: string + human_instruction: string + creates_runtime_action: false + requires_human_review: true + }> + display_redaction_contract: { + redaction_required: true + raw_prompt_display_allowed: false + private_reasoning_display_allowed: false + secret_value_display_allowed: false + raw_telegram_payload_display_allowed: false + work_window_transcript_display_allowed: false + allowed_display_fields: string[] + blocked_display_fields: string[] + } + rollups: { + permission_lane_count: number + operation_category_count: number + observe_only_category_count: number + no_write_replay_allowed_category_count: number + proposal_only_category_count: number + human_approval_required_category_count: number + explicitly_blocked_category_count: number + human_approval_required_category_ids: string[] + explicitly_blocked_category_ids: string[] + agent_role_count: number + gate_transition_count: number + operator_decision_template_count: number + runtime_execution_count: number + gateway_queue_write_count: number + telegram_send_count: number + telegram_bot_api_call_count: number + delivery_receipt_write_count: number + ai_runtime_worker_run_count: number + medium_low_auto_execution_count: number + post_action_verifier_live_readback_count: number + production_write_count: number + secret_value_read_count: number + paid_provider_call_count: number + host_or_cluster_command_count: number + destructive_operation_count: number + } +} + export interface AiAgentOwnerApprovedFixtureDryRunSnapshot { schema_version: 'ai_agent_owner_approved_fixture_dry_run_v1' generated_at: string diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 5041b495..6b6d712e 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,31 @@ +## 2026-06-12|P2-101 操作類別權限模型 + +**背景**:P2-404 已把 fixture/readback/verifier dry-run promotion 成 runtime worker shadow / no-write execution evidence gate;統帥指出 TG 批准後仍沒有真正自動化,也沒有產生可操作的人工下一步。P2-101 先把每一類操作的權限、風險、Agent 責任、下一個 gate 與人工處置模板固定成可查模型,避免批准後落到 `learning_recorded` 或 `manual_review` 但沒有 SOP。 + +**完成(本地)**: + +- 新增 `ai_agent_operation_permission_model_v1` schema、committed snapshot 與 backend loader,強制 runtime execution、Gateway queue write、Telegram send、Bot API、delivery receipt、AI runtime worker、中低風險 auto worker、verifier live readback、production write、secret value read、paid provider call、host / cluster command 與 destructive action 全部維持 `false / 0`。 +- 新增 `GET /api/v1/agents/agent-operation-permission-model` 只讀 API 與測試;API 只回傳 permission lane、operation category、Agent responsibility、gate transition 與 operator decision template,不寫 queue、不送 Telegram、不啟動 worker。 +- 治理頁 `/zh-TW/governance?tab=automation-inventory` 新增 P2-101 區塊,顯示 5 條 permission lane、13 類 operation category、3 個 Agent permission role、8 個 gate transition 與 5 個人工操作模板。 +- 更新 `AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md`、`AI_AGENT_INTERACTION_LEARNING_PROOF_2026-06-11.md` 與 MASTER §3.2 / §5,將 P2-101 標記為完成,下一步改為 `P2-102` 候選操作 dry-run 證據。 + +**驗證(本地)**: + +- `python3 -m json.tool` 檢查 P2-101 snapshot / schema / `zh-TW.json` / `en.json` 通過。 +- `python3 -m py_compile apps/api/src/services/ai_agent_operation_permission_model.py apps/api/src/api/v1/agents.py` 通過。 +- `DATABASE_URL='postgresql+asyncpg://test:test@localhost/test' PYTHONPATH=apps/api pytest -q apps/api/tests/test_ai_agent_operation_permission_model.py apps/api/tests/test_ai_agent_operation_permission_model_api.py`:`10 passed`。 +- `cmp -s apps/web/messages/zh-TW.json apps/web/messages/en.json` 通過,兩份訊息檔維持繁體中文鏡像。 +- `pnpm --filter @awoooi/web typecheck` 通過。 +- `NEXT_PUBLIC_API_URL=https://awoooi.wooo.work NEXT_PRIVATE_BUILD_WORKER_COUNT=1 SENTRY_SUPPRESS_GLOBAL_ERROR_HANDLER_FILE_WARNING=1 pnpm --filter @awoooi/web build`:Next compile / static page generation 通過;standalone copy 階段因本機磁碟只剩約 `127MiB` 觸發 `ENOSPC`,已清除本輪忽略產物 `apps/web/.next`,等待 Gitea CD runner 重新 build 作正式部署依據。 + +**完成度同步**: + +- P2-101:本地完成度 `97%`;permission lane `5`、operation category `13`、Agent permission role `3`、gate transition `8`、operator decision template `5`。 +- runtime execution、Gateway queue write、Telegram send、Telegram Bot API、delivery receipt write、AI runtime worker、中低風險 auto worker、verifier live readback、production write、secret value read、paid provider call、host / cluster command、destructive action:全部仍為 `0`。 +- P2-102:下一步要讓所有候選操作具備 dry-run 證據、side-effect count、evidence hash 與 verifier plan;完成前不得 live worker、queue write、Telegram send、production write 或 verifier live readback。 + +**邊界**:本段不送 Telegram、不寫 Gateway queue、不呼叫 Bot API、不寫 delivery receipt、不啟動 live AI runtime worker、不啟動中低風險 auto worker、不跑 verifier live readback、不讀 secret、不呼叫付費 provider、不執行 host / cluster / destructive action、不顯示內部對話內容、不提供前端批准 / 執行 / 發送按鈕;不得把 operation permission model 解讀成 runtime loop 已運作。 + ## 2026-06-12|P2-404 runtime worker shadow / no-write execution evidence gate **背景**:P2-403N 已把報表 dry-run 草案提升到 fixture smoke、queue preview readback 與 verifier dry-run;統帥要求不要停在報表與 UI,而要往真正 AI Agent 自動化流程推進。本段將 P2-403N 的 promotion 轉成 runtime worker shadow / no-write execution evidence gate,但仍不啟動 live worker 或任何 production write。 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 0f6a381e..b2b92174 100644 --- a/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md +++ b/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md @@ -20,9 +20,9 @@ AI Agent 自動化工作包目前完成度:**92%**。本工作清單文件本 三 Agent 佈建布局目前完成度:**45%**。第一波已完成只讀 schema / snapshot / API / 測試 / 報告,第二波已接入治理頁自動化盤點 UI;正式 runtime 佈署、Telegram E2E 發送與 AgentSession 工作流仍需逐項 gate。 -三 Agent 主動溝通、學習與成長證據目前完成度:**100%**。已完成只讀契約、互動 / 接手 / 學習 / 成長證據面板、P2-403B live read model gate、P2-403C Redis dry-run gate、P2-403D learning writeback approval package、P2-403E Telegram receipt approval package、P2-403F owner-approved learning dry-run preview、人工操作選項與 fixture-only dry-run 總包、P2-403G runtime write gate review、P2-403H post-write verifier implementation package、P2-403I runtime verifier evidence implementation review、P2-403J 報表真相 / 告警有效性 / 日週月報 / Agent 工作量 / 圖表化報告 / AI 建議 / 風險自動化政策審查、P2-403K SRE 戰情室路由程式收斂、P2-403L 報表派送 / Telegram queue / 讀報回執 / AI 讀報分析 / 中低風險自動處理 / 高風險審核啟動前閘門、P2-403M no-write dry-run / SRE 戰情室 Gateway queue 草案 / readback verifier、P2-403N fixture smoke / queue preview readback / verifier dry-run,以及 P2-404 runtime worker shadow / no-write execution evidence gate;目前 live AgentSession、Agent message、handoff、learning write、Telegram receipt、Gateway queue write、runtime verifier execution、report delivery、AI analysis runtime、中低風險 auto worker、Telegram 實發、shadow worker live 與 delivery receipt E2E 仍全部為 `0`,下一步依優先順序推 `P2-101` 操作類別權限模型,但在批准前仍不得啟動 runtime loop。 +三 Agent 主動溝通、學習與成長證據目前完成度:**100%**。已完成只讀契約、互動 / 接手 / 學習 / 成長證據面板、P2-403B live read model gate、P2-403C Redis dry-run gate、P2-403D learning writeback approval package、P2-403E Telegram receipt approval package、P2-403F owner-approved learning dry-run preview、人工操作選項與 fixture-only dry-run 總包、P2-403G runtime write gate review、P2-403H post-write verifier implementation package、P2-403I runtime verifier evidence implementation review、P2-403J 報表真相 / 告警有效性 / 日週月報 / Agent 工作量 / 圖表化報告 / AI 建議 / 風險自動化政策審查、P2-403K SRE 戰情室路由程式收斂、P2-403L 報表派送 / Telegram queue / 讀報回執 / AI 讀報分析 / 中低風險自動處理 / 高風險審核啟動前閘門、P2-403M no-write dry-run / SRE 戰情室 Gateway queue 草案 / readback verifier、P2-403N fixture smoke / queue preview readback / verifier dry-run、P2-404 runtime worker shadow / no-write execution evidence gate,以及 P2-101 操作類別權限模型;目前 live AgentSession、Agent message、handoff、learning write、Telegram receipt、Gateway queue write、runtime verifier execution、report delivery、AI analysis runtime、中低風險 auto worker、Telegram 實發、shadow worker live 與 delivery receipt E2E 仍全部為 `0`,下一步依優先順序推 `P2-102` 候選操作 dry-run 證據,但在批准前仍不得啟動 runtime loop。 -AI Agent 主動營運委派與版本生命週期目前完成度:**100%**。已完成 12 類版本 domain、24 類可委派能力、5 種 cadence、8 類 MCP、4 類 RAG memory、只讀 API、`P2-402B` repo-only daily version freshness snapshot、`P2-402C` Renovate / OSV-Scanner / Trivy / Syft / Grype 工具採用批准包、`P2-402D` Telegram action-required digest policy、`P2-402E` Gitea PR 草案 lane、`P2-402F` host OS / K3s / stateful services 版本只讀盤點,以及 `P2-402G` governance UI 顯示可委派能力;`P2-403A` 到 `P2-404` 已先補互動、學習證據面、live read model gate、Redis dry-run gate、learning writeback approval package、Telegram receipt approval package、owner-approved learning dry-run preview、runtime write gate review、post-write verifier package、runtime verifier evidence review、報表真相、TG 戰情室收斂、日週月報、Agent 工作量、圖表化報告、風險自動化政策、報表 runtime 啟動前閘門、no-write dry-run 證據包、fixture/readback/verifier dry-run 證據包與 shadow/no-write execution gate。下一步是 `P2-101` 操作類別權限模型,外部 registry / package source / host probe / SSH / kubectl / 工具安裝 / CI 變更 / 實際 PR creation / Telegram 實發與 learning write 仍需 gate。 +AI Agent 主動營運委派與版本生命週期目前完成度:**100%**。已完成 12 類版本 domain、24 類可委派能力、5 種 cadence、8 類 MCP、4 類 RAG memory、只讀 API、`P2-402B` repo-only daily version freshness snapshot、`P2-402C` Renovate / OSV-Scanner / Trivy / Syft / Grype 工具採用批准包、`P2-402D` Telegram action-required digest policy、`P2-402E` Gitea PR 草案 lane、`P2-402F` host OS / K3s / stateful services 版本只讀盤點,以及 `P2-402G` governance UI 顯示可委派能力;`P2-403A` 到 `P2-404` 已先補互動、學習證據面、live read model gate、Redis dry-run gate、learning writeback approval package、Telegram receipt approval package、owner-approved learning dry-run preview、runtime write gate review、post-write verifier package、runtime verifier evidence review、報表真相、TG 戰情室收斂、日週月報、Agent 工作量、圖表化報告、風險自動化政策、報表 runtime 啟動前閘門、no-write dry-run 證據包、fixture/readback/verifier dry-run 證據包、shadow/no-write execution gate 與 P2-101 操作類別權限模型。下一步是 `P2-102` 候選操作 dry-run 證據,外部 registry / package source / host probe / SSH / kubectl / 工具安裝 / CI 變更 / 實際 PR creation / Telegram 實發與 learning write 仍需 gate。 完成度計算模型: @@ -973,7 +973,7 @@ UI: | P2-403M | 完成 | 91 | OpenClaw + Hermes + NemoTron | 報表 runtime no-write dry-run、SRE 戰情室 Gateway queue 草案、readback verifier 草案 | `ai_agent_report_runtime_dry_run_v1` / snapshot / 只讀 API / governance UI;5 個 dry-run artifact、3 個 queue digest 草案、4 個 readback verifier case、3 個 Agent dry-run role、6 個 operator checkpoint;live delivery / queue write / Bot API / receipt write / AI runtime / 中低風險 auto worker / verifier live readback 全部 `0` | 不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 delivery receipt、不啟動 AI runtime worker、不啟動中低風險 auto worker、不執行 verifier live readback、不讀 secret、不顯示內部對話內容 | | P2-403N | 完成 | 94 | Hermes + NemoTron + OpenClaw | fixture smoke / queue preview readback / verifier dry-run | `ai_agent_report_runtime_fixture_readback_v1` / snapshot / 只讀 API / governance UI;5 個 fixture smoke、3 個 queue preview readback、4 個 verifier dry-run case、3 個 Agent fixture role、5 個 operator checkpoint;live delivery / queue write / Telegram send / Bot API / receipt write / AI runtime / 中低風險 auto worker / verifier live readback 全部 `0` | 仍不得 live send / live write;中低風險自動處理與高風險審核需另行批准 | | P2-404 | 完成 | 96 | OpenClaw + Hermes + NemoTron | runtime worker shadow / no-write execution evidence gate | `ai_agent_runtime_worker_shadow_gate_v1` / snapshot / 只讀 API / governance UI;5 個 shadow candidate、4 個 no-write replay、4 個 verifier shadow case、3 個 Agent shadow role、6 個 operator checkpoint;shadow live / Gateway queue write / Telegram send / Bot API / receipt write / AI runtime / 中低風險 auto worker / verifier live readback / production write 全部 `0` | 下一步 P2-101 操作類別權限模型;未完成前不得 live worker、queue write、Telegram send 或 production write | -| P2-101 | 待辦 | 0 | OpenClaw | 定義操作類別權限模型 | 操作政策 schema | HITL 關卡 | +| P2-101 | 完成 | 97 | OpenClaw + Hermes + NemoTron | 定義操作類別權限模型 | `ai_agent_operation_permission_model_v1` / snapshot / 只讀 API / governance UI;5 條 permission lane、13 類操作、3 個 Agent permission role、8 個 gate transition、5 個人工操作模板;runtime execution / Gateway queue write / Telegram send / Bot API / receipt write / AI runtime worker / 中低風險 auto worker / verifier live readback / production write / secret read / paid provider / host command / destructive action 全部 `0` | 下一步 P2-102 候選操作 dry-run 證據;未完成前不得 live worker、queue write、Telegram send 或 production write | | P2-102 | 待辦 | 0 | OpenClaw | 所有候選操作都要有 dry-run 證據 | dry-run 合約 | 不直接 apply | | P2-103 | 待辦 | 0 | Hermes | 把任務結果接回 KM / LOGBOOK / 稽核軌跡 | 證據寫入器 | 不洩漏 secret | | P2-104 | 待辦 | 0 | OpenClaw | 修復 `matched_playbook_id` 學習缺口 | playbook trust 更新 | 測試 + live 證據 | diff --git a/docs/ai/AI_AGENT_INTERACTION_LEARNING_PROOF_2026-06-11.md b/docs/ai/AI_AGENT_INTERACTION_LEARNING_PROOF_2026-06-11.md index 2b605a19..cde541d9 100644 --- a/docs/ai/AI_AGENT_INTERACTION_LEARNING_PROOF_2026-06-11.md +++ b/docs/ai/AI_AGENT_INTERACTION_LEARNING_PROOF_2026-06-11.md @@ -1,8 +1,8 @@ # AI Agent 互動、溝通、學習與成長證據報告 > 日期:2026-06-11(台北時間) -> 文件定位:P2-403A 證據面 + P2-403B AgentSession / Redis Streams live read model gate + P2-403C Redis dry-run gate + P2-403D learning writeback approval package + P2-403E Telegram receipt approval package + P2-403F owner-approved learning dry-run / fixture dry-run、P2-403G runtime write gate review、P2-403H post-write verifier package、P2-403I runtime verifier evidence implementation review、P2-403J 報表真相 / 日週月報 / Agent 工作量 / 風險自動化 review、P2-403L 報表派送與自動處理啟動前閘門、P2-403M 報表 runtime no-write dry-run 證據包、P2-403N fixture smoke / queue preview readback / verifier dry-run、P2-404 runtime worker shadow / no-write execution evidence gate、API 與治理頁 UI。 -> 事實邊界:本波只建立可見證據面、read model gate、報表治理 review、runtime readiness gate、no-write dry-run、fixture/readback/verifier dry-run 與 shadow/no-write execution 證據包,不啟動 runtime worker、不建立 DB migration、不開 Redis consumer group、不發 Telegram、不寫 Gateway queue、不寫 delivery receipt、不排程實發報告、不啟動中低風險 auto worker、不執行 verifier live readback、不執行生產優化、不顯示工作視窗對話內容。 +> 文件定位:P2-403A 證據面 + P2-403B AgentSession / Redis Streams live read model gate + P2-403C Redis dry-run gate + P2-403D learning writeback approval package + P2-403E Telegram receipt approval package + P2-403F owner-approved learning dry-run / fixture dry-run、P2-403G runtime write gate review、P2-403H post-write verifier package、P2-403I runtime verifier evidence implementation review、P2-403J 報表真相 / 日週月報 / Agent 工作量 / 風險自動化 review、P2-403L 報表派送與自動處理啟動前閘門、P2-403M 報表 runtime no-write dry-run 證據包、P2-403N fixture smoke / queue preview readback / verifier dry-run、P2-404 runtime worker shadow / no-write execution evidence gate、P2-101 操作類別權限模型、API 與治理頁 UI。 +> 事實邊界:本波只建立可見證據面、read model gate、報表治理 review、runtime readiness gate、no-write dry-run、fixture/readback/verifier dry-run、shadow/no-write execution 證據包與 operation permission lane,不啟動 runtime worker、不建立 DB migration、不開 Redis consumer group、不發 Telegram、不寫 Gateway queue、不寫 delivery receipt、不排程實發報告、不啟動中低風險 auto worker、不執行 verifier live readback、不執行生產優化、不顯示工作視窗對話內容。 ## 0. P2-403J 補記:報表真相、日週月報與風險自動化 Review @@ -34,9 +34,15 @@ 本段把 P2-403N fixture/readback/verifier dry-run promotion 成 shadow worker no-write 證據:5 個 shadow candidate、4 個 no-write replay、4 個 verifier shadow case、3 個 Agent shadow role 與 6 個 operator checkpoint。Hermes 負責日週月報與 SRE 戰情室 queue candidate preview;OpenClaw 負責中低風險 no-op action 分類與 P2-101 操作權限模型 handoff;NemoTron 負責 shadow candidate 的 verifier binding、failure lane 與 no-write replay hash。本段仍不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 delivery receipt、不啟動 live AI runtime worker、不啟動中低風險 auto worker、不跑 verifier live readback、不讀 secret,所有 live count 仍為 `0`。 +## 0.5 P2-101 補記:操作類別權限模型 + +2026-06-12 已新增 P2-101:`ai_agent_operation_permission_model_v1`、`docs/evaluations/ai_agent_operation_permission_model_2026-06-12.json`、`GET /api/v1/agents/agent-operation-permission-model` 與治理頁區塊。 + +本段把 P2-404 的 shadow/no-write handoff 轉成 5 條 permission lane、13 類 operation category、3 個 Agent permission role、8 個 gate transition 與 5 個 operator decision template。OpenClaw 負責操作類別、風險層級、approval lane 與修復候選仲裁;Hermes 負責 KM / Runbook / 報表 / SRE 戰情室 queue candidate 草案;NemoTron 負責 no-write replay、verifier fixture、redaction / cost / secret boundary review。本段仍不啟動 runtime worker、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 delivery receipt、不跑 verifier live readback、不寫 production target、不讀 secret、不呼叫付費 provider、不執行 host / cluster / destructive action,所有 live count 仍為 `0`。 + ## 1. 結論 -已完成 P2-403A、P2-403B、P2-403C、P2-403D、P2-403E、P2-403F、P2-403G、P2-403H、P2-403I、P2-403J、P2-403L、P2-403M、P2-403N 與 P2-404:讓統帥能在治理頁看到 OpenClaw / Hermes / NemoTron 的互動、接手、學習與成長是否真的有證據,並看到 live read model、Redis dry-run、handoff envelope、ack / dead-letter / replay、learning writeback approval、Telegram receipt approval、fixture dry-run、runtime write gate review、post-write verifier package、runtime verifier evidence review、報表真相、日週月報、Agent 工作量、圖表化報告、風險自動化政策、報表 runtime 啟動前閘門、no-write dry-run 證據包、fixture/readback/verifier dry-run 證據包與 shadow/no-write execution evidence gate 下一步要通過哪些 gate。 +已完成 P2-403A、P2-403B、P2-403C、P2-403D、P2-403E、P2-403F、P2-403G、P2-403H、P2-403I、P2-403J、P2-403L、P2-403M、P2-403N、P2-404 與 P2-101:讓統帥能在治理頁看到 OpenClaw / Hermes / NemoTron 的互動、接手、學習與成長是否真的有證據,並看到 live read model、Redis dry-run、handoff envelope、ack / dead-letter / replay、learning writeback approval、Telegram receipt approval、fixture dry-run、runtime write gate review、post-write verifier package、runtime verifier evidence review、報表真相、日週月報、Agent 工作量、圖表化報告、風險自動化政策、報表 runtime 啟動前閘門、no-write dry-run 證據包、fixture/readback/verifier dry-run 證據包、shadow/no-write execution evidence gate 與操作類別權限模型下一步要通過哪些 gate。 目前真相: @@ -59,6 +65,7 @@ | P2-403M no-write dry-run package | 已完成,正式寫入 / 發送 / worker / verifier live readback 全為 `0` | | P2-403N fixture readback package | 已完成,queue write / Telegram send / Bot API / worker / verifier live readback 全為 `0` | | P2-404 runtime worker shadow gate | 已完成,shadow worker live / queue write / Telegram send / production write 全為 `0` | +| P2-101 operation permission model | 已完成,13 類操作已歸入只讀 / no-write replay / 提案 / 人工批准 / 明確阻擋,runtime execution / queue write / Telegram send / production write 全為 `0` | 這代表使用者現在可以看見「哪裡已準備好、哪裡仍未運作、被哪個 gate 阻擋、下一步要如何驗證」。但還不能宣稱三個 Agent 已經在 production runtime 主動互傳訊息或自主學習。 @@ -120,17 +127,20 @@ | `docs/schemas/ai_agent_runtime_worker_shadow_gate_v1.schema.json` | P2-404 runtime worker shadow / no-write execution evidence gate schema;強制 shadow live worker、queue write、Telegram send、Bot API、receipt write、auto worker、verifier live readback、production write 與 secret read 維持未授權 | | `docs/evaluations/ai_agent_runtime_worker_shadow_gate_2026-06-12.json` | P2-404 committed snapshot,完成度 `96%`,5 個 shadow candidate、4 個 no-write replay、4 個 verifier shadow case、3 個 Agent shadow role、6 個 operator checkpoint;所有 live counts 全為 `0` | | `GET /api/v1/agents/agent-runtime-worker-shadow-gate` | 只讀 API;不啟動 live worker、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 production target | +| `docs/schemas/ai_agent_operation_permission_model_v1.schema.json` | P2-101 操作類別權限模型 schema;強制 runtime execution、queue write、Telegram send、Bot API、receipt write、auto worker、verifier live readback、production write、secret / paid provider、host command 與 destructive action 維持未授權 | +| `docs/evaluations/ai_agent_operation_permission_model_2026-06-12.json` | P2-101 committed snapshot,完成度 `97%`,5 條 permission lane、13 類 operation category、3 個 Agent permission role、8 個 gate transition、5 個 operator decision template;所有 live counts 全為 `0` | +| `GET /api/v1/agents/agent-operation-permission-model` | 只讀 API;不啟動 runtime worker、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 production target、不讀 secret | | `apps/api/src/services/ai_agent_interaction_learning_proof.py` | 只讀 loader 與安全驗證 | | `apps/api/src/services/ai_agent_live_read_model_gate.py` | P2-403B 只讀 loader;拒絕 live DB query、Redis consumer、unsafe fields、Telegram 與 writeback | | `GET /api/v1/agents/agent-interaction-learning-proof` | 只讀 API,不啟動 worker、不碰 Redis / DB runtime、不發 Telegram | | `GET /api/v1/agents/agent-live-read-model-gate` | 只讀 API,不連 DB、不讀寫 Redis、不發 Telegram | -| governance UI | 新增證據階梯、目前真相、P2-403B live read gate、P2-403C Redis dry-run gate、P2-403D learning writeback approval package、P2-403E Telegram receipt approval package、P2-403F owner-approved learning dry-run / fixture dry-run、P2-403G runtime write gate review、P2-403H post-write verifier package、P2-403I runtime verifier evidence review、P2-403J 報表真相 / 日週月報 / Agent 工作量 / 圖表 / AI 建議、P2-403L 報表 runtime readiness、P2-403M no-write dry-run、P2-403N fixture readback、P2-404 shadow gate、Agent lane、可觀測訊號、runtime gates、前端 redaction | +| governance UI | 新增證據階梯、目前真相、P2-403B live read gate、P2-403C Redis dry-run gate、P2-403D learning writeback approval package、P2-403E Telegram receipt approval package、P2-403F owner-approved learning dry-run / fixture dry-run、P2-403G runtime write gate review、P2-403H post-write verifier package、P2-403I runtime verifier evidence review、P2-403J 報表真相 / 日週月報 / Agent 工作量 / 圖表 / AI 建議、P2-403L 報表 runtime readiness、P2-403M no-write dry-run、P2-403N fixture readback、P2-404 shadow gate、P2-101 operation permission model、Agent lane、可觀測訊號、runtime gates、前端 redaction | ## 5. 後續優先順序 | 優先 | ID | 工作 | gate | |---:|---|---|---| -| 1 | P2-101 | 定義操作類別權限模型 | HITL 關卡 | +| 1 | P2-102 | 所有候選操作都要有 dry-run 證據 | dry-run 合約 | ## 6. 紅線 diff --git a/docs/evaluations/ai_agent_operation_permission_model_2026-06-12.json b/docs/evaluations/ai_agent_operation_permission_model_2026-06-12.json new file mode 100644 index 00000000..ebe75e10 --- /dev/null +++ b/docs/evaluations/ai_agent_operation_permission_model_2026-06-12.json @@ -0,0 +1,543 @@ +{ + "schema_version": "ai_agent_operation_permission_model_v1", + "generated_at": "2026-06-12T15:40:00+08:00", + "program_status": { + "overall_completion_percent": 97, + "current_priority": "P2", + "current_task_id": "P2-101", + "next_task_id": "P2-102", + "read_only_mode": true, + "runtime_authority": "operation_permission_model_only_no_live_execution_or_send", + "status_note": "P2-101 把 P2-404 shadow/no-write candidate 轉成操作類別權限模型;目前只定義 observe、no-write replay、proposal、人工批准與明確阻擋 lane,不啟動 live worker、不寫 Gateway queue、不送 Telegram、不寫 production target。" + }, + "source_refs": [ + "docs/evaluations/ai_agent_runtime_worker_shadow_gate_2026-06-12.json", + "docs/evaluations/ai_agent_report_runtime_fixture_readback_2026-06-12.json", + "docs/evaluations/ai_agent_report_runtime_dry_run_2026-06-12.json", + "docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md", + "docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md" + ], + "operation_permission_truth": { + "permission_model_ready": true, + "operation_category_matrix_ready": true, + "risk_tier_mapping_ready": true, + "agent_responsibility_mapping_ready": true, + "approval_gate_mapping_ready": true, + "manual_sop_lane_ready": true, + "p2_404_shadow_gate_handoff_ready": true, + "runtime_execution_enabled": false, + "gateway_queue_write_enabled": false, + "telegram_send_enabled": false, + "telegram_bot_api_call_enabled": false, + "delivery_receipt_write_enabled": false, + "ai_runtime_worker_enabled": false, + "medium_low_auto_worker_enabled": false, + "post_action_verifier_live_readback_enabled": false, + "production_write_enabled": false, + "secret_value_read_enabled": false, + "paid_provider_call_enabled": false, + "host_or_cluster_command_enabled": false, + "destructive_operation_enabled": false, + "work_window_transcript_display_allowed": false, + "runtime_execution_count_24h": 0, + "gateway_queue_write_count_24h": 0, + "telegram_send_count_24h": 0, + "telegram_bot_api_call_count_24h": 0, + "delivery_receipt_write_count_24h": 0, + "ai_runtime_worker_run_count_24h": 0, + "medium_low_auto_execution_count_24h": 0, + "post_action_verifier_live_readback_count_24h": 0, + "production_write_count_24h": 0, + "secret_value_read_count_24h": 0, + "paid_provider_call_count_24h": 0, + "host_or_cluster_command_count_24h": 0, + "destructive_operation_count_24h": 0, + "truth_note": "P2-101 只建立操作類別與權限 lane;所有實際執行、queue write、Telegram send、Bot API、receipt write、worker、verifier live readback、production write、secret / paid provider access 與 destructive action 仍保持 0。" + }, + "permission_lanes": [ + { + "lane_id": "observe_only", + "display_name": "只讀觀察", + "summary": "可自動讀取 committed snapshot、已允許的 redacted API、健康摘要與證據鏈;不得寫入或觸發外部副作用。", + "allowed_outputs": ["證據摘要", "狀態分類", "治理頁顯示"], + "required_gate_before_promotion": "P2-102 dry-run evidence gate", + "live_execution_allowed": false, + "production_write_allowed": false + }, + { + "lane_id": "no_write_replay_allowed", + "display_name": "no-write replay", + "summary": "可重放 fixture / shadow artifact 並產生 diff、hash、verifier input;不得寫 Gateway queue 或正式資料。", + "allowed_outputs": ["redacted diff", "evidence hash", "queue candidate preview"], + "required_gate_before_promotion": "P2-102 dry-run evidence gate + verifier binding", + "live_execution_allowed": false, + "production_write_allowed": false + }, + { + "lane_id": "proposal_only", + "display_name": "提案 / SOP", + "summary": "可產生人工可審核的處置建議、Runbook 草案、rollback 計畫與操作選項;不得自行套用。", + "allowed_outputs": ["人工 SOP", "修復候選", "rollback 草案"], + "required_gate_before_promotion": "owner review + P2-102 dry-run evidence", + "live_execution_allowed": false, + "production_write_allowed": false + }, + { + "lane_id": "human_approval_required", + "display_name": "人工批准後才可推進", + "summary": "涉及 queue write、Telegram 實發、verifier live readback、低中風險自動化與任何可能影響外部狀態的操作,必須先人工批准並有 verifier / rollback。", + "allowed_outputs": ["批准包", "dry-run hash", "verifier plan"], + "required_gate_before_promotion": "explicit owner approval + rollback owner + post-action verifier", + "live_execution_allowed": false, + "production_write_allowed": false + }, + { + "lane_id": "explicitly_blocked", + "display_name": "明確阻擋", + "summary": "secret 明文、付費 provider 擴量、破壞性主機 / 叢集操作與 production config / data write 在本階段全部阻擋,只能降級成提案或證據收集。", + "allowed_outputs": ["阻擋原因", "降級提案", "缺口清單"], + "required_gate_before_promotion": "另行維護窗口、費用 / 依賴 / 資安批准", + "live_execution_allowed": false, + "production_write_allowed": false + } + ], + "operation_categories": [ + { + "category_id": "observe_inventory_read", + "display_name": "只讀盤點 / 狀態讀取", + "risk_tier": "low", + "permission_lane": "observe_only", + "primary_agent": "hermes", + "allowed_outputs": ["狀態摘要", "freshness 判讀", "缺口清單"], + "blocked_actions": ["production write", "secret value read"], + "required_evidence": ["source ref", "generated_at", "redacted fields"], + "next_gate": "P2-102 dry-run evidence gate", + "queue_write_allowed": false, + "telegram_send_allowed": false, + "production_write_allowed": false, + "secret_value_read_allowed": false, + "destructive_action_allowed": false, + "live_execution_allowed": false, + "evidence_hash": "sha256:1111111111111111111111111111111111111111111111111111111111111111" + }, + { + "category_id": "diagnose_correlate_evidence", + "display_name": "診斷 / 證據關聯", + "risk_tier": "low", + "permission_lane": "observe_only", + "primary_agent": "openclaw", + "allowed_outputs": ["嚴重度判讀", "MCP evidence 摘要", "manual next step"], + "blocked_actions": ["repair execution", "queue write"], + "required_evidence": ["MCP evidence ref", "incident ref", "correlation reason"], + "next_gate": "P2-102 dry-run evidence gate", + "queue_write_allowed": false, + "telegram_send_allowed": false, + "production_write_allowed": false, + "secret_value_read_allowed": false, + "destructive_action_allowed": false, + "live_execution_allowed": false, + "evidence_hash": "sha256:2222222222222222222222222222222222222222222222222222222222222222" + }, + { + "category_id": "report_digest_queue_candidate", + "display_name": "報表 / 戰情室 queue candidate", + "risk_tier": "medium", + "permission_lane": "no_write_replay_allowed", + "primary_agent": "hermes", + "allowed_outputs": ["queue candidate diff", "digest preview", "readback checklist"], + "blocked_actions": ["Gateway queue write", "Telegram sendMessage"], + "required_evidence": ["redacted digest hash", "recipient route ref", "zero-signal classification"], + "next_gate": "gateway_queue_write_permission_gate", + "queue_write_allowed": false, + "telegram_send_allowed": false, + "production_write_allowed": false, + "secret_value_read_allowed": false, + "destructive_action_allowed": false, + "live_execution_allowed": false, + "evidence_hash": "sha256:3333333333333333333333333333333333333333333333333333333333333333" + }, + { + "category_id": "shadow_no_write_replay", + "display_name": "shadow / no-write replay", + "risk_tier": "medium", + "permission_lane": "no_write_replay_allowed", + "primary_agent": "nemotron", + "allowed_outputs": ["promotion hash", "verifier fixture", "side-effect report"], + "blocked_actions": ["live worker", "result write"], + "required_evidence": ["fixture id", "replay hash", "side-effect count"], + "next_gate": "P2-102 dry-run evidence gate", + "queue_write_allowed": false, + "telegram_send_allowed": false, + "production_write_allowed": false, + "secret_value_read_allowed": false, + "destructive_action_allowed": false, + "live_execution_allowed": false, + "evidence_hash": "sha256:4444444444444444444444444444444444444444444444444444444444444444" + }, + { + "category_id": "manual_sop_draft", + "display_name": "人工 SOP / 操作選項草案", + "risk_tier": "medium", + "permission_lane": "proposal_only", + "primary_agent": "hermes", + "allowed_outputs": ["人工步驟", "驗證步驟", "rollback 說明"], + "blocked_actions": ["自動執行", "host command"], + "required_evidence": ["owner role", "affected scope", "rollback owner"], + "next_gate": "owner review", + "queue_write_allowed": false, + "telegram_send_allowed": false, + "production_write_allowed": false, + "secret_value_read_allowed": false, + "destructive_action_allowed": false, + "live_execution_allowed": false, + "evidence_hash": "sha256:5555555555555555555555555555555555555555555555555555555555555555" + }, + { + "category_id": "repair_candidate_proposal", + "display_name": "修復候選提案", + "risk_tier": "medium", + "permission_lane": "proposal_only", + "primary_agent": "openclaw", + "allowed_outputs": ["修復候選", "風險理由", "dry-run 要求"], + "blocked_actions": ["Ansible apply", "kubectl apply", "service restart"], + "required_evidence": ["MCP evidence", "PlayBook trust", "verifier plan"], + "next_gate": "P2-102 dry-run evidence gate", + "queue_write_allowed": false, + "telegram_send_allowed": false, + "production_write_allowed": false, + "secret_value_read_allowed": false, + "destructive_action_allowed": false, + "live_execution_allowed": false, + "evidence_hash": "sha256:6666666666666666666666666666666666666666666666666666666666666666" + }, + { + "category_id": "low_risk_noop_execution", + "display_name": "低風險 no-op 自動處理", + "risk_tier": "medium", + "permission_lane": "human_approval_required", + "primary_agent": "openclaw", + "allowed_outputs": ["批准包", "no-op plan", "post-action verifier plan"], + "blocked_actions": ["live worker execution"], + "required_evidence": ["explicit approval", "dry-run hash", "replay hash"], + "next_gate": "medium_low_auto_worker_permission_gate", + "queue_write_allowed": false, + "telegram_send_allowed": false, + "production_write_allowed": false, + "secret_value_read_allowed": false, + "destructive_action_allowed": false, + "live_execution_allowed": false, + "evidence_hash": "sha256:7777777777777777777777777777777777777777777777777777777777777777" + }, + { + "category_id": "medium_risk_repair_execution", + "display_name": "中風險修復執行", + "risk_tier": "high", + "permission_lane": "human_approval_required", + "primary_agent": "openclaw", + "allowed_outputs": ["批准包", "rollback plan", "維護窗口建議"], + "blocked_actions": ["repair apply", "restart", "config reload"], + "required_evidence": ["owner approval", "rollback owner", "verifier live gate"], + "next_gate": "production_write_permission_gate", + "queue_write_allowed": false, + "telegram_send_allowed": false, + "production_write_allowed": false, + "secret_value_read_allowed": false, + "destructive_action_allowed": false, + "live_execution_allowed": false, + "evidence_hash": "sha256:8888888888888888888888888888888888888888888888888888888888888888" + }, + { + "category_id": "post_action_verifier_live_readback", + "display_name": "post-action verifier live readback", + "risk_tier": "high", + "permission_lane": "human_approval_required", + "primary_agent": "nemotron", + "allowed_outputs": ["verifier plan", "expected signal", "failure lane"], + "blocked_actions": ["canonical target readback", "result write"], + "required_evidence": ["allow-list", "redacted output", "failure receipt plan"], + "next_gate": "post_action_verifier_live_gate", + "queue_write_allowed": false, + "telegram_send_allowed": false, + "production_write_allowed": false, + "secret_value_read_allowed": false, + "destructive_action_allowed": false, + "live_execution_allowed": false, + "evidence_hash": "sha256:9999999999999999999999999999999999999999999999999999999999999999" + }, + { + "category_id": "telegram_gateway_queue_write", + "display_name": "Telegram Gateway queue write / 實發", + "risk_tier": "high", + "permission_lane": "human_approval_required", + "primary_agent": "hermes", + "allowed_outputs": ["route approval packet", "queue candidate", "delivery verifier plan"], + "blocked_actions": ["Gateway queue write", "Bot API sendMessage"], + "required_evidence": ["SRE route ref", "dedupe key", "delivery receipt plan"], + "next_gate": "telegram_send_permission_gate", + "queue_write_allowed": false, + "telegram_send_allowed": false, + "production_write_allowed": false, + "secret_value_read_allowed": false, + "destructive_action_allowed": false, + "live_execution_allowed": false, + "evidence_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + { + "category_id": "production_config_or_data_write", + "display_name": "production config / data write", + "risk_tier": "critical", + "permission_lane": "explicitly_blocked", + "primary_agent": "openclaw", + "allowed_outputs": ["blocked reason", "owner approval requirements", "dry-run requirement"], + "blocked_actions": ["DB write", "ConfigMap apply", "Nginx reload", "workflow mutation"], + "required_evidence": ["source-of-truth", "rollback ref", "maintenance window"], + "next_gate": "production_write_permission_gate", + "queue_write_allowed": false, + "telegram_send_allowed": false, + "production_write_allowed": false, + "secret_value_read_allowed": false, + "destructive_action_allowed": false, + "live_execution_allowed": false, + "evidence_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + { + "category_id": "secret_or_paid_provider_access", + "display_name": "secret value / paid provider access", + "risk_tier": "critical", + "permission_lane": "explicitly_blocked", + "primary_agent": "nemotron", + "allowed_outputs": ["metadata-only checklist", "cost approval packet", "secret name parity"], + "blocked_actions": ["secret value read", "paid API call", "token hash collection"], + "required_evidence": ["費用批准", "redaction policy", "owner response"], + "next_gate": "secret_or_paid_provider_gate", + "queue_write_allowed": false, + "telegram_send_allowed": false, + "production_write_allowed": false, + "secret_value_read_allowed": false, + "destructive_action_allowed": false, + "live_execution_allowed": false, + "evidence_hash": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + }, + { + "category_id": "destructive_host_or_cluster_action", + "display_name": "破壞性主機 / 叢集操作", + "risk_tier": "critical", + "permission_lane": "explicitly_blocked", + "primary_agent": "openclaw", + "allowed_outputs": ["break-glass checklist", "rollback owner request", "maintenance window request"], + "blocked_actions": ["ssh write", "sudo", "restart", "reboot", "delete", "active scan"], + "required_evidence": ["維護窗口", "rollback owner", "post-check plan"], + "next_gate": "另行資安 / 維護窗口批准", + "queue_write_allowed": false, + "telegram_send_allowed": false, + "production_write_allowed": false, + "secret_value_read_allowed": false, + "destructive_action_allowed": false, + "live_execution_allowed": false, + "evidence_hash": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + } + ], + "agent_permission_roles": [ + { + "agent_id": "openclaw", + "display_name": "OpenClaw", + "permission_responsibility": "負責 operation category、risk tier、approval lane 與 repair candidate arbitration;可產生提案,不可自我批准或執行。", + "allowed_lanes": ["observe_only", "proposal_only", "human_approval_required"], + "blocked_now": ["self approval", "production write", "host command", "Telegram direct send"], + "self_approval_allowed": false, + "live_action_count_24h": 0 + }, + { + "agent_id": "hermes", + "display_name": "Hermes", + "permission_responsibility": "負責 KM / Runbook / 報表 / SRE 戰情室 queue candidate 的整理與草案;不得寫 Gateway queue 或發 Telegram。", + "allowed_lanes": ["observe_only", "no_write_replay_allowed", "proposal_only"], + "blocked_now": ["Gateway queue write", "Telegram send", "production data write"], + "self_approval_allowed": false, + "live_action_count_24h": 0 + }, + { + "agent_id": "nemotron", + "display_name": "NemoTron", + "permission_responsibility": "負責 no-write replay、verifier fixture、redaction / cost / secret boundary review;不得呼叫付費 provider 或讀 secret。", + "allowed_lanes": ["observe_only", "no_write_replay_allowed", "proposal_only"], + "blocked_now": ["paid provider call", "secret value read", "live verifier execution"], + "self_approval_allowed": false, + "live_action_count_24h": 0 + } + ], + "gate_transitions": [ + { + "gate_id": "p2_101_permission_review_gate", + "display_name": "P2-101 permission review gate", + "current_status": "ready_for_review", + "required_before": "所有 P2-404 shadow candidate promotion", + "next_safe_step": "以本模型審查每個 candidate 的 lane 與風險,不啟動 worker。", + "opens_live_execution": false + }, + { + "gate_id": "p2_102_dry_run_evidence_gate", + "display_name": "P2-102 dry-run evidence gate", + "current_status": "blocked_until_evidence", + "required_before": "任何可被批准的低 / 中風險 candidate", + "next_safe_step": "每個候選操作都要有 dry-run evidence、hash、side-effect count 與 verifier plan。", + "opens_live_execution": false + }, + { + "gate_id": "gateway_queue_write_permission_gate", + "display_name": "Gateway queue write permission gate", + "current_status": "blocked_until_evidence", + "required_before": "任何 Gateway queue write", + "next_safe_step": "先完成 route、dedupe、receipt、failure lane 與人工批准。", + "opens_live_execution": false + }, + { + "gate_id": "telegram_send_permission_gate", + "display_name": "Telegram send permission gate", + "current_status": "blocked_until_evidence", + "required_before": "任何 Bot API sendMessage", + "next_safe_step": "先走 Gateway queue + receipt E2E,不允許 direct Bot API。", + "opens_live_execution": false + }, + { + "gate_id": "medium_low_auto_worker_permission_gate", + "display_name": "中低風險 auto worker permission gate", + "current_status": "blocked_until_evidence", + "required_before": "任何 low / medium risk 自動處理", + "next_safe_step": "需要 operation lane、dry-run、rollback/no-op evidence、post-action verifier 與人工批准。", + "opens_live_execution": false + }, + { + "gate_id": "post_action_verifier_live_gate", + "display_name": "post-action verifier live gate", + "current_status": "blocked_until_evidence", + "required_before": "任何 verifier live readback", + "next_safe_step": "先定義 input/output allow-list、redaction、failure receipt 與 result write boundary。", + "opens_live_execution": false + }, + { + "gate_id": "production_write_permission_gate", + "display_name": "production write permission gate", + "current_status": "blocked_by_policy", + "required_before": "任何 production config / data write", + "next_safe_step": "另行 owner approval、maintenance window、rollback owner、dry-run、verifier plan。", + "opens_live_execution": false + }, + { + "gate_id": "secret_or_paid_provider_gate", + "display_name": "secret / paid provider gate", + "current_status": "blocked_by_policy", + "required_before": "任何 secret 明文、付費 provider 呼叫或費用增加", + "next_safe_step": "只允許 metadata / secret name / cost approval packet;不得讀 value 或增加呼叫頻率。", + "opens_live_execution": false + } + ], + "operator_decision_templates": [ + { + "template_id": "evidence_collect_next_step", + "display_name": "收集證據", + "when_to_use": "分類、監控、MCP evidence 不足或 provider fresh 但未匹配時。", + "human_instruction": "查看來源、補 MCP evidence、確認 incident / run / source refs 是否串上。", + "creates_runtime_action": false, + "requires_human_review": true + }, + { + "template_id": "manual_sop_next_step", + "display_name": "產生人工 SOP", + "when_to_use": "AI 選擇不執行修復,或 action candidate 缺少 rollback / verifier。", + "human_instruction": "依 SOP 檢查服務、log、回滾點與驗證項,完成後回填 outcome。", + "creates_runtime_action": false, + "requires_human_review": true + }, + { + "template_id": "repair_proposal_next_step", + "display_name": "修復提案", + "when_to_use": "OpenClaw 有中低風險候選,但尚未有 dry-run hash 或 verifier plan。", + "human_instruction": "審查 MCP evidence、PlayBook trust、dry-run、rollback owner 與 blast radius。", + "creates_runtime_action": false, + "requires_human_review": true + }, + { + "template_id": "queue_candidate_next_step", + "display_name": "戰情室 queue 候選", + "when_to_use": "日週月報、action-required digest 或 failure digest 需要進 SRE 戰情室但尚未准許實發。", + "human_instruction": "確認 route、dedupe、receipt、降噪與內容 redaction,再決定是否進下一 gate。", + "creates_runtime_action": false, + "requires_human_review": true + }, + { + "template_id": "rollback_or_fix_next_step", + "display_name": "人工修復 / 回滾", + "when_to_use": "處置失敗、verifier degraded、或高風險需要人工處理。", + "human_instruction": "依 rollback owner 與維護窗口處理;禁止由 AI 自行啟動 host / cluster / production write。", + "creates_runtime_action": false, + "requires_human_review": true + } + ], + "display_redaction_contract": { + "redaction_required": true, + "raw_prompt_display_allowed": false, + "private_reasoning_display_allowed": false, + "secret_value_display_allowed": false, + "raw_telegram_payload_display_allowed": false, + "work_window_transcript_display_allowed": false, + "allowed_display_fields": [ + "category_id", + "display_name", + "risk_tier", + "permission_lane", + "primary_agent", + "allowed_outputs", + "blocked_actions", + "required_evidence", + "next_gate", + "evidence_hash" + ], + "blocked_display_fields": [ + "raw_prompt", + "chain_of_thought", + "telegram_chat_id", + "telegram_message_id", + "bot_token", + "authorization_header", + "secret_value", + "work_window_transcript" + ] + }, + "rollups": { + "permission_lane_count": 5, + "operation_category_count": 13, + "observe_only_category_count": 2, + "no_write_replay_allowed_category_count": 2, + "proposal_only_category_count": 2, + "human_approval_required_category_count": 4, + "explicitly_blocked_category_count": 3, + "human_approval_required_category_ids": [ + "low_risk_noop_execution", + "medium_risk_repair_execution", + "post_action_verifier_live_readback", + "telegram_gateway_queue_write" + ], + "explicitly_blocked_category_ids": [ + "destructive_host_or_cluster_action", + "production_config_or_data_write", + "secret_or_paid_provider_access" + ], + "agent_role_count": 3, + "gate_transition_count": 8, + "operator_decision_template_count": 5, + "runtime_execution_count": 0, + "gateway_queue_write_count": 0, + "telegram_send_count": 0, + "telegram_bot_api_call_count": 0, + "delivery_receipt_write_count": 0, + "ai_runtime_worker_run_count": 0, + "medium_low_auto_execution_count": 0, + "post_action_verifier_live_readback_count": 0, + "production_write_count": 0, + "secret_value_read_count": 0, + "paid_provider_call_count": 0, + "host_or_cluster_command_count": 0, + "destructive_operation_count": 0 + } +} diff --git a/docs/schemas/ai_agent_operation_permission_model_v1.schema.json b/docs/schemas/ai_agent_operation_permission_model_v1.schema.json new file mode 100644 index 00000000..e817be08 --- /dev/null +++ b/docs/schemas/ai_agent_operation_permission_model_v1.schema.json @@ -0,0 +1,370 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://awoooi.wooo.work/schemas/ai_agent_operation_permission_model_v1.schema.json", + "title": "AI Agent Operation Permission Model v1", + "type": "object", + "required": [ + "schema_version", + "generated_at", + "program_status", + "source_refs", + "operation_permission_truth", + "permission_lanes", + "operation_categories", + "agent_permission_roles", + "gate_transitions", + "operator_decision_templates", + "display_redaction_contract", + "rollups" + ], + "properties": { + "schema_version": { "const": "ai_agent_operation_permission_model_v1" }, + "generated_at": { "type": "string" }, + "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": { "enum": ["P0", "P1", "P2", "P3"] }, + "current_task_id": { "const": "P2-101" }, + "next_task_id": { "const": "P2-102" }, + "read_only_mode": { "const": true }, + "runtime_authority": { "const": "operation_permission_model_only_no_live_execution_or_send" }, + "status_note": { "type": "string" } + }, + "additionalProperties": false + }, + "source_refs": { "type": "array", "items": { "type": "string" }, "minItems": 1 }, + "operation_permission_truth": { + "type": "object", + "required": [ + "permission_model_ready", + "operation_category_matrix_ready", + "risk_tier_mapping_ready", + "agent_responsibility_mapping_ready", + "approval_gate_mapping_ready", + "manual_sop_lane_ready", + "p2_404_shadow_gate_handoff_ready", + "runtime_execution_enabled", + "gateway_queue_write_enabled", + "telegram_send_enabled", + "telegram_bot_api_call_enabled", + "delivery_receipt_write_enabled", + "ai_runtime_worker_enabled", + "medium_low_auto_worker_enabled", + "post_action_verifier_live_readback_enabled", + "production_write_enabled", + "secret_value_read_enabled", + "paid_provider_call_enabled", + "host_or_cluster_command_enabled", + "destructive_operation_enabled", + "work_window_transcript_display_allowed", + "runtime_execution_count_24h", + "gateway_queue_write_count_24h", + "telegram_send_count_24h", + "telegram_bot_api_call_count_24h", + "delivery_receipt_write_count_24h", + "ai_runtime_worker_run_count_24h", + "medium_low_auto_execution_count_24h", + "post_action_verifier_live_readback_count_24h", + "production_write_count_24h", + "secret_value_read_count_24h", + "paid_provider_call_count_24h", + "host_or_cluster_command_count_24h", + "destructive_operation_count_24h", + "truth_note" + ], + "properties": { + "permission_model_ready": { "const": true }, + "operation_category_matrix_ready": { "const": true }, + "risk_tier_mapping_ready": { "const": true }, + "agent_responsibility_mapping_ready": { "const": true }, + "approval_gate_mapping_ready": { "const": true }, + "manual_sop_lane_ready": { "const": true }, + "p2_404_shadow_gate_handoff_ready": { "const": true }, + "runtime_execution_enabled": { "const": false }, + "gateway_queue_write_enabled": { "const": false }, + "telegram_send_enabled": { "const": false }, + "telegram_bot_api_call_enabled": { "const": false }, + "delivery_receipt_write_enabled": { "const": false }, + "ai_runtime_worker_enabled": { "const": false }, + "medium_low_auto_worker_enabled": { "const": false }, + "post_action_verifier_live_readback_enabled": { "const": false }, + "production_write_enabled": { "const": false }, + "secret_value_read_enabled": { "const": false }, + "paid_provider_call_enabled": { "const": false }, + "host_or_cluster_command_enabled": { "const": false }, + "destructive_operation_enabled": { "const": false }, + "work_window_transcript_display_allowed": { "const": false }, + "runtime_execution_count_24h": { "const": 0 }, + "gateway_queue_write_count_24h": { "const": 0 }, + "telegram_send_count_24h": { "const": 0 }, + "telegram_bot_api_call_count_24h": { "const": 0 }, + "delivery_receipt_write_count_24h": { "const": 0 }, + "ai_runtime_worker_run_count_24h": { "const": 0 }, + "medium_low_auto_execution_count_24h": { "const": 0 }, + "post_action_verifier_live_readback_count_24h": { "const": 0 }, + "production_write_count_24h": { "const": 0 }, + "secret_value_read_count_24h": { "const": 0 }, + "paid_provider_call_count_24h": { "const": 0 }, + "host_or_cluster_command_count_24h": { "const": 0 }, + "destructive_operation_count_24h": { "const": 0 }, + "truth_note": { "type": "string" } + }, + "additionalProperties": false + }, + "permission_lanes": { + "type": "array", + "minItems": 5, + "items": { + "type": "object", + "required": [ + "lane_id", + "display_name", + "summary", + "allowed_outputs", + "required_gate_before_promotion", + "live_execution_allowed", + "production_write_allowed" + ], + "properties": { + "lane_id": { + "enum": [ + "observe_only", + "no_write_replay_allowed", + "proposal_only", + "human_approval_required", + "explicitly_blocked" + ] + }, + "display_name": { "type": "string" }, + "summary": { "type": "string" }, + "allowed_outputs": { "type": "array", "items": { "type": "string" } }, + "required_gate_before_promotion": { "type": "string" }, + "live_execution_allowed": { "const": false }, + "production_write_allowed": { "const": false } + }, + "additionalProperties": false + } + }, + "operation_categories": { + "type": "array", + "minItems": 13, + "items": { + "type": "object", + "required": [ + "category_id", + "display_name", + "risk_tier", + "permission_lane", + "primary_agent", + "allowed_outputs", + "blocked_actions", + "required_evidence", + "next_gate", + "queue_write_allowed", + "telegram_send_allowed", + "production_write_allowed", + "secret_value_read_allowed", + "destructive_action_allowed", + "live_execution_allowed", + "evidence_hash" + ], + "properties": { + "category_id": { "type": "string" }, + "display_name": { "type": "string" }, + "risk_tier": { "enum": ["low", "medium", "high", "critical"] }, + "permission_lane": { + "enum": [ + "observe_only", + "no_write_replay_allowed", + "proposal_only", + "human_approval_required", + "explicitly_blocked" + ] + }, + "primary_agent": { "enum": ["openclaw", "hermes", "nemotron"] }, + "allowed_outputs": { "type": "array", "items": { "type": "string" } }, + "blocked_actions": { "type": "array", "items": { "type": "string" } }, + "required_evidence": { "type": "array", "items": { "type": "string" } }, + "next_gate": { "type": "string" }, + "queue_write_allowed": { "const": false }, + "telegram_send_allowed": { "const": false }, + "production_write_allowed": { "const": false }, + "secret_value_read_allowed": { "const": false }, + "destructive_action_allowed": { "const": false }, + "live_execution_allowed": { "const": false }, + "evidence_hash": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" } + }, + "additionalProperties": false + } + }, + "agent_permission_roles": { + "type": "array", + "minItems": 3, + "items": { + "type": "object", + "required": [ + "agent_id", + "display_name", + "permission_responsibility", + "allowed_lanes", + "blocked_now", + "self_approval_allowed", + "live_action_count_24h" + ], + "properties": { + "agent_id": { "enum": ["openclaw", "hermes", "nemotron"] }, + "display_name": { "type": "string" }, + "permission_responsibility": { "type": "string" }, + "allowed_lanes": { "type": "array", "items": { "type": "string" } }, + "blocked_now": { "type": "array", "items": { "type": "string" } }, + "self_approval_allowed": { "const": false }, + "live_action_count_24h": { "const": 0 } + }, + "additionalProperties": false + } + }, + "gate_transitions": { + "type": "array", + "minItems": 8, + "items": { + "type": "object", + "required": [ + "gate_id", + "display_name", + "current_status", + "required_before", + "next_safe_step", + "opens_live_execution" + ], + "properties": { + "gate_id": { "type": "string" }, + "display_name": { "type": "string" }, + "current_status": { + "enum": ["ready_for_review", "blocked_until_evidence", "blocked_by_policy"] + }, + "required_before": { "type": "string" }, + "next_safe_step": { "type": "string" }, + "opens_live_execution": { "const": false } + }, + "additionalProperties": false + } + }, + "operator_decision_templates": { + "type": "array", + "minItems": 5, + "items": { + "type": "object", + "required": [ + "template_id", + "display_name", + "when_to_use", + "human_instruction", + "creates_runtime_action", + "requires_human_review" + ], + "properties": { + "template_id": { "type": "string" }, + "display_name": { "type": "string" }, + "when_to_use": { "type": "string" }, + "human_instruction": { "type": "string" }, + "creates_runtime_action": { "const": false }, + "requires_human_review": { "const": true } + }, + "additionalProperties": false + } + }, + "display_redaction_contract": { + "type": "object", + "required": [ + "redaction_required", + "raw_prompt_display_allowed", + "private_reasoning_display_allowed", + "secret_value_display_allowed", + "raw_telegram_payload_display_allowed", + "work_window_transcript_display_allowed", + "allowed_display_fields", + "blocked_display_fields" + ], + "properties": { + "redaction_required": { "const": true }, + "raw_prompt_display_allowed": { "const": false }, + "private_reasoning_display_allowed": { "const": false }, + "secret_value_display_allowed": { "const": false }, + "raw_telegram_payload_display_allowed": { "const": false }, + "work_window_transcript_display_allowed": { "const": false }, + "allowed_display_fields": { "type": "array", "items": { "type": "string" } }, + "blocked_display_fields": { "type": "array", "items": { "type": "string" } } + }, + "additionalProperties": false + }, + "rollups": { + "type": "object", + "required": [ + "permission_lane_count", + "operation_category_count", + "observe_only_category_count", + "no_write_replay_allowed_category_count", + "proposal_only_category_count", + "human_approval_required_category_count", + "explicitly_blocked_category_count", + "human_approval_required_category_ids", + "explicitly_blocked_category_ids", + "agent_role_count", + "gate_transition_count", + "operator_decision_template_count", + "runtime_execution_count", + "gateway_queue_write_count", + "telegram_send_count", + "telegram_bot_api_call_count", + "delivery_receipt_write_count", + "ai_runtime_worker_run_count", + "medium_low_auto_execution_count", + "post_action_verifier_live_readback_count", + "production_write_count", + "secret_value_read_count", + "paid_provider_call_count", + "host_or_cluster_command_count", + "destructive_operation_count" + ], + "properties": { + "permission_lane_count": { "type": "integer" }, + "operation_category_count": { "type": "integer" }, + "observe_only_category_count": { "type": "integer" }, + "no_write_replay_allowed_category_count": { "type": "integer" }, + "proposal_only_category_count": { "type": "integer" }, + "human_approval_required_category_count": { "type": "integer" }, + "explicitly_blocked_category_count": { "type": "integer" }, + "human_approval_required_category_ids": { "type": "array", "items": { "type": "string" } }, + "explicitly_blocked_category_ids": { "type": "array", "items": { "type": "string" } }, + "agent_role_count": { "type": "integer" }, + "gate_transition_count": { "type": "integer" }, + "operator_decision_template_count": { "type": "integer" }, + "runtime_execution_count": { "const": 0 }, + "gateway_queue_write_count": { "const": 0 }, + "telegram_send_count": { "const": 0 }, + "telegram_bot_api_call_count": { "const": 0 }, + "delivery_receipt_write_count": { "const": 0 }, + "ai_runtime_worker_run_count": { "const": 0 }, + "medium_low_auto_execution_count": { "const": 0 }, + "post_action_verifier_live_readback_count": { "const": 0 }, + "production_write_count": { "const": 0 }, + "secret_value_read_count": { "const": 0 }, + "paid_provider_call_count": { "const": 0 }, + "host_or_cluster_command_count": { "const": 0 }, + "destructive_operation_count": { "const": 0 } + }, + "additionalProperties": false + } + }, + "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 ec354bdb..4e3ea91d 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 @@ -638,6 +638,7 @@ Alert / Sentry / SigNoz / Gitea / Market Watch / Operator | `docs/evaluations/ai_agent_report_runtime_dry_run_2026-06-12.json` + `GET /api/v1/agents/agent-report-runtime-dry-run` | P2-403M 報表 runtime no-write dry-run 證據包;建立 5 個 dry-run artifact、3 個 SRE 戰情室 queue digest 草案、4 個 readback verifier case、3 個 Agent dry-run role 與 6 個 operator checkpoint;不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 delivery receipt、不啟動 worker、不跑 verifier live readback、不讀 secret,已由 P2-403N fixture readback 承接 | | `docs/evaluations/ai_agent_report_runtime_fixture_readback_2026-06-12.json` + `GET /api/v1/agents/agent-report-runtime-fixture-readback` | P2-403N fixture smoke / queue preview readback / verifier dry-run 證據包;建立 5 個 fixture smoke、3 個 SRE 戰情室 queue preview readback、4 個 verifier dry-run case、3 個 Agent fixture role 與 5 個 operator checkpoint;不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 delivery receipt、不啟動 worker、不跑 verifier live readback、不讀 secret,下一步 P2-404 | | `docs/evaluations/ai_agent_runtime_worker_shadow_gate_2026-06-12.json` + `GET /api/v1/agents/agent-runtime-worker-shadow-gate` | P2-404 runtime worker shadow / no-write execution evidence gate;建立 5 個 shadow candidate、4 個 no-write replay、4 個 verifier shadow case、3 個 Agent shadow role 與 6 個 operator checkpoint;shadow live worker、Gateway queue write、Telegram send、Bot API、delivery receipt、auto worker、verifier live readback、production write 與 secret read 全部 `0 / false`,下一步 P2-101 | +| `docs/evaluations/ai_agent_operation_permission_model_2026-06-12.json` + `GET /api/v1/agents/agent-operation-permission-model` | P2-101 操作類別權限模型;建立 5 條 permission lane、13 類 operation category、3 個 Agent permission role、8 個 gate transition 與 5 個 operator decision template;runtime execution、Gateway queue write、Telegram send、Bot API、delivery receipt、auto worker、verifier live readback、production write、secret / paid provider、host command 與 destructive action 全部 `0 / false`,下一步 P2-102 | | `docs/evaluations/ai_agent_live_read_model_gate_2026-06-11.json` + `GET /api/v1/agents/agent-live-read-model-gate` | P2-403B AgentSession / Redis Streams live read model gate;定義 safe fields、Redis envelope、worker gate、rollback plan 與 no-write smoke,不連 DB、不讀寫 Redis、不啟動 worker | #### 3.2.1c 2026-06-11 AI Agent 主動營運委派與版本生命週期契約 @@ -726,7 +727,8 @@ Repo / registry / release notes / K8s / host / observability / backup evidence 18. 建立報表派送、Telegram Gateway queue、讀報回執、AI 讀報後分析、中低風險自動處理、高風險審核與 post-action verifier 啟動前閘門。✅ P2-403L 已完成;live delivery、Gateway queue write、AI runtime worker、中低風險 auto worker、高風險自動執行與 production optimization 仍為 `0 / false`。 19. 建立報表 runtime no-write dry-run、SRE 戰情室 Gateway queue 草案與 readback verifier 草案。✅ P2-403M 已完成;Gateway queue write、Telegram send、Bot API、delivery receipt、AI runtime worker、中低風險 auto worker、verifier live readback、production write 與 secret value read 仍為 `0 / false`。 20. 建立 fixture smoke、queue preview readback 與 verifier dry-run 證據包。✅ P2-403N 已完成;fixture smoke `5`、queue preview readback `3`、verifier dry-run case `4`,Gateway queue write、Telegram send、Bot API、delivery receipt、AI runtime worker、中低風險 auto worker、verifier live readback、production write 與 secret value read 仍為 `0 / false`。 -21. 建立 runtime worker shadow / no-write execution evidence gate。✅ P2-404 已完成;shadow candidate `5`、no-write replay `4`、verifier shadow case `4`、Agent shadow role `3`、operator checkpoint `6`,shadow live worker、Gateway queue write、Telegram send、Bot API、delivery receipt、AI runtime worker、中低風險 auto worker、verifier live readback、production write 與 secret value read 仍為 `0 / false`。下一步 P2-101 操作類別權限模型。 +21. 建立 runtime worker shadow / no-write execution evidence gate。✅ P2-404 已完成;shadow candidate `5`、no-write replay `4`、verifier shadow case `4`、Agent shadow role `3`、operator checkpoint `6`,shadow live worker、Gateway queue write、Telegram send、Bot API、delivery receipt、AI runtime worker、中低風險 auto worker、verifier live readback、production write 與 secret value read 仍為 `0 / false`。已由 P2-101 承接。 +22. 定義操作類別權限模型。✅ P2-101 已完成;permission lane `5`、operation category `13`、Agent permission role `3`、gate transition `8`、operator decision template `5`,runtime execution、Gateway queue write、Telegram send、Bot API、delivery receipt、AI runtime worker、中低風險 auto worker、verifier live readback、production write、secret / paid provider、host command 與 destructive action 仍為 `0 / false`。下一步 P2-102 候選操作 dry-run 證據。 #### 3.2.1d 2026-06-11 Agent 互動、學習與成長證據面 @@ -756,6 +758,7 @@ Repo / registry / release notes / K8s / host / observability / backup evidence | `docs/evaluations/ai_agent_report_runtime_dry_run_2026-06-12.json` + `GET /api/v1/agents/agent-report-runtime-dry-run` | P2-403M 報表 runtime no-write dry-run 證據包;5 個 dry-run artifact、3 個 queue digest 草案、4 個 readback verifier case、3 個 Agent dry-run role、6 個 operator checkpoint;不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 delivery receipt、不啟動 worker、不跑 verifier live readback | | `docs/evaluations/ai_agent_report_runtime_fixture_readback_2026-06-12.json` + `GET /api/v1/agents/agent-report-runtime-fixture-readback` | P2-403N fixture smoke / queue preview readback / verifier dry-run 證據包;5 個 fixture smoke、3 個 queue preview readback、4 個 verifier dry-run case、3 個 Agent fixture role、5 個 operator checkpoint;不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 delivery receipt、不啟動 worker、不跑 verifier live readback | | `docs/evaluations/ai_agent_runtime_worker_shadow_gate_2026-06-12.json` + `GET /api/v1/agents/agent-runtime-worker-shadow-gate` | P2-404 runtime worker shadow / no-write execution evidence gate;5 個 shadow candidate、4 個 no-write replay、4 個 verifier shadow case、3 個 Agent shadow role、6 個 operator checkpoint;不啟動 live worker、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 production target | +| `docs/evaluations/ai_agent_operation_permission_model_2026-06-12.json` + `GET /api/v1/agents/agent-operation-permission-model` | P2-101 操作類別權限模型;5 條 permission lane、13 類 operation category、3 個 Agent permission role、8 個 gate transition、5 個 operator decision template;不啟動 runtime worker、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 production target、不讀 secret | | `apps/api/src/services/ai_agent_interaction_learning_proof.py` | 只讀 loader;強制 live flags / DB / Redis / Telegram / transcript / 私有推理全部關閉 | | `GET /api/v1/agents/agent-interaction-learning-proof` | 治理 API;只回傳證據面,不啟動 worker、不碰 live DB/Redis、不發 Telegram | | `docs/schemas/ai_agent_live_read_model_gate_v1.schema.json` | P2-403B live read model gate schema;強制 DB / Redis / worker / Telegram / learning writeback 仍需批准 | @@ -1909,6 +1912,13 @@ Phase 6 完成後 - 政策裁決:P2-404 只允許 redacted promotion hash、no-write replay hash、verifier shadow evidence 與 operator checkpoint;任何 shadow live worker、Gateway queue write、Telegram send、Bot API、delivery receipt、AI runtime worker、中低風險 auto worker、verifier live readback、production write 或 secret value read 都仍為 `0 / false`。 - 本波仍不送 Telegram、不寫 Gateway queue、不呼叫 Bot API、不寫 delivery receipt、不啟動 live runtime worker、不跑 verifier live readback、不讀 secret、不回傳工作視窗對話內容;下一步 P2-101 才定義操作類別權限模型。 +### 2026-06-12 15:40 (台北) — §3.2 / §5 — 完成 P2-101 操作類別權限模型 — 把 shadow handoff 轉成可審核操作 lane + +- 新增 `ai_agent_operation_permission_model_v1` schema / committed snapshot / loader / API / 測試,定義 5 條 permission lane、13 類 operation category、3 個 Agent permission role、8 個 gate transition 與 5 個 operator decision template。 +- `apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx` 接入 `GET /api/v1/agents/agent-operation-permission-model`,治理頁顯示只讀、no-write replay、提案、人工批准、明確阻擋的操作矩陣,並呈現每類操作下一個 gate 與人工處置模板。 +- 政策裁決:P2-101 只允許操作分類、風險分層、Agent 責任、gate transition 與人工下一步模板;任何 runtime execution、Gateway queue write、Telegram send、Bot API、delivery receipt、AI runtime worker、中低風險 auto worker、verifier live readback、production write、secret / paid provider、host command 或 destructive action 都仍為 `0 / false`。 +- 本波仍不送 Telegram、不寫 Gateway queue、不呼叫 Bot API、不寫 delivery receipt、不啟動 live runtime worker、不跑 verifier live readback、不讀 secret、不回傳工作視窗對話內容;下一步 P2-102 才要求每個候選操作具備 dry-run 證據。 + ### 2026-06-12 11:55 (台北) — §3.2 / §5 — 完成 P2-403M 報表 runtime no-write dry-run 證據包 — 把 queue / verifier 草案固定成可審核證據 - 新增 `ai_agent_report_runtime_dry_run_v1` schema / committed snapshot / loader / API / 測試,定義 report_run snapshot preview、Telegram digest payload preview、AI post-report analysis packet、中低風險 no-op plan、post-action verifier readback plan。