Files
pikasTech-HWLAB/web/hwlab-cloud-web/scripts/check.mjs
T
2026-05-25 03:16:55 +00:00

1348 lines
75 KiB
JavaScript

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,
runDevCloudWorkbenchLayoutSmoke,
runDevCloudWorkbenchMobileSmoke,
runDevCloudWorkbenchAuthFixtureSmoke,
runDevCloudWorkbenchQuickPromptsFixtureSmoke,
runDevCloudWorkbenchSlowBlockerFixtureSmoke,
runDevCloudWorkbenchStaticSmoke
} from "../../../scripts/src/dev-cloud-workbench-smoke-lib.mjs";
import { gateSummary } from "../gate-summary.mjs";
import { runtime } from "../runtime.mjs";
import {
codeAgentM3EvidenceContractSummary,
extractCodeAgentM3Evidence,
m3EvidenceRows
} from "../code-agent-m3-evidence.mjs";
import { runM3ControlPanelGuard } from "./m3-control-panel-guard.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",
"auth.mjs",
"app.mjs",
"code-agent-facts.mjs",
"code-agent-status.mjs",
"code-agent-m3-evidence.mjs",
"live-status.mjs",
"gate-summary.mjs",
"runtime.mjs",
"wiring-status.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 auth = fs.readFileSync(path.resolve(rootDir, "auth.mjs"), "utf8");
const app = fs.readFileSync(path.resolve(rootDir, "app.mjs"), "utf8");
const codeAgentFacts = fs.readFileSync(path.resolve(rootDir, "code-agent-facts.mjs"), "utf8");
const codeAgentStatus = fs.readFileSync(path.resolve(rootDir, "code-agent-status.mjs"), "utf8");
const codeAgentM3Evidence = fs.readFileSync(path.resolve(rootDir, "code-agent-m3-evidence.mjs"), "utf8");
const liveStatus = fs.readFileSync(path.resolve(rootDir, "live-status.mjs"), "utf8");
const wiringStatus = fs.readFileSync(path.resolve(rootDir, "wiring-status.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 cloudWebRoutes = fs.readFileSync(path.resolve(repoRoot, "internal/dev-entrypoint/cloud-web-routes.mjs"), "utf8");
const cloudWebProxy = fs.readFileSync(path.resolve(repoRoot, "internal/dev-entrypoint/cloud-web-proxy.mjs"), "utf8");
const cloudApiServer = fs.readFileSync(path.resolve(repoRoot, "internal/cloud/server.mjs"), "utf8");
const cloudJsonRpc = fs.readFileSync(path.resolve(repoRoot, "internal/cloud/json-rpc.mjs"), "utf8");
const gatewayDemoRegistry = fs.readFileSync(path.resolve(repoRoot, "internal/cloud/gateway-demo-registry.mjs"), "utf8");
const gatewayMain = fs.readFileSync(path.resolve(repoRoot, "cmd/hwlab-gateway/main.mjs"), "utf8");
const gatewayDemoSmoke = fs.readFileSync(path.resolve(repoRoot, "scripts/gateway-outbound-demo-smoke.mjs"), "utf8");
const codexStdioSession = fs.readFileSync(path.resolve(repoRoot, "internal/cloud/codex-stdio-session.mjs"), "utf8");
const gatewayShellTool = fs.readFileSync(path.resolve(repoRoot, "tools/hwlab-gateway-shell.mjs"), "utf8");
const frontendSource = `${html}\n${auth}\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 m3EvidenceFixture = {
status: "completed",
provider: "hwlab-skill-cli",
model: "controlled-m3-io",
backend: "hwlab-cloud-api/hwlab-agent-runtime-skill-cli",
traceId: "trc_check_m3_evidence",
conversationId: "conv_check_m3_evidence",
sessionId: "sess_check_m3_evidence",
messageId: "msg_check_m3_evidence",
runner: {
kind: "hwlab-m3-io-skill-cli"
},
providerTrace: {
runnerKind: "hwlab-m3-io-skill-cli",
skill: "hwlab-agent-runtime.m3-io",
route: "/v1/m3/io",
status: "succeeded",
accepted: true,
operationId: "op_check_m3_evidence",
traceId: "trc_check_m3_evidence"
},
toolCalls: [
{
type: "skill-cli",
name: "hwlab-agent-runtime.m3-io",
route: "/v1/m3/io",
status: "completed",
accepted: true,
operationId: "op_check_m3_evidence",
traceId: "trc_check_m3_evidence",
audit: {
auditId: "aud_check_m3_evidence"
},
evidence: {
evidenceId: "evd_check_m3_evidence"
},
command: {
action: "do.write",
resourceId: "res_boxsimu_1",
port: "DO1",
value: true
},
result: {
targetReadback: {
resourceId: "res_boxsimu_2",
port: "DI1",
value: true
}
},
safety: {
cloudApiRouteOnly: true,
allowedRoute: "/v1/m3/io",
directGatewayCalls: false,
directBoxCalls: false,
directPatchPanelCalls: false
}
}
]
};
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 resourceExplorerRemovalSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "feedback-352-resource-explorer-removed");
const conversationUxSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "code-agent-conversation-ux-states");
const codeAgentTimeoutSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "code-agent-long-timeout-contract");
const codeAgentQuickPromptsSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "code-agent-quick-prompts-contract");
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();
const localAgentNo4500Check = localAgentFixtureSmoke.checks.find((check) => check.id === "local-agent-fixture-no-4500ms-permanent-failure");
const localAgentMobilePendingCheck = localAgentFixtureSmoke.checks.find((check) => check.id === "local-agent-fixture-mobile-pending-layout");
const slowBlockerFixtureSmoke = await runDevCloudWorkbenchSlowBlockerFixtureSmoke();
const slowBlockerCheck = slowBlockerFixtureSmoke.checks.find((check) => check.id === "local-agent-fixture-slow-structured-blocker");
const quickPromptsFixtureSmoke = await runDevCloudWorkbenchQuickPromptsFixtureSmoke();
const quickPromptsFillCheck = quickPromptsFixtureSmoke.checks.find((check) => check.id === "quick-prompts-fill-input");
const quickPromptsNoAutosendCheck = quickPromptsFixtureSmoke.checks.find((check) => check.id === "quick-prompts-write-no-autosend");
const quickPromptsCopyCheck = quickPromptsFixtureSmoke.checks.find((check) => check.id === "quick-prompts-copy-boundary");
const quickPromptsLayoutCheck = quickPromptsFixtureSmoke.checks.find((check) => check.id === "quick-prompts-mobile-layout");
const authFixtureSmoke = await runDevCloudWorkbenchAuthFixtureSmoke();
const authSuccessDesktopCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-login-success-desktop");
const authFailureDesktopCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-login-failure-desktop");
const authRefreshDesktopCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-refresh-session-desktop");
const authLogoutDesktopCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-logout-expiry-desktop");
const authSuccessMobileCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-login-success-mobile");
const authFailureMobileCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-login-failure-mobile");
const authRefreshMobileCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-refresh-session-mobile");
const authLogoutMobileCheck = authFixtureSmoke.checks.find((check) => check.id === "auth-logout-expiry-mobile");
const layoutSmokeReportPath = path.resolve(repoRoot, "tmp/dev-cloud-workbench-layout-web-check.json");
const layoutSmoke = await runDevCloudWorkbenchLayoutSmoke({ reportPath: layoutSmokeReportPath });
fs.mkdirSync(path.dirname(layoutSmokeReportPath), { recursive: true });
layoutSmoke.artifacts = { ...(layoutSmoke.artifacts ?? {}), reportPath: layoutSmokeReportPath };
fs.writeFileSync(layoutSmokeReportPath, `${JSON.stringify(layoutSmoke, null, 2)}\n`, "utf8");
assert.notEqual(
mobileWorkbenchSmoke.status,
"skip",
[
mobileWorkbenchSmoke.summary,
"Install repository dependencies and run `node scripts/dev-cloud-workbench-smoke.mjs --mobile` to generate the required mobile outer-scroll evidence."
].filter(Boolean).join(" ")
);
assert.notEqual(
layoutSmoke.status,
"skip",
[
layoutSmoke.summary,
"Install repository dependencies and run `node scripts/dev-cloud-workbench-layout-smoke.mjs --static` to generate the required layout evidence."
].filter(Boolean).join(" ")
);
assert.equal(workbenchSmoke.status, "pass", JSON.stringify(workbenchSmoke.blockers, null, 2));
assert.equal(workbenchSmoke.mode, "source");
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(resourceExplorerRemovalSmokeCheck?.status, "pass", "#352 resource explorer removal source contract must be present");
assert.equal(conversationUxSmokeCheck?.status, "pass", "Code Agent conversation UX states must remain distinct");
assert.equal(codeAgentTimeoutSmokeCheck?.status, "pass", "Code Agent chat must not reuse the 4500ms light API timeout");
assert.equal(codeAgentQuickPromptsSmokeCheck?.status, "pass", "Code Agent quick prompts must remain fill-only with explicit send semantics for writes");
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.ok(
mobileOuterScrollSmokeCheck,
"mobile smoke must include the mobile-outer-scroll-lock check; run `node scripts/dev-cloud-workbench-smoke.mjs --mobile` with Playwright installed."
);
assert.equal(mobileOuterScrollSmokeCheck.status, "pass", "mobile smoke must prove outer page scroll remains locked");
assert.ok(
mobileHelpSmokeCheck,
"mobile smoke must include the mobile-help-markdown-route check; run `node scripts/dev-cloud-workbench-smoke.mjs --mobile` with Playwright installed."
);
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);
assert.equal(localAgentNo4500Check?.status, "pass", "local Code Agent fixture must prove the 4500ms browser timeout path is not permanent");
assert.equal(localAgentMobilePendingCheck?.status, "pass", "local Code Agent fixture must keep mobile pending state visible with trace/session context");
assert.equal(slowBlockerFixtureSmoke.status, "pass", JSON.stringify(slowBlockerFixtureSmoke.blockers, null, 2));
assert.equal(slowBlockerFixtureSmoke.evidenceLevel, "SOURCE");
assert.equal(slowBlockerFixtureSmoke.devLive, false);
assert.equal(slowBlockerCheck?.status, "pass", "slow structured blocker fixture must show Chinese reason, trace, retry input, and no 4500ms permanent failure");
assert.equal(quickPromptsFixtureSmoke.status, "pass", JSON.stringify(quickPromptsFixtureSmoke.blockers, null, 2));
assert.equal(quickPromptsFixtureSmoke.evidenceLevel, "SOURCE");
assert.equal(quickPromptsFixtureSmoke.devLive, false);
assert.equal(quickPromptsFillCheck?.status, "pass", "quick prompt buttons must fill and focus the Code Agent input");
assert.equal(quickPromptsNoAutosendCheck?.status, "pass", "HWLAB API write quick prompts must not auto-send");
assert.equal(quickPromptsCopyCheck?.status, "pass", "quick prompt labels must say HWLAB API / Skill CLI without direct hardware-control copy");
assert.equal(quickPromptsLayoutCheck?.status, "pass", "quick prompt strip must stay contained on desktop and 390x844 mobile");
assert.equal(authFixtureSmoke.status, "pass", JSON.stringify(authFixtureSmoke.blockers, null, 2));
assert.equal(authFixtureSmoke.evidenceLevel, "SOURCE");
assert.equal(authFixtureSmoke.devLive, false);
assert.equal(authSuccessDesktopCheck?.status, "pass", "desktop login must accept admin/hwlab2026 and enter the Cloud Workbench");
assert.equal(authFailureDesktopCheck?.status, "pass", "desktop login failure must show a bounded Chinese error and keep workbench hidden");
assert.equal(authRefreshDesktopCheck?.status, "pass", "desktop refresh must preserve authenticated session state");
assert.equal(authLogoutDesktopCheck?.status, "pass", "desktop logout and expired session must return to login");
assert.equal(authSuccessMobileCheck?.status, "pass", "390x844 mobile login must enter workbench without outer scroll or hidden controls");
assert.equal(authFailureMobileCheck?.status, "pass", "390x844 mobile login failure must keep form visible and error contained");
assert.equal(authRefreshMobileCheck?.status, "pass", "390x844 mobile refresh must preserve authenticated session state");
assert.equal(authLogoutMobileCheck?.status, "pass", "390x844 mobile logout and expired session must return to login");
assert.equal(
layoutSmoke.status,
"pass",
JSON.stringify(compactLayoutSmokeFailures(layoutSmoke), null, 2)
);
assert.equal(layoutSmoke.sourceMode, "source-static");
assert.equal(layoutSmoke.evidenceLevel, "SOURCE");
assert.equal(layoutSmoke.devLive, false);
assert.equal(layoutSmoke.safety.layoutOnly, true);
assert.equal(layoutSmoke.safety.codeAgentPostSent, false);
assert.equal(layoutSmoke.safety.hardwareWriteApis, false);
assert.equal(layoutSmoke.failures.length, 0);
assert.equal(layoutSmoke.viewports.map((viewport) => `${viewport.width}x${viewport.height}`).join(","), "1366x768,1024x768,390x844");
assert.equal(
layoutSmoke.checks.find((check) => check.id === "layout-issue-287-future-hardware-status-tabs")?.status,
"pass",
"#287 hardware status tabs must remain covered by layout smoke"
);
assert.equal(
layoutSmoke.checks.find((check) => check.id === "layout-hardware-tabs-center-hit")?.status,
"pass",
"hardware status tabs must be center-hit reachable on desktop and 390x844 mobile"
);
assert.equal(
layoutSmoke.checks.find((check) => check.id === "layout-wiring-readable-two-column")?.status,
"pass",
"#276 wiring table must stay readable, non-overlapping, and horizontally contained"
);
assert.equal(
layoutSmoke.checks.find((check) => check.id === "layout-feedback-352-resource-explorer-removed")?.status,
"pass",
"#352 resource explorer removal must remain covered by desktop and 390x844 layout smoke"
);
assert.equal(
layoutSmoke.checks.find((check) => check.id === "layout-feedback-437-live-build-overlay")?.status,
"pass",
"#437 live build details overlay must not move the topbar, workspace, or conversation geometry"
);
assert.equal(
layoutSmoke.checks.find((check) => check.id === "layout-left-sidebar-collapse")?.status,
"pass",
"#278 left activity rail collapse/expand must remain covered by desktop and 390x844 layout smoke"
);
assert.equal(
layoutSmoke.checks.find((check) => check.id === "layout-issue-288-future-single-table-gate")?.status,
"skip",
"#288 single-table gate is future coverage; current /gate route remains covered by layout smoke"
);
for (const layoutCheckId of [
"layout-desktop-default",
"layout-narrow-desktop-default",
"layout-mobile-default",
"layout-feedback-352-resource-explorer-removed",
"layout-feedback-437-live-build-overlay",
"layout-left-sidebar-collapse",
"layout-right-sidebar-resize-desktop",
"layout-right-sidebar-resize-narrow-desktop",
"layout-right-sidebar-resize-mobile",
"layout-gate-desktop",
"layout-gate-narrow-desktop",
"layout-gate-mobile"
]) {
assert.equal(layoutSmoke.checks.find((check) => check.id === layoutCheckId)?.status, "pass", `${layoutCheckId} must pass in web:check layout gate`);
}
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 对话",
"工作清单",
"可信记录",
"内部复核",
"使用说明"
]) {
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, /M3 实况状态/);
assert.match(html, /硬件状态工作区/);
assert.match(html, /id="hardware-source-status"/);
assert.match(app, /function renderHardwareStatus/);
assert.match(app, /wiringPresentationFromSummary/);
assert.match(wiringStatus, /function wiringPresentationFromSummary/);
assert.match(wiringStatus, /function trustedM3StatusEvidenceFrom/);
assert.match(functionBody(wiringStatus, "trustedM3StatusEvidenceFrom"), /contractVersion !== "m3-status-v1"/);
assert.match(functionBody(wiringStatus, "trustedM3StatusEvidenceFrom"), /sourceKind !== "DEV-LIVE"/);
assert.match(functionBody(wiringStatus, "trustedM3StatusEvidenceFrom"), /trust\.durableStatus !== "green"/);
assert.match(functionBody(wiringStatus, "trustedM3StatusEvidenceFrom"), /trust\.blocker != null/);
assert.match(functionBody(wiringStatus, "trustedM3StatusEvidenceFrom"), /LIVE_M3_ID_FIELDS\.every/);
assert.match(functionBody(wiringStatus, "wiringPresentationFromSummary"), /接线实况 \/ 持久化受阻/);
assert.match(functionBody(wiringStatus, "wiringPresentationFromSummary"), /durableStatus !== "green"/);
assert.match(functionBody(wiringStatus, "wiringPresentationFromSummary"), /blockedTrust != null/);
assert.match(functionBody(wiringStatus, "wiringPresentationFromSummary"), /\/v1\/m3\/status 聚合 \/ 实况可信记录/);
assert.match(app, /function initHardwareTabs/);
assert.match(app, /function hardwareKvGroup/);
assert.match(app, /function sourceFallbackM3Status/);
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(functionBody(app, "sourceFallbackM3Status"), /unverified_cloud_api_m3_status/);
assert.match(functionBody(app, "sourceFallbackM3Status"), /SOURCE 拓扑不能冒充实况硬件状态/);
assert.doesNotMatch(functionBody(app, "renderHardwareStatus"), /hardwareRow\(\s*\{/u);
assert.match(functionBody(app, "renderHardwareStatus"), /Key\/Value groups/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 safeFailureReason/);
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|response\.data\?\.traceId \|\| traceId)/);
assert.match(app, /DEFAULT_API_TIMEOUT_MS\s*=\s*4500/);
assert.match(app, /DEFAULT_CODE_AGENT_TIMEOUT_MS\s*=\s*600000/);
assert.match(app, /DEFAULT_CODE_AGENT_SUBMIT_TIMEOUT_MS\s*=\s*60000/);
assert.match(app, /CODE_AGENT_TIMEOUT_STORAGE_KEY/);
assert.match(functionBody(app, "clampTimeoutMs"), /value === null \|\| value === undefined/);
assert.match(functionBody(app, "clampTimeoutMs"), /typeof value === "string" && value\.trim\(\) === ""/);
assert.match(functionBody(app, "sendAgentMessage"), /timeoutMs:\s*CODE_AGENT_SUBMIT_TIMEOUT_MS/);
assert.match(functionBody(app, "sendAgentMessage"), /shortConnection:\s*true/);
assert.match(functionBody(app, "waitForAgentMessageResult"), /idleMs\s*>=\s*CODE_AGENT_TIMEOUT_MS/);
assert.match(html, /id="code-agent-timeout"/);
assert.match(styles, /\.agent-timeout-control\s*{/);
assert.match(app, /activityRef:\s*\(\)\s*=>\s*state\.currentRequest/);
assert.match(functionBody(app, "fetchJson"), /timeoutMs\s*=\s*API_TIMEOUT_MS/);
assert.match(functionBody(app, "fetchJson"), /无新事件/);
for (const hardwareTerm of [
"Gateway 在线",
"BOX 在线",
"链路状态",
"接线盘连接",
"runtime durable",
"DISABLED 后续能力",
"SOURCE",
"DEV-LIVE",
"unverified_cloud_api_m3_status",
"/v1/m3/status",
"仍在等待完整可信记录",
"页面不直连模拟器,也不伪造硬件状态。"
]) {
assert.match(app, new RegExp(escapeRegExp(hardwareTerm)), `missing hardware term: ${hardwareTerm}`);
}
for (const hardwareHtmlTerm of ["Gateway-SIMU", "BOX-SIMU-1", "BOX-SIMU-2", "Patch Panel"]) {
assert.match(html, new RegExp(escapeRegExp(hardwareHtmlTerm)), `missing hardware HTML term: ${hardwareHtmlTerm}`);
}
assert.match(app, /function hardwareEvidenceLabel/);
assert.match(styles, /\.record-group\s*{/);
assert.match(styles, /\.record-group-title\s*{/);
assert.match(styles, /\.hardware-section\s*{/);
assert.match(styles, /\.hardware-status-tabs\s*{/);
assert.match(styles, /\.kv-row\s*{/);
assert.match(styles, /\.hardware-row\s*{/);
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",
"left-sidebar-toggle",
"conversation-list",
"agent-chat-status",
"code-agent-summary",
"code-agent-summary-label",
"code-agent-summary-capability",
"code-agent-summary-trace",
"right-sidebar",
"right-sidebar-resize",
"command-form",
"command-send",
"hardware-list",
"control-list",
"wiring-body",
"records-list",
"gate-review-body"
]) {
assert.match(html, new RegExp(workbenchElement));
}
for (const removedSelectorTerm of [
"id=\"resource-explorer\"",
"id=\"explorer-toggle\"",
"id=\"explorer-resize\"",
"id=\"resource-tree\"",
"explorer-capabilities",
"capability-count",
"tree-count",
"quick-actions",
"data-focus-command"
]) {
assert.doesNotMatch(html, new RegExp(escapeRegExp(removedSelectorTerm)), `removed resource explorer selector returned: ${removedSelectorTerm}`);
}
for (const removedUiText of [
"资源浏览与硬件资源树",
"资源浏览",
"硬件资源树",
"只读工作区",
"常用能力",
"收起资源树",
"展开资源树",
"拖拽调整左侧资源树宽度",
"资源查看",
"查看接线",
"查看状态",
"编写任务"
]) {
assert.doesNotMatch(`${html}\n${app}\n${styles}`, new RegExp(escapeRegExp(removedUiText)), `removed resource explorer copy returned: ${removedUiText}`);
}
assert.match(html, /id="hardware-sidebar"/);
assert.match(html, /id="activity-rail"/);
assert.match(html, /id="left-sidebar-toggle"/);
assert.match(html, /aria-label="折叠左侧导航"/);
assert.match(html, /title="折叠左侧导航"/);
assert.match(html, /aria-controls="activity-rail"/);
assert.match(html, /aria-expanded="true"/);
assert.match(html, /id="right-sidebar-resize"/);
assert.match(html, /aria-label="拖拽调整右侧硬件状态栏宽度"/);
assert.match(html, /aria-valuemin="560"/);
assert.match(html, /aria-valuemax="740"/);
assert.match(html, /aria-valuenow="728"/);
assert.doesNotMatch(app, /setExplorerCollapsed|initExplorerResize|clampExplorerWidth|syncMobileExplorer|collapseExplorerAfterMobileAction/);
assert.match(app, /LAYOUT_STORAGE_KEY\s*=\s*"hwlab\.workbench\.layout\.v1"/);
assert.match(app, /leftSidebarCollapsed:\s*payload\.leftSidebarCollapsed === true/);
assert.match(app, /leftSidebarCollapsed:\s*state\.layout\.leftSidebarCollapsed/);
assert.match(app, /function initLeftSidebarToggle/);
assert.match(app, /function setLeftSidebarCollapsed/);
assert.match(app, /is-left-sidebar-collapsed/);
assert.match(app, /展开左侧导航/);
assert.match(app, /折叠左侧导航/);
assert.match(app, /function initRightSidebarResize/);
assert.match(app, /function clampRightSidebarWidth/);
assert.match(app, /function rightSidebarResizeBounds/);
assert.match(app, /setPointerCapture/);
assert.match(app, /localStorage\?\.setItem\(\s*LAYOUT_STORAGE_KEY/);
assert.match(app, /rightSidebarWidth:\s*state\.layout\.rightSidebarWidth/);
assert.match(app, /ArrowLeft/);
assert.match(app, /ArrowRight/);
assert.match(app, /event\.key === "Home"/);
assert.match(app, /event\.key === "End"/);
assert.doesNotMatch(styles, /--explorer-width|--explorer-min-width|--explorer-max-width|\.explorer|\.explorer-resize|\.quick-actions|\.resource-tree|\.tree-panel|\.tree-row|\.tree-label/);
assert.match(styles, /--right-sidebar-width:\s*728px/);
assert.match(styles, /--right-sidebar-min-width:\s*560px/);
assert.match(styles, /--right-sidebar-max-width:\s*740px/);
assert.match(styles, /--right-width:\s*clamp\(var\(--right-sidebar-min-width\),\s*var\(--right-sidebar-width\),\s*var\(--right-sidebar-max-width\)\)/);
assert.match(styles, /--right-width-expanded:\s*clamp\(var\(--right-sidebar-min-width\),\s*var\(--right-sidebar-width\),\s*var\(--right-sidebar-max-width\)\)/);
assert.match(styles, /--rail-collapsed-width:\s*44px/);
assert.match(styles, /\.workbench-shell\.is-left-sidebar-collapsed\s*{[^}]*--rail-width:\s*var\(--rail-collapsed-width\);/s);
assert.match(styles, /\.workbench-shell\.is-left-sidebar-collapsed \.activity-rail \.rail-button:not\(\.sidebar-toggle\),\s*\n\.workbench-shell\.is-left-sidebar-collapsed \.rail-spacer\s*{[^}]*display:\s*none;/s);
assert.match(styles, /\.sidebar-toggle\[aria-expanded="false"\]\s*{/);
assert.match(styles, /\.right-sidebar-resize\s*{[^}]*cursor:\s*col-resize;/s);
assert.match(styles, /@media \(max-width: 1240px\)[\s\S]*?\.right-sidebar-resize\s*{[\s\S]*?display:\s*none;/);
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, /<section class="view help-view"[\s\S]*?hidden/);
assert.match(html, /id="help-content"/);
assert.match(html, /data-help-route-policy="non-default-internal-help"/);
assert.match(app, /from "\.\/third_party\/marked\/marked\.esm\.js"/);
assert.match(app, /marked\.parse\(/);
assert.doesNotMatch(app, /function\s+parseMarkdown|markedRegex|markdownRegex/);
assert.match(app, /fetch\("\/help\.md"/);
assert.match(app, /帮助内容加载失败/);
assert.match(functionBody(app, "routeFromLocation"), /replace\(\/\^#\\\/\?\//);
assert.match(functionBody(app, "routeFromLocation"), /helpPathnames\(\)\.has/);
assert.match(functionBody(app, "routeFromLocation"), /return "help"/);
assert.match(app, /return "workspace";/);
assert.match(styles, /\.help-view\s*{[^}]*overflow:\s*hidden;/s);
assert.match(styles, /\.help-panel\s*{[^}]*overflow:\s*hidden;/s);
assert.match(styles, /\.help-content\s*{[^}]*overflow:\s*auto;/s);
assert.match(styles, /body\s*>\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`",
"`/v1/diagnostics/gate`",
"只读 `/json-rpc`",
"`/health/live`",
"`:16666`",
"`:16667`"
]) {
assert.match(helpMarkdown, new RegExp(escapeRegExp(helpTerm)), `missing help term: ${helpTerm}`);
}
const defaultFirstViewportText = visibleTextFromHtml(defaultFirstViewportHtml(html));
for (const backendTerm of ["Gate", "诊断", "验收", "BLOCKED", "SOURCE", "DRY-RUN", "DEV-LIVE", "M0-M5", "M3 基本", "trace/evidence", "执行轨迹", "OPENAI_API_KEY", "Secret", "secretRef", "hwlab-code-agent-provider", "m3-route-required", "waiting-for-dev-live"]) {
assert.equal(textHasForbiddenTerm(defaultFirstViewportText, backendTerm), false, `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");
const wiringPanel = html.match(/<section\b[^>]*\bid="panel-wiring"[\s\S]*?<\/section>/u)?.[0] ?? "";
assert.match(wiringPanel, /data-wiring-layout="two-column-long-table"/u, "wiring panel must use the #276 two-column long-table layout");
assert.equal((wiringPanel.match(/<th\b/gu) ?? []).length, 2, "wiring table must have exactly two device header columns");
assert.equal((wiringPanel.match(/<td\b/gu) ?? []).length, 2, "wiring table static IO row must have exactly two port cells");
for (const wiringTerm of ["res_boxsimu_1", "res_boxsimu_2", "DO1", "DI1", "hwlab-patch-panel", "状态", "证据来源", "轨迹/证据"]) {
assert.match(wiringPanel, new RegExp(escapeRegExp(wiringTerm)), `missing wiring term ${wiringTerm}`);
}
for (const legacyWideHeader of ["源设备", "源端口", "接线盘", "目标设备", "目标端口", "状态", "证据来源", "轨迹/证据"]) {
assert.doesNotMatch(wiringPanel, new RegExp(`<th>${escapeRegExp(legacyWideHeader)}<\\/th>`), `legacy wide wiring column must not return: ${legacyWideHeader}`);
}
const gateHtml = sectionSource(html, "gate");
assert.match(gateHtml, /<h2 id="gate-title">内部复核<\/h2>/);
assert.match(gateHtml, /id="gate-source-status"/);
assert.match(gateHtml, /id="gate-status-filter"/);
assert.match(gateHtml, /id="gate-search"/);
assert.match(gateHtml, /id="gate-refresh"/);
assert.match(gateHtml, /class="gate-table"/);
assert.equal((gateHtml.match(/class="gate-table"/gu) ?? []).length, 1, "/gate must contain one main table");
for (const gateHeader of ["类别", "检查项", "状态", "归因对象", "关键细节", "证据/trace", "更新时间", "下一步"]) {
assert.match(gateHtml, new RegExp(`<th>${escapeRegExp(gateHeader)}<\\/th>`), `missing gate table column ${gateHeader}`);
}
for (const gateStatus of ["通过", "阻塞", "失败", "待验证", "信息"]) {
assert.match(`${gateHtml}\n${app}`, new RegExp(escapeRegExp(gateStatus)), `missing gate status ${gateStatus}`);
}
for (const forbiddenGateMarker of ["workspace-panel", "gate-grid", "gate-split", "milestone-grid", "trace-list", "health-body", "diagnostics-list", "method-list", "unblock-list"]) {
assert.doesNotMatch(gateHtml, new RegExp(escapeRegExp(forbiddenGateMarker)), `/gate must not contain legacy dashboard marker ${forbiddenGateMarker}`);
}
assert.match(app, /fetchJson\("\/v1\/diagnostics\/gate"/);
assert.match(app, /function renderGateTable/);
assert.match(app, /function normalizeGateRow/);
assert.match(app, /function gateLiveBlockerRow/);
assert.match(app, /sourceKind:\s*"LIVE-BACKEND"/);
assert.match(app, /live 后端聚合不可用;页面不会回退到 SOURCE 静态拓扑。/);
assert.doesNotMatch(app, /function\s+(?:renderTrace|renderUnblockList|renderDiagnostics|renderGateDiagnostics|renderMethodList)\s*\(/);
assert.match(styles, /\.gate-table\s*{/);
assert.match(styles, /\.gate-controls\s*{/);
assert.doesNotMatch(styles, /\.gate-grid\s*{/);
assert.doesNotMatch(styles, /\.milestone-grid\s*{/);
assert.doesNotMatch(styles, /\.trace-list\s*{/);
for (const trustedRecordTerm of requiredTrustedRecordTerms) {
assert.match(app, new RegExp(escapeRegExp(trustedRecordTerm)), `missing trusted-record term: ${trustedRecordTerm}`);
}
assert.match(codeAgentM3Evidence, /export function extractCodeAgentM3Evidence/);
assert.match(codeAgentM3Evidence, /operationId/);
assert.match(codeAgentM3Evidence, /auditId/);
assert.match(codeAgentM3Evidence, /evidenceId/);
assert.match(codeAgentM3Evidence, /readback-mismatch/);
assert.match(codeAgentM3Evidence, /direct-path-invalid/);
assert.match(codeAgentM3Evidence, /未产生\/不可证明/);
assert.match(codeAgentM3EvidenceContractSummary(), /operation\/audit\/evidence/u);
assert.equal(extractCodeAgentM3Evidence(m3EvidenceFixture).verdict.key, "accepted");
assert.equal(m3EvidenceRows(extractCodeAgentM3Evidence(m3EvidenceFixture)).find((row) => row.label === "auditId")?.value, "aud_check_m3_evidence");
assert.match(app, /extractCodeAgentM3Evidence\(result\)/);
assert.match(app, /function messageM3EvidencePanel/);
assert.match(app, /function m3EvidenceRowElement/);
assert.match(app, /function copyButton/);
assert.match(app, /isCodeAgentM3SkillCompletion\(value\)/);
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, /"code-agent-m3-evidence\.mjs"/);
assert.match(distContractScript, /"third_party\/marked\/marked\.esm\.js"/);
assert.match(distContractScript, /"third_party\/marked\/LICENSE"/);
assert.match(distContractScript, /"live-status\.mjs"/);
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, /classifyWorkbenchLiveStatus/);
assert.match(liveStatus, /export function classifyWorkbenchLiveStatus/);
assert.match(liveStatus, /label:\s*"API 正常"/);
assert.match(liveStatus, /label:\s*"API 错误"/);
assert.match(liveStatus, /label:\s*"等待验证"/);
assert.match(liveStatus, /label:\s*"只读模式"/);
assert.match(liveStatus, /internalRawStatuses/);
assert.match(liveStatus, /function classifyCodeAgentProbe/);
assert.match(liveStatus, /function classifyM3ControlProbe/);
assert.match(liveStatus, /function classifyM3StatusProbe/);
assert.match(liveStatus, /\/v1\/agent\/chat/);
assert.match(liveStatus, /\/v1\/m3\/io/);
assert.match(liveStatus, /\/v1\/m3\/status/);
assert.match(liveStatus, /hwlab-cloud-api/);
assert.match(liveStatus, /hwlab-patch-panel/);
assert.match(liveStatus, /hwlab-gateway-simu/);
assert.match(liveStatus, /hwlab-box-simu/);
assert.match(liveStatus, /runtime_durable_adapter_/);
assert.match(liveStatus, /fallback_text_chat_only/);
assert.match(liveStatus, /provider_http_/);
assert.match(liveStatus, /rawStatus/);
assert.match(liveStatus, /evidenceSummary/);
assert.match(liveStatus, /function evidenceSummaryFrom/);
assert.match(liveStatus, /api_degraded_without_attribution/);
assert.match(liveStatus, /API readiness is not pass and no concrete blocker was provided/);
assert.match(liveStatus, /trust\?\.evidenceId/);
assert.doesNotMatch(liveStatus, /API returned \$\{status\}/);
assert.doesNotMatch(functionBody(app, "workbenchApiSurfaceStatus"), /surface\.degraded/);
assert.doesNotMatch(app, /API 错误 \/ 只读模式/);
assert.doesNotMatch(app, /可信记录受阻 \/ 只读模式/);
assert.doesNotMatch(app, /API 降级 \/ 只读模式/);
assert.doesNotMatch(functionBody(app, "workbenchApiSurfaceStatus"), /runtime_durable_adapter_query_blocked|DB live readiness|Gate|诊断|验收|M0-M5|执行轨迹/u);
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, /fetchJson\("\/v1\/m3\/io"/);
assert.match(app, /fetchJson\("\/v1\/m3\/status"\)/);
assert.match(html, /id="m3-control-form"/);
assert.match(html, /id="m3-control-status"/);
assert.match(html, />写入 DO1<\/button>/);
assert.match(html, />读取 DI1<\/button>/);
assert.match(app, /function runM3IoAction/);
assert.match(app, /function renderM3ControlStatus/);
assert.match(app, /frontendBypass=false/);
assert.match(app, /按钮只走受控后端/);
assert.match(app, /页面不直连模拟器,也不伪造硬件状态。/);
assert.match(styles, /\.m3-control-form\s*{/);
assert.match(html, /data-hardware-tab="overview"[\s\S]*>总览<\/button>/);
assert.match(html, /data-hardware-tab="gateways"[\s\S]*>Gateway-SIMU<\/button>/);
assert.match(html, /data-hardware-tab="box1"[\s\S]*>BOX-SIMU-1<\/button>/);
assert.match(html, /data-hardware-tab="box2"[\s\S]*>BOX-SIMU-2<\/button>/);
assert.match(html, /data-hardware-tab="patch"[\s\S]*>Patch Panel<\/button>/);
assert.match(app, /function initHardwareTabs/);
assert.match(app, /function hardwareKvGroup/);
assert.match(app, /function m3LinkStatus/);
assert.match(app, /sourceFallbackM3Status/);
for (const hardwareLabel of ["链路状态", "接线盘连接", "在线", "更新时间", "阻塞原因", "接线状态", "连接已激活"]) {
assert.match(app, new RegExp(escapeRegExp(hardwareLabel)), `missing Chinese hardware label ${hardwareLabel}`);
}
assert.match(styles, /\.hardware-status-tabs\s*\{/);
assert.match(styles, /\.kv-list\s*\{/);
assert.match(styles, /\.right-sidebar \.table-wrap\s*\{[^}]*overflow-x:\s*hidden;/s);
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, /sanitizeCodeAgentAvailability/);
assert.match(app, /latestCompletedAgentMessage/);
assert.match(app, /服务受阻/);
assert.match(app, /provider_unavailable/);
assert.match(app, /OPENAI_API_KEY/);
assert.match(app, /只有真实完成回复才显示为完成/);
assert.match(app, /不能因为只有会话编号就当成实况完成/);
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, /value\?\.session\?\.status === "idle"/);
assert.match(app, /typeof value\?\.session\?\.idleTimeoutMs === "number"/);
assert.match(app, /Boolean\(value\?\.session\?\.lastTraceId\)/);
assert.match(app, /Boolean\(value\?\.sessionReuse\)/);
assert.match(app, /value\?\.sessionMode === CODEX_APP_SERVER_SESSION_MODE/);
assert.match(app, /value\?\.implementationType === CODEX_APP_SERVER_IMPLEMENTATION_TYPE/);
assert.match(app, /threadIdFrom\(value\)/);
assert.match(app, /hasProviderTrace\(value\)/);
assert.match(app, /value\?\.capabilityLevel === "long-lived-codex-stdio-session"/);
assert.match(app, /value\?\.session\?\.codexStdio === true/);
assert.match(app, /value\?\.runner\?\.codexStdio === true/);
assert.match(app, /value\?\.runner\?\.writeCapable === true/);
assert.match(app, /value\?\.runner\?\.durableSession === true/);
assert.match(app, /providerTrace:\s*result\.providerTrace/);
assert.match(app, /CODEX_APP_SERVER_RUNNER_KIND/);
assert.match(app, /CODEX_APP_SERVER_PROTOCOL/);
assert.match(app, /value\?\.providerTrace\?\.protocol === CODEX_APP_SERVER_PROTOCOL/);
assert.match(app, /providerTrace\.command/);
assert.match(app, /providerTrace\.terminalStatus/);
assert.match(app, /session:\s*result\.session/);
assert.match(app, /sessionMode:\s*result\.sessionMode/);
assert.match(app, /sessionReuse:\s*result\.sessionReuse/);
assert.match(app, /implementationType:\s*result\.implementationType/);
assert.match(app, /runnerLimitations:\s*result\.runnerLimitations/);
assert.match(app, /codexStdioFeasibility:\s*result\.codexStdioFeasibility/);
assert.match(app, /longLivedSessionGate:\s*result\.longLivedSessionGate/);
assert.match(app, /conversationFacts:\s*result\.conversationFacts/);
assert.match(app, /"Prefer":\s*"respond-async"/);
assert.match(app, /DEFAULT_CODE_AGENT_CANCEL_TIMEOUT_MS\s*=\s*30000/);
assert.match(app, /DEFAULT_GATEWAY_SHELL_TIMEOUT_MS\s*=\s*120000/);
assert.match(app, /GATEWAY_SHELL_TIMEOUT_STORAGE_KEY/);
assert.match(app, /gatewayShellTimeoutMs:\s*GATEWAY_SHELL_TIMEOUT_MS/);
assert.match(html, /id="gateway-shell-timeout"/);
assert.match(app, /CODE_AGENT_CANCEL_TIMEOUT_MS/);
assert.match(functionBody(app, "cancelAgentRequest"), /timeoutMs:\s*CODE_AGENT_CANCEL_TIMEOUT_MS/);
assert.match(app, /function stopRunningTraceStream/);
assert.match(functionBody(app, "cancelAgentMessage"), /stopRunningTraceStream\(traceId\)/);
assert.match(app, /function waitForAgentMessageResult/);
assert.match(app, /\/v1\/agent\/chat\/result\/\$\{encodeURIComponent\(traceId\)\}/);
assert.match(app, /function isTransientCodeAgentResultPollError/);
assert.match(functionBody(app, "isTransientCodeAgentResultPollError"), /\[408,\s*425,\s*429,\s*500,\s*502,\s*503,\s*504\]/);
assert.match(functionBody(app, "waitForAgentMessageResult"), /isTransientCodeAgentResultPollError\(response\)/);
assert.match(app, /function refreshTraceAfterResultPollError/);
assert.match(app, /function messageTracePanel/);
assert.match(app, /function messageTraceToolbar/);
assert.match(app, /function messageTraceCountText/);
assert.match(app, /显示全部\s+\$\{displayTotal\}\s+\/\s+原始\s+\$\{rawTotal\}/);
assert.match(app, /eventsCompacted:\s*snapshot\.eventsCompacted === true/);
assert.match(app, /eventWindow:\s*snapshot\.eventWindow/);
assert.match(app, /eventCount:\s*Number\.isInteger\(trace\?\.eventCount\)/);
assert.match(app, /traceDetailsOpen:\s*new Map\(\)/);
assert.match(app, /conversationScrollPosition:\s*\{\s*top:\s*0,\s*left:\s*0\s*\}/);
assert.match(app, /traceScrollPositions:\s*new Map\(\)/);
assert.match(app, /state\.traceDetailsOpen\.clear\(\)/);
assert.match(app, /state\.conversationScrollPosition\s*=\s*\{\s*top:\s*0,\s*left:\s*0\s*\}/);
assert.match(app, /state\.traceScrollPositions\.clear\(\)/);
assert.match(app, /function messageTraceUiKey/);
assert.match(app, /function defaultTraceDetailsOpen/);
assert.match(app, /initConversationScrollMemory\(\)/);
assert.match(app, /function initConversationScrollMemory/);
assert.match(app, /function captureConversationScrollPosition/);
assert.match(app, /function restoreConversationScrollPosition/);
assert.match(app, /function captureTraceScrollPositions/);
assert.match(app, /function restoreTraceScrollPositions/);
assert.match(app, /function rememberTraceScrollPosition/);
assert.match(functionBody(app, "renderConversation"), /captureConversationScrollPosition\(\)/);
assert.match(functionBody(app, "renderConversation"), /restoreConversationScrollPosition\(\)/);
assert.match(functionBody(app, "renderConversation"), /captureTraceScrollPositions\(\)/);
assert.match(functionBody(app, "renderConversation"), /restoreTraceScrollPositions\(\)/);
assert.match(functionBody(app, "renderConversation"), /requestAnimationFrame/);
assert.match(functionBody(app, "messageTracePanel"), /state\.traceDetailsOpen\.get\(traceUiKey\)/);
assert.match(functionBody(app, "messageTracePanel"), /defaultTraceDetailsOpen\(message\)/);
assert.match(functionBody(app, "messageTracePanel"), /state\.traceDetailsOpen\.set\(traceUiKey,\s*details\.open\)/);
assert.match(functionBody(app, "messageTracePanel"), /list\.dataset\.traceUiKey\s*=\s*traceUiKey/);
assert.match(functionBody(app, "messageTracePanel"), /rememberTraceScrollPosition\(traceUiKey,\s*list\)/);
assert.match(functionBody(app, "messageTracePanel"), /list\.dataset\.traceMode\s*=\s*"all"/);
assert.match(functionBody(app, "messageTracePanel"), /renderTraceEventList\(list,\s*rows\)/);
assert.doesNotMatch(app, /CODE_AGENT_TRACE_PREVIEW_LIMIT|tracePreviewRows|收起为最近|展开全部|data-trace-mode="tail"/);
assert.match(app, /复制 JSON/);
assert.match(app, /下载 trace/);
assert.match(app, /function renderTraceEventList/);
assert.match(app, /function traceDisplayRows/);
assert.match(app, /function traceDisplayRow/);
assert.match(app, /function traceToolOutputSummaryRow/);
assert.match(app, /function parseGatewayJsonRpcOutput/);
assert.match(app, /tool hardware\.invoke\.shell/);
assert.match(app, /function traceNoiseSummaryRow/);
assert.match(styles, /max-height:\s*min\(520px,\s*54dvh\)/);
assert.match(app, /assistant stream x\$\{visibleEvents\.length\}/);
assert.match(app, /compressed=\$\{assistantChunks\.length\} assistant chunks/);
assert.match(app, /waiting first assistant token/);
assert.match(app, /function compactTraceTextTail/);
assert.match(app, /function isAssistantChunkTraceEvent/);
assert.match(app, /function downloadTraceJson/);
assert.match(styles, /\.message-trace-toolbar\s*\{/);
assert.match(styles, /\.message-trace-count\s*\{/);
assert.match(styles, /\.message-trace-action\s*\{/);
assert.doesNotMatch(styles, /\.message-trace-events-full/);
assert.match(app, /function messageRuntimePathPanel/);
assert.match(app, /codeAgentRuntimePathFromMessage/);
assert.match(app, /function runnerTraceHeadline/);
assert.match(app, /function traceEventMeta/);
assert.match(app, /function subscribeRunnerTrace/);
assert.match(app, /function pollRunnerTrace/);
assert.match(app, /TRACE_STREAM_FALLBACK_MS\s*=\s*2500/);
assert.match(app, /fallbackStop\s*=\s*pollRunnerTrace\(traceId,\s*messageId\)/);
assert.match(app, /TRACE_POLL_INTERVAL_MS/);
assert.match(app, /function updateMessageTrace/);
assert.match(app, /function runnerTraceFromSnapshot/);
assert.match(app, /new EventSource\(`\/v1\/agent\/chat\/trace\/\$\{encodeURIComponent\(traceId\)\}\/stream`\)/);
assert.match(app, /source\.addEventListener\("runnerTrace"/);
assert.doesNotMatch(app, /providerTraceSummary\(message\.providerTrace\)/);
assert.doesNotMatch(app, /function messageEvidenceFields/);
assert.match(app, /codeAgentSummaryRow\("sessionId\/status", summary\.sessionIdStatus/);
assert.match(app, /codeAgentSummaryRow\("workspace", summary\.workspace/);
assert.match(app, /codeAgentSummaryRow\("sandbox", summary\.sandbox/);
assert.match(app, /codeAgentSummaryRow\("runnerKind", summary\.runnerKind/);
assert.match(app, /codeAgentSummaryRow\("protocol", summary\.protocol/);
assert.match(app, /codeAgentSummaryRow\("implementationType", summary\.implementationType/);
assert.match(app, /codeAgentSummaryRow\("providerTrace\.command", summary\.providerTraceCommand/);
assert.match(app, /codeAgentSummaryRow\("providerTrace\.terminalStatus", summary\.providerTraceTerminalStatus/);
assert.match(app, /codeAgentSummaryRow\("运行路径语义", summary\.runtimePathLabel/);
assert.match(app, /codeAgentSummaryRow\("toolCalls", summary\.toolCalls/);
assert.match(app, /codeAgentSummaryRow\("skills", summary\.skills/);
assert.match(app, /codeAgentSummaryRow\("conversation facts", conversationFactsSummary\(summary\.latestMessage\?\.conversationFacts\)/);
assert.match(app, /codeAgentSummaryRow\("fact traces", conversationFactTracesSummary\(summary\.latestMessage\?\.conversationFacts\)/);
assert.match(app, /codeAgentSummaryRow\("runnerTrace", summary\.runnerTrace/);
assert.match(app, /function sessionSummary/);
assert.match(app, /function conversationFactsSummary/);
assert.match(app, /function codeAgentAvailabilitySummary/);
assert.doesNotMatch(app, /function longLivedSessionGateSummary/);
assert.doesNotMatch(app, /function conversationFactRows/);
assert.match(app, /not-codex-stdio/);
assert.match(app, /not-write-capable/);
assert.match(app, /process-local-session-registry/);
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 messageTracePanel/);
assert.match(app, /function subscribeRunnerTrace/);
assert.match(app, /new EventSource\(`\/v1\/agent\/chat\/trace\/\$\{encodeURIComponent\(traceId\)\}\/stream`\)/);
assert.match(app, /source\.addEventListener\("runnerTrace"/);
assert.match(app, /function pollRunnerTrace/);
assert.match(app, /function updateMessageTrace/);
assert.match(app, /function runnerTraceFromSnapshot/);
assert.doesNotMatch(app, /function messageEvidencePanel/);
assert.doesNotMatch(app, /function messageAttributionPanel/);
assert.doesNotMatch(app, /function codeAgentCapabilityFactsPanel/);
assert.doesNotMatch(app, /codeAgentAttributionFromMessage/);
assert.doesNotMatch(app, /codeAgentFactsFromMessage/);
assert.doesNotMatch(app, /compactHwlabApiFact/);
assert.match(app, /function renderCodeAgentSummary/);
assert.match(app, /currentCodeAgentStatusSummary/);
assert.match(app, /classifyCodeAgentStatusSummary/);
assert.match(app, /codeAgent\.status/);
assert.match(app, /当前部署 revision/);
assert.match(codeAgentFacts, /openai-responses-fallback/);
assert.match(codeAgentFacts, /text-chat-only/);
assert.match(codeAgentFacts, /OpenAI fallback:只是文本回答/);
assert.match(codeAgentFacts, /字段缺失:证据不足/);
assert.match(codeAgentFacts, /codeAgentAttributionFromMessage/);
assert.match(codeAgentFacts, /stateless-one-shot/);
assert.match(codeAgentFacts, /controlled-readonly-session-registry/);
assert.match(codeAgentFacts, /read-only-session-tools:只读长会话/);
assert.match(codeAgentFacts, /hwlab-m3-io-skill-cli/);
assert.match(codeAgentFacts, /route", fact\.route/);
assert.match(codeAgentFacts, /operationId", fact\.operationId/);
assert.match(codeAgentFacts, /auditId", fact\.auditId/);
assert.match(codeAgentFacts, /evidenceId", fact\.evidenceId/);
assert.match(codeAgentFacts, /blocker", fact\.blocker/);
assert.match(codeAgentStatus, /fallback-text-chat-only/);
assert.match(codeAgentStatus, /stateless-one-shot/);
assert.match(codeAgentStatus, /read-only-session-tools/);
assert.match(codeAgentStatus, /skill-cli-api-control/);
assert.match(codeAgentStatus, /currentDeploymentRevision/);
assert.match(codeAgentStatus, /unsafeGreenForNonReady/);
assert.match(codeAgentStatus, /provider_config_blocked/);
assert.doesNotMatch(codeAgentStatus, /sourceMain|SOURCE main|待部署 commit|targetCommit/u);
assert.match(app, /sourceKind:\s*completion\.sourceKind/);
assert.match(app, /function\s+agentStatusTone\s*\(/);
assert.match(app, /m3_io_result/);
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-pending-context\s*{/);
assert.match(styles, /\.message-session-context\s*{/);
assert.match(styles, /\.message-compact-details\s*{/);
assert.match(styles, /\.workbench-dialog-layer\s*{/);
assert.match(styles, /\.workbench-dialog\s*{/);
assert.match(app, /function openWorkbenchDialog/);
assert.match(app, /document\.body\.append\(layer\)/);
assert.match(styles, /\.message-m3-evidence\s*{/);
assert.match(styles, /\.message-m3-rows\s*{/);
assert.match(styles, /\.message-m3-row\s*{/);
assert.match(styles, /\.copy-chip\s*{/);
assert.match(styles, /\.message-trace\s*{/);
assert.match(styles, /\.message-trace-events\s*{/);
assert.match(styles, /\.message-trace-line\s*{/);
assert.match(styles, /\.message-trace-body\s*{/);
assert.doesNotMatch(styles, /\.message-attribution\s*{/);
assert.doesNotMatch(styles, /\.message-evidence\s*{/);
assert.doesNotMatch(styles, /\.code-agent-facts\s*{/);
assert.match(styles, /\.code-agent-summary\s*{/);
assert.match(styles, /\.tone-border-warn/);
assert.match(styles, /\.tone-border-blocked/);
assert.match(styles, /\.tone-border-ok/);
assert.match(styles, /\.hwlab-api-fact-code\s*{/);
assert.match(styles, /\.hwlab-api-fact-code\s*{[^}]*user-select:\s*all;/s);
assert.match(styles, /\.rail-button\s*{[^}]*min-width:\s*0;[^}]*min-height:\s*36px;[^}]*text-align:\s*center;[^}]*word-break:\s*keep-all;/s);
assert.match(styles, /\.side-tab\s*{[^}]*min-width:\s*0;[^}]*min-height:\s*34px;[^}]*overflow-wrap:\s*anywhere;[^}]*word-break:\s*keep-all;/s);
assert.match(styles, /\.(?:status-dot|state-tag|badge)[^{]*{[^}]*max-width:\s*100%;[^}]*line-height:\s*1\.25;[^}]*white-space:\s*normal;[^}]*overflow-wrap:\s*anywhere;/s);
assert.match(styles, /\.probe-card\s*{[^}]*min-width:\s*0;/s);
assert.match(styles, /\.probe-card strong\s*{[^}]*line-height:\s*1\.2;[^}]*overflow-wrap:\s*anywhere;/s);
assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.message-m3-rows\s*{[\s\S]*?grid-template-columns:\s*1fr;/);
assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.message-runtime-path,\s*\n\s*\.message-session-context,\s*\n\s*\.message-trace,\s*\n\s*\.message-m3-evidence,\s*\n\s*\.message-actions\s*{[\s\S]*?grid-column:\s*1;/);
for (const userFacingFunctionName of [
"codeAgentStatusMessage",
"codeAgentPromptText",
"codeAgentControlSummary",
"codeAgentBlockedSummary",
"codeAgentAvailabilitySummary",
"failureMessage",
"safeFailureReason"
]) {
assert.doesNotMatch(functionBody(app, userFacingFunctionName), /OPENAI_API_KEY|hwlab-code-agent-provider|Secret|secretRef|m3-route-required|waiting-for-dev-live|BLOCKED 凭证缺口/u, `${userFacingFunctionName} must keep default copy sanitized`);
}
assert.match(app, /state\.conversationId/);
assert.match(app, /conversationId/);
assert.match(app, /state\.threadId/);
assert.match(app, /threadIdForNextRequest/);
assert.match(app, /threadId:\s*requestedThreadId/);
assert.match(app, /function\s+completedSessionContinuity/);
assert.match(app, /function\s+codeAgentContinuityBlocker/);
assert.match(app, /code_agent_session_evidence_missing/);
assert.match(app, /Code Agent completed 但会话证据缺失/);
assert.match(app, /继续当前会话/);
assert.match(app, /新会话\/会话降级/);
assert.match(app, /messageId/);
assert.match(app, /createdAt/);
assert.match(app, /updatedAt/);
assert.match(app, /error\?\.message/);
assert.match(app, /DEFAULT_API_TIMEOUT_MS\s*=\s*4500/);
assert.match(app, /DEFAULT_CODE_AGENT_TIMEOUT_MS\s*=\s*600000/);
assert.match(app, /CODE_AGENT_SUBMIT_TIMEOUT_MS/);
assert.match(app, /CODE_AGENT_TIMEOUT_MS/);
assert.match(functionBody(app, "sendAgentMessage"), /timeoutMs:\s*CODE_AGENT_SUBMIT_TIMEOUT_MS/);
assert.match(functionBody(app, "waitForAgentMessageResult"), /CODE_AGENT_TIMEOUT_MS/);
assert.match(app, /HWLAB_CLOUD_WEB_CONFIG/);
assert.match(app, /sourceKind:\s*"PENDING"/);
assert.match(app, /旧 4500ms/);
assert.match(app, /function messagePendingContextPanel/);
assert.match(app, /message-pending-context/);
assert.match(app, /function\s+restartRunningAgentMessage/);
assert.match(app, /先取消当前 trace,再用同一输入重新发送/);
assert.match(app, /canRetryTerminalAgentMessage/);
assert.match(app, /结构化 blocker/);
assert.match(app, /Code Agent 超过 \$\{response\.timeoutMs\}ms 无新事件/);
assert.match(app, /agentFailurePresentation/);
assert.match(app, /agentStatusLabel/);
assert.match(app, /Code Agent 无新事件超时/);
assert.match(app, /Code Agent Runner 忙碌/);
assert.match(app, /Code Agent Session 受阻/);
assert.match(app, /Code Agent API 错误/);
assert.match(app, /等待超时/);
assert.match(app, /Provider 不可用/);
assert.match(app, /Runner 忙碌/);
assert.match(app, /Session 受阻/);
assert.match(app, /API 错误/);
assert.match(app, /后端失败/);
assert.doesNotMatch(app, /发送失败/);
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, /\.(?:compact-list|conversation-list|hardware-list)[^{]*{[^}]*min-height:\s*0;[^}]*overflow:\s*auto;/s);
assert.doesNotMatch(html, /task-panel|task-list|下一步任务草稿/);
assert.doesNotMatch(app, /taskList|renderTaskList/);
assert.doesNotMatch(styles, /\.task-panel|\.task-list/);
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]*?\.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\("\/v1\/diagnostics\/gate"/);
assert.match(app, /fetchJson\("\/v1\/live-builds"\)/);
assert.match(html, /id="live-build-latest"/);
assert.match(html, /id="live-build-list"/);
assert.match(html, /id="live-build-toggle"[^>]*aria-haspopup="dialog"[^>]*aria-expanded="false"/);
assert.match(styles, /\.live-build-summary\s*{/);
assert.match(app, /initLiveBuildOverlay/);
assert.match(app, /openWorkbenchDialog/);
assert.match(app, /aria-expanded/);
assert.match(app, /formatBeijingTime/);
assert.match(app, /Asia\/Shanghai/);
assert.match(app, /最新镜像构建时间/);
assert.match(app, /构建时间不可用/);
assert.match(app, /revision \$\{shortToken\(latest\.revision\)\}/);
assert.match(app, /revision \$\{shortToken\(service\.revision\)\}/);
assert.match(app, /liveBuildSourceLabel/);
assert.match(app, /liveBuildDesiredStateText/);
assert.match(app, /外部镜像或非 HWLAB 构建产物/);
assert.doesNotMatch(app, /artifact report|artifact-report/);
assert.match(app, /live health/);
assert.match(styles, /\.live-build-summary-label\s*{[^}]*min-width:\s*0;[^}]*max-width:\s*100%;[^}]*overflow:\s*hidden;[^}]*text-overflow:\s*ellipsis;[^}]*white-space:\s*nowrap;/s);
assert.match(styles, /\.live-build-list\s*{[^}]*min-width:\s*0;[^}]*max-height:\s*min\(352px,\s*calc\(58dvh - 54px\)\);[^}]*overflow:\s*auto;/s);
assert.match(styles, /\.live-build-row-head\s*{[^}]*grid-template-columns:\s*minmax\(0,\s*0\.8fr\)\s*minmax\(0,\s*1\.2fr\);/s);
assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.live-build-row-head\s*{[\s\S]*?grid-template-columns:\s*1fr;/);
assert.match(styles, /\.live-build-meta span\s*{[^}]*overflow-wrap:\s*anywhere;/s);
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, /HWLAB_CLOUD_WEB_PROXY_TIMEOUT_MS/);
assert.match(artifactPublisher, /cloudApiProxyTimeoutMs/);
assert.match(cloudWebProxy, /setTimeout\(\(\) => \{/);
assert.match(cloudWebProxy, /cloud api proxy timed out after/);
assert.match(artifactPublisher, /readOnlyRpcMethods/);
assert.match(artifactPublisher, /requestUpstream/);
assert.match(artifactPublisher, /proxyCloudApiRequest/);
assert.match(artifactPublisher, /isCloudWebSseRoute\(url\.pathname\)/);
assert.match(cloudWebProxy, /text\\\/event-stream/);
assert.match(cloudWebProxy, /x-accel-buffering/);
assert.match(cloudWebProxy, /flushHeaders/);
assert.match(cloudWebProxy, /response\.write\(chunk\)/);
assert.match(cloudWebProxy, /response\.headersSent\)[\s\S]*?response\.destroy\(error\);[\s\S]*?settle\(resolve\);[\s\S]*?return;/);
assert.match(artifactPublisher, /response\.headersSent \|\| response\.writableEnded/);
assert.match(artifactPublisher, /if \(!response\.writableEnded\) response\.destroy\(error\);/);
assert.doesNotMatch(artifactPublisher, /await fetch\(target/);
assert.match(artifactPublisher, /cloudWebProxyRoutePolicy\(request\.method,\s*url\.pathname\)/);
assert.match(cloudWebRoutes, /"\/v1\/agent\/chat"/);
assert.match(cloudWebRoutes, /"\/v1\/agent\/chat\/cancel"/);
assert.match(cloudWebRoutes, /"\/v1\/m3\/io"/);
assert.match(cloudWebRoutes, /"POST \/v1\/agent\/chat"/);
assert.match(cloudWebRoutes, /"POST \/v1\/agent\/chat\/cancel"/);
assert.match(cloudWebRoutes, /isPublicCodeAgentPollRoute/);
assert.match(cloudWebRoutes, /\/v1\/agent\/chat\/result\//);
assert.match(cloudWebRoutes, /\/v1\/agent\/chat\/trace\//);
assert.match(cloudWebRoutes, /const GET_PROXY_PREFIXES = Object\.freeze\(\["\/v1\/"\]\)/);
assert.match(cloudWebRoutes, /const GET_PROXY_ROUTES = new Set\(\["\/v1"\]\)/);
assert.match(cloudWebRoutes, /authRequired: proxy && !publicRoute/);
assert.match(codexStdioSession, /turn:completed:transport_closed_after_assistant/);
assert.match(codexStdioSession, /turn:completed:idle_timeout_after_assistant/);
assert.match(codexStdioSession, /firstNonEmpty\(finalResponse,\s*assistantText\)/);
assert.match(codexStdioSession, /CODEX_STDIO_FIRST_TOKEN_PROGRESS_MS/);
assert.match(codexStdioSession, /turn:waiting:first_assistant_token/);
assert.match(codexStdioSession, /progressOnly:\s*true/);
assert.match(codexStdioSession, /\["assistant-message", "turn\/completed"\]\.includes\(activity\.waitingFor\)/);
assert.match(codexStdioSession, /does not reset the backend idle timer/);
assert.match(codexStdioSession, /--powershell-stdin/);
assert.match(codexStdioSession, /gatewayShellTimeoutMs/);
assert.match(codexStdioSession, /Read-HwlabText/);
assert.match(codexStdioSession, /ConvertTo-HwlabJson/);
assert.match(codexStdioSession, /-EncodedCommand/);
assert.match(codexStdioSession, /do not hand-build compound cmd \/c, cd &&, pipes, or nested quotes/);
assert.match(codexStdioSession, /Set-Location -LiteralPath/);
assert.match(codexStdioSession, /Windows filesystem inventory/);
assert.match(codexStdioSession, /Select-Object -First/);
assert.match(codexStdioSession, /ConvertTo-Json -Compress/);
assert.match(codexStdioSession, /C:\\\\Users\\\\liang\\\\\.agents\\\\skills\\\\keil/);
assert.match(codexStdioSession, /async job flow/);
assert.match(gatewayShellTool, /function powershellEncodedCommand/);
assert.match(gatewayShellTool, /DEFAULT_GATEWAY_SHELL_TIMEOUT_MS\s*=\s*120000/);
assert.match(gatewayShellTool, /REQUEST_TIMEOUT_GRACE_MS/);
assert.match(gatewayShellTool, /op_gateway_shell_cli_\$\{randomUUID\(\)\}/);
assert.match(gatewayShellTool, /operationId,/);
assert.match(gatewayShellTool, /Read-HwlabText/);
assert.match(gatewayShellTool, /Buffer\.from\(`\$\{prologue\}\$\{source\}`,\s*"utf16le"\)\.toString\("base64"\)/);
assert.match(gatewayShellTool, /--powershell-stdin/);
assert.match(gatewayShellTool, /--cwd WINDOWS_PATH/);
assert.match(gatewayShellTool, /powershell\.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand/);
assert.doesNotMatch(gatewayShellTool, /keil-find|keil-build|keil-job-status/);
assert.match(cloudJsonRpc, /Math\.max\(configured \?\? 0,\s*requested \?\? 0,\s*120000\)/);
assert.match(cloudJsonRpc, /op_gateway_shell_\$\{randomUUID\(\)\}/);
assert.match(gatewayDemoRegistry, /DEFAULT_DISPATCH_TIMEOUT_MS\s*=\s*120000/);
assert.match(gatewayDemoRegistry, /inflightCount/);
assert.match(gatewayDemoRegistry, /maxInflightRequests/);
assert.match(gatewayMain, /HWLAB_GATEWAY_MAX_INFLIGHT/);
assert.match(gatewayMain, /function startCloudRequest/);
assert.match(gatewayMain, /Promise\.resolve\(\)\s*\n\s*\.then\(\(\) => handleCloudRequest/);
assert.doesNotMatch(gatewayMain, /await handleCloudRequest\(response\.body\.request\)/);
assert.match(gatewayDemoSmoke, /quick request was blocked by slow request/);
assert.match(cloudApiServer, /HWLAB_GATEWAY_DISPATCH_TIMEOUT_MS,\s*120000/);
assert.match(codexStdioSession, /detached:\s*childDetached/);
assert.match(codexStdioSession, /function terminateChildProcess/);
assert.match(codexStdioSession, /process\.kill\(-child\.pid,\s*"SIGTERM"\)/);
assert.match(codexStdioSession, /process\.kill\(-child\.pid,\s*"SIGKILL"\)/);
assert.match(cloudApiServer, /function isCodeAgentResultCanceled/);
assert.match(cloudApiServer, /options\.codeAgentChatResults\?\.set\(traceId,\s*payload\)/);
assert.match(cloudApiServer, /if \(isCodeAgentResultCanceled\(results\.get\(traceId\)\)\) return;/);
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, /cloud_api_proxy_timeout/);
assert.match(artifactPublisher, /Code Agent 代理等待 cloud-api 超过/);
assert.match(artifactPublisher, /输入已保留,可稍后重试/);
assert.match(artifactPublisher, /retryable:\s*true/);
assert.match(artifactPublisher, /blocker:\s*\{/);
assert.match(artifactPublisher, /const traceId = request\.headers\["x-trace-id"\] \|\| null/);
assert.match(artifactPublisher, /traceId,\s*\n\s*route: url\.pathname/);
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.deepEqual(runtime.serviceRoute, [
"browser/CLI/gateway",
"master hwlab-edge-proxy",
"frp",
"D601 hwlab-dev/hwlab-router",
"cloud-web/cloud-api"
]);
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(app, /https?:\/\/[^"']*(?:gateway-simu|box-simu|patch-panel)[^"']*/iu);
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();
runM3ControlPanelGuard();
runWorkbenchHardwarePanelContract();
console.log("hwlab-cloud-web check ok: workbench shell, M3 control guard, 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(`<section\\b[^>]*\\bdata-view=["']${escapeRegExp(viewId)}["'][^>]*>`, "u"));
if (start === -1) return "";
const tagPattern = /<\/?section\b[^>]*>/giu;
tagPattern.lastIndex = start;
let depth = 0;
for (let match = tagPattern.exec(source); match; match = tagPattern.exec(source)) {
if (match[0].startsWith("</")) {
depth -= 1;
if (depth === 0) return source.slice(start, tagPattern.lastIndex);
} else {
depth += 1;
}
}
return source.slice(start);
}
function beforeSection(source, viewId) {
const index = source.search(new RegExp(`<section\\b[^>]*\\bdata-view=["']${escapeRegExp(viewId)}["'][^>]*>`, "u"));
return index === -1 ? source : source.slice(0, index);
}
function defaultFirstViewportHtml(source) {
return `${beforeSection(source, "help")}\n${rightSidebarDefaultHtml(source)}`;
}
function rightSidebarDefaultHtml(source) {
const start = source.search(/<aside\b[^>]*\bclass=["'][^"']*\bright-sidebar\b[^"']*["'][^>]*>/u);
if (start === -1) return "";
const wiringStart = source.slice(start).search(/<section\b[^>]*\bid=["']panel-wiring["'][^>]*>/u);
if (wiringStart !== -1) return source.slice(start, start + wiringStart);
const end = source.slice(start).search(/<\/aside>/u);
return end === -1 ? source.slice(start) : source.slice(start, start + end);
}
function visibleTextFromHtml(fragment) {
return fragment
.replace(/<script\b[\s\S]*?<\/script>/giu, " ")
.replace(/<style\b[\s\S]*?<\/style>/giu, " ")
.replace(/<[^>]+>/gu, " ")
.replace(/&nbsp;/gu, " ")
.replace(/&gt;/gu, ">")
.replace(/&lt;/gu, "<")
.replace(/&amp;/gu, "&")
.replace(/\s+/gu, " ")
.trim();
}
function textHasForbiddenTerm(text, term) {
if (/^[A-Za-z][A-Za-z0-9-]*$/u.test(term)) {
return new RegExp(`\\b${escapeRegExp(term)}\\b`, "u").test(text);
}
return text.includes(term);
}
function compactLayoutSmokeFailures(report) {
return {
status: report.status,
sourceMode: report.sourceMode,
artifacts: report.artifacts,
blockers: (report.blockers ?? []).map(compactLayoutFailure),
failures: (report.failures ?? []).map(compactLayoutFailure)
};
}
function compactLayoutFailure(failure) {
return {
checkId: failure.checkId ?? failure.scope ?? null,
viewport: failure.viewport ?? null,
selector: failure.selector ?? null,
failureType: failure.failureType ?? "blocked",
summary: failure.summary ?? null,
artifact: failure.artifact ?? null
};
}
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 "";
}