Files
Web_FreeCAD_Bitbybit/scripts/check-freecad-property-linksubhidden-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

37 lines
6.8 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 [runtimeContent, reportContent] = await Promise.all([readFile(resolve(root, runtimePath)), readFile(resolve(root, 'config/freecad-property-linksubhidden-inventory.json'))])
const runtime = JSON.parse(runtimeContent)
const report = JSON.parse(reportContent)
const fail = (message) => { throw new Error(`FreeCAD PropertyLinkSubHidden inventory check failed: ${message}`) }
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.propertyType !== 'App::PropertyLinkSubHidden' || report.classification !== 'opaque-fcstd-proxy' || report.baseline?.freecadVersion !== '1.1.1' || report.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('report boundary is invalid')
if (report.recordCount !== 4 || report.objectTypeCount !== 4 || report.writableRecordCount !== 4 || report.records?.length !== 4) fail('runtime record or host count changed')
const contract = report.typeContract
if (contract?.inheritedFrom !== 'App::PropertyLinkSub' || contract.linkScope !== 'Hidden' || contract.propertyStatus !== 'PropHidden' || contract.dynamicStatus !== 'LockDynamic' || contract.xmlElement !== 'LinkSub' || contract.xmlChildElement !== 'Sub' || contract.legacyRestoreType !== 'App::PropertyLinkSub' || contract.emptySubListMeans !== 'whole-object-reference') 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::PropertyLinkSubHidden') 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 = ['App::Link.ColoredElements', 'App::LinkGroup.ColoredElements', 'App::LinkGroupPython.ColoredElements', 'App::LinkPython.ColoredElements']
if (!isDeepStrictEqual(report.records.map(({ objectTypeId, propertyName }) => `${objectTypeId}.${propertyName}`), expectedKeys)) fail('native host/property identities changed')
for (const record of report.records) {
if (record.objectAvailable !== true || record.probeStatus !== 'available' || record.group !== ' Link' || !isDeepStrictEqual(record.statusRaw, ['LockDynamic', 26]) || !isDeepStrictEqual(record.statusNames, ['LockDynamic', 'PropHidden']) || record.defaultRaw !== null) fail(`native metadata changed for ${record.objectTypeId}.ColoredElements`)
const model = record.valueModel; const inputs = record.inputs; const dependencies = record.dependencies
if (model?.kind !== 'hidden-document-object-sub-element-reference' || model.inheritedFrom !== 'App::PropertyLinkSub' || model.elementType !== 'same-document App::DocumentObject plus ordered sub-element names' || model.orderPreserved !== true || model.duplicatesPreserved !== false || model.writable !== true) fail(`value model changed for ${record.objectTypeId}.ColoredElements`)
if (inputs?.nativeCppSetValues !== 'attached same-document DocumentObject plus sub-element string list' || inputs.emptySubListMeansWholeObject !== true || inputs.nullObjectClears !== true || inputs.detachedObjectRejected !== true || inputs.crossDocumentRejectedWithoutLinkAllowExternal !== true || inputs.invalidSubElementAcceptedAsReference !== true) fail(`input preconditions changed for ${record.objectTypeId}.ColoredElements`)
if (dependencies?.linkScope !== 'Hidden' || dependencies.createsBacklinks !== false || dependencies.includedByDefaultGetLinks !== false || dependencies.includedByGetLinksAll !== true || dependencies.defaultDagDependency !== false) fail(`hidden dependency scope changed for ${record.objectTypeId}.ColoredElements`)
if (record.applicability?.requiredObjectTypeId !== record.objectTypeId || record.applicability.role !== 'Link colored element override lookup' || record.applicability.propertyWriteRequiresShape !== false) fail(`applicability changed for ${record.objectTypeId}.ColoredElements`)
}
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 linkHeader = sources['.cache/freecad/FreeCAD/src/App/Link.h']; const draftSource = sources['.cache/freecad/FreeCAD/src/Mod/Draft/draftobjects/draftlink.py']
if (!linksHeader.includes('class AppExport PropertyLinkSubHidden: public PropertyLinkSub') || !linksHeader.includes('_pcScope = LinkScope::Hidden;') || !linksSource.includes('TYPESYSTEM_SOURCE(App::PropertyLinkSubHidden, App::PropertyLinkSub)') || !linksSource.includes('if (all || _pcScope != LinkScope::Hidden)') || !linksSource.includes('<LinkSub value=') || !linksSource.includes('reader.readElement("LinkSub")') || !linkHeader.includes('App::PropertyLinkSubHidden') || !draftSource.includes('addProperty("App::PropertyLinkSubHidden", "ColoredElements"') || !draftSource.includes('setPropertyStatus("ColoredElements", "Hidden")')) fail('locked type, persistence, dependency, host, or migration source contract changed')
console.log(JSON.stringify({ status: 'freecad-property-linksubhidden-inventory-pass', propertyType: report.propertyType, recordCount: report.recordCount, objectTypes: report.records.map(({ objectTypeId }) => objectTypeId), propertyStatus: contract.propertyStatus, dynamicStatus: contract.dynamicStatus, linkScope: contract.linkScope, classification: report.classification }, null, 2))