import { readFile, writeFile } from 'node:fs/promises' import { resolve } from 'node:path' const root = resolve(new URL('..', import.meta.url).pathname) const load = (path) => readFile(resolve(root, path), 'utf8').then(JSON.parse) const outputPath = resolve(root, 'config/freecad-property-direction-promotion.json') const [inventory, success, failure, mutation, roundTrip, chrome, semantics, progress] = await Promise.all([ load('config/freecad-property-direction-inventory.json'), load('config/freecad-property-direction-success.json'), load('config/freecad-property-direction-failure.json'), load('config/freecad-property-direction-mutation.json'), load('config/freecad-property-direction-roundtrip.json'), load('config/chrome-property-direction-verification.json'), load('config/freecad-native-property-semantics.json'), load('config/freecad-follow-up-task-progress.json'), ]) const fail = (message) => { throw new Error(`FreeCAD PropertyDirection promotion generation failed: ${message}`) } const requiredCompletedPhases = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] const completed = new Map(progress.completedTasks?.map((entry) => [entry.id, entry]) ?? []) for (const phase of requiredCompletedPhases) if (!completed.has(`PROP-app-propertydirection-${phase}`)) fail(`phase ${phase} is not completed in the evidence ledger`) if (inventory.status !== 'pass' || inventory.propertyType !== 'App::PropertyDirection' || inventory.recordCount !== 2 || inventory.records?.filter(({ valueModel }) => valueModel?.writable === true).length !== 2) fail('inventory evidence is invalid') for (const [name, artifact] of Object.entries({ success, failure, mutation, roundTrip, chrome })) if (artifact.status !== 'pass') fail(`${name} evidence is not passing`) const propertyType = semantics.types?.find(({ typeId }) => typeId === 'App::PropertyDirection') if (propertyType?.support !== 'native-editable-codec' || propertyType.recordCount !== 2 || propertyType.objectTypeCount !== 2 || propertyType.statusNames?.length !== 0) fail('global property semantics did not promote App::PropertyDirection') const opaque = semantics.supportSummary?.['opaque-fcstd-proxy'] const editable = semantics.supportSummary?.['native-editable-codec'] if (opaque?.typeCount !== 45 || opaque.recordCount !== 455 || editable?.typeCount !== 35 || editable.recordCount !== 4372) fail('global property support summary is not synchronized') if (roundTrip.classification?.zeroUnknownDrift !== true || chrome.persistence?.loadedType !== 'App::PropertyDirection' || chrome.persistence.fcstdElement !== 'PropertyVector' || chrome.persistence.fcstdValue !== JSON.stringify({ x: 0.25, y: -0.5, z: 1.5 }) || chrome.release?.workerTerminated !== true) fail('round-trip or browser closure evidence is incomplete') const report = { schemaVersion: 1, status: 'pass', taskId: 'PROP-app-propertydirection-I', baseline: semantics.baseline, propertyType: 'App::PropertyDirection', recordCount: 2, phaseEvidence: Object.fromEntries(requiredCompletedPhases.map((phase) => [phase, completed.get(`PROP-app-propertydirection-${phase}`).evidence])), promotion: { from: inventory.classification, to: propertyType.support, facadeValueModel: 'three-finite-vector-components-with-native-propertyvector-fcstd-payload', writableRecords: 2, fcstdElement: chrome.persistence.fcstdElement, nativeRoundTripValue: roundTrip.classification.resavedValue, browserRoundTripValue: chrome.persistence.loadedValue, zeroUnknownDrift: roundTrip.classification.zeroUnknownDrift, }, exactBlockerSync: { nativeEditableTypes: editable.typeCount, nativeEditableRecords: editable.recordCount, opaqueTypes: opaque.typeCount, opaqueRecords: opaque.recordCount, exactPromotionReady: semantics.exactPromotionReady, exactBlocker: semantics.exactBlocker, }, systemExact: false, generatedAt: new Date().toISOString(), } await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`) console.log(JSON.stringify({ status: 'freecad-property-direction-promotion-generated', output: 'config/freecad-property-direction-promotion.json', promotion: report.promotion, exactBlockerSync: report.exactBlockerSync }, null, 2))