export const RENDER_COMPOSITOR_MEDIA_RECOVERY_SCHEMA_VERSION = 1 as const; export const RENDER_COMPOSITOR_MEDIA_DOMAINS = ["RENDER", "COMPOSITOR", "MEDIA"] as const; export type RenderCompositorMediaDomain = typeof RENDER_COMPOSITOR_MEDIA_DOMAINS[number]; const SHA256 = /^[a-f0-9]{64}$/; const CANCELLATION_CODES = { RENDER: "OPEN_CANCELLED", COMPOSITOR: "COMPOSITOR_CANCELLED", MEDIA: "SEQUENCER_CANCELLED", } as const; const BUDGET_CODES = { RENDER: "GPU_TEXTURE_BUDGET_EXCEEDED", COMPOSITOR: "COMPOSITOR_BUDGET_EXCEEDED", MEDIA: "SEQUENCER_BUDGET_EXCEEDED", } as const; export interface RenderCompositorMediaRecoveryEvidenceIR { schemaVersion: typeof RENDER_COMPOSITOR_MEDIA_RECOVERY_SCHEMA_VERSION; domain: RenderCompositorMediaDomain; source: { byteLength: number; sha256: string; }; cancellation: { status: "CANCELLED"; code: typeof CANCELLATION_CODES[RenderCompositorMediaDomain]; publishedResults: 0; temporaryResourcesAfter: 0; }; restart: { status: "RECOVERED"; generationBefore: 1; generationAfter: 2; identityBefore: string; identityAfter: string; outputSha256Before: string; outputSha256After: string; }; budget: { status: "BLOCKED"; code: typeof BUDGET_CODES[RenderCompositorMediaDomain]; retainedIdentityHash: string; temporaryResourcesAfter: 0; }; release: { status: "RELEASED"; releasedBytes: number; releasedResources: number; resourcesAfter: 0; }; recovery: { status: "RECOVERED"; identityHash: string; outputSha256: string; outputBytes: number; visibleUnits: number; }; } function record(value: unknown, label: string): Record { if (typeof value !== "object" || value === null || Array.isArray(value)) { throw new Error(`M11_RECOVERY_INVALID: ${label} must be an object`); } return value as Record; } function exact(value: Record, fields: readonly string[], label: string): void { const allowed = new Set(fields); const extra = Object.keys(value).find((field) => !allowed.has(field)); if (extra) throw new Error(`M11_RECOVERY_INVALID: ${label}.${extra} is undeclared`); } function integer(value: unknown, label: string, minimum = 0): number { if (!Number.isSafeInteger(value) || (value as number) < minimum) { throw new Error(`M11_RECOVERY_INVALID: ${label} must be a safe integer >= ${minimum}`); } return value as number; } function digest(value: unknown, label: string): string { if (typeof value !== "string" || !SHA256.test(value)) { throw new Error(`M11_RECOVERY_INVALID: ${label} must be a SHA-256 digest`); } return value; } function zero(value: unknown, label: string): 0 { if (value !== 0) throw new Error(`M11_RECOVERY_INVALID: ${label} must be zero`); return 0; } export function parseRenderCompositorMediaRecoveryEvidence(value: unknown): RenderCompositorMediaRecoveryEvidenceIR { const input = record(value, "evidence"); exact(input, ["schemaVersion", "domain", "source", "cancellation", "restart", "budget", "release", "recovery"], "evidence"); if (input.schemaVersion !== RENDER_COMPOSITOR_MEDIA_RECOVERY_SCHEMA_VERSION) { throw new Error("M11_RECOVERY_INVALID: unsupported schema version"); } if (!RENDER_COMPOSITOR_MEDIA_DOMAINS.includes(input.domain as RenderCompositorMediaDomain)) { throw new Error("M11_RECOVERY_INVALID: domain is invalid"); } const domain = input.domain as RenderCompositorMediaDomain; const source = record(input.source, "source"); exact(source, ["byteLength", "sha256"], "source"); const sourceByteLength = integer(source.byteLength, "source.byteLength", 1); const sourceSha256 = digest(source.sha256, "source.sha256"); const cancellation = record(input.cancellation, "cancellation"); exact(cancellation, ["status", "code", "publishedResults", "temporaryResourcesAfter"], "cancellation"); if (cancellation.status !== "CANCELLED" || cancellation.code !== CANCELLATION_CODES[domain]) { throw new Error(`M11_RECOVERY_INVALID: ${domain} cancellation contract is invalid`); } zero(cancellation.publishedResults, "cancellation.publishedResults"); zero(cancellation.temporaryResourcesAfter, "cancellation.temporaryResourcesAfter"); const restart = record(input.restart, "restart"); exact(restart, ["status", "generationBefore", "generationAfter", "identityBefore", "identityAfter", "outputSha256Before", "outputSha256After"], "restart"); if (restart.status !== "RECOVERED" || restart.generationBefore !== 1 || restart.generationAfter !== 2) { throw new Error("M11_RECOVERY_INVALID: restart must advance exactly from generation 1 to 2"); } const identityBefore = digest(restart.identityBefore, "restart.identityBefore"); const identityAfter = digest(restart.identityAfter, "restart.identityAfter"); const outputSha256Before = digest(restart.outputSha256Before, "restart.outputSha256Before"); const outputSha256After = digest(restart.outputSha256After, "restart.outputSha256After"); if (identityBefore !== identityAfter || outputSha256Before !== outputSha256After) { throw new Error("M11_RECOVERY_INVALID: restart changed committed identity or output"); } const budget = record(input.budget, "budget"); exact(budget, ["status", "code", "retainedIdentityHash", "temporaryResourcesAfter"], "budget"); if (budget.status !== "BLOCKED" || budget.code !== BUDGET_CODES[domain]) { throw new Error(`M11_RECOVERY_INVALID: ${domain} budget contract is invalid`); } const retainedIdentityHash = digest(budget.retainedIdentityHash, "budget.retainedIdentityHash"); if (retainedIdentityHash !== identityAfter) { throw new Error("M11_RECOVERY_INVALID: budget failure changed committed identity"); } zero(budget.temporaryResourcesAfter, "budget.temporaryResourcesAfter"); const release = record(input.release, "release"); exact(release, ["status", "releasedBytes", "releasedResources", "resourcesAfter"], "release"); if (release.status !== "RELEASED") throw new Error("M11_RECOVERY_INVALID: release.status"); const releasedBytes = integer(release.releasedBytes, "release.releasedBytes", 1); const releasedResources = integer(release.releasedResources, "release.releasedResources", 1); zero(release.resourcesAfter, "release.resourcesAfter"); const recovery = record(input.recovery, "recovery"); exact(recovery, ["status", "identityHash", "outputSha256", "outputBytes", "visibleUnits"], "recovery"); if (recovery.status !== "RECOVERED") throw new Error("M11_RECOVERY_INVALID: recovery.status"); const recoveryIdentity = digest(recovery.identityHash, "recovery.identityHash"); const recoveryOutput = digest(recovery.outputSha256, "recovery.outputSha256"); if (recoveryIdentity !== identityAfter || recoveryOutput !== outputSha256After) { throw new Error("M11_RECOVERY_INVALID: recovered identity or output does not match generation 2"); } const outputBytes = integer(recovery.outputBytes, "recovery.outputBytes", 1); const visibleUnits = integer(recovery.visibleUnits, "recovery.visibleUnits", 1); return { schemaVersion: RENDER_COMPOSITOR_MEDIA_RECOVERY_SCHEMA_VERSION, domain, source: { byteLength: sourceByteLength, sha256: sourceSha256 }, cancellation: { status: "CANCELLED", code: CANCELLATION_CODES[domain], publishedResults: 0, temporaryResourcesAfter: 0, }, restart: { status: "RECOVERED", generationBefore: 1, generationAfter: 2, identityBefore, identityAfter, outputSha256Before, outputSha256After, }, budget: { status: "BLOCKED", code: BUDGET_CODES[domain], retainedIdentityHash, temporaryResourcesAfter: 0, }, release: { status: "RELEASED", releasedBytes, releasedResources, resourcesAfter: 0 }, recovery: { status: "RECOVERED", identityHash: recoveryIdentity, outputSha256: recoveryOutput, outputBytes, visibleUnits }, }; } export function parseRenderCompositorMediaRecoverySuite(value: unknown): RenderCompositorMediaRecoveryEvidenceIR[] { if (!Array.isArray(value) || value.length !== RENDER_COMPOSITOR_MEDIA_DOMAINS.length) { throw new Error("M11_RECOVERY_INVALID: suite must contain exactly three domains"); } const reports = value.map(parseRenderCompositorMediaRecoveryEvidence); if (new Set(reports.map((report) => report.domain)).size !== RENDER_COMPOSITOR_MEDIA_DOMAINS.length) { throw new Error("M11_RECOVERY_INVALID: suite contains duplicate domains"); } return RENDER_COMPOSITOR_MEDIA_DOMAINS.map((domain) => reports.find((report) => report.domain === domain)!); }