From d484a15c895a96407eaec7b85553476842e68794 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Sun, 22 Feb 2026 14:03:01 +0000 Subject: [PATCH] fix(#2): harden network data parsing and link resolution --- src/lib/data/loadNetworkData.ts | 10 ++++++++- src/lib/graph/transformNetworkData.ts | 29 +++++++++++++++++++++------ src/lib/types.ts | 10 ++++----- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/lib/data/loadNetworkData.ts b/src/lib/data/loadNetworkData.ts index a5a5b82..b5be388 100644 --- a/src/lib/data/loadNetworkData.ts +++ b/src/lib/data/loadNetworkData.ts @@ -20,7 +20,15 @@ export default async function loadNetworkData(jsonPath: string): Promise; } +function toOwnerKey(kind: EndpointOwner['kind'], name: string): string { + return `${kind}:${name}`; +} + function toMachineNodeId(name: string): string { return `machine:${encodeURIComponent(name)}`; } @@ -58,7 +63,7 @@ export function transformNetworkDataToGraph(data: NetworkData): GraphTransformRe vmCount: number; } > = {}; - const ownersByName = new Map(); + const ownersByKey = new Map(); const seenWarnings = new Set(); const warn = (message: string) => { @@ -69,13 +74,14 @@ export function transformNetworkDataToGraph(data: NetworkData): GraphTransformRe }; const addOwner = (owner: EndpointOwner) => { - if (ownersByName.has(owner.name)) { + const ownerKey = toOwnerKey(owner.kind, owner.name); + if (ownersByKey.has(ownerKey)) { warn( - `Duplicate node name "${owner.name}" found. First occurrence will be used for link resolution.` + `Duplicate ${owner.kind} name "${owner.name}" found. First occurrence will be used for link resolution.` ); return; } - ownersByName.set(owner.name, owner); + ownersByKey.set(ownerKey, owner); }; let hostingEdgeCounter = 0; @@ -163,6 +169,7 @@ export function transformNetworkDataToGraph(data: NetworkData): GraphTransformRe } addOwner({ + kind: 'machine', nodeId: machineNodeId, name: machine.machineName, ports: machinePorts @@ -198,6 +205,7 @@ export function transformNetworkDataToGraph(data: NetworkData): GraphTransformRe } addOwner({ + kind: 'device', nodeId: deviceNodeId, name: device.name, ports: devicePortMap @@ -207,12 +215,21 @@ export function transformNetworkDataToGraph(data: NetworkData): GraphTransformRe const seenPhysicalConnections = new Set(); let physicalEdgeCounter = 0; - for (const owner of ownersByName.values()) { + for (const owner of ownersByKey.values()) { for (const port of owner.ports.values()) { const connectedTo = parseConnectedTo(port.connectedTo); if (!connectedTo) continue; - const targetOwner = ownersByName.get(connectedTo.device); + const machineTargetOwner = ownersByKey.get(toOwnerKey('machine', connectedTo.device)); + const deviceTargetOwner = ownersByKey.get(toOwnerKey('device', connectedTo.device)); + if (machineTargetOwner && deviceTargetOwner) { + warn( + `Link from "${owner.name}:${port.portName}" references ambiguous device "${connectedTo.device}" (matches both machine and device names).` + ); + continue; + } + + const targetOwner = machineTargetOwner ?? deviceTargetOwner; if (!targetOwner) { warn( `Link from "${owner.name}:${port.portName}" references unknown device "${connectedTo.device}".` diff --git a/src/lib/types.ts b/src/lib/types.ts index d467406..2fb4293 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,14 +1,14 @@ +export interface ConnectedPortRef { + device: string; + port: string; +} + export interface Port { portName: string; speedGbps?: number; // default to 1 if not provided connectedTo?: ConnectedPortRef; } -export interface ConnectedPortRef { - device: string; - port: string; -} - export interface Hardware { cpu: string; ram: string;