Reorganize Blender Web execution around capabilities
This commit is contained in:
102
tools/web/reactivate-legacy-queue.mjs
Normal file
102
tools/web/reactivate-legacy-queue.mjs
Normal file
@@ -0,0 +1,102 @@
|
||||
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 queuePath = path.join(root, "docs/EXECUTION_QUEUE.md");
|
||||
const reviewPath = path.join(root, "tests/golden/corrective/C6-003/owner-review.json");
|
||||
const dryRunPath = path.join(root, "tests/golden/corrective/C6-003/queue-reactivation-dry-run.json");
|
||||
const writePreviewPath = path.join(root, "tests/golden/corrective/C6-003/queue-reactivation-write-preview.json");
|
||||
const currentManifestPath = path.join(root, "tests/golden/M16-GAP-00276/manifest.json");
|
||||
const currentStatusPath = path.join(root, "docs/status/M16-GAP-00276.md");
|
||||
const completionPath = path.join(root, "tests/golden/M15-03A/completed-gap-tasks.json");
|
||||
const nextPlanPath = path.join(root, "tests/golden/M15-03A/next-task-plan.json");
|
||||
const catalogPath = path.join(root, "tests/golden/M15-03A/task-catalog.jsonl");
|
||||
const indexPath = path.join(root, "tests/golden/M15-03A/task-index.json");
|
||||
const evidenceRoot = path.join(root, "tests/golden/corrective/C6-003");
|
||||
const relative = (file) => path.relative(root, file).replaceAll(path.sep, "/");
|
||||
const sha256 = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex");
|
||||
const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
|
||||
const writeJson = (file, value) => fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
||||
const write = process.argv.includes("--write");
|
||||
if (!write) throw new Error("queue reactivation is write-only after review; pass --write explicitly");
|
||||
|
||||
const review = readJson(reviewPath);
|
||||
assert.equal(review.task, "C6-003");
|
||||
assert.equal(review.status, "APPROVED");
|
||||
assert.equal(review.authorization?.approved, true);
|
||||
assert.equal(review.decision?.allowQueueMutation, true);
|
||||
assert.equal(review.decision?.targetTask, "M16-GAP-00277");
|
||||
|
||||
const dryRunBytes = fs.readFileSync(dryRunPath);
|
||||
const writePreviewBytes = fs.readFileSync(writePreviewPath);
|
||||
assert.equal(sha256(dryRunBytes), review.reviewedEvidence.dryRun.sha256);
|
||||
assert.equal(sha256(writePreviewBytes), review.reviewedEvidence.writePreview.sha256);
|
||||
assert.deepEqual(dryRunBytes, writePreviewBytes, "dry-run and write-preview bytes differ");
|
||||
const dryRun = JSON.parse(dryRunBytes);
|
||||
assert.equal(dryRun.status, "BLOCKED_OWNER_REVIEW");
|
||||
assert.equal(dryRun.queueMutation, false);
|
||||
assert.equal(dryRun.legacyQueue.currentTask, "M16-GAP-00276");
|
||||
assert.equal(dryRun.legacyQueue.parentManifest, "tests/golden/M16-GAP-00275/manifest.json");
|
||||
|
||||
const currentManifest = readJson(currentManifestPath);
|
||||
assert.equal(currentManifest.task, "M16-GAP-00276");
|
||||
assert.equal(currentManifest.status, "done", "complete the focused legacy task before queue write");
|
||||
assert.equal(currentManifest.nextTask, "M16-GAP-00277");
|
||||
assert.match(fs.readFileSync(currentStatusPath, "utf8"), /^status:\s*done\s*$/mu);
|
||||
assert.ok(readJson(completionPath).includes("operator:asset.clear_single"), "completion registry is not closed");
|
||||
const nextPlan = readJson(nextPlanPath);
|
||||
assert.equal(nextPlan.firstTask, "M16-GAP-00277");
|
||||
assert.equal(nextPlan.tasks.find((entry) => entry.id === "M16-GAP-00276")?.state, "completed");
|
||||
assert.equal(nextPlan.tasks.find((entry) => entry.id === "M16-GAP-00277")?.state, "active");
|
||||
const taskIndex = readJson(indexPath);
|
||||
assert.equal(taskIndex.activeTask, "M16-GAP-00277");
|
||||
const catalogRows = fs.readFileSync(catalogPath, "utf8").trimEnd().split("\n").map((line) => JSON.parse(line));
|
||||
assert.equal(catalogRows.find((entry) => entry.id === "M16-GAP-00276")?.state, "completed");
|
||||
assert.equal(catalogRows.find((entry) => entry.id === "M16-GAP-00277")?.state, "active");
|
||||
|
||||
const queueBefore = fs.readFileSync(queuePath);
|
||||
assert.equal(sha256(queueBefore), review.reviewedEvidence.protectedHashes["docs/EXECUTION_QUEUE.md"]);
|
||||
const parentManifestPath = path.join(root, "tests/golden/M16-GAP-00275/manifest.json");
|
||||
assert.equal(sha256(fs.readFileSync(parentManifestPath)), review.reviewedEvidence.protectedHashes["tests/golden/M16-GAP-00275/manifest.json"]);
|
||||
const queueSource = queueBefore.toString("utf8");
|
||||
assert.match(queueSource, /\|\s*当前任务\s*\|\s*`M16-GAP-00276`/u);
|
||||
assert.match(queueSource, /\|\s*parent manifest\s*\|\s*`tests\/golden\/M16-GAP-00275\/manifest\.json`/u);
|
||||
const queueAfter = queueSource
|
||||
.replace("| 当前任务 | `M16-GAP-00276`", "| 当前任务 | `M16-GAP-00277`")
|
||||
.replace("| parent manifest | `tests/golden/M16-GAP-00275/manifest.json`", "| parent manifest | `tests/golden/M16-GAP-00276/manifest.json`")
|
||||
.replace("| 任务卡 | [`tasks/M16-GAP-00276.md`](tasks/M16-GAP-00276.md)", "| 任务卡 | [`tasks/M16-GAP-00277.md`](tasks/M16-GAP-00277.md)")
|
||||
.replace("| 专项验收 | `npm --prefix web run test:generated-gap -- --task M16-GAP-00276`", "| 专项验收 | `npm --prefix web run test:generated-gap -- --task M16-GAP-00277`");
|
||||
assert.notEqual(queueAfter, queueSource);
|
||||
assert.match(queueAfter, /\|\s*当前任务\s*\|\s*`M16-GAP-00277`/u);
|
||||
assert.match(queueAfter, /\|\s*parent manifest\s*\|\s*`tests\/golden\/M16-GAP-00276\/manifest\.json`/u);
|
||||
|
||||
const beforePath = path.join(evidenceRoot, "queue-before.md");
|
||||
const afterPath = path.join(evidenceRoot, "queue-after.md");
|
||||
fs.writeFileSync(beforePath, queueBefore);
|
||||
fs.writeFileSync(afterPath, queueAfter);
|
||||
const temporary = `${queuePath}.c6-003-${process.pid}.tmp`;
|
||||
try {
|
||||
fs.writeFileSync(temporary, queueAfter, { mode: fs.statSync(queuePath).mode });
|
||||
fs.renameSync(temporary, queuePath);
|
||||
} finally {
|
||||
if (fs.existsSync(temporary)) fs.rmSync(temporary);
|
||||
}
|
||||
const observedAfter = fs.readFileSync(queuePath);
|
||||
assert.deepEqual(observedAfter, Buffer.from(queueAfter));
|
||||
const receipt = {
|
||||
schemaVersion: 1,
|
||||
task: "C6-003",
|
||||
operation: "CORRECTIVE_QUEUE_REACTIVATION_WRITE",
|
||||
status: "APPLIED",
|
||||
queueMutation: true,
|
||||
authorization: { review: relative(reviewPath), approved: true },
|
||||
before: { path: relative(beforePath), sha256: sha256(queueBefore), currentTask: "M16-GAP-00276", parentManifest: "tests/golden/M16-GAP-00275/manifest.json" },
|
||||
after: { path: relative(afterPath), sha256: sha256(observedAfter), currentTask: "M16-GAP-00277", parentManifest: "tests/golden/M16-GAP-00276/manifest.json" },
|
||||
changedFields: ["currentTask", "parentManifest", "taskCard", "focusedCommand"],
|
||||
rollback: { action: "restore before.path atomically", source: relative(beforePath) },
|
||||
};
|
||||
writeJson(path.join(evidenceRoot, "queue-reactivation-write-receipt.json"), receipt);
|
||||
process.stdout.write(`queue-reactivation-ok task=C6-003 queue=M16-GAP-00277 before=${receipt.before.sha256} after=${receipt.after.sha256}\n`);
|
||||
Reference in New Issue
Block a user