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 root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const inventoryPath = path.join(root, "tests/golden/M12-05A/format-inventory.json"); const parentPath = path.join(root, "tests/golden/M12-05D/runtime-receipts.json"); const boundPath = path.join(root, "tests/golden/M12-05E/bound-runtime-receipts.json"); const protocolPath = path.join(root, "web/protocol/io-format-receipt-binding.ts"); const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "io-format-receipt-bindings-")); const hashBytes = (value) => crypto.createHash("sha256").update(value).digest("hex"); const canonical = (value) => value === null || typeof value !== "object" ? JSON.stringify(value) : Array.isArray(value) ? `[${value.map(canonical).join(",")}]` : `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`; const hash = (value) => hashBytes(canonical(value)); try { const inventoryBytes = fs.readFileSync(inventoryPath); const parentBytes = fs.readFileSync(parentPath); const boundBytes = fs.readFileSync(boundPath); const inventory = JSON.parse(inventoryBytes); const parent = JSON.parse(parentBytes); const bound = JSON.parse(boundBytes); const transpiled = ts.transpileModule(fs.readFileSync(protocolPath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: protocolPath, reportDiagnostics: true }); assert.deepEqual(transpiled.diagnostics, []); const modulePath = path.join(temporary, "protocol.mjs"); fs.writeFileSync(modulePath, transpiled.outputText); const protocol = await import(pathToFileURL(modulePath)); const parsed = protocol.validateIOFormatBoundReceiptSet(bound, hashBytes(parentBytes), hashBytes(inventoryBytes)); assert.equal(parsed.receipts.length, 14); const runtimeSha256 = hash(inventory.runtime); for (const receipt of parsed.receipts) { const source = inventory.formats.find((entry) => entry.format === receipt.format)[receipt.operation.toLowerCase()]; assert.equal(receipt.sourceSha256, hash({ format: receipt.format, family: receipt.family, operation: receipt.operation, operator: receipt.operator, registered: receipt.registered, rnaIdentifier: receipt.rnaIdentifier })); assert.equal(receipt.settingsSha256, hash({ buildOption: receipt.buildOption, buildOptionEnabled: receipt.buildOptionEnabled, variants: receipt.variants, extensions: receipt.extensions, properties: source.properties })); assert.equal(receipt.runtimeSha256, runtimeSha256); } const regenerated = path.join(temporary, "bound-runtime-receipts.json"); execFileSync(process.execPath, [path.join(root, "tools/web/generate-io-format-receipt-bindings.mjs"), "--output", regenerated], { cwd: root }); assert.deepEqual(fs.readFileSync(regenerated), boundBytes); process.stdout.write("io-format-receipt-bindings-ok receipts=14 source-settings-runtime=bound deterministic=true\n"); } finally { fs.rmSync(temporary, { recursive: true, force: true }); }