feat(governance): 接入三 Agent 佈建布局
All checks were successful
CD Pipeline / tests (push) Successful in 1m24s
Code Review / ai-code-review (push) Successful in 15s
CD Pipeline / build-and-deploy (push) Successful in 6m5s
CD Pipeline / post-deploy-checks (push) Successful in 1m37s

This commit is contained in:
Your Name
2026-06-11 11:27:50 +08:00
parent 3418e014bc
commit e427af3cb2
13 changed files with 2308 additions and 41 deletions

View File

@@ -35,70 +35,73 @@ from pydantic import BaseModel, Field
from src.core.logging import get_logger
from src.core.sse import get_publisher
from src.services.agent_market_governance_snapshot import (
load_latest_agent_market_governance_snapshot,
)
from src.services.agent_service import (
AgentService,
TaskState,
get_agent_service,
)
from src.services.ai_agent_automation_backlog_snapshot import (
load_latest_ai_agent_automation_backlog_snapshot,
)
from src.services.ai_agent_automation_inventory_snapshot import (
load_latest_ai_agent_automation_inventory_snapshot,
)
from src.services.agent_market_governance_snapshot import (
load_latest_agent_market_governance_snapshot,
from src.services.ai_agent_deployment_layout import (
load_latest_ai_agent_deployment_layout,
)
from src.services.backup_dr_target_inventory import (
load_latest_backup_dr_target_inventory,
from src.services.ai_provider_route_matrix import (
load_latest_ai_provider_route_matrix,
)
from src.services.backup_dr_readiness_matrix import (
load_latest_backup_dr_readiness_matrix,
)
from src.services.backup_dr_target_inventory import (
load_latest_backup_dr_target_inventory,
)
from src.services.backup_notification_policy import (
load_latest_backup_notification_policy,
)
from src.services.backup_restore_drill_approval_package_template import (
load_latest_backup_restore_drill_approval_package_template,
)
from src.services.offsite_escrow_readiness_status import (
load_latest_offsite_escrow_readiness_status,
)
from src.services.runtime_surface_inventory import (
load_latest_runtime_surface_inventory,
)
from src.services.gitea_workflow_runner_health import (
load_latest_gitea_workflow_runner_health,
)
from src.services.observability_contract_matrix import (
load_latest_observability_contract_matrix,
)
from src.services.ai_provider_route_matrix import (
load_latest_ai_provider_route_matrix,
)
from src.services.service_health_gap_matrix import (
load_latest_service_health_gap_matrix,
)
from src.services.service_health_failure_notification_policy import (
load_latest_service_health_failure_notification_policy,
)
from src.services.package_supply_chain_inventory import (
load_latest_package_supply_chain_inventory,
)
from src.services.javascript_package_inventory import (
load_latest_javascript_package_inventory,
)
from src.services.docker_build_surface_inventory import (
load_latest_docker_build_surface_inventory,
from src.services.dependency_drift_check_plan import (
load_latest_dependency_drift_check_plan,
)
from src.services.dependency_risk_policy import (
load_latest_dependency_risk_policy,
)
from src.services.dependency_drift_check_plan import (
load_latest_dependency_drift_check_plan,
)
from src.services.dependency_upgrade_approval_package_template import (
load_latest_dependency_upgrade_approval_package_template,
)
from src.services.agent_service import (
AgentService,
TaskState,
get_agent_service,
from src.services.docker_build_surface_inventory import (
load_latest_docker_build_surface_inventory,
)
from src.services.gitea_workflow_runner_health import (
load_latest_gitea_workflow_runner_health,
)
from src.services.javascript_package_inventory import (
load_latest_javascript_package_inventory,
)
from src.services.observability_contract_matrix import (
load_latest_observability_contract_matrix,
)
from src.services.offsite_escrow_readiness_status import (
load_latest_offsite_escrow_readiness_status,
)
from src.services.package_supply_chain_inventory import (
load_latest_package_supply_chain_inventory,
)
from src.services.runtime_surface_inventory import (
load_latest_runtime_surface_inventory,
)
from src.services.service_health_failure_notification_policy import (
load_latest_service_health_failure_notification_policy,
)
from src.services.service_health_gap_matrix import (
load_latest_service_health_gap_matrix,
)
router = APIRouter(prefix="/agents", tags=["Agent Teams"])
@@ -494,6 +497,33 @@ async def get_automation_backlog_snapshot() -> dict[str, Any]:
) from exc
@router.get(
"/agent-deployment-layout",
response_model=dict[str, Any],
summary="取得 AI Agent 佈建布局快照",
description=(
"讀取最新已提交的 OpenClaw / Hermes / NemoTron 佈建布局快照;"
"此端點不部署 Agent、不呼叫外部模型、不送 Telegram、不碰 DB/Redis、不讀 Secret payload、"
"不批准 SDK/API/shadow/canary/生產路由或主機變更。"
),
)
async def get_agent_deployment_layout() -> dict[str, Any]:
"""Return the latest read-only AI Agent deployment layout snapshot."""
try:
return await asyncio.to_thread(load_latest_ai_agent_deployment_layout)
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_deployment_layout_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,135 @@
"""
AI Agent deployment layout snapshot.
Loads the latest committed, read-only layout for OpenClaw, Hermes, and
NemoTron across hosts, packages, tools, services, projects, web surfaces,
learning loops, and Telegram notification boundaries. This module never
deploys agents, sends Telegram messages, calls providers, or approves writes.
"""
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_deployment_layout_*.json"
_SCHEMA_VERSION = "ai_agent_deployment_layout_v1"
def load_latest_ai_agent_deployment_layout(
evaluations_dir: Path | None = None,
) -> dict[str, Any]:
"""Load the newest committed AI Agent deployment layout snapshot."""
directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR
candidates = sorted(directory.glob(_SNAPSHOT_PATTERN))
if not candidates:
raise FileNotFoundError(f"no AI Agent deployment layout 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_layout(payload, str(latest))
_require_rollup_consistency(payload, str(latest))
_require_frontend_redaction(payload, str(latest))
_require_target_boundaries(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_layout(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("deployment_authority") != "layout_only_no_runtime_deploy":
raise ValueError(f"{label}: deployment_authority must stay layout_only_no_runtime_deploy")
boundaries = payload.get("approval_boundaries") or {}
blocked_flags = {
"sdk_installation_allowed",
"paid_api_call_allowed",
"shadow_or_canary_allowed",
"production_routing_allowed",
"destructive_operation_allowed",
"secret_plaintext_allowed",
"autonomous_host_mutation_allowed",
"telegram_direct_send_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:
targets = payload.get("deployment_targets") or []
rollups = payload.get("rollups") or {}
if rollups.get("total_targets") != len(targets):
raise ValueError(f"{label}: rollups.total_targets must match deployment_targets")
if rollups.get("by_domain") != _count_by(targets, "domain_id"):
raise ValueError(f"{label}: rollups.by_domain must match deployment_targets")
if rollups.get("by_primary_agent") != _count_by(targets, "primary_agent"):
raise ValueError(f"{label}: rollups.by_primary_agent must match deployment_targets")
if rollups.get("by_deployment_state") != _count_by(targets, "deployment_state"):
raise ValueError(f"{label}: rollups.by_deployment_state must match deployment_targets")
if rollups.get("by_telegram_policy") != _count_by(targets, "telegram_policy"):
raise ValueError(f"{label}: rollups.by_telegram_policy must match deployment_targets")
blocked_target_ids = sorted(
target.get("target_id")
for target in targets
if target.get("deployment_state") == "blocked_by_gate"
or target.get("automation_level") == "blocked"
)
if sorted(rollups.get("blocked_target_ids") or []) != blocked_target_ids:
raise ValueError(f"{label}: rollups.blocked_target_ids must match blocked targets")
def _require_frontend_redaction(payload: dict[str, Any], label: str) -> None:
redaction = ((payload.get("collaboration_contract") or {}).get("frontend_redaction") or {})
if redaction.get("operator_conversation_display_allowed") is not False:
raise ValueError(f"{label}: operator conversation display must stay false")
if redaction.get("agent_private_reasoning_display_allowed") is not False:
raise ValueError(f"{label}: agent private reasoning display must stay false")
def _require_target_boundaries(payload: dict[str, Any], label: str) -> None:
targets = payload.get("deployment_targets") or []
missing = [
target.get("target_id")
for target in targets
if not target.get("approval_gate")
or not target.get("telegram_policy")
or not target.get("communication_channels")
]
if missing:
raise ValueError(f"{label}: deployment targets missing boundary fields: {sorted(missing)}")
invalid_nemotron_runtime = [
target.get("target_id")
for target in targets
if target.get("primary_agent") == "nemotron"
and target.get("automation_level") not in {"observe_only", "blocked"}
]
if invalid_nemotron_runtime:
raise ValueError(f"{label}: Nemotron targets must stay observe_only or blocked")
def _count_by(items: list[dict[str, Any]], key: str) -> dict[str, int]:
counts: dict[str, int] = {}
for item in items:
value = item.get(key)
counts[value] = counts.get(value, 0) + 1
return counts

View File

@@ -0,0 +1,186 @@
from __future__ import annotations
import json
import pytest
from src.services.ai_agent_deployment_layout import (
load_latest_ai_agent_deployment_layout,
)
def test_load_latest_ai_agent_deployment_layout_reads_committed_snapshot():
data = load_latest_ai_agent_deployment_layout()
assert data["schema_version"] == "ai_agent_deployment_layout_v1"
assert data["program_status"]["read_only_mode"] is True
assert data["program_status"]["deployment_authority"] == "layout_only_no_runtime_deploy"
assert data["rollups"]["total_targets"] == len(data["deployment_targets"]) == 42
assert data["rollups"]["by_primary_agent"]["openclaw"] == 17
assert data["rollups"]["by_primary_agent"]["hermes"] == 23
assert data["rollups"]["by_primary_agent"]["nemotron"] == 2
assert data["rollups"]["blocked_target_ids"] == [
"host_120",
"nemotron_replay_pipeline",
]
assert data["approval_boundaries"]["telegram_direct_send_allowed"] is False
assert data["approval_boundaries"]["production_routing_allowed"] is False
assert data["collaboration_contract"]["frontend_redaction"]["operator_conversation_display_allowed"] is False
nemotron_targets = [
target for target in data["deployment_targets"] if target["primary_agent"] == "nemotron"
]
assert {target["automation_level"] for target in nemotron_targets} <= {"observe_only", "blocked"}
def test_load_latest_ai_agent_deployment_layout_rejects_runtime_authority(tmp_path):
snapshot = _snapshot()
snapshot["approval_boundaries"]["production_routing_allowed"] = True
(tmp_path / "ai_agent_deployment_layout_2026-06-11.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="approval boundaries"):
load_latest_ai_agent_deployment_layout(tmp_path)
def test_load_latest_ai_agent_deployment_layout_rejects_rollup_mismatch(tmp_path):
snapshot = _snapshot()
snapshot["rollups"]["by_primary_agent"]["openclaw"] = 99
(tmp_path / "ai_agent_deployment_layout_2026-06-11.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="by_primary_agent"):
load_latest_ai_agent_deployment_layout(tmp_path)
def test_load_latest_ai_agent_deployment_layout_rejects_nemotron_runtime(tmp_path):
snapshot = _snapshot()
snapshot["deployment_targets"][0]["primary_agent"] = "nemotron"
snapshot["deployment_targets"][0]["automation_level"] = "hitl_execute_after_approval"
snapshot["rollups"]["by_primary_agent"] = {"nemotron": 1}
(tmp_path / "ai_agent_deployment_layout_2026-06-11.json").write_text(
json.dumps(snapshot),
encoding="utf-8",
)
with pytest.raises(ValueError, match="Nemotron targets"):
load_latest_ai_agent_deployment_layout(tmp_path)
def _snapshot() -> dict:
return {
"schema_version": "ai_agent_deployment_layout_v1",
"generated_at": "2026-06-11T18:20:00+08:00",
"program_status": {
"overall_completion_percent": 10,
"current_priority": "P1",
"current_task_id": "P1-401",
"next_task_id": "P1-402",
"read_only_mode": True,
"deployment_authority": "layout_only_no_runtime_deploy",
},
"agent_contracts": [
{
"agent_id": "openclaw",
"display_name": "OpenClaw",
"primary_specialty": "生產仲裁",
"deployment_lane": "production_decision_core",
"allowed_autonomy": ["只讀診斷"],
"must_delegate_to": ["Hermes"],
"blocked_actions": ["production_write"],
"learning_scope": ["incident"],
},
{
"agent_id": "hermes",
"display_name": "Hermes",
"primary_specialty": "治理",
"deployment_lane": "governance",
"allowed_autonomy": ["只讀盤點"],
"must_delegate_to": ["OpenClaw"],
"blocked_actions": ["production_write"],
"learning_scope": ["docs"],
},
{
"agent_id": "nemotron",
"display_name": "NemoTron",
"primary_specialty": "離線評估",
"deployment_lane": "offline_evaluator",
"allowed_autonomy": ["smoke"],
"must_delegate_to": ["OpenClaw"],
"blocked_actions": ["production_route"],
"learning_scope": ["replay"],
},
],
"domains": [
{
"domain_id": "hosts",
"display_name": "主機",
"description": "測試主機",
}
],
"deployment_targets": [
{
"target_id": "host_110",
"domain_id": "hosts",
"display_name": "110",
"target_type": "host",
"primary_agent": "openclaw",
"supporting_agents": ["hermes"],
"deployment_state": "active_governed",
"automation_level": "observe_only",
"capabilities": ["monitor"],
"telegram_policy": "failure_only",
"learning_inputs": ["metrics"],
"communication_channels": ["Prometheus"],
"approval_gate": "read_only_allowed",
"evidence_refs": ["infra/ansible/inventory/hosts.yml"],
"next_action": "觀察。",
}
],
"collaboration_contract": {
"message_bus": "Redis Streams",
"audit_trail": "AgentSession",
"handoff_rules": ["OpenClaw 仲裁"],
"frontend_redaction": {
"operator_conversation_display_allowed": False,
"agent_private_reasoning_display_allowed": False,
"display_policy": "只顯示摘要。",
},
},
"learning_contract": {
"event_sources": ["incident"],
"feedback_loops": ["trust"],
"growth_metrics": ["precision"],
"retention_policy": "redacted evidence only",
},
"telegram_contract": {
"primary_gateway": "telegram_gateway.py",
"bot_roles": ["OpenClaw"],
"notification_classes": ["failure"],
"redaction_policy": "redacted",
"e2e_validation": "ADR-035",
},
"rollups": {
"total_targets": 1,
"by_domain": {"hosts": 1},
"by_primary_agent": {"openclaw": 1},
"by_deployment_state": {"active_governed": 1},
"by_telegram_policy": {"failure_only": 1},
"blocked_target_ids": [],
"approval_required_target_ids": [],
},
"approval_boundaries": {
"sdk_installation_allowed": False,
"paid_api_call_allowed": False,
"shadow_or_canary_allowed": False,
"production_routing_allowed": False,
"destructive_operation_allowed": False,
"secret_plaintext_allowed": False,
"autonomous_host_mutation_allowed": False,
"telegram_direct_send_allowed": False,
},
}

View File

@@ -0,0 +1,31 @@
from __future__ import annotations
from fastapi import FastAPI
from fastapi.testclient import TestClient
from src.api.v1.agents import router
def test_ai_agent_deployment_layout_endpoint_returns_committed_snapshot():
app = FastAPI()
app.include_router(router, prefix="/api/v1")
client = TestClient(app)
response = client.get("/api/v1/agents/agent-deployment-layout")
assert response.status_code == 200
data = response.json()
assert data["schema_version"] == "ai_agent_deployment_layout_v1"
assert data["program_status"]["overall_completion_percent"] == 45
assert data["program_status"]["current_task_id"] == "P1-402"
assert data["program_status"]["next_task_id"] == "P1-403"
assert data["program_status"]["read_only_mode"] is True
assert data["rollups"]["total_targets"] == 42
assert data["rollups"]["by_domain"]["hosts"] == 6
assert data["rollups"]["by_domain"]["websites"] == 5
assert data["rollups"]["by_primary_agent"]["nemotron"] == 2
assert data["approval_boundaries"]["telegram_direct_send_allowed"] is False
assert data["approval_boundaries"]["autonomous_host_mutation_allowed"] is False
assert data["collaboration_contract"]["frontend_redaction"]["operator_conversation_display_allowed"] is False
assert any(target["target_id"] == "telegram_gateway" for target in data["deployment_targets"])
assert any(target["target_id"] == "nemotron_replay_pipeline" for target in data["deployment_targets"])

View File

@@ -2965,6 +2965,72 @@
"taskBoundaries": "任務邊界",
"explicitApprovalTasks": "需明確批准"
},
"deploymentLayout": {
"title": "OpenClaw / Hermes / NemoTron 佈建布局",
"source": "{generated} · {current} → {next}",
"targetsTitle": "優先佈建目標",
"targetsShown": "顯示 {count}/{total}",
"agentContractTitle": "Agent 專長分工",
"telegramTitle": "Telegram Bot 告警合約",
"telegramDetail": "統一入口 {gateway},通知類別 {classes} 種Agent 不直接持有 token也不直接發送。",
"learningTitle": "主動學習與協作",
"learningDetail": "事件來源 {sources}、回饋迴圈 {loops}、成長指標 {metrics};先落只讀證據,再走批准關卡。",
"redactionLocked": "前端不顯示對話內容",
"directSendBlocked": "Telegram 直送禁止",
"frontendSafe": "只顯示狀態、證據與邊界",
"metrics": {
"targets": "佈建目標",
"openclaw": "OpenClaw",
"hermes": "Hermes",
"nemotron": "NemoTron",
"approval": "需批准",
"blocked": "阻擋"
},
"labels": {
"domain": "領域",
"primary": "主責",
"support": "協作",
"telegram": "告警",
"automation": "自動化",
"evidence": "證據",
"bus": "協作匯流"
},
"agents": {
"openclaw": "OpenClaw",
"hermes": "Hermes",
"nemotron": "NemoTron"
},
"domains": {
"hosts": "主機",
"packages": "套件",
"tools": "工具",
"services": "服務",
"projects": "專案",
"websites": "網站前後台",
"learning": "學習協作"
},
"deploymentStates": {
"active_governed": "治理中",
"read_only_layout": "只讀布局",
"blocked_by_gate": "關卡阻擋",
"planned": "規劃中",
"candidate_only": "候選"
},
"automationLevels": {
"observe_only": "只讀觀察",
"prepare_only": "只準備提案",
"dry_run_only": "僅乾跑",
"hitl_execute_after_approval": "批准後 HITL 執行",
"blocked": "阻擋"
},
"telegramPolicies": {
"failure_only": "僅失敗",
"action_required": "需處置",
"approval_required": "需批准",
"daily_summary_only": "僅日報",
"no_direct_notify": "不直送"
}
},
"overview": {
"title": "決策指揮摘要",
"mode": "只讀決策支援",

View File

@@ -2965,6 +2965,72 @@
"taskBoundaries": "任務邊界",
"explicitApprovalTasks": "需明確批准"
},
"deploymentLayout": {
"title": "OpenClaw / Hermes / NemoTron 佈建布局",
"source": "{generated} · {current} → {next}",
"targetsTitle": "優先佈建目標",
"targetsShown": "顯示 {count}/{total}",
"agentContractTitle": "Agent 專長分工",
"telegramTitle": "Telegram Bot 告警合約",
"telegramDetail": "統一入口 {gateway},通知類別 {classes} 種Agent 不直接持有 token也不直接發送。",
"learningTitle": "主動學習與協作",
"learningDetail": "事件來源 {sources}、回饋迴圈 {loops}、成長指標 {metrics};先落只讀證據,再走批准關卡。",
"redactionLocked": "前端不顯示對話內容",
"directSendBlocked": "Telegram 直送禁止",
"frontendSafe": "只顯示狀態、證據與邊界",
"metrics": {
"targets": "佈建目標",
"openclaw": "OpenClaw",
"hermes": "Hermes",
"nemotron": "NemoTron",
"approval": "需批准",
"blocked": "阻擋"
},
"labels": {
"domain": "領域",
"primary": "主責",
"support": "協作",
"telegram": "告警",
"automation": "自動化",
"evidence": "證據",
"bus": "協作匯流"
},
"agents": {
"openclaw": "OpenClaw",
"hermes": "Hermes",
"nemotron": "NemoTron"
},
"domains": {
"hosts": "主機",
"packages": "套件",
"tools": "工具",
"services": "服務",
"projects": "專案",
"websites": "網站前後台",
"learning": "學習協作"
},
"deploymentStates": {
"active_governed": "治理中",
"read_only_layout": "只讀布局",
"blocked_by_gate": "關卡阻擋",
"planned": "規劃中",
"candidate_only": "候選"
},
"automationLevels": {
"observe_only": "只讀觀察",
"prepare_only": "只準備提案",
"dry_run_only": "僅乾跑",
"hitl_execute_after_approval": "批准後 HITL 執行",
"blocked": "阻擋"
},
"telegramPolicies": {
"failure_only": "僅失敗",
"action_required": "需處置",
"approval_required": "需批准",
"daily_summary_only": "僅日報",
"no_direct_notify": "不直送"
}
},
"overview": {
"title": "決策指揮摘要",
"mode": "只讀決策支援",

View File

@@ -35,6 +35,7 @@ import { GlassCard } from '@/components/ui/glass-card'
import { StatusOrb } from '@/components/ui/status-orb'
import {
apiClient,
type AiAgentDeploymentLayoutSnapshot,
type AiProviderRouteMatrixSnapshot,
type AiAgentAutomationBacklogSnapshot,
type AiAgentAutomationInventorySnapshot,
@@ -311,6 +312,7 @@ export function AutomationInventoryTab() {
const [giteaHealth, setGiteaHealth] = useState<GiteaWorkflowRunnerHealthSnapshot | null>(null)
const [observabilityMatrix, setObservabilityMatrix] = useState<ObservabilityContractMatrixSnapshot | null>(null)
const [providerRouteMatrix, setProviderRouteMatrix] = useState<AiProviderRouteMatrixSnapshot | null>(null)
const [deploymentLayout, setDeploymentLayout] = useState<AiAgentDeploymentLayoutSnapshot | null>(null)
const [serviceHealthGapMatrix, setServiceHealthGapMatrix] = useState<ServiceHealthGapMatrixSnapshot | null>(null)
const [serviceHealthNotificationPolicy, setServiceHealthNotificationPolicy] = useState<ServiceHealthFailureNotificationPolicySnapshot | null>(null)
const [loading, setLoading] = useState(true)
@@ -329,6 +331,7 @@ export function AutomationInventoryTab() {
apiClient.getGiteaWorkflowRunnerHealth(),
apiClient.getObservabilityContractMatrix(),
apiClient.getAiProviderRouteMatrix(),
apiClient.getAiAgentDeploymentLayout(),
apiClient.getServiceHealthGapMatrix(),
apiClient.getServiceHealthFailureNotificationPolicy(),
] as const
@@ -346,6 +349,7 @@ export function AutomationInventoryTab() {
giteaHealthResult,
observabilityMatrixResult,
providerRouteMatrixResult,
deploymentLayoutResult,
serviceHealthGapMatrixResult,
serviceHealthNotificationPolicyResult,
] = results
@@ -360,6 +364,7 @@ export function AutomationInventoryTab() {
setGiteaHealth(giteaHealthResult.status === 'fulfilled' ? giteaHealthResult.value : null)
setObservabilityMatrix(observabilityMatrixResult.status === 'fulfilled' ? observabilityMatrixResult.value : null)
setProviderRouteMatrix(providerRouteMatrixResult.status === 'fulfilled' ? providerRouteMatrixResult.value : null)
setDeploymentLayout(deploymentLayoutResult.status === 'fulfilled' ? deploymentLayoutResult.value : null)
setServiceHealthGapMatrix(serviceHealthGapMatrixResult.status === 'fulfilled' ? serviceHealthGapMatrixResult.value : null)
setServiceHealthNotificationPolicy(serviceHealthNotificationPolicyResult.status === 'fulfilled' ? serviceHealthNotificationPolicyResult.value : null)
setError([
@@ -372,6 +377,7 @@ export function AutomationInventoryTab() {
giteaHealthResult,
observabilityMatrixResult,
providerRouteMatrixResult,
deploymentLayoutResult,
serviceHealthGapMatrixResult,
serviceHealthNotificationPolicyResult,
].some(result => result.status === 'rejected'))
@@ -409,6 +415,35 @@ export function AutomationInventoryTab() {
.filter(group => group.items.length > 0)
}, [backlog])
const visibleDeploymentTargets = useMemo(() => {
if (!deploymentLayout) return []
const statePriority = {
blocked_by_gate: 0,
active_governed: 1,
read_only_layout: 2,
planned: 3,
candidate_only: 4,
} as Record<string, number>
const telegramPriority = {
approval_required: 0,
action_required: 1,
failure_only: 2,
daily_summary_only: 3,
no_direct_notify: 4,
} as Record<string, number>
return [...deploymentLayout.deployment_targets]
.sort((a, b) => {
const stateLeft = statePriority[a.deployment_state] ?? 5
const stateRight = statePriority[b.deployment_state] ?? 5
if (stateLeft !== stateRight) return stateLeft - stateRight
const telegramLeft = telegramPriority[a.telegram_policy] ?? 5
const telegramRight = telegramPriority[b.telegram_policy] ?? 5
if (telegramLeft !== telegramRight) return telegramLeft - telegramRight
return a.target_id.localeCompare(b.target_id)
})
.slice(0, 12)
}, [deploymentLayout])
const visibleReadinessRows = useMemo(() => {
if (!backupReadiness) return []
const priority = { blocked: 0, action_required: 1, deferred: 2, ready: 3 } as Record<string, number>
@@ -552,7 +587,7 @@ export function AutomationInventoryTab() {
)
}
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) {
return (
<div style={{ padding: 20 }}>
<GlassCard variant="subtle" padding="lg">
@@ -619,6 +654,12 @@ export function AutomationInventoryTab() {
+ providerRouteMatrix.rollups.shadow_or_canary_allowed_count
+ providerRouteMatrix.rollups.runtime_route_change_allowed_count
)
const deploymentLayoutTargets = deploymentLayout.rollups.total_targets
const deploymentLayoutOpenClawTargets = deploymentLayout.rollups.by_primary_agent.openclaw ?? 0
const deploymentLayoutHermesTargets = deploymentLayout.rollups.by_primary_agent.hermes ?? 0
const deploymentLayoutNemotronTargets = deploymentLayout.rollups.by_primary_agent.nemotron ?? 0
const deploymentLayoutBlockedTargets = deploymentLayout.rollups.blocked_target_ids.length
const deploymentLayoutApprovalTargets = deploymentLayout.rollups.approval_required_target_ids.length
const serviceHealthActions = serviceHealthGapMatrix.rollups.target_ids_requiring_action.length
const serviceHealthStaleGaps = serviceHealthGapMatrix.rollups.stale_endpoint_ids.length
const serviceHealthOperatorReviews = serviceHealthGapMatrix.rollups.health_gap_ids.length
@@ -771,6 +812,14 @@ export function AutomationInventoryTab() {
}
}
const deploymentLayoutValueLabel = (group: string, value: string) => {
try {
return t(`deploymentLayout.${group}.${value}` as never)
} catch {
return value
}
}
const serviceHealthValueLabel = (value: string) => {
try {
return t(`serviceHealth.values.${value}` as never)
@@ -942,6 +991,124 @@ export function AutomationInventoryTab() {
<MetricCard label={t('metrics.explicitApprovalTasks')} value={explicitApprovalTaskCount} tone={explicitApprovalTaskCount > 0 ? 'warn' : 'ok'} icon={<ShieldCheck size={16} />} />
</div>
<GlassCard variant="subtle" padding="md">
<div style={{ display: 'flex', flexDirection: 'column', gap: 13, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>
<Route size={14} style={{ color: '#d97757' }} />
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
{t('deploymentLayout.title')}
</span>
</div>
<Chip
value={t('deploymentLayout.source', {
generated: formatDateTime(deploymentLayout.generated_at),
current: deploymentLayout.program_status.current_task_id,
next: deploymentLayout.program_status.next_task_id,
})}
muted
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, minmax(0, 1fr))', gap: 12 }} className="automation-inventory-deployment-layout-kpi-grid">
<MetricCard label={t('deploymentLayout.metrics.targets')} value={deploymentLayoutTargets} icon={<Boxes size={16} />} />
<MetricCard label={t('deploymentLayout.metrics.openclaw')} value={deploymentLayoutOpenClawTargets} icon={<ShieldCheck size={16} />} />
<MetricCard label={t('deploymentLayout.metrics.hermes')} value={deploymentLayoutHermesTargets} icon={<FileText size={16} />} />
<MetricCard label={t('deploymentLayout.metrics.nemotron')} value={deploymentLayoutNemotronTargets} tone="warn" icon={<Target size={16} />} />
<MetricCard label={t('deploymentLayout.metrics.approval')} value={deploymentLayoutApprovalTargets} tone={deploymentLayoutApprovalTargets > 0 ? 'warn' : 'ok'} icon={<Lock size={16} />} />
<MetricCard label={t('deploymentLayout.metrics.blocked')} value={deploymentLayoutBlockedTargets} tone={deploymentLayoutBlockedTargets > 0 ? 'danger' : 'ok'} icon={<AlertTriangle size={16} />} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.25fr) minmax(0, 0.75fr)', gap: 12 }} className="automation-inventory-deployment-layout-grid">
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
{t('deploymentLayout.targetsTitle')}
</span>
<Chip value={t('deploymentLayout.targetsShown', { count: visibleDeploymentTargets.length, total: deploymentLayoutTargets })} muted />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }} className="automation-inventory-deployment-layout-target-grid">
{visibleDeploymentTargets.map(target => (
<div key={target.target_id} style={{ padding: 11, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, minWidth: 0 }}>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 11, fontWeight: 700, color: '#141413' }}>
{target.target_id}
</span>
<Chip value={deploymentLayoutValueLabel('deploymentStates', target.deployment_state)} muted={target.deployment_state === 'active_governed'} />
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
<Chip value={`${t('deploymentLayout.labels.domain')}: ${deploymentLayoutValueLabel('domains', target.domain_id)}`} muted />
<Chip value={`${t('deploymentLayout.labels.primary')}: ${deploymentLayoutValueLabel('agents', target.primary_agent)}`} />
<Chip value={`${t('deploymentLayout.labels.telegram')}: ${deploymentLayoutValueLabel('telegramPolicies', target.telegram_policy)}`} muted={target.telegram_policy !== 'approval_required'} />
<Chip value={`${t('deploymentLayout.labels.automation')}: ${deploymentLayoutValueLabel('automationLevels', target.automation_level)}`} muted={target.automation_level === 'observe_only'} />
</div>
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
{target.next_action}
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
{target.supporting_agents.length > 0 ? (
<Chip value={`${t('deploymentLayout.labels.support')}: ${target.supporting_agents.map(agent => deploymentLayoutValueLabel('agents', agent)).join(' / ')}`} muted />
) : null}
<Chip value={`${t('deploymentLayout.labels.evidence')}: ${target.evidence_refs.length}`} muted />
</div>
</div>
))}
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 9, minWidth: 0 }}>
<SmallLabel>{t('deploymentLayout.agentContractTitle')}</SmallLabel>
{deploymentLayout.agent_contracts.map(contract => (
<div key={contract.agent_id} style={{ display: 'flex', flexDirection: 'column', gap: 5, paddingBottom: 7, borderBottom: '0.5px solid #eee9dd' }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413' }}>
{deploymentLayoutValueLabel('agents', contract.agent_id)}
</span>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.45 }}>
{contract.primary_specialty}
</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
{contract.allowed_autonomy.slice(0, 2).map(item => (
<Chip key={`${contract.agent_id}-${item}`} value={item} muted />
))}
</div>
</div>
))}
</div>
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<SmallLabel>{t('deploymentLayout.telegramTitle')}</SmallLabel>
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
{t('deploymentLayout.telegramDetail', {
gateway: deploymentLayout.telegram_contract.primary_gateway,
classes: deploymentLayout.telegram_contract.notification_classes.length,
})}
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={t('deploymentLayout.redactionLocked')} />
<Chip value={t('deploymentLayout.directSendBlocked')} muted />
</div>
</div>
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<SmallLabel>{t('deploymentLayout.learningTitle')}</SmallLabel>
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.5, overflowWrap: 'anywhere' }}>
{t('deploymentLayout.learningDetail', {
sources: deploymentLayout.learning_contract.event_sources.length,
loops: deploymentLayout.learning_contract.feedback_loops.length,
metrics: deploymentLayout.learning_contract.growth_metrics.length,
})}
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={`${t('deploymentLayout.labels.bus')}: ${deploymentLayout.collaboration_contract.message_bus}`} muted />
<Chip value={t('deploymentLayout.frontendSafe')} />
</div>
</div>
</div>
</div>
</div>
</GlassCard>
<GlassCard variant="subtle" padding="md">
<div style={{ display: 'flex', flexDirection: 'column', gap: 13, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
@@ -2389,6 +2556,9 @@ export function AutomationInventoryTab() {
.automation-inventory-provider-map-grid,
.automation-inventory-provider-grid,
.automation-inventory-provider-route-grid,
.automation-inventory-deployment-layout-kpi-grid,
.automation-inventory-deployment-layout-grid,
.automation-inventory-deployment-layout-target-grid,
.automation-inventory-service-health-kpi-grid,
.automation-inventory-service-health-map-grid,
.automation-inventory-service-health-evidence-grid,

View File

@@ -262,6 +262,11 @@ export const apiClient = {
return handleResponse<AiAgentAutomationBacklogSnapshot>(res)
},
async getAiAgentDeploymentLayout() {
const res = await fetch(`${API_BASE_URL}/agents/agent-deployment-layout`)
return handleResponse<AiAgentDeploymentLayoutSnapshot>(res)
},
async getRuntimeSurfaceInventory() {
const res = await fetch(`${API_BASE_URL}/agents/runtime-surface-inventory`)
return handleResponse<RuntimeSurfaceInventorySnapshot>(res)
@@ -819,6 +824,109 @@ export interface AiAgentAutomationBacklogSnapshot {
>
}
export interface AiAgentDeploymentLayoutSnapshot {
schema_version: 'ai_agent_deployment_layout_v1'
generated_at: string
program_status: {
overall_completion_percent: number
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
current_task_id: string
next_task_id: string
read_only_mode: true
deployment_authority: 'layout_only_no_runtime_deploy'
}
agent_contracts: Array<{
agent_id: string
display_name: string
primary_specialty: string
deployment_lane: string
allowed_autonomy: string[]
must_delegate_to: string[]
blocked_actions: string[]
learning_scope: string[]
}>
domains: Array<{
domain_id: string
display_name: string
description: string
}>
deployment_targets: Array<{
target_id: string
domain_id: string
display_name: string
target_type: string
primary_agent: string
supporting_agents: string[]
deployment_state:
| 'active_governed'
| 'read_only_layout'
| 'blocked_by_gate'
| 'planned'
| 'candidate_only'
automation_level:
| 'observe_only'
| 'prepare_only'
| 'dry_run_only'
| 'hitl_execute_after_approval'
| 'blocked'
capabilities: string[]
telegram_policy:
| 'failure_only'
| 'action_required'
| 'approval_required'
| 'daily_summary_only'
| 'no_direct_notify'
learning_inputs: string[]
communication_channels: string[]
approval_gate: string
evidence_refs: string[]
next_action: string
}>
collaboration_contract: {
message_bus: string
audit_trail: string
handoff_rules: string[]
frontend_redaction: {
operator_conversation_display_allowed: false
agent_private_reasoning_display_allowed: false
display_policy: string
}
}
learning_contract: {
event_sources: string[]
feedback_loops: string[]
growth_metrics: string[]
retention_policy: string
}
telegram_contract: {
primary_gateway: string
bot_roles: string[]
notification_classes: string[]
redaction_policy: string
e2e_validation: string
}
rollups: {
total_targets: number
by_domain: Record<string, number>
by_primary_agent: Record<string, number>
by_deployment_state: Record<string, number>
by_telegram_policy: Record<string, number>
blocked_target_ids: string[]
approval_required_target_ids: string[]
}
approval_boundaries: Record<
| 'sdk_installation_allowed'
| 'paid_api_call_allowed'
| 'shadow_or_canary_allowed'
| 'production_routing_allowed'
| 'destructive_operation_allowed'
| 'secret_plaintext_allowed'
| 'autonomous_host_mutation_allowed'
| 'telegram_direct_send_allowed',
false
>
}
export interface RuntimeSurfaceInventorySnapshot {
schema_version: 'runtime_surface_inventory_v1'
generated_at: string