/** API client for the Studio 2.0 control plane (/api/studio2). */ async function handle(res: Response): Promise { if (!res.ok) { const body = await res.text().catch(() => ''); throw new Error(`API ${res.status}: ${body || res.statusText}`); } return res.json() as Promise; } const get = (url: string) => fetch(url).then((r) => handle(r)); const post = (url: string, body?: unknown) => fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body ?? {}), }).then((r) => handle(r)); // ------------------------------------------------------------------ types export interface Studio2Status { registry_counts: Record; dependencies: Record; soft_dependencies: string[]; } export interface RegistryEntity { entity_id: string; kind: string; name?: string; created_at?: string; provenance?: Record; [key: string]: unknown; } export interface DatasetEntity extends RegistryEntity { role: string; protected_evaluation: boolean; governance_overrides: { actor: string; reason: string; timestamp: string }[]; role_history: { from: string | null; to: string; actor: string }[]; lineage: { parents: string[] }; } export interface RunEntity extends RegistryEntity { engine: string; reproducibility: 'REPRODUCIBLE' | 'NON_REPRODUCIBLE'; missing_components: string[]; reproducibility_tuple: Record; results?: Record; } export interface ReleaseDecision { entity_id: string; status: 'GO' | 'REVIEW' | 'NO_GO'; confidence: number; evidence_completeness: number; blocking_conditions: string[]; unresolved_questions: string[]; degraded_inputs: string[]; human_approval_required: boolean; deployment_authorized: boolean; approval: { approver: string; rationale: string; approved_at: string } | null; policy_version: string; evidence_tuple: Record; evaluated_at: string; } export interface HardwareRow { combination: Record; combination_label: string; metrics: Record | null; n: number; status: 'PASS' | 'FAIL' | 'INSUFFICIENT'; critical: boolean; failed_checks?: string[]; reason?: string; derivation?: Record; } export interface HardwareMatrix { matrix_id: string; status: string; global_pass: boolean | null; global_metrics: Record | null; global_vs_matrix_note: string | null; n_combinations: number; n_pass: number; n_fail: number; n_insufficient: number; min_support: { n: number; method: string }; rows: HardwareRow[]; insufficient: { combination_label: string; reason: string; critical: boolean }[]; source_run_id?: string | null; } export interface FunnelStage { stage: string; label: string; available: boolean; source: string; data?: Record; status?: string; reason?: string; } export interface FunnelPanel { available: boolean; source: string; data?: Record; status?: string; reason?: string; } export interface Funnel { generated_at: string; stages: FunnelStage[]; safety: FunnelPanel; model_comparison: FunnelPanel; drift: FunnelPanel; compute: FunnelPanel; availability: Record; } export interface DemoStep { step: string; available: boolean; [key: string]: unknown; } export interface DemoResult { demo_id: string; seed: number; steps: DemoStep[]; decision: ReleaseDecision; regression_dataset: DatasetEntity | null; generated_at: string; } // ------------------------------------------------------------------ calls export const getStatus = () => get('/api/studio2/status'); export const listEntities = (kind: string) => get<{ kind: string; entities: RegistryEntity[] }>(`/api/studio2/registry/${kind}`); export const runIngest = () => post<{ registered: Record; totals: Record }>( '/api/studio2/registry/ingest' ); export const listDecisions = () => get<{ decisions: ReleaseDecision[] }>('/api/studio2/release/decisions'); export const evaluateRelease = () => post('/api/studio2/release/evaluate', { use_live_sources: true }); export const approveDecision = (decisionId: string, approver: string, rationale: string) => post(`/api/studio2/release/decisions/${decisionId}/approve`, { approver, rationale, }); export const getHardwareMatrix = (refresh = false) => get(`/api/studio2/hardware/matrix${refresh ? '?refresh=true' : ''}`); export const getFunnel = () => get('/api/studio2/funnel'); export const runDemo = (seed?: number) => post('/api/studio2/demo/run', seed !== undefined ? { seed } : {}); export const getLatestDemo = () => get('/api/studio2/demo/latest'); export const listDocs = () => get<{ docs: string[] }>('/api/studio2/docs'); export const getDoc = (name: string) => get<{ name: string; content: string }>(`/api/studio2/docs/${name}`);