> ## Documentation Index
> Fetch the complete documentation index at: https://docs.internal.nscale.com/llms.txt
> Use this file to discover all available pages before exploring further.

# NetBox Data Requirements

> What must be populated in NetBox for devices to sync correctly to Matrix (Azure Kusto) via the Matrix connector, plus the InfiniBand port-GUID contract Fleet Manager's own NetBox sync reads.

Use this as a checklist when adding new [devices](../start-here/glossary#node), sites, or cabling in [NetBox](../start-here/glossary#netbox).

## How the Sync Works

The Matrix connector is event-driven: NetBox changes reach Fleet Manager's inventory projections and flow to the connector as [NATS](../start-here/glossary#nats) events, with a polling backstop sweeping every scoped site every 150 seconds so nothing is ever missed. Ethernet devices and [InfiniBand](../start-here/glossary#infiniband) inventory are read from NetBox directly on a 120-second delta loop with a 6-hour keep-alive re-stamp. Devices are filtered by **role** and **status** (`active` only), transformed into Matrix table schemas, and ingested to Azure [Kusto](../start-here/glossary#kusto) staging tables. Deletions differ by table: projection-fed tables (Servers, NVSwitches) receive an `IsDeleted` tombstone within the [SLA](../start-here/glossary#sla), while NetBox-direct tables (EthDevices, IbInventory) prune on the next successful full refresh and the stale rows age out under the 7-day staging retention. A NetBox change is typically queryable in Kusto within seconds, well inside the 5-minute SLA. See [Matrix Connector Operations](./matrix-connector-operations) for the operational runbook.

<Note>The transforms and naming rules on this page previously ran in the retired `netbox_matrix` agent and are unchanged in the connector — "the agent" in the sections below refers to the sync's transform behaviour, which is identical.</Note>

Only devices in sites listed in `CONNECTOR_SITE_FILTER` are synced **to Matrix**. That filter scopes the Matrix connector alone. Fleet Manager's own NetBox sync agent selects its devices through `NETBOX_AGENT_DEVICE_FILTERS` instead, so a site the sync agent covers has its [RDMA](../start-here/glossary#rdma) GUID labels (section 6) read whether or not it appears in `CONNECTOR_SITE_FILTER`.

***

## Device and Asset Naming Conventions

The agent uses the `asset_tag` field as the primary display name in Matrix (falling back to `name` if unset). For Ethernet devices, the `asset_tag` is also the source for **asset type extraction** — a critical Matrix field. Names must follow specific conventions per device type.

### General Rules

* **`asset_tag`** is the preferred identifier sent to Matrix. Set it on every device.
* **`name`** is the NetBox hostname. It acts as a fallback when `asset_tag` is empty.
* The agent checks `asset_tag` first, then `name`, when resolving display names and extracting asset types.

### Ethernet Device Naming (Switches, Firewalls, Routers)

Ethernet device `asset_tag` values **must** follow this pattern for asset type extraction to work:

```
<site>-<location>-<position>-<number><type>
```

The agent applies the regex `-(\d+)([a-z]+\d*)$` (case-insensitive) to extract the **type suffix** from the end of the name. The `<number>` is a sequence number and the `<type>` is a short code identifying the device's function.

**Pattern breakdown:**

| Segment | Description | Example |
| - | - | - |
| `<site>` | Site code (from site description, lowercase) | `sie50`, `pdx51`, `sta1`, `sin1` |
| `<location>` | DataHall and position identifiers | `dh5-dc`, `m34-p1`, `m27-p1` |
| `<number>` | Sequence number (digits) | `01`, `02`, `11` |
| `<type>` | Asset type suffix (letters, optionally followed by a digit) | `t0`, `t1`, `fbs`, `cab`, `m0`, `fw` |

**Known asset type suffixes:**

| Suffix | Device Type | Example Asset Tag |
| - | - | - |
| `t0` | Tier-0 spine switch | `sta1-m34-p1-01t0` |
| `t1` | Tier-1 leaf switch | `pdx51-m34-p1-01t1` |
| `t2` | Tier-2 access switch | `sin1-m27-p1-01t2` |
| `fbs` | Frontend border switch | `sie50-dh5-dc-01fbs` |
| `oobfbs` | [OOB](../start-here/glossary#oob) frontend border switch | `sie50-dh5-dc-01oobfbs` |
| `cab` | Cable aggregation box | `pdx51-m34-dc-01cab` |
| `m0`, `m1`, `m2` | Management switch (tiers) | `sin1-m27-p1-01m0` |
| `fw` | Firewall | `pdx51-m34-p1-01fw` |

**Examples with full extraction:**

```
sie50-dh5-dc-01fbs      →  regex matches "-01fbs"      →  asset_type = "fbs"
sie50-dh5-dc-01oobfbs   →  regex matches "-01oobfbs"   →  asset_type = "oobfbs"
sta1-m34-p1-01t0        →  regex matches "-01t0"       →  asset_type = "t0"
pdx51-m34-p1r01-02t0 →  regex matches "-02t0"   →  asset_type = "t0"
sin1-m27-p1-01m2     →  regex matches "-01m2"   →  asset_type = "m2"
```

**Role-based fallbacks** (when the regex doesn't match). These apply to the asset type extraction function used by the EthDevices and EthInterfaceLinks transforms:

| Role | Fallback Asset Type |
| - | - |
| `console-server`, `console-manager` | `console-server` |
| `nvlink-switch` | `nvs` when `asset_tag` ends in `-<digits>nvs` or `-<optional digits>nv`; otherwise empty |
| All other roles | Empty string (logged as warning) |

If neither the regex nor a role fallback applies, the `asset_type` field in Matrix will be empty — this indicates a naming issue that should be fixed.

<Note>`gpu-node` and `compute-tray` devices sync via the Servers table (section 4a), not EthDevices. If they do appear in EthDevices context (e.g., as cable endpoints in EthInterfaceLinks), the function returns `GPUNode` as a fallback.</Note>

### InfiniBand Switch Naming

IB switches have a **hard requirement**: the device `name` must contain `-ifs-`. Without it, the device is filtered out entirely even if it has the correct role.

**`name` pattern:** `<site>-<pod>-ifs-<switch_type><number>`

| Example Name | Explanation |
| - | - |
| `sin1-p-ifs-bes115` | sin1, pod p, IB fabric switch, backend spine 115 |
| `sta1-p-ifs-bes101` | sta1, pod p, IB fabric switch, backend spine 101 |
| `sta1-p-ifs-bel201` | sta1, pod p, IB fabric switch, backend leaf 201 |

**`asset_tag` convention:** The `asset_tag` is used as the display name in Matrix (`device_name`) when present, and follows a different convention based on the switch's position in the IB fabric topology. This applies to both NDR and XDR clusters:

<Note>The `asset_tag` may carry a site prefix (e.g. `sys1-wdc1.L3.1.1`), because NetBox requires asset tags to be globally unique and the same fabric designation is reused across sites. The agent strips the prefix on transform, so Matrix always receives the bare designation (`L3.1.1`).</Note>

**Core switches** — `CX.Y`:

| Segment | Description |
| - | - |
| `C` | Core switch prefix |
| `X` | Core group number |
| `Y` | Sequence number within the core group |

Example: `C1.2` = core group 1, switch 2

**Spine switches** — `SX.Y.Z`:

| Segment | Description |
| - | - |
| `S` | Spine switch prefix |
| `X` | Switch sequence number in the rail |
| `Y` | Super pod number (Scalable Unit number) |
| `Z` | Rail number in the SU |

Example: `S1.3.2` = switch 1, super pod 3, rail 2

**Leaf switches** — `LX.Y.Z`:

| Segment | Description |
| - | - |
| `L` | Leaf switch prefix |
| `X` | Switch sequence number in the rail |
| `Y` | Super pod number (Scalable Unit number) |
| `Z` | Rail number in the SU |

Example: `L2.1.4` = switch 2, super pod 1, rail 4

### GPU Compute Server Naming

[GPU](../start-here/glossary#gpu) servers don't have a strict naming regex, but common patterns include:

| Example Name | Example Asset Tag |
| - | - |
| `sie50-dh6-a01-11-gpu1` | `SIE50-dh1-A1-gpu1` |

The `asset_tag` (or `name` as fallback) becomes the `node_name` in the Servers table.

### NVLink Switch Naming

[NVLink](../start-here/glossary#nvlink) switches use `asset_tag` as the display name. Their `asset_type` is normalized to `nvs` when the tag ends in either supported site convention:

| Asset Tag Suffix | Example | Matrix `asset_type` |
| - | - | - |
| `-<digits>nvs` | `sie50-dh06-a01-19-01nvs` | `nvs` |
| `-<optional digits>nv` | `ink50-dh01-d01-19-nv`, `ink50-dh01-d01-19-02nv` | `nvs` |

Any other NVLink `asset_tag` suffix produces an empty `asset_type`; correct the tag rather than relying on the device name as a fallback.

| Example Asset Tag |
| - |
| `sie50-dh06-a01-19-01nvs` |

### UFM Server Naming

UFM servers sync to the IbSwitches table. No `-ifs-` naming requirement — role match alone is sufficient. The `asset_tag` (or `name`) becomes the display name.

### NetBox `name` vs `asset_tag` Summary

| Field | Purpose | Used When |
| - | - | - |
| `name` | NetBox hostname / infrastructure identifier | Fallback when `asset_tag` is empty |
| `asset_tag` | Matrix display name, asset type extraction source | Always preferred when set |

**Recommendation:** Always set both fields. Use `name` for the internal infrastructure hostname (e.g., `sin1-p-swi-feb1`) and `asset_tag` for the Matrix-facing name that follows the naming convention (e.g., `sie50-dh5-dc-01fbs`).

### TenantId (from the `name` cluster prefix)

The `TenantId` column on **Servers**, **NVSwitches**, **EthDevices**, **EthInterfaceLinks**, **IbInventory** and **IbSwitchLinks** is the Nscale **cluster number** `N` from the leading `sys<N>-` prefix of the NetBox `name` — never from `asset_tag`, which carries no cluster prefix. The customer calls this number `TenantId`; it is zero-padded to two digits:

| NetBox `name` | `TenantId` |
| - | - |
| `sys1-wdc1-p-swi-fel1` | `01` |
| `sys2-wdc1-p-swi-fes3` | `02` |
| `sin1-p-swi-feb1` | *(empty)* |

A link takes the `TenantId` of its **start** device (for IbSwitchLinks, the IB switch end). IB switches share fabric designations across clusters — both `sys1-wdc1.L3.1.1` and `sys2-wdc1.L3.1.1` publish as `L3.1.1` — so on the IB tables `TenantId` is what tells them apart. A device whose `name` lacks the `sys<N>-` prefix publishes an empty `TenantId` rather than a guessed one, so a misnamed device shows up as a blank, not as the wrong tenant.

Every one of these tables also carries a `TenantID` column (capital D) with the same value. Microsoft's validator (MTX-2261) reads `TenantID` and Kusto column names are case-sensitive, so both spellings are published until Microsoft confirms which one to keep; the other is then dropped.

***

## 1. Site Setup

**NetBox model:** `dcim/sites`

Every site that should sync to Matrix must be configured with:

| Field | Required | Used For | Example |
| - | - | - | - |
| `slug` | **Yes** | Site identification, `source_cluster` field | `sta1`, `glo1`, `sin1` |
| `name` | **Yes** | DataCenters table vendor data | `Staging Area 1` |
| `description` | **Yes** | `dc_region` / `dc_code` across all tables | `SIE50` |
| `custom_fields.syslog_server` | Recommended | EthDevices syslog IP (overrides derived value) | `192.168.40.97` |

**Critical:** The `description` field becomes the `dc_region` value in every Matrix table. If it is missing, region fields will be empty across Servers, IbSwitches, NVSwitches, and EthDevices.

### Site Prefixes (for Ethernet devices only)

**NetBox model:** `ipam/prefixes`

Each site needs an RFC1918 supernet prefix to derive the `site_id` used in EthDevices and link tables:

| Field | Required | Example |
| - | - | - |
| `prefix` | **Yes** | `10.40.0.0/16` or `10.17.0.0/15` |
| `scope_type` | **Yes** | Must be `dcim.site` |
| `scope_id` | **Yes** | The site's NetBox ID |
| `tenant.slug` | **Yes** (preferred) | `matrix` — this is the primary way the agent identifies the supernet |
| `description` | Fallback | Must contain `rfc1918 supernet` (case-insensitive) if [tenant](../start-here/glossary#tenant) is not set |

The second octet of the prefix becomes the `site_id` (e.g., `10.40.0.0/16` → site\_id `40`). This value drives:

* `SiteId` in EthDevices and EthInterfaceLinks
* `SyslogServerIp` → `192.168.<site_id>.97`
* `SNMPServerIp` → `192.168.<site_id>.135`

***

## 2. Location Hierarchy

**NetBox model:** `dcim/locations`

Locations are used to resolve the **DataHall** for servers and switches. The agent walks the location tree:

* If a location has **no parent** → it IS the datahall
* If the location's parent has **no parent** → the parent is site-level, and the current location is the datahall
* Otherwise → the parent is the datahall

| Field | Required | Example |
| - | - | - |
| `name` | **Yes** | `DH110`, `Row 5` |
| `site` | **Yes** | Must reference the correct site |
| `parent` | Set correctly | Determines datahall resolution |

**Example hierarchy:**

```
Site: STA1
  └── DH110          ← datahall (no parent, or parent is site-level)
       └── Row 5     ← row level (parent = DH110)
```

A device assigned to `Row 5` will resolve datahall = `dh110`.

***

## 3. Device Types

**NetBox model:** `dcim/device-types`

Device types must be fully populated since they provide hardware metadata:

| Field | Required For | Maps To |
| - | - | - |
| `manufacturer.name` | Servers, NVSwitches, EthDevices — not IB Switches/UFM servers | `hw_manufacturer` (Servers, NVSwitches), `switch_sku` component (EthDevices) |
| `model` | All devices | `model` (Servers, NVSwitches), `hardware_type` (IbSwitches), `switch_sku` (EthDevices) |
| `part_number` | IB Switches | `hardware_product_id_sku` |
| `u_height` | IB Switches, IB Switch Links | `u_height`, `start_u_height`, `end_u_height` |
| `custom_fields.gpu_type` | GPU compute servers (Fleet Manager enrolment) | GPUTYPE component of `platform_class` — e.g. `GB300`, `B200`. Enrolment of a `gpu_compute` device **fails** when this is empty. Leave empty on device types with no GPUs; they resolve `NOGPU`. |

**Fleet Manager writes `custom_fields.platform_class` on the device** (not the device type)
during enrolment, once the site's `platform_class_activity_ready` gate is on:
`<GPUTYPE>_<DEVICETYPE>_<FABRICTYPE>_<NN>`, e.g. `GB300_LENOVOHG635NV2_IB_01`.
`resource_class` is deliberately separate: it is operator-set in
`config_context.ironic_settings`, shared by a device type, and customer-facing through Ironic,
Nova/Placement, and SLURM. `platform_class` is the Fleet Manager-owned per-device variant key
for hardware-descriptor lookups: burn-in derives GPU-count and GPU-name test parameters that
a test's params leave unset from it, and a test that needs such a value on a device without
one fails. Threshold and topology selection still use `resource_class`. Fleet Manager writes the key only when the NetBox field is empty
and never overwrites a populated value. Correct a key by clearing
`custom_fields.platform_class` in NetBox and running
`fleetctl netbox backfill-platform-class` for that device. See [enrollment phase 0b](../developer-guide/enrollment-workflow#0b-platform-class)
for the full derivation and write policy.

***

## 4. Devices by Role

### 4a. GPU Compute Servers (→ Servers table)

**Roles:** `gpu-node`, `compute-tray`

| NetBox Field | Required | Matrix Field | Notes |
| - | - | - | - |
| `name` | **Yes** | `node_name` (fallback) | Validation fails without this |
| `asset_tag` | **Yes** (preferred) | `node_name` | Used over `name` when present |
| `serial` | **Yes** | `serial_number` | |
| `status` | **Yes** | — | Must be `active` to sync |
| `site` | **Yes** | `dc_region`, `location` | Via site description |
| `location` | **Yes** | `data_hall`, `location` | Resolves datahall from hierarchy |
| `rack` | **Yes** | `rack_id`, `location` | Rack name, lowercased |
| `position` | **Yes** | `rack_unit` | Rack unit number |
| `oob_ip` | **Yes** | `bmc_ip` | [BMC](../start-here/glossary#bmc)/[IPMI](../start-here/glossary#ipmi) IP address |
| `description` | Recommended | `hw_sku` | First word extracted (e.g., `GB300 NVL72` → `GB300`); falls back to `"unknown"` if empty |
| `device_type.model` | **Yes** | `model` | |
| `device_type.manufacturer` | **Yes** | `hw_manufacturer` | |
| `custom_fields.backend_id` | Recommended | `BackendId` | Text field, two-digit value; publishes `"01"` when empty |

**Interfaces required:**

| Interface Name Pattern | Matrix Field | Priority |
| - | - | - |
| `bmc`, `ipmi`, `xcc`, `mgmt`, `ilo`, `idrac`, `management` | `bmc_mac` | Matched in this order |
| `eth0`, `eth1`, `ib0`, `ib1` (or first with MAC) | `provisioning_mac` | BMC interfaces excluded |
| All others with MAC | `other_macs` | Remaining interfaces |

**If no interfaces exist for a device, the device is skipped entirely.**

The `location` field is built as: `<dc_region>-<datahall>-<rack_id>` (all lowercase, hyphen-joined).

**Back-end fabric (`config_context.ztp`) — Fleet Manager enrolment only.** Fleet Manager derives the FABRICTYPE component of `platform_class` from the device's rendered `config_context.ztp` block, and only for GPU compute devices:

| `ztp` block | FABRICTYPE | Enrolment outcome |
| - | - | - |
| absent | `NORDMA` | proceeds |
| contains an `infiniband` section | `IB` | proceeds |
| present but no `infiniband` section (including an empty `{}`) | — | **fails** with `PLATFORM_CLASS_UNRESOLVABLE`; a guessed fabric would select the wrong hardware descriptor. The RoCE marker is not yet defined. |

CPU baremetal and hypervisor devices (no `gpu_type`) are always `NORDMA` and their `ztp` block is never read.

***

### 4b. InfiniBand Switches and UFM Servers (→ IbSwitches table)

**IB Switch roles:** `be-core-switch`, `be-leaf-switch`, `be-spine-switch`
**Additional filter:** Device name must contain `-ifs-` (e.g., `sin1-p-ifs-bes115`). Devices with the right role but without `-ifs-` in the name are excluded.

**UFM Server role:** `ufm-server`
UFM servers sync to the same IbSwitches table using the same transform. No `-ifs-` naming convention required — role match alone is sufficient.

Both IB switches and UFM servers use the same field requirements:

| NetBox Field | Required | Matrix Field | Notes |
| - | - | - | - |
| `name` | **Yes** | `device_name` (fallback) | |
| `asset_tag` | **Yes** (preferred) | `device_name` | |
| `serial` | Recommended | `serial_no` | Empty string if missing |
| `status` | **Yes** | — | Must be `active` |
| `site` | **Yes** | `region`, `source_cluster` | `region` from site description, `source_cluster` from site slug |
| `location` | **Yes** | `device_location`, `data_hall` | |
| `rack` | **Yes** | `device_location`, `rack_id` | |
| `position` | **Yes** | `device_location`, `rack_unit` | |
| `primary_ip` or `oob_ip` | Recommended | `management_ip` | `primary_ip` preferred, `oob_ip` as fallback |
| `device_type.model` | **Yes** | `hardware_type` | |
| `device_type.part_number` | Recommended | `hardware_product_id_sku` | |
| `device_type.u_height` | Recommended | `u_height` | Set on the device type, not the device |
| `custom_fields.software_version` | Recommended | `firmware_version` | |
| `custom_fields.os_version` | Recommended | `os_version` | |
| `custom_fields.system_guid` | Recommended | `guid` | InfiniBand GUID |
| `custom_fields.backend_id` | Recommended | `BackendId` | Text field, two-digit value; publishes `"01"` when empty |

**Location validation is strict** — if all of `location`, `rack`, and `position` are missing, the device fails validation and is skipped.

The `device_location` field is built by joining `location_name`, `rack_name`, and `position` with `/`. Note that `rack_name` in NetBox can itself contain `/` separators (e.g., `Row 2 / 11`), so the output may have more than 3 visual segments:

* `("DH110", "Row 2 / 11", 42)` → `DH110 / Row 2 / 11 / 42`
* `("DH110", "Rack-05", 10)` → `DH110 / Rack-05 / 10`

***

### 4c. NVLink Switches (→ NVSwitches table)

**Role:** `nvlink-switch`

| NetBox Field | Required | Matrix Field | Notes |
| - | - | - | - |
| `name` | **Yes** | `device_name` (fallback) | |
| `asset_tag` | **Yes** (preferred) | `device_name` | |
| `serial` | Recommended | `serial_number` | |
| `status` | **Yes** | — | Must be `active` |
| `site` | **Yes** | `dc_region`, `device_location` | From site description |
| `location` | **Yes** | `data_hall`, `device_location` | Datahall resolved from hierarchy, lowercased |
| `rack` | **Yes** | `rack_id`, `device_location` | Lowercased |
| `position` | **Yes** | `rack_unit` | |
| `oob_ip` | **Yes** | `bmc_ip` | BMC management IP |
| `primary_ip` | Recommended | `oob_ip_address` | IP extracted without CIDR mask |
| `device_type.model` | **Yes** | `model` | |
| `device_type.manufacturer` | **Yes** | `hw_manufacturer` | |
| `custom_fields.backend_id` | Recommended | `BackendId` | Text field, two-digit value; publishes `"01"` when empty |

**Interfaces required:**

| Interface Name Pattern | Matrix Field | Priority |
| - | - | - |
| `bmc`, `ipmi`, `xcc`, `ilo`, `idrac` | `bmc_mac` | Matched in this order |
| `oob`, `mgmt`, `management` | `oob_mac` | OOB management port |
| `serial`, `console` | `serial_mac_address` | Serial console port |
| All others with MAC | `other_macs` | Comma-separated string |

The `device_location` is: `<dc_region>-<datahall>-<rack_id>` (all lowercase, hyphen-joined).

***

### 4d. Ethernet Devices (→ EthDevices table)

**Roles:** See full list below. Devices with `tenant.slug = "nscale"` are **excluded**.

| Role Category | Slugs |
| - | - |
| Frontend Ethernet | `fe-leaf-switch`, `fe-spine-switch`, `fe-border-leaf-switch`, `fe-core-switch`, `fe-edge`, `ancillary-leaf` |
| 400G Switches | `400g-leaf-switch`, `400g-spine-switch`, `400g-access-switch`, `400g-core-switch` |
| OOB Management | `oob-leaf`, `oob-spine`, `oob-border-leaf`, `oob-core`, `oob-edge` |
| Management | `mgmt-10g-switch`, `ipmi-switch` |
| Console | `console-server`, `console-manager` |
| Firewalls/Routing | `management-firewall`, `web-facing-firewall`, `edge-router` |
| DCI/Storage | `dci`, `mdci`, `vast-switch` |
| Legacy/Lab | `100g-core-switch`, `core-switch`, `lab-core-switch`, `lab-switch`, `office-switch` |

| NetBox Field | Required | Matrix Field | Notes |
| - | - | - | - |
| `name` | **Yes** | `device_name` (fallback) | Validation fails without this |
| `asset_tag` | **Yes** (preferred) | `device_name` | Used over `name` when present |
| `serial` | Recommended | `asset_id` | |
| `status` | **Yes** | `status` | `active` → `InProduction`, others PascalCased |
| `site` | **Yes** | `dc_region`, `dc_code`, `site_id` | Description → region; prefix → site\_id |
| `location` | Recommended | `elevation` | Location name used directly |
| `rack` | Recommended | `rack_id` | |
| `position` | Recommended | `rack_unit` | |
| `primary_ip` or `oob_ip` | Recommended | `management_ip` | `primary_ip` preferred |
| `device_type.model` | Recommended | `switch_sku` | |
| `custom_fields.os_version` | Recommended | `firmware_version` | |

**Asset type extraction:** The agent extracts `asset_type` from the device name or asset\_tag using the regex pattern `-<digits><type>$`:

* `sie50-dh5-dc-01fbs` → `fbs`
* `sie50-dh5-dc-01oobfbs` → `oobfbs`
* `pdx51-m34-p1-01t1` → `t1`
* `pdx51-m34-dc-01cab` → `cab`

If the regex doesn't match, a role-based fallback applies:

* `console-server` / `console-manager` → `console-server`
* `nvlink-switch` → `nvs` for the documented NVLink asset-tag suffixes; otherwise empty

**Static and TBD fields:** The following Matrix fields are set automatically and do not require NetBox data:

* `cloud_type` → always `"public"`
* `asn` → from device custom field `switch_asn`
* `dhcp_gateway` → list from device custom fields `dhcp_primary` and `dhcp_secondary`
* `vlan_id`, `mac_address`, `cluster_id` → always empty (TBD / not sourced from NetBox)

**For `fe-leaf-switch` devices only:**

Two interfaces feed the compute SVI fields, and **`vlan101-104` takes precedence when both
exist**. A switch moves from the single aggregate to the per-[VLAN](../start-here/glossary#vlan) values as soon as
`vlan101-104` is created, so migration is per-device with no coordinated cutover.

* `vlan101-104` — the per-VLAN tenant SVIs. Assign the switch's own address for each VLAN:
  one IPv4 (e.g. a `/27`) and one [IPv6](../start-here/glossary#ipv6) (e.g. a `/64`) per VLAN. Populates all four of
  `compute_ipv4`, `compute_subnet`, `compute_ipv6` and `compute_ipv6_subnet` as
  comma-separated lists, ordered by address.
* `vlan100-116` — the legacy single aggregate. Used **only when `vlan101-104` is absent**,
  and then exactly as before: the lowest non-anycast IPv4 and its subnet, with
  `compute_ipv6` / `compute_ipv6_subnet` left empty.

Mark VRR gateway addresses with IP role `anycast` on either interface — they are skipped, as
they are not the switch's own address. If you create `vlan101-104` but assign only IPv4, the
IPv6 fields stay empty; the IPv4 fields still switch to the per-VLAN list.

Example for a switch with four tenant VLANs on `vlan101-104`:

```
compute_ipv4        10.35.0.29,10.35.0.61,10.35.0.93,10.35.0.125
compute_subnet      10.35.0.0/27,10.35.0.32/27,10.35.0.64/27,10.35.0.96/27
compute_ipv6        2607:6bc0:8112:1010::2,2607:6bc0:8112:1011::2,...
compute_ipv6_subnet 2607:6bc0:8112:1010::/64,2607:6bc0:8112:1011::/64,...
```

**For all Ethernet devices:**

* Create an interface named `lo` with an IPv4 address assigned → populates `loopback_ipv4`

***

## 5. Cables / Links

### 5a. Ethernet Interface Links (→ EthInterfaceLinks table)

**NetBox model:** `dcim/cables`

| Field | Required | Notes |
| - | - | - |
| `type` | **Yes** | Must be a recognized cable type (e.g., `cat6a`, `mmf`, `dac-passive`). Cables without a type are filtered out at the reflector level and never reach the reconciler. The Matrix `link_type` field is always `DeviceInterfaceLink` regardless of cable type. |
| A termination | **Yes** | Must be `dcim.interface` type |
| B termination | **Yes** | Must be `dcim.interface` type |

Both termination interfaces must have:

* `name` set
* Belong to devices with `name` set
* Belong to devices in the **same site**

The `speed` on the A-side interface is converted to `bandwidth_in_gbps` (kbps ÷ 1,000,000).

**Only device-to-device cables are synced.** Cables to patch panels, power outlets, or console ports are ignored.

### 5b. InfiniBand Switch Links (→ IbSwitchLinks table)

Same cable requirements as Ethernet links, plus:

| Field | Required | Notes |
| - | - | - |
| `type` | **Yes** | Cable type: `mmf`, `smf`, `dac-passive`, `dac-active`, `aoc`, etc. |
| Both endpoints | **Yes** | At least one must be an IB switch (role + `-ifs-` naming) |

**Connection types:**

* **IB\_IB**: Both endpoints are IB switches
* **IB\_SERVER**: One endpoint is an IB switch, the other is any non-IB-switch device (typically a `gpu-node` or `compute-tray`, but the agent does not enforce a specific role on the non-switch end)

For IB\_SERVER links, the output is normalized so the IB switch is always on the start side.

Both endpoint devices need `location`, `rack`, and `position` set — the `device_location` field (built by joining these with `/`) is required and the transform fails without at least one of these.

The `speed` on the interface is formatted as `<N>G` (e.g., `400000000` kbps → `400G`).

For IB\_SERVER links, the server's `serial` number (or `name` as fallback) becomes the `hostname` field.

***

## 6. InfiniBand Port GUIDs on RDMA Interfaces

<Note>This is the one requirement on this page the Matrix connector does not read. It is consumed by Fleet Manager's **NetBox sync agent**, which mirrors NetBox devices into Fleet Manager's own inventory and fills `interfaces[].ib_device_guid` from it.</Note>

**The interface `name` selects; the interface `label` carries the value.** The sync agent looks at every interface whose **name** matches `^RDMA[0-9]+$` (case-insensitive — `RDMA0`, `rdma7`, and `Rdma11` all match) and reads the InfiniBand **port GUID** out of that interface's NetBox **`label`**. No custom field is involved: it is the built-in `label` field on the interface. That split between the selecting field and the carrying field is the surprising part of this contract, and it is easy to satisfy one half of it and not the other.

**A host RDMA port named anything else is silently ignored.** Host-side port naming varies by server and NIC vendor, and a switch-style name such as `swp1s0` can never match `RDMA<N>`. A non-matching name yields no GUID, no warning, and no failure, so a whole site can sit at zero GUID coverage with nothing to indicate a problem. Where the names do not match, the interfaces have to be renamed to `RDMA<N>` — there is no alternative source for this value.

### Accepted Label Formats

| Label in NetBox | Stored as `ib_device_guid` |
| - | - |
| `0x0002c903005ac084` — `0x` or `0X` followed by 16 hex digits | `0002c903005ac084` |
| `00:02:c9:03:00:5a:c0:84` — eight colon-separated hex byte pairs | `0002c903005ac084` |

Both forms normalize to **bare lowercase 16 hex digits**, which is the only form Fleet Manager stores. Anything else — the wrong digit count, neither a `0x` prefix nor colons, a GUID with surrounding commentary — is rejected; the two rows above are the complete list of what the sync agent parses. Bare lowercase hex is Fleet Manager's canonical form for a GUID: every accepted spelling collapses to one string, so GUIDs compare and de-duplicate as plain text, and it is the representation the [NVIDIA UFM pkey GUIDs REST API](https://docs.nvidia.com/networking/display/ufmenterpriserestapiv6242/pkey-guids-rest-api) uses in its own examples, so a stored value can be passed to the fabric manager as-is.

<Warning>
  **A port GUID is not a system GUID.** `custom_fields.system_guid` (section 4b) is a per-**device** value on IB switches and UFM servers that syncs to the Matrix `guid` column. The RDMA interface label is a per-**port** value on a compute host — one per HCA port, so eight on a typical GPU node. They are different values with different purposes. Copying a device's system GUID into its interface labels produces eight wrong entries per node that still look populated.
</Warning>

### What Happens When the Label is Missing or Malformed

Nothing fails. Enrolment is unaffected, the interface still syncs, and the device stays usable — it simply carries no GUID:

| Situation | Result |
| - | - |
| `RDMA<N>` interface with an empty label | Interface syncs with no `ib_device_guid`; the agent logs a warning with `ib_guid_gap=missing_label` |
| `RDMA<N>` interface with an unparseable label | Interface syncs with no `ib_device_guid`; the agent logs a warning with `ib_guid_gap=invalid_label` and the rejected text in `interface_label` |
| RDMA port not named `RDMA<N>` | No GUID is read; nothing is logged |

Search the sync agent's logs by **structured field, not by message text** — the wording of a warning is not a contract and changes between releases; the fields are. Every GUID-gap warning carries:

| Field | Value |
| - | - |
| `ib_guid_gap` | `missing_label` (nobody populated the label) or `invalid_label` (somebody populated it wrongly) — the two need different fixes, which is why they are kept apart |
| `interface` | The `RDMA<N>` interface name |
| `interface_label` | The rejected label text; present on `invalid_label` only |
| `site_slug`, `device_name`, `nb_device_id` | The Fleet Manager site slug and the NetBox device the interface belongs to, bound onto every record from the surrounding reconcile |

Filter the agent's stream on `ib_guid_gap` being present and group by `site_slug` and `ib_guid_gap`: that gives coverage gaps per site, by cause, without reading a single message. In Grafana Explore against `product-loki` (add your hub cluster's `cluster` label to the selector):

```logql theme={null}
sum by (site_slug, ib_guid_gap) (count_over_time({namespace="fleet-manager",service_name="fleet-manager-netbox-agent"} | json | ib_guid_gap != "" [1h]))
```

**This gap is silent by default.** No job fails, no device condition is raised, and no alert fires. The two warnings above are the only log output there is, and they exist only for interfaces already named `RDMA<N>` — a port named anything else produces no log line at all. A site whose host ports are all named outside the pattern therefore generates *nothing*: searching the sync agent's logs finds no warning, and that absence does not mean there is no gap. Coverage has to be verified deliberately; a healthy-looking sync says nothing about it.

<Warning>
  **Verify coverage before a fabric partition change, not after.** InfiniBand partition (pkey) membership is keyed on port GUIDs. When a node's GUIDs are missing or only partly populated, some of its HCA ports can be left out of a partition — and on a fabric whose default partition is limited-membership, a port left out is *isolated*, not merely unconfigured. That presents as a customer-visible fabric outage which looks like a cabling or firmware fault, and it has happened. Confirm full coverage across every host in scope first.
</Warning>

### Populating Labels in Bulk

`fleetctl netbox populate-ib-guids` writes the labels from a per-hostname GPU-to-GUID export. It reads the same NetBox configuration as the sync agent (`NETBOX_URL` plus a token, or the [1Password](../start-here/glossary#1password) credential lookup).

The input JSON maps hostname → `RDMA<N>` → that port's `guid` and `os_name`:

```json theme={null}
{
  "sie50-dh6-a01-11-gpu1": {
    "RDMA0": { "guid": "0x0002c903005ac084", "os_name": "mlx5_0" },
    "RDMA1": { "guid": "0x0002c903005ac085", "os_name": "mlx5_1" }
  }
}
```

Each hostname must match exactly one NetBox device `name`. Interfaces are matched on the **integer N** in `RDMA<N>` rather than on the exact string, so an export key of `rdma01` still resolves to NetBox's `RDMA1`. Any additional key inside a port entry is ignored.

```bash theme={null}
# Dry-run (the default): prints the plan and writes nothing
uv run fleetctl netbox populate-ib-guids ./site-ib-guids.json

# Write the labels
uv run fleetctl netbox populate-ib-guids ./site-ib-guids.json --apply
```

Each interface in the plan gets one of three outcomes:

| Existing label | Outcome |
| - | - |
| Empty | **Write** — the GUID is set verbatim (the sync agent normalizes it on read) |
| Already holds the same GUID | **Skip** — no write, so re-running is a no-op |
| Holds anything else, including text that cannot be parsed | **Conflict** — the command refuses to overwrite it |

**The refusal to overwrite is what makes the command safe to re-run**: it can never quietly replace a GUID somebody else put there. A conflict can surface at two points, and they behave differently:

* **Preflight (dry-run and `--apply` alike)** — the plan is built from a first read of every interface. A conflict found here — along with an unknown hostname, a hostname matching more than one NetBox device, a missing `RDMA<N>` interface, a port key that is not `RDMA<N>`, an unparseable GUID, the same GUID on two entries, a duplicated JSON key, two input keys colliding on one RDMA index, or NetBox itself holding two interface names that resolve to one index (`RDMA1` alongside `rdma01`) — aborts the whole run before a single label is written. Fix the input and run it again.
* **Apply time (`--apply` only)** — NetBox has no compare-and-set, so each label is re-read immediately before it is written. If a different value landed there (or the interface disappeared) between preflight and the write, that one interface is **skipped with a warning** and the command carries on with the rest; the summary line reports how many were `conflicting since preflight` and the command exits non-zero. **Labels written before the conflict stay written.** A non-zero exit from `--apply` therefore means a *partial* backfill, not an untouched NetBox — read the summary, resolve the conflict, and re-run; everything already set is a skip on the next pass. If the re-read finds the identical GUID already in place, that counts as already set, not as a conflict.

### Verifying Coverage

Labels reach Fleet Manager on the sync agent's next reconcile. Read a device back from the API and list every interface with its name and GUID:

```bash theme={null}
uv run fleetctl api device get <device-id> --format json \
  | jq '.interfaces[] | {name, type, ib_device_guid}'
```

Every `RDMA<N>` entry should carry 16 hex digits; `null` means the label is absent or was rejected. To list only the interfaces with no GUID:

```bash theme={null}
uv run fleetctl api device get <device-id> --format json \
  | jq -r '.interfaces[] | select((.ib_device_guid // "") == "") | .name'
```

Every name that command prints is an interface with no GUID. Ethernet and BMC interfaces appear in that list as a matter of course; what must not appear is any `RDMA<N>` name — and a host port that shows up under a non-matching name (`swp4s1`, say) is the misnamed-port case from the top of this section, visible here precisely because the command does not filter on the pattern. List *all* interfaces deliberately rather than filtering on the name pattern: a filter on `RDMA<N>` inherits the contract's own blind spot, because a physical port that is absent from NetBox or named outside the pattern (`swp4s1`, say) never appears in the filtered output, and seven valid GUIDs read as a complete node when the hardware has eight ports.

<Warning>
  **A passing check does not prove coverage.** Fleet Manager reads no expected HCA port count for a device — the sync agent does not consult NetBox device-type interface templates, and no Fleet Manager field records how many ports a node should have — so the output above can only be judged against a count you bring yourself. Compare the number of `RDMA<N>` interfaces carrying a GUID against the node's physical HCA port count: the GPU-to-GUID export for that hardware (one entry per port the node itself reported), or the node directly (`ibstat`, or the entries under `/sys/class/infiniband/`). A shortfall means a port is missing from NetBox or is named outside the pattern; both are fixed in NetBox, not in Fleet Manager.
</Warning>

In NetBox itself, the equivalent check is that each host has as many `RDMA<N>` interfaces as it has HCA ports and the **Label** column is populated on every one of them.

<Note>Documenting this contract does not populate anyone's NetBox. Backfilling a site still needs a GPU-to-GUID export for that hardware to exist in the first place, collected from the nodes themselves.</Note>

***

## 7. RoCE Back-end Ports for the VRF Flip

<Note>Like section 6, the Matrix connector does not read this. It is the contract for Fleet Manager's back-end VRF flip, which is being built (spec 0064): which leaf sub-ports will move between the customer and maintenance VRFs when a host is isolated for repair. Today `fleetctl netbox be-port-plan` checks a host against it.</Note>

A host's back-end ports are selected by **name**, never by MAC address or interface type. Each site sets its naming rules in `TEMPORAL_WORKER_BACKEND_PORT_SHAPE` (the satellite chart's `temporalWorker.backendPortShape`); Fleet Manager ships no default, and a site that sets none is refused before NetBox is read. The example below is GSO1's (XE9712 GB300 trays on SN5610 back-end leaves); the back-end VRF runbook has its JSON.

| Rule | GSO1 setting | What it means in NetBox |
| - | - | - |
| Host back-end port name | `eth_p{plane}_r{rail}`, plane and rail 1–4 | One interface per physical function (PF). Virtual functions (`eth_r{n}_vf0`) and RoCE LAGs (`roce_r{n}`) do not match and are never selected. |
| Ports per host | 16, forming the full rail × plane grid | Every host has exactly one PF per rail and plane. |
| Leaf name | contains `-bel{rail}-su{N}-pl{plane}` | Each PF reaches the leaf of its own rail and plane. All of a host's leaves are in one scalable unit (SU). A switch whose name does not match, such as a front-end ToR, is never selected. |
| Leaf port | a breakout sub-port `swp1s0`–`swp32s3` | The PF reaches the sub-port, not the cage (`swp5`), which serves several hosts. The sub-port is not a LAG member. |
| Leaf management address | `oob_ip`, or `primary_ip` when `TEMPORAL_WORKER_BACKEND_SWITCH_API_ADDRESS_FIELD=primary_ip` | Set on every leaf device. |

**Each PF's leaf comes from the `netbox_datacenter` plugin's fibre graph.** A PF reaches its leaf through passive shuffle cassettes, which NetBox's own cable trace cannot cross, so GSO1 records no native cable for a back-end PF. Fleet Manager reads the plugin's `fiber_graph` interface peer mapping instead, one request per host, and needs `view` on `netbox_datacenter.inventoryinfointerfacepeermapping` to do so. Each PF must have exactly one `mapped` row naming the leaf's interface. A native cable on a PF is not required; if one exists and ends directly on an interface, it must be the mapped one. The plugin recomputes the mapping on its own schedule, and Fleet Manager does not check how old it is.

The `be-meta` and `be-nscale` VRFs are created on every leaf by network engineering. Fleet Manager never creates or deletes a VRF.

### Checking a host

`fleetctl netbox be-port-plan` reads NetBox only and touches no switch. Run it with the site's worker settings, including its port shape:

```bash theme={null}
uv run fleetctl netbox be-port-plan sys1-gso1-p-phy-gpu1-su22
```

On a correctly modelled host it prints 16 rows (PF, rail, plane, leaf, leaf port, management address) and the SU, and exits 0. Otherwise it exits 1 and lists **every** defect at once, one line per PF, so an import can be fixed in one pass.

| NetBox state | Result |
| - | - |
| A PF missing, or a cell of the grid empty | Refused: count and empty cells named |
| Two names mapping to one rail and plane | Refused: both names listed |
| A PF with no fibre-graph mapping row, or more than one | Refused for that PF, naming the rows |
| A mapping row that is `unmapped`, `conflict` or `skipped`, or names no leaf interface | Refused for that PF, naming the status and revision |
| A native cable ending directly on an interface other than the mapped one | Refused for that PF, naming both |
| A PF mapped to another rail's or plane's leaf | Refused, naming both |
| Leaves in two SUs | Refused, listing the leaves per SU |
| A cage, a port outside `swp1`–`swp32`, a LAG member, or a management or uplink port | Refused for that PF |
| A leaf with no management address | Refused for that PF |

A refusal is fixed in NetBox, not in Fleet Manager.

***

## 8. Quick Reference Checklist

Use this when adding a new device to NetBox:

### Before adding any device:

* [ ] Site exists and has `description` set (e.g., `SIE50`)
* [ ] Site is in `CONNECTOR_SITE_FILTER`
* [ ] Location hierarchy exists (datahall → row) with correct parent relationships
* [ ] Device type exists with `model` populated (`manufacturer` too, for Servers/NVSwitches/EthDevices — not required for IB switches/UFM servers)

### For every device:

* [ ] `name` is set
* [ ] `asset_tag` is set (preferred display name in Matrix)
* [ ] `status` = `active`
* [ ] `role` is one of the supported roles (see sections 4a-4d)
* [ ] `site` is assigned
* [ ] `location` is assigned (for datahall resolution)
* [ ] `rack` is assigned
* [ ] `position` (rack unit) is set
* [ ] `serial` number is set

### For GPU compute servers (`gpu-node`, `compute-tray`):

* [ ] `oob_ip` is set
* [ ] The device type's `custom_fields.gpu_type` is set (e.g. `GB300`) — enrolment fails without it
* [ ] `config_context.ztp` is either absent or carries an `infiniband` section — any other shape fails enrolment
* [ ] `description` starts with hardware SKU (e.g., `GB300 NVL72`)
* [ ] At least one interface exists with a MAC address
* [ ] BMC interface exists (named `bmc`, `ipmi`, `xcc`, `mgmt`, `ilo`, `idrac`, or `management`)
* [ ] Primary network interface exists (named `eth0`, `eth1`, `ib0`, or `ib1`) with MAC
* [ ] Every RDMA port is named `RDMA<N>` and its interface `label` holds that port's IB GUID (section 6)
* [ ] (optional) `custom_fields.backend_id` is set — publishes `"01"` when left empty

### For IB switches (`be-core-switch`, `be-leaf-switch`, `be-spine-switch`):

* [ ] Device name contains `-ifs-`
* [ ] `primary_ip` or `oob_ip` is set
* [ ] `device_type.part_number` is set (SKU)
* [ ] `device_type.u_height` is set on the device type
* [ ] `custom_fields.software_version` is set (firmware)
* [ ] `custom_fields.os_version` is set
* [ ] `custom_fields.system_guid` is set (InfiniBand GUID)
* [ ] (optional) `custom_fields.backend_id` is set — publishes `"01"` when left empty

### For UFM servers (`ufm-server`):

* [ ] Same as IB switches, but no `-ifs-` naming requirement

### For NVLink switches (`nvlink-switch`):

* [ ] `oob_ip` is set (BMC IP)
* [ ] `primary_ip` is set (OOB management IP)
* [ ] Interfaces created: `bmc`/`ipmi`, `oob`/`mgmt`, `serial`/`console` with MACs
* [ ] (optional) `custom_fields.backend_id` is set — publishes `"01"` when left empty

### For Ethernet devices:

* [ ] Device is NOT tenanted to `nscale`
* [ ] Site has an RFC1918 supernet prefix (e.g., `10.40.0.0/16`) with `tenant.slug = "matrix"`
* [ ] `asset_tag` or `name` follows naming convention `<site>-<rack>-<loc>-<num><type>` for asset type extraction
* [ ] `custom_fields.os_version` is set (firmware version)
* [ ] (fe-leaf-switch only) Interface `vlan101-104` exists with the per-VLAN IPv4 **and** IPv6 SVI addresses — takes precedence over `vlan100-116`
* [ ] (fe-leaf-switch only) …or, if not yet migrated, interface `vlan100-116` exists with IPv4 addresses (legacy single-value output, no IPv6)
* [ ] (fe-leaf-switch only) VRR gateway addresses carry IP role `anycast` so they are excluded
* [ ] Interface `lo` exists with IPv4 address (for loopback)

### For cables:

* [ ] Both ends terminated on `dcim.interface` (not patch panels/power/console)
* [ ] Both endpoint interfaces have `name` set
* [ ] Both endpoint devices have `name` set
* [ ] Both endpoint devices are in the same site
* [ ] Cable `type` is set (required — untyped cables are not synced. E.g., `cat6a`, `mmf`, `smf`, `dac-passive`)
* [ ] Interface `speed` is set on at least the A-side (for bandwidth calculation)
* [ ] (IB links only) At least one endpoint is an IB switch with `-ifs-` in the name
* [ ] (IB links only) Both endpoints have `location`, `rack`, and `position` set

***

## 9. What Happens When Data is Missing

| Missing Field | Impact |
| - | - |
| `device.name` | **Device skipped** — validation error |
| `device.status` != `active` | **Device skipped** — filtered out |
| `device.site` | **Device skipped** — no site reference |
| Site not in `CONNECTOR_SITE_FILTER` | **Device skipped** — not polled |
| No interfaces (Servers) | **Device skipped** — logged as warning |
| No interfaces (NVSwitches) | Device synced with empty MAC fields (`bmc_mac`, `oob_mac`, etc.) |
| Location + rack + position all missing (IB Switches) | **Device skipped** — validation error |
| `device.description` (GPU servers) | `hw_sku` = `"unknown"` |
| `site.description` | `dc_region` = empty string |
| `device.serial` | `serial_number` = empty string |
| `device.asset_tag` | Falls back to `device.name` |
| `device.oob_ip` | `bmc_ip` = empty string |
| `device.rack` | `rack_id` = empty string, incomplete `location`/`device_location` |
| `device.position` | `rack_unit` = null |
| Interface MACs missing | `bmc_mac`, `provisioning_mac`, `oob_mac` = empty string |
| `RDMA<N>` interface `label` empty or unparseable (section 6) | Interface syncs with no `ib_device_guid` — warning only, nothing fails |
| `device_type.custom_fields.gpu_type` on a `gpu_compute` device | **Enrolment fails** — `PLATFORM_CLASS_UNRESOLVABLE`; Matrix sync is unaffected |
| `config_context.ztp` present without an `infiniband` section (GPU compute) | **Enrolment fails** — `PLATFORM_CLASS_UNRESOLVABLE`; Matrix sync is unaffected |
| RDMA port not named `RDMA<N>` (section 6) | No GUID read for that port — **no warning**, nothing fails |
| RFC1918 prefix missing (EthDevices) | **Device skipped** — validation error |
| Cable endpoint missing name | **Link skipped** — validation error |
| Cross-site cable | **Link skipped** — validation error |

***

## 10. Connector Configuration

The connector is configured through the Fleet Manager hub chart (`matrixConnector` values), which renders these environment variables:

```
CONNECTOR_SITE_FILTER=["sin1"]                             # NetBox site slugs this connector may ingest (required, fail-closed)
CONNECTOR_SITE_SLUG_MAP={"sin1": "start-campus-1"}         # NetBox slug -> FM Site.slug for every site_filter entry (required)
CONNECTOR_POLL_INTERVAL_SECONDS=150                        # Polling backstop cadence (sweeps every scoped site)
CONNECTOR_NETBOX_DIRECT_ENABLED=true                       # NetBox-direct feed for EthDevices / IbInventory
CONNECTOR_NETBOX_DIRECT_KEEPALIVE_INTERVAL=21600           # NetBox-direct keep-alive re-stamp (default: 6 hours) — refreshes 7-day staging rows
AZURE_KUSTO_CLUSTER_URL=...                                # Primary Kusto cluster
AZURE_KUSTO_DATABASE=...                                   # Primary Kusto database
AZURE_KUSTO_MIRRORS=[...]                                  # Best-effort mirror destinations (e.g. the customer dev DB)
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.