Files
workinf_Blender_Wasm/web/protocol/io-format-capability-matrix.ts
mes123456 380cbed4ff
Some checks are pending
M6 deployable RC / quick (push) Waiting to run
M6 deployable RC / chromium (push) Blocked by required conditions
M6 deployable RC / release (push) Blocked by required conditions
Checkpoint web parity through Chromium input tasks
2026-08-19 10:39:03 -04:00

136 lines
7.9 KiB
TypeScript

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<ErrorCode, "IO_FORMAT_UNSUPPORTED" | "SERVER_JOB_UNAVAILABLE"> | 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<IOFormatMatrixOperation, IOFormatMatrixOperationIR>;
}
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<string, unknown> {
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new IOFormatCapabilityMatrixError("ASSET_MANIFEST_INVALID", `${path} must be an object`);
return value as Record<string, unknown>;
}
function exactKeys(value: Record<string, unknown>, 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 };
}