# Network design repository

> The network design as text in a repository, reviewed like code, compared with what the devices really say, and tested: intents the design must keep, what-if failures, and a merge gate.

Source: https://zaryn.xyz/docs/en/network-design

## What it is

A **network design repository** is the network's source of truth, what it is supposed to be: its sites, the devices in them, their ports, the links between them, the circuits a provider sells you and the ducts those circuits run through. It is plain text (`.zevtopo` files), versioned like code: every change is a commit with an author and a reason, reviewed in a pull request before it's accepted.

Zaryn adds what a drawing or a spreadsheet can't: the design is **checked against reality**. Linked to a [site](/docs/en/network-sites), it is compared with what the site's devices report about themselves (their neighbors, addresses and ports), so the documentation can't quietly drift away from the network.

## The problem it solves

Every network has a design somewhere: a Visio file, a wiki page, a spreadsheet of cables. Within months it is wrong. A device was added in a hurry, a link moved to another port, a "temporary" connection became permanent. Nobody notices until an outage, when the diagram everyone trusts sends the engineer to the wrong rack.

The other half of the problem is hidden risk. Two links can look redundant on a diagram and still fail together because they share a fiber duct, a conduit under one bridge, or one provider's backbone. A diagram doesn't know that; a design that names it can.

Zaryn treats the three views of a network as one loop:

| | What it is | Where |
|---|---|---|
| **Design** | what the network should be | this repository |
| **Reality** | what the devices say it is | [Sites](/docs/en/network-sites) |
| **Prediction** | what a change would do | the [digital twin](/docs/en/network-twin) |

```diagram
type: flow
title: Three views of one network, kept in step
loop: every merged change updates the design, every device report checks it
---
Design | What the network should be: sites, devices, links, circuits | accent | 📐
~ compared with
Reality | What the devices say: neighbors, addresses, ports | good | 📡
~ predicts
Prediction | What a change would do, before anyone makes it | warn | 🔮
```


## Import a running site

You don't have to write a design from nothing. If the network is already a site on Zaryn, import it:

```bash
zev clone <you>/network-design && cd network-design
zev topo import <owner>/<site>
zev add topology && zev commit -m "Design from the live network" && zev push
```

`zev topo import` writes `topology/<site>.zevtopo` with every device of the site (name, vendor, model, management address) and every link the devices see (with the ports on both ends). On the site's **Map** tab, **Download the site as .zevtopo** gives the same file.

Then make it a design: give devices their roles, name the circuits, mark what links share.

## The .zevtopo format

`.zevtopo` is TOML-like: blocks that start with `[[kind]]`, then `key = value` lines. `#` starts a comment.

```toml
[[site]]
name = "thr-dc1"
address = "Frankfurt, data center 1"
lat = 35.70
lon = 51.40

[[device]]
name = "core1"
vendor = "cisco"
role = "core"
site = "thr-dc1"
model = "C9500-48Y4C"
mgmt = "10.0.0.1"
owner = "sara"

[[interface]]
device = "core1"
name = "Te1/0/1"
lag = "Po1"
ip = "10.1.12.1/30"
description = "to core2"

[[link]]
a = "core1"
b = "core2"
a_if = "Te1/0/1"
b_if = "Te1/0/1"
kind = "fiber"
capacity = 10
weight = 10
provider = "TIC"
circuit = "TIC-4411"
srlg = ["duct-north", "valiasr-bridge"]

[[vlan]]
id = 10
name = "users"
subnet = "10.10.0.0/24"

[[static_route]]
device = "core1"
network = "0.0.0.0/0"
next_hop = "10.255.0.1"
```

| Block | Keys |
|---|---|
| `[[site]]` | `name`, `address`, `lat`, `lon` |
| `[[device]]` | `name`, `vendor`, `role` (core, distribution, access, edge…), `site`, `model`, `mgmt` (the management address), `owner`, `lat`, `lon`, `mtbf` and `mttr` (hours, see [Availability](#availability-capacity-and-disjoint-paths)) |
| `[[interface]]` | `device`, `name`, `ip`, `description`, `lag` (the port channel it belongs to), `vlan` |
| `[[link]]` | `a`, `b`, `a_if`, `b_if` (the ports), `kind` (fiber, copper, wireless, vpn, virtual), `capacity`, `weight`, `provider`, `circuit`, `srlg` (shared risk groups: a list), `mtbf`, `mttr` |
| `[[srlg]]` | `name`, `mtbf`, `mttr`: a shared risk group's own failures (a duct cut, a building losing power) |
| `[[vlan]]` | `id`, `name`, `subnet`, `description` |
| `[[static_route]]` | `device`, `network`, `next_hop`, `distance` |

Every key except a device's `name` and a link's `a` and `b` is optional. Files written before these keys existed read exactly as before.

### One design, many files

Every `.zevtopo` file at the root of the repository or anywhere under `topology/` is part of the design, read in name order as one. Split it the way your team works: one file per site (`topology/thr-dc1.zevtopo`, `topology/mhd-pop.zevtopo`), or one for devices and one for circuits. A link may join devices from two files.

## Edit it: code and canvas

The repository's **Design editor** tab shows the design twice, side by side: the `.zevtopo` files in a code editor and the network drawn on a canvas. Both are the same design, live:

- **Type in the file** and the canvas redraws as you type. Devices you already placed stay where they are; new ones find a spot next to what they connect to.
- **Change the canvas** and the file is edited in place: only the lines of that device or link change, so your comments, order and spacing stay as you wrote them.
- **Put the cursor inside a block** and that device or link is selected on the canvas; **select something on the canvas** and its lines are marked in the file (switching to the file it lives in).

On the canvas:

| Do | How |
|---|---|
| Add a device | **+ Device**, or `n` |
| Link two devices | select one, then shift-click the other; or `l`, then click the other end |
| Edit details | the inspector under the canvas: vendor, role, site, model, management address for a device; ports, weight, capacity, kind, provider, circuit and shared risk groups for a link |
| Rename a device | its name in the inspector; every link, interface and static route naming it is renamed with it, in every file |
| Remove | `Delete`; a device goes with the links, interfaces and static routes that name it |
| Arrange | drag devices (only the picture moves; positions aren't saved in the design) |

Single points of failure and critical links are worked out again on every change, so a link you add shows at once whether it removed one. Under the editor, mistakes are listed with their line: a line that isn't `key = value`, a text value without its closing quote, a device declared twice, a link to a device with no `[[device]]` block, a weight that isn't a number. Click one to jump to it.

**Saving.** **Save** (or `Ctrl S`) commits the changed files from the browser, signed with your browser key like any web commit. The default is to **propose it as a pull request**: a new branch, then the pull request page, where the network checks and [intents](#intents) run before anything reaches the branch. **Commit straight to** the branch is there for repositories that don't need review. If someone saved to the branch after you opened it, nothing is overwritten: your work stays as a draft, you reload and restore it. Unsaved work is kept as a draft in this browser, and offered back when you open the editor again.

**+** above the editor adds a file under `topology/` (see [One design, many files](#one-design-many-files)); new devices and links go to the file that's open.

```diagram
type: flow
title: From an edit to a certified change
loop: the next change starts from this design
---
Edit | In the file or on the canvas, both stay in step | accent | ✎
~ commit
Pull request | A branch with the changed `.zevtopo` lines | | ⎇
~ on every change
Checks | The graph, then every intent | warn | ✓
~ if they hold
Merge | The design on the target branch changes | good | ⇄
? Blocked | An intent that held no longer does: the pull request names the failure | bad | ✕
~ Premium
Certificate | Signed, linked to the one before | accent | ✍
```

## Views

The repository's **Network overview** draws the design four ways:

- **Logical**: devices and links, with single points of failure and critical links (Tarjan's articulation points and bridges).
- **Sites**: devices grouped by their site, so the links between sites (your WAN) stand out.
- **Shared risk**: links colored by their shared risk group, and a list of which links fail together. Two "redundant" links in one color are one failure away from both going down.
- **Design and reality**: the design drawn over what the linked site reports (below).

Click a device to see its model, site, management address, ports and the circuits on its links.

## Design and reality

On a site, **Collector and settings** → **Design repository** links the design (you need to be an admin of the site and of the repository). From then on Zaryn compares them, both on the repository's Network overview (**Design and reality**) and on the site's Map tab:

- **Designed, not on the network** (dashed red): a device no collector reaches, a link no device sees. Not built yet, or broken.
- **On the network, not in the design** (dotted amber): a device or link the design doesn't have. Added without updating the design, or not allowed to be there.
- **Disagreements**: the same device with another vendor, management address or name; the same link on other ports than designed.

Devices are matched by name (ignoring case and domain: `CORE1.lab` is `core1`) or by management address; links by the two devices, then by ports. Reality comes from what the devices said last (CDP, LLDP, MikroTik neighbors, OSPF and BGP peers, shared subnets), so a link both sides keep quiet about can't be seen.

Seeing a comparison needs both: reading the repository and seeing the site. A teammate who only sees the site sees that a design is linked, not what's in it.

## Intents

A diagram can look redundant and not be. An **intent** is a promise about the design written as one line of text, like "HQ stays connected to the data center if any single duct is cut". Zaryn checks every intent on the design, shows the failures that break it, and refuses to merge a pull request that breaks one that held.

Intents live in `.zevintent` files next to the design: at the root of the repository or under `topology/`, read in name order as one.

```text
# topology/wan.zevintent
links     = survive site:hq site:dc link
ducts     = survive site:hq site:dc srlg
hq-to-dc  = max-hops hq1 dc2 3
no-spof
```

| Line | Holds when |
|---|---|
| `reach A B` | A and B are connected |
| `survive A B [link\|srlg\|device\|any]` | A and B are still connected after any single failure of that kind (`any` by default) |
| `max-hops A B N` | the shortest path between them is at most N links |
| `no-spof` | no single device failing splits the network |
| `no-bridge` | no single link failing splits the network |
| `no-shared-risk` | no single shared risk group (a duct, a building, a provider) failing splits the network |
| `availability A B P` | A reaches B at least P percent of the time (Premium, [below](#availability-capacity-and-disjoint-paths)) |
| `capacity A B N [link\|srlg\|device\|any\|none]` | A can send B at least N, also after any single failure of that kind (Premium) |
| `disjoint A B [link\|device]` | there are two paths that share no link, or no device in between (Premium) |

A and B are a device name, or `site:NAME` for any device of that site. A name before ` = ` is optional; without one the line is its own name. `#` starts a comment.

### What a failing intent shows

Not "failed", but the failure that does it:

```text
ducts: 1 single failure(s) cut site:hq off from site:dc
  shared risk group duct-north (2 links) → site:hq and site:dc are cut apart
```

Two circuits that look redundant, both labeled with the same `srlg`, are one cut away from both going down. A link failure alone would not have shown it, which is why `survive … srlg` exists. In the API every sentence also comes as a template with `{0}`, `{1}`… and its values (`parts` on a result, `effect_t` and `effect_a` on a failure), so a client can say it in another language; this page does so in Persian.

```diagram
type: graph
title: Redundant on the drawing, one cut in practice
alt: Two sites joined by two circuits that both run through the same duct; when the duct is cut both circuits go down and the sites are cut apart
legend: down=Circuit cut
start: Two circuits
caption: Two circuits, two ports, two providers' labels. The drawing says redundant.
---
box Headquarters | 3 8 38 40 | accent
box Data center | 59 8 38 40 | good
node hq1 | hq1 | 14 22 | router | | Core router at headquarters, end of circuit TIC-1
node hq2 | hq2 | 14 40 | router | | Second core router, end of circuit TIC-2
node dc1 | dc1 | 86 22 | router | | Data center router
node dc2 | dc2 | 86 40 | router | | Data center router
link hq1 hq2
link dc1 dc2
link hq1 dc1 | TIC-1 | | duct-north
link hq2 dc2 | TIC-2 | | duct-north
step The design names the duct | Both circuits carry `srlg = ["duct-north"]`: the design knows what the drawing doesn't.
note 50 53 | both circuits: duct-north | warn
step One cut in the north duct | A backhoe, one cut, two circuits down: the sites are apart. `survive site:hq site:dc srlg` finds this before it happens.
link hq1 dc1 | down
link hq2 dc2 | down
node dc1 | bad
node dc2 | bad
note 50 53 | duct-north cut: 2 circuits, 1 failure | bad
```

### Failure playground

On the repository's **Topology analysis** page, the **Intents** tab (key `4`) lists every intent and has a **What if these fail?** box: pick shared risk groups, links and devices, and the graph shows what is left. It lists the pieces the network falls into, the devices cut off, and the intents that stop holding. Each failure under a failing intent has a **try it** button. The same answers are at `GET /v1/repos/<owner>/<repo>/topology-failure?down=srlg:duct-north,device:core2,link:hq1~hq2` (a link by its two ends, or by its number from the response).

### On pull requests

Every pull request that changes the design is checked against the intents:

- **The target branch's intents are the contract.** Deleting an intent in a pull request doesn't excuse it; the target's still apply, and intents the change adds apply as well.
- An intent that held and no longer does **blocks the merge**, and the pull request says which failure breaks it. A new intent the design doesn't meet is the change's to fix.
- An intent that was already failing on the target branch is a **warning**: it's debt, not something this change did.
- A repository admin can still merge anyway; that is recorded.

The pull request also **draws the change**: under *Network topology impact*, the design before and after as one graph, with devices and links that were added in green, removed in dashed red, and changed in amber. Click one to see what changed (for example `circuit: TCI-4411 → TCI-9000`, or `role: distribution → core`).

```diagram
type: flow
title: What the target branch's intents decide
---
Pull request | Changes the design | accent | ⎇
~ checked against
Intents | The target branch's, plus the ones this change adds | | ✓
~ each one
Held before | Still holds: nothing to say | good | ✓
? Held, now broken | Blocks the merge and names the failure that does it | bad | ✕
? Already failing | A warning: debt, not something this change did | warn | !
```

### Limits

- The check runs on the design graph: links and devices are up or down. It knows nothing of routes, ACLs or capacity; [Path Explain](/docs/en/network-paths) and [the twin](/docs/en/network-twin) check those against the real configs.
- `survive`, `no-spof`, `no-bridge`, `no-shared-risk` and `capacity` try one failure at a time; `availability` tries one and two. The playground takes any combination, and shows whether `capacity` (with what is still up) and `disjoint` still hold.
- Shared risk is only what you wrote down. A duct no `srlg` names is invisible to the check, so list the circuits of one duct with the same group.
- Designs of up to 5,000 devices.

## Availability, capacity and disjoint paths

"Redundant" says two paths exist. It doesn't say how often the service is down, whether the backup can carry the traffic, or which two paths to build. Three Premium intents answer those, on the same design, in the same pull request check:

```text
# topology/campus.zevintent
up         = availability edge dc 99.95
uplinks    = capacity edge dc 10000 link
two-ways   = disjoint edge dc device
```

### Availability

`availability A B 99.95` holds when A reaches B at least 99.95% of the time. Each device and link has a **mean time between failures** and a **mean time to repair** in hours (`mtbf`, `mttr`), so it is down `mttr / (mtbf + mttr)` of the time. A shared risk group with its own `[[srlg]]` block fails on its own too, taking all its links at once:

```diagram
type: stats
title: What the nines mean in a year
caption: Between 99.9% and 99.99% is the difference between a working day lost and a lunch break. Availability is the share of the time A can reach B.
---
99% | Two nines | about 3 days 15 hours down | 100 | bad
99.9% | Three nines | about 8 hours 46 minutes | 73 | warn
99.99% | Four nines | about 53 minutes | 47 | accent
99.999% | Five nines | about 5 minutes | 19 | good
```

```toml
[[device]]
name = "core1"
mtbf = 200000   # about 23 years
mttr = 4        # spare on site

[[link]]
a = "edge"
b = "core1"
srlg = ["riser"]
mtbf = 50000
mttr = 8

[[srlg]]
name = "riser"
mtbf = 50000    # a riser cut every ~6 years
mttr = 48       # two days to re-pull fiber
```

Zaryn finds every single failure and every pair of failures that cuts A from B, adds up how often each happens, and reports the availability, the expected minutes down a year, and the cuts that cost the most, with **try it** for each. A device or link without figures gets 150,000 h / 4 h (device) or 50,000 h / 8 h (link), and the result says how many used them.

How it's worked out: failures are independent and cuts are added up (the rare-event approximation). Three failures at once are left out; overlapping cuts are counted twice, so the figure errs on the low side. On designs too big to try every pair, only single failures count and the result says so.

### Capacity under failure

`capacity A B 10000 link` holds when A can send B at least 10000 (in the units of the links' `capacity`) with nothing failed, and still after any single link fails. The kind is `link`, `srlg`, `device`, `any` (the default) or `none` (only with everything up). It is a max flow over the links' capacities in both directions; a link without a `capacity` counts as unlimited, and the result says how many there are. The answer names the worst single failure and what it leaves: `the worst (link edge core1) leaves 10000`.

```diagram
type: graph
title: Capacity under failure: two uplinks, one riser
alt: Edge reaches the data center through two cores with 10000 on each uplink; one uplink cut leaves 10000, the riser cut leaves nothing
legend: path=Uplink to core1; path2=Uplink to core2; down=Cut
start: Nothing failed
caption: 20000 gets through: 10000 on each uplink, with room to spare beyond the cores.
---
node edge | edge | 10 30 | router | accent | The access switch the users are on
node core1 | core1 | 45 14 | router | | 40000 toward the data center
node core2 | core2 | 45 46 | router | | 40000 toward the data center
node dc | dc | 88 30 | server | good | Where the users' traffic goes
link edge core1 | 10000 | path | riser
link edge core2 | 10000 | path2 | riser
link core1 dc | 40000 | path
link core2 dc | 40000 | path2
step One uplink cut | Lose the uplink to core1: 10000 still gets through. `capacity edge dc 10000 link` holds.
link edge core1 | down
link core1 dc | dim
step The riser is cut | Both uplinks climb the same riser: one cut takes both and nothing gets through. `capacity edge dc 10000 srlg` fails, before anyone pulls a cable.
link edge core2 | down
link core2 dc | dim
node dc | bad
note 66 30 | 0 gets through | bad
```

### Disjoint paths

`disjoint A B link` holds when there are two paths from A to B that share no link; `device` also keeps them apart at every device in between. The answer is the proof: the two paths, the pair with the least total `weight` (Suurballe's algorithm, which finds the pair when the shortest path alone would block the second). **show on the graph** draws them in two colors. If both paths cross the same shared risk group, it says so: two link-disjoint paths in one duct are one cut away from both going down, which `survive A B srlg` catches.

```diagram
type: graph
title: Why the pair is not just "shortest, then next shortest"
alt: The cheapest single path from s to t crosses the middle link and leaves no second path; the cheapest disjoint pair uses the two outer routes
legend: path=First path; path2=Second path; ghost=Not used
start: The shortest path first
caption: The shortest path, s-x-y-t, costs 3. Take it, and every other way from s to t shares a link with it: no second path is left.
---
node s | s | 8 30 | user | accent
node x | x | 38 12 | router
node y | y | 62 48 | router
node t | t | 92 30 | server | good
link s x | 1 | path
link x y | 1 | path
link y t | 1 | path
link x t | 5 | dim
link s y | 5 | dim
step The cheapest pair | Suurballe's algorithm finds the pair that shares nothing and costs least together: s-x-t plus s-y-t, 12 in all.
link x y | ghost
link x t | path
link s y | path2
link y t | path2
```

### On Free

On a repository whose owner isn't on Premium, these three intents are listed as waiting for Premium. They are not failures and never block a merge; the rest of the intents are checked as usual.

## Resilience, and the links that would help

The network overview scores the design: **resilience** is the share of single failures, one device or one link at a time, that cut nobody off. For each failure that does, it says who is cut off, worst first (`core1 fails: cuts off 3 devices: dist1, acc1, acc2`). **Show the heat** colours every device by how much its own failure would cut off, from amber to red.

Then it looks for the fix. It tries every link the design doesn't have yet, and ranks them the way an engineer would choose: what each one removes first (single points of failure and critical links), a short run before a long one (same site before a new circuit between sites), and never two leaf devices wired to each other. The best few are shown with what they remove. **Add it in the editor** opens the [editor](#edit-it-code-and-canvas) with that link already added, ready to review, save on a branch and open as a pull request, where the [intents](#intents) and the twin check it.

```diagram
type: graph
title: The best link to add
alt: core1 is a single point of failure for dist1 and its two access switches; adding a link from core2 to acc1 gives them a second way out, so core1's failure no longer cuts them off
legend: down=Single point of failure; new=The suggested link
start: Today
caption: Point at a device. Today core1's failure cuts off dist1, acc1 and acc2: they all hang on it.
height: 46
---
node core1 | core1 | 30 8 | router | bad | A single point of failure: three devices hang on it
node core2 | core2 | 70 8 | router
node dist1 | dist1 | 30 24 | switch | warn
node acc1 | acc1 | 18 40 | switch | warn
node acc2 | acc2 | 42 40 | switch | warn
link core1 core2
link core1 dist1 | | down
link dist1 acc1
link dist1 acc2
link core2 acc1 | | ghost
step Add core2 ⟷ acc1 | The suggestion: one link at the same site. core1 is no longer a single point of failure, and core1 — dist1 and dist1 — acc1 are no longer critical links.
link core2 acc1 | new | suggested
link core1 dist1 | normal
node core1 | normal
node dist1 | normal
node acc1 | good
```

## The design over time

**The design over time** compares today's design with any of its last commits. Move the slider back and the graph shows what changed since then: devices and links added in green, removed in dashed red, changed in amber, with the commit, who pushed it and when. **Play** walks from the oldest of them to now. It's the quickest way to answer "what did we change since the outage?"

## Copy for an AI agent

**Copy for an AI agent (Markdown)** puts the design on the clipboard as a few lines of Markdown: the devices and links as tables, the resilience score, the single failures that cut devices off and the suggested links. Paste it into Claude Code, Codex or a ticket: a fraction of the tokens of the `.zevtopo` files, with the analysis already done. Agents connected through [`zev mcp`](/docs/en/automation-and-agents) can also read a design's intents and failures themselves.

## Run the design as a lab

A design that has never run is a hypothesis. The Network overview's **Download .clab.yml** turns the design into a [Containerlab](https://containerlab.dev) topology (or, with **Download .gns3**, a [GNS3 project](#in-gns3)), so you can deploy it in containers or VMs, ping across it, pull a link, and see what your intents and failure playground predicted actually happen.

```diagram
type: flow
title: From a design to a lab
loop: change the design, run it again
---
Design | The `.zevtopo` files in the repository | accent | 📐
~ Download .clab.yml
Lab file | Nodes by vendor, links with their ports | | 📄
~ containerlab deploy
Running lab | Containers or VMs wired as the design says | good | ▶
~ ping, pull a link
What happens | Compare with your intents and the failure playground | warn | 🔬
```

```bash
containerlab deploy -t my-network.clab.yml
```

Also at `GET /v1/repos/<owner>/<repo>/topology-export?format=containerlab|gns3[&ref=<branch>]`, as `{file, text, warnings, nodes, links}`. It is free, like the rest of the design repository.

How the design maps:

| Design | Lab |
|---|---|
| a `[[device]]` | a node; `vendor` picks the kind and image: `frr` runs the FRRouting container, `arista` is `ceos`, `cisco` is `cisco_iol`, `juniper` is `juniper_crpd`, `mikrotik` is `mikrotik_ros`, `vyos` is `vyosnetworks_vyos`; any other vendor is a plain Linux container |
| `site`, `role`, `model` | the node's labels |
| a `[[link]]` | two endpoints, one per device |
| ports (`a_if`, `b_if`) | kept only where they are already `ethN`; every other name (`Gi0/1`, `Te1/0/1`) is written in a comment next to the link, and the endpoint gets the next free `ethN` |
| a device only named by a link | a node too |

What it can't do for you, and says so in a list of notes: vendor images (`ceos`, `cisco_iol`, `crpd`, `vrnetlab/*`) are not freely downloadable, so the file names placeholders you import or build yourself; names Containerlab doesn't accept (spaces, slashes) are cleaned and listed; a link from a device to itself is left out; vendors without a Containerlab kind run as Linux containers. The lab has the design's shape, not its configuration: load the real configs from your [site](/docs/en/network-sites) or config repository, or write them with the [twin](/docs/en/network-twin) in mind.

### In GNS3

If your team builds labs in [GNS3](https://gns3.com) rather than Containerlab, **Download .gns3** gives the same design as a GNS3 project. In GNS3, choose **File → Open project** and pick the file: the devices are already placed (edge at the top, then core, distribution, access, hosts at the bottom, each row by site) and wired.

| Design | GNS3 |
|---|---|
| `frr`, `arista`, `juniper` | Docker nodes: the FRRouting container, `ceos:latest` (with the environment and start command cEOS needs), `crpd:latest` |
| `cisco`, `mikrotik`, `vyos` | QEMU VMs: IOSv (`vios-adventerprisek9-m.spa.159-3.m8.qcow2`), MikroTik CHR (`chr-7.16.img`), VyOS (`vyos-1.4.qcow2`), with RAM and adapters set |
| no vendor, a switch role (`access`, `leaf`, `switch`) | GNS3's built-in Ethernet switch: runs with no image at all |
| no vendor, a host role with one link | a VPCS host: no image either |
| any other vendor | a Linux container (network-multitool) |
| ports | a port the design names in the node's own scheme (`eth2`, `ether3`, `Gi0/1`) is that adapter; any other name gets the next free adapter and is shown as that port's name on the canvas, so `Te1/0/1` still reads `Te1/0/1` |
| `vendor`, `role`, `site`, `model` | the node's notes |

The same design always gives the same file (node and link IDs come from the names), so a `.gns3` kept next to the design only changes where the design did. The images are named, not shipped, and GNS3 won't open a project while one of them is missing, so the first note lists them: add the Docker images to the GNS3 server (or GNS3 VM) and the QEMU disks under its images folder first. Checked against GNS3 2.2. API: `?format=gns3`, file `<repo>.gns3`.

## Change certificates

An auditor, a customer or your own incident review asks: who changed the network design, what was checked, and who agreed? A pull request page answers that today; a **change certificate** answers it in a form that can't be quietly edited later and can be checked without trusting Zaryn's database.

With Premium, every merged pull request that changes a design's `.zevtopo` or `.zevintent` files gets one. It records:

- the repository, the pull request, its title and author, who merged it and when, who approved it
- the commits before and after, and the SHA-256 of every design and intent file on both sides
- the network checks' verdict, what changed in the graph (devices, pieces, new single points of failure and critical links) and every intent's result
- whether it was merged past failing checks (**forced**)
- `prev`: the previous certificate of the same repository

It is a dag-cbor document signed by Zaryn's Ed25519 key, the same key that signs the [transparency log](/docs/en/verified-storage), and it is a leaf of that log. Because each certificate names the one before, a repository's certificates form a chain: the design's history, step by step, with nothing left out unnoticed (the Network overview lists the chain and marks a gap).

```diagram
type: flow
title: A chain of certificates
caption: Each certificate names the one before it (`prev`), so a missing or altered one shows. The newest is signed over the chain's earlier link.
---
#1 · Second uplink | Merged by sara, approved by omid, intents held | good | 1
~ prev
#2 · Riser repair 24 h | Merged by sara, intents held | good | 2
~ prev
#3 · One uplink | Merged past failing checks (forced) | bad | 3
```

**Check one.** On the merged pull request, **Verify in this browser** checks five things with nothing but the bytes: the content address (CID) matches the signed document, the signed bytes say what the page shows, the Ed25519 signature, that the key is the one the transparency log is signed with, and that the certified files are the repository's files at that commit. **Download** saves the certificate as JSON to check elsewhere. The API:

```text
GET /v1/repos/<owner>/<repo>/pulls/<n>/certificate     payload (base64), payload_cid, signature, public_key, log_index, certificate (JSON)
GET /v1/repos/<owner>/<repo>/design-certificates       the chain, newest first
GET /v1/transparency/proof?leaf_index=<log_index>      the certificate's place in the log
```

To check by hand: base64-decode `payload`; its CID is CIDv1, dag-cbor, BLAKE3-256 of those bytes (`b3sum`); the signature is Ed25519 over the same bytes with `public_key` (`openssl pkeyutl -verify -rawin`).

```diagram
type: sequence
title: Verify in this browser
lanes: Your browser | Zaryn | Transparency log
alt: The browser fetches the certificate, recomputes the content address, checks the signature and the key, and compares the files with the repository
---
Your browser -> Zaryn : certificate and signed bytes
Your browser -> Your browser : 1 · the CID of the bytes matches
Your browser -> Your browser : 2 · the bytes say what the page shows
Your browser -> Your browser : 3 · the Ed25519 signature is valid
Your browser -> Transparency log : 4 · the key signs the log too
Transparency log --> Your browser : signed tree head
Your browser -> Zaryn : 5 · the files at that commit
Zaryn --> Your browser : their SHA-256 matches the certificate | good
```

A certificate covers the design checks Zaryn ran on the merge. Twin results for linked sites and eval results are on the pull request, not in the certificate. On the Free plan merges work the same, without certificates.

## Where it goes next

The design is the start of the loop: [the twin](/docs/en/network-twin) predicts what a config change does, and [rollouts](/docs/en/network-rollouts) make it and check it on the devices. Designs and configs live in separate repositories: the design says what the network is, the config repository says how each device is set up.
