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`);