Files
awoooi/apps/api/src/services/ai_agent_proactive_operations_contract.py
Your Name 0f9f341afc
Some checks failed
CD Pipeline / tests (push) Successful in 1m26s
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
feat(governance): 定義 Agent 主動營運委派契約
2026-06-11 12:18:34 +08:00

155 lines
6.4 KiB
Python

"""
AI Agent proactive operations and version lifecycle contract snapshot.
Loads the latest committed, read-only contract for work that OpenClaw,
Hermes, and NemoTron may proactively perform across version lifecycle,
operations, security, backup, observability, cost, UI smoke, and learning
loops. This module never updates versions, installs tools, enables schedules,
sends Telegram messages, pulls images, mutates hosts, or changes production.
"""
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_proactive_operations_contract_*.json"
_SCHEMA_VERSION = "ai_agent_proactive_operations_contract_v1"
def load_latest_ai_agent_proactive_operations_contract(
evaluations_dir: Path | None = None,
) -> dict[str, Any]:
"""Load the newest committed AI Agent proactive operations contract."""
directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR
candidates = sorted(directory.glob(_SNAPSHOT_PATTERN))
if not candidates:
raise FileNotFoundError(
f"no AI Agent proactive operations contract 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_delegation_safety(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") != "contract_only_no_version_or_runtime_update":
raise ValueError(
f"{label}: runtime_authority must stay contract_only_no_version_or_runtime_update"
)
boundaries = payload.get("approval_boundaries") or {}
blocked_flags = {
"runtime_version_update_allowed",
"package_upgrade_allowed",
"host_upgrade_allowed",
"container_pull_allowed",
"workflow_schedule_enabled",
"auto_merge_allowed",
"telegram_direct_send_allowed",
"secret_plaintext_allowed",
"paid_external_service_allowed",
"production_route_change_allowed",
}
allowed = sorted(flag for flag in blocked_flags if boundaries.get(flag) is not False)
if allowed:
raise ValueError(f"{label}: approval boundaries must remain false: {allowed}")
def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None:
rollups = payload.get("rollups") or {}
expected_counts = {
"version_domain_count": len(payload.get("version_lifecycle_domains") or []),
"delegable_capability_count": len(payload.get("delegable_capabilities") or []),
"cadence_count": len(payload.get("cadence_matrix") or []),
"mcp_tool_count": len(payload.get("mcp_tool_requirements") or []),
"rag_memory_count": len(payload.get("rag_memory_contract") or []),
"rollout_task_count": len(payload.get("rollout_tasks") 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}")
auto_execute_allowed_count = sum(
1
for capability in payload.get("delegable_capabilities") or []
if capability.get("automation_level") in {"L4_execute_after_human_approval", "L5_auto_execute"}
)
if rollups.get("auto_execute_allowed_count") != auto_execute_allowed_count:
raise ValueError(f"{label}: rollups.auto_execute_allowed_count mismatch")
blocked_domain_ids = sorted(
domain.get("domain_id")
for domain in payload.get("version_lifecycle_domains") or []
if domain.get("update_authority") != "auto_update_allowed"
)
if sorted(rollups.get("blocked_update_domain_ids") or []) != blocked_domain_ids:
raise ValueError(f"{label}: rollups.blocked_update_domain_ids mismatch")
telegram_action_required = sorted(
capability.get("capability_id")
for capability in payload.get("delegable_capabilities") or []
if "action_required" in str(capability.get("telegram_policy") or "")
)
if sorted(rollups.get("telegram_action_required_capability_ids") or []) != telegram_action_required:
raise ValueError(f"{label}: rollups.telegram_action_required_capability_ids mismatch")
def _require_delegation_safety(payload: dict[str, Any], label: str) -> None:
dangerous_levels = {"L5_auto_execute", "auto_update", "auto_merge"}
unsafe_capabilities = [
capability.get("capability_id")
for capability in payload.get("delegable_capabilities") or []
if capability.get("automation_level") in dangerous_levels
]
if unsafe_capabilities:
raise ValueError(f"{label}: capabilities must not auto execute: {unsafe_capabilities}")
missing_gates = [
item.get("capability_id") or item.get("domain_id") or item.get("tool_id")
for section in (
payload.get("delegable_capabilities") or [],
payload.get("version_lifecycle_domains") or [],
payload.get("mcp_tool_requirements") or [],
)
for item in section
if not item.get("approval_gate")
]
if missing_gates:
raise ValueError(f"{label}: all proactive operation items need approval gates: {missing_gates}")
external_cadence_enabled = [
cadence.get("cadence_id")
for cadence in payload.get("cadence_matrix") or []
if "external" in str(cadence.get("cadence_id"))
and cadence.get("allowed_now") is not False
]
if external_cadence_enabled:
raise ValueError(f"{label}: external cadence must stay disabled until approved")