# DPL JSON Schemas

Published [JSON Schema](https://json-schema.org/) (draft 2020-12) descriptions of the documents `dpl` reads and
writes. They let an agent write a `dpl.plan/1` plan, and read the engine's diagnostics, findings, questions, shot list and
replan result, without reading the C# source. Print any of them with the command line:

```bash
dpl schema                 # list: plan, findings, shot-list, constraints, diagnostics, missing, replan, call-sheet, shoot-pack, bench, takes, coverage-status, edit-guide, edit-export, brief, draft, draft-next, draft-gate, draft-grammar, summary, diff, dispositions
dpl schema plan            # the plan schema, on standard output
```

Each schema has a stable `$id` of the form `https://theamateur.co.uk/schemas/dpl/<name>.schema.json`. Nothing is
published at those addresses yet; the id is only a name, and schemas refer to each other by it (the shot list
reuses the plan's `ShotPicture`, `BeatFunction`, `ShotSize` and other definitions). A validator that does not fetch
documents needs the files loaded under their ids (`dpl.replan-1` refers to `dpl.findings-1`).

| File | Document | Direction | Made from |
|---|---|---|---|
| `dpl.plan-1.schema.json` | the plan, `dpl.plan/1` | `dpl` reads it | the reader's own contract (`PlanJson`), generated |
| `dpl.findings-1.schema.json` | `dpl readiness <plan> --format json`, `dpl.findings/1` | `dpl` writes it | the writer's contract (`FindingsDocument`), generated |
| `dpl.shot-list-1.schema.json` | `dpl shot-list <plan> --format json`, `dpl.shot-list/1` | `dpl` writes it | written by hand (`ShotList.Json` builds its document by hand too) |
| `dpl.constraints-1.schema.json` | the `dpl replan <plan> <constraints.json>` input | `dpl` reads it | the reader's contract (`ShootConstraints`, with the plan's own `kit` object), generated; since E4 it also holds minutes, places, people, kit and a finishing time |
| `dpl.diagnostics-1.schema.json` | `dpl validate <plan> --format json`, `dpl.diagnostics/1` | `dpl` writes it | the writer's contract (`DiagnosticsDocument`), generated |
| `dpl.missing-1.schema.json` | `dpl missing <plan> --format json`, `dpl.missing/1` | `dpl` writes it | the writer's contract (`MissingReport`), generated |
| `dpl.replan-1.schema.json` | `dpl replan <plan> <constraints.json> --format json`, `dpl.replan/1` | `dpl` writes it | the writer's contract (`ReplanResult`), generated; its `findings` is `dpl.findings-1` |
| `dpl.call-sheet-1.schema.json` | `dpl call-sheet <plan> --format json`, `dpl.call-sheet/1` | `dpl` writes it | the writer's contract (`CallSheetDocument`), generated |
| `dpl.shoot-pack-1.schema.json` | `dpl shoot-pack <plan> --format json`, `dpl.shoot-pack/1` | `dpl` writes it | the writer's contract (`ShootPackDocument`), generated; see `docs/dpl/DPL_SHOOT_PACK.md` |
| `dpl.bench-1.schema.json` | `dpl bench <corpus-dir> --format json`, `dpl.bench/1` | `dpl` writes it | the writer's contract (`BenchmarkResult`), generated |
| `dpl.takes-1.schema.json` | the take log, `dpl.takes/1` (`dpl takes validate`, `dpl takes merge`, `dpl coverage-status` read it) | `dpl` reads it | the reader's own contract (`TakesJson`), generated |
| `dpl.coverage-status-1.schema.json` | `dpl coverage-status <plan> <takes> --format json`, `dpl.coverage-status/1` | `dpl` writes it | the writer's contract (`CoverageStatusDocument`), generated |
| `dpl.edit-guide-1.schema.json` | `dpl edit-guide <plan> [<takes>] --format json`, `dpl.edit-guide/1` | `dpl` writes it | the writer's contract (`EditGuideDocument`), generated |
| `dpl.edit-export-1.schema.json` | `dpl export-edit <plan> <takes> --format ... --out ...`, the report `dpl.edit-export/1` beside the OTIO, FCPXML, EDL or ALE files | `dpl` writes it | the writer's contract (`EditExportDocument`), generated |
| `dpl.brief-1.schema.json` | the drafting brief, `dpl.brief/1` (`dpl draft start` reads it) | `dpl` reads it | the reader's own contract (`BriefJson`), generated |
| `dpl.draft-1.schema.json` | `dpl draft start <brief> --format json`, `dpl.draft/1`: a skeleton plan, the layers, the recipes, the first action | `dpl` writes it | the writer's contract (`DraftDocument`), generated; its `plan` is `dpl.plan-1` |
| `dpl.draft-next-1.schema.json` | `dpl draft next <plan> --format json`, `dpl.draft-next/1`: the layer reached, what it lacks, questions, findings, recipes, the next action | `dpl` writes it | the writer's contract (`DraftNextDocument`), generated; its `questions` are `dpl.missing-1` and its `findings` are `dpl.findings-1` |
| `dpl.draft-gate-1.schema.json` | `dpl draft gate <plan> --format json`, `dpl.draft-gate/1`: pass or fail with reasons and a certificate | `dpl` writes it | the writer's contract (`DraftGateDocument`), generated |
| `dpl.draft-grammar-1.schema.json` | `draft/draft-grammar-1.json`, `dpl.draft-grammar/1`: the coverage recipes and platform shapes the protocol reads | `dpl` reads it | the reader's contract (`DraftGrammar`), generated |
| `dpl.summary-1.schema.json` | `dpl summary <plan>`, `dpl.summary/1` | `dpl` writes it | the writer's contract (`PlanSummary`), generated |
| `dpl.diff-1.schema.json` | `dpl diff <old> <new>`, `dpl.diff/1` | `dpl` writes it | the writer's contract (`PlanDiff`), generated |
| `dpl.dispositions-1.schema.json` | `dpl disposition` writes it, `dpl readiness <plan> --dispositions <file>` reads it, `dpl.dispositions/1` | both | the reader's contract (`DispositionsDocument`), generated |

The prose format description is `docs/dpl/DPL_PLAN_FORMAT.md`. The schema is the exact list of members and terms;
the prose says what they are for.

## The short-form profile's data files

`profiles/` holds the versioned data the short-form profile (E2, 18 Oct 2026) reads, embedded in the plan library. They are not JSON
Schemas; they are the figures and rules the readiness rules apply, kept as data so nothing is hard-coded:

| File | What it holds | Made from |
|---|---|---|
| `profiles/short-timing-1.json` | how long a shoot takes: per-role setup, minimum record, pad, takes and reset (Shoot Plan's role names such as `MASTER`, `SINGLE`, `INSERT`), the day's preparation, location setup, sound check and contingency, one-take pre-roll and post-roll. Every value cites its Shoot Plan rule and `rules_version`. | Shoot Plan's planner rules `2026-10-06.r6` (`customer/shoot-plan-rules.mjs`, app-observability commit `c356cfde`), value for value |
| `profiles/short-kit-1.json` | what each camera move needs from the kit (which supports can make it, and the nearest moves to offer when the kit has none), the tolerances for a lens, multiplier or frame rate | written for the profile; MovieLabs' Gimbal Shot synonym of Steadicam Shot is why a gimbal can make the `steadicam` move |

Changing a figure is a change to the file and its version, not to code; `RDY-TIME` and `RDY-KIT` quote the version in the rules version of a short plan's findings.

## The drafting protocol's data file

`draft/draft-grammar-1.json` is the versioned data the drafting protocol (D1 to D3, 18 Oct 2026) reads, embedded in the drafting library: the coverage
recipes (the shots a kind of scene usually needs, as roles, sizes, angles and moves, never as words for a film; each cites the prior-art register entries
it rests on) and the picture shape of known posting platforms. `dpl.draft-grammar-1.schema.json` describes it, and a test holds every recipe to the plan
format. A grammar profile learned from studied films (I2) will be another file of the same shape. Changing a recipe is a change to the file and its version,
not to code.

## What each schema checks

### Plan (`dpl.plan-1`)

The schema matches the reader, `PlanJson.Read`, member for member:

- **Required members**: exactly those the C# type marks `required`. An absent term never falls back to a default.
- **Unknown members are refused** (`additionalProperties: false`) in every object, as the reader refuses them.
- **Terms** (`size`, `movement`, `kind` and the rest) are string enumerations in DPL's `snake_case` spelling. `"Hold"`,
  `"PushIn"` and `"static-shot"` are not terms.
- **Whole milliseconds**: every time is an `integer`. `1500.5` is refused.
- **Null**: an optional member may be written `null` (the reader accepts that and treats it as absent). A member that is
  not optional may not be null, and nor may a list.
- **Defaults** are shown as `default` on optional members that have one (for example a layer's `presence`). They are
  documentation; the reader applies them when the member is absent.
- **`format`** must be `"dpl.plan/1"`. (The reader takes any text there and leaves it to `DPL-FORMAT`; the schema
  names the one format it describes.)
- **Descriptions** are the doc comments from the C# source.

A plan that passes the schema is *readable*. It is *valid* only when `dpl validate` reports no error.

### What stays in the validator

A schema cannot express cross-references, time ordering or most editorial rules. `PlanValidator` (`dpl validate`,
and the first step of `dpl readiness`) enforces them, and reports each with a code. Errors stop the shot list and
the readiness checks from running; warnings do not. Every code below is checked into this table by a test.

| Code | Severity | What it enforces |
|---|---|---|
| `DPL-JSON` | error | Not a readable plan (written by the reader, with the JSON path): the rest of this table starts only after it reads. Includes a `null` where a list item or a map value should be (reported at each such item). |
| `DPL-FORMAT` | error | `format` is not `dpl.plan/1`. |
| `DPL-REQUIRED` | error | A required text is empty, or a member that another one makes necessary is missing (a turning line says who takes it; a join that serves the story says what happens). |
| `DPL-EMPTY` | error | No scenes, or a scene with no shots. |
| `DPL-DUPLICATE` | error | An id appears twice (shot ids are unique across the plan; a character, prop and location share a namespace; one phrase per carrier per scene; one of each function per scene). |
| `DPL-REF` | error | A reference does not resolve: a location, a scene's shot, a sound carrier, a layer, a speech line, a beat's speech, a picture's beat, a transition's shots. |
| `DPL-REFERENCE` | error | A diagram link names a node that is not in the diagram, or an intensity point or takeover names a carrier that is not a sound carrier. |
| `DPL-RECALL` | error | A recalled shot does not name the movement and shot it reuses, or a shot that is not a recall carries one. |
| `DPL-TIME` | error | Negative time; end not after start; scenes, shots, beats or path phases overlapping, out of order, or outside their scene or the work; target runtime not positive. |
| `DPL-RANGE` | error | A number outside its allowed range: a fraction outside 0 to 1, a non-positive duration or hold, a minimum above its maximum. |
| `DPL-GAP` | warning | Time covered by no scene, or no shot. (Not the time before the first scene of a plan that carries `slice_of`.) |
| `DPL-RUNTIME` | warning | The scenes do not add up to the target runtime. |
| `DPL-ANCHOR` | error | A speech anchor's authored time disagrees with its line, or measured words lack `source_ms`, or an anchored action lacks `at` and `duration_ms`. |
| `DPL-TIMING` | error | A caption's allowed hold range cannot be satisfied. |
| `DPL-OVERLAY` | error, warning | An overlay's kind does not match its members (an inset shows a subject; a counter runs from one number to another; only a diagram has nodes), or it is on screen too briefly to read. |
| `DPL-LAYER` | error, warning | A layer's depth, convergence or keys are inconsistent with its subject or shot (for example a converging layer says where it settles). |
| `DPL-STYLE` | error | A technique name is not lower-case words joined by underscores. |
| `DPL-MAP` | error | A map camera needs two valid `[west, south, east, north]` views and a positive interval inside its shot. |
| `DPL-RIG` | error | A puppet rig is inconsistent: parts, roles, ground, attachments or staging actions that name something that is not there. |
| `DPL-PATH` | error | A path phase gives a relation and its subject together, or neither. |
| `DPL-ATTENTION` | error | An attention beat gives a target and its effect together, or neither. |
| `DPL-RAMP` | error | A motion ramp goes to still, inside its shot. |
| `DPL-REMOVAL` | error | A removal takes away one layer or one sound carrier (or a stem of it). |
| `DPL-PHRASE` | error | A carried phrase changes its register (that is a transform). |
| `DPL-JOURNEY` | error | A journey has fewer than two stages, or a scene names a stage that no journey governs. (A destination or scene stage that is not one of the journey's stages is `DPL-REF`.) |
| `DPL-NONE` | error | A `none` entry that does not fit: a kind in the wrong place (a scene says `none` about a journey; the film about spoken lines), or a plan that contradicts what it declares absent (a scene that declares no `spoken_lines` has `lines`; one that declares no `line_of_action` has an `axis`; a film or sequence that declares no `journey` has one). (A repeat of a kind is `DPL-DUPLICATE`; a `line_of_action` with no `why` is `DPL-REQUIRED`.) |
| `DPL-TARGET` | error | A spoken line's typed target does not fit: a turning line said to the `camera` or the `audience` (nobody on screen takes it), or `among` on a line that is not said to the `group`, or naming the speaker. |
| `DPL-SOUND` | error | Sound targets out of range; a carrier holds two roles in one beat; an authored quiet withholds nothing; a bed lift follows no spoken line. |
| `DPL-SFX` | error | A pause, sound event or join does not fit the pause or speech it was made for. |
| `DPL-RHYTHM` | error | A line or event lands on a carrier that has no pulse. |
| `DPL-INTENSITY` | error, warning | Drums brought forward on something that is not music; a level that does not change. |
| `DPL-TAKEOVER` | error, warning | A takeover that does not rise, overlaps another, or hides an intensity point. |
| `DPL-SPEECH-JOIN` | error, warning | A spoken line starts inside a timed join, or too soon after one. |
| `DPL-STRANDS` | error, warning | The strands of a sequence or scene are inconsistent (one leads; a unison strikes once, all three together), or one strand leads every scene. |
| `DPL-TREATMENT` | error, warning | Not exactly one treatment taken; only one direction considered. |
| `DPL-APPEARANCE` | warning | A generated character's appearance does not fix what the picture will be asked for. |
| `DPL-RULE` | warning | A rule of light is described with words an image model will read as dark. |
| `DPL-MOTION` | warning | Less of the screen time is in motion than the target. |
| `DPL-VARIETY` | warning | Three joins in a row use the same device. |
| `DPL-PINGPONG` | warning | The frame hands back and forth between the same holder. |
| `DPL-MOMENTUM` | warning | Every change only moves attention; none moves the story on. |
| `DPL-CLOCK` | error | A short-form clock time that is not `HH:MM` (24-hour, two digits each), a day or window that ends at or before it starts, a malformed date, or a set-up with both `at` and `not_before`. |
| `DPL-KIT` | error | A short-form kit that contradicts itself: a phone with lenses or a camera with multipliers, a lens that is neither a prime nor a zoom, a shot with both `lens_multiplier` and `focal_mm` or a lens of the wrong kind for its `device`, `none` listed with other lights. |
| `DPL-PROFILE` | warning | Short-form members (`kit`, `schedule`, `people`, `delivery`, `filming`...) on a plan whose `work.profile` is not `short`: they are read and kept, and no short-form rule runs. |

Every diagnostic carries a `fix`: what to change, in the plan's own ids and paths, with the allowed ids or terms (the
closest match first, for a misspelling, by edit distance; a long set is cut to the nearest few). See "Diagnostics" below.

The same checks produce `DPL-*` findings in the findings document, as `integrity`, `creative_intent` or
`sound_attention` findings. The edit-readiness rules (`RDY-*`, and `REC-*` from `dpl reconcile`) are not described by
any schema, except that their codes are allowed in a finding; they are rules over a valid plan, listed in `docs/capability/EDIT_READINESS.md`.

### Findings (`dpl.findings-1`)

Describes the output of `dpl readiness <plan> --format json`. (Reconciling a plan with what was shot builds the same
document in the library, with `REC-*` codes; `dpl reconcile` prints it as Markdown.) Its members are:
`format`, `work_id`, `plan_hash` (64 hex digits), `rules_version`, `summary` (counts), `findings` and `not_assessed`.
A finding has an `id` (`f-` and 12 hex digits, stable across plan revisions), a `code`, `category`, `severity`,
`confidence` (kept separate on purpose), the plan ids it is about, `message` and `why`, `evidence` (plan path and fact),
`suggestions` and a `disposition` (`open` or `overridden`). A suggestion carries typed `changes` (the operations
`set`, `set_scene`, `insert_shot`, `remove_shot`, `set_join`, `add_state_change`) and its `validation`: whether the
change was applied to a copy and checked again, whether it resolves the finding, what it introduces, and the computed
`safe` flag.

The schema is for output: every member that is always written is required, null members are left out, and nothing
else may appear. What a schema cannot say: that `summary` counts the open findings by severity (and `overridden`
the others), and that `plan_hash` is the SHA-256 of the plan in its standard layout (`dpl format`). The tests check both on
real runs. A finding id names a rule and what it is about, so it is stable across revisions; it is not promised unique
within one document.

### Shot list (`dpl.shot-list-1`)

Describes `dpl shot-list <plan> --format json`: every shot with its DPL camera terms and the MovieLabs terms they map
to, the vocabulary's name, version and file hash, and (when the plan has them) each shot's `picture` and the plan's
`beat_functions`. The command writes this document by hand-building JSON, so the schema is written by hand too.
`industry` is null when the vocabulary has no term (`match: "none"`).

### Constraints (`dpl.constraints-1`)

The input of `dpl replan`: `max_shots` and `max_setups`, both optional whole numbers. An empty object is no constraints.

### Diagnostics (`dpl.diagnostics-1`)

Describes `dpl validate <plan> --format json` (plain `dpl validate` keeps its text output and exit codes). Its members are:
`schema` (`dpl.diagnostics/1`), `plan_hash` (64 hex digits, the same revision hash `dpl readiness` and `dpl missing` report;
**present only when the plan could be read**), `valid` (no error; warnings do not make a plan invalid), `summary`
(`errors`, `warnings`) and `diagnostics`. A diagnostic has a `code`, a `severity` (`error` or `warning`), a `path` (a JSON
path into the plan, always beginning `$`), the `message` and a `fix`. The message says what is wrong and never changes
between releases; the fix says what to do next and is never empty. The exit code is 0 when valid, 1 when not.

```json
{
  "code": "DPL-REF",
  "severity": "error",
  "path": "$.scenes[0].location",
  "message": "No location \"kitchn\".",
  "fix": "Add a location with id \"kitchn\" to $.locations, or set $.scenes[0].location to one of: kitchen, hall (closest to \"kitchn\": \"kitchen\")."
}
```

How an agent should use it: take each error in order, make the change the fix describes at the path, and validate
again. A `DPL-JSON` diagnostic stops everything after it (the plan is not read yet); it is reported alone, except that every
null list item or map value is reported at once. Warnings are for a director to look at; their fixes say how to clear them.

### Missing details (`dpl.missing-1`)

Describes `dpl missing <plan> --format json`: `schema` (`dpl.missing/1`), `work_id`, `plan_hash` and `rules_version`
(those of `dpl readiness`), `summary` and the `questions` to put to the human, each with its `kind`, `priority`, the plan
paths it `fills`, the rules it `unlocks` and the `answer_shape`. Exit 0, or 4 when a question blocks the assessment of a scene.

### Replan result (`dpl.replan-1`)

Describes `dpl replan <plan> <constraints.json> --format json`: `format` (`dpl.replan/1`), `met`, the shot and setup counts
before and after, each `sacrifice` with what it cost the edit, `kept` shots (named elsewhere in the plan), `plan` (the path
written with `--out`, or `"(not written)"`) and `findings`, the readiness document of the revised plan (`dpl.findings-1`).
When the limit cannot be met (exit 3), `unmet` says which and `unmet_fix` says by how much and what could be relaxed:
raise the limit to the number that remains, or lower `coverage.priority` on the musts.

### Benchmark result (`dpl.bench-1`)

Describes `dpl bench <corpus-dir> --format json`: `format` (`dpl.bench/1`), `rules_version`, `summary`, `mutants` and `clean_plans`.
Each mutant is scored against its own plan: only open findings that the mutant has and its plan lacks (by finding id) count.
A planted fault has an `outcome` of `caught`, `caught_other`, `masked` or `missed`; a decoy (no `expect`) is an `alarm` or `quiet`.
A mutant lists its `new_findings`, the `other_codes` of a `caught_other`, what it was `masked_by`, its `extra_new` findings, and whether a
safe fix was offered for the new expected finding and cleared it. `clean_plans` lists the open findings on the unmutated plans; they are
not called false positives. The document is deterministic (timings are left out). Exit 0 when every fault is `caught` and no decoy is an
`alarm`, 2 otherwise. The scoring is described in `docs/capability/READINESS_BENCHMARK.md`.

### Take log (`dpl.takes-1`)

Describes what `dpl takes validate`, `dpl takes merge` and `dpl coverage-status` read: `format` (`dpl.takes/1`), `work_id`, an optional `plan_hash` and the `takes`. A take has `op_id`, `shot`, `kind` and `verdict` (required) and optionally `supersedes`, `setup`, `take`, `slate` (American: `scene`, `angle`; European: `number`), `camera`, `files` (`camera`, `name`, `tc_in`, `tc_out`), `duration_ms`, `note`, `logged_at` and `by`. Unknown members and misspelt terms are refused, as in the plan; numbers are whole. The schema is the reader's contract, and it also carries the shapes the validator checks as patterns (an `op_id` without spaces, a camera letter, `HH:MM:SS:FF` timecodes, a file name without a path, an ISO 8601 `logged_at`, a take number from 1). What it cannot say stays in `dpl takes validate` (`TakesValidator`): the shot ids against the plan, `supersedes` pointing at an entry that exists and not looping, an `op_id` used for two takes, and slates that fit their system. Its codes (`DPL-TAKE*`, `DPL-TAKES-*`, `DPL-MERGE-*`, `DPL-COVERAGE-*`) are in the table in `docs/dpl/DPL_TAKES.md`, and a test holds the table to the source. `dpl takes validate --format json` writes `dpl.diagnostics/1` (above), its paths pointing into the take log. The prose description, the OMC term mapping and what every field is for are in `docs/dpl/DPL_TAKES.md`.

### Coverage status (`dpl.coverage-status-1`)

Describes `dpl coverage-status <plan> <takes> --format json`: `format` (`dpl.coverage-status/1`), `work_id`, `plan_hash`, `takes_hash`, `rules_version`, the resolved `scope` (with `days_from`: `schedule` or `logged_at`, when a day was asked for), the `summary` counts and `complete`, every `shots` entry in scope with its state and the `chosen` take, the `missing` shots (must-haves first) with a fix each, the `never_attempted` ids, the `edit_needs` (readiness findings that appear when the rules are run on the plan with only the shots that have a usable take, with the finding id and code, the missing shots that cause each and a fix), the `continuity` for the next setup, the `room_tone` status, the `takes` counts and the `warnings` (`dpl.diagnostics/1` items about the log). Every member listed is always written, or left out when null. **Exit code: 0 when `summary.complete` (every must-have shot in scope has a usable take), 5 when one is missing**, 1 when the plan or the take log cannot be read or the scope names nothing, 64 for wrong arguments. How the answer is worked out is in `docs/dpl/DPL_TAKES.md`.

### Editing guide (`dpl.edit-guide-1`)

Describes `dpl edit-guide <plan> [<takes>] --format json`: `format` (`dpl.edit-guide/1`), `work_id`, `title`, `plan_hash`, an optional `takes_hash` (only when a take log was given), `rules_version`, the `reaction_window_ms`, the `summary` counts, the `scenes` in story order and the `notes` about the whole work, the findings `set_aside` (about the shoot, not the edit) and the `warnings`. A scene has its `purpose`, `change`, `entry`, `functions`, its `shots` (each with what it is for, its coverage `role` and `priority`, its dramatic `function`, the `cut` out of it with the `basis`, an optional `offset` of `l_cut` or `j_cut`, and, given a take log, its `take` with the `chosen` take), the `bridges` (which cutaway or insert covers which jump), the `sound` to lay and the `notes`. Every note points at a readiness finding (`finding_id`, a `dpl.findings/1` id) and/or at plan shot ids (`shots`); a test holds that to be so. The prose is in `docs/dpl/DPL_EDIT_EXPORT.md`.

### Export report (`dpl.edit-export-1`)

Describes the report `dpl export-edit ... --out ...` writes to standard output: `format` (`dpl.edit-export/1`), `work_id`, the two hashes, `edit_format` (`otio`, `fcpxml`, `edl` or `ale`), `frame_rate` (`24`, `25`, `23.976` or `29.97`), `drop_frame`, `timecode_start`, `media_root`, `sync_sound`, the `files` written (an EDL is one list for each video track, split at 999 events, with `part` and `of`), the `tracks`, the `duration_ms`, the count of `clips`, the `gaps` (slugs and gaps with the reason), the `media` to relink with the `reel` of each file, the `warnings` and what the format `loses`. The files themselves are OpenTimelineIO JSON, FCPXML 1.10, CMX 3600 or Avid ALE text and have no JSON Schema of their own (OTIO's serialized schema is OpenTimelineIO's; a test checks the file against it and reads it back with the OpenTimelineIO package). The prose is in `docs/dpl/DPL_EDIT_EXPORT.md`.
Describes `dpl coverage-status <plan> <takes> --format json`: `format` (`dpl.coverage-status/1`), `work_id`, `plan_hash`, `takes_hash`, `rules_version`, the resolved `scope`, the `summary` counts and `complete`, every `shots` entry in scope with its state and the `chosen` take, the `missing` shots (must-haves first) with a fix each, the `never_attempted` ids, the `edit_needs` (readiness findings that appear when the rules are run on the plan with only the shots that have a usable take, with the finding id and code, the missing shots that cause each and a fix), the `continuity` for the next setup, the `room_tone` status, the `takes` counts and the `warnings` (`dpl.diagnostics/1` items about the log). Every member listed is always written, or left out when null. **Exit code: 0 when `summary.complete` (every must-have shot in scope has a usable take), 5 when one is missing**, 1 when the plan or the take log cannot be read or the scope names nothing, 64 for wrong arguments. How the answer is worked out is in `docs/dpl/DPL_TAKES.md`.
### Plan summary, plan diff and dispositions (`dpl.summary-1`, `dpl.diff-1`, `dpl.dispositions-1`)

The context tools for an agent whose context is short; `docs/dpl/DPL_CONTEXT_TOOLS.md` says what each is for and how it behaves.

- **`dpl.summary/1`** (`dpl summary <plan> [--findings f.json]`): `format`, `work_id`, `title`, `plan_hash`, `rules_version`,
  `runtime_ms`, `counts`, `profile` (a short-form plan), `thesis`, `journey`, `sequences`, `scenes` (as many as the word budget allows, with `*_omitted` counts for the
  rest), `scene_ids` (every scene), `findings` (open counts, closed count, the first five open findings; only when a findings
  document was given) and `missing` (the `dpl missing` counts). Written members only; deterministic.
- **`dpl.diff/1`** (`dpl diff <old> <new>`): `old_plan_hash`, `new_plan_hash`, `summary` (counts), `entities` (each entity that
  differs: `kind`, `id`, `change` of `added`, `removed`, `changed` or `possible_rename`, `fields` with `path`, `change`, `old` and `new`),
  `findings` (`new`, `resolved`, `unchanged`, when both findings documents were given) and `markdown`. A value of a field may be any JSON.
- **`dpl.dispositions/1`**: `format` and `dispositions`, each `{finding_id, disposition, reason, at_plan_hash, evidence_hash, by, at}`.
  The input of `dpl readiness --dispositions`; unknown members are refused, and so is a `reason` or `by` that is empty, a malformed hash or
  a `at` that is not an ISO 8601 date and time with a zone. What the schema cannot say: that `evidence_hash` is the SHA-256 of the
  finding's code, its sorted entities and its sorted evidence facts (a test computes it again from the findings).

A plan that `dpl slice` cuts is an ordinary `dpl.plan/1`; the one member that marks it is `slice_of` (`plan_hash`, `scenes`, `shots`),
which a hand-written plan never needs.

The findings document gained, for these tools, two members of a finding (`remembered` and `reopened_because`) and two values of
`disposition` (`accepted`, `wont_fix`); a finding the tools have not touched is written as before.

## Known gaps between a schema and the reader

The reader (.NET `System.Text.Json`, strict options) is the authority. These are the places a generic JSON Schema
validator is more lenient than it, or the schema leaves a check to the code:

- **Whole numbers.** JSON Schema calls `1500.0` an `integer`; the reader refuses it. Write whole milliseconds with no
  decimal point or exponent. (A test pins this.)
- **Null list items** are no longer a gap. The serializer takes `null` as an item of a list (`"scenes": [null]`) or a value
  of a map, although the item types are not nullable, and the validator used to throw on a null scene. `PlanJson.Read` now
  refuses them with a `DPL-JSON` diagnostic at the item, as the schema does, and the differential test holds the two together.
- **`format`.** The reader takes any text for `format` and leaves it to `DPL-FORMAT`; the schema requires `dpl.plan/1`.
- **64-bit range.** Times are 64-bit integers (counts and word indexes 32-bit); the schemas do not state the bounds.
- **Depth.** The reader refuses documents nested deeper than 32 levels. Real plans are far shallower.
- **Strict JSON.** Comments and trailing commas are refused by the reader.
- **Clock times and dates.** The short-form profile writes clock times as `"HH:MM"` (24-hour, two digits) and a day's `date` as `YYYY-MM-DD`. The reader takes any text; `DPL-CLOCK` refuses anything else. The schema leaves it to the validator so that it and the reader stay identical.
- **Replan constraints** may also be written as a bare `null`, which the reader takes as no constraints.
- **Findings are read back leniently.** `dpl apply` ignores members it does not know, so an older `dpl` can read a
  newer findings document; the schema, being for output, does not allow them.

## How the schemas are kept in step with the code

Most of each schema is generated from the code, and the rest is checked against it, in the DPL test projects
(`tests/contract/VideoEditor.Dpl.Plan.Tests`, `VideoEditor.Dpl.Readiness.Tests` and `VideoEditor.Dpl.Takes.Tests`, sharing
`tests/contract/DplSchemaSupport`). Any change to a field, a term or a requirement in C# fails one of these until the
schema is updated.

- **Contract test.** The committed plan, findings, constraints, diagnostics, missing, replan and bench schemas are compared with a schema the test builds
  from the serializer's own contract for the same types (names, requiredness, nullability, enumerated terms and
  defaults). Prose (descriptions, `$comment`) is not compared, so rewording a doc comment never breaks the build.
- **Reflection test.** Independently of the serializer, every record's member names in the schema are compared with
  the C# type's property names in DPL's `snake_case`, and the required members with the `required` modifiers, so adding
  a field in C# without updating the schema fails the build.
- **Real documents.** Every plan under `tests/corpus/**` and the contract fixtures validates against the plan schema;
  each mutant of `tests/corpus/readiness/mutations.json` still does once applied (mutants are readiness faults, not
  schema faults); the output of the real command line (`dpl readiness --format json`, `dpl shot-list --format json`, `dpl validate --format json`,
  `dpl missing --format json`, and `dpl replan --format json` under four sets of constraints)
  on every one of those plans validates against the findings, shot-list, diagnostics, missing and replan schemas.
- **Differential test.** About 2,500 one-step changes to a plan that uses every member of the schema (built from the
  schema alone), and about 1,000 to the repository's real plans (remove a member, null it, give it the wrong type,
  replace a term, add an unknown member, write a fraction), are each given to the schema and to the reader; they must
  give the same verdict. The two exceptions above are the only ones.
- **Deliberately broken plans** (an unknown member, a misspelt term, a fractional millisecond, a missing required
  member, and others) fail both the schema and `PlanJson.ReadAndValidate`.
- **`dpl schema`** prints the files above, embedded in the readiness library, and a test holds the output equal to them. The HTTP service (`docs/operations/DPL_SERVICE.md`) serves the same embedded files at `GET /v1/schemas/{name}`.
- **The prose format page.** `docs/dpl/DPL_PLAN_FORMAT.md` is held to the plan schema by `PlanFormatDocTests`: every fenced
  `json` example is a complete plan that reads and validates with no diagnostics, or a fragment marked
  `<!-- dpl:fragment scenes[0].shots[0] -->` that fits that part of the schema (or a document of another schema, marked
  `<!-- dpl:document dpl.diagnostics-1 -->`); every member name in the plan schema appears in the page as `` `name` ``; and
  so does every term of every enumeration, unless the test's allow-list lists it with a reason.
- **This table.** Every `DPL-*` code in `PlanValidator.cs` and `PlanJson.cs` must appear in the table above, and have a
  real broken plan in `AgentErrorsTests` that produces it with a fix that names the path.
- **The fixes.** For 39 of the 41 codes a test applies the fix by hand to the broken plan and checks the diagnostic is gone, and
  a reviewed golden file (`tests/contract/VideoEditor.Dpl.Plan.Tests/Golden/diagnostics.golden.json`) holds the exact JSON of
  eleven. To change it after rewording a fix: `DPL_GOLDEN_REGENERATE=1 dotnet test tests/contract/VideoEditor.Dpl.Plan.Tests -c Release --filter "FullyQualifiedName~golden"`, then read the diff.

The tests use a small validator for the subset of JSON Schema these files use (`MiniJsonSchema`), so the repository
takes no schema-validator package. Any complete draft 2020-12 validator reads the same files.

To change a schema after changing the code:

```bash
DPL_SCHEMA_REGENERATE=1 dotnet test tests/contract/VideoEditor.Dpl.Plan.Tests -c Release --filter "FullyQualifiedName~Committed_"
DPL_SCHEMA_REGENERATE=1 dotnet test tests/contract/VideoEditor.Dpl.Readiness.Tests -c Release --filter "FullyQualifiedName~Committed_"
DPL_SCHEMA_REGENERATE=1 dotnet test tests/contract/VideoEditor.Dpl.Takes.Tests -c Release --filter "FullyQualifiedName~Committed_"
```

then read the diff, and update this README and `dpl.shot-list-1.schema.json` (written by hand) if the change touches
them. A change to a term or requirement that existing plans rely on is a new format version (`dpl.plan/2`), not an edit
to this schema.
