#!/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 { execFileSync } from "node:child_process"; import { fileURLToPath, pathToFileURL } from "node:url"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const source = path.join(root, "freecad-source"); const build = path.resolve(process.argv[2] ?? path.join(source, "build-web/cam-configure-r1/bin")); const docs = path.join(root, "docs/p6"); const manifest = JSON.parse(fs.readFileSync(path.join(docs, "python-api-manifest.json"), "utf8")); const records = manifest.records.filter((row) => row.module === "CAM" && row.tier === "tier-1-readonly").map((row) => ({ apiId: row.id, name: row.name, family: row.family })); const command = "cam.pythonFamilyBatch"; const schema = "freecad-web-cam/python-family-batch/v1"; const loaderPath = path.join(build, "FreeCADWebCAMProbe.js"); globalThis.__dirname = `${build}${path.sep}`; globalThis.require = createRequire(import.meta.url); process.chdir(path.dirname(build)); const loader = await import(pathToFileURL(loaderPath)); const kernel = await loader.default({ wasmBinary: fs.readFileSync(path.join(build, "FreeCADWebCAMProbe.wasm")), locateFile: (file) => path.join(build, file) }); const dispatch = (request) => JSON.parse(String(kernel.ccall("fc_web_dispatch_json", "string", ["string"], [JSON.stringify(request)]))); const request = { cmd: command, args: { tier: "tier-1-readonly", records } }; const first = dispatch(request); const second = dispatch(request); const invalid = dispatch({ cmd: command, args: {} }); const wrongTier = dispatch({ cmd: command, args: { tier: "tier-2-pure", records: records.slice(0, 1) } }); const unsupported = dispatch({ cmd: "cam.unknown", args: { records } }); const stable = (value) => crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex"); const counts = Array.isArray(first.records) ? first.records.reduce((acc, item) => { acc[item.status] = (acc[item.status] ?? 0) + 1; return acc; }, {}) : {}; const recordEvidence = Array.isArray(first.records) ? first.records.map((item) => ({ apiId: item.apiId, name: item.name, family: item.family, disposition: item.status, evidenceSha256: stable({ command, tier: "tier-1-readonly", record: item }) })) : []; const idsMatch = Array.isArray(first.records) && first.records.every((item, index) => item.apiId === records[index]?.apiId && item.name === records[index]?.name && item.family === records[index]?.family); const result = first.ok === true && first.schema === schema && first.module === "CAM" && first.tier === "tier-1-readonly" && first.source === "freecad-source" && first.count === 612 && records.length === 612 && first.records.length === records.length && counts.restricted === 105 && counts.not_implemented === 507 && new Set(first.records.map((item) => item.apiId)).size === records.length && idsMatch && first.resourcesReleased === true && first.records.every((item) => item.native === true && ["restricted", "not_implemented"].includes(item.status)) && stable(first) === stable(second) && invalid.ok === false && invalid.code === "invalid_request" && wrongTier.ok === false && wrongTier.code === "invalid_request" && unsupported.ok === false && unsupported.code === "unsupported_command"; const report = { schema: "freecad-web-p6-cam-tier1-readonly-native/v1", taskId: "FW-P6-FPA-F1-CAM", result: result ? "pass_with_boundaries" : "fail", sourceRevision: execFileSync("git", ["-C", source, "rev-parse", "HEAD"], { encoding: "utf8" }).trim(), dispatcher: "fc_web_dispatch_json", command, requestSchema: schema, coverage: { manifestRecords: records.length, returnedRecords: Array.isArray(first.records) ? first.records.length : 0, statusCounts: counts, uniqueIds: Array.isArray(first.records) ? new Set(first.records.map((item) => item.apiId)).size : 0, idsMatch }, records: recordEvidence, response: { firstSha256: stable(first), secondSha256: stable(second), identical: stable(first) === stable(second), resourcesReleased: first.resourcesReleased === true }, errors: { invalid, wrongTier, unsupported }, artifacts: { wasm: "web/public/kernel/FreeCADWebCAMProbe.wasm", loader: "web/public/kernel/FreeCADWebCAMProbe.js" }, boundaries: ["CAM view-resource APIs are restricted because PathGui is unavailable in the GUI-free Web kernel.", "Generic-object and geometry-value records remain not_implemented until method-level readonly parameter/return evidence is available.", "Each disposition is derived from the native FreeCAD batch response; JavaScript only hashes and validates the response."], }; fs.mkdirSync(docs, { recursive: true }); fs.writeFileSync(path.join(docs, "cam-tier1-readonly-native.json"), `${JSON.stringify(report, null, 2)}\n`); fs.writeFileSync(path.join(root, "docs/freecad-p6-cam-f1-readonly.log"), `${JSON.stringify({ result: report.result, coverage: report.coverage, firstSha256: report.response.firstSha256, invalidCode: invalid.code, wrongTierCode: wrongTier.code, unsupportedCode: unsupported.code })}\n`); console.log(JSON.stringify({ result: report.result, coverage: report.coverage, invalidCode: invalid.code, wrongTierCode: wrongTier.code, unsupportedCode: unsupported.code })); if (!result) process.exitCode = 1;