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