import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { loadMvpGateSummary } from "../../../internal/mvp-gate/summary.mjs"; import { gateSummary } from "../gate-summary.mjs"; import { runCloudWebM3ReadonlyContract } from "./m3-readonly-contract.mjs"; import { runWorkbenchHardwarePanelContract } from "./workbench-hardware-panel-contract.mjs"; const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const repoRoot = path.resolve(rootDir, "../.."); const requiredFiles = [ "index.html", "styles.css", "app.mjs", "gate-summary.mjs", "runtime.mjs", "workbench-hardware-panel.mjs" ]; for (const file of requiredFiles) { const filePath = path.resolve(rootDir, file); if (!fs.existsSync(filePath)) { throw new Error(`missing web asset: ${file}`); } } const html = fs.readFileSync(path.resolve(rootDir, "index.html"), "utf8"); const styles = fs.readFileSync(path.resolve(rootDir, "styles.css"), "utf8"); const app = fs.readFileSync(path.resolve(rootDir, "app.mjs"), "utf8"); const artifactPublisher = fs.readFileSync(path.resolve(repoRoot, "scripts/dev-artifact-publish.mjs"), "utf8"); const frontendSource = `${html}\n${app}\n${artifactPublisher}`; assert.match(html, /HWLAB Cloud Workbench/); assert.match(html, /Agent Conversation \/ Trace Workspace/); assert.match(html, /BOX-SIMU \/ Gateway-SIMU \/ Patch Panel/); assert.match(html, /Gate \/ Diagnostics/); for (const workbenchElement of [ "activity-rail", "explorer", "resource-tree", "conversation-list", "trace-list", "right-sidebar", "command-form", "hardware-list", "control-list", "wiring-list", "records-list", "diagnostics-list" ]) { assert.match(html, new RegExp(workbenchElement)); } for (const viewId of ["workspace", "gate"]) { assert.match(html, new RegExp(`data-view="${viewId}"`)); } assert.match(html, /live-status/); assert.match(html, /method-list/); assert.match(html, /source-strip/); assert.match(html, /SOURCE loading/); assert.match(html, /DRY-RUN loading/); assert.match(html, /DEV-LIVE probing/); assert.match(html, /BLOCKED pending/); assert.doesNotMatch(html, /M3 Diagnostics Console/); assert.match(styles, /html,\s*\nbody\s*{[^}]*height:\s*100%;[^}]*overflow:\s*hidden;/s); assert.match(styles, /body\s*>\s*\[data-app-shell\]\s*{[^}]*min-height:\s*0;/s); assert.match(styles, /\.workbench-shell\s*{[^}]*height:\s*100(?:d)?vh;[^}]*overflow:\s*hidden;/s); assert.match(styles, /\.view\s*{[^}]*min-height:\s*0;[^}]*overflow:\s*auto;/s); assert.match(styles, /\.(?:resource-tree|compact-list|conversation-list|trace-list|hardware-list)[^{]*{[^}]*min-height:\s*0;[^}]*overflow:\s*auto;/s); assert.match(app, /fetchJson\("\/v1"\)/); assert.match(app, /fetchJson\("\/health\/live"\)/); assert.match(app, /callRpc\("system\.health"\)/); assert.match(app, /callRpc\("cloud\.adapter\.describe"\)/); assert.match(app, /callRpc\("audit\.event\.query"/); assert.match(app, /callRpc\("evidence\.record\.query"/); assert.match(artifactPublisher, /HWLAB_API_BASE_URL/); assert.match(artifactPublisher, /readOnlyRpcMethods/); assert.match(artifactPublisher, /requestUpstream/); assert.match(artifactPublisher, /from "node:http"/); assert.doesNotMatch(artifactPublisher, /await fetch\(target/); assert.match(artifactPublisher, /"system\.health"/); assert.match(artifactPublisher, /"cloud\.adapter\.describe"/); assert.match(artifactPublisher, /"audit\.event\.query"/); assert.match(artifactPublisher, /"evidence\.record\.query"/); assert.match(artifactPublisher, /readonly_rpc_required/); assert.match(artifactPublisher, /cloud_api_proxy_failed/); assert.match(app, /degraded/); assert.match(app, /runtime\.serviceRoute\.join\(" -> "\)/); assert.doesNotMatch(app, /callRpc\("hardware\./); assert.doesNotMatch(app, /hardware\.operation\.request/); assert.doesNotMatch(app, /hardware\.invoke\.shell/); assert.doesNotMatch(app, /audit\.event\.write/); assert.doesNotMatch(app, /evidence\.record\.write/); assert.doesNotMatch(frontendSource, /--live --confirm-dev --confirmed-non-production/); assert.doesNotMatch(frontendSource, /74\.48\.78\.17:666[67]\b|:666[67]\b/); const sourceSummary = loadMvpGateSummary(repoRoot); assert.equal(gateSummary.endpoint, sourceSummary.endpoint); assert.equal(gateSummary.gateStatus, sourceSummary.gateStatus); assert.equal(gateSummary.milestones.length, 6); assert.deepEqual( gateSummary.milestones.map((item) => item.id), ["M0", "M1", "M2", "M3", "M4", "M5"] ); assert.equal(gateSummary.artifactCount, 13); assert.equal(gateSummary.healthCount, 14); assert.equal(gateSummary.gatewaySessionCount, 2); assert.equal(gateSummary.boxResourceCount, 2); assert.equal(gateSummary.topology.patchPanel.serviceId, "hwlab-patch-panel"); assert.equal(gateSummary.agent.agentServiceId, "hwlab-agent-mgr"); assert.equal(gateSummary.agent.workerServiceId, "hwlab-agent-worker"); assert.equal(gateSummary.evidenceRecords.length, 2); assert.equal(gateSummary.safety.allowNetwork, false); runCloudWebM3ReadonlyContract(); runWorkbenchHardwarePanelContract(); console.log("hwlab-cloud-web check ok: workbench shell, hardware evidence panel, and read-only diagnostics contract are present");