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 { runDevCloudWorkbenchLocalAgentFixtureSmoke, runDevCloudWorkbenchMobileSmoke, runDevCloudWorkbenchStaticSmoke } from "../../../scripts/src/dev-cloud-workbench-smoke-lib.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", "help.md", "third_party/marked/marked.esm.js", "third_party/marked/LICENSE" ]; 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 helpMarkdown = fs.readFileSync(path.resolve(rootDir, "help.md"), "utf8"); const buildScript = fs.readFileSync(path.resolve(rootDir, "scripts/build.mjs"), "utf8"); const distContractScript = fs.readFileSync(path.resolve(rootDir, "scripts/dist-contract.mjs"), "utf8"); const markedLicense = fs.readFileSync(path.resolve(rootDir, "third_party/marked/LICENSE"), "utf8"); const artifactPublisher = fs.readFileSync(path.resolve(repoRoot, "scripts/dev-artifact-publish.mjs"), "utf8"); const frontendSource = `${html}\n${app}\n${artifactPublisher}`; const requiredTrustedRecordTerms = Object.freeze([ "Code Agent 对话记录", "接线/operation", "audit 记录", "evidence 证据", "trace/messageId", "audit.event.query + SOURCE 回退", "evidence.record.query + SOURCE 回退", "conversationId、traceId 和 messageId", "provider/model/trace/conversation", "失败原因=", "runtime.durable=false;只读 audit/evidence 不能标为 durable ready 或 DEV-LIVE。", "只读 audit 缺少 M3 patch-panel live report / operation / trace / evidence 绑定", "只读 evidence 缺少 M3 patch-panel live report / operation / trace / audit 绑定", "当前显示 SOURCE 回退,不能冒充 DEV-LIVE" ]); const workbenchSmoke = runDevCloudWorkbenchStaticSmoke(); const helpSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "help-md-contract"); const outerScrollSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "outer-scroll-contract"); const mobileLayoutSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "mobile-workbench-layout-contract"); const conversationUxSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "code-agent-conversation-ux-states"); const mobileWorkbenchSmoke = await runDevCloudWorkbenchMobileSmoke(); const mobileOuterScrollSmokeCheck = mobileWorkbenchSmoke.checks.find((check) => check.id === "mobile-outer-scroll-lock"); const mobileHelpSmokeCheck = mobileWorkbenchSmoke.checks.find((check) => check.id === "mobile-help-markdown-route"); const localAgentFixtureSmoke = await runDevCloudWorkbenchLocalAgentFixtureSmoke(); assert.equal(workbenchSmoke.status, "pass", JSON.stringify(workbenchSmoke.blockers, null, 2)); assert.equal(workbenchSmoke.evidenceLevel, "SOURCE"); assert.equal(workbenchSmoke.devLive, false); assert.equal(helpSmokeCheck?.status, "pass", "PR #114 help Markdown route must be present and ready"); assert.equal(outerScrollSmokeCheck?.status, "pass", "desktop workbench shell must lock outer page scrolling"); assert.equal(mobileLayoutSmokeCheck?.status, "pass", "mobile 390x844 workbench layout contract must be present"); assert.equal(conversationUxSmokeCheck?.status, "pass", "Code Agent conversation UX states must remain distinct"); assert.equal(workbenchSmoke.help.status, "pass", "smoke report must not leave #114 help contract pending"); assert.notEqual(mobileWorkbenchSmoke.status, "blocked", JSON.stringify(mobileWorkbenchSmoke.blockers, null, 2)); assert.equal(mobileWorkbenchSmoke.evidenceLevel, "SOURCE"); assert.equal(mobileWorkbenchSmoke.devLive, false); assert.equal(mobileOuterScrollSmokeCheck?.status, "pass", "mobile smoke must prove outer page scroll remains locked"); assert.equal(mobileHelpSmokeCheck?.status, "pass", "mobile help route must remain non-default Markdown content"); assert.equal(localAgentFixtureSmoke.status, "pass", JSON.stringify(localAgentFixtureSmoke.blockers, null, 2)); assert.equal(localAgentFixtureSmoke.evidenceLevel, "SOURCE"); assert.equal(localAgentFixtureSmoke.devLive, false); if (mobileWorkbenchSmoke.status === "pass") { assert.equal(mobileWorkbenchSmoke.viewport.width, 390); assert.equal(mobileWorkbenchSmoke.viewport.height, 844); assert.equal(mobileWorkbenchSmoke.checks.every((check) => check.status === "pass"), true); } assert.match(html, /HWLAB 云工作台/); for (const chineseWorkbenchText of [ "硬件资源", "Agent 对话", "工作清单", "可信记录", "内部复核", "使用说明", "允许的只读 RPC" ]) { assert.match(`${html}\n${app}`, new RegExp(chineseWorkbenchText)); } for (const legacyEnglishTitle of [ /Agent Conversation \/ Trace Workspace/, /Resource Tree/, /Current Blockers/, /Control Drafts/, /Trusted Records/, /Same-Origin Read Probe/, /Allowed Read RPC/, /Open Blockers/, /Light Validation/, /DEV Health Contract/, /Secondary Entry/, /Runtime Surface/ ]) { assert.doesNotMatch(`${html}\n${app}`, legacyEnglishTitle); } assert.match(html, /硬件状态:BOX-SIMU \/ Gateway-SIMU \/ hwlab-patch-panel/); assert.match(html, /id="hardware-source-status"/); assert.match(app, /function renderHardwareStatus/); assert.match(app, /function hardwareGroup/); assert.match(app, /function hardwareRow/); assert.match(app, /function trustedM3Link/); assert.match(app, /function hasTrustedM3LiveEvidence/); assert.match(app, /function hasTrustedPatchPanelLiveReport/); assert.match(app, /function linkStatusLabel/); assert.match(app, /function normalizePort/); assert.match(app, /LIVE_M3_ID_FIELDS/); assert.match(app, /patchPanelLiveReport/); assert.match(app, /runtimeCountsSourceKind = "SOURCE"/); assert.doesNotMatch(functionBody(app, "renderHardwareStatus"), /hardwareRow\(\s*\{/u); assert.match(functionBody(app, "hardwareGroup"), /group\.rows\.map\(hardwareRow\)/u); assert.doesNotMatch(app, /sourceKind:\s*counts\s*\?\s*"DEV-LIVE"/); assert.doesNotMatch(app, /counts\s*&&\s*(?:liveLink|sourceLink|m3Link)[\s\S]{0,80}\?\s*"DEV-LIVE"/); assert.match(app, /function renderRecords/); assert.match(app, /function codeAgentRecordCards/); assert.match(app, /function operationRecordCards/); assert.match(app, /function auditRecordCards/); assert.match(app, /function evidenceRecordCards/); assert.match(app, /function auditCard/); assert.match(app, /function evidenceCard/); assert.match(app, /function liveRecordTone/); assert.match(app, /function liveRecordDurabilityBlocker/); assert.match(app, /function isDurableRuntimeReady/); assert.match(app, /runtimeSummary\?\.durable !== true/); assert.match(app, /summary\.liveRuntimeEvidence !== true/); assert.match(app, /auditCard\(event, liveRecordTone\(event, live\), liveRecordDurabilityBlocker\(live\)\)/); assert.match(app, /evidenceCard\(record, liveRecordTone\(record, live\), liveRecordDurabilityBlocker\(live\)\)/); assert.match(app, /function recordGroup/); assert.match(app, /function recordField/); assert.match(app, /function sourceKindLabel/); assert.match(app, /function liveFailureCard/); assert.match(app, /state\.liveSurface/); assert.match(app, /renderRecords\(state\.liveSurface\)/); assert.match(app, /traceId: error\.traceId \|\| traceId/); assert.match(app, /error\.traceId = traceId/); for (const hardwareTerm of [ "Gateway/Box 在线", "BOX-SIMU 端口", "Patch Panel 连线", "DO1 -> patch-panel -> DI1", "AI/AO/FREQ", "future capability", "SOURCE", "DEV-LIVE", "BLOCKED", "runtime counts;不是 M3 DEV-LIVE 证据", "counts 不是 patch-panel live report", "缺少 patch-panel live report 与 operation/trace/audit/evidence IDs", "Web 不提供硬件写 RPC;不会新增伪硬件写操作。" ]) { assert.match(app, new RegExp(escapeRegExp(hardwareTerm)), `missing hardware term: ${hardwareTerm}`); } assert.match(app, /dataset\.sourceKind/); assert.match(styles, /\.record-group\s*{/); assert.match(styles, /\.record-group-title\s*{/); assert.match(styles, /\.hardware-section\s*{/); assert.match(styles, /\.hardware-row\s*{/); assert.match(html, /Gate \/ 诊断 \/ 验收/); assert.match(app, /new Set\(\["\/gate", "\/diagnostics\/gate"\]\)/); assert.match(html, /href="\/styles\.css"/); assert.match(html, /src="\/app\.mjs"/); for (const workbenchElement of [ "activity-rail", "explorer", "resource-explorer", "resource-tree", "conversation-list", "agent-chat-status", "task-list", "right-sidebar", "command-form", "command-send", "hardware-list", "control-list", "wiring-body", "records-list", "diagnostics-list" ]) { assert.match(html, new RegExp(workbenchElement)); } assert.match(html, /aria-controls="resource-explorer"/); assert.match(html, /aria-expanded="true"/); for (const viewId of ["workspace", "gate"]) { assert.match(html, new RegExp(`data-view="${viewId}"`)); } assert.match(html, /data-route="help"/); assert.match(html, /data-view="help"/); assert.match(html, /
\s*\[data-app-shell\]\s*{[^}]*height:\s*100%;[^}]*overflow:\s*hidden;[^}]*overscroll-behavior:\s*contain;/s); assert.match(styles, /\.workbench-shell\s*{[^}]*height:\s*100dvh;[^}]*max-height:\s*100dvh;[^}]*overflow:\s*hidden;[^}]*overscroll-behavior:\s*contain;/s); assert.match(helpMarkdown, /^# 云工作台内部使用说明/m); for (const helpTerm of [ "左侧资源与功能导航", "Agent 对话与工作清单", "完整中文标签", "内部复核", "BOX-SIMU", "Gateway-SIMU", "hwlab-patch-panel", "控制", "接线", "可信记录", "诊断", "Gate", "SOURCE", "LOCAL", "DRY-RUN", "DEV-LIVE", "BLOCKED", "res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1", "same-origin `/v1`", "只读 `/json-rpc`", "`/health/live`", "`:16666`", "`:16667`" ]) { assert.match(helpMarkdown, new RegExp(escapeRegExp(helpTerm)), `missing help term: ${helpTerm}`); } const workspaceHtml = sectionSource(html, "workspace"); const visibleShellHtml = `${beforeSection(html, "help")}\n${workspaceHtml}`; for (const backendTerm of [/Gate/u, /诊断/u, /验收/u, /BLOCKED/u, /M0-M5/u, /执行轨迹/u]) { assert.doesNotMatch(visibleShellHtml, backendTerm, `default workbench must not expose backend term ${backendTerm}`); } for (const fullNavLabel of ["工作台", "内部复核", "使用说明", "资源树"]) { assert.match(html, new RegExp(`>${escapeRegExp(fullNavLabel)}<`), `missing full nav label ${fullNavLabel}`); } for (const oneCharNavLabel of [">台<", ">证<", ">诊<", ">帮<"]) { assert.doesNotMatch(html, new RegExp(escapeRegExp(oneCharNavLabel)), `legacy one-character nav label must be removed: ${oneCharNavLabel}`); } assert.doesNotMatch(html, /data-side-tab-jump="records"/u, "trusted records must not remain a left-side standalone shortcut"); for (const wiringHeader of ["源设备", "源端口", "接线盘", "目标设备", "目标端口", "状态", "证据来源", "trace/evidence"]) { assert.match(html, new RegExp(`${escapeRegExp(wiringHeader)}<\\/th>`), `missing wiring column ${wiringHeader}`); } assert.match(html, /id="unblock-list"/); assert.match(app, /DB live readiness/); assert.match(app, /res_boxsimu_1:DO1 -> res_boxsimu_2:DI1/); assert.match(app, /patch-panel DO1 -> DI1/); for (const trustedRecordTerm of requiredTrustedRecordTerms) { assert.match(app, new RegExp(escapeRegExp(trustedRecordTerm)), `missing trusted-record term: ${trustedRecordTerm}`); } assert.match(functionBody(app, "renderWiringList"), /M3_TRUSTED_ROUTE\.fromResourceId/); assert.match(functionBody(app, "renderWiringList"), /M3_TRUSTED_ROUTE\.patchPanelServiceId/); assert.doesNotMatch(functionBody(app, "renderWiringList"), /activeConnections\.map/u); assert.doesNotMatch(functionBody(app, "renderWiringList"), /link\.fromResourceId/u); assert.match(buildScript, /buildCloudWebDist/); assert.match(distContractScript, /"help\.md"/); assert.match(distContractScript, /"third_party\/marked\/marked\.esm\.js"/); assert.match(distContractScript, /"third_party\/marked\/LICENSE"/); assert.match(distContractScript, /gate\/index\.html/); assert.match(distContractScript, /diagnostics\/gate\/index\.html/); assert.match(distContractScript, /help\/index\.html/); assert.match(markedLicense, /MarkedJS/); assert.match(markedLicense, /Permission is hereby granted, free of charge/); assert.match(html, /live-status/); assert.match(app, /function workbenchApiSurfaceStatus/); assert.match(app, /function workbenchSurfaceReadiness/); assert.match(app, /function hasDurableRuntimeQueryBlock/); assert.match(functionBody(app, "workbenchApiSurfaceStatus"), /surface\.durableQueryBlocked/); assert.match(functionBody(app, "workbenchApiSurfaceStatus"), /surface\.degraded/); assert.match(app, /function readinessBooleanValues/); assert.match(functionBody(app, "readinessBooleanValues"), /payload\.ready/); assert.match(functionBody(app, "readinessBooleanValues"), /payload\.readiness\?\.ready/); assert.match(functionBody(app, "workbenchSurfaceReadiness"), /readyValues\.includes\(false\)/); assert.match(functionBody(app, "hasDurableRuntimeQueryBlock"), /runtime_durable_adapter_query_blocked/); assert.match(functionBody(app, "hasDurableRuntimeQueryBlock"), /payload\.runtime\?\.blocker/); assert.match(functionBody(app, "hasDurableRuntimeQueryBlock"), /payload\.readiness\?\.durability\?\.blocker/); assert.match(functionBody(app, "hasDurableRuntimeQueryBlock"), /blockedLayer === "durability_query"/); assert.match(app, /可信记录受阻 \/ 只读模式/); assert.match(app, /API 降级 \/ 只读模式/); assert.match(app, /同源 API 可响应但处于降级状态;当前保持只读查看和本地任务整理。/); assert.match(app, /同源 API 可响应,但可信记录持久化尚未就绪;当前只保留只读查看和本地任务整理。/); assert.doesNotMatch(functionBody(app, "workbenchApiSurfaceStatus"), /runtime_durable_adapter_query_blocked|DB live readiness|Gate|诊断|验收|M0-M5|执行轨迹/u); assert.match(html, /method-list/); assert.match(html, /quick-actions/); assert.match(html, /编写任务/); assert.match(html, /查看接线/); assert.match(html, /查看状态/); assert.match(html, /placeholder="输入要发送给 Code Agent 的消息;不会直接触发硬件变更。"/); assert.match(html, />发送<\/button>/); assert.doesNotMatch(html, />添加草稿<\/button>/); assert.doesNotMatch(html, /主流程[\s\S]*添加草稿/u); assert.match(app, /sendAgentMessage/); assert.match(app, /fetchJson\("\/v1\/agent\/chat"/); assert.match(app, /Code Agent 调用失败/); assert.match(app, /normalizeBlockedAgentResult/); assert.match(app, /isBlockedAgentResponse/); assert.match(app, /isHttpNon2xxStatus/); assert.match(app, /providerStatus/); assert.match(app, /codeAgentAvailability/); assert.match(app, /codeAgentAvailabilityFrom/); assert.match(app, /latestCompletedAgentMessage/); assert.match(app, /BLOCKED 凭证缺口/); assert.match(app, /provider_unavailable/); assert.match(app, /OPENAI_API_KEY/); assert.match(app, /hwlab-code-agent-provider\/openai-api-key/); assert.match(app, /只有真实 completed 回复才标 DEV-LIVE/); assert.match(app, /不能因为只有 conversationId 标为开发实况/); assert.match(app, /TRUSTED_CODE_AGENT_PROVIDERS/); assert.match(app, /UNTRUSTED_CODE_AGENT_PROVIDER_PATTERN/); assert.match(app, /isRealCompletedChatResult/); assert.match(app, /isRealCompletedChatMessage/); assert.match(app, /hasRealCodeAgentEvidence/); assert.match(app, /isTrustedCodeAgentProvider\(value\?\.provider\)/); assert.match(app, /Boolean\(value\?\.model\)/); assert.match(app, /Boolean\(value\?\.backend\)/); assert.match(app, /Boolean\(value\?\.traceId\)/); assert.match(app, /Boolean\(value\?\.conversationId \|\| value\?\.sessionId\)/); assert.match(app, /providerTrace:\s*result\.providerTrace/); assert.match(app, /providerTraceSummary\(message\.providerTrace\)/); assert.match(app, /recordField\("conversation", message\.conversationId\)/); assert.match(app, /recordField\("provider", message\.provider\)/); assert.match(app, /Code Agent 完成证据不足/); assert.match(app, /本次不会标记为真实完成/); assert.match(app, /untrusted_completion/); assert.match(app, /function classifyCodeAgentCompletion/); assert.match(app, /function isSourceFixtureChatResult/); assert.match(app, /function isSourceFixtureCompletion/); assert.match(app, /function isSourceFixtureCompletedChatMessage/); assert.match(app, /Code Agent SOURCE 回复/); assert.match(app, /SOURCE fixture 只可显示为 SOURCE 回复,不能冒充 DEV-LIVE/); assert.match(app, /function messageEvidencePanel/); assert.match(app, /function messageEvidenceSummary/); assert.match(app, /function boundedEvidenceField/); assert.match(app, /sourceKind:\s*completion\.sourceKind/); assert.match(app, /status === "completed" \? "dev-live"/); assert.doesNotMatch(app, /status === "failed" \? "dev-live"/); assert.doesNotMatch(`${html}\n${app}`, /provider_unavailable[\s\S]{0,120}tone-[\w-]*dev-live/iu); assert.doesNotMatch(app, /tone:\s*state\.conversationId\s*\?\s*"dev-live"/); assert.doesNotMatch(`${html}\n${app}`, /sk-[A-Za-z0-9._-]{8,}/u); assert.match(styles, /\.message-card\.status-completed\s*{/); assert.match(styles, /\.message-card\.status-source\s*{/); assert.match(styles, /\.message-user\s*{/); assert.match(styles, /\.message-agent,\s*\n\.message-system\s*{/); assert.match(styles, /\.message-evidence\s*{/); assert.match(styles, /\.message-evidence-chip\s*{/); assert.match(app, /state\.conversationId/); assert.match(app, /conversationId/); assert.match(app, /messageId/); assert.match(app, /createdAt/); assert.match(app, /updatedAt/); assert.match(app, /error\?\.message/); 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|task-list|hardware-list)[^{]*{[^}]*min-height:\s*0;[^}]*overflow:\s*auto;/s); assert.match(app, /function syncMobileExplorer/); assert.match(app, /window\.matchMedia\("\(max-width: 860px\)"\)/); assert.match(app, /setExplorerCollapsed\(true\)/); assert.match(app, /function collapseExplorerAfterMobileAction/); assert.match(functionBody(app, "setExplorerCollapsed"), /aria-expanded/); assert.match(styles, /\.explorer-collapsed \.explorer\s*{[^}]*pointer-events:\s*none;/s); assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.activity-rail\s*{[\s\S]*?grid-row:\s*1 \/ 3;/); assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.explorer\s*{[\s\S]*?grid-row:\s*1 \/ 3;[\s\S]*?z-index:\s*4;/); assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.center-workspace\s*{[\s\S]*?grid-row:\s*1;/); assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.right-sidebar\s*{[\s\S]*?grid-row:\s*2;/); assert.match(styles, /@media \(max-width: 520px\)[\s\S]*?\.command-bar\s*{[\s\S]*?grid-template-columns:\s*minmax\(0, 1fr\) auto auto;/); 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, /url\.pathname === "\/v1\/agent\/chat"/); 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(artifactPublisher, /url\.pathname\.replace\(\/\\\/\+\$\/,\s*""\) \|\| "\/"/); assert.match(artifactPublisher, /routePath === "\/gate"/); assert.match(artifactPublisher, /routePath === "\/diagnostics\/gate"/); assert.match(artifactPublisher, /routePath === "\/help"/); 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.deepEqual(gateSummary.topology.gateways.map((gateway) => gateway.gatewayId), ["gwsimu_1", "gwsimu_2"]); assert.deepEqual(gateSummary.topology.boxResources.map((resource) => resource.resourceId), ["res_boxsimu_1", "res_boxsimu_2"]); assert.deepEqual(gateSummary.topology.patchPanel.activeConnections, [ { fromResourceId: "res_boxsimu_1", fromPort: "DO1", toResourceId: "res_boxsimu_2", toPort: "DI1" } ]); assert.doesNotMatch( JSON.stringify(gateSummary.topology), /res_m5-control-relay|res_m5-target-board|out1|reset/u, "16666 default topology must not render M5/reset wiring" ); assert.equal(gateSummary.m5DryRunTopology.patchPanel.patchPanelStatusId, "pps_m5-0001"); assert.equal(gateSummary.agent.agentServiceId, "hwlab-agent-mgr"); assert.equal(gateSummary.agent.workerServiceId, "hwlab-agent-worker"); assert.equal(gateSummary.operations.length, 2); assert.ok(gateSummary.operations.every((operation) => operation.dryRun === true)); assert.ok(gateSummary.operations.every((operation) => operation.requestedAt && operation.updatedAt)); assert.equal(gateSummary.auditEvents.length, 7); assert.ok(gateSummary.auditEvents.every((event) => event.dryRun === true)); assert.ok(gateSummary.auditEvents.every((event) => event.traceId && event.occurredAt)); assert.equal(gateSummary.evidenceRecords.length, 2); assert.ok(gateSummary.evidenceRecords.every((record) => record.traceId && record.createdAt && record.dryRun === true)); 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"); function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function sectionSource(source, viewId) { const start = source.search(new RegExp(`]*\\bdata-view=["']${escapeRegExp(viewId)}["'][^>]*>`, "u")); if (start === -1) return ""; const next = source.slice(start + 1).search(/]*\bdata-view=["'][^"']+["'][^>]*>/u); return next === -1 ? source.slice(start) : source.slice(start, start + 1 + next); } function beforeSection(source, viewId) { const index = source.search(new RegExp(`]*\\bdata-view=["']${escapeRegExp(viewId)}["'][^>]*>`, "u")); return index === -1 ? source : source.slice(0, index); } function functionBody(source, functionName) { const match = source.match(new RegExp(`function\\s+${escapeRegExp(functionName)}\\s*\\([^)]*\\)\\s*\\{`, "u")); if (!match) return ""; let depth = 0; for (let index = match.index + match[0].length - 1; index < source.length; index += 1) { const char = source[index]; if (char === "{") depth += 1; if (char === "}") { depth -= 1; if (depth === 0) return source.slice(match.index, index + 1); } } return ""; }