77 lines
3.7 KiB
JavaScript
77 lines
3.7 KiB
JavaScript
#!/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 }));
|