3948 lines
177 KiB
Python
3948 lines
177 KiB
Python
"""
|
||
Agent Teams API - Phase 9.5 多專家協作系統
|
||
==========================================
|
||
|
||
Endpoints:
|
||
- POST /api/v1/agents/analyze - 觸發 Agent Teams 分析
|
||
- GET /api/v1/agents/status/{task_id} - 查詢分析狀態
|
||
- GET /api/v1/agents/result/{task_id} - 取得分析結果
|
||
- GET /api/v1/agents/stream/{task_id} - SSE 串流進度
|
||
|
||
Phase 9.4-9.5 核心功能:
|
||
1. ConsensusEngine 整合多專家意見
|
||
2. BackgroundTasks 執行長時間分析
|
||
3. Redis Working Memory 儲存結果
|
||
4. SSE 推送即時進度
|
||
|
||
Phase 17 技術債修復 (2026-03-26):
|
||
- Router 層不再直接存取 Redis
|
||
- 所有業務邏輯封裝至 AgentService
|
||
- 符合 leWOOOgo 積木化原則
|
||
|
||
統帥鐵律:
|
||
- 所有分析任務必須可追蹤 (task_id)
|
||
- 超過 60 秒的分析必須用 BackgroundTasks
|
||
- 結果必須存入 Redis (7 天 TTL)
|
||
"""
|
||
|
||
import asyncio
|
||
import json
|
||
from typing import Any
|
||
|
||
from fastapi import APIRouter, BackgroundTasks, HTTPException, status
|
||
from fastapi.responses import StreamingResponse
|
||
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_12_agent_war_room import (
|
||
load_latest_ai_agent_12_agent_war_room,
|
||
)
|
||
from src.services.ai_agent_action_audit_ledger import (
|
||
load_latest_ai_agent_action_audit_ledger,
|
||
)
|
||
from src.services.ai_agent_action_owner_acceptance_event_bus import (
|
||
load_latest_ai_agent_action_owner_acceptance_event_bus,
|
||
)
|
||
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.ai_agent_autonomous_runtime_control import (
|
||
build_ai_agent_autonomous_runtime_control,
|
||
)
|
||
from src.services.ai_agent_candidate_operation_dry_run_evidence import (
|
||
load_latest_ai_agent_candidate_operation_dry_run_evidence,
|
||
)
|
||
from src.services.ai_agent_canonical_runtime_readback_owner_acceptance import (
|
||
load_latest_ai_agent_canonical_runtime_readback_owner_acceptance,
|
||
)
|
||
from src.services.ai_agent_communication_learning_contract import (
|
||
load_latest_ai_agent_communication_learning_contract,
|
||
)
|
||
from src.services.ai_agent_controlled_executor_handoff import (
|
||
load_latest_ai_agent_controlled_executor_handoff,
|
||
)
|
||
from src.services.ai_agent_critic_reviewer_result_capture import (
|
||
load_latest_ai_agent_critic_reviewer_result_capture,
|
||
)
|
||
from src.services.ai_agent_deployment_layout import (
|
||
load_latest_ai_agent_deployment_layout,
|
||
)
|
||
from src.services.ai_agent_failure_receipt_no_send_replay import (
|
||
load_latest_ai_agent_failure_receipt_no_send_replay,
|
||
)
|
||
from src.services.ai_agent_gitea_pr_draft_lane import (
|
||
load_latest_ai_agent_gitea_pr_draft_lane,
|
||
)
|
||
from src.services.ai_agent_high_risk_owner_review_queue import (
|
||
load_latest_ai_agent_high_risk_owner_review_queue,
|
||
)
|
||
from src.services.ai_agent_host_stateful_version_inventory import (
|
||
load_latest_ai_agent_host_stateful_version_inventory,
|
||
)
|
||
from src.services.ai_agent_interaction_learning_proof import (
|
||
load_latest_ai_agent_interaction_learning_proof,
|
||
)
|
||
from src.services.ai_agent_learning_writeback_approval_package import (
|
||
load_latest_ai_agent_learning_writeback_approval_package,
|
||
)
|
||
from src.services.ai_agent_live_read_model_gate import (
|
||
load_latest_ai_agent_live_read_model_gate,
|
||
)
|
||
from src.services.ai_agent_low_medium_risk_whitelist import (
|
||
load_latest_ai_agent_low_medium_risk_whitelist,
|
||
)
|
||
from src.services.ai_agent_market_radar_readback import (
|
||
load_latest_ai_agent_market_radar_readback,
|
||
)
|
||
from src.services.ai_agent_matched_playbook_learning_gap import (
|
||
load_latest_ai_agent_matched_playbook_learning_gap,
|
||
)
|
||
from src.services.ai_agent_operation_permission_model import (
|
||
load_latest_ai_agent_operation_permission_model,
|
||
)
|
||
from src.services.ai_agent_owner_approved_fixture_dry_run import (
|
||
load_latest_ai_agent_owner_approved_fixture_dry_run,
|
||
)
|
||
from src.services.ai_agent_owner_approved_fixture_promotion_gate import (
|
||
load_latest_ai_agent_owner_approved_fixture_promotion_gate,
|
||
)
|
||
from src.services.ai_agent_owner_approved_learning_dry_run import (
|
||
load_latest_ai_agent_owner_approved_learning_dry_run,
|
||
)
|
||
from src.services.ai_agent_owner_approved_result_capture_dry_run import (
|
||
load_latest_ai_agent_owner_approved_result_capture_dry_run,
|
||
)
|
||
from src.services.ai_agent_owner_approved_result_capture_promotion_dry_run import (
|
||
load_latest_ai_agent_owner_approved_result_capture_promotion_dry_run,
|
||
)
|
||
from src.services.ai_agent_owner_approved_result_capture_readback import (
|
||
load_latest_ai_agent_owner_approved_result_capture_readback,
|
||
)
|
||
from src.services.ai_agent_post_write_verifier_package import (
|
||
load_latest_ai_agent_post_write_verifier_package,
|
||
)
|
||
from src.services.ai_agent_proactive_operations_contract import (
|
||
load_latest_ai_agent_proactive_operations_contract,
|
||
)
|
||
from src.services.ai_agent_professional_task_expansion import (
|
||
load_latest_ai_agent_professional_task_expansion,
|
||
)
|
||
from src.services.ai_agent_receipt_readback_owner_review import (
|
||
load_latest_ai_agent_receipt_readback_owner_review,
|
||
)
|
||
from src.services.ai_agent_redis_dry_run_gate import (
|
||
load_latest_ai_agent_redis_dry_run_gate,
|
||
)
|
||
from src.services.ai_agent_report_automation_review import (
|
||
load_latest_ai_agent_report_automation_review,
|
||
)
|
||
from src.services.ai_agent_report_live_delivery_approval_package import (
|
||
load_latest_ai_agent_report_live_delivery_approval_package,
|
||
)
|
||
from src.services.ai_agent_report_no_write_analysis_runtime import (
|
||
load_latest_ai_agent_report_no_write_analysis_runtime,
|
||
)
|
||
from src.services.ai_agent_report_runtime_dry_run import (
|
||
load_latest_ai_agent_report_runtime_dry_run,
|
||
)
|
||
from src.services.ai_agent_report_runtime_fixture_readback import (
|
||
load_latest_ai_agent_report_runtime_fixture_readback,
|
||
)
|
||
from src.services.ai_agent_report_runtime_readiness import (
|
||
load_latest_ai_agent_report_runtime_readiness,
|
||
)
|
||
from src.services.ai_agent_report_source_health import (
|
||
build_ai_agent_report_source_health,
|
||
)
|
||
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,
|
||
)
|
||
from src.services.ai_agent_result_capture_final_release_candidate_readback import (
|
||
load_latest_ai_agent_result_capture_final_release_candidate_readback,
|
||
)
|
||
from src.services.ai_agent_result_capture_no_write_readback import (
|
||
load_latest_ai_agent_result_capture_no_write_readback,
|
||
)
|
||
from src.services.ai_agent_result_capture_owner_acceptance_maintenance_gate import (
|
||
load_latest_ai_agent_result_capture_owner_acceptance_maintenance_gate,
|
||
)
|
||
from src.services.ai_agent_result_capture_owner_acceptance_readback_preflight_hold import (
|
||
load_latest_ai_agent_result_capture_owner_acceptance_readback_preflight_hold,
|
||
)
|
||
from src.services.ai_agent_result_capture_owner_approved_execution_rehearsal import (
|
||
load_latest_ai_agent_result_capture_owner_approved_execution_rehearsal,
|
||
)
|
||
from src.services.ai_agent_result_capture_owner_approved_preflight_release_package import (
|
||
load_latest_ai_agent_result_capture_owner_approved_preflight_release_package,
|
||
)
|
||
from src.services.ai_agent_result_capture_owner_approved_release_readiness_readback import (
|
||
load_latest_ai_agent_result_capture_owner_approved_release_readiness_readback,
|
||
)
|
||
from src.services.ai_agent_result_capture_owner_promotion_review import (
|
||
load_latest_ai_agent_result_capture_owner_promotion_review,
|
||
)
|
||
from src.services.ai_agent_result_capture_owner_release_approval_gate import (
|
||
load_latest_ai_agent_result_capture_owner_release_approval_gate,
|
||
)
|
||
from src.services.ai_agent_result_capture_post_release_verifier_rollback_gate import (
|
||
load_latest_ai_agent_result_capture_post_release_verifier_rollback_gate,
|
||
)
|
||
from src.services.ai_agent_result_capture_promotion_approval_gate import (
|
||
load_latest_ai_agent_result_capture_promotion_approval_gate,
|
||
)
|
||
from src.services.ai_agent_result_capture_release_authorization_hold import (
|
||
load_latest_ai_agent_result_capture_release_authorization_hold,
|
||
)
|
||
from src.services.ai_agent_result_capture_release_authorization_readback_gate import (
|
||
load_latest_ai_agent_result_capture_release_authorization_readback_gate,
|
||
)
|
||
from src.services.ai_agent_result_capture_release_decision_hold import (
|
||
load_latest_ai_agent_result_capture_release_decision_hold,
|
||
)
|
||
from src.services.ai_agent_result_capture_release_decision_input_prep import (
|
||
load_latest_ai_agent_result_capture_release_decision_input_prep,
|
||
)
|
||
from src.services.ai_agent_result_capture_release_decision_next_handoff import (
|
||
load_latest_ai_agent_result_capture_release_decision_next_handoff,
|
||
)
|
||
from src.services.ai_agent_result_capture_release_decision_owner_response_acceptance_gate import (
|
||
load_latest_ai_agent_result_capture_release_decision_owner_response_acceptance_gate,
|
||
)
|
||
from src.services.ai_agent_result_capture_release_decision_owner_response_preflight import (
|
||
load_latest_ai_agent_result_capture_release_decision_owner_response_preflight,
|
||
)
|
||
from src.services.ai_agent_result_capture_release_decision_owner_response_readback import (
|
||
load_latest_ai_agent_result_capture_release_decision_owner_response_readback,
|
||
)
|
||
from src.services.ai_agent_result_capture_release_decision_readback import (
|
||
load_latest_ai_agent_result_capture_release_decision_readback,
|
||
)
|
||
from src.services.ai_agent_result_capture_release_verifier_owner_review_packet import (
|
||
load_latest_ai_agent_result_capture_release_verifier_owner_review_packet,
|
||
)
|
||
from src.services.ai_agent_result_capture_release_verifier_preflight_gate import (
|
||
load_latest_ai_agent_result_capture_release_verifier_preflight_gate,
|
||
)
|
||
from src.services.ai_agent_result_capture_write_gate_review import (
|
||
load_latest_ai_agent_result_capture_write_gate_review,
|
||
)
|
||
from src.services.ai_agent_result_capture_writer_dry_run_fixture import (
|
||
load_latest_ai_agent_result_capture_writer_dry_run_fixture,
|
||
)
|
||
from src.services.ai_agent_result_capture_writer_dry_run_readback import (
|
||
load_latest_ai_agent_result_capture_writer_dry_run_readback,
|
||
)
|
||
from src.services.ai_agent_result_capture_writer_implementation_review import (
|
||
load_latest_ai_agent_result_capture_writer_implementation_review,
|
||
)
|
||
from src.services.ai_agent_reviewer_queue_no_write_readback import (
|
||
load_latest_ai_agent_reviewer_queue_no_write_readback,
|
||
)
|
||
from src.services.ai_agent_runtime_readback_approval_package import (
|
||
load_latest_ai_agent_runtime_readback_approval_package,
|
||
)
|
||
from src.services.ai_agent_runtime_readback_fixture_approval import (
|
||
load_latest_ai_agent_runtime_readback_fixture_approval,
|
||
)
|
||
from src.services.ai_agent_runtime_readback_implementation_review import (
|
||
load_latest_ai_agent_runtime_readback_implementation_review,
|
||
)
|
||
from src.services.ai_agent_runtime_readback_promotion_gate import (
|
||
load_latest_ai_agent_runtime_readback_promotion_gate,
|
||
)
|
||
from src.services.ai_agent_runtime_verifier_evidence_review import (
|
||
load_latest_ai_agent_runtime_verifier_evidence_review,
|
||
)
|
||
from src.services.ai_agent_runtime_worker_shadow_gate import (
|
||
load_latest_ai_agent_runtime_worker_shadow_gate,
|
||
)
|
||
from src.services.ai_agent_runtime_write_gate_review import (
|
||
load_latest_ai_agent_runtime_write_gate_review,
|
||
)
|
||
from src.services.ai_agent_task_result_audit_trail import (
|
||
load_latest_ai_agent_task_result_audit_trail,
|
||
)
|
||
from src.services.ai_agent_telegram_action_required_digest_policy import (
|
||
load_latest_ai_agent_telegram_action_required_digest_policy,
|
||
)
|
||
from src.services.ai_agent_telegram_receipt_approval_package import (
|
||
load_latest_ai_agent_telegram_receipt_approval_package,
|
||
)
|
||
from src.services.ai_agent_tool_adoption_approval_package import (
|
||
load_latest_ai_agent_tool_adoption_approval_package,
|
||
)
|
||
from src.services.ai_agent_version_freshness_snapshot import (
|
||
load_latest_ai_agent_version_freshness_snapshot,
|
||
)
|
||
from src.services.ai_agent_version_lifecycle_update_proposal import (
|
||
load_latest_ai_agent_version_lifecycle_update_proposal,
|
||
)
|
||
from src.services.ai_provider_route_matrix import (
|
||
load_latest_ai_provider_route_matrix,
|
||
)
|
||
from src.services.ai_technology_radar_readback import (
|
||
load_latest_ai_technology_radar_readback,
|
||
)
|
||
from src.services.ai_technology_report_cadence_readback import (
|
||
load_latest_ai_technology_report_cadence_readback,
|
||
)
|
||
from src.services.awoooi_status_cleanup_dashboard import (
|
||
load_latest_awoooi_status_cleanup_dashboard,
|
||
)
|
||
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.delivery_closure_workbench import (
|
||
load_delivery_closure_workbench,
|
||
)
|
||
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_supply_chain_drift_monitor import (
|
||
load_latest_dependency_supply_chain_drift_monitor,
|
||
)
|
||
from src.services.dependency_upgrade_approval_package_template import (
|
||
load_latest_dependency_upgrade_approval_package_template,
|
||
)
|
||
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.github_target_private_backup_evidence_gate import (
|
||
load_latest_github_target_private_backup_evidence_gate,
|
||
preflight_github_target_owner_response_submission,
|
||
validate_github_target_safe_credential_evidence_refs,
|
||
)
|
||
from src.services.host_runaway_aiops_loop_readiness import (
|
||
load_latest_host_runaway_aiops_loop_readiness,
|
||
)
|
||
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.product_code_review_gate import (
|
||
load_latest_product_code_review_gate,
|
||
)
|
||
from src.services.public_redaction import redact_public_lan_topology
|
||
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"])
|
||
logger = get_logger("awoooi.agents")
|
||
|
||
|
||
# =============================================================================
|
||
# Request/Response Models
|
||
# =============================================================================
|
||
|
||
class AnalyzeRequest(BaseModel):
|
||
"""分析請求"""
|
||
incident_id: str | None = Field(
|
||
None,
|
||
description="現有 Incident ID (二選一)"
|
||
)
|
||
# 或直接提供 Incident 資訊
|
||
severity: str | None = Field(
|
||
None,
|
||
description="事件嚴重度 (P0/P1/P2/P3)"
|
||
)
|
||
affected_services: list[str] | None = Field(
|
||
None,
|
||
description="受影響服務列表"
|
||
)
|
||
alert_names: list[str] | None = Field(
|
||
None,
|
||
description="告警名稱列表"
|
||
)
|
||
context: dict[str, Any] | None = Field(
|
||
None,
|
||
description="額外上下文"
|
||
)
|
||
|
||
|
||
class AnalyzeResponse(BaseModel):
|
||
"""分析回應"""
|
||
task_id: str
|
||
status: str
|
||
message: str
|
||
estimated_seconds: int = 30
|
||
|
||
|
||
class TaskStatusResponse(BaseModel):
|
||
"""任務狀態回應"""
|
||
task_id: str
|
||
state: str
|
||
progress: int # 0-100
|
||
current_step: str | None = None
|
||
agents_completed: int = 0
|
||
total_agents: int = 4
|
||
started_at: str | None = None
|
||
completed_at: str | None = None
|
||
error: str | None = None
|
||
|
||
|
||
class TaskResultResponse(BaseModel):
|
||
"""任務結果回應"""
|
||
task_id: str
|
||
state: str
|
||
consensus_id: str | None = None
|
||
incident_id: str | None = None
|
||
consensus_score: float | None = None
|
||
recommended_action: str | None = None
|
||
recommended_kubectl: str | None = None
|
||
risk_level: str | None = None
|
||
final_reasoning: str | None = None
|
||
opinions: list[dict[str, Any]] | None = None
|
||
dissenting_opinions: list[str] | None = None
|
||
created_at: str | None = None
|
||
|
||
|
||
# =============================================================================
|
||
# Background Task Wrapper
|
||
# =============================================================================
|
||
|
||
async def _run_analysis_task(
|
||
service: AgentService,
|
||
task_id: str,
|
||
incident_id: str,
|
||
) -> None:
|
||
"""
|
||
背景任務包裝器
|
||
|
||
從 AgentService 取得 Incident 並執行分析
|
||
"""
|
||
incident = await service.get_incident(incident_id)
|
||
if incident is None:
|
||
logger.error("background_task_incident_not_found", incident_id=incident_id)
|
||
return
|
||
|
||
await service.run_analysis(task_id, incident)
|
||
|
||
|
||
# =============================================================================
|
||
# API Endpoints
|
||
# =============================================================================
|
||
|
||
@router.post(
|
||
"/analyze",
|
||
response_model=AnalyzeResponse,
|
||
summary="觸發 Agent Teams 分析",
|
||
description="""
|
||
觸發多專家協作分析。
|
||
|
||
可提供:
|
||
- 現有 Incident ID (從 Redis 讀取)
|
||
- 或直接提供事件資訊 (severity, affected_services, alert_names)
|
||
|
||
分析在背景執行,使用 task_id 追蹤進度。
|
||
|
||
專家團隊:
|
||
- SRE Agent: 系統穩定性分析
|
||
- Security Agent: 資安風險評估
|
||
- Cost Agent: 成本效益分析
|
||
- Performance Agent: 效能優化建議
|
||
""",
|
||
)
|
||
async def analyze(
|
||
request: AnalyzeRequest,
|
||
background_tasks: BackgroundTasks,
|
||
) -> AnalyzeResponse:
|
||
"""
|
||
觸發 Agent Teams 分析
|
||
|
||
返回 task_id 用於追蹤進度
|
||
"""
|
||
service = get_agent_service()
|
||
|
||
# 取得或建立 Incident
|
||
if request.incident_id:
|
||
# 從 Redis 讀取現有 Incident
|
||
incident = await service.get_incident(request.incident_id)
|
||
|
||
if incident is None:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail=f"Incident not found: {request.incident_id}",
|
||
)
|
||
|
||
elif request.severity and request.affected_services:
|
||
# 建立臨時 Incident
|
||
incident = service.create_adhoc_incident(
|
||
severity=request.severity,
|
||
affected_services=request.affected_services,
|
||
alert_names=request.alert_names,
|
||
)
|
||
|
||
else:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="Must provide either incident_id or (severity + affected_services)",
|
||
)
|
||
|
||
# 建立任務
|
||
task_id = await service.create_analysis_task(incident, trigger="manual")
|
||
|
||
# 加入背景任務
|
||
background_tasks.add_task(
|
||
service.run_analysis,
|
||
task_id,
|
||
incident,
|
||
)
|
||
|
||
logger.info(
|
||
"agent_analysis_started",
|
||
task_id=task_id,
|
||
incident_id=incident.incident_id,
|
||
severity=incident.severity.value,
|
||
)
|
||
|
||
return AnalyzeResponse(
|
||
task_id=task_id,
|
||
status="pending",
|
||
message="Agent Teams 分析已啟動",
|
||
estimated_seconds=30,
|
||
)
|
||
|
||
|
||
@router.get(
|
||
"/status/{task_id}",
|
||
response_model=TaskStatusResponse,
|
||
summary="查詢分析狀態",
|
||
description="查詢 Agent Teams 分析任務的目前狀態與進度。",
|
||
)
|
||
async def get_status(task_id: str) -> TaskStatusResponse:
|
||
"""
|
||
查詢任務狀態
|
||
|
||
返回進度百分比與目前步驟
|
||
"""
|
||
service = get_agent_service()
|
||
task_data = await service.get_task_status(task_id)
|
||
|
||
if task_data is None:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail=f"Task not found: {task_id}",
|
||
)
|
||
|
||
return TaskStatusResponse(
|
||
task_id=task_id,
|
||
state=task_data.get("state", "unknown"),
|
||
progress=task_data.get("progress", 0),
|
||
current_step=task_data.get("current_step"),
|
||
agents_completed=task_data.get("agents_completed", 0),
|
||
total_agents=task_data.get("total_agents", 4),
|
||
started_at=task_data.get("started_at"),
|
||
completed_at=task_data.get("completed_at"),
|
||
error=task_data.get("error"),
|
||
)
|
||
|
||
|
||
@router.get(
|
||
"/result/{task_id}",
|
||
response_model=TaskResultResponse,
|
||
summary="取得分析結果",
|
||
description="取得 Agent Teams 分析的完整結果,包含所有專家意見與共識決策。",
|
||
)
|
||
async def get_result(task_id: str) -> TaskResultResponse:
|
||
"""
|
||
取得分析結果
|
||
|
||
只有 COMPLETED 狀態才有完整結果
|
||
"""
|
||
service = get_agent_service()
|
||
task_data = await service.get_task_result(task_id)
|
||
|
||
if task_data is None:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail=f"Task not found: {task_id}",
|
||
)
|
||
|
||
return TaskResultResponse(
|
||
task_id=task_id,
|
||
state=task_data.get("state", "unknown"),
|
||
consensus_id=task_data.get("consensus_id"),
|
||
incident_id=task_data.get("incident_id"),
|
||
consensus_score=task_data.get("consensus_score"),
|
||
recommended_action=task_data.get("recommended_action"),
|
||
recommended_kubectl=task_data.get("recommended_kubectl"),
|
||
risk_level=task_data.get("risk_level"),
|
||
final_reasoning=task_data.get("final_reasoning"),
|
||
opinions=task_data.get("opinions"),
|
||
dissenting_opinions=task_data.get("dissenting_opinions"),
|
||
created_at=task_data.get("completed_at"),
|
||
)
|
||
|
||
|
||
@router.get(
|
||
"/stream/{task_id}",
|
||
summary="SSE 串流進度",
|
||
description="透過 Server-Sent Events 即時接收分析進度更新。",
|
||
)
|
||
async def stream_progress(task_id: str) -> StreamingResponse:
|
||
"""
|
||
SSE 串流分析進度
|
||
|
||
客戶端可訂閱此端點接收即時更新
|
||
"""
|
||
service = get_agent_service()
|
||
|
||
# 驗證任務存在
|
||
task_data = await service.get_task_status(task_id)
|
||
if task_data is None:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail=f"Task not found: {task_id}",
|
||
)
|
||
|
||
async def generate():
|
||
"""SSE 串流生成器"""
|
||
publisher = await get_publisher()
|
||
client = await publisher.subscribe(
|
||
topics=[f"agent_task:{task_id}"],
|
||
metadata={"task_id": task_id},
|
||
)
|
||
|
||
try:
|
||
# 發送初始狀態
|
||
current_data = await service.get_task_status(task_id)
|
||
if current_data:
|
||
payload = json.dumps(
|
||
{"type": "status", **current_data},
|
||
ensure_ascii=False,
|
||
)
|
||
yield f"data: {payload}\n\n"
|
||
|
||
# 串流後續更新
|
||
async for event_str in publisher.stream(client):
|
||
yield event_str
|
||
|
||
# 檢查是否完成或失敗
|
||
current_data = await service.get_task_status(task_id)
|
||
if current_data:
|
||
final_states = [TaskState.COMPLETED.value, TaskState.FAILED.value]
|
||
if current_data.get("state") in final_states:
|
||
break
|
||
|
||
except asyncio.CancelledError:
|
||
logger.info("agent_stream_cancelled", task_id=task_id)
|
||
raise
|
||
finally:
|
||
await publisher.unsubscribe(client.id)
|
||
|
||
return StreamingResponse(
|
||
generate(),
|
||
media_type="text/event-stream",
|
||
headers={
|
||
"Cache-Control": "no-cache",
|
||
"Connection": "keep-alive",
|
||
"X-Accel-Buffering": "no",
|
||
},
|
||
)
|
||
|
||
|
||
@router.get(
|
||
"/market-governance-snapshot",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 市場治理快照",
|
||
description=(
|
||
"讀取最新已提交的 Agent market governance snapshot;"
|
||
"此 endpoint 不呼叫外部來源、不批准 SDK/API/replay/shadow/canary/production change。"
|
||
),
|
||
)
|
||
async def get_market_governance_snapshot() -> dict[str, Any]:
|
||
"""Return the latest read-only Agent market governance snapshot."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_agent_market_governance_snapshot)
|
||
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("agent_market_governance_snapshot_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="Agent market governance snapshot is invalid",
|
||
) 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(
|
||
"/ai-technology-radar-readback",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI 技術雷達與滾動更新讀回",
|
||
description=(
|
||
"讀取最新已提交的 AI 技術雷達 readback;"
|
||
"此端點只呈現 AI 技術 primary sources、技術領域、審核佇列、Agent 分工與滾動更新 Gate。"
|
||
"它不呼叫外部來源、不安裝 SDK、不呼叫付費 API、不切換模型、不送 Telegram、"
|
||
"不改主機、不修改 production routing、不替換 OpenClaw。"
|
||
),
|
||
)
|
||
async def get_ai_technology_radar_readback() -> dict[str, Any]:
|
||
"""回傳最新 AI 技術雷達與滾動更新只讀快照。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_technology_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_technology_radar_readback_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI 技術雷達 readback 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/ai-technology-report-cadence-readback",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI 技術雷達日週月報與報告後分析讀回",
|
||
description=(
|
||
"讀取最新已提交的 AI 技術雷達日報、週報、月報 readback;"
|
||
"此端點只呈現報告節奏、Agent 工作狀態、圖表化摘要、報告後 AI 分析包、"
|
||
"低中高風險處理邊界與 Telegram no-send 審核包。"
|
||
"它不送 Telegram、不寫 receipt、不呼叫 Bot API、不執行低中風險 runtime write、"
|
||
"不安裝 SDK、不呼叫付費 API、不切換模型、不改主機、不修改 production routing、不替換 OpenClaw。"
|
||
),
|
||
)
|
||
async def get_ai_technology_report_cadence_readback() -> dict[str, Any]:
|
||
"""回傳 AI 技術雷達日週月報與報告後分析只讀快照。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_technology_report_cadence_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_technology_report_cadence_readback_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI 技術雷達日週月報 readback 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/automation-inventory-snapshot",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 自動化盤點快照",
|
||
description=(
|
||
"讀取最新已提交的 AI Agent 自動化盤點快照;"
|
||
"此端點不呼叫外部來源、不碰 DB/Redis、不批准 SDK/API/shadow/canary/生產變更。"
|
||
),
|
||
)
|
||
async def get_automation_inventory_snapshot() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent automation inventory snapshot."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_automation_inventory_snapshot)
|
||
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_automation_inventory_snapshot_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent automation inventory snapshot is invalid",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-autonomous-runtime-control",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 目前有效自主化控制層",
|
||
description=(
|
||
"回傳目前有效的 AI Agent 自主化控制層;此端點明確覆寫舊 no-send / no-live "
|
||
"歷史快照,宣告 low / medium / high 風險可在 allowlist、Ansible check-mode、"
|
||
"controlled apply、post-apply verifier、KM 與 Telegram Gateway receipt 下受控自動處理。"
|
||
"它不讀 secret、不呼叫 Bot API、不暴露 chat id、不執行 runtime 動作;runtime 動作由既有 worker / Gateway 接手。"
|
||
),
|
||
)
|
||
async def get_agent_autonomous_runtime_control() -> dict[str, Any]:
|
||
"""回傳目前有效 AI Agent 自主化控制層。"""
|
||
try:
|
||
return await asyncio.to_thread(build_ai_agent_autonomous_runtime_control)
|
||
except ValueError as exc:
|
||
logger.error("ai_agent_autonomous_runtime_control_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 目前有效自主化控制層無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/automation-backlog-snapshot",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 自動化待辦快照",
|
||
description=(
|
||
"讀取最新已提交的 AI Agent 自動化待辦快照;"
|
||
"此端點不呼叫外部來源、不碰 DB/Redis、不批准 SDK/API/shadow/canary/生產變更。"
|
||
),
|
||
)
|
||
async def get_automation_backlog_snapshot() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent automation backlog snapshot."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_automation_backlog_snapshot)
|
||
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_automation_backlog_snapshot_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent automation backlog snapshot is invalid",
|
||
) 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(
|
||
"/awoooi-status-cleanup-dashboard",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AWOOOI 狀態清理儀表板",
|
||
description=(
|
||
"讀取最新已提交的 AWOOOI Status Cleanup Dashboard 只讀快照;"
|
||
"此端點只呈現狀態清理完成度、owner gate、apply gate、artifact sync 與 Wazuh handoff 邊界。"
|
||
"它不更新 project_current_status / memory、不同步 raw Codex App DB / auth / conversations / sessions、"
|
||
"不呼叫 Wazuh live API、不執行 active response、不改主機、不修改 workflow / repo refs、"
|
||
"不執行 backup / restore / migration。"
|
||
),
|
||
)
|
||
async def get_awoooi_status_cleanup_dashboard() -> dict[str, Any]:
|
||
"""回傳最新 AWOOOI 狀態清理儀表板只讀快照。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_awoooi_status_cleanup_dashboard)
|
||
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("awoooi_status_cleanup_dashboard_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AWOOOI 狀態清理儀表板快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/delivery-closure-workbench",
|
||
response_model=dict[str, Any],
|
||
summary="取得交付閉環工作台彙總",
|
||
description=(
|
||
"彙總 AWOOOI 狀態清理、GitHub 私有備援、Gitea / CI-CD、Runtime surface "
|
||
"與 Backup / DR 的既有只讀快照,回傳前端工作台可直接使用的交付主線、"
|
||
"完成度、阻擋數與下一步。此端點不呼叫 GitHub / Gitea / Wazuh / K8s live API、"
|
||
"不建立 repo、不改 visibility、不同步 refs、不觸發 workflow、不執行備份或還原、"
|
||
"不讀 secret、不做 runtime write。"
|
||
),
|
||
)
|
||
async def get_delivery_closure_workbench() -> dict[str, Any]:
|
||
"""回傳 AWOOOI 交付閉環工作台只讀彙總。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_delivery_closure_workbench)
|
||
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("delivery_closure_workbench_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AWOOOI 交付閉環工作台彙總無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/github-target-private-backup-evidence-gate",
|
||
response_model=dict[str, Any],
|
||
summary="取得 GitHub 私有備援證據閘門",
|
||
description=(
|
||
"彙整既有 GitHub target decision、owner response、approval package 與 probe snapshot,"
|
||
"用只讀方式判定 GitHub 備援是否具備 private visibility、safe credential 與 owner evidence。"
|
||
"此端點不呼叫 GitHub live API、不建立 repo、不改 visibility、不同步 refs、不觸發 workflow、"
|
||
"不收 private clone URL credential 或任何 secret value。"
|
||
),
|
||
)
|
||
async def get_github_target_private_backup_evidence_gate() -> dict[str, Any]:
|
||
"""回傳 GitHub 私有備援 evidence gate 只讀彙總。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_github_target_private_backup_evidence_gate)
|
||
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("github_target_private_backup_evidence_gate_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="GitHub 私有備援證據閘門無效",
|
||
) from exc
|
||
|
||
|
||
@router.post(
|
||
"/github-target-owner-response-intake-preflight",
|
||
response_model=dict[str, Any],
|
||
summary="預檢 GitHub target owner response candidate",
|
||
description=(
|
||
"只驗證一份 GitHub target owner response candidate 是否符合 read-only intake 規則;"
|
||
"此端點不持久化 submission、不呼叫 GitHub live API、不建立 repo、不改 visibility、不同步 refs、"
|
||
"不觸發 workflow、不收 private clone URL credential 或任何 secret value。"
|
||
),
|
||
)
|
||
async def preflight_github_target_owner_response_intake(
|
||
submission: dict[str, Any],
|
||
) -> dict[str, Any]:
|
||
"""Validate a GitHub target owner response candidate without persisting it."""
|
||
try:
|
||
payload = await asyncio.to_thread(
|
||
preflight_github_target_owner_response_submission,
|
||
submission,
|
||
)
|
||
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(
|
||
"github_target_owner_response_intake_preflight_invalid",
|
||
error=str(exc),
|
||
)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="GitHub target owner response intake preflight 無效",
|
||
) from exc
|
||
|
||
|
||
@router.post(
|
||
"/github-target-safe-credential-evidence-reviewer-validation/validate-redacted-refs",
|
||
response_model=dict[str, Any],
|
||
summary="驗證 GitHub target safe credential 脫敏 evidence refs",
|
||
description=(
|
||
"針對單次 owner-provided redacted safe credential evidence refs 進行 no-persist "
|
||
"reviewer validation,回傳 accepted / needs supplement / quarantined / rejected runtime "
|
||
"action 分流。此端點不保存 payload、不呼叫 GitHub live API、不建立 repo、不改 visibility、"
|
||
"不同步 refs、不觸發 workflow、不收 private clone URL credential 或任何 secret value,也不更新 "
|
||
"safe credential accepted evidence 總帳。"
|
||
),
|
||
)
|
||
async def validate_github_target_safe_credential_evidence_review(
|
||
submission: dict[str, Any],
|
||
) -> dict[str, Any]:
|
||
"""回傳單次 GitHub safe credential 脫敏 evidence refs 公開安全驗證結果。"""
|
||
try:
|
||
payload = await asyncio.to_thread(
|
||
validate_github_target_safe_credential_evidence_refs,
|
||
submission,
|
||
)
|
||
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(
|
||
"github_target_safe_credential_evidence_review_invalid",
|
||
error=str(exc),
|
||
)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="GitHub target safe credential evidence review 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-12-agent-war-room",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 12-Agent War Room 快照",
|
||
description=(
|
||
"讀取最新已提交的 12-Agent War Room 只讀快照;"
|
||
"此端點只呈現 OpenClaw、Hermes、NemoTron、SRE、Security、DevOps、Data/DR、"
|
||
"Supply Chain、Product/UI、QA、Market Scout、Telegram Ops 的分工、工作量、阻擋項與批准邊界,"
|
||
"不開 runtime writer、不送 Telegram、不呼叫 Bot API、不安裝 SDK、不呼叫付費 API、"
|
||
"不讀 secret、不執行 production write。"
|
||
),
|
||
)
|
||
async def get_agent_12_agent_war_room() -> dict[str, Any]:
|
||
"""回傳最新 12-Agent War Room 只讀快照。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_12_agent_war_room)
|
||
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_12_agent_war_room_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 12-Agent War Room 快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-professional-task-expansion",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 專業任務擴展與 Telegram Runtime Bridge 快照",
|
||
description=(
|
||
"讀取最新已提交的 P2-405F AI Agent 專業任務擴展與 Telegram Runtime Bridge 只讀快照;"
|
||
"此端點只呈現 OpenClaw、Hermes、NemoTron 與專責 Agent 可承接的專業任務、MCP/RAG、"
|
||
"風險分層、Telegram no-send preview 與後續 canary gate,"
|
||
"不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不讀 secret、不執行 production write、"
|
||
"不改主機、不執行 kubectl。"
|
||
),
|
||
)
|
||
async def get_agent_professional_task_expansion() -> dict[str, Any]:
|
||
"""回傳最新 AI Agent 專業任務擴展與 Telegram Runtime Bridge 只讀快照。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_professional_task_expansion)
|
||
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_professional_task_expansion_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 專業任務擴展與 Telegram Runtime Bridge 快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-receipt-readback-owner-review",
|
||
response_model=dict[str, Any],
|
||
summary="取得 P2-406B AI Agent receipt readback owner review",
|
||
description=(
|
||
"讀取最新已提交的 P2-406B receipt readback owner review 只讀快照;"
|
||
"此端點只呈現日報 / 週報 / 月報、Telegram receipt、P2-004 供應鏈漂移與報表真相 gate 的 owner review,"
|
||
"不啟用排程、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 receipt production target、"
|
||
"不啟動 AI analysis worker、不執行中低風險自動處理、不寫 production、不讀 secret、"
|
||
"不呼叫付費 API、不改主機、不執行 kubectl 或不可逆操作。"
|
||
),
|
||
)
|
||
async def get_agent_receipt_readback_owner_review() -> dict[str, Any]:
|
||
"""回傳最新 P2-406B receipt readback owner review 只讀快照。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_receipt_readback_owner_review)
|
||
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_receipt_readback_owner_review_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="P2-406B AI Agent receipt readback owner review 快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-report-no-write-analysis-runtime",
|
||
response_model=dict[str, Any],
|
||
summary="取得 P2-407 AI Agent 報表 no-write 分析 runtime",
|
||
description=(
|
||
"讀取最新已提交的 P2-407 AI Agent 報表 no-write 分析快照;此端點只呈現 "
|
||
"OpenClaw、Hermes、NemoTron 讀取日報 / 週報 / 月報、P2-406B receipt owner review、"
|
||
"P2-004 供應鏈漂移與 P2-403J 報表真相後產生的分析草稿與風險分級。"
|
||
"它不啟動 live AI worker、不排程實發、不寫 Gateway queue、不送 Telegram、"
|
||
"不呼叫 Bot API、不寫 receipt production target、不寫 production、不讀 secret、"
|
||
"不呼叫付費 API、不改主機、不執行 kubectl 或不可逆操作。"
|
||
),
|
||
)
|
||
async def get_agent_report_no_write_analysis_runtime() -> dict[str, Any]:
|
||
"""回傳最新 P2-407 report no-write analysis runtime 只讀快照。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_report_no_write_analysis_runtime)
|
||
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_no_write_analysis_runtime_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="P2-407 AI Agent 報表 no-write 分析 runtime 快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-low-medium-risk-whitelist",
|
||
response_model=dict[str, Any],
|
||
summary="取得 P2-408 AI Agent 中低風險自動處理白名單",
|
||
description=(
|
||
"讀取最新已提交的 P2-408 AI Agent 中 / 低風險候選白名單快照;此端點只呈現 "
|
||
"low / medium action policy、dry-run verifier、rollback proof、audit reason 與高風險分流。"
|
||
"它不啟動 auto worker、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、"
|
||
"不寫 receipt production target、不寫 production、不讀 secret、不呼叫付費 API、"
|
||
"不改主機、不執行 kubectl 或不可逆操作。"
|
||
),
|
||
)
|
||
async def get_agent_low_medium_risk_whitelist() -> dict[str, Any]:
|
||
"""回傳最新 P2-408 low / medium risk whitelist 只讀快照。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_low_medium_risk_whitelist)
|
||
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_low_medium_risk_whitelist_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="P2-408 AI Agent 中低風險自動處理白名單快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-high-risk-owner-review-queue",
|
||
response_model=dict[str, Any],
|
||
summary="取得 P2-409 AI Agent 高風險受控執行 / Break-glass 佇列",
|
||
description=(
|
||
"讀取最新已提交的 P2-409 AI Agent 高風險受控執行 / critical break-glass 只讀快照;"
|
||
"此端點呈現 high controlled apply queue、critical break-glass queue、packet、"
|
||
"rejection guard、reviewer checklist、Telegram policy 與執行邊界。"
|
||
"它不自行啟動 auto worker、不執行 live action、不寫 Gateway queue、不送 Telegram、"
|
||
"不呼叫 Bot API、不寫 receipt production target、不寫 production、不讀 secret、"
|
||
"不呼叫付費 API、不改主機、不執行 kubectl 或不可逆操作。"
|
||
),
|
||
)
|
||
async def get_agent_high_risk_owner_review_queue() -> dict[str, Any]:
|
||
"""回傳最新 P2-409 high-risk controlled apply queue 只讀快照。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_high_risk_owner_review_queue)
|
||
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_high_risk_owner_review_queue_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="P2-409 AI Agent 高風險受控執行 / Break-glass 佇列快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-controlled-executor-handoff",
|
||
response_model=dict[str, Any],
|
||
summary="取得 P2-415 AI Agent 受控 Executor 交接跑道",
|
||
description=(
|
||
"讀取最新已提交的 P2-415 AI Agent controlled executor handoff 只讀快照;"
|
||
"此端點呈現 high risk packet 是否具備 allowlist、Ansible check-mode、rollback、"
|
||
"post-action verifier、Telegram evidence、KM / PlayBook trust writeback 條件,"
|
||
"以及 critical break-glass 邊界。它不 dispatch executor、不執行 live apply、"
|
||
"不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 KM、不更新 PlayBook trust、"
|
||
"不寫 production、不讀 secret、不呼叫付費 API、不改主機、不執行 kubectl 或不可逆操作。"
|
||
),
|
||
)
|
||
async def get_agent_controlled_executor_handoff() -> dict[str, Any]:
|
||
"""回傳最新 P2-415 controlled executor handoff 只讀快照。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_controlled_executor_handoff)
|
||
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_controlled_executor_handoff_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="P2-415 AI Agent 受控 Executor 交接跑道快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-action-audit-ledger",
|
||
response_model=dict[str, Any],
|
||
summary="取得 P2-410 AI Agent 行動審計帳本",
|
||
description=(
|
||
"讀取最新已提交的 P2-410 AI Agent 行動審計帳本快照;此端點只呈現 "
|
||
"immutable audit event template、source readback、verifier receipt gate、redaction contract "
|
||
"與 no-write activation boundary。它不寫 audit DB、不寫 timeline、不寫 KM、不更新 PlayBook trust、"
|
||
"不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 production、不讀 secret、"
|
||
"不呼叫付費 API、不改主機、不執行 kubectl 或不可逆操作。"
|
||
),
|
||
)
|
||
async def get_agent_action_audit_ledger() -> dict[str, Any]:
|
||
"""回傳最新 P2-410 action audit ledger 只讀快照。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_action_audit_ledger)
|
||
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_action_audit_ledger_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="P2-410 AI Agent 行動審計帳本快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-action-owner-acceptance-event-bus",
|
||
response_model=dict[str, Any],
|
||
summary="取得 P2-411 AI Agent Owner Acceptance / Handoff Event Bus",
|
||
description=(
|
||
"讀取最新已提交的 P2-411 AI Agent owner acceptance / handoff event bus "
|
||
"no-write 快照;此端點只呈現 owner acceptance lane、handoff event template、"
|
||
"RAG memory proposal、verifier gate 與 no-write activation boundary。它不 publish event bus、"
|
||
"不寫 audit DB、不寫 timeline、不寫 KM、不更新 PlayBook trust、不寫 Gateway queue、"
|
||
"不送 Telegram、不呼叫 Bot API、不 dispatch worker、不寫 production、不讀 secret、"
|
||
"不呼叫付費 API、不改主機、不執行 kubectl 或不可逆操作。"
|
||
),
|
||
)
|
||
async def get_agent_action_owner_acceptance_event_bus() -> dict[str, Any]:
|
||
"""回傳最新 P2-411 owner acceptance / handoff event bus 只讀快照。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_action_owner_acceptance_event_bus)
|
||
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_action_owner_acceptance_event_bus_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="P2-411 AI Agent Owner Acceptance / Handoff Event Bus 快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-host-runaway-aiops-loop-readiness",
|
||
response_model=dict[str, Any],
|
||
summary="取得 P3-009 Host runaway AIOps loop readiness",
|
||
description=(
|
||
"讀取最新已提交的 P3-009 Host runaway AIOps loop readiness 只讀快照;"
|
||
"此端點只呈現 110 runaway process 的監控、告警、AI event packet、PlayBook、KM / Verifier "
|
||
"與 gated remediation 合約,不送 Telegram、不寫 Gateway queue、不呼叫 Bot API、不讀 secret、"
|
||
"不 kill process、不重啟 Docker / systemd / Nginx、不改 firewall、不執行 kubectl、不寫 production。"
|
||
),
|
||
)
|
||
async def get_agent_host_runaway_aiops_loop_readiness() -> dict[str, Any]:
|
||
"""回傳最新 P3-009 Host runaway AIOps loop readiness 只讀快照。"""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_host_runaway_aiops_loop_readiness)
|
||
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("host_runaway_aiops_loop_readiness_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="P3-009 Host runaway AIOps loop readiness 快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-communication-learning-contract",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 主動溝通與學習契約",
|
||
description=(
|
||
"讀取最新已提交的 OpenClaw / Hermes / NemoTron 主動溝通、學習、記錄、MCP 與 RAG 契約;"
|
||
"此端點不啟動 worker、不建立 DB migration、不送 Telegram、不安裝 SDK、不呼叫付費服務、"
|
||
"不修改生產路由或主機。"
|
||
),
|
||
)
|
||
async def get_agent_communication_learning_contract() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent communication learning contract."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_communication_learning_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_communication_learning_contract_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 主動溝通與學習契約無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-interaction-learning-proof",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 互動與學習證據面",
|
||
description=(
|
||
"讀取最新已提交的 OpenClaw / Hermes / NemoTron 互動、接手、學習、成長與 Telegram 收據證據面;"
|
||
"此端點不啟動 worker、不讀寫 Redis consumer group、不建立 DB migration、不送 Telegram、"
|
||
"不回傳內部協作逐字稿、提示詞、私有推理或機密值。"
|
||
),
|
||
)
|
||
async def get_agent_interaction_learning_proof() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent interaction and learning proof surface."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_interaction_learning_proof)
|
||
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_interaction_learning_proof_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 互動與學習證據面無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-live-read-model-gate",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent live read model gate",
|
||
description=(
|
||
"讀取最新已提交的 AgentSession / Redis Streams live read model gate;"
|
||
"此端點不連 DB、不讀寫 Redis、不啟動 worker、不建立 DB migration、不送 Telegram、"
|
||
"不回傳內部協作逐字稿、Agent 原始輸出、提示詞、私有推理或機密值。"
|
||
),
|
||
)
|
||
async def get_agent_live_read_model_gate() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent live read model gate snapshot."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_live_read_model_gate)
|
||
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_live_read_model_gate_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent live read model gate 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-redis-dry-run-gate",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent Redis dry-run gate",
|
||
description=(
|
||
"讀取最新已提交的 Redis Streams consumer group dry-run、handoff envelope、"
|
||
"ack / dead-letter / replay gate;此端點不連 Redis、不建立 consumer group、不 XADD、"
|
||
"不 XREADGROUP、不 ACK、不寫 dead-letter、不 replay、不送 Telegram、不做 learning writeback、"
|
||
"不回傳未核准內部細節。"
|
||
),
|
||
)
|
||
async def get_agent_redis_dry_run_gate() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent Redis dry-run gate snapshot."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_redis_dry_run_gate)
|
||
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_redis_dry_run_gate_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent Redis dry-run gate 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-learning-writeback-approval-package",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent learning writeback approval package",
|
||
description=(
|
||
"讀取最新已提交的 KM / PlayBook trust / timeline learning / replay score 回寫批准包;"
|
||
"此端點不寫 KM、不更新 PlayBook trust、不寫 timeline、不寫 replay score、不送 Telegram、"
|
||
"不啟動 runtime worker、不回傳未核准內部細節。"
|
||
),
|
||
)
|
||
async def get_agent_learning_writeback_approval_package() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent learning writeback approval package."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_learning_writeback_approval_package)
|
||
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_learning_writeback_approval_package_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent learning writeback approval package 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-telegram-receipt-approval-package",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent Telegram receipt approval package",
|
||
description=(
|
||
"讀取最新已提交的 Telegram receipt / queue / delivery / ack / failure / retry 批准包;"
|
||
"此端點不寫 Gateway queue、不呼叫 Telegram Bot API、不改 receiver route、不發送通知、"
|
||
"不啟動 runtime worker、不回傳 Telegram token、raw chat id 或未脫敏 payload。"
|
||
),
|
||
)
|
||
async def get_agent_telegram_receipt_approval_package() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent Telegram receipt approval package."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_telegram_receipt_approval_package)
|
||
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_telegram_receipt_approval_package_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent Telegram receipt approval package 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-owner-approved-learning-dry-run",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent owner-approved learning dry-run contract",
|
||
description=(
|
||
"讀取最新已提交的 owner-approved learning writeback dry-run 契約;"
|
||
"此端點只回傳 dry-run preview、人工操作選項、驗證與 rollback 契約,"
|
||
"不寫 KM、不更新 PlayBook trust、不寫 timeline、不寫 replay score、不發 Telegram、"
|
||
"不啟動 runtime worker、不回傳未脫敏 payload。"
|
||
),
|
||
)
|
||
async def get_agent_owner_approved_learning_dry_run() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent owner-approved learning dry-run contract."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_owner_approved_learning_dry_run)
|
||
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_owner_approved_learning_dry_run_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent owner-approved learning dry-run 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-runtime-write-gate-review",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent runtime write gate review",
|
||
description=(
|
||
"讀取最新已提交的 runtime write gate review 契約;此端點只回傳雙重批准、"
|
||
"dry-run hash、post-write verifier 與 redaction 欄位檢查,"
|
||
"不寫 KM、不更新 PlayBook trust、不寫 timeline、不寫 replay score、不發 Telegram、"
|
||
"不啟動 runtime worker、不回傳未脫敏 payload。"
|
||
),
|
||
)
|
||
async def get_agent_runtime_write_gate_review() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent runtime write gate review."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_runtime_write_gate_review)
|
||
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_runtime_write_gate_review_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent runtime write gate review 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-post-write-verifier-package",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent post-write verifier package",
|
||
description=(
|
||
"讀取最新已提交的 post-write verifier implementation package;此端點只回傳 verifier package、"
|
||
"rollback lane、failure lane 與人工操作選項,"
|
||
"不寫 KM、不更新 PlayBook trust、不寫 timeline、不寫 replay score、不發 Telegram、"
|
||
"不啟動 runtime worker、不讀 canonical target、不回傳未脫敏 payload。"
|
||
),
|
||
)
|
||
async def get_agent_post_write_verifier_package() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent post-write verifier package."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_post_write_verifier_package)
|
||
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_post_write_verifier_package_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent post-write verifier package 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-runtime-verifier-evidence-review",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent runtime verifier evidence review",
|
||
description=(
|
||
"讀取最新已提交的 runtime verifier evidence implementation review;此端點只回傳 "
|
||
"evidence checks、implementation review lanes、redaction policy 與人工操作選項,"
|
||
"不實作或執行 verifier、不讀 canonical target、不寫 rollback work item、不發 Telegram、"
|
||
"不寫 KM / PlayBook trust / timeline / replay score、不啟動 runtime worker。"
|
||
),
|
||
)
|
||
async def get_agent_runtime_verifier_evidence_review() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent runtime verifier evidence review."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_runtime_verifier_evidence_review)
|
||
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_runtime_verifier_evidence_review_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent runtime verifier evidence review 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-report-truth-actionability-review",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 報表真相與告警可處置性審查",
|
||
description=(
|
||
"讀取最新已提交的日報 / 週報 / 月報真相與告警可處置性審查;此端點只回傳 "
|
||
"zero-signal findings、cadence contracts、actionability lanes 與人工操作選項,"
|
||
"不發 Telegram、不修改 CronJob、不改 Prometheus / Alertmanager、不建立 work item、"
|
||
"不寫 KM / PlayBook trust、不啟動 runtime worker。"
|
||
),
|
||
)
|
||
async def get_agent_report_truth_actionability_review() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent report truth actionability review."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_report_truth_actionability_review)
|
||
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_truth_actionability_review_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 報表真相與告警可處置性審查無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-report-automation-review",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 日週月報與風險自動化 review",
|
||
description=(
|
||
"讀取最新已提交的 AI Agent 日報、週報、月報、Agent 工作量、圖表化報告、"
|
||
"AI 分析建議與高/中/低風險自動化政策;此端點不排程實發、不送 Telegram、"
|
||
"不啟動中低風險自動執行器、不執行生產優化、不讀 secret、不回傳內部工作視窗對話。"
|
||
),
|
||
)
|
||
async def get_agent_report_automation_review() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent report automation review."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_report_automation_review)
|
||
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_automation_review_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 日週月報與風險自動化 review 無效",
|
||
) 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-source-health",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 報表資料源健康與 no-send preview",
|
||
description=(
|
||
"回傳日報 / 週報 / 月報資料源健康、全 0 判讀、no-send preview、"
|
||
"KM / PlayBook / 腳本 / 排程 / Verifier 沉澱與 report-source-gap 工作項;"
|
||
"此端點只做 redacted readback,不送 Telegram、不寫 Gateway queue、不改排程、"
|
||
"不啟動 AI runtime、不執行中低風險自動修復、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_report_source_health() -> dict[str, Any]:
|
||
"""Return the read-only AI Agent report source health model."""
|
||
try:
|
||
payload = await build_ai_agent_report_source_health()
|
||
return redact_public_lan_topology(payload)
|
||
except Exception as exc:
|
||
logger.error("ai_agent_report_source_health_failed", 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],
|
||
summary="取得 AI Agent 報表 runtime 啟動前閘門",
|
||
description=(
|
||
"讀取最新已提交的 P2-403L 日週月報派送、Telegram Gateway queue、讀報回執、"
|
||
"AI 讀報後分析、中低風險自動處理、高風險審核與 post-action verifier 啟動前閘門;"
|
||
"此端點不排程實發、不送 Telegram、不寫 Gateway queue、不啟動 AI runtime worker、"
|
||
"不啟動中低風險 auto worker、不執行生產優化、不讀 secret、不回傳內部對話內容。"
|
||
),
|
||
)
|
||
async def get_agent_report_runtime_readiness() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent report runtime readiness gate."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_report_runtime_readiness)
|
||
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_runtime_readiness_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 報表 runtime 啟動前閘門無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-report-runtime-dry-run",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 報表 runtime no-write dry-run 證據包",
|
||
description=(
|
||
"讀取最新已提交的 P2-403M 報表 runtime no-write dry-run、Telegram Gateway queue 草案、"
|
||
"讀報回執 redaction 與 readback verifier 草案;此端點不排程實發、不送 Telegram、"
|
||
"不寫 Gateway queue、不寫讀報回執、不啟動 AI runtime worker、不啟動中低風險 auto worker、"
|
||
"不執行 verifier live readback、不讀 secret、不回傳內部對話內容。"
|
||
),
|
||
)
|
||
async def get_agent_report_runtime_dry_run() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent report runtime dry-run package."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_report_runtime_dry_run)
|
||
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_runtime_dry_run_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 報表 runtime no-write dry-run 證據包無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-report-runtime-fixture-readback",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 報表 runtime fixture readback 證據包",
|
||
description=(
|
||
"讀取最新已提交的 P2-403N fixture smoke、Telegram Gateway queue preview readback "
|
||
"與 verifier dry-run 證據包;此端點不排程實發、不送 Telegram、不呼叫 Bot API、"
|
||
"不寫 Gateway queue、不寫讀報回執、不啟動 AI runtime worker、不啟動中低風險 auto worker、"
|
||
"不執行 verifier live readback、不寫 production target、不讀 secret、不回傳內部對話內容。"
|
||
),
|
||
)
|
||
async def get_agent_report_runtime_fixture_readback() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent fixture readback package."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_report_runtime_fixture_readback)
|
||
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_runtime_fixture_readback_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 報表 runtime fixture readback 證據包無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-runtime-worker-shadow-gate",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent runtime worker shadow/no-write 證據閘門",
|
||
description=(
|
||
"讀取最新已提交的 P2-404 runtime worker shadow / no-write execution evidence gate;"
|
||
"此端點只回傳 shadow candidate、no-write replay、verifier shadow case 與 operator checkpoint,"
|
||
"不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫讀報回執、不啟動 live AI runtime worker、"
|
||
"不啟動中低風險 auto worker、不執行 verifier live readback、不寫 production target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_runtime_worker_shadow_gate() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent runtime worker shadow gate."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_runtime_worker_shadow_gate)
|
||
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_runtime_worker_shadow_gate_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent runtime worker shadow/no-write 證據閘門無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-operation-permission-model",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 操作類別權限模型",
|
||
description=(
|
||
"讀取最新已提交的 P2-101 操作類別權限模型;此端點只回傳 permission lane、"
|
||
"operation category、Agent responsibility、gate transition 與 operator template,"
|
||
"不啟動 runtime worker、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、"
|
||
"不寫讀報回執、不執行 verifier live readback、不寫 production target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_operation_permission_model() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent operation permission model."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_operation_permission_model)
|
||
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_operation_permission_model_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 操作類別權限模型無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-candidate-operation-dry-run-evidence",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 候選操作 dry-run 證據",
|
||
description=(
|
||
"讀取最新已提交的 P2-102 候選操作 dry-run 證據;此端點只回傳 candidate operation、"
|
||
"dry-run evidence hash、side-effect count、verifier plan、gate requirement 與 operator handoff,"
|
||
"不啟動 runtime worker、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、"
|
||
"不寫讀報回執、不執行 verifier live readback、不寫 production target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_candidate_operation_dry_run_evidence() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent candidate operation dry-run evidence."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_candidate_operation_dry_run_evidence)
|
||
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_candidate_operation_dry_run_evidence_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 候選操作 dry-run 證據無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-task-result-audit-trail",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 任務結果稽核軌跡",
|
||
description=(
|
||
"讀取最新已提交的 P2-103 任務結果稽核軌跡;此端點只回傳 result route、"
|
||
"KM / LOGBOOK / audit / timeline writeback contract、operator handoff 與 redaction boundary,"
|
||
"不寫 KM、不 runtime append LOGBOOK、不寫 audit DB、不寫 timeline、不更新 PlayBook trust、"
|
||
"不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 production target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_task_result_audit_trail() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent task result audit trail contract."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_task_result_audit_trail)
|
||
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_task_result_audit_trail_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 任務結果稽核軌跡無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-matched-playbook-learning-gap",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent matched PlayBook 學習缺口",
|
||
description=(
|
||
"讀取最新已提交的 P2-104 matched PlayBook 學習缺口;此端點只回傳 production DB 只讀回查摘要、"
|
||
"learning gap、gate、writeback candidate 與 redaction boundary,"
|
||
"不寫 learning、不更新 PlayBook trust、不寫 KM、不 runtime append LOGBOOK、不寫 audit DB、不寫 timeline、"
|
||
"不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 production target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_matched_playbook_learning_gap() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent matched PlayBook learning gap contract."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_matched_playbook_learning_gap)
|
||
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_matched_playbook_learning_gap_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent matched PlayBook 學習缺口無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-critic-reviewer-result-capture",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent critic / reviewer result capture 契約",
|
||
description=(
|
||
"讀取最新已提交的 P2-105 critic / reviewer 評分與 result capture 契約;"
|
||
"此端點只回傳 scorecard、result capture contract、promotion gate、candidate route 與 redaction boundary,"
|
||
"不寫 learning、不更新 PlayBook trust、不寫 KM、不 runtime append LOGBOOK、不寫 audit DB、不寫 timeline、"
|
||
"不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 production target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_critic_reviewer_result_capture() -> dict[str, Any]:
|
||
"""Return the latest read-only critic / reviewer result capture contract."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_critic_reviewer_result_capture)
|
||
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_critic_reviewer_result_capture_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent critic / reviewer result capture 契約無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-owner-approved-result-capture-dry-run",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent owner-approved result capture dry-run",
|
||
description=(
|
||
"讀取最新已提交的 P2-106 owner-approved result capture dry-run;"
|
||
"此端點只回傳 owner approval packet、no-write dry-run template、score fixture、"
|
||
"dry-run gate 與 operator action,"
|
||
"不寫 score、不寫 result capture、不寫 learning、不更新 PlayBook trust、不寫 KM、"
|
||
"不寫 audit DB、不寫 timeline、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、"
|
||
"不寫 production target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_owner_approved_result_capture_dry_run() -> dict[str, Any]:
|
||
"""Return the latest read-only owner-approved result capture dry-run contract."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_owner_approved_result_capture_dry_run)
|
||
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_owner_approved_result_capture_dry_run_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent owner-approved result capture dry-run 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-owner-approved-result-capture-readback",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent owner-approved result capture readback",
|
||
description=(
|
||
"讀取最新已提交的 P2-107 owner-approved result capture readback;"
|
||
"此端點只回傳 result capture readback digest、promotion readiness review、failure lane、"
|
||
"reviewer queue preview 與 operator action,"
|
||
"不讀 canonical runtime target、不寫 score、不寫 result capture、不寫 learning、不更新 PlayBook trust、"
|
||
"不寫 reviewer queue、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 production target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_owner_approved_result_capture_readback() -> dict[str, Any]:
|
||
"""Return the latest read-only owner-approved result capture readback contract."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_owner_approved_result_capture_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_owner_approved_result_capture_readback_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent owner-approved result capture readback 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-runtime-readback-approval-package",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent runtime readback 批准包",
|
||
description=(
|
||
"讀取最新已提交的 P2-109 runtime readback approval package;"
|
||
"此端點只回傳 approval packet、canonical readback plan、rollback drill、"
|
||
"Telegram 失敗收據 gate 與 operator action,"
|
||
"不讀 canonical runtime target、不寫 score、不寫 result capture、不寫 learning、不更新 PlayBook trust、"
|
||
"不寫 reviewer queue、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 rollback work item、"
|
||
"不寫 production target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_runtime_readback_approval_package() -> dict[str, Any]:
|
||
"""Return the latest read-only runtime readback approval package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_runtime_readback_approval_package)
|
||
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_runtime_readback_approval_package_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent runtime readback approval package 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-runtime-readback-implementation-review",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent runtime readback 實作審查",
|
||
description=(
|
||
"讀取最新已提交的 P2-110 runtime readback implementation review;"
|
||
"此端點只回傳 implementation review card、no-write verifier、阻塞原因與 operator action,"
|
||
"不讀 canonical runtime target、不做 live query、不寫 score、不寫 result capture、不寫 learning、"
|
||
"不更新 PlayBook trust、不寫 reviewer queue、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、"
|
||
"不寫 rollback work item、不寫 production target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_runtime_readback_implementation_review() -> dict[str, Any]:
|
||
"""Return the latest read-only runtime readback implementation review."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_runtime_readback_implementation_review)
|
||
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_runtime_readback_implementation_review_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent runtime readback implementation review 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-report-live-delivery-approval-package",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent report live delivery 批准包",
|
||
description=(
|
||
"讀取最新已提交的 P2-111 report live delivery approval package;"
|
||
"此端點只回傳日報、週報、月報、失敗限定摘要與讀報回執的實發批准包、"
|
||
"route lock、payload redaction、no-send receipt 與 operator action,"
|
||
"不排程、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、不寫 report receipt、"
|
||
"不啟動 AI analysis、不做中低風險自動優化、不寫 production target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_report_live_delivery_approval_package() -> dict[str, Any]:
|
||
"""Return the latest read-only report live delivery approval package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_report_live_delivery_approval_package)
|
||
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_live_delivery_approval_package_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent report live delivery approval package 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-runtime-readback-fixture-approval",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent runtime readback fixture 批准包",
|
||
description=(
|
||
"讀取最新已提交的 P2-112 runtime readback fixture approval package;"
|
||
"此端點只回傳 fixture approval card、adapter contract、verifier fixture、"
|
||
"blocker mapping 與 operator action,不讀 canonical runtime target、不做 live query、"
|
||
"不執行 runtime readback、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、"
|
||
"不寫 report receipt、不寫 result capture、不寫 production target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_runtime_readback_fixture_approval() -> dict[str, Any]:
|
||
"""Return the latest read-only runtime readback fixture approval package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_runtime_readback_fixture_approval)
|
||
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_runtime_readback_fixture_approval_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent runtime readback fixture approval 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-runtime-readback-promotion-gate",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent runtime readback promotion gate",
|
||
description=(
|
||
"讀取最新已提交的 P2-113 runtime readback promotion gate;"
|
||
"此端點只回傳 failure receipt、reviewer queue、result capture 的 no-write promotion "
|
||
"lane、preview、verifier 與 blocker,不讀 canonical runtime target、不做 live query、"
|
||
"不寫 reviewer queue、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、"
|
||
"不寫 report receipt、不寫 result capture、不寫 learning / PlayBook trust、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_runtime_readback_promotion_gate() -> dict[str, Any]:
|
||
"""Return the latest read-only runtime readback promotion gate."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_runtime_readback_promotion_gate)
|
||
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_runtime_readback_promotion_gate_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent runtime readback promotion gate 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-owner-approved-fixture-promotion-gate",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent owner-approved fixture promotion gate",
|
||
description=(
|
||
"讀取最新已提交的 P2-114 owner-approved fixture promotion gate;"
|
||
"此端點只回傳 owner approval packet、acceptance template、fixture review、"
|
||
"no-write verifier 與 blocked promotion,不讀 canonical runtime target、不做 live query、"
|
||
"不寫 reviewer queue、不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、"
|
||
"不寫 report receipt、不寫 result capture、不寫 learning / PlayBook trust、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_owner_approved_fixture_promotion_gate() -> dict[str, Any]:
|
||
"""Return the latest read-only owner-approved fixture promotion gate."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_owner_approved_fixture_promotion_gate)
|
||
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_owner_approved_fixture_promotion_gate_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent owner-approved fixture promotion gate 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-canonical-runtime-readback-owner-acceptance",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent canonical runtime readback owner acceptance",
|
||
description=(
|
||
"讀取最新已提交的 P2-115 canonical runtime readback owner acceptance;"
|
||
"此端點只回傳 owner acceptance package、canonical target scope、no-live verifier、"
|
||
"blocked acceptance 與 operator handoff,不讀 canonical runtime target、不做 live query、"
|
||
"不寫 acceptance record、不寫 reviewer queue、不寫 Gateway queue、不送 Telegram、"
|
||
"不呼叫 Bot API、不寫 result capture、不寫 learning / PlayBook trust、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_canonical_runtime_readback_owner_acceptance() -> dict[str, Any]:
|
||
"""Return the latest read-only canonical runtime readback owner acceptance."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_canonical_runtime_readback_owner_acceptance)
|
||
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_canonical_runtime_readback_owner_acceptance_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent canonical runtime readback owner acceptance 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-failure-receipt-no-send-replay",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent failure receipt no-send replay",
|
||
description=(
|
||
"讀取最新已提交的 P2-116 failure receipt no-send replay;"
|
||
"此端點只回傳 no-send replay fixture、SRE 戰情室 route lock、redaction verifier、"
|
||
"blocked send 與 operator handoff,不寫 Gateway queue、不送 Telegram、不呼叫 Bot API、"
|
||
"不寫 reviewer queue、不寫 report receipt、不寫 result capture、不寫 learning / PlayBook trust、"
|
||
"不讀 canonical runtime target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_failure_receipt_no_send_replay() -> dict[str, Any]:
|
||
"""Return the latest read-only failure receipt no-send replay package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_failure_receipt_no_send_replay)
|
||
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_failure_receipt_no_send_replay_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent failure receipt no-send replay 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-reviewer-queue-no-write-readback",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent reviewer queue no-write readback",
|
||
description=(
|
||
"讀取最新已提交的 P2-117 reviewer queue no-write readback;"
|
||
"此端點只回傳 reviewer queue preview fixture、queue item mapping、no-write verifier、"
|
||
"blocked queue write 與 operator handoff,不寫 reviewer queue、不寫 Gateway queue、"
|
||
"不送 Telegram、不呼叫 Bot API、不寫 report receipt / result capture / learning / PlayBook trust、"
|
||
"不讀 canonical runtime target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_reviewer_queue_no_write_readback() -> dict[str, Any]:
|
||
"""Return the latest read-only reviewer queue no-write readback package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_reviewer_queue_no_write_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_reviewer_queue_no_write_readback_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent reviewer queue no-write readback 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-no-write-readback",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture no-write readback",
|
||
description=(
|
||
"讀取最新已提交的 P2-118 result capture no-write readback;"
|
||
"此端點只回傳 result capture preview fixture、capture field mapping、no-write verifier、"
|
||
"blocked result capture write 與 operator handoff,不寫 result capture、learning、"
|
||
"PlayBook trust、Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 canonical runtime target、"
|
||
"不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_no_write_readback() -> dict[str, Any]:
|
||
"""Return the latest read-only result capture no-write readback package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_no_write_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_result_capture_no_write_readback_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture no-write readback 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-promotion-approval-gate",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture promotion approval gate",
|
||
description=(
|
||
"讀取最新已提交的 P2-119 result capture promotion approval gate;"
|
||
"此端點只回傳 promotion approval packet、acceptance template、verifier、"
|
||
"blocked promotion write 與 operator handoff,不寫 result capture、learning、"
|
||
"PlayBook trust、Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 canonical runtime target、"
|
||
"不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_promotion_approval_gate() -> dict[str, Any]:
|
||
"""Return the latest read-only result capture promotion approval gate package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_promotion_approval_gate)
|
||
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_result_capture_promotion_approval_gate_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture promotion approval gate 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-owner-approved-result-capture-promotion-dry-run",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent owner-approved result capture promotion dry-run",
|
||
description=(
|
||
"讀取最新已提交的 P2-120 owner-approved result capture promotion dry-run;"
|
||
"此端點只回傳 dry-run template、owner acceptance fixture、verifier、blocked runtime promotion "
|
||
"與 operator handoff,不寫 result capture、learning、PlayBook trust、Gateway queue,"
|
||
"不送 Telegram、不呼叫 Bot API、不讀 canonical runtime target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_owner_approved_result_capture_promotion_dry_run() -> dict[str, Any]:
|
||
"""Return the latest read-only owner-approved result capture promotion dry-run package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_owner_approved_result_capture_promotion_dry_run)
|
||
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_owner_approved_result_capture_promotion_dry_run_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent owner-approved result capture promotion dry-run 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-write-gate-review",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture write gate review",
|
||
description=(
|
||
"讀取最新已提交的 P2-121 result capture write gate review;"
|
||
"此端點只回傳 writer gate、approval gate、post-write verifier、blocked live write "
|
||
"與 operator handoff,不寫 result capture、learning、PlayBook trust、reviewer queue、"
|
||
"Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 canonical runtime target、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_write_gate_review() -> dict[str, Any]:
|
||
"""Return the latest read-only result capture write gate review package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_write_gate_review)
|
||
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_result_capture_write_gate_review_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture write gate review 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-writer-implementation-review",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture writer implementation review",
|
||
description=(
|
||
"讀取最新已提交的 P2-122 result capture writer implementation review;"
|
||
"此端點只回傳 writer implementation、implementation gate、post-implementation verifier、"
|
||
"blocked runtime change 與 operator handoff,不套用 writer、不寫 result capture、learning、"
|
||
"PlayBook trust、reviewer queue、Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_writer_implementation_review() -> dict[str, Any]:
|
||
"""Return the latest read-only result capture writer implementation review package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_writer_implementation_review)
|
||
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_result_capture_writer_implementation_review_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture writer implementation review 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-writer-dry-run-fixture",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture writer dry-run fixture",
|
||
description=(
|
||
"讀取最新已提交的 P2-123 result capture writer dry-run fixture;"
|
||
"此端點只回傳 writer dry-run fixture、receipt preview、idempotency replay、"
|
||
"rollback rehearsal、blocked runtime write 與 operator handoff,不套用 writer、"
|
||
"不寫 result capture、learning、PlayBook trust、reviewer queue、Gateway queue,"
|
||
"不送 Telegram、不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_writer_dry_run_fixture() -> dict[str, Any]:
|
||
"""Return the latest read-only result capture writer dry-run fixture package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_writer_dry_run_fixture)
|
||
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_result_capture_writer_dry_run_fixture_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture writer dry-run fixture 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-writer-dry-run-readback",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture writer dry-run readback",
|
||
description=(
|
||
"讀取最新已提交的 P2-124 result capture writer dry-run readback;"
|
||
"此端點只回傳 dry-run readback、receipt verifier、promotion readiness、"
|
||
"blocked promotion write 與 operator handoff,不套用 writer、不執行 dry-run、"
|
||
"不寫 receipt、不寫 result capture、learning、PlayBook trust、reviewer queue、"
|
||
"Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_writer_dry_run_readback() -> dict[str, Any]:
|
||
"""Return the latest read-only result capture writer dry-run readback package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_writer_dry_run_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_result_capture_writer_dry_run_readback_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture writer dry-run readback 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-owner-promotion-review",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture owner promotion review",
|
||
description=(
|
||
"讀取最新已提交的 P2-125 result capture owner promotion review;"
|
||
"此端點只回傳 owner promotion packet、execution gate、rollback owner review、"
|
||
"blocked execution write 與 operator handoff,不套用 writer、不執行 live apply、"
|
||
"不寫 receipt、不寫 result capture、learning、PlayBook trust、reviewer queue、"
|
||
"Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_owner_promotion_review() -> dict[str, Any]:
|
||
"""Return the latest read-only result capture owner promotion review package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_owner_promotion_review)
|
||
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_result_capture_owner_promotion_review_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture owner promotion review 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-owner-approved-execution-rehearsal",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture owner-approved execution rehearsal",
|
||
description=(
|
||
"讀取最新已提交的 P2-126 owner-approved execution rehearsal;"
|
||
"此端點只回傳 no-write execution rehearsal、apply gate、verifier rehearsal、"
|
||
"rollback drill、blocked live apply 與 operator handoff,不套用 writer、不執行 live apply、"
|
||
"不寫 receipt、不寫 result capture、learning、PlayBook trust、reviewer queue、"
|
||
"Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_owner_approved_execution_rehearsal() -> dict[str, Any]:
|
||
"""Return the latest read-only owner-approved execution rehearsal package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_owner_approved_execution_rehearsal)
|
||
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_result_capture_owner_approved_execution_rehearsal_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture owner-approved execution rehearsal 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-owner-acceptance-maintenance-gate",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture owner acceptance maintenance gate",
|
||
description=(
|
||
"讀取最新已提交的 P2-127 owner acceptance / maintenance gate;"
|
||
"此端點只回傳 owner acceptance packet、maintenance window、rollback owner、"
|
||
"post-apply verifier gate、blocked live write 與 operator handoff,不套用 writer、"
|
||
"不執行 live apply、不寫 receipt、不寫 result capture、learning、PlayBook trust、"
|
||
"reviewer queue、Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_owner_acceptance_maintenance_gate() -> dict[str, Any]:
|
||
"""Return the latest read-only owner acceptance / maintenance gate package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_owner_acceptance_maintenance_gate)
|
||
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_result_capture_owner_acceptance_maintenance_gate_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture owner acceptance maintenance gate 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-owner-acceptance-readback-preflight-hold",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture owner acceptance readback preflight hold",
|
||
description=(
|
||
"讀取最新已提交的 P2-128 owner acceptance readback / preflight hold;"
|
||
"此端點只回傳 owner acceptance readback、live-apply preflight hold、"
|
||
"live apply hold gate、rollback preflight、blocked apply transition 與 operator handoff,"
|
||
"不釋放 live apply、不套用 writer、不寫 receipt、不寫 result capture、learning、PlayBook trust、"
|
||
"reviewer queue、Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_owner_acceptance_readback_preflight_hold() -> dict[str, Any]:
|
||
"""Return the latest read-only owner acceptance readback / preflight hold package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_owner_acceptance_readback_preflight_hold)
|
||
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_result_capture_owner_acceptance_readback_preflight_hold_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture owner acceptance readback preflight hold 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-owner-approved-preflight-release-package",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture owner-approved preflight release package",
|
||
description=(
|
||
"讀取最新已提交的 P2-129 owner-approved preflight release package;"
|
||
"此端點只回傳 owner-approved release package、release preflight check、"
|
||
"live apply release gate、rollback release check、blocked release transition 與 operator handoff,"
|
||
"不釋放 live apply、不套用 writer、不寫 receipt、不寫 result capture、learning、PlayBook trust、"
|
||
"reviewer queue、Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_owner_approved_preflight_release_package() -> dict[str, Any]:
|
||
"""Return the latest read-only owner-approved preflight release package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_owner_approved_preflight_release_package)
|
||
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_result_capture_owner_approved_preflight_release_package_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture owner-approved preflight release package 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-owner-approved-release-readiness-readback",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture owner-approved release readiness readback",
|
||
description=(
|
||
"讀取最新已提交的 P2-130 owner-approved release readiness readback;"
|
||
"此端點只回傳 release readiness readback、owner release readiness check、"
|
||
"live apply readiness gate、rollback readiness check、blocked readiness transition 與 operator handoff,"
|
||
"不批准 owner release、不批准 maintenance window、不釋放 live apply、不套用 writer、不寫 receipt、"
|
||
"不寫 result capture、learning、PlayBook trust、reviewer queue、Gateway queue,不送 Telegram、"
|
||
"不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_owner_approved_release_readiness_readback() -> dict[str, Any]:
|
||
"""Return the latest read-only owner-approved release readiness readback."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_owner_approved_release_readiness_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_result_capture_owner_approved_release_readiness_readback_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture owner-approved release readiness readback 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-owner-release-approval-gate",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture owner release approval gate",
|
||
description=(
|
||
"讀取最新已提交的 P2-131 owner release approval gate;"
|
||
"此端點只回傳 owner release approval packet、maintenance window gate、"
|
||
"live apply approval gate、rollback owner approval check、blocked approval transition 與 operator handoff,"
|
||
"不批准 owner release、不批准 maintenance window、不釋放 live apply、不套用 writer、不寫 receipt、"
|
||
"不寫 result capture、learning、PlayBook trust、reviewer queue、Gateway queue,不送 Telegram、"
|
||
"不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_owner_release_approval_gate() -> dict[str, Any]:
|
||
"""Return the latest read-only owner release approval gate."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_owner_release_approval_gate)
|
||
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_result_capture_owner_release_approval_gate_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture owner release approval gate 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-post-release-verifier-rollback-gate",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture post-release verifier rollback gate",
|
||
description=(
|
||
"讀取最新已提交的 P2-132 post-release verifier / rollback gate;"
|
||
"此端點只回傳 post-release verifier gate、rollback release gate、release verification hold、"
|
||
"live apply post-release gate、blocked post-release transition 與 operator handoff,"
|
||
"不批准 owner release、不批准 maintenance window、不確認 rollback owner、不釋放 live apply、"
|
||
"不套用 writer、不寫 receipt、不寫 result capture、learning、PlayBook trust、reviewer queue、"
|
||
"Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_post_release_verifier_rollback_gate() -> dict[str, Any]:
|
||
"""Return the latest read-only post-release verifier / rollback gate."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_post_release_verifier_rollback_gate)
|
||
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_result_capture_post_release_verifier_rollback_gate_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture post-release verifier rollback gate 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-final-release-candidate-readback",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture final release candidate readback",
|
||
description=(
|
||
"讀取最新已提交的 P2-133 final release candidate readback;"
|
||
"此端點只回傳 final release candidate readback、rollback candidate readback、candidate acceptance hold、"
|
||
"live apply candidate hold、blocked final candidate transition 與 operator handoff,"
|
||
"不批准 owner release、不批准 maintenance window、不確認 rollback owner、不通過 final candidate、"
|
||
"不釋放 live apply、不套用 writer、不寫 receipt、不寫 result capture、learning、PlayBook trust、"
|
||
"reviewer queue、Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_final_release_candidate_readback() -> dict[str, Any]:
|
||
"""Return the latest read-only final release candidate readback."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_final_release_candidate_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_result_capture_final_release_candidate_readback_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture final release candidate readback 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-release-authorization-hold",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture release authorization hold",
|
||
description=(
|
||
"讀取最新已提交的 P2-134 release authorization hold;"
|
||
"此端點只回傳 release authorization hold、rollback authorization hold、release window hold、"
|
||
"live apply authorization hold、blocked authorization transition 與 operator handoff,"
|
||
"不授權 owner release、不批准 maintenance window、不確認 rollback owner、不通過 release authorization、"
|
||
"不釋放 live apply、不套用 writer、不寫 receipt、不寫 result capture、learning、PlayBook trust、"
|
||
"reviewer queue、Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_release_authorization_hold() -> dict[str, Any]:
|
||
"""Return the latest read-only release authorization hold."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_release_authorization_hold)
|
||
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_result_capture_release_authorization_hold_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture release authorization hold 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-release-authorization-readback-gate",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture release authorization readback gate",
|
||
description=(
|
||
"讀取最新已提交的 P2-135 release authorization readback gate;"
|
||
"此端點只回傳 release authorization readback、rollback release readback、maintenance window readback hold、"
|
||
"live apply release readback hold、blocked release readback transition 與 operator handoff,"
|
||
"不授權 owner release、不批准 maintenance window、不確認 rollback owner、不通過 release authorization、"
|
||
"不釋放 rollback release 或 live apply、不套用 writer、不寫 receipt、不寫 result capture、learning、PlayBook trust、"
|
||
"reviewer queue、Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_release_authorization_readback_gate() -> dict[str, Any]:
|
||
"""Return the latest read-only release authorization readback gate."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_release_authorization_readback_gate)
|
||
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_result_capture_release_authorization_readback_gate_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture release authorization readback gate 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-release-verifier-preflight-gate",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture release verifier preflight gate",
|
||
description=(
|
||
"讀取最新已提交的 P2-136 release verifier preflight gate;"
|
||
"此端點只回傳 release verifier preflight、rollback verifier preflight、maintenance window verifier hold、"
|
||
"live apply verifier hold、blocked verifier preflight transition 與 operator handoff,"
|
||
"不授權 owner release、不批准 maintenance window、不確認 rollback owner、不通過 release authorization、"
|
||
"不釋放 rollback release 或 live apply、不套用 writer、不寫 receipt、不寫 result capture、learning、PlayBook trust、"
|
||
"reviewer queue、Gateway queue,不送 Telegram、不呼叫 Bot API、不讀 secret。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_release_verifier_preflight_gate() -> dict[str, Any]:
|
||
"""Return the latest read-only release verifier preflight gate."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_release_verifier_preflight_gate)
|
||
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_result_capture_release_verifier_preflight_gate_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture release verifier preflight gate 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-release-verifier-owner-review-packet",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture release verifier owner review packet",
|
||
description=(
|
||
"讀取最新已提交的 P2-137 release verifier owner review packet;"
|
||
"此端點只回傳 owner review packet、verifier review packet、rollback owner review、maintenance window review hold、"
|
||
"live apply owner review hold、blocked owner review transition 與 operator action,"
|
||
"不授權 owner release、不批准維護窗口、不確認 rollback owner、不通過 post-release verifier、"
|
||
"不核發或通過 release authorization、不釋放 rollback release 或 live apply、不套用 writer、"
|
||
"不寫 receipt、result capture、learning、PlayBook trust、reviewer queue 或 Gateway queue,"
|
||
"不送 Telegram、不呼叫 Bot API、不讀 secret、不執行 production 寫入。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_release_verifier_owner_review_packet() -> dict[str, Any]:
|
||
"""Return the latest read-only release verifier owner review packet."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_release_verifier_owner_review_packet)
|
||
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_result_capture_release_verifier_owner_review_packet_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture release verifier owner review packet 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-release-decision-hold",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture release decision hold",
|
||
description=(
|
||
"讀取最新已提交的 P2-138 release decision hold;"
|
||
"此端點只回傳 release decision hold、owner / verifier / rollback / live apply decision hold、"
|
||
"blocked decision transition 與 operator action,"
|
||
"不把 owner review packet 視為決策通過、不授權 owner release、不通過 release decision、"
|
||
"不核發或通過 release authorization、不釋放 rollback release 或 live apply、不套用 writer、"
|
||
"不寫 reviewer queue、Gateway queue、receipt、result capture、learning 或 PlayBook trust,"
|
||
"不送 Telegram、不呼叫 Bot API、不讀 secret、不執行 production 寫入。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_release_decision_hold() -> dict[str, Any]:
|
||
"""Return the latest read-only release decision hold."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_release_decision_hold)
|
||
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_result_capture_release_decision_hold_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture release decision hold 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-release-decision-readback",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture release decision readback",
|
||
description=(
|
||
"讀取最新已提交的 P2-139 release decision readback;"
|
||
"此端點只回讀 P2-138 release decision hold 後的釋出、負責人、驗證器、回滾、"
|
||
"維護窗口與正式套用決策狀態,"
|
||
"不把 hold 視為決策通過、不授權 owner release、不核發 release authorization、"
|
||
"不寫 reviewer queue、Gateway queue、receipt、result capture、learning 或 PlayBook trust,"
|
||
"不送 Telegram、不呼叫 Bot API、不讀 secret、不執行 production 寫入。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_release_decision_readback() -> dict[str, Any]:
|
||
"""Return the latest read-only release decision readback."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_release_decision_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_result_capture_release_decision_readback_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture release decision readback 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-release-decision-next-handoff",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture release decision next handoff",
|
||
description=(
|
||
"讀取最新已提交的 P2-140 release decision next handoff;"
|
||
"此端點只回讀 P2-139 release decision readback 的下一關交接,隔離 P2-139 自我迴圈提示,"
|
||
"不把 handoff 視為 owner approval、不核發 release authorization、"
|
||
"不寫 reviewer queue、Gateway queue、receipt、result capture、learning 或 PlayBook trust,"
|
||
"不送 Telegram、不呼叫 Bot API、不讀 secret、不執行 production 寫入。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_release_decision_next_handoff() -> dict[str, Any]:
|
||
"""Return the latest read-only release decision next-handoff readback."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_release_decision_next_handoff)
|
||
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_result_capture_release_decision_next_handoff_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture release decision next handoff 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-release-decision-input-prep",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture release decision input prep",
|
||
description=(
|
||
"讀取最新已提交的 P2-141 release decision input prep;"
|
||
"此端點只把 P2-140 next handoff 轉成 owner / verifier 決策輸入準備包,"
|
||
"不把準備包視為 owner approval、不核發 release authorization、"
|
||
"不寫 reviewer queue、Gateway queue、receipt、result capture、learning 或 PlayBook trust,"
|
||
"不送 Telegram、不呼叫 Bot API、不讀 secret、不執行 production 寫入。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_release_decision_input_prep() -> dict[str, Any]:
|
||
"""Return the latest read-only release decision input-prep package."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_release_decision_input_prep)
|
||
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_result_capture_release_decision_input_prep_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture release decision input prep 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-release-decision-owner-response-preflight",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture release decision owner response preflight",
|
||
description=(
|
||
"讀取最新已提交的 P2-143 release decision owner response preflight;"
|
||
"此端點只把 P2-141 決策輸入準備包轉成 owner response 預檢與拒收邊界,"
|
||
"不把預檢視為正式收件或批准、不核發 release authorization、"
|
||
"不寫 reviewer queue、Gateway queue、receipt、result capture、learning 或 PlayBook trust,"
|
||
"不送 Telegram、不呼叫 Bot API、不讀 secret、不執行 production 寫入。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_release_decision_owner_response_preflight() -> dict[str, Any]:
|
||
"""Return the latest read-only release decision owner-response preflight."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_release_decision_owner_response_preflight)
|
||
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_result_capture_release_decision_owner_response_preflight_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture release decision owner response preflight 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-release-decision-owner-response-readback",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture release decision owner response readback",
|
||
description=(
|
||
"讀取最新已提交的 P2-144 release decision owner response readback;"
|
||
"此端點只讀回 P2-143 預檢後的外部 owner response 狀態,"
|
||
"不把未收到回覆視為正式收件或批准、不核發 release authorization、"
|
||
"不寫 reviewer queue、Gateway queue、receipt、result capture、learning 或 PlayBook trust,"
|
||
"不送 Telegram、不呼叫 Bot API、不讀 secret、不執行 production 寫入。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_release_decision_owner_response_readback() -> dict[str, Any]:
|
||
"""Return the latest read-only release decision owner-response readback."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_ai_agent_result_capture_release_decision_owner_response_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_result_capture_release_decision_owner_response_readback_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture release decision owner response readback 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-result-capture-release-decision-owner-response-acceptance-gate",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent result capture release decision owner response acceptance gate",
|
||
description=(
|
||
"讀取最新已提交的 P2-145 release decision owner response acceptance gate;"
|
||
"此端點只建立 P2-144 readback 後的驗收門檻,"
|
||
"不把未收到或未遮罩回覆視為正式收件、接受、拒絕或 release authorization,"
|
||
"不寫 reviewer queue、Gateway queue、receipt、result capture、learning 或 PlayBook trust,"
|
||
"不送 Telegram、不呼叫 Bot API、不讀 secret、不執行 production 寫入。"
|
||
),
|
||
)
|
||
async def get_agent_result_capture_release_decision_owner_response_acceptance_gate() -> dict[str, Any]:
|
||
"""Return the latest read-only release decision owner-response acceptance gate."""
|
||
try:
|
||
payload = await asyncio.to_thread(
|
||
load_latest_ai_agent_result_capture_release_decision_owner_response_acceptance_gate
|
||
)
|
||
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_result_capture_release_decision_owner_response_acceptance_gate_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent result capture release decision owner response acceptance gate 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-owner-approved-fixture-dry-run",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent owner-approved fixture dry-run 批准包",
|
||
description=(
|
||
"讀取最新已提交的 owner-approved fixture dry-run 批准包;此端點只回傳 fixture-only dry-run 證據,"
|
||
"不寫 KM、不更新 PlayBook trust、不寫 timeline / replay score、不寫 Gateway queue、不呼叫 Telegram Bot API、"
|
||
"不啟動 runtime worker、不開 Redis consumer group、不執行 DB migration、不觸發 workflow、"
|
||
"不執行主機或 cluster 指令、不使用 secrets 或付費 API、不回傳未核准內部細節。"
|
||
),
|
||
)
|
||
async def get_agent_owner_approved_fixture_dry_run() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent owner-approved fixture dry-run package."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_owner_approved_fixture_dry_run)
|
||
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_owner_approved_fixture_dry_run_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent owner-approved fixture dry-run 批准包無效",
|
||
) 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(
|
||
"/agent-version-lifecycle-update-proposal",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 版本生命週期更新提案佇列",
|
||
description=(
|
||
"讀取最新已提交的 AI Agent / 套件 / 工具 / 服務 / 主機版本生命週期更新提案;"
|
||
"此端點只回傳已脫敏治理資料與批准 gate,不啟用排程、不外查 registry/CVE/市場來源、"
|
||
"不升級套件、不寫 lockfile、不 pull/build/push image、不操作 host/K3s/stateful、"
|
||
"不建立 PR、不 auto merge、不送 Telegram、不讀取機密、不切換 provider、"
|
||
"不替換 OpenClaw、不修改生產路由。"
|
||
),
|
||
)
|
||
async def get_agent_version_lifecycle_update_proposal() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent version lifecycle update proposal."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_version_lifecycle_update_proposal)
|
||
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_version_lifecycle_update_proposal_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 版本生命週期更新提案無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-version-freshness-snapshot",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent repo-only 版本新鮮度快照",
|
||
description=(
|
||
"讀取最新已提交的 AI Agent repo-only 版本新鮮度快照;此端點不啟用每日排程、"
|
||
"不查外部 registry/CVE、不安裝或升級套件、不寫 lockfile、不 build/pull image、"
|
||
"不 probe 主機、不建立 PR、不發 Telegram、不呼叫付費服務、不修改生產路由。"
|
||
),
|
||
)
|
||
async def get_agent_version_freshness_snapshot() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent version freshness snapshot."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_version_freshness_snapshot)
|
||
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_version_freshness_snapshot_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 版本新鮮度快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-tool-adoption-approval-package",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent 工具採用批准包",
|
||
description=(
|
||
"讀取最新已提交的 Renovate / OSV-Scanner / Trivy / Syft / Grype 工具採用批准包;"
|
||
"此端點不安裝工具、不寫 CI workflow、不下載漏洞資料庫、不查外部 registry、不升級套件、"
|
||
"不寫 lockfile、不 build/pull image、不建立 Gitea PR、不 auto merge、不發 Telegram、"
|
||
"不呼叫付費服務、不修改生產路由。"
|
||
),
|
||
)
|
||
async def get_agent_tool_adoption_approval_package() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent tool adoption approval package."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_tool_adoption_approval_package)
|
||
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_tool_adoption_approval_package_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent 工具採用批准包無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-telegram-action-required-digest-policy",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent Telegram action-required digest policy",
|
||
description=(
|
||
"讀取最新已提交的 AI Agent Telegram action-required digest policy;"
|
||
"此端點只回傳 critical / action-required / failure-only digest 規則與 redaction 邊界,"
|
||
"不送 Telegram、不寫 Telegram Gateway queue、不改 Alertmanager route / receiver、"
|
||
"不寫 AwoooP event、不觸發 workflow、不查外部掃描、不執行 runtime、不讀取 secret、"
|
||
"不回傳內部協作逐字稿。"
|
||
),
|
||
)
|
||
async def get_agent_telegram_action_required_digest_policy() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent Telegram action-required digest policy."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_telegram_action_required_digest_policy)
|
||
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_telegram_action_required_digest_policy_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent Telegram action-required digest policy 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-gitea-pr-draft-lane",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent Gitea PR 草案 lane",
|
||
description=(
|
||
"讀取最新已提交的 AI Agent Gitea PR 草案 lane;"
|
||
"此端點只回傳 grouping、automerge=false、測試證據、rollback、owner response 與 redaction 邊界,"
|
||
"不 push branch、不建立或更新 Gitea PR、不留言、不 auto merge、不觸發 workflow、不改 CI、"
|
||
"不寫 lockfile、不升級套件、不 build/pull image、不改 production route、不發 Telegram、"
|
||
"不讀取 secret、不回傳內部協作逐字稿。"
|
||
),
|
||
)
|
||
async def get_agent_gitea_pr_draft_lane() -> dict[str, Any]:
|
||
"""Return the latest read-only AI Agent Gitea PR draft lane policy."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_gitea_pr_draft_lane)
|
||
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_gitea_pr_draft_lane_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent Gitea PR 草案 lane 無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/agent-host-stateful-version-inventory",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Agent host / K3s / stateful 版本只讀盤點",
|
||
description=(
|
||
"讀取最新已提交的 AI Agent host OS / K3s / stateful services 版本只讀盤點與 "
|
||
"maintenance window 批准包;此端點不 SSH、不執行 host command、不執行 kubectl、"
|
||
"不 apt upgrade、不升級 kernel/K3s、不 drain node、不 reboot、不 restart stateful service、"
|
||
"不做 DB migration、不刪備份、不 restore、不 pull image、不安裝套件、不查外部版本來源、"
|
||
"不 active scan、不發 Telegram、不讀取 secret、不回傳內部協作逐字稿。"
|
||
),
|
||
)
|
||
async def get_agent_host_stateful_version_inventory() -> dict[str, Any]:
|
||
"""Return the latest read-only host / K3s / stateful version inventory."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_agent_host_stateful_version_inventory)
|
||
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_host_stateful_version_inventory_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Agent host / K3s / stateful 版本只讀盤點無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/runtime-surface-inventory",
|
||
response_model=dict[str, Any],
|
||
summary="取得 Runtime surface 只讀盤點",
|
||
description=(
|
||
"讀取最新已提交的 API / Web / Worker / K8s runtime surface 盤點;"
|
||
"此端點不呼叫 live cluster、不碰 DB/Redis、不讀 Secret payload、"
|
||
"不執行 rollout/restart/scale/delete、不 patch K8s、不改 workflow、不改生產路由。"
|
||
),
|
||
)
|
||
async def get_runtime_surface_inventory() -> dict[str, Any]:
|
||
"""Return the latest read-only runtime surface inventory."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_runtime_surface_inventory)
|
||
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("runtime_surface_inventory_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="Runtime surface 盤點快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/gitea-workflow-runner-health",
|
||
response_model=dict[str, Any],
|
||
summary="取得 Gitea 工作流程與 runner 健康合約",
|
||
description=(
|
||
"讀取最新已提交的 Gitea workflow / runner health contract;"
|
||
"此端點不呼叫 Gitea API、不修改 workflow、不重啟 runner、不停止 container、"
|
||
"不讀 Secret payload、不送 Telegram 測試通知、不觸發 deploy 或 migration。"
|
||
),
|
||
)
|
||
async def get_gitea_workflow_runner_health() -> dict[str, Any]:
|
||
"""Return the latest read-only Gitea workflow / runner health contract."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_gitea_workflow_runner_health)
|
||
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("gitea_workflow_runner_health_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="Gitea 工作流程與 runner 健康合約快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/observability-contract-matrix",
|
||
response_model=dict[str, Any],
|
||
summary="取得監控合約與降噪機會矩陣",
|
||
description=(
|
||
"讀取最新已提交的 Prometheus / Alertmanager / Grafana / SigNoz / ClickHouse / Sentry "
|
||
"只讀 observability matrix;此端點不修改 alert rules、不呼叫 silence API、"
|
||
"不建立 Grafana dashboard、不改 SigNoz / Sentry 設定、不讀 Secret payload、"
|
||
"不送 Telegram 測試通知、不觸發 monitoring deploy。"
|
||
),
|
||
)
|
||
async def get_observability_contract_matrix() -> dict[str, Any]:
|
||
"""Return the latest read-only observability contract matrix."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_observability_contract_matrix)
|
||
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("observability_contract_matrix_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="監控合約與降噪機會矩陣快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/ai-provider-route-matrix",
|
||
response_model=dict[str, Any],
|
||
summary="取得 AI Provider 路由只讀矩陣",
|
||
description=(
|
||
"讀取最新已提交的 AI Router / Ollama / OpenClaw / Nemotron / Gemini provider route matrix;"
|
||
"此端點不切換 provider、不呼叫 Gemini / NVIDIA / Claude、不改 USE_AI_ROUTER、"
|
||
"不修改 fallback order、不讀 Secret payload、不進 shadow / canary、"
|
||
"不觸發 workflow / deploy / reload / runtime execution。"
|
||
),
|
||
)
|
||
async def get_ai_provider_route_matrix() -> dict[str, Any]:
|
||
"""Return the latest read-only AI provider route matrix."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_ai_provider_route_matrix)
|
||
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_provider_route_matrix_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="AI Provider 路由只讀矩陣快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/service-health-gap-matrix",
|
||
response_model=dict[str, Any],
|
||
summary="取得服務健康缺口與過期端點矩陣",
|
||
description=(
|
||
"讀取最新已提交的 service health gap matrix;此端點不做 live probe、"
|
||
"不重啟服務、不修改 endpoint / ConfigMap、不讀 Secret/Redis/DB payload、"
|
||
"不發通知、不觸發 workflow/deploy/reload/runtime execution。"
|
||
),
|
||
)
|
||
async def get_service_health_gap_matrix() -> dict[str, Any]:
|
||
"""Return the latest read-only service health gap matrix."""
|
||
try:
|
||
payload = await asyncio.to_thread(load_latest_service_health_gap_matrix)
|
||
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("service_health_gap_matrix_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="服務健康缺口矩陣快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/backup-dr-target-inventory",
|
||
response_model=dict[str, Any],
|
||
summary="取得 Backup / DR 目標盤點",
|
||
description=(
|
||
"讀取最新已提交的 Backup / DR 目標盤點;"
|
||
"此端點不呼叫外部來源、不執行備份/restore/offsite sync、"
|
||
"不寫 credential marker、不改排程、不批准任何破壞性操作。"
|
||
),
|
||
)
|
||
async def get_backup_dr_target_inventory() -> dict[str, Any]:
|
||
"""Return the latest read-only Backup / DR target inventory."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_backup_dr_target_inventory)
|
||
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("backup_dr_target_inventory_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="Backup / DR target inventory is invalid",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/backup-dr-readiness-matrix",
|
||
response_model=dict[str, Any],
|
||
summary="取得 Backup / DR 準備度矩陣",
|
||
description=(
|
||
"讀取最新已提交的 Backup / DR 準備度矩陣;"
|
||
"此端點不呼叫外部來源、不執行備份/restore/offsite sync、"
|
||
"不寫 credential marker、不改排程、不批准任何破壞性操作。"
|
||
),
|
||
)
|
||
async def get_backup_dr_readiness_matrix() -> dict[str, Any]:
|
||
"""Return the latest read-only Backup / DR readiness matrix."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_backup_dr_readiness_matrix)
|
||
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("backup_dr_readiness_matrix_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="Backup / DR readiness matrix is invalid",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/backup-notification-policy",
|
||
response_model=dict[str, Any],
|
||
summary="取得備份通知政策",
|
||
description=(
|
||
"讀取最新已提交的備份通知政策;此端點只回傳 success-noise suppression、"
|
||
"failure/action-required 升級與每日摘要合約,不送通知、不執行備份/restore/offsite sync、"
|
||
"不寫 credential marker、不改排程、不寫 workflow、不發 Telegram 測試訊息。"
|
||
),
|
||
)
|
||
async def get_backup_notification_policy() -> dict[str, Any]:
|
||
"""Return the latest read-only backup notification policy."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_backup_notification_policy)
|
||
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("backup_notification_policy_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="備份通知政策快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/service-health-failure-notification-policy",
|
||
response_model=dict[str, Any],
|
||
summary="取得服務健康失敗限定通知合約",
|
||
description=(
|
||
"讀取最新已提交的 service health failure-only Telegram / AwoooP 通知合約;"
|
||
"此端點只回傳成功降噪、action-required 與 failure escalation 規則,"
|
||
"不送通知、不做 live probe、不重啟服務、不改 endpoint、不觸發 workflow / runtime execution、"
|
||
"不讀取 secret payload、不回傳內部協作逐字稿或提示詞。"
|
||
),
|
||
)
|
||
async def get_service_health_failure_notification_policy() -> dict[str, Any]:
|
||
"""Return the latest read-only service health failure-only notification policy."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_service_health_failure_notification_policy)
|
||
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("service_health_failure_notification_policy_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="服務健康失敗限定通知合約快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/backup-restore-drill-approval-package-template",
|
||
response_model=dict[str, Any],
|
||
summary="取得 Backup / DR 復原演練批准包模板",
|
||
description=(
|
||
"讀取最新已提交的 Backup / DR restore drill、credential escrow review、"
|
||
"K8s resource recovery、observability recovery 與 route reconstruction 批准包模板;"
|
||
"此端點只回傳 read-only template,不執行 backup、restore、offsite sync、"
|
||
"不寫 credential marker、不改排程、不寫 workflow、不送 Telegram 測試通知、"
|
||
"不輸出 secret 明文、不做破壞性 prune、不呼叫付費 API、不建立 shadow/canary、不改生產路由。"
|
||
),
|
||
)
|
||
async def get_backup_restore_drill_approval_package_template() -> dict[str, Any]:
|
||
"""Return the latest read-only Backup / DR restore drill approval package template."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_backup_restore_drill_approval_package_template)
|
||
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("backup_restore_drill_approval_package_template_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="Backup / DR 復原演練批准包模板快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/offsite-escrow-readiness-status",
|
||
response_model=dict[str, Any],
|
||
summary="取得異地 / Escrow 準備度狀態",
|
||
description=(
|
||
"讀取最新已提交的異地備份、credential escrow 與 K8s resource offsite readiness 狀態;"
|
||
"此端點只回傳 read-only status,不執行 backup、restore、offsite sync、"
|
||
"不寫 credential marker、不讀 credential、不輸出 secret 明文、不改排程、不寫 workflow、"
|
||
"不送 Telegram 測試通知、不做破壞性 prune、不改生產路由。"
|
||
),
|
||
)
|
||
async def get_offsite_escrow_readiness_status() -> dict[str, Any]:
|
||
"""Return the latest read-only offsite / escrow readiness status."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_offsite_escrow_readiness_status)
|
||
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("offsite_escrow_readiness_status_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="異地 / Escrow 準備度狀態快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/package-supply-chain-inventory",
|
||
response_model=dict[str, Any],
|
||
summary="取得套件 / 供應鏈盤點",
|
||
description=(
|
||
"讀取最新已提交的套件 / 供應鏈盤點;"
|
||
"此端點不呼叫外部來源、不安裝依賴、不升級套件、"
|
||
"不寫 lockfile、不查外部 CVE、不重建 image、不改生產路由。"
|
||
),
|
||
)
|
||
async def get_package_supply_chain_inventory() -> dict[str, Any]:
|
||
"""Return the latest read-only package supply-chain inventory."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_package_supply_chain_inventory)
|
||
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("package_supply_chain_inventory_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="套件 / 供應鏈盤點快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/javascript-package-inventory",
|
||
response_model=dict[str, Any],
|
||
summary="取得 JavaScript 套件盤點",
|
||
description=(
|
||
"讀取最新已提交的 JavaScript / pnpm 套件盤點;"
|
||
"此端點不呼叫外部來源、不安裝套件、不升級套件、"
|
||
"不寫 lockfile、不執行 npm audit、不改生產路由。"
|
||
),
|
||
)
|
||
async def get_javascript_package_inventory() -> dict[str, Any]:
|
||
"""Return the latest read-only JavaScript package inventory."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_javascript_package_inventory)
|
||
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("javascript_package_inventory_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="JavaScript 套件盤點快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/docker-build-surface-inventory",
|
||
response_model=dict[str, Any],
|
||
summary="取得 Docker build surface 盤點",
|
||
description=(
|
||
"讀取最新已提交的 Docker base image 與 build surface 盤點;"
|
||
"此端點不執行 docker build、不 pull image、不推 registry、"
|
||
"不查外部 CVE、不安裝套件、不改生產路由。"
|
||
),
|
||
)
|
||
async def get_docker_build_surface_inventory() -> dict[str, Any]:
|
||
"""Return the latest read-only Docker build surface inventory."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_docker_build_surface_inventory)
|
||
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("docker_build_surface_inventory_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="Docker build surface 盤點快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/dependency-risk-policy",
|
||
response_model=dict[str, Any],
|
||
summary="取得依賴風險政策",
|
||
description=(
|
||
"讀取最新已提交的 CVE / license / drift 嚴重度政策;"
|
||
"此端點不呼叫外部 CVE 或 license 來源、不安裝套件、不升級套件、"
|
||
"不寫 lockfile、不執行 docker build、不 pull image、不推 registry、"
|
||
"不呼叫付費 API、不建立 shadow/canary、不改生產路由。"
|
||
),
|
||
)
|
||
async def get_dependency_risk_policy() -> dict[str, Any]:
|
||
"""Return the latest read-only dependency risk policy."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_dependency_risk_policy)
|
||
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("dependency_risk_policy_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="依賴風險政策快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/dependency-drift-check-plan",
|
||
response_model=dict[str, Any],
|
||
summary="取得依賴漂移檢查設計",
|
||
description=(
|
||
"讀取最新已提交的定期依賴漂移、外部資料來源與 AI Agent 市場觀察設計;"
|
||
"此端點只回傳 read-only plan,不啟用排程、不寫 workflow、不呼叫外部 CVE / license / registry / 市場來源、"
|
||
"不安裝 SDK、不呼叫付費 API、不安裝或升級套件、不寫 lockfile、"
|
||
"不執行 docker build、不 pull image、不推 registry、不建立 shadow/canary、不改生產路由。"
|
||
),
|
||
)
|
||
async def get_dependency_drift_check_plan() -> dict[str, Any]:
|
||
"""Return the latest read-only dependency drift check plan."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_dependency_drift_check_plan)
|
||
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("dependency_drift_check_plan_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="依賴漂移檢查設計快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/dependency-supply-chain-drift-monitor",
|
||
response_model=dict[str, Any],
|
||
summary="取得 P2-004 依賴 / 供應鏈漂移監控",
|
||
description=(
|
||
"讀取最新已提交的 P2-004 依賴 / 供應鏈漂移監控;"
|
||
"此端點只回傳 repo-only committed snapshot,不啟用排程、不寫 workflow、"
|
||
"不呼叫外部 CVE / license / registry / Agent market 來源、不安裝或升級套件、"
|
||
"不寫 lockfile、不執行 npm audit、不執行 docker build、不 pull image、不推 registry、"
|
||
"不建立 PR、不送 Telegram、不讀 secret、不做 host probe、不做 production write。"
|
||
),
|
||
)
|
||
async def get_dependency_supply_chain_drift_monitor() -> dict[str, Any]:
|
||
"""Return the latest read-only P2-004 dependency / supply-chain drift monitor."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_dependency_supply_chain_drift_monitor)
|
||
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("dependency_supply_chain_drift_monitor_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="P2-004 依賴 / 供應鏈漂移監控快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/product-code-review-gate",
|
||
response_model=dict[str, Any],
|
||
summary="取得 P2-111 全產品 Code Review 防木馬 Gate",
|
||
description=(
|
||
"讀取最新已提交的 P2-111 全產品推版前後 Code Review / 防木馬 Gate;"
|
||
"此端點會把 AwoooP tenants 資產台帳、Gitea code-review、供應鏈漂移、Aider 事件與 AI reviewer "
|
||
"分工收斂成只讀 read model。它不啟用外部 scanner、不寫 workflow、不 auto-merge、"
|
||
"不部署、不讀 secret、不推 registry、不簽 artifact、不送 Telegram、不寫 Gateway queue、不做 host probe。"
|
||
),
|
||
)
|
||
async def get_product_code_review_gate() -> dict[str, Any]:
|
||
"""Return the latest read-only all-product code review gate."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_product_code_review_gate)
|
||
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("product_code_review_gate_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="P2-111 全產品 Code Review Gate 快照無效",
|
||
) from exc
|
||
|
||
|
||
@router.get(
|
||
"/dependency-upgrade-approval-package-template",
|
||
response_model=dict[str, Any],
|
||
summary="取得依賴升級批准包模板",
|
||
description=(
|
||
"讀取最新已提交的依賴升級、digest pin、publish boundary 與外部來源啟用批准包模板;"
|
||
"此端點只回傳 read-only template,不安裝或升級套件、不寫 manifest 或 lockfile、"
|
||
"不修改 Dockerfile、不執行 docker build、不 pull image、不推 registry、不 publish package、"
|
||
"不安裝 SDK、不呼叫付費 API、不建立 shadow/canary、不改生產路由。"
|
||
),
|
||
)
|
||
async def get_dependency_upgrade_approval_package_template() -> dict[str, Any]:
|
||
"""Return the latest read-only dependency upgrade approval package template."""
|
||
try:
|
||
return await asyncio.to_thread(load_latest_dependency_upgrade_approval_package_template)
|
||
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("dependency_upgrade_approval_package_template_invalid", error=str(exc))
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail="依賴升級批准包模板快照無效",
|
||
) from exc
|
||
|
||
|
||
# =============================================================================
|
||
# Integration with Incident Flow
|
||
# =============================================================================
|
||
|
||
async def trigger_agent_analysis_for_incident(
|
||
incident_id: str,
|
||
background_tasks: BackgroundTasks,
|
||
) -> str | None:
|
||
"""
|
||
整合點: 當 Incident 需要複雜決策時自動觸發 Agent Teams
|
||
|
||
這個函數可被 incident_engine 或 webhooks 調用
|
||
|
||
Returns:
|
||
task_id if triggered, None if skipped
|
||
"""
|
||
service = get_agent_service()
|
||
|
||
task_id, incident = await service.trigger_for_incident(incident_id)
|
||
|
||
if task_id is None:
|
||
return None
|
||
|
||
if incident is None:
|
||
return None
|
||
|
||
# 加入背景任務
|
||
background_tasks.add_task(
|
||
service.run_analysis,
|
||
task_id,
|
||
incident,
|
||
)
|
||
|
||
return task_id
|