Merge remote-tracking branch 'gitea/main' into codex/iwooos-wazuh-boundary-guard-20260624
# Conflicts: # docs/LOGBOOK.md
This commit is contained in:
@@ -38,6 +38,9 @@ from src.core.sse import get_publisher
|
||||
from src.services.agent_market_governance_snapshot import (
|
||||
load_latest_agent_market_governance_snapshot,
|
||||
)
|
||||
from src.services.ai_agent_market_radar_readback import (
|
||||
load_latest_ai_agent_market_radar_readback,
|
||||
)
|
||||
from src.services.agent_service import (
|
||||
AgentService,
|
||||
TaskState,
|
||||
@@ -686,6 +689,35 @@ async def get_market_governance_snapshot() -> dict[str, Any]:
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/ai-agent-market-radar-readback",
|
||||
response_model=dict[str, Any],
|
||||
summary="取得 AI Agent 市場雷達與近期變更盤點",
|
||||
description=(
|
||||
"讀取最新已提交的 AI Agent 市場雷達 readback;"
|
||||
"此端點只呈現近期治理變更、市場主流 Agent 技術來源、候選角色、優先工作清單與封鎖 gate。"
|
||||
"它不呼叫外部來源、不安裝 SDK、不呼叫付費 API、不跑 replay、不進 shadow/canary、"
|
||||
"不送 Telegram、不改主機、不修改 workflow、不替換 OpenClaw。"
|
||||
),
|
||||
)
|
||||
async def get_ai_agent_market_radar_readback() -> dict[str, Any]:
|
||||
"""回傳最新 AI Agent 市場雷達與近期變更盤點只讀快照。"""
|
||||
try:
|
||||
payload = await asyncio.to_thread(load_latest_ai_agent_market_radar_readback)
|
||||
return redact_public_lan_topology(payload)
|
||||
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_market_radar_readback_invalid", error=str(exc))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="AI Agent 市場雷達 readback 無效",
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/automation-inventory-snapshot",
|
||||
response_model=dict[str, Any],
|
||||
|
||||
68
apps/api/src/services/ai_agent_market_radar_readback.py
Normal file
68
apps/api/src/services/ai_agent_market_radar_readback.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
AI Agent market radar readback.
|
||||
|
||||
Loads the committed read-only radar artifact. The radar is an operator
|
||||
decision surface only; it does not approve SDK installs, paid API calls,
|
||||
replay, shadow/canary, Telegram sends, host writes, or production routing.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from src.services.snapshot_paths import default_operations_dir
|
||||
|
||||
_DEFAULT_OPERATIONS_DIR = default_operations_dir(Path(__file__))
|
||||
_SNAPSHOT_NAME = "ai-agent-market-radar-readback.snapshot.json"
|
||||
|
||||
|
||||
def load_latest_ai_agent_market_radar_readback(
|
||||
operations_dir: Path | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Load the committed AI Agent market radar readback snapshot."""
|
||||
directory = operations_dir or _DEFAULT_OPERATIONS_DIR
|
||||
snapshot_path = directory / _SNAPSHOT_NAME
|
||||
with snapshot_path.open(encoding="utf-8") as handle:
|
||||
payload = json.load(handle)
|
||||
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError(f"{snapshot_path}: expected JSON object")
|
||||
if payload.get("schema_version") != "ai_agent_market_radar_readback_v1":
|
||||
raise ValueError(f"{snapshot_path}: unexpected schema_version")
|
||||
|
||||
policy = payload.get("policy") or {}
|
||||
forbidden_true = [
|
||||
key
|
||||
for key in [
|
||||
"sdk_installation_approved",
|
||||
"paid_api_calls_approved",
|
||||
"replay_candidate_approved",
|
||||
"shadow_or_canary_approved",
|
||||
"production_routing_approved",
|
||||
"telegram_send_approved",
|
||||
"host_write_approved",
|
||||
"workflow_modification_approved",
|
||||
"openclaw_replacement_approved",
|
||||
]
|
||||
if policy.get(key) is not False
|
||||
]
|
||||
if forbidden_true:
|
||||
raise ValueError(f"{snapshot_path}: unsafe policy flags: {forbidden_true}")
|
||||
|
||||
serialized = json.dumps(payload, ensure_ascii=False)
|
||||
forbidden_fragments = [
|
||||
"/Users/",
|
||||
".claude/projects",
|
||||
".codex",
|
||||
"192.168.",
|
||||
"auth.json",
|
||||
"conversations",
|
||||
"sessions",
|
||||
]
|
||||
leaked = [fragment for fragment in forbidden_fragments if fragment in serialized]
|
||||
if leaked:
|
||||
raise ValueError(f"{snapshot_path}: forbidden local or raw-history fragment: {leaked}")
|
||||
|
||||
return payload
|
||||
59
apps/api/tests/test_ai_agent_market_radar_readback.py
Normal file
59
apps/api/tests/test_ai_agent_market_radar_readback.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from src.services.ai_agent_market_radar_readback import (
|
||||
load_latest_ai_agent_market_radar_readback,
|
||||
)
|
||||
|
||||
|
||||
def test_ai_agent_market_radar_readback_committed_snapshot_is_safe():
|
||||
payload = load_latest_ai_agent_market_radar_readback()
|
||||
|
||||
assert payload["schema_version"] == "ai_agent_market_radar_readback_v1"
|
||||
assert payload["summary"]["overall_completion_percent"] == 42.2
|
||||
assert payload["summary"]["market_candidates"] == 13
|
||||
assert payload["summary"]["market_sources"] == 34
|
||||
assert payload["summary"]["changed_candidates"] == 13
|
||||
assert payload["summary"]["integration_blocked_candidates"] == 13
|
||||
assert payload["summary"]["replacement_decisions_approved"] == 0
|
||||
|
||||
policy = payload["policy"]
|
||||
assert policy["read_only"] is True
|
||||
assert policy["sdk_installation_approved"] is False
|
||||
assert policy["paid_api_calls_approved"] is False
|
||||
assert policy["replay_candidate_approved"] is False
|
||||
assert policy["shadow_or_canary_approved"] is False
|
||||
assert policy["production_routing_approved"] is False
|
||||
assert policy["telegram_send_approved"] is False
|
||||
assert policy["host_write_approved"] is False
|
||||
assert policy["workflow_modification_approved"] is False
|
||||
assert policy["openclaw_replacement_approved"] is False
|
||||
|
||||
serialized = json.dumps(payload, ensure_ascii=False)
|
||||
assert "/Users/" not in serialized
|
||||
assert ".claude/projects" not in serialized
|
||||
assert ".codex" not in serialized
|
||||
assert "192.168." not in serialized
|
||||
assert "auth.json" not in serialized
|
||||
|
||||
|
||||
def test_ai_agent_market_radar_readback_contains_market_practice_plan():
|
||||
payload = load_latest_ai_agent_market_radar_readback()
|
||||
|
||||
practices = {
|
||||
row["practice"]: row["awoooi_status"]
|
||||
for row in payload["market_practice_alignment"]
|
||||
}
|
||||
assert practices["多 Agent handoff / specialist delegation"] == "partially_modeled"
|
||||
assert practices["Durable execution / persistence / human-in-the-loop"] == (
|
||||
"needed_for_incident_loop"
|
||||
)
|
||||
|
||||
candidates = {
|
||||
row["candidate_id"]: row
|
||||
for row in payload["candidate_role_plan"]
|
||||
}
|
||||
assert candidates["openclaw_incumbent"]["gate_status"] == "production_baseline"
|
||||
assert candidates["nemo_nemotron_fabric"]["gate_status"] == "integration_blocked"
|
||||
assert candidates["langgraph_incident_kernel"]["gate_status"] == "integration_blocked"
|
||||
34
apps/api/tests/test_ai_agent_market_radar_readback_api.py
Normal file
34
apps/api/tests/test_ai_agent_market_radar_readback_api.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from src.api.v1.agents import router
|
||||
|
||||
|
||||
def test_ai_agent_market_radar_readback_endpoint_returns_committed_snapshot():
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/api/v1/agents/ai-agent-market-radar-readback")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["schema_version"] == "ai_agent_market_radar_readback_v1"
|
||||
assert data["summary"]["overall_completion_percent"] == 42.2
|
||||
assert data["summary"]["market_candidates"] == 13
|
||||
assert data["summary"]["market_sources"] == 34
|
||||
assert data["summary"]["changed_candidates"] == 13
|
||||
assert data["summary"]["integration_blocked_candidates"] == 13
|
||||
assert data["summary"]["replacement_decisions_approved"] == 0
|
||||
assert data["policy"]["openclaw_replacement_approved"] is False
|
||||
assert data["policy"]["telegram_send_approved"] is False
|
||||
|
||||
serialized = json.dumps(data, ensure_ascii=False)
|
||||
assert "/Users/" not in serialized
|
||||
assert ".claude/projects" not in serialized
|
||||
assert ".codex" not in serialized
|
||||
assert "192.168." not in serialized
|
||||
Reference in New Issue
Block a user