feat(delivery): add closure workbench summary api

This commit is contained in:
ogt
2026-06-26 18:46:52 +08:00
parent 69f5eb6f52
commit 0c43b74f4f
5 changed files with 641 additions and 7 deletions

View File

@@ -322,6 +322,9 @@ from src.services.dependency_supply_chain_drift_monitor import (
from src.services.dependency_upgrade_approval_package_template import (
load_latest_dependency_upgrade_approval_package_template,
)
from src.services.delivery_closure_workbench import (
load_delivery_closure_workbench,
)
from src.services.docker_build_surface_inventory import (
load_latest_docker_build_surface_inventory,
)
@@ -896,6 +899,36 @@ async def get_awoooi_status_cleanup_dashboard() -> dict[str, Any]:
) 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(
"/agent-12-agent-war-room",
response_model=dict[str, Any],

View File

@@ -0,0 +1,321 @@
"""Delivery closure workbench summary.
Builds the product-facing delivery closure view from existing committed,
read-only snapshots. The summary is intentionally compact so the UI does not
need to fan out across five separate endpoints or duplicate blocker math.
"""
from __future__ import annotations
from typing import Any
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.gitea_workflow_runner_health import (
load_latest_gitea_workflow_runner_health,
)
from src.services.runtime_surface_inventory import (
load_latest_runtime_surface_inventory,
)
_SCHEMA_VERSION = "delivery_closure_workbench_v1"
def load_delivery_closure_workbench() -> dict[str, Any]:
"""Load existing delivery snapshots and return a compact workbench model."""
status_cleanup = load_latest_awoooi_status_cleanup_dashboard()
github = _load_github_private_backup_evidence_gate()
gitea = load_latest_gitea_workflow_runner_health()
runtime = load_latest_runtime_surface_inventory()
backup = load_latest_backup_dr_readiness_matrix()
return build_delivery_closure_workbench(
status_cleanup=status_cleanup,
github=github,
gitea=gitea,
runtime=runtime,
backup=backup,
)
def build_delivery_closure_workbench(
*,
status_cleanup: dict[str, Any],
github: dict[str, Any],
gitea: dict[str, Any],
runtime: dict[str, Any],
backup: dict[str, Any],
) -> dict[str, Any]:
"""Build the delivery workbench response from already validated snapshots."""
status_summary = _dict(status_cleanup.get("summary"))
github_summary = _dict(github.get("summary"))
gitea_status = _dict(gitea.get("program_status"))
gitea_rollups = _dict(gitea.get("rollups"))
runtime_status = _dict(runtime.get("program_status"))
runtime_rollups = _dict(runtime.get("rollups"))
backup_status = _dict(backup.get("program_status"))
backup_rollups = _dict(backup.get("rollups"))
github_required = _int(github_summary.get("approval_required_target_count"))
github_verified = _int(github_summary.get("private_backup_verified_count"))
runtime_action_required = set(_strings(runtime_rollups.get("action_required_surface_ids")))
runtime_secret_surfaces = set(_strings(runtime_rollups.get("secret_surface_ids")))
lanes = [
{
"id": "release",
"source_id": "status_cleanup",
"completion_percent": _percent(status_summary.get("overall_completion_percent")),
"status": str(status_summary.get("dashboard_status") or "unknown"),
"blocker_count": _int(status_summary.get("blocked_gate_count")),
"metric": {
"kind": "blocked_gate",
"blocked": _int(status_summary.get("blocked_gate_count")),
"total": _int(status_summary.get("gate_count")),
},
"href": "/governance?tab=automation-inventory",
"next_action": _first_string(status_cleanup.get("next_actions")),
},
{
"id": "github",
"source_id": "github_private_backup",
"completion_percent": _percent(
(github_verified / github_required) * 100 if github_required else 0
),
"status": str(github.get("status") or "unknown"),
"blocker_count": _int(github_summary.get("blocked_target_count")),
"metric": {
"kind": "private_backup_verified",
"verified": github_verified,
"total": github_required,
},
"href": "/governance?tab=automation-inventory",
"next_action": str(github.get("next_action") or _first_target_action(github.get("targets"))),
},
{
"id": "gitea",
"source_id": "gitea_ci_cd",
"completion_percent": _percent(gitea_status.get("overall_completion_percent")),
"status": str(gitea_status.get("current_task_id") or "unknown"),
"blocker_count": len(_strings(gitea_rollups.get("runner_contracts_requiring_action"))),
"metric": {
"kind": "workflow_count",
"count": _int(gitea_rollups.get("total_workflows")),
},
"href": "/deployments",
"next_action": _first_contract_action(gitea.get("runner_contracts")),
},
{
"id": "runtime",
"source_id": "runtime_surface",
"completion_percent": _percent(runtime_status.get("overall_completion_percent")),
"status": str(runtime_status.get("current_task_id") or "unknown"),
"blocker_count": len(runtime_action_required | runtime_secret_surfaces),
"metric": {
"kind": "surface_count",
"total": _int(runtime_rollups.get("total_surfaces")),
},
"href": "/governance?tab=automation-inventory",
"next_action": _first_surface_action(runtime.get("runtime_surfaces")),
},
{
"id": "backup",
"source_id": "backup_dr",
"completion_percent": _percent(backup_status.get("overall_completion_percent")),
"status": str(backup_status.get("current_task_id") or "unknown"),
"blocker_count": len(_strings(backup_rollups.get("blocked_row_ids"))),
"metric": {
"kind": "readiness_row_count",
"rows": _int(backup_rollups.get("total_rows")),
},
"href": "/operations",
"next_action": _first_backup_action(backup.get("readiness_rows")),
},
]
for lane in lanes:
lane["tone"] = _tone(_int(lane["blocker_count"]), _int(lane["completion_percent"]))
source_statuses = [
_source_status("status_cleanup", status_cleanup),
_source_status("github_private_backup", github),
_source_status("gitea_ci_cd", gitea),
_source_status("runtime_surface", runtime),
_source_status("backup_dr", backup),
]
generated_candidates = [source["generated_at"] for source in source_statuses if source["generated_at"]]
loaded_source_count = sum(1 for source in source_statuses if source["loaded"])
high_risk_blocker_count = sum(_int(lane["blocker_count"]) for lane in lanes)
average_completion = _percent(
sum(_int(lane["completion_percent"]) for lane in lanes) / max(len(lanes), 1)
)
next_focus = [
{
"lane_id": lane["id"],
"blocker_count": lane["blocker_count"],
"completion_percent": lane["completion_percent"],
"next_action": lane["next_action"],
}
for lane in lanes
if _int(lane["blocker_count"]) > 0 or _int(lane["completion_percent"]) < 80
][:5]
return {
"schema_version": _SCHEMA_VERSION,
"generated_at": max(generated_candidates) if generated_candidates else "",
"status": "blocked_delivery_actions_required" if high_risk_blocker_count else "ready",
"summary": {
"source_count": len(source_statuses),
"loaded_source_count": loaded_source_count,
"average_completion_percent": average_completion,
"high_risk_blocker_count": high_risk_blocker_count,
"runtime_execution_authorized": False,
"remote_write_authorized": False,
"repo_creation_authorized": False,
"refs_sync_authorized": False,
"workflow_trigger_authorized": False,
"secret_values_collected": False,
},
"source_statuses": source_statuses,
"lanes": lanes,
"next_focus": next_focus,
"operation_boundaries": {
"read_only_api_allowed": True,
"runtime_write_allowed": False,
"remote_write_allowed": False,
"repo_creation_allowed": False,
"visibility_change_allowed": False,
"refs_sync_allowed": False,
"workflow_trigger_allowed": False,
"secret_value_collection_allowed": False,
"backup_restore_execution_allowed": False,
"active_scan_allowed": False,
},
}
def _source_status(source_id: str, payload: dict[str, Any]) -> dict[str, Any]:
source_missing = payload.get("source_missing") is True
return {
"id": source_id,
"loaded": not source_missing,
"schema_version": str(payload.get("schema_version") or ""),
"generated_at": str(payload.get("generated_at") or ""),
"missing_reason": str(payload.get("missing_reason") or "") if source_missing else "",
}
def _load_github_private_backup_evidence_gate() -> dict[str, Any]:
try:
from src.services.github_target_private_backup_evidence_gate import (
load_latest_github_target_private_backup_evidence_gate,
)
return load_latest_github_target_private_backup_evidence_gate()
except ModuleNotFoundError as exc:
if exc.name != "src.services.github_target_private_backup_evidence_gate":
raise
return _missing_github_private_backup_source("service_module_missing_on_release_base")
except FileNotFoundError:
return _missing_github_private_backup_source("snapshot_missing_on_release_base")
def _missing_github_private_backup_source(reason: str) -> dict[str, Any]:
return {
"schema_version": "missing_github_target_private_backup_evidence_gate_v1",
"generated_at": "",
"source_missing": True,
"missing_reason": reason,
"status": "blocked_github_private_backup_source_missing",
"next_action": "補上 GitHub 私有備援 evidence gate source 後重新回讀,不可把缺席來源標成已驗證。",
"summary": {
"approval_required_target_count": 0,
"private_backup_verified_count": 0,
"blocked_target_count": 1,
},
"targets": [],
}
def _tone(blocker_count: int, percent: int) -> str:
if blocker_count > 0:
return "danger"
if percent < 80:
return "warn"
return "ok"
def _dict(value: Any) -> dict[str, Any]:
return value if isinstance(value, dict) else {}
def _int(value: Any) -> int:
if isinstance(value, bool):
return int(value)
if isinstance(value, (int, float)):
return int(value)
return 0
def _percent(value: Any) -> int:
return max(0, min(100, round(float(value or 0))))
def _strings(value: Any) -> list[str]:
if not isinstance(value, list):
return []
return [str(item) for item in value if item is not None]
def _first_string(value: Any) -> str:
if isinstance(value, list) and value:
return str(value[0])
return ""
def _first_target_action(value: Any) -> str:
if not isinstance(value, list):
return ""
for row in value:
if isinstance(row, dict) and row.get("approval_required") is True:
return str(row.get("next_action") or "")
return _first_row_action(value)
def _first_contract_action(value: Any) -> str:
if not isinstance(value, list):
return ""
for row in value:
if isinstance(row, dict) and row.get("status") == "action_required":
return str(row.get("next_action") or "")
return _first_row_action(value)
def _first_surface_action(value: Any) -> str:
if not isinstance(value, list):
return ""
for row in value:
if isinstance(row, dict) and row.get("status") != "manifest_mapped":
return str(row.get("next_action") or "")
return _first_row_action(value)
def _first_backup_action(value: Any) -> str:
if not isinstance(value, list):
return ""
for row in value:
if isinstance(row, dict) and row.get("overall_readiness") in {"blocked", "action_required"}:
return str(row.get("next_action") or "")
return _first_row_action(value)
def _first_row_action(value: Any) -> str:
if not isinstance(value, list):
return ""
for row in value:
if isinstance(row, dict) and row.get("next_action"):
return str(row["next_action"])
return ""

View File

@@ -0,0 +1,57 @@
from __future__ import annotations
from fastapi import FastAPI
from fastapi.testclient import TestClient
from src.api.v1.agents import router
def test_delivery_closure_workbench_endpoint_returns_product_summary():
app = FastAPI()
app.include_router(router, prefix="/api/v1")
client = TestClient(app)
response = client.get("/api/v1/agents/delivery-closure-workbench")
assert response.status_code == 200
data = response.json()
assert data["schema_version"] == "delivery_closure_workbench_v1"
assert data["summary"]["source_count"] == 5
assert 4 <= data["summary"]["loaded_source_count"] <= 5
assert data["summary"]["runtime_execution_authorized"] is False
assert data["summary"]["remote_write_authorized"] is False
assert data["summary"]["repo_creation_authorized"] is False
assert data["summary"]["refs_sync_authorized"] is False
assert data["summary"]["workflow_trigger_authorized"] is False
assert data["summary"]["secret_values_collected"] is False
assert data["summary"]["average_completion_percent"] >= 0
assert data["summary"]["high_risk_blocker_count"] > 0
lanes = {lane["id"]: lane for lane in data["lanes"]}
sources = {source["id"]: source for source in data["source_statuses"]}
assert sorted(lanes) == ["backup", "gitea", "github", "release", "runtime"]
assert lanes["release"]["metric"]["kind"] == "blocked_gate"
assert lanes["github"]["metric"]["kind"] == "private_backup_verified"
assert lanes["gitea"]["metric"]["kind"] == "workflow_count"
assert lanes["runtime"]["metric"]["kind"] == "surface_count"
assert lanes["backup"]["metric"]["kind"] == "readiness_row_count"
if sources["github_private_backup"]["loaded"] is False:
assert lanes["github"]["blocker_count"] == 1
assert lanes["github"]["status"] == "blocked_github_private_backup_source_missing"
assert sources["github_private_backup"]["missing_reason"]
assert all(0 <= lane["completion_percent"] <= 100 for lane in lanes.values())
assert all(lane["tone"] in {"ok", "warn", "danger"} for lane in lanes.values())
boundaries = data["operation_boundaries"]
assert boundaries["read_only_api_allowed"] is True
assert boundaries["runtime_write_allowed"] is False
assert boundaries["remote_write_allowed"] is False
assert boundaries["repo_creation_allowed"] is False
assert boundaries["visibility_change_allowed"] is False
assert boundaries["refs_sync_allowed"] is False
assert boundaries["workflow_trigger_allowed"] is False
assert boundaries["secret_value_collection_allowed"] is False
assert boundaries["backup_restore_execution_allowed"] is False
assert boundaries["active_scan_allowed"] is False
assert "192.168.0." not in response.text

View File

@@ -21,6 +21,7 @@ import {
apiClient,
type AwoooIStatusCleanupDashboardSnapshot,
type BackupDrReadinessMatrixSnapshot,
type DeliveryClosureWorkbenchSnapshot,
type GiteaWorkflowRunnerHealthSnapshot,
type GithubTargetPrivateBackupEvidenceGateSnapshot,
type RuntimeSurfaceInventorySnapshot,
@@ -44,11 +45,14 @@ interface DeliveryLane {
status: string
metric: string
blockerCount: number
nextAction: string
href: string
tone: DeliveryTone
Icon: typeof Rocket
}
const SOURCE_COUNT = 5
const EMPTY_DATA: DeliveryData = {
statusCleanup: null,
github: null,
@@ -79,6 +83,18 @@ const resolveTone = (blockerCount: number, percent: number): DeliveryTone => {
return 'ok'
}
const firstActionRequiredTarget = (data: GithubTargetPrivateBackupEvidenceGateSnapshot | null) =>
data?.targets.find(target => target.approval_required)?.next_action ?? ''
const firstActionRequiredRunner = (data: GiteaWorkflowRunnerHealthSnapshot | null) =>
data?.runner_contracts.find(contract => contract.status === 'action_required')?.next_action ?? ''
const firstRuntimeAction = (data: RuntimeSurfaceInventorySnapshot | null) =>
data?.runtime_surfaces.find(surface => surface.status !== 'manifest_mapped')?.next_action ?? ''
const firstBackupAction = (data: BackupDrReadinessMatrixSnapshot | null) =>
data?.readiness_rows.find(row => row.overall_readiness === 'blocked' || row.overall_readiness === 'action_required')?.next_action ?? ''
function StatusPill({ tone, label }: { tone: DeliveryTone; label: string }) {
return (
<span
@@ -212,6 +228,7 @@ function LaneCard({ lane, locale }: { lane: DeliveryLane; locale: string }) {
export default function DeliveryPage({ params }: { params: { locale: string } }) {
const t = useTranslations('delivery')
const [workbench, setWorkbench] = useState<DeliveryClosureWorkbenchSnapshot | null>(null)
const [data, setData] = useState<DeliveryData>(EMPTY_DATA)
const [errors, setErrors] = useState<string[]>([])
const [loading, setLoading] = useState(true)
@@ -220,6 +237,19 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
let cancelled = false
const load = async () => {
setLoading(true)
setErrors([])
try {
const summary = await apiClient.getDeliveryClosureWorkbench()
if (cancelled) return
setWorkbench(summary)
setData(EMPTY_DATA)
setErrors([])
setLoading(false)
return
} catch {
// Summary endpoint may not exist until the API release lands; keep the page useful with legacy reads.
}
const results = await Promise.allSettled([
apiClient.getAwoooIStatusCleanupDashboard(),
apiClient.getGithubTargetPrivateBackupEvidenceGate(),
@@ -241,6 +271,7 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
.filter(({ result }) => result.status === 'rejected')
.map(({ index }) => t(`sources.${index}.error`))
setWorkbench(null)
setData(nextData)
setErrors(nextErrors)
setLoading(false)
@@ -253,6 +284,42 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
}, [t])
const lanes = useMemo<DeliveryLane[]>(() => {
if (workbench) {
return workbench.lanes.map(lane => {
const iconMap = {
release: Rocket,
github: GitBranch,
gitea: PackageCheck,
runtime: Server,
backup: HardDrive,
}
const metric =
lane.metric.kind === 'blocked_gate'
? t('lanes.release.metric', { blocked: lane.metric.blocked })
: lane.metric.kind === 'private_backup_verified'
? t('lanes.github.metric', { verified: lane.metric.verified, total: lane.metric.total })
: lane.metric.kind === 'workflow_count'
? t('lanes.gitea.metric', { count: lane.metric.count })
: lane.metric.kind === 'surface_count'
? t('lanes.runtime.metric', { total: lane.metric.total })
: t('lanes.backup.metric', { rows: lane.metric.rows })
return {
id: lane.id,
title: t(`lanes.${lane.id}.title`),
description: t(`lanes.${lane.id}.description`),
percent: clampPercent(lane.completion_percent),
status: lane.status || t('states.noData'),
metric,
blockerCount: lane.blocker_count,
nextAction: lane.next_action,
href: lane.href,
tone: lane.tone,
Icon: iconMap[lane.id],
}
})
}
const statusBlocked = Number(data.statusCleanup?.summary.blocked_gate_count ?? 0)
const statusPercent = clampPercent(data.statusCleanup?.summary.overall_completion_percent)
const githubRequired = data.github?.summary.approval_required_target_count ?? 0
@@ -275,6 +342,7 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
status: data.statusCleanup?.summary.dashboard_status ?? t('states.noData'),
metric: t('lanes.release.metric', { blocked: statusBlocked }),
blockerCount: statusBlocked,
nextAction: data.statusCleanup?.next_actions[0] ?? '',
href: '/governance?tab=automation-inventory',
tone: resolveTone(statusBlocked, statusPercent),
Icon: Rocket,
@@ -287,6 +355,7 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
status: data.github?.status ?? t('states.noData'),
metric: t('lanes.github.metric', { verified: githubVerified, total: githubRequired }),
blockerCount: githubBlocked,
nextAction: firstActionRequiredTarget(data.github),
href: '/governance?tab=automation-inventory',
tone: resolveTone(githubBlocked, githubPercent),
Icon: GitBranch,
@@ -299,6 +368,7 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
status: data.gitea?.program_status.current_task_id ?? t('states.noData'),
metric: t('lanes.gitea.metric', { count: data.gitea?.rollups.total_workflows ?? 0 }),
blockerCount: giteaBlocked,
nextAction: firstActionRequiredRunner(data.gitea),
href: '/deployments',
tone: resolveTone(giteaBlocked, giteaPercent),
Icon: PackageCheck,
@@ -311,6 +381,7 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
status: data.runtime?.program_status.current_task_id ?? t('states.noData'),
metric: t('lanes.runtime.metric', { total: data.runtime?.rollups.total_surfaces ?? 0 }),
blockerCount: runtimeBlocked,
nextAction: firstRuntimeAction(data.runtime),
href: '/governance?tab=automation-inventory',
tone: resolveTone(runtimeBlocked, runtimePercent),
Icon: Server,
@@ -323,17 +394,19 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
status: data.backup?.program_status.current_task_id ?? t('states.noData'),
metric: t('lanes.backup.metric', { rows: data.backup?.rollups.total_rows ?? 0 }),
blockerCount: backupBlocked,
nextAction: firstBackupAction(data.backup),
href: '/operations',
tone: resolveTone(backupBlocked, backupPercent),
Icon: HardDrive,
},
]
}, [data, t])
}, [data, t, workbench])
const loadedCount = Object.values(data).filter(Boolean).length
const highRiskBlockers = lanes.reduce((sum, lane) => sum + lane.blockerCount, 0)
const averageCompletion = clampPercent(lanes.reduce((sum, lane) => sum + lane.percent, 0) / Math.max(lanes.length, 1))
const pageTone: DeliveryTone = highRiskBlockers > 0 ? 'danger' : loadedCount === lanes.length ? 'ok' : 'warn'
const sourceTotal = workbench?.summary.source_count ?? SOURCE_COUNT
const loadedCount = workbench?.summary.loaded_source_count ?? Object.values(data).filter(Boolean).length
const highRiskBlockers = workbench?.summary.high_risk_blocker_count ?? lanes.reduce((sum, lane) => sum + lane.blockerCount, 0)
const averageCompletion = workbench?.summary.average_completion_percent ?? clampPercent(lanes.reduce((sum, lane) => sum + lane.percent, 0) / Math.max(lanes.length, 1))
const pageTone: DeliveryTone = highRiskBlockers > 0 ? 'danger' : loadedCount === sourceTotal ? 'ok' : 'warn'
return (
<AppLayout locale={params.locale}>
@@ -378,7 +451,7 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
</section>
<section className="delivery-metrics">
<MetricTile label={t('metrics.loaded')} value={`${loadedCount}/5`} detail={t('metrics.loadedDetail')} tone={loadedCount === 5 ? 'ok' : 'warn'} />
<MetricTile label={t('metrics.loaded')} value={`${loadedCount}/${sourceTotal}`} detail={t('metrics.loadedDetail')} tone={loadedCount === sourceTotal ? 'ok' : 'warn'} />
<MetricTile label={t('metrics.completion')} value={`${averageCompletion}%`} detail={t('metrics.completionDetail')} tone={averageCompletion >= 80 ? 'ok' : 'warn'} />
<MetricTile label={t('metrics.blockers')} value={highRiskBlockers} detail={t('metrics.blockersDetail')} tone={highRiskBlockers > 0 ? 'danger' : 'ok'} />
<MetricTile label={t('metrics.execution')} value="0" detail={t('metrics.executionDetail')} tone="ok" />
@@ -423,7 +496,7 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
<lane.Icon size={17} aria-hidden="true" />
<div>
<strong>{lane.title}</strong>
<span>{lane.metric}</span>
<span>{lane.nextAction || lane.metric}</span>
</div>
<StatusPill tone={lane.tone} label={`${lane.percent}%`} />
</div>

View File

@@ -473,6 +473,16 @@ export const apiClient = {
return handleResponse<AwoooIStatusCleanupDashboardSnapshot>(res)
},
async getDeliveryClosureWorkbench() {
const res = await fetch(`${API_BASE_URL}/agents/delivery-closure-workbench`)
return handleResponse<DeliveryClosureWorkbenchSnapshot>(res)
},
async getGithubTargetPrivateBackupEvidenceGate() {
const res = await fetch(`${API_BASE_URL}/agents/github-target-private-backup-evidence-gate`)
return handleResponse<GithubTargetPrivateBackupEvidenceGateSnapshot>(res)
},
async getAiAgent12AgentWarRoom() {
const res = await fetch(`${API_BASE_URL}/agents/agent-12-agent-war-room`)
return handleResponse<AiAgent12AgentWarRoomSnapshot>(res)
@@ -1752,6 +1762,64 @@ export interface AwoooIStatusCleanupDashboardSnapshot {
ui_implementation_authorized: false
}
export interface DeliveryClosureWorkbenchSnapshot {
schema_version: 'delivery_closure_workbench_v1'
generated_at: string
status: 'blocked_delivery_actions_required' | 'ready' | string
summary: {
source_count: number
loaded_source_count: number
average_completion_percent: number
high_risk_blocker_count: number
runtime_execution_authorized: false
remote_write_authorized: false
repo_creation_authorized: false
refs_sync_authorized: false
workflow_trigger_authorized: false
secret_values_collected: false
}
source_statuses: Array<{
id: string
loaded: boolean
schema_version: string
generated_at: string
}>
lanes: Array<{
id: 'release' | 'github' | 'gitea' | 'runtime' | 'backup'
source_id: string
completion_percent: number
status: string
blocker_count: number
metric:
| { kind: 'blocked_gate'; blocked: number; total: number }
| { kind: 'private_backup_verified'; verified: number; total: number }
| { kind: 'workflow_count'; count: number }
| { kind: 'surface_count'; total: number }
| { kind: 'readiness_row_count'; rows: number }
href: string
next_action: string
tone: 'ok' | 'warn' | 'danger'
}>
next_focus: Array<{
lane_id: string
blocker_count: number
completion_percent: number
next_action: string
}>
operation_boundaries: {
read_only_api_allowed: true
runtime_write_allowed: false
remote_write_allowed: false
repo_creation_allowed: false
visibility_change_allowed: false
refs_sync_allowed: false
workflow_trigger_allowed: false
secret_value_collection_allowed: false
backup_restore_execution_allowed: false
active_scan_allowed: false
}
}
export interface AiAgent12AgentWarRoomSnapshot {
schema_version: 'ai_agent_12_agent_war_room_v1'
generated_at: string
@@ -13046,6 +13114,88 @@ export interface GiteaWorkflowRunnerHealthSnapshot {
approval_boundaries: Record<string, false>
}
export interface GithubTargetPrivateBackupEvidenceGateSnapshot {
schema_version: 'github_target_private_backup_evidence_gate_v1'
generated_at: string
status:
| 'blocked_public_visibility_and_safe_credential_evidence_required'
| 'blocked_private_visibility_and_safe_credential_evidence_required'
mode: 'read_only_private_backup_evidence_gate'
source_reviews: Record<string, string>
summary: {
target_decision_count: number
approval_required_target_count: number
approval_package_item_count: number
public_probe_visible_target_count: number
not_found_or_private_target_count: number
private_backup_verified_count: number
private_visibility_evidence_missing_count: number
safe_credential_required_count: number
safe_credential_accepted_evidence_count: number
owner_response_received_count: number
owner_response_accepted_count: number
execution_ready_count: number
blocked_target_count: number
external_scope_target_count: number
forbidden_action_count: number
repo_creation_authorized: false
visibility_change_authorized: false
refs_sync_authorized: false
github_primary_switch_authorized: false
workflow_modification_authorized: false
workflow_trigger_authorized: false
secret_value_collection_allowed: false
private_clone_url_collection_allowed: false
not_found_or_private_as_absent_allowed: false
public_repo_allowed: false
}
targets: Array<{
github_repo: string
source_key: string
approval_required: boolean
probe_status: string
target_state: string
risk: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' | string
visibility_evidence_status:
| 'external_scope_not_backup_target'
| 'blocked_public_probe_visible_private_evidence_required'
| 'blocked_private_or_absent_not_verified'
| 'blocked_probe_status_unknown'
private_backup_verified: false
private_visibility_owner_evidence_ref: string | null
safe_credential_evidence_status: string
safe_credential_evidence_ref: string | null
owner_response_accepted: false
refs_sync_ready: false
execution_ready: false
blockers: string[]
evidence_refs: string[]
next_action: string
forbidden_actions: string[]
repo_creation_authorized: false
visibility_change_authorized: false
refs_sync_authorized: false
github_primary_switch_authorized: false
secret_values_collected: false
}>
acceptance_requirements: string[]
rejection_rules: string[]
operation_boundaries: {
read_only_api_allowed: true
github_api_write_allowed: false
gitea_api_write_allowed: false
repo_creation_allowed: false
visibility_change_allowed: false
refs_sync_allowed: false
workflow_modification_allowed: false
workflow_trigger_allowed: false
github_primary_switch_allowed: false
secret_value_collection_allowed: false
private_clone_url_collection_allowed: false
}
authorization_flags: Record<string, false>
}
export interface ObservabilityContractMatrixSnapshot {
schema_version: 'observability_contract_matrix_v1'
generated_at: string