feat: advance TSN drift and ordered pair evidence
This commit is contained in:
102
scripts/freecad-production-drift-classification.mjs
Normal file
102
scripts/freecad-production-drift-classification.mjs
Normal file
@@ -0,0 +1,102 @@
|
||||
const sha256 = (value) => typeof value === 'string' && /^[0-9a-f]{64}$/.test(value)
|
||||
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
|
||||
|
||||
export const productionDriftDecisions = [
|
||||
{
|
||||
taskId: 'TSN-PROD-DRIFT-000',
|
||||
operation: 'cut',
|
||||
classification: 'allowed_evolution',
|
||||
reasonCode: 'native-downstream-topology-stabilizes-after-edit-restore',
|
||||
rationale: 'FreeCAD deterministically evolves the downstream Fillet, Mirrored, and LinearPattern semantic topology after a Pocket Length edit/restore while restoring their geometry; the evolved topology and mapped names remain stable through save, reopen, and resave.',
|
||||
},
|
||||
{
|
||||
taskId: 'TSN-PROD-DRIFT-001',
|
||||
operation: 'rotate',
|
||||
classification: 'allowed_evolution',
|
||||
reasonCode: 'native-downstream-topology-stabilizes-after-edit-restore',
|
||||
rationale: 'FreeCAD deterministically evolves the downstream Mirrored and LinearPattern semantic topology after a Rotate Angle edit/restore while restoring their geometry; the evolved topology and mapped names remain stable through save, reopen, and resave.',
|
||||
},
|
||||
]
|
||||
|
||||
export const same = (left, right) => JSON.stringify(canonical(left)) === JSON.stringify(canonical(right))
|
||||
|
||||
const stageFingerprint = (driftReplay, ordinal, phase) => {
|
||||
const mutation = driftReplay.mutation
|
||||
const persistence = driftReplay.persistence?.[phase]?.[ordinal]
|
||||
if (!persistence) throw new Error(`${driftReplay.operation}/${ordinal} lacks ${phase} persistence evidence.`)
|
||||
return {
|
||||
geometrySignature: persistence.geometrySignature,
|
||||
semanticTopologyDigest: persistence.semanticTopologyDigest,
|
||||
mappedNames: persistence.mappedNames,
|
||||
mappedNameDigest: persistence.mappedNameDigest,
|
||||
}
|
||||
}
|
||||
|
||||
export const buildProductionDriftSnapshot = (report, operation) => {
|
||||
const driftReplay = report?.driftReplays?.find((entry) => entry.operation === operation)
|
||||
if (!driftReplay) throw new Error(`${operation} has no isolated production drift replay.`)
|
||||
const mutation = driftReplay.mutation
|
||||
const ordinals = mutation.restoreTopologyDriftOrdinals
|
||||
if (!Array.isArray(ordinals) || ordinals.length === 0) throw new Error(`${operation} has no recovered production topology drift.`)
|
||||
const stages = ordinals.map((ordinal) => ({
|
||||
ordinal,
|
||||
operation: driftReplay.persistence.initial[ordinal].operation,
|
||||
name: driftReplay.persistence.initial[ordinal].name,
|
||||
before: {
|
||||
geometrySignature: mutation.beforeGeometrySignatures[ordinal],
|
||||
semanticTopologyDigest: mutation.beforeTopologyDigests[ordinal],
|
||||
},
|
||||
edited: {
|
||||
geometrySignature: mutation.editedGeometrySignatures[ordinal],
|
||||
semanticTopologyDigest: mutation.editedTopologyDigests[ordinal],
|
||||
},
|
||||
restored: {
|
||||
geometrySignature: mutation.restoredGeometrySignatures[ordinal],
|
||||
semanticTopologyDigest: mutation.restoredTopologyDigests[ordinal],
|
||||
},
|
||||
persistence: Object.fromEntries(['initial', 'reopened', 'resaved'].map((phase) => [phase, stageFingerprint(driftReplay, ordinal, phase)])),
|
||||
}))
|
||||
return {
|
||||
operation,
|
||||
contract: {
|
||||
featureName: mutation.featureName,
|
||||
typeId: mutation.typeId,
|
||||
propertyPath: mutation.propertyPath,
|
||||
},
|
||||
values: {
|
||||
before: mutation.beforeParameter,
|
||||
edited: mutation.editedParameter,
|
||||
restored: mutation.restoredParameter,
|
||||
},
|
||||
driftOrdinals: ordinals,
|
||||
stages,
|
||||
checks: {
|
||||
parameterRestored: mutation.parameterChanged === true && same(mutation.beforeParameter, mutation.restoredParameter),
|
||||
geometryRestored: mutation.restoredGeometrically === true && mutation.restoredExactly === true && same(mutation.beforeGeometrySignatures, mutation.restoredGeometrySignatures),
|
||||
topologyEvolved: stages.every(({ before, restored }) => before.semanticTopologyDigest !== restored.semanticTopologyDigest),
|
||||
restoredMatchesInitial: driftReplay.checks?.restoredMatchesInitial === true && stages.every(({ restored, persistence }) => same(restored.geometrySignature, persistence.initial.geometrySignature) && restored.semanticTopologyDigest === persistence.initial.semanticTopologyDigest),
|
||||
persistenceStable: driftReplay.checks?.geometryStable === true && driftReplay.checks?.topologyStable === true && driftReplay.checks?.mappedNamesStable === true && stages.every(({ persistence }) => ['reopened', 'resaved'].every((phase) => same(persistence.initial, persistence[phase]))),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const validateProductionDriftSnapshot = (snapshot) => {
|
||||
if (!snapshot || typeof snapshot.operation !== 'string' || !snapshot.operation) throw new Error('production drift snapshot has no operation identity.')
|
||||
if (!Array.isArray(snapshot.driftOrdinals) || snapshot.driftOrdinals.length === 0 || snapshot.stages?.length !== snapshot.driftOrdinals.length) throw new Error(`${snapshot.operation} has an invalid drift-stage set.`)
|
||||
if (!same(snapshot.stages.map(({ ordinal }) => ordinal), snapshot.driftOrdinals)) throw new Error(`${snapshot.operation} drift-stage identities are inconsistent.`)
|
||||
for (const stage of snapshot.stages) {
|
||||
if (!Number.isInteger(stage.ordinal) || !stage.operation || !stage.name) throw new Error(`${snapshot.operation} has an invalid stage identity.`)
|
||||
for (const phase of ['before', 'edited', 'restored']) {
|
||||
if (!Array.isArray(stage[phase]?.geometrySignature) || !sha256(stage[phase]?.semanticTopologyDigest)) throw new Error(`${snapshot.operation}/${stage.name}/${phase} has an invalid fingerprint.`)
|
||||
}
|
||||
for (const phase of ['initial', 'reopened', 'resaved']) {
|
||||
const value = stage.persistence?.[phase]
|
||||
if (!Array.isArray(value?.geometrySignature) || !value?.mappedNames || typeof value.mappedNames !== 'object' || !sha256(value?.semanticTopologyDigest) || !sha256(value?.mappedNameDigest)) throw new Error(`${snapshot.operation}/${stage.name}/${phase} has invalid persistence evidence.`)
|
||||
}
|
||||
}
|
||||
if (!Object.values(snapshot.checks ?? {}).every((value) => value === true)) throw new Error(`${snapshot.operation} does not satisfy the allowed-evolution evidence contract.`)
|
||||
}
|
||||
Reference in New Issue
Block a user