feat: extend FreeCAD parity queue and pair diagnostics
Some checks failed
real-verification / chrome (push) Has been cancelled
real-verification / freecad-oracle (push) Has been cancelled
real-verification / wasm (push) Has been cancelled

This commit is contained in:
2026-08-15 06:29:20 -04:00
parent 83cbc80971
commit 378a6e7c0a
13 changed files with 21979 additions and 84 deletions

View File

@@ -18,25 +18,54 @@ for (const artifact of report.nativeProbe?.artifacts ?? []) {
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] },
{ pair: 'fuse->fuse', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepAlgoAPI_Fuse', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [12, 11, 12] },
{ pair: 'fuse->cut', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'toolSize', mutationTrajectory: [4, 3, 4] },
{ pair: 'fuse->common', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepAlgoAPI_Common', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [10, 9, 10] },
{ pair: 'fuse->rotate', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepBuilderAPI_Transform', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [15, 22.5, 15] },
{ pair: 'fuse->pad', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakePrism', secondInputCount: 1, mutationParameter: 'length', mutationTrajectory: [5, 7.5, 5], nativeTypeId: 'PartDesign::Pad' },
{ pair: 'fuse->pocket', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', secondBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'length', mutationTrajectory: [5, 4, 5] },
{ pair: 'fuse->loft', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', rejectionAuthority: 'freecad-document', kernelOutcome: 'accepted', secondBuilder: 'BRepOffsetAPI_ThruSections', secondInputCount: 2, mutationParameter: 'ruled', mutationTrajectory: [false, true, false], nativeTypeId: 'Part::Loft', freeCadDiagnostic: 'Profile shape is not a single vertex, edge, wire nor face.' },
{ pair: 'fuse->pipe', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', rejectionAuthority: 'freecad-document', kernelOutcome: 'invalid-result', secondBuilder: 'BRepOffsetAPI_MakePipe', secondInputCount: 2, mutationParameter: 'spineLength', mutationTrajectory: [15, 12, 15], nativeTypeId: 'Part::Sweep', freeCadDiagnostic: 'A fatal error occurred when making the sweep' },
]
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.taskId !== `TSN-PAIR-${expected.pair.replace('->', '-')}` || entry.pair !== expected.pair || entry.classification !== expected.decision || entry.reasonCode !== expected.reasonCode || entry.nativeDecision !== expected.decision || entry.first?.operation !== 'fuse' || entry.first.builder !== 'BRepAlgoAPI_Fuse' || entry.first.inputCount !== 2 || entry.first.historyProvider !== 'occt-native' || entry.first.summary?.isValid !== true || entry.first.historyRecords < 1) fail(`${expected.pair} decision contract is invalid.`)
if (expected.decision === 'rejected') {
if ('second' in entry || 'mutation' in entry || 'naming' in entry) fail(`${expected.pair} fabricates evidence for a result that FreeCAD rejected.`)
const rejection = entry.rejection
if (rejection?.scope !== 'second-operation-only' || rejection.authority !== expected.rejectionAuthority || rejection.parameter !== expected.mutationParameter || JSON.stringify(rejection.trajectory) !== JSON.stringify(expected.mutationTrajectory)) fail(`${expected.pair} rejection trajectory is invalid.`)
if (expected.rejectionAuthority === 'occt-builder') {
if (rejection.stable !== true || rejection.attempts?.length !== 3) fail(`${expected.pair} OCCT rejection is unstable.`)
for (const [attemptIndex, attempt] of rejection.attempts.entries()) {
if (attempt.operation !== secondOperation || attempt.builder !== expected.secondBuilder || attempt.inputCount !== expected.secondInputCount || attempt.parameter !== expected.mutationParameter || attempt.parameterValue !== expected.mutationTrajectory[attemptIndex] || attempt.runtime !== 'occt-native' || attempt.outcome !== 'rejected' || attempt.resultProduced !== false || attempt.shapeProduced !== false || attempt.historyProduced !== false || attempt.rejectionStage !== 'builder' || attempt.exceptionTransport !== 'emscripten-cpp-exception' || attempt.thrownType !== 'number') fail(`${expected.pair} OCCT attempt ${attemptIndex} is invalid.`)
for (const absent of ['summary', 'historyProvider', 'historyRecords', 'historySha256', 'namingStatus', 'namingEvidenceSha256', 'namingSemanticSha256', 'resultStep', 'resultBrep', 'shape', 'history']) if (absent in attempt) fail(`${expected.pair} OCCT attempt ${attemptIndex} fabricates ${absent}.`)
}
} else {
if (rejection.kernelRestoredExactly !== true || rejection.kernelAttempts?.length !== 3) fail(`${expected.pair} OCCT diagnostic trajectory is incomplete.`)
for (const [attemptIndex, attempt] of rejection.kernelAttempts.entries()) if (attempt.operation !== secondOperation || attempt.builder !== expected.secondBuilder || attempt.inputCount !== expected.secondInputCount || attempt.parameter !== expected.mutationParameter || attempt.parameterValue !== expected.mutationTrajectory[attemptIndex] || attempt.runtime !== 'occt-native' || attempt.outcome !== expected.kernelOutcome || attempt.resultProduced !== true || attempt.shapeProduced !== true || attempt.validShapeProduced !== (expected.kernelOutcome === 'accepted') || attempt.historyProduced !== true || attempt.historyProvider !== 'occt-native' || attempt.summary?.isValid !== (expected.kernelOutcome === 'accepted') || attempt.historyRecords < 1 || !attempt.historySha256 || attempt.authority !== (expected.kernelOutcome === 'accepted' ? 'kernel-superset-only' : 'kernel-invalid-result')) fail(`${expected.pair} OCCT compatibility attempt ${attemptIndex} is invalid.`)
}
const persistence = rejection.persistence
const diagnostic = persistence?.diagnostic
if (persistence?.status !== 'pass' || persistence.decision !== 'rejected' || persistence.pair !== entry.pair || persistence.operation !== secondOperation || persistence.nativeTypeId !== expected.nativeTypeId || persistence.freecadVersion !== '1.1.1' || !Object.values(persistence.checks ?? {}).every(Boolean)) fail(`${expected.pair} FCStd rejection evidence is incomplete.`)
if (diagnostic?.nativeTypeId !== expected.nativeTypeId || diagnostic.parameter?.name !== expected.mutationParameter || diagnostic.parameter.value !== expected.mutationTrajectory[0] || diagnostic.inputShape?.solids !== 1 || diagnostic.recomputeResult !== true || diagnostic.shapeNull !== true || diagnostic.shapeValid !== false || !diagnostic.state?.includes('Invalid') || diagnostic.statusString === 'Valid' || !diagnostic.objects?.some(({ name, typeId }) => name === 'PairResult' && typeId === expected.nativeTypeId)) fail(`${expected.pair} did not capture a real rejected FreeCAD recompute.`)
if (expected.rejectionAuthority === 'freecad-document' && diagnostic.statusString !== expected.freeCadDiagnostic) fail(`${expected.pair} FreeCAD profile diagnostic changed.`)
if (persistence.source?.shape?.solids !== 1 || !persistence.source.shape.brepSha256 || JSON.stringify(persistence.rollback?.before) !== JSON.stringify(persistence.rollback?.after)) fail(`${expected.pair} abort did not restore the source object set and Shape.`)
const phases = persistence.phases
if (JSON.stringify(phases?.initial) !== JSON.stringify(phases?.reopened) || JSON.stringify(phases?.initial) !== JSON.stringify(phases?.resaved) || phases.initial.objects?.length !== persistence.rollback.before.objects.length || phases.initial.objects?.some(({ typeId }) => typeId === 'PartDesign::Body' || typeId === 'PartDesign::Pad' || typeId === 'Part::Loft')) fail(`${expected.pair} polluted the clean FCStd save/reopen/resave chain.`)
continue
}
if (entry.second?.operation !== secondOperation || entry.second.builder !== expected.secondBuilder || entry.second.inputCount !== expected.secondInputCount || entry.second.historyProvider !== 'occt-native') fail(`${expected.pair} accepted second operation is invalid.`)
if (entry.second.summary?.isValid !== true || 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.`)
if (entry.persistence?.status !== 'pass' || entry.persistence.decision !== 'accepted' || 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 }
const expectedSummary = { registeredOperations: 19, orderedPairs: 361, classifiedPairs: 8, accepted: 5, rejected: 3, unknown: 353 }
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))