#!/usr/bin/env node import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { createPatchPanel } from "../internal/patchpanel/model.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const fixturePath = path.resolve( repoRoot, process.argv[2] ?? "fixtures/mvp/m3-hardware-loop/topology.json" ); const REQUIRED_AUDIT_FIELDS = Object.freeze([ "auditId", "traceId", "actorType", "actorId", "action", "targetType", "targetId", "serviceId", "environment", "occurredAt" ]); const RECOMMENDED_AUDIT_FIELDS = Object.freeze([ "projectId", "gatewaySessionId", "operationId", "outcome", "metadata" ]); function clone(value) { return JSON.parse(JSON.stringify(value)); } function assertPort(box, port) { assert.ok(box.ports, `${box.boxId} missing ports`); assert.ok(Object.hasOwn(box.ports, port), `${box.boxId} missing port ${port}`); assert.equal(box.ports[port].port, port, `${box.boxId}.${port} port name`); } function assertTimestamp(value, name) { assert.equal(typeof value, "string", `${name} must be a string`); assert.ok(!Number.isNaN(Date.parse(value)), `${name} must be an RFC 3339 timestamp`); } function assertOnePatchPanel(topology) { const patchPanel = topology.services?.patchPanel; assert.ok(patchPanel, "topology requires one patch panel"); assert.equal(patchPanel.serviceId, "hwlab-patch-panel"); assert.equal(patchPanel.state, "active"); assert.equal(patchPanel.environment, "dev"); assert.equal(patchPanel.projectId, topology.projectId); assert.equal(patchPanel.wiringConfigId, topology.wiringConfig.wiringConfigId); assert.equal(patchPanel.metadata?.propagation, "patch-panel-only"); assert.equal(patchPanel.activeConnections.length, topology.wiringConfig.connections.length); return patchPanel; } function indexBoxes(boxes) { const byResourceId = new Map(); const byAlias = new Map(); for (const box of boxes) { assert.equal(box.serviceId, "hwlab-box-simu"); assert.equal(box.live, true, `${box.boxId} must be live`); assert.equal(box.projectId, box.resource.projectId, `${box.boxId} project must match resource`); assert.equal(box.environment ?? box.resource.environment, "dev", `${box.boxId} environment`); assert.equal(box.constraints.crossDevicePropagation, "patch-panel-only"); assert.equal(box.constraints.localLoopbackEnabled, false); assert.equal(box.resource.metadata.propagation, "patch-panel-only"); assertPort(box, "DO1"); assertPort(box, "DI1"); byResourceId.set(box.resource.resourceId, box); for (const alias of box.aliases ?? []) { byAlias.set(alias, box); } } return { byResourceId, byAlias }; } function assertGateways(topology, resourceIds) { const gateways = topology.services?.gateways; assert.ok(Array.isArray(gateways), "topology requires gateways"); assert.equal(gateways.length, 2, "topology requires exactly two gateway simulators"); const seenSessions = new Set(); for (const gateway of gateways) { assert.equal(gateway.serviceId, "hwlab-gateway-simu"); assert.equal(gateway.live, true, `${gateway.gatewayId} must be live`); assert.equal(gateway.projectId, topology.projectId); assert.equal(gateway.session.serviceId, "hwlab-gateway-simu"); assert.equal(gateway.session.status, "connected"); assert.equal(gateway.session.environment, "dev"); assert.equal(gateway.session.projectId, topology.projectId); seenSessions.add(gateway.session.gatewaySessionId); assert.ok(Array.isArray(gateway.boxes) && gateway.boxes.length >= 1, `${gateway.gatewayId} boxes`); for (const resourceId of gateway.boxes) { assert.ok(resourceIds.has(resourceId), `${gateway.gatewayId} references known box resource`); } } assert.ok( seenSessions.has(topology.wiringConfig.gatewaySessionId), "wiring gateway session must belong to a gateway simulator" ); } function assertWiringMatchesPatchPanel(wiringConfig, patchPanel) { assert.equal(wiringConfig.status, "active"); assert.equal(wiringConfig.constraints.propagation, "patch-panel-only"); assert.equal(wiringConfig.constraints.localLoopbackSubstituteAllowed, false); assert.equal(wiringConfig.connections.length, 1, "M3 smoke expects one DO1->DI1 connection"); const connection = wiringConfig.connections[0]; const activeConnection = patchPanel.activeConnections[0]; assert.deepEqual(activeConnection, { fromResourceId: connection.from.resourceId, fromPort: connection.from.port, toResourceId: connection.to.resourceId, toPort: connection.to.port }); return connection; } function assertAuditEvent(topology) { const auditEvent = topology.expectedAuditEvent; assert.ok(auditEvent && typeof auditEvent === "object", "expectedAuditEvent is required"); for (const field of REQUIRED_AUDIT_FIELDS) { assert.ok(Object.hasOwn(auditEvent, field), `expected audit.${field}`); } for (const field of RECOMMENDED_AUDIT_FIELDS) { assert.ok(Object.hasOwn(auditEvent, field), `expected audit.${field}`); } assert.equal(auditEvent.traceId, topology.directCall.traceId); assert.equal(auditEvent.projectId, topology.projectId); assert.equal(auditEvent.gatewaySessionId, topology.wiringConfig.gatewaySessionId); assert.equal(auditEvent.operationId, topology.directCall.operationId); assert.equal(auditEvent.targetType, "wiring_config"); assert.equal(auditEvent.targetId, topology.wiringConfig.wiringConfigId); assert.equal(auditEvent.serviceId, "hwlab-patch-panel"); assert.equal(auditEvent.environment, "dev"); assert.equal(auditEvent.outcome, "succeeded"); assert.equal(auditEvent.metadata.databaseWriteRequired, false); assert.equal(auditEvent.metadata.directCall, true); assertTimestamp(auditEvent.occurredAt, "audit.occurredAt"); } function applyOutput(box, port, value, updatedAt) { assertPort(box, port); assert.equal(box.ports[port].direction, "output", `${box.boxId}.${port} must be an output`); box.ports[port] = { ...box.ports[port], value, source: "local", updatedAt }; } function applyDeliveries(boxesByResourceId, deliveries) { for (const delivery of deliveries) { const box = boxesByResourceId.get(delivery.resourceId); assert.ok(box, `delivery target ${delivery.resourceId} exists`); assertPort(box, delivery.port); assert.equal(box.ports[delivery.port].direction, "input", `${box.boxId}.${delivery.port} must be an input`); box.ports[delivery.port] = { ...box.ports[delivery.port], value: delivery.value, source: delivery.sourceResourceId, sourcePort: delivery.sourcePort, propagatedBy: "hwlab-patch-panel", updatedAt: delivery.observedAt }; } } const raw = await readFile(fixturePath, "utf8"); const topology = JSON.parse(raw); assert.equal(topology.environment, "dev"); assert.equal(topology.contract.mode, "local-direct-call"); assert.equal(topology.contract.databaseWriteRequired, false); assert.equal(topology.contract.realHardwareRequired, false); assert.equal(topology.contract.cloudApiRequired, false); assert.equal(topology.constraints.patchPanelIsOnlyCrossDeviceChannel, true); assert.equal(topology.constraints.boxSimuLocalLoopbackForCrossDeviceSync, false); assert.equal(topology.constraints.realDevHardwareMutationAllowed, false); assert.equal(topology.constraints.cloudApiImplementationMutationAllowed, false); const boxes = topology.services?.boxes; assert.ok(Array.isArray(boxes), "topology requires boxes"); assert.equal(boxes.length, 2, "topology requires exactly two box simulators"); const { byResourceId, byAlias } = indexBoxes(boxes); assertGateways(topology, new Set(byResourceId.keys())); const patchPanelStatus = assertOnePatchPanel(topology); const connection = assertWiringMatchesPatchPanel(topology.wiringConfig, patchPanelStatus); assertAuditEvent(topology); const directCall = topology.directCall; const sourceBox = byAlias.get(directCall.source.boxAlias); const targetBox = byAlias.get(directCall.target.boxAlias); assert.ok(sourceBox, "box-simu-1 alias must exist"); assert.ok(targetBox, "box-simu-2 alias must exist"); assert.equal(connection.from.resourceId, sourceBox.resource.resourceId); assert.equal(connection.from.port, "DO1"); assert.equal(connection.to.resourceId, targetBox.resource.resourceId); assert.equal(connection.to.port, "DI1"); assert.equal(directCall.source.resourceId, sourceBox.resource.resourceId); assert.equal(directCall.source.port, "DO1"); assert.equal(directCall.target.resourceId, targetBox.resource.resourceId); assert.equal(directCall.target.port, "DI1"); const localBoxes = new Map([...byResourceId].map(([resourceId, box]) => [resourceId, clone(box)])); applyOutput( localBoxes.get(directCall.source.resourceId), directCall.source.port, directCall.source.value, directCall.expected.observedAt ); const patchPanel = createPatchPanel({ wiringConfig: topology.wiringConfig, now: () => directCall.expected.observedAt }); const routeResult = patchPanel.routeSignal({ fromResourceId: directCall.source.resourceId, fromPort: directCall.source.port, value: directCall.source.value }); assert.equal(routeResult.accepted, directCall.expected.accepted); assert.equal(routeResult.propagatedBy, directCall.expected.propagatedBy); assert.equal(routeResult.deliveryCount, directCall.expected.deliveryCount); assert.deepEqual(routeResult.deliveries, [ { resourceId: directCall.target.resourceId, port: directCall.target.port, value: directCall.target.expectedValue, sourceResourceId: directCall.source.resourceId, sourcePort: directCall.source.port, observedAt: directCall.expected.observedAt } ]); applyDeliveries(localBoxes, routeResult.deliveries); const targetState = localBoxes.get(directCall.target.resourceId).ports[directCall.target.port]; assert.equal(targetState.value, directCall.target.expectedValue); assert.equal(targetState.propagatedBy, "hwlab-patch-panel"); assert.equal(targetState.source, directCall.source.resourceId); assert.equal(targetState.sourcePort, directCall.source.port); const bypassProbe = patchPanel.routeSignal({ fromResourceId: directCall.source.resourceId, fromPort: "DI1", value: true }); assert.equal(bypassProbe.deliveryCount, 0, "unwired direct box signal must not cross devices"); console.log("M3 hardware loop smoke passed"); console.log("topology: 2 box simulators, 2 gateway simulators, 1 patch panel"); console.log("wiring: box-simu-1 DO1 -> box-simu-2 DI1 via hwlab-patch-panel"); console.log(`audit expectation: ${[...REQUIRED_AUDIT_FIELDS, ...RECOMMENDED_AUDIT_FIELDS].join(", ")}`);