Advance M8-M11 parity workflows
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

This commit is contained in:
mes123456
2026-08-17 04:37:07 -04:00
parent 7c16b279ae
commit 0fe8d2bb56
324 changed files with 31920 additions and 863 deletions

View File

@@ -2,6 +2,14 @@ import type { ErrorCode } from "./error";
import { blockedGate, capabilityIssue, readyGate, type CapabilityGateResult } from "./capability-gates";
export const NLA_PROTOCOL_SCHEMA = 1 as const;
export const NLA_STACK_BUDGET = Object.freeze({
maxTracks: 4_096,
maxStripsPerTrack: 16_384,
maxTotalStrips: 65_536,
maxIdentifierBytes: 256,
maxNameBytes: 1_024,
maxUnsupportedReasonBytes: 4_096,
});
export type NlaBlendMode = "REPLACE" | "ADD" | "MULTIPLY" | "COMBINE";
export type NlaExtrapolation = "NOTHING" | "HOLD" | "HOLD_FORWARD";
@@ -51,6 +59,15 @@ export interface NlaValidationResult {
issues: Array<{ code: ErrorCode; message: string; path?: string }>;
}
export interface NlaMoveStripCommand {
type: "moveNLAStrip";
objectId: string;
trackId: string;
stripId: string;
frameStart: number;
baseRevision: number;
}
export class NlaValidationError extends Error {
readonly code: ErrorCode;
readonly path?: string;
@@ -67,14 +84,36 @@ function record(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function finite(value: unknown, path: string): number {
if (typeof value !== "number" || !Number.isFinite(value)) throw new NlaValidationError("NLA_INVALID_STACK", `${path} must be finite`, path);
function exactKeys(value: Record<string, unknown>, allowed: readonly string[], path: string): void {
const allowedSet = new Set(allowed);
if (Object.keys(value).some((key) => !allowedSet.has(key))) {
throw new NlaValidationError("NLA_INVALID_STACK", `${path} contains undeclared fields`, path);
}
}
function boundedText(value: unknown, path: string, maximumBytes: number): string {
if (typeof value !== "string" || value.length === 0 || new TextEncoder().encode(value).byteLength > maximumBytes) {
throw new NlaValidationError("NLA_BUDGET_EXCEEDED", `${path} is outside its text budget`, path);
}
return value;
}
function finite(value: unknown, path: string, maximumMagnitude = 1_000_000): number {
if (typeof value !== "number" || !Number.isFinite(value) || Math.abs(value) > maximumMagnitude) {
throw new NlaValidationError("NLA_INVALID_STACK", `${path} must be finite and bounded`, path);
}
return value;
}
function parseStrip(value: unknown, path: string): NlaStripIR {
if (!record(value)) throw new NlaValidationError("NLA_INVALID_STACK", `${path} must be an object`, path);
if (typeof value.id !== "string" || value.id.length === 0 || typeof value.actionId !== "string" || value.actionId.length === 0) throw new NlaValidationError("NLA_INVALID_STACK", `${path} requires id and actionId`, path);
exactKeys(value, [
"id", "actionId", "frameStart", "frameEnd", "actionFrameStart", "actionFrameEnd",
"scale", "repeat", "blendIn", "blendOut", "influence", "blendMode", "extrapolation",
"muted", "selected", "reverse", "useTimeWarp", "stripType", "unsupportedReason",
], path);
boundedText(value.id, `${path}.id`, NLA_STACK_BUDGET.maxIdentifierBytes);
boundedText(value.actionId, `${path}.actionId`, NLA_STACK_BUDGET.maxIdentifierBytes);
const strip = value as Record<string, unknown>;
const frameStart = finite(strip.frameStart, `${path}.frameStart`);
const frameEnd = finite(strip.frameEnd, `${path}.frameEnd`);
@@ -92,19 +131,34 @@ function parseStrip(value: unknown, path: string): NlaStripIR {
if (strip.reverse !== undefined && typeof strip.reverse !== "boolean") throw new NlaValidationError("NLA_INVALID_STACK", `${path}.reverse must be boolean`, `${path}.reverse`);
if (strip.useTimeWarp !== undefined && typeof strip.useTimeWarp !== "boolean") throw new NlaValidationError("NLA_INVALID_STACK", `${path}.useTimeWarp must be boolean`, `${path}.useTimeWarp`);
if (strip.stripType !== undefined && !["CLIP", "TRANSITION", "META", "SOUND", "UNKNOWN"].includes(strip.stripType as string)) throw new NlaValidationError("NLA_INVALID_STACK", `${path}.stripType is invalid`, `${path}.stripType`);
if (strip.unsupportedReason !== undefined && (typeof strip.unsupportedReason !== "string" || strip.unsupportedReason.length === 0)) throw new NlaValidationError("NLA_INVALID_STACK", `${path}.unsupportedReason is invalid`, `${path}.unsupportedReason`);
if (strip.unsupportedReason !== undefined) boundedText(strip.unsupportedReason, `${path}.unsupportedReason`, NLA_STACK_BUDGET.maxUnsupportedReasonBytes);
return value as unknown as NlaStripIR;
}
export function parseNlaTracks(value: unknown): NlaTrackIR[] {
if (!Array.isArray(value)) throw new NlaValidationError("NLA_INVALID_STACK", "nlaTracks must be an array", "nlaTracks");
if (value.length > NLA_STACK_BUDGET.maxTracks) {
throw new NlaValidationError("NLA_BUDGET_EXCEEDED", "NLA track count exceeds the bounded stack budget", "nlaTracks");
}
const tracks: NlaTrackIR[] = [];
const ids = new Set<string>();
let totalStrips = 0;
for (const [index, item] of value.entries()) {
const path = `nlaTracks[${index}]`;
if (!record(item) || item.schemaVersion !== NLA_PROTOCOL_SCHEMA || typeof item.id !== "string" || item.id.length === 0 || typeof item.ownerId !== "string" || item.ownerId.length === 0 || typeof item.name !== "string" || item.name.length === 0 || !Array.isArray(item.strips)) throw new NlaValidationError("NLA_INVALID_STACK", `${path} is invalid`, path);
if (ids.has(item.id)) throw new NlaValidationError("NLA_INVALID_STACK", `duplicate NLA track ID: ${item.id}`, path);
ids.add(item.id);
if (!record(item) || item.schemaVersion !== NLA_PROTOCOL_SCHEMA || !Array.isArray(item.strips)) throw new NlaValidationError("NLA_INVALID_STACK", `${path} is invalid`, path);
exactKeys(item, ["schemaVersion", "id", "ownerId", "name", "strips", "muted", "solo", "selected"], path);
const trackId = boundedText(item.id, `${path}.id`, NLA_STACK_BUDGET.maxIdentifierBytes);
boundedText(item.ownerId, `${path}.ownerId`, NLA_STACK_BUDGET.maxIdentifierBytes);
boundedText(item.name, `${path}.name`, NLA_STACK_BUDGET.maxNameBytes);
if (item.strips.length > NLA_STACK_BUDGET.maxStripsPerTrack) {
throw new NlaValidationError("NLA_BUDGET_EXCEEDED", `${path}.strips exceeds the per-track budget`, `${path}.strips`);
}
totalStrips += item.strips.length;
if (!Number.isSafeInteger(totalStrips) || totalStrips > NLA_STACK_BUDGET.maxTotalStrips) {
throw new NlaValidationError("NLA_BUDGET_EXCEEDED", "NLA strip count exceeds the total stack budget", "nlaTracks");
}
if (ids.has(trackId)) throw new NlaValidationError("NLA_INVALID_STACK", `duplicate NLA track ID: ${trackId}`, path);
ids.add(trackId);
if (typeof item.muted !== "boolean" || typeof item.solo !== "boolean" || typeof item.selected !== "boolean") throw new NlaValidationError("NLA_INVALID_STACK", `${path} track flags are invalid`, path);
tracks.push({ ...item, strips: item.strips.map((strip, stripIndex) => parseStrip(strip, `${path}.strips[${stripIndex}]`)) } as NlaTrackIR);
}
@@ -158,3 +212,39 @@ export function gateNlaTracks(value: unknown, context: NlaValidationContext): Ca
return blockedGate("N-014", "NLA_STRIP_STACK", [capabilityIssue(issue.code ?? "NLA_INVALID_STACK", issue.message, issue.path)]);
}
}
export function moveNlaStrip(
value: unknown,
command: NlaMoveStripCommand,
context: NlaValidationContext,
): NlaTrackIR[] {
if (!Number.isSafeInteger(command.baseRevision) || command.baseRevision < 0 ||
typeof command.objectId !== "string" || command.objectId.length === 0 ||
typeof command.trackId !== "string" || command.trackId.length === 0 ||
typeof command.stripId !== "string" || command.stripId.length === 0 ||
!Number.isFinite(command.frameStart) || Math.abs(command.frameStart) > 1_000_000) {
throw new NlaValidationError("NLA_INVALID_STACK", "moveNLAStrip command is invalid");
}
if (context.ownerId !== undefined && command.objectId !== context.ownerId) {
throw new NlaValidationError("NLA_PATH_INCOMPATIBLE", "moveNLAStrip owner does not match the current object", "objectId");
}
const tracks = structuredClone(parseNlaTracks(value));
const track = tracks.find((candidate) => candidate.id === command.trackId);
if (!track || track.ownerId !== command.objectId) {
throw new NlaValidationError("NLA_INVALID_STACK", `NLA track was not found: ${command.trackId}`, "trackId");
}
const strip = track.strips.find((candidate) => candidate.id === command.stripId);
if (!strip) {
throw new NlaValidationError("NLA_INVALID_STACK", `NLA strip was not found: ${command.stripId}`, "stripId");
}
const duration = strip.frameEnd - strip.frameStart;
strip.frameStart = command.frameStart;
strip.frameEnd = command.frameStart + duration;
track.strips.sort((left, right) => left.frameStart - right.frameStart || left.id.localeCompare(right.id));
const validation = validateNlaTracks(tracks, context);
if (validation.status === "BLOCKED") {
const issue = validation.issues[0];
throw new NlaValidationError(issue.code, issue.message, issue.path);
}
return tracks;
}