import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { M3_IO_CHAIN, M3_IO_CONTROL_ROUTE, M3_STATUS_ROUTE } from "../../../internal/cloud/m3-io-control.mjs"; import { checkFrontendNoDirectRuntimeCalls } from "../../../scripts/src/m3-io-control-e2e.mjs"; const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const repoRoot = path.resolve(rootDir, "../.."); const exactTrustedRoute = `${M3_IO_CHAIN.sourceResourceId}:${M3_IO_CHAIN.sourcePort} -> ${M3_IO_CHAIN.patchPanelServiceId} -> ${M3_IO_CHAIN.targetResourceId}:${M3_IO_CHAIN.targetPort}`; const readOnlyRpcMethods = Object.freeze([ "system.health", "cloud.adapter.describe", "audit.event.query", "evidence.record.query" ]); function readText(relativePath) { return fs.readFileSync(path.resolve(repoRoot, relativePath), "utf8"); } export function runM3ControlPanelGuard() { const html = readText("web/hwlab-cloud-web/index.html"); const app = readText("web/hwlab-cloud-web/app.mjs"); const styles = readText("web/hwlab-cloud-web/styles.css"); const artifactPublisher = readText("scripts/dev-artifact-publish.mjs"); const cloudWebRouteSource = readText("internal/dev-entrypoint/cloud-web-routes.mjs"); const browserSource = `${html}\n${app}\n${styles}`; assert.equal(M3_IO_CONTROL_ROUTE, "/v1/m3/io", "M3 IO control route must remain exact"); assert.equal(M3_STATUS_ROUTE, "/v1/m3/status", "M3 status aggregate route must remain exact"); assert.equal( exactTrustedRoute, "res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1", "M3 trusted route constant must remain exact" ); assertControlPanelDom(html); assertControlPanelSource(app); assertBrowserWriteBoundaries({ app, html, artifactPublisher, cloudWebRouteSource }); assertNoStaticDevLiveClaim({ html, app }); for (const pattern of [ /#panel-control\s*{\s*grid-template-rows:\s*auto auto auto minmax\(0,\s*1fr\);/s, /\.m3-flow-summary\s*{/s, /\.m3-flow-line\s*{/s, /\.m3-flow-kv\s*{/s, /\.m3-control-form\s*{/s, /\.m3-control-grid\s*{/s, /\.m3-control-actions\s*{/s ]) { assert.match(styles, pattern, `missing M3 control style ${pattern.source}`); } for (const copy of [ "M3 IO 控制请求失败", "M3 IO 只读 readiness 未 green", "M3 IO 只读 readiness 已确认", "受控路径受阻", "等待 cloud-api 返回 M3 IO 只读 readiness", "不可用时页面不直连模拟器", "浏览器不直连 box-simu", "阻塞原因=", "仍在等待完整可信记录", "页面不直连模拟器,也不伪造硬件状态。", "控制可达 / 等待记录", "待可信记录", "选择 DO1 true/false 后点击“写入 DO1”", "最近 trace 与 operation 可在“可信记录”页签追踪", "DO1目标=", "DI1观测=", "trusted=", "durable=" ]) { assert.match(browserSource, new RegExp(escapeRegExp(copy), "u"), `missing Chinese blocked/error copy: ${copy}`); } } function assertControlPanelDom(html) { const controlPanel = elementSectionById(html, "panel-control"); assert.match(controlPanel, /data-side-panel="control"/u, "right-side control tab panel must be present"); assert.match(controlPanel, /