import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { execFileSync } from "node:child_process"; import { fileURLToPath } from "node:url"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const abs = (file) => path.join(root, file); const json = (file) => JSON.parse(fs.readFileSync(abs(file), "utf8")); const write = (file, value) => fs.writeFileSync(abs(file), `${JSON.stringify(value, null, 2)}\n`); const sha = (file) => crypto.createHash("sha256").update(fs.readFileSync(abs(file))).digest("hex"); const argIndex = process.argv.indexOf("--task"); const task = argIndex >= 0 ? process.argv[argIndex + 1] : null; if (!task) throw new Error("usage: node tools/web/finalize-wbv2-module-batch.mjs --task "); const fullInventoryAudit = process.argv.includes("--full-inventory") || process.env.WBV2_FULL_SOURCE_AUDIT === "1"; const manifestPath = `tests/golden/${task}/manifest.json`; const manifest = json(manifestPath); const receiptPath = manifest.artifacts.moduleReceipt.path; const statusPath = manifest.artifacts.status.path; const taskPlan = json(manifest.artifacts.plan.path); const taskRecord = taskPlan.tasks.find((candidate) => candidate.id === task); const bindingDelta = taskRecord?.module?.coverageBindings?.length ?? 0; execFileSync(process.execPath, [abs("tools/web/bind-wbv2-module-coverage.mjs"), "--task", task], { cwd: root, stdio: "inherit" }); const inventory = json("tests/golden/WBV2/dynamic-inventory.json"); const verifiedUnits = inventory.coverage.verifiedUnits; const unverifiedUnits = inventory.coverage.unverifiedUnits; const receipt = json(receiptPath); if (receipt.inventory?.dynamic) { receipt.inventory.dynamic.verifiedUnits = verifiedUnits; receipt.inventory.dynamic.unverifiedUnits = unverifiedUnits; receipt.inventory.dynamic.sha256 = sha("tests/golden/WBV2/dynamic-inventory.json"); } write(receiptPath, receipt); let status = fs.readFileSync(abs(statusPath), "utf8"); status = status.replace(/verified `\d+`/g, `verified \`${verifiedUnits}\``).replace(/unverified `\d+`/g, `unverified \`${unverifiedUnits}\``); if (!status.includes("this-segment binding delta:")) status = status.replace(/(dynamic source inventory:[^\n]*)/, `$1; this-segment binding delta: \`${bindingDelta}\``); fs.writeFileSync(abs(statusPath), status); for (const artifact of Object.values(manifest.artifacts ?? {})) { if (artifact?.path && fs.existsSync(abs(artifact.path))) artifact.sha256 = sha(artifact.path); } manifest.assertions.dynamicVerifiedUnits = verifiedUnits; manifest.assertions.dynamicUnverifiedUnits = unverifiedUnits; write(manifestPath, manifest); if (fullInventoryAudit) { execFileSync(process.execPath, [abs("tools/web/generate-source-dynamic-inventory.mjs"), "--output", abs("tests/golden/WBV2/dynamic-inventory.json")], { cwd: root, stdio: "inherit" }); } const inventoryPath = "tests/golden/WBV2/dynamic-inventory.json"; const checkReportPath = "tests/golden/WBV2/dynamic-inventory-check-report.json"; const inventoryManifestPath = "tests/golden/WBV2/dynamic-inventory-manifest.json"; const coverageManifestPath = "tests/golden/WBV2/dynamic-coverage-manifest.json"; const bindingsPath = "tests/golden/WBV2/dynamic-coverage-bindings.json"; const auditPath = "tests/golden/WBV2/dynamic-coverage-audit.json"; // The checker validates manifest hashes before it rewrites its compact report. // Bind the newly generated inventory and the previous report first, then run // the checker once, and finally bind the report it produced. const inventoryManifest = json(inventoryManifestPath); inventoryManifest.artifacts.checker.sha256 = sha("tools/web/check-source-dynamic-inventory.mjs"); inventoryManifest.artifacts.inventory.sha256 = sha(inventoryPath); inventoryManifest.artifacts.report.sha256 = sha(checkReportPath); write(inventoryManifestPath, inventoryManifest); const inventoryCheckArgs = fullInventoryAudit ? [] : ["--task", task]; execFileSync(process.execPath, [abs("tools/web/check-source-dynamic-inventory.mjs"), ...inventoryCheckArgs], { cwd: root, stdio: "inherit" }); const refreshedInventoryManifest = json(inventoryManifestPath); refreshedInventoryManifest.artifacts.inventory.sha256 = sha(inventoryPath); refreshedInventoryManifest.artifacts.report.sha256 = sha(checkReportPath); write(inventoryManifestPath, refreshedInventoryManifest); const coverageManifest = json(coverageManifestPath); coverageManifest.artifacts.inventory.sha256 = sha(inventoryPath); coverageManifest.artifacts.inventoryManifest.sha256 = sha(inventoryManifestPath); coverageManifest.artifacts.bindings.sha256 = sha(bindingsPath); coverageManifest.artifacts.report.sha256 = sha(auditPath); coverageManifest.coverage = { ...coverageManifest.coverage, unitCount: inventory.summary.unitCount, verifiedUnits, unverifiedUnits, sourceTreeSha256: inventory.authority.sourceTreeSha256 }; write(coverageManifestPath, coverageManifest); process.stdout.write(`wbv2-module-batch-finalized task=${task} verified=${verifiedUnits} unverified=${unverifiedUnits} inventoryAudit=${fullInventoryAudit ? "full" : "batch"}\n`);