Reorganize Blender Web execution around capabilities
This commit is contained in:
74
tools/web/write-corrective-handoff.mjs
Normal file
74
tools/web/write-corrective-handoff.mjs
Normal file
@@ -0,0 +1,74 @@
|
||||
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)), "../..");
|
||||
|
||||
function arg(name) {
|
||||
const index = process.argv.indexOf(name);
|
||||
return index >= 0 ? process.argv[index + 1] : undefined;
|
||||
}
|
||||
|
||||
const task = arg("--task");
|
||||
const status = arg("--status");
|
||||
const gate = arg("--gate");
|
||||
const next = arg("--next");
|
||||
const evidenceRootArg = arg("--evidence-root");
|
||||
const sourceHashesArg = arg("--source-hashes");
|
||||
const commandExitCodesArg = arg("--command-exit-codes");
|
||||
const resolutionArg = arg("--resolution");
|
||||
const queueMutationArg = arg("--queue-mutation");
|
||||
const queueMutation = queueMutationArg === undefined ? false : queueMutationArg === "true";
|
||||
if (!task || !status || !gate || !next || !evidenceRootArg) {
|
||||
throw new Error("usage: node tools/web/write-corrective-handoff.mjs --task C0-xxx --status done --gate G0 --next C0-xxx --evidence-root tests/golden/corrective/C0-xxx [--queue-mutation true]");
|
||||
}
|
||||
|
||||
const evidenceRoot = path.resolve(root, evidenceRootArg);
|
||||
if (!evidenceRoot.startsWith(`${root}${path.sep}`)) throw new Error("evidence root must stay inside the repository");
|
||||
if (!fs.existsSync(evidenceRoot) || !fs.statSync(evidenceRoot).isDirectory()) throw new Error(`missing evidence root: ${evidenceRootArg}`);
|
||||
|
||||
const relative = (file) => path.relative(root, file).replaceAll(path.sep, "/");
|
||||
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
|
||||
const reports = ["dry-run.json", "write.json"]
|
||||
.map((name) => path.join(evidenceRoot, name))
|
||||
.filter((file) => fs.existsSync(file))
|
||||
.map((file) => JSON.parse(fs.readFileSync(file, "utf8")));
|
||||
const resolution = resolutionArg && fs.existsSync(path.resolve(root, resolutionArg)) ? JSON.parse(fs.readFileSync(path.resolve(root, resolutionArg), "utf8")) : null;
|
||||
const inputDigests = [...new Set([...reports.map((report) => report.inputDigest), resolution?.inputDigest].filter(Boolean))];
|
||||
if (inputDigests.length > 1 && !resolution) throw new Error("dry-run and write inputDigest differ");
|
||||
const sourceHashValue = sourceHashesArg && fs.existsSync(path.resolve(root, sourceHashesArg)) ? JSON.parse(fs.readFileSync(path.resolve(root, sourceHashesArg), "utf8")) : resolution?.sourceHashes ?? reports.find((report) => report.sourceHashes)?.sourceHashes ?? null;
|
||||
const sourceHashes = sourceHashValue?.sourceHashes ?? sourceHashValue;
|
||||
const commandExitValue = commandExitCodesArg && fs.existsSync(path.resolve(root, commandExitCodesArg)) ? JSON.parse(fs.readFileSync(path.resolve(root, commandExitCodesArg), "utf8")) : resolution?.commandExitCodes ?? null;
|
||||
const commandExitCodes = commandExitValue?.commandExitCodes ?? commandExitValue;
|
||||
const artifacts = [];
|
||||
const stack = [evidenceRoot];
|
||||
while (stack.length) {
|
||||
const directory = stack.pop();
|
||||
for (const entry of fs.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
||||
const file = path.join(directory, entry.name);
|
||||
if (entry.isDirectory()) stack.push(file);
|
||||
else if (entry.isFile() && entry.name !== "manifest.json" && entry.name !== "handoff.json") {
|
||||
artifacts.push({ path: relative(file), sha256: sha256(file), bytes: fs.statSync(file).size });
|
||||
}
|
||||
}
|
||||
}
|
||||
artifacts.sort((a, b) => a.path.localeCompare(b.path));
|
||||
const handoff = {
|
||||
schemaVersion: 1,
|
||||
task,
|
||||
status,
|
||||
gate,
|
||||
queueMutation,
|
||||
evidenceRoot: relative(evidenceRoot),
|
||||
artifactCount: artifacts.length,
|
||||
artifacts,
|
||||
...(resolution?.inputDigest ? { inputDigest: resolution.inputDigest } : inputDigests.length === 1 ? { inputDigest: inputDigests[0] } : {}),
|
||||
...(sourceHashes ? { sourceHashes } : {}),
|
||||
...(commandExitCodes ? { commandExitCodes } : {}),
|
||||
...(resolution ? { resolution: { status: resolution.status, writeOutcome: resolution.writeOutcome, authorization: resolution.authorization, candidate: resolution.candidate ?? null } } : {}),
|
||||
nextCorrectiveTask: next,
|
||||
};
|
||||
fs.writeFileSync(path.join(evidenceRoot, "handoff.json"), `${JSON.stringify(handoff, null, 2)}\n`);
|
||||
fs.writeFileSync(path.join(evidenceRoot, "manifest.json"), `${JSON.stringify(handoff, null, 2)}\n`);
|
||||
process.stdout.write(`corrective-handoff-ok task=${task} status=${status} gate=${gate} artifacts=${artifacts.length} next=${next}\n`);
|
||||
Reference in New Issue
Block a user