feat(agent99): establish enterprise AI automation work ledger

This commit is contained in:
ogt
2026-07-10 15:20:23 +08:00
parent a8080e13b9
commit 579d12c3fa
10 changed files with 1283 additions and 2 deletions

View File

@@ -35,6 +35,9 @@ from pydantic import BaseModel, Field
from src.core.logging import get_logger
from src.core.sse import get_publisher
from src.services.agent99_enterprise_ai_automation_work_items import (
load_latest_agent99_enterprise_ai_automation_work_items,
)
from src.services.agent_market_governance_snapshot import (
load_latest_agent_market_governance_snapshot,
)
@@ -1133,6 +1136,40 @@ async def get_delivery_closure_workbench() -> dict[str, Any]:
) from exc
@router.get(
"/agent99-enterprise-ai-automation-work-items",
response_model=dict[str, Any],
summary="取得 Agent99 全域 AI 自動化工作總帳",
description=(
"讀取 Agent99 對全部主機、產品、網站、服務、工具、套件與 AI 控制面"
"的唯一工作總帳,包含 current P0、完整排序、完成契約、verifier、rollback "
"與 coverage。此端點只讀 committed snapshot不連主機、不執行修復、不讀 secret、"
"不呼叫 GitHub也不觸發 workflow。"
),
)
async def get_agent99_enterprise_ai_automation_work_items() -> dict[str, Any]:
"""回傳 Agent99 全域 AI 自動化工作總帳。"""
try:
payload = await asyncio.to_thread(
load_latest_agent99_enterprise_ai_automation_work_items
)
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(
"agent99_enterprise_ai_automation_work_items_invalid",
error=str(exc),
)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Agent99 全域 AI 自動化工作總帳無效",
) from exc
@router.get(
"/awoooi-priority-work-order-readback",
response_model=dict[str, Any],

View File

@@ -0,0 +1,177 @@
"""Agent99 enterprise AI-automation work-item readback."""
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_FILE = "agent99-enterprise-ai-automation-work-items.snapshot.json"
_SCHEMA_VERSION = "agent99_enterprise_ai_automation_work_items_v1"
_VALID_PRIORITIES = {"P0", "P1", "P2"}
_VALID_STATUSES = {"planned", "in_progress", "in_progress_blocked", "complete"}
def load_latest_agent99_enterprise_ai_automation_work_items(
operations_dir: Path | None = None,
) -> dict[str, Any]:
"""Load and validate the canonical Agent99 enterprise work ledger."""
directory = operations_dir or _DEFAULT_OPERATIONS_DIR
path = directory / _SNAPSHOT_FILE
with path.open(encoding="utf-8") as handle:
payload = json.load(handle)
if not isinstance(payload, dict):
raise ValueError(f"{path}: expected JSON object")
if payload.get("schema_version") != _SCHEMA_VERSION:
raise ValueError(f"{path}: unexpected schema_version")
coverage = _require_dict(payload, "coverage", path)
hosts = _require_list(coverage, "hosts", path)
products = _require_list(coverage, "products", path)
public_surfaces = _require_list(coverage, "public_surfaces", path)
work_items = _require_list(payload, "work_items", path)
summary = _require_dict(payload, "summary", path)
current_p0 = _require_dict(payload, "current_p0", path)
promotion_contract = _require_dict(payload, "promotion_contract", path)
required_p0_fields = _require_list(
promotion_contract,
"required_item_fields",
path,
)
_require_count(summary, "host_count", len(hosts), path)
_require_count(summary, "product_count", len(products), path)
_require_count(summary, "public_surface_count", len(public_surfaces), path)
_require_count(summary, "work_item_count", len(work_items), path)
ids: list[str] = []
orders: list[int] = []
priority_counts = {priority: 0 for priority in _VALID_PRIORITIES}
status_counts = {status: 0 for status in _VALID_STATUSES}
p0_ids: list[str] = []
for index, item in enumerate(work_items, start=1):
if not isinstance(item, dict):
raise ValueError(f"{path}: work_items[{index - 1}] must be an object")
item_id = item.get("id")
order = item.get("order")
priority = item.get("priority")
item_status = item.get("status")
if not isinstance(item_id, str) or not item_id:
raise ValueError(f"{path}: work_items[{index - 1}].id is required")
if not isinstance(order, int):
raise ValueError(f"{path}: {item_id}.order must be an integer")
if priority not in _VALID_PRIORITIES:
raise ValueError(f"{path}: {item_id}.priority is invalid")
if item_status not in _VALID_STATUSES:
raise ValueError(f"{path}: {item_id}.status is invalid")
if order != index:
raise ValueError(f"{path}: {item_id}.order must equal {index}")
ids.append(item_id)
orders.append(order)
priority_counts[priority] += 1
status_counts[item_status] += 1
if priority == "P0":
p0_ids.append(item_id)
missing = [field for field in required_p0_fields if item.get(field) is None]
if missing:
raise ValueError(f"{path}: {item_id} missing P0 fields: {missing}")
if len(ids) != len(set(ids)):
raise ValueError(f"{path}: duplicate work item id")
if orders != list(range(1, len(work_items) + 1)):
raise ValueError(f"{path}: work item order is not contiguous")
current_p0_id = current_p0.get("id")
if current_p0_id not in p0_ids:
raise ValueError(f"{path}: current_p0.id is not a P0 work item")
if not p0_ids or p0_ids[0] != current_p0_id:
raise ValueError(f"{path}: current_p0 must be the first ordered P0")
if payload.get("next_execution_order") != p0_ids:
raise ValueError(f"{path}: next_execution_order must equal ordered P0 ids")
_require_count(summary, "p0_count", priority_counts["P0"], path)
_require_count(summary, "p1_count", priority_counts["P1"], path)
_require_count(summary, "p2_count", priority_counts["P2"], path)
_require_count(summary, "planned_count", status_counts["planned"], path)
_require_count(
summary,
"in_progress_count",
status_counts["in_progress"],
path,
)
_require_count(
summary,
"in_progress_blocked_count",
status_counts["in_progress_blocked"],
path,
)
_require_count(summary, "complete_count", status_counts["complete"], path)
_require_count(summary, "current_p0_count", 1, path)
return payload
def build_agent99_enterprise_priority_projection(
payload: dict[str, Any],
) -> dict[str, Any]:
"""Build the bounded projection embedded in the main priority endpoint."""
coverage = payload["coverage"]
summary = payload["summary"]
return {
"schema_version": payload["schema_version"],
"generated_at": payload["generated_at"],
"status": payload["status"],
"scope_complete": payload["scope_complete"],
"current_p0": payload["current_p0"],
"coverage": {
"host_count": len(coverage["hosts"]),
"product_count": len(coverage["products"]),
"public_surface_count": len(coverage["public_surfaces"]),
"service_domain_count": len(coverage["service_domains"]),
"package_domain_count": len(coverage["package_domains"]),
},
"work_item_counts": {
"total": summary["work_item_count"],
"p0": summary["p0_count"],
"p1": summary["p1_count"],
"p2": summary["p2_count"],
"in_progress": summary["in_progress_count"],
"in_progress_blocked": summary["in_progress_blocked_count"],
"planned": summary["planned_count"],
"complete": summary["complete_count"],
},
"next_execution_order": payload["next_execution_order"],
"full_readback_api": "/api/v1/agents/agent99-enterprise-ai-automation-work-items",
}
def _require_dict(payload: dict[str, Any], key: str, path: Path) -> dict[str, Any]:
value = payload.get(key)
if not isinstance(value, dict):
raise ValueError(f"{path}: {key} must be an object")
return value
def _require_list(payload: dict[str, Any], key: str, path: Path) -> list[Any]:
value = payload.get(key)
if not isinstance(value, list):
raise ValueError(f"{path}: {key} must be an array")
return value
def _require_count(
summary: dict[str, Any],
key: str,
expected: int,
path: Path,
) -> None:
if summary.get(key) != expected:
raise ValueError(
f"{path}: summary.{key}={summary.get(key)!r}, expected {expected}"
)

View File

@@ -17,6 +17,10 @@ from pathlib import Path
from typing import Any
from zoneinfo import ZoneInfo
from src.services.agent99_enterprise_ai_automation_work_items import (
build_agent99_enterprise_priority_projection,
load_latest_agent99_enterprise_ai_automation_work_items,
)
from src.services.snapshot_paths import default_operations_dir
_DEFAULT_OPERATIONS_DIR = default_operations_dir(Path(__file__))
@@ -870,11 +874,38 @@ def load_latest_awoooi_priority_work_order_readback(
)
_require_mainline_consistency(payload, str(path))
_apply_commander_inserted_requirement_work_items(payload)
_apply_agent99_enterprise_ai_automation_projection(payload, directory)
_mark_runtime_generated_at(payload)
apply_ai_automation_node_receipts(payload)
return payload
def _apply_agent99_enterprise_ai_automation_projection(
payload: dict[str, Any],
operations_dir: Path,
) -> None:
"""Attach a bounded projection while keeping fixture-only snapshots usable."""
try:
enterprise = load_latest_agent99_enterprise_ai_automation_work_items(
operations_dir
)
except FileNotFoundError:
return
payload["agent99_enterprise_ai_automation"] = (
build_agent99_enterprise_priority_projection(enterprise)
)
source_refs = payload.setdefault("source_refs", {})
if isinstance(source_refs, dict):
source_refs["agent99_enterprise_ai_automation_work_items"] = (
"docs/operations/"
"agent99-enterprise-ai-automation-work-items.snapshot.json"
)
source_refs["agent99_enterprise_ai_automation_work_items_api"] = (
"/api/v1/agents/agent99-enterprise-ai-automation-work-items"
)
def load_controlled_cd_lane_live_metric_readback(
metrics_url: str = _CONTROLLED_CD_LANE_NODE_EXPORTER_URL,
prometheus_query_url: str = _CONTROLLED_CD_LANE_PROMETHEUS_QUERY_URL,

View File

@@ -0,0 +1,109 @@
from __future__ import annotations
# ruff: noqa: E402
import json
import os
from pathlib import Path
os.environ.setdefault("DATABASE_URL", "postgresql+asyncpg://test:test@localhost/test")
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from src.api.v1.agents import router
from src.services.agent99_enterprise_ai_automation_work_items import (
build_agent99_enterprise_priority_projection,
load_latest_agent99_enterprise_ai_automation_work_items,
)
from src.services.awoooi_priority_work_order_readback import (
load_latest_awoooi_priority_work_order_readback,
)
_REPO_ROOT = Path(__file__).resolve().parents[3]
_OPERATIONS_DIR = _REPO_ROOT / "docs" / "operations"
def test_agent99_enterprise_work_items_loader_returns_complete_scope() -> None:
payload = load_latest_agent99_enterprise_ai_automation_work_items()
assert payload["schema_version"] == (
"agent99_enterprise_ai_automation_work_items_v1"
)
assert payload["scope_complete"] is False
assert payload["current_p0"]["id"] == "AG99-P0-001"
assert payload["summary"] == {
"host_count": 7,
"product_count": 12,
"public_surface_count": 23,
"work_item_count": 25,
"p0_count": 14,
"p1_count": 8,
"p2_count": 3,
"in_progress_count": 5,
"in_progress_blocked_count": 3,
"planned_count": 17,
"complete_count": 0,
"current_p0_count": 1,
}
assert payload["next_execution_order"][0] == "AG99-P0-001"
assert payload["next_execution_order"][-1] == "AG99-P0-014"
def test_agent99_enterprise_projection_is_bounded() -> None:
payload = load_latest_agent99_enterprise_ai_automation_work_items()
projection = build_agent99_enterprise_priority_projection(payload)
assert projection["current_p0"]["id"] == "AG99-P0-001"
assert projection["coverage"] == {
"host_count": 7,
"product_count": 12,
"public_surface_count": 23,
"service_domain_count": 12,
"package_domain_count": 7,
}
assert projection["work_item_counts"]["total"] == 25
assert "work_items" not in projection
assert projection["full_readback_api"] == (
"/api/v1/agents/agent99-enterprise-ai-automation-work-items"
)
def test_priority_readback_projects_agent99_enterprise_order() -> None:
payload = load_latest_awoooi_priority_work_order_readback()
projection = payload["agent99_enterprise_ai_automation"]
assert projection["current_p0"]["id"] == "AG99-P0-001"
assert projection["work_item_counts"]["p0"] == 14
assert (
payload["source_refs"]["agent99_enterprise_ai_automation_work_items_api"]
== "/api/v1/agents/agent99-enterprise-ai-automation-work-items"
)
def test_agent99_enterprise_loader_rejects_duplicate_ids(tmp_path: Path) -> None:
source = _OPERATIONS_DIR / (
"agent99-enterprise-ai-automation-work-items.snapshot.json"
)
payload = json.loads(source.read_text(encoding="utf-8"))
payload["work_items"][1]["id"] = payload["work_items"][0]["id"]
target = tmp_path / source.name
target.write_text(json.dumps(payload), encoding="utf-8")
with pytest.raises(ValueError, match="duplicate work item id"):
load_latest_agent99_enterprise_ai_automation_work_items(tmp_path)
def test_agent99_enterprise_work_items_endpoint_returns_snapshot() -> None:
app = FastAPI()
app.include_router(router, prefix="/api/v1/agents")
client = TestClient(app)
response = client.get("/api/v1/agents/agent99-enterprise-ai-automation-work-items")
assert response.status_code == 200
payload = response.json()
assert payload["current_p0"]["id"] == "AG99-P0-001"
assert payload["summary"]["host_count"] == 7
assert payload["summary"]["product_count"] == 12
assert payload["summary"]["public_surface_count"] == 23