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
asset.color.amf, asset.color.ocio_configAn asset (v0.94)
asset.color.*_transform.asset, look_transforms[].assetAn asset (v0.94)
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_FOUND—The file could not be read
OWG_INVALID_JSON—The 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
OWG_A2A_PERFORMER_REQUIRED2An a2a task names no performed_by (v0.93)
OWG_A2A_PERFORMER_KIND2An a2a task's performer is not kind agent
OWG_A2A_NO_AGENT_CARD2An a2a task's agent declares no agent_card
OWG_A2A_UNACCOUNTABLE2An a2a task's agent names neither operated_by nor works_for
OWG_AGENT_CARD_KIND2A participant that is not an agent declares an agent_card
OWG_DELEGATION_SELF2delegation.by is the task's own performer
OWG_DELEGATION_WIDENS2delegation.scope grants something the delegator's credential_scope does not hold
OWG_IPTC_AI_MISMATCH2An asset's iptc.DigitalSourceType contradicts the ai_role of the task that produced it (v0.93). See IPTC
OWG_COLOR_INTEROP_ID_UNPUBLISHED2An asset's color.interop_id has no namespace but is not an ID the ASWF Color Interop Forum has published (v0.94). See Color

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 do — depends_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.