import type { ErrorCode } from "./error"; export const IO_FORMAT_CAPABILITY_MATRIX_SCHEMA = 1 as const; export const IO_FORMAT_MATRIX_FORMATS = ["GLTF", "GLB", "OBJ", "STL", "PLY", "USD", "ALEMBIC"] as const; export const IO_FORMAT_MATRIX_OPERATIONS = ["IMPORT", "EXPORT"] as const; export type IOFormatMatrixFormat = typeof IO_FORMAT_MATRIX_FORMATS[number]; export type IOFormatMatrixOperation = typeof IO_FORMAT_MATRIX_OPERATIONS[number]; export type IOFormatMatrixFeatureStatus = "SUPPORTED" | "PARTIAL" | "UNVERIFIED"; export type IOFormatMatrixRouteStatus = "READY" | "BLOCKED"; export type IOFormatMatrixExecution = "LOCAL" | "SERVER" | "NONE"; export interface IOFormatMatrixRouteIR { status: IOFormatMatrixRouteStatus; execution: IOFormatMatrixExecution; code: Extract | null; } export interface IOFormatMatrixFeatureIR { status: IOFormatMatrixFeatureStatus; evidence: string; } export interface IOFormatMatrixOperationIR { local: IOFormatMatrixRouteIR; server: IOFormatMatrixRouteIR; geometry: IOFormatMatrixFeatureIR; material: IOFormatMatrixFeatureIR; animation: IOFormatMatrixFeatureIR; } export interface IOFormatMatrixEntryIR { format: IOFormatMatrixFormat; runtimeImportStatus: "AVAILABLE" | "OPERATOR_UNREGISTERED"; runtimeExportStatus: "AVAILABLE" | "OPERATOR_UNREGISTERED"; operations: Record; } export interface IOFormatCapabilityMatrixIR { schemaVersion: typeof IO_FORMAT_CAPABILITY_MATRIX_SCHEMA; task: "M12-05B"; runtimeInventorySha256: string; formats: IOFormatMatrixEntryIR[]; } export class IOFormatCapabilityMatrixError extends Error { readonly code: ErrorCode; constructor(code: ErrorCode, message: string) { super(`${code}: ${message}`); this.name = "IOFormatCapabilityMatrixError"; this.code = code; } } const SHA256 = /^[a-f0-9]{64}$/; const FEATURE_STATUSES = ["SUPPORTED", "PARTIAL", "UNVERIFIED"] as const; const ROUTE_STATUSES = ["READY", "BLOCKED"] as const; const EXECUTIONS = ["LOCAL", "SERVER", "NONE"] as const; const RUNTIME_STATUSES = ["AVAILABLE", "OPERATOR_UNREGISTERED"] as const; function record(value: unknown, path: string): Record { if (typeof value !== "object" || value === null || Array.isArray(value)) throw new IOFormatCapabilityMatrixError("ASSET_MANIFEST_INVALID", `${path} must be an object`); return value as Record; } function exactKeys(value: Record, expected: readonly string[], path: string): void { const actual = Object.keys(value).sort(); const allowed = [...expected].sort(); if (actual.length !== allowed.length || actual.some((key, index) => key !== allowed[index])) throw new IOFormatCapabilityMatrixError("ASSET_MANIFEST_INVALID", `${path} contains undeclared fields`); } function text(value: unknown, path: string, maximum = 512): string { if (typeof value !== "string" || value.length === 0 || value.length > maximum) throw new IOFormatCapabilityMatrixError("ASSET_MANIFEST_INVALID", `${path} is invalid`); return value; } function parseRoute(value: unknown, path: string): IOFormatMatrixRouteIR { const input = record(value, path); exactKeys(input, ["status", "execution", "code"], path); if (!ROUTE_STATUSES.includes(input.status as IOFormatMatrixRouteStatus) || !EXECUTIONS.includes(input.execution as IOFormatMatrixExecution)) throw new IOFormatCapabilityMatrixError("ASSET_MANIFEST_INVALID", `${path} route status is invalid`); const status = input.status as IOFormatMatrixRouteStatus; const execution = input.execution as IOFormatMatrixExecution; if (status === "READY" && ((execution !== "LOCAL" && execution !== "SERVER") || input.code !== null)) throw new IOFormatCapabilityMatrixError("IO_FORMAT_UNSUPPORTED", `${path} ready route is not bound to an executor`); if (status === "BLOCKED" && (execution !== "NONE" || !["IO_FORMAT_UNSUPPORTED", "SERVER_JOB_UNAVAILABLE"].includes(input.code as string))) throw new IOFormatCapabilityMatrixError("IO_FORMAT_UNSUPPORTED", `${path} blocked route is not fail-closed`); return { status, execution, code: input.code as IOFormatMatrixRouteIR["code"] }; } function parseFeature(value: unknown, path: string): IOFormatMatrixFeatureIR { const input = record(value, path); exactKeys(input, ["status", "evidence"], path); if (!FEATURE_STATUSES.includes(input.status as IOFormatMatrixFeatureStatus)) throw new IOFormatCapabilityMatrixError("ASSET_MANIFEST_INVALID", `${path}.status is invalid`); return { status: input.status as IOFormatMatrixFeatureStatus, evidence: text(input.evidence, `${path}.evidence`) }; } function parseOperation(value: unknown, path: string): IOFormatMatrixOperationIR { const input = record(value, path); exactKeys(input, ["local", "server", "geometry", "material", "animation"], path); const local = parseRoute(input.local, `${path}.local`); const server = parseRoute(input.server, `${path}.server`); const geometry = parseFeature(input.geometry, `${path}.geometry`); const material = parseFeature(input.material, `${path}.material`); const animation = parseFeature(input.animation, `${path}.animation`); if (local.status === "READY" && [geometry, material, animation].some((feature) => feature.status === "UNVERIFIED")) throw new IOFormatCapabilityMatrixError("ASSET_MANIFEST_INVALID", `${path} ready local route has unverified feature support`); return { local, server, geometry, material, animation }; } function parseEntry(value: unknown, index: number): IOFormatMatrixEntryIR { const path = `formats[${index}]`; const input = record(value, path); exactKeys(input, ["format", "runtimeImportStatus", "runtimeExportStatus", "operations"], path); if (!IO_FORMAT_MATRIX_FORMATS.includes(input.format as IOFormatMatrixFormat)) throw new IOFormatCapabilityMatrixError("ASSET_MANIFEST_INVALID", `${path}.format is invalid`); if (!RUNTIME_STATUSES.includes(input.runtimeImportStatus as IOFormatMatrixEntryIR["runtimeImportStatus"]) || !RUNTIME_STATUSES.includes(input.runtimeExportStatus as IOFormatMatrixEntryIR["runtimeExportStatus"])) throw new IOFormatCapabilityMatrixError("ASSET_MANIFEST_INVALID", `${path} runtime status is invalid`); const operations = record(input.operations, `${path}.operations`); exactKeys(operations, IO_FORMAT_MATRIX_OPERATIONS, `${path}.operations`); return { format: input.format as IOFormatMatrixFormat, runtimeImportStatus: input.runtimeImportStatus as IOFormatMatrixEntryIR["runtimeImportStatus"], runtimeExportStatus: input.runtimeExportStatus as IOFormatMatrixEntryIR["runtimeExportStatus"], operations: { IMPORT: parseOperation(operations.IMPORT, `${path}.operations.IMPORT`), EXPORT: parseOperation(operations.EXPORT, `${path}.operations.EXPORT`), }, }; } export function parseIOFormatCapabilityMatrix(value: unknown): IOFormatCapabilityMatrixIR { const input = record(value, "input"); exactKeys(input, ["schemaVersion", "task", "runtimeInventorySha256", "formats"], "input"); if (input.schemaVersion !== IO_FORMAT_CAPABILITY_MATRIX_SCHEMA || input.task !== "M12-05B" || typeof input.runtimeInventorySha256 !== "string" || !SHA256.test(input.runtimeInventorySha256)) throw new IOFormatCapabilityMatrixError("ASSET_MANIFEST_INVALID", "matrix header is invalid"); if (!Array.isArray(input.formats) || input.formats.length !== IO_FORMAT_MATRIX_FORMATS.length) throw new IOFormatCapabilityMatrixError("ASSET_MANIFEST_INVALID", "matrix must contain each inventoried format exactly once"); const formats = input.formats.map(parseEntry); const seen = new Set(formats.map((entry) => entry.format)); if (seen.size !== IO_FORMAT_MATRIX_FORMATS.length || IO_FORMAT_MATRIX_FORMATS.some((format) => !seen.has(format))) throw new IOFormatCapabilityMatrixError("ASSET_MANIFEST_INVALID", "matrix format identities are incomplete or duplicated"); return { schemaVersion: IO_FORMAT_CAPABILITY_MATRIX_SCHEMA, task: "M12-05B", runtimeInventorySha256: input.runtimeInventorySha256, formats }; }