56 lines
8.1 KiB
JavaScript
56 lines
8.1 KiB
JavaScript
import { readFile } from 'node:fs/promises'
|
|
import { resolve } from 'node:path'
|
|
|
|
const root = resolve(new URL('..', import.meta.url).pathname)
|
|
const report = JSON.parse(await readFile(resolve(root, 'config/freecad-cam-path-oracle.json'), 'utf8'))
|
|
const fail = (message) => { throw new Error(`FreeCAD CAM Path oracle check: ${message}`) }
|
|
|
|
if (report.schemaVersion !== 1 || report.baselineId !== 'freecad-1.1.1' || report.sourceCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('the locked FreeCAD baseline is invalid')
|
|
if (report.status !== 'verified-with-native-path-editable-subset' || report.boundary?.exactParityClaim !== false) fail('the report must retain the explicit non-exact Web codec boundary')
|
|
if (report.runtime?.guiUp !== true || !String(report.runtime?.pathModule).includes('install-desktop')) fail('the native GUI Path runtime evidence is missing')
|
|
if (report.profileAlgorithm?.fixture !== 'Mod/CAM/CAMTests/test_profile.fcstd' || report.profileAlgorithm?.pathPropertyType !== 'Path::PropertyPath') fail('the native Profile fixture contract is invalid')
|
|
if (report.profileAlgorithm?.cuttingCommandCount !== 20 || report.profileAlgorithm?.commandCount < 20 || report.profileAlgorithm?.normalizationToleranceMm !== 0.01 || !/^[0-9a-f]{64}$/.test(report.profileAlgorithm?.commandSha256 ?? '')) fail('the native Profile command evidence is incomplete')
|
|
if (report.profileAlgorithm?.firstCuttingCommand?.name !== 'G1' || report.profileAlgorithm?.lastCuttingCommand?.name !== 'G2') fail('the locked Profile cutting path endpoints changed')
|
|
if (report.profileAlgorithm?.variants?.length !== 2 || report.profileAlgorithm.variants[0]?.id !== 'outside-cw-tool-comp' || report.profileAlgorithm.variants[1]?.id !== 'outside-cw-no-comp' || report.profileAlgorithm.variants.some((variant) => variant.commandCount !== 32 || variant.cuttingCommandCount !== 20 || !/^[0-9a-f]{64}$/.test(variant.commandSha256 ?? ''))) fail('the Profile compensation variants are incomplete')
|
|
|
|
const helix = report.helixAlgorithm
|
|
const expectedHelixDirections = { 'inside-conventional': ['CW', 'G2'], 'outside-climb': ['CW', 'G2'], 'inside-climb': ['CCW', 'G3'], 'outside-conventional': ['CCW', 'G3'] }
|
|
if (helix?.fixture !== 'Mod/CAM/CAMTests/test_holes00.fcstd' || helix?.pathPropertyType !== 'Path::PropertyPath' || helix?.toolDiameterMm !== 0.9 || helix?.baseSubElementCount !== 9 || helix?.scenarioCount !== 4) fail('the native Helix fixture contract is invalid')
|
|
if (helix.scenarios.some((scenario) => {
|
|
const expected = expectedHelixDirections[scenario.id]
|
|
return !expected || scenario.direction !== expected[0] || scenario.expectedDirection !== expected[0] || scenario.expectedArc !== expected[1] || JSON.stringify(scenario.arcNames) !== JSON.stringify([expected[1]]) || scenario.arcCommandCount !== 1260 || scenario.commandCount < 1400 || !/^[0-9a-f]{64}$/.test(scenario.commandSha256 ?? '') || scenario.commandHistogram?.[expected[1]] !== 1260
|
|
})) fail('a native Helix direction scenario changed')
|
|
|
|
const qt = report.qtDynamicOracle
|
|
if (qt?.guiUp !== true || qt?.workbench !== 'CAMWorkbench' || qt?.activeWorkbench !== 'CAMWorkbench' || !/^6\./.test(qt?.qtVersion ?? '')) fail('the Qt CAM workbench evidence is invalid')
|
|
if (qt?.camCommandCount < 50 || qt?.toolbarCount < 1 || qt?.menuCount < 1) fail('the live Qt CAM action/menu/toolbar surface is incomplete')
|
|
const required = ['CAM_Job', 'CAM_Profile', 'CAM_Pocket_Shape', 'CAM_Drilling', 'CAM_Adaptive', 'CAM_Pocket3D', 'CAM_Post', 'CAM_Simulator', 'CAM_Sanity', 'CAM_ToolBitDock']
|
|
if (required.some((id) => !qt?.requiredCommands?.some((command) => command.id === id && command.emptySelection?.registered === true && command.selectedModel?.registered === true && command.selectedJob?.registered === true && command.selectedOperation?.registered === true && command.emptyDocument?.registered === true && command.selectedModel?.actionCount >= 1 && command.selectedModel?.iconPresent === true))) fail('a required live CAM QAction is missing')
|
|
if (!qt.requiredCommands.some((command) => command.selectionChangesEnabledState)) fail('the Qt oracle did not observe any selection-dependent CAM action state')
|
|
if (qt.taskLifecycleVerified !== true || !Array.isArray(qt.taskLifecycles) || qt.taskLifecycles.length < 3 || qt.taskLifecycles.some((item) => item.dialogOpened !== true || item.dialogClosed !== true || item.buttonClicked !== true || item.active.buttonBoxCount !== 1 || item.active.fieldCount < 10 || !item.active.buttonTexts.some((button) => button.text === 'OK') || !item.active.buttonTexts.some((button) => button.text === 'Cancel'))) fail('the live Qt CAM Task panel lifecycle evidence is incomplete')
|
|
|
|
const fcstd = report.fcstdRoundTrip
|
|
if (![fcstd?.source, fcstd?.mutated, fcstd?.webTransparentPreservation?.archive].every((archive) => /^[0-9a-f]{64}$/.test(archive?.semanticSha256 ?? ''))) fail('deterministic FCStd semantic hashes are missing')
|
|
if (fcstd.source.semanticSha256 !== fcstd.webTransparentPreservation.archive.semanticSha256) fail('Web-preserved FCStd Path semantics changed')
|
|
if (fcstd?.sourceBeforeClose?.length !== 2 || fcstd?.sourceReopened?.length !== 2 || fcstd?.mutatedReopened?.length !== 2) fail('the native Path FCStd lifecycle is incomplete')
|
|
if (fcstd.sourceReopened.some((object) => object.commandCount !== 5 || object.pathPropertyType !== 'Path::PropertyPath')) fail('native Path objects did not survive the initial save/open')
|
|
if (fcstd.mutatedReopened.some((object) => object.commandCount !== 6 || !object.label.endsWith('Mutated') || !object.oracleTag.endsWith('-v2'))) fail('native Path objects did not survive mutate/save/open')
|
|
if (JSON.stringify(fcstd.webInspection?.objectTypes) !== JSON.stringify(['Path::Feature', 'Path::FeaturePython']) || fcstd.webInspection?.pathPropertyTypes?.some((type) => type !== 'Path::PropertyPath')) fail('Web inspection did not retain Path types')
|
|
if (fcstd.webInspection?.proxyReadOnly !== true || fcstd.webInspection?.compatibility?.blockedObjects < 1) fail('the Web FeaturePython security boundary was lost')
|
|
if (fcstd.webTransparentPreservation?.byteExact !== true || fcstd.webTransparentPreservation?.nativeReopenVerified !== true || fcstd.webTransparentPreservation?.objects?.some((object) => object.commandCount !== 5)) fail('native-Web-native transparent round-trip evidence is incomplete')
|
|
if (fcstd.webEditablePathProperty?.objectName !== 'NativePath' || fcstd.webEditablePathProperty?.sourceCommandCount !== 5 || fcstd.webEditablePathProperty?.editedCommandCount !== 6 || fcstd.webEditablePathProperty?.lastCommand?.name !== 'M3' || fcstd.webEditablePathProperty?.nativeReopenVerified !== true || fcstd.webEditablePathProperty?.featurePythonExecutionBlocked !== true) fail('the editable native Path::PropertyPath codec evidence is incomplete')
|
|
if (fcstd.webEditableFeaturePythonPathProperty?.objectName !== 'PythonPath' || fcstd.webEditableFeaturePythonPathProperty?.sourceCommandCount !== 5 || fcstd.webEditableFeaturePythonPathProperty?.editedCommandCount !== 6 || fcstd.webEditableFeaturePythonPathProperty?.lastCommand?.name !== 'M2' || fcstd.webEditableFeaturePythonPathProperty?.nativeReopenVerified !== true || fcstd.webEditableFeaturePythonPathProperty?.scriptExecution !== 'blocked' || fcstd.webEditableFeaturePythonPathProperty?.optInRequired !== true) fail('the safe opt-in FeaturePython Path::PropertyPath codec evidence is incomplete')
|
|
if (report.claims?.nativeProfilePathAlgorithm !== 'verified' || report.claims?.nativeHelixPathAlgorithm !== 'verified-four-direction-cases' || report.claims?.qtCamWorkbenchDynamicOracle !== 'verified' || report.claims?.nativePathFeatureFcstdSaveOpenMutateSaveOpen !== 'verified' || report.claims?.nativeWebNativePathFcstdTransparentRoundTrip !== 'verified' || report.claims?.webEditablePathPropertyCodec !== 'verified-native-path-feature') fail('claim states are inconsistent')
|
|
|
|
console.log(JSON.stringify({
|
|
status: report.status,
|
|
profileCommands: report.profileAlgorithm.commandCount,
|
|
cuttingCommands: report.profileAlgorithm.cuttingCommandCount,
|
|
helixScenarios: helix.scenarioCount,
|
|
camCommands: qt.camCommandCount,
|
|
qtVersion: qt.qtVersion,
|
|
nativeRoundTripObjects: fcstd.sourceReopened.length,
|
|
webTransparentRoundTrip: fcstd.webTransparentPreservation.nativeReopenVerified,
|
|
remainingGap: report.boundary.remainingGap,
|
|
}, null, 2))
|