import { createHash } from 'node:crypto' import { access, readFile } from 'node:fs/promises' import { resolve } from 'node:path' import { pathToFileURL } from 'node:url' const root = resolve(new URL('..', import.meta.url).pathname) const artifactNames = ['bitbybit-occt-history.js', 'bitbybit-occt-history.wasm', 'bitbybit-occt-history.data'] const distRoot = resolve(root, 'native/occt-history/dist') const publicRoot = resolve(root, 'public/native/occt-history') const exists = async (path) => access(path).then(() => true).catch(() => false) const hash = (bytes) => createHash('sha256').update(bytes).digest('hex') const distPresent = await Promise.all(artifactNames.map((name) => exists(resolve(distRoot, name)))) const publicPresent = await Promise.all(artifactNames.map((name) => exists(resolve(publicRoot, name)))) if (!distPresent.some(Boolean) && !publicPresent.some(Boolean)) { console.log(JSON.stringify({ status: 'artifact-not-built', reason: 'Run ./npmw run build:occt-history when the pinned Emscripten/OCCT inputs are available.' }, null, 2)) process.exit(0) } if (distPresent.some((present) => !present) || publicPresent.some((present) => !present)) throw new Error('OCCT history browser artifact is incomplete; JS, WASM and DATA must be published together.') for (const name of artifactNames) { const distBytes = await readFile(resolve(distRoot, name)) const publicBytes = await readFile(resolve(publicRoot, name)) if (hash(distBytes) !== hash(publicBytes)) throw new Error(`OCCT history ${name} differs between dist and public deployment paths.`) if (distBytes.length === 0) throw new Error(`OCCT history ${name} is empty.`) } const javascript = await readFile(resolve(publicRoot, 'bitbybit-occt-history.js'), 'utf8') if (!javascript.includes('bitbybit-occt-history.wasm') || !javascript.includes('export default')) throw new Error('OCCT history JS artifact does not expose the expected ESM factory and WASM locator.') const workerClient = await readFile(resolve(root, 'src/facade/nativeHistoryWorkerClient.ts'), 'utf8') if (!workerClient.includes("'/native/occt-history/bitbybit-occt-history.js'")) throw new Error('Native history Worker client URL does not match the published artifact.') const files = [] for (const name of artifactNames) files.push({ name, sha256: hash(await readFile(resolve(publicRoot, name))) }) let namingAbi = { availability: 'unavailable', callbacks: [] } const factoryModule = await import(pathToFileURL(resolve(distRoot, 'bitbybit-occt-history.js')).href) const factory = factoryModule.default if (typeof factory !== 'function') throw new TypeError('OCCT history artifact default export is not a factory.') const nativeModule = await factory({ locateFile: (fileName) => resolve(distRoot, fileName.split('/').at(-1)) }) const callbacks = ['freecadNamingAbiVersion', 'freecadNamingCapabilitiesJson', 'freecadNamingEvidenceJson'].filter((name) => typeof nativeModule[name] === 'function') if (callbacks.length !== 0 && callbacks.length !== 3) throw new Error(`FreeCAD naming ABI is partially exported: ${callbacks.join(', ')}.`) if (callbacks.length === 3) { const abiVersion = nativeModule.freecadNamingAbiVersion() const descriptor = JSON.parse(nativeModule.freecadNamingCapabilitiesJson()) if (abiVersion !== 1 || descriptor.schemaVersion !== 1 || descriptor.freecadVersion !== '1.1.1' || descriptor.sourceCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || descriptor.mappedNameRef !== true || descriptor.stringHasher !== true || descriptor.elementMap2 !== true || !Array.isArray(descriptor.operations) || !descriptor.operations.includes('cut')) throw new Error('FreeCAD naming ABI descriptor is not the locked v1 contract with Cut support.') let object let tool let evidence try { object = nativeModule.makeBox(10, 10, 10) tool = nativeModule.makeBox(5, 5, 5) const objectStep = nativeModule.shapeToStep(object) const toolStep = nativeModule.shapeToStep(tool) const history = nativeModule.booleanHistoryFromStep(objectStep, toolStep, 'cut') const seenResults = new Set() const records = [] for (const record of history.records) { if (record.relation === 'deleted') continue for (const resultIndex of record.resultIndexes ?? [record.resultIndex]) { const key = `${record.resultKind ?? record.kind}:${resultIndex}` if (!Number.isSafeInteger(resultIndex) || resultIndex < 0 || seenResults.has(key)) continue seenResults.add(key) records.push({ ...record, resultIndex, resultIndexes: undefined }) } } if (records.length === 0) throw new Error('Artifact Cut probe returned no unique native result relations.') const request = { schemaVersion: 1, requestId: 'artifact-private-naming-probe', documentId: 'artifact-probe-document', documentVersion: 1, operationId: 'artifact-probe-cut', operation: 'cut', stageId: 'artifact-probe:stage:0', resultObjectId: 'artifact-probe:result', resultObjectTag: 3, inputs: [ { inputId: 'object', objectId: 'artifact-probe:object', role: 'object', step: objectStep, objectTag: 1 }, { inputId: 'tool', objectId: 'artifact-probe:tool', role: 'tool', step: toolStep, objectTag: 2 }, ], stages: [{ stageId: 'artifact-probe:stage:0', operation: 'cut', inputIds: ['object', 'tool'], ordinal: 0 }], resultStep: history.resultStep, resultBrep: history.resultBrep, history: { ...history, records }, } evidence = JSON.parse(nativeModule.freecadNamingEvidenceJson(JSON.stringify(request))) } finally { object?.delete?.() tool?.delete?.() } if (!evidence || evidence.schemaVersion !== 1 || evidence.stageId !== 'artifact-probe:stage:0' || evidence.resultObjectId !== 'artifact-probe:result' || !['native-evidence', 'ambiguous'].includes(evidence.status) || !Array.isArray(evidence.mappedNames) || evidence.mappedNames.length === 0 || !evidence.stringHasher || !evidence.elementMap2) throw new Error('FreeCAD naming ABI did not return complete stage-bound Cut evidence.') namingAbi = { availability: 'available', callbacks, abiVersion, descriptor, evidenceProbe: { operation: 'cut', status: evidence.status, mappedNames: evidence.mappedNames.length, stringHasher: true, elementMap2: true } } } const abiContract = JSON.parse(await readFile(resolve(root, 'config/freecad-sketcher-partdesign-abi-contract.json'), 'utf8')) const expectedImplementation = abiContract.privateNamingAbi?.shippedWorkerImplementation if (expectedImplementation === 'not-linked' && namingAbi.availability !== 'unavailable') throw new Error('OCCT history artifact exports FreeCAD naming callbacks but the shipped-worker contract still says not-linked.') if (expectedImplementation === 'freecad-linked' && namingAbi.availability !== 'available') throw new Error('Shipped-worker contract claims FreeCAD linkage but the artifact ABI probe is unavailable.') if (!['not-linked', 'freecad-linked'].includes(expectedImplementation)) throw new Error('Shipped-worker FreeCAD naming implementation status is invalid.') console.log(JSON.stringify({ status: 'artifact-pass', files, namingAbi, exactBoundary: namingAbi.availability === 'available' ? 'production-linked; EX-TSN-02 completed; broader exact plan remains open' : 'invalid production boundary' }, null, 2))