44 lines
3.6 KiB
JavaScript
44 lines
3.6 KiB
JavaScript
import { createHash } from 'node:crypto'
|
|
import { readFile, stat } from 'node:fs/promises'
|
|
import { resolve } from 'node:path'
|
|
import { buildProductionDriftSnapshot, productionDriftDecisions, same, validateProductionDriftSnapshot } from './freecad-production-drift-classification.mjs'
|
|
|
|
const root = resolve(new URL('..', import.meta.url).pathname)
|
|
const load = (path) => readFile(resolve(root, path), 'utf8').then(JSON.parse)
|
|
const fail = (message) => { throw new Error(`FreeCAD production drift classification: ${message}`) }
|
|
const [report, oracle] = await Promise.all([
|
|
load('config/freecad-production-drift-classification.json'),
|
|
load('config/freecad-tsn-stage-correlation-oracle.json'),
|
|
])
|
|
if (report.schemaVersion !== 1 || report.baseline?.freecadVersion !== '1.1.1' || report.baseline.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.baseline.oracleId !== oracle.baselineId) fail('baseline is invalid.')
|
|
const harnessPath = resolve(root, report.nativeProbe?.path ?? '')
|
|
const [harnessContent, harnessBytes] = await Promise.all([readFile(harnessPath), stat(harnessPath).then(({ size }) => size)])
|
|
if (report.nativeProbe.path !== 'scripts/freecad-tsn-stage-correlation-oracle.py' || report.nativeProbe.bytes !== harnessBytes || report.nativeProbe.sha256 !== createHash('sha256').update(harnessContent).digest('hex') || report.nativeProbe.independentRuns < 2) fail('native replay provenance is missing or stale.')
|
|
if (!Array.isArray(report.classifications) || report.classifications.length !== productionDriftDecisions.length) fail('classification report does not contain the complete serial decision prefix.')
|
|
|
|
let classifiedStages = 0
|
|
for (const [index, entry] of report.classifications.entries()) {
|
|
const decision = productionDriftDecisions[index]
|
|
if (!same({ taskId: entry.taskId, operation: entry.operation, classification: entry.classification, reasonCode: entry.reasonCode, rationale: entry.rationale }, decision) || entry.replayStable !== true || entry.implementationDefect !== (decision.classification === 'implementation_defect')) fail(`${decision.taskId} classification contract is invalid.`)
|
|
const expected = buildProductionDriftSnapshot(oracle, entry.operation)
|
|
validateProductionDriftSnapshot(expected)
|
|
validateProductionDriftSnapshot(entry.reference)
|
|
validateProductionDriftSnapshot(entry.replay)
|
|
if (!same(entry.reference, expected) || !same(entry.replay, expected)) fail(`${entry.operation} classification evidence is stale or the independent replay differs.`)
|
|
classifiedStages += expected.driftOrdinals.length
|
|
}
|
|
const driftCases = oracle.driftReplays.length
|
|
const countClassification = (classification) => productionDriftDecisions.filter((entry) => entry.classification === classification).length
|
|
const expectedSummary = { driftCases, classifiedCases: productionDriftDecisions.length, classifiedStages, stableSemantics: countClassification('stable_semantics'), allowedEvolution: countClassification('allowed_evolution'), implementationDefects: countClassification('implementation_defect'), unknown: driftCases - productionDriftDecisions.length }
|
|
if (!same(report.summary, expectedSummary)) fail('classification summary is inconsistent.')
|
|
|
|
console.log(JSON.stringify({
|
|
status: 'freecad-production-drift-classification-pass',
|
|
completedTasks: report.classifications.map(({ taskId }) => taskId),
|
|
cases: report.classifications.map(({ operation, classification }) => ({ operation, classification })),
|
|
independentRuns: report.nativeProbe.independentRuns,
|
|
classifiedCases: report.summary.classifiedCases,
|
|
classifiedStages: report.summary.classifiedStages,
|
|
remainingCases: report.summary.unknown,
|
|
}, null, 2))
|