validate
cotctl validate checks your YAML before you deploy it. Getting into the habit of validating first is one of the highest-value things you can do as a partner: it catches mistakes on your machine, in seconds, instead of as a half-applied change in a customer's environment.
There are three things you might want to validate, and validate has a mode for each:
| Mode | Flag | Network | What it's for |
|---|---|---|---|
| File | -f <file> | Offline | Check a single YAML file of any supported kind |
| Directory | --dir <path> | Offline | Cross-check a whole folder of resources before apply --dir |
| Workflow | --workflow <nameCode> | Online | Run the production-readiness checklist against a live workflow |
File mode — one file, offline
The quickest check. It validates a single YAML file against the schema for its kind, with no API call:
cotctl validate -f my-survey.yaml
✓ my-survey.yaml is valid
If something's wrong, it tells you what and where:
✗ my-survey.yaml has validation errors:
- code: code must start with a lowercase letter and contain only lowercase letters, numbers, and underscores
File mode isn't survey-only. It reads the kind field and runs the matching schema for seven kinds — Survey, AccessRole, PropertyType, Property, JobTitle, Workflow, User. (A file with no kind is treated as a Survey, for backward compatibility.)
validate recognises seven kinds; apply applies twelve. The five apply handles that validate does not are Routine, Sla, Schedule, Bot and Webhook. A file of one of those kinds is reported as an unrecognized kind and fails the run — so the natural pipeline of validate --dir followed by apply --dir stops at the validation step on a directory that apply would have deployed without complaint.
Until the gap closes, either keep those five kinds in a directory of their own, or let the validation step tolerate them. This is a gap in cotctl, not a problem with your files.
A single file can hold several documents separated by ---. validate checks each one and accumulates the errors — it doesn't stop at the first bad document — then reports a per-kind tally like 2 Survey documents, 1 User document validated successfully, so you can fix everything in one pass.
Under the hood, up to three layers of checking run — but only the first applies to every kind:
| Layer | What it checks | Applies to | How to skip |
|---|---|---|---|
| Structure (Zod) | Types, required fields, enums | All kinds | Always on |
| Semantic | function run() in exec hooks, buttons in the wrong stage, deprecated fields | Survey only | --skip-semantic-validation |
| Remote | Identifier uniqueness across the company, that referenced entities exist | Survey only | Needs --remote + -c <profile> |
Non-Survey kinds get the structural (Zod) layer only. The semantic and remote layers are Survey-specific. Remote checks reach the API, so they require a profile — and --remote can't be combined with --dir:
cotctl validate -f my-survey.yaml --remote -c acme
Since 0.12.0 the semantic layer also runs in directory mode, which it did not before. See the warning under Directory mode below: a folder that passed clean can start failing, and the failure was always there.
Directory mode — a whole folder, offline
This is the one you'll use most when working with scaffolded workflows. It validates every YAML file in a folder and checks that they reference each other correctly — all offline. Run it right before apply --dir:
cotctl validate --dir ordenes-compra/
It runs two families of checks. Schema checks, per file:
| ID | Check |
|---|---|
| S1 | File parses as valid YAML |
| S2 | kind is present and recognized |
| S3 | Document validates against the schema for its kind |
| S4 | New in 0.12.0. A survey's semantic rules — repeated identifiers, reserved identifiers, a dependsOn pointing at an identifier that does not exist. FAIL for errors, WARN for warnings |
And cross-reference checks, across files — this is what catches a property pointing at a property type that doesn't exist:
| ID | Severity | Check |
|---|---|---|
| X1 | warn | Permission strings (name:action) are defined as AccessRoles |
| X2 | fail | Property.propertyType references an existing PropertyType |
| X3 | fail | Workflow state machine PropertyType references resolve — propertyType, asset.propertyType, and since 0.12.0 each cardLabels slot and every entry of allowedExtensions |
| X4 | fail | Workflow states[].property references an existing Property |
| X5 | fail | The state machine initialState references an existing Property |
| X6 | warn | Workflow permissions are defined as AccessRoles |
Changed in 0.12.0 — a directory that exits 0 today can start exiting 1. Two classes of problem now surface here that used to wait until apply:
- A survey's semantic errors (check
S4above). Those rules used to live inside the apply, sovalidate --dirpassed a survey that the deploy would then refuse. - A
file://reference that does not resolve, in any kind — not just surveys. A PropertyType carryingeditable.src: "file://missing.js"passed as a plain string before.
Nothing new is wrong with your files. Run it once before you upgrade a CI gate and fix what it reports; the same failure was already waiting on the next apply.
Related, in the same release: validate --dir now resolves file:// references before validating a survey's JavaScript, so an external exec hook is no longer reported as a syntax error in the literal string file://....
A clean run ends with a clear verdict:
Results: 11 PASS, 0 WARN, 0 FAIL — ready to apply
Add --json if you want to consume the result in a script.
Workflow mode — production readiness, online
Once a workflow is live, this mode runs the Marcha Blanca (go-live) checklist against it. It's an online check, so it needs a profile:
cotctl validate --workflow ordenes_compra -c prod
The checklist is organized in three sections, and you can run just one with --section:
- Nomenclature (
nomenclature) — naming conventions for codes, forms, properties, and permissions. - Permissions (
permissions) — that a Manager role exists with all flow permissions, wired into the linked forms. - Configuration (
configuration) — technical rules, like control fields being read-only and an error state existing.
# only the naming checks
cotctl validate --workflow ordenes_compra --section nomenclature -c prod
Results: 13 PASS, 1 WARN, 0 FAIL — production ready
Checks are graded WARN (a recommendation) or FAIL (a real problem). The command exits 0 when everything passes or only warns, and 1 when at least one check fails — which is exactly what you want as a gate in a pipeline.
validate exits 1 on a failure, not 2. Several other commands reserve 2 for a validation refusal, so this one surprises people wiring up their first gate. It holds for all three modes. CI/CD has the full map.
A couple of known limits. Deep JavaScript code-quality checks on exec hooks aren't implemented, and the error-state check (T3) looks for the _estado_error naming convention — if your implementation names its error state differently, expect a warning even when an error state exists.
See also
- apply — deploy your resources once validation passes
- scaffolding — generate a workflow skeleton to validate and apply