Reorganize Blender Web execution around capabilities
This commit is contained in:
92
tools/web/check-corrective-plan.mjs
Normal file
92
tools/web/check-corrective-plan.mjs
Normal file
@@ -0,0 +1,92 @@
|
||||
import assert from "node:assert/strict";
|
||||
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 read = (relative) => fs.readFileSync(path.join(root, relative), "utf8");
|
||||
const json = (relative) => JSON.parse(read(relative));
|
||||
const exists = (relative) => fs.existsSync(path.join(root, relative));
|
||||
|
||||
const planPath = "docs/BLENDER_WASM_CORRECTIVE_TASK_PLAN.json";
|
||||
const matrixPath = "docs/BLENDER_WASM_CAPABILITY_MATRIX_TEMPLATE.json";
|
||||
const plan = json(planPath);
|
||||
const matrix = json(matrixPath);
|
||||
const queue = read("docs/EXECUTION_QUEUE.md");
|
||||
const handoff = read("nextTask1.md");
|
||||
const currentTask = queue.match(/\|\s*当前任务\s*\|\s*`([^`]+)`/u)?.[1];
|
||||
const parentPath = queue.match(/\|\s*parent manifest\s*\|\s*`([^`]+)`/iu)?.[1];
|
||||
const parent = json(parentPath);
|
||||
const currentCorrectiveTask = handoff.match(/current corrective task\s*\|\s*`([^`]+)`/iu)?.[1];
|
||||
|
||||
function isLegacyCheckpointAncestor(checkpointTask) {
|
||||
if (checkpointTask === currentTask) return true;
|
||||
let cursor = parent;
|
||||
const visited = new Set();
|
||||
while (cursor?.task && !visited.has(cursor.task)) {
|
||||
visited.add(cursor.task);
|
||||
if (cursor.task === checkpointTask) return true;
|
||||
if (!cursor.parentTask) return false;
|
||||
const manifestPath = path.join(root, "tests/golden", cursor.parentTask, "manifest.json");
|
||||
if (!fs.existsSync(manifestPath)) return false;
|
||||
cursor = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
assert.equal(plan.schemaVersion, 2, "corrective task plan schema must be v2");
|
||||
assert.equal(plan.queuePolicy.mode, "READ_ONLY_UNTIL_C6_003");
|
||||
assert.ok(isLegacyCheckpointAncestor(plan.queuePolicy.currentTask), "corrective queue checkpoint must be current or an ancestor");
|
||||
assert.ok(isLegacyCheckpointAncestor(plan.queuePolicy.parentNextTask), "corrective parent checkpoint must be current or an ancestor");
|
||||
assert.equal(plan.queuePolicy.currentTask, plan.currentCheckpoint.queueCurrentTask);
|
||||
assert.equal(plan.queuePolicy.parentNextTask, plan.currentCheckpoint.queueParentNextTask);
|
||||
assert.notEqual(plan.currentCheckpoint.queueParentNextTask, plan.currentCheckpoint.reservedSuccessor);
|
||||
assert.equal(parent.nextTask, currentTask);
|
||||
assert.ok(currentCorrectiveTask, "nextTask1 must identify the current corrective task");
|
||||
assert.equal(plan.tasks.filter((task) => task.ready === true).map((task) => task.id).join(","), currentCorrectiveTask);
|
||||
assert.equal(plan.tasks.find((task) => task.id === "C0-001").status, "done");
|
||||
assert.equal(plan.tasks.find((task) => task.id === "C0-002").status, "done");
|
||||
assert.ok(["planned", "in_progress", "done"].includes(plan.tasks.find((task) => task.id === "C0-003").status));
|
||||
assert.ok(["planned", "in_progress"].includes(plan.tasks.find((task) => task.id === currentCorrectiveTask).status));
|
||||
|
||||
const taskIds = new Set(plan.tasks.map((task) => task.id));
|
||||
assert.equal(taskIds.size, plan.tasks.length, "corrective task IDs must be unique");
|
||||
const lifecycleStatuses = new Set(plan.taskContract.lifecycleStatuses);
|
||||
for (const task of plan.tasks) {
|
||||
assert.ok(lifecycleStatuses.has(task.status ?? "planned"), `${task.id} lifecycle status is invalid`);
|
||||
if (task.ready === true) assert.ok(["planned", "in_progress"].includes(task.status ?? "planned"), `${task.id} ready task has invalid lifecycle status`);
|
||||
if (task.ready === true || task.phase === "P0") {
|
||||
assert.ok(task.card && exists(task.card), `${task.id} task card is missing`);
|
||||
const card = read(task.card);
|
||||
const cardStatus = card.match(/(?:lifecycleStatus|状态)\s*[::]\s*`?([a-z_]+|done|blocked|planned|in_progress)/iu)?.[1]?.toLowerCase();
|
||||
if (task.status) assert.equal(cardStatus, task.status, `${task.id} card status is stale`);
|
||||
if (task.id === "C6-003") assert.match(card, /queueMutationAllowed\s*[::]\s*`?(?:false|true)/iu, `${task.id} card must declare queue mutation policy`);
|
||||
else assert.match(card, /queueMutationAllowed\s*[::]\s*`?false/iu, `${task.id} card must forbid queue mutation`);
|
||||
}
|
||||
assert.ok(task.gate && plan.gates.some((gate) => gate.id === task.gate), `${task.id} gate is invalid`);
|
||||
for (const dependency of task.dependsOn ?? []) assert.ok(taskIds.has(dependency), `${task.id} dependency is missing: ${dependency}`);
|
||||
assert.equal(task.queueMutationAllowed ?? false, task.id === "C6-003", `${task.id} queue mutation policy is invalid`);
|
||||
}
|
||||
|
||||
const allowedLevels = new Set(matrix.allowedParityLevels);
|
||||
const allowedClasses = new Set(matrix.allowedCompletionClasses);
|
||||
const allowedProvenanceStates = new Set(matrix.allowedProvenanceStates);
|
||||
assert.equal(matrix.schemaVersion, 2, "capability matrix schema must be v2");
|
||||
assert.equal(matrix.queuePolicy.mode, "SHADOW_ONLY");
|
||||
for (const entry of matrix.entries) {
|
||||
assert.ok(allowedLevels.has(entry.parityLevel), `${entry.capabilityId} parity level is invalid`);
|
||||
assert.ok(allowedLevels.has(entry.targetParityLevel), `${entry.capabilityId} target parity level is invalid`);
|
||||
assert.ok(allowedClasses.has(entry.completionClass), `${entry.capabilityId} completion class is invalid`);
|
||||
assert.ok(["planned", "in_progress", "blocked", "done"].includes(entry.lifecycleStatus), `${entry.capabilityId} lifecycle status is invalid`);
|
||||
if (entry.lifecycleStatus !== "done") assert.notEqual(entry.completionClass, "FEATURE_PARITY", `${entry.capabilityId} cannot claim feature parity before done`);
|
||||
if (entry.lifecycleStatus === "planned") assert.equal(entry.parityLevel, "L0", `${entry.capabilityId} planned entry must not claim proven parity`);
|
||||
assert.ok(["PASS", "FAIL", "BLOCKED", "NOT_RUN"].includes(entry.evidence?.status), `${entry.capabilityId} evidence status is invalid`);
|
||||
assert.ok(allowedProvenanceStates.has(entry.evidence?.provenanceState), `${entry.capabilityId} provenance state is invalid`);
|
||||
assert.ok(entry.evidence && Array.isArray(entry.evidence.required), `${entry.capabilityId} evidence contract is missing`);
|
||||
assert.equal(entry.migration?.legacyCompletionPreserved, true, `${entry.capabilityId} must preserve legacy completion records`);
|
||||
}
|
||||
|
||||
assert.match(read("docs/BLENDER_WASM_CORRECTIVE_EXECUTION_RUNBOOK.md"), /C0-001.*capture-corrective-baseline/isu);
|
||||
assert.match(read("docs/BLENDER_WASM_CORRECTIVE_MIGRATION_PLAN.md"), /MIGRATION_SHADOW_ONLY/iu);
|
||||
assert.equal(plan.tasks.find((task) => task.id === currentCorrectiveTask).ready, true);
|
||||
process.stdout.write(`corrective-plan-ok tasks=${plan.tasks.length} capabilities=${matrix.entries.length} queue=${currentTask} parentNext=${parent.nextTask} corrective=${currentCorrectiveTask}\n`);
|
||||
Reference in New Issue
Block a user