118 lines
8.3 KiB
JavaScript
118 lines
8.3 KiB
JavaScript
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 !== 2) fail('compatibility matrix must retain the current non-exact evaluation and two remaining exact 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))
|