Skip to content

The configuration JSON Schema

Your editor can validate a Veridor configuration while you type it. The source tree carries a JSON Schema with a stable $id, and every editor with a JSON language server — VS Code, Neovim, Helix, JetBrains, anything speaking LSP — will use it if you tell it to.

This buys you completion on key names, hover text on every field, and a red squiggle under a value the schema rejects. It does not replace loading the file: the loader enforces rules a JSON Schema cannot express, and the schema is deliberately stricter than the loader about unknown keys. Both limits are stated at the bottom of this page.

Where the schema is served

The schema's $id is https://docs.veridor.org/schema/veridor-config-v1.schema.json, and this site serves a copy of the file at that address. It is a copy of schema/veridor-config-v1.schema.json in the Veridor source, refreshed when the tool changes. Nothing in it depends on fetching it, so a local copy works the same way.

Point your config at it

Add one key to the top of the file:

{
  "$schema": "https://docs.veridor.org/schema/veridor-config-v1.schema.json",
  "version": 1,
  "target": {
    "base_url": "https://staging.example.com"
  }
}

That is the whole setup. $schema is an unrecognised top-level key as far as Veridor is concerned, so the loader accepts it and ignores it, the same way it accepts _comment. It changes nothing about a run.

Note

veridor init does not write $schema into the config it scaffolds. If you want it, add it yourself.

Or match by filename in VS Code

If you would rather not edit every config, VS Code can bind the schema to a filename pattern. In .vscode/settings.json:

{
  "json.schemas": [
    {
      "fileMatch": ["veridor.json", "veridor.*.json", "**/veridor/*.json"],
      "url": "https://docs.veridor.org/schema/veridor-config-v1.schema.json"
    }
  ]
}

fileMatch patterns are glob-style and are matched against the workspace-relative path. Add whatever your repository actually calls the file — the tool imposes no naming convention, and --config takes any path.

The equivalent for other editors goes through their JSON language server configuration; the schema URL is the same in every case. The $schema key in the file itself is the portable option, and it works in editors where you cannot change settings.

What the schema knows

The schema is one file, veridor-config-v1.schema.json, drafted against JSON Schema 2020-12, and it covers configuration versions 1 and 2 (sequences, sessions, captures, matrix and coverage included). Its $id is the URL above, where this site serves it.

It carries the descriptions, not only the types. Hovering marker in an editor gives you:

A unique value planted in THIS identity's data. If it appears in another identity's response, the string provably crossed.

Hovering establishes:

The proof the login worked: exactly one of cookie or capture. Without it, 'the session is up' is an assumption.

That is deliberate. The schema is a promise an editor shows people while they type, and the field names alone do not carry why a field exists.

It is checked against the loader, both ways

The schema lives in the tool repository, beside the code it describes, and tests/test_schema_parity.py holds the two together. The test suite takes no third-party dependency, so it validates with a small Draft 2020-12 validator for exactly the keywords the schema uses, and that validator raises on any keyword it does not implement, so a schema edit cannot slip an unchecked keyword past it.

Checked How
Everything Veridor ships, generates or documents is accepted by both the veridor init scaffolds for every shipped OpenAPI fixture, the shipped configuration fixtures, and the configurations in the README, including its version-2 sequences and session examples
Forms the loader refuses, the schema refuses too the fixtures whose markers are too short, and a set of mutations of a base document
Every key the schema names is one the loader reads, and the other way round root, identity, request, expectation, session, login step, capture, establishes, inject, sequence, sequence step, teardown entry, matrix and specimen keys, each compared with the loader's own constant
Enumerations and limits version, session reestablish, step role, capture from, the safe methods; marker length, timeout_seconds, login steps, sequences and steps per sequence

The schema is not the validator

A schema check is necessary and not sufficient. These rules are enforced by the loader alone, exit 2, and no editor will warn you about any of them:

  • Marker strength beyond length and alphabet. The schema checks 24 to 128 characters of [A-Za-z0-9_-]; the loader also checks distinct-character count, character frequency, run length and periodicity.
  • Marker uniqueness. Two identities may not share a marker, and one marker may not be a substring of another.
  • The reflection guard. A marker that appears anywhere in a request Veridor would send, after percent, HTML, escape, base64 and hex decoding, is a load error.
  • Identical credentials. Two identities sending the same token is refused.
  • The control requirement. Every excludes_marker needs a contains_marker control in the same check, or a marker_probe on the marker's owner.
  • Cross-references. expect naming an undeclared identity; contains_marker naming an identity with no marker; establishes naming a capture the session does not declare.
  • Capture ordering and single assignment. See sessions and CSRF.
  • Duplicate ids, across checks and sequences together.
  • Unsubstituted {id} in a path, under the verbs that send it.
  • Where a request may go. A . or .. segment in a path, in any spelling (..%2f, ..;, %c0%ae), a path that becomes an absolute URL when joined, and a Host header in any header map are refused by the loader and accepted by the schema (configuration reference).
  • An unset or empty ${VAR}, which is a property of the environment, not the file.
  • --read-only, which is a flag rather than a property of the file.

Run the file to find out whether it loads:

veridor run --config veridor.json --read-only

Where the schema and the loader still differ

Unknown keys outside request blocks. The loader accepts an unrecognised key in most objects, the root included, as an annotation, and refuses only a near-miss of a real key (none at all in target or a check's own keys). The schema accepts annotations only when they start with _ (_comment, _generated_by), and refuses every other unknown key, at the root too, so a typo shows in your editor. Neither judges a key that starts with _. An editor bound to the schema will therefore mark "owner_team": "payments" on a check as invalid while veridor run accepts it. Use _owner_team.

Request blocks now agree. In a check's request, a marker probe, a login step, a sequence step's request and a teardown entry's request, the loader refuses every key but method, path, headers, json, body and a _ annotation, as the schema always did. It used to accept params, query and a capitalised Headers there and drop them.

Measured against commit 38025ed, with the schema checked by the validator in tests/test_schema_parity.py and the loader's own load_config, each row one change to the shipped demo configuration:

Document Schema Loader
"version": 2 accepts accepts
an expect entry that is the string "allow" refuses refuses
expect naming one identity refuses refuses (must expect results for at least two users)
target.teardown_timeout_seconds accepts accepts
_comment on a check accepts accepts
a 16-character marker refuses refuses
unknown key params in a check's request refuses refuses (has unsupported keys: params)
Headers in a check's request refuses refuses (did you mean 'headers'?)
_comment in a check's request accepts accepts
"version": true refuses refuses
a coverage block with denial_statuses: [403] accepts accepts
unknown key zzz at the top level refuses accepts
Checks at the top level refuses refuses (did you mean 'checks')
unknown key zzz in target refuses accepts
unknown key owner_team on a check refuses accepts
_marker_note on an identity accepts accepts
a Host header in a check's request accepts refuses
path /../api/admin/audit accepts refuses

Controls: the unchanged demo configuration was accepted by both, and a check with no request was refused by both.

In every case the loader is the authority. It is the thing that decides whether a run happens.