143 lines
8.0 KiB
JavaScript
143 lines
8.0 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
const abs = (file) => path.join(root, file);
|
|
const read = (file) => fs.readFileSync(abs(file), "utf8");
|
|
const json = (file) => JSON.parse(read(file));
|
|
const sha = (file) => crypto.createHash("sha256").update(fs.readFileSync(abs(file))).digest("hex");
|
|
const arg = (name, fallback = null) => {
|
|
const index = process.argv.indexOf(name);
|
|
return index >= 0 ? process.argv[index + 1] : fallback;
|
|
};
|
|
const task = arg("--task");
|
|
if (!task) throw new Error("usage: node tools/web/bind-wbv2-module-coverage.mjs --task <task-id>");
|
|
const manifestPath = `tests/golden/${task}/manifest.json`;
|
|
const manifest = json(manifestPath);
|
|
assert.equal(manifest.status, "done");
|
|
const planPath = manifest.artifacts.plan.path;
|
|
const plan = json(planPath);
|
|
const taskRecord = plan.tasks.find((candidate) => candidate.id === task);
|
|
const module = taskRecord?.module;
|
|
const productionPath = arg("--production", module?.evidencePaths?.[0] ?? `tests/golden/${task}/modifier-reader-batch-production.json`);
|
|
const requestedUnits = (arg("--units") ?? "").split(",").map((value) => value.trim()).filter(Boolean);
|
|
const coverageBindings = requestedUnits.length > 0 ? requestedUnits : module?.coverageBindings ?? [];
|
|
const capabilityId = module?.moduleId ?? arg("--capability", task);
|
|
if (!coverageBindings.length) throw new Error(`task ${task} has no coverageBindings`);
|
|
const production = json(productionPath);
|
|
assert.equal(production.browser, "chromium");
|
|
assert.equal(production.queueMutation, false);
|
|
const inventoryPath = "tests/golden/WBV2/dynamic-inventory.json";
|
|
const inventory = json(inventoryPath);
|
|
const fixturePaths = Array.isArray(production.fixtures) ? production.fixtures : production.fixture ? [production.fixture] : [];
|
|
const validateEvidence = (evidencePath) => {
|
|
const evidence = json(evidencePath);
|
|
assert.equal(evidence.browser, "chromium");
|
|
assert.equal(evidence.queueMutation, false);
|
|
const paths = Array.isArray(evidence.fixtures) ? evidence.fixtures : evidence.fixture ? [evidence.fixture] : [];
|
|
assert.ok(paths.length, `production evidence has no fixture(s): ${evidencePath}`);
|
|
const entries = Array.isArray(evidence.fixtures) ? paths.map((fixture) => evidence[fixture]) : [evidence];
|
|
for (const entry of entries) {
|
|
assert.ok(entry && typeof entry === "object", "fixture result is missing");
|
|
assert.equal(entry.error, undefined);
|
|
if (entry.saveReopenExact !== undefined) assert.equal(entry.saveReopenExact, true);
|
|
if (entry.deterministicExact !== undefined) assert.equal(entry.deterministicExact, true);
|
|
if (entry.exact !== undefined) assert.equal(entry.exact, true);
|
|
}
|
|
if (Array.isArray(evidence.fixtures)) {
|
|
assert.equal(typeof evidence.fixtureSha256, "object");
|
|
for (const fixture of paths) assert.equal(evidence.fixtureSha256[fixture], sha(fixture), `fixture hash drift: ${fixture}`);
|
|
} else {
|
|
assert.equal(evidence.fixtureSha256, sha(evidence.fixture));
|
|
}
|
|
return evidence;
|
|
};
|
|
if (production.reusedEvidence?.length) {
|
|
assert.ok(production.dependencyProofs?.length, "dependency reuse evidence has no proofs");
|
|
for (const evidence of production.reusedEvidence) {
|
|
assert.ok(evidence.path, "reused evidence path is missing");
|
|
validateEvidence(evidence.path);
|
|
assert.equal(evidence.sha256, sha(evidence.path), `reused evidence hash drift: ${evidence.path}`);
|
|
}
|
|
} else {
|
|
assert.ok(fixturePaths.length, "production evidence has no fixture(s)");
|
|
validateEvidence(productionPath);
|
|
}
|
|
const bindingsPath = "tests/golden/WBV2/dynamic-coverage-bindings.json";
|
|
const existing = fs.existsSync(abs(bindingsPath)) ? json(bindingsPath) : [];
|
|
const byId = new Map(existing.map((binding) => [binding.unitId, binding]));
|
|
for (const unitId of coverageBindings) {
|
|
const unit = inventory.units.find((candidate) => candidate.id === unitId);
|
|
assert.ok(unit, `dynamic unit missing: ${unitId}`);
|
|
if (unit.coverageStatus === "VERIFIED") {
|
|
assert.equal(byId.get(unitId)?.task, task, `dynamic unit already belongs to another task: ${unitId}`);
|
|
}
|
|
const sourceLocation = unit.locations?.[0];
|
|
assert.ok(sourceLocation?.path, `source location missing: ${unitId}`);
|
|
byId.set(unitId, {
|
|
unitId,
|
|
task,
|
|
capabilityId,
|
|
sourceLocation,
|
|
evidenceManifest: { path: manifestPath, sha256: sha(manifestPath) },
|
|
productionEvidence: { path: productionPath, sha256: sha(productionPath) },
|
|
sourceTreeSha256: inventory.authority.sourceTreeSha256,
|
|
status: "VERIFIED",
|
|
});
|
|
}
|
|
const bindings = [...byId.values()];
|
|
const verifiedIds = new Set(bindings.map((binding) => binding.unitId));
|
|
const updatedUnits = inventory.units.map((unit) => verifiedIds.has(unit.id)
|
|
? { ...unit, coverageStatus: "VERIFIED", coverage: bindings.find((binding) => binding.unitId === unit.id) }
|
|
: { ...unit, coverageStatus: "UNVERIFIED", ...(unit.coverage ? {} : {}) });
|
|
const verifiedUnits = updatedUnits.filter((unit) => unit.coverageStatus === "VERIFIED").length;
|
|
const updated = {
|
|
...inventory,
|
|
coverage: { ...inventory.coverage, verifiedUnits, unverifiedUnits: inventory.summary.unitCount - verifiedUnits, sourceMappingRequired: inventory.summary.unitCount, inventoryMutation: "explicit_source_case_bindings_only", lastReconciledAt: "2026-09-01" },
|
|
units: updatedUnits,
|
|
coverageLedger: bindings,
|
|
};
|
|
fs.writeFileSync(abs(bindingsPath), `${JSON.stringify(bindings, null, 2)}\n`);
|
|
fs.writeFileSync(abs(inventoryPath), `${JSON.stringify(updated, null, 2)}\n`);
|
|
const coverageManifestPath = "tests/golden/WBV2/dynamic-coverage-manifest.json";
|
|
if (fs.existsSync(abs(coverageManifestPath))) {
|
|
const coverageManifest = json(coverageManifestPath);
|
|
coverageManifest.artifacts.inventory.sha256 = sha(inventoryPath);
|
|
coverageManifest.artifacts.bindings.sha256 = sha(bindingsPath);
|
|
coverageManifest.coverage = { ...coverageManifest.coverage, unitCount: inventory.summary.unitCount, verifiedUnits, unverifiedUnits: inventory.summary.unitCount - verifiedUnits, boundUnitDelta: coverageBindings.length, sourceTreeSha256: inventory.authority.sourceTreeSha256 };
|
|
fs.writeFileSync(abs(coverageManifestPath), `${JSON.stringify(coverageManifest, null, 2)}\n`);
|
|
}
|
|
const inventoryManifestPath = "tests/golden/WBV2/dynamic-inventory-manifest.json";
|
|
if (fs.existsSync(abs(inventoryManifestPath))) {
|
|
const inventoryManifest = json(inventoryManifestPath);
|
|
if (inventoryManifest.artifacts?.inventory) inventoryManifest.artifacts.inventory.sha256 = sha(inventoryPath);
|
|
if (inventoryManifest.inventory) {
|
|
inventoryManifest.inventory.verifiedUnits = verifiedUnits;
|
|
inventoryManifest.inventory.unverifiedUnits = inventory.summary.unitCount - verifiedUnits;
|
|
inventoryManifest.inventory.boundCaseDelta = coverageBindings.length;
|
|
}
|
|
fs.writeFileSync(abs(inventoryManifestPath), `${JSON.stringify(inventoryManifest, null, 2)}\n`);
|
|
}
|
|
const auditPath = "tests/golden/WBV2/dynamic-coverage-audit.json";
|
|
if (fs.existsSync(abs(auditPath))) {
|
|
const audit = json(auditPath);
|
|
const coverageByKind = {};
|
|
const coverageByFamily = {};
|
|
for (const unit of updatedUnits) {
|
|
const kind = coverageByKind[unit.kind] ??= { verified: 0, unverified: 0 };
|
|
const family = coverageByFamily[unit.family] ??= { verified: 0, unverified: 0 };
|
|
const key = unit.coverageStatus === "VERIFIED" ? "verified" : "unverified";
|
|
kind[key] += 1;
|
|
family[key] += 1;
|
|
}
|
|
audit.dynamicInventory = { ...(audit.dynamicInventory ?? {}), path: inventoryPath, afterSha256: sha(inventoryPath), unitCount: inventory.summary.unitCount, verifiedUnits, unverifiedUnits: inventory.summary.unitCount - verifiedUnits };
|
|
audit.explicitBindings = bindings;
|
|
audit.coverageByKind = coverageByKind;
|
|
audit.coverageByFamily = coverageByFamily;
|
|
fs.writeFileSync(abs(auditPath), `${JSON.stringify(audit, null, 2)}\n`);
|
|
}
|
|
process.stdout.write(`wbv2-module-coverage-bound task=${task} module=${capabilityId} added=${coverageBindings.length} verified=${verifiedUnits} unverified=${inventory.summary.unitCount - verifiedUnits}\n`);
|