103 lines
6.5 KiB
JavaScript
103 lines
6.5 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 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`);
|