feat: advance FreeCAD exact parity evidence
This commit is contained in:
@@ -1,16 +1,25 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import { elementMap2SemanticDigest, parseElementMap2, validateElementMap2, writeElementMap2 } from '../src/facade/elementMap2.ts'
|
||||
import { validateNativeNamingEvidence } from '../src/facade/nativeNamingEvidence.ts'
|
||||
import { migrateStringHasherSchema, parseStringHasherTable, validateStringHasherTable, writeStringHasherTable } from '../src/facade/stringHasher.ts'
|
||||
import { validateCompositeMutationEvidence } from './freecad-composite-mutation-evidence.mjs'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/freecad-composite-history-elementmap-oracle.json'), 'utf8'))
|
||||
const resaveReport = JSON.parse(await readFile(resolve(root, 'config/freecad-composite-history-resave-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.baselineId !== 'freecad-1.1.1-composite-history-elementmap2' || report.freecadVersion !== '1.1.1' || report.status !== 'pass' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') throw new Error('FreeCAD composite history ElementMap2 oracle baseline is invalid.')
|
||||
if (report.summary?.cases !== 30 || report.summary?.passed !== 30 || report.summary?.failed !== 0 || !Array.isArray(report.cases) || report.cases.length !== 30) throw new Error('FreeCAD composite oracle must contain exactly 30 passing cases.')
|
||||
if (resaveReport.schemaVersion !== 1 || resaveReport.baselineId !== report.baselineId || resaveReport.freecadVersion !== report.freecadVersion || resaveReport.gitCommit !== report.gitCommit || resaveReport.status !== 'pass' || resaveReport.summary?.cases !== 30 || resaveReport.summary?.passed !== 30 || resaveReport.summary?.failed !== 0 || resaveReport.summary?.roundtripNameDrift !== 0 || resaveReport.summary?.resaveNameDrift !== 0 || resaveReport.summary?.nativeDesktopResaveCases !== 30 || !Array.isArray(resaveReport.cases) || resaveReport.cases.length !== 30) throw new Error('FreeCAD composite resave oracle must contain exactly 30 passing save/reopen/resave cases.')
|
||||
if (report.summary?.cases !== 30 || report.summary?.passed !== 30 || report.summary?.failed !== 0 || report.summary?.stageCorrelations !== 219 || !(report.summary?.relationRecords > 0) || !Array.isArray(report.cases) || report.cases.length !== 30) throw new Error('FreeCAD composite oracle must contain exactly 30 passing cases and 219 correlated stages.')
|
||||
if (resaveReport.schemaVersion !== 1 || resaveReport.baselineId !== report.baselineId || resaveReport.freecadVersion !== report.freecadVersion || resaveReport.gitCommit !== report.gitCommit || resaveReport.status !== 'pass' || resaveReport.summary?.cases !== 30 || resaveReport.summary?.passed !== 30 || resaveReport.summary?.failed !== 0 || resaveReport.summary?.roundtripNameDrift !== 0 || resaveReport.summary?.resaveNameDrift !== 0 || resaveReport.summary?.nativeDesktopResaveCases !== 30 || resaveReport.summary?.stageCorrelations !== 219 || resaveReport.summary?.relationRecords !== report.summary.relationRecords || !Array.isArray(resaveReport.cases) || resaveReport.cases.length !== 30) throw new Error('FreeCAD composite resave oracle must contain exactly 30 passing save/reopen/resave cases and 219 correlated stages.')
|
||||
const reportIds = report.cases.map(({ id }) => id)
|
||||
const resaveIds = resaveReport.cases.map(({ id }) => id)
|
||||
if (new Set(reportIds).size !== 30 || JSON.stringify(resaveIds) !== JSON.stringify(reportIds)) throw new Error('FreeCAD composite and resave case identities are not joined one-to-one.')
|
||||
const resaveById = new Map(resaveReport.cases.map((fixture) => [fixture.id, fixture]))
|
||||
for (const fixture of resaveReport.cases) if (fixture.roundtripNameDrift !== 0 || fixture.resaveNameDrift !== 0 || fixture.nativeDesktopResaveCovered !== true) throw new Error(`FreeCAD composite fixture ${fixture.id} changed mapped names after native FreeCAD resave.`)
|
||||
const canonical = (value) => Array.isArray(value) ? value.map(canonical) : value && typeof value === 'object' ? Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, canonical(entry)])) : value
|
||||
const digest = (value) => createHash('sha256').update(JSON.stringify(canonical(value))).digest('hex')
|
||||
const mutationEvidence = validateCompositeMutationEvidence(report)
|
||||
let parsedResources = 0
|
||||
let parsedStringHasherResources = 0
|
||||
let namingEvidenceStages = 0
|
||||
@@ -22,12 +31,27 @@ let nativeIndexedNameStages = 0
|
||||
let privateTokenEvidenceCompleteStages = 0
|
||||
let internalBuilderEvidenceStages = 0
|
||||
let internalBuilderEvidenceMissingStages = 0
|
||||
let stageCorrelations = 0
|
||||
let relationRecords = 0
|
||||
let wrongBindings = 0
|
||||
let unexplainedRelations = 0
|
||||
for (const fixture of report.cases) {
|
||||
if (fixture.status !== 'pass' || !fixture.finalObject || !fixture.stages?.length) throw new Error(`FreeCAD composite fixture ${fixture.id} has no valid feature history stages.`)
|
||||
if (fixture.roundtripNameDrift !== 0) throw new Error(`FreeCAD composite fixture ${fixture.id} changed mapped names after FreeCAD reopen.`)
|
||||
const names = new Set()
|
||||
const stageNames = new Set(fixture.stages.map(({ name }) => name))
|
||||
const stageByName = new Map(fixture.stages.map((stage) => [stage.name, stage]))
|
||||
const dependencyClosure = (stageName, seen = new Set()) => {
|
||||
if (seen.has(stageName)) return seen
|
||||
seen.add(stageName)
|
||||
for (const linked of stageByName.get(stageName)?.linkedObjects ?? []) if (stageNames.has(linked)) dependencyClosure(linked, seen)
|
||||
return seen
|
||||
}
|
||||
const resaveFixture = resaveById.get(fixture.id)
|
||||
const correlations = resaveFixture?.stageCorrelations
|
||||
if (!correlations || !['initial', 'reopened', 'resaved'].every((phase) => Array.isArray(correlations[phase]) && correlations[phase].length === fixture.stages.length)) throw new Error(`FreeCAD composite fixture ${fixture.id} lacks complete initial/reopen/resave stage correlations.`)
|
||||
let historyStages = 0
|
||||
for (const stage of fixture.stages) {
|
||||
for (const [ordinal, stage] of fixture.stages.entries()) {
|
||||
if (!stage.shape?.valid || !stage.names?.length) throw new Error(`FreeCAD composite fixture ${fixture.id}/${stage.name} has invalid topology.`)
|
||||
namingEvidenceStages += 1
|
||||
if (!['final-shape-only', 'opaque-preserved', 'native-evidence', 'ambiguous', 'missing'].includes(stage.namingEvidenceStatus)) namingEvidenceMissing += 1
|
||||
@@ -49,6 +73,25 @@ for (const fixture of report.cases) {
|
||||
else if (stage.nativeEvidence.status === 'native-evidence' && stage.nativeEvidence.internalBuilderEvidence !== true && /^(Part::(Fuse|Cut|Common|Extrusion|Revolution|Loft|Sweep|Fillet|Chamfer)|PartDesign::(Pad|Pocket|Revolution|Groove|AdditiveLoft|SubtractiveLoft|AdditivePipe|SubtractivePipe|Fillet|Chamfer|Draft|Thickness|Mirrored|MultiTransform|LinearPattern|PolarPattern|Hole))$/.test(stage.typeId || '')) internalBuilderEvidenceMissingStages += 1
|
||||
const stageHasHistory = stage.names.some((entry) => Array.isArray(entry.history) && entry.history.length > 0)
|
||||
if (stageHasHistory) historyStages += 1
|
||||
if (!Array.isArray(stage.linkedObjects) || !Array.isArray(stage.relations) || stage.relations.length !== stage.names.length || stage.relationDigest !== digest(stage.relations) || !/^[0-9a-f]{64}$/.test(stage.semanticNameDigest || '')) throw new Error(`FreeCAD composite fixture ${fixture.id}/${stage.name} lacks relation-level naming evidence.`)
|
||||
const expectedKey = `${fixture.id}:${String(ordinal).padStart(3, '0')}:${stage.name}`
|
||||
const phaseRecords = ['initial', 'reopened', 'resaved'].map((phase) => correlations[phase][ordinal])
|
||||
for (const record of phaseRecords) if (record.key !== expectedKey || record.ordinal !== ordinal || record.name !== stage.name || record.typeId !== stage.typeId || record.relationCount !== stage.relations.length) throw new Error(`FreeCAD composite fixture ${expectedKey} changed stage identity across native resave.`)
|
||||
if (phaseRecords.some((record) => record.relationDigest !== stage.relationDigest || record.semanticNameDigest !== stage.semanticNameDigest)) throw new Error(`FreeCAD composite fixture ${expectedKey} changed relation or semantic-name evidence across native resave.`)
|
||||
stageCorrelations += 1
|
||||
relationRecords += stage.relations.length
|
||||
const allowedSources = dependencyClosure(stage.name)
|
||||
allowedSources.delete(stage.name)
|
||||
let stageSourceRelations = 0
|
||||
for (const [relationIndex, relation] of stage.relations.entries()) {
|
||||
if (relation.resultName !== stage.names[relationIndex]?.name || !['generated', 'modified', 'preserved'].includes(relation.relation) || !Array.isArray(relation.sources)) unexplainedRelations += 1
|
||||
for (const source of relation.sources) {
|
||||
stageSourceRelations += 1
|
||||
if (!stageNames.has(source.sourceObject) || !/^(?:(?:Face|Edge|Vertex)\d+|#[a-zA-Z0-9]+:[0-9a-fA-F]+|#[0-9a-fA-F]+|g\d+(?:v\d+)?;SKT)$/.test(source.sourceElement || '')) unexplainedRelations += 1
|
||||
else if (!allowedSources.has(source.sourceObject)) wrongBindings += 1
|
||||
}
|
||||
}
|
||||
if (stage.nativeEvidence.internalBuilderEvidence === true && stageSourceRelations === 0) unexplainedRelations += 1
|
||||
for (const entry of stage.names) {
|
||||
if (names.has(`${stage.name}:${entry.name}`)) throw new Error(`Duplicate ElementMap name ${fixture.id}/${stage.name}/${entry.name}.`)
|
||||
names.add(`${stage.name}:${entry.name}`)
|
||||
@@ -74,7 +117,9 @@ for (const fixture of report.cases) {
|
||||
parsedResources += 1
|
||||
}
|
||||
}
|
||||
if (stageCorrelations !== 219 || stageCorrelations !== report.summary.stageCorrelations || relationRecords !== report.summary.relationRecords) throw new Error(`FreeCAD stage-correlation totals drifted: stages=${stageCorrelations}, relations=${relationRecords}.`)
|
||||
if (wrongBindings !== 0 || unexplainedRelations !== 0) throw new Error(`FreeCAD relation-level topology correlation failed: wrongBindings=${wrongBindings}, unexplainedRelations=${unexplainedRelations}.`)
|
||||
if (parsedResources === 0) throw new Error('FreeCAD composite oracle did not capture any ElementMap2 resources.')
|
||||
if (nativeIndexedNameStages !== namingEvidenceStages) throw new Error(`FreeCAD composite oracle must retain direct indexed-name evidence for every stage, found ${nativeIndexedNameStages}/${namingEvidenceStages}.`)
|
||||
if (namingEvidenceMissing !== 0 || namingEvidenceBoundaryViolations !== 0) throw new Error(`FreeCAD naming evidence boundary is incomplete: missing=${namingEvidenceMissing}, violations=${namingEvidenceBoundaryViolations}.`)
|
||||
console.log(JSON.stringify({ status: 'freecad-composite-history-elementmap-pass', cases: report.summary.cases, parsedResources, parsedStringHasherResources, namingEvidenceStages, nativeEvidenceValidatedStages, nativeIndexedNameStages, nativeMappedNameStages, indexedOnlyStages: nativeIndexedNameStages - nativeMappedNameStages, privateTokenEvidenceCompleteStages, namingEvidenceMissing, namingEvidenceBoundaryViolations, internalBuilderEvidenceStages, internalBuilderEvidenceMissingStages, nativeDesktopResaveCases: resaveReport.summary.nativeDesktopResaveCases, resaveNameDrift: resaveReport.summary.resaveNameDrift }, null, 2))
|
||||
console.log(JSON.stringify({ status: 'freecad-composite-history-elementmap-pass', cases: report.summary.cases, categoryCases: mutationEvidence.categoryCases, mutationCases: mutationEvidence.mutationCases, mutationPassed: mutationEvidence.mutationPassed, mutationStageRecords: mutationEvidence.mutationStageRecords, mutationPhaseStageRecords: mutationEvidence.mutationPhaseStageRecords, mutationFinalPropagationFailures: mutationEvidence.mutationFinalPropagationFailures, mutationStageRestoreFailures: mutationEvidence.mutationStageRestoreFailures, mutationNamingRestoreDriftCases: mutationEvidence.mutationNamingRestoreDriftCases, mutationNamingRestoreDriftStages: mutationEvidence.mutationNamingRestoreDriftStages, stageCorrelations, relationRecords, wrongBindings, unexplainedRelations, parsedResources, parsedStringHasherResources, namingEvidenceStages, nativeEvidenceValidatedStages, nativeIndexedNameStages, nativeMappedNameStages, indexedOnlyStages: nativeIndexedNameStages - nativeMappedNameStages, privateTokenEvidenceCompleteStages, namingEvidenceMissing, namingEvidenceBoundaryViolations, internalBuilderEvidenceStages, internalBuilderEvidenceMissingStages, nativeDesktopResaveCases: resaveReport.summary.nativeDesktopResaveCases, resaveNameDrift: resaveReport.summary.resaveNameDrift }, null, 2))
|
||||
|
||||
Reference in New Issue
Block a user