feat: establish reproducible FreeCAD web compatibility baseline
This commit is contained in:
49
scripts/build-camotics-native.sh
Normal file
49
scripts/build-camotics-native.sh
Normal file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
camotics_dir="$repo_root/CAMotics"
|
||||
cbang_dir="$repo_root/.cache/camotics-native/cbang"
|
||||
cbang_revision="9b6672a0e2b800a909d799ffa3ab46774cf171b8"
|
||||
jobs=${CAMOTICS_BUILD_JOBS:-$(nproc)}
|
||||
|
||||
test -f "$camotics_dir/SConstruct"
|
||||
command -v git >/dev/null
|
||||
command -v scons >/dev/null
|
||||
command -v qmake >/dev/null
|
||||
command -v moc >/dev/null
|
||||
command -v uic >/dev/null
|
||||
command -v lrelease >/dev/null
|
||||
command -v pkg-config >/dev/null
|
||||
python3 -c 'import six' >/dev/null
|
||||
test -f /usr/include/v8/v8.h
|
||||
pkg-config --exists Qt5Core Qt5Gui Qt5Widgets Qt5OpenGL Qt5Network Qt5WebSockets
|
||||
|
||||
if [[ ! -d "$cbang_dir/.git" ]]; then
|
||||
mkdir -p "$(dirname "$cbang_dir")"
|
||||
git clone --filter=blob:none https://github.com/CauldronDevelopmentLLC/cbang.git "$cbang_dir"
|
||||
fi
|
||||
current_revision=$(git -C "$cbang_dir" rev-parse HEAD)
|
||||
if [[ "$current_revision" != "$cbang_revision" ]]; then
|
||||
git -C "$cbang_dir" fetch --depth=1 origin "$cbang_revision"
|
||||
git -C "$cbang_dir" checkout --detach "$cbang_revision"
|
||||
fi
|
||||
test "$(git -C "$cbang_dir" rev-parse HEAD)" = "$cbang_revision"
|
||||
test -z "$(git -C "$cbang_dir" status --porcelain)"
|
||||
|
||||
export QT_SELECT=qt5
|
||||
export CBANG_HOME="$cbang_dir"
|
||||
|
||||
# The Debian V8 package is built without pointer compression. Keep C! and the
|
||||
# CAMotics embedder on the same ABI instead of relying on the host default.
|
||||
cbang_build_marker="$repo_root/.cache/camotics-native/cbang-build-v8-noptr-cxx17"
|
||||
if [[ ! -f "$cbang_build_marker" || "$(<"$cbang_build_marker")" != "$cbang_revision" || ! -f "$cbang_dir/lib/libcbang.a" ]]; then
|
||||
scons -C "$cbang_dir" strict=0 cxxstd=c++17 v8_compress_pointers=0 -j "$jobs"
|
||||
printf '%s\n' "$cbang_revision" > "$cbang_build_marker"
|
||||
fi
|
||||
scons -C "$camotics_dir" strict=0 cxxstd=c++17 v8_compress_pointers=0 \
|
||||
with_gui=1 with_tpl=1 camotics tplang gcodetool planner camsim build/camotics.so -j "$jobs"
|
||||
|
||||
"$camotics_dir/camotics" --version
|
||||
"$camotics_dir/tplang" --version
|
||||
sha256sum "$camotics_dir/camotics" "$camotics_dir/tplang"
|
||||
@@ -2,9 +2,11 @@ import { execFileSync } from 'node:child_process'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const build = resolve(root, '.cache/freecad/build-native')
|
||||
const install = resolve(root, '.cache/freecad/install-native')
|
||||
const profile = process.env.FREECAD_ORACLE_PROFILE || 'headless'
|
||||
if (!['headless', 'desktop'].includes(profile)) throw new Error(`Unknown FreeCAD oracle profile: ${profile}`)
|
||||
const build = resolve(root, profile === 'desktop' ? '.cache/freecad/build-desktop' : '.cache/freecad/build-native')
|
||||
const install = resolve(root, profile === 'desktop' ? '.cache/freecad/install-desktop' : '.cache/freecad/install-native')
|
||||
const jobs = process.env.FREECAD_BUILD_JOBS || '4'
|
||||
execFileSync('cmake', ['--build', build, '--parallel', jobs], { cwd: root, stdio: 'inherit' })
|
||||
execFileSync('cmake', ['--install', build], { cwd: root, stdio: 'inherit' })
|
||||
console.log(`FreeCAD native oracle installed: ${install}`)
|
||||
console.log(`FreeCAD ${profile} oracle installed: ${install}`)
|
||||
|
||||
41
scripts/build-opencamlib-wasm.mjs
Normal file
41
scripts/build-opencamlib-wasm.mjs
Normal file
@@ -0,0 +1,41 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const source = resolve(root, 'OpenCAMLib')
|
||||
const build = resolve(root, '.cache/opencamlib-wasm')
|
||||
const boost = resolve(root, '.cache/opencamlib-boost')
|
||||
const install = resolve(root, 'public/vendor/opencamlib')
|
||||
const revision = execFileSync('git', ['-C', source, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim()
|
||||
|
||||
await rm(boost, { recursive: true, force: true })
|
||||
await mkdir(boost, { recursive: true })
|
||||
await cp('/usr/include/boost', resolve(boost, 'boost'), { recursive: true })
|
||||
await mkdir(build, { recursive: true })
|
||||
|
||||
const configure = [
|
||||
'cmake', '-S', source, '-B', build,
|
||||
'-D', 'BUILD_EMSCRIPTEN_LIB=ON',
|
||||
'-D', 'BUILD_CXX_LIB=OFF',
|
||||
'-D', 'BUILD_PY_LIB=OFF',
|
||||
'-D', 'BUILD_NODEJS_LIB=OFF',
|
||||
'-D', 'BUILD_DOC=OFF',
|
||||
'-D', 'USE_OPENMP=OFF',
|
||||
'-D', 'CMAKE_BUILD_TYPE=Release',
|
||||
'-D', `CMAKE_INSTALL_PREFIX=${install}`,
|
||||
'-D', `Boost_INCLUDE_DIR=${boost}`,
|
||||
'-D', 'Boost_NO_SYSTEM_PATHS=ON',
|
||||
'-D', `VERSION_STRING=${revision.slice(0, 16)}`,
|
||||
]
|
||||
execFileSync('emcmake', configure, { cwd: root, stdio: 'inherit' })
|
||||
|
||||
// The Debian Emscripten package omits the executable used only for optional JS minification.
|
||||
const linkPath = resolve(build, 'src/CMakeFiles/ocl.dir/link.txt')
|
||||
const originalLink = await readFile(linkPath, 'utf8')
|
||||
const reproducibleLink = originalLink.replace(/\s+--closure\s+1\b/, '')
|
||||
if (reproducibleLink === originalLink) throw new Error('OpenCAMLib generated link command did not contain the expected optional Closure flag.')
|
||||
await writeFile(linkPath, reproducibleLink)
|
||||
execFileSync('cmake', ['--build', build, '--target', 'ocl', '--parallel', '4'], { cwd: root, stdio: 'inherit' })
|
||||
execFileSync('cmake', ['--install', build], { cwd: root, stdio: 'inherit' })
|
||||
console.log(`Built OpenCAMLib ${revision} into ${install}`)
|
||||
@@ -19,6 +19,9 @@ const sourceCppFiles = (await run('git', ['-C', sourcePath, 'ls-tree', '-r', '--
|
||||
if (sourceCppFiles.length !== 0) throw new Error(`Bitbybit source snapshot unexpectedly contains native source files (${sourceCppFiles.length}); review the custom binding build inputs.`)
|
||||
const declarationPath = resolve(root, 'node_modules/@bitbybit-dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.d.ts')
|
||||
const declarations = await readFile(declarationPath, 'utf8')
|
||||
const wasmArtifactPath = resolve(root, matrix.bitbybitSource.wasmArtifact.path)
|
||||
const wasmArtifactHash = createHash('sha256').update(await readFile(wasmArtifactPath)).digest('hex')
|
||||
if (wasmArtifactHash !== matrix.bitbybitSource.wasmArtifact.sha256) throw new Error(`Bitbybit OCCT WASM hash mismatch: expected ${matrix.bitbybitSource.wasmArtifact.sha256}, got ${wasmArtifactHash}.`)
|
||||
|
||||
const interfaceBody = (name) => {
|
||||
const match = declarations.match(new RegExp(`export interface ${name} extends ClassHandle \\{([\\s\\S]*?)\\n\\}`))
|
||||
@@ -57,6 +60,7 @@ console.log(JSON.stringify({
|
||||
version: packageJson.version,
|
||||
sourceCommit,
|
||||
sourceForm: matrix.bitbybitSource.sourceForm,
|
||||
wasmArtifact: { status: matrix.bitbybitSource.wasmArtifact.status, path: matrix.bitbybitSource.wasmArtifact.path, sha256: wasmArtifactHash },
|
||||
missingBuildInputs: matrix.bitbybitSource.missingBuildInputs,
|
||||
declarationPath: 'node_modules/@bitbybit-dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.d.ts',
|
||||
nativeBooleanHistory: { generated: false, modified: false, deleted: false },
|
||||
|
||||
11
scripts/check-built-pwa-assets.mjs
Normal file
11
scripts/check-built-pwa-assets.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const serviceWorker = await readFile(resolve(root, 'dist/sw.js'), 'utf8')
|
||||
const shellMatch = serviceWorker.match(/const SHELL = (\[[^\n]+\])/)
|
||||
if (!shellMatch) throw new Error('Built PWA Service Worker shell is missing.')
|
||||
const shell = JSON.parse(shellMatch[1])
|
||||
const assetCount = shell.filter((entry) => entry.startsWith('/assets/')).length
|
||||
if (!shell.includes('/index.html') || !shell.includes('/manifest.webmanifest') || assetCount < 5 || !shell.some((entry) => entry.endsWith('.wasm'))) throw new Error(`Built PWA precache is incomplete: ${assetCount} assets.`)
|
||||
console.log(JSON.stringify({ status: 'built-pwa-assets-pass', shellEntries: shell.length, assetCount, wasmPrecached: shell.some((entry) => entry.endsWith('.wasm')) }, null, 2))
|
||||
61
scripts/check-cam-parity.mjs
Normal file
61
scripts/check-cam-parity.mjs
Normal file
@@ -0,0 +1,61 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const [contract, inventory, desktop, cam, app, pkg] = await Promise.all([
|
||||
readFile(resolve(root, 'config/freecad-cam-parity.json'), 'utf8').then(JSON.parse),
|
||||
readFile(resolve(root, 'config/freecad-gui-command-inventory.json'), 'utf8').then(JSON.parse),
|
||||
readFile(resolve(root, 'config/freecad-desktop-oracle.json'), 'utf8').then(JSON.parse),
|
||||
readFile(resolve(root, 'config/chrome-cam-verification.json'), 'utf8').then(JSON.parse),
|
||||
readFile(resolve(root, 'config/chrome-app-e2e-verification.json'), 'utf8').then(JSON.parse),
|
||||
readFile(resolve(root, 'package.json'), 'utf8').then(JSON.parse),
|
||||
])
|
||||
|
||||
const fail = (message) => { throw new Error(message) }
|
||||
if (contract.schemaVersion !== 1 || contract.status !== 'in_progress' || contract.claim !== 'compatible-browser-subset-not-exact-freecad-cam-parity') fail('CAM parity contract must preserve its explicit compatible, not exact, claim.')
|
||||
if (contract.baseline?.commit !== inventory.baseline?.commit || contract.baseline.commit !== desktop.verification?.sourceCommit) fail('CAM parity sources do not match the locked FreeCAD baseline.')
|
||||
if (desktop.verification?.guiUp !== true || desktop.status !== 'desktop-build-probed') fail('The locked FreeCAD desktop oracle has not been probed.')
|
||||
|
||||
const commandIds = contract.commandSurface?.freecadCommandIds || []
|
||||
if (contract.commandSurface?.toolbarGroups !== 4 || contract.commandSurface.menuGroups !== 5 || contract.commandSurface.webCommands !== 60 || commandIds.length !== 60 || new Set(commandIds).size !== 60) fail('CAM command surface contract is incomplete.')
|
||||
const runtimeIds = new Set(inventory.commands?.map((command) => command.id))
|
||||
const sourceOnlyIds = new Set(inventory.sourceOnlyCommands?.map((command) => command.id))
|
||||
const freecadRuntimeCamIds = [...runtimeIds].filter((id) => id.startsWith('CAM_'))
|
||||
const freecadSourceOnlyCamIds = [...sourceOnlyIds].filter((id) => id.startsWith('CAM_'))
|
||||
if (contract.commandSurface.freecadCamRuntimeCommands !== freecadRuntimeCamIds.length || contract.commandSurface.freecadCamSourceOnlyCommands !== freecadSourceOnlyCamIds.length) fail('FreeCAD CAM inventory command count changed; regenerate and review the parity contract.')
|
||||
const missingIds = commandIds.filter((id) => !runtimeIds.has(id) && !sourceOnlyIds.has(id))
|
||||
if (missingIds.length) fail(`CAM commands missing from the FreeCAD inventory: ${missingIds.join(', ')}`)
|
||||
if (commandIds.filter((id) => runtimeIds.has(id)).length !== 56 || commandIds.filter((id) => sourceOnlyIds.has(id)).length !== 4) fail('CAM command inventory runtime/source-only split changed; regenerate and review the parity contract.')
|
||||
const notYetSurfaced = contract.commandSurface.notYetSurfacedCommands || []
|
||||
const allFreecadCamIds = new Set([...freecadRuntimeCamIds, ...freecadSourceOnlyCamIds])
|
||||
const nonActionableTemplates = contract.commandSurface.nonActionableSourceTemplates || []
|
||||
if (notYetSurfaced.length !== 0 || nonActionableTemplates.length !== 1 || nonActionableTemplates.some((id) => !sourceOnlyIds.has(id)) || new Set([...commandIds, ...nonActionableTemplates]).size !== allFreecadCamIds.size) fail('CAM surfaced/inventory command partition is inconsistent.')
|
||||
|
||||
const domain = contract.browserDomain || {}
|
||||
if (domain.stockModes !== 4 || domain.toolShapes !== 7 || domain.operationKinds !== 22 || domain.dressupKinds !== 9 || domain.postprocessors !== 7) fail('CAM browser domain enumeration is incomplete.')
|
||||
if (!Array.isArray(contract.coverage) || contract.coverage.length !== 12 || new Set(contract.coverage.map((item) => item.id)).size !== 12) fail('CAM-21..CAM-32 coverage must enumerate twelve unique tasks.')
|
||||
if (contract.coverage.filter((item) => item.level === 'exact').length !== 1 || contract.coverage.find((item) => item.level === 'exact')?.id !== 'CAM-21') fail('Only the locked static CAM command manifest may currently claim exact coverage.')
|
||||
for (const item of contract.coverage) if (!pkg.scripts?.[item.evidence]) fail(`CAM parity evidence script is missing for ${item.id}: ${item.evidence}`)
|
||||
const tasks = contract.tasks || {}
|
||||
if (tasks.completed !== 8 || tasks.inProgress !== 4 || tasks.pending !== 0 || tasks.blocked !== 0 || tasks.completed + tasks.inProgress + tasks.pending + tasks.blocked !== 12) fail('CAM parity task status summary is inconsistent.')
|
||||
if (!Array.isArray(contract.unclosedDifferences) || contract.unclosedDifferences.length !== 7) fail('CAM parity must keep all seven unclosed difference classes explicit.')
|
||||
|
||||
if (cam.status !== 'pass' || cam.browserId !== 'chrome' || cam.job?.tools !== 2 || cam.job.controllers !== 2 || cam.job.operations !== 3 || cam.job.pathPoints < 20 || cam.job.dressups !== 1) fail('Chrome CAM domain evidence is incomplete.')
|
||||
if (cam.job?.sanity !== 'pass' || cam.job.simulation !== 'pass' || cam.job.collisionFixture < 1 || cam.job.unsafePostRejected !== true || cam.job.deterministic !== true || Object.keys(cam.job.postHashes || {}).length !== 7) fail('Chrome CAM safety, simulation or post evidence is incomplete.')
|
||||
if (cam.extended?.commandSurface !== 60 || cam.extended.operationKinds !== 22 || cam.extended.dressupKinds !== 9 || cam.extended.toolAssetRoundTrip !== true || cam.extended.jobAssetRoundTrip !== true || cam.extended.undoRedo !== true || cam.extended.startPointChanged !== true || cam.extended.materialRemoval !== true || cam.extended.removalTimelinePoints !== cam.job.pathPoints || cam.extended.fixtureCollision !== true || cam.extended.multiAxis !== true) fail('Chrome CAM extended command, asset lifecycle, material removal, fixture or multi-axis evidence is incomplete.')
|
||||
if (cam.persistence?.roundTrip !== true || cam.persistence.released !== true || cam.afterRelease?.shapeCount !== 0 || cam.afterRelease.kernelReferenceCount !== 0) fail('Chrome CAM lifecycle evidence is incomplete.')
|
||||
const requiredAppWorkflows = ['cam-workbench-job-flow', 'viewport-multi-object-box-selection']
|
||||
if (app.status !== 'pass' || !Array.isArray(app.workflows) || app.workflows.length < 13 || requiredAppWorkflows.some((workflow) => !app.workflows.includes(workflow)) || app.pageErrors?.length !== 0) fail('Chrome application CAM workflow evidence is incomplete.')
|
||||
if (app.uiParity?.camMenu?.items !== 60 || app.uiParity.camMenu.groups?.length !== 5 || app.cam?.ui?.commandGroups !== 4 || app.cam.ui.toolbarCommands !== 60 || app.cam.ui.toolpathPoints < 5 || app.cam.ui.toolpathOperations < 1 || app.cam.ui.toolpathLegend !== true || app.cam.ui.horizontalOverflow !== 0) fail('CAM workbench UI evidence is incomplete.')
|
||||
|
||||
console.log(JSON.stringify({
|
||||
status: 'cam-parity-contract-pass',
|
||||
baseline: contract.baseline,
|
||||
claim: contract.claim,
|
||||
commands: { total: commandIds.length, runtimeProbed: 56, sourceOnly: 4, nonActionableTemplates: nonActionableTemplates.length },
|
||||
browserDomain: domain,
|
||||
tasks,
|
||||
chrome: { workflows: app.workflows.length, menuItems: app.uiParity.camMenu.items, toolbarCommands: app.cam.ui.toolbarCommands, menuGroups: app.uiParity.camMenu.groups.length, toolpathPoints: app.cam.ui.toolpathPoints },
|
||||
unclosedDifferences: contract.unclosedDifferences.length,
|
||||
notYetSurfacedCommands: notYetSurfaced.length,
|
||||
}, null, 2))
|
||||
30
scripts/check-cam-pipeline.mjs
Normal file
30
scripts/check-cam-pipeline.mjs
Normal file
@@ -0,0 +1,30 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const source = resolve(root, 'CAMotics')
|
||||
if (!existsSync(source)) throw new Error('CAMotics source tree is required by the CAM pipeline.')
|
||||
const revision = execFileSync('git', ['-C', source, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim()
|
||||
if (revision !== 'e84665f2fa9d1151f03282ac7e01320bc65e015b') throw new Error(`CAMotics source revision drift: ${revision}`)
|
||||
for (const file of ['src/gcodetool.cpp', 'src/camsim.cpp', 'src/gcode/parse/Parser.cpp']) {
|
||||
if (!existsSync(resolve(source, file))) throw new Error(`CAMotics source artifact missing: ${file}`)
|
||||
}
|
||||
for (const file of ['camsim', 'gcodetool', 'planner', 'build/camotics.so']) {
|
||||
if (!existsSync(resolve(source, file))) throw new Error(`CAMotics native artifact missing: ${file}`)
|
||||
}
|
||||
console.log(JSON.stringify({
|
||||
status: 'cam-pipeline-pass',
|
||||
order: ['CAD', 'OCL', 'CAMotics', 'GCODE', 'LinuxCNC WASM'],
|
||||
camotics: {
|
||||
sourcePath: 'CAMotics',
|
||||
sourceRevision: revision,
|
||||
generator: 'camotics-source-stage',
|
||||
simulator: 'camotics-camsim',
|
||||
workspaceNativeCli: 'verified-sidecar',
|
||||
browserNativeExecutable: false,
|
||||
parserAuthority: 'linuxcnc-wasm',
|
||||
camoticsParsesCanonicalGcode: false,
|
||||
},
|
||||
linuxcnc: { backend: 'linuxcnc-wasm', parserAuthority: 'linuxcnc-wasm' },
|
||||
}, null, 2))
|
||||
32
scripts/check-camotics-gui-tpl.mjs
Normal file
32
scripts/check-camotics-gui-tpl.mjs
Normal file
@@ -0,0 +1,32 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { execFileSync, spawnSync } from 'node:child_process'
|
||||
import { readFile, stat } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const evidence = JSON.parse(await readFile(resolve(root, 'config/camotics-native-artifact.json'), 'utf8'))
|
||||
const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex')
|
||||
|
||||
if (evidence.schemaVersion !== 2 || evidence.status !== 'pass' || evidence.build.withGui !== true || evidence.build.withTpl !== true) throw new Error('CAMotics GUI/TPL evidence is not passing.')
|
||||
const revision = execFileSync('git', ['-C', resolve(root, evidence.source.path), 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim()
|
||||
if (revision !== evidence.source.revision) throw new Error(`CAMotics GUI/TPL source revision drift: ${revision}`)
|
||||
for (const [path, expected] of Object.entries(evidence.artifacts)) {
|
||||
const absolute = resolve(root, path)
|
||||
const bytes = await readFile(absolute)
|
||||
const metadata = await stat(absolute)
|
||||
const digest = sha256(bytes)
|
||||
if (!metadata.isFile() || (metadata.mode & 0o111) === 0 || bytes.byteLength !== expected.bytes || digest !== expected.sha256) throw new Error(`CAMotics GUI/TPL artifact drift: ${path}`)
|
||||
}
|
||||
|
||||
const runHelp = (binary, env = {}) => {
|
||||
const result = spawnSync(resolve(root, binary), ['--help'], { encoding: 'utf8', env: { ...process.env, ...env } })
|
||||
const output = `${result.stdout}${result.stderr}`
|
||||
return { status: result.status, output }
|
||||
}
|
||||
const camotics = runHelp('CAMotics/camotics', { QT_QPA_PLATFORM: 'offscreen' })
|
||||
const tplang = runHelp('CAMotics/tplang')
|
||||
if (camotics.status !== 0 || tplang.status !== 0 || !camotics.output.includes('Usage:') || !tplang.output.includes('Usage:')) throw new Error('CAMotics GUI/TPL help smoke failed.')
|
||||
if (evidence.guiSmoke.status !== 'pass' || evidence.guiSmoke.window.mapState !== 'IsViewable' || evidence.guiSmoke.window.width !== 1200 || evidence.guiSmoke.window.height !== 800 || evidence.guiSmoke.browserRuntime !== false) throw new Error('CAMotics GUI Xvfb smoke boundary is invalid.')
|
||||
if (evidence.pipelineBoundary.workspaceNativeGuiAvailable !== true || evidence.pipelineBoundary.workspaceNativeTplAvailable !== true || evidence.pipelineBoundary.browserNativeGuiAvailable !== false || evidence.pipelineBoundary.browserNativeTplAvailable !== false || evidence.pipelineBoundary.canonicalGcodeParser !== 'linuxcnc-wasm') throw new Error('CAMotics GUI/TPL browser boundary is invalid.')
|
||||
|
||||
console.log(JSON.stringify({ status: 'camotics-gui-tpl-pass', sourceRevision: revision, version: evidence.source.version, artifacts: evidence.artifacts, smoke: { camotics: { exitCode: camotics.status }, tplang: { exitCode: tplang.status }, gui: evidence.guiSmoke }, browserBoundary: evidence.pipelineBoundary }, null, 2))
|
||||
79
scripts/check-camotics-native.mjs
Normal file
79
scripts/check-camotics-native.mjs
Normal file
@@ -0,0 +1,79 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { execFileSync, spawnSync } from 'node:child_process'
|
||||
import { readFile, stat } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const evidence = JSON.parse(await readFile(resolve(root, 'config/camotics-native-artifact.json'), 'utf8'))
|
||||
const sha256 = (value) => createHash('sha256').update(value).digest('hex')
|
||||
const run = (command, args, options = {}) => {
|
||||
const result = spawnSync(command, args, { encoding: 'utf8', ...options })
|
||||
if (result.error || result.status !== 0) throw new Error(`${command} failed: ${result.error?.message || result.stderr || result.stdout}`)
|
||||
return result
|
||||
}
|
||||
|
||||
if (evidence.schemaVersion !== 2 || evidence.status !== 'pass') throw new Error('CAMotics native evidence is not passing.')
|
||||
const camoticsPath = resolve(root, evidence.source.path)
|
||||
const camoticsRevision = execFileSync('git', ['-C', camoticsPath, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim()
|
||||
if (camoticsRevision !== evidence.source.revision) throw new Error(`CAMotics source revision drift: ${camoticsRevision}`)
|
||||
const cbangPath = resolve(root, evidence.cbang.path)
|
||||
const cbangRevision = execFileSync('git', ['-C', cbangPath, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim()
|
||||
if (cbangRevision !== evidence.cbang.revision) throw new Error(`C! source revision drift: ${cbangRevision}`)
|
||||
if (execFileSync('git', ['-C', cbangPath, 'status', '--porcelain'], { encoding: 'utf8' }).trim() !== '') throw new Error('Pinned C! checkout is dirty.')
|
||||
|
||||
const cbangSConstruct = await readFile(resolve(root, 'CAMotics/cbang/SConstruct'), 'utf8')
|
||||
if (!cbangSConstruct.includes(`version = '${evidence.cbang.version}'`)) throw new Error('C! version does not match native evidence.')
|
||||
if (!evidence.build.options.includes('v8_compress_pointers=0') || evidence.build.withGui !== true || evidence.build.withTpl !== true || evidence.build.browserExecutable !== false || evidence.pipelineBoundary.workspaceNativeCliAvailable !== true) throw new Error('CAMotics native build boundary is invalid.')
|
||||
|
||||
for (const [path, expected] of Object.entries(evidence.artifacts)) {
|
||||
const absolute = resolve(root, path)
|
||||
const artifact = await readFile(absolute)
|
||||
const metadata = await stat(absolute)
|
||||
if (!metadata.isFile() || (metadata.mode & 0o111) === 0) throw new Error(`CAMotics artifact is not executable: ${path}`)
|
||||
if (artifact.byteLength !== expected.bytes || sha256(artifact) !== expected.sha256) throw new Error(`CAMotics artifact size or digest mismatch: ${path}`)
|
||||
if (artifact.subarray(0, 4).toString('ascii') !== '\x7fELF') throw new Error(`CAMotics artifact is not an ELF executable: ${path}`)
|
||||
}
|
||||
|
||||
for (const [path, expected] of Object.entries(evidence.cliArtifacts ?? {})) {
|
||||
const absolute = resolve(root, path)
|
||||
const artifact = await readFile(absolute)
|
||||
const metadata = await stat(absolute)
|
||||
if (!metadata.isFile() || (metadata.mode & 0o111) === 0 || artifact.byteLength !== expected.bytes || sha256(artifact) !== expected.sha256) throw new Error(`CAMotics CLI artifact size or digest mismatch: ${path}`)
|
||||
if (artifact.subarray(0, 4).toString('ascii') !== '\x7fELF') throw new Error(`CAMotics CLI artifact is not an ELF file: ${path}`)
|
||||
}
|
||||
|
||||
const camoticsVersionProbe = run(resolve(root, 'CAMotics/camotics'), ['--version'])
|
||||
const tplVersionProbe = run(resolve(root, 'CAMotics/tplang'), ['--version'])
|
||||
const camoticsVersion = `${camoticsVersionProbe.stdout}${camoticsVersionProbe.stderr}`.trim()
|
||||
const tplVersion = `${tplVersionProbe.stdout}${tplVersionProbe.stderr}`.trim()
|
||||
if (camoticsVersion !== evidence.source.version || tplVersion !== evidence.source.version) throw new Error(`CAMotics version probe failed: ${camoticsVersion}/${tplVersion}`)
|
||||
|
||||
const tplTests = run(resolve(root, 'CAMotics/tests/testHarness'), ['--no-color'], { cwd: resolve(root, 'CAMotics/tests/tplTests') })
|
||||
const passed = Number(tplTests.stdout.match(/^Passed\s+(\d+)/m)?.[1] ?? -1)
|
||||
const failed = Number(tplTests.stdout.match(/^Failed\s+(\d+)/m)?.[1] ?? -1)
|
||||
if (passed !== evidence.tplTests.passed || failed !== evidence.tplTests.failed) throw new Error(`TPL test result drift: ${passed}/${failed}`)
|
||||
|
||||
const tplExample = run(resolve(root, 'CAMotics/tplang'), [resolve(root, 'CAMotics/examples/box/box.tpl')])
|
||||
const tplBytes = Buffer.from(tplExample.stdout)
|
||||
if (tplBytes.byteLength !== evidence.tplExample.bytes || sha256(tplBytes) !== evidence.tplExample.sha256) throw new Error('TPL example output drift.')
|
||||
|
||||
const nativeLinks = run('ldd', [resolve(root, 'CAMotics/camotics')]).stdout
|
||||
for (const required of ['libQt5Widgets', 'libQt5WebSockets', 'libQt5Network', 'libQt5Core', 'libnode']) if (!nativeLinks.includes(required)) throw new Error(`CAMotics GUI is missing runtime dependency ${required}.`)
|
||||
|
||||
if (evidence.guiSmoke.status !== 'pass' || evidence.guiSmoke.window.mapState !== 'IsViewable' || evidence.guiSmoke.window.width !== 1200 || evidence.guiSmoke.window.height !== 800 || evidence.guiSmoke.window.depth !== 24 || evidence.guiSmoke.processAliveBeforeTerminate !== true || evidence.guiSmoke.browserRuntime !== false) throw new Error('CAMotics GUI smoke boundary is invalid.')
|
||||
if (evidence.pipelineBoundary.workspaceNativeGuiAvailable !== true || evidence.pipelineBoundary.workspaceNativeTplAvailable !== true || evidence.pipelineBoundary.workspaceNativeCliAvailable !== true || evidence.pipelineBoundary.browserNativeGuiAvailable !== false || evidence.pipelineBoundary.browserNativeTplAvailable !== false || evidence.pipelineBoundary.canonicalGcodeParser !== 'linuxcnc-wasm' || evidence.pipelineBoundary.camoticsParsesCanonicalPipelineGcode !== false) throw new Error('CAMotics/LinuxCNC authority boundary is invalid.')
|
||||
for (const [path, smoke] of Object.entries(evidence.cliSmoke ?? {})) if (smoke.exitCode !== 0 || smoke.containsUsage !== true) throw new Error(`CAMotics CLI smoke failed: ${path}`)
|
||||
|
||||
console.log(JSON.stringify({
|
||||
status: 'camotics-native-pass',
|
||||
camoticsRevision,
|
||||
cbangRevision,
|
||||
versions: { camotics: camoticsVersion, tplang: tplVersion },
|
||||
artifacts: evidence.artifacts,
|
||||
cliArtifacts: evidence.cliArtifacts,
|
||||
cliSmoke: evidence.cliSmoke,
|
||||
tplTests: { passed, failed },
|
||||
tplExample: evidence.tplExample,
|
||||
guiSmoke: evidence.guiSmoke,
|
||||
pipelineBoundary: evidence.pipelineBoundary,
|
||||
}, null, 2))
|
||||
38
scripts/check-camotics-wasm.mjs
Normal file
38
scripts/check-camotics-wasm.mjs
Normal file
@@ -0,0 +1,38 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const evidence = JSON.parse(await readFile(resolve(root, 'config/camotics-wasm-artifact.json'), 'utf8'))
|
||||
const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex')
|
||||
|
||||
if (evidence.schemaVersion !== 1 || evidence.status !== 'pass' || evidence.kind !== 'standalone-camotics-sweep-wasm-kernel') throw new Error('CAMotics WASM evidence is not passing.')
|
||||
const revision = execFileSync('git', ['-C', resolve(root, evidence.upstream.path), 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim()
|
||||
if (revision !== evidence.upstream.revision) throw new Error(`CAMotics WASM source revision drift: ${revision}`)
|
||||
for (const [path, expected] of Object.entries(evidence.build.sourceFiles)) {
|
||||
const bytes = await readFile(resolve(root, path))
|
||||
if (sha256(bytes) !== expected) throw new Error(`CAMotics WASM source fingerprint drift: ${path}`)
|
||||
}
|
||||
|
||||
const bytes = await readFile(resolve(root, evidence.artifact.path))
|
||||
if (bytes.byteLength !== evidence.artifact.bytes || sha256(bytes) !== evidence.artifact.sha256) throw new Error('CAMotics WASM artifact size or digest mismatch.')
|
||||
const module = new WebAssembly.Module(bytes)
|
||||
const imports = WebAssembly.Module.imports(module)
|
||||
if (imports.length !== 0 || evidence.artifact.imports.length !== 0) throw new Error('CAMotics WASM must be standalone with zero imports.')
|
||||
const exports = new Set(WebAssembly.Module.exports(module).map((entry) => entry.name))
|
||||
for (const required of evidence.artifact.requiredExports) if (!exports.has(required)) throw new Error(`CAMotics WASM export is missing: ${required}`)
|
||||
|
||||
const instance = await WebAssembly.instantiate(module, {})
|
||||
const api = instance.exports
|
||||
if (api.camotics_sweep_abi_version() !== evidence.artifact.abiVersion) throw new Error('CAMotics WASM ABI version drift.')
|
||||
const smoke = {
|
||||
conicInsideDepth: api.camotics_conic_depth(5, 2, 2, 0, 0, 0, 10, 0, 0, 5, 0, 0),
|
||||
conicOutsideDepth: api.camotics_conic_depth(5, 2, 2, 0, 0, 0, 10, 0, 0, 5, 4, 0),
|
||||
spheroidInsideDepth: api.camotics_spheroid_depth(2, 4, 0, 0, 0, 10, 0, 0, 5, 0, 0),
|
||||
longMoveBoundingBoxes: api.camotics_conic_bbox_count(5, 2, 2, 0, 0, 0, 100, 0, 0, 0.01),
|
||||
}
|
||||
if (JSON.stringify(smoke) !== JSON.stringify(evidence.smoke)) throw new Error(`CAMotics WASM smoke drift: ${JSON.stringify(smoke)}`)
|
||||
if (evidence.scope.actualUpstreamCppCompiled !== true || evidence.scope.browserExecutable !== true || evidence.scope.fullCamoticsProgram !== false || evidence.scope.gcodeParserIncluded !== false || evidence.scope.canonicalGcodeParser !== 'linuxcnc-wasm') throw new Error('CAMotics WASM capability boundary is invalid.')
|
||||
|
||||
console.log(JSON.stringify({ status: 'camotics-wasm-pass', sourceRevision: revision, artifact: { bytes: bytes.byteLength, sha256: sha256(bytes), imports: imports.length, exports: [...exports].filter((name) => evidence.artifact.requiredExports.includes(name)) }, abiVersion: evidence.artifact.abiVersion, smoke, scope: evidence.scope }, null, 2))
|
||||
3
scripts/check-chrome-addon.mjs
Normal file
3
scripts/check-chrome-addon.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname); const report = JSON.parse(await readFile(resolve(root, 'config/chrome-addon-verification.json'), 'utf8')); if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome addon governance verification is not passing.'); if (report.catalog?.installed !== 1 || report.catalog.revision !== 1 || report.catalog.rollbackVersion !== '1.0.0') throw new Error('Addon update/rollback evidence is incomplete.'); if (!report.security?.forgedRejected || !report.security.permissionRejected || !report.security.removed) throw new Error('Addon security evidence is incomplete.'); if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('Addon OPFS resource evidence is incomplete.'); if (report.opfs?.markerSuite !== 'ADDON-ALL' || report.opfs.markerInstalled !== 1 || report.opfs.markerRemoved !== true) throw new Error('Addon OPFS marker evidence is incomplete.'); if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('Addon harness leaked ShapeHandles.'); console.log(JSON.stringify({ status: 'chrome-addon-pass', browser: report.browser.product, catalog: report.catalog, security: report.security, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
43
scripts/check-chrome-app-e2e-verification.mjs
Normal file
43
scripts/check-chrome-app-e2e-verification.mjs
Normal file
@@ -0,0 +1,43 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-app-e2e-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.status !== 'pass') throw new Error('Chrome application E2E evidence is not passing.')
|
||||
if (!/^HeadlessChrome\//.test(report.browser?.product || '') && !/^Chrome\//.test(report.browser?.product || '')) throw new Error('Chrome application E2E evidence has an unexpected browser product.')
|
||||
if (report.headers?.coop !== 'same-origin' || report.headers?.coep !== 'require-corp') throw new Error('Chrome application E2E evidence is missing cross-origin isolation headers.')
|
||||
if (!Array.isArray(report.pageErrors) || report.pageErrors.length !== 0) throw new Error('Chrome application E2E evidence contains page errors.')
|
||||
if (report.desktop?.path !== '/workspace/pump-housing' || report.desktop?.workbench !== 'Part Design' || report.desktop?.bodyHorizontalOverflow > 1 || report.desktop?.overflowingControls !== 0 || report.desktop?.panelsSeparated !== true || report.desktop?.commandGroups < 4 || !report.desktop?.comboTabs?.some((label) => label.startsWith('Model')) || !report.desktop?.comboTabs?.some((label) => label.startsWith('Tasks')) || report.desktop?.selectionView !== true || report.desktop?.statusBar !== true) throw new Error('Chrome desktop workflow or layout evidence is invalid.')
|
||||
if (report.uiParity?.fileMenu?.items < 7 || report.uiParity?.fileMenu?.expanded !== 'File' || !report.uiParity?.fileMenu?.labels?.some((label) => label.includes('Save'))) throw new Error('Chrome FreeCAD menu evidence is invalid.')
|
||||
if (report.uiParity?.commandDialog?.modal !== true || report.uiParity?.commandDialog?.results < 10 || report.uiParity?.commandDialog?.title !== 'Command search' || report.uiParity?.commandDialog?.focused !== 'Command search') throw new Error('Chrome command search dialog evidence is invalid.')
|
||||
if (report.uiParity?.preferencesDialog?.modal !== true || report.uiParity?.preferencesDialog?.selects !== 3 || report.uiParity?.preferencesDialog?.checks !== 2 || report.uiParity?.aboutDialog?.modal !== true || report.uiParity?.aboutDialog?.baseline !== true) throw new Error('Chrome Preferences/About dialog evidence is invalid.')
|
||||
if (report.uiParity?.contextMenu?.items?.length !== 4 || !report.uiParity?.contextMenu?.items?.some((label) => label.includes('Toggle visibility')) || !report.uiParity?.contextMenu?.items?.some((label) => label.includes('Delete')) || report.uiParity?.contextMenu?.left < 0 || report.uiParity?.contextMenu?.top < 0) throw new Error('Chrome model tree context menu evidence is invalid.')
|
||||
if (report.uiParity?.multiSelection?.count !== 2 || JSON.stringify(report.uiParity.multiSelection.labels) !== JSON.stringify(['Sketch', 'Pad'])) throw new Error('Chrome model tree multi-selection evidence is invalid.')
|
||||
if (!report.workflows?.includes('viewport-multi-object-box-selection') || report.uiParity?.viewportBoxSelection?.objectCount < 2 || report.uiParity.viewportBoxSelection.topologySource !== 'occt-analytic' || report.uiParity.viewportBoxSelection.edgeCount < 4 || report.uiParity.viewportBoxSelection.vertexCount < 4 || report.uiParity.viewportBoxSelection.selectedCount < 2 || !report.uiParity.viewportBoxSelection.labels?.includes('Box') || !report.uiParity.viewportBoxSelection.labels?.includes('Cylinder')) throw new Error('Chrome viewport multi-object box-selection or analytic topology evidence is invalid.')
|
||||
if (!report.workflows?.includes('cam-workbench-job-flow') || report.uiParity?.camMenu?.items !== 60 || JSON.stringify(report.uiParity?.camMenu?.groups) !== JSON.stringify(['Project Setup', 'Simulation and Tools', '2D Operations', 'Machining Operations', 'Path Modification'])) throw new Error('Chrome FreeCAD CAM menu evidence is invalid.')
|
||||
if (report.cam?.jobTask?.stockModes !== 4 || report.cam.jobTask.fields < 5 || report.cam?.toolTask?.selects < 2 || report.cam.toolTask.inputs !== 13 || report.cam?.operationTask?.inputs < 5 || report.cam.operationTask.selects < 2) throw new Error('Chrome CAM Task panel evidence is invalid.')
|
||||
if (report.cam?.undoRedo?.undo?.pathPoints !== 0 || report.cam.undoRedo.undo.redoEnabled !== true || report.cam?.undoRedo?.redo?.pathPoints < 5 || report.cam.undoRedo.redo.undoEnabled !== true) throw new Error('Chrome CAM Undo/Redo UI evidence is invalid.')
|
||||
if (report.cam?.extendedTasks?.fixture?.inputs !== 7 || report.cam.extendedTasks.fixture.section !== true || !report.cam.extendedTasks.fixture.labels?.some((label) => label.includes('Fixture ID')) || !report.cam.extendedTasks.fixture.labels?.some((label) => label.includes('Max Z')) || report.cam?.extendedTasks?.toolBitLoad?.inputs !== 1 || report.cam.extendedTasks.toolBitLoad.textareas !== 1 || report.cam.extendedTasks.toolBitLoad.schemaVersion !== true || report.cam?.extendedTasks?.drilling?.inputs !== 5 || report.cam.extendedTasks.drilling.selects !== 2 || !report.cam.extendedTasks.drilling.labels?.some((label) => label.includes('Final Depth')) || report.cam?.extendedTasks?.dragKnife?.inputs !== 1 || report.cam.extendedTasks.dragKnife.selects !== 1 || report.cam.extendedTasks.dragKnife.knifeOffset !== true || report.cam?.extendedTasks?.post?.inputs !== 4 || report.cam.extendedTasks.post.selects !== 4 || !report.cam.extendedTasks.post.labels?.some((label) => label.includes('Rotary End')) || !report.cam.extendedTasks.post.labels?.some((label) => label.includes('Tilt End')) || !report.cam.extendedTasks.post.note?.includes('rotary limit checks')) throw new Error('Chrome extended CAM Task panel evidence is invalid.')
|
||||
if (report.cam?.ui?.workbench !== 'CAM' || report.cam.ui.commandGroups !== 4 || report.cam.ui.toolbarCommands !== 60 || report.cam.ui.jobTree !== true || report.cam.ui.treeRows < 7 || report.cam.ui.operations !== 1 || report.cam.ui.generated !== true || report.cam.ui.toolpathPoints < 5 || report.cam.ui.toolpathOperations !== 1 || report.cam.ui.toolpathLegend !== true || report.cam.ui.horizontalOverflow > 1) throw new Error('Chrome CAM Job tree, toolbar or toolpath evidence is invalid.')
|
||||
if (report.loftTask?.sectionCount < 1 || report.loftTask?.checkedSectionCount !== 1 || !report.loftTask?.labels?.includes('Sketch (sketch)') || report.loftTask?.panelHorizontalOverflow > 1) throw new Error('Chrome Loft task evidence is invalid.')
|
||||
if (report.desktop?.kernelPreviewSource !== 'bitbybit-occt' || report.desktop?.canvas?.width < 100 || report.desktop?.canvas?.height < 100 || report.desktop?.canvas?.uniqueColors < 4 || report.desktop?.canvas?.glError !== 0) throw new Error('Chrome OCCT preview or WebGL canvas evidence is blank or invalid.')
|
||||
if (report.persistenceRoundTrip?.path !== '/workspace/pump-housing' || !report.persistenceRoundTrip?.documentLabel?.includes('Pump Housing') || report.persistenceRoundTrip?.persistenceMode !== 'sqlite-opfs') throw new Error('Chrome OPFS save/reopen evidence is invalid.')
|
||||
if (report.importFlow?.path !== '/import' || report.importFlow?.accept !== '.FCStd,.fcstd,.step,.stp,.iges,.igs,.brep,.brp' || report.importFlow?.exchangeHint !== true) throw new Error('Chrome import format workflow evidence is invalid.')
|
||||
if (report.exportFlow?.path !== '/export' || report.exportFlow?.formats?.STEP?.selected !== true || report.exportFlow?.formats?.STEP?.disabled !== false || report.exportFlow?.formats?.STL?.disabled !== false || report.exportFlow?.formats?.IGES?.disabled !== false || report.exportFlow?.selectedAfterInteraction !== 'STL') throw new Error('Chrome export format workflow evidence is invalid.')
|
||||
if (report.mobile?.path !== '/start' || report.mobile?.viewport?.width !== 390 || report.mobile?.bodyHorizontalOverflow > 1 || report.mobile?.visibleHeading !== true || report.mobile?.visibleActions !== true) throw new Error('Chrome mobile layout evidence is invalid.')
|
||||
if (!Number.isSafeInteger(report.accessibility?.checked) || report.accessibility.checked < 1 || !Array.isArray(report.accessibility.missing) || report.accessibility.missing.length !== 0) throw new Error('Chrome accessibility naming evidence is invalid.')
|
||||
if (!Number.isSafeInteger(report.screenReader?.nodes) || report.screenReader.nodes < 10 || report.screenReader.controls < 8 || report.screenReader.namedControls !== report.screenReader.controls || report.screenReader.unnamedControls !== 0) throw new Error('Chrome accessibility tree evidence is invalid.')
|
||||
if (report.keyboard?.steps !== 8 || report.keyboard?.named !== 8 || report.keyboard?.unnamedVisible !== 0 || !Array.isArray(report.keyboard.sequence) || report.keyboard.sequence.length !== 8) throw new Error('Chrome keyboard focus evidence is invalid.')
|
||||
if (report.pwa?.manifestLinked !== true || report.pwa?.manifestStatus !== 200 || report.pwa?.name !== 'BitBybit CAD Studio' || report.pwa?.startUrl !== '/start' || report.pwa?.scope !== '/' || report.pwa?.display !== 'standalone') throw new Error('Chrome PWA manifest evidence is invalid.')
|
||||
if (report.offline?.path !== '/start' || report.offline?.shell !== true || report.offline?.heading !== true) throw new Error('Chrome offline shell evidence is invalid.')
|
||||
if (Object.keys(report.screenshots || {}).length !== 3 || !report.screenshots?.cam?.path?.endsWith('cam-workbench.png')) throw new Error('Chrome CAM screenshot evidence is missing.')
|
||||
|
||||
for (const screenshot of Object.values(report.screenshots || {})) {
|
||||
if (!screenshot?.path || screenshot.byteLength < 20_000 || !/^[a-f0-9]{64}$/.test(screenshot.sha256 || '')) throw new Error('Chrome screenshot metadata is invalid.')
|
||||
const bytes = await readFile(resolve(root, screenshot.path))
|
||||
if (bytes.byteLength !== screenshot.byteLength || bytes.subarray(0, 8).toString('hex') !== '89504e470d0a1a0a') throw new Error(`Chrome screenshot is missing or is not PNG: ${screenshot.path}`)
|
||||
if (createHash('sha256').update(bytes).digest('hex') !== screenshot.sha256) throw new Error(`Chrome screenshot hash mismatch: ${screenshot.path}`)
|
||||
}
|
||||
|
||||
console.log(JSON.stringify({ status: 'chrome-app-e2e-verification-pass', browserId: report.browserId, workflows: report.workflows, accessibility: report.screenReader, screenshots: report.screenshots }, null, 2))
|
||||
14
scripts/check-chrome-assembly.mjs
Normal file
14
scripts/check-chrome-assembly.mjs
Normal file
@@ -0,0 +1,14 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-assembly-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome Assembly verification is not passing.')
|
||||
if (report.geometry?.components !== 2 || report.geometry.validShapes !== 2) throw new Error('Assembly component/Bitbybit shape evidence is incomplete.')
|
||||
if (report.solver?.status !== 'solved' || report.solver.iterations !== 2 || report.solver.coincident !== 'solved' || report.solver.distance !== 'solved' || report.solver.shaftX !== 5 || report.solver.diagnostics !== 0 || report.solver.workerStatus !== 'solved' || report.solver.angleStatus !== 'solved' || Math.abs(report.solver.angleYaw - Math.PI / 2) > 1e-12) throw new Error('Assembly Worker/fixed/distance/angle solver evidence is incomplete.')
|
||||
if (report.tools?.bomRows !== 2 || report.tools.collisionCount !== 1 || report.tools.explodedMoved !== true || report.tools.variantX !== 7 || report.tools.motionFrames !== 5 || report.tools.motionEndX !== 9) throw new Error('Assembly tools and motion evidence is incomplete.')
|
||||
if (report.scale?.components !== 1000 || report.scale.bomRows !== 10 || report.scale.durationMs > report.scale.budgetMs || report.scale.budgetMs !== 1000) throw new Error('Assembly large-model budget evidence is incomplete.')
|
||||
if (report.fcstd?.bytes <= 0 || report.fcstd.objects !== 4 || report.fcstd.links !== 1 || report.fcstd.reopenedObjects !== 4 || report.fcstd.roundTrip !== true || report.fcstd.released !== true) throw new Error('Assembly FCStd link round-trip evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || !/^[0-9a-f]{64}$/.test(report.persistence.hash) || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('Assembly OPFS resource evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'ASM-TOOLS' || report.opfs.markerComponents !== 2 || report.opfs.markerRemoved !== true) throw new Error('Assembly OPFS marker evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('Assembly harness leaked ShapeHandles.')
|
||||
console.log(JSON.stringify({ status: 'chrome-assembly-pass', browser: report.browser.product, geometry: report.geometry, solver: report.solver, tools: report.tools, scale: report.scale, fcstd: report.fcstd, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
11
scripts/check-chrome-bim.mjs
Normal file
11
scripts/check-chrome-bim.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-bim-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome BIM verification is not passing.')
|
||||
if (report.model?.sites !== 1 || report.model.buildings !== 1 || report.model.levels !== 1 || report.model.spaces !== 1 || report.model.materials !== 1 || report.model.elements !== 1 || report.model.scheduleRows !== 1 || report.model.ifc4Bytes <= 0 || report.model.ifc2x3Bytes <= 0 || report.model.ifc4Schema !== 'IFC4' || report.model.ifc2x3Schema !== 'IFC2X3' || report.model.ifc4Entities < 7 || report.model.ifc4Hierarchy !== 4 || report.model.ifc4PropertySets < 1 || report.model.ifc4Classifications !== 1 || report.model.ifc2x3RoundTrip !== true) throw new Error('BIM hierarchy/IFC2X3/IFC4 evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || !/^[0-9a-f]{64}$/.test(report.persistence.hash) || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('BIM OPFS resource evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'BIM-IFC' || report.opfs.elements !== 1 || report.opfs.markerRemoved !== true) throw new Error('BIM OPFS marker was not verified and removed.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('BIM harness leaked ShapeHandles.')
|
||||
console.log(JSON.stringify({ status: 'chrome-bim-pass', browser: report.browser.product, model: report.model, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
11
scripts/check-chrome-cam-linuxcnc-machine.mjs
Normal file
11
scripts/check-chrome-cam-linuxcnc-machine.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-cam-linuxcnc-machine-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome') throw new Error('Chrome CAM LinuxCNC machine verification is not passing.')
|
||||
if (JSON.stringify(report.stages) !== JSON.stringify(['CAD', 'OCL', 'CAMotics', 'GCODE', 'LinuxCNC WASM'])) throw new Error('CAM machine stage order is invalid.')
|
||||
if (report.openCamLib?.backend !== 'upstream-opencamlib-wasm' || report.openCamLib.sourceRevision?.length !== 40 || report.openCamLib.artifactSha256?.length !== 64 || report.openCamLib.triangleCount < 2 || report.openCamLib.inputPoints < 2 || report.openCamLib.outputPoints < report.openCamLib.inputPoints || !(report.openCamLib.sampling > 0)) throw new Error('OpenCAMLib WASM stage evidence is incomplete.')
|
||||
if (!report.gcode?.hasM428 || !report.gcode.hasM429 || !report.gcode.hasG93 || !report.gcode.hasG94 || !report.gcode.hasB || !report.gcode.hasC359 || !report.gcode.hasC361) throw new Error('LinuxCNC XYZBC RTCP G-code evidence is incomplete.')
|
||||
if (report.machine?.backend !== 'linuxcnc-wasm' || report.machine.status !== 'accepted' || report.machine.dryRunStatus !== 'dry-run' || report.machine.parserAuthority !== 'linuxcnc-wasm' || report.machine.lines < 10 || report.machine.lines >= 1000 || report.machine.blocks < 2 || report.machine.blocks >= 1000 || report.machine.trajectorySegments < 2 || report.machine.trajectorySegments >= 1000) throw new Error('LinuxCNC WASM machine acceptance evidence is incomplete or belongs to a different program.')
|
||||
console.log(JSON.stringify({ status: 'chrome-cam-linuxcnc-machine-pass', stages: report.stages, openCamLib: report.openCamLib, gcode: report.gcode, machine: report.machine, browser: report.browser }, null, 2))
|
||||
14
scripts/check-chrome-cam-native-simulation.mjs
Normal file
14
scripts/check-chrome-cam-native-simulation.mjs
Normal file
@@ -0,0 +1,14 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-cam-native-simulation-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome native CAM simulation verification is not passing.')
|
||||
if (report.capabilities?.openCamLib?.status !== 'available' || report.capabilities.openCamLib.backend !== 'upstream-opencamlib-wasm' || report.capabilities.camotics?.status !== 'native-host-available' || report.capabilities.camotics.backend !== 'camotics-native-host-qt5-tpl' || report.capabilities.camotics.workspaceNativeExecutable !== true || report.capabilities.camotics.nativeGuiPath !== 'CAMotics/camotics' || report.capabilities.camotics.nativeTplPath !== 'CAMotics/tplang' || report.capabilities.camotics.browserExecutable !== false || report.capabilities.camotics.browserWasmKernelExecutable !== true || report.capabilities.camotics.wasmKernel?.backend !== 'camotics-upstream-sweep-wasm' || report.capabilities.camotics.wasmKernel.fullCamoticsProgram !== false || report.capabilities.camotics.wasmKernel.gcodeParserIncluded !== false || report.capabilities.camotics.parserAuthority !== 'linuxcnc-wasm' || report.capabilities.camotics.camoticsParsesCanonicalGcode !== false) throw new Error('Native CAM capability evidence is incomplete.')
|
||||
if (report.openCamLib?.backend !== 'upstream-opencamlib-wasm' || report.openCamLib.triangles !== 2 || report.openCamLib.points < 2 || Math.abs(report.openCamLib.minimumZ - 2) > 1e-9 || Math.abs(report.openCamLib.maximumZ - 2) > 1e-9 || report.openCamLib.deterministic !== true || !/^[0-9a-f]{64}$/.test(report.openCamLib.artifactSha256)) throw new Error('OpenCAMLib drop-cutter evidence is incomplete.')
|
||||
if (report.camotics?.backend !== 'camotics-source-stage-contract' || report.camotics.nativeExecutable !== false || report.camotics.lineCount < 6 || report.camotics.gcodeBytes < 1 || !/^[0-9a-f]{64}$/.test(report.camotics.gcodeHash) || report.camotics.parserAuthority !== 'linuxcnc-wasm' || report.camotics.camoticsParsesCanonicalGcode !== false) throw new Error('CAMotics source-stage G-code evidence is incomplete.')
|
||||
if (JSON.stringify(report.pipeline?.stages) !== JSON.stringify(['cad', 'opencamlib-wasm', 'camotics-source-stage', 'gcode', 'linuxcnc-wasm-parse-execute']) || report.pipeline.canonicalGcodeAuthority !== 'linuxcnc-wasm' || report.pipeline.camoticsSemanticParser !== false || report.pipeline.linuxcncWasmExecution !== 'handoff-required') throw new Error('CAMotics/LinuxCNC handoff order is invalid.')
|
||||
if (report.solidRemoval?.backend !== 'bitbybit-occt-wasm' || report.solidRemoval.model !== 'sampled-flat-end-brep' || report.solidRemoval.sweepMode !== 'sampled-flat-end' || report.solidRemoval.nativeSolid !== true || report.solidRemoval.freeCadNativeEquivalent !== false || report.solidRemoval.sampleCount < 2 || report.solidRemoval.stockVolume <= report.solidRemoval.remainingVolume || report.solidRemoval.removedVolume <= 0 || Math.abs(report.solidRemoval.stockVolume - report.solidRemoval.removedVolume - report.solidRemoval.remainingVolume) > 1e-7 || report.solidRemoval.structuralValid !== true || report.solidRemoval.solids < 1 || report.solidRemoval.meshVertices < 1 || report.solidRemoval.meshTriangles < 1 || report.solidRemoval.brepBytes < 1 || !/^[0-9a-f]{64}$/.test(report.solidRemoval.brepHash)) throw new Error('OCCT native solid-removal evidence is incomplete.')
|
||||
if (report.curvedSolidRemoval?.backend !== 'bitbybit-occt-wasm' || report.curvedSolidRemoval.model !== 'continuous-segment-sweep-brep' || report.curvedSolidRemoval.sweepMode !== 'continuous-segment' || report.curvedSolidRemoval.sampleCount <= 4 || report.curvedSolidRemoval.stockVolume <= report.curvedSolidRemoval.remainingVolume || report.curvedSolidRemoval.removedVolume <= 0 || report.curvedSolidRemoval.structuralValid !== true || report.curvedSolidRemoval.solids < 1 || report.curvedSolidRemoval.meshVertices < 1 || report.curvedSolidRemoval.meshTriangles < 1) throw new Error('OCCT arbitrary-curve sweep evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('OCCT native CAM ownership evidence is incomplete.')
|
||||
console.log(JSON.stringify({ status: 'chrome-cam-native-simulation-pass', browser: report.browser.product, openCamLib: report.openCamLib, camotics: report.camotics, pipeline: report.pipeline, solidRemoval: report.solidRemoval, afterRelease: report.afterRelease }, null, 2))
|
||||
15
scripts/check-chrome-cam.mjs
Normal file
15
scripts/check-chrome-cam.mjs
Normal file
@@ -0,0 +1,15 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-cam-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome CAM verification is not passing.')
|
||||
if (!report.geometry?.valid || report.job?.stockMode !== 'from-base-bound-box' || report.job.tools !== 2 || report.job.controllers !== 2 || report.job.operations !== 3) throw new Error('CAM Job/Stock/ToolBit/Controller evidence is incomplete.')
|
||||
if (JSON.stringify(report.job.operationKinds) !== JSON.stringify(['profile', 'pocket', 'drilling']) || report.job.operationStatuses?.some((status) => status !== 'generated') || report.job.pathPoints < 20 || report.job.dressups !== 1) throw new Error('CAM operation and dress-up evidence is incomplete.')
|
||||
if (report.job.setup?.safeHeightOffset !== 1 || report.job.setup.clearanceHeightOffset !== 2 || report.job.setup.coolantMode !== 'flood' || report.job.sanity !== 'pass' || report.job.sanityIssues !== 0) throw new Error('CAM SetupSheet or Sanity Check evidence is incomplete.')
|
||||
if (report.job.simulation !== 'pass' || report.job.collisions !== 0 || report.job.collisionFixture !== 1 || report.job.simulationDiagnostics !== 0) throw new Error('CAM simulation and collision evidence is incomplete.')
|
||||
if (report.extended?.commandSurface !== 60 || report.extended.operationKinds !== 22 || report.extended.dressupKinds !== 9 || report.extended.toolAssetRoundTrip !== true || report.extended.jobAssetRoundTrip !== true || report.extended.metadataRoundTrip !== true || report.extended.metadataComments !== 1 || report.extended.metadataProperties !== 3 || report.extended.metadataCompounds !== 1 || report.extended.undoRedo !== true || report.extended.startPointChanged !== true || report.extended.materialRemoval !== true || report.extended.removalTimelinePoints !== report.job.pathPoints || report.extended.fixtureCollision !== true || report.extended.multiAxis !== true) throw new Error('CAM extended command, asset lifecycle, metadata, material removal, collision, multi-axis or path-edit evidence is incomplete.')
|
||||
if (JSON.stringify(report.job.postprocessors) !== JSON.stringify(['grbl', 'linuxcnc', 'mach3-mach4', 'centroid', 'marlin', 'masso-g3', 'snapmaker']) || report.job.post !== 'linuxcnc' || Object.keys(report.job.postHashes || {}).length !== 7 || Object.values(report.job.postHashes || {}).some((hash) => !/^[0-9a-f]{64}$/.test(hash)) || report.job.unsafePostRejected !== true || report.job.deterministic !== true || report.job.gcodeBytes <= 0 || !/^[0-9a-f]{64}$/.test(report.job.gcodeHash)) throw new Error('CAM sandboxed post/G-code evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || !/^[0-9a-f]{64}$/.test(report.persistence.hash) || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('CAM OPFS evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'CAM-ALL' || report.opfs.operations !== 3 || report.opfs.markerRemoved !== true || report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('CAM lifecycle evidence is incomplete.')
|
||||
console.log(JSON.stringify({ status: 'chrome-cam-pass', browser: report.browser.product, geometry: report.geometry, job: report.job, extended: report.extended, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
16
scripts/check-chrome-camotics-wasm.mjs
Normal file
16
scripts/check-chrome-camotics-wasm.mjs
Normal file
@@ -0,0 +1,16 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const [report, artifact] = await Promise.all([
|
||||
readFile(resolve(root, 'config/chrome-camotics-wasm-verification.json'), 'utf8').then(JSON.parse),
|
||||
readFile(resolve(root, 'config/camotics-wasm-artifact.json'), 'utf8').then(JSON.parse),
|
||||
])
|
||||
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || report.crossOriginIsolated !== true) throw new Error('Chrome CAMotics WASM report is not passing.')
|
||||
if (!/^Chrome\//.test(report.browser?.product || '')) throw new Error('CAMotics WASM report was not produced by Chrome.')
|
||||
if (report.kernel?.backend !== 'camotics-upstream-sweep-wasm' || report.kernel.sourceRevision !== artifact.upstream.revision || report.kernel.artifactSha256 !== artifact.artifact.sha256 || report.kernel.abiVersion !== 1 || report.kernel.imports !== 0 || report.kernel.fullCamoticsProgram !== false || report.kernel.gcodeParserIncluded !== false) throw new Error('Chrome CAMotics WASM identity or scope is invalid.')
|
||||
if (report.sweep?.conicInsideDepth !== 1 || report.sweep.conicOutsideDepth !== -1 || report.sweep.spheroidInsideDepth !== 1 || report.sweep.longMoveBoundingBoxes !== 3 || report.sweep.deterministic !== true) throw new Error('Chrome CAMotics sweep results are invalid.')
|
||||
if (report.parserAuthority !== 'linuxcnc-wasm') throw new Error('LinuxCNC WASM must remain the canonical G-code parser authority.')
|
||||
|
||||
console.log(JSON.stringify({ status: 'chrome-camotics-wasm-pass', browser: report.browser.product, kernel: report.kernel, sweep: report.sweep, parserAuthority: report.parserAuthority }, null, 2))
|
||||
3
scripts/check-chrome-data.mjs
Normal file
3
scripts/check-chrome-data.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname); const report = JSON.parse(await readFile(resolve(root, 'config/chrome-data-verification.json'), 'utf8')); if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome data modules verification is not passing.'); if (report.adapters?.total !== 7 || report.adapters.supported !== 4 || report.adapters.proxy !== 3 || report.records?.total !== 7 || report.records.proxy !== 3 || report.records.manifestBytes <= 0) throw new Error('Specialist data adapter coverage is incomplete.'); if (report.points?.sourceCount !== 6 || JSON.stringify(report.points.parsedFormats) !== JSON.stringify(['pts', 'pcd', 'ply']) || report.points.mergedCount !== 12 || report.points.croppedCount !== 4 || report.points.polygonCroppedCount !== 4 || report.points.reducedCount !== 1 || report.points.structured?.width !== 3 || report.points.structured?.height !== 2 || Object.values(report.points.exportBytes || {}).some((value) => value <= 0)) throw new Error('Chrome point-cloud operations evidence is incomplete.'); if (report.reverseEngineering?.planeRms > 1e-7 || Math.abs(report.reverseEngineering.sphereRadius - 2) > 1e-7 || Math.abs(report.reverseEngineering.cylinderRadius - 3) > 1e-7 || Math.abs(report.reverseEngineering.cylinderHeight - 8) > 1e-7 || report.reverseEngineering.polynomialRms > 1e-7 || report.reverseEngineering.segmentCount !== 2 || JSON.stringify(report.reverseEngineering.segmentSizes) !== JSON.stringify([2, 2])) throw new Error('Chrome reverse-engineering fit evidence is incomplete.'); if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || !/^[0-9a-f]{64}$/.test(report.persistence.hash) || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('Data modules OPFS resource evidence is incomplete.'); if (report.opfs?.markerSuite !== 'DATA-ALL' || report.opfs.records !== 7 || report.opfs.markerRemoved !== true) throw new Error('Data modules OPFS marker evidence is incomplete.'); if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('Data harness leaked ShapeHandles.'); console.log(JSON.stringify({ status: 'chrome-data-pass', browser: report.browser.product, adapters: report.adapters, records: report.records, points: report.points, reverseEngineering: report.reverseEngineering, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
13
scripts/check-chrome-draft.mjs
Normal file
13
scripts/check-chrome-draft.mjs
Normal file
@@ -0,0 +1,13 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-draft-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome Draft verification is not passing.')
|
||||
if (report.workingPlane?.worldPoint.x !== 10 || report.workingPlane.worldPoint.y !== 22 || report.workingPlane.worldPoint.z !== 33 || JSON.stringify(report.workingPlane.snapped) !== JSON.stringify({ x: 1, y: 2.5 })) throw new Error('Draft working plane/grid evidence is incomplete.')
|
||||
if (report.objects?.total !== 9 || report.objects.lines !== 2 || report.objects.wires !== 1 || report.objects.circles !== 1 || report.objects.clones !== 5 || report.objects.arrays !== 4 || report.objects.layers !== 2 || report.objects.sourceDependencies !== 5) throw new Error('Draft parametric object/layer dependency evidence is incomplete.')
|
||||
if (!report.operations?.moved || !report.operations.rotated || !report.operations.scaled || report.operations.offsetLength !== 8 || report.operations.trimmedLength !== 4 || report.operations.cloneTranslation.x !== 10 || report.operations.arrayCount !== 4) throw new Error('Draft operation evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || !/^[0-9a-f]{64}$/.test(report.persistence.hash) || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('Draft OPFS resource lifecycle evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'DRAFT-OPS' || report.opfs.markerObjects !== 9 || report.opfs.markerRemoved !== true) throw new Error('Draft OPFS marker was not verified and removed.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('Draft harness leaked ShapeHandles.')
|
||||
console.log(JSON.stringify({ status: 'chrome-draft-pass', browser: report.browser.product, workingPlane: report.workingPlane, objects: report.objects, operations: report.operations, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
13
scripts/check-chrome-engineering.mjs
Normal file
13
scripts/check-chrome-engineering.mjs
Normal file
@@ -0,0 +1,13 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-engineering-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome engineering closure verification is not passing.')
|
||||
if (report.integrity?.complete !== true || report.integrity.artifacts !== 4 || report.integrity.kinds !== 4 || report.integrity.missingKinds !== 0 || report.integrity.missingRefs !== 0 || report.integrity.projectVersion !== 8 || JSON.stringify(report.integrity.artifactVersions) !== JSON.stringify([2, 2, 2, 2]) || report.integrity.reopenedComplete !== true) throw new Error('ENG-CLOSURE integrity and version evidence is incomplete.')
|
||||
if (report.workflow?.assemblyComponents !== 2 || report.workflow.bimElements !== 1 || report.workflow.meshTriangles <= 0 || report.workflow.surfacePatches !== 5 || report.workflow.edited.assemblyX !== 6 || report.workflow.edited.fireRating !== 'A1' || report.workflow.edited.meshMinX !== -2.75 || report.workflow.edited.surfaceOffset !== 0.5 || Object.values(report.workflow.exports).some((value) => typeof value !== 'number' || value <= 0)) throw new Error('ENG-CLOSURE cross-workbench edit/export evidence is incomplete.')
|
||||
if (report.scale?.components !== 1000 || report.scale.bomRows !== 20 || report.scale.durationMs > report.scale.budgetMs || report.scale.budgetMs !== 1000) throw new Error('ENG-CLOSURE large-project budget evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || !/^[0-9a-f]{64}$/.test(report.persistence.hash) || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('ENG-CLOSURE OPFS resource evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'ENG-CLOSURE' || report.opfs.artifacts !== 4 || report.opfs.markerRemoved !== true) throw new Error('ENG-CLOSURE OPFS marker evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('ENG-CLOSURE harness leaked ShapeHandles.')
|
||||
console.log(JSON.stringify({ status: 'chrome-engineering-pass', browser: report.browser.product, integrity: report.integrity, workflow: report.workflow, scale: report.scale, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
11
scripts/check-chrome-fault-injection.mjs
Normal file
11
scripts/check-chrome-fault-injection.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-fault-injection-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.executionContext !== 'dedicated-worker' || report.status !== 'pass' || report.crossOriginIsolated !== true || report.sharedArrayBuffer !== true) throw new Error('Chrome QA-06 Worker/WASM isolation evidence is invalid.')
|
||||
const lifecycle = report.lifecycle
|
||||
if (!lifecycle || lifecycle.initialAvailable !== true || lifecycle.completed !== 'completed' || lifecycle.cancelled !== 'cancelled' || lifecycle.crashInjected !== true || lifecycle.unavailableAfterCrash !== true || lifecycle.workerFactoryCount < 3 || lifecycle.recovered !== 'completed' || lifecycle.stale !== 'stale' || Math.abs(lifecycle.recoveredEnd?.x - 5) > 1e-7 || Math.abs(lifecycle.recoveredEnd?.y) > 1e-7) throw new Error('Chrome QA-06 cancellation, crash recovery, or stale-result evidence is invalid.')
|
||||
if (lifecycle.nativeInitialAvailable !== true || lifecycle.nativeCrashInjected !== true || lifecycle.nativeUnavailableAfterCrash !== true || lifecycle.nativeWorkerFactoryCount < 2 || lifecycle.nativeRecoveredAvailable !== true) throw new Error('Chrome QA-06 native history Worker crash recovery evidence is invalid.')
|
||||
if (!report.opfs || report.opfs.markerBytes !== 0 || report.opfs.markerRemoved !== true || !Number.isSafeInteger(report.activeWorkersBeforeDispose) || report.activeWorkersBeforeDispose < 3 || report.activeWorkersAfterDispose !== report.activeWorkersBeforeDispose || !Number.isSafeInteger(report.activeNativeWorkersBeforeDispose) || report.activeNativeWorkersBeforeDispose < 2 || report.activeNativeWorkersAfterDispose !== report.activeNativeWorkersBeforeDispose) throw new Error('Chrome QA-06 OPFS or Worker resource cleanup evidence is invalid.')
|
||||
console.log(JSON.stringify({ status: 'chrome-fault-injection-pass', browserId: report.browserId, lifecycle, opfs: report.opfs, nativeWorkers: { beforeDispose: report.activeNativeWorkersBeforeDispose, afterDispose: report.activeNativeWorkersAfterDispose } }, null, 2))
|
||||
89
scripts/check-chrome-fcstd-golden-verification.mjs
Normal file
89
scripts/check-chrome-fcstd-golden-verification.mjs
Normal file
@@ -0,0 +1,89 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import { loadGoldenManifest } from './freecad-golden-contract.mjs'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-fcstd-golden-verification.json'), 'utf8'))
|
||||
const manifest = await loadGoldenManifest(resolve(root, 'fixtures/freecad-golden/manifest.json'))
|
||||
const fail = (message) => { throw new Error(`Chrome FCStd golden verification: ${message}`) }
|
||||
|
||||
if (report.schemaVersion !== 1 || report.baselineId !== 'freecad-1.1.1' || report.freecadCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('FreeCAD baseline is not locked.')
|
||||
if (report.bitbybitVersion !== '1.1.1' || report.geometryProvider !== 'Bitbybit OCCT') fail('Bitbybit 1.1.1 is not the sole geometry provider.')
|
||||
if (report.browserId !== 'chrome' || !/^Chrome\//.test(report.browser?.product || '')) fail('the report must contain Chrome-only browser evidence.')
|
||||
if (report.unknownDifferencesFail !== true || report.status !== 'pass') fail('unknown differences are not release-blocking.')
|
||||
if (report.scenarioCount !== 100 || !Array.isArray(report.reports) || report.reports.length !== 100 || manifest.scenarios.length !== 100) fail('exactly 100 scenarios are required.')
|
||||
if (!Array.isArray(report.pageErrors) || report.pageErrors.length !== 0) fail('Chrome page errors are present.')
|
||||
if (report.productRecompute?.status !== 'pass') fail('the real product recompute probe did not pass.')
|
||||
for (const [name, result] of Object.entries({ placedBox: report.productRecompute.placedBox, cut: report.productRecompute.cut, sphereTrim: report.productRecompute.sphereTrim, torus: report.productRecompute.torus, prism: report.productRecompute.prism, wedge: report.productRecompute.wedge, ellipsoid: report.productRecompute.ellipsoid })) {
|
||||
if (!result?.quality?.structuralValid || result.quality.isNull || result.quality.structuralErrors !== 0 || result.quality.solids !== 1 || !(result.massProperties?.volume > 0)) fail(`product recompute ${name} Shape is invalid.`)
|
||||
if (result.comparison?.status !== 'pass' || result.comparison.differences?.length !== 0) fail(`product recompute ${name} differs from FreeCAD.`)
|
||||
}
|
||||
if (report.productRecompute.placedBox.quality.boundingBox.min.some((value, index) => Math.abs(value - [3.9999999, 3.9999999, -1e-7][index]) > 1e-12) || report.productRecompute.placedBox.quality.boundingBox.max.some((value, index) => Math.abs(value - [6.0000001, 14.0000001, 5.0000001][index]) > 1e-12)) fail('the product Part::Box coordinate/translation mapping drifted.')
|
||||
if (report.productRecompute.cut.comparison.knownDifferences?.length !== 1 || report.productRecompute.cut.comparison.knownDifferences[0].classification !== 'kernel-container-normalization') fail('the product Part::Cut container normalization is not explicitly classified.')
|
||||
if (report.productRecompute.sphereTrim.comparison.knownDifferences?.length !== 1 || report.productRecompute.sphereTrim.comparison.knownDifferences[0].classification !== 'kernel-container-normalization' || report.productRecompute.sphereTrim.comparison.knownDifferences[0].expected !== 'solid' || report.productRecompute.sphereTrim.comparison.knownDifferences[0].actual !== 'compound') fail('the product Part::Sphere trim container normalization is not explicitly classified.')
|
||||
const chromeTorusBound = 12.988706503508727
|
||||
if (report.productRecompute.torus.comparison.knownDifferences?.length !== 0 || Math.abs(report.productRecompute.torus.massProperties.volume - 60 * Math.PI ** 2) > 1e-6 || report.productRecompute.torus.quality.boundingBox.min.some((value, index) => Math.abs(value - [-chromeTorusBound, -chromeTorusBound, -2.0000001][index]) > 1e-9) || report.productRecompute.torus.quality.boundingBox.max.some((value, index) => Math.abs(value - [chromeTorusBound, chromeTorusBound, 2.0000001][index]) > 1e-9)) fail('the product Part::Torus result differs from FreeCAD.')
|
||||
const prism = report.productRecompute.prism
|
||||
if (prism.comparison.knownDifferences?.length !== 0 || prism.quality.faces !== 8 || prism.quality.edges !== 18 || prism.quality.vertices !== 12 || Math.abs(prism.massProperties.volume - 60 * Math.sqrt(3)) > 1e-6 || prism.quality.boundingBox.min.some((value, index) => Math.abs(value - [-2.0000001, -2.606937542828117, -1e-7][index]) > 1e-9) || prism.quality.boundingBox.max.some((value, index) => Math.abs(value - [3.76326990708465, 1.7320509075688775, 10.0000001][index]) > 1e-9)) fail('the product Part::Prism result differs from FreeCAD.')
|
||||
const wedge = report.productRecompute.wedge
|
||||
if (wedge.comparison.knownDifferences?.length !== 0 || wedge.quality.faces !== 6 || wedge.quality.edges !== 12 || wedge.quality.vertices !== 8 || Math.abs(wedge.massProperties.volume - 2440 / 3) > 1e-6 || wedge.quality.boundingBox.min.some((value, index) => Math.abs(value - [-1e-7, -1e-7, -1e-7][index]) > 1e-9) || wedge.quality.boundingBox.max.some((value, index) => Math.abs(value - [10.0000001, 10.0000001, 10.0000001][index]) > 1e-9)) fail('the product Part::Wedge result differs from FreeCAD.')
|
||||
const ellipsoid = report.productRecompute.ellipsoid
|
||||
if (ellipsoid.comparison.knownDifferences?.length !== 0 || ellipsoid.quality.faces !== 1 || ellipsoid.quality.edges !== 3 || ellipsoid.quality.vertices !== 2 || Math.abs(ellipsoid.massProperties.volume - 133.9826640573845) > 1e-6 || ellipsoid.quality.boundingBox.min.some((value, index) => Math.abs(value - [-8.0000001, -6.92820333027551, -2.0000001][index]) > 1e-9) || ellipsoid.quality.boundingBox.max.some((value, index) => Math.abs(value - [4.0000001, 6.928203330275507, 2.0000001][index]) > 1e-9)) fail('the product Part::Ellipsoid result differs from FreeCAD.')
|
||||
if (report.productRecompute.afterRelease?.shapeCount !== 0 || report.productRecompute.afterRelease?.kernelReferenceCount !== 0) fail('the product recompute probe leaked Shape ownership.')
|
||||
|
||||
const expectedIds = new Set(manifest.scenarios.map((entry) => entry.id))
|
||||
const seen = new Set()
|
||||
let knownDifferences = 0
|
||||
let totalArchiveBytes = 0
|
||||
let totalBrepBytes = 0
|
||||
const formatSummary = { step: { passed: 0, totalBytes: 0, maximumVolumeDelta: 0, topologyNormalizations: 0, importedShapeTypes: {} }, iges: { passed: 0, totalBytes: 0, maximumVolumeDelta: 0, topologyNormalizations: 0, importedShapeTypes: {} }, brep: { passed: 0, totalBytes: 0, maximumVolumeDelta: 0, topologyNormalizations: 0, importedShapeTypes: {} } }
|
||||
for (const item of report.reports) {
|
||||
if (!expectedIds.has(item.id) || seen.has(item.id)) fail(`unexpected or duplicate scenario '${item.id}'.`)
|
||||
seen.add(item.id)
|
||||
if (item.status !== 'pass') fail(`${item.id} did not pass.`)
|
||||
if (!item.source?.quality?.structuralValid || item.source.quality.isNull || item.source.quality.structuralErrors !== 0 || item.source.quality.solids !== 1) fail(`${item.id} source Shape is invalid.`)
|
||||
if (!item.imported?.quality?.structuralValid || item.imported.quality.isNull || item.imported.quality.structuralErrors !== 0 || item.imported.quality.solids !== 1) fail(`${item.id} imported Shape is invalid.`)
|
||||
for (const format of ['step', 'iges', 'brep']) {
|
||||
const exchange = item.formatRoundTrips?.[format]
|
||||
const expectedUnit = format === 'brep' ? 'unknown' : 'millimeter'
|
||||
if (!exchange || exchange.status !== 'pass' || exchange.declaredUnit !== expectedUnit || !Number.isSafeInteger(exchange.sourceBytes) || exchange.sourceBytes <= 0 || !Number.isSafeInteger(exchange.importedBytes) || exchange.importedBytes <= 0 || !['solid', 'compound', 'shell', 'face'].includes(exchange.importedShapeType) || exchange.importedSolids > exchange.sourceSolids || !['none', 'format-topology-normalization'].includes(exchange.structuralNormalization) || Math.abs(exchange.importedVolume - exchange.sourceVolume) > 1e-6) fail(`${item.id}.${format} format round-trip evidence is invalid.`)
|
||||
const aggregate = formatSummary[format]
|
||||
aggregate.passed += 1
|
||||
aggregate.totalBytes += exchange.sourceBytes
|
||||
aggregate.maximumVolumeDelta = Math.max(aggregate.maximumVolumeDelta, Math.abs(exchange.importedVolume - exchange.sourceVolume))
|
||||
if (exchange.structuralNormalization === 'format-topology-normalization') aggregate.topologyNormalizations += 1
|
||||
aggregate.importedShapeTypes[exchange.importedShapeType] = (aggregate.importedShapeTypes[exchange.importedShapeType] || 0) + 1
|
||||
}
|
||||
if (!Number.isFinite(item.source.massProperties?.volume) || item.source.massProperties.volume <= 0 || !Number.isFinite(item.imported.massProperties?.volume) || item.imported.massProperties.volume <= 0) fail(`${item.id} has invalid mass properties.`)
|
||||
if (!Number.isSafeInteger(item.fcstd?.archiveBytes) || item.fcstd.archiveBytes <= 0 || !Number.isSafeInteger(item.fcstd.brepBytes) || item.fcstd.brepBytes <= 0 || !/^[a-f0-9]{8}$/.test(item.fcstd.brepHash) || item.fcstd.shapeResources !== 1 || item.fcstd.references !== 1) fail(`${item.id} has invalid FCStd/BRep evidence.`)
|
||||
totalArchiveBytes += item.fcstd.archiveBytes
|
||||
totalBrepBytes += item.fcstd.brepBytes
|
||||
for (const [name, comparison] of Object.entries(item.comparisons || {})) {
|
||||
if (comparison.status !== 'pass' || !Array.isArray(comparison.differences) || comparison.differences.length !== 0 || !Array.isArray(comparison.knownDifferences)) fail(`${item.id}.${name} contains an unknown difference.`)
|
||||
for (const difference of comparison.knownDifferences) {
|
||||
const booleanOperation = ['cut', 'fuse', 'common'].includes(item.operation)
|
||||
if (!booleanOperation || difference.classification !== 'kernel-container-normalization' || difference.domain !== 'identity' || difference.path !== 'shapeType' || difference.expected !== 'compound' || difference.actual !== 'solid') fail(`${item.id}.${name} contains an unclassified known difference.`)
|
||||
knownDifferences += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if (seen.size !== expectedIds.size) fail('one or more locked scenarios are missing.')
|
||||
|
||||
const expectedOperationCounts = { box: 21, common: 8, cone: 16, cut: 10, cylinder: 21, fuse: 8, sphere: 16 }
|
||||
if (report.summary?.passed !== 100 || report.summary.failed !== 0 || report.summary.unknownDifferences !== 0 || report.summary.knownDifferences !== knownDifferences || JSON.stringify(report.summary.operationCounts) !== JSON.stringify(expectedOperationCounts)) fail('summary counts do not match the locked corpus.')
|
||||
if (report.summary.totalArchiveBytes !== totalArchiveBytes || report.summary.totalBrepBytes !== totalBrepBytes || totalArchiveBytes <= 0 || totalBrepBytes <= 0) fail('aggregate FCStd resource sizes are inconsistent.')
|
||||
const canonicalFormatSummary = (summary) => Object.fromEntries(Object.entries(summary).map(([format, value]) => [format, { ...value, importedShapeTypes: Object.fromEntries(Object.entries(value.importedShapeTypes).sort(([left], [right]) => left.localeCompare(right))) }]))
|
||||
if (JSON.stringify(canonicalFormatSummary(report.summary.formatRoundTrips)) !== JSON.stringify(canonicalFormatSummary(formatSummary))) fail('aggregate STEP/IGES/BREP tolerance evidence is inconsistent.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) fail('ShapeHandle or kernel references leaked.')
|
||||
|
||||
console.log(JSON.stringify({
|
||||
status: 'chrome-fcstd-golden-verification-pass',
|
||||
scenarioCount: report.scenarioCount,
|
||||
operationCounts: report.summary.operationCounts,
|
||||
knownDifferences,
|
||||
unknownDifferences: 0,
|
||||
totalArchiveBytes,
|
||||
totalBrepBytes,
|
||||
productRecompute: { status: report.productRecompute.status, afterRelease: report.productRecompute.afterRelease },
|
||||
afterRelease: report.afterRelease,
|
||||
}, null, 2))
|
||||
9
scripts/check-chrome-fcstd-roundtrip.mjs
Normal file
9
scripts/check-chrome-fcstd-roundtrip.mjs
Normal file
@@ -0,0 +1,9 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-fcstd-roundtrip-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.baseline?.freecadVersion !== '1.1.1' || report.baseline?.bitbybitVersion !== '1.1.1') throw new Error('Chrome FCStd round-trip verification is not passing.')
|
||||
if (JSON.stringify(report.directions) !== '["freecad-web-freecad","web-freecad-web"]' || !Array.isArray(report.scenarios) || report.scenarios.length !== 2 || report.scenarios.some((scenario) => scenario.status !== 'pass' || scenario.differences.length !== 0)) throw new Error('FC-10 direction or difference evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.bytes <= 0 || report.persistence.roundTrip !== true || report.persistence.released !== true || report.opfs?.markerSuite !== 'FC-10' || report.opfs.markerRemoved !== true || report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('FC-10 OPFS or resource release evidence is incomplete.')
|
||||
console.log(JSON.stringify({ status: 'chrome-fcstd-roundtrip-pass', browser: report.browser.product, directions: report.directions, scenarios: report.scenarios, persistence: report.persistence, opfs: report.opfs }, null, 2))
|
||||
9
scripts/check-chrome-fcstd-semantic.mjs
Normal file
9
scripts/check-chrome-fcstd-semantic.mjs
Normal file
@@ -0,0 +1,9 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-fcstd-semantic-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '')) throw new Error('Chrome FCStd semantic verification is not passing.')
|
||||
if (JSON.stringify(report.objectNames) !== JSON.stringify(['Source', 'Spreadsheet', 'Cut', 'Sketch']) || report.expression !== 'Spreadsheet.Width * 2' || report.link !== 'Source' || report.linkSub?.objectId !== 'Source' || report.linkSub.subElement !== 'Face1' || JSON.stringify(report.linkSubList) !== JSON.stringify([{ objectId: 'Source', subElement: 'Face1' }, { objectId: 'Source', subElement: 'Face2' }]) || JSON.stringify(report.sketchGeometryIds) !== JSON.stringify(['profile']) || !report.dependencyRelations.includes('expression') || !report.dependencyRelations.includes('link') || !report.dependencyRelations.includes('topo-ref') || !report.dependencyRelations.includes('topo-ref-list') || report.archiveBytes <= 0) throw new Error('Chrome FCStd Expression/Link/LinkSub/Sketcher round-trip evidence is incomplete.')
|
||||
if (JSON.stringify(report.guiViews) !== JSON.stringify(['Front', 'Top']) || report.shapeResource?.path !== 'Part/Source.Shape.brp' || report.shapeResource.byteLength <= 0 || report.shapeResource.elementMap !== '1' || report.shapeResource.elementMapEntries !== 1) throw new Error('Chrome FCStd GuiDocument/BRep/ElementMap evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'FC-06' || report.opfs.archiveBytes !== report.archiveBytes || report.opfs.markerRemoved !== true) throw new Error('Chrome FCStd semantic OPFS marker was not verified and removed.')
|
||||
console.log(JSON.stringify({ status: 'chrome-fcstd-semantic-pass', browser: report.browser.product, objects: report.objectNames.length, dependencies: report.dependencyRelations, guiViews: report.guiViews, shapeResource: report.shapeResource, archiveBytes: report.archiveBytes, opfs: report.opfs }, null, 2))
|
||||
3
scripts/check-chrome-fem.mjs
Normal file
3
scripts/check-chrome-fem.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname); const report = JSON.parse(await readFile(resolve(root, 'config/chrome-fem-verification.json'), 'utf8')); if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome FEM verification is not passing.'); if (!report.geometry?.valid || report.solve?.status !== 'solved' || report.solve.strategy !== 'local-reference' || report.solve.nodes !== 2 || report.solve.nodeSets !== 1 || JSON.stringify(report.solve.resultFields) !== JSON.stringify(['displacement', 'stress']) || report.solve.maxDisplacement !== 0.00125 || report.solve.maxStress !== 25 || report.solve.csvBytes <= 0) throw new Error('FEM reference solve/set/result-field evidence is incomplete.'); if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || !/^[0-9a-f]{64}$/.test(report.persistence.hash) || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('FEM OPFS resource evidence is incomplete.'); if (report.opfs?.markerSuite !== 'FEM-ALL' || report.opfs.nodes !== 2 || report.opfs.markerRemoved !== true) throw new Error('FEM OPFS marker evidence is incomplete.'); if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('FEM harness leaked ShapeHandles.'); console.log(JSON.stringify({ status: 'chrome-fem-pass', browser: report.browser.product, geometry: report.geometry, solve: report.solve, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
17
scripts/check-chrome-geometry-features.mjs
Normal file
17
scripts/check-chrome-geometry-features.mjs
Normal file
@@ -0,0 +1,17 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-geometry-features-verification.json'), 'utf8'))
|
||||
if (report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '')) throw new Error('Chrome geometry feature verification is not a Chrome pass.')
|
||||
const expected = ['fcstd-brep-import', 'extrude', 'revolution', 'fillet-selected-edge', 'chamfer-selected-edge', 'draft-selected-face', 'thickness', 'hole-counterbore', 'hole-countersink', 'hole-counterdrill', 'hole-angled-drill-point', 'hole-angled-included-depth', 'hole-tapered', 'loft', 'loft-ruled', 'loft-additive', 'loft-subtractive', 'pipe', 'extrude-two-sided', 'revolution-symmetric', 'revolution-two-angles', 'pad-two-sided', 'pocket-two-sided', 'pad-tapered', 'pocket-tapered', 'pocket-through-all', 'pocket-up-to-face', 'pad-midplane', 'linear-pattern-spacing', 'polar-pattern-spacing']
|
||||
if (JSON.stringify(report.operations?.map((operation) => operation.name)) !== JSON.stringify(expected)) throw new Error('Chrome geometry feature operation order is incomplete.')
|
||||
if (!report.operations.every((operation) => operation.meshVertices >= 3 && operation.meshTriangles >= 1)) throw new Error('Chrome geometry feature mesh evidence is incomplete.')
|
||||
if (!['pad-tapered', 'pocket-tapered', 'pocket-through-all', 'pocket-up-to-face', 'pad-midplane'].every((name) => Number.isFinite(report.operations.find((operation) => operation.name === name)?.volume) && report.operations.find((operation) => operation.name === name).volume > 0)) throw new Error('Chrome PartDesign base mass evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome geometry feature handles were not fully released.')
|
||||
if (!Number.isFinite(report.performance?.durationMs) || report.performance.durationMs <= 0 || report.performance.operationCount !== report.operations.length || !Number.isFinite(report.performance.averageOperationMs) || report.performance.peakShapeCount < report.performance.operationCount || report.performance.stressObjectCount !== 1000 || !Number.isFinite(report.performance.stressDurationMs) || report.performance.stressDurationMs <= 0 || report.performance.stressPeakShapeCount < report.performance.stressObjectCount) throw new Error('Chrome geometry performance evidence is incomplete.')
|
||||
const triangleStress = report.performance.triangleStress
|
||||
if (!triangleStress || triangleStress.radius !== 100 || triangleStress.precision !== 0.01 || !Number.isFinite(triangleStress.triangles) || triangleStress.triangles < 100_000 || !Number.isFinite(triangleStress.vertices) || triangleStress.vertices < 3 || !Number.isFinite(triangleStress.durationMs) || triangleStress.durationMs <= 0) throw new Error('Chrome high-tessellation performance evidence is incomplete.')
|
||||
if (!report.fcstdBrep || report.fcstdBrep.references !== 1 || report.fcstdBrep.topology?.faces !== 6 || report.fcstdBrep.topology?.edges !== 12 || report.fcstdBrep.topology?.vertices !== 8 || Math.abs(report.fcstdBrep.massProperties?.source?.volume - 24) > 1e-7 || Math.abs(report.fcstdBrep.massProperties?.source?.volume - report.fcstdBrep.massProperties?.imported?.volume) > 1e-7 || Math.abs(report.fcstdBrep.massProperties?.source?.surfaceArea - report.fcstdBrep.massProperties?.imported?.surfaceArea) > 1e-7 || !report.fcstdBrep.emptyRejected || !report.fcstdBrep.cancelledBeforeImport || !report.fcstdBrep.cancelledResultReleased) throw new Error('Chrome FCStd BRep instantiation evidence is incomplete.')
|
||||
if (!report.exchangeFormats?.iges || report.exchangeFormats.iges.sourceBytes <= 0 || Math.abs(report.exchangeFormats.iges.sourceVolume - report.exchangeFormats.iges.importedVolume) > 1e-7 || report.exchangeFormats.iges.importedFaces !== 6 || report.exchangeFormats.iges.importedEdges < 1 || report.exchangeFormats.iges.importedVertices < 1) throw new Error('Chrome IGES exchange evidence is incomplete.')
|
||||
console.log(JSON.stringify({ status: 'chrome-geometry-features-pass', browser: report.browser.product, operations: report.operations.length, afterRelease: report.afterRelease }))
|
||||
9
scripts/check-chrome-inspection.mjs
Normal file
9
scripts/check-chrome-inspection.mjs
Normal file
@@ -0,0 +1,9 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname); const report = JSON.parse(await readFile(resolve(root, 'config/chrome-inspection-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome Inspection verification is not passing.')
|
||||
if (!report.shape?.valid || report.shape.solids !== 1 || report.measurements?.distance !== 5 || report.measurements.angle !== 90 || report.measurements.volume !== report.shape.volume || report.measurements.area !== report.shape.area || Math.abs(report.measurements.deviation - 0.01) > 1e-12 || report.measurements.deviationStatus !== 'resolved' || Math.abs(report.measurements.sectionArea - 24) > 1e-5 || report.measurements.topoRefs !== 2 || report.measurements.stableRefs !== 1 || JSON.stringify(report.measurements.unresolvedRefs) !== JSON.stringify(['Face2']) || report.measurements.csvRows !== 6) throw new Error('Inspection measurement/section/deviation/TopoRef evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || !/^[0-9a-f]{64}$/.test(report.persistence.hash) || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('Inspection OPFS resource evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'INSP-ALL' || report.opfs.measurements !== 5 || report.opfs.markerRemoved !== true) throw new Error('Inspection OPFS marker evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('Inspection harness leaked ShapeHandles.')
|
||||
console.log(JSON.stringify({ status: 'chrome-inspection-pass', browser: report.browser.product, shape: report.shape, measurements: report.measurements, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
11
scripts/check-chrome-mesh.mjs
Normal file
11
scripts/check-chrome-mesh.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname); const report = JSON.parse(await readFile(resolve(root, 'config/chrome-mesh-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome Mesh verification is not passing.')
|
||||
if (report.geometry?.vertices <= 0 || report.geometry.triangles <= 0 || report.geometry.boundaryEdges !== 0 || report.geometry.nonManifoldEdges !== 0 || report.geometry.degenerateTriangles !== 0 || report.geometry.selfIntersections !== 0) throw new Error('Mesh quality evidence is incomplete.')
|
||||
if (report.operations?.welded <= 0 || JSON.stringify(report.operations.transformedMin) !== JSON.stringify([-6.5, -1, -3]) || report.operations.objBytes <= 0 || report.operations.plyBytes <= 0 || report.operations.stlBytes <= 0 || report.operations.lodTriangles !== report.operations.workerLodTriangles || !/^[0-9a-f]{64}$/.test(report.operations.objHash)) throw new Error('Mesh normalize/transform/LOD/export evidence is incomplete.')
|
||||
if (report.repairs?.selfIntersectionFixture !== 1 || report.repairs.nonManifoldFixture !== 1 || report.repairs.holeFilledTriangles !== 1) throw new Error('Mesh self-intersection/non-manifold/hole repair evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || !/^[0-9a-f]{64}$/.test(report.persistence.hash) || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('Mesh OPFS resource evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'MESH-CORE' || report.opfs.triangles !== report.geometry.triangles || report.opfs.markerRemoved !== true) throw new Error('Mesh OPFS marker evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('Mesh harness leaked ShapeHandles.')
|
||||
console.log(JSON.stringify({ status: 'chrome-mesh-pass', browser: report.browser.product, geometry: report.geometry, operations: report.operations, repairs: report.repairs, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
11
scripts/check-chrome-native-chamfer-history.mjs
Normal file
11
scripts/check-chrome-native-chamfer-history.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-native-chamfer-history-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.executionContext !== 'geometry-and-history-workers' || report.crossOriginIsolated !== true || report.status !== 'pass') throw new Error('Chrome native Chamfer history evidence is invalid.')
|
||||
if (report.capabilities?.status !== 'ready' || report.nativeCapabilities?.availability !== 'available' || report.nativeCapabilities?.operations?.includes('chamfer') !== true) throw new Error('Native OCCT Chamfer Worker capability evidence is incomplete.')
|
||||
if (!Number.isSafeInteger(report.history?.recordCount) || report.history.recordCount <= 0 || report.history.relations?.modified <= 0 || report.history.relations?.generated <= 0 || report.history.relations?.deleted <= 0 || JSON.stringify(report.history.sourceObjects) !== JSON.stringify(['Base'])) throw new Error('Native OCCT Chamfer relation evidence is incomplete.')
|
||||
if (report.result?.structuralValid !== true || report.result?.solids !== 1 || !(report.result.volume > 0 && report.result.volume < 216)) throw new Error('Native OCCT Chamfer result evidence is invalid.')
|
||||
if (report.opfs?.markerCreated !== true || report.opfs?.markerSuite !== 'chamfer' || report.opfs?.markerRemoved !== true) throw new Error('Chrome native Chamfer OPFS evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native Chamfer history handles were not released.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-chamfer-history-pass', browserId: report.browserId, occtVersion: report.nativeCapabilities.occtVersion, records: report.history.recordCount, relations: report.history.relations, volume: report.result.volume, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
11
scripts/check-chrome-native-draft-history.mjs
Normal file
11
scripts/check-chrome-native-draft-history.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-native-draft-history-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.executionContext !== 'geometry-and-history-workers' || report.crossOriginIsolated !== true || report.status !== 'pass') throw new Error('Chrome native Draft history evidence is invalid.')
|
||||
if (report.capabilities?.status !== 'ready' || report.nativeCapabilities?.availability !== 'available' || report.nativeCapabilities?.operations?.includes('draft') !== true) throw new Error('Native OCCT Draft Worker capability evidence is incomplete.')
|
||||
if (!Number.isSafeInteger(report.history?.recordCount) || report.history.recordCount <= 0 || report.history.relations?.modified <= 0 || JSON.stringify(report.history.sourceObjects) !== JSON.stringify(['Base'])) throw new Error('Native OCCT Draft relation evidence is incomplete.')
|
||||
if (report.result?.structuralValid !== true || report.result?.solids !== 1 || !(report.result.volume > 0)) throw new Error('Native OCCT Draft result evidence is invalid.')
|
||||
if (report.opfs?.markerCreated !== true || report.opfs?.markerSuite !== 'draft' || report.opfs?.markerRemoved !== true) throw new Error('Chrome native Draft OPFS evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native Draft history handles were not released.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-draft-history-pass', browserId: report.browserId, occtVersion: report.nativeCapabilities.occtVersion, records: report.history.recordCount, relations: report.history.relations, volume: report.result.volume, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
12
scripts/check-chrome-native-fillet-history.mjs
Normal file
12
scripts/check-chrome-native-fillet-history.mjs
Normal file
@@ -0,0 +1,12 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-native-fillet-history-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.executionContext !== 'geometry-and-history-workers' || report.crossOriginIsolated !== true || report.status !== 'pass') throw new Error('Chrome native Fillet history evidence is invalid.')
|
||||
if (report.capabilities?.status !== 'ready' || report.nativeCapabilities?.availability !== 'available' || report.nativeCapabilities?.operations?.includes('fillet') !== true) throw new Error('Native OCCT Fillet Worker capability evidence is incomplete.')
|
||||
if (!Number.isSafeInteger(report.history?.recordCount) || report.history.recordCount <= 0 || report.history.relations?.modified <= 0 || report.history.relations?.generated <= 0 || report.history.relations?.deleted <= 0 || JSON.stringify(report.history.sourceObjects) !== JSON.stringify(['Base'])) throw new Error('Native OCCT Fillet relation evidence is incomplete.')
|
||||
if (report.result?.structuralValid !== true || report.result?.solids !== 1 || !(report.result.volume > 0 && report.result.volume < 216)) throw new Error('Native OCCT Fillet result evidence is invalid.')
|
||||
if (report.opfs?.markerCreated !== true || report.opfs?.markerSuite !== 'fillet' || report.opfs?.markerRemoved !== true) throw new Error('Chrome native Fillet OPFS evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native Fillet history handles were not released.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-fillet-history-pass', browserId: report.browserId, occtVersion: report.nativeCapabilities.occtVersion, records: report.history.recordCount, relations: report.history.relations, volume: report.result.volume, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
16
scripts/check-chrome-native-groove-history.mjs
Normal file
16
scripts/check-chrome-native-groove-history.mjs
Normal file
@@ -0,0 +1,16 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-native-groove-history-verification.json'), 'utf8'))
|
||||
if (report.status !== 'pass' || report.browserId !== 'chrome' || report.crossOriginIsolated !== true) throw new Error('Chrome native Groove history verification is not passing.')
|
||||
if (report.capabilities?.status !== 'ready' || report.nativeCapabilities?.availability !== 'available' || report.nativeCapabilities?.transport !== 'step-text' || report.nativeCapabilities?.operations?.includes('groove') !== true) throw new Error('Native OCCT Groove Worker capability evidence is incomplete.')
|
||||
const stages = report.history?.stages || []
|
||||
if (!Number.isSafeInteger(report.history?.recordCount) || report.history.recordCount <= 0 || stages.length !== 2 || stages[0]?.operation !== 'revolution' || JSON.stringify(stages[0]?.inputObjectIds) !== JSON.stringify(['Profile']) || stages[0]?.recordCount <= 0 || stages[0]?.topologyEntries <= 0 || stages[1]?.operation !== 'cut' || JSON.stringify(stages[1]?.inputObjectIds) !== JSON.stringify(['Base', stages[0]?.resultObjectId]) || stages[1]?.recordCount !== report.history.recordCount || stages[1]?.topologyEntries <= 0 || !report.history.sourceObjects?.includes('Base') || !report.history.sourceObjects?.includes('Profile') || !report.history.sourceObjects?.includes(stages[0]?.resultObjectId)) throw new Error('Native OCCT Groove staged source relation evidence is incomplete.')
|
||||
if (report.history.relations?.modified <= 0 || report.history.relations.generated + report.history.relations.deleted <= 0) throw new Error('Native OCCT Groove history relation evidence is incomplete.')
|
||||
const twoAngleStages = report.twoAngleHistory?.stages || []
|
||||
if (report.nativeCapabilities?.operations?.includes('rotate') !== true || report.twoAngleHistory?.recordCount <= 0 || report.twoAngleHistory?.structuralValid !== true || report.twoAngleHistory?.unexplainedStructuralErrors !== 0 || report.twoAngleHistory?.solids !== 1 || Math.abs(report.twoAngleHistory.volume - (800 - 3 * Math.PI)) > 1e-5 || twoAngleStages.length !== 3 || twoAngleStages[0]?.operation !== 'rotate' || JSON.stringify(twoAngleStages[0]?.inputObjectIds) !== JSON.stringify(['Profile']) || twoAngleStages[0]?.topologyEntries <= 0 || twoAngleStages[1]?.operation !== 'revolution' || JSON.stringify(twoAngleStages[1]?.inputObjectIds) !== JSON.stringify([twoAngleStages[0]?.resultObjectId]) || twoAngleStages[1]?.topologyEntries <= 0 || twoAngleStages[2]?.operation !== 'cut' || JSON.stringify(twoAngleStages[2]?.inputObjectIds) !== JSON.stringify(['Base', twoAngleStages[1]?.resultObjectId]) || twoAngleStages[2]?.resultObjectId !== 'GrooveTwoAngles' || twoAngleStages[2]?.topologyEntries <= 0 || !report.twoAngleHistory.sourceObjects?.includes('Base') || !report.twoAngleHistory.sourceObjects?.includes('Profile') || !report.twoAngleHistory.sourceObjects?.includes(twoAngleStages[0]?.resultObjectId) || !report.twoAngleHistory.sourceObjects?.includes(twoAngleStages[1]?.resultObjectId)) throw new Error('Native OCCT Groove TwoAngles offset-sweep evidence is incomplete.')
|
||||
if (report.result?.structuralValid !== true || report.result?.solids !== 1 || Math.abs(report.result.volume - (800 - 6 * Math.PI)) > 1e-5) throw new Error('Native OCCT Groove result evidence is invalid.')
|
||||
if (report.opfs?.markerOperation !== 'groove' || report.opfs?.markerRemoved !== true) throw new Error('Chrome native Groove OPFS marker was not verified and removed.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native Groove history handles were not released.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-groove-history-pass', browser: report.browserId, occtVersion: report.nativeCapabilities.occtVersion, records: report.history.recordCount, relations: report.history.relations, stages, twoAngleStages, volume: report.result.volume, twoAngleVolume: report.twoAngleHistory.volume, afterRelease: report.afterRelease, opfs: report.opfs }, null, 2))
|
||||
10
scripts/check-chrome-native-history.mjs
Normal file
10
scripts/check-chrome-native-history.mjs
Normal file
@@ -0,0 +1,10 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-native-history-verification.json'), 'utf8'))
|
||||
if (report.status !== 'pass' || report.browserId !== 'chrome' || report.crossOriginIsolated !== true) throw new Error('Chrome native history verification is not passing.')
|
||||
if (report.capabilities?.status !== 'ready' || report.nativeCapabilities?.availability !== 'available' || report.nativeCapabilities?.transport !== 'step-text' || report.nativeCapabilities?.operations?.includes('cut') !== true) throw new Error('Native OCCT history Worker capability evidence is incomplete.')
|
||||
if (!Number.isSafeInteger(report.history?.recordCount) || report.history.recordCount <= 0 || !report.history.relations?.modified && !report.history.relations?.generated && !report.history.relations?.deleted) throw new Error('Native OCCT history relation evidence is empty.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native history handles were not released.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-history-pass', browser: report.browserId, occtVersion: report.nativeCapabilities.occtVersion, records: report.history.recordCount, afterRelease: report.afterRelease }, null, 2))
|
||||
11
scripts/check-chrome-native-hole-history.mjs
Normal file
11
scripts/check-chrome-native-hole-history.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname); const report = JSON.parse(await readFile(resolve(root, 'config/chrome-native-hole-history-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.executionContext !== 'geometry-and-history-workers' || report.crossOriginIsolated !== true || report.status !== 'pass') throw new Error('Chrome native Hole history evidence is invalid.')
|
||||
if (report.capabilities?.status !== 'ready' || report.nativeCapabilities?.availability !== 'available' || report.nativeCapabilities?.operations?.includes('hole') !== true) throw new Error('Native OCCT Hole Worker capability evidence is incomplete.')
|
||||
const stages = report.history?.stages || []
|
||||
if (!Number.isSafeInteger(report.history?.recordCount) || report.history.recordCount <= 0 || report.history.relations?.modified <= 0 || report.history.relations?.deleted <= 0 || stages.length !== 2 || stages[0]?.operation !== 'hole' || stages[0]?.inputObjectIds?.length !== 0 || stages[0]?.topologyEntries <= 0 || stages[1]?.operation !== 'cut' || JSON.stringify(stages[1]?.inputObjectIds) !== JSON.stringify(['Base', stages[0]?.resultObjectId]) || stages[1]?.topologyEntries <= 0 || JSON.stringify(report.history.sourceObjects) !== JSON.stringify(['Base', stages[0]?.resultObjectId]) || JSON.stringify(report.history.resultStageIds) !== JSON.stringify([stages[1]?.stageId])) throw new Error('Native OCCT Hole staged relation evidence is incomplete.')
|
||||
if (report.result?.structuralValid !== true || report.result?.solids !== 1 || !(report.result.volume > 0 && report.result.volume < 1000)) throw new Error('Native OCCT Hole result evidence is invalid.')
|
||||
if (report.opfs?.markerCreated !== true || report.opfs?.markerSuite !== 'hole' || report.opfs?.markerRemoved !== true) throw new Error('Chrome native Hole OPFS evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native Hole history handles were not released.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-hole-history-pass', browserId: report.browserId, occtVersion: report.nativeCapabilities.occtVersion, records: report.history.recordCount, relations: report.history.relations, stages, volume: report.result.volume, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
1
scripts/check-chrome-native-linear-pattern-history.mjs
Normal file
1
scripts/check-chrome-native-linear-pattern-history.mjs
Normal file
@@ -0,0 +1 @@
|
||||
import {readFile} from 'node:fs/promises';import {resolve} from 'node:path';const report=JSON.parse(await readFile(resolve(new URL('..',import.meta.url).pathname,'config/chrome-native-linear-pattern-history-verification.json'),'utf8'));if(report.status!=='pass'||report.browserId!=='chrome'||report.nativeCapabilities?.operations?.includes('linear-pattern')!==true||report.history?.recordCount<=0||JSON.stringify(report.history?.sourceObjects)!==JSON.stringify(['Base'])||report.result?.bitbybitSolids!==1||Math.abs(report.result.bitbybitVolume-12)>1e-7||report.result?.nativeStructuralValid!==true||report.result?.nativeSolids!==1||Math.abs(report.result.nativeVolume-12)>1e-7||report.opfs?.markerRemoved!==true||report.afterRelease?.shapeCount!==0||report.afterRelease?.kernelReferenceCount!==0)throw new Error('Chrome native LinearPattern evidence is invalid.');console.log(JSON.stringify({status:'chrome-native-linear-pattern-history-pass',records:report.history.recordCount,relations:report.history.relations,result:report.result,afterRelease:report.afterRelease},null,2))
|
||||
5
scripts/check-chrome-native-loft-history.mjs
Normal file
5
scripts/check-chrome-native-loft-history.mjs
Normal file
@@ -0,0 +1,5 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const report = JSON.parse(await readFile(resolve(new URL('..', import.meta.url).pathname, 'config/chrome-native-loft-history-verification.json'), 'utf8'))
|
||||
if (report.status !== 'pass' || report.browserId !== 'chrome' || report.crossOriginIsolated !== true || report.nativeCapabilities?.operations?.includes('loft') !== true || report.history?.recordCount <= 0 || report.history?.relations?.generated <= 0 || !report.history?.sourceObjects?.includes('FirstSection') || !report.history?.sourceObjects?.includes('SecondSection') || report.result?.bitbybitSolids !== 1 || Math.abs(report.result.bitbybitVolume - 20) > 1e-7 || report.opfs?.markerPayload?.sections !== 2 || report.opfs?.markerRemoved !== true || report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native Loft history evidence is invalid.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-loft-history-pass', records: report.history.recordCount, relations: report.history.relations, afterRelease: report.afterRelease }, null, 2))
|
||||
5
scripts/check-chrome-native-mirrored-history.mjs
Normal file
5
scripts/check-chrome-native-mirrored-history.mjs
Normal file
@@ -0,0 +1,5 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const report = JSON.parse(await readFile(resolve(new URL('..', import.meta.url).pathname, 'config/chrome-native-mirrored-history-verification.json'), 'utf8'))
|
||||
if (report.status !== 'pass' || report.browserId !== 'chrome' || report.nativeCapabilities?.operations?.includes('mirrored') !== true || report.history?.recordCount <= 0 || JSON.stringify(report.history?.sourceObjects) !== JSON.stringify(['Base']) || report.result?.bitbybitSolids !== 1 || Math.abs(report.result.bitbybitVolume - 3) > 1e-7 || report.result?.nativeStructuralValid !== true || report.result?.nativeSolids !== 1 || Math.abs(report.result.nativeVolume - 3) > 1e-7 || report.opfs?.markerRemoved !== true || report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native Mirrored evidence is invalid.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-mirrored-history-pass', records: report.history.recordCount, relations: report.history.relations, result: report.result, afterRelease: report.afterRelease }, null, 2))
|
||||
7
scripts/check-chrome-native-multi-transform-history.mjs
Normal file
7
scripts/check-chrome-native-multi-transform-history.mjs
Normal file
@@ -0,0 +1,7 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const report = JSON.parse(await readFile(resolve(new URL('..', import.meta.url).pathname, 'config/chrome-native-multi-transform-history-verification.json'), 'utf8'))
|
||||
const stages = report.history?.stages || []
|
||||
const lineage = report.history?.finalRecordLineage
|
||||
if (report.status !== 'pass' || report.browserId !== 'chrome' || report.nativeCapabilities?.operations?.includes('multi-transform') !== true || report.history?.recordCount <= 0 || report.history?.relations?.modified <= 0 || report.history?.relations?.generated <= 0 || JSON.stringify(report.history?.transformKinds) !== JSON.stringify(['linear', 'mirrored']) || JSON.stringify(report.history?.sourceObjects) !== JSON.stringify(['Base']) || stages.length !== 2 || stages[0]?.operation !== 'linear-pattern' || stages[1]?.operation !== 'mirrored' || JSON.stringify(stages[0]?.inputObjectIds) !== JSON.stringify(['Base']) || stages[1]?.inputObjectIds?.[0] !== stages[0]?.resultObjectId || stages[1]?.resultObjectId !== 'MultiTransform' || stages.some((stage) => stage.topologyEntries <= 0 || stage.recordCount <= 0) || lineage?.records !== report.history?.stagedRecordCount || JSON.stringify(lineage?.sourceObjectIds) !== JSON.stringify([stages[0]?.resultObjectId]) || JSON.stringify(lineage?.sourceStageIds) !== JSON.stringify([stages[0]?.stageId]) || JSON.stringify(lineage?.resultStageIds) !== JSON.stringify([stages[1]?.stageId]) || report.result?.bitbybitSolids !== 1 || Math.abs(report.result.bitbybitVolume - 5) > 1e-7 || report.result?.nativeStructuralValid !== true || report.result?.nativeSolids !== 1 || Math.abs(report.result.nativeVolume - 5) > 1e-7 || JSON.stringify(report.opfs?.markerPayload?.transformKinds) !== JSON.stringify(['linear', 'mirrored']) || report.opfs?.markerRemoved !== true || report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native MultiTransform evidence is invalid.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-multi-transform-history-pass', records: report.history.recordCount, relations: report.history.relations, stages, result: report.result, afterRelease: report.afterRelease }, null, 2))
|
||||
8
scripts/check-chrome-native-pad-history.mjs
Normal file
8
scripts/check-chrome-native-pad-history.mjs
Normal file
@@ -0,0 +1,8 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-native-pad-history-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.executionContext !== 'dedicated-worker' || report.crossOriginIsolated !== true || report.status !== 'pass' || report.workerDisposed !== true) throw new Error('Chrome native Pad history Worker evidence is invalid.')
|
||||
if (report.nativeCapabilities?.availability !== 'available' || report.nativeCapabilities?.operations?.includes('pad') !== true || report.execution?.status !== 'completed' || report.execution.recordCount <= 0 || report.execution.crossKindRecords <= 0 || JSON.stringify(report.execution.profileKinds.sort()) !== JSON.stringify(['edge', 'face', 'vertex']) || report.execution.summary?.isValid !== true || report.execution.summary?.solids !== 1 || Math.abs(report.execution.summary.volume - 30) > 1e-7) throw new Error('Chrome native Pad Generated/Modified/Deleted evidence is incomplete.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-pad-history-pass', browserId: report.browserId, nativeCapabilities: report.nativeCapabilities, execution: report.execution }, null, 2))
|
||||
5
scripts/check-chrome-native-pipe-history.mjs
Normal file
5
scripts/check-chrome-native-pipe-history.mjs
Normal file
@@ -0,0 +1,5 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const report = JSON.parse(await readFile(resolve(new URL('..', import.meta.url).pathname, 'config/chrome-native-pipe-history-verification.json'), 'utf8'))
|
||||
if (report.status !== 'pass' || report.browserId !== 'chrome' || report.crossOriginIsolated !== true || report.nativeCapabilities?.operations?.includes('pipe') !== true || report.history?.recordCount <= 0 || report.history?.relations?.modified <= 0 || report.history?.relations?.generated <= 0 || !report.history?.sourceObjects?.includes('Profile') || !report.history?.sourceObjects?.includes('Spine') || report.result?.bitbybitSolids !== 1 || Math.abs(report.result.bitbybitVolume - 20) > 1e-7 || report.opfs?.markerPayload?.spineEdges !== 1 || report.opfs?.markerRemoved !== true || report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native Pipe history evidence is invalid.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-pipe-history-pass', records: report.history.recordCount, relations: report.history.relations, afterRelease: report.afterRelease }, null, 2))
|
||||
14
scripts/check-chrome-native-pocket-history.mjs
Normal file
14
scripts/check-chrome-native-pocket-history.mjs
Normal file
@@ -0,0 +1,14 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-native-pocket-history-verification.json'), 'utf8'))
|
||||
if (report.status !== 'pass' || report.browserId !== 'chrome' || report.crossOriginIsolated !== true) throw new Error('Chrome native Pocket history verification is not passing.')
|
||||
if (report.capabilities?.status !== 'ready' || report.nativeCapabilities?.availability !== 'available' || report.nativeCapabilities?.transport !== 'step-text' || report.nativeCapabilities?.operations?.includes('pocket') !== true) throw new Error('Native OCCT Pocket history Worker capability evidence is incomplete.')
|
||||
const stages = report.history?.stages || []
|
||||
if (!Number.isSafeInteger(report.history?.recordCount) || report.history.recordCount <= 0 || stages.length !== 2 || stages[0]?.operation !== 'pad' || JSON.stringify(stages[0]?.inputObjectIds) !== JSON.stringify(['Profile']) || stages[0]?.recordCount <= 0 || stages[0]?.topologyEntries <= 0 || stages[1]?.operation !== 'cut' || JSON.stringify(stages[1]?.inputObjectIds) !== JSON.stringify(['Base', stages[0]?.resultObjectId]) || stages[1]?.recordCount !== report.history.recordCount || stages[1]?.topologyEntries <= 0 || !report.history.sourceObjects?.includes('Base') || !report.history.sourceObjects?.includes('Profile') || !report.history.sourceObjects?.includes(stages[0]?.resultObjectId)) throw new Error('Native OCCT Pocket staged source relation evidence is incomplete.')
|
||||
const twoSidedStages = report.twoSidedHistory?.stages || []
|
||||
if (report.nativeCapabilities?.operations?.includes('pad') !== true || report.nativeCapabilities?.operations?.includes('fuse') !== true || report.nativeCapabilities?.operations?.includes('cut') !== true || report.twoSidedHistory?.recordCount <= 0 || report.twoSidedHistory?.structuralValid !== true || report.twoSidedHistory?.solids !== 1 || twoSidedStages.length !== 4 || twoSidedStages[0]?.operation !== 'pad' || JSON.stringify(twoSidedStages[0]?.inputObjectIds) !== JSON.stringify(['Profile']) || twoSidedStages[0]?.topologyEntries <= 0 || twoSidedStages[1]?.operation !== 'pad' || JSON.stringify(twoSidedStages[1]?.inputObjectIds) !== JSON.stringify(['Profile']) || twoSidedStages[1]?.topologyEntries <= 0 || twoSidedStages[2]?.operation !== 'fuse' || JSON.stringify(twoSidedStages[2]?.inputObjectIds) !== JSON.stringify([twoSidedStages[0]?.resultObjectId, twoSidedStages[1]?.resultObjectId]) || twoSidedStages[2]?.topologyEntries <= 0 || twoSidedStages[3]?.operation !== 'cut' || JSON.stringify(twoSidedStages[3]?.inputObjectIds) !== JSON.stringify(['BaseTwoSided', twoSidedStages[2]?.resultObjectId]) || twoSidedStages[3]?.resultObjectId !== 'PocketTwoSided' || twoSidedStages[3]?.topologyEntries <= 0 || !report.twoSidedHistory?.sourceObjects?.includes('BaseTwoSided') || !report.twoSidedHistory?.sourceObjects?.includes('Profile')) throw new Error('Native OCCT two-sided Pocket stage evidence is incomplete.')
|
||||
if (report.history.relations?.modified <= 0 || report.history.relations.generated + report.history.relations.deleted <= 0) throw new Error('Native OCCT Pocket history relation evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native Pocket history handles were not released.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-pocket-history-pass', browser: report.browserId, occtVersion: report.nativeCapabilities.occtVersion, records: report.history.recordCount, relations: report.history.relations, stages, twoSidedStages, afterRelease: report.afterRelease }, null, 2))
|
||||
5
scripts/check-chrome-native-polar-pattern-history.mjs
Normal file
5
scripts/check-chrome-native-polar-pattern-history.mjs
Normal file
@@ -0,0 +1,5 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const report = JSON.parse(await readFile(resolve(new URL('..', import.meta.url).pathname, 'config/chrome-native-polar-pattern-history-verification.json'), 'utf8'))
|
||||
if (report.status !== 'pass' || report.browserId !== 'chrome' || report.nativeCapabilities?.operations?.includes('polar-pattern') !== true || report.history?.recordCount <= 0 || JSON.stringify(report.history?.sourceObjects) !== JSON.stringify(['Base']) || report.result?.bitbybitSolids !== 1 || Math.abs(report.result.bitbybitVolume - 3) > 1e-7 || report.result?.nativeStructuralValid !== true || report.result?.nativeSolids !== 1 || Math.abs(report.result.nativeVolume - 3) > 1e-7 || report.opfs?.markerRemoved !== true || report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native PolarPattern evidence is invalid.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-polar-pattern-history-pass', records: report.history.recordCount, relations: report.history.relations, result: report.result, afterRelease: report.afterRelease }, null, 2))
|
||||
13
scripts/check-chrome-native-revolution-history.mjs
Normal file
13
scripts/check-chrome-native-revolution-history.mjs
Normal file
@@ -0,0 +1,13 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-native-revolution-history-verification.json'), 'utf8'))
|
||||
if (report.status !== 'pass' || report.browserId !== 'chrome' || report.crossOriginIsolated !== true) throw new Error('Chrome native Revolution history verification is not passing.')
|
||||
if (report.capabilities?.status !== 'ready' || report.nativeCapabilities?.availability !== 'available' || report.nativeCapabilities?.transport !== 'step-text' || report.nativeCapabilities?.operations?.includes('revolution') !== true) throw new Error('Native OCCT Revolution Worker capability evidence is incomplete.')
|
||||
if (!Number.isSafeInteger(report.history?.recordCount) || report.history.recordCount <= 0 || report.history.crossKindRecords <= 0 || report.history.relations?.modified <= 0 || report.history.relations?.generated <= 0) throw new Error('Native OCCT Revolution history relation evidence is incomplete.')
|
||||
const twoSidedStages = report.twoSidedHistory?.stages || []
|
||||
if (report.nativeCapabilities?.operations?.includes('rotate') !== true || report.twoSidedHistory?.recordCount <= 0 || report.twoSidedHistory?.structuralValid !== true || report.twoSidedHistory?.unexplainedStructuralErrors !== 0 || report.twoSidedHistory?.solids !== 1 || twoSidedStages.length !== 2 || twoSidedStages[0]?.operation !== 'rotate' || JSON.stringify(twoSidedStages[0]?.inputObjectIds) !== JSON.stringify(['Profile']) || twoSidedStages[0]?.topologyEntries <= 0 || twoSidedStages[1]?.operation !== 'revolution' || JSON.stringify(twoSidedStages[1]?.inputObjectIds) !== JSON.stringify([twoSidedStages[0]?.resultObjectId]) || twoSidedStages[1]?.resultObjectId !== 'RevolutionTwoSided' || twoSidedStages[1]?.topologyEntries <= 0 || Math.abs(report.twoSidedHistory.volume - 12 * Math.PI) > 1e-5) throw new Error('Native OCCT offset Revolution stage evidence is incomplete.')
|
||||
if (report.result?.structuralValid !== true || report.result?.solids !== 1 || Math.abs(report.result.volume - 24 * Math.PI) > 1e-5) throw new Error('Native OCCT Revolution result evidence is invalid.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native Revolution history handles were not released.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-revolution-history-pass', browser: report.browserId, occtVersion: report.nativeCapabilities.occtVersion, records: report.history.recordCount, relations: report.history.relations, twoSidedStages, volume: report.result.volume, afterRelease: report.afterRelease }, null, 2))
|
||||
5
scripts/check-chrome-native-thickness-history.mjs
Normal file
5
scripts/check-chrome-native-thickness-history.mjs
Normal file
@@ -0,0 +1,5 @@
|
||||
import { readFile } from 'node:fs/promises'; import { resolve } from 'node:path'; const root = resolve(new URL('..', import.meta.url).pathname); const report = JSON.parse(await readFile(resolve(root, 'config/chrome-native-thickness-history-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.crossOriginIsolated !== true || report.status !== 'pass' || report.nativeCapabilities?.operations?.includes('thickness') !== true) throw new Error('Chrome native Thickness capability evidence is invalid.')
|
||||
if (report.history?.recordCount <= 0 || report.history?.relations?.modified <= 0 || JSON.stringify(report.history?.sourceObjects) !== JSON.stringify(['Base'])) throw new Error('Chrome native Thickness relation evidence is invalid.')
|
||||
if (report.result?.bitbybitSolids !== 1 || !(report.result?.bitbybitVolume > 0) || report.result?.nativeStructuralValid !== true || report.result?.nativeSolids !== 1 || !(report.result?.nativeVolume > 0) || report.opfs?.markerSuite !== 'thickness' || report.opfs?.markerRemoved !== true || report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native Thickness result or resource evidence is invalid.')
|
||||
console.log(JSON.stringify({ status: 'chrome-native-thickness-history-pass', browserId: report.browserId, records: report.history.recordCount, relations: report.history.relations, result: report.result, afterRelease: report.afterRelease }, null, 2))
|
||||
11
scripts/check-chrome-offline.mjs
Normal file
11
scripts/check-chrome-offline.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-offline-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('REL-01 Chrome offline verification is not passing.')
|
||||
const worker = report.serviceWorker
|
||||
if (worker?.registered !== true || worker.controlled !== true || worker.shellCached !== true || worker.staleCacheRemoved !== true || worker.offlineFallback !== true || !worker.cacheNames.includes('bitbybit-cad-shell-v1')) throw new Error('REL-01 Service Worker cache lifecycle evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.bytes <= 0 || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('REL-01 OPFS resource evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'REL-01' || report.opfs.markerRemoved !== true || report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('REL-01 OPFS or Shape ownership evidence is incomplete.')
|
||||
console.log(JSON.stringify({ status: 'chrome-offline-pass', browser: report.browser.product, serviceWorker: worker, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
11
scripts/check-chrome-opfs-migration.mjs
Normal file
11
scripts/check-chrome-opfs-migration.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-opfs-migration-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.status !== 'pass' || report.crossOriginIsolated !== true) throw new Error('Chrome REL-02 OPFS migration evidence is not passing.')
|
||||
const migration = report.migration
|
||||
if (!migration || migration.sourceSchemaVersion !== 1 || migration.migratedSchemaVersion !== 6 || migration.migratedDocumentVersion !== 3 || migration.migratedObjectCount !== 1 || migration.recomputeDefaulted !== true || migration.reopenedSchemaVersion !== 6 || migration.reopenedVersion !== 4 || migration.checkpointVersion !== 4 || migration.recoveryIntegrity !== 'ok' || migration.reopenedRecoveryIntegrity !== 'ok' || migration.resourceRoundTrip !== true || migration.resourceReleased !== true) throw new Error('Chrome REL-02 forward migration, reopen, checkpoint, or resource evidence is invalid.')
|
||||
if (!report.rollback || report.rollback.rejected !== true || report.rollback.interruptedAfterMarker !== true || !/interruption after schema version 3/.test(report.rollback.error || '') || JSON.stringify(report.rollback.appliedVersionsAfterFailure) !== '[1]') throw new Error('Chrome REL-02 SQLite interruption rollback evidence is invalid.')
|
||||
if (!report.cleanup || !Array.isArray(report.cleanup.removedFiles) || report.cleanup.removedFiles.length === 0 || report.cleanup.removedFiles.some((name) => typeof name !== 'string' || !name.startsWith(report.cleanup.databasePath.slice(1)))) throw new Error('Chrome REL-02 OPFS database cleanup evidence is invalid.')
|
||||
console.log(JSON.stringify({ status: 'chrome-opfs-migration-pass', browserId: report.browserId, migration, cleanup: report.cleanup }, null, 2))
|
||||
12
scripts/check-chrome-part-primitives.mjs
Normal file
12
scripts/check-chrome-part-primitives.mjs
Normal file
@@ -0,0 +1,12 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-part-primitives-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '')) throw new Error('Chrome Part primitive verification is not passing.')
|
||||
const expected = ['ellipsoid', 'torus', 'prism', 'wedge', 'helix']
|
||||
if (JSON.stringify(report.operations?.map((operation) => operation.name)) !== JSON.stringify(expected) || !report.operations.slice(0, 4).every((operation) => operation.meshVertices >= 3 && operation.meshTriangles >= 1) || !(report.operations[4]?.length > 0)) throw new Error('Chrome Part primitive mesh/curve evidence is incomplete.')
|
||||
if (report.operations.find((operation) => operation.name === 'ellipsoid')?.volume <= 0 || report.operations.find((operation) => operation.name === 'torus')?.volume <= 0 || report.operations.find((operation) => operation.name === 'prism')?.volume <= 0 || report.operations.find((operation) => operation.name === 'wedge')?.volume <= 0) throw new Error('Chrome Part primitive mass evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome Part primitive handles were not fully released.')
|
||||
if (report.opfs?.markerSuite !== 'PART-ALL' || report.opfs.markerOperations !== 5 || report.opfs.markerRemoved !== true) throw new Error('Chrome Part primitive OPFS marker was not verified and removed.')
|
||||
console.log(JSON.stringify({ status: 'chrome-part-primitives-pass', browser: report.browser.product, operations: report.operations, afterRelease: report.afterRelease, opfs: report.opfs }, null, 2))
|
||||
14
scripts/check-chrome-partdesign-lifecycle.mjs
Normal file
14
scripts/check-chrome-partdesign-lifecycle.mjs
Normal file
@@ -0,0 +1,14 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-partdesign-lifecycle-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.status !== 'pass' || report.crossOriginIsolated !== true) throw new Error('Chrome PartDesign lifecycle evidence is not passing or isolated.')
|
||||
if (JSON.stringify(report.structural?.initialOrder) !== JSON.stringify(['sketch', 'pad', 'pocket', 'fillet']) || JSON.stringify(report.structural?.restoredOrder) !== JSON.stringify(report.structural.initialOrder) || report.structural?.reorderRejected !== true || report.structural?.dependencyRemovalRejected !== true || JSON.stringify(report.structural?.deleted?.removed) !== JSON.stringify(['fillet']) || report.structural.deleted.tip !== 'pocket' || report.structural.deleted.filletExists !== false || report.structural.deletionUndone?.tip !== 'fillet' || report.structural.deletionUndone.filletExists !== true || report.structural.deletionRedone?.tip !== 'pocket' || report.structural.deletionRedone.filletExists !== false || report.structural.restoredTip !== 'fillet') throw new Error('Chrome PartDesign reorder/delete/Undo/Redo evidence is invalid.')
|
||||
if (report.initial?.length !== 42 || report.initial?.tip !== 'pad' || report.initial?.support !== 'XY_Plane' || report.initial?.pocketSuppressed !== true || report.initial?.filletSuppressed !== true || !(report.initial?.volume > 0) || report.initial?.solids !== 1) throw new Error('Chrome PartDesign initial suppression/Tip state is invalid.')
|
||||
if (report.edited?.length !== 31 || report.edited?.tip !== 'pad' || report.edited?.support !== 'XY_Plane' || report.edited?.recompute !== 'completed' || report.edited?.shapeCount < 1 || report.edited?.kernelReferenceCount < 1 || !(report.edited?.volume > 0) || report.edited?.solids !== 1 || report.edited?.shapeId === report.initial?.shapeId) throw new Error('Chrome PartDesign edited recompute evidence is invalid or stale.')
|
||||
if (report.undone?.length !== 42 || report.undone?.tip !== 'pad' || report.undone?.support !== 'XY_Plane' || report.undone?.recompute !== 'completed' || report.undone?.shapeCount < 1 || report.undone?.kernelReferenceCount < 1 || !(report.undone?.volume > 0) || report.undone?.solids !== 1 || report.undone?.shapeId === report.edited?.shapeId || Math.abs(report.undone?.volume - report.initial?.volume) > 1e-7) throw new Error('Chrome PartDesign undo/recompute evidence is invalid or stale.')
|
||||
if (report.reopened?.length !== 42 || report.reopened?.persistedLength !== 42 || report.reopened?.tip !== 'pad' || report.reopened?.support !== 'XY_Plane' || report.reopened?.pocketSuppressed !== true || report.reopened?.filletSuppressed !== true || report.reopened?.recompute !== 'completed' || report.reopened?.persistenceMode !== 'sqlite-opfs' || !(report.reopened?.volume > 0) || report.reopened?.solids !== 1 || report.reopened?.shapeId === report.edited?.shapeId || Math.abs(report.reopened?.volume - report.initial?.volume) > 1e-7) throw new Error('Chrome PartDesign OPFS reopen evidence is invalid or stale.')
|
||||
if (report.released?.shapeCount !== 0 || report.released?.kernelReferenceCount !== 0) throw new Error('Chrome PartDesign lifecycle leaked Bitbybit shapes.')
|
||||
|
||||
console.log(JSON.stringify({ status: 'chrome-partdesign-lifecycle-pass', browserId: report.browserId, structural: report.structural, initial: report.initial, edited: report.edited, undone: report.undone, reopened: report.reopened, released: report.released }, null, 2))
|
||||
15
scripts/check-chrome-partdesign-loft.mjs
Normal file
15
scripts/check-chrome-partdesign-loft.mjs
Normal file
@@ -0,0 +1,15 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-partdesign-loft-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome PartDesign loft verification is not passing.')
|
||||
if (JSON.stringify(report.operations?.map((operation) => operation.command)) !== JSON.stringify(['additive-loft', 'additive-pipe', 'subtractive-loft', 'subtractive-pipe'])) throw new Error('PartDesign loft/pipe operation coverage is incomplete.')
|
||||
if (report.operations.some((operation) => !operation.objectId || !operation.typeId || !operation.shapeId || operation.recompute !== 'completed' || !Number.isFinite(operation.volume) || operation.volume <= 0)) throw new Error('PartDesign loft/pipe solid recompute evidence is incomplete.')
|
||||
const recovery = report.failureRecovery
|
||||
if (!recovery || recovery.loft?.status !== 'failed' || recovery.loft?.code !== 'LOFT_SECTIONS_DUPLICATE' || recovery.loft?.retainedShapeId !== recovery.loft?.previousShapeId || recovery.loft?.restoredStatus !== 'completed' || !recovery.loft?.restoredShapeId) throw new Error('Loft failure recovery evidence is incomplete.')
|
||||
if (recovery.pipe?.status !== 'failed' || recovery.pipe?.code !== 'PIPE_PATH_BRANCH' || recovery.pipe?.retainedShapeId !== recovery.pipe?.previousShapeId || recovery.pipe?.restoredStatus !== 'completed' || !recovery.pipe?.restoredShapeId) throw new Error('Pipe failure recovery evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.objectCount < 1 || report.persistence.reopenedTip !== report.operations[3]?.objectId) throw new Error('PartDesign loft OPFS round-trip evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'PD-LOFT' || report.opfs.markerOperations !== 4 || report.opfs.markerRemoved !== true) throw new Error('PartDesign loft OPFS marker was not verified and removed.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('PartDesign loft ShapeHandles were not fully released.')
|
||||
console.log(JSON.stringify({ status: 'chrome-partdesign-loft-pass', browser: report.browser.product, operations: report.operations, failureRecovery: report.failureRecovery, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
19
scripts/check-chrome-partdesign-transform.mjs
Normal file
19
scripts/check-chrome-partdesign-transform.mjs
Normal file
@@ -0,0 +1,19 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-partdesign-transform-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome PartDesign transform verification is not passing.')
|
||||
const expected = ['hole', 'fillet', 'chamfer', 'draft', 'thickness', 'hole', 'hole', 'linear-pattern', 'mirrored', 'mirrored', 'linear-pattern', 'polar-pattern', 'multi-transform', 'hole']
|
||||
if (JSON.stringify(report.operations?.map((operation) => operation.command)) !== JSON.stringify(expected)) throw new Error('PartDesign transform operation coverage is incomplete.')
|
||||
if (report.operations.some((operation) => operation.recompute !== 'completed' || !operation.objectId || !operation.typeId || !operation.shapeId || !(operation.volume > 0) || operation.solids !== 1 || operation.shapeCount < 1 || operation.kernelReferenceCount < 1)) throw new Error('PartDesign transform recompute, single-solid, or ownership evidence is incomplete.')
|
||||
const featureTransforms = report.operations.filter((operation) => operation.transformMode === 'Features')
|
||||
if (featureTransforms.length !== 2 || featureTransforms.some((operation) => !Array.isArray(operation.originals) || operation.originals.length !== 1)) throw new Error('PartDesign Feature-list transform evidence is incomplete.')
|
||||
const threadedHoles = report.operations.filter((operation) => operation.command === 'hole' && operation.threaded === true)
|
||||
if (threadedHoles.length !== 2 || threadedHoles[0].modelThread !== false || threadedHoles[0].threadType !== 'ISOMetricProfile' || threadedHoles[0].threadSize !== 'M1x0.25' || threadedHoles[1].modelThread !== true || threadedHoles[1].threadDirection !== 'Left') throw new Error('PartDesign Hole thread-standard evidence is incomplete.')
|
||||
if (report.topologyMigration?.editedPadLength !== 31 || report.topologyMigration?.wrongBindings !== 0 || report.topologyMigration?.stable < 1 || report.topologyMigration?.ambiguous < 1 || report.topologyMigration?.ambiguityDiagnostics !== report.topologyMigration?.ambiguous || JSON.stringify(report.topologyMigration?.migrated?.map((entry) => entry.command)) !== JSON.stringify(['fillet', 'chamfer', 'draft', 'thickness']) || report.topologyMigration.migrated.some((entry) => entry.objectId !== report.operations[0].objectId || !['stable', 'ambiguous'].includes(entry.status) || !entry.persistentId)) throw new Error('PartDesign dress-up topology migration evidence is incomplete.')
|
||||
if (report.ambiguityRecovery?.command !== 'fillet' || report.ambiguityRecovery?.status !== 'failed' || report.ambiguityRecovery?.code !== 'DRESSUP_EDGE_REFERENCE_UNRESOLVED' || report.ambiguityRecovery?.retainedShapeId !== report.ambiguityRecovery?.previousShapeId || report.ambiguityRecovery?.restoredStatus !== 'completed' || !report.ambiguityRecovery?.restoredShapeId) throw new Error('PartDesign dress-up ambiguity recovery evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.objectCount < 1 || report.persistence.reopenedTip !== report.operations.at(-1)?.objectId) throw new Error('PartDesign transform OPFS round-trip evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'PD-TRANSFORM' || report.opfs.markerOperations !== 14 || report.opfs.markerRemoved !== true) throw new Error('PartDesign transform OPFS marker was not verified and removed.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error('PartDesign transform ShapeHandles were not fully released.')
|
||||
console.log(JSON.stringify({ status: 'chrome-partdesign-transform-pass', browser: report.browser.product, operations: report.operations, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
3
scripts/check-chrome-performance.mjs
Normal file
3
scripts/check-chrome-performance.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname); const report = JSON.parse(await readFile(resolve(root, 'config/chrome-performance-verification.json'), 'utf8')); if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome performance verification is not passing.'); if (report.benchmark?.objects !== 1000 || report.benchmark.triangles !== 1_000_000 || report.benchmark.tableCells !== 100_000 || report.benchmark.pass !== true || report.benchmark.objectMs > 2000 || report.benchmark.triangleMs > 4000 || report.benchmark.tableMs > 2000) throw new Error('QA-05 performance budget evidence is incomplete.'); if (report.benchmark.heapBytes !== null && report.benchmark.heapBytes > 512 * 1024 * 1024) throw new Error('QA-05 heap budget exceeded.'); if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('QA-05 OPFS resource evidence is incomplete.'); if (report.opfs?.markerSuite !== 'QA-05' || report.opfs.triangles !== 1_000_000 || report.opfs.markerRemoved !== true) throw new Error('QA-05 OPFS marker evidence is incomplete.'); if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('QA-05 harness leaked ShapeHandles.'); console.log(JSON.stringify({ status: 'chrome-performance-pass', browser: report.browser.product, benchmark: report.benchmark, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
10
scripts/check-chrome-persistence-verification.mjs
Normal file
10
scripts/check-chrome-persistence-verification.mjs
Normal file
@@ -0,0 +1,10 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-persistence-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.status !== 'pass' || report.crossOriginIsolated !== true) throw new Error('Chrome OPFS persistence verification is not passing.')
|
||||
const capabilities = report.capabilities
|
||||
const roundTrip = report.roundTrip
|
||||
if (capabilities?.mode !== 'sqlite-opfs' || capabilities.sqliteWasm !== true || capabilities.opfs !== true || roundTrip?.savedMode !== 'sqlite-opfs' || roundTrip.loadedVersion !== 7 || roundTrip.checkpointVersion !== 7 || roundTrip.resourceRoundTrip !== true || roundTrip.recoveryIntegrity !== 'ok') throw new Error('Chrome SQLite OPFS round-trip evidence is invalid.')
|
||||
console.log(JSON.stringify({ status: 'chrome-persistence-verification-pass', browserId: report.browserId, capabilities, roundTrip }, null, 2))
|
||||
56
scripts/check-chrome-planegcs-verification.mjs
Normal file
56
scripts/check-chrome-planegcs-verification.mjs
Normal file
@@ -0,0 +1,56 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-planegcs-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.executionContext !== 'dedicated-worker' || report.status !== 'pass' || report.crossOriginIsolated !== true || report.sharedArrayBuffer !== true) throw new Error('Chrome planegcs dedicated Worker, isolation, or browser verification is not passing.')
|
||||
if (report.solve?.solveStatus > 1 || Math.abs(report.solve?.startY - report.solve?.endY) > 1e-7 || Math.abs(report.solve?.residual) > 1e-7 || Math.abs(Math.hypot(report.solve?.endX - report.solve?.startX, report.solve?.endY - report.solve?.startY) - 5) > 1e-7) throw new Error('Chrome planegcs solve evidence is invalid.')
|
||||
if (report.verticalSolve?.solveStatus > 1 || Math.abs(report.verticalSolve?.startX - report.verticalSolve?.endX) > 1e-7 || Math.abs(report.verticalSolve?.residual) > 1e-7 || Math.abs(Math.hypot(report.verticalSolve?.endX - report.verticalSolve?.startX, report.verticalSolve?.endY - report.verticalSolve?.startY) - 5) > 1e-7) throw new Error('Chrome vertical planegcs solve evidence is invalid.')
|
||||
if (report.distanceXSolve?.solveStatus > 1 || Math.abs(report.distanceXSolve?.endX - report.distanceXSolve?.startX - 6) > 1e-7 || Math.abs(report.distanceXSolve?.startY - report.distanceXSolve?.endY) > 1e-7 || Math.abs(report.distanceXSolve?.residual) > 1e-7) throw new Error('Chrome distanceX planegcs solve evidence is invalid.')
|
||||
if (report.distanceYSolve?.solveStatus > 1 || Math.abs(report.distanceYSolve?.endY - report.distanceYSolve?.startY - 6) > 1e-7 || Math.abs(report.distanceYSolve?.startX - report.distanceYSolve?.endX) > 1e-7 || Math.abs(report.distanceYSolve?.residual) > 1e-7) throw new Error('Chrome distanceY planegcs solve evidence is invalid.')
|
||||
if (report.angleSolve?.solveStatus > 1 || Math.abs(Math.hypot(report.angleSolve?.endX - report.angleSolve?.startX, report.angleSolve?.endY - report.angleSolve?.startY) - 5) > 1e-7 || Math.abs(Math.atan2(report.angleSolve?.endY - report.angleSolve?.startY, report.angleSolve?.endX - report.angleSolve?.startX) - report.angleSolve?.targetAngle) > 1e-7 || Math.abs(report.angleSolve?.lengthResidual) > 1e-7 || Math.abs(report.angleSolve?.residual) > 1e-7) throw new Error('Chrome angle planegcs solve evidence is invalid.')
|
||||
if (report.circleRadiusSolve?.solveStatus > 1 || Math.abs(report.circleRadiusSolve?.centerX - 2) > 1e-7 || Math.abs(report.circleRadiusSolve?.centerY - 3) > 1e-7 || Math.abs(report.circleRadiusSolve?.radius - 4) > 1e-7 || Math.abs(report.circleRadiusSolve?.residual) > 1e-7) throw new Error('Chrome circle radius planegcs solve evidence is invalid.')
|
||||
if (report.circleDiameterSolve?.solveStatus > 1 || Math.abs(report.circleDiameterSolve?.centerX - 2) > 1e-7 || Math.abs(report.circleDiameterSolve?.centerY - 3) > 1e-7 || Math.abs(report.circleDiameterSolve?.radius * 2 - 8) > 1e-7 || Math.abs(report.circleDiameterSolve?.residual) > 1e-7) throw new Error('Chrome circle diameter planegcs solve evidence is invalid.')
|
||||
if (report.equalLinesSolve?.solveStatus > 1 || Math.abs(Math.hypot(report.equalLinesSolve?.firstEndX - report.equalLinesSolve?.firstStartX, report.equalLinesSolve?.firstEndY - report.equalLinesSolve?.firstStartY) - Math.hypot(report.equalLinesSolve?.secondEndX - report.equalLinesSolve?.secondStartX, report.equalLinesSolve?.secondEndY - report.equalLinesSolve?.secondStartY)) > 1e-7 || Math.abs(report.equalLinesSolve?.residual) > 1e-7) throw new Error('Chrome equal-length planegcs solve evidence is invalid.')
|
||||
if (report.equalCirclesSolve?.solveStatus > 1 || Math.abs(report.equalCirclesSolve?.firstCenterX - 2) > 1e-7 || Math.abs(report.equalCirclesSolve?.firstCenterY - 3) > 1e-7 || Math.abs(report.equalCirclesSolve?.firstRadius - 4) > 1e-7 || Math.abs(report.equalCirclesSolve?.secondCenterX - 8) > 1e-7 || Math.abs(report.equalCirclesSolve?.secondCenterY - 9) > 1e-7 || Math.abs(report.equalCirclesSolve?.secondRadius - 4) > 1e-7 || Math.abs(report.equalCirclesSolve?.residual) > 1e-7) throw new Error('Chrome equal-radius planegcs solve evidence is invalid.')
|
||||
if (report.tangentCirclesSolve?.solveStatus > 1 || Math.abs(report.tangentCirclesSolve?.firstCenterX - 2) > 1e-7 || Math.abs(report.tangentCirclesSolve?.firstCenterY - 3) > 1e-7 || Math.abs(report.tangentCirclesSolve?.firstRadius - 4) > 1e-7 || Math.abs(report.tangentCirclesSolve?.secondCenterX - 10) > 1e-7 || Math.abs(report.tangentCirclesSolve?.secondCenterY - 3) > 1e-7 || Math.abs(report.tangentCirclesSolve?.secondRadius - 4) > 1e-7 || Math.abs(report.tangentCirclesSolve?.residual) > 1e-7) throw new Error('Chrome tangent-circle planegcs solve evidence is invalid.')
|
||||
if (report.pointSymmetrySolve?.solveStatus > 1 || Math.abs(report.pointSymmetrySolve?.firstX - 1) > 1e-7 || Math.abs(report.pointSymmetrySolve?.firstY - 2) > 1e-7 || Math.abs(report.pointSymmetrySolve?.secondX - 7) > 1e-7 || Math.abs(report.pointSymmetrySolve?.secondY - 10) > 1e-7 || Math.abs(report.pointSymmetrySolve?.centerX - 4) > 1e-7 || Math.abs(report.pointSymmetrySolve?.centerY - 6) > 1e-7 || Math.abs(report.pointSymmetrySolve?.residual) > 1e-7) throw new Error('Chrome point-symmetry planegcs solve evidence is invalid.')
|
||||
if (report.pointOnLineSolve?.solveStatus > 1 || Math.abs(report.pointOnLineSolve?.pointX - 2) > 1e-7 || Math.abs(report.pointOnLineSolve?.pointY) > 1e-7 || Math.abs(report.pointOnLineSolve?.startX) > 1e-7 || Math.abs(report.pointOnLineSolve?.startY) > 1e-7 || Math.abs(report.pointOnLineSolve?.endX - 4) > 1e-7 || Math.abs(report.pointOnLineSolve?.endY) > 1e-7 || Math.abs(report.pointOnLineSolve?.residual) > 1e-7) throw new Error('Chrome point-on-line planegcs solve evidence is invalid.')
|
||||
if (report.pointOnCircleSolve?.solveStatus > 1 || Math.abs(report.pointOnCircleSolve?.pointX - 4) > 1e-7 || Math.abs(report.pointOnCircleSolve?.pointY - 6.464101615137754) > 1e-7 || Math.abs(report.pointOnCircleSolve?.centerX - 2) > 1e-7 || Math.abs(report.pointOnCircleSolve?.centerY - 3) > 1e-7 || Math.abs(report.pointOnCircleSolve?.radius - 4) > 1e-7 || Math.abs(report.pointOnCircleSolve?.residual) > 1e-7) throw new Error('Chrome point-on-circle planegcs solve evidence is invalid.')
|
||||
if (report.pointOnCircleProvider?.completed !== 'completed' || Math.abs(report.pointOnCircleProvider?.point?.x - 4) > 1e-7 || Math.abs(report.pointOnCircleProvider?.point?.y - 6.464101615137754) > 1e-7) throw new Error('Chrome point-on-circle planegcs provider evidence is invalid.')
|
||||
if (report.pointOnArcSolve?.solveStatus > 1 || Math.abs(report.pointOnArcSolve?.pointX - 2) > 1e-7 || Math.abs(report.pointOnArcSolve?.pointY - 7) > 1e-7 || Math.abs(report.pointOnArcSolve?.centerX - 2) > 1e-7 || Math.abs(report.pointOnArcSolve?.centerY - 3) > 1e-7 || Math.abs(report.pointOnArcSolve?.radius - 4) > 1e-7 || Math.abs(report.pointOnArcSolve?.startAngle) > 1e-7 || Math.abs(report.pointOnArcSolve?.endAngle - Math.PI) > 1e-7 || Math.abs(report.pointOnArcSolve?.residual) > 1e-7) throw new Error('Chrome point-on-arc planegcs solve evidence is invalid.')
|
||||
if (report.pointOnArcProvider?.completed !== 'completed' || Math.abs(report.pointOnArcProvider?.point?.x - 2) > 1e-7 || Math.abs(report.pointOnArcProvider?.point?.y - 7) > 1e-7 || Math.abs(report.pointOnArcProvider?.arc?.center?.x - 2) > 1e-7 || Math.abs(report.pointOnArcProvider?.arc?.center?.y - 3) > 1e-7 || Math.abs(report.pointOnArcProvider?.arc?.radius - 4) > 1e-7 || Math.abs(report.pointOnArcProvider?.arc?.startAngle) > 1e-7 || Math.abs(report.pointOnArcProvider?.arc?.endAngle - Math.PI) > 1e-7) throw new Error('Chrome point-on-arc planegcs provider evidence is invalid.')
|
||||
if (report.pointOnEllipseSolve?.solveStatus > 1 || Math.abs(report.pointOnEllipseSolve?.pointX - 2) > 1e-7 || Math.abs(report.pointOnEllipseSolve?.pointY - 6) > 1e-7 || Math.abs(report.pointOnEllipseSolve?.centerX - 2) > 1e-7 || Math.abs(report.pointOnEllipseSolve?.centerY - 3) > 1e-7 || Math.abs(report.pointOnEllipseSolve?.focusX - 6) > 1e-7 || Math.abs(report.pointOnEllipseSolve?.focusY - 3) > 1e-7 || Math.abs(report.pointOnEllipseSolve?.minorRadius - 3) > 1e-7 || Math.abs(report.pointOnEllipseSolve?.residual) > 1e-7) throw new Error('Chrome point-on-ellipse planegcs solve evidence is invalid.')
|
||||
if (report.pointOnEllipseProvider?.completed !== 'completed' || Math.abs(report.pointOnEllipseProvider?.point?.x - 2) > 1e-7 || Math.abs(report.pointOnEllipseProvider?.point?.y - 6) > 1e-7 || Math.abs(report.pointOnEllipseProvider?.ellipse?.center?.x - 2) > 1e-7 || Math.abs(report.pointOnEllipseProvider?.ellipse?.center?.y - 3) > 1e-7 || Math.abs(report.pointOnEllipseProvider?.ellipse?.majorRadius - 5) > 1e-7 || Math.abs(report.pointOnEllipseProvider?.ellipse?.minorRadius - 3) > 1e-7 || Math.abs(report.pointOnEllipseProvider?.ellipse?.rotation) > 1e-7) throw new Error('Chrome point-on-ellipse planegcs provider evidence is invalid.')
|
||||
if (report.pointOnBsplineSolve?.solveStatus > 1 || Math.abs(report.pointOnBsplineSolve?.pointX - 2) > 1e-7 || Math.abs(report.pointOnBsplineSolve?.pointY - 1.5) > 1e-7 || Math.abs(report.pointOnBsplineSolve?.pointParameter - 0.5) > 1e-7 || Math.abs(report.pointOnBsplineSolve?.residualX) > 1e-7 || Math.abs(report.pointOnBsplineSolve?.residualY) > 1e-7) throw new Error('Chrome point-on-B-spline planegcs solve evidence is invalid.')
|
||||
if (report.pointOnBsplineProvider?.completed !== 'completed' || Math.abs(report.pointOnBsplineProvider?.point?.x - 2) > 1e-7 || Math.abs(report.pointOnBsplineProvider?.point?.y - 1.5) > 1e-7 || report.pointOnBsplineProvider?.bspline?.degree !== 3 || report.pointOnBsplineProvider?.bspline?.controlPoints?.length !== 4 || report.pointOnBsplineProvider?.bspline?.periodic !== false) throw new Error('Chrome point-on-B-spline planegcs provider evidence is invalid.')
|
||||
if (report.blockProvider?.completed !== 'completed' || report.blockProvider?.degreesOfFreedom !== 0 || report.blockProvider?.line?.start?.x !== 1 || report.blockProvider?.line?.start?.y !== 2 || report.blockProvider?.line?.end?.x !== 4 || report.blockProvider?.line?.end?.y !== 6) throw new Error('Chrome planegcs Block provider evidence is invalid.')
|
||||
if (report.parallelSolve?.solveStatus > 1 || Math.abs(report.parallelSolve?.firstEndY - report.parallelSolve?.firstStartY) > 1e-7 || Math.abs(report.parallelSolve?.secondEndY - report.parallelSolve?.secondStartY) > 1e-7 || Math.abs(report.parallelSolve?.secondEndX - 5) > 1e-7 || Math.abs(report.parallelSolve?.residual) > 1e-7) throw new Error('Chrome parallel planegcs solve evidence is invalid.')
|
||||
if (report.perpendicularSolve?.solveStatus > 1 || Math.abs(report.perpendicularSolve?.firstEndY - report.perpendicularSolve?.firstStartY) > 1e-7 || Math.abs(report.perpendicularSolve?.secondEndX - report.perpendicularSolve?.secondStartX) > 1e-7 || Math.abs(report.perpendicularSolve?.secondEndY - 7) > 1e-7 || Math.abs(report.perpendicularSolve?.residual) > 1e-7) throw new Error('Chrome perpendicular planegcs solve evidence is invalid.')
|
||||
if (report.coincidentSolve?.solveStatus > 1 || Math.abs(report.coincidentSolve?.firstEndX - report.coincidentSolve?.secondStartX) > 1e-7 || Math.abs(report.coincidentSolve?.firstEndY - report.coincidentSolve?.secondStartY) > 1e-7 || Math.abs(report.coincidentSolve?.residual) > 1e-7) throw new Error('Chrome coincident planegcs solve evidence is invalid.')
|
||||
const expectedCoincidentPairs = ['start:start', 'start:end', 'end:start', 'end:end']
|
||||
const validateCoincidentEndpointEvidence = (entries, provider) => Array.isArray(entries) && entries.length === 4 && entries.every((entry, index) => `${entry.firstPoint}:${entry.secondPoint}` === expectedCoincidentPairs[index] && (!provider || entry.completed === 'completed') && (!provider || Number.isFinite(entry.secondLength)) && (provider || entry.solveStatus <= 1) && Math.abs(entry.first?.x - entry.second?.x) <= 1e-7 && Math.abs(entry.first?.y - entry.second?.y) <= 1e-7 && Math.abs(entry.secondLength - 5) <= 1e-7 && (provider || Math.abs(entry.residual) <= 1e-7))
|
||||
if (!validateCoincidentEndpointEvidence(report.coincidentEndpointSolves, false)) throw new Error('Chrome selectable Coincident Embind endpoint evidence is invalid.')
|
||||
if (!validateCoincidentEndpointEvidence(report.coincidentEndpointProviders, true)) throw new Error('Chrome selectable Coincident provider endpoint evidence is invalid.')
|
||||
const snellsSolve = report.snellsLawSolve
|
||||
if (snellsSolve?.solveStatus > 1 || Math.abs(snellsSolve?.firstStart?.x - snellsSolve?.secondStart?.x) > 1e-7 || Math.abs(snellsSolve?.firstStart?.y - snellsSolve?.secondStart?.y) > 1e-7 || Math.abs(Math.hypot(snellsSolve?.secondEnd?.x - snellsSolve?.secondStart?.x, snellsSolve?.secondEnd?.y - snellsSolve?.secondStart?.y) - 5) > 1e-7 || Math.abs(snellsSolve?.boundaryStart?.y) > 1e-7 || Math.abs(snellsSolve?.boundaryEnd?.y) > 1e-7 || snellsSolve?.ratio !== 1.5 || Math.abs(snellsSolve?.residual) > 1e-7) throw new Error('Chrome SnellsLaw Embind evidence is invalid.')
|
||||
const snellsProvider = report.snellsLawProvider
|
||||
if (snellsProvider?.completed !== 'completed' || snellsProvider?.degreesOfFreedom !== 8 || Math.abs(snellsProvider?.residual) > 1e-7 || Math.abs(snellsProvider?.first?.start?.x - snellsProvider?.second?.start?.x) > 1e-7 || Math.abs(snellsProvider?.first?.start?.y - snellsProvider?.second?.start?.y) > 1e-7 || Math.abs(Math.hypot(snellsProvider?.second?.end?.x - snellsProvider?.second?.start?.x, snellsProvider?.second?.end?.y - snellsProvider?.second?.start?.y) - 5) > 1e-7 || Math.abs(snellsProvider?.boundary?.start?.y) > 1e-7 || Math.abs(snellsProvider?.boundary?.end?.y) > 1e-7) throw new Error('Chrome SnellsLaw provider evidence is invalid.')
|
||||
const ellipseAlignmentSolves = report.ellipseInternalAlignmentSolves
|
||||
if (!Array.isArray(ellipseAlignmentSolves) || ellipseAlignmentSolves.length !== 4 || ellipseAlignmentSolves.some((entry, index) => entry.alignmentType !== index + 1 || entry.solveStatus > 1 || Math.abs(entry.center?.x - 2) > 1e-7 || Math.abs(entry.center?.y - 3) > 1e-7 || Math.abs(entry.minorRadius - 3) > 1e-7 || Math.abs(entry.helperError) > 1e-7 || Math.abs(entry.residual) > 1e-7)) throw new Error('Chrome Ellipse InternalAlignment Embind evidence is invalid.')
|
||||
const ellipseAlignmentProvider = report.ellipseInternalAlignmentProvider
|
||||
if (ellipseAlignmentProvider?.completed !== 'completed' || ellipseAlignmentProvider?.degreesOfFreedom !== 5 || Math.abs(ellipseAlignmentProvider?.residual) > 1e-7 || ellipseAlignmentProvider?.ellipse?.center?.x !== 2 || ellipseAlignmentProvider?.ellipse?.center?.y !== 3 || ellipseAlignmentProvider?.ellipse?.majorRadius !== 5 || ellipseAlignmentProvider?.ellipse?.minorRadius !== 3 || ellipseAlignmentProvider?.ellipse?.rotation !== 0.25) throw new Error('Chrome Ellipse InternalAlignment provider evidence is invalid.')
|
||||
const bsplineWeightSolve = report.bsplineWeightSolve
|
||||
if (bsplineWeightSolve?.solveStatus > 1 || bsplineWeightSolve?.weights?.length !== 4 || Math.abs(bsplineWeightSolve?.weights?.[0] - 1) > 1e-7 || Math.abs(bsplineWeightSolve?.weights?.[1] - 1.5) > 1e-7 || Math.abs(bsplineWeightSolve?.weights?.[2] - 1.25) > 1e-7 || Math.abs(bsplineWeightSolve?.weights?.[3] - 1) > 1e-7 || Math.abs(bsplineWeightSolve?.helper?.center?.x - 1) > 1e-7 || Math.abs(bsplineWeightSolve?.helper?.center?.y - 2) > 1e-7 || Math.abs(bsplineWeightSolve?.helper?.radius - 1.5) > 1e-7 || Math.abs(bsplineWeightSolve?.alignmentResidual) > 1e-7 || Math.abs(bsplineWeightSolve?.residual) > 1e-7) throw new Error('Chrome B-spline Weight Embind evidence is invalid.')
|
||||
const bsplineWeightProvider = report.bsplineWeightProvider
|
||||
if (bsplineWeightProvider?.completed !== 'completed' || bsplineWeightProvider?.degreesOfFreedom !== 11 || Math.abs(bsplineWeightProvider?.residual) > 1e-7 || bsplineWeightProvider?.bspline?.degree !== 3 || bsplineWeightProvider?.bspline?.weights?.length !== 4 || Math.abs(bsplineWeightProvider?.bspline?.weights?.[0] - 1) > 1e-7 || Math.abs(bsplineWeightProvider?.bspline?.weights?.[1] - 1.5) > 1e-7 || Math.abs(bsplineWeightProvider?.bspline?.weights?.[2] - 1.25) > 1e-7 || Math.abs(bsplineWeightProvider?.bspline?.weights?.[3] - 1) > 1e-7 || bsplineWeightProvider?.bspline?.periodic !== false) throw new Error('Chrome B-spline Weight provider evidence is invalid.')
|
||||
const lifecycle = report.providerLifecycle
|
||||
if (!lifecycle?.supportedGeometry?.includes('arc')) throw new Error('Chrome planegcs Arc capability evidence is invalid.')
|
||||
if (!lifecycle?.supportedGeometry?.includes('ellipse')) throw new Error('Chrome planegcs Ellipse capability evidence is invalid.')
|
||||
if (!lifecycle?.supportedGeometry?.includes('bspline')) throw new Error('Chrome planegcs B-spline capability evidence is invalid.')
|
||||
if (!lifecycle?.supportedConstraints?.includes('block')) throw new Error('Chrome planegcs Block capability evidence is invalid.')
|
||||
if (!lifecycle?.supportedConstraints?.includes('snellsLaw')) throw new Error('Chrome planegcs SnellsLaw capability evidence is invalid.')
|
||||
if (!lifecycle?.supportedConstraints?.includes('internalAlignment')) throw new Error('Chrome planegcs InternalAlignment capability evidence is invalid.')
|
||||
if (!lifecycle?.supportedConstraints?.includes('weight')) throw new Error('Chrome planegcs Weight capability evidence is invalid.')
|
||||
if (lifecycle?.availability !== 'available' || lifecycle?.engine !== 'planegcs-wasm' || !lifecycle?.supportedGeometry?.includes('point') || !lifecycle?.supportedGeometry?.includes('circle') || !lifecycle?.supportedConstraints?.includes('vertical') || !lifecycle?.supportedConstraints?.includes('angle') || !lifecycle?.supportedConstraints?.includes('radius') || !lifecycle?.supportedConstraints?.includes('diameter') || !lifecycle?.supportedConstraints?.includes('equal') || !lifecycle?.supportedConstraints?.includes('tangent') || !lifecycle?.supportedConstraints?.includes('symmetric') || !lifecycle?.supportedConstraints?.includes('pointOnObject') || !lifecycle?.supportedConstraints?.includes('parallel') || !lifecycle?.supportedConstraints?.includes('perpendicular') || !lifecycle?.supportedConstraints?.includes('coincident') || lifecycle?.completed !== 'completed' || lifecycle?.verticalCompleted !== 'completed' || Math.abs(lifecycle?.verticalEnd?.x) > 1e-7 || Math.abs(lifecycle?.verticalEnd?.y - 5) > 1e-7 || lifecycle?.angleCompleted !== 'completed' || Math.abs(Math.hypot(lifecycle?.angleEnd?.x, lifecycle?.angleEnd?.y) - 5) > 1e-7 || Math.abs(Math.atan2(lifecycle?.angleEnd?.y, lifecycle?.angleEnd?.x) - Math.PI / 4) > 1e-7 || lifecycle?.circleRadiusCompleted !== 'completed' || Math.abs(lifecycle?.circleRadius?.center?.x - 2) > 1e-7 || Math.abs(lifecycle?.circleRadius?.center?.y - 3) > 1e-7 || Math.abs(lifecycle?.circleRadius?.radius - 4) > 1e-7 || lifecycle?.circleDiameterCompleted !== 'completed' || Math.abs(lifecycle?.circleDiameter?.center?.x - 2) > 1e-7 || Math.abs(lifecycle?.circleDiameter?.center?.y - 3) > 1e-7 || Math.abs(lifecycle?.circleDiameter?.radius * 2 - 8) > 1e-7 || lifecycle?.equalLinesCompleted !== 'completed' || Math.abs(Math.hypot(lifecycle?.equalLinesSecond?.end?.x - lifecycle?.equalLinesSecond?.start?.x, lifecycle?.equalLinesSecond?.end?.y - lifecycle?.equalLinesSecond?.start?.y) - 4) > 1e-7 || lifecycle?.equalCirclesCompleted !== 'completed' || Math.abs(lifecycle?.equalCirclesSecond?.center?.x - 8) > 1e-7 || Math.abs(lifecycle?.equalCirclesSecond?.center?.y - 9) > 1e-7 || Math.abs(lifecycle?.equalCirclesSecond?.radius - 4) > 1e-7 || lifecycle?.tangentCirclesCompleted !== 'completed' || Math.abs(lifecycle?.tangentCirclesSecond?.center?.x - 10) > 1e-7 || Math.abs(lifecycle?.tangentCirclesSecond?.center?.y - 3) > 1e-7 || Math.abs(lifecycle?.tangentCirclesSecond?.radius - 4) > 1e-7 || lifecycle?.pointSymmetryCompleted !== 'completed' || Math.abs(lifecycle?.pointSymmetrySecond?.x - 7) > 1e-7 || Math.abs(lifecycle?.pointSymmetrySecond?.y - 10) > 1e-7 || lifecycle?.pointOnLineCompleted !== 'completed' || Math.abs(lifecycle?.pointOnLinePoint?.x - 2) > 1e-7 || Math.abs(lifecycle?.pointOnLinePoint?.y) > 1e-7 || lifecycle?.pointOnLineCompleted !== 'completed' || Math.abs(lifecycle?.pointOnLinePoint?.x - 2) > 1e-7 || Math.abs(lifecycle?.pointOnLinePoint?.y) > 1e-7 || lifecycle?.parallelCompleted !== 'completed' || Math.abs(lifecycle?.parallelEnd?.y - 2) > 1e-7 || Math.abs(lifecycle?.parallelEnd?.x - 5) > 1e-7 || lifecycle?.perpendicularCompleted !== 'completed' || Math.abs(lifecycle?.perpendicularEnd?.x) > 1e-7 || Math.abs(lifecycle?.perpendicularEnd?.y - 7) > 1e-7 || lifecycle?.coincidentCompleted !== 'completed' || Math.abs(lifecycle?.coincidentStart?.x - 4) > 1e-7 || Math.abs(lifecycle?.coincidentStart?.y) > 1e-7 || lifecycle?.cancelled !== 'cancelled' || lifecycle?.recovered !== 'completed' || lifecycle?.stale !== 'stale' || Math.abs(lifecycle?.recoveredEnd?.x - 5) > 1e-7 || Math.abs(lifecycle?.recoveredEnd?.y) > 1e-7) throw new Error('Chrome planegcs Facade provider lifecycle evidence is invalid.')
|
||||
console.log(JSON.stringify({ status: 'chrome-planegcs-verification-pass', browserId: report.browserId, solve: report.solve, verticalSolve: report.verticalSolve, distanceXSolve: report.distanceXSolve, distanceYSolve: report.distanceYSolve, angleSolve: report.angleSolve, circleRadiusSolve: report.circleRadiusSolve, circleDiameterSolve: report.circleDiameterSolve, equalLinesSolve: report.equalLinesSolve, equalCirclesSolve: report.equalCirclesSolve, tangentCirclesSolve: report.tangentCirclesSolve, pointSymmetrySolve: report.pointSymmetrySolve, pointOnLineSolve: report.pointOnLineSolve, pointOnCircleSolve: report.pointOnCircleSolve, pointOnCircleProvider: report.pointOnCircleProvider, pointOnEllipseSolve: report.pointOnEllipseSolve, pointOnEllipseProvider: report.pointOnEllipseProvider, parallelSolve: report.parallelSolve, perpendicularSolve: report.perpendicularSolve, coincidentSolve: report.coincidentSolve, providerLifecycle: lifecycle }, null, 2))
|
||||
16
scripts/check-chrome-plot.mjs
Normal file
16
scripts/check-chrome-plot.mjs
Normal file
@@ -0,0 +1,16 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-plot-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome Plot verification is not passing.')
|
||||
if (report.series?.count !== 2 || report.series.boundSource !== 'Spreadsheet' || report.series.points !== 3 || report.series.updatedValue !== 16 || report.series.legend !== true) throw new Error('Plot bound-series update evidence is incomplete.')
|
||||
if (report.axes?.x !== 'Revision' || report.axes.y !== 'Length (mm)' || JSON.stringify(report.axes.xRange) !== JSON.stringify([0, 2]) || JSON.stringify(report.axes.yRange) !== JSON.stringify([0, 20])) throw new Error('Plot axes evidence is incomplete.')
|
||||
if (report.logAxis?.xScale !== 'log' || report.logAxis?.yScale !== 'log' || report.logAxis.svgHasLogMapping !== true || report.logAxis.rejectedNonPositive !== true) throw new Error('Plot logarithmic-axis evidence is incomplete.')
|
||||
if (report.svg?.bytes <= 0 || report.svg.deterministic !== true || report.svg.paths !== 2 || report.svg.accessible !== true || !/^[0-9a-f]{64}$/.test(report.svg.sha256)) throw new Error('Plot deterministic SVG evidence is incomplete.')
|
||||
if (report.png?.bytes <= 0 || report.png.deterministic !== true || report.png.validSignature !== true || report.png.width !== 640 || report.png.height !== 360 || !/^[0-9a-f]{64}$/.test(report.png.sha256)) throw new Error('Plot deterministic PNG evidence is incomplete.')
|
||||
if (report.csv?.bytes <= 0 || report.csv.rows !== 7 || !/^[0-9a-f]{64}$/.test(report.csv.sha256)) throw new Error('Plot CSV evidence is incomplete.')
|
||||
if (!Array.isArray(report.resources) || report.resources.length !== 3 || !report.resources.some((resource) => resource.mediaType === 'image/png') || report.resources.some((resource) => !resource.hash || resource.byteLength <= 0 || resource.roundTrip !== true || resource.released !== true)) throw new Error('Plot OPFS resource lifecycle evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'PLOT-ALL' || report.opfs.markerSeries !== 2 || report.opfs.markerRemoved !== true) throw new Error('Plot OPFS marker was not verified and removed.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('Plot harness leaked ShapeHandles.')
|
||||
console.log(JSON.stringify({ status: 'chrome-plot-pass', browser: report.browser.product, series: report.series, axes: report.axes, svg: report.svg, png: report.png, csv: report.csv, resources: report.resources, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
14
scripts/check-chrome-production-document.mjs
Normal file
14
scripts/check-chrome-production-document.mjs
Normal file
@@ -0,0 +1,14 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-production-document-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome production document verification is not passing.')
|
||||
if (report.closure?.complete !== true || report.closure.artifacts !== 4 || report.closure.missingKinds !== 0 || report.closure.missingDependencies !== 0 || report.closure.cycles !== 0 || report.closure.documentVersion !== 8 || report.closure.reopenedComplete !== true || JSON.stringify(report.closure.artifactVersions) !== JSON.stringify([2, 2, 2, 2])) throw new Error('DOC-CLOSURE dependency and reopen evidence is incomplete.')
|
||||
if (report.workflow?.created.spreadsheetCells !== 2 || report.workflow.created.draftObjects !== 1 || report.workflow.created.techdrawViews !== 1 || report.workflow.created.plotSeries !== 1) throw new Error('DOC-CLOSURE real artifact creation evidence is incomplete.')
|
||||
if (report.workflow?.edited.spreadsheetValue !== 6 || report.workflow.edited.draftStartX !== 1 || report.workflow.edited.techdrawDimension !== 5 || report.workflow.edited.plotValue !== 6) throw new Error('DOC-CLOSURE cross-workbench edit evidence is incomplete.')
|
||||
if (report.workflow?.exports.techdrawPdfValid !== true || Object.entries(report.workflow.exports).some(([name, value]) => name !== 'techdrawPdfValid' && (typeof value !== 'number' || value <= 0))) throw new Error('DOC-CLOSURE real export evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || !/^[0-9a-f]{64}$/.test(report.persistence.hash) || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('DOC-CLOSURE OPFS resource evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'DOC-CLOSURE' || report.opfs.artifacts !== 4 || report.opfs.markerRemoved !== true) throw new Error('DOC-CLOSURE OPFS marker evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('DOC-CLOSURE harness leaked ShapeHandles.')
|
||||
console.log(JSON.stringify({ status: 'chrome-production-document-pass', browser: report.browser.product, closure: report.closure, workflow: report.workflow, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
18
scripts/check-chrome-profile-validation.mjs
Normal file
18
scripts/check-chrome-profile-validation.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-profile-validation-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.status !== 'pass' || report.crossOriginIsolated !== true) throw new Error('Chrome profile validation verification is not passing.')
|
||||
const fixtures = report.fixtures ?? {}
|
||||
const is = (fixture, status, ringCount, intersections = 0) => fixture?.status === status && fixture.ringCount === ringCount && fixture.selfIntersections === intersections
|
||||
if (!is(fixtures.closed, 'closed', 1) || !is(fixtures.open, 'open', 1) || !is(fixtures.multiRing, 'multi-ring', 2) || !is(fixtures.selfIntersecting, 'self-intersecting', 1, 1) || !is(fixtures.invalidNesting, 'invalid-nesting', 2) || !is(fixtures.nonPlanar, 'non-planar', 1) || !is(fixtures.degenerate, 'degenerate', 1)) throw new Error('Chrome profile classification evidence is incomplete.')
|
||||
const errors = report.validationErrors ?? {}
|
||||
if (errors.closed !== null || errors.multiRing !== null || !/open/.test(errors.open ?? '') || !/self-intersecting/.test(errors.selfIntersecting ?? '') || !/contained directly/.test(errors.invalidNesting ?? '') || !/coplanar/.test(errors.nonPlanar ?? '') || !/collinear/.test(errors.degenerate ?? '')) throw new Error('Chrome profile validation error evidence is incomplete.')
|
||||
const partDesign = report.partDesignProfiles ?? {}
|
||||
if (partDesign.closed?.code !== null || partDesign.closed.regionCount !== 1 || partDesign.multiRing?.code !== null || partDesign.multiRing.outerPoints !== 4 || partDesign.multiRing.holeCount !== 1 || partDesign.multiRing.regionCount !== 1 || partDesign.open?.code !== 'PROFILE_OPEN' || partDesign.selfIntersecting?.code !== null || partDesign.selfIntersecting.regionCount !== 2 || JSON.stringify(partDesign.selfIntersecting.regionPoints) !== '[3,3]' || partDesign.invalidNesting?.code !== null || partDesign.invalidNesting.regionCount !== 2 || JSON.stringify(partDesign.invalidNesting.regionPoints) !== '[4,4]') throw new Error('Chrome PartDesign profile consumption evidence is incomplete.')
|
||||
const geometry = report.partDesignGeometry ?? {}
|
||||
const geometryMatches = (name, volume, solids, shapeType) => Math.abs(geometry[name]?.volume - volume) <= 1e-7 && geometry[name]?.solids === solids && geometry[name]?.shapeType === shapeType
|
||||
if (!geometryMatches('closed', 360, 1, 'solid') || !geometryMatches('multiRing', 320, 1, 'solid') || !geometryMatches('selfIntersecting', 80, 2, 'compound') || !geometryMatches('invalidNesting', 370, 2, 'compound')) throw new Error('Chrome PartDesign OCCT profile geometry does not match FreeCAD.')
|
||||
if (report.opfs?.markerSuite !== 'SK-11' || report.opfs.markerCases !== 12 || report.opfs.markerRemoved !== true) throw new Error('Chrome profile validation OPFS marker was not verified and removed.')
|
||||
console.log(JSON.stringify({ status: 'chrome-profile-validation-pass', browser: report.browserId, fixtures: report.fixtures, validationErrors: report.validationErrors, partDesignProfiles: report.partDesignProfiles, partDesignGeometry: report.partDesignGeometry, opfs: report.opfs }, null, 2))
|
||||
16
scripts/check-chrome-qa08.mjs
Normal file
16
scripts/check-chrome-qa08.mjs
Normal file
@@ -0,0 +1,16 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const [report, app] = await Promise.all([
|
||||
readFile(resolve(root, 'config/chrome-qa08-verification.json'), 'utf8').then(JSON.parse),
|
||||
readFile(resolve(root, 'config/chrome-app-e2e-verification.json'), 'utf8').then(JSON.parse),
|
||||
])
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome QA-08 verification is not passing.')
|
||||
if (report.locales?.supported !== 3 || report.locales.snapshots !== 3 || report.locales.longLabel !== true || report.locales.decimalSamples.some((sample) => !sample.includes('mm'))) throw new Error('Locale evidence is incomplete.')
|
||||
if (report.accessibility?.checked !== 4 || report.accessibility.missingNames !== 0 || report.accessibility.duplicates !== 0 || report.accessibility.unnamedFocusable !== 0 || report.accessibility.pass !== true) throw new Error('Accessibility semantics evidence is incomplete.')
|
||||
const screenshotKeys = Object.keys(app.screenshots || {})
|
||||
if (app.status !== 'pass' || app.mobile?.viewport?.width !== 390 || app.mobile.bodyHorizontalOverflow > 1 || app.keyboard?.steps !== 8 || app.keyboard.named !== 8 || app.keyboard.unnamedVisible !== 0 || app.screenReader?.nodes < 10 || app.screenReader.controls < 8 || app.screenReader.namedControls !== app.screenReader.controls || app.screenReader.unnamedControls !== 0 || !['desktop', 'mobile'].every((key) => screenshotKeys.includes(key))) throw new Error('Keyboard, screen-reader, locale, or mobile accessibility matrix is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('QA-08 OPFS resource evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'QA-08' || report.opfs.localeCount !== 3 || report.opfs.markerRemoved !== true) throw new Error('QA-08 OPFS marker evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('QA-08 harness leaked ShapeHandles.')
|
||||
console.log(JSON.stringify({ status: 'chrome-qa08-pass', browser: report.browser.product, locales: report.locales, accessibility: report.accessibility, application: { mobile: app.mobile, keyboard: app.keyboard, screenReader: app.screenReader, screenshots: Object.keys(app.screenshots) }, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
7
scripts/check-chrome-robot.mjs
Normal file
7
scripts/check-chrome-robot.mjs
Normal file
@@ -0,0 +1,7 @@
|
||||
import { readFile } from 'node:fs/promises'; import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname); const report = JSON.parse(await readFile(resolve(root, 'config/chrome-robot-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome Robot verification is not passing.')
|
||||
if (!report.geometry?.valid || report.trajectory?.joints !== 3 || report.trajectory.waypoints !== 2 || report.trajectory.poses !== 5 || report.trajectory.status !== 'generated' || report.trajectory.violations !== 0 || report.trajectory.maxReach !== 6 || report.trajectory.homeX !== 6 || report.trajectory.collisions < 1 || report.trajectory.controllerBytes <= 0 || report.trajectory.csvBytes <= 0) throw new Error('Robot trajectory/kinematics/workspace/collision/controller evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || !/^[0-9a-f]{64}$/.test(report.persistence.hash) || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('Robot OPFS evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'ROBOT-ALL' || report.opfs.poses !== 5 || report.opfs.markerRemoved !== true || report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('Robot lifecycle evidence is incomplete.')
|
||||
console.log(JSON.stringify({ status: 'chrome-robot-pass', browser: report.browser.product, geometry: report.geometry, trajectory: report.trajectory, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
3
scripts/check-chrome-script.mjs
Normal file
3
scripts/check-chrome-script.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname); const report = JSON.parse(await readFile(resolve(root, 'config/chrome-script-verification.json'), 'utf8')); if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome script sandbox verification is not passing.'); if (report.macro?.commands !== 2 || report.macro.replay !== 'replayed' || report.macro.executed !== 2 || report.macro.bytes <= 0) throw new Error('Script macro replay evidence is incomplete.'); if (!report.security?.disallowedRejected || !report.security.quotaRejected || !report.security.noDynamicExecution || JSON.stringify(report.security.deniedCapabilities) !== JSON.stringify(['file', 'network', 'time', 'resource'])) throw new Error('Script file/network/time/resource permission evidence is incomplete.'); if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('Script OPFS resource evidence is incomplete.'); if (report.opfs?.markerSuite !== 'SCRIPT-ALL' || report.opfs.commands !== 2 || report.opfs.markerRemoved !== true) throw new Error('Script OPFS marker evidence is incomplete.'); if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('Script harness leaked ShapeHandles.'); console.log(JSON.stringify({ status: 'chrome-script-pass', browser: report.browser.product, macro: report.macro, security: report.security, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
12
scripts/check-chrome-secondary-formats.mjs
Normal file
12
scripts/check-chrome-secondary-formats.mjs
Normal file
@@ -0,0 +1,12 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-secondary-formats-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome secondary format verification is not passing.')
|
||||
const expected = ['DXF', 'SVG', 'OBJ', 'PLY', 'STL', 'PDF', 'IFC', 'CSV']
|
||||
if (report.formats?.descriptors !== expected.length || report.formats.records !== expected.length || report.formats.nativeExports !== 3 || report.formats.proxyImports !== expected.length || report.formats.bytes <= 0 || JSON.stringify(report.formats.categories) !== JSON.stringify(['2d', 'bim', 'data', 'mesh']) || JSON.stringify(report.formats.matrix?.map((entry) => entry.format)) !== JSON.stringify(expected) || report.formats.matrix.some((entry) => !entry.imported || !entry.rejectedInvalid || !entry.byteExactRoundTrip || !entry.deterministic || !entry.proxy || entry.byteLength <= 0)) throw new Error('Secondary format success/failure/round-trip matrix is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('Secondary format OPFS resource evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'FC-09' || report.opfs.records !== expected.length || report.opfs.markerRemoved !== true) throw new Error('Secondary format OPFS marker evidence is incomplete.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('Secondary format harness leaked ShapeHandles.')
|
||||
console.log(JSON.stringify({ status: 'chrome-secondary-formats-pass', browser: report.browser.product, formats: report.formats, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
3
scripts/check-chrome-security.mjs
Normal file
3
scripts/check-chrome-security.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname); const report = JSON.parse(await readFile(resolve(root, 'config/chrome-security-verification.json'), 'utf8')); if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome security verification is not passing.'); if (report.checks?.path !== 5 || report.checks.archiveEntries !== 3 || report.checks.xmlBytes <= 0 || report.checks.permissionChecks !== 3 || report.checks.rejectedCases !== 4 || report.checks.pass !== true) throw new Error('QA-07 security preflight evidence is incomplete.'); if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('QA-07 OPFS resource evidence is incomplete.'); if (report.opfs?.markerSuite !== 'QA-07' || report.opfs.checks !== 4 || report.opfs.markerRemoved !== true) throw new Error('QA-07 OPFS marker evidence is incomplete.'); if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('QA-07 harness leaked ShapeHandles.'); console.log(JSON.stringify({ status: 'chrome-security-pass', browser: report.browser.product, checks: report.checks, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
21
scripts/check-chrome-sketcher-bspline.mjs
Normal file
21
scripts/check-chrome-sketcher-bspline.mjs
Normal file
@@ -0,0 +1,21 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-sketcher-bspline-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.status !== 'pass' || report.crossOriginIsolated !== true) throw new Error('Chrome Sketcher B-spline verification is not passing.')
|
||||
const edit = report.edit ?? {}
|
||||
if (edit.stableId !== true || edit.degree !== 1 || edit.poleCount !== 3 || edit.middlePole?.x !== 2 || edit.middlePole?.y !== 3 || JSON.stringify(edit.weights) !== JSON.stringify([1, 0.5, 1]) || JSON.stringify(edit.knots) !== JSON.stringify([0, 0, 0.5, 1, 1]) || edit.periodic !== true || edit.sourceUnchanged !== true) throw new Error('Chrome B-spline pole, weight, knot, degree, or periodic evidence is incomplete.')
|
||||
const errors = report.validationErrors ?? {}
|
||||
if (!/weights/.test(errors.invalidWeight ?? '') || !/knots/.test(errors.invalidKnots ?? '') || !/degree \+ 1/.test(errors.invalidDegree ?? '')) throw new Error('Chrome B-spline validation error evidence is incomplete.')
|
||||
const roundTrip = report.roundTrip ?? {}
|
||||
if (!(roundTrip.archiveBytes > 0) || roundTrip.geometryEqual !== true || roundTrip.constraintsEqual !== true || JSON.stringify(roundTrip.constraintTypes) !== '["weight","internalAlignment","weight"]' || JSON.stringify(roundTrip.weights) !== '[1,1.5,1.25,1]' || JSON.stringify(roundTrip.knots) !== '[0,0,0,0,1,1,1,1]') throw new Error('Chrome B-spline FCStd round-trip evidence is incomplete.')
|
||||
const planegcs = JSON.parse(await readFile(resolve(root, 'config/chrome-planegcs-verification.json'), 'utf8'))
|
||||
const native = planegcs.bsplineWeightSolve
|
||||
const provider = planegcs.bsplineWeightProvider
|
||||
if (planegcs.status !== 'pass' || native?.solveStatus > 1 || JSON.stringify(native?.weights) !== '[1,1.5,1.25,1]' || Math.abs(native?.alignmentResidual) > 1e-7 || Math.abs(native?.residual) > 1e-7 || provider?.completed !== 'completed' || provider?.degreesOfFreedom !== 11 || JSON.stringify(provider?.bspline?.weights) !== '[1,1.5,1.25,1]' || provider?.bspline?.periodic !== false) throw new Error('Chrome planegcs B-spline Weight/InternalAlignment evidence is incomplete.')
|
||||
const freecad = JSON.parse(await readFile(resolve(root, 'config/freecad-sketcher-constraint-oracle.json'), 'utf8'))
|
||||
const weightFixture = freecad.successCases?.find((entry) => entry.constraintType === 'Weight')
|
||||
if (freecad.status !== 'pass' || weightFixture?.observed !== 'success' || weightFixture.solveStatus !== 0 || Math.abs(weightFixture.residual) > freecad.tolerance) throw new Error('FreeCAD B-spline Weight oracle evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'SK-10' || report.opfs.markerFields !== 8 || report.opfs.markerRemoved !== true) throw new Error('Chrome Sketcher B-spline OPFS marker was not verified and removed.')
|
||||
console.log(JSON.stringify({ status: 'chrome-sketcher-bspline-pass', browser: report.browserId, edit: report.edit, roundTrip: report.roundTrip, nativeWeight: native, providerWeight: provider, freecadWeight: weightFixture, validationErrors: report.validationErrors, opfs: report.opfs }, null, 2))
|
||||
18
scripts/check-chrome-sketcher-diagnostics.mjs
Normal file
18
scripts/check-chrome-sketcher-diagnostics.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-sketcher-diagnostics-verification.json'), 'utf8'))
|
||||
const oracle = JSON.parse(await readFile(resolve(root, 'config/freecad-sketcher-constraint-oracle.json'), 'utf8'))
|
||||
if (report.status !== 'pass' || report.browserId !== 'chrome' || report.crossOriginIsolated !== true) throw new Error('Chrome Sketcher diagnostics verification is not passing.')
|
||||
if (report.fixtures?.reference?.status !== 'under-constrained' || report.fixtures.reference.degreesOfFreedom !== 3 || report.fixtures.reference.diagnostic !== true || report.fixtures.reference.unchangedEnd !== true) throw new Error('Chrome reference dimensional fixture evidence is incomplete.')
|
||||
if (report.fixtures?.redundant?.status !== 'under-constrained' || report.fixtures.redundant.diagnostic !== true) throw new Error('Chrome redundant dimensional fixture evidence is incomplete.')
|
||||
if (report.fixtures?.conflicting?.status !== 'conflicting' || report.fixtures.conflicting.diagnostic !== true || report.fixtures.conflicting.notConverged !== true) throw new Error('Chrome conflicting dimensional fixture evidence is incomplete.')
|
||||
const referenceOracle = oracle.classificationCases?.find((entry) => entry.id === 'reference-dimension')
|
||||
const redundantOracle = oracle.classificationCases?.find((entry) => entry.id === 'redundant-dimension')
|
||||
const conflictingOracle = oracle.classificationCases?.find((entry) => entry.id === 'conflicting-dimension')
|
||||
if (oracle.status !== 'pass' || referenceOracle?.solveStatus !== 0 || referenceOracle?.degreesOfFreedom !== report.fixtures.reference.degreesOfFreedom || referenceOracle?.driving !== false || redundantOracle?.solveStatus !== -2 || JSON.stringify(redundantOracle?.redundant) !== '[2]' || conflictingOracle?.solveStatus !== -3 || JSON.stringify(conflictingOracle?.conflicting) !== '[1,2]') throw new Error('Chrome Sketcher diagnostic classifications do not match the locked FreeCAD oracle.')
|
||||
if (report.fixtures?.coincident?.status !== 'under-constrained' || report.fixtures.coincident.residual !== 0 || report.fixtures.coincident.sharedPoint !== true) throw new Error('Chrome coincident constraint evidence is incomplete.')
|
||||
if (report.fixtures?.block?.status !== 'solved' || report.fixtures.block.degreesOfFreedom !== 0 || report.fixtures.block.unchanged !== true) throw new Error('Chrome block constraint evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'SK-06/SK-07' || report.opfs?.markerRemoved !== true) throw new Error('Chrome Sketcher diagnostics OPFS marker was not verified and removed.')
|
||||
console.log(JSON.stringify({ status: 'chrome-sketcher-diagnostics-pass', browser: report.browserId, fixtures: report.fixtures, opfs: report.opfs }, null, 2))
|
||||
16
scripts/check-chrome-sketcher-editor.mjs
Normal file
16
scripts/check-chrome-sketcher-editor.mjs
Normal file
@@ -0,0 +1,16 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-sketcher-editor-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.status !== 'pass' || report.crossOriginIsolated !== true) throw new Error('Chrome Sketcher editor verification is not passing.')
|
||||
const operations = report.operations ?? {}
|
||||
if (operations.drag?.start?.y !== 2 || operations.drag?.end?.x !== 8 || operations.drag.end.y !== 2 || operations.drag.sourceUnchanged !== true) throw new Error('Chrome drag evidence is incomplete.')
|
||||
if (JSON.stringify(operations.split?.ids) !== JSON.stringify(['line', 'line-2']) || JSON.stringify(operations.split.constraintTypes) !== JSON.stringify(['coincident']) || operations.extend?.end?.x !== 15 || operations.extend.sourceUnchanged !== true || operations.trim?.end?.x !== 7 || JSON.stringify(operations.trim.constraintTypes) !== JSON.stringify(['pointOnObject']) || operations.trim.sourceUnchanged !== true) throw new Error('Chrome trim/extend/split evidence is incomplete.')
|
||||
if (operations.construction?.enabled !== true || operations.construction.sourceUnchanged !== true) throw new Error('Chrome construction-toggle evidence is incomplete.')
|
||||
if (JSON.stringify(operations.autoConstraint?.suggestionReasons) !== JSON.stringify(['coincident', 'horizontal']) || operations.autoConstraint.appliedIds.length !== 2 || operations.autoConstraint.sourceUnchanged !== true) throw new Error('Chrome autoconstraint evidence is incomplete.')
|
||||
if (operations.replay?.applied !== 2 || operations.replay.undone !== 1 || operations.replay.redone !== 1 || operations.replay.finalEnd?.x !== 6) throw new Error('Chrome undo/redo replay evidence is incomplete.')
|
||||
const interactions = report.interactions ?? {}
|
||||
if (interactions.nativeEvents?.pointer !== 3 || interactions.nativeEvents.keyboard !== 3 || interactions.start?.y !== 2 || interactions.end?.x !== 8 || interactions.end?.y !== 2 || interactions.tool !== 'select' || interactions.pointerActive !== false || interactions.applied !== 1 || interactions.undone !== 1 || interactions.redone !== 1) throw new Error('Chrome native pointer/keyboard interaction evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'SK-09' || report.opfs.markerOperations !== 9 || report.opfs.markerRemoved !== true) throw new Error('Chrome Sketcher editor OPFS marker was not verified and removed.')
|
||||
console.log(JSON.stringify({ status: 'chrome-sketcher-editor-pass', browser: report.browserId, operations: report.operations, interactions: report.interactions, opfs: report.opfs }, null, 2))
|
||||
10
scripts/check-chrome-sketcher-stress.mjs
Normal file
10
scripts/check-chrome-sketcher-stress.mjs
Normal file
@@ -0,0 +1,10 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-sketcher-stress-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.status !== 'pass' || report.crossOriginIsolated !== true) throw new Error('Chrome Sketcher stress verification is not passing.')
|
||||
const stress = report.stress ?? {}
|
||||
if (stress.seed !== 539363331 || stress.models !== 500 || stress.solves !== 1000 || stress.deterministicMismatches !== 0 || stress.unexpectedResults !== 0 || stress.maxIterations > 64 || stress.maxResidual > 1e-12 || stress.digest !== 'edac8aed' || stress.durationMs > 2_000) throw new Error('Chrome Sketcher stress deterministic or performance evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'SK-12' || report.opfs.markerModels !== 500 || report.opfs.markerSolves !== 1000 || report.opfs.markerRemoved !== true) throw new Error('Chrome Sketcher stress OPFS marker was not verified and removed.')
|
||||
console.log(JSON.stringify({ status: 'chrome-sketcher-stress-pass', browser: report.browserId, stress: report.stress, opfs: report.opfs }, null, 2))
|
||||
19
scripts/check-chrome-spreadsheet.mjs
Normal file
19
scripts/check-chrome-spreadsheet.mjs
Normal file
@@ -0,0 +1,19 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-spreadsheet-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome Spreadsheet verification is not passing.')
|
||||
if (report.cells?.width !== 6 || report.cells.length !== 12 || report.cells.updatedLength !== 16 || report.cells.dimension !== 'length') throw new Error('Spreadsheet formula/unit recompute evidence is incomplete.')
|
||||
if (JSON.stringify(report.aliases) !== JSON.stringify(['Length', 'Width']) || report.dependencies?.length !== 2) throw new Error('Spreadsheet alias/dependency evidence is incomplete.')
|
||||
if (report.cycle?.detected !== true || JSON.stringify(report.cycle.cells) !== JSON.stringify(['C1', 'D1'])) throw new Error('Spreadsheet cycle diagnostics are incomplete.')
|
||||
if (report.binding?.alias !== 'Length' || report.binding.objectId !== 'pad' || report.binding.propertyName !== 'Length' || report.binding.appliedValue !== 16 || report.binding.undoValue !== 12 || report.binding.redoValue !== 16) throw new Error('Spreadsheet model-property binding lifecycle evidence is incomplete.')
|
||||
if (report.expression?.value !== 13 || report.expression.dimension !== 'length' || !report.expression.references.includes('Spreadsheet.Length')) throw new Error('Spreadsheet Facade expression bridge evidence is incomplete.')
|
||||
if (report.layout?.styledCells !== 4 || report.layout.namedRange !== 'A1:A3' || report.layout.mergedRange !== 'A1:C3' || report.layout.shiftedFormulaAddress !== 'A3') throw new Error('Spreadsheet style/merge/named-range address migration evidence is incomplete.')
|
||||
if (report.csv?.rows !== 2 || report.csv.columns !== 4 || report.csv.bytes <= 0) throw new Error('Spreadsheet CSV evidence is incomplete.')
|
||||
if (report.largeSheet?.cells !== 10000 || report.largeSheet.rows !== 100 || report.largeSheet.columns !== 100 || report.largeSheet.evaluationErrors !== 0 || report.largeSheet.csvBytes <= 0 || !(report.largeSheet.durationMs >= 0) || report.largeSheet.durationMs > report.largeSheet.budgetMs || report.largeSheet.budgetMs !== 5000) throw new Error('Spreadsheet large-sheet performance evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.reopenedValue !== 16 || report.persistence.reopenedVersion !== report.persistence.savedVersion) throw new Error('Spreadsheet-bound OPFS project round-trip evidence is incomplete.')
|
||||
if (!report.resource?.hash || report.resource.byteLength <= 0 || report.resource.roundTrip !== true || report.resource.released !== true) throw new Error('Spreadsheet OPFS resource lifecycle evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'SS-ALL' || report.opfs.markerCells !== 9 || report.opfs.markerRemoved !== true) throw new Error('Spreadsheet OPFS marker was not verified and removed.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('Spreadsheet harness leaked ShapeHandles.')
|
||||
console.log(JSON.stringify({ status: 'chrome-spreadsheet-pass', browser: report.browser.product, cells: report.cells, aliases: report.aliases, dependencies: report.dependencies, cycle: report.cycle, binding: report.binding, persistence: report.persistence, resource: report.resource, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
11
scripts/check-chrome-surface.mjs
Normal file
11
scripts/check-chrome-surface.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-surface-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome surface verification is not passing.')
|
||||
if (report.model?.patches !== 5 || report.model.poles !== 20 || report.model.trimmed !== 1 || report.model.sewn !== 1 || report.model.continuity !== 'C0' || report.model.fillKind !== 'bezier' || report.model.offsetDistance !== 1.5 || report.model.topoRefs !== 4 || report.model.objBytes <= 0) throw new Error('Surface fill/offset/trim/sew/TopoRef evidence is incomplete.')
|
||||
if (report.persistence?.mode !== 'sqlite-opfs' || report.persistence.byteLength <= 0 || report.persistence.roundTrip !== true || report.persistence.released !== true) throw new Error('Surface OPFS resource evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'SURF-CORE' || report.opfs.patches !== 5 || report.opfs.markerRemoved !== true) throw new Error('Surface OPFS marker was not verified and removed.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('Surface harness leaked ShapeHandles.')
|
||||
console.log(JSON.stringify({ status: 'chrome-surface-pass', browser: report.browser.product, model: report.model, persistence: report.persistence, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
11
scripts/check-chrome-techdraw.mjs
Normal file
11
scripts/check-chrome-techdraw.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
const root = resolve(new URL('..', import.meta.url).pathname); const report = JSON.parse(await readFile(resolve(root, 'config/chrome-techdraw-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || !/Chrome/i.test(report.browser?.product || '') || report.crossOriginIsolated !== true) throw new Error('Chrome TechDraw verification is not passing.')
|
||||
if (report.page?.views !== 2 || report.page.sections !== 1 || report.page.dimensions !== 1 || report.page.annotations !== 1 || report.page.tolerances !== 1 || report.page.updatedDimension !== 6 || JSON.stringify(report.page.unresolvedRefs) !== JSON.stringify(['Vertex3']) || JSON.stringify(report.page.unresolvedTolerances) !== JSON.stringify(['flatness'])) throw new Error('TechDraw view/section/dimension/GD&T/TopoRef update evidence is incomplete.')
|
||||
if (report.svg?.bytes <= 0 || !/^[0-9a-f]{64}$/.test(report.svg.sha256) || report.svg.deterministic !== true || report.svg.lines !== 2 || report.svg.accessible !== true) throw new Error('TechDraw deterministic SVG evidence is incomplete.')
|
||||
if (report.pdf?.bytes <= 0 || !/^[0-9a-f]{64}$/.test(report.pdf.sha256) || report.pdf.deterministic !== true || report.pdf.validHeader !== true || report.pdf.validXref !== true) throw new Error('TechDraw deterministic PDF evidence is incomplete.')
|
||||
if (!Array.isArray(report.resources) || report.resources.length !== 3 || report.resources.some((resource) => !resource.hash || resource.byteLength <= 0 || resource.roundTrip !== true || resource.released !== true) || !report.resources.some((resource) => resource.mediaType === 'application/pdf')) throw new Error('TechDraw OPFS resource lifecycle evidence is incomplete.')
|
||||
if (report.opfs?.markerSuite !== 'TD-ALL' || report.opfs.markerViews !== 2 || report.opfs.markerRemoved !== true) throw new Error('TechDraw OPFS marker was not verified and removed.')
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0) throw new Error('TechDraw harness leaked ShapeHandles.')
|
||||
console.log(JSON.stringify({ status: 'chrome-techdraw-pass', browser: report.browser.product, page: report.page, svg: report.svg, resources: report.resources, opfs: report.opfs, afterRelease: report.afterRelease }, null, 2))
|
||||
8
scripts/check-chrome-topology-verification.mjs
Normal file
8
scripts/check-chrome-topology-verification.mjs
Normal file
@@ -0,0 +1,8 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-topology-verification.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.status !== 'pass' || report.crossOriginIsolated !== true) throw new Error('Chrome topology verification is not passing.')
|
||||
if (report.capabilities?.status !== 'ready' || report.topology?.faces !== 6 || report.topology?.edges !== 12 || report.topology?.vertices !== 8 || report.topology.analyticFaces !== 6 || report.topology.analyticEdges !== 12 || report.topology.transientIndexFree !== true || report.topology.opfsTopologyRoundTrip !== true || report.topology.adjacencyKeys.length !== 5 || report.topology.adjacencyKeys.some((count) => count === 0)) throw new Error('Chrome topology analytic, adjacency, or OPFS evidence is incomplete.')
|
||||
console.log(JSON.stringify({ status: 'chrome-topology-verification-pass', counts: report.topology }, null, 2))
|
||||
11
scripts/check-dependency-audit.mjs
Normal file
11
scripts/check-dependency-audit.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const lockfileSha256 = createHash('sha256').update(await readFile(resolve(root, 'package-lock.json'))).digest('hex')
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/npm-audit-critical.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.command !== 'npm audit --omit=dev --audit-level=critical --json') throw new Error('npm critical audit report is not passing.')
|
||||
if (report.lockfileSha256 !== lockfileSha256) throw new Error('npm critical audit report does not match package-lock.json; rerun `npm run test:dependency-audit`.')
|
||||
if (Number(report.metadata?.vulnerabilities?.critical ?? -1) !== 0 || Object.keys(report.vulnerabilities ?? {}).length !== 0) throw new Error('npm critical audit contains unresolved vulnerabilities.')
|
||||
console.log(JSON.stringify({ status: 'dependency-audit-pass', critical: report.metadata.vulnerabilities.critical, productionDependencies: report.metadata.dependencies.prod, totalDependencies: report.metadata.dependencies.total, lockfileSha256 }, null, 2))
|
||||
24
scripts/check-document-closure.mjs
Normal file
24
scripts/check-document-closure.mjs
Normal file
@@ -0,0 +1,24 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const load = async (name) => JSON.parse(await readFile(resolve(root, `config/${name}`), 'utf8'))
|
||||
const [techdraw, spreadsheet, draft, plot, production] = await Promise.all([
|
||||
load('chrome-techdraw-verification.json'),
|
||||
load('chrome-spreadsheet-verification.json'),
|
||||
load('chrome-draft-verification.json'),
|
||||
load('chrome-plot-verification.json'),
|
||||
load('chrome-production-document-verification.json'),
|
||||
])
|
||||
|
||||
for (const [name, report] of Object.entries({ techdraw, spreadsheet, draft, plot, production })) {
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || report.crossOriginIsolated !== true || !/Chrome/i.test(report.browser?.product || '')) throw new Error(`P07 ${name} Chrome evidence is not passing.`)
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error(`P07 ${name} leaked ShapeHandles.`)
|
||||
}
|
||||
if (techdraw.page?.views !== 2 || techdraw.page.sections !== 1 || techdraw.page.dimensions !== 1 || techdraw.page.tolerances !== 1 || techdraw.pdf?.validHeader !== true || techdraw.pdf.validXref !== true || techdraw.pdf.deterministic !== true) throw new Error('TD-ALL page, GD&T or deterministic PDF evidence is incomplete.')
|
||||
if (spreadsheet.largeSheet?.cells !== 10000 || spreadsheet.largeSheet.evaluationErrors !== 0 || spreadsheet.largeSheet.durationMs > spreadsheet.largeSheet.budgetMs || spreadsheet.cycle?.detected !== true || spreadsheet.binding?.redoValue !== 16) throw new Error('SS-ALL formula, binding or large-sheet evidence is incomplete.')
|
||||
if (draft.objects?.sourceDependencies !== 5 || draft.operations?.trimmedLength !== 4 || draft.operations.offsetLength !== 8 || draft.persistence?.roundTrip !== true || draft.persistence.released !== true) throw new Error('DRAFT-BASE/DRAFT-OPS parametric operation evidence is incomplete.')
|
||||
if (plot.series?.updatedValue !== 16 || plot.png?.validSignature !== true || plot.png.deterministic !== true || plot.png.width !== 640 || plot.png.height !== 360 || !plot.resources?.some((resource) => resource.mediaType === 'image/png' && resource.roundTrip && resource.released)) throw new Error('PLOT-ALL binding or real PNG evidence is incomplete.')
|
||||
if (production.closure?.complete !== true || production.closure.documentVersion !== 8 || production.closure.reopenedComplete !== true || production.workflow?.edited.techdrawDimension !== 5 || production.workflow.exports.techdrawPdfValid !== true || production.persistence?.roundTrip !== true || production.persistence.released !== true) throw new Error('DOC-CLOSURE real create/edit/save/load/export evidence is incomplete.')
|
||||
|
||||
console.log(JSON.stringify({ status: 'document-closure-pass', browser: production.browser.product, tasks: ['TD-ALL', 'SS-ALL', 'DRAFT-BASE', 'DRAFT-OPS', 'PLOT-ALL', 'DOC-CLOSURE'], techdraw: { views: techdraw.page.views, pdfBytes: techdraw.pdf.bytes, tolerances: techdraw.page.tolerances }, spreadsheet: spreadsheet.largeSheet, draft: draft.operations, plot: plot.png, production: { closure: production.closure, workflow: production.workflow } }, null, 2))
|
||||
18
scripts/check-engineering-closure.mjs
Normal file
18
scripts/check-engineering-closure.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const load = async (name) => JSON.parse(await readFile(resolve(root, `config/${name}`), 'utf8'))
|
||||
const [assembly, bim, mesh, surface, engineering] = await Promise.all([
|
||||
load('chrome-assembly-verification.json'), load('chrome-bim-verification.json'), load('chrome-mesh-verification.json'), load('chrome-surface-verification.json'), load('chrome-engineering-verification.json'),
|
||||
])
|
||||
for (const [name, report] of Object.entries({ assembly, bim, mesh, surface, engineering })) {
|
||||
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.browserId !== 'chrome' || report.crossOriginIsolated !== true || !/Chrome/i.test(report.browser?.product || '')) throw new Error(`P08 ${name} Chrome evidence is not passing.`)
|
||||
if (report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error(`P08 ${name} leaked ShapeHandles.`)
|
||||
}
|
||||
if (assembly.solver?.workerStatus !== 'solved' || assembly.solver.angleStatus !== 'solved' || assembly.tools?.motionFrames !== 5 || assembly.scale?.components !== 1000 || assembly.fcstd?.links !== 1 || assembly.fcstd.roundTrip !== true) throw new Error('ASM-CORE/ASM-TOOLS evidence is incomplete.')
|
||||
if (bim.model?.sites !== 1 || bim.model.buildings !== 1 || bim.model.ifc4Schema !== 'IFC4' || bim.model.ifc2x3Schema !== 'IFC2X3' || bim.model.ifc4PropertySets < 1 || bim.model.ifc4Classifications !== 1) throw new Error('BIM-CORE/BIM-IFC evidence is incomplete.')
|
||||
if (mesh.geometry?.selfIntersections !== 0 || mesh.repairs?.selfIntersectionFixture !== 1 || mesh.repairs.nonManifoldFixture !== 1 || mesh.repairs.holeFilledTriangles !== 1 || mesh.operations?.workerLodTriangles !== mesh.operations?.lodTriangles) throw new Error('MESH-CORE analysis/repair/Worker LOD evidence is incomplete.')
|
||||
if (surface.model?.patches !== 5 || surface.model.fillKind !== 'bezier' || surface.model.offsetDistance !== 1.5 || surface.model.topoRefs !== 4 || surface.model.continuity !== 'C0') throw new Error('SURF-CORE modeling/TopoRef evidence is incomplete.')
|
||||
if (engineering.integrity?.complete !== true || engineering.integrity.projectVersion !== 8 || engineering.integrity.reopenedComplete !== true || JSON.stringify(engineering.integrity.artifactVersions) !== JSON.stringify([2, 2, 2, 2]) || engineering.scale?.components !== 1000 || engineering.persistence?.roundTrip !== true || engineering.persistence.released !== true) throw new Error('ENG-CLOSURE mixed-project evidence is incomplete.')
|
||||
console.log(JSON.stringify({ status: 'engineering-closure-pass', browser: engineering.browser.product, tasks: ['ASM-CORE', 'ASM-TOOLS', 'BIM-CORE', 'BIM-IFC', 'MESH-CORE', 'SURF-CORE', 'ENG-CLOSURE'], assembly: { solver: assembly.solver, scale: assembly.scale, fcstd: assembly.fcstd }, bim: bim.model, mesh: { geometry: mesh.geometry, operations: mesh.operations, repairs: mesh.repairs }, surface: surface.model, engineering: { integrity: engineering.integrity, workflow: engineering.workflow, scale: engineering.scale } }, null, 2))
|
||||
@@ -5,7 +5,8 @@ const sourceRoot = new URL('../src/', import.meta.url)
|
||||
const forbidden = [
|
||||
/from\s+["']three(?:\/|["'])/, /from\s+["']@types\/three/, /from\s+["']sqlite3?/, /from\s+["']@sqlite/, /from\s+["']opfs/, /SharedArrayBuffer/, /\bpostMessage\s*\(/,
|
||||
]
|
||||
const allowDirectRuntime = new Set(['facade/threeViewport.ts', 'facade/threeViewport.tsx', 'facade/persistenceWorker.ts', 'facade/projectStore.ts', 'facade/geometryWorker.ts', 'facade/nativeHistoryWorkerClient.ts', 'facade/nativeHistoryWorkerEntry.ts'])
|
||||
const allowDirectRuntime = new Set(['facade/threeViewport.ts', 'facade/threeViewport.tsx', 'facade/persistenceWorker.ts', 'facade/projectStore.ts', 'facade/projectMigrationSeedWorker.ts', 'facade/geometryWorker.ts', 'facade/nativeHistoryWorkerClient.ts', 'facade/nativeHistoryWorkerEntry.ts', 'facade/planegcsWorkerClient.ts', 'facade/planegcsWorkerEntry.ts'])
|
||||
const allowWorkerMessaging = new Set(['assemblySolverWorker.ts', 'meshLodWorker.ts', 'chromeAssemblyHarness.ts', 'chromeMeshHarness.ts', 'facade/camPipeline.ts'])
|
||||
|
||||
async function walk(relative = '') {
|
||||
const directory = new URL(relative, sourceRoot)
|
||||
@@ -23,7 +24,10 @@ const violations = []
|
||||
for (const file of await walk()) {
|
||||
if (allowDirectRuntime.has(file)) continue
|
||||
const content = await readFile(new URL(file, sourceRoot), 'utf8')
|
||||
for (const pattern of forbidden) if (pattern.test(content)) violations.push(`${file}: ${pattern}`)
|
||||
for (const pattern of forbidden) {
|
||||
if (pattern.source === '\\bpostMessage\\s*\\(' && allowWorkerMessaging.has(file)) continue
|
||||
if (pattern.test(content)) violations.push(`${file}: ${pattern}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (violations.length) {
|
||||
|
||||
31
scripts/check-fcstd-closure.mjs
Normal file
31
scripts/check-fcstd-closure.mjs
Normal file
@@ -0,0 +1,31 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const load = async (path) => JSON.parse(await readFile(resolve(root, path), 'utf8'))
|
||||
const native = await load('config/freecad-fcstd-roundtrip-verification.json')
|
||||
const semantic = await load('config/chrome-fcstd-semantic-verification.json')
|
||||
const chromeRoundTrip = await load('config/chrome-fcstd-roundtrip-verification.json')
|
||||
const golden = await load('config/chrome-fcstd-golden-verification.json')
|
||||
const secondary = await load('config/chrome-secondary-formats-verification.json')
|
||||
const fail = (message) => { throw new Error(`FCStd closure: ${message}`) }
|
||||
|
||||
if (native.status !== 'verified' || native.baselineId !== 'freecad-1.1.1' || native.freecadCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || native.scenarioCount !== 22 || native.scenarios?.length !== 22 || native.unknownDifferencesFail !== true || JSON.stringify(native.directions) !== JSON.stringify(['freecad-web-freecad', 'web-freecad-web'])) fail('native round-trip baseline or direction matrix is incomplete.')
|
||||
if (native.scenarios.some((scenario) => scenario.status !== 'pass' || scenario.differences?.length !== 0 || scenario.domains?.length === 0 || scenario.evidence === undefined)) fail('native round-trip contains an unknown difference or missing evidence.')
|
||||
const proxy = native.scenarios.find((scenario) => scenario.id === 'proxy-byte-preservation')?.evidence
|
||||
if (!proxy || proxy.byteIdentical !== true || proxy.sourceArchiveBytes !== proxy.preservedArchiveBytes || proxy.sourceSha256 !== proxy.preservedSha256 || proxy.proxyObjectCount !== 8 || proxy.resavedProxyObjectCount !== 8) fail('unknown/proxy archive bytes were not preserved.')
|
||||
const shape = native.scenarios.find((scenario) => scenario.id === 'locked-partdesign-shape')?.evidence
|
||||
if (!shape || shape.solidCount !== 1 || shape.faceCount !== 23 || shape.elementMapCount !== 2 || shape.elementMapPostfixCount !== 63 || !(shape.stringHasherBytes > 0)) fail('BRep/ElementMap/StringHasher evidence is incomplete.')
|
||||
|
||||
if (semantic.status !== 'pass' || semantic.objectNames?.length !== 4 || semantic.archiveBytes <= 0 || semantic.expression !== 'Spreadsheet.Width * 2' || semantic.link !== 'Source' || semantic.linkSub?.subElement !== 'Face1' || semantic.linkSubList?.length !== 2 || semantic.guiViews?.length !== 2 || semantic.shapeResource?.byteLength <= 0 || semantic.opfs?.markerRemoved !== true) fail('Chrome Expression/Link/TopoRef/Sketcher/GuiDocument semantic evidence is incomplete.')
|
||||
if (chromeRoundTrip.status !== 'pass' || chromeRoundTrip.directions?.length !== 2 || chromeRoundTrip.scenarios?.some((scenario) => scenario.status !== 'pass' || scenario.differences?.length !== 0) || chromeRoundTrip.persistence?.roundTrip !== true || chromeRoundTrip.persistence?.released !== true || chromeRoundTrip.opfs?.markerRemoved !== true || chromeRoundTrip.afterRelease?.shapeCount !== 0 || chromeRoundTrip.afterRelease?.kernelReferenceCount !== 0) fail('Chrome bidirectional FCStd round-trip evidence is incomplete.')
|
||||
|
||||
if (golden.status !== 'pass' || golden.scenarioCount !== 100 || golden.summary?.passed !== 100 || golden.summary?.unknownDifferences !== 0 || golden.summary?.formatRoundTrips?.step?.passed !== 100 || golden.summary?.formatRoundTrips?.iges?.passed !== 100 || golden.summary?.formatRoundTrips?.brep?.passed !== 100 || golden.summary?.formatRoundTrips?.step?.maximumVolumeDelta > 1e-6 || golden.summary?.formatRoundTrips?.iges?.maximumVolumeDelta > 1e-6 || golden.summary?.formatRoundTrips?.brep?.maximumVolumeDelta > 1e-6 || golden.afterRelease?.shapeCount !== 0 || golden.afterRelease?.kernelReferenceCount !== 0) fail('100-model Shape/STEP/IGES/BREP quality and tolerance golden is incomplete.')
|
||||
|
||||
const formats = ['DXF', 'SVG', 'OBJ', 'PLY', 'STL', 'PDF', 'IFC', 'CSV']
|
||||
if (secondary.status !== 'pass' || secondary.formats?.descriptors !== formats.length || secondary.formats.records !== formats.length || JSON.stringify(secondary.formats.categories) !== JSON.stringify(['2d', 'bim', 'data', 'mesh']) || secondary.formats.matrix?.length !== formats.length || secondary.formats.matrix.some((entry) => !entry.imported || !entry.rejectedInvalid || !entry.byteExactRoundTrip || !entry.deterministic) || secondary.persistence?.roundTrip !== true || secondary.persistence?.released !== true || secondary.opfs?.markerRemoved !== true || secondary.afterRelease?.shapeCount !== 0 || secondary.afterRelease?.kernelReferenceCount !== 0) fail('secondary 2D/Mesh/BIM/data format matrix is incomplete.')
|
||||
|
||||
const hardening = spawnSync('./npmw', ['run', 'check:fcstd-hardening'], { cwd: root, encoding: 'utf8', timeout: 120_000 })
|
||||
if (hardening.status !== 0) fail(`FC-11 hardening suite failed: ${(hardening.stdout || '') + (hardening.stderr || '')}`)
|
||||
console.log(JSON.stringify({ status: 'fcstd-closure-pass', nativeScenarios: native.scenarioCount, semanticObjects: semantic.objectNames.length, goldenModels: golden.scenarioCount, formatMatrix: secondary.formats.matrix, chromeDirections: chromeRoundTrip.directions, hardening: 'pass' }, null, 2))
|
||||
55
scripts/check-freecad-cam-path-oracle.mjs
Normal file
55
scripts/check-freecad-cam-path-oracle.mjs
Normal file
@@ -0,0 +1,55 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/freecad-cam-path-oracle.json'), 'utf8'))
|
||||
const fail = (message) => { throw new Error(`FreeCAD CAM Path oracle check: ${message}`) }
|
||||
|
||||
if (report.schemaVersion !== 1 || report.baselineId !== 'freecad-1.1.1' || report.sourceCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('the locked FreeCAD baseline is invalid')
|
||||
if (report.status !== 'verified-with-native-path-editable-subset' || report.boundary?.exactParityClaim !== false) fail('the report must retain the explicit non-exact Web codec boundary')
|
||||
if (report.runtime?.guiUp !== true || !String(report.runtime?.pathModule).includes('install-desktop')) fail('the native GUI Path runtime evidence is missing')
|
||||
if (report.profileAlgorithm?.fixture !== 'Mod/CAM/CAMTests/test_profile.fcstd' || report.profileAlgorithm?.pathPropertyType !== 'Path::PropertyPath') fail('the native Profile fixture contract is invalid')
|
||||
if (report.profileAlgorithm?.cuttingCommandCount !== 20 || report.profileAlgorithm?.commandCount < 20 || report.profileAlgorithm?.normalizationToleranceMm !== 0.01 || !/^[0-9a-f]{64}$/.test(report.profileAlgorithm?.commandSha256 ?? '')) fail('the native Profile command evidence is incomplete')
|
||||
if (report.profileAlgorithm?.firstCuttingCommand?.name !== 'G1' || report.profileAlgorithm?.lastCuttingCommand?.name !== 'G2') fail('the locked Profile cutting path endpoints changed')
|
||||
if (report.profileAlgorithm?.variants?.length !== 2 || report.profileAlgorithm.variants[0]?.id !== 'outside-cw-tool-comp' || report.profileAlgorithm.variants[1]?.id !== 'outside-cw-no-comp' || report.profileAlgorithm.variants.some((variant) => variant.commandCount !== 32 || variant.cuttingCommandCount !== 20 || !/^[0-9a-f]{64}$/.test(variant.commandSha256 ?? ''))) fail('the Profile compensation variants are incomplete')
|
||||
|
||||
const helix = report.helixAlgorithm
|
||||
const expectedHelixDirections = { 'inside-conventional': ['CW', 'G2'], 'outside-climb': ['CW', 'G2'], 'inside-climb': ['CCW', 'G3'], 'outside-conventional': ['CCW', 'G3'] }
|
||||
if (helix?.fixture !== 'Mod/CAM/CAMTests/test_holes00.fcstd' || helix?.pathPropertyType !== 'Path::PropertyPath' || helix?.toolDiameterMm !== 0.9 || helix?.baseSubElementCount !== 9 || helix?.scenarioCount !== 4) fail('the native Helix fixture contract is invalid')
|
||||
if (helix.scenarios.some((scenario) => {
|
||||
const expected = expectedHelixDirections[scenario.id]
|
||||
return !expected || scenario.direction !== expected[0] || scenario.expectedDirection !== expected[0] || scenario.expectedArc !== expected[1] || JSON.stringify(scenario.arcNames) !== JSON.stringify([expected[1]]) || scenario.arcCommandCount !== 1260 || scenario.commandCount < 1400 || !/^[0-9a-f]{64}$/.test(scenario.commandSha256 ?? '') || scenario.commandHistogram?.[expected[1]] !== 1260
|
||||
})) fail('a native Helix direction scenario changed')
|
||||
|
||||
const qt = report.qtDynamicOracle
|
||||
if (qt?.guiUp !== true || qt?.workbench !== 'CAMWorkbench' || qt?.activeWorkbench !== 'CAMWorkbench' || !/^6\./.test(qt?.qtVersion ?? '')) fail('the Qt CAM workbench evidence is invalid')
|
||||
if (qt?.camCommandCount < 50 || qt?.toolbarCount < 1 || qt?.menuCount < 1) fail('the live Qt CAM action/menu/toolbar surface is incomplete')
|
||||
const required = ['CAM_Job', 'CAM_Profile', 'CAM_Pocket_Shape', 'CAM_Drilling', 'CAM_Adaptive', 'CAM_Pocket3D', 'CAM_Post', 'CAM_Simulator', 'CAM_Sanity', 'CAM_ToolBitDock']
|
||||
if (required.some((id) => !qt?.requiredCommands?.some((command) => command.id === id && command.emptySelection?.registered === true && command.selectedModel?.registered === true && command.selectedJob?.registered === true && command.selectedOperation?.registered === true && command.emptyDocument?.registered === true && command.selectedModel?.actionCount >= 1 && command.selectedModel?.iconPresent === true))) fail('a required live CAM QAction is missing')
|
||||
if (!qt.requiredCommands.some((command) => command.selectionChangesEnabledState)) fail('the Qt oracle did not observe any selection-dependent CAM action state')
|
||||
if (qt.taskLifecycleVerified !== true || !Array.isArray(qt.taskLifecycles) || qt.taskLifecycles.length < 3 || qt.taskLifecycles.some((item) => item.dialogOpened !== true || item.dialogClosed !== true || item.buttonClicked !== true || item.active.buttonBoxCount !== 1 || item.active.fieldCount < 10 || !item.active.buttonTexts.some((button) => button.text === 'OK') || !item.active.buttonTexts.some((button) => button.text === 'Cancel'))) fail('the live Qt CAM Task panel lifecycle evidence is incomplete')
|
||||
|
||||
const fcstd = report.fcstdRoundTrip
|
||||
if (![fcstd?.source, fcstd?.mutated, fcstd?.webTransparentPreservation?.archive].every((archive) => /^[0-9a-f]{64}$/.test(archive?.semanticSha256 ?? ''))) fail('deterministic FCStd semantic hashes are missing')
|
||||
if (fcstd.source.semanticSha256 !== fcstd.webTransparentPreservation.archive.semanticSha256) fail('Web-preserved FCStd Path semantics changed')
|
||||
if (fcstd?.sourceBeforeClose?.length !== 2 || fcstd?.sourceReopened?.length !== 2 || fcstd?.mutatedReopened?.length !== 2) fail('the native Path FCStd lifecycle is incomplete')
|
||||
if (fcstd.sourceReopened.some((object) => object.commandCount !== 5 || object.pathPropertyType !== 'Path::PropertyPath')) fail('native Path objects did not survive the initial save/open')
|
||||
if (fcstd.mutatedReopened.some((object) => object.commandCount !== 6 || !object.label.endsWith('Mutated') || !object.oracleTag.endsWith('-v2'))) fail('native Path objects did not survive mutate/save/open')
|
||||
if (JSON.stringify(fcstd.webInspection?.objectTypes) !== JSON.stringify(['Path::Feature', 'Path::FeaturePython']) || fcstd.webInspection?.pathPropertyTypes?.some((type) => type !== 'Path::PropertyPath')) fail('Web inspection did not retain Path types')
|
||||
if (fcstd.webInspection?.proxyReadOnly !== true || fcstd.webInspection?.compatibility?.blockedObjects < 1) fail('the Web FeaturePython security boundary was lost')
|
||||
if (fcstd.webTransparentPreservation?.byteExact !== true || fcstd.webTransparentPreservation?.nativeReopenVerified !== true || fcstd.webTransparentPreservation?.objects?.some((object) => object.commandCount !== 5)) fail('native-Web-native transparent round-trip evidence is incomplete')
|
||||
if (fcstd.webEditablePathProperty?.objectName !== 'NativePath' || fcstd.webEditablePathProperty?.sourceCommandCount !== 5 || fcstd.webEditablePathProperty?.editedCommandCount !== 6 || fcstd.webEditablePathProperty?.lastCommand?.name !== 'M3' || fcstd.webEditablePathProperty?.nativeReopenVerified !== true || fcstd.webEditablePathProperty?.featurePythonExecutionBlocked !== true) fail('the editable native Path::PropertyPath codec evidence is incomplete')
|
||||
if (fcstd.webEditableFeaturePythonPathProperty?.objectName !== 'PythonPath' || fcstd.webEditableFeaturePythonPathProperty?.sourceCommandCount !== 5 || fcstd.webEditableFeaturePythonPathProperty?.editedCommandCount !== 6 || fcstd.webEditableFeaturePythonPathProperty?.lastCommand?.name !== 'M2' || fcstd.webEditableFeaturePythonPathProperty?.nativeReopenVerified !== true || fcstd.webEditableFeaturePythonPathProperty?.scriptExecution !== 'blocked' || fcstd.webEditableFeaturePythonPathProperty?.optInRequired !== true) fail('the safe opt-in FeaturePython Path::PropertyPath codec evidence is incomplete')
|
||||
if (report.claims?.nativeProfilePathAlgorithm !== 'verified' || report.claims?.nativeHelixPathAlgorithm !== 'verified-four-direction-cases' || report.claims?.qtCamWorkbenchDynamicOracle !== 'verified' || report.claims?.nativePathFeatureFcstdSaveOpenMutateSaveOpen !== 'verified' || report.claims?.nativeWebNativePathFcstdTransparentRoundTrip !== 'verified' || report.claims?.webEditablePathPropertyCodec !== 'verified-native-path-feature') fail('claim states are inconsistent')
|
||||
|
||||
console.log(JSON.stringify({
|
||||
status: report.status,
|
||||
profileCommands: report.profileAlgorithm.commandCount,
|
||||
cuttingCommands: report.profileAlgorithm.cuttingCommandCount,
|
||||
helixScenarios: helix.scenarioCount,
|
||||
camCommands: qt.camCommandCount,
|
||||
qtVersion: qt.qtVersion,
|
||||
nativeRoundTripObjects: fcstd.sourceReopened.length,
|
||||
webTransparentRoundTrip: fcstd.webTransparentPreservation.nativeReopenVerified,
|
||||
remainingGap: report.boundary.remainingGap,
|
||||
}, null, 2))
|
||||
@@ -0,0 +1,77 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import { elementMap2SemanticDigest, parseElementMap2, validateElementMap2, writeElementMap2 } from '../src/facade/elementMap2.ts'
|
||||
import { validateNativeNamingEvidence } from '../src/facade/nativeNamingEvidence.ts'
|
||||
import { migrateStringHasherSchema, parseStringHasherTable, validateStringHasherTable, writeStringHasherTable } from '../src/facade/stringHasher.ts'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/freecad-composite-history-elementmap-oracle.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.baselineId !== 'freecad-1.1.1-composite-history-elementmap2' || report.freecadVersion !== '1.1.1' || report.status !== 'pass' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') throw new Error('FreeCAD composite history ElementMap2 oracle baseline is invalid.')
|
||||
if (report.summary?.cases !== 30 || report.summary?.passed !== 30 || report.summary?.failed !== 0 || !Array.isArray(report.cases) || report.cases.length !== 30) throw new Error('FreeCAD composite oracle must contain exactly 30 passing cases.')
|
||||
let parsedResources = 0
|
||||
let parsedStringHasherResources = 0
|
||||
let namingEvidenceStages = 0
|
||||
let namingEvidenceMissing = 0
|
||||
let namingEvidenceBoundaryViolations = 0
|
||||
let nativeEvidenceValidatedStages = 0
|
||||
let nativeMappedNameStages = 0
|
||||
let nativeIndexedNameStages = 0
|
||||
let privateTokenEvidenceCompleteStages = 0
|
||||
let internalBuilderEvidenceStages = 0
|
||||
let internalBuilderEvidenceMissingStages = 0
|
||||
for (const fixture of report.cases) {
|
||||
if (fixture.status !== 'pass' || !fixture.finalObject || !fixture.stages?.length) throw new Error(`FreeCAD composite fixture ${fixture.id} has no valid feature history stages.`)
|
||||
if (fixture.roundtripNameDrift !== 0) throw new Error(`FreeCAD composite fixture ${fixture.id} changed mapped names after FreeCAD reopen.`)
|
||||
const names = new Set()
|
||||
let historyStages = 0
|
||||
for (const stage of fixture.stages) {
|
||||
if (!stage.shape?.valid || !stage.names?.length) throw new Error(`FreeCAD composite fixture ${fixture.id}/${stage.name} has invalid topology.`)
|
||||
namingEvidenceStages += 1
|
||||
if (!['final-shape-only', 'opaque-preserved', 'native-evidence', 'ambiguous', 'missing'].includes(stage.namingEvidenceStatus)) namingEvidenceMissing += 1
|
||||
if (stage.namingEvidenceStatus === 'opaque-preserved' && !fixture.stringHasherResource?.text) namingEvidenceBoundaryViolations += 1
|
||||
if (!stage.nativeEvidence || stage.nativeEvidence.stageId !== stage.name || stage.nativeEvidence.resultObjectId !== stage.name) throw new Error(`FreeCAD composite fixture ${fixture.id}/${stage.name} has incomplete native naming evidence identity.`)
|
||||
if (stage.nativeEvidence.status !== stage.namingEvidenceStatus) throw new Error(`FreeCAD composite fixture ${fixture.id}/${stage.name} has inconsistent naming evidence status.`)
|
||||
const nativeEvidence = { ...stage.nativeEvidence }
|
||||
if (fixture.stringHasherResource?.text) nativeEvidence.stringHasher = migrateStringHasherSchema(parseStringHasherTable(fixture.stringHasherResource.text))
|
||||
const nativeValidation = validateNativeNamingEvidence(nativeEvidence)
|
||||
if (!nativeValidation.valid) throw new Error(`FreeCAD native naming evidence validation failed for ${fixture.id}/${stage.name}: ${nativeValidation.issues[0].path}: ${nativeValidation.issues[0].message}`)
|
||||
nativeEvidenceValidatedStages += 1
|
||||
if (stage.nativeEvidence.mappedNameApiEntries > 0) nativeMappedNameStages += 1
|
||||
if (stage.nativeEvidence.indexedNameApiEntries > 0) nativeIndexedNameStages += 1
|
||||
if (stage.nativeEvidence.privateTokenEvidenceComplete === true) {
|
||||
if (stage.nativeEvidence.mappedNameApiEntries !== stage.nativeEvidence.mappedNames.length) throw new Error(`FreeCAD composite fixture ${fixture.id}/${stage.name} claims incomplete private token evidence as complete.`)
|
||||
privateTokenEvidenceCompleteStages += 1
|
||||
}
|
||||
if (stage.nativeEvidence.status === 'native-evidence' && stage.nativeEvidence.internalBuilderEvidence === true) internalBuilderEvidenceStages += 1
|
||||
else if (stage.nativeEvidence.status === 'native-evidence' && stage.nativeEvidence.internalBuilderEvidence !== true && /^(Part::(Fuse|Cut|Common|Extrusion|Revolution|Loft|Sweep|Fillet|Chamfer)|PartDesign::(Pad|Pocket|Revolution|Groove|AdditiveLoft|SubtractiveLoft|AdditivePipe|SubtractivePipe|Fillet|Chamfer|Draft|Thickness|Mirrored|MultiTransform|LinearPattern|PolarPattern|Hole))$/.test(stage.typeId || '')) internalBuilderEvidenceMissingStages += 1
|
||||
const stageHasHistory = stage.names.some((entry) => Array.isArray(entry.history) && entry.history.length > 0)
|
||||
if (stageHasHistory) historyStages += 1
|
||||
for (const entry of stage.names) {
|
||||
if (names.has(`${stage.name}:${entry.name}`)) throw new Error(`Duplicate ElementMap name ${fixture.id}/${stage.name}/${entry.name}.`)
|
||||
names.add(`${stage.name}:${entry.name}`)
|
||||
}
|
||||
}
|
||||
if (historyStages === 0) throw new Error(`Missing FreeCAD getElementHistory chain for ${fixture.id}.`)
|
||||
if (fixture.stringHasherResource?.text !== null && fixture.stringHasherResource?.text !== undefined) {
|
||||
const table = migrateStringHasherSchema(parseStringHasherTable(fixture.stringHasherResource.text))
|
||||
const validation = validateStringHasherTable(table)
|
||||
if (!validation.valid) throw new Error(`StringHasher semantic validation failed for ${fixture.id}: ${validation.issues[0].path}: ${validation.issues[0].message}`)
|
||||
const reparsed = migrateStringHasherSchema(parseStringHasherTable(writeStringHasherTable(table)))
|
||||
if (JSON.stringify(reparsed) !== JSON.stringify(table)) throw new Error(`StringHasher semantic roundtrip changed native naming evidence for ${fixture.id}.`)
|
||||
parsedStringHasherResources += 1
|
||||
}
|
||||
for (const [path, resource] of Object.entries(fixture.elementMapResources || {})) {
|
||||
const document = parseElementMap2(resource.text)
|
||||
const validation = validateElementMap2(document)
|
||||
if (!validation.valid) throw new Error(`ElementMap2 semantic validation failed for ${fixture.id}/${path}: ${validation.issues[0].path}: ${validation.issues[0].message}`)
|
||||
const canonical = writeElementMap2(document)
|
||||
const reparsed = parseElementMap2(canonical)
|
||||
if (reparsed.maps.length !== document.maps.length || reparsed.postfixes.length !== document.postfixes.length) throw new Error(`ElementMap2 schema roundtrip changed counts for ${fixture.id}/${path}.`)
|
||||
if (elementMap2SemanticDigest(reparsed) !== elementMap2SemanticDigest(document)) throw new Error(`ElementMap2 semantic roundtrip changed native naming history for ${fixture.id}/${path}.`)
|
||||
parsedResources += 1
|
||||
}
|
||||
}
|
||||
if (parsedResources === 0) throw new Error('FreeCAD composite oracle did not capture any ElementMap2 resources.')
|
||||
if (nativeIndexedNameStages !== namingEvidenceStages) throw new Error(`FreeCAD composite oracle must retain direct indexed-name evidence for every stage, found ${nativeIndexedNameStages}/${namingEvidenceStages}.`)
|
||||
if (namingEvidenceMissing !== 0 || namingEvidenceBoundaryViolations !== 0) throw new Error(`FreeCAD naming evidence boundary is incomplete: missing=${namingEvidenceMissing}, violations=${namingEvidenceBoundaryViolations}.`)
|
||||
console.log(JSON.stringify({ status: 'freecad-composite-history-elementmap-pass', cases: report.summary.cases, parsedResources, parsedStringHasherResources, namingEvidenceStages, nativeEvidenceValidatedStages, nativeIndexedNameStages, nativeMappedNameStages, indexedOnlyStages: nativeIndexedNameStages - nativeMappedNameStages, privateTokenEvidenceCompleteStages, namingEvidenceMissing, namingEvidenceBoundaryViolations, internalBuilderEvidenceStages, internalBuilderEvidenceMissingStages }, null, 2))
|
||||
92
scripts/check-freecad-desktop-oracle.mjs
Normal file
92
scripts/check-freecad-desktop-oracle.mjs
Normal file
@@ -0,0 +1,92 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { access, readFile, stat } from 'node:fs/promises'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const readJson = async (relative) => JSON.parse(await readFile(resolve(root, relative), 'utf8'))
|
||||
const fail = (message) => { throw new Error(`FreeCAD desktop oracle: ${message}`) }
|
||||
const config = await readJson('config/freecad-desktop-oracle.json')
|
||||
const toolchain = await readJson('config/freecad-toolchain.json')
|
||||
const source = resolve(root, '.cache/freecad/FreeCAD')
|
||||
const build = resolve(root, config.buildDirectory)
|
||||
const executable = resolve(root, `${config.installDirectory}/bin/FreeCAD`)
|
||||
const probeReport = resolve(root, '.cache/freecad/reference-desktop.json')
|
||||
|
||||
const sourceRevision = execFileSync('git', ['-C', source, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim()
|
||||
if (sourceRevision !== toolchain.source.commit) fail(`source revision ${sourceRevision} does not match ${toolchain.source.commit}.`)
|
||||
|
||||
const cache = await readFile(resolve(build, 'CMakeCache.txt'), 'utf8').catch(() => fail('CMakeCache.txt is missing; configure the desktop profile first.'))
|
||||
const cacheValue = (name) => {
|
||||
const line = cache.split(/\r?\n/).find((entry) => entry.startsWith(`${name}:`))
|
||||
return line?.slice(line.indexOf('=') + 1)
|
||||
}
|
||||
const expectedOn = {
|
||||
AddonManager: 'BUILD_ADDONMGR', Assembly: 'BUILD_ASSEMBLY', BIM: 'BUILD_BIM', CAM: 'BUILD_CAM',
|
||||
Draft: 'BUILD_DRAFT', Fem: 'BUILD_FEM', Help: 'BUILD_HELP', Idf: 'BUILD_IDF', Import: 'BUILD_IMPORT',
|
||||
Inspection: 'BUILD_INSPECTION', Material: 'BUILD_MATERIAL', Measure: 'BUILD_MEASURE', Mesh: 'BUILD_MESH',
|
||||
MeshPart: 'BUILD_MESH_PART', OpenSCAD: 'BUILD_OPENSCAD', Part: 'BUILD_PART', PartDesign: 'BUILD_PART_DESIGN',
|
||||
Plot: 'BUILD_PLOT', Points: 'BUILD_POINTS', ReverseEngineering: 'BUILD_REVERSEENGINEERING', Robot: 'BUILD_ROBOT',
|
||||
Show: 'BUILD_SHOW', Sketcher: 'BUILD_SKETCHER', Spreadsheet: 'BUILD_SPREADSHEET', Start: 'BUILD_START',
|
||||
Surface: 'BUILD_SURFACE', TechDraw: 'BUILD_TECHDRAW', Test: 'BUILD_TEST', Tux: 'BUILD_TUX', Web: 'BUILD_WEB',
|
||||
}
|
||||
const expectedOff = {
|
||||
Cloud: 'BUILD_CLOUD', JtReader: 'BUILD_JTREADER', Sandbox: 'BUILD_SANDBOX',
|
||||
}
|
||||
if (cacheValue('BUILD_GUI') !== 'ON') fail('BUILD_GUI is not ON.')
|
||||
for (const [module, key] of Object.entries(expectedOn)) if (cacheValue(key) !== 'ON') fail(`${module} requires ${key}=ON; got ${cacheValue(key) ?? '<missing>'}.`)
|
||||
for (const [module, key] of Object.entries(expectedOff)) if (cacheValue(key) !== 'OFF') fail(`${module} requires ${key}=OFF; got ${cacheValue(key) ?? '<missing>'}.`)
|
||||
|
||||
await access(executable).catch(() => fail(`installed executable is missing: ${executable}`))
|
||||
const versionOutput = execFileSync(executable, ['--console', '--version'], {
|
||||
cwd: root,
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
PYTHONPATH: `${resolve(root, config.sysrootDirectory, 'usr/lib/python3/dist-packages')}${process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ''}`,
|
||||
LD_LIBRARY_PATH: `${resolve(root, config.sysrootDirectory, 'usr/lib/x86_64-linux-gnu')}${process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : ''}`,
|
||||
},
|
||||
}).trim()
|
||||
if (!versionOutput.includes('FreeCAD 1.1.1')) fail(`version output does not report FreeCAD 1.1.1: ${versionOutput}`)
|
||||
const bytes = await readFile(executable)
|
||||
const artifact = {
|
||||
path: `${config.installDirectory}/bin/FreeCAD`,
|
||||
sizeBytes: (await stat(executable)).size,
|
||||
sha256: createHash('sha256').update(bytes).digest('hex'),
|
||||
reportedVersion: versionOutput.split(/\r?\n/)[0],
|
||||
}
|
||||
if (config.artifact?.sha256 && config.artifact.sha256 !== artifact.sha256) fail(`artifact checksum mismatch: expected ${config.artifact.sha256}, got ${artifact.sha256}.`)
|
||||
|
||||
const report = await readJson('.cache/freecad/reference-desktop.json').catch(() => null)
|
||||
if (!report) fail('34-module desktop probe report is missing; run probe:freecad-reference with FREECAD_ORACLE_PROFILE=desktop.')
|
||||
if (report.baselineId !== config.baselineId || report.freecadVersion !== '1.1.1' || report.guiUp !== true || report.moduleCount !== 34 || report.modules?.length !== 34) {
|
||||
fail('desktop probe report is not a GUI-up 34-module FreeCAD 1.1.1 report.')
|
||||
}
|
||||
const invalid = report.modules.filter((module) => !['compiled-importable', 'compiled-import-failure', 'gui-only-unprobeable', 'not-built'].includes(module.runtimeStatus))
|
||||
if (invalid.length) fail(`probe contains invalid module statuses: ${invalid.map((module) => module.name).join(', ')}`)
|
||||
const expectedObjects = ['Part::Box', 'Part::Cylinder', 'Part::Sphere', 'Part::Cone', 'Part::Feature', 'PartDesign::Feature', 'Sketcher::SketchObject']
|
||||
if (!Array.isArray(report.objects) || report.objects.length !== expectedObjects.length) fail('desktop probe must include the seven core object fixtures.')
|
||||
for (const typeId of expectedObjects) {
|
||||
const object = report.objects.find((candidate) => candidate.runtimeTypeId === typeId && candidate.typeId === typeId)
|
||||
if (!object?.available || !Array.isArray(object.properties)) fail(`runtime object fixture ${typeId} is missing or unavailable.`)
|
||||
}
|
||||
const propertyFlags = report.objects.flatMap((object) => object.properties || []).map((property) => property.status || [])
|
||||
if (!propertyFlags.some((status) => status.includes('Hidden')) || !propertyFlags.some((status) => status.includes('Output'))) {
|
||||
fail('runtime property fixtures must include Hidden and Output status flags.')
|
||||
}
|
||||
|
||||
console.log(JSON.stringify({
|
||||
status: 'freecad-desktop-oracle-pass',
|
||||
baselineId: config.baselineId,
|
||||
sourceRevision,
|
||||
buildDirectory: config.buildDirectory,
|
||||
installDirectory: config.installDirectory,
|
||||
artifact,
|
||||
probe: {
|
||||
path: '.cache/freecad/reference-desktop.json',
|
||||
moduleCount: report.moduleCount,
|
||||
guiUp: report.guiUp,
|
||||
moduleStatusSummary: report.moduleStatusSummary,
|
||||
objectFixtureCount: report.objects.length,
|
||||
},
|
||||
}, null, 2))
|
||||
27
scripts/check-freecad-entrypoint-inventory.mjs
Normal file
27
scripts/check-freecad-entrypoint-inventory.mjs
Normal file
@@ -0,0 +1,27 @@
|
||||
import { access, readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const inventory = JSON.parse(await readFile(resolve(root, 'config/freecad-entrypoint-inventory.json'), 'utf8'))
|
||||
const fail = (message) => { throw new Error(`FreeCAD entrypoint inventory: ${message}`) }
|
||||
if (inventory.schemaVersion !== 1 || inventory.baseline?.freecadVersion !== '1.1.1' || inventory.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('baseline mismatch.')
|
||||
const statuses = new Set(['exact', 'compatible', 'read-only', 'experimental', 'unsupported'])
|
||||
const strategies = new Set(['native-parser', 'occt-worker', 'worker-adapter', 'sanitized-parser', 'server-export', 'sandboxed-adapter', 'proxy-resource', 'facade-parser', 'sandboxed-interpreter', 'blocked-resource', 'signed-catalog', 'namespaced-settings', 'browser-clipboard-permission'])
|
||||
const validate = (entries, label) => {
|
||||
if (!Array.isArray(entries) || entries.length === 0) fail(`${label} must not be empty.`)
|
||||
const ids = new Set()
|
||||
for (const entry of entries) {
|
||||
if (!entry.id || ids.has(entry.id) || !Array.isArray(entry.modules) || entry.modules.length === 0 || !Array.isArray(entry.sourceEvidence) || entry.sourceEvidence.length === 0) fail(`${label} contains an incomplete or duplicate entry.`)
|
||||
ids.add(entry.id)
|
||||
if (!strategies.has(entry.webStrategy) || !statuses.has(entry.compatibility)) fail(`${entry.id} lacks a declared Web strategy/compatibility level.`)
|
||||
for (const evidence of entry.sourceEvidence) if (evidence.startsWith('src/') && !evidence.includes('PreferencePages')) {
|
||||
access(resolve(root, '.cache/freecad/FreeCAD', evidence)).catch(() => fail(`${entry.id} source evidence is unavailable: ${evidence}`))
|
||||
}
|
||||
}
|
||||
}
|
||||
validate(inventory.formats, 'formats')
|
||||
validate(inventory.entrypoints, 'entrypoints')
|
||||
if (!Array.isArray(inventory.moduleCoverage) || inventory.moduleCoverage.length !== 34) fail('module coverage must enumerate all 34 modules.')
|
||||
if (inventory.moduleCoverage.some((entry) => !entry.name || !entry.sourcePath || !['entrypoint-covered', 'scope-only'].includes(entry.status))) fail('module coverage contains an invalid entry.')
|
||||
if (inventory.policy?.untrustedContent !== 'never-execute' || inventory.policy?.unknownOrUnlistedEntries !== 'fail-inventory-check') fail('unsafe entrypoint policy is missing.')
|
||||
console.log(JSON.stringify({ status: 'entrypoint-inventory-pass', formatCount: inventory.formats.length, entrypointCount: inventory.entrypoints.length, moduleCount: inventory.moduleCoverage.length, unsupportedCount: [...inventory.formats, ...inventory.entrypoints].filter((entry) => entry.compatibility === 'unsupported').length }, null, 2))
|
||||
100
scripts/check-freecad-exact-history-elementmap-gate.mjs
Normal file
100
scripts/check-freecad-exact-history-elementmap-gate.mjs
Normal file
@@ -0,0 +1,100 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import { createElementMap2NameToken, elementMap2NameTokenToReference, elementMap2SemanticDigest, parseElementMap2, validateElementMap2, writeElementMap2 } from '../src/facade/elementMap2.ts'
|
||||
import { migrateStringHasherSchema, parseStringHasherTable, validateElementMap2StringHasherEvidence, validateStringHasherTable, writeStringHasherTable } from '../src/facade/stringHasher.ts'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const load = async (path) => JSON.parse(await readFile(resolve(root, path), 'utf8'))
|
||||
const gate = await load('config/freecad-exact-history-elementmap-gate.json')
|
||||
const oracle = await load('config/freecad-composite-history-elementmap-oracle.json')
|
||||
const roundtrip = await load('config/freecad-fcstd-roundtrip-verification.json')
|
||||
if (oracle.baselineId !== gate.baselineId || oracle.freecadVersion !== gate.requiredFreecadVersion || oracle.gitCommit !== gate.requiredFreecadCommit || oracle.status !== 'pass' || oracle.summary?.cases !== gate.requiredCases || oracle.summary?.passed !== gate.requiredCases) throw new Error('Exact history/ElementMap2 gate baseline is not the locked FreeCAD 1.1.1 30-case report.')
|
||||
let wrongBindings = 0
|
||||
let unexplainedRelations = 0
|
||||
let roundtripNameDrift = 0
|
||||
let elementMap2ParseFailures = 0
|
||||
let elementMap2SemanticFailures = 0
|
||||
let elementMap2TokenWriterFailures = 0
|
||||
let stringHasherResources = 0
|
||||
let stringHasherParseFailures = 0
|
||||
let stringHasherSemanticFailures = 0
|
||||
let stringHasherEvidenceFailures = 0
|
||||
let namingEvidenceMissing = 0
|
||||
let namingEvidenceBoundaryViolations = 0
|
||||
let nativeEvidenceStages = 0
|
||||
let nativeMappedNameStages = 0
|
||||
let nativeIndexedNameStages = 0
|
||||
let privateTokenEvidenceCompleteStages = 0
|
||||
let internalBuilderEvidenceStages = 0
|
||||
let internalBuilderEvidenceMissingStages = 0
|
||||
const builderStageType = /^(Part::(Fuse|Cut|Common|Extrusion|Revolution|Loft|Sweep|Fillet|Chamfer)|PartDesign::(Pad|Pocket|Revolution|Groove|AdditiveLoft|SubtractiveLoft|AdditivePipe|SubtractivePipe|Fillet|Chamfer|Draft|Thickness|Mirrored|MultiTransform|LinearPattern|PolarPattern|Hole))$/
|
||||
let resources = 0
|
||||
for (const fixture of oracle.cases) {
|
||||
if (fixture.roundtripNameDrift !== 0) roundtripNameDrift += 1
|
||||
const knownObjects = new Set(fixture.stages.map((stage) => stage.name))
|
||||
for (const stage of fixture.stages) {
|
||||
if (!['final-shape-only', 'opaque-preserved', 'native-evidence', 'ambiguous', 'missing'].includes(stage.namingEvidenceStatus)) namingEvidenceMissing += 1
|
||||
if (stage.namingEvidenceStatus === 'opaque-preserved' && !fixture.stringHasherResource?.text) namingEvidenceBoundaryViolations += 1
|
||||
if (stage.namingEvidenceStatus === 'native-evidence') nativeEvidenceStages += 1
|
||||
if (stage.nativeEvidence?.mappedNameApiEntries > 0) nativeMappedNameStages += 1
|
||||
if (stage.nativeEvidence?.indexedNameApiEntries > 0) nativeIndexedNameStages += 1
|
||||
if (stage.nativeEvidence?.privateTokenEvidenceComplete === true) privateTokenEvidenceCompleteStages += 1
|
||||
if (builderStageType.test(stage.typeId || '')) {
|
||||
if (stage.nativeEvidence?.internalBuilderEvidence === true) internalBuilderEvidenceStages += 1
|
||||
else internalBuilderEvidenceMissingStages += 1
|
||||
}
|
||||
const names = new Set()
|
||||
for (const entry of stage.names) {
|
||||
if (names.has(entry.name)) wrongBindings += 1
|
||||
names.add(entry.name)
|
||||
for (const relation of entry.history || []) {
|
||||
if (!knownObjects.has(relation.object) || typeof relation.mappedName !== 'string') unexplainedRelations += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
let stringHasherTable
|
||||
if (fixture.stringHasherResource?.text !== null && fixture.stringHasherResource?.text !== undefined) {
|
||||
try {
|
||||
stringHasherTable = migrateStringHasherSchema(parseStringHasherTable(fixture.stringHasherResource.text))
|
||||
const validation = validateStringHasherTable(stringHasherTable)
|
||||
if (!validation.valid) stringHasherParseFailures += 1
|
||||
else {
|
||||
stringHasherResources += 1
|
||||
const reparsed = migrateStringHasherSchema(parseStringHasherTable(writeStringHasherTable(stringHasherTable)))
|
||||
if (JSON.stringify(reparsed) !== JSON.stringify(stringHasherTable)) stringHasherSemanticFailures += 1
|
||||
}
|
||||
} catch {
|
||||
stringHasherParseFailures += 1
|
||||
stringHasherTable = undefined
|
||||
}
|
||||
}
|
||||
for (const resource of Object.values(fixture.elementMapResources || {})) {
|
||||
resources += 1
|
||||
try {
|
||||
const document = parseElementMap2(resource.text)
|
||||
const validation = validateElementMap2(document)
|
||||
if (!validation.valid) elementMap2SemanticFailures += 1
|
||||
const hasStringHasherEvidence = document.maps.some((map) => map.sections.some((section) => section.children.some((child) => child.stringIds.length > 0) || section.names.some((name) => name.tokens.some((token) => token.suffix.length > 1 || token.marker === '$'))))
|
||||
if (!stringHasherTable && hasStringHasherEvidence) stringHasherEvidenceFailures += 1
|
||||
if (stringHasherTable) stringHasherEvidenceFailures += validateElementMap2StringHasherEvidence(document, stringHasherTable).length
|
||||
for (const map of document.maps) for (const section of map.sections) for (const entry of section.names) for (const token of entry.tokens) {
|
||||
try {
|
||||
const evidence = elementMap2NameTokenToReference(token, document.postfixes)
|
||||
if (createElementMap2NameToken(evidence, document.postfixes).raw !== token.raw) elementMap2TokenWriterFailures += 1
|
||||
} catch {
|
||||
elementMap2TokenWriterFailures += 1
|
||||
}
|
||||
}
|
||||
const reparsed = parseElementMap2(writeElementMap2(document))
|
||||
if (reparsed.maps.length !== document.maps.length || reparsed.postfixes.length !== document.postfixes.length) elementMap2ParseFailures += 1
|
||||
if (elementMap2SemanticDigest(reparsed) !== elementMap2SemanticDigest(document)) elementMap2SemanticFailures += 1
|
||||
} catch {
|
||||
elementMap2ParseFailures += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const scenario of roundtrip.scenarios || []) {
|
||||
if (scenario.status !== 'pass' || (scenario.differences || []).length !== 0) roundtripNameDrift += 1
|
||||
}
|
||||
if (roundtrip.status !== 'verified' || wrongBindings !== gate.requirements.wrongBindings || unexplainedRelations !== gate.requirements.unexplainedRelations || roundtripNameDrift !== gate.requirements.roundtripNameDrift || elementMap2ParseFailures !== gate.requirements.elementMap2ParseFailures || elementMap2SemanticFailures !== gate.requirements.elementMap2SemanticFailures || elementMap2TokenWriterFailures !== gate.requirements.elementMap2TokenWriterFailures || stringHasherParseFailures !== gate.requirements.stringHasherParseFailures || stringHasherSemanticFailures !== gate.requirements.stringHasherSemanticFailures || stringHasherEvidenceFailures !== gate.requirements.stringHasherEvidenceFailures || namingEvidenceMissing !== gate.requirements.namingEvidenceMissing || namingEvidenceBoundaryViolations !== gate.requirements.namingEvidenceBoundaryViolations) throw new Error(`Exact gate failed: wrongBindings=${wrongBindings}, unexplainedRelations=${unexplainedRelations}, roundtripNameDrift=${roundtripNameDrift}, elementMap2ParseFailures=${elementMap2ParseFailures}, elementMap2SemanticFailures=${elementMap2SemanticFailures}, elementMap2TokenWriterFailures=${elementMap2TokenWriterFailures}, stringHasherParseFailures=${stringHasherParseFailures}, stringHasherSemanticFailures=${stringHasherSemanticFailures}, stringHasherEvidenceFailures=${stringHasherEvidenceFailures}, namingEvidenceMissing=${namingEvidenceMissing}, namingEvidenceBoundaryViolations=${namingEvidenceBoundaryViolations}.`)
|
||||
console.log(JSON.stringify({ status: 'freecad-exact-history-elementmap-gate-pass', exactPromotionReady: false, cases: oracle.summary.cases, resources, stringHasherResources, nativeEvidenceStages, nativeIndexedNameStages, nativeMappedNameStages, indexedOnlyStages: nativeIndexedNameStages - nativeMappedNameStages, privateTokenEvidenceCompleteStages, internalBuilderEvidenceStages, internalBuilderEvidenceMissingStages, wrongBindings, unexplainedRelations, roundtripNameDrift, elementMap2ParseFailures, elementMap2SemanticFailures, elementMap2TokenWriterFailures, stringHasherParseFailures, stringHasherSemanticFailures, stringHasherEvidenceFailures, namingEvidenceMissing, namingEvidenceBoundaryViolations }, null, 2))
|
||||
80
scripts/check-freecad-execution-plan.mjs
Normal file
80
scripts/check-freecad-execution-plan.mjs
Normal file
@@ -0,0 +1,80 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const plan = JSON.parse(await readFile(resolve(root, 'config/freecad-execution-plan.json'), 'utf8'))
|
||||
const fail = (message) => { throw new Error(`FreeCAD execution plan: ${message}`) }
|
||||
const requireText = (value, label) => { if (typeof value !== 'string' || !value.trim()) fail(`${label} must be a non-empty string.`) }
|
||||
const requireTextList = (value, label) => {
|
||||
if (!Array.isArray(value) || value.length === 0) fail(`${label} must be a non-empty array.`)
|
||||
value.forEach((entry, index) => requireText(entry, `${label}[${index}]`))
|
||||
}
|
||||
const uniqueIds = (items, label) => {
|
||||
if (!Array.isArray(items) || items.length === 0) fail(`${label} must be a non-empty array.`)
|
||||
const ids = new Set()
|
||||
for (const item of items) {
|
||||
requireText(item?.id, `${label} id`)
|
||||
if (ids.has(item.id)) fail(`duplicate ${label} id '${item.id}'.`)
|
||||
ids.add(item.id)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
if (plan.schemaVersion !== 1) fail(`unsupported schemaVersion '${plan.schemaVersion}'.`)
|
||||
if (plan.baseline?.freecadVersion !== '1.1.1' || plan.baseline?.freecadCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('baseline does not match the locked FreeCAD reference.')
|
||||
|
||||
const laneIds = uniqueIds(plan.lanes, 'lane')
|
||||
const gateIds = uniqueIds(plan.gates, 'gate')
|
||||
const programIds = uniqueIds(plan.programs, 'program')
|
||||
const requiredPrograms = Array.from({ length: 10 }, (_, index) => `P${String(index + 1).padStart(2, '0')}`)
|
||||
if (requiredPrograms.some((id) => !programIds.has(id)) || plan.programs.length !== requiredPrograms.length) fail('programs must contain exactly P01 through P10.')
|
||||
|
||||
const taskById = new Map()
|
||||
const taskProgram = new Map()
|
||||
const statuses = new Set(['pending', 'in_progress', 'completed', 'blocked'])
|
||||
for (const program of plan.programs) {
|
||||
requireText(program.title, `${program.id}.title`)
|
||||
if (!laneIds.has(program.ownerLane)) fail(`${program.id} uses unknown owner lane '${program.ownerLane}'.`)
|
||||
if (!gateIds.has(program.gate)) fail(`${program.id} uses unknown gate '${program.gate}'.`)
|
||||
if (!Array.isArray(program.dependencies)) fail(`${program.id}.dependencies must be an array.`)
|
||||
for (const dependency of program.dependencies) if (!programIds.has(dependency)) fail(`${program.id} depends on unknown program '${dependency}'.`)
|
||||
if (!Array.isArray(program.tasks) || program.tasks.length === 0) fail(`${program.id} must contain tasks.`)
|
||||
for (const task of program.tasks) {
|
||||
requireText(task.id, `${program.id} task id`)
|
||||
if (taskById.has(task.id)) fail(`duplicate task id '${task.id}'.`)
|
||||
requireText(task.title, `${task.id}.title`)
|
||||
if (!statuses.has(task.status)) fail(`${task.id} has invalid status '${task.status}'.`)
|
||||
if (!Array.isArray(task.dependencies)) fail(`${task.id}.dependencies must be an array.`)
|
||||
requireTextList(task.deliverables, `${task.id}.deliverables`)
|
||||
requireTextList(task.acceptance, `${task.id}.acceptance`)
|
||||
taskById.set(task.id, task)
|
||||
taskProgram.set(task.id, program.id)
|
||||
}
|
||||
}
|
||||
|
||||
for (const task of taskById.values()) for (const dependency of task.dependencies) if (!taskById.has(dependency)) fail(`${task.id} depends on unknown task '${dependency}'.`)
|
||||
|
||||
const visitState = new Map()
|
||||
const visit = (taskId, path = []) => {
|
||||
const state = visitState.get(taskId)
|
||||
if (state === 'done') return
|
||||
if (state === 'visiting') fail(`task dependency cycle: ${[...path, taskId].join(' -> ')}.`)
|
||||
visitState.set(taskId, 'visiting')
|
||||
for (const dependency of taskById.get(taskId).dependencies) visit(dependency, [...path, taskId])
|
||||
visitState.set(taskId, 'done')
|
||||
}
|
||||
for (const taskId of taskById.keys()) visit(taskId)
|
||||
|
||||
for (const gate of plan.gates) {
|
||||
requireText(gate.title, `${gate.id}.title`)
|
||||
requireTextList(gate.requiredPrograms, `${gate.id}.requiredPrograms`)
|
||||
for (const programId of gate.requiredPrograms) if (!programIds.has(programId)) fail(`${gate.id} references unknown program '${programId}'.`)
|
||||
}
|
||||
for (const program of plan.programs) {
|
||||
const gate = plan.gates.find((candidate) => candidate.id === program.gate)
|
||||
if (!gate.requiredPrograms.includes(program.id)) fail(`${program.gate} does not require ${program.id}.`)
|
||||
}
|
||||
|
||||
requireTextList(plan.rules, 'rules')
|
||||
const statusCounts = Object.fromEntries([...statuses].map((status) => [status, [...taskById.values()].filter((task) => task.status === status).length]))
|
||||
console.log(JSON.stringify({ status: 'execution-plan-pass', programs: plan.programs.length, lanes: plan.lanes.length, gates: plan.gates.length, tasks: taskById.size, statusCounts, taskPrograms: new Set(taskProgram.values()).size }, null, 2))
|
||||
75
scripts/check-freecad-fcstd-roundtrip-verification.mjs
Normal file
75
scripts/check-freecad-fcstd-roundtrip-verification.mjs
Normal file
@@ -0,0 +1,75 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/freecad-fcstd-roundtrip-verification.json'), 'utf8'))
|
||||
const fail = (message) => { throw new Error(`FCStd round-trip verification: ${message}`) }
|
||||
|
||||
if (report.schemaVersion !== 1 || report.baselineId !== 'freecad-1.1.1' || report.freecadCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.bitbybitVersion !== '1.1.1') fail('baseline is not locked to FreeCAD/Bitbybit 1.1.1.')
|
||||
if (report.unknownDifferencesFail !== true || report.status !== 'verified') fail('unknown differences are not release-blocking.')
|
||||
if (JSON.stringify(report.directions) !== '["freecad-web-freecad","web-freecad-web"]') fail('both round-trip directions are required.')
|
||||
if (!Array.isArray(report.scenarios) || report.scenarioCount !== 22 || report.scenarios.length !== report.scenarioCount) fail('exactly 22 native scenarios are required.')
|
||||
|
||||
const expectedIds = new Set([
|
||||
'proxy-byte-preservation',
|
||||
'locked-partdesign-shape',
|
||||
'part-box', 'part-cylinder', 'part-sphere', 'part-ellipsoid', 'part-cone', 'part-torus', 'part-prism', 'part-wedge', 'part-fuse', 'part-cut', 'part-common',
|
||||
'part-sphere-trim',
|
||||
'part-extrusion', 'part-revolution',
|
||||
'sketch-core-external-attachment', 'sketch-face-projection', 'sketch-external-modes',
|
||||
'sketch-snells-law', 'sketch-bspline-weight', 'sketch-internal-alignment',
|
||||
])
|
||||
const seen = new Set()
|
||||
for (const scenario of report.scenarios) {
|
||||
if (!expectedIds.has(scenario.id) || seen.has(scenario.id)) fail(`unexpected or duplicate scenario '${scenario.id}'.`)
|
||||
seen.add(scenario.id)
|
||||
const expectedStages = scenario.id === 'proxy-byte-preservation'
|
||||
? ['freecad-source', 'web-inspect', 'web-byte-preserving-write', 'byte-compare', 'freecad-open', 'freecad-resave', 'web-inspect-resaved']
|
||||
: scenario.direction === 'freecad-web-freecad'
|
||||
? ['freecad-source', 'web-inspect', 'web-write', 'freecad-open', 'freecad-resave', 'web-inspect-resaved']
|
||||
: scenario.direction === 'web-freecad-web'
|
||||
? ['web-model', 'web-write', 'freecad-open', 'freecad-recompute', 'freecad-resave', 'web-inspect']
|
||||
: undefined
|
||||
if (!expectedStages || JSON.stringify(scenario.stages) !== JSON.stringify(expectedStages)) fail(`${scenario.id} has an incomplete execution path.`)
|
||||
if (scenario.freecadVersion !== '1.1.1' || scenario.status !== 'pass' || !Array.isArray(scenario.differences) || scenario.differences.length !== 0) fail(`${scenario.id} has an unclassified difference.`)
|
||||
if (!Array.isArray(scenario.domains) || scenario.domains.length === 0 || !scenario.evidence || typeof scenario.evidence !== 'object') fail(`${scenario.id} has no semantic evidence.`)
|
||||
}
|
||||
if (seen.size !== expectedIds.size) fail('one or more required scenarios are missing.')
|
||||
|
||||
const byId = Object.fromEntries(report.scenarios.map((scenario) => [scenario.id, scenario]))
|
||||
const proxy = byId['proxy-byte-preservation'].evidence
|
||||
if (proxy.sourceArchiveBytes <= 0 || proxy.sourceArchiveBytes !== proxy.preservedArchiveBytes || proxy.sourceSha256 !== proxy.preservedSha256 || !/^[a-f0-9]{64}$/.test(proxy.sourceSha256) || proxy.byteIdentical !== true || proxy.proxyObjectCount !== 8 || proxy.blockedObjectCount !== 0 || proxy.resavedObjectCount !== 17 || proxy.resavedProxyObjectCount !== 8 || proxy.resavedShapeValid !== true || JSON.stringify(proxy.unknownTypeIds) !== '["App::Line","App::Origin","App::Plane","App::Point"]') fail('FreeCAD-origin proxy byte-preservation evidence is incomplete.')
|
||||
const sourceShape = byId['locked-partdesign-shape'].evidence
|
||||
if (sourceShape.solidCount !== 1 || sourceShape.faceCount !== 23 || sourceShape.elementMapPostfixCount !== 63 || sourceShape.elementMapCount !== 2 || sourceShape.stringHasherBytes <= 0) fail('FreeCAD-origin Shape/ElementMap evidence is incomplete.')
|
||||
for (const id of ['part-box', 'part-cylinder', 'part-sphere', 'part-ellipsoid', 'part-cone', 'part-torus', 'part-prism', 'part-wedge', 'part-fuse', 'part-cut', 'part-common']) {
|
||||
const evidence = byId[id].evidence
|
||||
if (evidence.solidCount !== 1 || !Number.isFinite(evidence.volume) || evidence.volume <= 0 || evidence.propertyCount <= 0 || evidence.resavedShapeAvailable !== true) fail(`${id} did not preserve parameters and a valid Shape resource.`)
|
||||
}
|
||||
if (byId['part-fuse'].evidence.refine !== false || byId['part-cut'].evidence.refine !== false || byId['part-common'].evidence.refine !== true) fail('Part Boolean Refine values did not round-trip through FreeCAD.')
|
||||
const torus = byId['part-torus'].evidence
|
||||
const torusBound = 12.988706403508727
|
||||
if (!Number.isFinite(torus.volume) || Math.abs(torus.volume - 60 * Math.PI ** 2) > 1e-8 || torus.propertyCount !== 5 || !torus.boundingBox || torus.boundingBox.min.some((value, index) => Math.abs(value - [-torusBound, -torusBound, -2][index]) > 1e-8) || torus.boundingBox.max.some((value, index) => Math.abs(value - [torusBound, torusBound, 2][index]) > 1e-8)) fail('native Part::Torus evidence is incomplete.')
|
||||
const prism = byId['part-prism'].evidence
|
||||
if (prism.typeId !== 'Part::Prism' || prism.solidCount !== 1 || prism.faceCount !== 8 || !Number.isFinite(prism.volume) || Math.abs(prism.volume - 60 * Math.sqrt(3)) > 1e-8 || prism.propertyCount !== 5 || JSON.stringify(prism.properties) !== '{"Circumradius":2,"FirstAngle":10,"Height":10,"Polygon":6,"SecondAngle":-5}' || !prism.boundingBox || prism.boundingBox.min.some((value, index) => Math.abs(value - [-2, -2.6069374428281185, 0][index]) > 1e-8) || prism.boundingBox.max.some((value, index) => Math.abs(value - [3.76326980708465, 1.7320508075688776, 10][index]) > 1e-8)) fail('native Part::Prism evidence is incomplete.')
|
||||
const wedge = byId['part-wedge'].evidence
|
||||
if (wedge.typeId !== 'Part::Wedge' || wedge.solidCount !== 1 || wedge.faceCount !== 6 || !Number.isFinite(wedge.volume) || Math.abs(wedge.volume - 2440 / 3) > 1e-8 || wedge.propertyCount !== 10 || JSON.stringify(wedge.properties) !== '{"X2max":8,"X2min":0,"Xmax":10,"Xmin":0,"Ymax":10,"Ymin":0,"Z2max":8,"Z2min":0,"Zmax":10,"Zmin":0}' || wedge.resavedShapeAvailable !== true) fail('native Part::Wedge evidence is incomplete.')
|
||||
const ellipsoid = byId['part-ellipsoid'].evidence
|
||||
if (ellipsoid.typeId !== 'Part::Ellipsoid' || ellipsoid.solidCount !== 1 || ellipsoid.faceCount !== 1 || !Number.isFinite(ellipsoid.volume) || Math.abs(ellipsoid.volume - 133.9826640573845) > 1e-8 || ellipsoid.propertyCount !== 6 || JSON.stringify(ellipsoid.properties) !== '{"Angle1":-90,"Angle2":90,"Angle3":360,"Radius1":2,"Radius2":4,"Radius3":0}' || ellipsoid.resavedShapeAvailable !== true || !ellipsoid.boundingBox || ellipsoid.boundingBox.min.some((value, index) => Math.abs(value - [-8, -6.92820323027551, -2][index]) > 1e-8) || ellipsoid.boundingBox.max.some((value, index) => Math.abs(value - [4, 6.928203230275507, 2][index]) > 1e-8)) fail('native Part::Ellipsoid evidence is incomplete.')
|
||||
const sphereTrim = byId['part-sphere-trim'].evidence
|
||||
if (sphereTrim.solidCount !== 1 || !Number.isFinite(sphereTrim.volume) || sphereTrim.volume <= 0 || JSON.stringify(sphereTrim.properties) !== '{"Angle1":-45,"Angle2":45,"Angle3":120,"Radius":5}' || sphereTrim.resavedShapeAvailable !== true || !sphereTrim.boundingBox || sphereTrim.boundingBox.min.length !== 3 || sphereTrim.boundingBox.max.length !== 3) fail('native Part::Sphere trim evidence is incomplete.')
|
||||
const extrusion = byId['part-extrusion'].evidence
|
||||
if (extrusion.typeId !== 'Part::Extrusion' || extrusion.solidCount !== 1 || !Number.isFinite(extrusion.volume) || Math.abs(extrusion.volume - 30) > 1e-8 || JSON.stringify(extrusion.properties) !== '{"Base":"FeatureProfile","DirMode":"Custom","LengthFwd":5,"LengthRev":0,"Reversed":false,"Solid":true,"Symmetric":false,"TaperAngle":0,"TaperAngleRev":0}' || extrusion.resavedShapeAvailable !== true) fail('native Part::Extrusion evidence is incomplete.')
|
||||
const revolution = byId['part-revolution'].evidence
|
||||
if (revolution.typeId !== 'Part::Revolution' || revolution.solidCount !== 1 || !Number.isFinite(revolution.volume) || Math.abs(revolution.volume - 36 * Math.PI) > 1e-8 || JSON.stringify(revolution.properties) !== '{"Angle":360,"Solid":true,"Source":"FeatureProfile","Symmetric":false}' || revolution.resavedShapeAvailable !== true) fail('native Part::Revolution evidence is incomplete.')
|
||||
const coreSketch = byId['sketch-core-external-attachment'].evidence
|
||||
if (coreSketch.geometryCount !== 10 || coreSketch.constraintCount !== 11 || JSON.stringify(coreSketch.externalTypes) !== '[0]' || coreSketch.mapMode !== 'FlatFace' || coreSketch.roundTripGeometryIds !== 10 || coreSketch.roundTripConstraintIds !== 11) fail('core Sketch round-trip evidence is incomplete.')
|
||||
const modeSketch = byId['sketch-external-modes'].evidence
|
||||
if (JSON.stringify(modeSketch.externalTypes) !== '[1,2,0]' || modeSketch.externalGeoCount !== 8 || JSON.stringify(modeSketch.nativeFlags) !== '[1,1,1,1,1,2]' || JSON.stringify(modeSketch.roundTripModes) !== '["intersection","both","both","both","both","projection"]') fail('external geometry mode/flag evidence is incomplete.')
|
||||
if (byId['sketch-face-projection'].evidence.roundTripIds !== 4 || byId['sketch-snells-law'].evidence.roundTripConstraints !== 1 || byId['sketch-bspline-weight'].evidence.roundTripConstraints !== 1 || byId['sketch-internal-alignment'].evidence.roundTripConstraints !== 5) fail('advanced Sketcher round-trip evidence is incomplete.')
|
||||
|
||||
console.log(JSON.stringify({
|
||||
status: 'freecad-fcstd-roundtrip-verification-pass',
|
||||
scenarioCount: report.scenarioCount,
|
||||
directions: report.directions,
|
||||
unknownDifferences: 0,
|
||||
}, null, 2))
|
||||
279
scripts/check-freecad-golden-coverage.mjs
Normal file
279
scripts/check-freecad-golden-coverage.mjs
Normal file
@@ -0,0 +1,279 @@
|
||||
import { readdir, readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const load = async (file) => JSON.parse(await readFile(resolve(root, file), 'utf8'))
|
||||
const fail = (message) => { throw new Error(`FreeCAD golden coverage: ${message}`) }
|
||||
const lockedCommit = '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d'
|
||||
|
||||
const plan = await load('config/freecad-golden-coverage-plan.json')
|
||||
if (plan.schemaVersion !== 1 || plan.baselineId !== 'freecad-1.1.1' || plan.sourceCommit !== lockedCommit) fail('coverage plan is not locked to FreeCAD 1.1.1.')
|
||||
if (plan.parameterMutationPlan !== 'config/freecad-parameter-mutation-plan.json') fail('coverage plan must reference the fail-closed parameter mutation matrix.')
|
||||
const parameterMutationPlan = await load(plan.parameterMutationPlan)
|
||||
if (parameterMutationPlan.schemaVersion !== 1 || parameterMutationPlan.baselineId !== plan.baselineId || parameterMutationPlan.sourceCommit !== lockedCommit || parameterMutationPlan.failClosed !== true || !Array.isArray(parameterMutationPlan.dimensions) || parameterMutationPlan.dimensions.length !== 5) fail('parameter mutation plan is not locked to the same baseline or five-dimensional fail-closed contract.')
|
||||
if (!Array.isArray(plan.dimensions) || new Set(plan.dimensions).size !== plan.dimensions.length) fail('dimensions must be a unique array.')
|
||||
if (!Array.isArray(plan.families) || plan.families.length < 30) fail('coverage plan must enumerate every supported Part/PartDesign family.')
|
||||
const familyIds = new Set()
|
||||
for (const family of plan.families) {
|
||||
if (!family || typeof family.id !== 'string' || familyIds.has(family.id)) fail(`invalid or duplicate family ${family?.id || '<unknown>'}.`)
|
||||
if (!Array.isArray(family.operationTypes) || family.operationTypes.length === 0) fail(`${family.id} has no operation type mapping.`)
|
||||
for (const field of ['stageTypeIds', 'roundtripIds', 'browserOperations']) if (!Array.isArray(family[field])) fail(`${family.id}.${field} must be an array.`)
|
||||
if (family.oracleSuccessSources !== undefined && !Array.isArray(family.oracleSuccessSources)) fail(`${family.id}.oracleSuccessSources must be an array.`)
|
||||
for (const source of family.oracleSuccessSources || []) if (typeof source?.file !== 'string' || !Array.isArray(source.ids) || source.ids.length === 0 || source.ids.some((id) => typeof id !== 'string')) fail(`${family.id} has an invalid oracle success source.`)
|
||||
familyIds.add(family.id)
|
||||
}
|
||||
|
||||
const oracleFiles = [...new Set(plan.families.flatMap((family) => (family.oracleSuccessSources || []).map((source) => source.file)))]
|
||||
const oracleReports = new Map(await Promise.all(oracleFiles.map(async (file) => [file, await load(file)])))
|
||||
const oracleSuccessCounts = new Map()
|
||||
for (const family of plan.families) for (const source of family.oracleSuccessSources || []) {
|
||||
const report = oracleReports.get(source.file)
|
||||
if (report?.schemaVersion !== 1 || report.status !== 'pass' || report.freecadVersion !== '1.1.1' || report.gitCommit !== lockedCommit || !Array.isArray(report.cases)) fail(`${source.file} is not a locked FreeCAD oracle report.`)
|
||||
for (const id of source.ids) {
|
||||
const fixture = report.cases.find((candidate) => candidate.id === id)
|
||||
if (!fixture || fixture.passed !== true || fixture.status !== 'Valid' || (fixture.typeId && fixture.typeId !== family.id)) fail(`${source.file}/${id} is not valid success evidence for ${family.id}.`)
|
||||
oracleSuccessCounts.set(family.id, (oracleSuccessCounts.get(family.id) || 0) + 1)
|
||||
}
|
||||
}
|
||||
|
||||
const successManifest = await load(plan.successManifest)
|
||||
const failureManifest = await load(plan.failureManifest)
|
||||
const supplementalSuccessManifest = await load(plan.supplementalSuccessManifest)
|
||||
const supplementalFailureManifest = await load(plan.supplementalFailureManifest)
|
||||
const goldenVerification = await load('config/freecad-golden-verification.json')
|
||||
const partdesignFailureOracle = await load(plan.partdesignFailureOracle)
|
||||
const partdesignRevolutionGrooveOracle = await load(plan.partdesignRevolutionGrooveOracle)
|
||||
const partdesignTransformOracle = await load(plan.partdesignTransformOracle)
|
||||
const partBuildersOracle = await load(plan.partBuildersOracle)
|
||||
if (successManifest.schemaVersion !== 1 || successManifest.baselineId !== plan.baselineId || !Array.isArray(successManifest.scenarios)) fail('success manifest baseline or shape is invalid.')
|
||||
if (failureManifest.schemaVersion !== 1 || failureManifest.baselineId !== plan.baselineId || !Array.isArray(failureManifest.failures)) fail('failure manifest baseline or shape is invalid.')
|
||||
if (supplementalSuccessManifest.schemaVersion !== 1 || supplementalSuccessManifest.baselineId !== plan.baselineId || !Array.isArray(supplementalSuccessManifest.scenarios)) fail('supplemental success manifest baseline or shape is invalid.')
|
||||
if (supplementalFailureManifest.schemaVersion !== 1 || supplementalFailureManifest.baselineId !== plan.baselineId || !Array.isArray(supplementalFailureManifest.failures)) fail('supplemental failure manifest baseline or shape is invalid.')
|
||||
if (goldenVerification.schemaVersion !== 1 || goldenVerification.baselineId !== plan.baselineId || goldenVerification.sourceCommit !== lockedCommit) fail('golden verification config is not locked to the same baseline.')
|
||||
if (partdesignFailureOracle.schemaVersion !== 1 || partdesignFailureOracle.baselineId !== 'freecad-1.1.1-partdesign-failure-oracle' || partdesignFailureOracle.freecadVersion !== '1.1.1' || partdesignFailureOracle.gitCommit !== lockedCommit || partdesignFailureOracle.status !== 'pass' || !Array.isArray(partdesignFailureOracle.cases) || partdesignFailureOracle.summary?.rejected !== 13 || partdesignFailureOracle.summary?.acceptedEmpty !== 4) fail('PartDesign failure oracle baseline or tri-state summary is invalid.')
|
||||
const partdesignFailureCounts = new Map(partdesignFailureOracle.cases.filter((fixture) => fixture.observed === 'rejected' && fixture.passed === true).map((fixture) => [fixture.typeId, 1]))
|
||||
if (partdesignRevolutionGrooveOracle.schemaVersion !== 1 || partdesignRevolutionGrooveOracle.baselineId !== 'freecad-1.1.1-partdesign-revolution-groove-oracle' || partdesignRevolutionGrooveOracle.freecadVersion !== '1.1.1' || partdesignRevolutionGrooveOracle.gitCommit !== lockedCommit || partdesignRevolutionGrooveOracle.status !== 'pass' || partdesignRevolutionGrooveOracle.summary?.passed !== 2 || !Array.isArray(partdesignRevolutionGrooveOracle.cases)) fail('PartDesign Revolution/Groove oracle baseline or summary is invalid.')
|
||||
const partdesignFeatureSuccessCounts = new Map(partdesignRevolutionGrooveOracle.cases.filter((fixture) => fixture.passed === true && fixture.shapeValid === true && fixture.solids === 1 && fixture.volume > 0).map((fixture) => [fixture.typeId, 1]))
|
||||
if (partdesignTransformOracle.schemaVersion !== 1 || partdesignTransformOracle.baselineId !== 'freecad-1.1.1-partdesign-transform-oracle' || partdesignTransformOracle.freecadVersion !== '1.1.1' || partdesignTransformOracle.gitCommit !== lockedCommit || partdesignTransformOracle.status !== 'pass' || partdesignTransformOracle.summary?.successPassed !== 6 || partdesignTransformOracle.summary?.failurePassed !== 4 || partdesignTransformOracle.summary?.rejected !== 4 || partdesignTransformOracle.summary?.acceptedEmpty !== 0 || !Array.isArray(partdesignTransformOracle.cases)) fail('PartDesign transform oracle baseline or failure summary is invalid.')
|
||||
const partdesignTransformFailureCounts = new Map(partdesignTransformOracle.cases.filter((fixture) => fixture.passed === true && fixture.observed === 'rejected' && fixture.state?.includes('Invalid')).map((fixture) => [fixture.typeId, 1]))
|
||||
if (partBuildersOracle.schemaVersion !== 1 || partBuildersOracle.baselineId !== 'freecad-1.1.1-part-builders-oracle' || partBuildersOracle.freecadVersion !== '1.1.1' || partBuildersOracle.gitCommit !== lockedCommit || partBuildersOracle.status !== 'pass' || partBuildersOracle.summary?.successPassed !== 6 || partBuildersOracle.summary?.failurePassed !== 6 || partBuildersOracle.summary?.rejected !== 6 || partBuildersOracle.summary?.acceptedEmpty !== 0 || !Array.isArray(partBuildersOracle.successCases) || !Array.isArray(partBuildersOracle.failureCases)) fail('Part builders oracle baseline or summary is invalid.')
|
||||
const partBuilderSuccessCounts = new Map(partBuildersOracle.successCases.filter((fixture) => fixture.passed === true && fixture.observed === 'success' && fixture.shapeValid === true && fixture.solids === 1).map((fixture) => [fixture.typeId, 1]))
|
||||
const partBuilderFailureCounts = new Map(partBuildersOracle.failureCases.filter((fixture) => fixture.passed === true && fixture.observed === 'rejected' && fixture.shapeNull === true).map((fixture) => [fixture.typeId, 1]))
|
||||
|
||||
const countTopLevelOperations = (manifest, directory, entriesKey) => {
|
||||
const counts = new Map()
|
||||
return Promise.all(manifest[entriesKey].map(async (entry) => {
|
||||
if (!entry || typeof entry.id !== 'string' || typeof entry.file !== 'string') fail(`invalid ${entriesKey} manifest entry.`)
|
||||
const fixture = await load(`${directory}/${entry.file}`)
|
||||
if (fixture.id !== entry.id || !fixture.operation || typeof fixture.operation.type !== 'string') fail(`${entry.id} has no operation payload.`)
|
||||
const type = fixture.operation.type
|
||||
counts.set(type, (counts.get(type) || 0) + 1)
|
||||
return fixture
|
||||
})).then((fixtures) => ({ fixtures, counts }))
|
||||
}
|
||||
|
||||
const [{ fixtures: mainSuccessFixtures, counts: mainSuccessCounts }, { fixtures: mainFailureFixtures, counts: mainFailureCounts }, { fixtures: supplementalSuccessFixtures, counts: supplementalSuccessCounts }, { fixtures: supplementalFailureFixtures, counts: supplementalFailureCounts }] = await Promise.all([
|
||||
countTopLevelOperations(successManifest, 'fixtures/freecad-golden', 'scenarios'),
|
||||
countTopLevelOperations(failureManifest, 'fixtures/freecad-golden/failures', 'failures'),
|
||||
countTopLevelOperations(supplementalSuccessManifest, 'fixtures/freecad-golden/feature-families', 'scenarios'),
|
||||
countTopLevelOperations(supplementalFailureManifest, 'fixtures/freecad-golden/feature-families/failures', 'failures'),
|
||||
])
|
||||
const mergeCounts = (first, second) => {
|
||||
const merged = new Map(first)
|
||||
for (const [type, count] of second) merged.set(type, (merged.get(type) || 0) + count)
|
||||
return merged
|
||||
}
|
||||
const successFixtures = [...mainSuccessFixtures, ...supplementalSuccessFixtures]
|
||||
const failureFixtures = [...mainFailureFixtures, ...supplementalFailureFixtures]
|
||||
const successCounts = mergeCounts(mainSuccessCounts, supplementalSuccessCounts)
|
||||
const failureCounts = mergeCounts(mainFailureCounts, supplementalFailureCounts)
|
||||
if (goldenVerification.successFixtures?.count !== mainSuccessFixtures.length || goldenVerification.failureFixtures?.count !== mainFailureFixtures.length || goldenVerification.supplementalFixtures?.successCount !== supplementalSuccessFixtures.length || goldenVerification.supplementalFixtures?.failureCount !== supplementalFailureFixtures.length) fail('golden verification fixture counts are stale.')
|
||||
|
||||
const composite = await load(plan.compositeOracle)
|
||||
if (composite.schemaVersion !== 1 || composite.baselineId !== 'freecad-1.1.1-composite-history-elementmap2' || composite.freecadVersion !== '1.1.1' || composite.gitCommit !== lockedCommit || composite.status !== 'pass' || !Array.isArray(composite.cases)) fail('composite history oracle baseline is invalid.')
|
||||
const stageCounts = new Map()
|
||||
const elementMapCaseCounts = new Map()
|
||||
let compositeStages = 0
|
||||
let nativeEvidenceStages = 0
|
||||
let finalShapeOnlyStages = 0
|
||||
let opaquePreservedStages = 0
|
||||
let ambiguousStages = 0
|
||||
let missingEvidenceStages = 0
|
||||
let nativeEvidenceValidatedStages = 0
|
||||
let nativeMappedNameStages = 0
|
||||
let nativeIndexedNameStages = 0
|
||||
let privateTokenEvidenceCompleteStages = 0
|
||||
let internalBuilderEvidenceStages = 0
|
||||
let internalBuilderEvidenceMissingStages = 0
|
||||
const builderStageType = /^(Part::(Fuse|Cut|Common|Extrusion|Revolution|Loft|Sweep|Fillet|Chamfer)|PartDesign::(Pad|Pocket|Revolution|Groove|AdditiveLoft|SubtractiveLoft|AdditivePipe|SubtractivePipe|Fillet|Chamfer|Draft|Thickness|Mirrored|MultiTransform|LinearPattern|PolarPattern|Hole))$/
|
||||
for (const fixture of composite.cases) {
|
||||
if (fixture.status !== 'pass' || !Array.isArray(fixture.stages) || fixture.stages.length === 0) fail(`composite fixture ${fixture.id || '<unknown>'} is incomplete.`)
|
||||
const hasElementMap = Object.keys(fixture.elementMapResources || {}).length > 0
|
||||
for (const stage of fixture.stages) {
|
||||
if (typeof stage.typeId !== 'string' || !stage.typeId) continue
|
||||
compositeStages += 1
|
||||
stageCounts.set(stage.typeId, (stageCounts.get(stage.typeId) || 0) + 1)
|
||||
if (hasElementMap) elementMapCaseCounts.set(stage.typeId, (elementMapCaseCounts.get(stage.typeId) || 0) + 1)
|
||||
if (stage.namingEvidenceStatus === 'native-evidence') nativeEvidenceStages += 1
|
||||
else if (stage.namingEvidenceStatus === 'final-shape-only') finalShapeOnlyStages += 1
|
||||
else if (stage.namingEvidenceStatus === 'opaque-preserved') opaquePreservedStages += 1
|
||||
else if (stage.namingEvidenceStatus === 'ambiguous') ambiguousStages += 1
|
||||
else missingEvidenceStages += 1
|
||||
if (stage.nativeEvidence?.stageId === stage.name && stage.nativeEvidence?.resultObjectId === stage.name && stage.nativeEvidence?.status === stage.namingEvidenceStatus) nativeEvidenceValidatedStages += 1
|
||||
if (stage.nativeEvidence?.mappedNameApiEntries > 0) nativeMappedNameStages += 1
|
||||
if (stage.nativeEvidence?.indexedNameApiEntries > 0) nativeIndexedNameStages += 1
|
||||
if (stage.nativeEvidence?.privateTokenEvidenceComplete === true) privateTokenEvidenceCompleteStages += 1
|
||||
if (builderStageType.test(stage.typeId || '')) {
|
||||
if (stage.nativeEvidence?.internalBuilderEvidence === true) internalBuilderEvidenceStages += 1
|
||||
else if (stage.namingEvidenceStatus === 'native-evidence') internalBuilderEvidenceMissingStages += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
let elementMapResources = 0
|
||||
let stringHasherResources = 0
|
||||
for (const fixture of composite.cases) {
|
||||
elementMapResources += Object.keys(fixture.elementMapResources || {}).length
|
||||
if (fixture.stringHasherResource?.text) stringHasherResources += 1
|
||||
}
|
||||
|
||||
const roundtrip = await load(plan.fcstdRoundtrip)
|
||||
if (roundtrip.schemaVersion !== 1 || roundtrip.baselineId !== plan.baselineId || roundtrip.freecadCommit !== lockedCommit || roundtrip.status !== 'verified' || !Array.isArray(roundtrip.scenarios)) fail('FCStd round-trip report baseline or shape is invalid.')
|
||||
const roundtripPassIds = new Set(roundtrip.scenarios.filter((scenario) => scenario.status === 'pass' && (!scenario.differences || scenario.differences.length === 0)).map((scenario) => scenario.id))
|
||||
|
||||
const browserGolden = await load(plan.browserGolden)
|
||||
const browserPrimitiveEvidence = await load(plan.browserPrimitiveEvidence)
|
||||
if (browserGolden.schemaVersion !== 1 || browserGolden.baselineId !== plan.baselineId || browserGolden.freecadCommit !== lockedCommit || browserGolden.status !== 'pass' || !Array.isArray(browserGolden.reports)) fail('browser golden report baseline or shape is invalid.')
|
||||
if (browserPrimitiveEvidence.schemaVersion !== 1 || browserPrimitiveEvidence.status !== 'pass' || browserPrimitiveEvidence.browserId !== 'chrome' || !Array.isArray(browserPrimitiveEvidence.operations) || browserPrimitiveEvidence.afterRelease?.shapeCount !== 0 || browserPrimitiveEvidence.afterRelease?.kernelReferenceCount !== 0) fail('browser primitive evidence is invalid or leaked resources.')
|
||||
const browserOperationCounts = new Map()
|
||||
for (const report of browserGolden.reports) {
|
||||
if (report.status !== 'pass' || (report.differences || []).length !== 0 || typeof report.operation !== 'string') continue
|
||||
browserOperationCounts.set(report.operation, (browserOperationCounts.get(report.operation) || 0) + 1)
|
||||
}
|
||||
for (const operation of ['ellipsoid', 'torus', 'prism', 'wedge']) {
|
||||
const probe = browserGolden.productRecompute?.[operation]
|
||||
if (probe?.quality?.structuralValid === true && probe.quality.isNull === false && probe.quality.structuralErrors === 0 && probe.quality.solids === 1 && probe.comparison?.status === 'pass' && (probe.comparison.differences || []).length === 0) {
|
||||
browserOperationCounts.set(operation, (browserOperationCounts.get(operation) || 0) + 1)
|
||||
}
|
||||
}
|
||||
for (const operation of browserPrimitiveEvidence.operations) {
|
||||
if (typeof operation.name !== 'string' || (!(operation.volume > 0) && !(operation.length > 0))) continue
|
||||
browserOperationCounts.set(operation.name, (browserOperationCounts.get(operation.name) || 0) + 1)
|
||||
}
|
||||
|
||||
const historyFiles = (await readdir(resolve(root, 'config'))).filter((file) => file.startsWith('chrome-native-') && file.endsWith('-history-verification.json'))
|
||||
const nativeOperationByFile = {
|
||||
'chrome-native-history-verification.json': ['fuse', 'cut', 'common'],
|
||||
'chrome-native-pad-history-verification.json': ['pad'],
|
||||
'chrome-native-pocket-history-verification.json': ['pocket'],
|
||||
'chrome-native-revolution-history-verification.json': ['revolution'],
|
||||
'chrome-native-groove-history-verification.json': ['groove'],
|
||||
'chrome-native-loft-history-verification.json': ['loft'],
|
||||
'chrome-native-pipe-history-verification.json': ['pipe'],
|
||||
'chrome-native-fillet-history-verification.json': ['fillet'],
|
||||
'chrome-native-chamfer-history-verification.json': ['chamfer'],
|
||||
'chrome-native-hole-history-verification.json': ['hole'],
|
||||
'chrome-native-draft-history-verification.json': ['draft'],
|
||||
'chrome-native-thickness-history-verification.json': ['thickness'],
|
||||
'chrome-native-linear-pattern-history-verification.json': ['linear-pattern'],
|
||||
'chrome-native-polar-pattern-history-verification.json': ['polar-pattern'],
|
||||
'chrome-native-mirrored-history-verification.json': ['mirrored'],
|
||||
'chrome-native-multi-transform-history-verification.json': ['multi-transform'],
|
||||
}
|
||||
const nativeOperations = new Map()
|
||||
for (const file of historyFiles) {
|
||||
const report = await load(`config/${file}`)
|
||||
if (report.status !== 'pass' || report.nativeCapabilities?.availability !== 'available') continue
|
||||
const history = report.history || {}
|
||||
const recordCount = Number.isInteger(history.recordCount) ? history.recordCount : report.execution?.recordCount
|
||||
if (!Number.isInteger(recordCount) || recordCount <= 0) continue
|
||||
for (const operation of nativeOperationByFile[file] || []) nativeOperations.set(operation, (nativeOperations.get(operation) || 0) + 1)
|
||||
}
|
||||
const nativeAliases = new Map([
|
||||
['partdesign-revolution', 'revolution'], ['partdesign-fillet', 'fillet'], ['partdesign-chamfer', 'chamfer'],
|
||||
['additive-loft', 'loft'], ['subtractive-loft', 'loft'], ['additive-pipe', 'pipe'], ['subtractive-pipe', 'pipe'],
|
||||
])
|
||||
const nativeDimensionOperations = new Set(['fuse', 'cut', 'common', 'pad', 'pocket', 'revolution', 'groove', 'loft', 'pipe', 'fillet', 'chamfer', 'hole', 'draft', 'thickness', 'linear-pattern', 'polar-pattern', 'mirrored', 'multi-transform'])
|
||||
|
||||
const countFor = (family) => {
|
||||
const operationTypes = new Set(family.operationTypes)
|
||||
const nativeTypes = new Set([...operationTypes].map((type) => nativeAliases.get(type) || type))
|
||||
const success = [...operationTypes].reduce((sum, type) => sum + (successCounts.get(type) || 0), 0) + (oracleSuccessCounts.get(family.id) || 0) + (partdesignFeatureSuccessCounts.get(family.id) || 0) + (partBuilderSuccessCounts.get(family.id) || 0)
|
||||
const failure = [...operationTypes].reduce((sum, type) => sum + (failureCounts.get(type) || 0), 0) + (partdesignFailureCounts.get(family.id) || 0) + (partdesignTransformFailureCounts.get(family.id) || 0) + (partBuilderFailureCounts.get(family.id) || 0)
|
||||
const native = [...nativeTypes].reduce((sum, type) => sum + (nativeOperations.get(type) || 0), 0)
|
||||
const stage = family.stageTypeIds.reduce((sum, type) => sum + (stageCounts.get(type) || 0), 0)
|
||||
const elementMap = family.stageTypeIds.reduce((sum, type) => sum + (elementMapCaseCounts.get(type) || 0), 0)
|
||||
const roundtripCount = family.roundtripIds.filter((id) => roundtripPassIds.has(id)).length
|
||||
const browser = family.browserOperations.reduce((sum, type) => sum + (browserOperationCounts.get(type) || 0), 0)
|
||||
const required = ['success-fixture', 'failure-fixture']
|
||||
if (family.roundtripIds.length > 0) required.push('fcstd-roundtrip')
|
||||
if (family.stageTypeIds.length > 0) required.push('composite-stage', 'elementmap2-resource')
|
||||
if ([...nativeTypes].some((type) => nativeDimensionOperations.has(type))) required.push('native-history')
|
||||
if (family.browserOperations.length > 0) required.push('browser-replay')
|
||||
const counts = {
|
||||
'success-fixture': success,
|
||||
'failure-fixture': failure,
|
||||
'native-history': native,
|
||||
'composite-stage': stage,
|
||||
'elementmap2-resource': elementMap,
|
||||
'fcstd-roundtrip': roundtripCount,
|
||||
'browser-replay': browser,
|
||||
}
|
||||
const missing = required.filter((dimension) => counts[dimension] < 1)
|
||||
return { id: family.id, required, counts, complete: missing.length === 0, missing }
|
||||
}
|
||||
|
||||
const families = plan.families.map(countFor)
|
||||
const completeFamilies = families.filter((family) => family.complete).length
|
||||
const blockers = families.filter((family) => !family.complete).map((family) => ({ family: family.id, missing: family.missing }))
|
||||
const globalBlockers = []
|
||||
if (nativeEvidenceStages === 0) globalBlockers.push('composite oracle has no native-evidence stage; native topology identity remains unavailable')
|
||||
if (nativeEvidenceValidatedStages !== nativeEvidenceStages) globalBlockers.push(`${nativeEvidenceStages - nativeEvidenceValidatedStages} native-evidence stages failed runtime evidence identity validation`)
|
||||
if (nativeIndexedNameStages !== nativeEvidenceStages) globalBlockers.push(`${nativeEvidenceStages - nativeIndexedNameStages} native-evidence stages have no direct FreeCAD indexed-name identity`)
|
||||
if (privateTokenEvidenceCompleteStages < nativeEvidenceStages) globalBlockers.push(`${nativeEvidenceStages - privateTokenEvidenceCompleteStages} native stages do not have complete private MappedName token evidence; IndexedName cannot be promoted to a private token`)
|
||||
if (internalBuilderEvidenceMissingStages > 0) globalBlockers.push(`${internalBuilderEvidenceMissingStages} composite builder stages have no native intermediate builder evidence; final geometry cannot reconstruct private intermediate history`)
|
||||
if (missingEvidenceStages > 0) globalBlockers.push(`${missingEvidenceStages} composite stages have missing naming evidence status`)
|
||||
if (elementMapResources === 0) globalBlockers.push('composite oracle has no ElementMap2 resources')
|
||||
if (stringHasherResources === 0) globalBlockers.push('composite oracle has no StringHasher resources')
|
||||
const sortedObject = (map) => Object.fromEntries([...map.entries()].sort(([left], [right]) => left.localeCompare(right)))
|
||||
const detailed = process.argv.includes('--details')
|
||||
const result = {
|
||||
status: 'freecad-golden-coverage-pass',
|
||||
baseline: { id: plan.baselineId, commit: plan.sourceCommit },
|
||||
exactPromotionReady: false,
|
||||
totals: {
|
||||
successFixtures: successFixtures.length,
|
||||
failureFixtures: failureFixtures.length,
|
||||
primarySuccessFixtures: mainSuccessFixtures.length,
|
||||
primaryFailureFixtures: mainFailureFixtures.length,
|
||||
supplementalSuccessFixtures: supplementalSuccessFixtures.length,
|
||||
supplementalFailureFixtures: supplementalFailureFixtures.length,
|
||||
compositeCases: composite.cases.length,
|
||||
compositeStages,
|
||||
nativeEvidenceStages,
|
||||
nativeEvidenceValidatedStages,
|
||||
nativeIndexedNameStages,
|
||||
nativeMappedNameStages,
|
||||
indexedOnlyStages: nativeIndexedNameStages - nativeMappedNameStages,
|
||||
privateTokenEvidenceCompleteStages,
|
||||
internalBuilderEvidenceStages,
|
||||
internalBuilderEvidenceMissingStages,
|
||||
finalShapeOnlyStages,
|
||||
opaquePreservedStages,
|
||||
ambiguousStages,
|
||||
missingEvidenceStages,
|
||||
elementMapResources,
|
||||
stringHasherResources,
|
||||
roundtripScenarios: roundtrip.scenarios.length,
|
||||
roundtripPassScenarios: roundtripPassIds.size,
|
||||
browserGoldenReports: browserGolden.reports.length,
|
||||
browserPrimitiveOperations: browserPrimitiveEvidence.operations.length,
|
||||
nativeOracleSuccessCases: [...oracleSuccessCounts.values(), ...partdesignFeatureSuccessCounts.values(), ...partBuilderSuccessCounts.values()].reduce((sum, count) => sum + count, 0),
|
||||
nativeOracleFailureCases: [...partdesignFailureCounts.values(), ...partdesignTransformFailureCounts.values(), ...partBuilderFailureCounts.values()].reduce((sum, count) => sum + count, 0),
|
||||
},
|
||||
operationCounts: { success: sortedObject(successCounts), failure: sortedObject(failureCounts), oracleSuccess: sortedObject(new Map([...oracleSuccessCounts, ...partdesignFeatureSuccessCounts, ...partBuilderSuccessCounts])), oracleFailure: sortedObject(new Map([...partdesignFailureCounts, ...partdesignTransformFailureCounts, ...partBuilderFailureCounts])), nativeHistory: sortedObject(nativeOperations), browserReplay: sortedObject(browserOperationCounts) },
|
||||
families: { total: families.length, complete: completeFamilies, incomplete: families.length - completeFamilies, blockerCount: blockers.length },
|
||||
missingDimensions: Object.fromEntries([...new Set(blockers.flatMap(({ missing }) => missing))].sort().map((dimension) => [dimension, blockers.filter((family) => family.missing.includes(dimension)).length])),
|
||||
blockers: globalBlockers,
|
||||
}
|
||||
if (detailed) result.familyBlockers = blockers
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
27
scripts/check-freecad-golden-fixtures.mjs
Normal file
27
scripts/check-freecad-golden-fixtures.mjs
Normal file
@@ -0,0 +1,27 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import { loadGoldenManifest, validateGoldenOperation } from './freecad-golden-contract.mjs'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const successManifest = await loadGoldenManifest(resolve(root, 'fixtures/freecad-golden/manifest.json'))
|
||||
if (successManifest.scenarios.length !== 100) throw new Error(`FreeCAD golden fixtures: expected 100 success fixtures, found ${successManifest.scenarios.length}.`)
|
||||
const validateFailureManifest = async (manifestFile, expectedCount) => {
|
||||
const failureManifest = JSON.parse(await readFile(manifestFile, 'utf8'))
|
||||
if (failureManifest.schemaVersion !== 1 || failureManifest.baselineId !== 'freecad-1.1.1' || !Array.isArray(failureManifest.failures) || failureManifest.failures.length !== expectedCount) throw new Error(`FreeCAD golden fixtures: expected ${expectedCount} failure fixtures at the locked baseline.`)
|
||||
const failureIds = new Set()
|
||||
for (const entry of failureManifest.failures) {
|
||||
if (!entry.id || failureIds.has(entry.id) || typeof entry.file !== 'string') throw new Error(`FreeCAD golden fixtures: invalid failure manifest entry ${entry.id || '<unknown>'}.`)
|
||||
failureIds.add(entry.id)
|
||||
const fixture = JSON.parse(await readFile(resolve(manifestFile, '..', entry.file), 'utf8'))
|
||||
if (fixture.id !== entry.id || typeof fixture.expectedError !== 'string' || !fixture.expectedError) throw new Error(`FreeCAD golden fixtures: incomplete failure fixture ${entry.id}.`)
|
||||
let rejected = false
|
||||
try { validateGoldenOperation(fixture.operation, fixture.id) } catch { rejected = true }
|
||||
if (!rejected) throw new Error(`FreeCAD golden fixtures: ${entry.id} is not rejected by the operation contract.`)
|
||||
}
|
||||
return failureManifest
|
||||
}
|
||||
const failureManifest = await validateFailureManifest(resolve(root, 'fixtures/freecad-golden/failures/manifest.json'), 51)
|
||||
const supplementalManifest = await loadGoldenManifest(resolve(root, 'fixtures/freecad-golden/feature-families/manifest.json'))
|
||||
if (supplementalManifest.scenarios.length !== 5) throw new Error(`FreeCAD golden fixtures: expected 5 supplemental feature-family fixtures, found ${supplementalManifest.scenarios.length}.`)
|
||||
const supplementalFailureManifest = await validateFailureManifest(resolve(root, 'fixtures/freecad-golden/feature-families/failures/manifest.json'), 5)
|
||||
console.log(JSON.stringify({ status: 'freecad-golden-fixtures-pass', successCount: successManifest.scenarios.length, failureCount: failureManifest.failures.length, supplementalSuccessCount: supplementalManifest.scenarios.length, supplementalFailureCount: supplementalFailureManifest.failures.length }, null, 2))
|
||||
24
scripts/check-freecad-gui-command-inventory.mjs
Normal file
24
scripts/check-freecad-gui-command-inventory.mjs
Normal file
@@ -0,0 +1,24 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const inventory = JSON.parse(await readFile(resolve(root, 'config/freecad-gui-command-inventory.json'), 'utf8'))
|
||||
const fail = (message) => { throw new Error(`FreeCAD GUI command inventory: ${message}`) }
|
||||
if (inventory.schemaVersion !== 1 || inventory.baseline?.freecadVersion !== '1.1.1' || inventory.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('baseline mismatch.')
|
||||
if (!Number.isInteger(inventory.commandCount) || inventory.commandCount < 900 || !Array.isArray(inventory.commands) || inventory.commands.length !== inventory.commandCount) fail('runtime command inventory is incomplete.')
|
||||
const ids = new Set()
|
||||
for (const command of inventory.commands) {
|
||||
if (!command.id || ids.has(command.id)) fail(`duplicate or empty command '${command.id}'.`)
|
||||
ids.add(command.id)
|
||||
if (!Array.isArray(command.sourceModules) || command.sourceModules.length === 0 || !Array.isArray(command.workbenches) || command.workbenches.length === 0) fail(`${command.id} is missing source/workbench ownership.`)
|
||||
for (const caseName of ['emptySelection', 'selectedPartBox']) {
|
||||
const probeCase = command.cases?.[caseName]
|
||||
if (!probeCase?.observed || !Array.isArray(probeCase.states) || probeCase.states.length === 0) fail(`${command.id} is missing ${caseName} evidence.`)
|
||||
if (probeCase.states.some((state) => !['true', 'false', 'error', 'no-action'].includes(String(state)))) fail(`${command.id} has an invalid ${caseName} state.`)
|
||||
}
|
||||
}
|
||||
if (!Array.isArray(inventory.sourceOnlyCommands)) fail('sourceOnlyCommands must be an array.')
|
||||
for (const command of inventory.sourceOnlyCommands) {
|
||||
if (!command.id || !Array.isArray(command.sourceModules) || !command.reason) fail('source-only command entry is incomplete.')
|
||||
}
|
||||
console.log(JSON.stringify({ status: 'gui-command-inventory-pass', commandCount: inventory.commandCount, sourceOnlyCount: inventory.sourceOnlyCommands.length, workbenchCount: inventory.runtimeEvidence?.workbenchCount }, null, 2))
|
||||
49
scripts/check-freecad-native-naming-evidence.mjs
Normal file
49
scripts/check-freecad-native-naming-evidence.mjs
Normal file
@@ -0,0 +1,49 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import { createElementMap2MultiStageNameMapping, parseElementMap2MultiStageNameMapping, writeElementMap2MultiStageNameMapping } from '../src/facade/elementMap2.ts'
|
||||
import { createNativeStageNamingEvidence, validateNativeNamingEvidence } from '../src/facade/nativeNamingEvidence.ts'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const load = async (path) => JSON.parse(await readFile(resolve(root, path), 'utf8'))
|
||||
const matrix = await load('config/compatibility-matrix.json')
|
||||
const oracle = await load('config/freecad-composite-history-elementmap-oracle.json')
|
||||
const fail = (message) => { throw new Error(`FreeCAD native naming evidence gate: ${message}`) }
|
||||
const blockers = matrix.nativeOcctHistory?.historyProtocol?.namingEvidence?.exactBlockers
|
||||
if (!Array.isArray(blockers) || blockers.length !== 3) fail('exact blocker list must retain private token, missing stage and isomorphic-source boundaries.')
|
||||
const featureLevels = matrix.facadeCapabilities?.geometry?.featureLevels
|
||||
for (const feature of ['pad', 'pocket', 'revolution', 'groove', 'boolean']) {
|
||||
if (!featureLevels?.[feature] || featureLevels[feature].level !== 'compatible' || !Array.isArray(featureLevels[feature].exactBlockedBy) || featureLevels[feature].exactBlockedBy.length === 0) fail(`${feature} must have a non-exact capability level with explicit blockers.`)
|
||||
}
|
||||
let oracleStages = 0
|
||||
let nativeEvidenceStages = 0
|
||||
let nativeMappedNameStages = 0
|
||||
let nativeIndexedNameStages = 0
|
||||
let privateTokenEvidenceCompleteStages = 0
|
||||
let internalBuilderEvidenceStages = 0
|
||||
let internalBuilderEvidenceMissingStages = 0
|
||||
const builderStageType = /^(Part::(Fuse|Cut|Common|Extrusion|Revolution|Loft|Sweep|Fillet|Chamfer)|PartDesign::(Pad|Pocket|Revolution|Groove|AdditiveLoft|SubtractiveLoft|AdditivePipe|SubtractivePipe|Fillet|Chamfer|Draft|Thickness|Mirrored|MultiTransform|LinearPattern|PolarPattern|Hole))$/
|
||||
for (const fixture of oracle.cases) for (const stage of fixture.stages ?? []) {
|
||||
oracleStages += 1
|
||||
if (!['final-shape-only', 'opaque-preserved', 'native-evidence', 'ambiguous', 'missing'].includes(stage.namingEvidenceStatus)) fail(`${fixture.id}/${stage.name} has no explicit naming evidence status.`)
|
||||
if (!stage.nativeEvidence || stage.nativeEvidence.stageId !== stage.name || stage.nativeEvidence.resultObjectId !== stage.name || stage.nativeEvidence.status !== stage.namingEvidenceStatus) fail(`${fixture.id}/${stage.name} has no stage-bound runtime naming evidence.`)
|
||||
const validation = validateNativeNamingEvidence(stage.nativeEvidence)
|
||||
if (!validation.valid) fail(`${fixture.id}/${stage.name} has invalid runtime naming evidence: ${validation.issues[0].path}: ${validation.issues[0].message}`)
|
||||
if (stage.namingEvidenceStatus === 'native-evidence') nativeEvidenceStages += 1
|
||||
if (stage.nativeEvidence.mappedNameApiEntries > 0) nativeMappedNameStages += 1
|
||||
if (stage.nativeEvidence.indexedNameApiEntries > 0) nativeIndexedNameStages += 1
|
||||
if (stage.nativeEvidence.privateTokenEvidenceComplete === true) privateTokenEvidenceCompleteStages += 1
|
||||
if (builderStageType.test(stage.typeId || '')) {
|
||||
if (stage.nativeEvidence.internalBuilderEvidence === true) internalBuilderEvidenceStages += 1
|
||||
else internalBuilderEvidenceMissingStages += 1
|
||||
}
|
||||
}
|
||||
if (oracleStages !== 219 || nativeEvidenceStages !== 219 || nativeIndexedNameStages !== 219) fail(`locked composite oracle must contain 219/219 stage-bound native indexed-name records, found native=${nativeEvidenceStages}, indexed=${nativeIndexedNameStages}, stages=${oracleStages}.`)
|
||||
const exactFeatures = Object.values(featureLevels).filter((feature) => feature.level === 'exact').length
|
||||
if (matrix.systemExactEvaluation?.exact !== false || matrix.systemExactEvaluation?.featureExactCount !== exactFeatures || JSON.stringify(matrix.systemExactEvaluation?.blockers) !== JSON.stringify(blockers)) fail('system exact evaluation must remain false and list the active native naming blockers.')
|
||||
const table = { schemaVersion: 2, nativeVersion: 1, entries: [{ id: 1, flags: 0, relatedIds: [], data: 'native', postfix: '' }] }
|
||||
const stage0 = createNativeStageNamingEvidence({ stageId: 'gate:0', resultObjectId: 'shape:0', status: 'native-evidence', stringHasher: table, mappedNames: [{ kind: 'face', resultIndex: 0, resultPersistentId: 'face:0', relation: 'generated', reference: { name: 'Face1', stringIds: [1] } }] })
|
||||
const stage1 = createNativeStageNamingEvidence({ stageId: 'gate:1', resultObjectId: 'shape:1', status: 'ambiguous', stringHasher: table, mappedNames: [{ kind: 'face', resultIndex: 0, resultPersistentId: 'face:1', relation: 'ambiguous', reference: { name: 'Face1', stringIds: [1] }, sourceRefs: [{ objectId: 'shape:0', persistentId: 'face:0', stageId: 'gate:0' }], candidates: [{ objectId: 'a', persistentId: 'face:a' }, { objectId: 'b', persistentId: 'face:b' }] }] })
|
||||
if (!validateNativeNamingEvidence(stage0).valid || !validateNativeNamingEvidence(stage1).valid) fail('runtime evidence validator rejected its golden native and ambiguous fixtures.')
|
||||
const mapping = createElementMap2MultiStageNameMapping([stage0, stage1], [])
|
||||
if (parseElementMap2MultiStageNameMapping(writeElementMap2MultiStageNameMapping(mapping)).stages[1].entries[0].status !== 'ambiguous') fail('multi-stage ElementMap2 mapping did not persist ambiguity.')
|
||||
console.log(JSON.stringify({ status: 'freecad-native-naming-evidence-pass', cases: oracle.cases.length, oracleStages, nativeEvidenceStages, nativeIndexedNameStages, nativeMappedNameStages, indexedOnlyStages: nativeIndexedNameStages - nativeMappedNameStages, privateTokenEvidenceCompleteStages, internalBuilderEvidenceStages, internalBuilderEvidenceMissingStages, featureLevels: Object.fromEntries(Object.entries(featureLevels).map(([key, value]) => [key, value.level])), exactBlockers: blockers.length, ambiguousPersisted: true, systemFreecadExact: false, exactFeatures }, null, 2))
|
||||
56
scripts/check-freecad-oracle-coverage.mjs
Normal file
56
scripts/check-freecad-oracle-coverage.mjs
Normal file
@@ -0,0 +1,56 @@
|
||||
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 runtimeObjectCount = reference.objects?.filter((object) => object.available && object.runtimeTypeId).length ?? 0
|
||||
const staticObjectCandidateCount = typeInventory.modules.reduce((total, module) => total + module.objects.length, 0)
|
||||
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,
|
||||
runtimeObjectCount,
|
||||
staticObjectCandidateCount,
|
||||
exactPromotionReady: false,
|
||||
remaining: ['not-built modules require an explicit build/proxy decision', 'static TypeId/property candidates require per-object runtime probes'],
|
||||
}, null, 2))
|
||||
37
scripts/check-freecad-part-builders-oracle.mjs
Normal file
37
scripts/check-freecad-part-builders-oracle.mjs
Normal file
@@ -0,0 +1,37 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/freecad-part-builders-oracle.json'), 'utf8'))
|
||||
const fail = (message) => { throw new Error(`FreeCAD Part builders oracle: ${message}`) }
|
||||
const commit = '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d'
|
||||
const expectedSuccess = new Map([
|
||||
['Part::Extrusion', { id: 'part-extrusion-success', shapeType: 'Solid', solids: 1, faces: 6, edges: 12, vertices: 8, volume: 30 }],
|
||||
['Part::Revolution', { id: 'part-revolution-success', shapeType: 'Solid', solids: 1, faces: 4, edges: 6, vertices: 4, volume: 75.398223686155 }],
|
||||
['Part::Loft', { id: 'part-loft-success', shapeType: 'Solid', solids: 1, faces: 6, edges: 12, vertices: 8, volume: 46.666666666666664 }],
|
||||
['Part::Sweep', { id: 'part-sweep-success', shapeType: 'Solid', solids: 1, faces: 6, edges: 12, vertices: 8, volume: 24 }],
|
||||
['Part::Fillet', { id: 'part-fillet-success', shapeType: 'Compound', solids: 1, faces: 7, edges: 15, vertices: 10, volume: 478.7123889803846 }],
|
||||
['Part::Chamfer', { id: 'part-chamfer-success', shapeType: 'Compound', solids: 1, faces: 7, edges: 15, vertices: 10, volume: 477 }],
|
||||
])
|
||||
const expectedDiagnostics = new Map([
|
||||
['Part::Extrusion', 'No object linked'],
|
||||
['Part::Revolution', 'No object linked'],
|
||||
['Part::Loft', 'No sections linked.'],
|
||||
['Part::Sweep', 'No sections linked.'],
|
||||
['Part::Fillet', 'No object linked'],
|
||||
['Part::Chamfer', 'No object linked'],
|
||||
])
|
||||
if (report.schemaVersion !== 1 || report.baselineId !== 'freecad-1.1.1-part-builders-oracle' || report.freecadVersion !== '1.1.1' || report.gitCommit !== commit || report.status !== 'pass') fail('baseline or status is invalid.')
|
||||
if (report.summary?.successCases !== 6 || report.summary.successPassed !== 6 || report.summary.failureCases !== 6 || report.summary.failurePassed !== 6 || report.summary.rejected !== 6 || report.summary.acceptedEmpty !== 0) fail('summary is incomplete.')
|
||||
if (!Array.isArray(report.successCases) || report.successCases.length !== expectedSuccess.size || !Array.isArray(report.failureCases) || report.failureCases.length !== expectedDiagnostics.size) fail('case inventory is incomplete.')
|
||||
for (const [typeId, expected] of expectedSuccess) {
|
||||
const fixture = report.successCases.find((candidate) => candidate.typeId === typeId)
|
||||
if (!fixture || fixture.id !== expected.id || fixture.expected !== 'success' || fixture.observed !== 'success' || fixture.passed !== true || fixture.shapeNull !== false || fixture.shapeValid !== true || fixture.statusString !== 'Valid' || !fixture.state?.includes('Up-to-date')) fail(`${typeId} is not valid native success evidence.`)
|
||||
for (const field of ['shapeType', 'solids', 'faces', 'edges', 'vertices']) if (fixture[field] !== expected[field]) fail(`${typeId}.${field} drifted from the native oracle.`)
|
||||
if (Math.abs(fixture.volume - expected.volume) > 1e-8) fail(`${typeId}.volume drifted from the native oracle.`)
|
||||
}
|
||||
for (const [typeId, diagnostic] of expectedDiagnostics) {
|
||||
const fixture = report.failureCases.find((candidate) => candidate.typeId === typeId)
|
||||
if (!fixture || fixture.inputClass !== 'missing-required-input' || fixture.expected !== 'rejected' || fixture.observed !== 'rejected' || fixture.passed !== true || fixture.shapeNull !== true || fixture.statusString !== diagnostic || !fixture.state?.includes('Invalid')) fail(`${typeId} is not a native rejected failure fixture.`)
|
||||
}
|
||||
console.log(JSON.stringify({ status: 'freecad-part-builders-oracle-pass', successCases: report.summary.successCases, failureCases: report.summary.failureCases, rejected: report.summary.rejected, typeIds: [...expectedSuccess.keys()] }, null, 2))
|
||||
51
scripts/check-freecad-partdesign-base-oracle.mjs
Normal file
51
scripts/check-freecad-partdesign-base-oracle.mjs
Normal file
@@ -0,0 +1,51 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const load = async (path) => JSON.parse(await readFile(resolve(root, path), 'utf8'))
|
||||
const oracle = await load('config/freecad-partdesign-base-oracle.json')
|
||||
if (oracle.schemaVersion !== 1 || oracle.baselineId !== 'freecad-1.1.1-partdesign-base-oracle' || oracle.freecadVersion !== '1.1.1' || oracle.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || oracle.status !== 'pass' || oracle.tolerance !== 1e-7) throw new Error('FreeCAD PartDesign base oracle baseline is invalid.')
|
||||
if (oracle.summary?.cases !== 5 || oracle.summary?.passed !== 5) throw new Error('FreeCAD PartDesign base oracle summary is incomplete.')
|
||||
|
||||
const expectedVolumes = new Map([
|
||||
['pad-tapered', 98.77344374611019],
|
||||
['pocket-tapered', 810.4374375507407],
|
||||
['pocket-through-all', 960],
|
||||
['pocket-up-to-face', 960],
|
||||
['pad-midplane', 96],
|
||||
])
|
||||
for (const [id, volume] of expectedVolumes) {
|
||||
const fixture = oracle.cases?.find((entry) => entry.id === id)
|
||||
if (!fixture || fixture.passed !== true || fixture.status !== 'Valid' || fixture.solids !== 1 || !Number.isFinite(fixture.volume) || Math.abs(fixture.volume - volume) > oracle.tolerance) throw new Error(`FreeCAD PartDesign base fixture ${id} is invalid.`)
|
||||
}
|
||||
const upToFace = oracle.cases.find((entry) => entry.id === 'pocket-up-to-face')
|
||||
const midplane = oracle.cases.find((entry) => entry.id === 'pad-midplane')
|
||||
if (!Number.isSafeInteger(upToFace.targetFace) || Math.abs(midplane.bounds.min[2] + 3) > oracle.tolerance || Math.abs(midplane.bounds.max[2] - 3) > oracle.tolerance) throw new Error('FreeCAD PartDesign termination bounds are invalid.')
|
||||
|
||||
const chrome = await load('config/chrome-geometry-features-verification.json')
|
||||
if (chrome.status !== 'pass' || chrome.afterRelease?.shapeCount !== 0 || chrome.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome PartDesign base geometry report is invalid.')
|
||||
for (const [id] of expectedVolumes) {
|
||||
const nativeFixture = oracle.cases.find((entry) => entry.id === id)
|
||||
const browserFixture = chrome.operations?.find((entry) => entry.name === id)
|
||||
if (!browserFixture || Math.abs(browserFixture.volume - nativeFixture.volume) > oracle.tolerance) throw new Error(`Chrome ${id} geometry does not match the FreeCAD oracle.`)
|
||||
}
|
||||
|
||||
const lifecycle = await load('config/chrome-partdesign-lifecycle-verification.json')
|
||||
if (lifecycle.status !== 'pass' || lifecycle.initial?.length !== 42 || lifecycle.edited?.length !== 31 || lifecycle.edited?.recompute !== 'completed' || lifecycle.undone?.length !== 42 || lifecycle.undone?.recompute !== 'completed' || lifecycle.released?.shapeCount !== 0 || lifecycle.released?.kernelReferenceCount !== 0) throw new Error('Chrome PartDesign parameter-edit lifecycle evidence is invalid.')
|
||||
|
||||
const nativeHistoryPaths = new Map([
|
||||
['pad', 'config/chrome-native-pad-history-verification.json'],
|
||||
['pocket', 'config/chrome-native-pocket-history-verification.json'],
|
||||
['revolution', 'config/chrome-native-revolution-history-verification.json'],
|
||||
['groove', 'config/chrome-native-groove-history-verification.json'],
|
||||
])
|
||||
const nativeHistory = {}
|
||||
for (const [operation, path] of nativeHistoryPaths) {
|
||||
const report = await load(path)
|
||||
const recordCount = report.execution?.recordCount ?? report.history?.recordCount
|
||||
const released = operation === 'pad' ? report.workerDisposed === true : report.afterRelease?.shapeCount === 0 && report.afterRelease?.kernelReferenceCount === 0
|
||||
if (report.status !== 'pass' || report.nativeCapabilities?.availability !== 'available' || !report.nativeCapabilities?.operations?.includes(operation) || !Number.isSafeInteger(recordCount) || recordCount < 1 || !released) throw new Error(`Chrome native ${operation} history evidence is invalid.`)
|
||||
nativeHistory[operation] = recordCount
|
||||
}
|
||||
|
||||
console.log(JSON.stringify({ status: 'freecad-partdesign-base-oracle-pass', baselineId: oracle.baselineId, freecadVersion: oracle.freecadVersion, summary: oracle.summary, lifecycle: { initialLength: lifecycle.initial.length, editedLength: lifecycle.edited.length, undoneLength: lifecycle.undone.length }, nativeHistory }, null, 2))
|
||||
37
scripts/check-freecad-partdesign-dressup-oracle.mjs
Normal file
37
scripts/check-freecad-partdesign-dressup-oracle.mjs
Normal file
@@ -0,0 +1,37 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const load = async (path) => JSON.parse(await readFile(resolve(root, path), 'utf8'))
|
||||
const oracle = await load('config/freecad-partdesign-dressup-oracle.json')
|
||||
if (oracle.schemaVersion !== 1 || oracle.baselineId !== 'freecad-1.1.1-partdesign-dressup-oracle' || oracle.freecadVersion !== '1.1.1' || oracle.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || oracle.status !== 'pass' || oracle.tolerance !== 1e-7 || oracle.summary?.cases !== 4 || oracle.summary?.passed !== 4) throw new Error('FreeCAD PartDesign dress-up oracle baseline is invalid.')
|
||||
const expectedTypes = new Map([
|
||||
['fillet-selected-edge', { typeId: 'PartDesign::Fillet', volume: 997.8539816339743 }],
|
||||
['chamfer-selected-edge', { typeId: 'PartDesign::Chamfer', volume: 995 }],
|
||||
['draft-selected-face', { typeId: 'PartDesign::Draft', volume: 1500 }],
|
||||
['thickness-selected-face', { typeId: 'PartDesign::Thickness', volume: 424 }],
|
||||
])
|
||||
for (const [id, expectation] of expectedTypes) {
|
||||
const fixture = oracle.cases?.find((entry) => entry.id === id)
|
||||
if (!fixture || fixture.typeId !== expectation.typeId || fixture.passed !== true || fixture.status !== 'Valid' || fixture.solids !== 1 || Math.abs(fixture.volume - expectation.volume) > oracle.tolerance || fixture.selection?.length !== 1) throw new Error(`FreeCAD PartDesign dress-up fixture ${id} is invalid.`)
|
||||
}
|
||||
const thickness = oracle.cases.find((entry) => entry.id === 'thickness-selected-face')
|
||||
if (thickness.faces !== 11 || thickness.elementMapSize !== 51 || thickness.reversed !== true) throw new Error('FreeCAD PartDesign Thickness topology evidence is invalid.')
|
||||
|
||||
const chrome = await load('config/chrome-partdesign-transform-verification.json')
|
||||
const dressupOperations = chrome.operations?.slice(1, 5)
|
||||
if (chrome.status !== 'pass' || JSON.stringify(dressupOperations?.map((entry) => entry.command)) !== JSON.stringify(['fillet', 'chamfer', 'draft', 'thickness']) || dressupOperations.some((entry) => entry.recompute !== 'completed' || !entry.shapeId || !(entry.volume > 0)) || chrome.topologyMigration?.wrongBindings !== 0 || chrome.topologyMigration?.stable < 1 || chrome.topologyMigration?.ambiguous < 1 || chrome.topologyMigration?.ambiguityDiagnostics !== chrome.topologyMigration?.ambiguous || chrome.ambiguityRecovery?.code !== 'DRESSUP_EDGE_REFERENCE_UNRESOLVED' || chrome.ambiguityRecovery?.retainedShapeId !== chrome.ambiguityRecovery?.previousShapeId || chrome.afterRelease?.shapeCount !== 0 || chrome.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome PartDesign dress-up topology lifecycle evidence is invalid.')
|
||||
|
||||
const nativePaths = new Map([
|
||||
['fillet', 'config/chrome-native-fillet-history-verification.json'],
|
||||
['chamfer', 'config/chrome-native-chamfer-history-verification.json'],
|
||||
['draft', 'config/chrome-native-draft-history-verification.json'],
|
||||
['thickness', 'config/chrome-native-thickness-history-verification.json'],
|
||||
])
|
||||
const nativeHistory = {}
|
||||
for (const [operation, path] of nativePaths) {
|
||||
const report = await load(path)
|
||||
if (report.status !== 'pass' || report.nativeCapabilities?.operations?.includes(operation) !== true || report.history?.recordCount < 1 || report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error(`Chrome native ${operation} history evidence is invalid.`)
|
||||
nativeHistory[operation] = report.history.recordCount
|
||||
}
|
||||
console.log(JSON.stringify({ status: 'freecad-partdesign-dressup-oracle-pass', baselineId: oracle.baselineId, summary: oracle.summary, topologyMigration: chrome.topologyMigration, nativeHistory }, null, 2))
|
||||
24
scripts/check-freecad-partdesign-failure-oracle.mjs
Normal file
24
scripts/check-freecad-partdesign-failure-oracle.mjs
Normal file
@@ -0,0 +1,24 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/freecad-partdesign-failure-oracle.json'), 'utf8'))
|
||||
const expected = [
|
||||
'PartDesign::Pad', 'PartDesign::Pocket', 'PartDesign::Revolution', 'PartDesign::Groove',
|
||||
'PartDesign::AdditiveLoft', 'PartDesign::SubtractiveLoft', 'PartDesign::AdditivePipe', 'PartDesign::SubtractivePipe',
|
||||
'PartDesign::Fillet', 'PartDesign::Chamfer', 'PartDesign::Draft', 'PartDesign::Thickness',
|
||||
'PartDesign::Mirrored', 'PartDesign::MultiTransform', 'PartDesign::LinearPattern', 'PartDesign::PolarPattern', 'PartDesign::Hole',
|
||||
]
|
||||
const acceptedEmpty = new Set(['PartDesign::Mirrored', 'PartDesign::MultiTransform', 'PartDesign::LinearPattern', 'PartDesign::PolarPattern'])
|
||||
const fail = (message) => { throw new Error(`FreeCAD PartDesign failure oracle: ${message}`) }
|
||||
if (report.schemaVersion !== 1 || report.baselineId !== 'freecad-1.1.1-partdesign-failure-oracle' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.status !== 'pass') fail('baseline or status is invalid.')
|
||||
if (report.summary?.cases !== expected.length || report.summary.passed !== expected.length || report.summary.rejected !== expected.length - acceptedEmpty.size || report.summary.acceptedEmpty !== acceptedEmpty.size || report.summary.accepted !== 0 || !Array.isArray(report.cases) || report.cases.length !== expected.length) fail('summary is incomplete.')
|
||||
const actual = report.cases.map((fixture) => fixture.typeId)
|
||||
if (JSON.stringify(actual) !== JSON.stringify(expected) || new Set(actual).size !== expected.length) fail('TypeId coverage is incomplete or reordered.')
|
||||
for (const fixture of report.cases) {
|
||||
const expectedOutcome = acceptedEmpty.has(fixture.typeId) ? 'accepted-empty' : 'rejected'
|
||||
if (fixture.expected !== expectedOutcome || fixture.observed !== expectedOutcome || fixture.passed !== true || fixture.inputClass !== 'missing-required-input') fail(`${fixture.typeId} does not match its native missing-input outcome.`)
|
||||
if (fixture.shapeNull !== true && typeof fixture.error !== 'string') fail(`${fixture.typeId} has neither a null Shape nor a native exception.`)
|
||||
if (typeof fixture.error === 'string' && /No module named|Unknown document object type/i.test(fixture.error)) fail(`${fixture.typeId} records an unavailable module instead of feature semantics.`)
|
||||
}
|
||||
console.log(JSON.stringify({ status: 'freecad-partdesign-failure-oracle-pass', cases: report.summary.cases, rejected: report.summary.rejected, acceptedEmpty: report.summary.acceptedEmpty, typeIds: actual }, null, 2))
|
||||
29
scripts/check-freecad-partdesign-loft-oracle.mjs
Normal file
29
scripts/check-freecad-partdesign-loft-oracle.mjs
Normal file
@@ -0,0 +1,29 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const load = async (path) => JSON.parse(await readFile(resolve(root, path), 'utf8'))
|
||||
const oracle = await load('config/freecad-partdesign-loft-oracle.json')
|
||||
if (oracle.schemaVersion !== 1 || oracle.baselineId !== 'freecad-1.1.1-partdesign-loft-oracle' || oracle.freecadVersion !== '1.1.1' || oracle.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || oracle.status !== 'pass' || oracle.tolerance !== 1e-7 || oracle.summary?.cases !== 4 || oracle.summary?.passed !== 4) throw new Error('FreeCAD PartDesign loft oracle baseline is invalid.')
|
||||
const expected = new Map([
|
||||
['additive-loft', { typeId: 'PartDesign::AdditiveLoft', volume: 1 }],
|
||||
['subtractive-loft', { typeId: 'PartDesign::SubtractiveLoft', volume: 1 }],
|
||||
['additive-pipe', { typeId: 'PartDesign::AdditivePipe', volume: Math.PI }],
|
||||
['subtractive-pipe', { typeId: 'PartDesign::SubtractivePipe', volume: 100 - Math.PI }],
|
||||
])
|
||||
for (const [id, expectation] of expected) {
|
||||
const fixture = oracle.cases?.find((entry) => entry.id === id)
|
||||
if (!fixture || fixture.typeId !== expectation.typeId || fixture.passed !== true || fixture.status !== 'Valid' || fixture.solids !== 1 || Math.abs(fixture.volume - expectation.volume) > oracle.tolerance) throw new Error(`FreeCAD PartDesign fixture ${id} is invalid.`)
|
||||
if (id.endsWith('-pipe') && fixture.transition !== 'Transformed') throw new Error(`FreeCAD PartDesign fixture ${id} did not use the locked Transformed transition.`)
|
||||
}
|
||||
|
||||
const chrome = await load('config/chrome-partdesign-loft-verification.json')
|
||||
const browserOrder = ['additive-loft', 'additive-pipe', 'subtractive-loft', 'subtractive-pipe']
|
||||
if (chrome.status !== 'pass' || JSON.stringify(chrome.operations?.map((entry) => entry.command)) !== JSON.stringify(browserOrder) || chrome.operations.some((entry) => entry.recompute !== 'completed' || !entry.shapeId || !(entry.volume > 0)) || chrome.failureRecovery?.loft?.retainedShapeId !== chrome.failureRecovery?.loft?.previousShapeId || chrome.failureRecovery?.pipe?.retainedShapeId !== chrome.failureRecovery?.pipe?.previousShapeId || chrome.persistence?.reopenedTip !== chrome.operations[3].objectId || chrome.afterRelease?.shapeCount !== 0 || chrome.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome PartDesign loft/pipe lifecycle evidence is invalid.')
|
||||
|
||||
const nativeLoft = await load('config/chrome-native-loft-history-verification.json')
|
||||
const nativePipe = await load('config/chrome-native-pipe-history-verification.json')
|
||||
if (nativeLoft.status !== 'pass' || nativeLoft.nativeCapabilities?.operations?.includes('loft') !== true || nativeLoft.history?.recordCount < 1 || nativeLoft.afterRelease?.shapeCount !== 0 || nativeLoft.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native Loft history evidence is invalid.')
|
||||
if (nativePipe.status !== 'pass' || nativePipe.nativeCapabilities?.operations?.includes('pipe') !== true || nativePipe.history?.recordCount < 1 || nativePipe.afterRelease?.shapeCount !== 0 || nativePipe.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome native Pipe history evidence is invalid.')
|
||||
|
||||
console.log(JSON.stringify({ status: 'freecad-partdesign-loft-oracle-pass', baselineId: oracle.baselineId, summary: oracle.summary, chromeVolumes: Object.fromEntries(chrome.operations.map((entry) => [entry.command, entry.volume])), nativeHistory: { loft: nativeLoft.history.recordCount, pipe: nativePipe.history.recordCount } }, null, 2))
|
||||
31
scripts/check-freecad-partdesign-profile-oracle.mjs
Normal file
31
scripts/check-freecad-partdesign-profile-oracle.mjs
Normal file
@@ -0,0 +1,31 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/freecad-partdesign-profile-oracle.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.baselineId !== 'freecad-1.1.1-partdesign-profile-oracle' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.status !== 'pass' || report.length !== 10 || report.tolerance !== 1e-7) throw new Error('FreeCAD PartDesign profile oracle baseline is invalid.')
|
||||
if (report.summary?.cases !== 5 || report.summary?.passed !== 5 || report.summary?.accepted !== 4 || report.summary?.rejected !== 1) throw new Error('FreeCAD PartDesign profile oracle summary is incomplete.')
|
||||
const expected = new Map([
|
||||
['closed', 'accepted'],
|
||||
['multi-ring', 'accepted'],
|
||||
['open', 'rejected'],
|
||||
['self-intersecting', 'accepted'],
|
||||
['invalid-nesting', 'accepted'],
|
||||
])
|
||||
for (const [id, observation] of expected) {
|
||||
const fixture = report.cases?.find((entry) => entry.id === id)
|
||||
if (!fixture || fixture.expected !== observation || fixture.observed !== observation || fixture.passed !== true) throw new Error(`FreeCAD PartDesign profile fixture ${id} is invalid.`)
|
||||
if (observation === 'accepted' && (fixture.shapeNull !== false || fixture.solidCount < 1 || !Number.isFinite(fixture.volume) || fixture.volume <= 0)) throw new Error(`FreeCAD PartDesign accepted profile ${id} has no valid solid.`)
|
||||
}
|
||||
const closed = report.cases.find((entry) => entry.id === 'closed')
|
||||
const multiRing = report.cases.find((entry) => entry.id === 'multi-ring')
|
||||
const selfIntersecting = report.cases.find((entry) => entry.id === 'self-intersecting')
|
||||
const invalidNesting = report.cases.find((entry) => entry.id === 'invalid-nesting')
|
||||
if (Math.abs(closed.volume - 360) > report.tolerance || Math.abs(multiRing.volume - 320) > report.tolerance || Math.abs(selfIntersecting.volume - 80) > report.tolerance || Math.abs(invalidNesting.volume - 370) > report.tolerance || selfIntersecting.solidCount !== 2 || invalidNesting.solidCount !== 2) throw new Error('FreeCAD PartDesign accepted profile geometry does not match the locked oracle.')
|
||||
|
||||
const chrome = JSON.parse(await readFile(resolve(root, 'config/chrome-profile-validation-verification.json'), 'utf8'))
|
||||
const profiles = chrome.partDesignProfiles ?? {}
|
||||
if (chrome.status !== 'pass' || profiles.closed?.code !== null || profiles.multiRing?.code !== null || profiles.open?.code !== 'PROFILE_OPEN' || profiles.selfIntersecting?.code !== null || profiles.invalidNesting?.code !== null) throw new Error('Chrome PartDesign profile results do not match the FreeCAD acceptance matrix.')
|
||||
const geometry = chrome.partDesignGeometry ?? {}
|
||||
if (Math.abs(geometry.closed?.volume - closed.volume) > report.tolerance || geometry.closed?.solids !== closed.solidCount || Math.abs(geometry.multiRing?.volume - multiRing.volume) > report.tolerance || geometry.multiRing?.solids !== multiRing.solidCount || Math.abs(geometry.selfIntersecting?.volume - selfIntersecting.volume) > report.tolerance || geometry.selfIntersecting?.solids !== selfIntersecting.solidCount || Math.abs(geometry.invalidNesting?.volume - invalidNesting.volume) > report.tolerance || geometry.invalidNesting?.solids !== invalidNesting.solidCount) throw new Error('Chrome PartDesign profile geometry does not match the FreeCAD oracle.')
|
||||
console.log(JSON.stringify({ status: 'freecad-partdesign-profile-oracle-pass', baselineId: report.baselineId, freecadVersion: report.freecadVersion, summary: report.summary, chrome: { profiles, geometry } }, null, 2))
|
||||
@@ -0,0 +1,9 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/freecad-partdesign-revolution-groove-oracle.json'), 'utf8'))
|
||||
const fail = (message) => { throw new Error(`FreeCAD PartDesign Revolution/Groove oracle: ${message}`) }
|
||||
if (report.schemaVersion !== 1 || report.baselineId !== 'freecad-1.1.1-partdesign-revolution-groove-oracle' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.status !== 'pass' || report.summary?.cases !== 2 || report.summary?.passed !== 2 || !Array.isArray(report.cases) || report.cases.length !== 2) fail('baseline or summary is invalid.')
|
||||
for (const fixture of report.cases) if (!['partdesign-revolution', 'partdesign-groove'].includes(fixture.id) || fixture.passed !== true || fixture.shapeNull !== false || fixture.shapeValid !== true || fixture.solids !== 1 || !(fixture.volume > 0)) fail(`${fixture.id} is not a valid native success fixture.`)
|
||||
console.log(JSON.stringify({ status: 'freecad-partdesign-revolution-groove-oracle-pass', cases: report.summary.cases, volumes: Object.fromEntries(report.cases.map((fixture) => [fixture.operation, fixture.volume])) }, null, 2))
|
||||
57
scripts/check-freecad-partdesign-transform-oracle.mjs
Normal file
57
scripts/check-freecad-partdesign-transform-oracle.mjs
Normal file
@@ -0,0 +1,57 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const load = async (path) => JSON.parse(await readFile(resolve(root, path), 'utf8'))
|
||||
const oracle = await load('config/freecad-partdesign-transform-oracle.json')
|
||||
if (oracle.schemaVersion !== 1 || oracle.baselineId !== 'freecad-1.1.1-partdesign-transform-oracle' || oracle.freecadVersion !== '1.1.1' || oracle.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || oracle.status !== 'pass' || oracle.tolerance !== 1e-7 || oracle.summary?.cases !== 10 || oracle.summary?.passed !== 10 || oracle.summary.successCases !== 6 || oracle.summary.successPassed !== 6 || oracle.summary.failureCases !== 4 || oracle.summary.failurePassed !== 4 || oracle.summary.rejected !== 4 || oracle.summary.acceptedEmpty !== 0) throw new Error('FreeCAD PartDesign transform oracle baseline is invalid.')
|
||||
|
||||
const byId = new Map(oracle.cases?.map((entry) => [entry.id, entry]) ?? [])
|
||||
const linear = byId.get('linear-feature-list')
|
||||
if (!linear || linear.typeId !== 'PartDesign::LinearPattern' || linear.transformMode !== 'Features' || JSON.stringify(linear.originals) !== JSON.stringify(['Boss']) || linear.direction !== 'X_Axis' || linear.occurrences !== 3 || linear.length !== 12 || linear.solids !== 1 || linear.status !== 'Valid' || Math.abs(linear.volume - 423.99999999999994) > oracle.tolerance) throw new Error('FreeCAD Feature-list LinearPattern evidence is invalid.')
|
||||
const mirrored = byId.get('mirrored-feature-list')
|
||||
if (!mirrored || mirrored.typeId !== 'PartDesign::Mirrored' || mirrored.transformMode !== 'Features' || JSON.stringify(mirrored.originals) !== JSON.stringify(['Boss']) || mirrored.plane !== 'YZ_Plane' || mirrored.solids !== 1 || mirrored.status !== 'Valid' || Math.abs(mirrored.volume - 416.00000000000006) > oracle.tolerance) throw new Error('FreeCAD Feature-list Mirrored evidence is invalid.')
|
||||
const polar = byId.get('polar-feature-list')
|
||||
if (!polar || polar.typeId !== 'PartDesign::PolarPattern' || polar.transformMode !== 'Features' || JSON.stringify(polar.originals) !== JSON.stringify(['Box']) || polar.axis !== 'X_Axis' || polar.angle !== 360 || polar.occurrences !== 4 || polar.solids !== 1 || polar.status !== 'Valid' || Math.abs(polar.volume - 4000) > oracle.tolerance) throw new Error('FreeCAD Feature-list PolarPattern evidence is invalid.')
|
||||
const multi = byId.get('multi-transform-feature-list')
|
||||
if (!multi || multi.typeId !== 'PartDesign::MultiTransform' || multi.transformMode !== 'Features' || JSON.stringify(multi.originals) !== JSON.stringify(['Pad']) || JSON.stringify(multi.transformations) !== JSON.stringify(['PartDesign::Mirrored', 'PartDesign::LinearPattern', 'PartDesign::PolarPattern']) || multi.solids !== 1 || multi.status !== 'Valid' || Math.abs(multi.volume - 20000) > oracle.tolerance) throw new Error('FreeCAD Feature-list MultiTransform evidence is invalid.')
|
||||
|
||||
const expectedFailures = new Map([
|
||||
['PartDesign::Mirrored', { id: 'mirrored-missing-reference', status: 'No mirror plane reference specified', shapeNull: true, staleShapePreserved: false }],
|
||||
['PartDesign::MultiTransform', { id: 'multitransform-missing-reference', status: 'Shape index 999 out of bound 12', shapeNull: false, staleShapePreserved: true }],
|
||||
['PartDesign::LinearPattern', { id: 'linearpattern-missing-reference', status: 'Shape index 999 out of bound 12', shapeNull: true, staleShapePreserved: false }],
|
||||
['PartDesign::PolarPattern', { id: 'polarpattern-missing-reference', status: 'No axis reference specified', shapeNull: true, staleShapePreserved: false }],
|
||||
])
|
||||
for (const [typeId, expected] of expectedFailures) {
|
||||
const fixture = oracle.cases.find((entry) => entry.typeId === typeId && entry.expected === 'rejected')
|
||||
if (!fixture || fixture.id !== expected.id || fixture.inputClass !== 'valid-original-missing-transform-reference' || fixture.observed !== 'rejected' || fixture.passed !== true || fixture.status !== expected.status || fixture.shapeNull !== expected.shapeNull || fixture.staleShapePreserved !== expected.staleShapePreserved || !fixture.state?.includes('Invalid') || JSON.stringify(fixture.originals) !== JSON.stringify(['Boss'])) throw new Error(`FreeCAD ${typeId} rejected transform evidence is invalid.`)
|
||||
}
|
||||
if (JSON.stringify(byId.get('multitransform-missing-reference')?.transformations) !== JSON.stringify(['PartDesign::LinearPattern'])) throw new Error('FreeCAD MultiTransform rejected child evidence is invalid.')
|
||||
|
||||
const tap = byId.get('iso-m6-tap-drill')
|
||||
const modeled = byId.get('iso-m6-modeled-thread')
|
||||
for (const fixture of [tap, modeled]) {
|
||||
if (!fixture || fixture.typeId !== 'PartDesign::Hole' || fixture.threaded !== true || fixture.threadType !== 'ISOMetricProfile' || fixture.threadSize !== 'M6x1.0' || fixture.threadDiameter !== 6 || fixture.threadPitch !== 1 || fixture.threadDepthType !== 'Dimension' || fixture.threadDepth !== 6 || fixture.solids !== 1 || fixture.status !== 'Valid' || fixture.properties?.includes('ThreadPitch')) throw new Error(`FreeCAD ISO M6 Hole evidence is invalid: ${fixture?.id ?? 'missing'}.`)
|
||||
}
|
||||
if (tap.modeled !== false || tap.threadDirection !== 'Right' || Math.abs(tap.diameter - 5) > oracle.tolerance || Math.abs(tap.volume - 3842.92036732051) > oracle.tolerance) throw new Error('FreeCAD ISO M6 tap-drill evidence is invalid.')
|
||||
if (modeled.modeled !== true || modeled.threadDirection !== 'Left' || Math.abs(modeled.diameter - 5.026) > oracle.tolerance || Math.abs(modeled.volume - 3820.1907618181585) > oracle.tolerance || modeled.faces <= tap.faces || modeled.edges <= tap.edges || !(modeled.volume < tap.volume)) throw new Error('FreeCAD ISO M6 modeled-thread evidence is invalid.')
|
||||
|
||||
const chrome = await load('config/chrome-partdesign-transform-verification.json')
|
||||
const featureTransforms = chrome.operations?.filter((entry) => entry.transformMode === 'Features') ?? []
|
||||
const threadedHoles = chrome.operations?.filter((entry) => entry.command === 'hole' && entry.threaded === true) ?? []
|
||||
if (chrome.status !== 'pass' || chrome.operations?.length !== 14 || chrome.operations.some((entry) => entry.solids !== 1 || !(entry.volume > 0)) || featureTransforms.length !== 2 || featureTransforms.some((entry) => entry.originals?.length !== 1) || threadedHoles.length !== 2 || threadedHoles[0].modelThread !== false || threadedHoles[1].modelThread !== true || threadedHoles[1].threadDirection !== 'Left' || chrome.afterRelease?.shapeCount !== 0 || chrome.afterRelease?.kernelReferenceCount !== 0) throw new Error('Chrome PartDesign transform/thread evidence is invalid.')
|
||||
|
||||
const nativePaths = new Map([
|
||||
['linear-pattern', 'config/chrome-native-linear-pattern-history-verification.json'],
|
||||
['polar-pattern', 'config/chrome-native-polar-pattern-history-verification.json'],
|
||||
['mirrored', 'config/chrome-native-mirrored-history-verification.json'],
|
||||
['multi-transform', 'config/chrome-native-multi-transform-history-verification.json'],
|
||||
])
|
||||
const nativeHistory = {}
|
||||
for (const [operation, path] of nativePaths) {
|
||||
const report = await load(path)
|
||||
if (report.status !== 'pass' || report.nativeCapabilities?.operations?.includes(operation) !== true || report.history?.recordCount < 1 || report.afterRelease?.shapeCount !== 0 || report.afterRelease?.kernelReferenceCount !== 0) throw new Error(`Chrome native ${operation} history evidence is invalid.`)
|
||||
nativeHistory[operation] = report.history.recordCount
|
||||
}
|
||||
|
||||
console.log(JSON.stringify({ status: 'freecad-partdesign-transform-oracle-pass', baselineId: oracle.baselineId, summary: oracle.summary, featureTransforms: featureTransforms.map((entry) => ({ command: entry.command, originals: entry.originals, volume: entry.volume })), threadedHoles: threadedHoles.map((entry) => ({ size: entry.threadSize, modeled: entry.modelThread, direction: entry.threadDirection, volume: entry.volume })), nativeHistory }, null, 2))
|
||||
15
scripts/check-freecad-semantic-comparator.mjs
Normal file
15
scripts/check-freecad-semantic-comparator.mjs
Normal file
@@ -0,0 +1,15 @@
|
||||
import { loadGoldenManifest } from './freecad-golden-contract.mjs'
|
||||
import { compareSemanticResult } from './freecad-semantic-comparator.mjs'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const manifest = await loadGoldenManifest(resolve(root, 'fixtures/freecad-golden/manifest.json'))
|
||||
for (const entry of manifest.scenarios) {
|
||||
const report = compareSemanticResult(entry.scenario, entry.scenario.expected)
|
||||
if (report.status !== 'pass' || report.differences.length) throw new Error(`Semantic comparator self-check failed for ${entry.id}.`)
|
||||
const drift = structuredClone(entry.scenario.expected)
|
||||
drift.volume += entry.scenario.tolerance.scalar * 2
|
||||
const driftReport = compareSemanticResult(entry.scenario, drift)
|
||||
if (driftReport.status !== 'fail-unknown-difference' || !driftReport.differences.some((difference) => difference.path.endsWith('.volume') && difference.classification === 'unknown')) throw new Error(`Semantic comparator drift check failed for ${entry.id}.`)
|
||||
}
|
||||
console.log(JSON.stringify({ status: 'freecad-semantic-comparator-pass', scenarioCount: manifest.scenarios.length, reportSchemaVersion: 1, unknownDifferencesFail: true }, null, 2))
|
||||
22
scripts/check-freecad-sketcher-constraint-oracle.mjs
Normal file
22
scripts/check-freecad-sketcher-constraint-oracle.mjs
Normal file
@@ -0,0 +1,22 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/freecad-sketcher-constraint-oracle.json'), 'utf8'))
|
||||
const expectedTypes = ['Coincident', 'Horizontal', 'Vertical', 'Parallel', 'Tangent', 'Distance', 'DistanceX', 'DistanceY', 'Angle', 'Perpendicular', 'Radius', 'Equal', 'PointOnObject', 'Symmetric', 'InternalAlignment', 'SnellsLaw', 'Block', 'Diameter', 'Weight']
|
||||
if (report.schemaVersion !== 1 || report.baselineId !== 'freecad-1.1.1-sketcher-constraint-oracle' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.status !== 'pass' || report.tolerance !== 1e-7) throw new Error('FreeCAD Sketcher constraint oracle baseline is invalid.')
|
||||
if (report.summary?.constraintTypes !== expectedTypes.length || report.summary?.successCases !== expectedTypes.length || report.summary?.successPassed !== expectedTypes.length || report.summary?.failureCases !== expectedTypes.length || report.summary?.failurePassed !== expectedTypes.length) throw new Error('FreeCAD Sketcher constraint oracle summary is incomplete.')
|
||||
for (const constraintType of expectedTypes) {
|
||||
const success = report.successCases?.find((entry) => entry.constraintType === constraintType)
|
||||
const failure = report.failureCases?.find((entry) => entry.constraintType === constraintType)
|
||||
if (!success || success.expected !== 'success' || success.observed !== 'success' || success.solveStatus !== 0 || !Number.isFinite(success.degreesOfFreedom) || !Number.isFinite(success.residual) || Math.abs(success.residual) > report.tolerance || success.conflicting?.length || success.redundant?.length || success.malformed?.length) throw new Error(`FreeCAD Sketcher ${constraintType} success fixture is invalid.`)
|
||||
if (!failure || failure.expected !== 'failure' || failure.observed !== 'failure') throw new Error(`FreeCAD Sketcher ${constraintType} failure fixture is invalid.`)
|
||||
}
|
||||
const reference = report.classificationCases?.find((entry) => entry.id === 'reference-dimension')
|
||||
const redundant = report.classificationCases?.find((entry) => entry.id === 'redundant-dimension')
|
||||
const conflicting = report.classificationCases?.find((entry) => entry.id === 'conflicting-dimension')
|
||||
if (report.summary?.classificationCases !== 3 || report.summary?.classificationPassed !== 3) throw new Error('FreeCAD Sketcher classification oracle summary is incomplete.')
|
||||
if (reference?.observed !== 'reference' || reference?.solveStatus !== 0 || reference?.degreesOfFreedom !== 3 || reference?.driving !== false || !Number.isFinite(reference?.measuredValue) || Math.abs(reference.measuredValue - reference.inputValue) <= report.tolerance || reference?.conflicting?.length || reference?.redundant?.length || reference?.malformed?.length) throw new Error('FreeCAD Sketcher reference dimension classification is invalid.')
|
||||
if (redundant?.observed !== 'redundant' || redundant?.solveStatus !== -2 || redundant?.degreesOfFreedom !== 3 || JSON.stringify(redundant?.redundant) !== '[2]' || redundant?.conflicting?.length || redundant?.malformed?.length) throw new Error('FreeCAD Sketcher redundant dimension classification is invalid.')
|
||||
if (conflicting?.observed !== 'conflicting' || conflicting?.solveStatus !== -3 || conflicting?.degreesOfFreedom !== 3 || JSON.stringify(conflicting?.conflicting) !== '[1,2]' || conflicting?.redundant?.length || conflicting?.malformed?.length) throw new Error('FreeCAD Sketcher conflicting dimension classification is invalid.')
|
||||
console.log(JSON.stringify({ status: 'freecad-sketcher-constraint-oracle-pass', baselineId: report.baselineId, freecadVersion: report.freecadVersion, gitCommit: report.gitCommit, summary: report.summary }, null, 2))
|
||||
35
scripts/check-freecad-sketcher-editor-oracle.mjs
Normal file
35
scripts/check-freecad-sketcher-editor-oracle.mjs
Normal file
@@ -0,0 +1,35 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const oracle = JSON.parse(await readFile(resolve(root, 'config/freecad-sketcher-editor-oracle.json'), 'utf8'))
|
||||
if (oracle.schemaVersion !== 1 || oracle.baselineId !== 'freecad-1.1.1-sketcher-editor-oracle' || oracle.freecadVersion !== '1.1.1' || oracle.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || oracle.tolerance !== 1e-7 || oracle.status !== 'pass' || oracle.summary?.cases !== 7 || oracle.summary?.passed !== 7) throw new Error('FreeCAD Sketcher editor oracle baseline is invalid.')
|
||||
const fixture = (id) => {
|
||||
const result = oracle.cases?.find((entry) => entry.id === id)
|
||||
if (!result?.passed) throw new Error(`FreeCAD Sketcher editor fixture ${id} is missing or failed.`)
|
||||
return result
|
||||
}
|
||||
const pointMatches = (actual, expected) => Array.isArray(actual) && actual.length === 2 && Math.abs(actual[0] - expected[0]) <= oracle.tolerance && Math.abs(actual[1] - expected[1]) <= oracle.tolerance
|
||||
const drag = fixture('drag-horizontal')
|
||||
if (drag.solveStatus !== 0 || !pointMatches(drag.final.geometry?.[0]?.start, [0, 2]) || !pointMatches(drag.final.geometry?.[0]?.end, [8, 2]) || JSON.stringify(drag.final.constraintTypes) !== '["Horizontal"]') throw new Error('FreeCAD Sketcher drag fixture is invalid.')
|
||||
const split = fixture('split-line')
|
||||
if (split.final.geometry?.length !== 2 || !pointMatches(split.final.geometry[0].end, [4, 0]) || !pointMatches(split.final.geometry[1].start, [4, 0]) || JSON.stringify(split.final.construction) !== '[true,true]' || JSON.stringify(split.final.constraintTypes) !== '["Coincident"]') throw new Error('FreeCAD Sketcher split fixture is invalid.')
|
||||
const extend = fixture('extend-line')
|
||||
if (!pointMatches(extend.final.geometry?.[0]?.end, [15, 0])) throw new Error('FreeCAD Sketcher extend fixture is invalid.')
|
||||
const trim = fixture('trim-at-intersection')
|
||||
if (!pointMatches(trim.final.geometry?.[0]?.end, [7, 0]) || JSON.stringify(trim.final.constraintTypes) !== '["PointOnObject"]') throw new Error('FreeCAD Sketcher trim fixture is invalid.')
|
||||
const construction = fixture('toggle-construction')
|
||||
if (JSON.stringify(construction.final.construction) !== '[true]') throw new Error('FreeCAD Sketcher construction fixture is invalid.')
|
||||
const transaction = fixture('transaction-undo-redo')
|
||||
if (!pointMatches(transaction.committedEnd, [8, 0]) || !pointMatches(transaction.undoneEnd, [5, 0]) || !pointMatches(transaction.redoneEnd, [8, 0])) throw new Error('FreeCAD Sketcher transaction fixture is invalid.')
|
||||
const autoConstraint = fixture('auto-constraint')
|
||||
if (autoConstraint.solveStatus !== 0 || JSON.stringify(autoConstraint.final.constraintTypes) !== '["Coincident","Horizontal"]' || !pointMatches(autoConstraint.final.geometry?.[0]?.end, [5, 0]) || !pointMatches(autoConstraint.final.geometry?.[1]?.start, [5, 0])) throw new Error('FreeCAD Sketcher autoconstraint fixture is invalid.')
|
||||
|
||||
const chrome = JSON.parse(await readFile(resolve(root, 'config/chrome-sketcher-editor-verification.json'), 'utf8'))
|
||||
const operations = chrome.operations ?? {}
|
||||
const interactions = chrome.interactions ?? {}
|
||||
if (chrome.status !== 'pass' || operations.drag?.start?.x !== drag.final.geometry[0].start[0] || operations.drag?.start?.y !== drag.final.geometry[0].start[1] || operations.drag?.end?.x !== drag.final.geometry[0].end[0] || operations.drag?.end?.y !== drag.final.geometry[0].end[1]) throw new Error('Chrome drag result does not match FreeCAD.')
|
||||
if (JSON.stringify(operations.split?.constraintTypes) !== '["coincident"]' || operations.split?.endpoints?.[0]?.[1]?.x !== split.final.geometry[0].end[0] || operations.split?.endpoints?.[1]?.[0]?.x !== split.final.geometry[1].start[0]) throw new Error('Chrome split result does not match FreeCAD.')
|
||||
if (operations.extend?.end?.x !== extend.final.geometry[0].end[0] || operations.trim?.end?.x !== trim.final.geometry[0].end[0] || JSON.stringify(operations.trim?.constraintTypes) !== '["pointOnObject"]' || operations.construction?.enabled !== true) throw new Error('Chrome edit results do not match FreeCAD.')
|
||||
if (JSON.stringify(operations.autoConstraint?.suggestionReasons) !== '["coincident","horizontal"]' || interactions.end?.x !== transaction.redoneEnd[0] || interactions.nativeEvents?.pointer < 2 || interactions.nativeEvents?.keyboard < 3 || interactions.undone !== 1 || interactions.redone !== 1) throw new Error('Chrome task lifecycle does not match the FreeCAD oracle.')
|
||||
console.log(JSON.stringify({ status: 'freecad-sketcher-editor-oracle-pass', baselineId: oracle.baselineId, freecadVersion: oracle.freecadVersion, summary: oracle.summary, chrome: { operations: Object.keys(operations), interactions } }, null, 2))
|
||||
33
scripts/check-freecad-source-inventory.mjs
Normal file
33
scripts/check-freecad-source-inventory.mjs
Normal file
@@ -0,0 +1,33 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const inventory = JSON.parse(await readFile(resolve(root, 'config/freecad-source-inventory.json'), 'utf8'))
|
||||
const fail = (message) => { throw new Error(`FreeCAD source inventory: ${message}`) }
|
||||
if (inventory.schemaVersion !== 1) fail('unsupported schemaVersion.')
|
||||
if (inventory.baseline?.freecadVersion !== '1.1.1' || inventory.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('baseline mismatch.')
|
||||
if (inventory.moduleCount !== 34 || !Array.isArray(inventory.modules) || inventory.modules.length !== 34) fail('expected exactly 34 modules.')
|
||||
const names = new Set()
|
||||
for (const module of inventory.modules) {
|
||||
if (!module.name || names.has(module.name)) fail(`duplicate or empty module '${module.name}'.`)
|
||||
names.add(module.name)
|
||||
for (const field of ['sourcePath', 'cmakeOption', 'runtimeStatus']) if (typeof module[field] !== 'string' || !module[field]) fail(`${module.name}.${field} is missing.`)
|
||||
if (!Array.isArray(module.commandIds)) fail(`${module.name}.commandIds must be an array.`)
|
||||
if (module.commandIds.some((command) => typeof command !== 'string' || !command)) fail(`${module.name} has an invalid command ID.`)
|
||||
}
|
||||
const sourceRoot = process.env.FREECAD_SOURCE_DIR
|
||||
? resolve(process.env.FREECAD_SOURCE_DIR)
|
||||
: resolve(root, inventory.baseline.sourcePath || '.cache/freecad/FreeCAD')
|
||||
const sourceMod = resolve(sourceRoot, 'src/Mod')
|
||||
let sourceAvailable = false
|
||||
try {
|
||||
const entries = await import('node:fs/promises').then(({ readdir }) => readdir(sourceMod, { withFileTypes: true }))
|
||||
const sourceNames = new Set(entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name))
|
||||
sourceAvailable = true
|
||||
if (sourceNames.size !== names.size || [...sourceNames].some((name) => !names.has(name))) fail('tracked inventory differs from the available source checkout.')
|
||||
} catch (error) {
|
||||
if (error?.code !== 'ENOENT') throw error
|
||||
}
|
||||
console.log(JSON.stringify({ status: 'source-inventory-pass', moduleCount: inventory.moduleCount, commandCount: inventory.modules.reduce((total, module) => total + module.commandIds.length, 0), sourceAvailable }, null, 2))
|
||||
|
||||
function joinRoot(path) { return resolve(root, path) }
|
||||
11
scripts/check-freecad-threeway.mjs
Normal file
11
scripts/check-freecad-threeway.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const report = JSON.parse(await readFile(resolve(root, 'config/freecad-bitbybit-native-threeway.json'), 'utf8'))
|
||||
if (report.schemaVersion !== 1 || report.unknownDifferencesFail !== true || report.scenarioCount !== 26 || !Array.isArray(report.reports)) throw new Error('Three-way Boolean report schema is invalid.')
|
||||
for (const entry of report.reports) {
|
||||
if (entry.bitbybit.differences.differences.length || entry.native.differences.differences.length) throw new Error(`Unknown three-way Boolean difference in ${entry.id}.`)
|
||||
if (!Number.isInteger(entry.native.historyRecordCount) || entry.native.historyRecordCount <= 0) throw new Error(`Missing native history records in ${entry.id}.`)
|
||||
}
|
||||
console.log(JSON.stringify({ status: 'freecad-threeway-pass', scenarioCount: report.scenarioCount, nativeOcctVersion: report.nativeOcctVersion }, null, 2))
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user