fix(web): reject false macro coverage and record source gaps
This commit is contained in:
83
tools/web/repair-wbv2-macro-bindings.mjs
Normal file
83
tools/web/repair-wbv2-macro-bindings.mjs
Normal file
@@ -0,0 +1,83 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import crypto from "node:crypto";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const abs = (file) => path.join(root, file);
|
||||
const readJson = (file) => JSON.parse(fs.readFileSync(abs(file), "utf8"));
|
||||
const sha = (file) => crypto.createHash("sha256").update(fs.readFileSync(abs(file))).digest("hex");
|
||||
const writeJson = (file, value) => fs.writeFileSync(abs(file), `${JSON.stringify(value, null, 2)}\n`);
|
||||
|
||||
const bindingsPath = "tests/golden/WBV2/dynamic-coverage-bindings.json";
|
||||
const inventoryPath = "tests/golden/WBV2/dynamic-inventory.json";
|
||||
const coverageManifestPath = "tests/golden/WBV2/dynamic-coverage-manifest.json";
|
||||
const inventoryManifestPath = "tests/golden/WBV2/dynamic-inventory-manifest.json";
|
||||
const auditPath = "tests/golden/WBV2/dynamic-coverage-audit.json";
|
||||
const repairPath = "tests/golden/WBV2/macro-binding-repair.json";
|
||||
|
||||
const bindings = readJson(bindingsPath);
|
||||
const inventory = readJson(inventoryPath);
|
||||
const macroTasks = new Set();
|
||||
const index = readJson("tests/golden/WBV2/task-index.json");
|
||||
const plan = readJson(index.source.path);
|
||||
for (const task of plan.tasks ?? []) {
|
||||
if (String(task.module?.moduleId ?? "").endsWith("_MACRO_BATCH")) macroTasks.add(task.id);
|
||||
}
|
||||
const removed = bindings.filter((binding) => macroTasks.has(binding.task) || String(binding.capabilityId ?? "").endsWith("_MACRO_BATCH"));
|
||||
const kept = bindings.filter((binding) => !macroTasks.has(binding.task) && !String(binding.capabilityId ?? "").endsWith("_MACRO_BATCH"));
|
||||
const removedIds = new Set(removed.map((binding) => binding.unitId));
|
||||
const updatedUnits = inventory.units.map((unit) => {
|
||||
if (!removedIds.has(unit.id)) return unit;
|
||||
const { coverage, ...withoutCoverage } = unit;
|
||||
return { ...withoutCoverage, coverageStatus: "UNVERIFIED" };
|
||||
});
|
||||
const verifiedUnits = updatedUnits.filter((unit) => unit.coverageStatus === "VERIFIED").length;
|
||||
const updatedInventory = {
|
||||
...inventory,
|
||||
units: updatedUnits,
|
||||
coverage: { ...inventory.coverage, verifiedUnits, unverifiedUnits: inventory.summary.unitCount - verifiedUnits },
|
||||
coverageLedger: kept,
|
||||
};
|
||||
writeJson(bindingsPath, kept);
|
||||
writeJson(inventoryPath, updatedInventory);
|
||||
const refresh = (file) => {
|
||||
if (!fs.existsSync(abs(file))) return;
|
||||
const value = readJson(file);
|
||||
if (file === coverageManifestPath) {
|
||||
value.artifacts.inventory.sha256 = sha(inventoryPath);
|
||||
value.artifacts.bindings.sha256 = sha(bindingsPath);
|
||||
value.coverage = { ...value.coverage, verifiedUnits, unverifiedUnits: inventory.summary.unitCount - verifiedUnits, boundUnitDelta: -removed.length };
|
||||
} else if (file === inventoryManifestPath) {
|
||||
value.artifacts.inventory.sha256 = sha(inventoryPath);
|
||||
value.inventory = { ...value.inventory, verifiedUnits, unverifiedUnits: inventory.summary.unitCount - verifiedUnits, boundCaseDelta: -removed.length };
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
value.dynamicInventory = { ...(value.dynamicInventory ?? {}), afterSha256: sha(inventoryPath), verifiedUnits, unverifiedUnits: inventory.summary.unitCount - verifiedUnits };
|
||||
value.coverageByKind = coverageByKind;
|
||||
value.coverageByFamily = coverageByFamily;
|
||||
value.explicitBindings = kept;
|
||||
}
|
||||
writeJson(file, value);
|
||||
};
|
||||
refresh(coverageManifestPath);
|
||||
refresh(inventoryManifestPath);
|
||||
refresh(auditPath);
|
||||
writeJson(repairPath, {
|
||||
schemaVersion: 1,
|
||||
operation: "REMOVE_FALSE_BROAD_MACRO_BINDINGS",
|
||||
removedBindingCount: removed.length,
|
||||
removedUnitIds: [...removedIds],
|
||||
verifiedUnits,
|
||||
unverifiedUnits: inventory.summary.unitCount - verifiedUnits,
|
||||
rule: "macro names require an exact native command gate; family substring matches are forbidden",
|
||||
});
|
||||
process.stdout.write(`macro-binding-repair removed=${removed.length} verified=${verifiedUnits} unverified=${inventory.summary.unitCount - verifiedUnits}\n`);
|
||||
Reference in New Issue
Block a user