import { createHash } from 'node:crypto' import { readFile, stat } 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-ordered-operation-pair-classification.json'), 'utf8')) const fail = (message) => { throw new Error(`FreeCAD ordered operation pair classification: ${message}`) } const sha256 = (value) => createHash('sha256').update(value).digest('hex') if (report.schemaVersion !== 1 || report.baseline?.freecadVersion !== '1.1.1' || report.baseline.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || !/^8\./.test(report.baseline.occtVersion)) fail('baseline is invalid.') for (const harness of [report.nativeProbe?.executor, report.nativeProbe?.matrix, report.nativeProbe?.resave]) { const path = resolve(root, harness?.path ?? '') const [content, bytes] = await Promise.all([readFile(path), stat(path).then(({ size }) => size)]) if (bytes !== harness.bytes || sha256(content) !== harness.sha256) fail(`${harness.path} provenance is stale.`) } for (const artifact of report.nativeProbe?.artifacts ?? []) { const path = resolve(root, 'native/occt-history/dist', artifact.name) const [content, bytes] = await Promise.all([readFile(path), stat(path).then(({ size }) => size)]) if (bytes !== artifact.bytes || sha256(content) !== artifact.sha256) fail(`${artifact.name} provenance is stale.`) } const expectedPairs = [ { pair: 'fuse->fuse', secondBuilder: 'BRepAlgoAPI_Fuse', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [12, 11, 12] }, { pair: 'fuse->cut', secondBuilder: 'BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'toolSize', mutationTrajectory: [4, 3, 4] }, { pair: 'fuse->common', secondBuilder: 'BRepAlgoAPI_Common', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [10, 9, 10] }, { pair: 'fuse->rotate', secondBuilder: 'BRepBuilderAPI_Transform', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [15, 22.5, 15] }, ] if (report.nativeProbe?.artifacts?.length !== 3 || report.classifications?.length !== expectedPairs.length) fail('native artifacts or classification prefix is incomplete.') for (const [index, entry] of report.classifications.entries()) { const expected = expectedPairs[index] const secondOperation = expected.pair.split('->')[1] if (entry.taskId !== `TSN-PAIR-${expected.pair.replace('->', '-')}` || entry.pair !== expected.pair || entry.classification !== 'accepted' || entry.nativeDecision !== 'accepted' || entry.first?.operation !== 'fuse' || entry.first.builder !== 'BRepAlgoAPI_Fuse' || entry.first.inputCount !== 2 || entry.second?.operation !== secondOperation || entry.second.builder !== expected.secondBuilder || entry.second.inputCount !== expected.secondInputCount || entry.first.historyProvider !== 'occt-native' || entry.second.historyProvider !== 'occt-native') fail(`${expected.pair} decision contract is invalid.`) if (entry.first.summary?.isValid !== true || entry.second.summary?.isValid !== true || entry.first.historyRecords < 1 || entry.second.historyRecords < 1 || entry.mutation?.changed !== true || entry.mutation?.restoredExactly !== true) fail(`${expected.pair} builder or mutation evidence is incomplete.`) if (entry.mutation.scope !== 'second-operation-only' || entry.mutation.parameter !== expected.mutationParameter || JSON.stringify(entry.mutation.trajectory) !== JSON.stringify(expected.mutationTrajectory) || entry.mutation.beforeValue !== expected.mutationTrajectory[0] || entry.mutation.editedValue !== expected.mutationTrajectory[1] || entry.mutation.restoredValue !== expected.mutationTrajectory[2]) fail(`${expected.pair} mutation trajectory is invalid.`) if (entry.mutation.editedEvidence?.operation !== secondOperation || entry.mutation.restoredEvidence?.operation !== secondOperation || entry.mutation.editedEvidence?.builder !== expected.secondBuilder || entry.mutation.restoredEvidence?.builder !== expected.secondBuilder || entry.mutation.editedEvidence?.inputCount !== expected.secondInputCount || entry.mutation.restoredEvidence?.inputCount !== expected.secondInputCount || entry.mutation.editedEvidence?.historyProvider !== 'occt-native' || entry.mutation.restoredEvidence?.historyProvider !== 'occt-native') fail(`${expected.pair} mutation history provider is invalid.`) if (entry.second.namingSemanticSha256 !== entry.mutation.restoredEvidence?.namingSemanticSha256) fail(`${expected.pair} semantic naming evidence did not restore to nominal.`) if (entry.naming?.upstreamEvidenceRestored !== true || entry.naming?.downstreamEvidence !== true || entry.naming?.jsonRoundtripStable !== true) fail(`${expected.pair} naming evidence is incomplete.`) if (entry.persistence?.status !== 'pass' || entry.persistence.pair !== entry.pair || entry.persistence.freecadVersion !== '1.1.1' || !Object.values(entry.persistence.checks ?? {}).every(Boolean)) fail(`${expected.pair} FCStd persistence evidence is incomplete.`) const phases = entry.persistence.phases if (JSON.stringify(phases?.initial) !== JSON.stringify(phases?.reopened) || JSON.stringify(phases?.initial) !== JSON.stringify(phases?.resaved) || phases.initial.namingEvidenceSha256 !== entry.second.namingEvidenceSha256) fail(`${expected.pair} FCStd phases changed Shape or naming evidence.`) } const expectedSummary = { registeredOperations: 19, orderedPairs: 361, classifiedPairs: 4, accepted: 4, rejected: 0, unknown: 357 } if (JSON.stringify(report.summary) !== JSON.stringify(expectedSummary)) fail('summary is inconsistent.') console.log(JSON.stringify({ status: 'freecad-ordered-operation-pair-classification-pass', completedTasks: report.classifications.map(({ taskId }) => taskId), pairs: report.classifications.map(({ pair, classification }) => ({ pair, classification })), nativeBuilderRuns: report.classifications.length * 4, fcstdPhases: report.classifications.length * 3, remainingPairs: report.summary.unknown }, null, 2))