Reorganize Blender Web execution around capabilities
This commit is contained in:
141
tools/web/check-execution-control-plane.mjs
Normal file
141
tools/web/check-execution-control-plane.mjs
Normal file
@@ -0,0 +1,141 @@
|
||||
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 allowUnrunnable = process.argv.includes("--allow-unrunnable");
|
||||
const relative = (file) => path.relative(root, file).replaceAll(path.sep, "/");
|
||||
const read = (relativePath) => fs.readFileSync(path.join(root, relativePath), "utf8");
|
||||
const json = (relativePath) => JSON.parse(read(relativePath));
|
||||
const sha256 = (bytes) => {
|
||||
return crypto.createHash("sha256").update(bytes).digest("hex");
|
||||
};
|
||||
|
||||
function parseQueue() {
|
||||
const source = read("docs/EXECUTION_QUEUE.md");
|
||||
const currentTask = source.match(/\|\s*当前任务\s*\|\s*`([^`]+)`/u)?.[1];
|
||||
const parentManifest = source.match(/\|\s*parent manifest\s*\|\s*`([^`]+)`/iu)?.[1];
|
||||
const coverageTask = source.match(/\|\s*legacy coverage checkpoint\s*\|\s*`([^`]+)`/iu)?.[1] ?? currentTask;
|
||||
const coverageParentManifest = source.match(/\|\s*legacy coverage parent\s*\|\s*`([^`]+)`/iu)?.[1] ?? (currentTask.startsWith("M") ? parentManifest : null);
|
||||
if (!currentTask || !parentManifest || !coverageTask || !coverageParentManifest) throw new Error("queue pointer is incomplete");
|
||||
return { currentTask, parentManifest, coverageTask, coverageParentManifest };
|
||||
}
|
||||
|
||||
function addIssue(issues, code, detail) {
|
||||
issues.push({ code, detail });
|
||||
}
|
||||
|
||||
const issues = [];
|
||||
let queue = null;
|
||||
let parent = null;
|
||||
let index = null;
|
||||
let catalog = [];
|
||||
let plan = null;
|
||||
let matrix = null;
|
||||
let completed = [];
|
||||
|
||||
try {
|
||||
queue = parseQueue();
|
||||
const parentPath = path.join(root, queue.parentManifest);
|
||||
if (!fs.existsSync(parentPath)) addIssue(issues, "PARENT_MANIFEST_MISSING", queue.parentManifest);
|
||||
else {
|
||||
parent = JSON.parse(fs.readFileSync(parentPath, "utf8"));
|
||||
if (parent.nextTask !== queue.currentTask) addIssue(issues, "PARENT_NEXT_TASK_MISMATCH", `${parent.nextTask ?? "NONE"}!=${queue.currentTask}`);
|
||||
}
|
||||
|
||||
const capabilityIndex = queue.currentTask.startsWith("WBV2-");
|
||||
index = json(capabilityIndex ? "tests/golden/WBV2/task-index.json" : "tests/golden/M15-03A/task-index.json");
|
||||
if (index.activeTask !== queue.currentTask) addIssue(issues, "INDEX_ACTIVE_TASK_MISMATCH", `${index.activeTask}!=${queue.currentTask}`);
|
||||
const catalogPath = path.join(root, index.catalog.path);
|
||||
if (!fs.existsSync(catalogPath)) addIssue(issues, "CATALOG_MISSING", index.catalog.path);
|
||||
else {
|
||||
catalog = fs.readFileSync(catalogPath, "utf8").trimEnd().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
||||
const active = catalog.filter((entry) => entry.state === "active");
|
||||
if (active.length !== 1) addIssue(issues, "ACTIVE_TASK_COUNT", `count=${active.length}`);
|
||||
else if (active[0].id !== queue.currentTask) addIssue(issues, "CATALOG_ACTIVE_TASK_MISMATCH", `${active[0].id}!=${queue.currentTask}`);
|
||||
}
|
||||
|
||||
plan = json("tests/golden/M15-03A/next-task-plan.json");
|
||||
matrix = json("docs/BLENDER_WASM_CAPABILITY_MATRIX_TEMPLATE.json");
|
||||
completed = json("tests/golden/M15-03A/completed-gap-tasks.json");
|
||||
} catch (error) {
|
||||
addIssue(issues, "CONTROL_PLANE_READ_ERROR", error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
|
||||
const active = catalog.find((entry) => entry.state === "active") ?? null;
|
||||
let legacyCoverage = null;
|
||||
try {
|
||||
const legacyIndex = json("tests/golden/M15-03A/task-index.json");
|
||||
const legacyCatalog = fs.readFileSync(path.join(root, legacyIndex.catalog.path), "utf8").trimEnd().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
||||
legacyCoverage = legacyCatalog.find((entry) => entry.id === queue.coverageTask) ?? null;
|
||||
if (!legacyCoverage) addIssue(issues, "LEGACY_COVERAGE_TASK_MISSING", queue.coverageTask);
|
||||
} catch (error) {
|
||||
addIssue(issues, "LEGACY_COVERAGE_INDEX_ERROR", error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
const legacySummary = plan?.summary ?? {};
|
||||
const capabilities = matrix?.entries ?? [];
|
||||
const mappedGapIds = [...new Set(capabilities.flatMap((entry) => entry.sourceGaps ?? []))];
|
||||
const capabilityClusters = Object.fromEntries(capabilities.map((entry) => [entry.family, {
|
||||
capabilityId: entry.capabilityId,
|
||||
sourceGaps: (entry.sourceGaps ?? []).length,
|
||||
parityLevel: entry.parityLevel,
|
||||
targetParityLevel: entry.targetParityLevel,
|
||||
lifecycleStatus: entry.lifecycleStatus,
|
||||
}]));
|
||||
const runnableInputs = active ? {
|
||||
taskCard: fs.existsSync(path.join(root, "docs/tasks", `${active.id}.md`)),
|
||||
capabilityTask: active.id.startsWith("WBV2-"),
|
||||
fixture: active.id.startsWith("WBV2-") ? null : fs.existsSync(path.join(root, "tests/files/web/generated", `${active.id}-${active.gapId.replace(/[^A-Za-z0-9_.-]+/gu, "-")}.blend`)),
|
||||
generator: active.id.startsWith("WBV2-") ? null : fs.existsSync(path.join(root, "tools/web/generated", `${active.id}.py`)),
|
||||
} : null;
|
||||
if (!allowUnrunnable && runnableInputs && (!runnableInputs.taskCard || (!runnableInputs.capabilityTask && (!runnableInputs.fixture || !runnableInputs.generator)))) {
|
||||
addIssue(issues, "ACTIVE_TASK_NOT_RUNNABLE", JSON.stringify(runnableInputs));
|
||||
}
|
||||
|
||||
const report = {
|
||||
schemaVersion: 1,
|
||||
operation: "BLENDER_WEB_EXECUTION_CONTROL_PLANE",
|
||||
status: issues.length === 0 ? "PASS" : "BLOCKED",
|
||||
authority: {
|
||||
executionPointer: "docs/EXECUTION_QUEUE.md",
|
||||
productCapabilities: "docs/BLENDER_WASM_CAPABILITY_MATRIX_TEMPLATE.json",
|
||||
legacyCoverage: "tests/golden/M15-03A/next-task-plan.json",
|
||||
historicalEvidence: "tests/golden/corrective/",
|
||||
},
|
||||
queue: queue ? {
|
||||
currentTask: queue.currentTask,
|
||||
parentManifest: queue.parentManifest,
|
||||
parentTask: parent?.task ?? null,
|
||||
parentNextTask: parent?.nextTask ?? null,
|
||||
activeCatalogTask: active?.id ?? null,
|
||||
legacyCoverageTask: queue.coverageTask,
|
||||
legacyCoverageParentManifest: queue.coverageParentManifest,
|
||||
legacyCoverageState: legacyCoverage?.state ?? null,
|
||||
runnableInputs,
|
||||
} : null,
|
||||
efficiency: {
|
||||
legacyGapTasks: legacySummary.taskCount ?? catalog.length,
|
||||
legacyCompleted: completed.length,
|
||||
legacyPending: legacySummary.pending ?? null,
|
||||
capabilityClusters: capabilities.length,
|
||||
mappedLegacyGaps: mappedGapIds.length,
|
||||
averageGapsPerCapability: capabilities.length === 0 ? 0 : Number((mappedGapIds.length / capabilities.length).toFixed(2)),
|
||||
productiveLane: capabilities.filter((entry) => entry.lifecycleStatus === "done" && ["L2", "L3", "L4", "L5"].includes(entry.parityLevel)).length,
|
||||
rule: "one capability task may close multiple legacy cases; legacy cases remain regression coverage",
|
||||
},
|
||||
capabilityClusters,
|
||||
issues,
|
||||
remediation: issues.map(({ code }) => ({
|
||||
PARENT_NEXT_TASK_MISMATCH: "repair generated queue state from the completion registry",
|
||||
INDEX_ACTIVE_TASK_MISMATCH: "run generate-task-index.mjs after regenerating the plan",
|
||||
CATALOG_ACTIVE_TASK_MISMATCH: "run generate-task-index.mjs; do not edit catalog offsets",
|
||||
ACTIVE_TASK_COUNT: "leave exactly one active task in the generated catalog",
|
||||
ACTIVE_TASK_NOT_RUNNABLE: "generate the task card and fixture/generator before Blender/WASM startup",
|
||||
}[code] ?? "inspect the control-plane issue and preserve its evidence")),
|
||||
allowUnrunnable,
|
||||
inputDigest: sha256(JSON.stringify({ queue, parent: parent?.nextTask, active: active?.id, completed: completed.length, capabilities: capabilities.length })),
|
||||
};
|
||||
|
||||
process.stdout.write(`${JSON.stringify(report)}\n`);
|
||||
if (issues.length > 0) process.exitCode = 2;
|
||||
Reference in New Issue
Block a user