feat: add candidate FreeCAD naming SDK and attachment oracles
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

Build and verify the candidate-only FreeCAD naming bridge and OCCT worker path, including three-stage StringHasher restoration. Add Datum, ShapeBinder, attachment-mode, and PartDesign structure oracles plus offline SDK build plans and CI boundary checks.
This commit is contained in:
2026-08-13 17:16:07 -04:00
parent 58c0807219
commit f97eae4153
79 changed files with 7936 additions and 77 deletions

View File

@@ -44,19 +44,48 @@ establishes ABI linkage; exact promotion additionally requires valid
MappedNameRef, StringHasher, ElementMap2, stage history, and round-trip evidence.
The repository also carries a prerequisite-only source probe. It builds QtBase
6.8.2 `Qt6Core` for wasm, then compiles the locked FreeCAD `IndexedName.cpp` and
`MappedName.cpp` sources without exporting the production naming callbacks:
6.8.2 `Qt6Core` for wasm, then compiles the locked FreeCAD `IndexedName.cpp`,
`MappedName.cpp`, `StringHasher.cpp`, `MappedElement.cpp`,
`ElementNamingUtils.cpp`, `ElementMap.cpp`, and `Base/Handle.cpp` sources without
exporting the production naming callbacks:
```bash
./npmw run build:qt6-wasm-core
./npmw run build:freecad-naming-source-probe
./npmw run test:freecad-naming-source-probe
./npmw run check:freecad-naming-sdk-readiness
./npmw run check:freecad-private-naming-boundary
```
Passing this probe establishes that the private source subset and toolchain are
cross-compilable. It does not link the production OCCT Worker, does not close
EX-TSN-02, and does not change `systemExact=false`.
cross-compilable. Its runtime test covers mapped-name parsing, StringHasher
deduplication, SHA-1 threshold handling, indexed mapped-name references,
ElementMap encoding/lookup/history/save/restore, and stable mapped-element
ordering. It also feeds native MappedNameRef, StringHasher, and ElementMap2
resources through the strict Web ABI validator using deliberately separate
`freecadNamingCandidate*` callback names.
The seven locked FreeCAD source units are first compiled into the isolated
`libFreeCADPrivateNamingProbe.a` archive. The smoke test checks that it contains
exactly seven wasm object members before linking the runnable probe. This
archive remains host-adapter-bound and is not a substitute for FreeCADBase or
FreeCADApp.
Persistence, Python wrappers, type-system, logging, and Application/Document
lifecycle interfaces remain standalone host adapters. FreeCADApp/Part/Python
static libraries, real Application/Document integration, OCCT builder context,
and the production bridge are not linked. The candidate callbacks are never
published to the production Worker. The probe therefore does not close
EX-TSN-02 and does not change `systemExact=false`.
`check:freecad-naming-sdk-readiness` audits the pre-production SDK plan without
publishing anything. It parses every present `.a` archive and rejects native
ELF members; the current local resource set verifies QtCore plus its bundled
Pcre2/Zlib wasm dependencies, but still lacks FreeCADBase, FreeCADApp, Part,
Python, and the production bridge. `generate:freecad-naming-sdk-manifest` is
fail-closed and writes no manifest until all inputs are present. The complete
SDK checker applies the same wasm-archive rule, so an x86 static library cannot
satisfy the manifest by hash alone.
The versioned JSON request carries document `objectId`, stable positive
`objectTag`, prior naming evidence, result object identity, result tag, stage

View File

@@ -20,6 +20,7 @@ if [[ -z "${OCCT_BUILD_DIR:-}" ]]; then
fi
DIST_DIR="${OCCT_HISTORY_DIST_DIR:-${ROOT_DIR}/native/occt-history/dist}"
PUBLIC_DIR="${OCCT_HISTORY_PUBLIC_DIR:-${ROOT_DIR}/public/native/occt-history}"
PUBLISH_ARTIFACT="${OCCT_HISTORY_PUBLISH:-1}"
JOBS="${JOBS:-$(nproc)}"
FREECAD_WASM_SDK_DIR="${FREECAD_WASM_SDK_DIR:-}"
@@ -40,11 +41,18 @@ if ! command -v emcmake >/dev/null || ! command -v em++ >/dev/null; then
exit 1
fi
OCCT_CXX_FLAGS="-fexceptions"
OCCT_CXX_RELEASE_FLAGS="-O0 -DNDEBUG -fexceptions"
if [[ -n "${FREECAD_WASM_SDK_DIR}" ]]; then
OCCT_CXX_FLAGS+=" -pthread"
OCCT_CXX_RELEASE_FLAGS+=" -pthread"
fi
emcmake cmake -S "${OCCT_SOURCE_DIR}" -B "${OCCT_BUILD_DIR}" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_MAKE_PROGRAM="$(command -v ninja)" \
-DCMAKE_CXX_FLAGS="-fexceptions" \
-DCMAKE_CXX_FLAGS_RELEASE="-O0 -DNDEBUG -fexceptions" \
-DCMAKE_CXX_FLAGS="${OCCT_CXX_FLAGS}" \
-DCMAKE_CXX_FLAGS_RELEASE="${OCCT_CXX_RELEASE_FLAGS}" \
-DBUILD_RELEASE_DISABLE_EXCEPTIONS=OFF \
-DBUILD_LIBRARY_TYPE=Static \
-DBUILD_MODULE_FoundationClasses=ON \
@@ -74,18 +82,29 @@ SOURCES=("${ROOT_DIR}/native/occt-history/occt_history.cpp")
INCLUDES=("-I${OCCT_BUILD_DIR}/include/opencascade" "-I${OCCT_SOURCE_DIR}/src")
DEFINES=()
FREECAD_LIBRARIES=()
FREECAD_LINK_OPTIONS=()
if [[ -n "${FREECAD_WASM_SDK_DIR}" ]]; then
BRIDGE_SOURCE="$(node -e 'const fs=require("node:fs"); const p=require("node:path"); const m=JSON.parse(fs.readFileSync(p.resolve(process.argv[1],"manifest.json"),"utf8")); console.log(p.isAbsolute(m.namingBridge.source) ? m.namingBridge.source : p.resolve(process.argv[1],m.namingBridge.source))' "${FREECAD_WASM_SDK_DIR}")"
SOURCES+=("${BRIDGE_SOURCE}")
while IFS= read -r include_dir; do INCLUDES+=("-I${include_dir}"); done < <(node -e 'const fs=require("node:fs"); const p=require("node:path"); const root=p.resolve(process.argv[1]); const m=JSON.parse(fs.readFileSync(p.resolve(root,"manifest.json"),"utf8")); for (const d of m.includeDirs) console.log(p.isAbsolute(d) ? d : p.resolve(root,d))' "${FREECAD_WASM_SDK_DIR}")
while IFS= read -r library; do FREECAD_LIBRARIES+=("${library}"); done < <(node -e 'const fs=require("node:fs"); const p=require("node:path"); const root=p.resolve(process.argv[1]); const m=JSON.parse(fs.readFileSync(p.resolve(root,"manifest.json"),"utf8")); for (const l of m.libraries) console.log(p.isAbsolute(l.path) ? l.path : p.resolve(root,l.path))' "${FREECAD_WASM_SDK_DIR}")
while IFS= read -r definition; do DEFINES+=("-D${definition}"); done < <(node -e 'const fs=require("node:fs"); const p=require("node:path"); const m=JSON.parse(fs.readFileSync(p.resolve(process.argv[1],"manifest.json"),"utf8")); for (const d of m.compileOptions.definitions) console.log(d)' "${FREECAD_WASM_SDK_DIR}")
CXX_STANDARD="$(node -e 'const fs=require("node:fs"); const p=require("node:path"); const m=JSON.parse(fs.readFileSync(p.resolve(process.argv[1],"manifest.json"),"utf8")); console.log(m.compileOptions.cxxStandard)' "${FREECAD_WASM_SDK_DIR}")"
FORCE_INCLUDE="$(node -e 'const fs=require("node:fs"); const p=require("node:path"); const root=p.resolve(process.argv[1]); const m=JSON.parse(fs.readFileSync(p.resolve(root,"manifest.json"),"utf8")); const v=m.compileOptions.forceInclude; console.log(p.isAbsolute(v) ? v : p.resolve(root,v))' "${FREECAD_WASM_SDK_DIR}")"
HOST_ADAPTER="$(node -e 'const fs=require("node:fs"); const p=require("node:path"); const root=p.resolve(process.argv[1]); const m=JSON.parse(fs.readFileSync(p.resolve(root,"manifest.json"),"utf8")); const v=m.namingBridge.hostAdapter; console.log(p.isAbsolute(v) ? v : p.resolve(root,v))' "${FREECAD_WASM_SDK_DIR}")"
FREECAD_LINK_OPTIONS+=("-include" "${FORCE_INCLUDE}" "-pthread" "--pre-js" "${HOST_ADAPTER}" "-sPTHREAD_POOL_SIZE=2")
while IFS=$'\t' read -r asset_path preload_to; do
FREECAD_LINK_OPTIONS+=("--preload-file" "${asset_path}@${preload_to}")
done < <(node -e 'const fs=require("node:fs"); const p=require("node:path"); const root=p.resolve(process.argv[1]); const m=JSON.parse(fs.readFileSync(p.resolve(root,"manifest.json"),"utf8")); for (const a of m.runtimeAssets) { const v=p.isAbsolute(a.path) ? a.path : p.resolve(root,a.path); console.log(`${v}\t${a.preloadTo}`) }' "${FREECAD_WASM_SDK_DIR}")
DEFINES+=("-DBITBYBIT_FREECAD_NAMING_LINKED=1")
else
CXX_STANDARD="c++17"
fi
em++ "${SOURCES[@]}" "${INCLUDES[@]}" "${DEFINES[@]}" \
em++ "${SOURCES[@]}" "${INCLUDES[@]}" "${DEFINES[@]}" "${FREECAD_LINK_OPTIONS[@]}" \
-I"${OCCT_BUILD_DIR}/include/opencascade" \
-I"${OCCT_SOURCE_DIR}/src" \
-std=c++17 -O2 -fexceptions --bind \
-std="${CXX_STANDARD}" -O2 -fexceptions --bind \
-Wl,--start-group \
"${OCCT_BUILD_DIR}/lin32/clang/lib/libTKBO.a" \
"${OCCT_BUILD_DIR}/lin32/clang/lib/libTKFillet.a" \
@@ -112,7 +131,13 @@ em++ "${SOURCES[@]}" "${INCLUDES[@]}" "${DEFINES[@]}" \
-o "${DIST_DIR}/bitbybit-occt-history.js"
cp "${ROOT_DIR}/native/occt-history/package.json" "${DIST_DIR}/package.json"
mkdir -p "${PUBLIC_DIR}"
cp "${DIST_DIR}/bitbybit-occt-history.js" "${PUBLIC_DIR}/bitbybit-occt-history.js"
cp "${DIST_DIR}/bitbybit-occt-history.wasm" "${PUBLIC_DIR}/bitbybit-occt-history.wasm"
sha256sum "${DIST_DIR}/bitbybit-occt-history.js" "${DIST_DIR}/bitbybit-occt-history.wasm"
if [[ "${PUBLISH_ARTIFACT}" == "1" ]]; then
mkdir -p "${PUBLIC_DIR}"
for artifact in "${DIST_DIR}"/bitbybit-occt-history.{js,wasm,data,worker.js}; do
[[ -f "${artifact}" ]] && cp "${artifact}" "${PUBLIC_DIR}/$(basename "${artifact}")"
done
elif [[ "${PUBLISH_ARTIFACT}" != "0" ]]; then
echo "OCCT_HISTORY_PUBLISH must be 0 or 1." >&2
exit 1
fi
sha256sum "${DIST_DIR}"/bitbybit-occt-history.{js,wasm,data} 2>/dev/null || sha256sum "${DIST_DIR}/bitbybit-occt-history.js" "${DIST_DIR}/bitbybit-occt-history.wasm"

View File

@@ -0,0 +1,149 @@
import { pathToFileURL } from 'node:url'
import { createHash } from 'node:crypto'
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises'
import { dirname } from 'node:path'
import { resolve } from 'node:path'
import {
captureFreeCadPrivateNamingEvidence,
createFreeCadPrivateNamingAbiRequest,
probeFreeCadPrivateNamingAbi,
type NativeFreeCadNamingAbiModule,
} from '../../src/facade/nativeNamingAbi'
type CandidateModule = NativeFreeCadNamingAbiModule & {
makeBox(x: number, y: number, z: number): unknown
shapeToStep(shape: unknown): string
booleanHistoryFromStep(objectStep: string, toolStep: string, operation: 'cut'): {
provider: 'occt-native'
occtVersion: string
hasModified: boolean
hasGenerated: boolean
hasDeleted: boolean
resultStep: string
records: Array<{
relation: 'modified' | 'generated' | 'deleted'
source: string
kind: 'face' | 'edge' | 'vertex'
resultKind?: 'face' | 'edge' | 'vertex'
sourceIndex: number
resultIndex?: number
resultIndexes?: number[]
}>
}
}
const dist = resolve(process.env.FREECAD_NAMING_WORKER_DIST ?? '.cache/candidates/freecad-naming-worker')
const moduleUrl = pathToFileURL(resolve(dist, 'bitbybit-occt-history.js')).href
const createCandidate = (await import(moduleUrl)).default as (options: { locateFile(path: string): string }) => Promise<CandidateModule>
const candidate = await createCandidate({ locateFile: (path) => resolve(dist, path) })
const probe = probeFreeCadPrivateNamingAbi(candidate)
if (probe.availability !== 'available') throw new Error(`Candidate Worker naming ABI probe failed: ${probe.reason}`)
const object = candidate.makeBox(10, 10, 10)
const tool = candidate.makeBox(5, 5, 5)
const objectStep = candidate.shapeToStep(object)
const toolStep = candidate.shapeToStep(tool)
const history = candidate.booleanHistoryFromStep(objectStep, toolStep, 'cut')
const record = history.records.find((entry) => entry.relation !== 'deleted'
&& Number.isSafeInteger(entry.sourceIndex)
&& (Number.isSafeInteger(entry.resultIndex) || entry.resultIndexes?.some(Number.isSafeInteger)))
if (!record) throw new Error('Candidate Worker OCCT cut returned no usable native history record.')
const resultIndex = Number.isSafeInteger(record.resultIndex) ? record.resultIndex! : record.resultIndexes!.find(Number.isSafeInteger)!
const selectedRecord = { ...record, resultIndex, resultIndexes: undefined }
const evidence = captureFreeCadPrivateNamingEvidence(candidate, createFreeCadPrivateNamingAbiRequest({
requestId: 'candidate-worker-cut-1',
documentId: 'candidate-worker-document',
documentVersion: 1,
operationId: 'candidate-worker-cut',
operation: 'cut',
stageId: 'candidate-worker:stage:1',
resultObjectId: 'candidate-worker:result:1',
resultObjectTag: 100,
inputs: [
{ inputId: 'object', objectId: 'candidate-object', role: 'object', objectTag: 42, step: objectStep },
{ inputId: 'tool', objectId: 'candidate-tool', role: 'tool', objectTag: 43, step: toolStep },
],
stages: [{ stageId: 'candidate-worker:stage:1', operation: 'cut', inputIds: ['object', 'tool'], ordinal: 0 }],
resultStep: history.resultStep,
history: { ...history, records: [selectedRecord] },
}), probe)
if (!evidence?.elementMap2 || !evidence.stringHasher || evidence.mappedNames?.length !== 1) throw new Error('Candidate Worker returned incomplete FreeCAD naming evidence for OCCT cut history.')
const secondEvidence = captureFreeCadPrivateNamingEvidence(candidate, createFreeCadPrivateNamingAbiRequest({
requestId: 'candidate-worker-cut-2',
documentId: 'candidate-worker-document',
documentVersion: 2,
operationId: 'candidate-worker-cut-2',
operation: 'cut',
stageId: 'candidate-worker:stage:2',
resultObjectId: 'candidate-worker:result:2',
resultObjectTag: 101,
inputs: [{ inputId: 'object', objectId: 'candidate-worker:result:1', role: 'object', stageId: 'candidate-worker:stage:1', objectTag: 100, step: history.resultStep, namingEvidence: evidence }],
stages: [{ stageId: 'candidate-worker:stage:2', operation: 'cut', inputIds: ['object'], ordinal: 0 }],
resultStep: history.resultStep,
history: { ...history, records: [{ relation: 'modified', source: 'object', kind: record.resultKind ?? record.kind, sourceIndex: resultIndex, resultIndex: resultIndex + 1 }] },
}), probe)
const thirdEvidence = secondEvidence && captureFreeCadPrivateNamingEvidence(candidate, createFreeCadPrivateNamingAbiRequest({
requestId: 'candidate-worker-cut-3',
documentId: 'candidate-worker-document',
documentVersion: 3,
operationId: 'candidate-worker-cut-3',
operation: 'cut',
stageId: 'candidate-worker:stage:3',
resultObjectId: 'candidate-worker:result:3',
resultObjectTag: 102,
inputs: [{ inputId: 'object', objectId: 'candidate-worker:result:2', role: 'object', stageId: 'candidate-worker:stage:2', objectTag: 101, step: history.resultStep, namingEvidence: secondEvidence }],
stages: [{ stageId: 'candidate-worker:stage:3', operation: 'cut', inputIds: ['object'], ordinal: 0 }],
resultStep: history.resultStep,
history: { ...history, records: [{ relation: 'modified', source: 'object', kind: record.resultKind ?? record.kind, sourceIndex: resultIndex + 1, resultIndex: resultIndex + 2 }] },
}), probe)
if (!secondEvidence?.stringHasher || !thirdEvidence?.stringHasher || secondEvidence.stringHasher.entries.length < 1 || thirdEvidence.stringHasher.entries.length <= secondEvidence.stringHasher.entries.length) throw new Error('Candidate Worker did not restore non-empty StringHasher evidence across three stages.')
let rejected = false
try {
captureFreeCadPrivateNamingEvidence(candidate, createFreeCadPrivateNamingAbiRequest({
requestId: 'candidate-worker-invalid',
documentId: 'candidate-worker-document',
documentVersion: 2,
operationId: 'candidate-worker-invalid',
operation: 'cut',
stageId: 'candidate-worker:invalid',
resultObjectId: 'candidate-worker:invalid-result',
resultObjectTag: 101,
inputs: [{ inputId: 'object', objectId: 'candidate-object', role: 'object', objectTag: 42, step: objectStep }],
stages: [{ stageId: 'candidate-worker:invalid', operation: 'cut', inputIds: ['object'], ordinal: 0 }],
history: { ...history, records: [] },
}), probe)
} catch (error) {
if (!(error instanceof Error) || !error.message.includes('requires inputs and native history records')) throw error
rejected = true
}
if (!rejected) throw new Error('Candidate Worker accepted incomplete native history.')
const result = {
status: 'freecad-naming-candidate-worker-pass',
occtVersion: history.occtVersion,
callbacks: ['freecadNamingAbiVersion', 'freecadNamingCapabilitiesJson', 'freecadNamingEvidenceJson'],
namingEvidence: {
mappedNames: evidence.mappedNames.length,
stringHasherEntries: evidence.stringHasher.entries.length,
elementMaps: evidence.elementMap2.maps.length,
},
chainedStage: { mappedNames: secondEvidence.mappedNames.length, stringHasherEntries: secondEvidence.stringHasher.entries.length },
thirdStage: { mappedNames: thirdEvidence.mappedNames.length, stringHasherEntries: thirdEvidence.stringHasher.entries.length },
invalidHistoryRejected: true,
candidateOnly: true,
productionPublication: false,
productionWorkerLinked: false,
}
const reportPath = process.env.FREECAD_NAMING_WORKER_REPORT
if (reportPath) {
const artifacts = await Promise.all(['bitbybit-occt-history.js', 'bitbybit-occt-history.wasm', 'bitbybit-occt-history.data'].map(async (name) => {
const path = resolve(dist, name)
const [bytes, content] = await Promise.all([stat(path).then(({ size }) => size), readFile(path)])
return { name, bytes, sha256: createHash('sha256').update(content).digest('hex') }
}))
const absoluteReportPath = resolve(reportPath)
await mkdir(dirname(absoluteReportPath), { recursive: true })
await writeFile(absoluteReportPath, `${JSON.stringify({ schemaVersion: 1, ...result, artifacts }, null, 2)}\n`)
}
console.log(JSON.stringify(result, null, 2))

View File

@@ -14,15 +14,60 @@
{ "name": "FreeCADApp", "path": "lib/libFreeCADApp.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "Part", "path": "lib/libPart.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "QtCore", "path": "lib/libQt6Core.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "Python", "path": "lib/libpython3.a", "sha256": "replace-with-64-lowercase-hex-digits" }
{ "name": "QtConcurrent", "path": "lib/libQt6Concurrent.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "QtNetwork", "path": "lib/libQt6Network.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "QtXml", "path": "lib/libQt6Xml.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "QtBundledPcre2", "path": "lib/libQt6BundledPcre2.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "QtBundledZLIB", "path": "lib/libQt6BundledZLIB.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "yaml-cpp", "path": "lib/libyaml-cpp.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "ICUCommon", "path": "lib/libicuuc.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "ICUI18N", "path": "lib/libicui18n.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "ICUData", "path": "lib/libicudata.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "XercesC", "path": "lib/libxerces-c.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "BoostProgramOptions", "path": "lib/libboost_program_options.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "BoostRegex", "path": "lib/libboost_regex.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "BoostThread", "path": "lib/libboost_thread.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "BoostDateTime", "path": "lib/libboost_date_time.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "BoostAtomic", "path": "lib/libboost_atomic.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "Python", "path": "lib/libpython3.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "PythonMpdecimal", "path": "lib/python-deps/libmpdec.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "PythonExpat", "path": "lib/python-deps/libexpat.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "PythonHaclSha2", "path": "lib/python-deps/libHacl_Hash_SHA2.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "PythonZlib", "path": "lib/python-deps/libz.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "PythonBzip2", "path": "lib/python-deps/libbz2.a", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "name": "PythonSqlite3", "path": "lib/python-deps/libsqlite3-mt.a", "sha256": "replace-with-64-lowercase-hex-digits" }
],
"namingBridge": {
"source": "src/freecad_naming_bridge.cpp",
"sha256": "replace-with-64-lowercase-hex-digits",
"hostAdapter": "runtime/freecad_naming_pre.js",
"hostAdapterSha256": "replace-with-64-lowercase-hex-digits",
"exports": [
"freecadNamingAbiVersion",
"freecadNamingCapabilitiesJson",
"freecadNamingEvidenceJson"
]
},
"compileOptions": {
"cxxStandard": "c++20",
"pthread": true,
"forceInclude": "include/freecad-wasm-sdk-compat.h",
"forceIncludeSha256": "replace-with-64-lowercase-hex-digits",
"definitions": ["__linux__=1", "QT_NO_KEYWORDS", "HAVE_CONFIG_H", "PYCXX_6_2_COMPATIBILITY"]
},
"runtimeAssets": [{
"name": "PythonWasmStdlib",
"path": "runtime/python",
"preloadTo": "/usr/local",
"files": [
{ "path": "lib/python313.zip", "sha256": "replace-with-64-lowercase-hex-digits" },
{ "path": "lib/python3.13/os.py", "sha256": "replace-with-64-lowercase-hex-digits" }
]
}],
"productionPublication": false,
"boundary": {
"freecadNamingBuildStatus": "contract-only",
"exTsn02": "in_progress",
"systemExact": false
}
}