Repository navigation
Releases: netdryrun/Netdryrun
Release list
Netdryrun v0.4.0
Verify a change against its intent. 0.4.0 adds netdryrun verify: declare what a change is meant to do, and every other difference in the network's behavior fails. It boots the lab before and after the change, snapshots both, and matches the difference against the intent. No checks to write. FRR only in 0.4; other nodes are refused or named as unobserved.
netdryrun verify(CLI), the MCP toolverify_change, and the GitHub Action'smode: verify: one engine. The verdicts areINTENT MATCHED,NO BEHAVIOR CHANGE,UNDECLARED CHANGES: DO NOT APPLYandINCONCLUSIVE.verifynever printsSAFE TO PROCEED, and every report echoes the intent: explained is not the same as safe. An approval takes four boots (each side twice), and a side that doesn't reproduce isINCONCLUSIVE. The standing guards innetdryrun.ymlcome from the base side, never from the change. Reference: verify.- The snapshot covers BGP sessions, VRFs, routes of every protocol (selected, FIB-installed, next hops), every BGP path with its attributes, the Adj-RIB-Out of every session and each node's running config. A config change with no observable effect in the lab fails unless declared, and every approval lists the changed config lines the lab didn't exercise.
- The Action judges what lands: the PR merged into the base branch. A PR that edits the intent file, the workflow judging it, or the checks together with the configs fails before any lab boots, unless a code-owner review of that file is required and the workflow opts in.
- Fixes for false passes in 0.3.3 (live mixed-vendor interop labs: cJunosEvolved, cEOS, SR Linux, FRR):
- Juniper: a path with no MED read as MED 0, so
med: 0passed on a route sent without a MED. Andbgp_prefixesread Junos's accepted count, which also leaves out a received route hidden for an unusable next hop. It now subtracts only the routes rejected by import policy, as the other drivers count. A hidden route with a reason the driver doesn't know leaves the count a range, and a bound is judged only when the whole range agrees. - SR Linux: a route rejected by import policy counted as received, so
active: falsepassed on it andpresent: falsefailed. It is now left out, as on every other driver. - Arista EOS: cEOS prints MED 0 for a path with no MED, so a
med: 0check on EOS is refused before the lab boots (exit2). AS sets are read in the same form as every other driver. - A topology
exec:command that fails is an error on every platform (containerlab only logs it). Startup config a NOS rejected is never a pass on Arista cEOS, SR Linux and SONiC too (FRR since 0.3.3). Advisory: advisories/0.3.3-exec-not-checked. - A repeated key in device JSON is never "the last one wins".
- Juniper: a path with no MED read as MED 0, so
- Advertisement intervals stretch
--settleand--hold, so a leak sent after the default windows can't let a negative check pass on a count still at 0.recordcovers every active VRF on FRR and SONiC. - A machine-readable capability statement: capabilities.json, generated from the source (on netdryrun.com once the site deploys 0.4).
- The repository is now github.com/netdryrun/Netdryrun. Old links and
uses: netdryrun/netdryrun@vXkeep working through GitHub's redirect. - Moved to 0.4.1:
verifyon an FRR lab with equal multipath paths to an advertised prefix gives no verdict (exit2, never an approval); reading the graceful-restart stale flag on SR Linux, EOS and SONiC; counting only usable routes in SR Linux prefix counts; EOSout-delay. - How this release was checked: releases/0.4.0/GATE.md records static checks, unit tests on Python 3.10–3.14, mutation testing of the verdict logic (the
verifymodules included), and the live lab matrix with both Verdict Corpora and chaos tests. review.md holds the reviews: a cold code review, an expert gauntlet, a multi-vendor audit, an adversarial review of every mutation acceptance, and two runs of a mixed-vendor interop test. Every finding has its decision.
Full list: CHANGELOG.
Netdryrun v0.3.3
Advisory: 0.3.3 and earlier can pass on a lab whose topology
exec:command failed (containerlab logs it and still exits 0). Put routing config insetup:(or, on FRR, infrr.conf), not in topologyexec:. Fixed in 0.4. Details: advisories/0.3.3-exec-not-checked.
The agent runtime. 0.3.3 lets an AI agent get a verdict without local Docker, answers MCP calls within seconds, and makes record write baselines that survive normal growth. The verdict strings, exit codes and the netdryrun.mcp-result/v1 schema are unchanged.
- Fix for a false pass in 0.3.2: an FRR lab that skipped a config line. FRR loads its config line by line at boot and skips a line it rejects (a typo such as
default-orignate, an incomplete command). 0.3.2 ran the checks on the lab without it, so a run could pass on a change that never loaded. 0.3.3 reads every FRR node's boot log after the checks: any rejected line makes the run an error (exit 2), naming the node, line and reason. A line a daemon answered with "Failure to communicate" but applied anyway doesn't count: that daemon's own running config has it in the same section, with its whole block as written. Every other rejection counts, and so does anything that can't be read. Arista cEOS, SR Linux and SONiC startup config isn't checked yet (a documented limit, fix scheduled for 0.4); config applied withsetup:commands is. Advisory: advisories/0.3.2-rejected-config. - Correction: Verdict Corpus v2 is 16 of 17 deterministic cases, not 17/18. The release gate found that v2 case 18 (
18-aggregate-discard-route-removed) gives different verdicts on the same build: FRR's BGP import check sometimes withholds the aggregate and sometimes announces it (three reruns: unsafe, unsafe, safe). We had credited its earlier flip to 0.3.1; that was wrong. The case is now listed as nondeterministic with every verdict seen and left out of the score; the case and its expected verdict are unchanged. Corpus results. - A verdict through a pull request, for agents without Docker.
netdryrun run --verdict FILE(andcheck) writes the agent result, the same object the MCP tools return, with the files tested. The GitHub Action writes it (verdict-report) and uploads it. Its PR comment now starts with a fixed, machine-readable header: status, verdict, counts and the head commit. There is one comment per lab, and only a comment bycomment-author(defaultgithub-actions[bot]) is updated. The header is held to the run's checked exit code: a missing or contradicting result readserror. Guide: AI agents without Docker. - Asynchronous MCP:
start_runreturns arun_idat once, andget_resultpolls until the result, which is exactlydry_run's. A dry run takes 60–120 s, longer than some clients wait for one tool call.dry_runis unchanged. Each run writes its own evidence file, and the server destroys the labs it started when the client disconnects. - A run whose files changed while it ran is an error. The result hashes the files again at the end; if any changed, it reads
status: errorand lists them intested.changed_during_run. recordwrites prefix-count ranges by default (today's count asmin, 20% headroom asmax), instead of exact counts that broke on normal growth.--exact-prefixeskeeps the old behaviour.recordnow adds the standard guards;--no-guardsto opt out. On every BGP node it writes no default route via BGP and no link subnet of the topology via BGP. Each guard is run on the recording lab first; one the lab already fails is left out, with a comment in the file saying why, and one that can't be evaluated is anINCOMPLETE BASELINEline. A prefix range has room for one leaked default route, and the guard is what catches it. The MCPrecord_baselinetool does the same (guards: falseto opt out).- A partial baseline is loud: more routes than
recordwrites, a failed route read, or a link with no address where Netdryrun reads it (SONiC, SR Linux) is an# INCOMPLETE BASELINEline at the top of the file. - Driver fixes from the cross-platform and delta reviews: a link-local neighbor that several sessions share (
fe80::1on two interfaces) is never evaluated, since either session's state or count would answer for the other: check a global address (FRR, SR Linux, EOS); on FRR a route check never passes on a graceful-restart stale path; a link-localnext_hopin abgp_routecheck is refused (two sessions can share it, so the check couldn't say which path it tests); a path with no MED attribute no longer passesmed: 0(its MED is unknown, and withmissing-as-worstit is the worst, not 0); every SR Linux BGP route type (EVPN, L3VPN, labeled) counts as BGP; EOSIdle(Admin)reads asidle. - Known limits, stated, fix scheduled for 0.4: on SR Linux, EOS and SONiC a graceful-restart stale path still counts as present (leave GR off in labs, or use a
timeoutabove the stale timer); SR Linux prefix counts include routes dropped by loop detection (pair the count withroutechecks);recordsnapshots the default VRF only (add checks for other VRFs by hand). - Exact MCP setup for Cursor, VS Code, Windsurf and Codex CLI on the AI agents page.
- How this release was checked: releases/0.3.3/GATE.md records static checks (now including a check for retired public strings), unit tests on Python 3.10–3.14, mutation testing of the verdict logic, and the live lab matrix with chaos tests. Each Verdict Corpus has its own line listing the harmful changes it approves (the known, published misses), so any new one blocks the release. review.md holds the reviews: a cold code review, a multi-vendor AI audit (Grok, ChatGPT and a local open model), an adversarial review of every surviving mutant in a verdict-deciding function, a cross-platform review of the drivers, and a fresh review of every change made after them. Every finding has its decision.
Full list: CHANGELOG.
Netdryrun v0.3.2
Corrected in 0.3.3: Verdict Corpus v2 is 16 of 17 deterministic cases, not 17/18 (case 18 gives different verdicts on the same build and is now left out of the score). 0.3.2 could also pass an FRR change whose config line was rejected at boot: see the advisory. Upgrade to 0.3.3.
The agent contract. 0.3.2 is about what an AI agent needs to choose Netdryrun, read its result and act on it safely. The CLI, its exit codes, the JSON report and the GitHub Action keep their behaviour, apart from the fixes below.
Breaking change for agents: the pass verdict is renamed. The MCP result's pass verdict SAFE TO PROCEED is now CHECKS PASSED (only what the checks cover): a pass covers only what the checks look at. An agent, prompt or AGENTS.md rule that matched the old text no longer sees it, so it treats a pass as not-a-pass (it fails closed). Decide on the status field, not the verdict text. The three verdict strings are now frozen for v1.
- A typed result.
dry_runandrun_checksresults follow a published JSON Schema, mcp-result-v1.json. A failed result lists each failed check with a one-line reason, an evidence excerpt and one read-onlynext_diagnosticcommand for its node. - Results name what they tested:
testedgives thenetdryrun.ymlthat ran and a sha256 of it, the topology and every file the topology uses, read before the run. A verdict applies to those exact files. - Tool descriptions carry the rules, because small models read only the tool text: write the change first, a failed run means fix and rerun, inconclusive means stop, never weaken checks, use
record_baselinewhen there are none. An agent skill (skills/netdryrun/SKILL.md) and anAGENTS.mdsnippet carry the same rules. - A router whose BGP config never loaded no longer passes its leak guards. A node the file says runs BGP that still reports no BGP neighbors at the convergence gate's cap now fails its post-convergence checks as "not evaluated". Found by this release's multi-vendor AI audit. Still a limit: a peer with a long advertisement interval can deliver a route late; raise
--settleand--holdabove it. - A lab that never reached a testable state is inconclusive (exit 3), with failure scenarios in the file too, and when the only readable answers were pings with no replies (bgpd off on every router).
- MCP paths: a relative path is read from
NETDRYRUN_MCP_ROOTwhen that is one folder, and refused with an example when it names two different files; the report is written to a new file and renamed into place. - An IPv6 scope in a check address must be an interface name: text after
%reached device CLI commands. - Network Change Verdict v1, an open spec for the report format: spec/verdict-v1.
- Verdict Corpus rerun on the built 0.3.2 package in the release gate: every verdict matches the published results (v1 24/30, v2 17/18).
- How this release was checked: releases/0.3.2/GATE.md records static checks, unit tests on Python 3.10–3.14, mutation testing of the verdict logic, the live lab matrix with chaos tests, and review.md: a cold code review and a multi-vendor AI audit (Grok, ChatGPT and a local open model), with every finding and its decision.
Full list: CHANGELOG.
Netdryrun v0.3.1
Upgrade if you use 0.3.0. A pre-launch audit found ways 0.3.0 could report passed without testing what the file said. 0.3.1 closes them, along with the security fixes listed in the changelog. 0.3.0 is yanked on PyPI: exact pins still install it.
Run netdryrun validate after upgrading. Some files 0.3.0 accepted are now refused (exit 2). Each of those was a check that didn't test what it said: a misspelled top-level key, a quoted "false", an unknown afi or protocol, a duplicate key, min: 0, vrf: all.
- Advisory, with files you can rerun in seconds against both versions: advisories/0.3.0-false-pass
- Failure scenarios are judged only once the fault is visible in BGP. Negative checks wait for every BGP session on their node.
- Drivers never read an error as "nothing": an unknown VRF, a failed command or unrecognized output is an error, not "no routes".
- New: the
inject_routesfault, thesrl-hidden-leakexample, a JSON Schema for reports, MCP path handling on WSL, andpipx install netdryrunin the docs (Ubuntu 24.04 refuses a plainpip install). - Verdict Corpus rerun on the released 0.3.1: v1 24/30 (unchanged), v2 17/18 (case 18, a removed aggregate discard route, was a false pass on earlier builds and is now rejected). These are fixed test sets that guard against regressions, not an accuracy rate for your network: results and method.
- How this release was checked: releases/0.3.1/GATE.md (cleared, including the proof from PyPI after publishing). The record covers the static checks, unit tests on Python 3.10–3.14, verdict invariants, mutation testing of the verdict logic (every surviving mutant killed or shown harmless), a live lab matrix with chaos tests, and an independent review. The wheel and sdist carry signed build provenance (
netdryrun-v0.3.1.sigstore.json).
Full list: CHANGELOG.
Netdryrun v0.3.0
Full Changelog: v0.2.0...v0.3.0
Netdryrun v0.2.0
Full Changelog: v0.1.0...v0.2.0
NetDryRun v0.1.0
Full Changelog: https://github.com/netdryrun/netdryrun/commits/v0.1.0