Advance N-023 through N-026 audited parity
This commit is contained in:
@@ -3,6 +3,9 @@ import { parseGreasePencilData, type GreasePencilDataIR } from "./grease-pencil"
|
||||
import { parseCompositorGraph, type CompositorGraphIR } from "./compositor";
|
||||
import { parseSequencerTimeline, type SequencerTimelineIR } from "./sequencer";
|
||||
import { parseTrackingMaskProject, type TrackingMaskProjectIR } from "./tracking-mask";
|
||||
import { normalizeProjectAssetPath } from "./asset-path";
|
||||
import { parseEditorWorkflow, type EditorWorkflowIR } from "./editor-workflow";
|
||||
import { parseScriptSourceInventory, type ScriptSourceInventoryIR } from "./scripting-platform";
|
||||
|
||||
export type SceneNodeType =
|
||||
| "EMPTY"
|
||||
@@ -474,6 +477,11 @@ export interface SceneSnapshotIR {
|
||||
greasePencils?: GreasePencilDataIR[];
|
||||
trackingMasks?: TrackingMaskProjectIR;
|
||||
trackingMaskStatus?: "AVAILABLE" | "BLOCKED";
|
||||
libraryStatus?: "AVAILABLE" | "BLOCKED";
|
||||
editorWorkflow?: EditorWorkflowIR;
|
||||
editorWorkflowStatus?: "AVAILABLE" | "BLOCKED";
|
||||
scriptSources?: ScriptSourceInventoryIR;
|
||||
scriptSourceStatus?: "AVAILABLE" | "BLOCKED";
|
||||
libraries?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -482,6 +490,8 @@ export interface SceneSnapshotIR {
|
||||
packedByteLength?: number;
|
||||
status: "PACKED" | "EXTERNAL_REQUIRED";
|
||||
errorCode?: "LINKED_LIBRARY_RESOURCE_REQUIRED";
|
||||
dependencyIds: string[];
|
||||
readOnly: true;
|
||||
}>;
|
||||
animations: AnimationIR[];
|
||||
nlaTracks?: NlaTrackIR[];
|
||||
@@ -1055,5 +1065,72 @@ export function parseSceneSnapshotIR(value: unknown): SceneSnapshotIR {
|
||||
parseTrackingMaskProject(value.trackingMasks);
|
||||
if (value.trackingMaskStatus !== "AVAILABLE") throw new Error("SceneIR.trackingMaskStatus must be AVAILABLE when trackingMasks is present");
|
||||
}
|
||||
if (value.libraryStatus !== undefined && !["AVAILABLE", "BLOCKED"].includes(value.libraryStatus as string)) {
|
||||
throw new Error("SceneIR.libraryStatus is invalid");
|
||||
}
|
||||
if (value.libraries !== undefined) {
|
||||
const libraries = value.libraries as unknown[];
|
||||
if (libraries.length > 0 && value.libraryStatus !== "AVAILABLE") throw new Error("SceneIR.libraryStatus must be AVAILABLE when libraries are present");
|
||||
if (libraries.length > 1024) throw new Error("SceneIR.libraries exceeds the budget");
|
||||
const libraryIds = new Set<string>();
|
||||
for (const [index, library] of libraries.entries()) {
|
||||
if (!isRecord(library)) throw new Error(`SceneIR.libraries[${index}] must be an object`);
|
||||
const id = requireString(library.id, `libraries[${index}].id`);
|
||||
if (!id || id.length > 256 || libraryIds.has(id)) throw new Error(`SceneIR.libraries[${index}].id is invalid`);
|
||||
libraryIds.add(id);
|
||||
requireString(library.name, `libraries[${index}].name`);
|
||||
const sourcePath = requireString(library.sourcePath, `libraries[${index}].sourcePath`);
|
||||
if (typeof library.packed !== "boolean" || library.readOnly !== true || !Array.isArray(library.dependencyIds) ||
|
||||
library.dependencyIds.length > 1024 || library.dependencyIds.some((dependency) => typeof dependency !== "string") ||
|
||||
new Set(library.dependencyIds).size !== library.dependencyIds.length ||
|
||||
!["PACKED", "EXTERNAL_REQUIRED"].includes(library.status as string)) {
|
||||
throw new Error(`SceneIR.libraries[${index}] is invalid`);
|
||||
}
|
||||
let projectPath = true;
|
||||
try { normalizeProjectAssetPath(sourcePath); } catch { projectPath = false; }
|
||||
if (!projectPath) {
|
||||
throw new Error(`SceneIR.libraries[${index}].sourcePath is outside the project`);
|
||||
}
|
||||
if (library.status === "PACKED" && (!library.packed || !Number.isSafeInteger(library.packedByteLength) || (library.packedByteLength as number) <= 0)) {
|
||||
throw new Error(`SceneIR.libraries[${index}] packed payload is invalid`);
|
||||
}
|
||||
if (library.status === "EXTERNAL_REQUIRED" && (library.packed || library.errorCode !== "LINKED_LIBRARY_RESOURCE_REQUIRED")) {
|
||||
throw new Error(`SceneIR.libraries[${index}] external resource state is invalid`);
|
||||
}
|
||||
}
|
||||
for (const [index, library] of libraries.entries()) {
|
||||
const dependencies = (library as Record<string, unknown>).dependencyIds as string[];
|
||||
if (dependencies.some((dependency) => !libraryIds.has(dependency))) throw new Error(`SceneIR.libraries[${index}] references a missing dependency`);
|
||||
}
|
||||
const byId = new Map(libraries.map((library) => {
|
||||
const record = library as Record<string, unknown>;
|
||||
return [record.id as string, record.dependencyIds as string[]] as const;
|
||||
}));
|
||||
const active = new Set<string>();
|
||||
const complete = new Set<string>();
|
||||
const visit = (id: string): void => {
|
||||
if (active.has(id)) throw new Error(`SceneIR.libraries dependency cycle includes ${id}`);
|
||||
if (complete.has(id)) return;
|
||||
active.add(id);
|
||||
for (const dependency of byId.get(id) ?? []) visit(dependency);
|
||||
active.delete(id);
|
||||
complete.add(id);
|
||||
};
|
||||
byId.forEach((_dependencies, id) => visit(id));
|
||||
}
|
||||
if (value.editorWorkflowStatus !== undefined && !["AVAILABLE", "BLOCKED"].includes(value.editorWorkflowStatus as string)) {
|
||||
throw new Error("SceneIR.editorWorkflowStatus is invalid");
|
||||
}
|
||||
if (value.editorWorkflow !== undefined) {
|
||||
parseEditorWorkflow(value.editorWorkflow);
|
||||
if (value.editorWorkflowStatus !== "AVAILABLE") throw new Error("SceneIR.editorWorkflowStatus must be AVAILABLE when editorWorkflow is present");
|
||||
}
|
||||
if (value.scriptSourceStatus !== undefined && !["AVAILABLE", "BLOCKED"].includes(value.scriptSourceStatus as string)) {
|
||||
throw new Error("SceneIR.scriptSourceStatus is invalid");
|
||||
}
|
||||
if (value.scriptSources !== undefined) {
|
||||
parseScriptSourceInventory(value.scriptSources);
|
||||
if (value.scriptSourceStatus !== "AVAILABLE") throw new Error("SceneIR.scriptSourceStatus must be AVAILABLE when scriptSources is present");
|
||||
}
|
||||
return value as unknown as SceneSnapshotIR;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user