import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import test from "node:test"; import ts from "typescript"; const root = path.resolve(import.meta.dirname, "../../.."); const sourcePath = path.join(root, "web/protocol/server-render-job.ts"); const source = fs.readFileSync(sourcePath, "utf8").replace('import type { ErrorCode } from "./error";\n', ""); const transpiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true, }); assert.deepEqual(transpiled.diagnostics, []); const jobs = await import("data:text/javascript;base64," + Buffer.from(transpiled.outputText).toString("base64")); const sourceBytes = new TextEncoder().encode("server-render-source-v1").buffer; const build = { version: "5.2.0", buildSha256: "a".repeat(64) }; const settings = { renderEngine: "BLENDER_CYCLES", frameStart: 1, frameEnd: 1, resolutionX: 32, resolutionY: 32, resolutionPercentage: 100, samples: 4, outputMime: "image/png", transparent: false, }; test("M11-06 binds source bytes, Blender build and canonical render settings", async () => { const request = await jobs.createServerRenderJobRequest(sourceBytes, build, settings, { jobId: "render:test", sourceRevision: 7 }); assert.equal(request.sourceBlendByteLength, sourceBytes.byteLength); assert.match(request.sourceBlendSha256, /^[a-f0-9]{64}$/); assert.match(request.settingsSha256, /^[a-f0-9]{64}$/); assert.match(request.requestSha256, /^[a-f0-9]{64}$/); assert.deepEqual(await jobs.verifyServerRenderJobRequest(request, sourceBytes), request); const reordered = await jobs.createServerRenderJobRequest(sourceBytes, build, { transparent: settings.transparent, ...settings }, { jobId: "render:test", sourceRevision: 7 }); assert.equal(reordered.settingsSha256, request.settingsSha256); assert.equal(await jobs.createServerRenderJobKey(request), await jobs.createServerRenderJobKey(reordered)); }); test("M11-06 verifies output hash and rejects every provenance drift", async () => { const request = await jobs.createServerRenderJobRequest(sourceBytes, build, settings, { jobId: "render:verify", sourceRevision: 2 }); const output = Uint8Array.from([0x89, 0x50, 0x4e, 0x47, 0x00, 0x01]).buffer; const result = await jobs.createServerRenderJobResult(request, output); assert.equal(result.status, "SUCCEEDED"); assert.deepEqual(await jobs.verifyServerRenderJobResult(result, request, output), result); await assert.rejects(jobs.verifyServerRenderJobRequest(request, Uint8Array.from([1, 2, 3]).buffer), { code: "SERVER_RENDER_SOURCE_HASH_MISMATCH" }); await assert.rejects(jobs.verifyServerRenderJobResult(result, request, Uint8Array.from([9, 9]).buffer), { code: "SERVER_RENDER_OUTPUT_HASH_MISMATCH" }); await assert.rejects(jobs.verifyServerRenderJobResult({ ...result, sourceBlendSha256: "b".repeat(64) }, request, output), { code: "SERVER_RENDER_BINDING_MISMATCH" }); await assert.rejects(jobs.verifyServerRenderJobRequest({ ...request, blenderBuild: { ...build, buildSha256: "b".repeat(64) } }), { code: "SERVER_RENDER_REQUEST_HASH_MISMATCH" }); await assert.rejects(jobs.createServerRenderJobRequest(sourceBytes, { ...build, version: "5.3.0" }, settings), { code: "SERVER_RENDER_BUILD_INVALID" }); await assert.rejects(jobs.createServerRenderJobRequest(sourceBytes, build, { ...settings, samples: 0 }), { code: "SERVER_RENDER_SETTINGS_INVALID" }); }); test("M11-06 rejects malformed result metadata and settings budget abuse", async () => { await assert.rejects(jobs.createServerRenderJobRequest(sourceBytes, build, { ...settings, payload: "x".repeat(300_000) }), { code: "SERVER_RENDER_SETTINGS_INVALID" }); const request = await jobs.createServerRenderJobRequest(sourceBytes, build, settings); const result = await jobs.createServerRenderJobResult(request, Uint8Array.from([1]).buffer); await assert.rejects(jobs.verifyServerRenderJobResult({ ...result, outputByteLength: result.outputByteLength + 1 }, request), { code: "SERVER_RENDER_RESULT_HASH_MISMATCH" }); await assert.rejects(jobs.verifyServerRenderJobResult({ ...result, outputMime: "image/openexr" }, request), { code: "SERVER_RENDER_OUTPUT_INVALID" }); await assert.rejects(jobs.verifyServerRenderJobResult({ ...result, errorCode: "SERVER_RENDER_FAILED" }, request), { code: "SERVER_RENDER_OUTPUT_INVALID" }); await assert.rejects(jobs.verifyServerRenderJobRequest({ ...request, unexpected: true }), { code: "SERVER_RENDER_REQUEST_INVALID" }); });