import { parseAssetLibraryManifest } from "./asset-library-io"; import { ASSET_CATALOG_SCHEMA, parseAssetCatalogManifestV2 } from "./asset-catalog-v2"; import type { ErrorCode } from "./error"; export const ASSET_CATALOG_COMPATIBILITY_SCHEMA = 1 as const; export type AssetCatalogLegacyOperation = "READ" | "CATALOG_WRITE" | "ASSET_WRITE" | "SAVE"; export interface AssetCatalogLegacySnapshotIR { revision: number; catalogs: Array<{ catalogId: string; path: string; simpleName: string }>; assets: Array<{ assetId: string; relativeAssetIdentifier: string; idType: string; name: string; catalogId: string | null }>; libraries: Array<{ libraryId: string; name: string; readOnly: boolean }>; } export interface AssetCatalogLegacyCompatibilityIR { schemaVersion: typeof ASSET_CATALOG_COMPATIBILITY_SCHEMA; task: "M12-01E"; readerSchemaVersion: 1; documentSchemaVersion: number; operation: AssetCatalogLegacyOperation; status: "READY" | "READ_ONLY" | "BLOCKED"; code: ErrorCode | null; recoverable: boolean; sourceSha256: string; snapshot: AssetCatalogLegacySnapshotIR | null; nextTask: "M12-01F"; } const encoder = new TextEncoder(); function record(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function stableJSON(value: unknown): string { if (Array.isArray(value)) return `[${value.map(stableJSON).join(",")}]`; if (record(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJSON(value[key])}`).join(",")}}`; return JSON.stringify(value); } async function sha256(value: unknown): Promise { const digest = await crypto.subtle.digest("SHA-256", encoder.encode(stableJSON(value))); return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join(""); } function operation(value: unknown): AssetCatalogLegacyOperation { if (value !== "READ" && value !== "CATALOG_WRITE" && value !== "ASSET_WRITE" && value !== "SAVE") { throw new Error("ASSET_MANIFEST_INVALID: legacy reader operation is invalid"); } return value; } export async function inspectAssetCatalogForLegacyReader( value: unknown, operationValue: unknown, ): Promise { const requestedOperation = operation(operationValue); const sourceSha256 = await sha256(value); if (!record(value) || typeof value.schemaVersion !== "number" || !Number.isSafeInteger(value.schemaVersion)) { return { schemaVersion: ASSET_CATALOG_COMPATIBILITY_SCHEMA, task: "M12-01E", readerSchemaVersion: 1, documentSchemaVersion: 0, operation: requestedOperation, status: "BLOCKED", code: "PROTOCOL_MISMATCH", recoverable: false, sourceSha256, snapshot: null, nextTask: "M12-01F", }; } if (value.schemaVersion === 1) { try { parseAssetLibraryManifest(value); } catch { return { schemaVersion: ASSET_CATALOG_COMPATIBILITY_SCHEMA, task: "M12-01E", readerSchemaVersion: 1, documentSchemaVersion: 1, operation: requestedOperation, status: "BLOCKED", code: "ASSET_MANIFEST_INVALID", recoverable: true, sourceSha256, snapshot: null, nextTask: "M12-01F", }; } return { schemaVersion: ASSET_CATALOG_COMPATIBILITY_SCHEMA, task: "M12-01E", readerSchemaVersion: 1, documentSchemaVersion: 1, operation: requestedOperation, status: "READY", code: null, recoverable: true, sourceSha256, snapshot: null, nextTask: "M12-01F", }; } if (value.schemaVersion !== ASSET_CATALOG_SCHEMA) { return { schemaVersion: ASSET_CATALOG_COMPATIBILITY_SCHEMA, task: "M12-01E", readerSchemaVersion: 1, documentSchemaVersion: value.schemaVersion, operation: requestedOperation, status: "BLOCKED", code: "PROTOCOL_MISMATCH", recoverable: false, sourceSha256, snapshot: null, nextTask: "M12-01F", }; } let document; try { document = await parseAssetCatalogManifestV2(value); } catch { return { schemaVersion: ASSET_CATALOG_COMPATIBILITY_SCHEMA, task: "M12-01E", readerSchemaVersion: 1, documentSchemaVersion: ASSET_CATALOG_SCHEMA, operation: requestedOperation, status: "BLOCKED", code: "ASSET_MANIFEST_INVALID", recoverable: true, sourceSha256, snapshot: null, nextTask: "M12-01F", }; } const snapshot: AssetCatalogLegacySnapshotIR = { revision: document.revision, catalogs: document.catalogs.map(({ catalogId, path, simpleName }) => ({ catalogId, path, simpleName })), assets: document.assets.map(({ assetId, relativeAssetIdentifier, idType, name, catalogId }) => ({ assetId, relativeAssetIdentifier, idType, name, catalogId })), libraries: document.libraries.map(({ libraryId, name, readOnly }) => ({ libraryId, name, readOnly })), }; return { schemaVersion: ASSET_CATALOG_COMPATIBILITY_SCHEMA, task: "M12-01E", readerSchemaVersion: 1, documentSchemaVersion: ASSET_CATALOG_SCHEMA, operation: requestedOperation, status: requestedOperation === "READ" ? "READ_ONLY" : "BLOCKED", code: "ASSET_SCHEMA_DOWNGRADE_BLOCKED", recoverable: true, sourceSha256, snapshot, nextTask: "M12-01F", }; }