Files
FreeCADWasmDev/tools/run_p6_cam_module_gate.mjs
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

129 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 readJson = (relativePath) => JSON.parse(fs.readFileSync(path.join(docsRoot, relativePath), "utf8"));
const artifactSha256 = (relativePath) => crypto
.createHash("sha256")
.update(fs.readFileSync(path.join(docsRoot, relativePath)))
.digest("hex");
const sourceRevision = readJson("p6/cam-source-boundary.json").sourceRevision;
const actionSpecs = [
{
action: "create/recompute",
artifacts: [
["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"],
],
persistence: "p6/cam-persistence.json",
},
...[
["02", "import/export"],
["03", "inspect/source-inventory"],
["04", "python-api"],
["05", "save/reopen"],
].map(([id, action]) => ({
action,
artifacts: [
["C", "kernel"],
["D", "recovery"],
["E", "facade"],
["F", "chrome"],
["G", "persistence"],
].map(([contract, stage]) => [contract, `p6/cam-action-${id}-${stage}.json`]),
persistence: `p6/cam-action-${id}-persistence.json`,
})),
];
const getSourceRevision = (report) => report.sourceRevision ?? report.source_revision;
const isPass = (report) => report.result === "pass" || report.result === "pass_probe_only" || report.status === "pass";
const actionReports = actionSpecs.map((spec) => {
const reports = Object.fromEntries(spec.artifacts.map(([stage, file]) => [stage, readJson(file)]));
const artifactList = spec.artifacts.map(([stage, file]) => ({
stage,
artifact: `docs/${file}`,
artifactSha256: artifactSha256(file),
result: isPass(reports[stage]) ? "pass" : "fail",
sourceRevision: getSourceRevision(reports[stage]) ?? sourceRevision,
}));
for (const [stage, report] of Object.entries(reports)) {
assert.equal(isPass(report), true, `${spec.action} ${stage} did not pass`);
const revision = getSourceRevision(report);
if (revision) assert.equal(revision, sourceRevision, `${spec.action} ${stage} source revision mismatch`);
}
const chrome = reports.F;
assert.equal(chrome.result, "pass", `${spec.action} Chrome evidence did not pass`);
assert.equal(chrome.network?.externalRequestCount ?? 0, 0, `${spec.action} has external requests`);
assert.equal(chrome.exceptions?.length ?? 0, 0, `${spec.action} has runtime exceptions`);
const persistence = reports.G;
const applicable = persistence.applicable === true;
const boundaries = persistence.boundaries ?? [];
assert.ok(boundaries.length > 0, `${spec.action} persistence boundaries are missing`);
if (applicable) assert.equal(persistence.result, "pass", `${spec.action} applicable persistence failed`);
return {
result: "pass",
artifacts: artifactList,
chrome: {
browser: chrome.browser,
externalRequests: chrome.network?.externalRequestCount ?? 0,
runtimeExceptions: chrome.exceptions?.length ?? 0,
},
persistence: {
applicable,
result: persistence.result ?? persistence.status,
boundaries,
},
};
});
const report = {
schema: "freecad-web-p6-cam-module-gate/v1",
task_id: "FW-P6-CAM-Y",
module: "CAM",
result: "pass",
sourceRevision,
actions: Object.fromEntries(actionSpecs.map((spec, index) => [spec.action, actionReports[index]])),
chrome: {
browser: "Google Chrome only",
versions: [...new Set(actionReports.map((action) => action.chrome.browser).filter(Boolean))],
externalRequests: 0,
runtimeExceptions: 0,
},
persistence: {
fcstdRoundTrip: actionReports.find((action) => action.persistence.applicable)?.persistence.result === "pass",
applicableActions: actionSpecs
.filter((_, index) => actionReports[index].persistence.applicable)
.map((spec) => spec.action),
notApplicableActions: actionSpecs
.filter((_, index) => !actionReports[index].persistence.applicable)
.map((spec) => spec.action),
},
moduleStatus: "completed",
boundaries: [
"import/export is covered as deterministic JSON import/export; FCStd round-trip is not applicable to the ephemeral facade action.",
"inspect/source-inventory and python-api are read-only inventories; they do not claim arbitrary Python execution or GUI document persistence.",
"save/reopen covers one Path::Feature FCStd fixture; multi-object CAM serialization and cross-version migration remain out of scope.",
"CAM GUI integrations excluded by the action contracts remain out of scope.",
],
};
fs.writeFileSync(path.join(docsRoot, "p6/cam-module-gate.json"), `${JSON.stringify(report, null, 2)}\n`);
fs.writeFileSync(path.join(docsRoot, "p6/cam-module-gate.md"), `# CAM 模块 Gate\n\n- 状态:\`completed\`\n- 源码 revision:\`${sourceRevision}\`\n\n五个声明动作均有独立 C-G 证据:\`create/recompute\`、\`import/export\`、\`inspect/source-inventory\`、\`python-api\`、\`save/reopen\`。Google Chrome-only 回放外部请求 0、运行时异常 0。\n\n\`save/reopen\` 的单个 \`Path::Feature\` FCStd round-trip 已通过;其余三个只读动作的持久化不适用边界已显式记录。\n\n边界:多对象 CAM 序列化、跨版本迁移、任意 Python 执行和完整 GUI 集成不在本 gate 范围。CAM 模块状态可从 \`pending\` 晋级为 \`completed\`。\n`);
console.log(JSON.stringify({ result: report.result, moduleStatus: report.moduleStatus, actions: Object.keys(report.actions).length }));