feat: establish reproducible FreeCAD web compatibility baseline
This commit is contained in:
61
scripts/check-cam-parity.mjs
Normal file
61
scripts/check-cam-parity.mjs
Normal file
@@ -0,0 +1,61 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const [contract, inventory, desktop, cam, app, pkg] = await Promise.all([
|
||||
readFile(resolve(root, 'config/freecad-cam-parity.json'), 'utf8').then(JSON.parse),
|
||||
readFile(resolve(root, 'config/freecad-gui-command-inventory.json'), 'utf8').then(JSON.parse),
|
||||
readFile(resolve(root, 'config/freecad-desktop-oracle.json'), 'utf8').then(JSON.parse),
|
||||
readFile(resolve(root, 'config/chrome-cam-verification.json'), 'utf8').then(JSON.parse),
|
||||
readFile(resolve(root, 'config/chrome-app-e2e-verification.json'), 'utf8').then(JSON.parse),
|
||||
readFile(resolve(root, 'package.json'), 'utf8').then(JSON.parse),
|
||||
])
|
||||
|
||||
const fail = (message) => { throw new Error(message) }
|
||||
if (contract.schemaVersion !== 1 || contract.status !== 'in_progress' || contract.claim !== 'compatible-browser-subset-not-exact-freecad-cam-parity') fail('CAM parity contract must preserve its explicit compatible, not exact, claim.')
|
||||
if (contract.baseline?.commit !== inventory.baseline?.commit || contract.baseline.commit !== desktop.verification?.sourceCommit) fail('CAM parity sources do not match the locked FreeCAD baseline.')
|
||||
if (desktop.verification?.guiUp !== true || desktop.status !== 'desktop-build-probed') fail('The locked FreeCAD desktop oracle has not been probed.')
|
||||
|
||||
const commandIds = contract.commandSurface?.freecadCommandIds || []
|
||||
if (contract.commandSurface?.toolbarGroups !== 4 || contract.commandSurface.menuGroups !== 5 || contract.commandSurface.webCommands !== 60 || commandIds.length !== 60 || new Set(commandIds).size !== 60) fail('CAM command surface contract is incomplete.')
|
||||
const runtimeIds = new Set(inventory.commands?.map((command) => command.id))
|
||||
const sourceOnlyIds = new Set(inventory.sourceOnlyCommands?.map((command) => command.id))
|
||||
const freecadRuntimeCamIds = [...runtimeIds].filter((id) => id.startsWith('CAM_'))
|
||||
const freecadSourceOnlyCamIds = [...sourceOnlyIds].filter((id) => id.startsWith('CAM_'))
|
||||
if (contract.commandSurface.freecadCamRuntimeCommands !== freecadRuntimeCamIds.length || contract.commandSurface.freecadCamSourceOnlyCommands !== freecadSourceOnlyCamIds.length) fail('FreeCAD CAM inventory command count changed; regenerate and review the parity contract.')
|
||||
const missingIds = commandIds.filter((id) => !runtimeIds.has(id) && !sourceOnlyIds.has(id))
|
||||
if (missingIds.length) fail(`CAM commands missing from the FreeCAD inventory: ${missingIds.join(', ')}`)
|
||||
if (commandIds.filter((id) => runtimeIds.has(id)).length !== 56 || commandIds.filter((id) => sourceOnlyIds.has(id)).length !== 4) fail('CAM command inventory runtime/source-only split changed; regenerate and review the parity contract.')
|
||||
const notYetSurfaced = contract.commandSurface.notYetSurfacedCommands || []
|
||||
const allFreecadCamIds = new Set([...freecadRuntimeCamIds, ...freecadSourceOnlyCamIds])
|
||||
const nonActionableTemplates = contract.commandSurface.nonActionableSourceTemplates || []
|
||||
if (notYetSurfaced.length !== 0 || nonActionableTemplates.length !== 1 || nonActionableTemplates.some((id) => !sourceOnlyIds.has(id)) || new Set([...commandIds, ...nonActionableTemplates]).size !== allFreecadCamIds.size) fail('CAM surfaced/inventory command partition is inconsistent.')
|
||||
|
||||
const domain = contract.browserDomain || {}
|
||||
if (domain.stockModes !== 4 || domain.toolShapes !== 7 || domain.operationKinds !== 22 || domain.dressupKinds !== 9 || domain.postprocessors !== 7) fail('CAM browser domain enumeration is incomplete.')
|
||||
if (!Array.isArray(contract.coverage) || contract.coverage.length !== 12 || new Set(contract.coverage.map((item) => item.id)).size !== 12) fail('CAM-21..CAM-32 coverage must enumerate twelve unique tasks.')
|
||||
if (contract.coverage.filter((item) => item.level === 'exact').length !== 1 || contract.coverage.find((item) => item.level === 'exact')?.id !== 'CAM-21') fail('Only the locked static CAM command manifest may currently claim exact coverage.')
|
||||
for (const item of contract.coverage) if (!pkg.scripts?.[item.evidence]) fail(`CAM parity evidence script is missing for ${item.id}: ${item.evidence}`)
|
||||
const tasks = contract.tasks || {}
|
||||
if (tasks.completed !== 8 || tasks.inProgress !== 4 || tasks.pending !== 0 || tasks.blocked !== 0 || tasks.completed + tasks.inProgress + tasks.pending + tasks.blocked !== 12) fail('CAM parity task status summary is inconsistent.')
|
||||
if (!Array.isArray(contract.unclosedDifferences) || contract.unclosedDifferences.length !== 7) fail('CAM parity must keep all seven unclosed difference classes explicit.')
|
||||
|
||||
if (cam.status !== 'pass' || cam.browserId !== 'chrome' || cam.job?.tools !== 2 || cam.job.controllers !== 2 || cam.job.operations !== 3 || cam.job.pathPoints < 20 || cam.job.dressups !== 1) fail('Chrome CAM domain evidence is incomplete.')
|
||||
if (cam.job?.sanity !== 'pass' || cam.job.simulation !== 'pass' || cam.job.collisionFixture < 1 || cam.job.unsafePostRejected !== true || cam.job.deterministic !== true || Object.keys(cam.job.postHashes || {}).length !== 7) fail('Chrome CAM safety, simulation or post evidence is incomplete.')
|
||||
if (cam.extended?.commandSurface !== 60 || cam.extended.operationKinds !== 22 || cam.extended.dressupKinds !== 9 || cam.extended.toolAssetRoundTrip !== true || cam.extended.jobAssetRoundTrip !== true || cam.extended.undoRedo !== true || cam.extended.startPointChanged !== true || cam.extended.materialRemoval !== true || cam.extended.removalTimelinePoints !== cam.job.pathPoints || cam.extended.fixtureCollision !== true || cam.extended.multiAxis !== true) fail('Chrome CAM extended command, asset lifecycle, material removal, fixture or multi-axis evidence is incomplete.')
|
||||
if (cam.persistence?.roundTrip !== true || cam.persistence.released !== true || cam.afterRelease?.shapeCount !== 0 || cam.afterRelease.kernelReferenceCount !== 0) fail('Chrome CAM lifecycle evidence is incomplete.')
|
||||
const requiredAppWorkflows = ['cam-workbench-job-flow', 'viewport-multi-object-box-selection']
|
||||
if (app.status !== 'pass' || !Array.isArray(app.workflows) || app.workflows.length < 13 || requiredAppWorkflows.some((workflow) => !app.workflows.includes(workflow)) || app.pageErrors?.length !== 0) fail('Chrome application CAM workflow evidence is incomplete.')
|
||||
if (app.uiParity?.camMenu?.items !== 60 || app.uiParity.camMenu.groups?.length !== 5 || app.cam?.ui?.commandGroups !== 4 || app.cam.ui.toolbarCommands !== 60 || app.cam.ui.toolpathPoints < 5 || app.cam.ui.toolpathOperations < 1 || app.cam.ui.toolpathLegend !== true || app.cam.ui.horizontalOverflow !== 0) fail('CAM workbench UI evidence is incomplete.')
|
||||
|
||||
console.log(JSON.stringify({
|
||||
status: 'cam-parity-contract-pass',
|
||||
baseline: contract.baseline,
|
||||
claim: contract.claim,
|
||||
commands: { total: commandIds.length, runtimeProbed: 56, sourceOnly: 4, nonActionableTemplates: nonActionableTemplates.length },
|
||||
browserDomain: domain,
|
||||
tasks,
|
||||
chrome: { workflows: app.workflows.length, menuItems: app.uiParity.camMenu.items, toolbarCommands: app.cam.ui.toolbarCommands, menuGroups: app.uiParity.camMenu.groups.length, toolpathPoints: app.cam.ui.toolpathPoints },
|
||||
unclosedDifferences: contract.unclosedDifferences.length,
|
||||
notYetSurfacedCommands: notYetSurfaced.length,
|
||||
}, null, 2))
|
||||
Reference in New Issue
Block a user