import { createHash, randomUUID } from "node:crypto"; import { ENVIRONMENT_DEV, ERROR_CODES, HwlabProtocolError, assertProtocolRecord } from "../protocol/index.mjs"; import { CLOUD_API_SERVICE_ID, createProtocolAuditEvent } from "../audit/index.mjs"; import { HWLAB_M3_IO_CAPABILITY_LEVELS } from "../../skills/hwlab-agent-runtime/scripts/src/m3-io-skill-client.mjs"; export const M3_IO_CONTROL_CONTRACT_VERSION = "m3-io-control-v1"; export const M3_IO_CONTROL_ROUTE = "/v1/m3/io"; export const M3_STATUS_CONTRACT_VERSION = "m3-status-v1"; export const M3_STATUS_ROUTE = "/v1/m3/status"; export const M3_IO_RPC_METHODS = Object.freeze({ doWrite: "m3.io.do.write", diRead: "m3.io.di.read" }); export const M3_IO_CHAIN = Object.freeze({ projectId: "prj_m3_hardware_loop", sourceGatewayId: "gwsimu_1", sourceGatewaySessionId: "gws_gwsimu_1", sourceResourceId: "res_boxsimu_1", sourceBoxId: "boxsimu_1", sourcePort: "DO1", targetGatewayId: "gwsimu_2", targetGatewaySessionId: "gws_gwsimu_2", targetResourceId: "res_boxsimu_2", targetBoxId: "boxsimu_2", targetPort: "DI1", patchPanelServiceId: "hwlab-patch-panel" }); export const M3_IO_BLOCKER_CODES = Object.freeze({ gatewayUnavailable: "m3_gateway_session_unavailable", gatewayIdentityMismatch: "m3_gateway_identity_mismatch", boxUnavailable: "m3_box_resource_unavailable", portDirectionInvalid: "m3_port_direction_invalid", wiringMissing: "m3_wiring_missing", patchPanelUnavailable: "m3_patch_panel_unavailable", dispatchFailed: "m3_gateway_dispatch_failed", runtimeDurableBlocked: "runtime_durable_not_green" }); const DEFAULT_TIMEOUT_MS = 2200; const DEV_SERVICE_ENDPOINTS = Object.freeze({ gateway1: "http://hwlab-gateway-simu-1.hwlab-dev.svc.cluster.local:7101", gateway2: "http://hwlab-gateway-simu-2.hwlab-dev.svc.cluster.local:7101", patchPanel: "http://hwlab-patch-panel.hwlab-dev.svc.cluster.local:7301" }); const ACTIONS = new Set(["do.write", "di.read"]); export function describeM3IoControl(options = {}) { const env = options.env ?? process.env; const config = buildM3IoConfig(env); return { serviceId: CLOUD_API_SERVICE_ID, contractVersion: M3_IO_CONTROL_CONTRACT_VERSION, route: M3_IO_CONTROL_ROUTE, status: config.enabled ? "available" : "blocked", capabilityLevel: HWLAB_M3_IO_CAPABILITY_LEVELS.blocked, readinessStatus: "not_checked", capabilityLevels: [ HWLAB_M3_IO_CAPABILITY_LEVELS.blocked, HWLAB_M3_IO_CAPABILITY_LEVELS.ready ], sourceKind: "SOURCE", chain: M3_IO_CHAIN, actions: [ { action: "do.write", label: "box-simu-1 DO1 write", gatewayId: M3_IO_CHAIN.sourceGatewayId, resourceId: M3_IO_CHAIN.sourceResourceId, boxId: M3_IO_CHAIN.sourceBoxId, port: M3_IO_CHAIN.sourcePort, valueType: "boolean" }, { action: "di.read", label: "box-simu-2 DI1 read", gatewayId: M3_IO_CHAIN.targetGatewayId, resourceId: M3_IO_CHAIN.targetResourceId, boxId: M3_IO_CHAIN.targetBoxId, port: M3_IO_CHAIN.targetPort, valueType: "boolean" } ], boundaries: { frontendCallsOnly: M3_IO_CONTROL_ROUTE, cloudApiDispatchesToGateway: true, patchPanelOwnsPropagation: true, directFrontendGatewayOrBoxAccess: false, genericHardwareRpcExposedToFrontend: false }, endpointsConfigured: { gateway1: Boolean(config.gateway1Url), gateway2: Boolean(config.gateway2Url), patchPanel: Boolean(config.patchPanelUrl) }, blockedReason: config.enabled ? null : "M3 IO 控制未启用;前端保持 blocked,不直连 gateway/box-simu。" }; } export async function describeM3IoControlLive(options = {}) { const contract = describeM3IoControl(options); const env = options.env ?? process.env; const config = buildM3IoConfig(env); const runtime = await safeRuntimeSummary(options.runtimeStore); const readiness = await buildM3IoReadiness({ config, requestJson: options.m3IoRequestJson ?? options.requestJson, now: options.now }); const controlReady = readiness.status === "ready" && readiness.controlReady === true; const capabilityLevel = controlReady ? HWLAB_M3_IO_CAPABILITY_LEVELS.ready : HWLAB_M3_IO_CAPABILITY_LEVELS.blocked; const trustReadiness = buildM3IoTrustReadiness(runtime, { observedAt: readiness.observedAt, controlReady }); return { ...contract, status: controlReady ? "available" : "blocked", sourceKind: controlReady ? "DEV-LIVE" : "BLOCKED", capabilityLevel, readinessStatus: readiness.status, readiness: { ...readiness, capabilityLevel, trust: trustReadiness }, trustReadiness, controlReady, blockedReason: controlReady ? null : readiness.blocker?.zh ?? contract.blockedReason ?? "M3 IO 只读 readiness 未 green;控制面板保持阻塞。" }; } export async function describeM3StatusLive(options = {}) { const env = options.env ?? process.env; const config = buildM3IoConfig(env); const requestJson = options.m3IoRequestJson ?? options.requestJson; const observedAt = options.now?.() ?? new Date().toISOString(); const runtime = await safeRuntimeSummary(options.runtimeStore); const trust = await buildM3StatusTrust({ runtimeStore: options.runtimeStore, runtime }); const base = { serviceId: CLOUD_API_SERVICE_ID, contractVersion: M3_STATUS_CONTRACT_VERSION, route: M3_STATUS_ROUTE, observedAt, chain: M3_IO_CHAIN, boundaries: { frontendCallsOnly: M3_STATUS_ROUTE, cloudApiAggregatesRuntimeStatus: true, patchPanelOwnsPropagation: true, directFrontendGatewayOrBoxAccess: false, sourceTopologyFallbackMustStayUnverified: true } }; 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, boxes, patchPanel, io: m3StatusIoSummary({ boxes, patchPanel, hardwareLive: false, observedAt }), operation: m3StatusOperationSummary(blockedTrust), trace: m3StatusTraceSummary(blockedTrust), audit: m3StatusAuditSummary(blockedTrust), runtimeDurable: m3StatusRuntimeDurableSummary(blockedTrust), trust: blockedTrust, blocker }; } const [sourceGateway, targetGateway, patchStatus, patchWiring] = await Promise.all([ readGatewaySession(config.gateway1Url, { gatewayRole: "source", expectedGatewayId: M3_IO_CHAIN.sourceGatewayId, expectedGatewaySessionId: M3_IO_CHAIN.sourceGatewaySessionId, requestJson, timeoutMs: config.timeoutMs }), readGatewaySession(config.gateway2Url, { gatewayRole: "target", expectedGatewayId: M3_IO_CHAIN.targetGatewayId, expectedGatewaySessionId: M3_IO_CHAIN.targetGatewaySessionId, requestJson, timeoutMs: config.timeoutMs }), requestRuntimeJson(new URL("/status", ensureTrailingSlash(config.patchPanelUrl)).toString(), { method: "GET", requestJson, timeoutMs: config.timeoutMs }), requestRuntimeJson(new URL("/wiring", ensureTrailingSlash(config.patchPanelUrl)).toString(), { method: "GET", requestJson, timeoutMs: config.timeoutMs }) ]); const sourceCommand = normalizeCommand("do.write", { value: false }); const targetCommand = normalizeCommand("di.read", {}); const sourceBox = sourceGateway.available ? findGatewayBox(sourceGateway.status, sourceCommand) : null; const targetBox = targetGateway.available ? findGatewayBox(targetGateway.status, targetCommand) : null; const [sourceDoRead, targetDiRead] = await Promise.all([ sourceGateway.available && sourceBox ? readM3StatusPort({ gateway: sourceGateway, command: sourceCommand, requestJson, timeoutMs: config.timeoutMs, observedAt, port: M3_IO_CHAIN.sourcePort, direction: "output" }) : Promise.resolve(unverifiedPortRead({ port: M3_IO_CHAIN.sourcePort, direction: "output", source: "gateway-simu", reason: sourceGateway.reason ?? `${M3_IO_CHAIN.sourceResourceId} 未在 source gateway registry 中可见`, observedAt })), targetGateway.available && targetBox ? readM3StatusPort({ gateway: targetGateway, command: targetCommand, requestJson, timeoutMs: config.timeoutMs, observedAt, port: M3_IO_CHAIN.targetPort, direction: "input" }) : Promise.resolve(unverifiedPortRead({ port: M3_IO_CHAIN.targetPort, direction: "input", source: "gateway-simu", reason: targetGateway.reason ?? `${M3_IO_CHAIN.targetResourceId} 未在 target gateway registry 中可见`, observedAt })) ]); const gateways = [ gatewayStatusEntry(sourceGateway, { id: M3_IO_CHAIN.sourceGatewayId, sessionId: M3_IO_CHAIN.sourceGatewaySessionId, role: "source", observedAt }), gatewayStatusEntry(targetGateway, { id: M3_IO_CHAIN.targetGatewayId, sessionId: M3_IO_CHAIN.targetGatewaySessionId, role: "target", observedAt }) ]; const boxes = [ boxStatusEntry({ gateway: sourceGateway, box: sourceBox, boxId: M3_IO_CHAIN.sourceBoxId, resourceId: M3_IO_CHAIN.sourceResourceId, gatewayId: M3_IO_CHAIN.sourceGatewayId, gatewaySessionId: M3_IO_CHAIN.sourceGatewaySessionId, observedAt, ports: { [M3_IO_CHAIN.sourcePort]: sourceDoRead.port } }), boxStatusEntry({ gateway: targetGateway, box: targetBox, boxId: M3_IO_CHAIN.targetBoxId, resourceId: M3_IO_CHAIN.targetResourceId, gatewayId: M3_IO_CHAIN.targetGatewayId, gatewaySessionId: M3_IO_CHAIN.targetGatewaySessionId, observedAt, ports: { [M3_IO_CHAIN.targetPort]: targetDiRead.port } }) ]; const patchPanel = patchPanelStatusEntry({ statusResponse: patchStatus, wiringResponse: patchWiring, observedAt }); const blockers = [ ...gateways.filter((gateway) => !gateway.online).map((gateway) => ({ code: gateway.blocker, layer: gateway.id, zh: gateway.lastError })), ...boxes.filter((box) => !box.online).map((box) => ({ code: M3_IO_BLOCKER_CODES.boxUnavailable, layer: box.resourceId, zh: `${box.resourceId} 未在 gateway-simu registry 中可见` })), patchPanel.connectionActive ? null : { code: patchPanel.observable === false ? M3_IO_BLOCKER_CODES.patchPanelUnavailable : M3_IO_BLOCKER_CODES.wiringMissing, layer: M3_IO_CHAIN.patchPanelServiceId, zh: patchPanel.lastError ?? `hwlab-patch-panel 未确认 active ${M3_IO_CHAIN.sourceResourceId}:${M3_IO_CHAIN.sourcePort} -> ${M3_IO_CHAIN.targetResourceId}:${M3_IO_CHAIN.targetPort} 接线` }, sourceDoRead.error ? { code: M3_IO_BLOCKER_CODES.dispatchFailed, layer: M3_IO_CHAIN.sourceResourceId, zh: sourceDoRead.error } : null, targetDiRead.error ? { code: M3_IO_BLOCKER_CODES.dispatchFailed, layer: M3_IO_CHAIN.targetResourceId, zh: targetDiRead.error } : null ].filter(Boolean); const hardwareLive = blockers.length === 0 && sourceDoRead.port.sourceKind === "DEV-LIVE" && targetDiRead.port.sourceKind === "DEV-LIVE"; const trustGreen = trust.durableStatus === "green" && trust.evidenceId && trust.auditId && trust.operationId && trust.traceId; const status = blockers.length > 0 ? 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, status, sourceKind, summary: status === "live" ? "M3 硬件状态与可信持久化均为 DEV-LIVE。" : hardwareLive ? "M3 DO1/DI1 实况读数来自 cloud-api 聚合入口;可信持久化仍保持 blocked,不得标为 trusted green。" : "M3 硬件状态尚未完整验证;SOURCE 拓扑只能作为未验证 fallback。", gateways, boxes, patchPanel, io: m3StatusIoSummary({ boxes, patchPanel, hardwareLive, observedAt }), operation: m3StatusOperationSummary(trust), trace: m3StatusTraceSummary(trust), audit: m3StatusAuditSummary(trust), runtimeDurable: m3StatusRuntimeDurableSummary(trust), trust, blocker }; } export async function handleM3IoControl(params = {}, context = {}) { const env = context.env ?? process.env; const now = context.now?.() ?? new Date().toISOString(); const config = buildM3IoConfig(env); const action = normalizeAction(params.action ?? params.operation); const meta = buildRequestMeta(params, context); const runtimeBefore = await safeRuntimeSummary(context.runtimeStore); if (!config.enabled) { return blockedResult({ action, meta, runtime: runtimeBefore, code: "m3_control_disabled", reason: "M3 IO 控制未启用;前端不能绕过 cloud-api 直连 gateway/box-simu。", httpStatus: 503, now }); } if (!ACTIONS.has(action)) { throw new HwlabProtocolError("M3 IO action must be do.write or di.read", { code: ERROR_CODES.invalidParams, data: { action, allowedActions: [...ACTIONS] } }); } const command = normalizeCommand(action, params); const directionIssue = validateM3Command(command); if (directionIssue) { return blockedResult({ action, meta, runtime: runtimeBefore, code: M3_IO_BLOCKER_CODES.portDirectionInvalid, reason: directionIssue, httpStatus: 400, now }); } const gatewayUrl = action === "do.write" ? config.gateway1Url : config.gateway2Url; const gatewayRole = action === "do.write" ? "source" : "target"; const gateway = await readGatewaySession(gatewayUrl, { gatewayRole, expectedGatewayId: command.gatewayId, expectedGatewaySessionId: command.gatewaySessionId, requestJson: context.requestJson, timeoutMs: config.timeoutMs }); if (!gateway.available) { return blockedResult({ action, command, meta, runtime: runtimeBefore, code: gateway.code ?? M3_IO_BLOCKER_CODES.gatewayUnavailable, reason: gateway.reason, httpStatus: 200, now, details: { gateway } }); } const box = findGatewayBox(gateway.status, command); if (!box) { return blockedResult({ action, command, meta, runtime: runtimeBefore, code: M3_IO_BLOCKER_CODES.boxUnavailable, reason: `${gatewayRole} gateway 已响应,但目标 box resource 未注册/不可用:${command.resourceId}`, httpStatus: 200, now, details: { gateway: publicGatewayStatus(gateway.status) } }); } const capability = capabilityFor(command, box); const operation = createOperationRecord({ action, command, capability, gatewaySessionId: gateway.gatewaySessionId, meta, now }); const registration = await persistRuntimeRegistrations({ runtimeStore: context.runtimeStore, gatewayStatus: gateway.status, box, capability, command, meta }); const operationWrite = await tryRuntimeWrite( "hardware.operation.request", () => context.runtimeStore?.requestHardwareOperation?.(operation, meta) ); if (action === "di.read") { return finalizeControlResult(await readDi({ config, command, capability, gateway, operation, meta, runtimeStore: context.runtimeStore, runtimeBefore, registration, operationWrite, requestJson: context.requestJson, now })); } return finalizeControlResult(await writeDo({ config, command, capability, gateway, operation, meta, runtimeStore: context.runtimeStore, runtimeBefore, registration, operationWrite, requestJson: context.requestJson, now })); } export async function buildM3IoReadiness({ config, requestJson, now } = {}) { const observedAt = now?.() ?? new Date().toISOString(); const checks = []; if (!config?.enabled) { return blockedReadiness({ checks, code: "m3_control_disabled", layer: "cloud-api", reason: "M3 IO 控制未启用;前端不能绕过 cloud-api 直连 gateway/box-simu。", observedAt }); } const sourceGateway = await readGatewaySession(config.gateway1Url, { gatewayRole: "source", expectedGatewayId: M3_IO_CHAIN.sourceGatewayId, expectedGatewaySessionId: M3_IO_CHAIN.sourceGatewaySessionId, requestJson, timeoutMs: config.timeoutMs }); checks.push(readinessCheckFromGateway("source-gateway", sourceGateway)); if (!sourceGateway.available) { return blockedReadiness({ checks, code: sourceGateway.code ?? M3_IO_BLOCKER_CODES.gatewayUnavailable, layer: "gateway-simu-1", reason: sourceGateway.reason, observedAt }); } const sourceCommand = normalizeCommand("do.write", {}); const sourceBox = findGatewayBox(sourceGateway.status, sourceCommand); checks.push(readinessCheckFromBox("source-box", sourceBox, sourceCommand)); if (!sourceBox) { return blockedReadiness({ checks, code: M3_IO_BLOCKER_CODES.boxUnavailable, layer: "box-simu-1", reason: `gateway-simu-1 已响应,但 ${M3_IO_CHAIN.sourceResourceId} 未注册/不可用;控制面板保持阻塞。`, observedAt }); } const targetGateway = await readGatewaySession(config.gateway2Url, { gatewayRole: "target", expectedGatewayId: M3_IO_CHAIN.targetGatewayId, expectedGatewaySessionId: M3_IO_CHAIN.targetGatewaySessionId, requestJson, timeoutMs: config.timeoutMs }); checks.push(readinessCheckFromGateway("target-gateway", targetGateway)); if (!targetGateway.available) { return blockedReadiness({ checks, code: targetGateway.code ?? M3_IO_BLOCKER_CODES.gatewayUnavailable, layer: "gateway-simu-2", reason: targetGateway.reason, observedAt }); } const targetCommand = normalizeCommand("di.read", {}); const targetBox = findGatewayBox(targetGateway.status, targetCommand); checks.push(readinessCheckFromBox("target-box", targetBox, targetCommand)); if (!targetBox) { return blockedReadiness({ checks, code: M3_IO_BLOCKER_CODES.boxUnavailable, layer: "box-simu-2", reason: `gateway-simu-2 已响应,但 ${M3_IO_CHAIN.targetResourceId} 未注册/不可用;控制面板保持阻塞。`, observedAt }); } const patchStatus = await requestRuntimeJson(new URL("/status", ensureTrailingSlash(config.patchPanelUrl)).toString(), { method: "GET", requestJson, timeoutMs: config.timeoutMs }); checks.push(readinessCheckFromPatchPanelStatus(patchStatus)); if (!patchStatus.ok) { return blockedReadiness({ checks, code: M3_IO_BLOCKER_CODES.patchPanelUnavailable, layer: "hwlab-patch-panel", reason: `hwlab-patch-panel 不可用:${patchStatus.error ?? `HTTP ${patchStatus.status}`}`, observedAt }); } const wiring = await requestRuntimeJson(new URL("/wiring", ensureTrailingSlash(config.patchPanelUrl)).toString(), { method: "GET", requestJson, timeoutMs: config.timeoutMs }); checks.push(readinessCheckFromPatchPanelWiring(wiring)); if (!wiring.ok) { return blockedReadiness({ checks, code: M3_IO_BLOCKER_CODES.wiringMissing, layer: "hwlab-patch-panel", reason: `hwlab-patch-panel 接线读取失败:${wiring.error ?? `HTTP ${wiring.status}`}`, observedAt }); } if (!hasExpectedPatchPanelWiring(patchStatus.body) || !hasExpectedPatchPanelWiring(wiring.body)) { return blockedReadiness({ checks, code: M3_IO_BLOCKER_CODES.wiringMissing, layer: "hwlab-patch-panel", reason: `hwlab-patch-panel 未确认 active ${M3_IO_CHAIN.sourceResourceId}:${M3_IO_CHAIN.sourcePort} -> ${M3_IO_CHAIN.targetResourceId}:${M3_IO_CHAIN.targetPort} 接线;控制面板保持阻塞。`, observedAt }); } return { status: "ready", controlReady: true, capabilityLevel: HWLAB_M3_IO_CAPABILITY_LEVELS.ready, sourceKind: "DEV-LIVE", evidenceLevel: "DEV-LIVE", observedAt, summary: "cloud-api 只读 readiness 已确认 gateway-simu、box-simu 与 hwlab-patch-panel 目标接线;这只表示控制面板可执行受控操作,不等于 M3 验收通过。", chain: M3_IO_CHAIN, checks, blocker: null }; } export async function handleM3IoRpc(action, params = {}, envelope = {}, context = {}) { return handleM3IoControl( { ...params, action, traceId: envelope.meta?.traceId ?? params.traceId, requestId: envelope.id ?? params.requestId, actorId: envelope.meta?.actorId ?? params.actorId }, { ...context, requestJson: context.requestJson ?? context.m3IoRequestJson, traceId: envelope.meta?.traceId ?? context.traceId, requestId: envelope.id ?? context.requestId, actorId: envelope.meta?.actorId ?? context.actorId } ); } export function buildM3IoConfig(env = process.env) { const enabledValue = String(env.HWLAB_M3_IO_CONTROL_ENABLED ?? env.HWLAB_M3_CONTROL_ENABLED ?? "true").toLowerCase(); return { enabled: !["0", "false", "no", "disabled"].includes(enabledValue), gateway1Url: trimUrl(env.HWLAB_M3_GATEWAY_SIMU_1_URL) ?? DEV_SERVICE_ENDPOINTS.gateway1, gateway2Url: trimUrl(env.HWLAB_M3_GATEWAY_SIMU_2_URL) ?? DEV_SERVICE_ENDPOINTS.gateway2, patchPanelUrl: trimUrl(env.HWLAB_M3_PATCH_PANEL_URL) ?? DEV_SERVICE_ENDPOINTS.patchPanel, timeoutMs: parseTimeout(env.HWLAB_M3_CONTROL_TIMEOUT_MS) }; } function blockedReadiness({ checks, code, layer, reason, observedAt }) { return { status: "blocked", controlReady: false, capabilityLevel: HWLAB_M3_IO_CAPABILITY_LEVELS.blocked, sourceKind: "BLOCKED", evidenceLevel: "BLOCKED", observedAt, summary: `M3 IO 只读 readiness 未 green:${reason}`, chain: M3_IO_CHAIN, blocker: { code, layer, zh: reason }, capabilityBlocker: { code, layer, zh: reason, category: blockerCategory(code) }, checks }; } function buildM3IoTrustReadiness(runtime, { observedAt, controlReady } = {}) { const durable = durableStatus(runtime); const ready = isDurableRuntimeReady(runtime); const blocker = ready ? null : { code: durable.blocker ?? M3_IO_BLOCKER_CODES.runtimeDurableBlocked, layer: "runtime-durable", zh: `runtime durable 未 green:${durable.blocker ?? M3_IO_BLOCKER_CODES.runtimeDurableBlocked}`, category: "runtime_durable" }; return { status: ready ? "ready" : "blocked", controlReady: Boolean(controlReady), trustedEvidenceReady: ready, capabilityLevel: controlReady ? HWLAB_M3_IO_CAPABILITY_LEVELS.ready : HWLAB_M3_IO_CAPABILITY_LEVELS.blocked, evidenceLevel: ready ? "DEV-LIVE" : "BLOCKED", sourceKind: ready ? "DEV-LIVE" : "BLOCKED", observedAt, durableStatus: durable, blocker, summary: ready ? "runtime durable 已 green;operation/audit/evidence 可作为可信记录写入。" : "受控硬件路径 readiness 与可信持久化分离;runtime durable 未 green 时不得宣称完整硬件控制验收。" }; } async function buildM3StatusTrust({ runtimeStore, runtime }) { const durable = durableStatus(runtime); const base = { operationId: null, traceId: null, auditId: null, evidenceId: null, durableStatus: isDurableRuntimeReady(runtime) ? "green" : "blocked", runtime: durable, blocker: isDurableRuntimeReady(runtime) ? null : durable.blocker }; const [audit, evidence] = await Promise.all([ safeRuntimeRead(() => runtimeStore?.queryAuditEvents?.({ projectId: M3_IO_CHAIN.projectId, limit: 8 })), safeRuntimeRead(() => runtimeStore?.queryEvidenceRecords?.({ projectId: M3_IO_CHAIN.projectId, limit: 8 })) ]); const latestAudit = latestM3AuditEvent(audit.value?.events ?? []); const latestEvidence = latestM3EvidenceRecord(evidence.value?.records ?? []); const operationId = latestEvidence?.operationId ?? latestAudit?.operationId ?? null; const traceId = latestEvidence?.traceId ?? latestEvidence?.metadata?.traceId ?? latestAudit?.traceId ?? null; return { ...base, operationId, traceId, auditId: latestAudit?.auditId ?? null, evidenceId: latestEvidence?.evidenceId ?? null, durableStatus: base.durableStatus, blocker: base.blocker ?? audit.error?.code ?? evidence.error?.code ?? null, readStatus: { audit: audit.ok ? "read" : "blocked", evidence: evidence.ok ? "read" : "blocked", auditError: audit.error?.message ?? null, evidenceError: evidence.error?.message ?? null } }; } 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 { ok: false, value: null, error: { code: "runtime_read_unavailable", message: "runtime read method is unavailable" } }; } try { const value = await read(); if (value === undefined || value === null) { return { ok: false, value: null, error: { code: "runtime_read_unavailable", message: "runtime read returned no value" } }; } return { ok: true, value, error: null }; } catch (error) { return { ok: false, value: null, error: { code: error?.code ?? "runtime_read_blocked", message: error instanceof Error ? redactBlockerMessage(error.message) : String(error) } }; } } function latestM3AuditEvent(events) { return [...events].reverse().find((event) => event?.operationId && event?.traceId && (event.action === "m3.io.do.write" || event.action === "m3.io.di.read" || String(event.metadata?.route ?? "").includes("res_boxsimu_1:DO1")) ) ?? null; } function latestM3EvidenceRecord(records) { return [...records].reverse().find((record) => record?.operationId && (record.serviceId === CLOUD_API_SERVICE_ID || String(record.metadata?.route ?? "").includes("res_boxsimu_1:DO1")) ) ?? null; } function defaultM3Gateways({ sourceKind, observedAt }) { return [ { id: M3_IO_CHAIN.sourceGatewayId, role: "source", online: false, observable: false, sessionId: M3_IO_CHAIN.sourceGatewaySessionId, lastSeenAt: observedAt, sourceKind, lastError: "未验证" }, { id: M3_IO_CHAIN.targetGatewayId, role: "target", online: false, observable: false, sessionId: M3_IO_CHAIN.targetGatewaySessionId, lastSeenAt: observedAt, sourceKind, lastError: "未验证" } ]; } function defaultM3Boxes({ sourceKind, observedAt }) { return [ { id: M3_IO_CHAIN.sourceBoxId, resourceId: M3_IO_CHAIN.sourceResourceId, gatewayId: M3_IO_CHAIN.sourceGatewayId, gatewaySessionId: M3_IO_CHAIN.sourceGatewaySessionId, online: false, observable: false, sourceKind, observedAt, ports: { [M3_IO_CHAIN.sourcePort]: { value: null, direction: "output", source: "未验证", sourceKind, observedAt } } }, { id: M3_IO_CHAIN.targetBoxId, resourceId: M3_IO_CHAIN.targetResourceId, gatewayId: M3_IO_CHAIN.targetGatewayId, gatewaySessionId: M3_IO_CHAIN.targetGatewaySessionId, online: false, observable: false, sourceKind, observedAt, ports: { [M3_IO_CHAIN.targetPort]: { value: null, direction: "input", source: "未验证", sourceKind, observedAt } } } ]; } function defaultM3PatchPanel({ observedAt }) { return { serviceId: M3_IO_CHAIN.patchPanelServiceId, observable: false, connectionActive: false, connectionVersion: null, wiringConfigId: null, lastSyncAt: observedAt, lastError: "未验证", sourceKind: "UNVERIFIED" }; } async function readM3StatusPort({ gateway, command, requestJson, timeoutMs, observedAt, port, direction }) { const capability = capabilityFor({ ...command, action: "di.read", port }, { boxId: command.boxId, resourceId: command.resourceId }); const dispatch = await invokeGateway(gateway.url, { operation: "hardware.port.read", projectId: M3_IO_CHAIN.projectId, gatewaySessionId: gateway.gatewaySessionId, resourceId: command.resourceId, boxId: command.boxId, capabilityId: capability.capabilityId, port, operationId: `op_m3_status_read_${port.toLowerCase()}_${randomUUID()}`, traceId: `trc_m3_status_read_${port.toLowerCase()}_${randomUUID()}`, actorType: "service", actorId: `svc_${CLOUD_API_SERVICE_ID}`, source: "cloud-api-m3-status" }, { requestJson, timeoutMs }); if (!dispatch.ok || dispatch.body?.accepted !== true) { return unverifiedPortRead({ port, direction, source: "gateway-simu", reason: `gateway-simu ${port} 读取失败:${dispatch.error ?? dispatch.body?.error?.message ?? "unknown"}`, observedAt }); } return { port: { value: readValueFromGatewayResult(dispatch.body), direction, source: port === M3_IO_CHAIN.targetPort ? "gateway-simu/patch-panel" : "gateway-simu", sourceKind: "DEV-LIVE", observedAt: dispatch.body?.result?.state?.updatedAt ?? dispatch.body?.state?.updatedAt ?? observedAt, auditId: dispatch.body?.auditId ?? dispatch.body?.result?.auditId ?? null, evidenceId: dispatch.body?.evidenceId ?? dispatch.body?.result?.evidenceId ?? null, state: dispatch.body?.result?.state ?? dispatch.body?.state ?? null }, error: null }; } function unverifiedPortRead({ port, direction, source, reason, observedAt }) { return { port: { value: null, direction, source, sourceKind: "UNVERIFIED", observedAt, lastError: reason }, error: reason }; } function gatewayStatusEntry(gateway, { id, sessionId, role, observedAt }) { return { id: gateway.gatewayId ?? id, role, online: gateway.available === true, observable: gateway.available === true, sessionId: gateway.gatewaySessionId ?? sessionId, lastSeenAt: gateway.status?.lastHeartbeatAt ?? gateway.status?.updatedAt ?? gateway.status?.session?.lastSeenAt ?? observedAt, sourceKind: gateway.available === true ? "DEV-LIVE" : "UNVERIFIED", lastError: gateway.available === true ? null : gateway.reason, blocker: gateway.available === true ? null : gateway.code ?? M3_IO_BLOCKER_CODES.gatewayUnavailable }; } function boxStatusEntry({ gateway, box, boxId, resourceId, gatewayId, gatewaySessionId, observedAt, ports }) { return { id: box?.boxId ?? boxId, resourceId: box?.resourceId ?? resourceId, gatewayId: gateway.gatewayId ?? gatewayId, gatewaySessionId: gateway.gatewaySessionId ?? gatewaySessionId, online: gateway.available === true && Boolean(box), observable: gateway.available === true && Boolean(box), sourceKind: gateway.available === true && box ? "DEV-LIVE" : "UNVERIFIED", observedAt, ports }; } function patchPanelStatusEntry({ statusResponse, wiringResponse, observedAt }) { const status = statusResponse.body ?? {}; const wiring = wiringResponse.body ?? {}; const connectionActive = statusResponse.ok && wiringResponse.ok && hasExpectedPatchPanelWiring(status) && hasExpectedPatchPanelWiring(wiring); const lastError = !statusResponse.ok ? `hwlab-patch-panel status 不可读:${statusResponse.error ?? `HTTP ${statusResponse.status}`}` : !wiringResponse.ok ? `hwlab-patch-panel wiring 不可读:${wiringResponse.error ?? `HTTP ${wiringResponse.status}`}` : connectionActive ? status.metadata?.evidenceSummary?.diagnostics?.errorCount > 0 ? "hwlab-patch-panel diagnostics reports errors" : null : `未确认 active ${M3_IO_CHAIN.sourceResourceId}:${M3_IO_CHAIN.sourcePort} -> ${M3_IO_CHAIN.targetResourceId}:${M3_IO_CHAIN.targetPort}`; return { serviceId: status.serviceId ?? M3_IO_CHAIN.patchPanelServiceId, observable: statusResponse.ok, connectionActive, connectionVersion: status.metadata?.evidenceSummary?.tickCount ?? status.metadata?.evidenceSummary?.routeCount ?? null, wiringConfigId: status.wiringConfigId ?? wiring.wiringConfigId ?? null, lastSyncAt: status.metadata?.evidenceSummary?.lastSyncAt ?? status.observedAt ?? wiring.updatedAt ?? observedAt, lastError, sourceKind: connectionActive ? "DEV-LIVE" : statusResponse.ok ? "UNVERIFIED" : "BLOCKED", activeConnections: status.activeConnections ?? [], diagnostics: status.metadata?.diagnostics ?? status.metadata?.evidenceSummary?.diagnostics ?? null }; } function readinessCheckFromGateway(id, gateway) { return { id, status: gateway.available ? "pass" : "blocked", layer: gateway.role, sourceKind: gateway.available ? "DEV-LIVE" : "BLOCKED", summary: gateway.available ? `${gateway.role} gateway ${gateway.gatewayId}/${gateway.gatewaySessionId} 只读状态可用。` : gateway.reason, gatewayId: gateway.gatewayId ?? null, gatewaySessionId: gateway.gatewaySessionId ?? null, url: gateway.url ? redactUrl(gateway.url) : null, blocker: gateway.available ? null : gateway.code ?? M3_IO_BLOCKER_CODES.gatewayUnavailable }; } function readinessCheckFromBox(id, box, command) { return { id, status: box ? "pass" : "blocked", sourceKind: box ? "DEV-LIVE" : "BLOCKED", summary: box ? `${command.resourceId}/${command.port} 已在 gateway registry 中可见。` : `${command.resourceId}/${command.port} 未在 gateway registry 中可见。`, resourceId: box?.resourceId ?? command.resourceId, boxId: box?.boxId ?? command.boxId, port: command.port, blocker: box ? null : M3_IO_BLOCKER_CODES.boxUnavailable }; } function readinessCheckFromPatchPanelStatus(response) { const expected = response.ok && hasExpectedPatchPanelWiring(response.body); return { id: "patch-panel-status", status: expected ? "pass" : "blocked", sourceKind: expected ? "DEV-LIVE" : "BLOCKED", summary: expected ? "hwlab-patch-panel status 包含目标 active 接线。" : response.ok ? "hwlab-patch-panel status 未包含目标 active 接线。" : `hwlab-patch-panel status 不可读:${response.error ?? `HTTP ${response.status}`}`, serviceId: response.body?.serviceId ?? null, state: response.body?.state ?? null, blocker: expected ? null : M3_IO_BLOCKER_CODES.wiringMissing }; } function readinessCheckFromPatchPanelWiring(response) { const expected = response.ok && hasExpectedPatchPanelWiring(response.body); return { id: "patch-panel-wiring", status: expected ? "pass" : "blocked", sourceKind: expected ? "DEV-LIVE" : "BLOCKED", summary: expected ? "hwlab-patch-panel /wiring 包含目标 active 接线。" : response.ok ? "hwlab-patch-panel /wiring 未包含目标 active 接线。" : `hwlab-patch-panel /wiring 不可读:${response.error ?? `HTTP ${response.status}`}`, wiringConfigId: response.body?.wiringConfigId ?? null, wiringStatus: response.body?.status ?? null, blocker: expected ? null : M3_IO_BLOCKER_CODES.wiringMissing }; } async function writeDo({ config, command, capability, gateway, operation, meta, runtimeStore, runtimeBefore, registration, operationWrite, requestJson, now }) { const dispatch = await invokeGateway(gateway.url, { operation: "hardware.port.write", projectId: M3_IO_CHAIN.projectId, gatewaySessionId: gateway.gatewaySessionId, resourceId: command.resourceId, boxId: command.boxId, capabilityId: capability.capabilityId, port: command.port, value: command.value, operationId: operation.operationId, traceId: meta.traceId, requestId: meta.requestId, actorType: "service", actorId: `svc_${CLOUD_API_SERVICE_ID}`, requestedBy: meta.actorId, source: "cloud-api-m3-io-control" }, { requestJson, timeoutMs: config.timeoutMs }); if (!dispatch.ok || dispatch.body?.accepted !== true) { return blockedControlResult({ action: command.action, command, operation, meta, runtimeStore, runtimeBefore, registration, operationWrite, code: M3_IO_BLOCKER_CODES.dispatchFailed, reason: `gateway-simu DO 写入失败:${dispatch.error ?? dispatch.body?.error?.message ?? "unknown"}`, gateway, dispatch, now }); } const patchPanel = await tickPatchPanel(config.patchPanelUrl, { signals: [ { fromResourceId: M3_IO_CHAIN.sourceResourceId, fromPort: M3_IO_CHAIN.sourcePort, value: command.value, operationId: operation.operationId, traceId: meta.traceId } ] }, { requestJson, timeoutMs: config.timeoutMs }); const patchIssue = classifyPatchPanelResult(patchPanel); if (patchIssue) { return blockedControlResult({ action: command.action, command, operation, meta, runtimeStore, runtimeBefore, registration, operationWrite, code: patchIssue.code, reason: patchIssue.reason, gateway, dispatch, patchPanel, now }); } const targetReadback = await readTargetDiAfterWrite({ config, operation, meta, requestJson }); if (targetReadback.status !== "succeeded" || targetReadback.value !== command.value) { return blockedControlResult({ action: command.action, command, operation, meta, runtimeStore, runtimeBefore, registration, operationWrite, code: targetReadback.code ?? M3_IO_BLOCKER_CODES.wiringMissing, reason: targetReadback.reason ?? `DI1 回读值 ${String(targetReadback.value)} 与 DO1 写入值 ${String(command.value)} 不一致;不能证明 M3 DO->DI 闭环。`, gateway, dispatch, patchPanel, targetReadback, now }); } const tracePayload = { action: command.action, command, gateway: gatewayResultSummary(gateway), dispatch: dispatch.body, patchPanel: patchPanel.body, targetReadback }; return successControlResult({ action: command.action, command, operation, meta, runtimeStore, runtimeBefore, registration, operationWrite, gateway, dispatch, patchPanel, targetReadback, tracePayload, now }); } async function readDi({ config, command, capability, gateway, operation, meta, runtimeStore, runtimeBefore, registration, operationWrite, requestJson, now }) { const dispatch = await invokeGateway(gateway.url, { operation: "hardware.port.read", projectId: M3_IO_CHAIN.projectId, gatewaySessionId: gateway.gatewaySessionId, resourceId: command.resourceId, boxId: command.boxId, capabilityId: capability.capabilityId, port: command.port, operationId: operation.operationId, traceId: meta.traceId, requestId: meta.requestId, actorType: "service", actorId: `svc_${CLOUD_API_SERVICE_ID}`, requestedBy: meta.actorId, source: "cloud-api-m3-io-control" }, { requestJson, timeoutMs: config.timeoutMs }); if (!dispatch.ok || dispatch.body?.accepted !== true) { return blockedControlResult({ action: command.action, command, operation, meta, runtimeStore, runtimeBefore, registration, operationWrite, code: M3_IO_BLOCKER_CODES.dispatchFailed, reason: `gateway-simu DI 读取失败:${dispatch.error ?? dispatch.body?.error?.message ?? "unknown"}`, gateway, dispatch, now }); } const tracePayload = { action: command.action, command, gateway: gatewayResultSummary(gateway), dispatch: dispatch.body, value: readValueFromGatewayResult(dispatch.body) }; return successControlResult({ action: command.action, command, operation, meta, runtimeStore, runtimeBefore, registration, operationWrite, gateway, dispatch, tracePayload, now }); } async function readTargetDiAfterWrite({ config, operation, meta, requestJson }) { const gateway = await readGatewaySession(config.gateway2Url, { gatewayRole: "target-readback", expectedGatewayId: M3_IO_CHAIN.targetGatewayId, expectedGatewaySessionId: M3_IO_CHAIN.targetGatewaySessionId, requestJson, timeoutMs: config.timeoutMs }); if (!gateway.available) { return { status: "blocked", code: gateway.code ?? M3_IO_BLOCKER_CODES.gatewayUnavailable, reason: gateway.reason }; } const command = normalizeCommand("di.read", {}); const box = findGatewayBox(gateway.status, command); if (!box) { return { status: "blocked", code: M3_IO_BLOCKER_CODES.boxUnavailable, reason: `target gateway 已响应,但 ${M3_IO_CHAIN.targetResourceId} 未注册/不可用` }; } const capability = capabilityFor(command, box); const dispatch = await invokeGateway(gateway.url, { operation: "hardware.port.read", projectId: M3_IO_CHAIN.projectId, gatewaySessionId: gateway.gatewaySessionId, resourceId: command.resourceId, boxId: command.boxId, capabilityId: capability.capabilityId, port: command.port, operationId: operation.operationId, traceId: meta.traceId, requestId: meta.requestId, actorType: "service", actorId: `svc_${CLOUD_API_SERVICE_ID}`, requestedBy: meta.actorId, source: "cloud-api-m3-io-control" }, { requestJson, timeoutMs: config.timeoutMs }); return dispatch.ok && dispatch.body?.accepted === true ? { status: "succeeded", value: readValueFromGatewayResult(dispatch.body), gatewaySessionId: gateway.gatewaySessionId, resourceId: command.resourceId, port: command.port, result: dispatch.body } : { status: "blocked", code: M3_IO_BLOCKER_CODES.dispatchFailed, reason: dispatch.error ?? dispatch.body?.error?.message ?? "target DI readback failed" }; } function normalizeAction(action) { return String(action ?? "").trim().toLowerCase(); } function normalizeCommand(action, params = {}) { if (action === "do.write") { return { action, gatewayId: params.gatewayId ?? params.sourceGatewayId ?? M3_IO_CHAIN.sourceGatewayId, gatewaySessionId: params.gatewaySessionId ?? M3_IO_CHAIN.sourceGatewaySessionId, resourceId: params.resourceId ?? M3_IO_CHAIN.sourceResourceId, boxId: params.boxId ?? M3_IO_CHAIN.sourceBoxId, port: normalizePort(params.port ?? M3_IO_CHAIN.sourcePort), value: normalizeBoolean(params.value) }; } return { action, gatewayId: params.gatewayId ?? params.targetGatewayId ?? M3_IO_CHAIN.targetGatewayId, gatewaySessionId: params.gatewaySessionId ?? M3_IO_CHAIN.targetGatewaySessionId, resourceId: params.resourceId ?? M3_IO_CHAIN.targetResourceId, boxId: params.boxId ?? M3_IO_CHAIN.targetBoxId, port: normalizePort(params.port ?? M3_IO_CHAIN.targetPort) }; } function validateM3Command(command) { if (command.action === "do.write") { if (command.gatewayId !== M3_IO_CHAIN.sourceGatewayId || command.resourceId !== M3_IO_CHAIN.sourceResourceId || command.port !== M3_IO_CHAIN.sourcePort) { return `M3 只允许 ${M3_IO_CHAIN.sourceGatewayId}/${M3_IO_CHAIN.sourceResourceId}/${M3_IO_CHAIN.sourcePort} 执行 DO write;当前请求会造成端口方向或资源越界。`; } if (typeof command.value !== "boolean") { return "M3 DO1 写入值必须是 boolean。"; } return null; } if (command.action === "di.read") { if (command.gatewayId !== M3_IO_CHAIN.targetGatewayId || command.resourceId !== M3_IO_CHAIN.targetResourceId || command.port !== M3_IO_CHAIN.targetPort) { return `M3 只允许 ${M3_IO_CHAIN.targetGatewayId}/${M3_IO_CHAIN.targetResourceId}/${M3_IO_CHAIN.targetPort} 执行 DI read;当前请求会造成端口方向或资源越界。`; } } return null; } function normalizePort(port) { return String(port ?? "").trim().toUpperCase(); } function normalizeBoolean(value) { if (typeof value === "boolean") return value; if (value === "true") return true; if (value === "false") return false; return value; } function buildRequestMeta(params = {}, context = {}) { return { traceId: params.traceId || context.traceId || `trc_${randomUUID()}`, requestId: params.requestId || context.requestId || `req_${randomUUID()}`, actorType: "user", actorId: params.actorId || context.actorId || "usr_hwlab_cloud_web", serviceId: CLOUD_API_SERVICE_ID, environment: ENVIRONMENT_DEV }; } function createOperationRecord({ action, command, capability, gatewaySessionId, meta, now }) { const operationId = command.operationId || `op_m3_${action.replace(".", "_")}_${randomUUID()}`; const operation = { operationId, projectId: M3_IO_CHAIN.projectId, gatewaySessionId, resourceId: command.resourceId, capabilityId: capability.capabilityId, requestedBy: meta.actorId, input: { action, port: command.port, ...(Object.hasOwn(command, "value") ? { value: command.value } : {}), route: { from: `${M3_IO_CHAIN.sourceResourceId}:${M3_IO_CHAIN.sourcePort}`, via: M3_IO_CHAIN.patchPanelServiceId, to: `${M3_IO_CHAIN.targetResourceId}:${M3_IO_CHAIN.targetPort}` }, frontendBypass: false }, status: "accepted", environment: ENVIRONMENT_DEV, requestedAt: now, updatedAt: now }; assertProtocolRecord("hardwareOperation", operation); return operation; } function capabilityFor(command, box = {}) { const suffix = command.action === "do.write" ? "write" : "read"; const portPart = command.port.toLowerCase(); const boxId = box.boxId ?? command.boxId; return { capabilityId: `cap_${boxId}_${portPart}_${suffix}`, resourceId: command.resourceId, projectId: M3_IO_CHAIN.projectId, name: command.action, description: `M3 ${command.action} ${command.resourceId}:${command.port}`, direction: command.action === "do.write" ? "output" : "input", valueType: "boolean", constraints: { port: command.port, portFamily: command.action === "do.write" ? "DO" : "DI", patchPanelOnlyPropagation: true, frontendBypassAllowed: false }, mutatesState: command.action === "do.write", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; } async function readGatewaySession(url, { gatewayRole, expectedGatewayId, expectedGatewaySessionId, requestJson, timeoutMs }) { if (!url) { return { available: false, role: gatewayRole, code: M3_IO_BLOCKER_CODES.gatewayUnavailable, reason: "gateway 未注册/不可用:cloud-api 未配置 gateway-simu endpoint" }; } const response = await requestRuntimeJson(new URL("/status", ensureTrailingSlash(url)).toString(), { method: "GET", requestJson, timeoutMs }); if (!response.ok) { return { available: false, role: gatewayRole, url: redactUrl(url), code: M3_IO_BLOCKER_CODES.gatewayUnavailable, reason: `gateway 未注册/不可用:${response.error ?? `HTTP ${response.status}`}` }; } const status = response.body ?? {}; const gatewaySessionId = status.session?.gatewaySessionId ?? status.gatewaySessionId ?? status.registry?.gatewaySessionId; const gatewayId = status.gatewayId ?? status.session?.gatewayId ?? status.registry?.gatewayId; if (!gatewaySessionId || !gatewayId) { return { available: false, role: gatewayRole, url: redactUrl(url), status, code: M3_IO_BLOCKER_CODES.gatewayUnavailable, reason: "gateway 未注册/不可用:/status 未返回 gatewaySessionId/gatewayId" }; } if (expectedGatewayId && gatewayId !== expectedGatewayId) { return { available: false, role: gatewayRole, url: redactUrl(url), status, code: M3_IO_BLOCKER_CODES.gatewayIdentityMismatch, reason: `gateway-simu 身份不匹配:期望 gatewayId=${expectedGatewayId},实际=${gatewayId};控制面板保持阻塞。` }; } if (expectedGatewaySessionId && gatewaySessionId !== expectedGatewaySessionId) { return { available: false, role: gatewayRole, url: redactUrl(url), status, code: M3_IO_BLOCKER_CODES.gatewayIdentityMismatch, reason: `gateway-simu 会话不匹配:期望 gatewaySessionId=${expectedGatewaySessionId},实际=${gatewaySessionId};控制面板保持阻塞。` }; } return { available: true, role: gatewayRole, url, gatewayId, gatewaySessionId, status }; } function findGatewayBox(gatewayStatus, command) { const boxes = [ ...(Array.isArray(gatewayStatus?.registry?.boxes) ? gatewayStatus.registry.boxes : []), ...(Array.isArray(gatewayStatus?.boxes) ? gatewayStatus.boxes.map((item) => typeof item === "string" ? { resourceId: item } : item) : []) ]; return boxes.find((box) => box?.resourceId === command.resourceId || box?.boxId === command.boxId) ?? null; } async function invokeGateway(gatewayUrl, body, options) { return requestRuntimeJson(new URL("/invoke", ensureTrailingSlash(gatewayUrl)).toString(), { method: "POST", body, requestJson: options.requestJson, timeoutMs: options.timeoutMs }); } async function tickPatchPanel(patchPanelUrl, body, options) { if (!patchPanelUrl) { return { ok: false, status: 0, error: "patch-panel endpoint is not configured" }; } return requestRuntimeJson(new URL("/sync/tick", ensureTrailingSlash(patchPanelUrl)).toString(), { method: "POST", body, requestJson: options.requestJson, timeoutMs: options.timeoutMs }); } function classifyPatchPanelResult(patchPanel) { if (!patchPanel.ok) { return { code: M3_IO_BLOCKER_CODES.patchPanelUnavailable, reason: `hwlab-patch-panel 不可用:${patchPanel.error ?? `HTTP ${patchPanel.status}`}` }; } if (patchPanel.body?.accepted !== true) { const diagnostic = firstDiagnostic(patchPanel.body); return { code: diagnostic?.code === "target_endpoint_missing" ? M3_IO_BLOCKER_CODES.boxUnavailable : M3_IO_BLOCKER_CODES.patchPanelUnavailable, reason: diagnostic?.message ?? "hwlab-patch-panel 拒绝同步;请检查 wiring 和 endpointMap。" }; } if (!hasExpectedPatchDelivery(patchPanel.body)) { return { code: M3_IO_BLOCKER_CODES.wiringMissing, reason: `hwlab-patch-panel 未路由 ${M3_IO_CHAIN.sourceResourceId}:${M3_IO_CHAIN.sourcePort} -> ${M3_IO_CHAIN.targetResourceId}:${M3_IO_CHAIN.targetPort};wiring 不存在或不是 active。` }; } return null; } function hasExpectedPatchDelivery(body) { const routes = Array.isArray(body?.routes) ? body.routes : []; return routes.some((route) => (route.deliveries ?? []).some((delivery) => delivery.sourceResourceId === M3_IO_CHAIN.sourceResourceId && delivery.sourcePort === M3_IO_CHAIN.sourcePort && delivery.resourceId === M3_IO_CHAIN.targetResourceId && delivery.port === M3_IO_CHAIN.targetPort && delivery.deliveryStatus !== "failed" ) ); } function hasExpectedPatchPanelWiring(body) { const connections = [ ...(Array.isArray(body?.activeConnections) ? body.activeConnections : []), ...(Array.isArray(body?.connections) ? body.connections : []), ...(Array.isArray(body?.wiringConfig?.connections) ? body.wiringConfig.connections : []) ]; const active = body?.state === undefined || !["faulted", "blocked", "disabled"].includes(String(body.state).toLowerCase()); const wiringActive = body?.status === undefined || String(body.status).toLowerCase() === "active"; return active && wiringActive && connections.some((connection) => { const from = connection.from ?? connection; const to = connection.to ?? connection; return String(from.resourceId ?? connection.fromResourceId ?? "") === M3_IO_CHAIN.sourceResourceId && normalizePort(from.port ?? connection.fromPort) === M3_IO_CHAIN.sourcePort && String(to.resourceId ?? connection.toResourceId ?? "") === M3_IO_CHAIN.targetResourceId && normalizePort(to.port ?? connection.toPort) === M3_IO_CHAIN.targetPort; }); } function firstDiagnostic(body) { return [ ...(Array.isArray(body?.diagnostics) ? body.diagnostics : []), ...(Array.isArray(body?.routes) ? body.routes.flatMap((route) => route.diagnostics ?? []) : []) ][0] ?? null; } async function requestRuntimeJson(url, { method = "GET", body, requestJson, timeoutMs = DEFAULT_TIMEOUT_MS }) { if (requestJson) { try { const result = await requestJson(url, { method, body, timeoutMs }); return normalizeRuntimeJsonResponse(result); } catch (error) { return { ok: false, status: 0, error: error instanceof Error ? error.message : String(error) }; } } const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetch(url, { method, headers: body === undefined ? { accept: "application/json" } : { accept: "application/json", "content-type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body), signal: controller.signal }); const text = await response.text(); let parsed = null; try { parsed = text ? JSON.parse(text) : null; } catch { parsed = null; } return { ok: response.ok, status: response.status, body: parsed, error: response.ok ? null : parsed?.error?.message ?? parsed?.message ?? response.statusText }; } catch (error) { return { ok: false, status: 0, error: error.name === "AbortError" ? `request timed out after ${timeoutMs}ms` : error.message }; } finally { clearTimeout(timer); } } function normalizeRuntimeJsonResponse(result = {}) { if (Object.hasOwn(result, "ok") || Object.hasOwn(result, "body")) { return { ok: result.ok ?? (Number(result.status ?? 200) >= 200 && Number(result.status ?? 200) < 300), status: result.status ?? 200, body: result.body ?? result.json ?? null, error: result.error ?? result.body?.error?.message ?? null }; } if (Object.hasOwn(result, "status") || Object.hasOwn(result, "json")) { const status = result.status ?? 200; return { ok: status >= 200 && status < 300, status, body: result.json ?? null, error: result.error ?? null }; } return { ok: true, status: 200, body: result }; } async function persistRuntimeRegistrations({ runtimeStore, gatewayStatus, box, capability, command, meta }) { const gatewaySessionId = gatewayStatus.session?.gatewaySessionId ?? gatewayStatus.gatewaySessionId ?? gatewayStatus.registry?.gatewaySessionId ?? command.gatewaySessionId; const gatewayId = gatewayStatus.gatewayId ?? gatewayStatus.session?.gatewayId ?? gatewayStatus.registry?.gatewayId ?? command.gatewayId; const gatewaySession = { projectId: M3_IO_CHAIN.projectId, gatewaySessionId, gatewayId, serviceId: "hwlab-gateway-simu", endpoint: gatewayStatus.registry?.endpoint ?? gatewayStatus.session?.endpoint, status: "connected", labels: { m3ControlSurface: "true" } }; const resource = { projectId: M3_IO_CHAIN.projectId, gatewaySessionId, resourceId: command.resourceId, boxId: box.boxId ?? command.boxId, resourceType: "simulator_endpoint", state: "available", metadata: { serviceId: "hwlab-box-simu", patchPanelOnlyPropagation: true, frontendBypassAllowed: false } }; const writes = []; writes.push(await tryRuntimeWrite("gateway.session.register", () => runtimeStore?.registerGatewaySession?.(gatewaySession, meta))); writes.push(await tryRuntimeWrite("box.resource.register", () => runtimeStore?.registerBoxResource?.(resource, meta))); writes.push(await tryRuntimeWrite("box.capability.report", () => runtimeStore?.reportBoxCapabilities?.({ capability }, meta))); return writes; } async function tryRuntimeWrite(label, write) { if (typeof write !== "function") { return { label, written: false, skipped: true, reason: "runtimeStore method unavailable" }; } try { const result = await write(); if (result === undefined) { return { label, written: false, skipped: true, reason: "runtimeStore method unavailable" }; } return { label, written: true, result }; } catch (error) { return { label, written: false, reason: error instanceof Error ? error.message : String(error), code: error?.code ?? null, data: error?.data ?? null }; } } async function successControlResult(input) { const records = await writeOutcomeRecords({ ...input, outcome: "succeeded", reason: null }); const target = controlTarget(input.command, input.operation.gatewaySessionId); const audit = auditState(records.auditWrite, records.runtimeAfter); const evidence = evidenceState(records.runtimeAfter, records); const readback = input.command.action === "di.read" ? { status: "succeeded", value: readValueFromGatewayResult(input.dispatch.body), resourceId: input.command.resourceId, port: input.command.port } : input.targetReadback ?? null; return { serviceId: CLOUD_API_SERVICE_ID, contractVersion: M3_IO_CONTROL_CONTRACT_VERSION, route: M3_IO_CONTROL_ROUTE, method: "POST", status: "succeeded", accepted: true, capabilityLevel: HWLAB_M3_IO_CAPABILITY_LEVELS.ready, controlReady: true, action: input.action, chain: M3_IO_CHAIN, command: input.command, target, gatewayId: target.gatewayId, gatewaySessionId: target.gatewaySessionId, resourceId: target.resourceId, boxId: target.boxId, port: target.port, value: target.value, operationId: input.operation.operationId, traceId: input.meta.traceId, auditId: records.auditEvent.auditId, evidenceId: records.evidenceRecord.evidenceId, audit: { auditId: records.auditEvent.auditId, ...audit }, evidence: { evidenceId: records.evidenceRecord.evidenceId, ...evidence }, operationState: { status: "succeeded", reachable: true }, auditState: audit, operation: { ...input.operation, status: "succeeded", completedAt: input.now, updatedAt: input.now }, result: { value: input.command.action === "di.read" ? readValueFromGatewayResult(input.dispatch.body) : input.command.value, dispatch: gatewayResultSummary(input.gateway), patchPanel: input.patchPanel?.body ?? null, targetReadback: input.targetReadback ?? null }, readback, controlPath: { status: "succeeded", cloudApi: true, gatewaySimu: true, boxSimu: true, patchPanel: input.action === "do.write", frontendBypass: false }, auditEvent: records.auditEvent, evidenceRecord: records.evidenceRecord, evidenceState: evidence, durableStatus: durableStatus(records.runtimeAfter), trustBlocker: trustBlocker(records.runtimeAfter), blockerClassification: redactedBlockerClassification({ code: records.runtimeAfter?.blocker, reason: records.runtimeAfter?.reason, runtime: records.runtimeAfter }), persistence: persistenceSummary(records.runtimeAfter, input.registration, input.operationWrite, records), observedAt: input.now }; } async function blockedControlResult(input) { const records = await writeOutcomeRecords({ ...input, outcome: "failed", reason: input.reason }); const target = controlTarget(input.command, input.operation.gatewaySessionId); const audit = auditState(records.auditWrite, records.runtimeAfter); const evidence = evidenceState(records.runtimeAfter, records); const readback = input.command.action === "di.read" ? null : input.targetReadback ?? null; return { serviceId: CLOUD_API_SERVICE_ID, contractVersion: M3_IO_CONTROL_CONTRACT_VERSION, route: M3_IO_CONTROL_ROUTE, method: "POST", status: "blocked", accepted: false, capabilityLevel: HWLAB_M3_IO_CAPABILITY_LEVELS.blocked, controlReady: false, action: input.action, chain: M3_IO_CHAIN, command: input.command, target, gatewayId: target.gatewayId, gatewaySessionId: target.gatewaySessionId, resourceId: target.resourceId, boxId: target.boxId, port: target.port, value: target.value, operationId: input.operation.operationId, traceId: input.meta.traceId, auditId: records.auditEvent.auditId, evidenceId: records.evidenceRecord.evidenceId, audit: { auditId: records.auditEvent.auditId, ...audit }, evidence: { evidenceId: records.evidenceRecord.evidenceId, ...evidence }, blocker: { code: input.code, message: input.reason, zh: input.reason }, blockerClassification: redactedBlockerClassification({ code: input.code, reason: input.reason, runtime: records.runtimeAfter }), operationState: { status: "blocked", reachable: false, blocker: input.code }, auditState: audit, operation: { ...input.operation, status: "failed", completedAt: input.now, updatedAt: input.now, error: { code: input.code, message: input.reason } }, result: { dispatch: input.gateway ? gatewayResultSummary(input.gateway) : null, patchPanel: input.patchPanel?.body ?? null, targetReadback: input.targetReadback ?? null }, readback, controlPath: { status: "blocked", cloudApi: true, gatewaySimu: Boolean(input.gateway), boxSimu: input.code !== M3_IO_BLOCKER_CODES.gatewayUnavailable, patchPanel: Boolean(input.patchPanel?.ok), frontendBypass: false }, auditEvent: records.auditEvent, evidenceRecord: records.evidenceRecord, evidenceState: evidence, durableStatus: durableStatus(records.runtimeAfter), trustBlocker: trustBlocker(records.runtimeAfter), persistence: persistenceSummary(records.runtimeAfter, input.registration, input.operationWrite, records), observedAt: input.now }; } function blockedResult({ action, command = null, meta, runtime, code, reason, httpStatus, now, details = {} }) { const target = controlTarget(command, command?.gatewaySessionId); const audit = { auditId: null, status: "not_written", durableStatus: durableStatus(runtime), reason: "operation was blocked before audit write" }; const evidence = evidenceState(runtime, { auditWrite: { written: false, reason: "operation was blocked before audit write" }, evidenceWrite: { written: false, reason: "operation was blocked before evidence write" } }); return { serviceId: CLOUD_API_SERVICE_ID, contractVersion: M3_IO_CONTROL_CONTRACT_VERSION, route: M3_IO_CONTROL_ROUTE, method: "POST", status: "blocked", accepted: false, capabilityLevel: HWLAB_M3_IO_CAPABILITY_LEVELS.blocked, controlReady: false, action: action || "unknown", chain: M3_IO_CHAIN, command, target, gatewayId: target.gatewayId, gatewaySessionId: target.gatewaySessionId, resourceId: target.resourceId, boxId: target.boxId, port: target.port, value: target.value, operationId: null, traceId: meta.traceId, auditId: null, evidenceId: null, audit, evidence: { evidenceId: null, ...evidence }, blocker: { code, message: reason, zh: reason }, blockerClassification: redactedBlockerClassification({ code, reason, runtime }), operationState: { status: "blocked", reachable: false, blocker: code }, auditState: audit, result: { value: null, targetReadback: null }, readback: null, controlPath: { status: "blocked", cloudApi: true, gatewaySimu: false, boxSimu: false, patchPanel: false, frontendBypass: false }, evidenceState: evidence, durableStatus: durableStatus(runtime), trustBlocker: trustBlocker(runtime), persistence: { runtime, writes: [] }, observedAt: now, httpStatus, ...details }; } async function writeOutcomeRecords({ action, command, operation, meta, runtimeStore, runtimeBefore, registration, operationWrite, tracePayload, outcome, reason, now }) { const auditEvent = createProtocolAuditEvent({ auditId: `aud_${operation.operationId.slice(3)}_${outcome}`, traceId: meta.traceId, actorType: "user", actorId: meta.actorId, action: action === "do.write" ? "m3.io.do.write" : "m3.io.di.read", targetType: "hardware_operation", targetId: operation.operationId, projectId: M3_IO_CHAIN.projectId, gatewaySessionId: operation.gatewaySessionId, operationId: operation.operationId, serviceId: CLOUD_API_SERVICE_ID, outcome, reason: reason ?? undefined, metadata: { route: `${M3_IO_CHAIN.sourceResourceId}:${M3_IO_CHAIN.sourcePort}->${M3_IO_CHAIN.patchPanelServiceId}->${M3_IO_CHAIN.targetResourceId}:${M3_IO_CHAIN.targetPort}`, command, frontendBypass: false, runtimeDurableBefore: runtimeBefore?.status ?? "unknown" }, occurredAt: now }); const evidenceRecord = createEvidenceRecord({ evidenceId: `evd_${operation.operationId.slice(3)}_${outcome}`, projectId: M3_IO_CHAIN.projectId, operationId: operation.operationId, traceId: meta.traceId, createdAt: now, payload: { outcome, reason, tracePayload, command, frontendBypass: false, patchPanelOnlyPropagation: true } }); const auditWrite = await tryRuntimeWrite("audit.event.write", () => runtimeStore?.writeAuditEvent?.({ event: auditEvent }, meta)); const evidenceWrite = await tryRuntimeWrite("evidence.record.write", () => runtimeStore?.writeEvidenceRecord?.({ record: evidenceRecord }, meta)); const runtimeAfter = await safeRuntimeSummary(runtimeStore); return { auditEvent, evidenceRecord, auditWrite, evidenceWrite, runtimeAfter, registration, operationWrite }; } function createEvidenceRecord({ evidenceId, projectId, operationId, traceId, payload, createdAt }) { const metadata = { traceId, serviceId: CLOUD_API_SERVICE_ID, route: `${M3_IO_CHAIN.sourceResourceId}:${M3_IO_CHAIN.sourcePort}->${M3_IO_CHAIN.patchPanelServiceId}->${M3_IO_CHAIN.targetResourceId}:${M3_IO_CHAIN.targetPort}`, frontendBypass: false, patchPanelOnlyPropagation: true }; const serialized = JSON.stringify({ serviceId: CLOUD_API_SERVICE_ID, projectId, operationId, traceId, payload, metadata, createdAt }); const record = { evidenceId, projectId, operationId, kind: "trace", uri: `memory://hwlab-cloud-api/${operationId}/m3-io-control.json`, mimeType: "application/json", sha256: createHash("sha256").update(serialized).digest("hex"), sizeBytes: Buffer.byteLength(serialized), serviceId: CLOUD_API_SERVICE_ID, environment: ENVIRONMENT_DEV, metadata, createdAt }; assertProtocolRecord("evidenceRecord", record); return record; } function evidenceState(runtime, records) { const durableReady = isDurableRuntimeReady(runtime); const recordsWritten = records.auditWrite?.written === true && records.evidenceWrite?.written === true; const evidenceWriteState = recordWriteState(records.evidenceWrite, runtime); if (durableReady && recordsWritten) { return { status: "green", sourceKind: "DEV-LIVE", durable: true, writeStatus: evidenceWriteState.status, durableStatus: durableStatus(runtime), reason: "operation/audit/evidence 已通过 durable runtime 写入;仍需 DEV 浏览器 E2E 才能宣称验收通过。" }; } const blocker = runtime?.blocker ?? M3_IO_BLOCKER_CODES.runtimeDurableBlocked; return { status: "blocked", sourceKind: "BLOCKED", durable: runtime?.durable === true, blocker, writeStatus: evidenceWriteState.status, durableStatus: durableStatus(runtime), reason: `操作链路可达性与可信持久化分离:runtime durable 未 green(status=${runtime?.status ?? "unknown"},blocker=${blocker}),不能宣称可信闭环完全 green。` }; } function auditState(auditWrite, runtime) { return { ...recordWriteState(auditWrite, runtime), durableStatus: durableStatus(runtime) }; } function recordWriteState(write, runtime) { if (write?.written === true && isDurableRuntimeReady(runtime)) { return { status: "persisted", durable: true, sourceKind: "DEV-LIVE" }; } if (write?.written === true) { return { status: "written_non_durable", durable: false, sourceKind: "BLOCKED", blocker: runtime?.blocker ?? M3_IO_BLOCKER_CODES.runtimeDurableBlocked }; } return { status: "not_written", durable: false, sourceKind: "BLOCKED", blocker: runtime?.blocker ?? M3_IO_BLOCKER_CODES.runtimeDurableBlocked, reason: write?.reason ?? "runtime write did not complete" }; } function durableStatus(runtime) { const ready = isDurableRuntimeReady(runtime); return { status: runtime?.status ?? "unknown", durable: runtime?.durable === true, ready: runtime?.ready === true, liveRuntimeEvidence: runtime?.liveRuntimeEvidence === true, blocker: ready ? null : runtime?.blocker ?? M3_IO_BLOCKER_CODES.runtimeDurableBlocked, redacted: true }; } function trustBlocker(runtime) { if (isDurableRuntimeReady(runtime)) return null; const durable = durableStatus(runtime); return { code: durable.blocker ?? M3_IO_BLOCKER_CODES.runtimeDurableBlocked, layer: "runtime-durable", zh: `runtime durable 未 green:${durable.blocker ?? M3_IO_BLOCKER_CODES.runtimeDurableBlocked}`, category: "runtime_durable", durableStatus: durable }; } function redactedBlockerClassification({ code, reason, runtime } = {}) { const blockerCode = code ?? runtime?.blocker ?? null; if (!blockerCode) { return null; } return { code: blockerCode, category: blockerCategory(blockerCode), runtimeStatus: runtime?.status ?? "unknown", redacted: true, message: reason ? redactBlockerMessage(reason) : null }; } function blockerCategory(code) { if (String(code).startsWith("runtime_durable")) return "runtime_durable"; if (String(code).includes("gateway")) return "gateway_session"; if (String(code).includes("patch_panel")) return "patch_panel"; if (String(code).includes("wiring")) return "patch_panel_wiring"; if (String(code).includes("box")) return "box_resource"; if (String(code).includes("port")) return "port_direction"; return "m3_io_control"; } function redactBlockerMessage(message) { return String(message) .replace(/https?:\/\/[^\s,,;]+/giu, "[redacted-url]") .replace(/postgres(?:ql)?:\/\/[^\s,,;]+/giu, "[redacted-db-url]"); } function controlTarget(command, gatewaySessionId) { return { gatewayId: command?.gatewayId ?? null, gatewaySessionId: gatewaySessionId ?? command?.gatewaySessionId ?? null, resourceId: command?.resourceId ?? null, boxId: command?.boxId ?? null, port: command?.port ?? null, value: Object.hasOwn(command ?? {}, "value") ? command.value : null }; } function persistenceSummary(runtime, registration, operationWrite, records) { return { runtime, writes: [ ...(registration ?? []), operationWrite, records.auditWrite, records.evidenceWrite ].filter(Boolean) }; } async function safeRuntimeSummary(runtimeStore) { try { if (typeof runtimeStore?.readiness === "function") { return await runtimeStore.readiness(); } if (typeof runtimeStore?.summary === "function") { return runtimeStore.summary(); } } catch (error) { return { adapter: "unknown", durable: false, ready: false, status: "blocked", blocker: "runtime_summary_unavailable", reason: error instanceof Error ? error.message : String(error), liveRuntimeEvidence: false }; } return { adapter: "none", durable: false, ready: false, status: "blocked", blocker: "runtime_store_missing", reason: "runtime store is not available", liveRuntimeEvidence: false }; } function isDurableRuntimeReady(runtime) { if (!runtime || typeof runtime !== "object") return false; if (runtime.durable !== true) return false; if (runtime.ready === false) return false; if (runtime.liveRuntimeEvidence !== true) return false; return !["blocked", "degraded", "failed"].includes(String(runtime.status ?? "").toLowerCase()); } function readValueFromGatewayResult(result) { return result?.result?.value ?? result?.value ?? null; } function gatewayResultSummary(gateway) { return { gatewayId: gateway.gatewayId, gatewaySessionId: gateway.gatewaySessionId, url: redactUrl(gateway.url), role: gateway.role }; } function publicGatewayStatus(status) { return { gatewayId: status?.gatewayId ?? status?.session?.gatewayId ?? null, gatewaySessionId: status?.session?.gatewaySessionId ?? status?.gatewaySessionId ?? null, boxCount: Array.isArray(status?.registry?.boxes) ? status.registry.boxes.length : null }; } function finalizeControlResult(result) { return result; } function ensureTrailingSlash(url) { return url.endsWith("/") ? url : `${url}/`; } function trimUrl(value) { const text = String(value ?? "").trim(); return text ? text : null; } function parseTimeout(value) { const parsed = Number.parseInt(value ?? "", 10); return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS; } function redactUrl(url) { try { const parsed = new URL(url); parsed.username = parsed.username ? "***" : ""; parsed.password = parsed.password ? "***" : ""; return parsed.toString(); } catch { return "invalid-url"; } }