feat: enrich cloud workbench hardware status

feat: enrich cloud workbench hardware status
This commit is contained in:
Lyon
2026-05-22 23:33:02 +08:00
committed by GitHub
5 changed files with 510 additions and 65 deletions
@@ -73,6 +73,21 @@ const requiredWiringColumns = Object.freeze([
"trace/evidence"
]);
const requiredHardwareStatusTerms = Object.freeze([
"hardware-source-status",
"hardwareGroup",
"hardwareRow",
"Gateway/Box 在线",
"BOX-SIMU 端口",
"Patch Panel 连线",
"DO1 -> patch-panel -> DI1",
"AI/AO/FREQ",
"future capability",
"SOURCE",
"DEV-LIVE",
"BLOCKED"
]);
const workbenchMarkers = Object.freeze([
"data-app-shell",
"workbench-shell",
@@ -192,6 +207,11 @@ function runStaticSmoke() {
evidence: requiredWiringColumns
});
addCheck(checks, blockers, "hardware-status-rich-structure", hardwareStatusStructureContract(files), "Right hardware status renders Gateway/Box, DI/DO/AI/AO/FREQ, patch-panel, and DO1 -> DI1 source-labeled link state.", {
blocker: "contract_blocker",
evidence: requiredHardwareStatusTerms
});
addCheck(checks, blockers, "outer-scroll-contract", hasScrollLockContract(files.styles), "Outer page scrolling is locked and internal workbench panes own scrolling.", {
blocker: "contract_blocker",
evidence: ["html/body overflow hidden", "workbench-shell 100vh/100dvh overflow hidden", ".view overflow auto"]
@@ -222,6 +242,24 @@ function runStaticSmoke() {
evidence: ["M3 remains blocked without live loop evidence", "checked-in evidence records remain dry-run"]
});
addCheck(checks, blockers, "m3-fixture-counts-not-dev-live", m3FixtureCountsNotDevLive(files.app), "Fixture/source patch-panel wiring plus generic runtime counts cannot render M3 DEV-LIVE.", {
blocker: "observability_blocker",
evidence: [
"hasTrustedM3LiveEvidence requires patchPanelLiveReport",
"operationId/traceId/auditId/evidenceId required",
"runtime counts are SOURCE-only"
]
});
addCheck(checks, blockers, "m3-rendered-workbench-not-m5-fixture", m3RenderedWorkbenchNotM5Fixture(files.app), "Right workbench hardware status and wiring table render the M3 chain, not M5 fixture rows or undefined DOM-node rows.", {
blocker: "runtime_blocker",
evidence: [
"renderHardwareStatus passes data objects into hardwareGroup",
"renderWiringList uses M3_TRUSTED_ROUTE",
"M5 activeConnections are not mapped into the workbench wiring table"
]
});
const help = inspectHelpContract(files);
addCheck(checks, blockers, "help-md-contract", help.status, help.summary, {
blocker: help.blocker,
@@ -432,6 +470,24 @@ function wiringTableContract(html) {
return requiredWiringColumns.every((column) => wiringPanel.includes(`<th>${column}</th>`));
}
function hardwareStatusStructureContract({ html, app, styles }) {
const source = `${html}\n${app}\n${styles}`;
return (
requiredHardwareStatusTerms.every((term) => source.includes(term)) &&
/id=["']hardware-source-status["']/u.test(html) &&
/function\s+renderHardwareStatus\s*\(/u.test(app) &&
/function\s+hardwareGroup\s*\(/u.test(app) &&
/function\s+hardwareRow\s*\(/u.test(app) &&
/function\s+trustedM3Link\s*\(/u.test(app) &&
/function\s+hasTrustedM3LiveEvidence\s*\(/u.test(app) &&
/function\s+linkStatusLabel\s*\(/u.test(app) &&
/function\s+normalizePort\s*\(/u.test(app) &&
/Web 不提供硬件写 RPC;不会新增伪硬件写操作。/u.test(app) &&
/\.hardware-section\s*\{/u.test(styles) &&
/\.hardware-row\s*\{/u.test(styles)
);
}
function finalRouteFallback(app) {
const body = app.match(/function\s+routeFromLocation\s*\(\)\s*\{([\s\S]*?)\n\}/u)?.[1] ?? "";
const returns = [...body.matchAll(/return\s+["']([^"']+)["']\s*;/gu)].map((match) => match[1]);
@@ -517,6 +573,58 @@ function sourceEvidenceNotDevLive(source) {
);
}
function m3FixtureCountsNotDevLive(app) {
const hardwareBody = functionBody(app, "renderHardwareStatus");
const linkDetailBody = functionBody(app, "m3LinkDetail");
const liveEvidenceBody = functionBody(app, "hasTrustedM3LiveEvidence");
const patchPanelReportBody = functionBody(app, "hasTrustedPatchPanelLiveReport");
const sourceOnlyRuntimeCounts =
/runtimeCountsSourceKind\s*=\s*["']SOURCE["']/u.test(hardwareBody) &&
!/sourceKind:\s*counts\s*\?\s*["']DEV-LIVE["']/u.test(hardwareBody) &&
!/counts\s*&&\s*(?:liveLink|sourceLink|m3Link)[\s\S]{0,80}\?\s*["']DEV-LIVE["']/u.test(hardwareBody);
return (
sourceOnlyRuntimeCounts &&
/hasTrustedM3LiveEvidence\(liveM3Evidence\)/u.test(hardwareBody) &&
/patchPanelLiveReport/u.test(liveEvidenceBody) &&
/LIVE_M3_ID_FIELDS\.every/u.test(liveEvidenceBody) &&
/hasTrustedPatchPanelLiveReport/u.test(liveEvidenceBody) &&
/serviceId\s*!==\s*M3_TRUSTED_ROUTE\.patchPanelServiceId/u.test(patchPanelReportBody) &&
/trustedM3Link\(\[link\]\)/u.test(patchPanelReportBody) &&
/counts 不是 patch-panel live report/u.test(linkDetailBody) &&
/SOURCE fixture 包含目标接线/u.test(linkDetailBody)
);
}
function m3RenderedWorkbenchNotM5Fixture(app) {
const hardwareBody = functionBody(app, "renderHardwareStatus");
const hardwareGroupBody = functionBody(app, "hardwareGroup");
const wiringBody = functionBody(app, "renderWiringList");
return (
/group\.rows\.map\(hardwareRow\)/u.test(hardwareGroupBody) &&
!/hardwareRow\(\s*\{/u.test(hardwareBody) &&
/M3_TRUSTED_ROUTE\.fromResourceId/u.test(wiringBody) &&
/M3_TRUSTED_ROUTE\.patchPanelServiceId/u.test(wiringBody) &&
/M3_TRUSTED_ROUTE\.toResourceId/u.test(wiringBody) &&
!/activeConnections\.map/u.test(wiringBody) &&
!/link\.fromResourceId/u.test(wiringBody)
);
}
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 "";
}
function inspectHelpContract({ html, app }) {
const markdownFiles = findHelpMarkdownFiles();
const rendererPackages = findMarkdownRenderers();