feat: enrich cloud workbench hardware status
feat: enrich cloud workbench hardware status
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user