#!/usr/bin/env node import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { execFileSync } from "node:child_process"; import { fileURLToPath } from "node:url"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const docs = path.join(root, "docs/p6"); const read = (file) => JSON.parse(fs.readFileSync(path.join(docs, file), "utf8")); const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(docs, file))).digest("hex"); const sourceRevision = execFileSync("git", ["-C", path.join(root, "freecad-source"), "rev-parse", "HEAD"], { encoding: "utf8" }).trim(); function facadePass(file) { const value = read(file); return value.success?.kind === "result" && value.expectedError?.kind === "error" && value.duplicate?.error?.code === "duplicate_request" && value.cancelled?.error?.code === "cancelled"; } function chromePass(file) { const value = read(file); return value.result === "pass" && value.mode === "headless-new" && value.viewport?.width === 1440 && value.viewport?.height === 900 && value.viewport?.deviceScaleFactor === 1 && value.cases?.length === 5 && value.cases.every((item) => item.status === "pass" && item.externalRequests.length === 0 && item.exceptions.length === 0); } const actionFiles = { "import/export": ["part-action-02-kernel.json", "part-action-02-recovery.json", "part-action-02-facade.json", "part-action-02-chrome.json", "part-action-02-persistence.json"], "inspect/source-inventory": ["part-action-03-kernel.json", "part-action-03-recovery.json", "part-action-03-facade.json", "part-action-03-chrome.json", "part-action-03-persistence.json"], "python-api": ["part-action-04-kernel.json", "part-action-04-recovery.json", "part-action-04-facade.json", "part-action-04-chrome.json", "part-action-04-persistence.json"], "save/reopen": ["part-action-05-kernel.json", "part-action-05-recovery.json", "part-action-05-facade.json", "part-action-05-chrome.json", "part-action-05-persistence.json"], }; const actions = { "create/recompute": { result: read("part-gate.json").status === "pass", artifact: "part-gate.json", sha256: sha256("part-gate.json"), evidence: ["part-gate.json"] }, }; for (const [action, files] of Object.entries(actionFiles)) { const [kernel, recovery, facade, chrome, persistence] = files; const pass = read(kernel).status === "pass" && read(recovery).status === "pass" && facadePass(facade) && chromePass(chrome) && (read(persistence).status === "pass" || read(persistence).result === "pass"); actions[action] = { result: pass, evidence: files.map((file) => ({ path: `docs/p6/${file}`, sha256: sha256(file) })), }; } const result = Object.values(actions).every((entry) => entry.result); const report = { schema: "freecad-web-p6-part-module-gate/v2", taskId: "FW-P6-PRT-Y", module: "Part", result: result ? "pass" : "fail", sourceRevision, moduleStatusBeforeGate: "pending", moduleStatusAfterGate: result ? "completed" : "pending", eligibleForCompletion: result, actions, boundaries: [ "All five declared Part actions have independent C-G conclusions; create/recompute is covered by the existing first-slice gate.", "BREP persistence is explicitly not applicable to import/export; inspect/source-inventory and python-api are read-only boundaries.", "Part save/reopen is closed only for the deterministic Part::Box FCStd fixture; arbitrary paths, GUI workflows and cross-version migration remain out of scope.", "The standalone PartBoxProbe smoke recovery remains explicit in FW-P6-PRT-ACT-05-G-R1 and does not change the DocumentProbe evidence.", ], commands: ["node tools/run_p6_part_module_gate.mjs"], }; fs.writeFileSync(path.join(docs, "part-module-gate.json"), `${JSON.stringify(report, null, 2)}\n`); fs.writeFileSync(path.join(docs, "part-module-gate.md"), `# Part module gate\n\n- Result: \`${report.result}\`\n- Source revision: \`${sourceRevision}\`\n- Declared actions: create/recompute, import/export, inspect/source-inventory, python-api, save/reopen\n- Part status after gate: \`${report.moduleStatusAfterGate}\`\n\nAll five declared actions have independent C-G evidence. Non-applicable persistence boundaries and the PartBoxProbe recovery are explicit.\n`); fs.writeFileSync(path.join(root, "docs/freecad-p6-part-y-gate.log"), `${JSON.stringify({ result: report.result, actions: Object.fromEntries(Object.entries(actions).map(([key, value]) => [key, value.result])), moduleStatus: report.moduleStatusAfterGate })}\n`); console.log(JSON.stringify({ result: report.result, actions: Object.keys(actions).length, moduleStatus: report.moduleStatusAfterGate })); if (!result) process.exitCode = 1;