80 lines
6.4 KiB
JavaScript
80 lines
6.4 KiB
JavaScript
import { readFile } 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 [sourceInventory, typeInventory, guiInventory, reference] = await Promise.all([
|
|
load('config/freecad-source-inventory.json'),
|
|
load('config/freecad-type-property-inventory.json'),
|
|
load('config/freecad-gui-command-inventory.json'),
|
|
load('.cache/freecad/reference-desktop.json'),
|
|
])
|
|
const fail = (message) => { throw new Error(`FreeCAD oracle coverage: ${message}`) }
|
|
const lockedCommit = '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d'
|
|
if (reference.schemaVersion !== 1 || reference.freecadVersion !== '1.1.1' || reference.gitCommit !== lockedCommit) fail('reference desktop report is not the locked FreeCAD 1.1.1 baseline.')
|
|
if (sourceInventory.moduleCount !== 34 || reference.moduleCount !== 34 || reference.modules?.length !== 34) fail('source and desktop oracle must both enumerate 34 modules.')
|
|
if (typeInventory.moduleCount !== 34 || typeInventory.modules?.length !== 34) fail('type/property inventory must enumerate all 34 modules.')
|
|
|
|
const sourceModules = new Map(sourceInventory.modules.map((module) => [module.name, module]))
|
|
const typeModules = new Map(typeInventory.modules.map((module) => [module.name, module]))
|
|
const referenceModules = new Map(reference.modules.map((module) => [module.name, module]))
|
|
for (const name of sourceModules.keys()) {
|
|
if (!referenceModules.has(name) || !typeModules.has(name)) fail(`module ${name} is missing from one oracle inventory.`)
|
|
const runtime = referenceModules.get(name)
|
|
if (!runtime.runtimeStatus || typeof runtime.available !== 'boolean' || typeof runtime.importable !== 'boolean') fail(`module ${name} lacks explicit runtime status.`)
|
|
}
|
|
const missingReferenceModules = [...referenceModules.keys()].filter((name) => !sourceModules.has(name))
|
|
if (missingReferenceModules.length > 0) fail(`desktop oracle has unregistered modules: ${missingReferenceModules.join(', ')}.`)
|
|
|
|
const referenceCommands = new Map((reference.guiCommands?.commands ?? []).map((command) => [command.id, command]))
|
|
const inventoryCommands = new Map(guiInventory.commands.map((command) => [command.id, command]))
|
|
if (referenceCommands.size !== guiInventory.commandCount || inventoryCommands.size !== guiInventory.commandCount) fail('GUI command counts or IDs are not unique and aligned.')
|
|
for (const [id, command] of inventoryCommands) {
|
|
const referenceCommand = referenceCommands.get(id)
|
|
if (!referenceCommand) fail(`GUI command ${id} is missing from the desktop oracle.`)
|
|
if (!Array.isArray(referenceCommand.observations) || referenceCommand.observations.length === 0) fail(`GUI command ${id} has no desktop observations.`)
|
|
if (command.status !== 'runtime-probed') fail(`GUI command ${id} is not marked runtime-probed.`)
|
|
}
|
|
for (const id of referenceCommands.keys()) if (!inventoryCommands.has(id)) fail(`desktop GUI command ${id} is missing from the generated inventory.`)
|
|
for (const command of guiInventory.sourceOnlyCommands) if (!command.reason || !Array.isArray(command.sourceModules)) fail(`source-only command ${command.id} lacks an explicit reason.`)
|
|
|
|
const runtimeObjects = reference.runtimeObjects
|
|
if (!runtimeObjects?.available || runtimeObjects.candidateSource !== 'Document.supportedTypes' || !Array.isArray(runtimeObjects.types)) fail('desktop oracle is missing the complete Document.supportedTypes runtime inventory.')
|
|
if (runtimeObjects.candidateCount !== runtimeObjects.types.length || runtimeObjects.availableCount + runtimeObjects.unavailableCount !== runtimeObjects.candidateCount) fail('runtime object counts are inconsistent.')
|
|
const runtimeTypeIds = new Set()
|
|
for (const object of runtimeObjects.types) {
|
|
if (typeof object.typeId !== 'string' || !object.typeId || runtimeTypeIds.has(object.typeId)) fail(`runtime object TypeId '${object.typeId || '<unknown>'}' is duplicated or invalid.`)
|
|
runtimeTypeIds.add(object.typeId)
|
|
if (object.available && (typeof object.runtimeTypeId !== 'string' || !object.runtimeTypeId || !Array.isArray(object.properties))) fail(`runtime object ${object.typeId} lacks TypeId/property metadata.`)
|
|
if (!object.available && (!object.error || object.probeStatus !== 'unavailable')) fail(`runtime object ${object.typeId} lacks an explicit unavailable status.`)
|
|
}
|
|
for (const coreTypeId of ['Part::Box', 'Part::Cylinder', 'Part::Sphere', 'Part::Cone', 'Part::Feature', 'PartDesign::Feature', 'Sketcher::SketchObject']) {
|
|
if (!runtimeTypeIds.has(coreTypeId)) fail(`core runtime object ${coreTypeId} is absent from Document.supportedTypes.`)
|
|
}
|
|
const runtimeObjectCount = runtimeObjects.availableCount
|
|
const sourceUsageTypeIds = new Set((typeInventory.documentObjectUsages ?? []).map((record) => record.typeId))
|
|
if (sourceUsageTypeIds.size === 0 || !Array.isArray(typeInventory.typeIdDeclarations)) fail('source TypeId declaration and addObject usage indexes are missing.')
|
|
const sourceUsageMissingRuntime = [...sourceUsageTypeIds].filter((typeId) => !runtimeTypeIds.has(typeId)).sort()
|
|
const staticObjectCandidateCount = sourceUsageTypeIds.size
|
|
const moduleStatusCounts = Object.fromEntries([...new Set(reference.modules.map((module) => module.runtimeStatus))].sort().map((status) => [status, reference.modules.filter((module) => module.runtimeStatus === status).length]))
|
|
console.log(JSON.stringify({
|
|
status: 'freecad-oracle-coverage-pass',
|
|
baseline: { freecadVersion: reference.freecadVersion, commit: reference.gitCommit },
|
|
modules: reference.moduleCount,
|
|
moduleStatusCounts,
|
|
guiCommands: guiInventory.commandCount,
|
|
guiCommandsRuntimeAligned: true,
|
|
sourceOnlyCommands: guiInventory.sourceOnlyCommands.length,
|
|
registeredObjectTypeCount: runtimeObjects.candidateCount,
|
|
runtimeObjectCount,
|
|
unavailableObjectCount: runtimeObjects.unavailableCount,
|
|
runtimePropertyCount: runtimeObjects.types.reduce((total, object) => total + (object.properties?.length ?? 0), 0),
|
|
sourceDeclaredTypeIdCount: typeInventory.typeIdDeclarations.length,
|
|
sourceDocumentObjectUsageCount: staticObjectCandidateCount,
|
|
sourceUsageMissingRuntimeCount: sourceUsageMissingRuntime.length,
|
|
sourceUsageMissingRuntime: sourceUsageMissingRuntime,
|
|
staticObjectCandidateCount,
|
|
exactPromotionReady: false,
|
|
remaining: ['not-built modules require an explicit build/proxy decision', 'source-declared TypeId/property candidates outside the registered document-object set require a native API inventory'],
|
|
}, null, 2))
|