import { createHash } from 'node:crypto' import { execFileSync } from 'node:child_process' import { existsSync } from 'node:fs' import { readFile } from 'node:fs/promises' import { resolve } from 'node:path' const root = resolve(new URL('..', import.meta.url).pathname) const evidence = JSON.parse(await readFile(resolve(root, 'config/opencamlib-wasm-artifact.json'), 'utf8')) if (evidence.schemaVersion !== 1 || evidence.status !== 'pass') throw new Error('OpenCAMLib artifact evidence is not passing.') const source = resolve(root, evidence.openCamLib.sourcePath) const revision = execFileSync('git', ['-C', source, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim() if (revision !== evidence.openCamLib.sourceRevision) throw new Error(`OpenCAMLib revision drift: ${revision}`) const artifact = await readFile(resolve(root, evidence.openCamLib.artifact)) const digest = createHash('sha256').update(artifact).digest('hex') if (artifact.byteLength !== evidence.openCamLib.byteLength || digest !== evidence.openCamLib.sha256) throw new Error('OpenCAMLib WASM artifact size or digest mismatch.') const binding = await readFile(resolve(root, evidence.openCamLib.bindingSource), 'utf8') for (const symbol of ['PathDropCutter', 'AdaptivePathDropCutter', 'Waterline', 'AdaptiveWaterline', 'CylCutter', 'BallCutter']) { if (!binding.includes(`\"${symbol}\"`)) throw new Error(`OpenCAMLib binding is missing ${symbol}.`) } const artifactText = artifact.toString('utf8') if (!artifactText.includes('data:application/octet-stream;base64,') || !artifactText.includes('WebAssembly.instantiate')) throw new Error('OpenCAMLib artifact does not contain an embedded executable WASM module.') if (evidence.solidRemoval.backend !== 'bitbybit-occt-wasm' || evidence.solidRemoval.nativeSolid !== true || evidence.solidRemoval.freeCadNativeEquivalent !== false) throw new Error('OCCT solid-removal boundary is invalid.') if (evidence.camotics.status !== 'native-host-available' || evidence.camotics.backend !== 'camotics-native-host-qt5-tpl' || evidence.camotics.nativeExecutable !== true || evidence.camotics.nativeBuildStatus !== 'verified-qt5-gui-tpl' || evidence.camotics.nativeGuiPath !== 'CAMotics/camotics' || evidence.camotics.nativeTplPath !== 'CAMotics/tplang' || evidence.camotics.browserExecutable !== false || evidence.camotics.browserWasmKernelExecutable !== true || evidence.camotics.browserWasmKernel?.fullCamoticsProgram !== false || evidence.camotics.browserWasmKernel.gcodeParserIncluded !== false) throw new Error('CAMotics native GUI/TPL/WASM capability boundary is invalid.') const camoticsSource = resolve(root, evidence.camotics.sourcePath) const camoticsRevision = execFileSync('git', ['-C', camoticsSource, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim() if (camoticsRevision !== evidence.camotics.sourceRevision) throw new Error(`CAMotics revision drift: ${camoticsRevision}`) for (const sourceFile of evidence.camotics.sourceFiles) if (!existsSync(resolve(root, sourceFile))) throw new Error(`CAMotics source evidence is missing ${sourceFile}.`) const camoticsGenerator = await readFile(resolve(root, 'CAMotics/src/gcode/machine/GCodeMachine.cpp'), 'utf8') for (const method of ['start', 'changeTool', 'setFeed', 'move', 'end']) if (!camoticsGenerator.includes(`GCodeMachine::${method}`)) throw new Error(`CAMotics G-code generator source is missing ${method}.`) for (const nativeArtifact of [evidence.camotics.nativeGuiPath, evidence.camotics.nativeTplPath]) if (!existsSync(resolve(root, nativeArtifact))) throw new Error(`CAMotics native artifact is missing: ${nativeArtifact}`) if (!existsSync(resolve(root, evidence.camotics.browserWasmKernel.artifact))) throw new Error('CAMotics browser WASM artifact is missing.') if (!existsSync(resolve(root, evidence.camotics.nativeArtifactEvidence))) throw new Error('CAMotics native artifact evidence is missing.') if (evidence.camotics.generationAuthority !== 'camotics-source-stage' || evidence.camotics.parserAuthority !== 'linuxcnc-wasm' || evidence.camotics.camoticsParsesCanonicalGcode !== false || evidence.camotics.pipeline?.canonicalGcodeAuthority !== 'linuxcnc-wasm' || evidence.camotics.pipeline?.camoticsSemanticParser !== false || evidence.camotics.pipeline?.linuxcncWasmExecution !== 'handoff-required') throw new Error('CAMotics/LinuxCNC pipeline authority is invalid.') const nativeEvidence = JSON.parse(await readFile(resolve(root, evidence.camotics.nativeArtifactEvidence), 'utf8')) if (nativeEvidence.schemaVersion !== 2 || nativeEvidence.status !== 'pass' || nativeEvidence.source.revision !== evidence.camotics.sourceRevision || nativeEvidence.cbang.revision !== evidence.camotics.cbangRevision || nativeEvidence.build.withGui !== true || nativeEvidence.build.withTpl !== true || nativeEvidence.guiSmoke.status !== 'pass') throw new Error('CAMotics native GUI/TPL evidence is missing or stale.') console.log(JSON.stringify({ status: 'opencamlib-wasm-pass', sourceRevision: revision, artifactBytes: artifact.byteLength, sha256: digest, algorithms: evidence.openCamLib.algorithms, solidRemoval: evidence.solidRemoval, camoticsRevision, camotics: evidence.camotics, nativeEvidence: { artifacts: nativeEvidence.artifacts, tplTests: nativeEvidence.tplTests, guiSmoke: nativeEvidence.guiSmoke } }, null, 2))