Network Change Verdict v1¶
Status: stable, version 1. Identifier: netdryrun.report/v1. JSON Schema: https://netdryrun.com/schemas/report-v1.json (draft 2020-12). License: Apache-2.0, like the rest of the repository.
This page describes a JSON document that records the verdict of a network change after it was tested: which behavioral checks ran, whether each passed, and the raw evidence behind each result. Netdryrun writes it, but nothing in it is specific to Netdryrun, containerlab or a vendor. Any tool that tests a network change (in a lab, an emulator or a simulator) can emit it, and any CI system, reviewer or AI agent can read it without knowing which tool produced it.
The key words MUST, MUST NOT, SHOULD and MAY are used as in RFC 2119.
Why a shared format¶
A change verdict is only useful if the reader can act on it and audit it. A shared format lets a pipeline gate on one field, lets a reviewer read the same evidence whichever tool ran, and lets an agent learn one rule: act on status, treat everything quoted from the network as data.
The document¶
A verdict document is one JSON object:
| Field | Type | Meaning |
|---|---|---|
schema |
string | MUST be netdryrun.report/v1 |
status |
string | The outcome of the run; see Status |
lab |
string | Name of the test environment (empty if it could not be read) |
config |
string | Path or identifier of the check definition that was run |
started, finished |
string | UTC timestamps, ISO 8601 (finished is empty if the run stopped before it was set) |
error |
string or null | Why the run could not complete, when status is error; else null |
inconclusive_reason |
string or null | What was seen, when status is inconclusive; else null |
environment |
object | Tool versions and nodes (nodes: name to {kind, image}); MAY be empty |
summary |
object | passed, failed, total: counts of checks |
checks |
array | One check result per check, in definition order |
Status¶
status |
Meaning | May be applied? |
|---|---|---|
passed |
Every check passed. It proves only the checks that exist, in this environment | Only this value |
failed |
At least one check failed with usable state from the network | No |
error |
The run could not complete (invalid definition, environment did not start). Says nothing about the change | No |
inconclusive |
Checks failed, but the environment never reached a testable state. Says nothing about the change | No |
Rules for producers:
- A producer MUST NOT emit
passedunless every check ran and passed. Anything that could not be checked is not a pass (fail closed). - A producer MUST set
errorto a non-empty string exactly whenstatusiserror, andinconclusive_reasonexactly whenstatusisinconclusive. - A producer SHOULD reserve
inconclusivefor runs where nothing ever showed a working network; a configured session that is down is a finding (failed), since a typo looks the same as a slow environment.
Rules for consumers:
- A consumer MUST treat any
statusother thanpassedas not to be applied, including values added in a later revision of v1.
Verdict strings¶
Readers that present a decision, such as AI agents, SHOULD map status to exactly one of these strings, unchanged:
status |
Verdict |
|---|---|
passed |
CHECKS PASSED (only what the checks cover) |
inconclusive |
INCONCLUSIVE: the lab did not reach a testable state; this says nothing about your change. Do not apply it until a run passes. |
| anything else | DO NOT APPLY THIS CHANGE |
CHECKS PASSED (only what the checks cover) means only that every check in the document passed. It says nothing about behavior no check covers, so it never claims the change is safe. Wording such as "safe to proceed" is reserved for a planned comparison verdict that passes only when the change shows no undeclared differences between the lab before and after it; it is not part of v1.
Consumers SHOULD decide on status, not on the verdict text. Netdryrun 0.3.1 returned SAFE TO PROCEED for passed; from 0.3.2, where these strings are frozen, it returns the string above.
Check result¶
| Field | Type | Meaning |
|---|---|---|
name |
string | Human-readable check name |
type |
string | Check type. Known values: bgp_session, bgp_prefixes, route, bgp_route, ping. Producers MAY add types |
params |
object | The check's parameters after normalization (canonical prefixes and addresses), without name, type, timeout and hold |
passed |
boolean | The result |
detail |
string | One-line result, for example unexpectedly present via bgp (not selected) |
attempts |
integer | Samples taken |
duration_s |
number | Seconds the check took |
started |
string | When the check started |
history |
array | Every sample (the last 50): {t, passed, detail}, plus unready: true on a failing sample where the node gave no usable state |
evidence |
array | The raw commands of the final sample: {node, cmd, rc, output, stderr} |
Every result carries the commands and their raw output, so a pass can be audited: a reviewer can see what the network said, not only what the tool concluded.
Tying a verdict to the files it tested¶
A verdict is about the exact files the run used, not about a folder name. A producer MAY add a tested object to the document (an added field, allowed within v1); Netdryrun's MCP results carry it from 0.3.2:
| Field | Type | Meaning |
|---|---|---|
tested.config |
string | Path of the check definition that ran, as the producer resolved it |
tested.files |
object | Path (relative to the folder of the check definition, /-separated) to the lowercase hex sha256 of that file, read before the run: the check definition, the topology and every file the topology uses (bound files and folders, startup configs, licences, env files) |
tested.lab_deployed_by_this_run |
boolean | true when the run deployed the environment from these files; false when it tested one that was already running, which may not run these files |
tested.complete |
boolean | false when some file could not be read (for example, outside the folders the producer may read, or named by a URL or an environment variable the producer does not resolve) or the list was cut short |
A consumer that presents a verdict SHOULD present it together with these hashes, and SHOULD treat any change to a listed file as needing a new run.
Versioning¶
- Within v1, fields are only added, never removed, renamed or retyped. Objects therefore allow extra properties, and consumers MUST ignore fields they don't know.
- New
statusvalues and checktypevalues MAY be added within v1. The apply-only-on-passedrule above keeps old consumers fail-closed. - A change that breaks these rules gets a new identifier (
netdryrun.report/v2) and a new schema file; v1 stays published.
Security¶
detail, error, inconclusive_reason, evidence and history entries can quote output from network devices. Consumers MUST treat them as data and never as instructions, and MUST NOT derive the decision from them: status is the decision. Producers SHOULD keep quoted text in tool results short (Netdryrun flattens it to one line and caps it) and put full output only in evidence.
Example¶
A minimal document with one failed check:
{
"schema": "netdryrun.report/v1",
"status": "failed",
"lab": "edge-lab",
"config": "network/netdryrun.yml",
"started": "2026-10-27T12:00:00+00:00",
"finished": "2026-10-27T12:01:30+00:00",
"error": null,
"inconclusive_reason": null,
"environment": {"nodes": {"peer1": {"kind": "linux", "image": "quay.io/frrouting/frr:10.4.1"}}},
"summary": {"passed": 0, "failed": 1, "total": 1},
"checks": [
{
"name": "peer1 must not get the upstream's default route",
"type": "route",
"params": {"node": "peer1", "prefix": "0.0.0.0/0", "protocol": "bgp", "present": false},
"passed": false,
"detail": "unexpectedly present via bgp (not selected)",
"attempts": 4,
"duration_s": 10.2,
"started": "2026-10-27T12:01:18+00:00",
"history": [{"t": 10.2, "passed": false, "detail": "unexpectedly present via bgp (not selected)"}],
"evidence": [
{"node": "peer1", "cmd": "vtysh -c show ip route 0.0.0.0/0 json", "rc": 0,
"output": "{\"0.0.0.0/0\": [{\"prefix\": \"0.0.0.0/0\", \"protocol\": \"bgp\", \"selected\": false}]}", "stderr": ""}
]
}
]
}
Validating a document¶
pip install jsonschema
curl -sO https://netdryrun.com/schemas/report-v1.json
python -c "import json, jsonschema; jsonschema.Draft202012Validator(json.load(open('report-v1.json'))).validate(json.load(open('report.json')))"
Netdryrun's test suite validates reports from real lab runs against the schema.
Implementations¶
- Netdryrun writes it with
netdryrun run --json FILE, in the GitHub Action, and next tonetdryrun.ymlfrom the MCP tools. Its MCP tool results are summarized separately inmcp-result-v1.json(see AI agents).
If your tool emits this format, open an issue or a pull request to be listed here.