#!/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"; const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), ".."); const docsRoot = path.join(root, "docs"); const read = (relativePath) => JSON.parse(fs.readFileSync(path.join(docsRoot, relativePath), "utf8")); const sha256 = (relativePath) => crypto.createHash("sha256").update(fs.readFileSync(path.join(docsRoot, relativePath))).digest("hex"); const contractFiles = { A: "p6/cam-source-boundary.json", B: "p6/cam-runtime.json", C: "p6/cam-kernel.json", D: "p6/cam-recovery.json", E: "p6/cam-facade.json", F: "p6/cam-chrome.json", G: "p6/cam-persistence.json", }; const reports = Object.fromEntries(Object.entries(contractFiles).map(([key, file]) => [key, read(file)])); const sourceRevision = reports.A.sourceRevision; assert.equal(reports.A.result, "pass_probe_only"); assert.equal(reports.B.status, "pass"); assert.equal(reports.C.status, "pass"); assert.equal(reports.D.status, "pass"); assert.equal(reports.E.status, "pass"); assert.equal(reports.F.result, "pass"); assert.equal(reports.G.result, "pass"); assert.equal(reports.F.network.externalRequestCount, 0); assert.equal(reports.F.exceptions.length, 0); assert.equal(reports.G.applicable, false); for (const key of ["B", "C", "D", "G"]) assert.equal(reports[key].source_revision ?? reports[key].sourceRevision, sourceRevision); const contracts = Object.fromEntries(Object.entries(contractFiles).map(([key, file]) => { const report = reports[key]; return [key, { artifact: `docs/${file}`, artifactSha256: sha256(file), result: report.status === "pass" || report.result === "pass" || report.result === "pass_probe_only" ? "pass" : "fail", sourceRevision: report.sourceRevision ?? report.source_revision ?? sourceRevision, }]; })); const report = { schema: "freecad-web-p6-cam-gate/v1", task_id: "FW-P6-CAM-X", module: "CAM", result: "pass", closedAction: "create/recompute", sourceRevision, contracts, chrome: { browser: reports.F.chrome.version, mode: reports.F.mode, viewport: reports.F.viewport, externalRequests: reports.F.network.externalRequestCount, runtimeExceptions: reports.F.exceptions.length, }, coverage: { createRecompute: "pass", importExport: "pending", inspectSourceInventory: "pending", pythonApi: "pending", saveReopen: "pending", }, moduleStatus: "pending", boundaries: [ "Only the create/recompute action has a complete A-G contract chain.", "The persistence contract records save/reopen as not applicable for the current ephemeral facade action.", "Other CAM actions remain pending until their own C-G chains and the Y gate are complete.", ], }; fs.writeFileSync(path.join(docsRoot, "p6/cam-gate.json"), `${JSON.stringify(report, null, 2)}\n`); fs.writeFileSync(path.join(docsRoot, "p6/cam-gate.md"), `# CAM 首切片 Gate\n\n- 状态:\`pass\`\n- 关闭动作:\`create/recompute\`\n- 源码 revision:\`${sourceRevision}\`\n\nA-G 七个原子合同均有独立产物、hash 和结论。${reports.F.chrome.version} ${reports.F.mode} 回放 viewport ${reports.F.viewport.width}x${reports.F.viewport.height},外部请求 ${reports.F.network.externalRequestCount}、运行时异常 ${reports.F.exceptions.length}。\n\nCAM 模块仍保持 \`pending\`:\`import/export\`、\`inspect/source-inventory\`、\`python-api\` 和 \`save/reopen\` 尚未完成各自动作级 C-G 子链;本 gate 不把首切片通过推断为完整模块。\n`); console.log(JSON.stringify({ result: report.result, closedAction: report.closedAction, moduleStatus: report.moduleStatus, contracts: Object.keys(contracts).length }));