# Document structure

> The OWG v0.92 document — every top-level field, the entity registries, parameters, and governance ceilings.

An OWG document is a single JSON object describing a production workflow: five entity registries, the tasks that connect them, the parameters a caller supplies, and the ceilings the run is held to.

```json
{
  "$schema": "https://openworkflowgraph.org/schemas/core/v0.92.json",
  "owg_version": "0.92",
  "id": "episodic-dailies",
  "profiles": ["core", "cloud-services"],
  "objective": "Turn camera cards into approved dailies with disclosure records.",

  "params":         { },
  "organizations":  [ ],
  "participants":   [ ],
  "infrastructure": [ ],
  "assets":         [ ],
  "contexts":       [ ],
  "relationships":  [ ],
  "tasks":          [ ],
  "governance":     { }
}
```

> **v0.92 is a draft specification.** It is the current version; v0.91 and v0.9 remain resolvable at their own schema URLs and a document targeting either stays valid — v0.92 is additive over v0.91, adding an optional `profiles` extension point on `context`, `asset`, and `relationships[]` (see [Extensions](#extensions)), and v0.91 was additive over v0.9, adding `task.fan_out` (see [Fan-out](/tasks#fan-out-v091)). Earlier internal iterations before v0.9 are superseded and are not described here. The version number will reach 1.0 when the specification is stable enough to promise compatibility. Until then, expect field-level change, and pin the schema you validate against. See the [changelog](/changelog) for what changed and when.

## Required fields

Only two:

| Field | Type | Notes |
|---|---|---|
| `owg_version` | string | Must be `"0.9"`, `"0.91"`, or `"0.92"`. Pattern `^\d+\.\d+(\.\d+)?(-[a-z0-9]+)?$` |
| `id` | string | Pattern `^[a-zA-Z0-9][a-zA-Z0-9._-]*$`, 1–128 chars |

Everything else is optional, including `tasks`. A document with registries and no tasks is a valid **registry document** — a shared project registry of participants, assets, and infrastructure that other documents reference. This is used in practice and is deliberate.

## All top-level fields

| Field | Type | Purpose |
|---|---|---|
| `owg_version` | string | **Required.** The specification version this document targets |
| `id` | string | **Required.** Document identifier |
| `$schema` | string | Schema URI. Advisory, but if present must match the version |
| `project_id` | string | Groups multiple documents under one production |
| `profiles` | string[] | Conformance profiles in use. See [Profiles](/profiles) |
| `description` | string | Max 2000 chars |
| `objective` | string | The macro-objective agents are governed against |
| `params` | object | Caller-supplied parameters. See below |
| `organizations` | array | Registry — studios, vendors, service providers |
| `participants` | array | Registry — humans, agents, services |
| `infrastructure` | array | Registry — compute, storage, facilities, devices |
| `assets` | array | Registry — media and data objects |
| `contexts` | array | Registry — productions, scenes, deliverable groupings |
| `context` | string \| object | The primary context: inline, or an id from `contexts[]` |
| `relationships` | array | Explicit and proposed edges. See [Relationships](/relationships) |
| `tasks` | array | The work, and the authoritative source of provenance edges |
| `governance` | object | Run-level ceilings and gates |
| `on_workflow_failure` | string | Task id to run if the workflow fails |
| `webhook_url` | string | HTTPS endpoint for run status updates |
| `timeout_seconds` | integer | Whole-workflow wall clock, ≥ 1 |
| `metadata` | object | Freeform. See [Extensions](#extensions) |

## Parameters

`params` declares what a caller must supply when submitting the document for execution. This is how a reusable workflow definition stays reusable: the definition holds no run-specific values, and the caller provides them.

```json
{
  "params": {
    "source_url": {
      "type": "string",
      "required": true,
      "description": "Public HTTPS URL of the source file"
    },
    "add_captions": {
      "type": "boolean",
      "required": false,
      "default": false
    },
    "territories": {
      "type": "array",
      "required": false,
      "default": ["US"]
    }
  }
}
```

Each entry is keyed by parameter name and takes:

| Field | Type | Notes |
|---|---|---|
| `type` | enum | **Required.** `string` \| `number` \| `boolean` \| `array` \| `object` |
| `required` | boolean | Default `false` |
| `default` | any | Only permitted when `required` is `false` |
| `description` | string | What the caller should supply |

Two rules the schema enforces rather than merely stating:

- A parameter with `required: true` **must not** carry a `default`.
- A `default`, when present, must match the declared `type`.

Tasks read parameters with `$.params.<key>`. The grammar is in [Reference syntax](/reference-syntax).

> **Validation resolves parameter references.** A task referencing `$.params.add_caption` when the document declares `add_captions` is a validation error, not a run-time surprise. See [Validation](/validation).

## Governance

Run-level ceilings. These are hard limits, not advisory budgets — an engine must refuse to exceed them.

```json
{
  "governance": {
    "max_total_reroutes": 12,
    "budget_usd": 4800,
    "disclosure_gate": true
  }
}
```

| Field | Type | Purpose |
|---|---|---|
| `max_total_reroutes` | integer ≥ 0 | Ceiling on QC re-routes across the whole run |
| `budget_usd` | number ≥ 0 | Spend ceiling for the whole run |
| `disclosure_gate` | boolean | Blocks delivery tasks until compliance certifies |

`max_total_reroutes` matters more than it looks: it is the circuit breaker on an agent-and-gate loop that would otherwise re-route indefinitely. See [Gates and re-routes](/tasks#gates-and-re-routes).

## Context

Context is what scopes work. It may be given inline or by reference into `contexts[]`.

```json
{
  "contexts": [
    { "id": "ctx_ep104", "show_id": "SHOW_001", "scene_id": "sc_014",
      "territory": "US", "work_type": "creative-fictional" }
  ],
  "context": "ctx_ep104"
}
```

| Field | Type | Notes |
|---|---|---|
| `id` | string | |
| `show_id` | string | Production code |
| `scene_id` | string | |
| `territory` | string | e.g. `US`, `EMEA` |
| `organization_id` | string | |
| `work_type` | enum | `creative-fictional` \| `factual-news` \| `advertising` \| `sports` \| `other` |
| `profiles` | object | Profile-contributed extensions, keyed by profile id (v0.92). See [Extensions](#extensions) |

`work_type` is load-bearing for compliance rather than descriptive — it selects which disclosure obligations apply. See [AI disclosure](/compliance).

Tasks and assets are **associated with** a context rather than contained in it, because the same asset means different things in different places.

> **No shot identifier.** OWG v0.9 deliberately has no `shot_id`. MovieLabs' OMC v3.0 has no Shot entity; the video pipeline covering production scene through editorial shots and sequences arrives in a later OMC release. Rather than invent a mapping that will conflict, v0.9 omits it. Carry a shot identifier in `identifiers[]` with your own scope until OMC defines one.

## Extensions

Every object defined by this specification sets `additionalProperties: false`. An unrecognised key is a **validation error**, not a silently accepted field.

This is deliberate, and it is the single most important protection the schema offers an agent-authored document: a hallucinated or misspelled property fails loudly rather than validating and doing nothing.

Extensibility is preserved through three explicit escape hatches:

- **`metadata`** — a freeform object available on the document and on every entity. Put anything here.
- **`x-` prefixed keys** — permitted anywhere, for vendor extensions that want to live alongside specified fields.
- **`profiles`** (v0.92) — a namespaced object on `context`, `asset`, and `relationships[]`, keyed by profile id. Unlike `metadata`, a profile can publish a schema for its own key so the shape is documented and, once the validator resolves it, checkable — and unlike `x-`, two profiles' contributions on the same entity cannot collide, because each owns only its own key. See [Profiles → Registry extension profiles](/profiles#registry-extension-profiles).

```json
{
  "id": "grade_0140",
  "metadata": { "internal_ticket": "POST-4471" },
  "x-acme-cost-centre": "CC-88",
  "profiles": { "broadcast-newsroom": { "evidential_position": "PRIMARY" } }
}
```

All three are ignored by the validator's structural checks today and preserved byte-for-byte on round-trip — `profiles` resolution against a named profile's own schema is not yet wired in, see [Profiles → Registry extension profiles](/profiles#registry-extension-profiles) for what that means in practice. Anything you would have been tempted to add as a bare unknown key belongs in one of the three.

## Schema identity

| Artifact | URI |
|---|---|
| Core v0.92 (current) | `https://openworkflowgraph.org/schemas/core/v0.92.json` |
| Core v0.91 | `https://openworkflowgraph.org/schemas/core/v0.91.json` |
| Core v0.9 | `https://openworkflowgraph.org/schemas/core/v0.9.json` |
| `broadcast-newsroom` profile v0.1 | `https://openworkflowgraph.org/schemas/profiles/broadcast-newsroom/v0.1.json` |
| JSON-LD context | `https://openworkflowgraph.org/context/v0.9.jsonld` |

Both are JSON Schema **draft 2020-12**.

## The JSON-LD context

The context document maps OWG terms onto published vocabularies, so lineage is portable rather than locked to this schema:

```json
{
  "@context": {
    "@version": 1.1,
    "owg":    "https://openworkflowgraph.org/schemas/core/v0.9#",
    "omc":    "https://movielabs.com/omc/json/schema/v3.0#",
    "prov":   "http://www.w3.org/ns/prov#",
    "c2pa":   "https://c2pa.org/specifications/...#",
    "did":    "https://www.w3.org/ns/did#",
    "schema": "https://schema.org/",

    "id":          "@id",
    "description": "schema:description",
    "owg_version": "schema:version",
    "objective":   "owg:objective",

    "organizations":  { "@id": "owg:hasOrganization",  "@container": "@set" },
    "participants":   { "@id": "owg:hasParticipant",   "@container": "@set" },
    "infrastructure": { "@id": "owg:hasInfrastructure","@container": "@set" },
    "assets":         { "@id": "owg:hasAsset",         "@container": "@set" },
    "contexts":       { "@id": "owg:hasContext",       "@container": "@set" },
    "relationships":  { "@id": "owg:hasRelationship",  "@container": "@set" },
    "tasks":          { "@id": "owg:hasTask",          "@container": "@set" },

    "used":         { "@id": "prov:used",              "@type": "@id" },
    "produced":     { "@id": "prov:generated",         "@type": "@id" },
    "produced_by":  { "@id": "prov:wasGeneratedBy",    "@type": "@id" },
    "performed_by": { "@id": "prov:wasAssociatedWith", "@type": "@id" },
    "operated_by":  { "@id": "prov:actedOnBehalfOf",   "@type": "@id" },
    "works_for":    { "@id": "prov:actedOnBehalfOf",   "@type": "@id" },
    "predecessor":  { "@id": "prov:wasDerivedFrom",    "@type": "@id" },
    "depends_on":   { "@id": "owg:dependsOn",          "@type": "@id" },
    "ran_on":       { "@id": "owg:ranOn",              "@type": "@id" },

    "kind":             "owg:participantKind",
    "role":             "owg:role",
    "organization_id":  { "@id": "omc:organization",   "@type": "@id" },
    "identity":         "owg:identity",
    "credential_scope": { "@id": "owg:credentialScope","@container": "@set" },
    "did_identity":     { "@id": "did:subject",        "@type": "@id" },

    "type":             "owg:entityType",
    "version_relation": "owg:versionRelation",
    "state":            "owg:lifecycleState",
    "assurance":        "owg:assurance",
    "confidence":       "owg:confidence",
    "method":           "owg:inferenceMethod",
    "credentials":      { "@id": "c2pa:manifest",      "@type": "@id" },
    "identifiers":      { "@id": "owg:identifier",     "@container": "@set" },
    "scope":            "owg:identifierScope",
    "storage":          "owg:storage",
    "provider":         "owg:storageProvider",
    "locator":          "owg:storageLocator",
    "timerange":        "owg:timerange",
    "composes":         { "@id": "owg:composes",       "@container": "@set" },
    "component":        { "@id": "owg:component",      "@type": "@id" },
    "arc_type":         "owg:compositionArc",

    "executor":      "owg:executor",
    "ai_role":       "owg:aiRole",
    "failure_mode":  "owg:failureMode",
    "when":          "owg:condition",
    "checks":        { "@id": "owg:check",             "@container": "@set" },
    "on_pass":       { "@id": "owg:onPass",            "@type": "@id" },
    "on_fail":       { "@id": "owg:onFail",            "@type": "@id" },
    "editorial_signoff": "owg:editorialSignoff",
    "billing":       "owg:billing",

    "show_id":             "omc:Context.identifier",
    "territory":           "omc:territory",
    "work_type":           "owg:workType",
    "depicts":             { "@id": "owg:depicts",     "@type": "@id" },
    "depicts_real_entity": "owg:depictsRealEntity",

    "governance":         "owg:governance",
    "disclosure_gate":    "owg:disclosureGate",
    "max_total_reroutes": "owg:maxTotalReroutes",
    "budget_usd":         "owg:budget"
  }
}
```

Three mapping decisions are worth explaining, because they are the ones a reader would otherwise
query.

**Provenance terms resolve to PROV, not OMC.** A JSON-LD term carries one IRI, and OMC's current
identifier is a JSON Schema URL rather than a published RDF vocabulary. The OMC edge
correspondences are normative in their own right and listed in
[Standards bindings](/standards-bindings#omc-the-vocabulary-not-a-neighbour); a direct `omc:` RDF
mapping follows MovieLabs' RDF/SHACL release.

**Registry arrays are `@set`, not `@list`.** Order carries no meaning anywhere in an OWG document
— execution order comes from `depends_on`, never from array position. Declaring them unordered
makes that explicit to an RDF consumer rather than leaving it as an assumption.

**`works_for` and `operated_by` share an IRI.** Both are PROV delegation; they differ only in
whether the principal is an organization or a participant. Collapsing them in RDF is correct —
the distinction lives in the endpoint's `kind`, which is where a query should look for it.

Fields deliberately unmapped: `params`, `inputs`, `outputs`, `retry`, `subgraph`, `fan_out`, and
`metadata`. These are execution mechanics rather than assertions about the world, and giving them
RDF semantics would imply a meaning they do not carry.

## Where to go next

- [Tasks and executors](/tasks) — the work objects, and the fields that route them
- [Reference syntax](/reference-syntax) — the formal grammar for `$.` references and `when` expressions
- [Relationships](/relationships) — explicit edges, assurance, and confidence
- [Validation](/validation) — what is checked, and what a failure looks like
