Files
Web_FreeCAD_Bitbybit/scripts/check-freecad-property-linksublistglobal-inventory.mjs
wangdequan 54649da16c
Some checks failed
real-verification / chrome (push) Has been cancelled
real-verification / freecad-oracle (push) Has been cancelled
real-verification / wasm (push) Has been cancelled
feat: close link sub property codecs
2026-08-17 21:37:33 -04:00

46 lines
7.2 KiB
JavaScript

import { createHash } from 'node:crypto'
import { isDeepStrictEqual } from 'node:util'
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
const root = resolve(new URL('..', import.meta.url).pathname)
const runtimePath = '.cache/freecad/reference-desktop.json'
const oraclePath = 'config/freecad-partdesign-structure-oracle.json'
const [runtimeContent, oracleContent, reportContent] = await Promise.all([
readFile(resolve(root, runtimePath)),
readFile(resolve(root, oraclePath)),
readFile(resolve(root, 'config/freecad-property-linksublistglobal-inventory.json')),
])
const runtime = JSON.parse(runtimeContent)
const oracle = JSON.parse(oracleContent)
const report = JSON.parse(reportContent)
const fail = (message) => { throw new Error(`FreeCAD PropertyLinkSubListGlobal inventory check failed: ${message}`) }
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.propertyType !== 'App::PropertyLinkSubListGlobal' || report.classification !== 'opaque-fcstd-proxy' || report.baseline?.freecadVersion !== '1.1.1' || report.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('report boundary is invalid')
if (report.recordCount !== 1 || report.objectTypeCount !== 1 || report.writableRecordCount !== 1 || report.records?.length !== 1) fail('runtime record or host count changed')
const contract = report.typeContract
if (contract?.inheritedFrom !== 'App::PropertyLinkSubList' || contract.linkScope !== 'Global' || !isDeepStrictEqual(contract.propertyStatus, []) || contract.xmlElement !== 'LinkSubList' || contract.xmlChildElement !== 'Link' || contract.emptyListMeans !== 'no-references' || contract.crossDocumentBoundary !== 'reject-without-LinkAllowExternal') fail('type contract is incomplete')
if (report.provenance?.runtime?.path !== runtimePath || report.provenance.runtime.bytes !== runtimeContent.length || report.provenance.runtime.sha256 !== createHash('sha256').update(runtimeContent).digest('hex')) fail('runtime provenance is stale')
if (report.provenance?.oracle?.path !== oraclePath || report.provenance.oracle.bytes !== oracleContent.length || report.provenance.oracle.sha256 !== createHash('sha256').update(oracleContent).digest('hex')) fail('structure oracle provenance is stale')
for (const locked of report.provenance?.sources ?? []) { const content = await readFile(resolve(root, locked.path)); if (content.length !== locked.bytes || createHash('sha256').update(content).digest('hex') !== locked.sha256) fail(`source provenance is stale for ${locked.path}`) }
const nativeMatches = []
for (const objectType of runtime.runtimeObjects?.types ?? []) for (const property of objectType.properties ?? []) if (property.typeId === 'App::PropertyLinkSubListGlobal') nativeMatches.push({ objectTypeId: objectType.typeId, objectAvailable: objectType.available === true, probeStatus: objectType.probeStatus, propertyName: property.name, group: property.group, statusRaw: property.status, defaultRaw: property.default })
const identity = ({ objectTypeId, objectAvailable, probeStatus, propertyName, group, statusRaw, defaultRaw }) => ({ objectTypeId, objectAvailable, probeStatus, propertyName, group, statusRaw, defaultRaw })
const sorted = (records) => records.map(identity).sort((left, right) => `${left.objectTypeId}.${left.propertyName}`.localeCompare(`${right.objectTypeId}.${right.propertyName}`))
if (!isDeepStrictEqual(sorted(nativeMatches), sorted(report.records))) fail('report diverges from the locked runtime oracle')
const record = report.records[0]
if (record.objectTypeId !== 'PartDesign::ShapeBinder' || record.objectAvailable !== true || record.probeStatus !== 'available' || record.propertyName !== 'Support' || record.group !== '' || !isDeepStrictEqual(record.statusRaw, []) || !isDeepStrictEqual(record.statusNames, []) || !isDeepStrictEqual(record.defaultRaw, [])) fail('native ShapeBinder Support metadata changed')
const model = record.valueModel; const inputs = record.inputs; const dependencies = record.dependencies
if (model?.kind !== 'same-document-object-and-ordered-sub-element-pair-list' || model.inheritedFrom !== 'App::PropertyLinkSubList' || model.orderPreserved !== true || model.duplicatesPreserved !== true || model.writable !== true) fail('value model changed')
if (inputs?.nativeCppSetValues !== 'parallel DocumentObject and sub-element lists' || inputs.emptyListMeansNoReferences !== true || inputs.nullObjectClears !== true || inputs.detachedObjectRejected !== true || inputs.crossDocumentRejectedWithoutLinkAllowExternal !== true || inputs.invalidSubElementAcceptedAsReference !== true) fail('input preconditions changed')
if (dependencies?.linkScope !== 'Global' || dependencies.createsBacklinks !== true || dependencies.includedByDefaultGetLinks !== true || dependencies.includedByGetLinksAll !== true || dependencies.defaultDagDependency !== true) fail('global dependency scope changed')
if (record.applicability?.requiredObjectTypeId !== 'PartDesign::ShapeBinder' || record.applicability.role !== 'ShapeBinder source geometry support' || record.applicability.propertyWriteRequiresShape !== false) fail('applicability changed')
const evidence = report.nativeEvidence
if (evidence?.structureOracle?.path !== oraclePath || evidence.structureOracle.baselineId !== 'freecad-1.1.1-partdesign-structure-oracle' || evidence.structureOracle.crossDocumentShapeBinderSupportType !== 'App::PropertyLinkSubListGlobal' || evidence.structureOracle.crossDocumentAccepted !== false || evidence.structureOracle.crossDocumentError !== 'PropertyLinkSubList does not support external object' || !isDeepStrictEqual(evidence.structureOracle.crossDocumentShapeBinderSupport, [])) fail('native ShapeBinder cross-document boundary evidence changed')
const linksHeader = await readFile(resolve(root, '.cache/freecad/FreeCAD/src/App/PropertyLinks.h'), 'utf8')
const linksSource = await readFile(resolve(root, '.cache/freecad/FreeCAD/src/App/PropertyLinks.cpp'), 'utf8')
const binderHeader = await readFile(resolve(root, '.cache/freecad/FreeCAD/src/Mod/PartDesign/App/ShapeBinder.h'), 'utf8')
const binderSource = await readFile(resolve(root, '.cache/freecad/FreeCAD/src/Mod/PartDesign/App/ShapeBinder.cpp'), 'utf8')
if (!linksHeader.includes('class AppExport PropertyLinkSubListGlobal: public PropertyLinkSubList') || !linksHeader.includes('_pcScope = LinkScope::Global;') || !linksSource.includes('TYPESYSTEM_SOURCE(App::PropertyLinkSubListGlobal, App::PropertyLinkSubList)') || !linksSource.includes('PropertyLinkSubList does not support external object') || !binderHeader.includes('App::PropertyLinkSubListGlobal Support') || !/ADD_PROPERTY_TYPE\(\s*Support\s*,/s.test(binderSource)) fail('locked type, scope, host, or cross-document source contract changed')
if (oracle.status !== 'pass' || oracle.crossDocument?.initial?.shapeBinderSupportType !== 'App::PropertyLinkSubListGlobal' || oracle.crossDocument.initial.shapeBinderExternalLink.accepted !== false) fail('partdesign structure oracle boundary changed')
console.log(JSON.stringify({ status: 'freecad-property-linksublistglobal-inventory-pass', propertyType: report.propertyType, recordCount: report.recordCount, objectTypes: report.records.map(({ objectTypeId }) => objectTypeId), propertyStatus: contract.propertyStatus, linkScope: contract.linkScope, crossDocument: evidence.structureOracle.crossDocumentError, classification: report.classification }, null, 2))