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
cookieorcapture. 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_markerneeds acontains_markercontrol in the same check, or amarker_probeon the marker's owner. - Cross-references.
expectnaming an undeclared identity;contains_markernaming an identity with nomarker;establishesnaming a capture the session does not declare. - Capture ordering and single assignment. See sessions and CSRF.
- Duplicate ids, across
checksandsequencestogether. - 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 aHostheader 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:
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.