import { readFile } from 'node:fs/promises' import { resolve } from 'node:path' const root = resolve(new URL('..', import.meta.url).pathname) const load = (path) => readFile(resolve(root, path), 'utf8').then(JSON.parse) const [plan, sourceInventory, entrypointInventory, packageJson, compatibility, platformCoverage] = await Promise.all([ load('config/freecad-web-exact-parity-plan.json'), load('config/freecad-source-inventory.json'), load('config/freecad-entrypoint-inventory.json'), load('package.json'), load('config/compatibility-matrix.json'), load('config/platform-module-coverage.json'), ]) const fail = (message) => { throw new Error(`FreeCAD Web exact-parity plan: ${message}`) } const requireText = (value, label) => { if (typeof value !== 'string' || !value.trim()) fail(`${label} must be a non-empty string.`) } const requireTextList = (value, label, allowEmpty = false) => { if (!Array.isArray(value) || (!allowEmpty && value.length === 0)) fail(`${label} must be ${allowEmpty ? 'an' : 'a non-empty'} array.`) value.forEach((entry, index) => requireText(entry, `${label}[${index}]`)) } const lockedCommit = '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' if (plan.schemaVersion !== 1 || plan.baseline?.freecadVersion !== '1.1.1' || plan.baseline?.freecadCommit !== lockedCommit) fail('baseline is not the locked FreeCAD 1.1.1 reference.') if (sourceInventory.baseline?.commit !== lockedCommit || entrypointInventory.baseline?.commit !== lockedCommit || platformCoverage.baseline?.commit !== lockedCommit) fail('an input inventory uses a different FreeCAD baseline.') if (plan.claim?.exact !== false) fail('system exact must remain false while exact-parity tasks are open.') requireText(plan.claim?.scope, 'claim.scope') requireText(plan.claim?.reason, 'claim.reason') if (!Array.isArray(plan.hardBoundaries) || plan.hardBoundaries.length !== 3) fail('exactly three native-evidence hard boundaries are required.') for (const [index, boundary] of plan.hardBoundaries.entries()) requireText(boundary, `hardBoundaries[${index}]`) const boundaryText = plan.hardBoundaries.join(' ').toLowerCase() for (const token of ['mappednameref', 'builder-stage', 'isomorphic']) if (!boundaryText.includes(token)) fail(`hard boundaries do not retain the ${token} rule.`) const expectedProgramIds = Array.from({ length: 10 }, (_, index) => `EX${String(index + 1).padStart(2, '0')}`) if (!Array.isArray(plan.programs) || JSON.stringify(plan.programs.map((program) => program.id)) !== JSON.stringify(expectedProgramIds)) fail('programs must contain ordered EX01 through EX10.') const taskById = new Map() const statuses = new Set(['pending', 'in_progress', 'completed', 'blocked']) const priorities = new Set(['P0', 'P1', 'P2']) for (const program of plan.programs) { requireText(program.title, `${program.id}.title`) if (!Array.isArray(program.tasks) || program.tasks.length === 0) fail(`${program.id}.tasks must be non-empty.`) for (const task of program.tasks) { requireText(task.id, `${program.id}.task.id`) requireText(task.title, `${task.id}.title`) if (taskById.has(task.id)) fail(`duplicate task ${task.id}.`) if (!priorities.has(task.priority)) fail(`${task.id} has invalid priority ${task.priority}.`) if (!statuses.has(task.status)) fail(`${task.id} has invalid status ${task.status}.`) requireTextList(task.deliverables, `${task.id}.deliverables`) requireTextList(task.acceptance, `${task.id}.acceptance`) requireTextList(task.evidence, `${task.id}.evidence`) requireTextList(task.exactBlockedBy, `${task.id}.exactBlockedBy`, task.status === 'completed') if (!Array.isArray(task.dependencies)) fail(`${task.id}.dependencies must be an array.`) if (task.status === 'completed' && task.exactBlockedBy.length !== 0) fail(`${task.id} is completed but still has exact blockers.`) if (task.status !== 'completed' && task.exactBlockedBy.length === 0) fail(`${task.id} is open without an explicit exact blocker.`) for (const evidence of task.evidence) if (!packageJson.scripts?.[evidence]) fail(`${task.id} references missing evidence script ${evidence}.`) taskById.set(task.id, task) } } if (taskById.size < 40) fail('the exact plan is not detailed enough to cover the full FreeCAD scope.') for (const task of taskById.values()) for (const dependency of task.dependencies) if (!taskById.has(dependency)) fail(`${task.id} depends on unknown task ${dependency}.`) const visitState = new Map() const visit = (taskId, chain = []) => { if (visitState.get(taskId) === 'done') return if (visitState.get(taskId) === 'visiting') fail(`task dependency cycle: ${[...chain, taskId].join(' -> ')}.`) visitState.set(taskId, 'visiting') for (const dependency of taskById.get(taskId).dependencies) visit(dependency, [...chain, taskId]) visitState.set(taskId, 'done') } for (const taskId of taskById.keys()) visit(taskId) const validateCoverage = (actual, expected, label) => { const actualIds = Object.keys(actual ?? {}).sort() const expectedIds = [...expected].sort() if (JSON.stringify(actualIds) !== JSON.stringify(expectedIds)) fail(`${label} coverage does not match the locked inventory.`) for (const [id, taskIds] of Object.entries(actual)) { if (!Array.isArray(taskIds) || taskIds.length === 0) fail(`${label} ${id} has no task coverage.`) for (const taskId of taskIds) if (!taskById.has(taskId)) fail(`${label} ${id} references unknown task ${taskId}.`) } } validateCoverage(plan.moduleCoverage, sourceInventory.modules.map((module) => module.name), 'module') validateCoverage(plan.entrypointCoverage?.formats, entrypointInventory.formats.map((format) => format.id), 'format') validateCoverage(plan.entrypointCoverage?.entrypoints, entrypointInventory.entrypoints.map((entrypoint) => entrypoint.id), 'entrypoint') const finalTask = taskById.get(plan.promotionGate?.finalTask) if (!finalTask || finalTask.id !== 'EX-REL-01' || finalTask.status === 'completed') fail('EX-REL-01 must remain open until all exact tasks close.') if (plan.promotionGate?.requiredExactModules !== sourceInventory.modules.length) fail('promotion gate must require every locked module to be exact.') requireTextList(plan.promotionGate?.requiredZeroMetrics, 'promotionGate.requiredZeroMetrics') requireTextList(plan.promotionGate?.requiredEvidenceClasses, 'promotionGate.requiredEvidenceClasses') if (new Set(plan.promotionGate.requiredZeroMetrics).size !== plan.promotionGate.requiredZeroMetrics.length) fail('promotion metrics must be unique.') if (new Set(plan.promotionGate.requiredEvidenceClasses).size !== plan.promotionGate.requiredEvidenceClasses.length) fail('promotion evidence classes must be unique.') const systemEvaluation = compatibility.systemExactEvaluation if (systemEvaluation?.exact !== false || systemEvaluation?.featureExactCount !== 0 || !Array.isArray(systemEvaluation.blockers) || systemEvaluation.blockers.length !== 3) fail('compatibility matrix must retain the current non-exact evaluation and three native blockers.') if (platformCoverage.modules.some((module) => module.level === 'exact')) fail('platform coverage cannot contain exact modules before their exact tasks complete.') const tasks = [...taskById.values()] const statusCounts = Object.fromEntries([...statuses].map((status) => [status, tasks.filter((task) => task.status === status).length])) const priorityCounts = Object.fromEntries([...priorities].map((priority) => [priority, tasks.filter((task) => task.priority === priority).length])) const matrixTaskStatus = systemEvaluation.taskStatus if (systemEvaluation.plan !== 'config/freecad-web-exact-parity-plan.json' || systemEvaluation.promotionTask !== finalTask.id) fail('compatibility matrix does not point at the exact-parity plan and final promotion task.') if (matrixTaskStatus?.completed !== statusCounts.completed || matrixTaskStatus?.inProgress !== statusCounts.in_progress || matrixTaskStatus?.pending !== statusCounts.pending || matrixTaskStatus?.blocked !== statusCounts.blocked) fail('compatibility matrix exact task counts are stale.') console.log(JSON.stringify({ status: 'freecad-web-exact-parity-plan-pass', systemExact: false, programs: plan.programs.length, tasks: tasks.length, statusCounts, priorityCounts, modules: Object.keys(plan.moduleCoverage).length, formats: Object.keys(plan.entrypointCoverage.formats).length, entrypoints: Object.keys(plan.entrypointCoverage.entrypoints).length, hardBoundaries: plan.hardBoundaries.length, finalGate: finalTask.status, }, null, 2))