Advance WebGPU volume and bounded workflows

This commit is contained in:
mes123456
2026-08-14 18:08:29 -04:00
parent 3da1dfc804
commit 68d50f810f
119 changed files with 9028 additions and 430 deletions

View File

@@ -4,7 +4,9 @@ import type { ErrorCode } from "./error";
export const SCRIPTING_PLATFORM_SCHEMA = 1 as const;
export const SCRIPT_SOURCE_SCHEMA = 1 as const;
export const SCRIPTING_BUDGET = { maxScripts: 1_024, maxPermissions: 64, maxDependencies: 128, maxCpuMs: 60_000, maxMemoryBytes: 512 * 1024 * 1024, maxWallMs: 300_000, maxSourceBytes: 1024 * 1024, maxSourceLines: 65_536 } as const;
export const SCRIPT_EXECUTION_AUDIT_SCHEMA = 1 as const;
export const SCRIPT_EXECUTION_AUDIT_LOG_SCHEMA = 1 as const;
export const SCRIPTING_BUDGET = { maxScripts: 1_024, maxPermissions: 64, maxDependencies: 128, maxCpuMs: 60_000, maxMemoryBytes: 512 * 1024 * 1024, maxWallMs: 300_000, maxSourceBytes: 1024 * 1024, maxSourceLines: 65_536, maxAuditEntries: 65_536 } as const;
export const SCRIPT_PERMISSIONS = ["READ_MAIN", "WRITE_MAIN", "READ_ASSET", "WRITE_ASSET", "SUBMIT_SERVER_JOB"] as const;
export type ScriptPermission = typeof SCRIPT_PERMISSIONS[number];
@@ -44,6 +46,30 @@ export interface ScriptSourceIR {
}
export interface ScriptSourceInventoryIR { schemaVersion: typeof SCRIPT_SOURCE_SCHEMA; sources: ScriptSourceIR[] }
export interface ServerScriptJobIR { scriptId: string; sourceSha256: string; inputBlendSha256: string; outputBlendSha256?: string; status: "QUEUED" | "RUNNING" | "COMPLETE" | "FAILED" }
export interface ScriptExecutionAuditIR {
schemaVersion: typeof SCRIPT_EXECUTION_AUDIT_SCHEMA;
requestId: string;
requestedAt: string;
scriptId: string;
sourceSha256: string;
manifestSha256: string;
permissions: ScriptPermission[];
budget: { cpuMs: number; memoryBytes: number; wallMs: number };
approvedKey: boolean;
decision: "DENY";
reason: "SCRIPT_SIGNATURE_INVALID" | "SCRIPT_SANDBOX_UNAVAILABLE";
requestSha256: string;
}
export interface ScriptExecutionAuditLogEntryIR {
sequence: number;
previousEntrySha256: string | null;
audit: ScriptExecutionAuditIR;
entrySha256: string;
}
export interface ScriptExecutionAuditLogIR {
schemaVersion: typeof SCRIPT_EXECUTION_AUDIT_LOG_SCHEMA;
entries: ScriptExecutionAuditLogEntryIR[];
}
export class ScriptingPlatformValidationError extends Error {
readonly code: ErrorCode;
@@ -56,6 +82,122 @@ function text(value: unknown, name: string, maximum = 256): string { if (typeof
function digest(value: unknown, name: string): string { if (typeof value !== "string" || !SHA256.test(value)) throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", `${name} must be a lowercase SHA-256 digest`); return value; }
function path(value: unknown, name: string): string { try { return normalizeProjectAssetPath(text(value, name, 2048)); } catch { throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", `${name} is outside the project`); } }
function integer(value: unknown, name: string, minimum: number, maximum: number): number { if (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum || value > maximum) throw new ScriptingPlatformValidationError("SCRIPT_BUDGET_EXCEEDED", `${name} exceeds the budget`); return 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: string): Promise<string> {
const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
}
function canonicalAuditRequest(audit: Omit<ScriptExecutionAuditIR, "schemaVersion" | "requestSha256">): Omit<ScriptExecutionAuditIR, "schemaVersion" | "requestSha256"> {
return { ...audit, permissions: [...audit.permissions].sort(), budget: { ...audit.budget } };
}
function isoDate(value: unknown, name: string): string {
const result = text(value, name, 64); const date = new Date(result);
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(result) || !Number.isFinite(date.getTime()) || date.toISOString() !== result) throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", `${name} is invalid`);
return result;
}
function auditRequestId(value: unknown, name: string): string {
const result = text(value, name);
if (!/^[-A-Za-z0-9:_./]{1,256}$/.test(result)) throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", `${name} is invalid`);
return result;
}
function canonicalManifest(manifest: ScriptingManifestIR): ScriptingManifestIR {
return {
schemaVersion: manifest.schemaVersion,
scripts: manifest.scripts
.map((script) => ({ ...script, permissions: [...script.permissions].sort(), dependencies: script.dependencies.map((dependency) => ({ ...dependency })).sort((a, b) => a.id.localeCompare(b.id)) }))
.sort((a, b) => a.id.localeCompare(b.id)),
};
}
export async function createScriptExecutionAudit(
manifest: unknown,
scriptId: string,
approvedKeyIds: ReadonlySet<string>,
options: { requestId?: string; requestedAt?: string } = {},
): Promise<ScriptExecutionAuditIR> {
const parsed = parseScriptingManifest(manifest);
const script = parsed.scripts.find((item) => item.id === scriptId);
if (!script) throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", `Unknown script ${scriptId}`);
const requestId = auditRequestId(options.requestId ?? `script-audit:${scriptId}:${Date.now()}`, "requestId");
const requestedAt = isoDate(options.requestedAt ?? new Date().toISOString(), "requestedAt");
const approvedKey = approvedKeyIds.has(script.keyId);
const reason = approvedKey ? "SCRIPT_SANDBOX_UNAVAILABLE" : "SCRIPT_SIGNATURE_INVALID";
const manifestSha256 = await sha256(stableJSON(canonicalManifest(parsed)));
const request = canonicalAuditRequest({ requestId, requestedAt, scriptId, sourceSha256: script.sourceSha256, manifestSha256, permissions: [...script.permissions], budget: { cpuMs: script.cpuMs, memoryBytes: script.memoryBytes, wallMs: script.wallMs }, approvedKey, decision: "DENY", reason });
const requestSha256 = await sha256(stableJSON(request));
return Object.freeze({
schemaVersion: SCRIPT_EXECUTION_AUDIT_SCHEMA,
requestId,
requestedAt,
scriptId,
sourceSha256: script.sourceSha256,
manifestSha256,
permissions: Object.freeze([...script.permissions].sort()) as unknown as ScriptPermission[],
budget: Object.freeze({ cpuMs: script.cpuMs, memoryBytes: script.memoryBytes, wallMs: script.wallMs }),
approvedKey,
decision: "DENY",
reason,
requestSha256,
});
}
export async function parseScriptExecutionAudit(value: unknown): Promise<ScriptExecutionAuditIR> {
if (!record(value) || value.schemaVersion !== SCRIPT_EXECUTION_AUDIT_SCHEMA || !Array.isArray(value.permissions) || !record(value.budget)) throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", "Script execution audit is invalid");
const permissions = value.permissions.map((permission, index) => text(permission, `audit.permissions[${index}]`, 64) as ScriptPermission);
if (permissions.length > SCRIPTING_BUDGET.maxPermissions || new Set(permissions).size !== permissions.length || permissions.some((permission) => !SCRIPT_PERMISSIONS.includes(permission)) || permissions.some((permission, index) => index > 0 && permissions[index - 1] > permission)) throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", "Audit permissions are invalid or not canonical");
if (typeof value.approvedKey !== "boolean" || value.decision !== "DENY" || !["SCRIPT_SIGNATURE_INVALID", "SCRIPT_SANDBOX_UNAVAILABLE"].includes(value.reason as string) || value.reason !== (value.approvedKey ? "SCRIPT_SANDBOX_UNAVAILABLE" : "SCRIPT_SIGNATURE_INVALID")) throw new ScriptingPlatformValidationError("SCRIPT_POLICY_DENIED", "Audit decision is inconsistent with the default-deny policy");
const request = canonicalAuditRequest({
requestId: auditRequestId(value.requestId, "audit.requestId"),
requestedAt: isoDate(value.requestedAt, "audit.requestedAt"),
scriptId: text(value.scriptId, "audit.scriptId"),
sourceSha256: digest(value.sourceSha256, "audit.sourceSha256"),
manifestSha256: digest(value.manifestSha256, "audit.manifestSha256"),
permissions,
budget: { cpuMs: integer(value.budget.cpuMs, "audit.budget.cpuMs", 1, SCRIPTING_BUDGET.maxCpuMs), memoryBytes: integer(value.budget.memoryBytes, "audit.budget.memoryBytes", 1, SCRIPTING_BUDGET.maxMemoryBytes), wallMs: integer(value.budget.wallMs, "audit.budget.wallMs", 1, SCRIPTING_BUDGET.maxWallMs) },
approvedKey: value.approvedKey,
decision: "DENY",
reason: value.reason as ScriptExecutionAuditIR["reason"],
});
const requestSha256 = digest(value.requestSha256, "audit.requestSha256");
if (await sha256(stableJSON(request)) !== requestSha256) throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", "Audit request digest does not match its canonical content");
return { schemaVersion: SCRIPT_EXECUTION_AUDIT_SCHEMA, ...request, requestSha256 };
}
export async function parseScriptExecutionAuditLog(value: unknown): Promise<ScriptExecutionAuditLogIR> {
if (!record(value) || value.schemaVersion !== SCRIPT_EXECUTION_AUDIT_LOG_SCHEMA || !Array.isArray(value.entries)) throw new ScriptingPlatformValidationError("PROTOCOL_MISMATCH", "Unsupported script execution audit log schema");
if (value.entries.length > SCRIPTING_BUDGET.maxAuditEntries) throw new ScriptingPlatformValidationError("SCRIPT_BUDGET_EXCEEDED", "Script audit log exceeds the entry budget");
const entries: ScriptExecutionAuditLogEntryIR[] = []; const requestIds = new Set<string>();
for (const [index, entryValue] of value.entries.entries()) {
if (!record(entryValue) || !record(entryValue.audit) || entryValue.sequence !== index + 1) throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", `Audit log entry ${index} has an invalid sequence`);
const audit = await parseScriptExecutionAudit(entryValue.audit);
if (requestIds.has(audit.requestId)) throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", `Audit request ${audit.requestId} is replayed`);
if (entries.length > 0 && audit.requestedAt <= entries[entries.length - 1].audit.requestedAt) throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", "Audit log timestamps are not strictly increasing");
const previousEntrySha256 = index === 0 ? null : entries[index - 1].entrySha256;
if (entryValue.previousEntrySha256 !== previousEntrySha256) throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", `Audit log entry ${index} breaks the hash chain`);
const entrySha256 = digest(entryValue.entrySha256, `entries[${index}].entrySha256`);
if (await sha256(stableJSON({ sequence: index + 1, previousEntrySha256, audit })) !== entrySha256) throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", `Audit log entry ${index} digest does not match`);
requestIds.add(audit.requestId); entries.push({ sequence: index + 1, previousEntrySha256, audit, entrySha256 });
}
return { schemaVersion: SCRIPT_EXECUTION_AUDIT_LOG_SCHEMA, entries };
}
export async function appendScriptExecutionAudit(logValue: unknown, auditValue: unknown): Promise<ScriptExecutionAuditLogIR> {
const log = await parseScriptExecutionAuditLog(logValue); const audit = await parseScriptExecutionAudit(auditValue);
if (log.entries.length >= SCRIPTING_BUDGET.maxAuditEntries) throw new ScriptingPlatformValidationError("SCRIPT_BUDGET_EXCEEDED", "Script audit log exceeds the entry budget");
if (log.entries.some((entry) => entry.audit.requestId === audit.requestId)) throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", `Audit request ${audit.requestId} is replayed`);
const previous = log.entries.at(-1);
if (previous && audit.requestedAt <= previous.audit.requestedAt) throw new ScriptingPlatformValidationError("SCRIPT_MANIFEST_INVALID", "Audit log timestamps must be strictly increasing");
const sequence = log.entries.length + 1; const previousEntrySha256 = previous?.entrySha256 ?? null;
const entrySha256 = await sha256(stableJSON({ sequence, previousEntrySha256, audit }));
return parseScriptExecutionAuditLog({ schemaVersion: SCRIPT_EXECUTION_AUDIT_LOG_SCHEMA, entries: [...log.entries, { sequence, previousEntrySha256, audit, entrySha256 }] });
}
export function parseScriptSourceInventory(value: unknown): ScriptSourceInventoryIR {
if (!record(value) || value.schemaVersion !== SCRIPT_SOURCE_SCHEMA || !Array.isArray(value.sources)) throw new ScriptingPlatformValidationError("PROTOCOL_MISMATCH", "Unsupported script source inventory schema");