Open Workflow GraphPre-release
Implementing

Validation

Every check the validator performs — structural, referential, graph, and expression — and the error codes it emits.

View as markdownMachine-readable source for agents and scripted implementers

Validation is what makes an OWG document safe to execute regardless of who or what wrote it. A workflow authored by an agent gets exactly the same scrutiny as one authored by a supervisor, and that symmetry is the point: it is what allows agent-authored workflows to be trusted at all.

Validation runs in four passes, in order. Each pass runs only if the previous one produced no errors, because a document that fails structurally cannot be meaningfully checked for anything else.

PassChecksWhy it cannot be earlier
1. StructuralJSON Schema — types, enums, patterns, required fields, unknown keys
2. ReferentialEvery id reference resolvesNeeds well-formed entities
3. GraphAcyclicity, uniqueness, reachabilityNeeds resolvable references
4. Expressionwhen conditions and $. references parse, resolve, and type-checkNeeds a valid graph to resolve against

Running the validator

owg-validate workflow.owg.json
FlagEffect
--jsonMachine-readable output
--schema-onlyStop after pass 1
--verboseInclude documentation links per error
--helpUsage

Success:

✓ Valid — "episodic-dailies" (v0.92): 7 tasks, 9 assets, 5 participants

Failure, human-readable — code, path, message:

✗ Invalid — 2 error(s):

  [OWG_UNKNOWN_DEPENDENCY] /tasks/3/depends_on/0
    Task "publish" depends on "transcodee", which doesn't exist in this document.

  [OWG_UNKNOWN_PROPERTY] /tasks/1
    Unknown property "performed_bye". Did you mean "performed_by"?

Failure, --json:

{
  "valid": false,
  "errors": [
    {
      "code": "OWG_CYCLE_DETECTED",
      "path": "/tasks",
      "message": "Cycle detected in task dependency graph: grade → qc → regrade → grade.",
      "docs": "https://openworkflowgraph.org/validation#graph-checks"
    }
  ]
}

Successes go to stdout, failures to stderr, exit code 0 or 1.

Pass 1 — structural

JSON Schema draft 2020-12, evaluated in all-errors mode so you get every structural problem at once rather than one per run.

The check worth calling out separately: unknown properties are errors. Every object in the specification sets additionalProperties: false, so a misspelled or invented field fails loudly instead of being silently accepted.

[OWG_UNKNOWN_PROPERTY] /tasks/1
  Unknown property "performed_bye". Did you mean "performed_by"?

This is the single most valuable check for machine-authored documents. A hallucinated property is the most common way a generated workflow validates and then does nothing, and closing the object shape is what converts that silent failure into a caught one. Where you genuinely need to carry extra data, use metadata or an x- prefix.

Also enforced here, because JSON Schema can express them conditionally:

  • A param with required: true must not carry a default.
  • A default must match its parameter's declared type.
  • A relationship with assurance: "inferred" must carry confidence and method; one with attested or asserted must not.

Pass 2 — referential

Every identifier reference must resolve within the document. These are the errors that catch typos, and they are the difference between a document that validates and a document that runs.

ReferenceMust resolve to
task.depends_on[]A task
task.used[], task.produced[]An asset
task.performed_byA participant
task.ran_onAn infrastructure entry
task.on_pass, task.on_failA task
task.on_failure_compensateA task
task.reroute_feedback.to_taskA task
asset.predecessorAn asset
asset.produced_byA task
asset.composes[].componentAn asset
participant.operated_byA participant
participant.organization_id, works_forAn organization
relationship.from.id, to.idAn entity of the stated kind
relationship.proposed_by, accepted_byA participant
context (string form)An entry in contexts[]
on_workflow_failureA task

Consistency checks in the same pass:

  • produced_by must agree with produced[]. If an asset names a producing task, that task must list the asset in produced[]. Emits OWG_PROVENANCE_MISMATCH.
  • An asset must not be produced by more than one task. Two producers means the lineage is ambiguous.
  • A nested OWG subgraph's required parameters must be satisfied by the parent task's inputs. A child parameter with required: true and no matching parent input emits OWG_SUBGRAPH_PARAM_UNSATISFIED. See Subgraphs.

Pass 3 — graph

CheckCode
The depends_on graph is acyclicOWG_CYCLE_DETECTED
Task ids are uniqueOWG_DUPLICATE_ID
Asset, participant, infrastructure, organization, context, relationship ids are unique within their registryOWG_DUPLICATE_ID
The operated_by chain terminates at a human or organization participantOWG_AUTHORITY_CYCLE
Gate routing (on_pass / on_fail) does not form an unbounded loop without max_reroutesOWG_UNBOUNDED_REROUTE
Nested OWG subgraph references do not form a cycleOWG_SUBGRAPH_CYCLE
Subgraph nesting does not exceed depth 8OWG_SUBGRAPH_DEPTH

Cycle errors name the whole cycle rather than one member, because a cycle is only fixable if you can see it:

[OWG_CYCLE_DETECTED] /tasks
  Cycle detected in task dependency graph: grade → qc → regrade → grade.

The authority check exists because an agent operated by an agent operated by the first agent has no accountable human, which quietly defeats the entire attribution model.

The re-route check is the one people find surprising. A gate whose on_fail routes back upstream is normal and intended; a gate that does so without max_reroutes, and in a document without governance.max_total_reroutes, describes a loop with no exit. That is a validation error rather than a run-time discovery.

Pass 4 — expressions

Every when condition and every $. reference is parsed against the grammar, then resolved and type-checked.

CheckCode
The expression parsesOWG_EXPRESSION_SYNTAX
Every reference resolves to a declared parameter, task, or fieldOWG_UNRESOLVED_REFERENCE
Comparison operands are type-compatibleOWG_TYPE_MISMATCH
Ordering operators are applied to numbersOWG_TYPE_MISMATCH
An interpolated value is a scalar, not a structureOWG_TYPE_MISMATCH
A referenced task is not downstream of the referring taskOWG_FORWARD_REFERENCE
[OWG_UNRESOLVED_REFERENCE] /tasks/2/when
  Reference "$.params.add_caption" does not resolve. The document declares
  no parameter "add_caption". Did you mean "add_captions"?
[OWG_TYPE_MISMATCH] /tasks/4/when
  Cannot compare string to number: "$.params.territory > 3".

The forward-reference check deserves explanation: a task cannot read the output or status of a task that does not precede it in the dependency graph, because at evaluation time that value does not exist. It is a common authoring error and a genuinely confusing run-time failure, so it is caught here.

Error codes

CodePassMeaning
OWG_FILE_NOT_FOUNDThe file could not be read
OWG_INVALID_JSONThe file is not valid JSON
OWG_SCHEMA_ERROR1A JSON Schema constraint failed
OWG_UNKNOWN_PROPERTY1An unrecognised key, with a suggestion where one is close
OWG_VERSION_UNSUPPORTED1owg_version is not a version this validator knows
OWG_UNKNOWN_DEPENDENCY2depends_on names a task that does not exist
OWG_UNKNOWN_REFERENCE2Any other id reference that does not resolve
OWG_PROVENANCE_MISMATCH2produced_by and produced[] disagree
OWG_DUPLICATE_ID3Two entities in one registry share an id
OWG_CYCLE_DETECTED3The dependency graph is cyclic
OWG_AUTHORITY_CYCLE3An operated_by chain does not terminate
OWG_UNBOUNDED_REROUTE3Gate routing loops with no ceiling
OWG_SUBGRAPH_PARAM_UNSATISFIED2A nested subgraph's required parameter has no matching parent input
OWG_SUBGRAPH_CYCLE3Nested subgraph references form a cycle
OWG_SUBGRAPH_DEPTH3Subgraph nesting exceeds depth 8
OWG_EXPRESSION_SYNTAX4An expression does not parse
OWG_UNRESOLVED_REFERENCE4A $. reference does not resolve
OWG_TYPE_MISMATCH4Operand or interpolation types are wrong
OWG_FORWARD_REFERENCE4A reference points at a task that is not upstream
OWG_COMPENSATE_MISSING_TARGET2COMPENSATE without on_failure_compensate
OWG_FANOUT_TOLERANCE_CONFLICT2fan_out declares both tolerated_failure_percentage and tolerated_failure_count

Every error carries code, path (a JSON Pointer into the document), message, and a docs URL.

Validating OMC instances

Inbound MovieLabs OMC data is validated before anything enters a graph:

omc-validate instance.json
✓ Valid OMC-JSON — 1243 entities pass schema v3.0

The OMC validator distinguishes errors from warnings. Warnings never affect the verdict; they catch things a schema pass structurally cannot:

CodeWhy a schema pass cannot catch it
OMC_SCHEMA_VERSIONAn older OMC document can validate cleanly against a newer schema
OMC_UNKNOWN_CONTROLLED_VALUEControlled vocabularies are advisory annotations, so out-of-vocabulary values pass untouched
OMC_UNKNOWN_EDGE_PREDICATEOMC leaves its edge map open, so undeclared predicates validate
OMC_UNEXPECTED_EDGE_TARGETLikewise for unexpected target types

All four are driven off the OMC schema itself rather than hardcoded tables — the vocabularies, predicate set, and declared target ranges are read out of the schema — because MovieLabs is actively developing relationship domains and ranges, so the tables must move when the schema moves.

Warnings are grouped and counted, so one defect reports once:

⚠ 323 conformance warning(s) in 37 group(s) — not schema failures:
     86×  assetStructureType: "digital.audiovisual" is not in the controlled
          vocabulary (extension point — allowed, but unrecognised)

Referential integrity is reported informationally, never fatally, because external references are legal in OMC:

⚠ 12 reference(s) point to entities not defined in this file
  (legal if they're external)

customData and annotation subtrees are never inspected — they are free-form by definition.

Nested documents

A task whose subgraph declares format: "owg" contains another OWG document, and that document is validated recursively — all four passes, to a maximum nesting depth of 8.

Error paths in a nested document are prefixed with the path of the task that contains it, so a failure names where it actually lives:

[OWG_UNKNOWN_REFERENCE] /tasks/4/subgraph/tasks/1/used/0
  Task "comp_0140" uses "plate_014", which doesn't exist in this document.
  Did you mean "plate_0140"?

Subgraphs with any other format are not descended into. They are foreign structures, stored and never parsed — so nothing inside them can be validated, and nothing inside them can be referenced from outside.

Multi-document projects

A document with registries and no tasks is a registry document: it holds the shared participants, organizations, infrastructure and contexts for a production, and the workflow documents carrying the same project_id reference them.

That means a workflow document from such a project cannot be fully checked on its own — its performed_by, works_for and organization_id references resolve into the registry, not into itself. Supply the siblings:

validateOWG(doc, { registry: [projectRegistryDoc] });

Entity references resolve across the documents you supply. Task references never dodepends_on, on_pass, on_fail and on_workflow_failure must name a task in the same document, because nesting another document's work is explicit, via subgraph.

Without a registry, an unresolved reference in a document that declares project_id says so in the error message, rather than leaving you to guess whether it is a typo or a cross-document link.

Conformance note

The four passes above are what the specification requires of a conformant validator. An implementation that performs only the structural pass is doing JSON Schema validation, not OWG validation — useful, but it will accept documents that cannot execute.

If you are relying on a validator you did not write, establish which passes it actually performs before trusting a clean result. The pre-flight checklist covers what to verify yourself in the meantime.

The reference validator implements all four, and the conformance corpus is checked by all four on every commit — which is the only reason the corpus can be offered as reference data rather than as illustrations.