# Networker DSL Schema

An XML-like markup language for describing datacenter network topologies.

A DSL file contains an optional `<Catalog>` block followed by a required `<Topology>` block.

---

## Root Structure

```
<Catalog>  (optional, at most one)
  ...catalog entries...
</Catalog>

<Topology view="rack-elevation|switch-port-mapping|power-budget|fabric-topology|bom">  (required, exactly one)
  ...racks, devices, cables, power cords...
</Topology>
```

---

## Attribute Syntax

- **String attributes**: `key="value"`
- **Object attributes**: `key={{ k1: v1, k2: v2 }}` (double-brace syntax, numeric values only)

---

## Catalog Block

Defines reusable templates for devices, NICs, and cables. Devices in the Topology can
reference a catalog entry via `template="id"` to inherit its NIC/port structure and dimensions.

### Device Templates (Server, Switch, PDU)

```
<Server id="ID" model="MODEL" manufacturer="MFR" rackUnits="N"
         cardWidth="PX" cardHeight="PX">
  <Nic label="LABEL" portCount="N" speed="SPEED" connector="CONN"
       layout={{ x: N, y: N, cols: N, groupSize: N, rows: N }} />
  ...
</Server>
```

| Attribute      | Required | Default | Description                           |
|----------------|----------|---------|---------------------------------------|
| `id`           | yes      |         | Template identifier, unique across **every** `<Catalog>` entry |
| `model`        | no       | `""`    | Model name                            |
| `manufacturer` | no       | `""`    | Manufacturer name                     |
| `rackUnits`    | no       | `1`     | Height in rack units                  |
| `cardWidth`    | no       |         | Set when the groups carry explicit `layout`s. Every rack device is the standard width — `424` card units, drawn as 440 mm — so any other value is rescaled to it on load (see [Layout Object](#layout-object)) |
| `cardHeight`   | no       |         | Card height in pixels (Port Mapping)  |

**Tags**: `<Server>`, `<Switch>`, `<PDU>` all accept the same attributes. A
`<PDU>` additionally carries an [electrical spec](#pdu-electrical-spec).

A server is a server whatever it is for: compute and storage are one kind. Say what a
server is for with [labels](#device-server-switch-pdu). Files from before that used
`<Compute>` and `<Storage>`: both are still read, as `<Server>`, and are written back as
`<Server>`. A `<Storage>` device without `rackUnits` was 2U, and is still read as 2U.

Inside `<Switch>`, NIC children may use `<PortGroup>` instead of `<Nic>` (both are accepted).
Inside `<PDU>`, outlets are grouped into `<Phase>` children instead — see [PDU](#pdu).
Every template except a `<PDU>` also accepts
[`<Inlet>`](#inlet-template-inside-device-template) children.

`id` lives in a single namespace shared by every catalog category: a device's `template="..."`
is resolved by id alone. Reusing one id for two entries is an error — without that check the second entry is
silently shadowed and any NIC only it declares appears to be missing, which shows up much
later as `Cable "from" path ... not found`.

### NIC/PortGroup Template (inside device template)

| Attribute    | Required | Default | Description                                   |
|--------------|----------|---------|-----------------------------------------------|
| `label`      | no       | `""`    | NIC label                                     |
| `portCount`  | no       | `1`     | Number of ports to generate                   |
| `portPrefix` | no       | `p`     | What the ports are called: this, followed by a number (see [Port Names](#port-names)) |
| `portStart`  | no       | `0`     | The number the first port gets (see [Port Names](#port-names)) |
| `speed`      | no       | `10G`   | Port speed (see Speed enum)                   |
| `connector`  | no       | `RJ45`  | Connector form factor (see Connector enum)    |
| `layout`     | no       |         | Port grid position: `{{ x, y, cols, groupSize?, rows? }}` |
| `breakout`   | no       |         | Breakout channels inherited by ports: `2` or `4` (**`<Switch>` only**) |
| `side`       | no       | `back`  | Chassis face the NIC is installed on: `front` or `back` |
| `facing`     | no       |         | Which way each row faces: `alternate`, `up` or `down` (see [Port Facing](#how-rows-face)) |
| `adjacentRows` | no     | `1`     | Rows per block that face the same way, with `facing="alternate"` |
| `align`      | no       | `left`  | Where the ports sit across a box wider than they are: `left`, `center` or `right` (see [Port Alignment](#where-ports-sit-in-the-box)) |
| `template`   | no       |         | Id of a [Standalone NIC Catalog](#standalone-nic-catalog) entry this NIC is inserted from |

#### Port Names

A device made from a template gets one port per `portCount`, named `portPrefix` followed by a
number that counts up from `portStart`. Left out, they are `p0`, `p1`, … Give a switch the names
its OS uses, so cables can be written the way the hardware is labelled:

```
<Switch id="sn5600" model="SN5600" manufacturer="NVIDIA" rackUnits="2">
  <PortGroup label="ports" portCount="64" speed="800G" connector="OSFP"
             portPrefix="swp" portStart="1" />
</Switch>
...
<Cable from="leaf-1/ports/swp1" to="spine-1/ports/swp64" />
```

A port name may contain `/`, the way many switch OSes write their ports. An FS switch
names its ports `xe-1/1/1`, `xe-1/1/2`, …, and an SN2100 running Onyx names them `Eth1/1`,
`Eth1/2`, …:

```
<Switch id="fs-n8550" model="N8550-32C" manufacturer="FS">
  <PortGroup label="ports" portCount="32" speed="100G" connector="QSFP28"
             portPrefix="xe-1/1/" portStart="1" />
</Switch>
...
<Cable from="compute-1/cx5/p1" to="FS-100G/ports/xe-1/1/15" />
```

A cable's `from`/`to` path is read from the left: the first part is the device, the second
the NIC or port group, and everything after that is the port. A breakout channel still goes
on the end (`FS-100G/ports/xe-1/1/15:2`).

`portPrefix` cannot be empty or contain spaces or `:` (a `:` would read as a breakout channel).
`portStart` is a whole number, `0` or more. Anything else is an import error. Changing either on a template
that devices already use renames their ports in place: each port keeps its cables.

A `<Nic>` with `template="ID"` is that NIC template installed in the device: its `portCount`,
`speed`, `connector`, `breakout` and the port layout and box size (`cols`, `rows`, `groupSize`,
`w`, `h`, `facing`, `adjacentRows`, `align`) come from the `<Nic id="ID">` entry, and win over
any written here. Only `label`, the position (`layout` `x`/`y`), `side` and the port names
(`portPrefix`, `portStart`) are the NIC's own.
The spec is still written out so the file reads on its own. A `template` that names no
`<Nic>` entry in the `<Catalog>` is an error.

```
<Nic id="cx7-2p" model="ConnectX-7 2P" manufacturer="NVIDIA" portCount="2" speed="200G"
     connector="QSFP56" layout={{ x: 0, y: 0, cols: 2, w: 380, h: 57 }} />
<Server id="srv" model="R760" manufacturer="Dell" rackUnits="2">
  <Nic label="nic0" template="cx7-2p" portCount="2" speed="200G" connector="QSFP56"
       layout={{ x: 20, y: 10, cols: 2, w: 380, h: 57 }} />
</Server>
```

A `<Nic>` inside a `<PDU>` is how the embedded ethernet monitor port is declared — the
outlets themselves live in `<Phase>` children instead.

`breakout` is only valid inside a `<Switch>` template — see [Breakout](#breakout). A
`<Server>` template that declares it is an error.

### Inlet Template (inside device template)

Where the chassis takes power. Valid inside `<Server>` and `<Switch>` — a
`<PDU>` is fed by its own input cord, which is part of its [electrical spec](#pdu-electrical-spec).

```
<Inlet label="LABEL" model="MODEL" count="N" connector="CONN" captive="true" cordLength="TEXT"
       watts="N" side="back" layout={{ x: N, y: N, cols: N }} />
```

An `<Inlet>` stands for a power supply: `model` is its part number, and each device records
the serial number of the one installed in it (see [Inlet (inside device)](#inlet-inside-device)).

| Attribute    | Required | Default | Description                                                        |
|--------------|----------|---------|--------------------------------------------------------------------|
| `label`      | yes      |         | Inlet group label — e.g. `PSU1`                                     |
| `model`      | no       |         | Part number of the power supply, free text — e.g. `PWS-2K05A-1R`    |
| `count`      | no       | `1`     | How many inlets this group generates (ports `p0`…`pN-1`)            |
| `connector`  | no       | `C14`   | Must be a power connector (see Connector enum)                      |
| `captive`    | no       | `false` | `true` when the cord is wired into the chassis and cannot be removed |
| `cordLength` | no       |         | Length of the captive cord, free text — only valid with `captive="true"` |
| `watts`      | no       |         | Nameplate draw for the group, in watts                              |
| `side`       | no       | `back`  | Chassis face the inlet is on: `front` or `back`                     |
| `layout`     | no       |         | Grid position on the card: `{{ x, y, cols, groupSize?, rows? }}`    |
| `facing`     | no       |         | Which way each row of inlets faces: `alternate`, `up` or `down` (see [Port Facing](#how-rows-face)) |
| `adjacentRows` | no     | `1`     | Rows per block that face the same way, with `facing="alternate"`    |
| `align`      | no       | `left`  | Where the inlets sit across a box wider than they are: `left`, `center` or `right` (see [Port Alignment](#where-ports-sit-in-the-box)) |

`captive="false"` (the default) means an **inlet**: a receptacle a detachable cord plugs into,
and `connector` is that receptacle — a C14 appliance inlet fed from a C13 outlet.
`captive="true"` means a **power cable**: the cord is part of the device, and `connector` then
names the plug on its free end, the one that goes into the PDU outlet.

`speed` on an `<Inlet>` is an error — a power connector carries current, not traffic. A
catalog `<Inlet>` is a leaf and cannot have children; use `count` instead. `serialnumber` on a
catalog `<Inlet>` is an error: a serial number belongs to one installed power supply.

### PDU

A rack power distribution unit. It has the same chassis shape as the other devices — its
port groups are load banks and its ports are outlets — plus the electrical spec that says
how it is fed.

```
<PDU id="ID" model="MODEL" manufacturer="MFR" rackUnits="N"
     phase="single|three" voltage="TEXT" amperage="N" deratedAmperage="N"
     capacityKw="N" inputConnector="CONN" cordLength="TEXT"
     metering="none|local|network" outletSwitching="true|false"
     cardWidth="PX" cardHeight="PX">
  <Phase label="LABEL" outletCount="N" receptacle="RECEPTACLE" breaker="N"
         layout={{ x: N, y: N, cols: N, groupSize: N, rows: N }} />
  ...
  <Nic label="LABEL" portCount="N" speed="SPEED" connector="CONN" />
</PDU>
```

#### PDU electrical spec

These attributes are valid on a `<PDU>` in both `<Catalog>` and `<Topology>`. Every one is
optional: a PDU whose outlets are all that matter is a valid PDU, and an attribute left off
stays unset rather than being written back out with an invented default.

| Attribute         | Required | Description                                                            |
|-------------------|----------|------------------------------------------------------------------------|
| `phase`           | no       | Feed topology: `single` or `three` (see Power Phase enum)               |
| `voltage`         | no       | Nominal input voltage, free text so a range survives — e.g. `208/230V`  |
| `amperage`        | no       | Input current rating in amps — e.g. `30`                                |
| `deratedAmperage` | no       | Continuous-load (agency derated) rating in amps — e.g. `24`             |
| `capacityKw`      | no       | Total capacity in kW — e.g. `5.5`                                       |
| `inputConnector`  | no       | The attached input plug — must be a power connector, e.g. `L6-30P`      |
| `cordLength`      | no       | Length of the attached input cord, free text — e.g. `12 ft. (3.66 m)`   |
| `metering`        | no       | How load is reported (see PDU Metering enum)                           |
| `outletSwitching` | no       | `true` when individual outlets can be switched remotely                |

A `deratedAmperage` above `amperage` is an error: the continuous rating derates the input
rating, so it can never be higher.

`metering="network"` says the unit reports over the network. The monitor port itself is
still declared explicitly, as a `<Nic>` with an `RJ45` `<Port>` — the attribute describes
the capability, the `<Nic>` describes the jack you can cable to.

#### `<Phase>` — one load bank

A `<Phase>` is a group of outlets fed from one phase leg and, on a metered unit, protected
by its own branch breaker. It stands in the same place a `<Nic>` does on a network device.

| Attribute     | Required | Default | Description                                                       |
|---------------|----------|---------|-------------------------------------------------------------------|
| `label`       | yes*     |         | Bank label, unique within the PDU (*optional in `<Catalog>`, defaults to `""`) |
| `outletCount` | no       | `1`     | Number of outlets to generate (`<Catalog>` only)                  |
| `receptacle`  | no       | `C13`   | Outlet type — must be a power connector (see Connector enum)       |
| `breaker`     | no       |         | Branch breaker rating in amps protecting this bank — e.g. `20`     |
| `layout`      | no       |         | Outlet grid position: `{{ x, y, cols, groupSize?, rows? }}`        |
| `side`        | no       | `back`  | Chassis face the bank is on: `front` or `back`                     |
| `facing`      | no       |         | Which way each row of outlets faces: `alternate`, `up` or `down` (see [Port Facing](#how-rows-face)) |
| `adjacentRows` | no      | `1`     | Rows per block that face the same way, with `facing="alternate"`   |
| `align`       | no       | `left`  | Where the outlets sit across a box wider than they are: `left`, `center` or `right` (see [Port Alignment](#where-ports-sit-in-the-box)) |

A `<Phase>` carries no `speed`: an outlet delivers current, not traffic, so its receptacle
is the whole spec. `breakout` is not valid either — that is a switch-side capability.

`<Phase>` and `<Outlet>` are only valid inside a `<PDU>`; a `<Server>` or
`<Switch>` that declares one is an error.

### Standalone NIC Catalog

```
<Nic id="ID" model="MODEL" manufacturer="MFR"
     portCount="N" speed="SPEED" connector="CONN"
     layout={{ x: 0, y: 0, cols: N, rows: N, w: N, h: N }} />
```

`layout` is optional: how the card's ports are arranged (`cols`, `rows`, `groupSize`, and
`facing`, `adjacentRows`, `align` beside it) and the size of its faceplate (`w`, `h`, left out
to fit the ports). `x` and `y` are always `0`: where the card sits is up to each device template
it is inserted into with `template="ID"` (see [NIC/PortGroup Template](#nicportgroup-template-inside-device-template)).

### Cable Catalog

```
<Cable id="ID" model="MODEL" manufacturer="MFR"
       speed="SPEED" connector="CONN" material="MATERIAL" mode="MODE" />
```

| Attribute      | Required | Default  | Description                          |
|----------------|----------|----------|--------------------------------------|
| `id`           | yes      |          | Unique cable identifier              |
| `model`        | no       | `""`     | Model name                           |
| `manufacturer` | no       | `""`     | Manufacturer name                    |
| `speed`        | no       | `10G`    | Port speed (see Speed enum)          |
| `connector`    | no       | `RJ45`   | Connector form factor (see Connector enum) |
| `material`     | no       |          | `copper` or `fiber`                  |
| `mode`         | no       |          | `active` or `passive`                |

A cable may also carry a **link assembly** describing its parts. Fiber (AOC) uses
`<Transceiver>` / `<Fiber>` / `<Transceiver>`(s); copper (DAC/ACC) uses
`<Transceiver>` / `<Copper>` / `<Transceiver>`(s). Either may break out: the far end is then
1→N (e.g. 1→2, 1→4, 1→8).
See [Link Assembly](#link-assembly-inside-cable); the syntax is identical in the catalog and the
Topology, so a catalog entry acts as the template for a whole class of links.

```
<Cable id="dr4-400g-5m" model="400G DR4 5m link" manufacturer="NVIDIA"
       speed="400G" connector="OSFP" material="fiber" mode="active">
  <Transceiver model="MMS1V00-WM" manufacturer="NVIDIA" connector="QSFP-DD" speed="400G" modulation="4x100Gb/s PAM4" />
  <Fiber model="MFP7E30-N005" manufacturer="NVIDIA" length="5m" connector="MPO12 APC to MPO12 APC" />
  <Transceiver model="MMS4X00-NS400" manufacturer="NVIDIA" connector="OSFP" speed="400G" modulation="4x100Gb/s PAM4" />
</Cable>

<Cable id="dac-400g-1m" model="400G QSFP-DD DAC" manufacturer="NVIDIA"
       speed="400G" connector="QSFP-DD" material="copper" mode="passive">
  <Transceiver model="MCP1600-C01AE30N-A" manufacturer="NVIDIA" connector="QSFP-DD" speed="400G" />
  <Copper model="MCP1600-C01AE30N" manufacturer="NVIDIA" length="1m" modulation="4x100Gb/s PAM4" />
  <Transceiver model="MCP1600-C01AE30N-B" manufacturer="NVIDIA" connector="QSFP-DD" speed="400G" />
</Cable>
```

### Transceiver Catalog

```
<Transceiver id="ID" model="MODEL" manufacturer="MFR"
             formFactor="CONN" breakout="N" outputConnector="CONN" />
```

| Attribute         | Required | Default  | Description                                    |
|-------------------|----------|----------|------------------------------------------------|
| `id`              | yes      |          | Unique transceiver identifier                  |
| `model`           | no       | `""`     | Model name                                     |
| `manufacturer`    | no       | `""`     | Manufacturer name                              |
| `formFactor`      | no       | `QSFP28` | Connector cage it fits (see Connector enum)   |
| `breakout`        | no       |          | Breakout channels: `2` or `4`                 |
| `outputConnector` | no       |          | Breakout output connector (see Connector enum) |

---

## Topology Block

### Rack

```
<Rack label="LABEL" units="N" row="N" />
```

| Attribute | Required | Default | Description                               |
|-----------|----------|---------|-------------------------------------------|
| `label`   | yes      |         | Unique rack label (referenced by devices) |
| `units`   | no       | `36`    | Rack height in U                          |
| `row`     | no       | `1`     | Visual row grouping in Rack Elevation     |

### Device (Server, Switch, PDU)

**Full form** (inline NICs and ports):

```
<Server label="LABEL" position={{ x: N, y: N }} labels="TAG,TAG"
         rackUnits="N" rackId="RACK_LABEL" rackElevation="N"
         cardWidth="PX" cardHeight="PX">
  <Nic label="LABEL" layout={{ x: N, y: N, cols: N, groupSize: N, rows: N }}>
    <Port label="LABEL" speed="SPEED" connector="CONN" />
    ...
  </Nic>
  ...
  <Bond label="LABEL" mode="MODE" ports="NIC/PORT,NIC/PORT" />
  ...
</Server>
```

**Template form** (references a catalog entry):

```
<Server label="LABEL" template="CATALOG_ID" position={{ x: N, y: N }} labels="TAG,TAG"
         rackUnits="N" rackId="RACK_LABEL" rackElevation="N" />
```

A templated device may still carry `<Bond>` children — the template describes the chassis,
the bonds describe how this particular device's ports were teamed:

```
<Server label="LABEL" template="CATALOG_ID" position={{ x: N, y: N }}>
  <Bond label="bond0" mode="lacp" ports="eth0/p0,eth0/p1" />
</Server>
```

A templated `<Switch>` may also carry `<Breakout>` children, to split one port differently
from what its template declares — see [Breakout override](#breakout-override-inside-templated-switch).

| Attribute       | Required | Default          | Description                                        |
|-----------------|----------|------------------|----------------------------------------------------|
| `label`         | yes      |                  | Unique device label                                |
| `template`      | no       |                  | Catalog template ID (expands NIC/port structure)   |
| `position`      | no       | auto-positioned  | `{{ x: N, y: N }}` position in Port Mapping view   |
| `labels`        | no       |                  | Comma-separated tags the views filter by — e.g. `labels="rack-a,gpu"`. Surrounding spaces are trimmed and repeats dropped; a tag cannot contain `,` or `"`. Instance data: device templates never carry labels |
| `rackUnits`     | no       | `1`              | Height in rack units                            |
| `rackId`        | no       |                  | Rack label to assign this device to                |
| `rackElevation` | no       |                  | Starting unit (1-indexed from bottom) in the rack  |
| `cardWidth`     | no       |                  | Standard width, `424` card units (440 mm); other values are rescaled on load |
| `cardHeight`    | no       |                  | Card height in pixels (Port Mapping view)          |

**Tags**: `<Server>`, `<Switch>`, `<PDU>` all accept the same attributes (`<Compute>` and
`<Storage>` are still read, as `<Server>`). A
`<PDU>` additionally carries the [electrical spec](#pdu-electrical-spec), and a templated
one inherits it from its template field by field, exactly as it inherits its outlets.

**Labels** are what the label filter at the top of each view's right side bar works on.
Every label used in the topology is a button there, all selected by default; a device is
kept when at least one of its labels is selected (a device with no labels is kept while
*Unlabeled* is selected). Devices left out are grayed out and hatched in Rack
Elevation, Port Mapping and Power Budget, hidden in Fabric Topology, and not counted in the
BOM. The filter is view state and is not written to the DSL.

**Cable labels.** A `<Cable>` or `<PowerCable>` carries its own `labels`, and two views add a
second filter group for them: Fabric Topology lists the labels of network cables as **Fabric
labels**, Power Budget the labels of power cords as **Power grid**. Within a group a cable is
kept while any of its labels is selected (or it has none and *Unlabeled* is selected). The
groups combine as an AND:

- While only the component group narrows, devices are filtered as above.
- While the cable group narrows, a cable is kept when it passes the cable group **and** — if
  the component group narrows too — a device at one of its ends passes the component group.
  Every device at an end of a kept cable is shown, even one whose own labels are switched
  off; a device no kept cable reaches is left out.
- The lanes of one breakout port (`port:0`, `port:1`, …) are one physical cable: their labels
  count together, they are kept or left out together, and the devices on every lane are its
  ends.

Fabric Topology hides what is left out; Power Budget grays out the devices and cords.

### Nic / PortGroup / Phase (inside device)

Inside `<Switch>`, use `<PortGroup>` or `<Nic>`. Inside `<Server>`, use `<Nic>`.
Inside `<PDU>`, use `<Phase>` for a load bank of outlets, or `<Nic>` for the ethernet
monitor port. Every device except a `<PDU>` also accepts [`<Inlet>`](#inlet-inside-device)
for the connections it takes power through.

| Attribute | Required | Default | Description                                                  |
|-----------|----------|---------|--------------------------------------------------------------|
| `label`   | yes      |         | NIC label (must be unique within the device)                 |
| `layout`  | no       |         | Port grid layout: `{{ x, y, cols, groupSize?, rows? }}`     |
| `side`    | no       | `back`  | Chassis face the NIC is installed on: `front` or `back`      |
| `breaker` | no       |         | Branch breaker rating in amps (**`<Phase>` only**)           |
| `facing`  | no       |         | Which way each row faces: `alternate`, `up` or `down` (see [Port Facing](#how-rows-face)) |
| `adjacentRows` | no  | `1`     | Rows per block that face the same way, with `facing="alternate"` |
| `align`   | no       | `left`  | Where the ports sit across a box wider than they are: `left`, `center` or `right` (see [Port Alignment](#where-ports-sit-in-the-box)) |

### Inlet (inside device)

Where the device takes power, in the topology. Valid inside `<Server>` and
`<Switch>`; the far end of a [`<PowerCable>`](#powercable-inside-topology).

**Short form** — `count` inlets, all alike, labelled `p0`…`pN-1`:

```
<Inlet label="PSU1" count="2" connector="C14" watts="750" />
```

**Long form** — the inlets spelled out, for when they are not all alike:

```
<Inlet label="PSU1" captive="true" cordLength="1.8 m" watts="750">
  <Port label="left" connector="C14" />
  <Port label="right" connector="C20" />
</Inlet>
```

The attributes are the same as the [inlet template](#inlet-template-inside-device-template),
plus `serialnumber`, the serial number of the power supply installed in this device:

```
<Inlet label="PSU1" model="PWS-2K05A-1R" serialnumber="P2K05AB12345" count="1" connector="C14" watts="2000" />
```

On a device placed from a template (`template="ID"`), its inlets come from the template, so an
`<Inlet>` child there only records a serial number: it names one of the template's inlets by
`label` and carries nothing but `serialnumber`. Any other attribute, or `<Port>` children, is
an error.

```
<Server label="srv-01" template="r760">
  <Inlet label="PSU1" serialnumber="P2K05AB12345" />
  <Inlet label="PSU2" serialnumber="P2K05AB12346" />
</Server>
```

A `<Port>` inside an `<Inlet>` takes only `label` and `connector`, the connector defaults to
the group's, and it must be a power connector; `speed` on either is an error. Setting `count`
and listing `<Port>` children at the same time is an error — use one or the other.

Inlet labels share the device's port-group namespace, so an `<Inlet>` may not reuse a
`<Nic>` label on the same device.

### Port (inside Nic/PortGroup)

```
<Port label="LABEL" speed="SPEED" connector="CONN" breakout="N" />
```

| Attribute   | Required | Default | Description                       |
|-------------|----------|---------|-----------------------------------|
| `label`     | yes      |         | Port label (unique in NIC). May contain `/`, e.g. `Eth1/1` (see [Port Names](#port-names)) |
| `speed`     | no       | `10G`   | See Speed enum                    |
| `connector` | no       | `RJ45`  | See Connector enum                |
| `breakout`  | no       |         | Breakout channels: `2` or `4` (**switch ports only**) |

Ports are leaf elements and cannot have children.

`breakout` is only valid on ports of a `<Switch>` — see [Breakout](#breakout). A breakout
port cannot be a bond member either — see [Bond](#bond-inside-device).

### Outlet (inside Phase)

One receptacle on a PDU load bank. It stands where a `<Port>` does on a network device, and
carries a receptacle instead of a speed.

```
<Outlet label="LABEL" receptacle="RECEPTACLE" />
```

| Attribute    | Required | Default | Description                                          |
|--------------|----------|---------|------------------------------------------------------|
| `label`      | yes      |         | Outlet label (unique within the bank)                |
| `receptacle` | no       | `C13`   | Outlet type — must be a power connector (see Connector enum) |

`speed` on an `<Outlet>` is an error: an outlet carries current, not traffic. `<Outlet>` is
a leaf element and cannot have children, and a `<Phase>` accepts no other child tag.

### Bond (inside device)

Several ports of one device presented as a single logical link — a LAG, port-channel or NIC
team. Unlike breakout, bonding is **not** a switch-side capability: a server
teams its uplinks exactly as a switch bundles a port-channel, so `<Bond>` is valid inside
`<Server>` and `<Switch>` alike.

```
<Bond label="bond0" mode="lacp" ports="eth0/p0,eth0/p1" />
```

| Attribute | Required | Default | Description                                                     |
|-----------|----------|---------|-----------------------------------------------------------------|
| `label`   | yes      |         | Bond label (must be unique within the device)                   |
| `mode`    | no       | `lacp`  | How traffic is spread over the members (see Bond Mode enum)     |
| `ports`   | yes      |         | Comma-separated member ports, each written `NicLabel/PortLabel` |

Members are written **relative to the device**, without the node label: a bond never spans
chassis, so a cross-device member is not a link the hardware could form and there is no way
to write one.

Rules, each an import error:

- a member that is not a port of this device (`Bond "bond0" member "eth9/p0" not found`)
- the same port in two bonds — a port has one logical interface, not several
- a breakout port as a member: each channel is already an independent link, so there is no
  single interface to aggregate (drop the `:N` and bond whole ports, or drop the breakout)
- a power outlet as a member: a bond aggregates traffic and an outlet carries none
- a duplicate bond label within one device
- a missing or empty `ports` — a bond is defined by what it aggregates
- `<Bond>` inside a `<Catalog>` entry: a template describes hardware, while a bond names the
  ports of one installed device, so it belongs on the `<Topology>` device

`<Bond>` is a leaf element and cannot have children. It may appear before or after the NICs
it references, and a device may carry any number of bonds.

Bonding does not change how cables are drawn: each member port still terminates its own
cable, and the bond is what says those links act as one.

### Breakout override (inside templated Switch)

A templated device is written as just `template="…"`, so its ports are whatever the catalog
entry declares — including the breakout of each port group. How each cage of one installed
switch is actually split is that switch's own decision, so `<Breakout>` overrides it for a
single port:

```
<Switch label="leaf-01" template="sn4700" position={{ x: 20, y: 180 }}>
  <Breakout port="links/p21" channels="4" />
  <Breakout port="links/p30" channels="none" />
</Switch>
<Cable from="leaf-01/links/p21:2" to="srv-07/eth0/p0" />
```

| Attribute  | Required | Default | Description                                                         |
|------------|----------|---------|---------------------------------------------------------------------|
| `port`     | yes      |         | Port relative to the device — `GroupLabel/PortLabel`, no `:N`       |
| `channels` | yes      |         | `2` or `4` to split the port (see Breakout enum), `none` to leave whole a port the template splits |

Only ports whose split differs from the template are written; the Port panel in Port Mapping
edits this directly. Overrides are applied before `<Bond>`s, so a port split here cannot be a
bond member. The following are import errors:

- `<Breakout>` on a device that is not a `<Switch>` — only switch ports can be breakout
- `<Breakout>` on a switch written in full form — put `breakout="N"` on its `<Port>` instead
- `<Breakout>` inside a `<Catalog>` entry — a template states its split on the `<PortGroup>`
- a `port` that is not on this device, names a channel (`:N`), or appears twice
- a `channels` value other than `2`, `4` or `none`

`<Breakout>` is a leaf element and cannot have children.

### Cable (inside Topology)

**Full form** (properties on the cable):

```
<Cable from="NodeLabel/NicLabel/PortLabel[:N]" to="NodeLabel/NicLabel/PortLabel[:N]"
       labels="TAG,TAG" material="MATERIAL" mode="MODE" speed="SPEED"
       negotiatedSpeed="SPEED" link="up|down" />
```

**Template form** (properties inherited from a catalog cable):

```
<Cable from="NodeLabel/NicLabel/PortLabel[:N]" to="NodeLabel/NicLabel/PortLabel[:N]"
       labels="TAG,TAG" template="CATALOG_CABLE_ID" negotiatedSpeed="SPEED" link="up|down" />
```

| Attribute  | Required | Default | Description                           |
|------------|----------|---------|---------------------------------------|
| `from`     | yes      |         | Source port path (with optional breakout channel) |
| `to`       | yes      |         | Target port path (with optional breakout channel) |
| `labels`   | no       |         | Comma-separated tags, the same format as a device's [`labels`](#device-server-switch-pdu) — per-instance, allowed with `template` (a catalog `<Cable>` never carries them). Fabric Topology filters by them as **Fabric labels** |
| `length`   | no       |         | Physical length of the run, free text — per-instance, allowed with `template` |
| `template` | no       |         | Id of a `<Cable>` entry in `<Catalog>`; inherits its `material`, `mode`, `speed` and link assembly, read-only |
| `negotiatedSpeed` | no |      | What this one link actually runs at, when it is below its capacity (see Speed enum) — per-instance, allowed with `template`. Left out, the link runs at capacity. See [Negotiated speed](#negotiated-speed) |
| `link`     | no       |         | `up` or `down`: whether this one link came up — per-instance, allowed with `template`. Left out, the state is **unknown**, which is not the same as up. See [Link state](#link-state) |

| `material` | no       |         | `copper` or `fiber`                   |
| `mode`     | no       |         | `active` or `passive`                 |
| `speed`    | no       |         | The cable's rated speed (see Speed enum) — its capacity, when it has no `template` |

`template` is how a whole class of identical links is described once. The catalog entry stays
the single source of truth: the inherited properties are resolved from it on read, so editing
the template in Materials → Cables updates every cable using it, and the Cable properties
panel shows those values locked.

Because inheritance is read-only, a cable may not carry `template` **and** a local
`material`, `mode`, `speed`, or assembly children — that combination is an error rather than a
silent override. Drop the `template` to set properties locally.

`labels` is the exception: it describes *this* link rather than the design, so it is always
allowed. `length`, `negotiatedSpeed`, `link` and serial numbers are the others — see below.

**`label` is gone (NETW-39).** A cable used to carry a single free-text `label` as well; its
tags are now all in `labels`. A file that still has `label="X"` on a `<Cable>` or
`<PowerCable>` opens with nothing lost: `X` is added to that cable's `labels` (once — a value
already there is not repeated), and the file is written back with `labels` only.

```xml
<Cable from="COE-01/cx7-1/p0" to="sn4700-400gbps/links/p1" template="dr4-400g-5m" />
```

#### Negotiated speed

A cable model is rated for a speed, but one link built from it can run slower. For example, a
400G QSFP-DD to 2× 200G QSFP56 breakout cable whose 200G legs plug into 100G QSFP28 server ports
trains at 100G on each leg. That 100G is a fact about the link, not the cable, so it is recorded
on the `<Cable>` itself as `negotiatedSpeed`, next to the template:

```xml
<Cable from="SN4700-400gbps/links/swp1:0" to="node-1/e810/p0" template="dac-400g-2x200g-fs-3m" negotiatedSpeed="100G" />
<Cable from="SN4700-400gbps/links/swp1:1" to="node-2/e810/p0" template="dac-400g-2x200g-fs-3m" negotiatedSpeed="100G" />
```

`speed` and `negotiatedSpeed` say different things:

- **Capacity** is what the cable is rated for: the template's `speed`, or the cable's own `speed`
  when it has no template. A breakout lane (`port:N`) is one leg of the assembly, so its capacity
  is that leg's far-end `<Transceiver>` speed (200G above), not the cable's aggregate 400G. A
  cable that lists a single far end gives every leg that speed.
- **`negotiatedSpeed`** is what this link actually runs at. Left out, the link runs at its
  capacity. Don't make a slower copy of a cable template to describe one link: that misstates
  the part and splits one part number into several templates.

`negotiatedSpeed` cannot be faster than the link's capacity, nor than the rated speed of either
port it plugs into. It is not allowed on a catalog `<Cable>` (it describes an installed link,
not the part) or on a `<PowerCable>`. Each of these is an import error.

The Cable panel shows the capacity, locked, next to an editable negotiated speed. Port Mapping
labels a link that has a negotiated speed with what it runs at, and its capacity when that is
higher: `100G (of 200G)`. Fabric Topology adds up what the links between two devices run at,
and shows the capacity too when they run below it. The BOM is unchanged: it counts cable parts,
not link speeds.

#### Link state

A cable can be plugged in with its link down: the cable is there, but nothing was negotiated.
`link` records what was observed on this one link:

```xml
<Cable from="SN4700-400gbps/links/swp3:0" to="gpu2/cx6/p1" template="acc-400g-2x200g-fs-5m" negotiatedSpeed="100G" link="up" />
<Cable from="SN4700-400gbps/links/swp3:1" to="gpu2/cx6/p0" template="acc-400g-2x200g-fs-5m" link="down" />
```

- `link="up"`: the link came up. It runs at `negotiatedSpeed`, or at capacity without one.
- `link="down"`: cabled, but the link is down.
- Left out: the state is unknown. Files written before `link` existed load as unknown, never as
  up.

On a breakout, each lane (`port:N`) is its own `<Cable>`, so each lane carries its own `link`.

`link="down"` together with `negotiatedSpeed` is an import error: a down link negotiated
nothing. Like `negotiatedSpeed`, `link` is not allowed on a catalog `<Cable>` or on a
`<PowerCable>`.

**How a port shows its link.** Nothing about the link is stored on a `<Port>`: a port shows
what the cable plugged into it says. The Port panel lists the port's rated speed (from its NIC
or port group), the cable's capacity, the negotiated speed (or "at capacity") and the link
state. A breakout cage lists every lane, e.g. `lane 0 up 100G (of 200G)`, `lane 1 down`. The
port glyph in Port Mapping shows the state: bright green when up, a muted green when cabled with
the state unknown, hollow when down, grey when free, with a small amber dot when the link runs
below its capacity. A breakout cage shows each lane on its own.

The Cable panel has a **Link** selector (Up / Down / Unknown) next to Negotiated speed. Choosing
Down clears the negotiated speed and disables it. Fabric Topology draws links that are all down
dashed and grey, says how many of a pair's links are down, and can hide down links with a
toolbar toggle.

### PowerCable (inside Topology)

A power cord: the run from one PDU outlet to one device inlet. It stands where a `<Cable>`
does, and carries none of a link's design — a cord has no optics, no material and no rate,
only where it runs and how long it is.

```
<PowerCable from="PduLabel/BankLabel/OutletLabel" to="NodeLabel/InletLabel/PortLabel"
            labels="TAG,TAG" length="TEXT" />
```

| Attribute | Required | Default | Description                                   |
|-----------|----------|---------|-----------------------------------------------|
| `from`    | yes      |         | One end's port path                           |
| `to`      | yes      |         | The other end's port path                     |
| `labels`  | no       |         | Comma-separated tags, as on a `<Cable>` — Power Budget filters by them as **Power grid**. An old `label` is read into it the same way |
| `length`  | no       |         | Cord length, free text — e.g. `2 m`           |

Both ends must be power connectors, and **exactly one** of them must be an `<Outlet>` on a
`<PDU>` — a cord is fed by a PDU and lands on the device it powers. Two PDU ends, no PDU end,
or a data port at either end is an error. The device end may be an `<Inlet>`, or any power
`<Port>` on the device.

`material`, `mode`, `speed`, `negotiatedSpeed`, `link` and `template` are errors on a `<PowerCable>`, as are
`<Transceiver>` / `<Fiber>` children: all of them describe a network link.

A `<Cable>` written between two power connectors is accepted and means the same thing — it is
normalised to `<PowerCable>` when the topology is written back out.

```xml
<PowerCable from="pdu-a/bank1/o0" to="srv-1/PSU1/p0" length="1.8 m" />
```

### Serial numbers

A serial number identifies one physical optic, so it never belongs to a design that many
links share. Record it on a `<Transceiver>` child of the topology `<Cable>`, naming the part
it belongs to by `model`:

```xml
<Cable from="COE-07/cx7-1/p0" to="sn4700-400gbps/links/p11" template="dr4-400g-5m">
  <Transceiver model="MMS1V00-WM" serialnumber="MT2447FT12457" />
  <Transceiver model="MMS4X00-NS400" serialnumber="MT2447NS07871" />
</Cable>
```

| Attribute      | Required | Description                                                     |
|----------------|----------|-----------------------------------------------------------------|
| `model`        | yes      | Which optic in the assembly this serial belongs to              |
| `serialnumber` | yes      | Serial number of the installed part                             |

On a **templated** cable these children may carry *only* `model` and `serialnumber` — the rest
of the design comes from the template, and restating `manufacturer`, `connector`, `speed` or
`modulation` is an error. The `model` must match a `<Transceiver>` in the template's assembly.
Either end may be given on its own, in any order; matching is by `model`, not position. When
both ends use the same model, list one child per end and they are matched in order.

On a cable with an **inline** assembly, `serialnumber` sits on the same `<Transceiver>` that
describes the part.

`serialnumber` is never valid inside `<Catalog>`, and the Materials editor's cable form has no
field for it. In the Cable properties panel each serial is edited inside its own optic's box.

Port paths use the format `NodeLabel/NicLabel/PortLabel`. For breakout ports, append `:N` where N is the 0-based channel index (e.g., `node-001/eth0/p0:0` for channel 0 of a breakout port). Non-breakout ports can only be connected to one cable; breakout ports accept one cable per channel.

A cable's two ends must be on the same side of the power/data divide: joining a power
outlet to a data port describes hardware that cannot be plugged together, and is an error.
A power cord therefore runs from a PDU `<Outlet>` to a power inlet — declared on the fed
device as an ordinary `<Port>` with a power `connector`, typically the even-numbered mate
of the outlet (`C13` outlet → `C14` inlet).

A `<Cable>` is a leaf element unless it carries a [link assembly](#link-assembly-inside-cable).

### Link Assembly (inside Cable)

A link assembly spells out the parts of a cable so the Bill of Materials and the Materials
editor can show real part numbers. Fiber (AOC) and copper (DAC/ACC) use different middle
tags; a cable cannot carry both `<Fiber>` and `<Copper>`.

#### Fiber (AOC)

An active optical link is three parts: an optic at each end and a passive fiber between them.

```
<Cable from="COE-12/cx7-1/p0" to="sn4700-400gbps/links/p21" material="fiber" mode="active" speed="400G">
  <Transceiver model="MMS1V00-WM" manufacturer="NVIDIA" connector="QSFP-DD" speed="400G" modulation="4x100Gb/s PAM4" serialnumber="MT2447FT12457" />
  <Fiber model="MFP7E30-N005" manufacturer="NVIDIA" length="5m" connector="MPO12 APC to MPO12 APC" />
  <Transceiver model="MMS4X00-NS400" manufacturer="NVIDIA" connector="OSFP" speed="400G" modulation="4x100Gb/s PAM4" serialnumber="MT2447NS07871" />
</Cable>
```

A fiber link may also **break out**: one optic at the `from` end fans out over an MTP/MPO
breakout fiber to several optics at the far end — usually 1→2, 1→4, or 1→8.

```
<!-- 800G → 2x400G MTP breakout -->
<Cable id="sr8-800g-to-2x400g" model="800G to 2x400G SR breakout" manufacturer="NVIDIA"
       speed="800G" connector="OSFP" material="fiber" mode="active">
  <Transceiver model="MMA4Z00-NS" manufacturer="NVIDIA" connector="OSFP" speed="800G" modulation="8x100Gb/s PAM4" />
  <Fiber model="MFP7E20-N003" manufacturer="NVIDIA" length="3m" connector="MPO16 APC to 2x MPO12 APC" />
  <Transceiver model="MMA1Z00-NS400" manufacturer="NVIDIA" connector="QSFP-DD" speed="400G" modulation="4x100Gb/s PAM4" />
  <Transceiver model="MMA1Z00-NS400" manufacturer="NVIDIA" connector="QSFP-DD" speed="400G" modulation="4x100Gb/s PAM4" />
</Cable>
```

**Child order is significant**: the first `<Transceiver>` is the optic at the cable's `from` end;
every later `<Transceiver>` is a far-end optic — one for a 1:1 link, several for a breakout.
At most one `<Fiber>` child is allowed; every child is optional, so a half-specified link is
valid. Modulation lives on each optic.

#### Copper (DAC / ACC)

DAC (passive) and ACC (active copper cable) assemblies are **non-interchangeable**: the
connectors are permanently attached to the copper run. The Materials editor shows a connector
on each side and the modulation on the copper section. Like fiber, a far end may be **1→N**
when the assembly breaks out.

```
<Cable id="dac-400g-1m" model="400G QSFP-DD DAC" manufacturer="NVIDIA"
       speed="400G" connector="QSFP-DD" material="copper" mode="passive">
  <Transceiver model="MCP1600-C01AE30N-A" manufacturer="NVIDIA" connector="QSFP-DD" speed="400G" />
  <Copper model="MCP1600-C01AE30N" manufacturer="NVIDIA" length="1m" modulation="4x100Gb/s PAM4" />
  <Transceiver model="MCP1600-C01AE30N-B" manufacturer="NVIDIA" connector="QSFP-DD" speed="400G" />
</Cable>

<!-- 1→4 breakout DAC -->
<Cable id="dac-400g-to-4x100" model="400G to 4x100G DAC" manufacturer="NVIDIA"
       speed="400G" connector="QSFP-DD" material="copper" mode="passive">
  <Transceiver model="MCP7Y60-H001-A" manufacturer="NVIDIA" connector="QSFP-DD" speed="400G" />
  <Copper model="MCP7Y60-H001" manufacturer="NVIDIA" length="1m" modulation="4x100Gb/s PAM4" />
  <Transceiver model="MCP7Y60-H001-B0" manufacturer="NVIDIA" connector="QSFP56" speed="100G" />
  <Transceiver model="MCP7Y60-H001-B1" manufacturer="NVIDIA" connector="QSFP56" speed="100G" />
  <Transceiver model="MCP7Y60-H001-B2" manufacturer="NVIDIA" connector="QSFP56" speed="100G" />
  <Transceiver model="MCP7Y60-H001-B3" manufacturer="NVIDIA" connector="QSFP56" speed="100G" />
</Cable>
```

The first `<Transceiver>` is the `from` end; every subsequent `<Transceiver>` is a far-end
connector. At most one `<Copper>` child is allowed. Modulation lives on `<Copper>`, not on the
ends. Mode `passive` is DAC; mode `active` is ACC.

Assemblies parse on any cable. The Materials editor and the Cable properties panel surface the
fiber editor when `material="fiber"` and the copper editor when `material="copper"`.

#### `<Transceiver>` (inside Cable)

| Attribute      | Required | Default | Description                                            |
|----------------|----------|---------|--------------------------------------------------------|
| `model`        | no       |  `""`   | Model / part number — e.g. `MMS4X00-NS400`             |
| `manufacturer` | no       |         | Manufacturer name                                      |
| `serialnumber` | no       |         | Serial number of the installed part — `<Topology>` only, see [Serial numbers](#serial-numbers) |
| `connector`    | no       |         | Cage form factor it plugs into (see Connector enum)    |
| `speed`        | no       |         | Optic / end speed (see Speed enum)                     |
| `modulation`   | no       |         | Line coding, free text — e.g. `4x100Gb/s PAM4`. Used on fiber optics; on copper DAC/ACC put modulation on `<Copper>` instead. A leading `Nx` also sets the number of lanes drawn in the Materials editor |

#### `<Fiber>` (inside Cable)

| Attribute      | Required | Default | Description                                                    |
|----------------|----------|---------|----------------------------------------------------------------|
| `model`        | no       |  `""`   | Model / part number — e.g. `MFP7E30-N005`                      |
| `manufacturer` | no       |         | Manufacturer name                                              |
| `length`       | no       |         | Free text so the unit travels with the value — e.g. `5m`       |
| `connector`    | no       |         | End-to-end connector spec, free text — e.g. `MPO12 APC to MPO12 APC` |

#### `<Copper>` (inside Cable)

| Attribute      | Required | Default | Description                                                    |
|----------------|----------|---------|----------------------------------------------------------------|
| `model`        | no       |  `""`   | Model / part number — e.g. `MCP1600-C01AE30N`                  |
| `manufacturer` | no       |         | Manufacturer name                                              |
| `length`       | no       |         | Free text so the unit travels with the value — e.g. `1m`       |
| `modulation`   | no       |         | Line coding for the copper run — e.g. `4x100Gb/s PAM4`. A leading `Nx` sets the lane count drawn in the Materials editor |

Note that `<Fiber connector>` is free text, unlike `<Transceiver connector>` which must be a
value from the Connector enum. `<Transceiver>`, `<Fiber>`, and `<Copper>` are leaf elements and
cannot have children.

The `<Transceiver>` tag means two different things depending on where it sits: a direct child of
`<Catalog>` is a reusable transceiver template keyed by `id` and `formFactor`, while a child of
`<Cable>` is one end of that cable's link assembly.

---

## Enums

### Speed

`1G` | `10G` | `25G` | `40G` | `100G` | `200G` | `400G` | `800G`

### Connector Form Factor

**Data** — `RJ45` | `SFP` | `SFP+` | `SFP28` | `SFP56` | `QSFP+` | `QSFP28` | `QSFP56` | `QSFP112` | `QSFP-DD` | `OSFP` | `CFP` | `CFP2` | `CFP4` | `MicroUSB` | `USB-C` | `DB9`

**Power** — the values a `receptacle` or `inputConnector` accepts:

- IEC 60320 couplers: `C13` | `C14` | `C15` | `C16` | `C19` | `C20` — odd numbers are the
  outlet on the PDU, even numbers the inlet on the device it feeds
- NEMA locking: `L5-30P` | `L5-30R` | `L6-20P` | `L6-20R` | `L6-30P` | `L6-30R` | `L14-30P` | `L15-30P` | `L21-20P` | `L21-30P` | `L21-30R` | `L22-30P`
- NEMA straight-blade: `NEMA5-15P` | `NEMA5-15R` | `NEMA5-20P` | `NEMA5-20R`
- IEC 60309 pin-and-sleeve: `IEC309-332P6` | `IEC309-532P6` | `IEC309-460P9` | `IEC309-560P9`
- `Hardwire` — permanently wired, no connector at all

A `<Port connector>` takes any value from either list, which is how a device's power inlet is
written. A `receptacle` takes power values only.

### Breakout

`2` | `4`

Breakout splits one physical cage into that many independent channels, each addressable as
its own cable endpoint (`Node/Nic/Port:N`). It is a switch-side capability: a host NIC port
terminates a single link and has nothing to split, so **only switch ports can be breakout**.

Declaring `breakout` anywhere else is an import error:

- `<Port breakout="N">` inside a `<Server>` node
- `<Nic breakout="N">` inside a `<Server>` catalog template
- a `<Server>` node whose `template="..."` resolves to a `<Switch>` entry that
  declares `breakout` (ids resolve across categories, so this combination is reachable)
- a `<Breakout>` override inside anything but a templated `<Switch>`

Like every other error in this schema, any one of these rejects the whole import — fix the
attribute or move the ports to a `<Switch>`.

### Bond Mode

`lacp` | `static` | `active-backup`

`lacp` negotiates the group with the peer, `static` is a manually pinned channel, and
`active-backup` keeps one member hot while the rest stand by. Any device kind can carry a
bond in any of these modes — see [Bond](#bond-inside-device).

### Power Phase

`single` | `three`

How a PDU is fed: one hot leg, or three.

### PDU Metering

`none` | `local` | `network`

`local` is a panel meter you have to walk up to; `network` is the embedded monitor, whose
jack is declared as a `<Nic>` on the PDU so it can be cabled.

### Port Facing

`alternate` | `up` | `down`

See [Port Facing](#how-rows-face) under the Layout Object.

### Port Alignment

`left` | `center` | `right`

See [Port Alignment](#where-ports-sit-in-the-box) under the Layout Object.

### Cable Material

`copper` | `fiber`

### Cable Mode

`active` | `passive`

### View

`rack-elevation` | `switch-port-mapping` | `power-budget` | `fabric-topology` | `bom`

`switch-port-mapping` (Port Mapping) designs the network links and `power-budget`
(Power Budget) designs the power feed. Each shows the other's wiring read-only.

---

## Layout Object

Used for `position` and `layout` attributes. Double-brace syntax with comma-separated `key: value` pairs:

```
{{ x: 100, y: 200 }}
{{ x: 0, y: 0, cols: 8, groupSize: 4, rows: 2 }}
```

| Field       | Used in    | Description                                  |
|-------------|------------|----------------------------------------------|
| `x`         | both       | X position (pixels)                          |
| `y`         | both       | Y position (pixels)                          |
| `cols`      | layout     | Number of columns in port grid               |
| `groupSize` | layout     | Visual grouping interval (separator spacing) |
| `rows`      | layout     | Number of horizontal rows of ports, at least `1` (enables column-major fill) |
| `w`         | layout     | Width of the group's box (card units). Its ports are fitted inside |
| `h`         | layout     | Height of the group's box (card units). Its ports are fitted inside |

**Card units and millimetres.** `layout` values are in *card units*, the coordinate space a
device card is drawn in: `424` across the standard 19" chassis width and `84` per rack unit,
measured from the top-left of the faceplate (below the card's title bar). The Materials
editor draws the faceplate true to scale and shows every size in millimetres, converting
with **440 mm = 424 units** across and **44.45 mm (1U) = 84 units** down. For example an OCP
NIC 3.0 SFF card, 76 mm wide, is `w: 73.24`.

**Rows and columns.** Without `rows`, a group is `cols` ports wide and has as many rows as
that takes, filled left to right, then top to bottom. With `rows`, it has exactly that many
rows, filled top to bottom, then left to right — ports 1 and 2 stacked, as on a switch — and
is `cols` wide, or wider if `rows × cols` would not hold every port: the grid then grows to
`ceil(ports / rows)` columns, so ports never overlap. `groupSize` still splits the columns
into groups with a gap between them.

#### How rows face

Once a group has more than one row, each row's ports are drawn either the right way up or
upside down (latch on the other side), as on a switch whose rows are stacked belly to belly.
Two attributes **on the group's element** — not inside `layout`, which only holds numbers —
set it:

| Attribute      | Values                        | Description |
|----------------|-------------------------------|-------------|
| `facing`       | `alternate` \| `up` \| `down` | `alternate`: blocks of rows take turns, the first block up, the next down. `up` / `down`: every row the same way |
| `adjacentRows` | whole number ≥ 1, default `1` | With `alternate`, how many rows in a row face the same way. `1` is up, down, up, down; `2` with 4 rows is up, up, down, down |

Without `facing`, a group with `rows` alternates (`adjacentRows` 1) and one without faces
all up. A single row always faces up. Both are only read alongside a `layout`.

```
<PortGroup label="ports" portCount="64" speed="800G" connector="OSFP"
           layout={{ x: 12, y: 0, cols: 16, groupSize: 4, rows: 4 }}
           facing="alternate" adjacentRows="2" />
```

#### Where ports sit in the box

A group whose box is wider than its ports — one stretched with `w` — can say where the ports
sit across it with `align` on the group's element, next to `facing`:

| Value    | Description |
|----------|-------------|
| `left`   | Against the box's left edge. The default, and where ports have always sat |
| `center` | Centred in the box |
| `right`  | Against the box's right edge |

The box itself does not move; only the ports inside it do, and cables attach to them where
they are drawn. A box as large as its port grid (no `w`) has no room to spare, so `align`
changes nothing there. Only read alongside a `layout`.

```
<Nic label="cx7-1" portCount="1" speed="400G" connector="OSFP"
     layout={{ x: 360, y: 12, cols: 1, w: 60 }} align="right" />
```

Without `w`/`h` a group's box is as large as its port grid. A device whose `cardWidth` is not
`424` was drawn before every chassis became the standard width: on load its groups' `x` and
`w` are rescaled by `424 / cardWidth` and it is written back at `424`.

---

## Example

```xml
<Catalog>
  <Switch id="leaf-sw" model="SN5600" manufacturer="NVIDIA" rackUnits="1"
          cardWidth="424" cardHeight="60">
    <PortGroup label="ports" portCount="64" speed="800G" connector="OSFP"
               layout={{ x: 0, y: 0, cols: 32, groupSize: 8, rows: 2, w: 380, h: 40 }} />
    <PortGroup label="mgmt" portCount="1" speed="1G" connector="RJ45"
               layout={{ x: 398, y: 20, cols: 1 }} />
  </Switch>

  <!-- 5.5kW single-phase local metered rPDU: two banks of C13s, each behind its own 20A
       breaker, the pair of C19s, and the ethernet monitor port. -->
  <PDU id="pdumh30hv" model="PDUMH30HV" manufacturer="Tripp Lite" rackUnits="2"
       phase="single" voltage="208/230V" amperage="30" deratedAmperage="24"
       capacityKw="5.5" inputConnector="L6-30P" cordLength="12 ft. (3.66 m)"
       metering="local" cardWidth="424" cardHeight="168">
    <Phase label="bank1" outletCount="8" receptacle="C13" breaker="20"
           layout={{ x: 24, y: 16, cols: 8, groupSize: 4 }} />
    <Phase label="bank2" outletCount="8" receptacle="C13" breaker="20"
           layout={{ x: 24, y: 60, cols: 8, groupSize: 4 }} />
    <Phase label="high" outletCount="2" receptacle="C19"
           layout={{ x: 300, y: 16, cols: 2 }} />
    <Nic label="mgmt" portCount="1" speed="1G" connector="RJ45"
         layout={{ x: 390, y: 60, cols: 1 }} />
  </PDU>
</Catalog>

<Topology view="rack-elevation">
  <Rack label="CR01" units="48" />
  <Rack label="NR01" units="48" row="2" />

  <Server label="node-001" position={{ x: 100, y: 100 }} labels="rack-cr01,gpu"
           rackId="CR01" rackElevation="1" rackUnits="10">
    <Nic label="eth0">
      <Port label="p0" speed="100G" connector="QSFP28" />
      <Port label="p1" speed="100G" connector="QSFP28" />
    </Nic>
    <!-- Redundant supplies: two C14 inlets drawing 1600W between them. -->
    <Inlet label="PSU1" count="2" connector="C14" watts="1600" />
    <Bond label="bond0" mode="lacp" ports="eth0/p0,eth0/p1" />
  </Server>

  <Switch label="leaf-01" template="leaf-sw"
          rackId="NR01" rackElevation="1" />

  <PDU label="pdu-a" template="pdumh30hv" rackId="CR01" rackElevation="47" />

  <!-- Power cords: a PDU outlet to each of the server's inlets. -->
  <PowerCable from="pdu-a/bank1/p0" to="node-001/PSU1/p0" length="1.8 m" />
  <PowerCable from="pdu-a/bank2/p0" to="node-001/PSU1/p1" length="1.8 m" />

  <Cable from="node-001/eth0/p0" to="leaf-01/ports/p0" material="fiber" mode="active" speed="100G">
    <Transceiver model="MMA1L10-CR" manufacturer="NVIDIA" connector="QSFP28" speed="100G" modulation="4x25Gb/s NRZ" serialnumber="MT2447FT12457" />
    <Fiber model="MFP7E30-N005" manufacturer="NVIDIA" length="5m" connector="MPO12 APC to MPO12 APC" />
    <Transceiver model="MMA1L10-CR" manufacturer="NVIDIA" connector="QSFP28" speed="100G" modulation="4x25Gb/s NRZ" serialnumber="MT2447NS07871" />
  </Cable>
</Topology>
```
