import assert from "node:assert/strict"; import { test } from "bun:test"; import { createCloudRuntimeStore } from "../db/runtime-store.ts"; import { createBoxState, createGatewayState } from "../sim/model.mjs"; import { writeBoxPort } from "../sim/l2-runtime.mjs"; import { validateResponse } from "../protocol/index.mjs"; import { handleJsonRpcRequest, SUPPORTED_RPC_METHODS } from "./json-rpc.ts"; import { HWLAB_M3_IO_CAPABILITY_LEVELS } from "../../skills/hwlab-agent-runtime/scripts/src/m3-io-skill-client.mjs"; import { M3_IO_BLOCKER_CODES, M3_IO_CHAIN, M3_IO_RPC_METHODS, M3_STATUS_ROUTE, describeM3IoControl, describeM3IoControlLive, describeM3StatusLive, handleM3IoControl } from "./m3-io-control.ts"; const fixedNow = "2026-05-23T00:00:00.000Z"; test("M3 IO control describes a cloud-api-only control surface contract", () => { const contract = describeM3IoControl({ env: { HWLAB_M3_IO_CONTROL_ENABLED: "true" } }); assert.equal(contract.status, "available"); assert.equal(contract.route, "/v1/m3/io"); assert.equal(contract.boundaries.frontendCallsOnly, "/v1/m3/io"); assert.equal(contract.boundaries.cloudApiDispatchesToGateway, true); assert.equal(contract.boundaries.patchPanelOwnsPropagation, true); assert.equal(contract.boundaries.directFrontendGatewayOrBoxAccess, false); assert.equal(contract.boundaries.genericHardwareRpcExposedToFrontend, false); assert.deepEqual(contract.chain, M3_IO_CHAIN); }); test("M3 IO live descriptor enables controls only after read-only backend readiness is green", async () => { const fixture = createM3ControlFixture(); const contract = await describeM3IoControlLive({ env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, now: () => fixedNow, m3IoRequestJson: fixture.requestJson }); assert.equal(contract.status, "available"); assert.equal(contract.controlReady, true); assert.equal(contract.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.ready); assert.equal(contract.readiness.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.ready); assert.equal(contract.readiness.status, "ready"); assert.equal(contract.readiness.sourceKind, "DEV-LIVE"); assert.equal(contract.trustReadiness.status, "blocked"); assert.equal(contract.trustReadiness.blocker.code, "runtime_store_missing"); assert.equal(contract.blockedReason, null); assert.deepEqual(fixture.calls.map((call) => call.path), [ "/status", "/status", "/status", "/wiring" ]); }); test("M3 IO live descriptor blocks controls with Chinese reason when readiness is not green", async () => { const fixture = createM3ControlFixture({ patchPanelStatusBody: { serviceId: "hwlab-patch-panel", state: "active", activeConnections: [] }, patchPanelWiringBody: { wiringConfigId: "wir_wrong", status: "active", connections: [] } }); const contract = await describeM3IoControlLive({ env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, now: () => fixedNow, m3IoRequestJson: fixture.requestJson }); assert.equal(contract.status, "blocked"); assert.equal(contract.controlReady, false); assert.equal(contract.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(contract.readiness.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(contract.readiness.status, "blocked"); assert.equal(contract.readiness.sourceKind, "BLOCKED"); assert.match(contract.blockedReason, /未确认 active|控制面板保持阻塞/u); assert.equal(contract.readiness.blocker.code, M3_IO_BLOCKER_CODES.wiringMissing); assert.equal(contract.readiness.capabilityBlocker.category, "patch_panel_wiring"); }); test("M3 IO live descriptor classifies gateway unavailable and patch-panel unavailable blockers", async () => { const gatewayBlocked = await describeM3IoControlLive({ env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, now: () => fixedNow, m3IoRequestJson: async (url) => { if (url.startsWith("http://gateway-1")) { return { ok: false, status: 503, body: { error: { message: "gateway unavailable" } } }; } throw new Error("readiness must stop at the first gateway blocker"); } }); assert.equal(gatewayBlocked.status, "blocked"); assert.equal(gatewayBlocked.controlReady, false); assert.equal(gatewayBlocked.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(gatewayBlocked.readiness.blocker.code, M3_IO_BLOCKER_CODES.gatewayUnavailable); assert.equal(gatewayBlocked.readiness.capabilityBlocker.category, "gateway_session"); assert.match(gatewayBlocked.blockedReason, /gateway 未注册\/不可用/u); const fixture = createM3ControlFixture(); const patchPanelBlocked = await describeM3IoControlLive({ env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, now: () => fixedNow, m3IoRequestJson: async (url, request) => { if (url === "http://patch-panel/status") { return { ok: false, status: 503, body: { error: { message: "patch-panel unavailable" } } }; } return fixture.requestJson(url, request); } }); assert.equal(patchPanelBlocked.status, "blocked"); assert.equal(patchPanelBlocked.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(patchPanelBlocked.readiness.blocker.code, M3_IO_BLOCKER_CODES.patchPanelUnavailable); assert.equal(patchPanelBlocked.readiness.capabilityBlocker.category, "patch_panel"); assert.match(patchPanelBlocked.blockedReason, /hwlab-patch-panel 不可用/u); }); test("M3 status live descriptor aggregates gateways, boxes, patch-panel, IO values, and blocked trust", async () => { const fixture = createM3ControlFixture(); writeBoxPort({ state: fixture.box1, port: "DO1", value: true, source: "gateway-simu" }); writeBoxPort({ state: fixture.box2, port: "DI1", value: true, source: "patch-panel", sourceResourceId: "res_boxsimu_1", sourcePort: "DO1", propagatedBy: "hwlab-patch-panel" }); const status = await describeM3StatusLive({ runtimeStore: createBlockedDurableRuntimeStore({ blocker: "runtime_durable_adapter_auth_blocked" }), env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, now: () => fixedNow, m3IoRequestJson: fixture.requestJson }); assert.equal(status.route, M3_STATUS_ROUTE); assert.equal(status.status, "blocked"); assert.equal(status.sourceKind, "DEV-LIVE"); assert.equal(status.chain.sourceResourceId, "res_boxsimu_1"); assert.equal(status.boundaries.frontendCallsOnly, "/v1/m3/status"); assert.equal(status.boundaries.directFrontendGatewayOrBoxAccess, false); assert.deepEqual(status.gateways.map((gateway) => [gateway.id, gateway.online, gateway.sessionId, gateway.sourceKind]), [ ["gwsimu_1", true, "gws_gwsimu_1", "DEV-LIVE"], ["gwsimu_2", true, "gws_gwsimu_2", "DEV-LIVE"] ]); const box1 = status.boxes.find((box) => box.resourceId === "res_boxsimu_1"); const box2 = status.boxes.find((box) => box.resourceId === "res_boxsimu_2"); assert.equal(box1.online, true); assert.equal(box1.ports.DO1.value, true); assert.equal(box1.ports.DO1.sourceKind, "DEV-LIVE"); assert.equal(box2.online, true); assert.equal(box2.ports.DI1.value, true); assert.equal(box2.ports.DI1.sourceKind, "DEV-LIVE"); assert.equal(box2.ports.DI1.source, "gateway-simu/patch-panel"); assert.equal(status.patchPanel.serviceId, "hwlab-patch-panel"); 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"); assert.equal(status.trust.evidenceId, null); assert.equal(status.blocker.code, "runtime_durable_adapter_auth_blocked"); assert.deepEqual(fixture.calls.map((call) => call.path), [ "/status", "/status", "/status", "/wiring", "/invoke", "/invoke" ]); }); test("M3 status clears nested runtime blocker when trusted durable evidence is green", async () => { const fixture = createM3ControlFixture(); const status = await describeM3StatusLive({ runtimeStore: createGreenDurableRuntimeStore({ blocker: M3_IO_BLOCKER_CODES.runtimeDurableBlocked }), env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, now: () => fixedNow, m3IoRequestJson: fixture.requestJson }); assert.equal(status.status, "live"); assert.equal(status.sourceKind, "DEV-LIVE"); assert.equal(status.patchPanel.connectionActive, true); assert.equal(status.trust.durableStatus, "green"); assert.equal(status.trust.blocker, null); assert.equal(status.trust.runtime.status, "ready"); assert.equal(status.trust.runtime.durable, true); assert.equal(status.trust.runtime.ready, true); assert.equal(status.trust.runtime.liveRuntimeEvidence, true); assert.equal(status.trust.runtime.blocker, null); assert.equal(status.blocker, null); assert.equal(status.trust.operationId, "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f"); 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 () => { const fixture = createM3ControlFixture({ patchPanelStatusBody: { serviceId: "hwlab-patch-panel", state: "active", activeConnections: [] }, patchPanelWiringBody: { wiringConfigId: "wir_wrong", status: "active", connections: [] } }); const status = await describeM3StatusLive({ runtimeStore: createBlockedDurableRuntimeStore(), env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, now: () => fixedNow, m3IoRequestJson: fixture.requestJson }); assert.equal(status.status, "blocked"); assert.equal(status.patchPanel.connectionActive, false); assert.equal(status.patchPanel.sourceKind, "UNVERIFIED"); assert.match(status.patchPanel.lastError, /未确认 active/u); assert.equal(status.blocker.code, M3_IO_BLOCKER_CODES.wiringMissing); assert.equal(status.boxes[0].ports.DO1.sourceKind, "DEV-LIVE"); assert.equal(status.boxes[1].ports.DI1.sourceKind, "DEV-LIVE"); assert.notEqual(status.sourceKind, "SOURCE"); }); test("M3 DO write dispatches through gateway-simu, ticks patch-panel, reads DI, and records audit/evidence fields", async () => { const fixture = createM3ControlFixture(); const runtimeStore = createCloudRuntimeStore({ now: () => fixedNow }); const result = await handleM3IoControl( { action: "do.write", gatewayId: "gwsimu_1", resourceId: "res_boxsimu_1", boxId: "boxsimu_1", port: "DO1", value: true, traceId: "trc_m3_control_write_true", requestId: "req_m3_control_write_true", actorId: "usr_m3_operator" }, { runtimeStore, now: () => fixedNow, env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, requestJson: fixture.requestJson } ); assert.equal(result.status, "succeeded"); assert.equal(result.accepted, true); assert.equal(result.route, "/v1/m3/io"); assert.equal(result.method, "POST"); assert.equal(result.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.ready); assert.equal(result.controlReady, true); assert.equal(result.action, "do.write"); assert.equal(result.command.port, "DO1"); assert.equal(result.command.value, true); assert.equal(result.gatewayId, "gwsimu_1"); assert.equal(result.gatewaySessionId, "gws_gwsimu_1"); assert.equal(result.resourceId, "res_boxsimu_1"); assert.equal(result.boxId, "boxsimu_1"); assert.equal(result.port, "DO1"); assert.equal(result.value, true); assert.deepEqual(result.target, { gatewayId: "gwsimu_1", gatewaySessionId: "gws_gwsimu_1", resourceId: "res_boxsimu_1", boxId: "boxsimu_1", port: "DO1", value: true }); assert.match(result.operationId, /^op_m3_do_write_/u); assert.equal(result.traceId, "trc_m3_control_write_true"); assert.match(result.auditId, /^aud_m3_do_write_/u); assert.match(result.evidenceId, /^evd_m3_do_write_/u); assert.equal(result.audit.auditId, result.auditId); assert.equal(result.audit.status, "written_non_durable"); assert.equal(result.evidence.evidenceId, result.evidenceId); assert.equal(result.evidence.status, "blocked"); assert.equal(result.readback.value, true); assert.equal(result.operationState.status, "succeeded"); assert.equal(result.auditState.status, "written_non_durable"); assert.equal(result.auditState.durableStatus.durable, false); assert.equal(result.durableStatus.status, "degraded"); assert.equal(result.durableStatus.redacted, true); assert.equal(result.trustBlocker.code, result.durableStatus.blocker); assert.equal(result.blockerClassification.category, "runtime_durable"); assert.equal(result.blockerClassification.redacted, true); assert.equal(result.controlPath.cloudApi, true); assert.equal(result.controlPath.gatewaySimu, true); assert.equal(result.controlPath.boxSimu, true); assert.equal(result.controlPath.patchPanel, true); assert.equal(result.controlPath.frontendBypass, false); assert.equal(result.result.value, true); assert.equal(result.result.targetReadback.value, true); assert.equal(result.evidenceState.status, "blocked"); assert.equal(result.evidenceState.sourceKind, "BLOCKED"); assert.equal(result.evidenceState.writeStatus, "written_non_durable"); assert.match(result.evidenceState.reason, /runtime durable 未 green/u); assert.equal(fixture.box1.ports.DO1.value, true); assert.equal(fixture.box2.ports.DI1.value, true); assert.deepEqual(fixture.calls.map((call) => call.path), [ "/status", "/invoke", "/sync/tick", "/status", "/invoke" ]); assert.equal(fixture.calls.every((call) => call.url.startsWith("http://gateway-") || call.url.startsWith("http://patch-panel")), true); assert.equal(fixture.calls[1].body.operation, "hardware.port.write"); assert.equal(fixture.calls[1].body.gatewaySessionId, "gws_gwsimu_1"); assert.equal(fixture.calls[1].body.resourceId, "res_boxsimu_1"); assert.equal(fixture.calls[1].body.port, "DO1"); assert.equal(fixture.calls[1].body.value, true); assert.equal(fixture.calls[2].body.signals[0].fromResourceId, "res_boxsimu_1"); assert.equal(fixture.calls[4].body.operation, "hardware.port.read"); assert.equal(fixture.calls[4].body.resourceId, "res_boxsimu_2"); assert.equal(fixture.calls[4].body.port, "DI1"); const audit = runtimeStore.queryAuditEvents({ projectId: M3_IO_CHAIN.projectId }); assert.ok(audit.events.some((event) => event.action === "m3.io.do.write")); const evidence = runtimeStore.queryEvidenceRecords({ projectId: M3_IO_CHAIN.projectId }); assert.equal(evidence.count, 1); assert.equal(evidence.records[0].operationId, result.operationId); }); test("M3 DO write can prove control reachability while durable trusted persistence stays blocked", async () => { const fixture = createM3ControlFixture(); const result = await handleM3IoControl( { action: "do.write", gatewayId: "gwsimu_1", resourceId: "res_boxsimu_1", boxId: "boxsimu_1", port: "DO1", value: true, traceId: "trc_m3_control_durable_blocked", requestId: "req_m3_control_durable_blocked", actorId: "usr_m3_operator" }, { runtimeStore: createBlockedDurableRuntimeStore(), now: () => fixedNow, env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, requestJson: fixture.requestJson } ); assert.equal(result.status, "succeeded"); assert.equal(result.accepted, true); assert.equal(result.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.ready); assert.equal(result.trustBlocker.code, "runtime_durable_adapter_query_blocked"); assert.equal(result.result.value, true); assert.equal(result.result.targetReadback.value, true); assert.equal(result.controlPath.status, "succeeded"); assert.equal(result.controlPath.patchPanel, true); assert.equal(result.evidenceState.status, "blocked"); assert.equal(result.evidenceState.sourceKind, "BLOCKED"); assert.equal(result.evidenceState.durable, false); assert.equal(result.evidenceState.blocker, "runtime_durable_adapter_query_blocked"); assert.equal(result.durableStatus.blocker, "runtime_durable_adapter_query_blocked"); assert.equal(result.blockerClassification.code, "runtime_durable_adapter_query_blocked"); assert.equal(result.blockerClassification.category, "runtime_durable"); assert.equal(result.persistence.runtime.status, "blocked"); assert.equal(result.persistence.runtime.liveRuntimeEvidence, false); assert.equal(result.persistence.writes.every((write) => write.written !== true), true); assert.match(result.evidenceState.reason, /操作链路可达性与可信持久化分离/u); }); test("M3 DI read returns structured value through gateway-simu without patch-panel mutation", async () => { const fixture = createM3ControlFixture(); writeBoxPort({ state: fixture.box2, port: "DI1", value: true, source: "patch-panel" }); const result = await handleM3IoControl( { action: "di.read", gatewayId: "gwsimu_2", resourceId: "res_boxsimu_2", boxId: "boxsimu_2", port: "DI1", traceId: "trc_m3_control_read_di", requestId: "req_m3_control_read_di", actorId: "usr_m3_operator" }, { runtimeStore: createCloudRuntimeStore({ now: () => fixedNow }), now: () => fixedNow, env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, requestJson: fixture.requestJson } ); assert.equal(result.status, "succeeded"); assert.equal(result.accepted, true); assert.equal(result.route, "/v1/m3/io"); assert.equal(result.method, "POST"); assert.equal(result.action, "di.read"); assert.equal(result.command.port, "DI1"); assert.equal(result.gatewayId, "gwsimu_2"); assert.equal(result.gatewaySessionId, "gws_gwsimu_2"); assert.equal(result.resourceId, "res_boxsimu_2"); assert.equal(result.boxId, "boxsimu_2"); assert.equal(result.port, "DI1"); assert.equal(result.value, null); assert.equal(result.operationState.status, "succeeded"); assert.equal(result.auditState.status, "written_non_durable"); assert.equal(result.result.value, true); assert.equal(result.readback.status, "succeeded"); assert.equal(result.readback.value, true); assert.equal(result.readback.resourceId, "res_boxsimu_2"); assert.equal(result.readback.port, "DI1"); assert.equal(result.controlPath.patchPanel, false); assert.deepEqual(fixture.calls.map((call) => call.path), ["/status", "/invoke"]); }); test("M3 IO control blocks with Chinese reason when gateway session is unavailable", async () => { const result = await handleM3IoControl( { action: "do.write", value: true, traceId: "trc_m3_gateway_missing", requestId: "req_m3_gateway_missing", actorId: "usr_m3_operator" }, { runtimeStore: createCloudRuntimeStore({ now: () => fixedNow }), now: () => fixedNow, env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, requestJson: async () => ({ ok: false, status: 503, body: { error: { message: "unreachable" } } }) } ); assert.equal(result.status, "blocked"); assert.equal(result.accepted, false); assert.equal(result.route, "/v1/m3/io"); assert.equal(result.method, "POST"); assert.equal(result.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(result.controlReady, false); assert.equal(result.blocker.code, M3_IO_BLOCKER_CODES.gatewayUnavailable); assert.equal(result.blockerClassification.category, "gateway_session"); assert.equal(result.audit.auditId, null); assert.equal(result.audit.status, "not_written"); assert.equal(result.evidence.evidenceId, null); assert.equal(result.evidence.status, "blocked"); assert.equal(result.readback, null); assert.equal(result.auditState.status, "not_written"); assert.equal(result.durableStatus.redacted, true); assert.match(result.blocker.zh, /gateway 未注册\/不可用/u); assert.equal(result.controlPath.cloudApi, true); assert.equal(result.controlPath.frontendBypass, false); }); test("M3 IO control blocks when patch-panel wiring does not deliver DO1 to DI1", async () => { const fixture = createM3ControlFixture({ patchPanelBody: { accepted: true, routes: [ { accepted: true, deliveries: [] } ] } }); const result = await handleM3IoControl( { action: "do.write", value: false, traceId: "trc_m3_wiring_missing", requestId: "req_m3_wiring_missing", actorId: "usr_m3_operator" }, { runtimeStore: createCloudRuntimeStore({ now: () => fixedNow }), now: () => fixedNow, env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, requestJson: fixture.requestJson } ); assert.equal(result.status, "blocked"); assert.equal(result.accepted, false); assert.equal(result.blocker.code, M3_IO_BLOCKER_CODES.wiringMissing); assert.equal(result.blockerClassification.category, "patch_panel_wiring"); assert.match(result.blocker.zh, /wiring 不存在|未路由/u); }); test("M3 IO control blocks when patch-panel reports missing target box", async () => { const fixture = createM3ControlFixture({ patchPanelBody: { accepted: false, diagnostics: [ { code: "target_endpoint_missing", message: "target endpoint missing for res_boxsimu_2" } ], routes: [] } }); const result = await handleM3IoControl( { action: "do.write", value: true, traceId: "trc_m3_target_box_missing", requestId: "req_m3_target_box_missing", actorId: "usr_m3_operator" }, { runtimeStore: createCloudRuntimeStore({ now: () => fixedNow }), now: () => fixedNow, env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, requestJson: fixture.requestJson } ); assert.equal(result.status, "blocked"); assert.equal(result.blocker.code, M3_IO_BLOCKER_CODES.boxUnavailable); assert.equal(result.blockerClassification.category, "box_resource"); assert.match(result.blocker.zh, /target endpoint missing/u); assert.equal(result.controlPath.cloudApi, true); assert.equal(result.controlPath.frontendBypass, false); }); test("M3 IO JSON-RPC methods route do.write and di.read through the cloud-api handler", async () => { const fixture = createM3ControlFixture(); const runtimeStore = createCloudRuntimeStore({ now: () => fixedNow }); const context = { runtimeStore, now: () => fixedNow, env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, m3IoRequestJson: fixture.requestJson }; assert.ok(SUPPORTED_RPC_METHODS.includes(M3_IO_RPC_METHODS.doWrite)); assert.ok(SUPPORTED_RPC_METHODS.includes(M3_IO_RPC_METHODS.diRead)); const write = await handleJsonRpcRequest( { jsonrpc: "2.0", id: "req_m3_rpc_write_true", method: M3_IO_RPC_METHODS.doWrite, params: { resourceId: "res_boxsimu_1", boxId: "boxsimu_1", port: "DO1", value: true }, meta: { traceId: "trc_m3_rpc_write_true", actorId: "usr_m3_rpc", serviceId: "hwlab-cloud-api", environment: "dev" } }, context ); validateResponse(write); assert.equal(Object.hasOwn(write, "error"), false, write.error?.message); assert.equal(write.result.status, "succeeded"); assert.equal(write.result.action, "do.write"); assert.equal(write.result.traceId, "trc_m3_rpc_write_true"); assert.equal(write.result.resourceId, "res_boxsimu_1"); assert.equal(write.result.gatewaySessionId, "gws_gwsimu_1"); assert.equal(write.result.port, "DO1"); assert.equal(write.result.value, true); assert.match(write.result.operationId, /^op_m3_do_write_/u); assert.match(write.result.auditId, /^aud_m3_do_write_/u); assert.match(write.result.evidenceId, /^evd_m3_do_write_/u); assert.equal(write.result.controlPath.frontendBypass, false); assert.equal(write.result.evidenceState.sourceKind, "BLOCKED"); const read = await handleJsonRpcRequest( { jsonrpc: "2.0", id: "req_m3_rpc_read_di", method: M3_IO_RPC_METHODS.diRead, params: { resourceId: "res_boxsimu_2", boxId: "boxsimu_2", port: "DI1" }, meta: { traceId: "trc_m3_rpc_read_di", actorId: "usr_m3_rpc", serviceId: "hwlab-cloud-api", environment: "dev" } }, context ); validateResponse(read); assert.equal(Object.hasOwn(read, "error"), false, read.error?.message); assert.equal(read.result.status, "succeeded"); assert.equal(read.result.action, "di.read"); assert.equal(read.result.traceId, "trc_m3_rpc_read_di"); assert.equal(read.result.gatewayId, "gwsimu_2"); assert.equal(read.result.gatewaySessionId, "gws_gwsimu_2"); assert.equal(read.result.resourceId, "res_boxsimu_2"); assert.equal(read.result.port, "DI1"); assert.equal(read.result.result.value, true); assert.equal(read.result.controlPath.patchPanel, false); assert.equal(read.result.evidenceState.sourceKind, "BLOCKED"); assert.equal(read.result.durableStatus.durable, false); }); test("M3 IO fixture success cannot be promoted to DEV-LIVE without durable evidence", async () => { const fixture = createM3ControlFixture(); const result = await handleM3IoControl( { action: "do.write", value: true, traceId: "trc_m3_fixture_not_dev_live", requestId: "req_m3_fixture_not_dev_live", actorId: "usr_m3_operator" }, { runtimeStore: createCloudRuntimeStore({ now: () => fixedNow }), now: () => fixedNow, env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, requestJson: fixture.requestJson } ); assert.equal(result.status, "succeeded"); assert.equal(result.result.targetReadback.value, true); assert.equal(result.evidenceState.status, "blocked"); assert.equal(result.evidenceState.sourceKind, "BLOCKED"); assert.equal(result.durableStatus.durable, false); assert.notEqual(result.evidenceState.sourceKind, "DEV-LIVE"); assert.notEqual(result.auditState.sourceKind, "DEV-LIVE"); }); function createBlockedDurableRuntimeStore({ blocker = "runtime_durable_adapter_query_blocked" } = {}) { return { async readiness() { return { adapter: "postgres", durable: false, durableRequested: true, durableCapable: false, ready: false, status: "blocked", blocker, reason: "Postgres runtime adapter could not complete a live runtime schema query", liveRuntimeEvidence: false, fixtureEvidence: false }; } }; } function createGreenDurableRuntimeStore({ blocker = null } = {}) { return { async readiness() { return { adapter: "postgres", durable: true, durableRequested: true, durableCapable: true, ready: true, status: "ready", blocker, reason: "Postgres durable runtime adapter completed live schema, migration, and read readiness queries", liveRuntimeEvidence: true, fixtureEvidence: false }; }, async queryAuditEvents() { return { events: [ { auditId: "aud_m3_di_read_d5afd84b-3a9d-475f-894f-dff8ea5f49ff_succeeded", traceId: "trc_hotfix_307_verify_1779527747043_read_false", actorType: "user", actorId: "usr_m3_operator", action: "m3.io.di.read", targetType: "hardware_operation", targetId: "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f", projectId: M3_IO_CHAIN.projectId, gatewaySessionId: M3_IO_CHAIN.targetGatewaySessionId, operationId: "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f", serviceId: "hwlab-cloud-api", outcome: "succeeded", metadata: { route: "res_boxsimu_1:DO1->hwlab-patch-panel->res_boxsimu_2:DI1" }, environment: "dev", occurredAt: fixedNow } ], count: 1 }; }, async queryEvidenceRecords() { return { records: [ { evidenceId: "evd_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f_succeeded", projectId: M3_IO_CHAIN.projectId, operationId: "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f", kind: "trace", uri: "memory://hwlab-cloud-api/op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f/m3-io-control.json", sha256: "0".repeat(64), serviceId: "hwlab-cloud-api", environment: "dev", metadata: { traceId: "trc_hotfix_307_verify_1779527747043_read_false", route: "res_boxsimu_1:DO1->hwlab-patch-panel->res_boxsimu_2:DI1" }, createdAt: fixedNow } ], count: 1 }; } }; } function createM3ControlFixture({ patchPanelBody, patchPanelStatusBody, patchPanelWiringBody, gateway1Id = "gwsimu_1", gateway2Id = "gwsimu_2" } = {}) { const calls = []; const box1 = createBoxState({ boxId: "boxsimu_1", gatewaySessionId: "gws_gwsimu_1", ports: ["DO1", "DI1"], now: fixedNow }); box1.resource.resourceId = "res_boxsimu_1"; box1.capabilities = box1.capabilities.map((capability) => ({ ...capability, resourceId: "res_boxsimu_1", projectId: M3_IO_CHAIN.projectId })); box1.projectId = M3_IO_CHAIN.projectId; box1.resource.projectId = M3_IO_CHAIN.projectId; const box2 = createBoxState({ boxId: "boxsimu_2", gatewaySessionId: "gws_gwsimu_2", ports: ["DO1", "DI1"], now: fixedNow }); box2.resource.resourceId = "res_boxsimu_2"; box2.capabilities = box2.capabilities.map((capability) => ({ ...capability, resourceId: "res_boxsimu_2", projectId: M3_IO_CHAIN.projectId })); box2.projectId = M3_IO_CHAIN.projectId; box2.resource.projectId = M3_IO_CHAIN.projectId; const gateway1 = createGatewayStatus(gateway1Id, "http://gateway-1", [box1]); const gateway2 = createGatewayStatus(gateway2Id, "http://gateway-2", [box2]); async function requestJson(url, { method = "GET", body } = {}) { const parsed = new URL(url); calls.push({ url, method, path: parsed.pathname, body }); if (url.startsWith("http://gateway-1") && parsed.pathname === "/status") { return { ok: true, status: 200, body: gateway1 }; } if (url.startsWith("http://gateway-2") && parsed.pathname === "/status") { return { ok: true, status: 200, body: gateway2 }; } if (url.startsWith("http://gateway-1") && parsed.pathname === "/invoke") { if (body.operation === "hardware.port.read") { const state = box1.ports[body.port]; return { ok: true, status: 200, body: { accepted: true, operationId: body.operationId, traceId: body.traceId, boxId: box1.boxId, resourceId: box1.resource.resourceId, capabilityId: body.capabilityId, port: body.port, value: state.value, state, auditId: "aud_box1_read", evidenceId: "evd_box1_read" } }; } return { ok: true, status: 200, body: writeBoxPort({ state: box1, port: body.port, capabilityId: body.capabilityId, value: body.value, source: "gateway-simu" }) }; } if (url.startsWith("http://gateway-2") && parsed.pathname === "/invoke") { return { ok: true, status: 200, body: { accepted: true, operationId: body.operationId, traceId: body.traceId, boxId: box2.boxId, resourceId: box2.resource.resourceId, capabilityId: body.capabilityId, port: body.port, value: box2.ports[body.port].value, state: box2.ports[body.port], auditId: "aud_box2_read", evidenceId: "evd_box2_read" } }; } if (url.startsWith("http://patch-panel") && parsed.pathname === "/status") { return { ok: true, status: 200, body: patchPanelStatusBody ?? { serviceId: "hwlab-patch-panel", state: "active", activeConnections: [ { fromResourceId: "res_boxsimu_1", fromPort: "DO1", toResourceId: "res_boxsimu_2", toPort: "DI1" } ] } }; } if (url.startsWith("http://patch-panel") && parsed.pathname === "/wiring") { return { ok: true, status: 200, body: patchPanelWiringBody ?? { wiringConfigId: "wir_m3_do1_di1", status: "active", connections: [ { from: { resourceId: "res_boxsimu_1", port: "DO1" }, to: { resourceId: "res_boxsimu_2", port: "DI1" }, mode: "exclusive" } ] } }; } if (url.startsWith("http://patch-panel") && parsed.pathname === "/sync/tick") { if (patchPanelBody) { return { ok: true, status: 200, body: patchPanelBody }; } box2.ports.DI1 = { ...box2.ports.DI1, value: body.signals[0].value, source: "patch-panel", sourceResourceId: "res_boxsimu_1", sourcePort: "DO1", propagatedBy: "hwlab-patch-panel", updatedAt: fixedNow }; return { ok: true, status: 200, body: { accepted: true, propagatedBy: "hwlab-patch-panel", routeCount: 1, deliveryCount: 1, routes: [ { accepted: true, deliveryCount: 1, deliveries: [ { resourceId: "res_boxsimu_2", port: "DI1", value: body.signals[0].value, sourceResourceId: "res_boxsimu_1", sourcePort: "DO1", deliveryStatus: "applied" } ] } ] } }; } throw new Error(`unexpected request ${method} ${url}`); } return { calls, box1, box2, requestJson }; } function createGatewayStatus(gatewayId, endpoint, boxes) { const state = createGatewayState({ gatewayId, endpoint, boxes: boxes.map((box) => box.resource.resourceId), now: fixedNow }); state.projectId = M3_IO_CHAIN.projectId; state.session.projectId = M3_IO_CHAIN.projectId; state.registry = { gatewayId, gatewaySessionId: state.session.gatewaySessionId, endpoint, boxes: boxes.map((box) => ({ boxId: box.boxId, resourceId: box.resource.resourceId, url: `http://${box.boxId}`, state: "registered", capabilities: box.capabilities })) }; return state; }