# Conformance

> What makes a document conforming, every numbered rule, and what the reference checker does not test.

A document **conforms** when it validates against the JSON Schema its `ocp` value selects and breaks none of the error rules below that apply to its version. Warnings do not make a document non-conforming; they point at something a consumer may want to know. A producer conforms when every document it writes conforms, and a consumer conforms when it reads every conforming 0.1, 0.2 and 0.3 document, ignoring what it does not know.

The rules cover what JSON Schema cannot express: references between entities, cycles, sums and consistency across fields. Each has a code: an `E` code is an error, a `W` code a warning.

> **Note:** The schema, with the meaning its descriptions give each field, and the rules on this page define OCP; the checker does not. `loopmath ocp validate` implements every numbered rule, and the code in each finding is the one on this page; where the two disagree, the checker has a bug. A requirement stated only in a field description is normative too, but the checker does not test every one, so a clean result does not prove a document meets every description. See [What the checker does not test](#checker-limits) and [Validate a document](https://orchestrationcontextprotocol.io/docs/develop/validate).

## Applicability

Under each code is its level and the version that added it. A rule applies to a document whose `ocp` version is that version or later. A 0.1 document is checked against the 0.1 rules only, even by a 0.3 checker, and a field that a later version added is an unknown field in an earlier document, so it is ignored.

## Reading the document

| Code | Rule |
|---|---|
| <span id="e001">E001</span><br /><small>error, since 0.1</small> | The file can be read and parsed as JSON. |
| <span id="e002">E002</span><br /><small>error, since 0.1</small> | The top-level value is an object. |
| <span id="e003">E003</span><br /><small>error, since 0.1</small> | `ocp` is present and names a known version (`0.1`, `0.2` or `0.3`). The other rules still run. |
| <span id="e010">E010</span><br /><small>error, since 0.1</small> | The document validates against the JSON Schema of its version. One finding per schema error. |
| <span id="e011">E011</span><br /><small>error, since 0.1</small> | The checker could load that schema. A checker fault, not a document fault. |
| <span id="w001">W001</span><br /><small>warning, since 0.1</small> | Schema validation was skipped because the checker has no JSON Schema library. The numbered rules still ran. |

## Ids and lists

| Code | Rule |
|---|---|
| <span id="e100">E100</span><br /><small>error, since 0.1</small> | Node ids are unique. |
| <span id="e101">E101</span><br /><small>error, since 0.1</small> | Attempt ids are unique. |
| <span id="e102">E102</span><br /><small>error, since 0.1</small> | Group ids are unique. |
| <span id="e103">E103</span><br /><small>error, since 0.2</small> | Artifact ids are unique. |
| <span id="e104">E104</span><br /><small>error, since 0.1</small> | `nodes`, `edges`, `attempts`, `events`, `groups` and `artifacts` are arrays when present, never `null`. |

## Run graph

| Code | Rule |
|---|---|
| <span id="e110">E110</span><br /><small>error, since 0.1</small> | `edge.from` names an existing node. |
| <span id="e111">E111</span><br /><small>error, since 0.1</small> | `edge.to` names an existing node. |
| <span id="e115">E115</span><br /><small>error, since 0.1</small> | `node.group` names an existing group. |
| <span id="e120">E120</span><br /><small>error, since 0.1</small> | `group.parent` names an existing group. |
| <span id="e121">E121</span><br /><small>error, since 0.1</small> | Group parent links do not form a cycle. |
| <span id="e122">E122</span><br /><small>error, since 0.1</small> | `dep` and `fan_in` edges together form a directed acyclic graph. `spawn`, `launch` and `artifact` edges record lineage, not scheduling, and MAY form cycles. |
| <span id="e160">E160</span><br /><small>error, since 0.2</small> | Every edge carries a `tier`, whatever its kind and whoever wrote it. |

See [Run graph](https://orchestrationcontextprotocol.io/specification/0.3/run-graph).

## Attempts and outcomes

| Code | Rule |
|---|---|
| <span id="e112">E112</span><br /><small>error, since 0.1</small> | `attempt.node` names an existing node. |
| <span id="e130">E130</span><br /><small>error, since 0.1</small> | A terminal attempt's `outcome.result` equals its `status`. |
| <span id="w131">W131</span><br /><small>warning, since 0.1</small> | A terminal attempt has an `outcome`. |
| <span id="w132">W132</span><br /><small>warning, since 0.1</small> | Only a terminal attempt has an `outcome`. |
| <span id="w140">W140</span><br /><small>warning, since 0.1</small> | `cause.ref` names an existing attempt. |
| <span id="w141">W141</span><br /><small>warning, since 0.1</small> | `outcome.via` names an existing node. |
| <span id="e119">E119</span><br /><small>error, since 0.2</small> | `origin.launched_by` names another existing attempt. |
| <span id="e161">E161</span><br /><small>error, since 0.2</small> | When `origin.launched_by` is set, a `launch` edge runs from the launcher's node to this attempt's node. |
| <span id="e165">E165</span><br /><small>error, since 0.2</small> | An origin is not both `external: true` and launched by an attempt. |

Terminal statuses are `done`, `failed`, `rejected`, `canceled`, `settled_unverified` and `lost`. See [Attempts](https://orchestrationcontextprotocol.io/specification/0.3/attempts).

## Artifacts

| Code | Rule |
|---|---|
| <span id="e116">E116</span><br /><small>error, since 0.2</small> | An `artifact` edge names an artifact. |
| <span id="e117">E117</span><br /><small>error, since 0.2</small> | The artifact an edge names exists. |
| <span id="e118">E118</span><br /><small>error, since 0.2</small> | `artifact.producer`, `writers` and `consumers` name existing attempts. |
| <span id="e123">E123</span><br /><small>error, since 0.2</small> | An edge's `from_attempt` and `to_attempt` name existing attempts. |
| <span id="e124">E124</span><br /><small>error, since 0.2</small> | `from_attempt` is an attempt at the edge's `from` node, and `to_attempt` one at its `to` node. |
| <span id="e125">E125</span><br /><small>error, since 0.2</small> | An `artifact` edge carries both `from_attempt` and `to_attempt`. |
| <span id="e162">E162</span><br /><small>error, since 0.2</small> | `artifact.producer` is `writers[0]`: the producer is the first writer. |
| <span id="e163">E163</span><br /><small>error, since 0.2</small> | A known `n_writes` is at least the number of writers, and a known `n_reads` at least the number of consumers. A `null` count is unknown and passes. |
| <span id="e164">E164</span><br /><small>error, since 0.2</small> | An artifact edge's `from_attempt` is the artifact's producer, and its `to_attempt` is one of its consumers. |
| <span id="e166">E166</span><br /><small>error, since 0.2</small> | `writers` is not empty, and no attempt appears twice in `writers` or in `consumers`. |
| <span id="e167">E167</span><br /><small>error, since 0.2</small> | `writers` and `consumers` are arrays when present, never `null`. |

See [Artifacts](https://orchestrationcontextprotocol.io/specification/0.3/artifacts).

## Cost

| Code | Rule |
|---|---|
| <span id="e170">E170</span><br /><small>error, since 0.2</small> | When `cache_creation_tokens`, `cache_creation_5m_tokens` and `cache_creation_1h_tokens` are all present, the two buckets add up to the total. |
| <span id="e171">E171</span><br /><small>error, since 0.2</small> | When `producer.name` names loopmath's log extractor (`loopmath`, or `dagr` in documents written before its rename, alone or followed by a non-word character, as in `loopmath/graph`), every cost record has `basis: measured`. One exception, from 0.3 and only when `producer.name` is loopmath's: `basis: allocated` is allowed when `cost.ext["dev.loopmath.logmatch"]` is an object whose `tier` is `heuristic` or whose `shared_session` is `true` or a non-empty object. |

See [Cost](https://orchestrationcontextprotocol.io/specification/0.3/cost).

## Events

| Code | Rule |
|---|---|
| <span id="e113">E113</span><br /><small>error, since 0.1</small> | `event.node` names an existing node. |
| <span id="e114">E114</span><br /><small>error, since 0.1</small> | `event.attempt` names an existing attempt. |
| <span id="w150">W150</span><br /><small>warning, since 0.1</small> | Events are in ascending time order. |

See [Events](https://orchestrationcontextprotocol.io/specification/0.3/events).

## Producer, extensions and vocabulary

| Code | Rule |
|---|---|
| <span id="w180">W180</span><br /><small>warning, since 0.2</small> | `producer.capabilities` is present. |
| <span id="e180">E180</span><br /><small>error, since 0.2</small> | No capability is declared `false` while the document contains that element. From 0.3 this covers `task`, `configuration`, `signals`, `slate` and `receipt` too. |
| <span id="e181">E181</span><br /><small>error, since 0.1</small> | No `ext` key uses a retired prefix: bare `dagr.` (write `dev.loopmath.`, or `dev.dagr.` before 0.3) or bare `ocp.` (write `io.orchestrationcontextprotocol.`). |
| <span id="w182">W182</span><br /><small>warning, since 0.3</small> | No `ext` key uses `dev.dagr.`, loopmath's namespace before 0.3. Such keys are still read through 0.4; write `dev.loopmath.`. |
| <span id="w200">W200</span><br /><small>warning, since 0.2</small> | A string in an open vocabulary comes from its recommended list. Other values are valid; the warning helps catch typos. |

See [Document](https://orchestrationcontextprotocol.io/specification/0.3/document#producer) and [Extensions](https://orchestrationcontextprotocol.io/specification/0.3/extensions).

## The observation

These rules are new in 0.3. Each has a passing and a failing example document; see [Rule pairs](https://orchestrationcontextprotocol.io/examples#rule-pairs).

| Code | Rule |
|---|---|
| <span id="e190">E190</span><br /><small>error, since 0.3</small> | `run.configuration.id` equals the [canonical id](https://orchestrationcontextprotocol.io/specification/0.3/configuration#configuration-id) of its workflow and settings. A workflow given by reference is resolved through the reader's catalog; a reader that cannot resolve it skips this rule and E191. [Pair](https://orchestrationcontextprotocol.io/examples#e190) |
| <span id="e191">E191</span><br /><small>error, since 0.3</small> | Every top-level piece of the workflow has an object in `configuration.settings` under its id, except a piece that holds a nested workflow and has no role. [Pair](https://orchestrationcontextprotocol.io/examples#e191) |
| <span id="e192">E192</span><br /><small>error, since 0.3</small> | Piece and artifact ids are unique in one namespace; every edge joins an existing piece and an existing artifact; the edges form no cycle. Nested inline workflows are checked the same way. [Pair](https://orchestrationcontextprotocol.io/examples#e192) |
| <span id="e193">E193</span><br /><small>error, since 0.3</small> | Every `control.gates` entry is a piece; every `control.repair` key is a gate and every value a piece. [Pair](https://orchestrationcontextprotocol.io/examples#e193) |
| <span id="e194">E194</span><br /><small>error, since 0.3</small> | The run's id is among `slate.members`. `task.base_commit` equals `slate.base_commit` when both are present. A preference that names a slate names this run's slate. A preference's `winner` is `tie` or one of the preference's `members`, or of the slate's members when the preference lists none. Across documents checked together, documents with the same `slate.id` share `task.id` and the base commit (`task.base_commit`, else `slate.base_commit`). [Pair](https://orchestrationcontextprotocol.io/examples#e194) |
| <span id="e195">E195</span><br /><small>error, since 0.3</small> | A signal's value fits its kind: a verdict is `accept`, `reject`, `pass`, `fail` or `error`; a score is a number, or `null` when declared and not measured yet; a score with `scale: fraction` lies in [0, 1]; an event's value is a reference string. `acceptance_rule.score.name` names no verdict or event signal; when `acceptance_rule.score.scale` is `fraction`, its `target` lies in [0, 1]; and `acceptance_rule.requires` names no score or event signal. [Pair](https://orchestrationcontextprotocol.io/examples#e195) |
| <span id="w196">W196</span><br /><small>warning, since 0.3</small> | Only loopmath writes `run.receipt`. [Pair](https://orchestrationcontextprotocol.io/examples#w196) |

The cross-file part of E194 can only be checked when a checker reads the slate's documents together, as `loopmath ocp validate` does when given several files.

## What the checker does not test

A clean result from `loopmath ocp validate` means the document passed the schema and every numbered rule. It does not prove the document meets every requirement in the field descriptions. The reference checker in loopmath 0.1.0 does not test the following; a consumer that relies on them should check them itself:

- Whether a preference's own `members` are members of the slate. When a preference lists members, E194 checks its winner against them only.
- Whether documents with the same `slate.id` carry the same slate object. Across files, E194 compares only `task.id` and the base commit.
- Whether a timestamp is a real date and time. The schema checks its shape, so a value such as `2026-99-99T99:99:99+99:99` passes.
- The length of strings the schema does not cap, such as `task.features` values, setting `options` and `ext` payloads, so a pass is not a size or privacy filter ([Privacy](https://orchestrationcontextprotocol.io/specification/0.3/privacy)).

OCP 0.4 plans warnings, not errors, for the first three: a reader repairs or ignores such a value with a note, and treats an impossible timestamp as missing. 0.4 also plans a warning for a `fraction` signal whose own `target` lies outside [0, 1].

## Not checked mechanically

Some requirements cannot be checked from the document alone. A producer is bound by them all the same:

- Under `metadata_only`, no field holds prompt, completion or transcript text ([Privacy](https://orchestrationcontextprotocol.io/specification/0.3/privacy)). A checker cannot tell a short label from a pasted prompt.
- `basis: "measured"` is written only for counts summed from usage records of that attempt ([Cost](https://orchestrationcontextprotocol.io/specification/0.3/cost#basis)).
- A tier is never upgraded without new mechanical evidence ([Attempts](https://orchestrationcontextprotocol.io/specification/0.3/attempts#evidence-tiers)).
- Ids SHOULD stay stable when the same run is written again ([Document](https://orchestrationcontextprotocol.io/specification/0.3/document)).

## Adding a rule

A new rule arrives in a minor version, through a [proposal](https://orchestrationcontextprotocol.io/community/proposals), with a new code, a line on this page, a passing and a failing example document, and the check in the reference checker.
