Skip to main content

cwright validate

cwright validate <model> [--json]

Parses a model document and checks it as a whole, reporting every problem found rather than stopping at the first one — every reference resolves (a step's resource exists, a rework target is an earlier step, a dedication chain is used consistently), every probability is in [0, 1], and every declared constraint is internally consistent.

A model path of - reads the document from stdin.

Output​

On success:

ok: inspection-booth (1 resources, 2 products, 2 releases)
model_hash: <hex>

model_hash is a content hash of the canonicalised document — the same model always hashes the same way, regardless of incidental formatting differences in the file. It is what run --store uses to recognise a model it has already evaluated, and what every result document's manifest.model_hash field points back to.

On failure, validate exits non-zero and prints every problem it found, each naming the field it concerns, so a document with several unrelated mistakes does not need several separate runs to diagnose.

--json, for an editor or a script​

The same answer as a machine-readable document on stdout. Every problem carries a JSON pointer to the field it concerns, so a consumer does not have to read prose to find out what to fix.

On an accepted document:

cwright validate booth.yaml --json
{
"model": "inspection-booth",
"model_hash": "<hex>",
"ok": true,
"problems": []
}

On a document that parses and is refused — problems is the list the human form prints, one entry per problem, in the same order:

cwright validate broken.yaml --json
{
"model": "inspection-booth",
"model_hash": "<hex>",
"ok": false,
"problems": [
{
"message": "resource 'booth' has no servers; work sent to it would wait forever",
"path": "/resources/0"
},
{
"message": "step names resource 'nope', which does not exist; known resources: booth",
"path": "/products/0/steps/0"
}
]
}

A document that could not be read has no problems list, because there is no document to point into. ⭐ Branch on line and path to learn whether there is a location, and read error for the cause — at most one locator is ever present, so that branch is decidable, and null on both is a legitimate answer rather than a single cause.

A file that could not be opened has neither, because nothing was ever parsed:

cwright validate absent.yaml --json
{
"column": null,
"error": "reading absent.yaml: No such file or directory (os error 2)",
"line": null,
"ok": false,
"path": null
}

A document that does not parse carries the line and column the parser itself reported:

cwright validate torn.yaml --json
{
"column": 5,
"error": "torn.yaml: read as YAML, not a valid model: resources[0]: missing field `servers` at line 3 column 5",
"line": 3,
"ok": false,
"path": null
}

A document that parses and is then refused by a reader guard that knows which field is at fault has no position, because there is no longer any text to point at. It carries a path instead:

cwright validate nan.yaml --json
{
"column": null,
"error": "nan.yaml: read as YAML, /horizon_seconds: NaN is not a finite number. JSON cannot express one, so the canonical form of this model -- which is what `model_hash` hashes -- would lose it, and `validate`'s range checks pass a NaN because every comparison against it is false. Give it a number.",
"line": null,
"ok": false,
"path": "/horizon_seconds"
}

The contract​

keywhen it is there
okalways, and it is the answer
errorthe document could not be read, and this names the cause
line, columnthe parser reported a position, so the text did not parse
paththe text parsed and a reader guard refused one field, naming it
neitherthere is no location to give. More than one refusal lands here — see below
model, model_hash, problemsthe document was read. problems is [] when the model is accepted

⚠️ Which refusals have no location at all​

Two reader guards are about the document as a whole rather than any one field, so they have no location to report and arrive with line, column and path all null — the same shape as a file that could not be opened:

refusalwhat it means
the file could not be openedthe path is wrong, or stdin was empty
it does not survive its canonical formmodel_hash is a hash of the canonical JSON, and reading the model back from it gives a different model
the two readers disagreethe text is valid JSON and valid YAML, and the two parsers read a number differently

⭐ So a consumer that wants to jump somewhere branches on the two locators and gives up gracefully on this bucket; a consumer that wants to explain the refusal reads error, which is the only thing that separates these three. Naming the bucket after any one of them would be wrong: an editor told null, null means "file not found" reports that about a file it has just opened and parsed.

A semantic problem has no line and column, and the document says nothing rather than guessing: those rules run against the parsed document, where there is no longer any text to point at. A JSON pointer is the more useful location anyway — it survives reformatting, and it names the same field in a YAML document and in its JSON equivalent, which are two spellings of one model.

warning

--json does not change the exit status. A refused document still exits non-zero, so cwright validate model.yaml --json can be used in a pipeline exactly as the plain form is, and a script that ignores the document still cannot mistake a refusal for a pass.

When to use it​

Run validate as the first step of any pipeline that generates model documents — a script, a search loop, or an agent proposing variants. It is deliberately cheap (no simulation runs) and answers a narrower question than run does: is this document well-formed and internally consistent, not yet what does it produce. lint asks the question in between — can what this describes actually run — and warns rather than refusing, because a transient overload is a legitimate thing to model. propose performs an equivalent check on a variant before ever running it, for the same reason.