feat: 聚合 M3 实况状态工作区

聚合 /v1/m3/status 的 M3 实况读入口,右侧硬件工作区显示 IO、operation、trace、audit 与 runtime durable blocker;覆盖后端契约与前端 fixture 验证。
This commit is contained in:
Lyon
2026-05-24 09:32:21 +08:00
committed by GitHub
parent 8c8942d232
commit f104f3e687
6 changed files with 230 additions and 21 deletions
+132 -17
View File
@@ -172,23 +172,33 @@ export async function describeM3StatusLive(options = {}) {
};
if (!config.enabled) {
const blocker = {
code: "m3_control_disabled",
layer: "cloud-api",
zh: "M3 状态聚合未启用;前端不能绕过 cloud-api 直连 gateway/box-simu。"
};
const gateways = defaultM3Gateways({ sourceKind: "UNVERIFIED", observedAt });
const boxes = defaultM3Boxes({ sourceKind: "UNVERIFIED", observedAt });
const patchPanel = defaultM3PatchPanel({ observedAt });
const blockedTrust = {
...trust,
blocker: "m3_control_disabled"
};
return {
...base,
status: "blocked",
sourceKind: "BLOCKED",
summary: "M3 状态聚合未启用;前端不能绕过 cloud-api 直连 gateway/box-simu。",
gateways: defaultM3Gateways({ sourceKind: "UNVERIFIED", observedAt }),
boxes: defaultM3Boxes({ sourceKind: "UNVERIFIED", observedAt }),
patchPanel: defaultM3PatchPanel({ observedAt }),
trust: {
...trust,
blocker: "m3_control_disabled"
},
blocker: {
code: "m3_control_disabled",
layer: "cloud-api",
zh: "M3 状态聚合未启用;前端不能绕过 cloud-api 直连 gateway/box-simu。"
}
gateways,
boxes,
patchPanel,
io: m3StatusIoSummary({ boxes, patchPanel, hardwareLive: false, observedAt }),
operation: m3StatusOperationSummary(blockedTrust),
trace: m3StatusTraceSummary(blockedTrust),
audit: m3StatusAuditSummary(blockedTrust),
runtimeDurable: m3StatusRuntimeDurableSummary(blockedTrust),
trust: blockedTrust,
blocker
};
}
@@ -342,6 +352,11 @@ export async function describeM3StatusLive(options = {}) {
? blockers.some((blocker) => blocker.code === M3_IO_BLOCKER_CODES.dispatchFailed || blocker.code === M3_IO_BLOCKER_CODES.patchPanelUnavailable) ? "error" : "blocked"
: trustGreen ? "live" : hardwareLive ? "blocked" : "unverified";
const sourceKind = hardwareLive ? "DEV-LIVE" : "UNVERIFIED";
const blocker = blockers[0] ?? (trust.blocker ? {
code: trust.blocker,
layer: "runtime-durable",
zh: `runtime durable 仍 blocked${trust.blocker}`
} : null);
return {
...base,
@@ -355,12 +370,13 @@ export async function describeM3StatusLive(options = {}) {
gateways,
boxes,
patchPanel,
io: m3StatusIoSummary({ boxes, patchPanel, hardwareLive, observedAt }),
operation: m3StatusOperationSummary(trust),
trace: m3StatusTraceSummary(trust),
audit: m3StatusAuditSummary(trust),
runtimeDurable: m3StatusRuntimeDurableSummary(trust),
trust,
blocker: blockers[0] ?? (trust.blocker ? {
code: trust.blocker,
layer: "runtime-durable",
zh: `runtime durable 仍 blocked${trust.blocker}`
} : null)
blocker
};
}
@@ -759,6 +775,105 @@ async function buildM3StatusTrust({ runtimeStore, runtime }) {
};
}
function m3StatusIoSummary({ boxes, patchPanel, hardwareLive, observedAt }) {
const sourceBox = boxes.find((box) => box.resourceId === M3_IO_CHAIN.sourceResourceId) ?? null;
const targetBox = boxes.find((box) => box.resourceId === M3_IO_CHAIN.targetResourceId) ?? null;
const do1 = sourceBox?.ports?.[M3_IO_CHAIN.sourcePort] ?? null;
const di1 = targetBox?.ports?.[M3_IO_CHAIN.targetPort] ?? null;
const blocker = !sourceBox?.online
? M3_IO_BLOCKER_CODES.boxUnavailable
: !targetBox?.online
? M3_IO_BLOCKER_CODES.boxUnavailable
: patchPanel?.connectionActive !== true
? M3_IO_BLOCKER_CODES.wiringMissing
: do1?.sourceKind !== "DEV-LIVE" || di1?.sourceKind !== "DEV-LIVE"
? "m3_io_unverified"
: null;
return {
status: hardwareLive ? "live" : blocker ? "blocked" : "unverified",
sourceKind: hardwareLive ? "DEV-LIVE" : "UNVERIFIED",
observedAt,
chain: {
from: `${M3_IO_CHAIN.sourceResourceId}:${M3_IO_CHAIN.sourcePort}`,
via: M3_IO_CHAIN.patchPanelServiceId,
to: `${M3_IO_CHAIN.targetResourceId}:${M3_IO_CHAIN.targetPort}`
},
do1: m3StatusPortSummary(sourceBox, M3_IO_CHAIN.sourcePort, do1),
di1: m3StatusPortSummary(targetBox, M3_IO_CHAIN.targetPort, di1),
patchPanel: {
serviceId: patchPanel?.serviceId ?? M3_IO_CHAIN.patchPanelServiceId,
connectionActive: patchPanel?.connectionActive === true,
sourceKind: patchPanel?.sourceKind ?? "UNVERIFIED",
observedAt: patchPanel?.lastSyncAt ?? observedAt,
blocker: patchPanel?.connectionActive === true ? null : patchPanel?.lastError ?? M3_IO_BLOCKER_CODES.wiringMissing
},
blocker
};
}
function m3StatusPortSummary(box, port, state) {
return {
resourceId: box?.resourceId ?? null,
boxId: box?.id ?? null,
gatewayId: box?.gatewayId ?? null,
gatewaySessionId: box?.gatewaySessionId ?? null,
port,
direction: state?.direction ?? null,
value: state?.value ?? null,
source: state?.source ?? "unverified",
sourceKind: state?.sourceKind ?? "UNVERIFIED",
observedAt: state?.observedAt ?? box?.observedAt ?? null,
blocker: state?.lastError ?? (box?.online ? null : M3_IO_BLOCKER_CODES.boxUnavailable)
};
}
function m3StatusOperationSummary(trust) {
return {
status: trust.operationId ? "read" : "blocked",
operationId: trust.operationId ?? null,
source: trust.operationId ? "runtime-store audit/evidence" : "runtime-store",
blocker: trust.operationId ? null : trust.blocker ?? "operation_not_persisted",
readStatus: {
audit: trust.readStatus?.audit ?? "blocked",
evidence: trust.readStatus?.evidence ?? "blocked"
}
};
}
function m3StatusTraceSummary(trust) {
return {
status: trust.traceId ? "read" : "blocked",
traceId: trust.traceId ?? null,
source: trust.traceId ? "runtime-store audit/evidence" : "runtime-store",
blocker: trust.traceId ? null : trust.blocker ?? "trace_not_persisted"
};
}
function m3StatusAuditSummary(trust) {
return {
status: trust.auditId ? "read" : "blocked",
auditId: trust.auditId ?? null,
readStatus: trust.readStatus?.audit ?? "blocked",
source: trust.auditId ? "runtime-store audit.event.query" : "runtime-store",
blocker: trust.auditId ? null : trust.readStatus?.auditError ?? trust.blocker ?? "audit_not_persisted"
};
}
function m3StatusRuntimeDurableSummary(trust) {
return {
status: trust.durableStatus === "green" ? "green" : "blocked",
green: trust.durableStatus === "green",
blocker: trust.blocker ?? null,
runtime: trust.runtime,
readStatus: trust.readStatus ?? {
audit: "blocked",
evidence: "blocked"
},
auditReady: trust.readStatus?.audit === "read",
evidenceReady: trust.readStatus?.evidence === "read"
};
}
async function safeRuntimeRead(read) {
if (typeof read !== "function") {
return {
+38
View File
@@ -206,6 +206,30 @@ test("M3 status live descriptor aggregates gateways, boxes, patch-panel, IO valu
assert.equal(status.patchPanel.connectionActive, true);
assert.equal(status.patchPanel.wiringConfigId, "wir_m3_do1_di1");
assert.equal(status.patchPanel.lastError, null);
assert.equal(status.io.status, "live");
assert.equal(status.io.sourceKind, "DEV-LIVE");
assert.equal(status.io.do1.resourceId, "res_boxsimu_1");
assert.equal(status.io.do1.port, "DO1");
assert.equal(status.io.do1.value, true);
assert.equal(status.io.do1.sourceKind, "DEV-LIVE");
assert.equal(status.io.di1.resourceId, "res_boxsimu_2");
assert.equal(status.io.di1.port, "DI1");
assert.equal(status.io.di1.value, true);
assert.equal(status.io.di1.sourceKind, "DEV-LIVE");
assert.equal(status.io.patchPanel.serviceId, "hwlab-patch-panel");
assert.equal(status.io.patchPanel.connectionActive, true);
assert.equal(status.operation.status, "blocked");
assert.equal(status.operation.operationId, null);
assert.equal(status.operation.blocker, "runtime_durable_adapter_auth_blocked");
assert.equal(status.trace.status, "blocked");
assert.equal(status.trace.traceId, null);
assert.equal(status.trace.blocker, "runtime_durable_adapter_auth_blocked");
assert.equal(status.audit.status, "blocked");
assert.equal(status.audit.auditId, null);
assert.equal(status.audit.readStatus, "blocked");
assert.equal(status.runtimeDurable.status, "blocked");
assert.equal(status.runtimeDurable.green, false);
assert.equal(status.runtimeDurable.blocker, "runtime_durable_adapter_auth_blocked");
assert.equal(status.trust.durableStatus, "blocked");
assert.equal(status.trust.blocker, "runtime_durable_adapter_auth_blocked");
assert.equal(status.trust.runtime.blocker, "runtime_durable_adapter_auth_blocked");
@@ -249,6 +273,20 @@ test("M3 status clears nested runtime blocker when trusted durable evidence is g
assert.equal(status.trust.traceId, "trc_hotfix_307_verify_1779527747043_read_false");
assert.equal(status.trust.auditId, "aud_m3_di_read_d5afd84b-3a9d-475f-894f-dff8ea5f49ff_succeeded");
assert.equal(status.trust.evidenceId, "evd_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f_succeeded");
assert.equal(status.operation.status, "read");
assert.equal(status.operation.operationId, "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f");
assert.equal(status.operation.blocker, null);
assert.equal(status.trace.status, "read");
assert.equal(status.trace.traceId, "trc_hotfix_307_verify_1779527747043_read_false");
assert.equal(status.trace.blocker, null);
assert.equal(status.audit.status, "read");
assert.equal(status.audit.auditId, "aud_m3_di_read_d5afd84b-3a9d-475f-894f-dff8ea5f49ff_succeeded");
assert.equal(status.audit.blocker, null);
assert.equal(status.runtimeDurable.status, "green");
assert.equal(status.runtimeDurable.green, true);
assert.equal(status.runtimeDurable.blocker, null);
assert.equal(status.runtimeDurable.auditReady, true);
assert.equal(status.runtimeDurable.evidenceReady, true);
});
test("M3 status live descriptor keeps SOURCE fallback unverified when patch-panel route is missing", async () => {
+41
View File
@@ -3613,6 +3613,33 @@ test("cloud api /v1 exposes M3 IO control contract without generic frontend hard
assert.equal(statusPayload.boxes.find((box) => box.resourceId === "res_boxsimu_1").ports.DO1.value, false);
assert.equal(statusPayload.boxes.find((box) => box.resourceId === "res_boxsimu_2").ports.DI1.value, false);
assert.equal(statusPayload.patchPanel.connectionActive, true);
assert.equal(statusPayload.io.status, "live");
assert.equal(statusPayload.io.do1.resourceId, "res_boxsimu_1");
assert.equal(statusPayload.io.do1.port, "DO1");
assert.equal(statusPayload.io.do1.value, false);
assert.equal(statusPayload.io.do1.sourceKind, "DEV-LIVE");
assert.equal(statusPayload.io.di1.resourceId, "res_boxsimu_2");
assert.equal(statusPayload.io.di1.port, "DI1");
assert.equal(statusPayload.io.di1.value, false);
assert.equal(statusPayload.io.di1.sourceKind, "DEV-LIVE");
assert.equal(statusPayload.io.patchPanel.serviceId, "hwlab-patch-panel");
assert.equal(statusPayload.io.patchPanel.connectionActive, true);
assert.equal(statusPayload.operation.status, "blocked");
assert.equal(statusPayload.operation.operationId, null);
assert.equal(statusPayload.operation.blocker, "runtime_durable_adapter_missing");
assert.equal(statusPayload.trace.status, "blocked");
assert.equal(statusPayload.trace.traceId, null);
assert.equal(statusPayload.trace.blocker, "runtime_durable_adapter_missing");
assert.equal(statusPayload.audit.status, "blocked");
assert.equal(statusPayload.audit.auditId, null);
assert.equal(statusPayload.audit.blocker, "runtime_durable_adapter_missing");
assert.equal(statusPayload.runtimeDurable.status, "blocked");
assert.equal(statusPayload.runtimeDurable.green, false);
assert.equal(statusPayload.runtimeDurable.blocker, "runtime_durable_adapter_missing");
assert.equal(statusPayload.runtimeDurable.readStatus.audit, "read");
assert.equal(statusPayload.runtimeDurable.readStatus.evidence, "read");
assert.equal(statusPayload.runtimeDurable.auditReady, true);
assert.equal(statusPayload.runtimeDurable.evidenceReady, true);
assert.equal(statusPayload.trust.durableStatus, "blocked");
assert.notEqual(statusPayload.trust.durableStatus, "green");
assert.equal(statusPayload.trust.runtime.blocker, "runtime_durable_adapter_missing");
@@ -3655,6 +3682,20 @@ test("cloud api /v1/m3/status clears nested runtime blocker when trusted durable
assert.equal(statusPayload.trust.traceId, "trc_hotfix_307_verify_1779527747043_read_false");
assert.equal(statusPayload.trust.auditId, "aud_m3_di_read_d5afd84b-3a9d-475f-894f-dff8ea5f49ff_succeeded");
assert.equal(statusPayload.trust.evidenceId, "evd_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f_succeeded");
assert.equal(statusPayload.operation.status, "read");
assert.equal(statusPayload.operation.operationId, "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f");
assert.equal(statusPayload.operation.blocker, null);
assert.equal(statusPayload.trace.status, "read");
assert.equal(statusPayload.trace.traceId, "trc_hotfix_307_verify_1779527747043_read_false");
assert.equal(statusPayload.trace.blocker, null);
assert.equal(statusPayload.audit.status, "read");
assert.equal(statusPayload.audit.auditId, "aud_m3_di_read_d5afd84b-3a9d-475f-894f-dff8ea5f49ff_succeeded");
assert.equal(statusPayload.audit.blocker, null);
assert.equal(statusPayload.runtimeDurable.status, "green");
assert.equal(statusPayload.runtimeDurable.green, true);
assert.equal(statusPayload.runtimeDurable.blocker, null);
assert.equal(statusPayload.runtimeDurable.auditReady, true);
assert.equal(statusPayload.runtimeDurable.evidenceReady, true);
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
@@ -6374,7 +6374,7 @@ async function inspectM3StatusFixtureViewport(browser, url, viewport) {
getComputedStyle(document.body).overflow === "hidden" &&
document.documentElement.scrollHeight <= document.documentElement.clientHeight + 2 &&
document.body.scrollHeight <= document.body.clientHeight + 2,
keysVisible: ["状态", "Gateway 在线", "BOX 在线", "链路状态", "blocked", "DO1", "DI1", "runtime durable", "blocker"].every((term) => hardwareText.includes(term)),
keysVisible: ["状态", "Gateway 在线", "BOX 在线", "链路状态", "blocked", "DO1", "DI1", "operation", "audit", "runtime durable", "blocker"].every((term) => hardwareText.includes(term)),
tabsVisible: ["总览", "Gateway-SIMU", "BOX-SIMU-1", "BOX-SIMU-2", "Patch Panel"].every((term) => tabLabels.includes(term)),
blockedVisible: text.includes("runtime_durable_adapter_auth_blocked") && !text.includes("实况可信"),
noHorizontalOverflow:
+10 -3
View File
@@ -1457,19 +1457,26 @@ function hardwareOverviewGroups(status) {
const di1 = box2?.ports?.DI1;
const patch = status.patchPanel ?? {};
const trust = status.trust ?? {};
const io = status.io ?? {};
const operation = status.operation ?? {};
const audit = status.audit ?? {};
const runtimeDurable = status.runtimeDurable ?? {};
const runtimeDurableStatus = runtimeDurable.status ?? trust.durableStatus ?? "blocked";
return [
hardwareKvGroup("总览", [
["状态", hardwareStatusLabel(status), statusTone(status)],
["观测时间", status.observedAt ?? "未验证", "source"],
["Gateway 在线", `${onlineCount(status.gateways)} / ${status.gateways?.length ?? 0}`, onlineCount(status.gateways) === 2 ? "live" : "blocked"],
["BOX 在线", `${onlineCount(status.boxes)} / ${status.boxes?.length ?? 0}`, onlineCount(status.boxes) === 2 ? "live" : "blocked"],
["链路状态", m3LinkStatus(status, patch), m3LinkTone(status, patch)],
["链路状态", io.status ?? m3LinkStatus(status, patch), m3LinkTone(status, patch)],
["链路", `${M3_TRUSTED_ROUTE.fromResourceId}:${M3_TRUSTED_ROUTE.fromPort} -> ${M3_TRUSTED_ROUTE.patchPanelServiceId} -> ${M3_TRUSTED_ROUTE.toResourceId}:${M3_TRUSTED_ROUTE.toPort}`, patch.connectionActive ? "live" : "blocked"],
["DO1", valueLabel(do1), portTone(do1)],
["DI1", valueLabel(di1), portTone(di1)],
["接线盘连接", String(Boolean(patch.connectionActive)), patch.connectionActive ? "live" : "blocked"],
["runtime durable", trust.durableStatus ?? "blocked", trust.durableStatus === "green" ? "dev-live" : "blocked"],
["blocker", trust.blocker ?? status.blocker?.code ?? "none", trust.blocker || status.blocker ? "blocked" : "source"]
["operation", operation.operationId ?? operation.blocker ?? "未持久化", operation.operationId ? "source" : "blocked"],
["audit", audit.auditId ?? audit.blocker ?? "未持久化", audit.auditId ? "source" : "blocked"],
["runtime durable", runtimeDurableStatus, runtimeDurableStatus === "green" ? "dev-live" : "blocked"],
["blocker", runtimeDurable.blocker ?? trust.blocker ?? status.blocker?.code ?? "none", runtimeDurable.blocker || trust.blocker || status.blocker ? "blocked" : "source"]
]),
hardwareKvGroup("可信证据", [
["operationId", trust.operationId ?? "未持久化", trust.operationId ? "source" : "blocked"],
+8
View File
@@ -366,6 +366,14 @@ assert.match(functionBody(app, "sourceFallbackM3Status"), /unverified_cloud_api_
assert.match(functionBody(app, "sourceFallbackM3Status"), /SOURCE 拓扑不能冒充实况硬件状态/);
assert.doesNotMatch(functionBody(app, "renderHardwareStatus"), /hardwareRow\(\s*\{/u);
assert.match(functionBody(app, "renderHardwareStatus"), /Key\/Value groups/u);
assert.match(functionBody(app, "hardwareOverviewGroups"), /status\.io \?\? \{\}/);
assert.match(functionBody(app, "hardwareOverviewGroups"), /status\.operation \?\? \{\}/);
assert.match(functionBody(app, "hardwareOverviewGroups"), /status\.audit \?\? \{\}/);
assert.match(functionBody(app, "hardwareOverviewGroups"), /status\.runtimeDurable \?\? \{\}/);
assert.match(functionBody(app, "hardwareOverviewGroups"), /\["operation", operation\.operationId \?\? operation\.blocker/u);
assert.match(functionBody(app, "hardwareOverviewGroups"), /\["audit", audit\.auditId \?\? audit\.blocker/u);
assert.match(functionBody(app, "hardwareOverviewGroups"), /\["runtime durable", runtimeDurableStatus/u);
assert.match(functionBody(app, "hardwareOverviewGroups"), /runtimeDurable\.blocker \?\? trust\.blocker/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/);