Reorganize Blender Web execution around capabilities
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

This commit is contained in:
mes123456
2026-08-24 18:37:46 -04:00
parent 0b2c3ba0dd
commit a081c68c87
2530 changed files with 763976 additions and 79 deletions

View File

@@ -0,0 +1,351 @@
export const CORRECTIVE_CASE_SCHEMA = 1 as const;
export type CaseSurface = "DESKTOP" | "WASM";
export type CommandStatus = "FINISHED" | "BLOCKED" | "CANCELLED" | "UNSUPPORTED" | "MALFORMED" | "EXTERNAL_REQUIRED";
export type MainMutation = "CHANGED" | "UNCHANGED";
export type TargetKind = "ASSET_LIBRARY" | "DATABLOCK" | "MESH" | "MODIFIER" | "SCENE";
export type EditMode = "READ_ONLY" | "EDITABLE";
export type Authorization = "NONE" | "USER_EDIT" | "SYSTEM";
export type SourceKind = "LOCAL" | "EXTERNAL" | "PACKED";
export type ResourceProvider = "NONE" | "OPFS" | "EXTERNAL_LIBRARY" | "PACKED_BLEND";
export interface CommandContext {
mainId: string;
targetKind: TargetKind;
targetId?: string;
editMode: EditMode;
authorization: Authorization;
source: { kind: SourceKind; sha256: string };
resourceProvider?: ResourceProvider;
}
export interface MainMutationCommand {
schemaVersion: typeof CORRECTIVE_CASE_SCHEMA;
requestId: string;
commandType: string;
baseRevision: number;
context: CommandContext;
payload: Record<string, unknown>;
}
export interface MainMutationResult {
schemaVersion: typeof CORRECTIVE_CASE_SCHEMA;
requestId: string;
status: CommandStatus;
code: string;
baseRevision: number;
mainRevisionBefore: number;
mainRevisionAfter: number;
mainMutation: MainMutation;
delta: Record<string, unknown> | null;
persistence: Record<string, unknown> | null;
error: Record<string, unknown> | null;
}
export interface SurfaceReceipt {
schemaVersion: typeof CORRECTIVE_CASE_SCHEMA;
surface: CaseSurface;
requestId: string;
commandType: string;
context: CommandContext | null;
result: MainMutationResult;
}
export interface SharedCaseReceipt {
schemaVersion: typeof CORRECTIVE_CASE_SCHEMA;
requestId: string;
commandType: string;
desktop: SurfaceReceipt;
wasm: SurfaceReceipt;
parity: "MATCH" | "MISMATCH";
differences: string[];
}
export interface SurfaceCaseOptions {
command: unknown;
mainRevision: number;
execute: (command: MainMutationCommand) => MainMutationResult | Promise<MainMutationResult>;
}
export class CorrectiveCaseRunnerError extends Error {
readonly code: string;
constructor(code: string, message: string) {
super(`${code}: ${message}`);
this.name = "CorrectiveCaseRunnerError";
this.code = code;
}
}
const SHA256 = /^[a-f0-9]{64}$/u;
const REQUEST_ID = /^[A-Za-z0-9._:-]{1,256}$/u;
const COMMAND_TYPE = /^[a-z][A-Za-z0-9]{0,127}$/u;
const STATUS_CODES: Readonly<Record<CommandStatus, string>> = {
FINISHED: "OK",
BLOCKED: "REVISION_CONFLICT",
CANCELLED: "CANCELLED",
UNSUPPORTED: "COMMAND_UNSUPPORTED",
MALFORMED: "COMMAND_MALFORMED",
EXTERNAL_REQUIRED: "RESOURCE_REQUIRED",
};
const STATUS_CODE_PAIRS = new Set([
"FINISHED:OK",
"BLOCKED:REVISION_CONFLICT",
"BLOCKED:INVALID_CONTEXT",
"CANCELLED:CANCELLED",
"UNSUPPORTED:COMMAND_UNSUPPORTED",
"MALFORMED:COMMAND_MALFORMED",
"EXTERNAL_REQUIRED:RESOURCE_REQUIRED",
]);
const STATUSES = new Set<CommandStatus>(Object.keys(STATUS_CODES) as CommandStatus[]);
const TARGET_KINDS = new Set<TargetKind>(["ASSET_LIBRARY", "DATABLOCK", "MESH", "MODIFIER", "SCENE"]);
const EDIT_MODES = new Set<EditMode>(["READ_ONLY", "EDITABLE"]);
const AUTHORIZATIONS = new Set<Authorization>(["NONE", "USER_EDIT", "SYSTEM"]);
const SOURCE_KINDS = new Set<SourceKind>(["LOCAL", "EXTERNAL", "PACKED"]);
const RESOURCE_PROVIDERS = new Set<ResourceProvider>(["NONE", "OPFS", "EXTERNAL_LIBRARY", "PACKED_BLEND"]);
function record(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function exactKeys(value: Record<string, unknown>, allowed: ReadonlySet<string>, label: string): void {
for (const key of Object.keys(value)) {
if (!allowed.has(key)) throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", `${label}.${key} is not allowed`);
}
}
function boundedText(value: unknown, label: string, pattern: RegExp, maximum = 256): string {
if (typeof value !== "string" || value.length === 0 || value.length > maximum || !pattern.test(value)) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", `${label} is invalid`);
}
return value;
}
function nonNegativeInteger(value: unknown, label: string): number {
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", `${label} must be a non-negative integer`);
}
return value;
}
function commandContext(value: unknown): CommandContext {
if (!record(value)) throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "context must be an object");
exactKeys(value, new Set(["mainId", "targetKind", "targetId", "editMode", "authorization", "source", "resourceProvider"]), "context");
const mainId = boundedText(value.mainId, "context.mainId", /^.{1,256}$/u);
if (typeof value.targetKind !== "string" || !TARGET_KINDS.has(value.targetKind as TargetKind)) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "context.targetKind is invalid");
}
const targetId = value.targetId === undefined ? undefined : boundedText(value.targetId, "context.targetId", /^.{1,256}$/u);
if (typeof value.editMode !== "string" || !EDIT_MODES.has(value.editMode as EditMode)) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "context.editMode is invalid");
}
if (typeof value.authorization !== "string" || !AUTHORIZATIONS.has(value.authorization as Authorization)) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "context.authorization is invalid");
}
if (!record(value.source) || Object.keys(value.source).some((key) => !["kind", "sha256"].includes(key))) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "context.source is invalid");
}
if (typeof value.source.kind !== "string" || !SOURCE_KINDS.has(value.source.kind as SourceKind) ||
typeof value.source.sha256 !== "string" || !SHA256.test(value.source.sha256)) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "context.source provenance is invalid");
}
const resourceProvider = value.resourceProvider === undefined ? undefined : value.resourceProvider;
if (resourceProvider !== undefined && (typeof resourceProvider !== "string" || !RESOURCE_PROVIDERS.has(resourceProvider as ResourceProvider))) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "context.resourceProvider is invalid");
}
return {
mainId,
targetKind: value.targetKind as TargetKind,
...(targetId === undefined ? {} : { targetId }),
editMode: value.editMode as EditMode,
authorization: value.authorization as Authorization,
source: { kind: value.source.kind as SourceKind, sha256: value.source.sha256 },
...(resourceProvider === undefined ? {} : { resourceProvider: resourceProvider as ResourceProvider }),
};
}
export function validateCommand(value: unknown): MainMutationCommand {
if (!record(value)) throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "command must be an object");
exactKeys(value, new Set(["schemaVersion", "requestId", "commandType", "baseRevision", "context", "payload"]), "command");
if (value.schemaVersion !== CORRECTIVE_CASE_SCHEMA) throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "schemaVersion is unsupported");
const requestId = boundedText(value.requestId, "requestId", REQUEST_ID);
const commandType = boundedText(value.commandType, "commandType", COMMAND_TYPE);
const baseRevision = nonNegativeInteger(value.baseRevision, "baseRevision");
const context = commandContext(value.context);
if (!record(value.payload)) throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "payload must be an object");
for (const key of ["requestId", "baseRevision", "context"]) {
if (Object.prototype.hasOwnProperty.call(value.payload, key)) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", `payload cannot override ${key}`);
}
}
return { schemaVersion: CORRECTIVE_CASE_SCHEMA, requestId, commandType, baseRevision, context, payload: { ...value.payload } };
}
function resultObject(value: unknown): Record<string, unknown> {
if (!record(value)) throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "result must be an object");
exactKeys(value, new Set(["schemaVersion", "requestId", "status", "code", "baseRevision", "mainRevisionBefore", "mainRevisionAfter", "mainMutation", "delta", "persistence", "error"]), "result");
return value;
}
function optionalObject(value: unknown, label: string): Record<string, unknown> | null {
if (value === undefined || value === null) return null;
if (!record(value)) throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", `${label} must be an object or null`);
return { ...value };
}
export function normalizeResult(value: unknown, command: MainMutationCommand, mainRevisionBefore: number): MainMutationResult {
const result = resultObject(value);
if (result.schemaVersion !== CORRECTIVE_CASE_SCHEMA || result.requestId !== command.requestId || result.baseRevision !== command.baseRevision) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "result identity is not bound to the command");
}
nonNegativeInteger(result.mainRevisionBefore, "mainRevisionBefore");
nonNegativeInteger(result.mainRevisionAfter, "mainRevisionAfter");
if (result.mainRevisionBefore !== mainRevisionBefore) throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "result before revision is not current");
if (typeof result.status !== "string" || !STATUSES.has(result.status as CommandStatus) || typeof result.code !== "string" || !STATUS_CODE_PAIRS.has(`${result.status}:${result.code}`)) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "status and code are not a closed pair");
}
const status = result.status as CommandStatus;
const mutation = result.mainMutation;
const expectedMutation: MainMutation = status === "FINISHED" ? "CHANGED" : "UNCHANGED";
if (mutation !== expectedMutation || result.mainRevisionAfter !== mainRevisionBefore + (status === "FINISHED" ? 1 : 0)) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "result revision or mutation invariant failed");
}
const delta = optionalObject(result.delta, "delta");
const persistence = optionalObject(result.persistence, "persistence");
const error = optionalObject(result.error, "error");
if (status !== "FINISHED" && (delta !== null || persistence !== null)) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "failure result cannot claim delta or persistence");
}
return {
schemaVersion: CORRECTIVE_CASE_SCHEMA,
requestId: command.requestId,
status,
code: STATUS_CODES[status],
baseRevision: command.baseRevision,
mainRevisionBefore,
mainRevisionAfter: result.mainRevisionAfter,
mainMutation: expectedMutation,
delta,
persistence,
error,
};
}
function failure(command: MainMutationCommand | null, status: Exclude<CommandStatus, "FINISHED">, before: number, message: string, code = STATUS_CODES[status]): MainMutationResult {
const requestId = command?.requestId ?? "invalid-request";
const baseRevision = command?.baseRevision ?? before;
return {
schemaVersion: CORRECTIVE_CASE_SCHEMA,
requestId,
status,
code,
baseRevision,
mainRevisionBefore: before,
mainRevisionAfter: before,
mainMutation: "UNCHANGED",
delta: null,
persistence: null,
error: { code, message },
};
}
function commandIdentity(value: unknown): { requestId: string; commandType: string } {
const input = record(value) ? value : {};
return {
requestId: typeof input.requestId === "string" && REQUEST_ID.test(input.requestId) ? input.requestId : "invalid-request",
commandType: typeof input.commandType === "string" && COMMAND_TYPE.test(input.commandType) ? input.commandType : "malformed",
};
}
function receipt(surface: CaseSurface, command: MainMutationCommand | null, result: MainMutationResult): SurfaceReceipt {
const identity = command ? { requestId: command.requestId, commandType: command.commandType } : commandIdentity(result);
return {
schemaVersion: CORRECTIVE_CASE_SCHEMA,
surface,
requestId: identity.requestId,
commandType: identity.commandType,
context: command?.context ?? null,
result,
};
}
async function runSurface(surface: CaseSurface, options: SurfaceCaseOptions): Promise<SurfaceReceipt> {
const before = nonNegativeInteger(options.mainRevision, "mainRevision");
let command: MainMutationCommand;
try {
command = validateCommand(options.command);
}
catch (error) {
const identity = commandIdentity(options.command);
const malformed = failure(null, "MALFORMED", before, error instanceof Error ? error.message : String(error));
malformed.requestId = identity.requestId;
return receipt(surface, null, malformed);
}
if (command.baseRevision !== before) return receipt(surface, command, failure(command, "BLOCKED", before, "base revision does not match current Main"));
if (command.context.editMode !== "EDITABLE" || command.context.authorization === "NONE") {
return receipt(surface, command, failure(command, "BLOCKED", before, "context cannot authorize a Main mutation", "INVALID_CONTEXT"));
}
if (command.context.source.kind === "EXTERNAL" && (!command.context.resourceProvider || command.context.resourceProvider === "NONE")) {
return receipt(surface, command, failure(command, "EXTERNAL_REQUIRED", before, "external source requires a resource provider"));
}
try {
return receipt(surface, command, normalizeResult(await options.execute(command), command, before));
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
const code = error instanceof CorrectiveCaseRunnerError ? error.code : "COMMAND_UNSUPPORTED";
const status = code === "RESOURCE_REQUIRED" ? "EXTERNAL_REQUIRED" : code === "CANCELLED" ? "CANCELLED" : code === "REVISION_CONFLICT" || code === "INVALID_CONTEXT" ? "BLOCKED" : code === "COMMAND_MALFORMED" ? "MALFORMED" : "UNSUPPORTED";
return receipt(surface, command, failure(command, status, before, message, code === "INVALID_CONTEXT" ? code : STATUS_CODES[status]));
}
}
export function runDesktopCase(options: SurfaceCaseOptions): Promise<SurfaceReceipt> {
return runSurface("DESKTOP", options);
}
export function runWasmCase(options: SurfaceCaseOptions): Promise<SurfaceReceipt> {
return runSurface("WASM", options);
}
function coreReceipt(receiptValue: SurfaceReceipt): Record<string, unknown> {
const result = receiptValue.result;
return {
requestId: receiptValue.requestId,
commandType: receiptValue.commandType,
status: result.status,
code: result.code,
baseRevision: result.baseRevision,
mainRevisionBefore: result.mainRevisionBefore,
mainRevisionAfter: result.mainRevisionAfter,
mainMutation: result.mainMutation,
delta: result.delta,
persistence: result.persistence,
};
}
function stable(value: unknown): unknown {
if (Array.isArray(value)) return value.map(stable);
if (record(value)) return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])]));
return value;
}
export function compareReceipts(desktop: SurfaceReceipt, wasm: SurfaceReceipt): { parity: "MATCH" | "MISMATCH"; differences: string[] } {
const left = coreReceipt(desktop);
const right = coreReceipt(wasm);
const differences = Object.keys(left).filter((key) => JSON.stringify(stable(left[key])) !== JSON.stringify(stable(right[key]))).map((key) => `${key}: desktop=${JSON.stringify(stable(left[key]))} wasm=${JSON.stringify(stable(right[key]))}`);
return { parity: differences.length === 0 ? "MATCH" : "MISMATCH", differences };
}
export async function runSharedCase(command: unknown, mainRevision: number, executors: { desktop: SurfaceCaseOptions["execute"]; wasm: SurfaceCaseOptions["execute"] }): Promise<SharedCaseReceipt> {
const desktop = await runDesktopCase({ command, mainRevision, execute: executors.desktop });
const wasm = await runWasmCase({ command, mainRevision, execute: executors.wasm });
const comparison = compareReceipts(desktop, wasm);
return {
schemaVersion: CORRECTIVE_CASE_SCHEMA,
requestId: desktop.requestId,
commandType: desktop.commandType,
desktop,
wasm,
...comparison,
};
}