#!/usr/bin/env node import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { createRequire } from "node:module"; import { pathToFileURL } from "node:url"; const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), ".."); const build = path.resolve(process.argv[2] ?? path.join(root, "freecad-source/build-web/configure/bin")); const docs = path.join(root, "docs/p6"); const manifestPath = path.join(root, "docs/p6/python-api-manifest.json"); const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")); const selected = manifest.records.filter((row) => row.module === "ReverseEngineering" && row.tier === "tier-1-readonly"); if (selected.length !== 1) throw new Error(`ReverseEngineering tier-1 manifest expected 1 record, got ${selected.length}`); const records = selected.map(({ id, name, family }) => ({ apiId: id, name, family })); const request = { cmd: "reverseengineering.pythonFamilyBatch", args: { tier: "tier-1-readonly", records } }; globalThis.__dirname = `${build}${path.sep}`; globalThis.require = createRequire(import.meta.url); process.chdir(path.dirname(build)); const loader = await import(pathToFileURL(path.join(build, "FreeCADWebReverseEngineeringRuntimeProbe.js"))); const kernel = await loader.default({ wasmBinary: fs.readFileSync(path.join(build, "FreeCADWebReverseEngineeringRuntimeProbe.wasm")), locateFile: (file) => path.join(build, file) }); const invoke = (value) => JSON.parse(String(kernel.ccall("fc_web_dispatch_json", "string", ["string"], [JSON.stringify(value)]))); const success = invoke(request); const repeated = invoke(request); const invalidRecord = invoke({ cmd: request.cmd, args: { tier: request.args.tier, records: [{ ...records[0], apiId: "py:BIM:class:ReverseEngineeringWorkbench" }] } }); const malformed = invoke({ cmd: request.cmd, args: { tier: "tier-2-pure", records } }); const malformedRepeat = invoke({ cmd: request.cmd, args: { tier: "tier-2-pure", records } }); const unsupported = invoke({ cmd: "reverseengineering.unknown", args: {} }); if (success.ok !== true || success.schema !== "freecad-web-reverseengineering/python-family-batch/v1" || success.module !== "ReverseEngineering" || success.tier !== "tier-1-readonly" || success.source !== "freecad-source" || success.count !== 1 || success.records?.length !== 1 || success.resourcesReleased !== true) throw new Error("ReverseEngineering family dispatch did not return a complete result"); const nativeRecord = success.records[0]; if (nativeRecord.status !== "restricted" || nativeRecord.code !== "gui_dependency_unavailable" || nativeRecord.native !== true || nativeRecord.resourcesReleased !== true || nativeRecord.sourceEntry !== "src/Mod/ReverseEngineering/InitGui.py") throw new Error("ReverseEngineeringWorkbench did not receive the expected GUI restriction"); if (JSON.stringify(repeated) !== JSON.stringify(success)) throw new Error("ReverseEngineering family replay changed the native result"); if (invalidRecord.ok !== true || invalidRecord.records?.[0]?.status !== "not_implemented" || invalidRecord.records?.[0]?.code !== "unmatched_source_record") throw new Error("module-scoped mismatch was not rejected"); if (malformed.ok !== false || malformed.code !== "invalid_request" || JSON.stringify(malformedRepeat) !== JSON.stringify(malformed)) throw new Error("invalid tier replay was not rejected deterministically"); if (unsupported.ok !== false || unsupported.code !== "unsupported_command") throw new Error("unsupported command was not rejected"); const hash = (value) => crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex"); const evidenceHash = hash(nativeRecord); const adapterManifestPath = path.join(docs, "reverseengineering-tier1-readonly-adapter-manifest.json"); for (const row of manifest.records) { if (row.module !== "ReverseEngineering" || row.tier !== "tier-1-readonly") continue; row.disposition = nativeRecord.status; row.dispositionEvidence = ["docs/p6/reverseengineering-tier1-readonly-family.json"]; row.dispositionEvidenceHash = evidenceHash; row.dispositionReason = "ReverseEngineeringWorkbench is registered from InitGui.py and requires FreeCADGui/desktop workbench capability unavailable in the GUI-free Web kernel"; } fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); const adapterManifest = JSON.parse(fs.readFileSync(adapterManifestPath, "utf8")); adapterManifest.records = adapterManifest.records.map((row) => ({ ...row, disposition: nativeRecord.status, adapterStatus: "restricted_gui_dependency", evidenceHash })); fs.writeFileSync(adapterManifestPath, `${JSON.stringify(adapterManifest, null, 2)}\n`); const report = { schema: "freecad-web-p6-reverseengineering-tier1-readonly-family/v1", taskId: "FW-P6-FPA-F1-REVERSEENGINEERING", module: "ReverseEngineering", tier: "tier-1-readonly", status: "pass", dispatcher: "fc_web_dispatch_json", route: { worker: "web/src/reverseengineering-python.worker.ts", command: request.cmd, native: "freecad-source/src/WebAssembly/FreeCADCoreProbe/ReverseEngineeringRuntimeProbe.cpp:fc_web_dispatch_json" }, sourceRevision: nativeRecord.sourceRevision, input: request, inputSha256: hash(request), result: success, resultSha256: hash(success), replay: { equal: true, resultSha256: hash(repeated), resourcesReleased: success.resourcesReleased }, invalidRecord: { result: invalidRecord, resultSha256: hash(invalidRecord) }, malformedRequest: { first: malformed, repeated: malformedRepeat, resultSha256: hash(malformed), stable: true }, unsupportedCommand: { result: unsupported, resultSha256: hash(unsupported) }, records: [{ apiId: nativeRecord.apiId, status: nativeRecord.status, code: nativeRecord.code, evidenceHash }], boundaries: [ "The selected class is a GUI workbench registration from InitGui.py; native FreeCAD marks it restricted rather than exposing a GUI object in the Web kernel.", "Exact module, API name, and family matching prevents cross-module name promotion; mismatched records are terminal not_implemented.", "No DocumentObject is created or mutated by this readonly dispatch; native resources are released on success and replay.", ], }; fs.writeFileSync(path.join(docs, "reverseengineering-tier1-readonly-family.json"), `${JSON.stringify(report, null, 2)}\n`); fs.writeFileSync(path.join(root, "docs/freecad-p6-reverseengineering-tier1-readonly-family.log"), `${JSON.stringify({ status: report.status, count: selected.length, disposition: nativeRecord.status, evidenceHash, resultSha256: report.resultSha256 })}\n`); console.log(JSON.stringify({ status: report.status, count: selected.length, disposition: nativeRecord.status, evidenceHash, resultSha256: report.resultSha256 }));