Files
workinf_Blender_Wasm/tools/web/generate-wbv2-integration-readiness.mjs
mes123456 c2c65d3a09
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Rebuild local Blender WASM environment and recovery evidence
2026-08-27 20:22:15 +08:00

147 lines
8.0 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 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`);