feat(governance): 新增 Agent 報告狀態總覽
All checks were successful
Code Review / ai-code-review (push) Successful in 12s
CD Pipeline / tests (push) Successful in 1m26s
CD Pipeline / build-and-deploy (push) Successful in 4m53s
CD Pipeline / post-deploy-checks (push) Successful in 18s

This commit is contained in:
Your Name
2026-06-13 14:37:46 +08:00
parent 151979d9bd
commit d0bbcc8dee
14 changed files with 1404 additions and 10 deletions

View File

@@ -115,6 +115,9 @@ from src.services.ai_agent_report_runtime_fixture_readback import (
from src.services.ai_agent_report_runtime_readiness import (
load_latest_ai_agent_report_runtime_readiness,
)
from src.services.ai_agent_report_status_board import (
load_latest_ai_agent_report_status_board,
)
from src.services.ai_agent_report_truth_actionability_review import (
load_latest_ai_agent_report_truth_actionability_review,
)
@@ -947,6 +950,35 @@ async def get_agent_report_automation_review() -> dict[str, Any]:
) from exc
@router.get(
"/agent-report-status-board",
response_model=dict[str, Any],
summary="取得 AI Agent 日週月報與工作狀態總覽",
description=(
"讀取最新已提交的 P2-108 AI Agent 日報、週報、月報完成狀態、"
"OpenClaw / Hermes / NemoTron 工作量、圖表化狀態、Telegram 草案與自動優化邊界;"
"此端點不排程實發、不送 Telegram、不寫 Gateway queue、不寫讀報回執、"
"不啟動 AI 分析 worker、不執行生產優化、不讀 secret、不回傳內部協作內容。"
),
)
async def get_agent_report_status_board() -> dict[str, Any]:
"""Return the latest read-only AI Agent report status board."""
try:
payload = await asyncio.to_thread(load_latest_ai_agent_report_status_board)
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_report_status_board_invalid", error=str(exc))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="AI Agent 日週月報與工作狀態總覽無效",
) from exc
@router.get(
"/agent-report-runtime-readiness",
response_model=dict[str, Any],

View File

@@ -0,0 +1,273 @@
"""
AI Agent report status board snapshot.
Loads the latest committed P2-108 daily / weekly / monthly report status board.
This module exposes a read-only management summary only. It never schedules
reports, sends Telegram, writes Gateway queues, records read receipts, starts
AI analysis workers, or writes production optimization results.
"""
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_report_status_board_*.json"
_SCHEMA_VERSION = "ai_agent_report_status_board_v1"
_RUNTIME_AUTHORITY = "report_status_board_only_no_live_send_or_write"
_FORBIDDEN_DISPLAY_TERMS = (
"工作視窗",
"對話內容",
"批准!繼續",
"In app browser",
"My request for Codex",
"browser_context",
"codex_user_message",
"prompt_text",
"raw prompt",
"raw_prompt",
"private reasoning",
"private_reasoning",
"chain of thought",
"chain_of_thought",
"authorization_header",
"authorization header",
"secret value",
"secret_value",
"raw payload",
"raw_payload",
"raw Telegram payload",
"raw_telegram_payload",
)
def load_latest_ai_agent_report_status_board(
evaluations_dir: Path | None = None,
) -> dict[str, Any]:
"""Load the newest committed AI Agent report status board snapshot."""
directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR
candidates = sorted(directory.glob(_SNAPSHOT_PATTERN))
if not candidates:
raise FileNotFoundError(f"no AI Agent report status board 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, str(latest))
_require_completion_truth(payload, str(latest))
_require_report_cards(payload, str(latest))
_require_agent_status_reports(payload, str(latest))
_require_visible_charts(payload, str(latest))
_require_operator_answers(payload, str(latest))
_require_activation_boundaries(payload, str(latest))
_require_display_redaction(payload, str(latest))
_require_no_forbidden_display_terms(payload, str(latest))
_require_rollup_consistency(payload, str(latest))
return payload
def _require_schema(payload: dict[str, Any], label: str) -> None:
if payload.get("schema_version") != _SCHEMA_VERSION:
raise ValueError(f"{label}: expected schema_version={_SCHEMA_VERSION}")
status = payload.get("program_status") or {}
if status.get("read_only_mode") is not True:
raise ValueError(f"{label}: program_status.read_only_mode must be true")
if status.get("runtime_authority") != _RUNTIME_AUTHORITY:
raise ValueError(f"{label}: runtime_authority must remain {_RUNTIME_AUTHORITY}")
if status.get("current_task_id") != "P2-108":
raise ValueError(f"{label}: current_task_id must be P2-108")
if status.get("overall_completion_percent") != 100:
raise ValueError(f"{label}: P2-108 status board must be 100 percent complete")
def _require_completion_truth(payload: dict[str, Any], label: str) -> None:
truth = payload.get("report_completion_truth") or {}
required_true = {
"daily_report_visible",
"weekly_report_visible",
"monthly_report_visible",
"per_agent_status_visible",
"workload_metrics_visible",
"chart_package_visible",
"telegram_digest_draft_visible",
"high_risk_human_approval_required",
}
missing = sorted(field for field in required_true if truth.get(field) is not True)
if missing:
raise ValueError(f"{label}: report visibility truth flags must remain true: {missing}")
required_false = {
"live_report_delivery_enabled",
"ai_post_report_analysis_enabled",
"medium_low_auto_optimization_enabled",
}
unsafe = sorted(field for field in required_false if truth.get(field) is not False)
if unsafe:
raise ValueError(f"{label}: live report automation flags must remain false: {unsafe}")
zero_counts = {
"live_telegram_send_count_24h",
"live_auto_optimization_count_24h",
}
non_zero = sorted(field for field in zero_counts if truth.get(field) != 0)
if non_zero:
raise ValueError(f"{label}: live report counters must remain zero: {non_zero}")
def _require_report_cards(payload: dict[str, Any], label: str) -> None:
cards = payload.get("report_status_cards") or []
cadence_ids = {card.get("cadence_id") for card in cards}
if cadence_ids != {"daily", "weekly", "monthly"}:
raise ValueError(f"{label}: report cards must include daily, weekly, monthly")
for card in cards:
cadence_id = card.get("cadence_id")
if card.get("completion_percent") != 100:
raise ValueError(f"{label}: report {cadence_id} must be 100 percent visible")
if card.get("contract_state") != "visible_contract_ready":
raise ValueError(f"{label}: report {cadence_id} contract_state must be visible_contract_ready")
if card.get("delivery_state") != "draft_only":
raise ValueError(f"{label}: report {cadence_id} delivery_state must remain draft_only")
if card.get("live_delivery_count") != 0:
raise ValueError(f"{label}: report {cadence_id} live_delivery_count must remain zero")
if not card.get("next_gate"):
raise ValueError(f"{label}: report {cadence_id} must include next_gate")
def _require_agent_status_reports(payload: dict[str, Any], label: str) -> None:
reports = payload.get("agent_status_reports") or []
agent_ids = {report.get("agent_id") for report in reports}
if agent_ids != {"openclaw", "hermes", "nemotron"}:
raise ValueError(f"{label}: agent status reports must include OpenClaw, Hermes, NemoTron")
for report in reports:
agent_id = report.get("agent_id")
total = report.get("work_units_total")
done = report.get("work_units_done")
waiting = report.get("work_units_waiting_approval")
if not isinstance(total, int) or not isinstance(done, int) or not isinstance(waiting, int):
raise ValueError(f"{label}: agent {agent_id} work units must be integers")
if done + waiting != total:
raise ValueError(f"{label}: agent {agent_id} done + waiting must equal total")
if report.get("live_runtime_work_units_24h") != 0:
raise ValueError(f"{label}: agent {agent_id} live_runtime_work_units_24h must remain zero")
if not report.get("primary_role") or not report.get("status_note"):
raise ValueError(f"{label}: agent {agent_id} must include role and status note")
def _require_visible_charts(payload: dict[str, Any], label: str) -> None:
charts = payload.get("visible_charts") or []
chart_ids = {chart.get("chart_id") for chart in charts}
required = {"report_cadence_completion", "agent_workload_status", "runtime_activation_boundary"}
if chart_ids != required:
raise ValueError(f"{label}: visible charts must match {sorted(required)}")
for chart in charts:
if not chart.get("series"):
raise ValueError(f"{label}: chart {chart.get('chart_id')} must include series")
def _require_operator_answers(payload: dict[str, Any], label: str) -> None:
answers = payload.get("operator_answer_cards") or []
answer_ids = {answer.get("answer_id") for answer in answers}
required = {
"daily_weekly_monthly_complete",
"per_agent_status_visible",
"telegram_and_auto_optimization_boundary",
"high_risk_review_policy",
}
if answer_ids != required:
raise ValueError(f"{label}: operator answers must match {sorted(required)}")
complete_answers = [answer for answer in answers if answer.get("status") == "complete"]
if len(complete_answers) < 2:
raise ValueError(f"{label}: at least report and per-agent answers must be complete")
def _require_activation_boundaries(payload: dict[str, Any], label: str) -> None:
boundaries = payload.get("activation_boundaries") or {}
required_false = {
"scheduler_enabled",
"gateway_queue_write_enabled",
"telegram_send_enabled",
"report_receipt_write_enabled",
"ai_analysis_run_enabled",
"medium_low_auto_execution_enabled",
"production_optimization_write_enabled",
}
unsafe = sorted(field for field in required_false if boundaries.get(field) is not False)
if unsafe:
raise ValueError(f"{label}: activation boundaries must remain false: {unsafe}")
if boundaries.get("high_risk_requires_human_approval") is not True:
raise ValueError(f"{label}: high_risk_requires_human_approval must remain true")
def _require_display_redaction(payload: dict[str, Any], label: str) -> None:
contract = payload.get("display_redaction_contract") or {}
if contract.get("redaction_required") is not True:
raise ValueError(f"{label}: display redaction is required")
forbidden_true = {
"raw_prompt_display_allowed",
"private_reasoning_display_allowed",
"secret_value_display_allowed",
"internal_transcript_display_allowed",
}
unsafe = sorted(field for field in forbidden_true if contract.get(field) is not False)
if unsafe:
raise ValueError(f"{label}: display redaction fields must remain false: {unsafe}")
def _require_no_forbidden_display_terms(payload: Any, label: str) -> None:
strings = _collect_strings(payload)
found = sorted({term for term in _FORBIDDEN_DISPLAY_TERMS for value in strings if term in value})
if found:
raise ValueError(f"{label}: forbidden display terms found: {found}")
def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None:
rollups = payload.get("rollups") or {}
report_cards = payload.get("report_status_cards") or []
agents = payload.get("agent_status_reports") or []
charts = payload.get("visible_charts") or []
answers = payload.get("operator_answer_cards") or []
expected = {
"report_card_count": len(report_cards),
"agent_status_count": len(agents),
"visible_chart_count": len(charts),
"operator_answer_count": len(answers),
"completed_report_count": len([card for card in report_cards if card.get("completion_percent") == 100]),
"workload_unit_total": sum(agent.get("work_units_total", 0) for agent in agents),
"workload_done_total": sum(agent.get("work_units_done", 0) for agent in agents),
"workload_waiting_approval_total": sum(agent.get("work_units_waiting_approval", 0) for agent in agents),
"live_delivery_count": sum(card.get("live_delivery_count", 0) for card in report_cards),
"live_telegram_send_count": 0,
"live_runtime_work_units": sum(agent.get("live_runtime_work_units_24h", 0) for agent in agents),
"live_auto_optimization_count": 0,
"high_risk_requires_human_approval": True,
}
mismatched = {
key: {"expected": value, "actual": rollups.get(key)}
for key, value in expected.items()
if rollups.get(key) != value
}
if mismatched:
raise ValueError(f"{label}: rollup counts must match payload sections: {mismatched}")
def _collect_strings(value: Any) -> list[str]:
if isinstance(value, str):
return [value]
if isinstance(value, list):
strings: list[str] = []
for item in value:
strings.extend(_collect_strings(item))
return strings
if isinstance(value, dict):
strings: list[str] = []
for item in value.values():
strings.extend(_collect_strings(item))
return strings
return []