#!/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 { fileURLToPath } from "node:url"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const docsRoot = path.join(root, "docs"); const outputPath = path.join(docsRoot, "p6", "cloud-gate.json"); const markdownPath = path.join(docsRoot, "p6", "cloud-gate.md"); const logPath = path.join(docsRoot, "freecad-p6-cloud-x-gate.log"); const ledger = JSON.parse(fs.readFileSync(path.join(docsRoot, "current", "task-state.json"), "utf8")); const revision = "b5a086fda59b0325fe0996147e6e9b344414f9bc"; const sha256 = (value) => crypto.createHash("sha256").update(value).digest("hex"); const isHash = (value) => typeof value === "string" && /^[0-9a-f]{64}$/.test(value); const contracts = [ ["A", "FW-P6-CLD-A", "cloud-source-boundary.json", "cloud/FW-P6-CLD-A.md"], ["B", "FW-P6-CLD-B", "cloud-runtime.json", "cloud/FW-P6-CLD-B.md"], ["C", "FW-P6-CLD-C", "cloud-kernel.json", "cloud/FW-P6-CLD-C.md"], ["D", "FW-P6-CLD-D", "cloud-recovery.json", "cloud/FW-P6-CLD-D.md"], ["E", "FW-P6-CLD-E", "cloud-facade.json", "cloud/FW-P6-CLD-E.md"], ["F", "FW-P6-CLD-F", "cloud-chrome.json", "cloud/FW-P6-CLD-F.md"], ["G", "FW-P6-CLD-G", "cloud-persistence.json", "cloud/FW-P6-CLD-G.md"], ]; function sourceRevision(evidence) { return evidence.source_revision ?? evidence.sourceRevision; } function inputHash(evidence) { return evidence.input_sha256 ?? evidence.fixture?.input_sha256 ?? evidence.invalid_input?.input_sha256 ?? (evidence.inputs ? sha256(JSON.stringify(evidence.inputs)) : null); } function resultHash(evidence, evidenceBytes) { return evidence.result_sha256 ?? evidence.result?.result_sha256 ?? evidence.evidence?.resultHash ?? sha256(evidenceBytes); } const checks = contracts.map(([step, taskId, evidenceName, cardName]) => { const evidencePath = path.join(docsRoot, "p6", evidenceName); const cardPath = path.join(docsRoot, "tasks", "p6", cardName); const evidenceBytes = fs.readFileSync(evidencePath); const evidence = JSON.parse(evidenceBytes); const card = fs.readFileSync(cardPath, "utf8"); const conclusion = evidence.status ?? evidence.result; const inputSha256 = inputHash(evidence); const resultSha256 = resultHash(evidence, evidenceBytes); const commandsPresent = (Array.isArray(evidence.commands) && evidence.commands.length > 0) || card.includes("复现"); const artifactsPresent = (evidence.artifacts && Object.keys(evidence.artifacts).length > 0) || card.includes("产物"); const pass = ledger.tasks[taskId] === "completed" && evidence.task_id === taskId && sourceRevision(evidence) === revision && (conclusion === "pass" || conclusion === "pass_probe_only") && isHash(inputSha256) && isHash(resultSha256) && commandsPresent && artifactsPresent; return { step, task_id: taskId, pass, conclusion, source_revision: sourceRevision(evidence), input_sha256: inputSha256, result_sha256: resultSha256, evidence: `docs/p6/${evidenceName}`, evidence_sha256: sha256(evidenceBytes), task_card: `docs/tasks/p6/${cardName}`, commands_present: commandsPresent, artifacts_present: artifactsPresent, }; }); const chrome = JSON.parse(fs.readFileSync(path.join(docsRoot, "p6", "cloud-chrome.json"), "utf8")); const chromeChecks = { result: chrome.result, version: chrome.chrome?.version, mode: chrome.mode, viewport: chrome.viewport, external_requests: chrome.network?.externalRequestCount, console_errors: chrome.console?.filter((entry) => entry.type === "error" || entry.type === "browser-error").length, exceptions: chrome.exceptions?.length, non_chrome_browsers_attempted: chrome.policy?.nonChromeBrowsersAttempted, screenshot_sha256: chrome.evidence?.screenshotSha256, }; const chromePass = chromeChecks.result === "pass" && chromeChecks.version?.startsWith("Google Chrome ") && chromeChecks.mode === "headless-new" && chromeChecks.viewport?.width === 1440 && chromeChecks.viewport?.height === 900 && chromeChecks.external_requests === 0 && chromeChecks.console_errors === 0 && chromeChecks.exceptions === 0 && chromeChecks.non_chrome_browsers_attempted === false && isHash(chromeChecks.screenshot_sha256); const remainingActions = ["FW-P6-CLD-ACT-02", "FW-P6-CLD-ACT-03", "FW-P6-CLD-ACT-04"].map((taskId) => ({ task_id: taskId, status: ledger.tasks[taskId], })); const pass = checks.every((check) => check.pass) && chromePass && remainingActions.every((task) => task.status === "planned") && ledger.tasks["FW-P6-CLD"] === "in_progress"; assert.equal(pass, true, JSON.stringify({ checks, chromeChecks, remainingActions })); const gateInput = { evidence: checks.map((check) => check.evidence_sha256), chrome: chromeChecks.screenshot_sha256 }; const gateResult = { checks: checks.map((check) => check.pass), chromePass, remainingActions }; const report = { schema: "freecad-web-p6-cloud-gate/v1", task_id: "FW-P6-CLD-X", module: "Cloud", gate: "first-slice", status: "pass", source_revision: revision, input_sha256: sha256(JSON.stringify(gateInput)), result_sha256: sha256(JSON.stringify(gateResult)), contracts: checks, chrome: { pass: chromePass, ...chromeChecks }, module_status_after_gate: "in_progress", remaining_actions: remainingActions, commands: ["node FreeCAD/tools/run_p6_cloud_gate.mjs", "python3 FreeCAD/tools/validate_web_context.py"], artifacts: { markdown: "docs/p6/cloud-gate.md", log: "docs/freecad-p6-cloud-x-gate.log" }, boundaries: [ "This gate closes only the first Cloud configure/recompute slice.", "Cloud inspect/source-inventory, Python API, and remote transfer action packets remain pending.", "No full-module, release, legal, performance, or non-Chrome compatibility claim is made." ], generated_at: new Date().toISOString(), }; const rows = checks.map((check) => `| ${check.step} | \`${check.task_id}\` | ${check.conclusion} | \`${check.evidence_sha256}\` |`).join("\n"); const markdown = `# Cloud first-slice gate\n\n- Result: \`pass\`\n- Source revision: \`${revision}\`\n- Closed scope: Cloud configure/recompute first slice only\n- Module status: \`in_progress\`\n\n| Step | Task | Conclusion | Evidence SHA-256 |\n|---|---|---|---|\n${rows}\n\nChrome \`${chromeChecks.version}\` passed in \`${chromeChecks.mode}\` at \`1440x900\`; external requests, console errors, and exceptions were all zero.\n\nRemaining action packets: \`FW-P6-CLD-ACT-02\`, \`FW-P6-CLD-ACT-03\`, and \`FW-P6-CLD-ACT-04\` are still \`planned\`. This gate does not mark the complete Cloud module as finished.\n`; fs.writeFileSync(outputPath, `${JSON.stringify(report, null, 2)}\n`); fs.writeFileSync(markdownPath, markdown); fs.writeFileSync(logPath, `contracts=${checks.length}\nchromePass=${chromePass}\nremainingActions=${remainingActions.length}\nmoduleStatus=${ledger.tasks["FW-P6-CLD"]}\nresult=pass\n`); console.log(JSON.stringify({ result: report.status, contracts: checks.length, chrome: chromePass, remainingActions: remainingActions.length }));