1197 lines
35 KiB
TypeScript
1197 lines
35 KiB
TypeScript
/**
|
||
* AWOOOI API Client
|
||
* ADR-005: 所有請求經過 BFF
|
||
*
|
||
* 專案鐵律: 禁止任何 Fallback IP,環境變數缺失即噴錯
|
||
*/
|
||
|
||
import { CURRENT_USER } from '@/lib/constants/user'
|
||
|
||
// 絕對純化: 環境變數缺失時直接拋出致命錯誤,嚴禁任何 Fallback
|
||
const getApiBaseUrl = (): string => {
|
||
const url = process.env.NEXT_PUBLIC_API_URL
|
||
if (!url) {
|
||
const fatalMsg = '[AWOOOI FATAL] Missing NEXT_PUBLIC_API_URL configuration.'
|
||
console.error(fatalMsg)
|
||
if (typeof window !== 'undefined') {
|
||
console.error('%c' + fatalMsg, 'color: #ef4444; font-weight: bold; font-size: 16px;')
|
||
}
|
||
throw new Error(fatalMsg)
|
||
}
|
||
return url.endsWith('/api/v1') ? url : `${url}/api/v1`
|
||
}
|
||
|
||
const API_BASE_URL = getApiBaseUrl()
|
||
|
||
export class ApiError extends Error {
|
||
constructor(
|
||
public status: number,
|
||
public code: string,
|
||
message: string
|
||
) {
|
||
super(message)
|
||
this.name = 'ApiError'
|
||
}
|
||
}
|
||
|
||
async function handleResponse<T>(response: Response): Promise<T> {
|
||
if (!response.ok) {
|
||
const error = await response.json().catch(() => ({}))
|
||
throw new ApiError(
|
||
response.status,
|
||
error.code || 'UNKNOWN_ERROR',
|
||
error.message || response.statusText
|
||
)
|
||
}
|
||
return response.json()
|
||
}
|
||
|
||
export const apiClient = {
|
||
// Health
|
||
async getHealth() {
|
||
const res = await fetch(`${API_BASE_URL}/health`)
|
||
return handleResponse<{
|
||
status: 'healthy' | 'degraded' | 'unhealthy'
|
||
version: string
|
||
timestamp: string
|
||
components: Record<string, {
|
||
status: 'up' | 'down' | 'degraded'
|
||
latency_ms?: number | null
|
||
error?: string | null
|
||
}>
|
||
ollama_route_order?: string[]
|
||
}>(res)
|
||
},
|
||
|
||
// Agent
|
||
async getAgentStatus() {
|
||
const res = await fetch(`${API_BASE_URL}/agent/status`)
|
||
return handleResponse<{
|
||
status: 'idle' | 'thinking' | 'executing' | 'waiting_approval'
|
||
active_conversations: number
|
||
current_task: string | null
|
||
last_activity: string | null
|
||
}>(res)
|
||
},
|
||
|
||
async chat(message: string, conversationId?: string) {
|
||
const res = await fetch(`${API_BASE_URL}/agent/chat`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ message, conversation_id: conversationId }),
|
||
})
|
||
return handleResponse<{
|
||
message: string
|
||
conversation_id: string
|
||
requires_approval: boolean
|
||
approval_id?: string
|
||
}>(res)
|
||
},
|
||
|
||
// Plugins
|
||
async listPlugins(category?: string) {
|
||
const params = category ? `?category=${category}` : ''
|
||
const res = await fetch(`${API_BASE_URL}/plugins${params}`)
|
||
return handleResponse<Array<{
|
||
id: string
|
||
name: string
|
||
version: string
|
||
category: string
|
||
enabled: boolean
|
||
description?: string
|
||
}>>(res)
|
||
},
|
||
|
||
// Approvals
|
||
async listApprovals(status?: string) {
|
||
const params = status ? `?status=${status}` : ''
|
||
const res = await fetch(`${API_BASE_URL}/approvals${params}`)
|
||
return handleResponse<{
|
||
items: Array<{
|
||
id: string
|
||
type: string
|
||
status: string
|
||
action: {
|
||
plugin_id: string
|
||
operation: string
|
||
risk_level: string
|
||
}
|
||
requested_at: string
|
||
}>
|
||
}>(res)
|
||
},
|
||
|
||
async signApproval(approvalId: string, signer: string = CURRENT_USER.id, comment?: string, csrfToken?: string | null) {
|
||
// Phase 22 P0: 加入 CSRF token + credentials (2026-03-31 Claude Code)
|
||
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
|
||
if (csrfToken) headers['X-CSRF-Token'] = csrfToken
|
||
const res = await fetch(`${API_BASE_URL}/approvals/${approvalId}/sign`, {
|
||
method: 'POST',
|
||
headers,
|
||
credentials: 'include',
|
||
body: JSON.stringify({
|
||
signer_id: signer,
|
||
signer_name: signer,
|
||
comment: comment,
|
||
}),
|
||
})
|
||
// 🔧 Fix: 回傳型別與後端實際結構對齊
|
||
return handleResponse<{
|
||
success: boolean
|
||
message: string
|
||
approval: ApprovalResponse
|
||
execution_triggered: boolean
|
||
// 向下相容舊欄位 (deprecated)
|
||
approval_id?: string
|
||
status?: string
|
||
current_signatures?: number
|
||
required_signatures?: number
|
||
}>(res)
|
||
},
|
||
|
||
async rejectApproval(approvalId: string, reason?: string, csrfToken?: string | null) {
|
||
// Phase 22 P0: 加入 CSRF token + credentials (2026-03-31 Claude Code)
|
||
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
|
||
if (csrfToken) headers['X-CSRF-Token'] = csrfToken
|
||
const res = await fetch(`${API_BASE_URL}/approvals/${approvalId}/reject`, {
|
||
method: 'POST',
|
||
headers,
|
||
credentials: 'include',
|
||
body: JSON.stringify({
|
||
rejector_id: CURRENT_USER.id,
|
||
rejector_name: CURRENT_USER.name,
|
||
reason: reason || 'Rejected via WarRoom',
|
||
}),
|
||
})
|
||
return handleResponse<{ id: string; status: string }>(res)
|
||
},
|
||
|
||
// =========================================================================
|
||
// Phase 7: Incidents API (真實血脈)
|
||
// =========================================================================
|
||
|
||
async listIncidents() {
|
||
const res = await fetch(`${API_BASE_URL}/incidents`)
|
||
return handleResponse<IncidentListResponse>(res)
|
||
},
|
||
|
||
async getIncident(incidentId: string) {
|
||
const res = await fetch(`${API_BASE_URL}/incidents/${incidentId}`)
|
||
return handleResponse<IncidentResponse>(res)
|
||
},
|
||
|
||
async getIncidentTimeline(incidentId: string) {
|
||
const res = await fetch(`${API_BASE_URL}/incidents/${incidentId}/timeline`)
|
||
return handleResponse<IncidentTimelineResponse>(res)
|
||
},
|
||
|
||
async generateProposal(incidentId: string) {
|
||
const res = await fetch(`${API_BASE_URL}/incidents/${incidentId}/proposal`, {
|
||
method: 'POST',
|
||
})
|
||
return handleResponse<ProposalGenerateResponse>(res)
|
||
},
|
||
|
||
// =========================================================================
|
||
// Phase 7: Pending Approvals API (真實血脈)
|
||
// =========================================================================
|
||
|
||
async getPendingApprovals() {
|
||
const res = await fetch(`${API_BASE_URL}/approvals/pending`)
|
||
return handleResponse<PendingApprovalsResponse>(res)
|
||
},
|
||
|
||
// =========================================================================
|
||
// Phase 10: Sentry Errors API (#40 BFF)
|
||
// =========================================================================
|
||
|
||
async getErrorStats() {
|
||
const res = await fetch(`${API_BASE_URL}/errors/stats`)
|
||
return handleResponse<ErrorStatsResponse>(res)
|
||
},
|
||
|
||
async listErrors(params?: { status?: string; level?: string; limit?: number }) {
|
||
const searchParams = new URLSearchParams()
|
||
if (params?.status) searchParams.set('status', params.status)
|
||
if (params?.level) searchParams.set('level', params.level)
|
||
if (params?.limit) searchParams.set('limit', params.limit.toString())
|
||
const query = searchParams.toString() ? `?${searchParams.toString()}` : ''
|
||
const res = await fetch(`${API_BASE_URL}/errors/issues${query}`)
|
||
return handleResponse<ErrorListResponse>(res)
|
||
},
|
||
|
||
async getErrorDetail(issueId: string) {
|
||
const res = await fetch(`${API_BASE_URL}/errors/issues/${issueId}`)
|
||
return handleResponse<ErrorDetailResponse>(res)
|
||
},
|
||
|
||
async getErrorTrends(period: '24h' | '7d' | '30d' = '24h') {
|
||
const res = await fetch(`${API_BASE_URL}/errors/trends?period=${period}`)
|
||
return handleResponse<ErrorTrendResponse>(res)
|
||
},
|
||
|
||
async analyzeError(issueId: string) {
|
||
const res = await fetch(`${API_BASE_URL}/errors/issues/${issueId}/analyze`, {
|
||
method: 'POST',
|
||
})
|
||
return handleResponse<ErrorAnalysisResponse>(res)
|
||
},
|
||
|
||
// =========================================================================
|
||
// Phase 19: UX Audit / Session Replay (#126)
|
||
// 2026-03-31 Claude Code - Frontend Replay UI Integration
|
||
// =========================================================================
|
||
|
||
async getUXAudit() {
|
||
const res = await fetch(`${API_BASE_URL}/errors/ux-audit`)
|
||
return handleResponse<UXAuditResponse>(res)
|
||
},
|
||
|
||
async getAgentMarketGovernanceSnapshot() {
|
||
const res = await fetch(`${API_BASE_URL}/agents/market-governance-snapshot`)
|
||
return handleResponse<AgentMarketGovernanceSnapshot>(res)
|
||
},
|
||
|
||
async getAiAgentAutomationInventorySnapshot() {
|
||
const res = await fetch(`${API_BASE_URL}/agents/automation-inventory-snapshot`)
|
||
return handleResponse<AiAgentAutomationInventorySnapshot>(res)
|
||
},
|
||
|
||
async getAiAgentAutomationBacklogSnapshot() {
|
||
const res = await fetch(`${API_BASE_URL}/agents/automation-backlog-snapshot`)
|
||
return handleResponse<AiAgentAutomationBacklogSnapshot>(res)
|
||
},
|
||
|
||
async getRuntimeSurfaceInventory() {
|
||
const res = await fetch(`${API_BASE_URL}/agents/runtime-surface-inventory`)
|
||
return handleResponse<RuntimeSurfaceInventorySnapshot>(res)
|
||
},
|
||
|
||
async getGiteaWorkflowRunnerHealth() {
|
||
const res = await fetch(`${API_BASE_URL}/agents/gitea-workflow-runner-health`)
|
||
return handleResponse<GiteaWorkflowRunnerHealthSnapshot>(res)
|
||
},
|
||
|
||
async getObservabilityContractMatrix() {
|
||
const res = await fetch(`${API_BASE_URL}/agents/observability-contract-matrix`)
|
||
return handleResponse<ObservabilityContractMatrixSnapshot>(res)
|
||
},
|
||
|
||
async getBackupDrTargetInventory() {
|
||
const res = await fetch(`${API_BASE_URL}/agents/backup-dr-target-inventory`)
|
||
return handleResponse<BackupDrTargetInventorySnapshot>(res)
|
||
},
|
||
|
||
async getBackupDrReadinessMatrix() {
|
||
const res = await fetch(`${API_BASE_URL}/agents/backup-dr-readiness-matrix`)
|
||
return handleResponse<BackupDrReadinessMatrixSnapshot>(res)
|
||
},
|
||
|
||
async getBackupNotificationPolicy() {
|
||
const res = await fetch(`${API_BASE_URL}/agents/backup-notification-policy`)
|
||
return handleResponse<BackupNotificationPolicySnapshot>(res)
|
||
},
|
||
|
||
async getOffsiteEscrowReadinessStatus() {
|
||
const res = await fetch(`${API_BASE_URL}/agents/offsite-escrow-readiness-status`)
|
||
return handleResponse<OffsiteEscrowReadinessStatusSnapshot>(res)
|
||
},
|
||
}
|
||
|
||
// =========================================================================
|
||
// Type Definitions (Phase 7)
|
||
// =========================================================================
|
||
|
||
/**
|
||
* Phase 6.5: 決策令牌資訊
|
||
* 確保 UI 永遠有決策可操作
|
||
*/
|
||
export interface DecisionInfo {
|
||
token: string
|
||
state: 'init' | 'analyzing' | 'ready' | 'executing' | 'completed' | 'error'
|
||
proposal_data: {
|
||
action: string
|
||
description: string
|
||
reasoning: string
|
||
risk_level: 'low' | 'medium' | 'critical'
|
||
kubectl_command: string
|
||
source: string
|
||
confidence: number
|
||
} | null
|
||
proposal_id: string | null
|
||
}
|
||
|
||
export interface IncidentResponse {
|
||
incident_id: string
|
||
status: 'investigating' | 'mitigating' | 'resolved' | 'closed'
|
||
severity: 'P0' | 'P1' | 'P2' | 'P3'
|
||
signal_count: number
|
||
affected_services: string[]
|
||
proposal_count: number
|
||
created_at: string
|
||
updated_at: string
|
||
/** Phase 6.5: 決策令牌 (確保 UI 永不鎖死) */
|
||
decision: DecisionInfo | null
|
||
}
|
||
|
||
export interface IncidentListResponse {
|
||
count: number
|
||
incidents: IncidentResponse[]
|
||
}
|
||
|
||
export interface IncidentTimelineEvent {
|
||
stage: string
|
||
status: string
|
||
title: string
|
||
description: string | null
|
||
actor: string | null
|
||
timestamp: string | null
|
||
source_table: string | null
|
||
data: Record<string, unknown>
|
||
}
|
||
|
||
export interface IncidentTimelineStage extends IncidentTimelineEvent {
|
||
label: string
|
||
events: IncidentTimelineEvent[]
|
||
}
|
||
|
||
export interface IncidentTimelineResponse {
|
||
incident_id: string
|
||
title: string
|
||
status: string
|
||
severity: string
|
||
started_at: string | null
|
||
updated_at: string | null
|
||
resolved_at: string | null
|
||
affected_services: string[]
|
||
approval_ids: string[]
|
||
timeline: IncidentTimelineStage[]
|
||
events: IncidentTimelineEvent[]
|
||
ascii_timeline: string
|
||
}
|
||
|
||
export interface BlastRadius {
|
||
affected_pods: number
|
||
estimated_downtime: string
|
||
related_services: string[]
|
||
data_impact: 'none' | 'read_only' | 'write' | 'destructive'
|
||
}
|
||
|
||
export interface DryRunCheck {
|
||
name: string
|
||
passed: boolean
|
||
message: string
|
||
}
|
||
|
||
export interface ApprovalResponse {
|
||
id: string
|
||
action: string
|
||
description: string
|
||
status: 'pending' | 'approved' | 'rejected' | 'expired'
|
||
risk_level: 'low' | 'medium' | 'high' | 'critical'
|
||
blast_radius: BlastRadius
|
||
dry_run_checks: DryRunCheck[]
|
||
required_signatures: number
|
||
current_signatures: number
|
||
signatures: Array<{ signer: string; signed_at: string }>
|
||
requested_by: string
|
||
created_at: string
|
||
expires_at: string | null
|
||
}
|
||
|
||
export interface PendingApprovalsResponse {
|
||
count: number
|
||
approvals: ApprovalResponse[]
|
||
}
|
||
|
||
export interface ProposalGenerateResponse {
|
||
success: boolean
|
||
message: string
|
||
incident_id: string
|
||
proposal: ApprovalResponse | null
|
||
incident_status: string | null
|
||
}
|
||
|
||
// =========================================================================
|
||
// Phase 10: Sentry Error Types (#40 BFF)
|
||
// =========================================================================
|
||
|
||
export interface SentryIssue {
|
||
id: string
|
||
short_id: string
|
||
title: string
|
||
culprit: string | null
|
||
level: 'error' | 'warning' | 'info' | 'fatal'
|
||
status: 'unresolved' | 'resolved' | 'ignored'
|
||
count: number
|
||
user_count: number
|
||
first_seen: string
|
||
last_seen: string
|
||
permalink: string | null
|
||
}
|
||
|
||
export interface ErrorStatsResponse {
|
||
total_issues: number
|
||
unresolved_issues: number
|
||
error_count_24h: number
|
||
critical_count: number
|
||
projects: string[]
|
||
}
|
||
|
||
export interface ErrorListResponse {
|
||
issues: SentryIssue[]
|
||
total: number
|
||
has_more: boolean
|
||
}
|
||
|
||
export interface ErrorDetailResponse {
|
||
issue: Record<string, unknown>
|
||
latest_event: Record<string, unknown> | null
|
||
sentry_url: string
|
||
}
|
||
|
||
export interface ErrorTrendPoint {
|
||
timestamp: string
|
||
count: number
|
||
}
|
||
|
||
export interface ErrorTrendResponse {
|
||
period: '24h' | '7d' | '30d'
|
||
data: ErrorTrendPoint[]
|
||
total_count: number
|
||
change_percent: number
|
||
}
|
||
|
||
export interface FixRecommendation {
|
||
summary: string
|
||
steps: string[]
|
||
code_suggestion: string | null
|
||
}
|
||
|
||
export interface PreventionMeasure {
|
||
type: string
|
||
description: string
|
||
}
|
||
|
||
export interface ErrorAnalysis {
|
||
root_cause: string
|
||
category: string
|
||
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
|
||
impact_assessment: string
|
||
fix_recommendation: FixRecommendation
|
||
prevention: PreventionMeasure[]
|
||
related_files: string[]
|
||
confidence: number
|
||
reasoning: string
|
||
}
|
||
|
||
export interface ErrorAnalysisResponse {
|
||
status: 'completed' | 'failed'
|
||
issue_id: string
|
||
provider: string
|
||
analysis?: ErrorAnalysis
|
||
analyzed_at?: string
|
||
sentry_url: string
|
||
message?: string
|
||
}
|
||
|
||
// =========================================================================
|
||
// Phase 19: UX Audit / Session Replay Types (#126)
|
||
// 2026-03-31 Claude Code - Frontend Replay UI Integration
|
||
// =========================================================================
|
||
|
||
export interface UXAuditDetail {
|
||
type: 'replay_with_errors' | 'ui_error'
|
||
replay_id?: string
|
||
issue_id?: string
|
||
url: string
|
||
error_count?: number
|
||
title?: string
|
||
count?: number
|
||
urls?: string[]
|
||
}
|
||
|
||
export interface UXAuditResponse {
|
||
replays_with_errors: number
|
||
rage_clicks: number
|
||
dead_clicks: number
|
||
ui_errors: number
|
||
health_score: 'good' | 'moderate' | 'poor'
|
||
details: UXAuditDetail[]
|
||
replay_dashboard_url: string
|
||
}
|
||
|
||
// =========================================================================
|
||
// Agent Market Governance Snapshot
|
||
// =========================================================================
|
||
|
||
export interface AgentMarketGovernanceSnapshot {
|
||
schema_version: 'agent_market_governance_snapshot_v1'
|
||
generated_at: string
|
||
current_decision: string
|
||
policy: Record<string, boolean>
|
||
evaluation_cadence: {
|
||
workflow: string
|
||
schedule: string
|
||
timezone: 'Asia/Taipei'
|
||
next_scheduled_run_at: string
|
||
trigger_modes: string[]
|
||
primary_source_policy: string
|
||
operator_review_gate: string
|
||
}
|
||
market_watch_health: {
|
||
status: 'healthy' | 'blocked'
|
||
freshness_sla_hours: 168
|
||
stale_grace_hours: 6
|
||
stale_after: string
|
||
source_failures_block_priority_upgrade: boolean
|
||
blocked_from_integration: number
|
||
operator_blockers: string[]
|
||
}
|
||
summary: {
|
||
candidate_count: number
|
||
source_count: number
|
||
source_failures: number
|
||
changed_candidates: number
|
||
integration_queue_count: number
|
||
blocked_from_integration: number
|
||
watch_only_candidates_reviewed: number
|
||
eligible_for_market_scorecard_prescreen: number
|
||
recommended_watch_additions_remaining: number
|
||
priority_upgrades_approved: number
|
||
market_scorecard_updates_approved: number
|
||
replay_candidates_approved: number
|
||
sdk_installations_approved: number
|
||
paid_api_calls_approved: number
|
||
production_changes_approved: number
|
||
shadow_or_canary_approved: number
|
||
replacement_decisions_approved: number
|
||
}
|
||
candidate_groups: {
|
||
production_baseline: string[]
|
||
replay_or_integration_blocked: string[]
|
||
watch_only_candidates: string[]
|
||
watch_only_scorecard_prescreen_ready: string[]
|
||
}
|
||
candidate_statuses: Array<{
|
||
candidate_id: string
|
||
display_name: string
|
||
role: string
|
||
evaluation_priority: string
|
||
gate_status:
|
||
| 'production_baseline'
|
||
| 'integration_blocked'
|
||
| 'integration_reviewed'
|
||
| 'watch_only_prescreen_ready'
|
||
| 'watch_only_blocked'
|
||
| 'watch_only_monitoring'
|
||
| 'registered_no_review'
|
||
current_gate: string
|
||
required_next_gate: string
|
||
integration_decision: string
|
||
score: number | null
|
||
evidence: {
|
||
latest_replay_summary: string | null
|
||
latest_smoke_gate: string | null
|
||
latest_smoke_matrix: string | null
|
||
latest_smoke_model: string | null
|
||
}
|
||
approvals: {
|
||
replay: false
|
||
sdk_install: false
|
||
paid_api: false
|
||
shadow_or_canary: false
|
||
production_routing: false
|
||
}
|
||
operator_blockers: string[]
|
||
}>
|
||
operator_decision_queue: Array<{
|
||
candidate_id: string
|
||
display_name: string
|
||
priority: number
|
||
queue_status:
|
||
| 'baseline_protected'
|
||
| 'blocked_needs_evidence'
|
||
| 'operator_review_required'
|
||
| 'operator_priority_review'
|
||
| 'watch_only_blocked'
|
||
| 'watch_only_monitoring'
|
||
| 'registered_no_review'
|
||
recommended_action: string
|
||
approval_boundary: {
|
||
replacement_adr_required: boolean
|
||
priority_upgrade_required: boolean
|
||
market_scorecard_update_required: boolean
|
||
replay_approval_required: boolean
|
||
sdk_install_approval_required: boolean
|
||
paid_api_approval_required: boolean
|
||
shadow_or_canary_approval_required: boolean
|
||
production_routing_approval_required: boolean
|
||
}
|
||
risk_notes: string[]
|
||
evidence_refs: string[]
|
||
}>
|
||
next_allowed_actions: string[]
|
||
forbidden_actions_without_new_approval: string[]
|
||
}
|
||
|
||
// =========================================================================
|
||
// AI Agent Automation Inventory Snapshot
|
||
// =========================================================================
|
||
|
||
export interface AiAgentAutomationInventorySnapshot {
|
||
schema_version: 'ai_agent_automation_inventory_snapshot_v1'
|
||
generated_at: string
|
||
program_status: {
|
||
overall_completion_percent: number
|
||
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
|
||
current_task_id: string
|
||
next_task_id: string
|
||
read_only_mode: true
|
||
}
|
||
status_taxonomy: {
|
||
task_statuses: string[]
|
||
gate_statuses: string[]
|
||
priorities: Array<'P0' | 'P1' | 'P2' | 'P3'>
|
||
}
|
||
agent_roles: Array<{
|
||
agent_id: string
|
||
display_name: string
|
||
primary_role: string
|
||
allowed_actions: string[]
|
||
blocked_actions: string[]
|
||
}>
|
||
asset_domains: Array<{
|
||
domain_id: string
|
||
display_name: string
|
||
description: string
|
||
}>
|
||
assets: Array<{
|
||
asset_id: string
|
||
domain_id: string
|
||
display_name: string
|
||
asset_type: string
|
||
status: string
|
||
gate_status: string
|
||
owner_agent: string
|
||
risk_level: 'low' | 'medium' | 'high' | 'critical'
|
||
evidence_refs: string[]
|
||
next_action: string
|
||
}>
|
||
workstreams: Array<{
|
||
workstream_id: string
|
||
display_name: string
|
||
completion_percent: number
|
||
status: string
|
||
next_task_id: string
|
||
}>
|
||
tasks: Array<{
|
||
task_id: string
|
||
priority: 'P0' | 'P1' | 'P2' | 'P3'
|
||
status: string
|
||
completion_percent: number
|
||
owner_agent: string
|
||
title: string
|
||
output: string
|
||
gate_status: string
|
||
approval_boundary: {
|
||
mode: string
|
||
display_summary: string
|
||
allowed_actions: string[]
|
||
blocked_actions: string[]
|
||
requires_operator_approval_for: string[]
|
||
}
|
||
next_action: string
|
||
}>
|
||
task_approval_boundary_rollup: {
|
||
total_tasks: number
|
||
by_mode: Record<string, number>
|
||
tasks_requiring_explicit_approval: string[]
|
||
tasks_with_blocked_operations: string[]
|
||
}
|
||
evidence: Array<{
|
||
evidence_id: string
|
||
kind: 'schema' | 'test' | 'browser' | 'api' | 'build' | 'doc' | 'runtime'
|
||
ref: string
|
||
result: string
|
||
}>
|
||
approval_boundaries: Record<
|
||
| 'sdk_installation_allowed'
|
||
| 'paid_api_call_allowed'
|
||
| 'shadow_or_canary_allowed'
|
||
| 'production_routing_allowed'
|
||
| 'destructive_operation_allowed',
|
||
false
|
||
>
|
||
}
|
||
|
||
export interface AiAgentAutomationBacklogSnapshot {
|
||
schema_version: 'ai_agent_automation_backlog_v1'
|
||
generated_at: string
|
||
source_inventory_snapshot_ref: string
|
||
program_status: {
|
||
overall_completion_percent: number
|
||
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
|
||
current_task_id: string
|
||
next_task_id: string
|
||
read_only_mode: true
|
||
}
|
||
rollups: {
|
||
total_items: number
|
||
by_priority: Record<string, number>
|
||
by_status: Record<string, number>
|
||
by_gate_status: Record<string, number>
|
||
by_owner_agent: Record<string, number>
|
||
}
|
||
progress_summary: {
|
||
overall_percent: number
|
||
done_items: number
|
||
planned_items: number
|
||
total_items: number
|
||
formula: string
|
||
by_priority: Array<{
|
||
priority: 'P0' | 'P1' | 'P2' | 'P3'
|
||
completion_percent: number
|
||
done_items: number
|
||
total_items: number
|
||
}>
|
||
by_workstream: Array<{
|
||
workstream_id: string
|
||
display_name: string
|
||
completion_percent: number
|
||
done_items: number
|
||
total_items: number
|
||
next_task_id: string
|
||
}>
|
||
}
|
||
backlog_items: Array<{
|
||
item_id: string
|
||
priority: 'P0' | 'P1' | 'P2' | 'P3'
|
||
status: string
|
||
workstream_id: string
|
||
source_asset_id: string
|
||
source_signal_kind: string
|
||
title: string
|
||
owner_agent: string
|
||
recommended_action: string
|
||
action_class: string
|
||
gate_status: string
|
||
risk_level: 'low' | 'medium' | 'high' | 'critical'
|
||
evidence_refs: string[]
|
||
acceptance_criteria: string[]
|
||
approval_boundary: {
|
||
mode: string
|
||
display_summary: string
|
||
allowed_actions: string[]
|
||
blocked_actions: string[]
|
||
requires_operator_approval_for: string[]
|
||
}
|
||
next_review: string
|
||
}>
|
||
item_approval_boundary_rollup: {
|
||
total_items: number
|
||
by_mode: Record<string, number>
|
||
items_requiring_explicit_approval: string[]
|
||
items_with_blocked_operations: string[]
|
||
}
|
||
approval_boundaries: Record<
|
||
| 'sdk_installation_allowed'
|
||
| 'paid_api_call_allowed'
|
||
| 'shadow_or_canary_allowed'
|
||
| 'production_routing_allowed'
|
||
| 'destructive_operation_allowed',
|
||
false
|
||
>
|
||
}
|
||
|
||
export interface RuntimeSurfaceInventorySnapshot {
|
||
schema_version: 'runtime_surface_inventory_v1'
|
||
generated_at: string
|
||
program_status: {
|
||
overall_completion_percent: number
|
||
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
|
||
current_task_id: string
|
||
next_task_id: string
|
||
read_only_mode: true
|
||
}
|
||
source_refs: string[]
|
||
rollups: {
|
||
total_surfaces: number
|
||
by_kind: Record<string, number>
|
||
by_status: Record<string, number>
|
||
by_evidence_level: Record<string, number>
|
||
action_required_surface_ids: string[]
|
||
secret_surface_ids: string[]
|
||
live_check_missing_surface_ids: string[]
|
||
total_source_components: number
|
||
source_components_with_runtime_binding: number
|
||
}
|
||
runtime_surfaces: Array<{
|
||
surface_id: string
|
||
display_name: string
|
||
kind: 'deployment' | 'service' | 'ingress' | 'cronjob' | 'configmap' | 'secret' | 'rbac' | 'policy' | 'autoscaler' | 'availability'
|
||
manifest_ref: string
|
||
status: 'manifest_mapped' | 'action_required' | 'blocked' | 'missing'
|
||
risk_level: 'low' | 'medium' | 'high' | 'critical'
|
||
evidence_level: 'committed_manifest' | 'source_file' | 'missing_manifest' | 'live_check_required'
|
||
runtime_binding: string
|
||
health_contract: string
|
||
secret_exposure: 'none' | 'name_only' | 'template_only' | 'payload_redacted'
|
||
live_check_status: 'not_run' | 'not_applicable' | 'required'
|
||
evidence_refs: string[]
|
||
next_action: string
|
||
}>
|
||
source_runtime_components: Array<{
|
||
component_id: string
|
||
display_name: string
|
||
source_ref: string
|
||
component_kind: string
|
||
runtime_binding: string
|
||
status: 'bound' | 'action_required' | 'source_only'
|
||
next_action: string
|
||
}>
|
||
evidence_gaps: Array<{
|
||
gap_id: string
|
||
severity: 'low' | 'medium' | 'high' | 'critical'
|
||
status: 'action_required' | 'blocked' | 'accepted'
|
||
summary: string
|
||
evidence_refs: string[]
|
||
next_action: string
|
||
}>
|
||
operator_contract: {
|
||
display_mode: 'read_only_runtime_surface'
|
||
must_not_interpret_as: string[]
|
||
secret_display_policy: string
|
||
}
|
||
operation_boundaries: Record<string, boolean>
|
||
approval_boundaries: Record<string, false>
|
||
}
|
||
|
||
export interface GiteaWorkflowRunnerHealthSnapshot {
|
||
schema_version: 'gitea_workflow_runner_health_v1'
|
||
generated_at: string
|
||
program_status: {
|
||
overall_completion_percent: number
|
||
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
|
||
current_task_id: string
|
||
next_task_id: string
|
||
read_only_mode: true
|
||
}
|
||
source_refs: string[]
|
||
rollups: {
|
||
total_workflows: number
|
||
by_workflow_status: Record<string, number>
|
||
by_runner_evidence_status: Record<string, number>
|
||
workflows_with_schedule: number
|
||
workflows_with_workflow_dispatch: number
|
||
workflows_with_notify_bridge: number
|
||
workflows_with_actionable_or_failure_quiet_policy: number
|
||
workflow_ids_requiring_runner_attestation: string[]
|
||
total_runner_contracts: number
|
||
runner_contracts_requiring_action: string[]
|
||
notification_contracts_total: number
|
||
notification_contracts_quiet_success_count: number
|
||
notification_contracts_quiet_success_ids: string[]
|
||
}
|
||
workflow_records: Array<{
|
||
workflow_id: string
|
||
file_ref: string
|
||
display_name: string
|
||
scope: string
|
||
status: 'manifest_mapped' | 'action_required' | 'blocked'
|
||
risk_level: 'low' | 'medium' | 'high' | 'critical'
|
||
triggers: string[]
|
||
schedule_cadence: string
|
||
runner_labels: string[]
|
||
runner_evidence_status: 'host_runner_mapped' | 'owner_attestation_required' | 'comment_ambiguous'
|
||
job_count: number
|
||
notification_policy: string
|
||
notify_bridge_calls: number
|
||
secrets_policy_status: string
|
||
evidence_refs: string[]
|
||
next_action: string
|
||
}>
|
||
runner_contracts: Array<{
|
||
contract_id: string
|
||
display_name: string
|
||
status: 'manifest_mapped' | 'action_required' | 'dry_run_only' | 'prepared_not_applied_by_snapshot'
|
||
risk_level: 'low' | 'medium' | 'high' | 'critical'
|
||
runner_labels: string[]
|
||
used_by_workflows: string[]
|
||
health_contract: string
|
||
guardrail_refs: string[]
|
||
evidence_refs: string[]
|
||
next_action: string
|
||
}>
|
||
notification_contracts: Array<{
|
||
contract_id: string
|
||
display_name: string
|
||
status: 'preserved' | 'exception_documented' | 'action_required'
|
||
policy_kind: 'failure_only' | 'actionable_only' | 'deployment_status_exception' | 'manual_status_exception' | 'read_only_no_notify'
|
||
success_noise_policy: string
|
||
failure_policy: string
|
||
workflow_refs: string[]
|
||
evidence_refs: string[]
|
||
next_action: string
|
||
}>
|
||
latest_observations: Array<{
|
||
observation_id: string
|
||
status: string
|
||
summary: string
|
||
evidence_refs: string[]
|
||
}>
|
||
operator_contract: {
|
||
display_mode: 'read_only_gitea_workflow_runner_health'
|
||
must_not_interpret_as: string[]
|
||
secret_display_policy: string
|
||
runner_mutation_policy: string
|
||
notification_policy: string
|
||
}
|
||
operation_boundaries: Record<string, boolean>
|
||
approval_boundaries: Record<string, false>
|
||
}
|
||
|
||
export interface ObservabilityContractMatrixSnapshot {
|
||
schema_version: 'observability_contract_matrix_v1'
|
||
generated_at: string
|
||
program_status: {
|
||
overall_completion_percent: number
|
||
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
|
||
current_task_id: string
|
||
next_task_id: string
|
||
read_only_mode: true
|
||
}
|
||
source_refs: string[]
|
||
rollups: {
|
||
total_surfaces: number
|
||
by_kind: Record<string, number>
|
||
by_status: Record<string, number>
|
||
by_evidence_status: Record<string, number>
|
||
by_noise_policy_status: Record<string, number>
|
||
surface_ids_requiring_action: string[]
|
||
surface_ids_with_proposal_only_noise_policy: string[]
|
||
noise_reduction_opportunities_total: number
|
||
approval_required_opportunity_ids: string[]
|
||
classification_gap_ids: string[]
|
||
read_only_denials_total: number
|
||
}
|
||
observability_surfaces: Array<{
|
||
surface_id: string
|
||
display_name: string
|
||
kind: string
|
||
status: 'verified' | 'action_required' | 'blocked'
|
||
risk_level: 'low' | 'medium' | 'high' | 'critical'
|
||
evidence_status: string
|
||
noise_policy_status: string
|
||
coverage_contract: string
|
||
current_contract?: string
|
||
evidence_refs: string[]
|
||
next_action: string
|
||
}>
|
||
noise_reduction_opportunities: Array<{
|
||
opportunity_id: string
|
||
display_name: string
|
||
status: string
|
||
proposal_only: true
|
||
impact: string
|
||
target_surface_ids?: string[]
|
||
evidence_refs: string[]
|
||
next_action: string
|
||
}>
|
||
classification_gaps: Array<{
|
||
gap_id: string
|
||
display_name: string
|
||
status: string
|
||
severity: 'low' | 'medium' | 'high' | 'critical'
|
||
summary: string
|
||
evidence_refs: string[]
|
||
next_action: string
|
||
}>
|
||
latest_observations: Array<{
|
||
observation_id: string
|
||
status: string
|
||
summary: string
|
||
evidence_refs: string[]
|
||
}>
|
||
operator_contract: {
|
||
display_mode: 'read_only_observability_contract_matrix'
|
||
must_not_interpret_as: string[]
|
||
secret_display_policy: string
|
||
alertmanager_route_policy: string
|
||
noise_reduction_policy: string
|
||
notification_policy: string
|
||
}
|
||
operation_boundaries: Record<string, boolean>
|
||
approval_boundaries: Record<string, false>
|
||
}
|
||
|
||
export interface BackupDrTargetInventorySnapshot {
|
||
schema_version: 'backup_dr_target_inventory_v1'
|
||
generated_at: string
|
||
source_refs: string[]
|
||
program_status: {
|
||
overall_completion_percent: number
|
||
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
|
||
current_task_id: string
|
||
next_task_id: string
|
||
read_only_mode: true
|
||
}
|
||
rollups: {
|
||
total_targets: number
|
||
by_status: Record<string, number>
|
||
by_target_type: Record<string, number>
|
||
by_gate_status: Record<string, number>
|
||
blocked_target_ids: string[]
|
||
}
|
||
backup_targets: Array<{
|
||
target_id: string
|
||
display_name: string
|
||
target_type: string
|
||
status: string
|
||
risk_level: 'low' | 'medium' | 'high' | 'critical'
|
||
owner_host: string
|
||
primary_script: string
|
||
schedule: string
|
||
rpo: string
|
||
storage_class: string
|
||
storage_ref: string
|
||
offsite_policy: string
|
||
automation_gate_status: string
|
||
restore_gate_status: string
|
||
secret_policy: string
|
||
evidence_refs: string[]
|
||
next_action: string
|
||
}>
|
||
approval_boundaries: Record<string, false>
|
||
operation_boundaries: Record<string, boolean>
|
||
}
|
||
|
||
export interface BackupDrReadinessMatrixSnapshot {
|
||
schema_version: 'backup_dr_readiness_matrix_v1'
|
||
generated_at: string
|
||
source_target_inventory_ref: string
|
||
source_refs: string[]
|
||
program_status: {
|
||
overall_completion_percent: number
|
||
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
|
||
current_task_id: string
|
||
next_task_id: string
|
||
read_only_mode: true
|
||
}
|
||
rollups: {
|
||
total_rows: number
|
||
by_overall_readiness: Record<string, number>
|
||
by_restore_drill_status: Record<string, number>
|
||
by_offsite_status: Record<string, number>
|
||
blocked_row_ids: string[]
|
||
action_required_row_ids: string[]
|
||
}
|
||
readiness_rows: Array<{
|
||
target_id: string
|
||
display_name: string
|
||
overall_readiness: string
|
||
freshness_status: string
|
||
integrity_status: string
|
||
restore_drill_status: string
|
||
offsite_status: string
|
||
notification_policy: string
|
||
gate_status: string
|
||
evidence_level: string
|
||
evidence_refs: string[]
|
||
blocker_summary: string
|
||
next_action: string
|
||
}>
|
||
approval_boundaries: Record<string, false>
|
||
operation_boundaries: Record<string, boolean>
|
||
}
|
||
|
||
export interface BackupNotificationPolicySnapshot {
|
||
schema_version: 'backup_notification_policy_v1'
|
||
generated_at: string
|
||
source_readiness_matrix_ref: string
|
||
source_refs: string[]
|
||
program_status: {
|
||
overall_completion_percent: number
|
||
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
|
||
current_task_id: string
|
||
next_task_id: string
|
||
read_only_mode: true
|
||
}
|
||
rollups: {
|
||
total_rules: number
|
||
by_decision: Record<string, number>
|
||
immediate_escalation_rule_ids: string[]
|
||
suppressed_success_rule_ids: string[]
|
||
}
|
||
notification_channels: Array<{
|
||
channel_id: string
|
||
purpose: string
|
||
immediate_allowed: boolean
|
||
success_immediate_allowed: boolean
|
||
requires_operator_action: boolean
|
||
}>
|
||
policy_rules: Array<{
|
||
rule_id: string
|
||
event_kind: string
|
||
backup_state: string
|
||
severity: string
|
||
decision: string
|
||
channels: string[]
|
||
owner_agent: string
|
||
requires_incident: boolean
|
||
requires_approval_record: boolean
|
||
message_contract: string
|
||
evidence_refs: string[]
|
||
}>
|
||
daily_summary_contract: Record<string, unknown>
|
||
approval_boundaries: Record<string, false>
|
||
operation_boundaries: Record<string, boolean>
|
||
}
|
||
|
||
export interface OffsiteEscrowReadinessStatusSnapshot {
|
||
schema_version: 'offsite_escrow_readiness_status_v1'
|
||
generated_at: string
|
||
source_refs: string[]
|
||
program_status: {
|
||
overall_completion_percent: number
|
||
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
|
||
current_task_id: string
|
||
next_task_id: string
|
||
read_only_mode: true
|
||
}
|
||
rollups: {
|
||
total_cards: number
|
||
by_readiness: Record<string, number>
|
||
by_kind: Record<string, number>
|
||
verified_offsite_card_ids: string[]
|
||
blocked_escrow_card_ids: string[]
|
||
action_required_card_ids: string[]
|
||
execution_blocked_card_ids: string[]
|
||
}
|
||
readiness_cards: Array<{
|
||
card_id: string
|
||
target_id: string
|
||
display_name: string
|
||
kind: 'offsite_mirror' | 'credential_escrow' | 'k8s_resource_offsite'
|
||
readiness: 'verified' | 'action_required' | 'blocked'
|
||
offsite_status: string
|
||
escrow_status: string
|
||
restore_drill_status: string
|
||
credential_exposure_status: string
|
||
automation_gate_status: string
|
||
operator_summary: string
|
||
next_action: string
|
||
evidence_refs: string[]
|
||
blocked_operations: string[]
|
||
}>
|
||
operator_contract: {
|
||
display_mode: 'read_only_status'
|
||
success_notification_policy: string
|
||
failure_notification_policy: string
|
||
credential_display_policy: string
|
||
must_not_interpret_as: string[]
|
||
}
|
||
approval_boundaries: Record<string, false>
|
||
operation_boundaries: Record<string, boolean>
|
||
}
|