Rebuild local Blender WASM environment and recovery evidence
This commit is contained in:
146
tools/web/generate-wbv2-integration-readiness.mjs
Normal file
146
tools/web/generate-wbv2-integration-readiness.mjs
Normal file
@@ -0,0 +1,146 @@
|
||||
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 relative = (file) => path.relative(root, file).replaceAll(path.sep, "/");
|
||||
const filePath = (value) => path.join(root, value);
|
||||
const readJson = (value) => JSON.parse(fs.readFileSync(filePath(value), "utf8"));
|
||||
const sha256 = (value) => crypto.createHash("sha256").update(fs.readFileSync(filePath(value))).digest("hex");
|
||||
|
||||
const planPath = "docs/BLENDER_WEB_PARITY_TASK_PLAN_V2.json";
|
||||
const inventoryPath = "tests/golden/WBV2-P7-001/gap-inventory-report.json";
|
||||
const atomicPath = "tests/golden/WBV2-P7-002/atomic-queue-report.json";
|
||||
const gatePath = "tests/golden/WBV2-P7-003/release-gate-report.json";
|
||||
const quickPath = "release/ci-reports/quick.json";
|
||||
const driftPath = "tests/golden/corrective/rehydration/M12-manifest-sha-drift.json";
|
||||
const provenanceIntakePath = "tests/golden/corrective/rehydration/M12-provenance-intake.json";
|
||||
const m12SnapshotPath = "tests/golden/corrective/rehydration/M12-git-history-snapshot/snapshot-manifest.json";
|
||||
const c5Path = "tests/golden/corrective/C5-002/chromium-family-report.json";
|
||||
const familyEvidencePath = "tests/golden/WBV2-P7-003/c5-family-evidence-report.json";
|
||||
const atomicReceiptPath = "tests/golden/WBV2-P7-003/atomic-chromium-receipt-index.json";
|
||||
const outputPath = process.argv.includes("--out")
|
||||
? path.resolve(root, process.argv[process.argv.indexOf("--out") + 1])
|
||||
: filePath("tests/golden/WBV2-P7-003/integration-readiness-report.json");
|
||||
|
||||
const plan = readJson(planPath);
|
||||
const inventory = readJson(inventoryPath);
|
||||
const atomic = readJson(atomicPath);
|
||||
const gate = readJson(gatePath);
|
||||
const quick = readJson(quickPath);
|
||||
const drift = readJson(driftPath);
|
||||
const provenanceIntake = readJson(provenanceIntakePath);
|
||||
const c5 = readJson(c5Path);
|
||||
const familyEvidence = readJson(familyEvidencePath);
|
||||
const atomicReceipts = readJson(atomicReceiptPath);
|
||||
assert.equal(plan.planId, "BLENDER_WEB_PARITY_V2");
|
||||
assert.equal(inventory.task, "WBV2-P7-001");
|
||||
assert.equal(atomic.task, "WBV2-P7-002");
|
||||
assert.equal(gate.task, "WBV2-P7-003");
|
||||
assert.equal(quick.lane, "quick");
|
||||
assert.equal(c5.browser, "chromium");
|
||||
assert.equal(c5.queueMutation, false);
|
||||
assert.equal(familyEvidence.task, "WBV2-P7-003");
|
||||
assert.equal(familyEvidence.queueMutation, false);
|
||||
assert.equal(familyEvidence.families.length, 3);
|
||||
assert.equal(atomicReceipts.browser, "chromium");
|
||||
assert.equal(atomicReceipts.queueMutation, false);
|
||||
assert.ok(Array.isArray(atomicReceipts.entries));
|
||||
assert.equal(atomicReceipts.entries.every((entry) => entry.manifestBound === true), true);
|
||||
assert.equal(provenanceIntake.historicalEvidencePreserved, true);
|
||||
assert.equal(provenanceIntake.driftEntries, drift.entries.length);
|
||||
|
||||
const taskCounts = plan.tasks.reduce((groups, task) => {
|
||||
(groups[task.status] ??= []).push(task);
|
||||
return groups;
|
||||
}, {});
|
||||
const manifestRows = plan.tasks.map((task) => {
|
||||
const manifest = readJson(`tests/golden/${task.id}/manifest.json`);
|
||||
return { id: task.id, status: manifest.status, nextTask: manifest.nextTask ?? null };
|
||||
});
|
||||
assert.equal(manifestRows.length, plan.tasks.length);
|
||||
assert.ok(manifestRows.every((row) => row.status === "done" || row.status === "blocked"));
|
||||
|
||||
const pilotFamilies = familyEvidence.families.map((family) => ({
|
||||
family: family.family,
|
||||
capabilityIds: family.capabilityIds,
|
||||
evidenceState: family.evidenceState,
|
||||
desktopPositive: family.evidence.desktopPositive.status,
|
||||
wasmMutation: family.evidence.wasmMutation.status,
|
||||
saveReopenExact: family.evidence.saveReopenExact.status,
|
||||
candidateEvidence: family.c5Candidate.status,
|
||||
featureParityEligible: family.featureParityEligible,
|
||||
missing: ["production_chromium_family_receipt", "manifested_atomic_successor"],
|
||||
sourceTask: family.evidence.sourceTask,
|
||||
}));
|
||||
|
||||
const queueText = fs.readFileSync(filePath("docs/EXECUTION_QUEUE.md"), "utf8");
|
||||
const queueTask = queueText.match(/\|\s*当前任务\s*\|\s*`([^`]+)`/u)?.[1] ?? null;
|
||||
const parentManifest = queueText.match(/\|\s*parent manifest\s*\|\s*`([^`]+)`/iu)?.[1] ?? null;
|
||||
assert.equal(queueTask, "WBV2-P7-003");
|
||||
assert.equal(parentManifest, "tests/golden/WBV2-P7-002/manifest.json");
|
||||
|
||||
const report = {
|
||||
schemaVersion: 1,
|
||||
task: "WBV2-P7-003",
|
||||
operation: "INTEGRATION_READINESS_CHECKPOINT",
|
||||
queue: { currentTask: queueTask, parentManifest, queueMutation: false, nextTask: null },
|
||||
completedWork: {
|
||||
wbv2Tasks: { total: plan.tasks.length, done: (taskCounts.done ?? []).length, inProgress: (taskCounts.in_progress ?? []).length, blocked: (taskCounts.blocked ?? []).length },
|
||||
manifestRows,
|
||||
runtimeFoundation: ["SceneIR", "Main transaction/history", "Worker lifecycle", "OPFS/IndexedDB recovery"],
|
||||
productSlices: ["open/context", "transform/hierarchy", "mesh edit", "viewport", "material/UV", "animation", "rig/skin", "GLB", "asset library", "modifier", "nodes/shader", "sculpt/paint", "simulation", "render", "media", "server", "security", "CI/SBOM/rollback"],
|
||||
},
|
||||
gapInventory: {
|
||||
entries: inventory.desktop.entries,
|
||||
families: inventory.desktop.clusters,
|
||||
currentParityLevel: "L0",
|
||||
featureParityClaims: 0,
|
||||
},
|
||||
atomicQueue: {
|
||||
tasks: atomic.desktop.tasks,
|
||||
active: atomic.desktop.active,
|
||||
planned: atomic.desktop.planned,
|
||||
currentTaskGate: gate.desktop.decision,
|
||||
c5CandidateReceiptCount: familyEvidence.families.length * 3,
|
||||
manifestBoundReceiptCount: atomicReceipts.entries.length,
|
||||
remainingManifestBoundReceipts: atomic.desktop.tasks - atomicReceipts.entries.length,
|
||||
atomicReceiptIndex: atomicReceiptPath,
|
||||
},
|
||||
reusablePilots: pilotFamilies,
|
||||
immediateWork: {
|
||||
firstFamily: "MESH",
|
||||
firstCapabilityId: "CAP-MESH-POSITIVE-EDIT",
|
||||
firstBehavior: "desktopPositive",
|
||||
order: ["desktopPositive", "wasmMutation", "saveReopenExact"],
|
||||
queueState: "PROPOSED_OVERLAY_NOT_QUEUED",
|
||||
reason: "P7-003 has no successor; activate only after a manifest-backed queue successor is emitted.",
|
||||
},
|
||||
releaseInputs: {
|
||||
quickCi: quick.status,
|
||||
m12Provenance: drift.provenanceState,
|
||||
m12Intake: provenanceIntake.provenanceState,
|
||||
m12SnapshotAvailable: provenanceIntake.immutableSourceSnapshotAvailable,
|
||||
m12Entries: drift.entries.length,
|
||||
m12RestoredEntries: provenanceIntake.restoredEntries,
|
||||
historicalEvidencePreserved: drift.historicalEvidencePreserved,
|
||||
parityClosure: gate.desktop.parity,
|
||||
releaseDecision: gate.desktop.decision,
|
||||
},
|
||||
blockers: [
|
||||
{ code: "PARITY_CLOSURE_BLOCKED", detail: `${atomic.desktop.tasks} atomic behavior tasks are not yet backed by manifest-bound family receipts; ${atomicReceipts.entries.length} are reusable Chromium receipts and ${atomic.desktop.tasks - atomicReceipts.entries.length} remain.` },
|
||||
{
|
||||
code: "HISTORICAL_EVIDENCE_PROVENANCE_BLOCKED",
|
||||
detail: provenanceIntake.immutableSourceSnapshotAvailable
|
||||
? `M12 immutable snapshot matched ${provenanceIntake.restoredEntries}/${provenanceIntake.driftEntries} entries; ${provenanceIntake.remainingEntries} SHA drift entries remain fail-closed.`
|
||||
: `M12 immutable snapshot is unavailable; ${provenanceIntake.remainingEntries} SHA drift entries remain fail-closed.`,
|
||||
},
|
||||
],
|
||||
sourceHashes: Object.fromEntries([planPath, inventoryPath, atomicPath, gatePath, quickPath, driftPath, provenanceIntakePath, c5Path, familyEvidencePath, atomicReceiptPath, m12SnapshotPath].filter((file) => fs.existsSync(filePath(file))).map((file) => [relative(filePath(file)), sha256(file)])),
|
||||
};
|
||||
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
fs.writeFileSync(outputPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
||||
process.stdout.write(`integration-readiness-ok task=${report.task} completed=${report.completedWork.wbv2Tasks.done} inventory=${report.gapInventory.entries} families=${report.gapInventory.families} atomic=${report.atomicQueue.tasks} first=${report.immediateWork.firstFamily} parity=${report.releaseInputs.parityClosure} decision=${report.releaseInputs.releaseDecision} output=${relative(outputPath)}\n`);
|
||||
Reference in New Issue
Block a user