feat(governance): 定義 Agent 主動營運委派契約
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

This commit is contained in:
Your Name
2026-06-11 12:18:23 +08:00
parent 4231fd3acf
commit 0f9f341afc
10 changed files with 1575 additions and 0 deletions

View File

@@ -55,6 +55,9 @@ from src.services.ai_agent_communication_learning_contract import (
from src.services.ai_agent_deployment_layout import (
load_latest_ai_agent_deployment_layout,
)
from src.services.ai_agent_proactive_operations_contract import (
load_latest_ai_agent_proactive_operations_contract,
)
from src.services.ai_provider_route_matrix import (
load_latest_ai_provider_route_matrix,
)
@@ -554,6 +557,33 @@ async def get_agent_communication_learning_contract() -> dict[str, Any]:
) from exc
@router.get(
"/agent-proactive-operations-contract",
response_model=dict[str, Any],
summary="取得 AI Agent 主動營運委派與版本生命週期契約",
description=(
"讀取最新已提交的 AI Agent 主動營運、版本生命週期、可委派能力、MCP、RAG 與 Telegram 邊界契約;"
"此端點不啟用排程、不升級套件、不更新主機、不 pull image、不 auto merge、不送 Telegram、"
"不呼叫付費服務、不修改生產路由。"
),
)
async def get_agent_proactive_operations_contract() -> dict[str, Any]:
"""Return the latest read-only AI Agent proactive operations contract."""
try:
return await asyncio.to_thread(load_latest_ai_agent_proactive_operations_contract)
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_proactive_operations_contract_invalid", error=str(exc))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="AI Agent 主動營運委派與版本生命週期契約無效",
) from exc
@router.get(
"/runtime-surface-inventory",
response_model=dict[str, Any],

View File

@@ -0,0 +1,154 @@
"""
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")

View File

@@ -0,0 +1,184 @@
from __future__ import annotations
import json
import pytest
from src.services.ai_agent_proactive_operations_contract import (
load_latest_ai_agent_proactive_operations_contract,
)
def test_load_latest_ai_agent_proactive_operations_contract_reads_committed_snapshot():
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"] == 30
assert data["program_status"]["current_task_id"] == "P2-402A"
assert data["program_status"]["next_task_id"] == "P2-402B"
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
assert data["approval_boundaries"]["package_upgrade_allowed"] is False
assert data["approval_boundaries"]["host_upgrade_allowed"] is False
assert data["approval_boundaries"]["workflow_schedule_enabled"] is False
assert data["approval_boundaries"]["telegram_direct_send_allowed"] is False
assert data["rollups"]["version_domain_count"] == len(data["version_lifecycle_domains"]) == 12
assert data["rollups"]["delegable_capability_count"] == len(data["delegable_capabilities"]) == 24
assert data["rollups"]["auto_execute_allowed_count"] == 0
assert any(domain["domain_id"] == "ai_agents_models" for domain in data["version_lifecycle_domains"])
assert any(
capability["capability_id"] == "telegram_delivery_audit"
for capability in data["delegable_capabilities"]
)
def test_load_latest_ai_agent_proactive_operations_contract_rejects_version_update(tmp_path):
snapshot = _snapshot()
snapshot["approval_boundaries"]["package_upgrade_allowed"] = True
(tmp_path / "ai_agent_proactive_operations_contract_2026-06-11.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="approval boundaries"):
load_latest_ai_agent_proactive_operations_contract(tmp_path)
def test_load_latest_ai_agent_proactive_operations_contract_rejects_rollup_mismatch(tmp_path):
snapshot = _snapshot()
snapshot["rollups"]["delegable_capability_count"] = 99
(tmp_path / "ai_agent_proactive_operations_contract_2026-06-11.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="rollup counts"):
load_latest_ai_agent_proactive_operations_contract(tmp_path)
def test_load_latest_ai_agent_proactive_operations_contract_rejects_auto_execute(tmp_path):
snapshot = _snapshot()
snapshot["delegable_capabilities"][0]["automation_level"] = "L5_auto_execute"
snapshot["rollups"]["auto_execute_allowed_count"] = 1
(tmp_path / "ai_agent_proactive_operations_contract_2026-06-11.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="auto execute"):
load_latest_ai_agent_proactive_operations_contract(tmp_path)
def _snapshot() -> dict:
return {
"schema_version": "ai_agent_proactive_operations_contract_v1",
"generated_at": "2026-06-11T21:30:00+08:00",
"program_status": {
"overall_completion_percent": 30,
"current_priority": "P2",
"current_task_id": "P2-402A",
"next_task_id": "P2-402B",
"read_only_mode": True,
"runtime_authority": "contract_only_no_version_or_runtime_update",
},
"delegation_model": {
"autonomy_levels": [],
"agent_responsibilities": [],
"telegram_policy": {},
},
"version_lifecycle_domains": [
{
"domain_id": "python_packages",
"display_name": "Python",
"primary_owner": "hermes",
"cadence": "daily",
"current_allowed_autonomy": "L2_approval_package_only",
"update_authority": "dependency approval",
"approval_gate": "dependency_approval_required",
}
],
"delegable_capabilities": [
{
"capability_id": "version_discovery_freshness",
"display_name": "版本發現",
"primary_owner": "hermes",
"risk_tier": "low",
"automation_level": "L1_report_only",
"outputs": ["report"],
"approval_gate": "read_only_allowed",
"telegram_policy": "failure_only",
}
],
"cadence_matrix": [
{
"cadence_id": "daily_repo_only",
"frequency": "daily",
"scope": "repo",
"allowed_now": True,
"next_gate": "none",
},
{
"cadence_id": "weekly_external_primary_sources",
"frequency": "weekly",
"scope": "external",
"allowed_now": False,
"next_gate": "approval",
},
],
"mcp_tool_requirements": [
{
"tool_id": "gitea_release_pr_mcp",
"display_name": "Gitea",
"purpose": "測試。",
"owner_agent": "hermes",
"status": "planned",
"approval_gate": "write_requires_human_gate",
}
],
"rag_memory_contract": [
{
"memory_id": "version_history",
"display_name": "Version",
"storage": "PostgreSQL",
"owner_agent": "hermes",
"purpose": "測試。",
"redaction_policy": "no secrets",
}
],
"rollout_tasks": [
{
"task_id": "P2-402A",
"priority": "P2",
"status": "done",
"completion_percent": 100,
"owner_agent": "Hermes",
"summary": "測試。",
"next_gate": "deploy",
}
],
"approval_boundaries": {
"runtime_version_update_allowed": False,
"package_upgrade_allowed": False,
"host_upgrade_allowed": False,
"container_pull_allowed": False,
"workflow_schedule_enabled": False,
"auto_merge_allowed": False,
"telegram_direct_send_allowed": False,
"secret_plaintext_allowed": False,
"paid_external_service_allowed": False,
"production_route_change_allowed": False,
},
"rollups": {
"version_domain_count": 1,
"delegable_capability_count": 1,
"cadence_count": 2,
"mcp_tool_count": 1,
"rag_memory_count": 1,
"rollout_task_count": 1,
"auto_execute_allowed_count": 0,
"approval_required_capability_count": 1,
"blocked_update_domain_ids": ["python_packages"],
"telegram_action_required_capability_ids": [],
},
}

View File

@@ -0,0 +1,34 @@
from __future__ import annotations
from fastapi import FastAPI
from fastapi.testclient import TestClient
from src.api.v1.agents import router
def test_ai_agent_proactive_operations_contract_endpoint_returns_committed_snapshot():
app = FastAPI()
app.include_router(router, prefix="/api/v1")
client = TestClient(app)
response = client.get("/api/v1/agents/agent-proactive-operations-contract")
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"] == 30
assert data["program_status"]["current_task_id"] == "P2-402A"
assert data["program_status"]["next_task_id"] == "P2-402B"
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
assert data["approval_boundaries"]["workflow_schedule_enabled"] is False
assert data["approval_boundaries"]["telegram_direct_send_allowed"] is False
assert data["rollups"]["version_domain_count"] == 12
assert data["rollups"]["delegable_capability_count"] == 24
assert data["rollups"]["auto_execute_allowed_count"] == 0
assert any(domain["domain_id"] == "host_os_packages" for domain in data["version_lifecycle_domains"])
assert any(
capability["capability_id"] == "market_watch_and_candidate_intake"
for capability in data["delegable_capabilities"]
)