import assert from "node:assert/strict"; import crypto from "node:crypto"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { execFileSync } from "node:child_process"; import { fileURLToPath, pathToFileURL } from "node:url"; import ts from "../../web/node_modules/typescript/lib/typescript.js"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const parentPath = path.join(repoRoot, "tests/golden/M12-05E/bound-runtime-receipts.json"); const freshPath = path.join(repoRoot, "tests/golden/M12-05F/fresh-runtime-receipts.json"); const appFreshPath = path.join(repoRoot, "web/app/src/capabilities/io-format-runtime-receipts-freshness.json"); const appExpectedPath = path.join(repoRoot, "web/app/src/capabilities/io-format-runtime-receipts-freshness-expected.json"); const appPath = path.join(repoRoot, "web/app/src/app/App.tsx"); const protocolPath = path.join(repoRoot, "web/protocol/io-format-receipt-freshness.ts"); const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "io-format-receipt-freshness-")); const sha256 = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex"); const stableValue = (value) => Array.isArray(value) ? value.map(stableValue) : value && typeof value === "object" ? Object.fromEntries(Object.keys(value).sort().map((key) => [key, stableValue(value[key])])) : value; const stableSha256 = (value) => sha256(JSON.stringify(stableValue(value))); function transpile(sourcePath, outputName, replacements = []) { const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true }); assert.deepEqual(transpiled.diagnostics, []); let output = transpiled.outputText; for (const [from, to] of replacements) output = output.replaceAll(from, to); const outputPath = path.join(temporary, outputName); fs.writeFileSync(outputPath, output); return outputPath; } try { const parentBytes = fs.readFileSync(parentPath); const bound = JSON.parse(parentBytes); const freshBytes = fs.readFileSync(freshPath); const fresh = JSON.parse(freshBytes); assert.deepEqual(fs.readFileSync(appFreshPath), freshBytes, "App freshness receipt drifted from golden"); const appExpected = JSON.parse(fs.readFileSync(appExpectedPath)); const runtimeModule = transpile(path.join(repoRoot, "web/protocol/io-format-runtime-receipt.ts"), "io-format-runtime-receipt.mjs"); const bindingModule = transpile(path.join(repoRoot, "web/protocol/io-format-receipt-binding.ts"), "io-format-receipt-binding.mjs"); const protocolModule = transpile(protocolPath, "io-format-receipt-freshness.mjs", [["./io-format-receipt-binding\"", "./io-format-receipt-binding.mjs\""], ["./io-format-runtime-receipt\"", "./io-format-runtime-receipt.mjs\""]]); const protocol = await import(pathToFileURL(protocolModule)); const expected = { parentBindingSha256: sha256(parentBytes), parentReceiptSetSha256: bound.parentReceiptSetSha256, inventorySha256: bound.inventorySha256, boundReceiptSetSha256: stableSha256(bound), runtimeSha256: stableSha256(bound.runtime), runtime: bound.runtime, receiptIdentities: bound.receipts, }; assert.equal(fresh.parentBindingSha256, expected.parentBindingSha256); assert.equal(fresh.boundReceiptSetSha256, expected.boundReceiptSetSha256); assert.equal(fresh.runtimeSha256, expected.runtimeSha256); assert.deepEqual(appExpected, { parentBindingSha256: expected.parentBindingSha256, parentReceiptSetSha256: expected.parentReceiptSetSha256, inventorySha256: expected.inventorySha256, boundReceiptSetSha256: expected.boundReceiptSetSha256, runtimeSha256: expected.runtimeSha256, runtime: expected.runtime, receiptIdentities: expected.receiptIdentities, }); const appSource = fs.readFileSync(appPath, "utf8"); assert.match(appSource, /resolveFreshIOFormatRuntimeRoute\(IO_FORMAT_RUNTIME_RECEIPTS, IO_FORMAT_RUNTIME_RECEIPT_EXPECTED/); await protocol.verifyIOFormatReceiptFreshness(fresh, expected); assert.equal(protocol.resolveFreshIOFormatRuntimeRoute(fresh, expected, { format: "GLB", operation: "EXPORT" }).status, "READY"); assert.equal(protocol.resolveFreshIOFormatRuntimeRoute(fresh, expected, { format: "USD", operation: "EXPORT" }).status, "BLOCKED"); const forged = structuredClone(fresh); forged.bound.receipts[0].operator = "forged.operator"; await assert.rejects(() => protocol.verifyIOFormatReceiptFreshness(forged, expected), (error) => error.reason === "RECEIPT_FORGED"); const stale = structuredClone(fresh); stale.bound.inventorySha256 = "a".repeat(64); assert.equal(protocol.resolveFreshIOFormatRuntimeRoute(stale, expected, { format: "GLB", operation: "EXPORT" }).reason, "RECEIPT_STALE"); const crossVersion = structuredClone(fresh); crossVersion.bound.runtime.versionTuple = [5, 3, 0]; assert.equal(protocol.resolveFreshIOFormatRuntimeRoute(crossVersion, expected, { format: "GLB", operation: "EXPORT" }).reason, "RECEIPT_CROSS_VERSION"); const regenerated = path.join(temporary, "fresh-runtime-receipts.json"); execFileSync(process.execPath, [path.join(repoRoot, "tools/web/generate-io-format-receipt-freshness.mjs"), "--output", regenerated], { cwd: repoRoot }); assert.deepEqual(fs.readFileSync(regenerated), freshBytes, "freshness receipt is not deterministic"); process.stdout.write("io-format-receipt-freshness-ok receipts=14 forged=BLOCKED stale=BLOCKED cross-version=BLOCKED deterministic=true\n"); } finally { fs.rmSync(temporary, { recursive: true, force: true }); }