diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index e1f46a7b..6b292d34 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -58,6 +58,9 @@ from src.services.ai_agent_deployment_layout import ( from src.services.ai_agent_gitea_pr_draft_lane import ( load_latest_ai_agent_gitea_pr_draft_lane, ) +from src.services.ai_agent_host_stateful_version_inventory import ( + load_latest_ai_agent_host_stateful_version_inventory, +) from src.services.ai_agent_proactive_operations_contract import ( load_latest_ai_agent_proactive_operations_contract, ) @@ -709,6 +712,35 @@ async def get_agent_gitea_pr_draft_lane() -> dict[str, Any]: ) from exc +@router.get( + "/agent-host-stateful-version-inventory", + response_model=dict[str, Any], + summary="取得 AI Agent host / K3s / stateful 版本只讀盤點", + description=( + "讀取最新已提交的 AI Agent host OS / K3s / stateful services 版本只讀盤點與 " + "maintenance window 批准包;此端點不 SSH、不執行 host command、不執行 kubectl、" + "不 apt upgrade、不升級 kernel/K3s、不 drain node、不 reboot、不 restart stateful service、" + "不做 DB migration、不刪備份、不 restore、不 pull image、不安裝套件、不查外部版本來源、" + "不 active scan、不發 Telegram、不讀取 secret、不回傳工作視窗對話內容。" + ), +) +async def get_agent_host_stateful_version_inventory() -> dict[str, Any]: + """Return the latest read-only host / K3s / stateful version inventory.""" + try: + return await asyncio.to_thread(load_latest_ai_agent_host_stateful_version_inventory) + 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_host_stateful_version_inventory_invalid", error=str(exc)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="AI Agent host / K3s / stateful 版本只讀盤點無效", + ) from exc + + @router.get( "/runtime-surface-inventory", response_model=dict[str, Any], diff --git a/apps/api/src/services/ai_agent_host_stateful_version_inventory.py b/apps/api/src/services/ai_agent_host_stateful_version_inventory.py new file mode 100644 index 00000000..f93d4cbd --- /dev/null +++ b/apps/api/src/services/ai_agent_host_stateful_version_inventory.py @@ -0,0 +1,286 @@ +""" +AI Agent host and stateful version inventory snapshot. + +Loads the latest committed, read-only host OS, K3s, and stateful services +inventory contract. This module never runs SSH, kubectl, package upgrades, +node drains, reboots, stateful restarts, live scans, Telegram sends, or exposes +work-window transcripts. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) +_SNAPSHOT_PATTERN = "ai_agent_host_stateful_version_inventory_*.json" +_SCHEMA_VERSION = "ai_agent_host_stateful_version_inventory_v1" +_RUNTIME_AUTHORITY = "host_stateful_readonly_inventory_no_upgrade_or_restart" +_TRANSCRIPT_MARKERS = { + "# In app browser", + "My request for Codex", + "Current URL:", + "AGENTS.md instructions", + "", + "批准!繼續", +} + + +def load_latest_ai_agent_host_stateful_version_inventory( + evaluations_dir: Path | None = None, +) -> dict[str, Any]: + """Load the newest committed host / K3s / stateful version inventory.""" + directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR + candidates = sorted(directory.glob(_SNAPSHOT_PATTERN)) + if not candidates: + raise FileNotFoundError( + f"no AI Agent host stateful version inventory snapshots found in {directory}" + ) + + latest = candidates[-1] + with latest.open(encoding="utf-8") as handle: + payload = json.load(handle) + + if not isinstance(payload, dict): + raise ValueError(f"{latest}: expected JSON object") + _require_schema(payload, _SCHEMA_VERSION, str(latest)) + _require_read_only_boundaries(payload, str(latest)) + _require_rollup_consistency(payload, str(latest)) + _require_inventory_safety(payload, str(latest)) + _require_maintenance_approval_contract(payload, str(latest)) + _require_display_redaction(payload, str(latest)) + _require_no_plaintext_secret_payload_keys(payload, str(latest)) + _require_no_conversation_transcript_content(payload, str(latest)) + return payload + + +def _require_schema(payload: dict[str, Any], expected: str, label: str) -> None: + actual = payload.get("schema_version") + if actual != expected: + raise ValueError(f"{label}: expected schema_version={expected}, got {actual!r}") + + +def _require_read_only_boundaries(payload: dict[str, Any], label: str) -> None: + program_status = payload.get("program_status") or {} + if program_status.get("read_only_mode") is not True: + raise ValueError(f"{label}: program_status.read_only_mode must be true") + if program_status.get("runtime_authority") != _RUNTIME_AUTHORITY: + raise ValueError(f"{label}: runtime_authority must stay {_RUNTIME_AUTHORITY}") + + operation_boundaries = payload.get("operation_boundaries") or {} + if operation_boundaries.get("read_only_inventory_allowed") is not True: + raise ValueError(f"{label}: read_only_inventory_allowed must be true") + + blocked_operation_flags = { + "ssh_login_allowed", + "host_command_execution_allowed", + "kubectl_command_execution_allowed", + "apt_upgrade_allowed", + "os_release_upgrade_allowed", + "kernel_upgrade_allowed", + "k3s_upgrade_allowed", + "kubelet_restart_allowed", + "node_drain_allowed", + "reboot_allowed", + "stateful_service_restart_allowed", + "database_migration_allowed", + "backup_delete_allowed", + "restore_execution_allowed", + "image_pull_allowed", + "package_install_allowed", + "external_version_lookup_allowed", + "active_network_scan_allowed", + "telegram_direct_send_allowed", + "telegram_gateway_queue_write_allowed", + "secret_plaintext_allowed", + "conversation_transcript_allowed", + } + allowed_operation_flags = sorted( + flag + for flag in blocked_operation_flags + if operation_boundaries.get(flag) is not False + ) + if allowed_operation_flags: + raise ValueError( + f"{label}: operation boundaries must remain false: {allowed_operation_flags}" + ) + + approval_boundaries = payload.get("approval_boundaries") or {} + allowed_approval_flags = sorted( + flag for flag, value in approval_boundaries.items() if value is not False + ) + if allowed_approval_flags: + raise ValueError( + f"{label}: approval boundaries must remain false: {allowed_approval_flags}" + ) + + +def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None: + host_inventory = payload.get("host_inventory") or [] + k3s_inventory = payload.get("k3s_inventory") or {} + stateful_services = payload.get("stateful_services") or [] + readonly_probe_plan = payload.get("readonly_probe_plan") or [] + maintenance_requirements = payload.get("maintenance_window_approval_package") or {} + rollups = payload.get("rollups") or {} + + expected_counts = { + "host_count": len(host_inventory), + "k3s_node_count": len(k3s_inventory.get("nodes") or []), + "stateful_service_count": len(stateful_services), + "readonly_probe_step_count": len(readonly_probe_plan), + "maintenance_required_field_count": len(maintenance_requirements.get("required_fields") or []), + } + mismatched = { + key: {"expected": expected, "actual": rollups.get(key)} + for key, expected in expected_counts.items() + if rollups.get(key) != expected + } + if mismatched: + raise ValueError(f"{label}: rollup counts must match payload sections: {mismatched}") + + expected_host_ids = sorted(host.get("host_id") for host in host_inventory) + if sorted(rollups.get("host_ids") or []) != expected_host_ids: + raise ValueError(f"{label}: rollups.host_ids mismatch") + + expected_service_ids = sorted(service.get("service_id") for service in stateful_services) + if sorted(rollups.get("stateful_service_ids") or []) != expected_service_ids: + raise ValueError(f"{label}: rollups.stateful_service_ids mismatch") + + zero_rollups = { + "ssh_login_allowed_count", + "kubectl_command_execution_allowed_count", + "apt_upgrade_allowed_count", + "k3s_upgrade_allowed_count", + "node_drain_allowed_count", + "reboot_allowed_count", + "stateful_service_restart_allowed_count", + "telegram_direct_send_allowed_count", + "conversation_transcript_allowed_count", + } + nonzero = sorted(key for key in zero_rollups if rollups.get(key) != 0) + if nonzero: + raise ValueError(f"{label}: safety counters must remain 0: {nonzero}") + + +def _require_inventory_safety(payload: dict[str, Any], label: str) -> None: + unsafe_hosts = [ + host.get("host_id") + for host in payload.get("host_inventory") or [] + if host.get("readonly_only") is not True + or host.get("host_update_authorized") is not False + or host.get("reboot_authorized") is not False + or host.get("maintenance_window_required") is not True + or not host.get("version_observation_status") + ] + if unsafe_hosts: + raise ValueError(f"{label}: host inventory must remain read-only and gated: {unsafe_hosts}") + + k3s = payload.get("k3s_inventory") or {} + if k3s.get("skew_policy_required") is not True: + raise ValueError(f"{label}: K3s skew policy must be required") + if k3s.get("upgrade_authorized") is not False: + raise ValueError(f"{label}: K3s upgrade must remain unauthorized") + unsafe_nodes = [ + node.get("node_id") + for node in k3s.get("nodes") or [] + if node.get("drain_authorized") is not False + or node.get("kubelet_restart_authorized") is not False + or node.get("readonly_only") is not True + ] + if unsafe_nodes: + raise ValueError(f"{label}: K3s nodes must remain read-only: {unsafe_nodes}") + + unsafe_services = [ + service.get("service_id") + for service in payload.get("stateful_services") or [] + if service.get("readonly_only") is not True + or service.get("restart_authorized") is not False + or service.get("upgrade_authorized") is not False + or service.get("backup_required_before_change") is not True + or not service.get("version_observation_status") + ] + if unsafe_services: + raise ValueError( + f"{label}: stateful services must remain read-only and backup-gated: {unsafe_services}" + ) + + unsafe_probe_steps = [ + step.get("step_id") + for step in payload.get("readonly_probe_plan") or [] + if step.get("run_now_allowed") is not False + or step.get("mutation_allowed") is not False + or not step.get("planned_output") + ] + if unsafe_probe_steps: + raise ValueError(f"{label}: readonly probe steps must stay planned-only: {unsafe_probe_steps}") + + +def _require_maintenance_approval_contract(payload: dict[str, Any], label: str) -> None: + required_fields = { + "owner", + "decision", + "maintenance_window", + "affected_hosts", + "affected_services", + "backup_snapshot_ref", + "rollback_owner", + "rollback_plan", + "smoke_plan", + "communication_plan", + "risk_acceptance", + } + package = payload.get("maintenance_window_approval_package") or {} + actual_fields = set(package.get("required_fields") or []) + if not required_fields.issubset(actual_fields): + raise ValueError(f"{label}: maintenance window approval package missing required fields") + if package.get("approval_required_before_probe") is not True: + raise ValueError(f"{label}: approval must be required before live probe") + if package.get("approval_required_before_change") is not True: + raise ValueError(f"{label}: approval must be required before changes") + if package.get("break_glass_record_required") is not True: + raise ValueError(f"{label}: break-glass record must be required") + + +def _require_display_redaction(payload: dict[str, Any], label: str) -> None: + display = payload.get("display_redaction_contract") or {} + if display.get("conversation_transcript_display_allowed") is not False: + raise ValueError(f"{label}: conversation transcript display must remain false") + if display.get("redaction_required") is not True: + raise ValueError(f"{label}: display redaction must be required") + + +def _require_no_plaintext_secret_payload_keys(value: Any, label: str, path: str = "$") -> None: + if isinstance(value, dict): + forbidden_key_fragments = { + "secret_value", + "token_plaintext", + "authorization_header", + "private_key", + "credential_value", + } + for key, nested in value.items(): + normalized_key = str(key).lower() + if any(fragment in normalized_key for fragment in forbidden_key_fragments): + raise ValueError(f"{label}: forbidden plaintext secret key at {path}.{key}") + _require_no_plaintext_secret_payload_keys(nested, label, f"{path}.{key}") + elif isinstance(value, list): + for index, nested in enumerate(value): + _require_no_plaintext_secret_payload_keys(nested, label, f"{path}[{index}]") + + +def _require_no_conversation_transcript_content(value: Any, label: str, path: str = "$") -> None: + if isinstance(value, str): + for marker in _TRANSCRIPT_MARKERS: + if marker in value: + raise ValueError( + f"{label}: forbidden work-window conversation content at {path}: {marker}" + ) + elif isinstance(value, dict): + for key, nested in value.items(): + _require_no_conversation_transcript_content(nested, label, f"{path}.{key}") + elif isinstance(value, list): + for index, nested in enumerate(value): + _require_no_conversation_transcript_content(nested, label, f"{path}[{index}]") diff --git a/apps/api/tests/test_ai_agent_host_stateful_version_inventory.py b/apps/api/tests/test_ai_agent_host_stateful_version_inventory.py new file mode 100644 index 00000000..8f9c0b2d --- /dev/null +++ b/apps/api/tests/test_ai_agent_host_stateful_version_inventory.py @@ -0,0 +1,122 @@ +from __future__ import annotations + +import json + +import pytest + +from src.services.ai_agent_host_stateful_version_inventory import ( + load_latest_ai_agent_host_stateful_version_inventory, +) + + +def test_load_latest_ai_agent_host_stateful_version_inventory_reads_committed_snapshot(): + data = load_latest_ai_agent_host_stateful_version_inventory() + + assert data["schema_version"] == "ai_agent_host_stateful_version_inventory_v1" + assert data["program_status"]["overall_completion_percent"] == 86 + assert data["program_status"]["current_task_id"] == "P2-402F" + assert data["program_status"]["next_task_id"] == "P2-402G" + assert data["program_status"]["read_only_mode"] is True + assert data["program_status"]["runtime_authority"] == ( + "host_stateful_readonly_inventory_no_upgrade_or_restart" + ) + assert data["operation_boundaries"]["read_only_inventory_allowed"] is True + assert data["operation_boundaries"]["ssh_login_allowed"] is False + assert data["operation_boundaries"]["kubectl_command_execution_allowed"] is False + assert data["operation_boundaries"]["apt_upgrade_allowed"] is False + assert data["operation_boundaries"]["k3s_upgrade_allowed"] is False + assert data["operation_boundaries"]["node_drain_allowed"] is False + assert data["operation_boundaries"]["reboot_allowed"] is False + assert data["operation_boundaries"]["stateful_service_restart_allowed"] is False + assert data["operation_boundaries"]["telegram_direct_send_allowed"] is False + assert data["operation_boundaries"]["conversation_transcript_allowed"] is False + assert data["rollups"]["host_count"] == len(data["host_inventory"]) == 5 + assert data["rollups"]["k3s_node_count"] == len(data["k3s_inventory"]["nodes"]) == 2 + assert data["rollups"]["stateful_service_count"] == len(data["stateful_services"]) == 12 + assert data["rollups"]["readonly_probe_step_count"] == len(data["readonly_probe_plan"]) == 6 + assert data["rollups"]["ssh_login_allowed_count"] == 0 + assert data["rollups"]["kubectl_command_execution_allowed_count"] == 0 + assert data["rollups"]["apt_upgrade_allowed_count"] == 0 + assert data["rollups"]["k3s_upgrade_allowed_count"] == 0 + assert data["rollups"]["node_drain_allowed_count"] == 0 + assert data["rollups"]["reboot_allowed_count"] == 0 + assert data["rollups"]["stateful_service_restart_allowed_count"] == 0 + assert data["rollups"]["telegram_direct_send_allowed_count"] == 0 + assert data["rollups"]["conversation_transcript_allowed_count"] == 0 + assert all(host["readonly_only"] is True for host in data["host_inventory"]) + assert all(service["restart_authorized"] is False for service in data["stateful_services"]) + + +def test_ai_agent_host_stateful_version_inventory_blocks_ssh(tmp_path): + snapshot = _snapshot() + snapshot["operation_boundaries"]["ssh_login_allowed"] = True + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="operation boundaries"): + load_latest_ai_agent_host_stateful_version_inventory(tmp_path) + + +def test_ai_agent_host_stateful_version_inventory_rejects_rollup_mismatch(tmp_path): + snapshot = _snapshot() + snapshot["rollups"]["host_count"] = 99 + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="rollup counts"): + load_latest_ai_agent_host_stateful_version_inventory(tmp_path) + + +def test_ai_agent_host_stateful_version_inventory_rejects_host_reboot(tmp_path): + snapshot = _snapshot() + snapshot["host_inventory"][0]["reboot_authorized"] = True + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="host inventory"): + load_latest_ai_agent_host_stateful_version_inventory(tmp_path) + + +def test_ai_agent_host_stateful_version_inventory_rejects_k3s_drain(tmp_path): + snapshot = _snapshot() + snapshot["k3s_inventory"]["nodes"][0]["drain_authorized"] = True + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="K3s nodes"): + load_latest_ai_agent_host_stateful_version_inventory(tmp_path) + + +def test_ai_agent_host_stateful_version_inventory_rejects_stateful_restart(tmp_path): + snapshot = _snapshot() + snapshot["stateful_services"][0]["restart_authorized"] = True + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="stateful services"): + load_latest_ai_agent_host_stateful_version_inventory(tmp_path) + + +def test_ai_agent_host_stateful_version_inventory_requires_maintenance_fields(tmp_path): + snapshot = _snapshot() + snapshot["maintenance_window_approval_package"]["required_fields"] = ["owner"] + snapshot["rollups"]["maintenance_required_field_count"] = 1 + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="maintenance window approval package"): + load_latest_ai_agent_host_stateful_version_inventory(tmp_path) + + +def test_ai_agent_host_stateful_version_inventory_rejects_transcript_markers(tmp_path): + snapshot = _snapshot() + snapshot["program_status"]["status_note"] = "禁止放入 My request for Codex" + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="forbidden work-window conversation content"): + load_latest_ai_agent_host_stateful_version_inventory(tmp_path) + + +def _write_snapshot(tmp_path, snapshot: dict) -> None: + (tmp_path / "ai_agent_host_stateful_version_inventory_2026-06-11.json").write_text( + json.dumps(snapshot), + encoding="utf-8", + ) + + +def _snapshot() -> dict: + return json.loads(json.dumps(load_latest_ai_agent_host_stateful_version_inventory())) diff --git a/apps/api/tests/test_ai_agent_host_stateful_version_inventory_api.py b/apps/api/tests/test_ai_agent_host_stateful_version_inventory_api.py new file mode 100644 index 00000000..ddd73b1f --- /dev/null +++ b/apps/api/tests/test_ai_agent_host_stateful_version_inventory_api.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import json + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from src.api.v1.agents import router + + +def test_agent_host_stateful_version_inventory_endpoint_returns_committed_snapshot(): + app = FastAPI() + app.include_router(router, prefix="/api/v1") + client = TestClient(app) + + response = client.get("/api/v1/agents/agent-host-stateful-version-inventory") + + assert response.status_code == 200 + data = response.json() + assert data["schema_version"] == "ai_agent_host_stateful_version_inventory_v1" + assert data["program_status"]["overall_completion_percent"] == 86 + assert data["program_status"]["current_task_id"] == "P2-402F" + assert data["program_status"]["next_task_id"] == "P2-402G" + assert data["program_status"]["read_only_mode"] is True + assert data["operation_boundaries"]["read_only_inventory_allowed"] is True + assert data["operation_boundaries"]["ssh_login_allowed"] is False + assert data["operation_boundaries"]["kubectl_command_execution_allowed"] is False + assert data["operation_boundaries"]["apt_upgrade_allowed"] is False + assert data["operation_boundaries"]["k3s_upgrade_allowed"] is False + assert data["operation_boundaries"]["node_drain_allowed"] is False + assert data["operation_boundaries"]["reboot_allowed"] is False + assert data["operation_boundaries"]["stateful_service_restart_allowed"] is False + assert data["operation_boundaries"]["telegram_direct_send_allowed"] is False + assert data["operation_boundaries"]["conversation_transcript_allowed"] is False + assert data["rollups"]["host_count"] == 5 + assert data["rollups"]["k3s_node_count"] == 2 + assert data["rollups"]["stateful_service_count"] == 12 + assert data["rollups"]["readonly_probe_step_count"] == 6 + assert data["rollups"]["maintenance_required_field_count"] == 11 + assert data["rollups"]["ssh_login_allowed_count"] == 0 + assert data["rollups"]["kubectl_command_execution_allowed_count"] == 0 + assert data["rollups"]["apt_upgrade_allowed_count"] == 0 + assert data["rollups"]["k3s_upgrade_allowed_count"] == 0 + assert data["rollups"]["node_drain_allowed_count"] == 0 + assert data["rollups"]["reboot_allowed_count"] == 0 + assert data["rollups"]["stateful_service_restart_allowed_count"] == 0 + assert data["rollups"]["telegram_direct_send_allowed_count"] == 0 + assert data["rollups"]["conversation_transcript_allowed_count"] == 0 + + serialized = json.dumps(data, ensure_ascii=False) + for marker in [ + "批准!繼續", + "My request for Codex", + "Current URL:", + "# In app browser", + "AGENTS.md instructions", + ]: + assert marker not in serialized diff --git a/apps/api/tests/test_ai_agent_proactive_operations_contract.py b/apps/api/tests/test_ai_agent_proactive_operations_contract.py index d8d6ae3d..f512a66d 100644 --- a/apps/api/tests/test_ai_agent_proactive_operations_contract.py +++ b/apps/api/tests/test_ai_agent_proactive_operations_contract.py @@ -13,9 +13,9 @@ def test_load_latest_ai_agent_proactive_operations_contract_reads_committed_snap data = load_latest_ai_agent_proactive_operations_contract() assert data["schema_version"] == "ai_agent_proactive_operations_contract_v1" - assert data["program_status"]["overall_completion_percent"] == 78 - assert data["program_status"]["current_task_id"] == "P2-402E" - assert data["program_status"]["next_task_id"] == "P2-402F" + assert data["program_status"]["overall_completion_percent"] == 86 + assert data["program_status"]["current_task_id"] == "P2-402F" + assert data["program_status"]["next_task_id"] == "P2-402G" assert data["program_status"]["read_only_mode"] is True assert data["program_status"]["runtime_authority"] == "contract_only_no_version_or_runtime_update" assert data["approval_boundaries"]["runtime_version_update_allowed"] is False diff --git a/apps/api/tests/test_ai_agent_proactive_operations_contract_api.py b/apps/api/tests/test_ai_agent_proactive_operations_contract_api.py index 53f3ee38..829591e6 100644 --- a/apps/api/tests/test_ai_agent_proactive_operations_contract_api.py +++ b/apps/api/tests/test_ai_agent_proactive_operations_contract_api.py @@ -16,9 +16,9 @@ def test_ai_agent_proactive_operations_contract_endpoint_returns_committed_snaps assert response.status_code == 200 data = response.json() assert data["schema_version"] == "ai_agent_proactive_operations_contract_v1" - assert data["program_status"]["overall_completion_percent"] == 78 - assert data["program_status"]["current_task_id"] == "P2-402E" - assert data["program_status"]["next_task_id"] == "P2-402F" + assert data["program_status"]["overall_completion_percent"] == 86 + assert data["program_status"]["current_task_id"] == "P2-402F" + assert data["program_status"]["next_task_id"] == "P2-402G" assert data["program_status"]["read_only_mode"] is True assert data["approval_boundaries"]["runtime_version_update_allowed"] is False assert data["approval_boundaries"]["package_upgrade_allowed"] is False diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index a684242d..fb3ff324 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,28 @@ +## 2026-06-11|P2-402F AI Agent host / K3s / stateful 版本只讀盤點 + +**背景**:P2-402E 已建立 Gitea PR 草案 lane,但 host OS、K3s、PostgreSQL、Redis、MinIO、Harbor、Gitea 與監控 / DevOps stateful 服務仍缺少統一版本盤點與 maintenance window 批准包。本階段先建立 committed read-only inventory,讓 OpenClaw / Hermes / NemoTron 可以整理版本與維護證據,但不得執行 SSH、kubectl、升級、重啟或 Telegram 實發。 + +**本輪完成**: + +- 新增 `docs/schemas/ai_agent_host_stateful_version_inventory_v1.schema.json`。 +- 新增 `docs/evaluations/ai_agent_host_stateful_version_inventory_2026-06-11.json`:5 台主機、2 個 K3s 節點、12 個 stateful / ops 服務、6 個只讀 probe step、11 個 maintenance window 必填欄位。 +- 新增 `apps/api/src/services/ai_agent_host_stateful_version_inventory.py`,強制驗證 SSH / kubectl / apt upgrade / K3s upgrade / node drain / reboot / stateful restart / Telegram direct send / conversation transcript 全部 false。 +- 新增 `GET /api/v1/agents/agent-host-stateful-version-inventory`。 +- 新增 loader / API 測試,覆蓋 SSH boundary、rollup consistency、host reboot、K3s drain、stateful restart、maintenance package 欄位與工作視窗內容 redaction。 +- 更新 `ai_agent_proactive_operations_contract_2026-06-11.json`:整體完成度 `86%`,current task `P2-402F`,next task `P2-402G`。 +- 同步 `AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md`、`AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md`、P2-402C/D/E 報告與 MASTER §3.2.1c / §5 / changelog。 + +**完成度同步**: + +- P2-402F:`100%`。 +- AI Agent 主動營運委派與版本生命週期:`86%`。 +- Current task:`P2-402F`。 +- Next task:`P2-402G` governance UI 顯示可委派能力。 + +**邊界**: + +- 本波仍不 SSH、不執行 host command、不執行 kubectl、不 apt / kernel / K3s upgrade、不 node drain、不 kubelet restart、不 reboot、不 stateful restart、不 DB migration、不 restore、不 pull image、不發 Telegram、不讀取或輸出 secret、不回傳工作視窗對話內容。 + ## 2026-06-11|P0 Telegram no-action 人工處置包 **背景**:使用者再次指出 Telegram 告警即使已改成 `NO_ACTION - REPAIR_CANDIDATE_MISSING`,卡片仍只說「AI 選擇不執行修復,需要人工判斷」,但沒有產出可操作的人工處置選項、證據補齊清單、修復候選建立方式或後續驗證說明。這代表前一輪只止住 fake execution,尚未把 no-action / 需人工路徑變成可接手的處置包。 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 57ccc6a8..ddf8ecff 100644 --- a/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md +++ b/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md @@ -13,7 +13,7 @@ | 工具 / 服務 / 套件 AI 自動化 | 92% | P0 已完成;P1 服務 / runtime / 監控 / provider / service health / 備份 / DR / 套件與供應鏈只讀基線已完成;P1-007 失敗限定通知合約與前端 redaction 合約已完成;下一主線是 P2-004 依賴 / 供應鏈漂移監控 | 狀態分類、盤點 schema、權限矩陣、靜態盤點種子、只讀 API、UI 骨架、驗證、自動化待辦 schema / 快照 / API / 分組 UI、Backup / DR 目標盤點、準備度矩陣、備份通知政策、Backup / DR 證據 UI、復原演練批准包模板、異地 / escrow 準備度狀態、任務批准邊界、確定性進度彙總、Python 套件 / 供應鏈只讀基線、JS pnpm/npm 只讀基線、Docker build surface 只讀基線、CVE / license / drift 嚴重度政策、定期依賴漂移與外部資料來源檢查設計、依賴升級批准包模板、runtime_surface_inventory_v1 schema / snapshot / API / UI、gitea_workflow_runner_health_v1 schema / snapshot / API / UI、observability_contract_matrix_v1 schema / snapshot / API / UI、ai_provider_route_matrix_v1 schema / snapshot / API / UI、service_health_gap_matrix_v1 schema / snapshot / API / UI、service health evidence cards UI、service_health_failure_notification_policy_v1 schema / snapshot / API / UI 已完成 | | OpenClaw / Hermes / NemoTron 佈建布局 | 45% | P1-401 / P1-402 已完成;仍是只讀 layout 與治理頁顯示,不是 runtime deploy | `ai_agent_deployment_layout_v1` schema、`ai_agent_deployment_layout_2026-06-11.json`、`GET /api/v1/agents/agent-deployment-layout`、治理頁自動化盤點 UI、`AI_AGENT_DEPLOYMENT_LAYOUT_2026-06-11.md` | | OpenClaw / Hermes / NemoTron 主動溝通與學習契約 | 35% | P2-401A 已完成只讀 contract;runtime worker、DB migration、Telegram 實發、SDK / 付費服務仍未開 gate | `ai_agent_communication_learning_contract_v1` schema、`ai_agent_communication_learning_contract_2026-06-11.json`、`GET /api/v1/agents/agent-communication-learning-contract`、MASTER §3.2.1b / §3.4.3 | -| AI Agent 主動營運委派與版本生命週期 | 78% | P2-402A / P2-402B / P2-402C / P2-402D / P2-402E 已完成;已建立 repo-only 版本新鮮度快照、工具採用批准包、Telegram action-required digest policy、Gitea PR 草案 lane 與 API。定期排程、外部版本查詢、工具安裝、CI 變更、套件升級、主機更新、container pull、實際 PR creation、auto merge、Telegram 實發仍未開 gate | `ai_agent_proactive_operations_contract_v1`、`ai_agent_version_freshness_snapshot_v1`、`ai_agent_tool_adoption_approval_package_v1`、`ai_agent_telegram_action_required_digest_policy_v1`、`ai_agent_gitea_pr_draft_lane_v1`、`GET /api/v1/agents/agent-proactive-operations-contract`、`GET /api/v1/agents/agent-version-freshness-snapshot`、`GET /api/v1/agents/agent-tool-adoption-approval-package`、`GET /api/v1/agents/agent-telegram-action-required-digest-policy`、`GET /api/v1/agents/agent-gitea-pr-draft-lane`、MASTER §3.2.1c | +| AI Agent 主動營運委派與版本生命週期 | 86% | P2-402A / P2-402B / P2-402C / P2-402D / P2-402E / P2-402F 已完成;已建立 repo-only 版本新鮮度快照、工具採用批准包、Telegram action-required digest policy、Gitea PR 草案 lane、host / K3s / stateful 版本只讀盤點與 API。定期排程、外部版本查詢、工具安裝、CI 變更、套件升級、主機更新、container pull、實際 PR creation、auto merge、Telegram 實發、SSH、kubectl、重啟仍未開 gate | `ai_agent_proactive_operations_contract_v1`、`ai_agent_version_freshness_snapshot_v1`、`ai_agent_tool_adoption_approval_package_v1`、`ai_agent_telegram_action_required_digest_policy_v1`、`ai_agent_gitea_pr_draft_lane_v1`、`ai_agent_host_stateful_version_inventory_v1`、`GET /api/v1/agents/agent-proactive-operations-contract`、`GET /api/v1/agents/agent-version-freshness-snapshot`、`GET /api/v1/agents/agent-tool-adoption-approval-package`、`GET /api/v1/agents/agent-telegram-action-required-digest-policy`、`GET /api/v1/agents/agent-gitea-pr-draft-lane`、`GET /api/v1/agents/agent-host-stateful-version-inventory`、MASTER §3.2.1c | | 本工作清單與分析報告 | 100% | 已完成 | 本 MD 文件 | AI Agent 自動化工作包目前完成度:**92%**。本工作清單文件本身完成度:**100%**。 @@ -22,7 +22,7 @@ AI Agent 自動化工作包目前完成度:**92%**。本工作清單文件本 三 Agent 主動溝通與學習契約目前完成度:**35%**。已完成只讀 schema / snapshot / API / 測試與 MASTER 同步;下一步依優先順序推 `P2-401B` AgentSession / Redis Streams migration 與 worker gate,但在批准前仍不得啟動 runtime loop。 -AI Agent 主動營運委派與版本生命週期目前完成度:**78%**。已完成 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 版本只讀盤點,外部 registry / package source / host probe / 工具安裝 / CI 變更 / 實際 PR creation / Telegram 實發仍需 gate。 +AI Agent 主動營運委派與版本生命週期目前完成度:**86%**。已完成 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 顯示可委派能力,外部 registry / package source / host probe / SSH / kubectl / 工具安裝 / CI 變更 / 實際 PR creation / Telegram 實發仍需 gate。 完成度計算模型: @@ -956,7 +956,7 @@ UI: | P2-402C | 完成 | 100 | OpenClaw | 建立 Renovate / OSV-Scanner / Trivy / Syft / Grype 工具採用批准包 | `ai_agent_tool_adoption_approval_package_v1` / snapshot / 只讀 API / 測試;5 工具、5 官方來源、4 採用 lane、6 批准欄位 | 只讀;tool install、CI change、external lookup、vulnerability DB、PR、Telegram 全部未啟用 | | P2-402D | 完成 | 100 | OpenClaw | 建立 Telegram action-required digest policy | `ai_agent_telegram_action_required_digest_policy_v1` / snapshot / 只讀 API / 測試;8 條 digest rule、3 個 channel 草案、5 類 trigger category、成功降噪與 redaction policy | 只讀;Telegram send、Gateway queue write、route / receiver change、workflow、runtime 全部未啟用 | | P2-402E | 完成 | 100 | Hermes | 設計 Gitea PR 草案 lane | `ai_agent_gitea_pr_draft_lane_v1` / snapshot / 只讀 API / 測試;6 條 grouping rule、7 個 lane step、9 個 required check、6 條 rollback requirement、4 個 draft template | 只讀;branch push、PR creation、workflow trigger、lockfile、package upgrade、auto merge、Telegram 全部未啟用 | -| P2-402F | 待辦 | 0 | OpenClaw | 建立 host OS / K3s / stateful services 版本只讀盤點 | host / K3s / DB / Redis / MinIO / Gitea 版本矩陣 | host readonly probe + maintenance window approval | +| P2-402F | 完成 | 100 | OpenClaw | 建立 host OS / K3s / stateful services 版本只讀盤點 | `ai_agent_host_stateful_version_inventory_v1` / snapshot / 只讀 API / 測試;5 台主機、2 個 K3s 節點、12 個 stateful / ops 服務、6 個只讀 probe step、maintenance window approval package | 只讀;SSH、kubectl、apt / K3s upgrade、node drain、reboot、stateful restart、Telegram 全部未啟用 | | P2-402G | 待辦 | 0 | Hermes | 接入 governance UI 顯示可委派能力 | 自主等級、gate、owner、Telegram policy | frontend UI change approval | | P2-101 | 待辦 | 0 | OpenClaw | 定義操作類別權限模型 | 操作政策 schema | HITL 關卡 | | P2-102 | 待辦 | 0 | OpenClaw | 所有候選操作都要有 dry-run 證據 | dry-run 合約 | 不直接 apply | diff --git a/docs/ai/AI_AGENT_GITEA_PR_DRAFT_LANE_2026-06-11.md b/docs/ai/AI_AGENT_GITEA_PR_DRAFT_LANE_2026-06-11.md index 46e6d398..653c6612 100644 --- a/docs/ai/AI_AGENT_GITEA_PR_DRAFT_LANE_2026-06-11.md +++ b/docs/ai/AI_AGENT_GITEA_PR_DRAFT_LANE_2026-06-11.md @@ -1,7 +1,7 @@ # AI Agent Gitea PR 草案 Lane 工作報告 > 日期:2026-06-11(台北時間) -> 狀態:P2-402E 已完成;整體 AI Agent 主動營運與版本生命週期完成度 78%。 +> 狀態:P2-402E 已完成;P2-402F 已接續完成;整體 AI Agent 主動營運與版本生命週期完成度 86%。 > 邊界:本文件與對應 API 只提供 Gitea PR 草案 lane,不 push branch、不建立或更新 Gitea PR、不留言、不 auto merge、不觸發 workflow、不改 CI、不寫 lockfile、不升級套件、不 build / pull image、不發 Telegram、不顯示工作視窗對話內容。 ## 1. 本波完成項目 @@ -13,7 +13,7 @@ | API loader | 完成 | `apps/api/src/services/ai_agent_gitea_pr_draft_lane.py` | | 只讀 API | 完成 | `GET /api/v1/agents/agent-gitea-pr-draft-lane` | | 測試 | 完成 | loader / API / PR creation boundary / automerge boundary / blocking checks / owner response / transcript redaction | -| 主契約同步 | 完成 | `ai_agent_proactive_operations_contract_v1` 完成度 78%,current `P2-402E`,next `P2-402F` | +| 主契約同步 | 完成 | `ai_agent_proactive_operations_contract_v1` 已由 P2-402F 推進到完成度 86%,current `P2-402F`,next `P2-402G` | ## 2. Grouping 規則摘要 @@ -66,7 +66,7 @@ | 優先 | ID | 工作 | 完成標準 | |---:|---|---|---| -| 1 | P2-402F | host OS / K3s / stateful services 版本只讀盤點 | 主機、K3s、PostgreSQL、Redis、MinIO、Harbor、Gitea 版本矩陣;只讀 probe / maintenance approval | +| 1 | P2-402F | host OS / K3s / stateful services 版本只讀盤點 | 已完成;只讀 schema / snapshot / API / 測試,SSH / kubectl / upgrade / reboot 未啟用 | | 2 | P2-402G | Governance UI 顯示可委派能力 | 前端顯示 autonomy level、gate、owner、Telegram policy,無執行按鈕 | | 3 | 候選池 | Gitea bot / branch policy approval package | bot account、branch naming、PR permission、rollback、audit trail | | 4 | 候選池 | Draft PR packet renderer | 把 grouping / test / rollback / owner response 轉為可審核 PR 草案,不呼叫 Gitea API | diff --git a/docs/ai/AI_AGENT_HOST_STATEFUL_VERSION_INVENTORY_2026-06-11.md b/docs/ai/AI_AGENT_HOST_STATEFUL_VERSION_INVENTORY_2026-06-11.md new file mode 100644 index 00000000..8e355800 --- /dev/null +++ b/docs/ai/AI_AGENT_HOST_STATEFUL_VERSION_INVENTORY_2026-06-11.md @@ -0,0 +1,66 @@ +# AI Agent Host / K3s / Stateful 版本只讀盤點 + +> 日期:2026-06-11(台北時間) +> 狀態:P2-402F 已完成;整體 AI Agent 主動營運與版本生命週期完成度 86%。 +> 事實來源:`ai_agent_host_stateful_version_inventory_v1`、`ai_agent_proactive_operations_contract_v1`、MASTER §3.2.1c。 + +## 1. 本波完成 + +| 項目 | 狀態 | 說明 | +|---|---|---| +| Schema | 完成 | `docs/schemas/ai_agent_host_stateful_version_inventory_v1.schema.json` | +| Snapshot | 完成 | `docs/evaluations/ai_agent_host_stateful_version_inventory_2026-06-11.json` | +| 只讀 loader | 完成 | 強制 SSH / kubectl / upgrade / drain / reboot / restart / Telegram / transcript gate 全為 false | +| API | 完成 | `GET /api/v1/agents/agent-host-stateful-version-inventory` | +| 測試 | 完成 | loader / API / rollup / reboot / drain / stateful restart / redaction boundary | +| 主契約同步 | 完成 | `ai_agent_proactive_operations_contract_v1` 完成度 86%,current `P2-402F`,next `P2-402G` | + +## 2. 盤點範圍 + +| 類別 | 數量 | 內容 | +|---|---:|---| +| Host | 5 | 188 AI / DB / monitoring / backup;110 DevOps / registry;112 Kali read-only;120 K3s blocked;121 K3s peer / VIP evidence | +| K3s node | 2 | 120 server、121 server;需要 skew policy、node readiness、ArgoCD sync / health 與 rollback ref | +| Stateful / ops service | 12 | PostgreSQL、Redis、MinIO、ClickHouse、SignOz、Prometheus、Alertmanager、Gitea、Harbor、Sentry、Langfuse、ArgoCD | +| Readonly probe step | 6 | endpoint map、CD banner evidence、host probe packet、K3s skew packet、stateful packet、maintenance packet | +| Maintenance required fields | 11 | owner、decision、maintenance window、affected scope、backup snapshot、rollback、smoke、communication、risk acceptance | + +## 3. 仍維持 false + +| Gate | 值 | +|---|---| +| SSH login | false | +| Host command execution | false | +| Kubectl command execution | false | +| Apt / kernel / K3s upgrade | false | +| Node drain / kubelet restart | false | +| Reboot | false | +| Stateful service restart / upgrade | false | +| DB migration / restore / backup delete | false | +| Image pull / package install | false | +| External version lookup / active scan | false | +| Telegram direct send / queue write | false | +| Secret plaintext / 工作視窗對話內容 | false | + +## 4. Maintenance Window 批准包 + +任何從「只讀盤點」進到「live probe」或「變更」前,都必須補齊: + +| 欄位 | 用途 | +|---|---| +| owner / decision | 明確責任人與是否接受風險 | +| maintenance_window | 維護時間窗,不可由 Agent 自行假設 | +| affected_hosts / affected_services | 爆炸半徑與服務清單 | +| backup_snapshot_ref | 變更前備份證據 | +| rollback_owner / rollback_plan | 回滾責任人與步驟 | +| smoke_plan | public / NodePort / stateful / backup / rollback readback | +| communication_plan | Telegram / AwoooP / owner 溝通草案 | +| risk_acceptance | 人工接受風險,不可由 AI 自動批准 | + +## 5. 下一步 + +| 優先 | 任務 | 關卡 | +|---:|---|---| +| 1 | P2-402G Governance UI 顯示可委派能力 | frontend UI change approval | +| 2 | Host readonly probe owner request | 需 owner approval,不得直接 SSH / kubectl | +| 3 | Stateful backup freshness packet | 需備份 evidence,不得 restore / prune | diff --git a/docs/ai/AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md b/docs/ai/AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md index 3af93525..898230c4 100644 --- a/docs/ai/AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md +++ b/docs/ai/AI_AGENT_PROACTIVE_OPERATIONS_2026-06-11.md @@ -1,7 +1,7 @@ # AI Agent 主動營運委派與版本生命週期分析報告 > 日期:2026-06-11(台北時間) -> 文件定位:P2-402A / P2-402B / P2-402C / P2-402D / P2-402E 只讀契約摘要。權威細節以 MASTER §3.2.1c、`ai_agent_proactive_operations_contract_v1`、`ai_agent_version_freshness_snapshot_v1`、`ai_agent_tool_adoption_approval_package_v1`、`ai_agent_telegram_action_required_digest_policy_v1` 與 `ai_agent_gitea_pr_draft_lane_v1` 為準。 +> 文件定位:P2-402A / P2-402B / P2-402C / P2-402D / P2-402E / P2-402F 只讀契約摘要。權威細節以 MASTER §3.2.1c、`ai_agent_proactive_operations_contract_v1`、`ai_agent_version_freshness_snapshot_v1`、`ai_agent_tool_adoption_approval_package_v1`、`ai_agent_telegram_action_required_digest_policy_v1`、`ai_agent_gitea_pr_draft_lane_v1` 與 `ai_agent_host_stateful_version_inventory_v1` 為準。 ## 1. 本波完成度 @@ -12,7 +12,8 @@ | 工具採用批准包 | 100% | P2-402C 已完成 Renovate / OSV-Scanner / Trivy / Syft / Grype schema / committed snapshot / API / 測試 | | Telegram action-required digest policy | 100% | P2-402D 已完成 critical / action-required / failure-only digest schema / committed snapshot / API / 測試 | | Gitea PR 草案 lane | 100% | P2-402E 已完成 grouping、automerge=false、測試證據、rollback、owner response schema / committed snapshot / API / 測試 | -| 整體主動營運與版本生命週期 | 78% | 已完成架構、repo-only freshness、工具採用批准包、Telegram digest policy 與 Gitea PR 草案 lane;runtime 排程、工具安裝、CI 變更、實際 PR 建立與更新仍未開 gate | +| Host / K3s / stateful 版本只讀盤點 | 100% | P2-402F 已完成 5 台主機、2 個 K3s 節點、12 個 stateful / ops 服務與 maintenance window 批准包 schema / committed snapshot / API / 測試 | +| 整體主動營運與版本生命週期 | 86% | 已完成架構、repo-only freshness、工具採用批准包、Telegram digest policy、Gitea PR 草案 lane 與 host / K3s / stateful 版本只讀盤點;runtime 排程、工具安裝、CI 變更、實際 PR 建立與更新、host probe、升級、重啟仍未開 gate | ## 2. 可交給 AI Agent 的工作分類 @@ -46,6 +47,9 @@ | `docs/schemas/ai_agent_gitea_pr_draft_lane_v1.schema.json` | Gitea PR 草案 lane schema;branch push、PR creation、workflow trigger、lockfile、automerge、Telegram 全部預設 false | | `docs/evaluations/ai_agent_gitea_pr_draft_lane_2026-06-11.json` | 6 條 grouping rule、7 個 lane step、9 個 required check、6 條 rollback requirement、4 個 draft template | | `GET /api/v1/agents/agent-gitea-pr-draft-lane` | 只讀 API;不 push branch、不建立 PR、不觸發 workflow、不改 CI、不升級套件、不發 Telegram | +| `docs/schemas/ai_agent_host_stateful_version_inventory_v1.schema.json` | Host / K3s / stateful 版本只讀盤點 schema;SSH、kubectl、upgrade、drain、reboot、restart、Telegram 全部預設 false | +| `docs/evaluations/ai_agent_host_stateful_version_inventory_2026-06-11.json` | 5 台主機、2 個 K3s 節點、12 個 stateful / ops 服務、6 個只讀 probe 步驟、maintenance window approval package | +| `GET /api/v1/agents/agent-host-stateful-version-inventory` | 只讀 API;不 SSH、不 kubectl、不升級、不 drain、不 reboot、不重啟 stateful、不發 Telegram | ## 4. P2-402B Repo-only 版本新鮮度快照 @@ -94,16 +98,28 @@ P2-402C 的重點是把「哪些工具可被採用、採用前要填哪些批准 P2-402E 的重點是先把「PR 草案要長什麼樣、要有哪些證據、誰要回覆、怎麼 rollback」固定成契約。它不是 Gitea bot 啟用;bot account、branch policy、實際 PR creation、workflow trigger、lockfile write、package upgrade、Telegram 實發全部維持 blocked。 -## 8. 下一步優先順序 +## 8. P2-402F Host / K3s / Stateful 版本只讀盤點 + +| 類別 | 本波定義 | 仍禁止 | +|---|---|---| +| Host inventory | 5 台主機:188 AI / DB / monitoring / backup、110 DevOps / registry、112 Kali read-only、120 K3s blocked、121 K3s peer / VIP evidence | 不 SSH、不跑 host command、不 apt upgrade、不 reboot | +| K3s inventory | 2 個 K3s server 節點與 VIP API endpoint;要求 version skew policy、node readiness、ArgoCD sync / health、rollback ref | 不 kubectl、不 K3s upgrade、不 node drain、不 kubelet restart | +| Stateful services | 12 個服務:PostgreSQL、Redis、MinIO、ClickHouse、SignOz、Prometheus、Alertmanager、Gitea、Harbor、Sentry、Langfuse、ArgoCD | 不 restart、不 upgrade、不 migration、不 restore、不刪 backup | +| Readonly probe packet | 6 個 planned-only step:repo endpoint map、CD banner evidence、host probe packet、K3s skew packet、stateful packet、maintenance window packet | 不把 planned probe 當已執行,不開 runtime gate | +| Maintenance window | owner、decision、maintenance window、affected scope、backup snapshot、rollback owner、smoke plan、communication plan、risk acceptance | 不用 Agent 自我批准取代 owner gate | + +P2-402F 的重點是把「要盤哪些主機與 stateful 服務、盤點前要誰批准、升級前要哪些備份與 rollback 證據」固定成資料契約。它不是主機探測或升級;SSH、kubectl、apt upgrade、K3s upgrade、node drain、reboot、stateful restart、Telegram 實發全部維持 blocked。 + +## 9. 下一步優先順序 | ID | 優先 | 任務 | 關卡 | |---|---|---|---| | P2-402D | 0 | Telegram action-required digest policy | 已完成,只讀;Telegram send / queue write 未啟用 | | P2-402E | 1 | Gitea PR 草案 lane | 已完成,只讀;branch push / PR creation / workflow trigger 未啟用 | -| P2-402F | 2 | host OS / K3s / stateful services 版本只讀盤點 | host probe / maintenance approval | +| P2-402F | 2 | host OS / K3s / stateful services 版本只讀盤點 | 已完成,只讀;SSH / kubectl / upgrade / reboot 未啟用 | | P2-402G | 3 | governance UI 顯示可委派能力 | frontend UI approval | -## 8. 仍維持 false 的安全邊界 +## 10. 仍維持 false 的安全邊界 - `runtime_version_update_allowed=false` - `package_upgrade_allowed=false` @@ -119,3 +135,10 @@ P2-402E 的重點是先把「PR 草案要長什麼樣、要有哪些證據、誰 - `external_registry_lookup_allowed=false` - `daily_schedule_enabled=false` - `gitea_pr_creation_allowed=false` +- `ssh_login_allowed=false` +- `kubectl_command_execution_allowed=false` +- `apt_upgrade_allowed=false` +- `k3s_upgrade_allowed=false` +- `node_drain_allowed=false` +- `reboot_allowed=false` +- `stateful_service_restart_allowed=false` diff --git a/docs/ai/AI_AGENT_TELEGRAM_ACTION_REQUIRED_DIGEST_POLICY_2026-06-11.md b/docs/ai/AI_AGENT_TELEGRAM_ACTION_REQUIRED_DIGEST_POLICY_2026-06-11.md index 227f4867..fdd85f96 100644 --- a/docs/ai/AI_AGENT_TELEGRAM_ACTION_REQUIRED_DIGEST_POLICY_2026-06-11.md +++ b/docs/ai/AI_AGENT_TELEGRAM_ACTION_REQUIRED_DIGEST_POLICY_2026-06-11.md @@ -50,6 +50,6 @@ | 優先 | ID | 工作 | 完成標準 | |---:|---|---|---| | 1 | P2-402E | Gitea PR 草案 lane | 已完成:Renovate grouping、automerge=false、測試要求、rollback、owner response;bot / branch policy 仍未批准 | -| 2 | P2-402F | host OS / K3s / stateful services 版本只讀盤點 | 主機、K3s、PostgreSQL、Redis、MinIO、Harbor、Gitea 只讀版本矩陣 | +| 2 | P2-402F | host OS / K3s / stateful services 版本只讀盤點 | 已完成;主機、K3s、PostgreSQL、Redis、MinIO、Harbor、Gitea 只讀版本矩陣 | | 3 | P2-402G | Governance UI 顯示可委派能力 | 前端顯示 autonomy level、gate、owner、Telegram policy,無執行按鈕 | | 4 | 候選池 | Telegram Gateway E2E 批准包 | queue write / send / callback / fallback path 的測試與 rollout gate | diff --git a/docs/ai/AI_AGENT_TOOL_ADOPTION_APPROVAL_PACKAGE_2026-06-11.md b/docs/ai/AI_AGENT_TOOL_ADOPTION_APPROVAL_PACKAGE_2026-06-11.md index 433f0116..b1bf60e3 100644 --- a/docs/ai/AI_AGENT_TOOL_ADOPTION_APPROVAL_PACKAGE_2026-06-11.md +++ b/docs/ai/AI_AGENT_TOOL_ADOPTION_APPROVAL_PACKAGE_2026-06-11.md @@ -39,7 +39,7 @@ |---:|---|---|---|---| | 1 | P2-402D | Telegram action-required digest policy | 定義 critical / action-required / failure-only,禁止成功洗版,接 Telegram Gateway E2E gate | 完成 | | 2 | P2-402E | Gitea PR 草案 lane | Renovate grouping、automerge=false、測試要求、rollback、owner response | 完成 | -| 3 | P2-402F | host OS / K3s / stateful services 版本只讀盤點 | 主機、K3s、PostgreSQL、Redis、MinIO、Harbor、Gitea 只讀版本矩陣 | 待辦 | +| 3 | P2-402F | host OS / K3s / stateful services 版本只讀盤點 | 主機、K3s、PostgreSQL、Redis、MinIO、Harbor、Gitea 只讀版本矩陣 | 完成 | | 4 | P2-402G | Governance UI 顯示可委派能力 | 前端顯示 autonomy level、gate、owner、Telegram policy,無執行按鈕 | 待辦 | | 5 | 候選池 | SBOM artifact policy | Syft / Trivy artifact 格式、保存、redaction、消費者規則 | 未排正式編號 | | 6 | 候選池 | Scanner false-positive policy | OSV / Trivy / Grype 差異、suppression、severity mapping、owner SLA | 未排正式編號 | diff --git a/docs/evaluations/ai_agent_host_stateful_version_inventory_2026-06-11.json b/docs/evaluations/ai_agent_host_stateful_version_inventory_2026-06-11.json new file mode 100644 index 00000000..7139e4be --- /dev/null +++ b/docs/evaluations/ai_agent_host_stateful_version_inventory_2026-06-11.json @@ -0,0 +1,586 @@ +{ + "schema_version": "ai_agent_host_stateful_version_inventory_v1", + "generated_at": "2026-06-11T15:31:00+08:00", + "program_status": { + "overall_completion_percent": 86, + "current_task_id": "P2-402F", + "next_task_id": "P2-402G", + "read_only_mode": true, + "runtime_authority": "host_stateful_readonly_inventory_no_upgrade_or_restart", + "status_note": "已建立 host OS / K3s / stateful services 版本只讀盤點與 maintenance window 批准包;仍不執行 SSH、kubectl、升級、重啟、drain、restore、Telegram 實發或外部版本查詢。" + }, + "source_refs": [ + { + "ref_id": "service_endpoints", + "path": "docs/reference/SERVICE-ENDPOINTS.md", + "usage": "主機、K3s VIP、AI、資料庫、監控、DevOps、備份與安全端點來源。" + }, + { + "ref_id": "deployment_layout", + "path": "docs/evaluations/ai_agent_deployment_layout_2026-06-11.json", + "usage": "AI Agent 在 188 / 110 / 112 / 120 / 121 的只讀佈建與 gate 來源。" + }, + { + "ref_id": "version_freshness", + "path": "docs/evaluations/ai_agent_version_freshness_snapshot_2026-06-11.json", + "usage": "P2-402B 已明確把 host / K3s live version probe 留給 P2-402F gate。" + }, + { + "ref_id": "latest_cd_evidence", + "path": "Gitea cd.yaml run 2667 log", + "usage": "部署過程只讀看到 121/125 login banner 與 rollout evidence;未使用 SSH 手動命令,未收 secret value。" + }, + { + "ref_id": "hard_rules_high_value_config", + "path": "docs/HARD_RULES.md#high-value-config-control", + "usage": "主機、K3s、workflow、secret、backup、AI provider 與 runtime config 變更必須 source-of-truth、owner gate、diff、rollback 與驗證。" + } + ], + "operation_boundaries": { + "read_only_inventory_allowed": true, + "ssh_login_allowed": false, + "host_command_execution_allowed": false, + "kubectl_command_execution_allowed": false, + "apt_upgrade_allowed": false, + "os_release_upgrade_allowed": false, + "kernel_upgrade_allowed": false, + "k3s_upgrade_allowed": false, + "kubelet_restart_allowed": false, + "node_drain_allowed": false, + "reboot_allowed": false, + "stateful_service_restart_allowed": false, + "database_migration_allowed": false, + "backup_delete_allowed": false, + "restore_execution_allowed": false, + "image_pull_allowed": false, + "package_install_allowed": false, + "external_version_lookup_allowed": false, + "active_network_scan_allowed": false, + "telegram_direct_send_allowed": false, + "telegram_gateway_queue_write_allowed": false, + "secret_plaintext_allowed": false, + "conversation_transcript_allowed": false + }, + "approval_boundaries": { + "host_readonly_probe_approved": false, + "ssh_probe_approved": false, + "kubectl_probe_approved": false, + "maintenance_window_approved": false, + "apt_upgrade_approved": false, + "kernel_upgrade_approved": false, + "k3s_upgrade_approved": false, + "node_drain_approved": false, + "reboot_approved": false, + "stateful_restart_approved": false, + "stateful_upgrade_approved": false, + "backup_restore_drill_approved": false, + "telegram_send_approved": false + }, + "agent_roles": [ + { + "agent": "OpenClaw", + "role": "仲裁者", + "responsibility": "判斷 host / K3s / stateful 版本盤點是否可進 owner gate,禁止在證據不足時推升級或重啟。" + }, + { + "agent": "Hermes", + "role": "執行規劃者", + "responsibility": "整理 repo evidence、probe 草案、maintenance window packet 與 rollback checklist,不執行命令。" + }, + { + "agent": "NemoTron", + "role": "研究助理", + "responsibility": "在 dry-run / human-review 邊界內協助摘要版本 skew 與相依性風險,不做生產建議最終裁決。" + } + ], + "host_inventory": [ + { + "host_id": "host_188_ai_db_monitoring_backup", + "display_name": "188 AI / DB / monitoring / backup host", + "known_addresses": [ + "192.168.0.188" + ], + "primary_surfaces": [ + "Ollama", + "OpenClaw", + "PostgreSQL", + "Redis", + "SignOz", + "ClickHouse", + "Prometheus", + "Alertmanager", + "MinIO" + ], + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "host_update_authorized": false, + "reboot_authorized": false, + "maintenance_window_required": true, + "next_evidence_needed": [ + "os-release", + "kernel version", + "docker / container runtime version", + "stateful service versions", + "backup freshness" + ], + "blocked_actions": [ + "apt upgrade", + "kernel upgrade", + "reboot", + "stateful restart", + "backup delete", + "restore execution" + ] + }, + { + "host_id": "host_110_devops_registry", + "display_name": "110 DevOps / registry host", + "known_addresses": [ + "192.168.0.110" + ], + "primary_surfaces": [ + "Gitea", + "Harbor registry", + "Sentry", + "Langfuse", + "Prometheus blackbox exporter" + ], + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "host_update_authorized": false, + "reboot_authorized": false, + "maintenance_window_required": true, + "next_evidence_needed": [ + "os-release", + "Gitea version", + "registry version", + "Sentry / Langfuse version", + "runner health" + ], + "blocked_actions": [ + "workflow change", + "runner restart", + "registry image delete", + "host package upgrade", + "reboot" + ] + }, + { + "host_id": "host_112_kali_readonly", + "display_name": "112 Kali scanner read-only host", + "known_addresses": [ + "192.168.0.112" + ], + "primary_surfaces": [ + "Kali scanner API", + "security evidence collector" + ], + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "host_update_authorized": false, + "reboot_authorized": false, + "maintenance_window_required": true, + "next_evidence_needed": [ + "scanner service version", + "tool package baseline", + "active scan gate state" + ], + "blocked_actions": [ + "active scan", + "credentialed scan", + "tool upgrade", + "reboot" + ] + }, + { + "host_id": "host_120_k3s_master_blocked", + "display_name": "120 K3s master / blocked recovery host", + "known_addresses": [ + "192.168.0.120" + ], + "primary_surfaces": [ + "K3s server", + "control-plane recovery context" + ], + "version_observation_status": "blocked_host_live_probe_not_run", + "readonly_only": true, + "host_update_authorized": false, + "reboot_authorized": false, + "maintenance_window_required": true, + "next_evidence_needed": [ + "reachability evidence", + "K3s version", + "node readiness", + "kubelet status" + ], + "blocked_actions": [ + "ssh probe", + "node drain", + "K3s upgrade", + "kubelet restart", + "reboot" + ] + }, + { + "host_id": "host_121_k3s_peer", + "display_name": "121 K3s peer host", + "known_addresses": [ + "192.168.0.121", + "192.168.0.125" + ], + "primary_surfaces": [ + "K3s server", + "VIP endpoint", + "AWOOOI API/Web/Worker rollout evidence" + ], + "version_observation_status": "cd_login_banner_observed_os_only", + "observed_evidence_summary": "Gitea CD run 2667 login banner observed Ubuntu 22.04.5 LTS and kernel 5.15.0-179-generic on the deploy target; this is deployment evidence only, not a full host version inventory.", + "readonly_only": true, + "host_update_authorized": false, + "reboot_authorized": false, + "maintenance_window_required": true, + "next_evidence_needed": [ + "K3s version", + "kubectl version", + "container runtime version", + "node readiness", + "pending package updates with owner gate" + ], + "blocked_actions": [ + "apt upgrade", + "do-release-upgrade", + "node drain", + "K3s upgrade", + "reboot" + ] + } + ], + "k3s_inventory": { + "cluster_id": "awoooi_prod_k3s", + "api_endpoint": "192.168.0.125:6443", + "version_observation_status": "repo_reference_only_kubectl_probe_not_run", + "skew_policy_required": true, + "upgrade_authorized": false, + "nodes": [ + { + "node_id": "k3s_120", + "host_id": "host_120_k3s_master_blocked", + "role": "server", + "readonly_only": true, + "drain_authorized": false, + "kubelet_restart_authorized": false, + "version_observation_status": "blocked_host_live_probe_not_run" + }, + { + "node_id": "k3s_121", + "host_id": "host_121_k3s_peer", + "role": "server", + "readonly_only": true, + "drain_authorized": false, + "kubelet_restart_authorized": false, + "version_observation_status": "rollout_evidence_only_version_probe_not_run" + } + ], + "required_pre_change_evidence": [ + "kubectl version --client/output redacted", + "K3s server version", + "node readiness", + "current ArgoCD sync / health", + "version skew policy assessment", + "rollback ref" + ] + }, + "stateful_services": [ + { + "service_id": "postgresql", + "display_name": "PostgreSQL", + "host_id": "host_188_ai_db_monitoring_backup", + "endpoint_ref": "docs/reference/SERVICE-ENDPOINTS.md#資料庫-1921680188", + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "restart_authorized": false, + "upgrade_authorized": false, + "backup_required_before_change": true + }, + { + "service_id": "redis", + "display_name": "Redis", + "host_id": "host_188_ai_db_monitoring_backup", + "endpoint_ref": "docs/reference/SERVICE-ENDPOINTS.md#資料庫-1921680188", + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "restart_authorized": false, + "upgrade_authorized": false, + "backup_required_before_change": true + }, + { + "service_id": "minio", + "display_name": "MinIO", + "host_id": "host_188_ai_db_monitoring_backup", + "endpoint_ref": "docs/reference/SERVICE-ENDPOINTS.md#備份-1921680188", + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "restart_authorized": false, + "upgrade_authorized": false, + "backup_required_before_change": true + }, + { + "service_id": "clickhouse", + "display_name": "ClickHouse", + "host_id": "host_188_ai_db_monitoring_backup", + "endpoint_ref": "docs/reference/SERVICE-ENDPOINTS.md#監控-1921680188--1921680110", + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "restart_authorized": false, + "upgrade_authorized": false, + "backup_required_before_change": true + }, + { + "service_id": "signoz", + "display_name": "SignOz", + "host_id": "host_188_ai_db_monitoring_backup", + "endpoint_ref": "docs/reference/SERVICE-ENDPOINTS.md#監控-1921680188--1921680110", + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "restart_authorized": false, + "upgrade_authorized": false, + "backup_required_before_change": true + }, + { + "service_id": "prometheus", + "display_name": "Prometheus", + "host_id": "host_188_ai_db_monitoring_backup", + "endpoint_ref": "docs/reference/SERVICE-ENDPOINTS.md#監控-1921680188--1921680110", + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "restart_authorized": false, + "upgrade_authorized": false, + "backup_required_before_change": true + }, + { + "service_id": "alertmanager", + "display_name": "Alertmanager", + "host_id": "host_188_ai_db_monitoring_backup", + "endpoint_ref": "docs/reference/SERVICE-ENDPOINTS.md#監控-1921680188--1921680110", + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "restart_authorized": false, + "upgrade_authorized": false, + "backup_required_before_change": true + }, + { + "service_id": "gitea", + "display_name": "Gitea", + "host_id": "host_110_devops_registry", + "endpoint_ref": "docs/reference/SERVICE-ENDPOINTS.md#devops-1921680110", + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "restart_authorized": false, + "upgrade_authorized": false, + "backup_required_before_change": true + }, + { + "service_id": "harbor_registry", + "display_name": "Harbor / registry", + "host_id": "host_110_devops_registry", + "endpoint_ref": "docs/reference/SERVICE-ENDPOINTS.md#devops-1921680110", + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "restart_authorized": false, + "upgrade_authorized": false, + "backup_required_before_change": true + }, + { + "service_id": "sentry", + "display_name": "Sentry", + "host_id": "host_110_devops_registry", + "endpoint_ref": "docs/reference/SERVICE-ENDPOINTS.md#監控-1921680188--1921680110", + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "restart_authorized": false, + "upgrade_authorized": false, + "backup_required_before_change": true + }, + { + "service_id": "langfuse", + "display_name": "Langfuse", + "host_id": "host_110_devops_registry", + "endpoint_ref": "docs/reference/SERVICE-ENDPOINTS.md#監控-1921680188--1921680110", + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "restart_authorized": false, + "upgrade_authorized": false, + "backup_required_before_change": true + }, + { + "service_id": "argocd", + "display_name": "ArgoCD", + "host_id": "host_121_k3s_peer", + "endpoint_ref": "docs/reference/SERVICE-ENDPOINTS.md#k3s-叢集管理", + "version_observation_status": "repo_reference_only_live_probe_not_run", + "readonly_only": true, + "restart_authorized": false, + "upgrade_authorized": false, + "backup_required_before_change": true + } + ], + "readonly_probe_plan": [ + { + "step_id": "repo_endpoint_map", + "display_name": "Repo endpoint map 對齊", + "planned_output": "整理 SERVICE-ENDPOINTS、runtime surface、deployment layout 的主機與服務 mapping。", + "run_now_allowed": false, + "mutation_allowed": false + }, + { + "step_id": "cd_banner_evidence_extract", + "display_name": "CD banner evidence 摘要", + "planned_output": "只讀引用最新 CD login banner / rollout log,標註其侷限,不當成完整版本盤點。", + "run_now_allowed": false, + "mutation_allowed": false + }, + { + "step_id": "host_readonly_probe_packet", + "display_name": "Host readonly probe packet", + "planned_output": "準備 os-release、kernel、container runtime、package pending summary 的 owner approval request。", + "run_now_allowed": false, + "mutation_allowed": false + }, + { + "step_id": "k3s_version_skew_packet", + "display_name": "K3s version skew packet", + "planned_output": "準備 K3s / kubectl / node readiness / skew policy 的只讀證據欄位。", + "run_now_allowed": false, + "mutation_allowed": false + }, + { + "step_id": "stateful_service_version_packet", + "display_name": "Stateful service version packet", + "planned_output": "準備 PostgreSQL、Redis、MinIO、Gitea、Harbor、監控服務的版本與備份前置欄位。", + "run_now_allowed": false, + "mutation_allowed": false + }, + { + "step_id": "maintenance_window_packet", + "display_name": "Maintenance window approval packet", + "planned_output": "產出 owner、affected scope、backup、rollback、smoke、communication 與 risk acceptance 欄位。", + "run_now_allowed": false, + "mutation_allowed": false + } + ], + "maintenance_window_approval_package": { + "package_id": "host_stateful_maintenance_window_packet_v1", + "approval_required_before_probe": true, + "approval_required_before_change": true, + "break_glass_record_required": true, + "required_fields": [ + "owner", + "decision", + "maintenance_window", + "affected_hosts", + "affected_services", + "backup_snapshot_ref", + "rollback_owner", + "rollback_plan", + "smoke_plan", + "communication_plan", + "risk_acceptance" + ], + "forbidden_fields": [ + "secret value", + "private key", + "authorization header", + "cookie", + "session payload", + "work-window transcript", + "chain-of-thought" + ], + "minimum_smoke_plan": [ + "public health", + "internal NodePort health when applicable", + "stateful service ready check", + "backup freshness readback", + "rollback ref readback" + ] + }, + "telegram_policy": { + "status": "draft_only", + "direct_send_allowed": false, + "gateway_queue_write_allowed": false, + "allowed_digest_types_after_gate": [ + "action_required_missing_evidence", + "critical_version_skew", + "maintenance_window_owner_response_needed", + "rollout_failure" + ], + "success_noise_suppression": true + }, + "display_redaction_contract": { + "conversation_transcript_display_allowed": false, + "redaction_required": true, + "allowed_frontend_fields": [ + "整體完成度", + "目前任務", + "下一任務", + "主機版本盤點摘要", + "K3s skew gate", + "stateful services 摘要", + "maintenance window approval package" + ], + "forbidden_frontend_content": [ + "工作視窗對話內容", + "secret 明文", + "private key", + "authorization header", + "cookie", + "prompt 全文", + "chain-of-thought", + "browser session context" + ] + }, + "rollups": { + "host_count": 5, + "k3s_node_count": 2, + "stateful_service_count": 12, + "readonly_probe_step_count": 6, + "maintenance_required_field_count": 11, + "host_ids": [ + "host_110_devops_registry", + "host_112_kali_readonly", + "host_120_k3s_master_blocked", + "host_121_k3s_peer", + "host_188_ai_db_monitoring_backup" + ], + "stateful_service_ids": [ + "alertmanager", + "argocd", + "clickhouse", + "gitea", + "harbor_registry", + "langfuse", + "minio", + "postgresql", + "prometheus", + "redis", + "sentry", + "signoz" + ], + "ssh_login_allowed_count": 0, + "kubectl_command_execution_allowed_count": 0, + "apt_upgrade_allowed_count": 0, + "k3s_upgrade_allowed_count": 0, + "node_drain_allowed_count": 0, + "reboot_allowed_count": 0, + "stateful_service_restart_allowed_count": 0, + "telegram_direct_send_allowed_count": 0, + "conversation_transcript_allowed_count": 0 + }, + "next_actions": [ + { + "task_id": "P2-402G", + "priority": "P2", + "summary": "把可委派能力接入 governance UI,顯示 autonomy level、gate、owner、Telegram policy。", + "gate": "frontend_ui_change_approval_required" + } + ] +} diff --git a/docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json b/docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json index eaa99576..0bdaa36c 100644 --- a/docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json +++ b/docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json @@ -2,13 +2,13 @@ "schema_version": "ai_agent_proactive_operations_contract_v1", "generated_at": "2026-06-11T21:30:00+08:00", "program_status": { - "overall_completion_percent": 78, + "overall_completion_percent": 86, "current_priority": "P2", - "current_task_id": "P2-402E", - "next_task_id": "P2-402F", + "current_task_id": "P2-402F", + "next_task_id": "P2-402G", "read_only_mode": true, "runtime_authority": "contract_only_no_version_or_runtime_update", - "status_note": "P2-402E 已建立 Gitea PR 草案 lane、schema、snapshot、只讀 API 與測試;本波仍不 push branch、不建立或更新 Gitea PR、不觸發 workflow、不改 CI、不寫 lockfile、不升級套件、不發 Telegram。" + "status_note": "P2-402F 已建立 host OS / K3s / stateful services 版本只讀盤點與 maintenance window 批准包;本波仍不 SSH、不執行 kubectl、不升級主機 / K3s / stateful service、不 drain、不 reboot、不發 Telegram。" }, "external_source_evidence": [ { @@ -646,16 +646,16 @@ "completion_percent": 100, "owner_agent": "Hermes", "summary": "建立 Gitea PR 草案 lane、schema、snapshot、只讀 API 與測試;定義 grouping、automerge=false、測試證據、rollback、owner response 與 redaction policy。", - "next_gate": "P2-402F_host_stateful_version_inventory" + "next_gate": "P2-402F_completed" }, { "task_id": "P2-402F", "priority": "P2", - "status": "planned", - "completion_percent": 0, + "status": "done", + "completion_percent": 100, "owner_agent": "OpenClaw", - "summary": "建立 host OS / K3s / stateful services 版本只讀盤點與 maintenance window 批准包。", - "next_gate": "host_readonly_probe_and_maintenance_window_approval_required" + "summary": "建立 host OS / K3s / stateful services 版本只讀盤點、maintenance window 批准包、schema、snapshot、只讀 API 與測試;所有 SSH / kubectl / upgrade / drain / reboot / restart gate 維持 false。", + "next_gate": "P2-402G_governance_ui_capability_display" }, { "task_id": "P2-402G", diff --git a/docs/schemas/ai_agent_host_stateful_version_inventory_v1.schema.json b/docs/schemas/ai_agent_host_stateful_version_inventory_v1.schema.json new file mode 100644 index 00000000..570937f2 --- /dev/null +++ b/docs/schemas/ai_agent_host_stateful_version_inventory_v1.schema.json @@ -0,0 +1,184 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://awoooi.wooo.work/schemas/ai_agent_host_stateful_version_inventory_v1.schema.json", + "title": "AI Agent Host Stateful Version Inventory v1", + "type": "object", + "required": [ + "schema_version", + "generated_at", + "program_status", + "operation_boundaries", + "approval_boundaries", + "host_inventory", + "k3s_inventory", + "stateful_services", + "readonly_probe_plan", + "maintenance_window_approval_package", + "display_redaction_contract", + "rollups" + ], + "additionalProperties": true, + "properties": { + "schema_version": { + "const": "ai_agent_host_stateful_version_inventory_v1" + }, + "generated_at": { + "type": "string" + }, + "program_status": { + "type": "object", + "required": [ + "overall_completion_percent", + "current_task_id", + "next_task_id", + "read_only_mode", + "runtime_authority" + ], + "properties": { + "overall_completion_percent": { + "const": 86 + }, + "current_task_id": { + "const": "P2-402F" + }, + "next_task_id": { + "const": "P2-402G" + }, + "read_only_mode": { + "const": true + }, + "runtime_authority": { + "const": "host_stateful_readonly_inventory_no_upgrade_or_restart" + } + } + }, + "operation_boundaries": { + "type": "object", + "required": [ + "read_only_inventory_allowed", + "ssh_login_allowed", + "host_command_execution_allowed", + "kubectl_command_execution_allowed", + "apt_upgrade_allowed", + "k3s_upgrade_allowed", + "node_drain_allowed", + "reboot_allowed", + "stateful_service_restart_allowed", + "telegram_direct_send_allowed", + "secret_plaintext_allowed", + "conversation_transcript_allowed" + ], + "properties": { + "read_only_inventory_allowed": { + "const": true + }, + "ssh_login_allowed": { + "const": false + }, + "host_command_execution_allowed": { + "const": false + }, + "kubectl_command_execution_allowed": { + "const": false + }, + "apt_upgrade_allowed": { + "const": false + }, + "k3s_upgrade_allowed": { + "const": false + }, + "node_drain_allowed": { + "const": false + }, + "reboot_allowed": { + "const": false + }, + "stateful_service_restart_allowed": { + "const": false + }, + "telegram_direct_send_allowed": { + "const": false + }, + "secret_plaintext_allowed": { + "const": false + }, + "conversation_transcript_allowed": { + "const": false + } + } + }, + "host_inventory": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "host_id", + "readonly_only", + "version_observation_status", + "host_update_authorized", + "reboot_authorized", + "maintenance_window_required" + ], + "properties": { + "readonly_only": { + "const": true + }, + "host_update_authorized": { + "const": false + }, + "reboot_authorized": { + "const": false + }, + "maintenance_window_required": { + "const": true + } + } + } + }, + "stateful_services": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "service_id", + "readonly_only", + "version_observation_status", + "restart_authorized", + "upgrade_authorized", + "backup_required_before_change" + ], + "properties": { + "readonly_only": { + "const": true + }, + "restart_authorized": { + "const": false + }, + "upgrade_authorized": { + "const": false + }, + "backup_required_before_change": { + "const": true + } + } + } + }, + "display_redaction_contract": { + "type": "object", + "required": [ + "conversation_transcript_display_allowed", + "redaction_required" + ], + "properties": { + "conversation_transcript_display_allowed": { + "const": false + }, + "redaction_required": { + "const": true + } + } + } + } +} 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 fe0d54bd..614fc9e5 100644 --- a/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md +++ b/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md @@ -670,7 +670,7 @@ Repo / registry / release notes / K8s / host / observability / backup evidence | 檔案 / API | 用途 | |---|---| | `docs/schemas/ai_agent_proactive_operations_contract_v1.schema.json` | 主動營運委派、版本生命週期、MCP、RAG、Telegram policy、approval boundary 契約 | -| `docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json` | 12 類版本 domain、24 類可委派能力、5 種 cadence、8 類 MCP、4 類 RAG memory;完成度 `78%` | +| `docs/evaluations/ai_agent_proactive_operations_contract_2026-06-11.json` | 12 類版本 domain、24 類可委派能力、5 種 cadence、8 類 MCP、4 類 RAG memory;完成度 `86%` | | `apps/api/src/services/ai_agent_proactive_operations_contract.py` | 只讀 loader;強制 runtime update / package upgrade / host upgrade / workflow schedule / auto merge / Telegram direct send 全部 false | | `GET /api/v1/agents/agent-proactive-operations-contract` | 治理 API;只回傳 committed snapshot,不啟用排程、不升級、不呼叫付費服務 | | `docs/schemas/ai_agent_version_freshness_snapshot_v1.schema.json` | P2-402B repo-only 版本新鮮度 schema;鎖定 schedule / external lookup / upgrade / Telegram / PR / host probe 全部 false | @@ -685,6 +685,9 @@ Repo / registry / release notes / K8s / host / observability / backup evidence | `docs/schemas/ai_agent_gitea_pr_draft_lane_v1.schema.json` | P2-402E Gitea PR 草案 lane schema;鎖定 branch push / PR creation / workflow trigger / lockfile / automerge / Telegram 全部 false | | `docs/evaluations/ai_agent_gitea_pr_draft_lane_2026-06-11.json` | P2-402E committed snapshot:6 條 grouping rule、7 個 lane step、9 個 required check、6 條 rollback requirement、4 個 draft template | | `GET /api/v1/agents/agent-gitea-pr-draft-lane` | 只讀 API;只回傳 PR 草案 lane,不 push branch、不建立 PR、不觸發 workflow、不改 CI、不升級套件、不發 Telegram | +| `docs/schemas/ai_agent_host_stateful_version_inventory_v1.schema.json` | P2-402F host / K3s / stateful 版本只讀盤點 schema;鎖定 SSH / kubectl / upgrade / drain / reboot / restart / Telegram 全部 false | +| `docs/evaluations/ai_agent_host_stateful_version_inventory_2026-06-11.json` | P2-402F committed snapshot:5 台主機、2 個 K3s 節點、12 個 stateful / ops 服務、6 個只讀 probe step、maintenance window approval package | +| `GET /api/v1/agents/agent-host-stateful-version-inventory` | 只讀 API;只回傳 host / K3s / stateful 版本盤點,不 SSH、不 kubectl、不升級、不重啟、不發 Telegram | **採用順序:** @@ -692,7 +695,8 @@ Repo / registry / release notes / K8s / host / observability / backup evidence 2. 再評估 external primary source weekly watch:Renovate、OSV-Scanner、Trivy、Syft、Grype、Kubernetes skew policy、Docker Scout。✅ P2-402C 已建立工具採用批准包;工具安裝、CI 變更、外部查詢仍未授權。 3. 建立 Telegram action-required digest policy,先定義 critical / action-required / failure-only 通知門檻,禁止成功洗版。✅ P2-402D 已建立 policy snapshot/API;Telegram 實發與 queue write 仍未授權。 4. 再進 Gitea PR 草案 lane:grouping、automerge=false、tests、rollback、owner response。✅ P2-402E 已建立 draft lane snapshot/API;branch push、PR creation、workflow trigger、lockfile、package upgrade、auto merge 仍未授權。 -5. 最後才進人工批准後的 dry-run / smoke / canary / production rollout。下一步 P2-402F host OS / K3s / stateful services 版本只讀盤點。 +5. 建立 host OS / K3s / stateful services 版本只讀盤點,先固定維護窗口、備份、rollback 與 smoke gate。✅ P2-402F 已建立 snapshot/API;SSH、kubectl、upgrade、drain、reboot、stateful restart 仍未授權。 +6. 最後才進人工批准後的 dry-run / smoke / canary / production rollout。下一步 P2-402G governance UI 顯示可委派能力。 #### 3.2.2 核心缺口與災難場景 @@ -1726,6 +1730,12 @@ Phase 6 完成後 - 更新 `ai_agent_proactive_operations_contract_2026-06-11.json`:整體完成度 `78%`,current task `P2-402E`,next task `P2-402F`。 - 本波仍不 push branch、不建立或更新 Gitea PR、不留言、不 auto merge、不觸發 workflow、不改 CI、不寫 lockfile、不升級套件、不 build / pull image、不發 Telegram、不讀取或輸出 secret、不回傳工作視窗對話內容;P2-402F 才建立 host OS / K3s / stateful services 版本只讀盤點。 +### 2026-06-11 23:59 (台北) — §3.2 / §5 — 完成 P2-402F host / K3s / stateful 版本只讀盤點 — 回應統帥要求讓 Agent 定期管理主機、套件、服務與維護窗口 + +- 新增 `ai_agent_host_stateful_version_inventory_v1` schema / committed snapshot / loader / API / 測試,定義 5 台主機、2 個 K3s 節點、12 個 stateful / ops 服務、6 個只讀 probe step、maintenance window approval package 與 redaction policy。 +- 更新 `ai_agent_proactive_operations_contract_2026-06-11.json`:整體完成度 `86%`,current task `P2-402F`,next task `P2-402G`。 +- 本波仍不 SSH、不執行 host command、不執行 kubectl、不 apt / kernel / K3s upgrade、不 node drain、不 kubelet restart、不 reboot、不 stateful restart、不 DB migration、不 restore、不 pull image、不發 Telegram、不讀取或輸出 secret、不回傳工作視窗對話內容;P2-402G 才接治理 UI 顯示可委派能力。 + ### 2026-04-15 (台北) — 全檔 — 建立 v2 骨架,§0/§1 完成 — 統帥批准「單 MASTER + 4 道閘門」結構 - 從 v1(plans/2026-04-15-MASTER-ai-autonomous-flywheel.md)繼承核心發現