Checkpoint web parity through Chromium input tasks
This commit is contained in:
135
web/protocol/io-format-capability-matrix.ts
Normal file
135
web/protocol/io-format-capability-matrix.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
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 };
|
||||
}
|
||||
Reference in New Issue
Block a user