From 4f65151b41652e2df7ecc620a407de513e1c3007 Mon Sep 17 00:00:00 2001 From: unidesk-code-queue-runner Date: Sat, 23 May 2026 08:01:07 +0000 Subject: [PATCH] fix: keep M3 ready status title honest --- web/hwlab-cloud-web/app.mjs | 11 ++++++++++- .../scripts/m3-control-panel-guard.mjs | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/web/hwlab-cloud-web/app.mjs b/web/hwlab-cloud-web/app.mjs index b0dbb58f..af4b9481 100644 --- a/web/hwlab-cloud-web/app.mjs +++ b/web/hwlab-cloud-web/app.mjs @@ -1252,7 +1252,7 @@ function renderM3ControlStatus() { el.m3ControlStatus.textContent = label; el.m3ControlStatus.className = `state-tag tone-${toneClass(tone)}`; - el.m3ControlStatus.title = operation?.blocker?.zh ?? operation?.evidenceState?.reason ?? m3ControlBlockedReason(contract); + el.m3ControlStatus.title = m3ControlStatusTitle({ operation, contract, controlReady }); const disabled = pending || !controlReady; for (const input of [el.m3GatewaySelect, el.m3BoxSelect, el.m3PortSelect, el.m3ValueSelect, el.m3WriteDo, el.m3ReadDi]) { input.disabled = disabled; @@ -1268,6 +1268,15 @@ function m3ControlCanOperate(contract = state.m3Control.contract) { contract?.readiness?.evidenceLevel === "DEV-LIVE"; } +function m3ControlStatusTitle({ operation, contract, controlReady }) { + if (operation?.blocker?.zh) return operation.blocker.zh; + if (operation?.evidenceState?.reason) return operation.evidenceState.reason; + if (controlReady) { + return "M3 IO 只读 readiness 已确认;控制仅通过 cloud-api 受控路径执行,不直连 gateway/box-simu。"; + } + return m3ControlBlockedReason(contract); +} + function m3ControlBlockedReason(contract = state.m3Control.contract) { if (!contract) return "等待 cloud-api 返回 M3 IO 只读 readiness;未确认前控制保持阻塞。"; return contract.readiness?.blocker?.zh ?? diff --git a/web/hwlab-cloud-web/scripts/m3-control-panel-guard.mjs b/web/hwlab-cloud-web/scripts/m3-control-panel-guard.mjs index 3d68cee3..28dfb132 100644 --- a/web/hwlab-cloud-web/scripts/m3-control-panel-guard.mjs +++ b/web/hwlab-cloud-web/scripts/m3-control-panel-guard.mjs @@ -54,6 +54,7 @@ export function runM3ControlPanelGuard() { for (const copy of [ "M3 IO 控制请求失败", "M3 IO 只读 readiness 未 green", + "M3 IO 只读 readiness 已确认", "受控路径受阻", "等待 cloud-api 返回 M3 IO 只读 readiness", "不可用时不会直连 gateway/box-simu", @@ -135,6 +136,14 @@ function assertControlPanelSource(app) { assert.match(canOperateBody, /readiness\?\.sourceKind === "DEV-LIVE"/u, "M3 controls must not unlock on SOURCE/DRY-RUN/BLOCKED readiness"); assert.match(canOperateBody, /readiness\?\.evidenceLevel === "DEV-LIVE"/u, "M3 controls must require live readiness evidence level"); + const statusBody = functionBody(app, "renderM3ControlStatus"); + assert.match(statusBody, /m3ControlStatusTitle\(\{ operation, contract, controlReady \}\)/u, "ready status title must use the control-ready-aware helper"); + + const statusTitleBody = functionBody(app, "m3ControlStatusTitle"); + assert.match(statusTitleBody, /if \(controlReady\)/u, "ready status title must not fall through to blocked copy"); + assert.match(statusTitleBody, /只读 readiness 已确认/u, "ready status title must show confirmed Chinese readiness copy"); + assert.match(statusTitleBody, /return m3ControlBlockedReason\(contract\)/u, "blocked status title must still surface Chinese blocked reason"); + const controlRowsBody = functionBody(app, "controlRows"); assert.match(controlRowsBody, /按钮只走受控后端/u, "control copy must describe the controlled backend route"); assert.match(controlRowsBody, /gateway-simu -> box-simu -> hwlab-patch-panel/u, "control copy must show backend-owned path");