#!/usr/bin/env node import assert from "node:assert/strict"; import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; const facadePath = process.argv[2]; const buildDirectory = path.resolve(process.argv[3] || "freecad-source/build-web/cloud-configure-r2/bin"); if (!facadePath) throw new Error("compiled cloud-facade module path is required"); const facade = await import(facadePath); const envelope = await import(path.resolve(path.dirname(facadePath), "facade-envelope.js")); const { CLOUD_ACTION_SCHEMA, CLOUD_COMMAND, CLOUD_FIXTURE, createCloudRequest, handleCloudConfigure, isCloudRequest, } = facade; const { FacadeSerialQueue, createCancel, isFacadeResponse } = envelope; globalThis.__dirname = `${buildDirectory}${path.sep}`; globalThis.require = (await import("node:module")).createRequire(import.meta.url); const loaderUrl = pathToFileURL(path.join(buildDirectory, "FreeCADWebCloudKernelProbe.js")).href; const loader = await import(loaderUrl); const kernel = await loader.default({ wasmBinary: fs.readFileSync(path.join(buildDirectory, "FreeCADWebCloudKernelProbe.wasm")), }); const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), ".."); const outputPath = path.join(root, "docs", "p6", "cloud-facade.json"); const schemaPath = path.join(root, "docs", "p6", "cloud-facade.schema.json"); const logPath = path.join(root, "docs", "freecad-p6-cloud-e-worker.log"); const queue = new FacadeSerialQueue(); const trace = []; let active = 0; let maxActive = 0; async function dispatch(request) { trace.push({ direction: "request", requestId: request.requestId, command: request.command }); const response = await queue.dispatch(request, async (payload, signal) => { active += 1; maxActive = Math.max(maxActive, active); trace.push({ direction: "handler-start", requestId: request.requestId }); try { return await handleCloudConfigure(payload, signal, kernel); } finally { active -= 1; trace.push({ direction: "handler-end", requestId: request.requestId }); } }); trace.push({ direction: "response", requestId: response.requestId, kind: response.kind, code: response.kind === "error" ? response.error.code : null }); return response; } const success = await dispatch(createCloudRequest("cloud-success")); const expectedError = await dispatch(createCloudRequest("cloud-error", { ...CLOUD_FIXTURE, url: "" })); const duplicate = await dispatch(createCloudRequest("cloud-success")); const cancelRequest = createCloudRequest("cloud-cancel"); const cancelPromise = dispatch(cancelRequest); trace.push({ direction: "cancel", requestId: "cloud-cancel-message", cancelOf: cancelRequest.requestId }); const cancelAccepted = queue.cancel(createCancel("cloud-cancel-message", cancelRequest.requestId, "cloud-config")); const cancelled = await cancelPromise; const afterCancel = await dispatch(createCloudRequest("cloud-after-cancel")); assert.equal(CLOUD_COMMAND, "cloud.configureRecompute"); assert.equal(isCloudRequest(createCloudRequest("schema-test")), true); assert.equal(isCloudRequest({ ...createCloudRequest("schema-test"), command: "other" }), false); assert.equal(success.kind, "result"); assert.equal(success.result.schema, CLOUD_ACTION_SCHEMA); assert.equal(success.result.configuration.setterCount, 6); assert.equal(success.result.recompute.count, 1); assert.equal(success.result.recompute.hasError, 0); assert.equal(success.result.recompute.summary, Object.values(CLOUD_FIXTURE).slice(2).join("|")); assert.equal(expectedError.kind, "error"); assert.equal(expectedError.error.code, "invalid_argument"); assert.equal(expectedError.error.diagnostic.field, "url"); assert.equal(duplicate.kind, "error"); assert.equal(duplicate.error.code, "duplicate_request"); assert.equal(cancelAccepted, true); assert.equal(cancelled.kind, "error"); assert.equal(cancelled.error.code, "cancelled"); assert.equal(afterCancel.kind, "result"); assert.deepEqual(afterCancel.result, success.result); assert.equal(maxActive, 1); assert.equal(active, 0); for (const response of [success, expectedError, duplicate, cancelled, afterCancel]) assert.equal(isFacadeResponse(response), true); const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8")); assert.equal(schema.$id, "https://freecad.org/schemas/freecad-web-cloud-configure-recompute-v1.json"); assert.equal(schema.$defs.request.properties.command.const, CLOUD_COMMAND); assert.equal(schema.$defs.typedResult.properties.schema.const, CLOUD_ACTION_SCHEMA); assert.equal(schema.$defs.payload.additionalProperties, false); const inputSha256 = crypto.createHash("sha256").update(JSON.stringify(CLOUD_FIXTURE)).digest("hex"); const resultSha256 = crypto.createHash("sha256").update(JSON.stringify(success.result)).digest("hex"); const report = { schema: "freecad-web-p6-cloud-facade/v1", task_id: "FW-P6-CLD-E", module: "Cloud", action: CLOUD_COMMAND, status: "pass", source_revision: "b5a086fda59b0325fe0996147e6e9b344414f9bc", input_sha256: inputSha256, result_sha256: resultSha256, responses: { success, expectedError, duplicate, cancelled, afterCancel }, trace, sequencing: { maxActive, activeAfter: active, requestCount: 5, responseCount: 5, cancelAccepted }, state: { beforeCancel: success.result.recompute.summary, afterCancel: afterCancel.result.recompute.summary, unchangedAfterCancel: success.result.recompute.summary === afterCancel.result.recompute.summary, }, schemaChecks: { request: true, result: true, expectedError: true, duplicate: true, cancel: true, noNativePointers: !JSON.stringify({ responses: { success, expectedError, duplicate, cancelled, afterCancel }, trace }).match(/pointer|native/i), }, commands: [ "npx tsc --target ES2022 --module ESNext --moduleResolution Bundler --strict --skipLibCheck --outDir /tmp/freecad-p6-cloud-facade --declaration false --noEmit false src/facade-envelope.ts src/cloud-facade.ts", "node tools/run_p6_cloud_facade_smoke.mjs /tmp/freecad-p6-cloud-facade/cloud-facade.js freecad-source/build-web/cloud-configure-r2/bin" ], artifacts: { schema: path.relative(root, schemaPath), workerLog: path.relative(root, logPath), wasm: "freecad-source/build-web/cloud-configure-r2/bin/FreeCADWebCloudKernelProbe.wasm", }, boundaries: [ "TypeScript validates and sequences envelope data; all Cloud configuration and recompute result fields come from the WebAssembly C ABI.", "The Facade and Worker never expose a native pointer.", "Only the locked configure/recompute action is included." ], generated_at: new Date().toISOString(), }; fs.writeFileSync(outputPath, `${JSON.stringify(report, null, 2)}\n`); fs.writeFileSync(logPath, `${trace.map((event, index) => `${String(index + 1).padStart(2, "0")} ${JSON.stringify(event)}`).join("\n")}\nresult=pass\nmaxActive=${maxActive}\n`); console.log(JSON.stringify({ result: report.status, requests: report.sequencing.requestCount, responses: report.sequencing.responseCount, maxActive }));