Files
Web_FreeCAD_Bitbybit/scripts/check-freecad-property-linklisthidden-inventory.mjs
wangdequan 7bd7ff5055
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 hidden link property evidence
2026-08-17 17:31:34 -04:00

51 lines
6.6 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 reportPath = 'config/freecad-property-linklisthidden-inventory.json'
const [runtimeContent, reportContent] = await Promise.all([readFile(resolve(root, runtimePath)), readFile(resolve(root, reportPath))])
const runtime = JSON.parse(runtimeContent)
const report = JSON.parse(reportContent)
const fail = (message) => { throw new Error(`FreeCAD PropertyLinkListHidden inventory check failed: ${message}`) }
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.propertyType !== 'App::PropertyLinkListHidden' || report.classification !== 'opaque-fcstd-proxy' || report.baseline?.freecadVersion !== '1.1.1' || report.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('report boundary is invalid')
if (report.recordCount !== 2 || report.objectTypeCount !== 2 || report.writableRecordCount !== 2 || report.records?.length !== 2) fail('runtime record or host count changed')
const typeContract = report.typeContract
if (typeContract?.inheritedFrom !== 'App::PropertyLinkList' || typeContract.linkScope !== 'Hidden' || typeContract.propertyStatus !== 'PropHidden' || typeContract.xmlElement !== 'LinkList' || typeContract.xmlChildElement !== 'Link' || typeContract.legacyRestoreType !== 'App::PropertyLinkList') 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')
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::PropertyLinkListHidden') 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 expectedKeys = ['Sketcher::SketchObject.Exports', 'Sketcher::SketchObjectPython.Exports']
if (!isDeepStrictEqual(report.records.map(({ objectTypeId, propertyName }) => `${objectTypeId}.${propertyName}`), expectedKeys)) fail('native host/property identities changed')
for (const record of report.records) {
const model = record.valueModel
const inputs = record.inputs
const dependencies = record.dependencies
if (record.objectAvailable !== true || record.probeStatus !== 'available' || record.group !== 'Sketch' || !isDeepStrictEqual(record.statusRaw, [26]) || !isDeepStrictEqual(record.statusNames, ['PropHidden']) || !isDeepStrictEqual(record.defaultRaw, [])) fail(`native metadata changed for ${record.objectTypeId}.Exports`)
if (model?.kind !== 'hidden-document-object-list' || model.inheritedFrom !== 'App::PropertyLinkList' || model.elementType !== 'same-document App::DocumentObject reference' || model.orderPreserved !== true || model.duplicatesPreserved !== true || model.writable !== true) fail(`value model changed for ${record.objectTypeId}.Exports`)
if (inputs?.nativeCppSetValues !== 'attached same-document DocumentObject list' || inputs.emptyListClears !== true || inputs.singleNullListClears !== true || inputs.detachedObjectRejected !== true || inputs.crossDocumentRejectedWithoutLinkAllowExternal !== true) fail(`input preconditions changed for ${record.objectTypeId}.Exports`)
if (dependencies?.linkScope !== 'Hidden' || dependencies.createsBacklinks !== false || dependencies.includedByDefaultGetLinks !== false || dependencies.includedByGetLinksAll !== true || dependencies.defaultDagDependency !== false) fail(`hidden dependency scope changed for ${record.objectTypeId}.Exports`)
if (record.applicability?.requiredObjectTypeId !== record.objectTypeId || record.applicability.role !== 'Sketch exported child geometry lookup by object name' || record.applicability.propertyWriteRequiresShape !== false) fail(`applicability changed for ${record.objectTypeId}.Exports`)
}
const sources = Object.fromEntries(await Promise.all(report.provenance.sources.map(async ({ path }) => [path, await readFile(resolve(root, path), 'utf8')])))
const linksHeader = sources['.cache/freecad/FreeCAD/src/App/PropertyLinks.h']
const linksSource = sources['.cache/freecad/FreeCAD/src/App/PropertyLinks.cpp']
const sketchHeader = sources['.cache/freecad/FreeCAD/src/Mod/Sketcher/App/SketchObject.h']
const sketchSource = sources['.cache/freecad/FreeCAD/src/Mod/Sketcher/App/SketchObject.cpp']
if (!linksHeader.includes('class AppExport PropertyLinkListHidden: public PropertyLinkList') || !linksHeader.includes('_pcScope = LinkScope::Hidden;') || !linksSource.includes('TYPESYSTEM_SOURCE(App::PropertyLinkListHidden, App::PropertyLinkList)') || !linksSource.includes('if (all || _pcScope != LinkScope::Hidden)') || !linksSource.includes('<LinkList count=') || !sketchHeader.includes('App ::PropertyLinkListHidden Exports;') || !sketchSource.includes('ADD_PROPERTY_TYPE(Exports,') || !sketchSource.includes('(App::PropertyType)(App::Prop_Hidden)') || !sketchSource.includes('auto child = Exports.find(name.c_str());') || !sketchSource.includes('if(strcmp(TypeName, "App::PropertyLinkList") == 0)')) fail('locked type, persistence, dependency, host, or migration source contract changed')
console.log(JSON.stringify({ status: 'freecad-property-linklisthidden-inventory-pass', propertyType: report.propertyType, recordCount: report.recordCount, objectTypes: report.records.map(({ objectTypeId }) => objectTypeId), propertyStatus: typeContract.propertyStatus, linkScope: typeContract.linkScope, classification: report.classification }, null, 2))