{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://theamateur.co.uk/schemas/dpl/dpl.draft-next-1.schema.json",
  "title": "DPL draft next (dpl.draft-next/1)",
  "description": "What `dpl draft next <plan> --format json` writes: which layer the plan has reached, what that layer still lacks as concrete plan paths, the `dpl missing` questions of that layer, the open findings of completed layers with their tested fixes in the order edits are judged, the recipe that fits each scene with no shots yet, advice that blocks nothing, and one next action. Every member listed here is always written, or left out when null; nothing else appears.",
  "$comment": "It never writes content for the plan. `questions` are `dpl.missing/1` questions and `findings` are `dpl.findings/1` findings, by reference. A next action always names plan paths or a command.",
  "type": "object",
  "properties": {
    "format": {
      "type": "string",
      "const": "dpl.draft-next/1"
    },
    "work_id": {
      "type": "string"
    },
    "profile": {
      "type": "string",
      "enum": [
        "film",
        "short"
      ]
    },
    "plan_hash": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$"
    },
    "rules_version": {
      "type": "string"
    },
    "grammar_version": {
      "type": "string"
    },
    "reached": {
      "description": "The id of the first layer that is not complete, or done when every layer is.",
      "type": "string",
      "enum": [
        "work",
        "world",
        "structure",
        "coverage",
        "performance",
        "sound",
        "production",
        "done"
      ]
    },
    "layers": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/LayerProgress"
      }
    },
    "missing": {
      "description": "What the reached layer still lacks, as concrete paths with instructions. Empty when every layer is complete.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/MissingItem"
      }
    },
    "questions": {
      "description": "The `dpl missing` questions of the reached layer (all open ones when every layer is complete), to put to the human.",
      "type": "array",
      "items": {
        "$ref": "https://theamateur.co.uk/schemas/dpl/dpl.missing-1.schema.json#/$defs/MissingQuestion"
      }
    },
    "findings": {
      "description": "Open findings from completed layers (blockers from any layer), in the order edits are judged, each with its tested fixes.",
      "type": "array",
      "items": {
        "$ref": "https://theamateur.co.uk/schemas/dpl/dpl.findings-1.schema.json#/$defs/Finding"
      }
    },
    "findings_open": {
      "description": "All open findings, shown or not.",
      "type": "integer",
      "minimum": 0
    },
    "findings_deferred": {
      "description": "Open findings left out because their layer is not complete yet; they come back when it is.",
      "type": "integer",
      "minimum": 0
    },
    "findings_not_shown": {
      "description": "Open findings beyond the ones listed in `findings` (the list is capped); `dpl readiness` lists them all.",
      "type": "integer",
      "minimum": 0
    },
    "recommendations": {
      "description": "For each scene with no shots yet, the recipe that fits it.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/RecipeRecommendation"
      }
    },
    "recipes": {
      "description": "The recipes the recommendations name, in full.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/Recipe"
      }
    },
    "advice": {
      "description": "Things worth writing that do not block anything.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/AdviceItem"
      }
    },
    "next_action": {
      "$ref": "#/$defs/NextAction"
    }
  },
  "required": [
    "format",
    "work_id",
    "profile",
    "plan_hash",
    "rules_version",
    "grammar_version",
    "reached",
    "layers",
    "missing",
    "questions",
    "findings",
    "findings_open",
    "findings_deferred",
    "findings_not_shown",
    "recommendations",
    "recipes",
    "advice",
    "next_action"
  ],
  "additionalProperties": false,
  "$defs": {
    "AdviceItem": {
      "type": "object",
      "properties": {
        "layer": {
          "type": "string"
        },
        "path": {
          "type": [
            "string",
            "null"
          ]
        },
        "text": {
          "type": "string"
        }
      },
      "required": [
        "layer",
        "text"
      ],
      "additionalProperties": false
    },
    "LayerProgress": {
      "type": "object",
      "description": "Where one layer stands.",
      "properties": {
        "id": {
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "state": {
          "$ref": "#/$defs/LayerState"
        },
        "missing_required": {
          "description": "Required items still missing (placeholders count).",
          "type": "integer",
          "minimum": 0
        },
        "open_questions": {
          "description": "`dpl missing` questions of this layer that are still open.",
          "type": "integer",
          "minimum": 0
        },
        "open_findings": {
          "description": "Open findings whose check belongs to this layer.",
          "type": "integer",
          "minimum": 0
        }
      },
      "required": [
        "id",
        "title",
        "state",
        "missing_required",
        "open_questions",
        "open_findings"
      ],
      "additionalProperties": false
    },
    "LayerState": {
      "type": "string",
      "enum": [
        "complete",
        "current",
        "waiting"
      ]
    },
    "MissingItem": {
      "type": "object",
      "description": "A required item that is missing: where, and what to write there.",
      "properties": {
        "layer": {
          "type": "string"
        },
        "path": {
          "description": "A concrete plan path ($.scenes[1].shots); not a pattern.",
          "type": "string"
        },
        "instruction": {
          "type": "string"
        },
        "scene": {
          "type": [
            "string",
            "null"
          ]
        },
        "shot": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "layer",
        "path",
        "instruction"
      ],
      "additionalProperties": false
    },
    "NextAction": {
      "type": "object",
      "description": "The one thing to do next. It always names a plan path, a finding or a command: paths are plan paths that exist or that the schema defines; tool is the command to run when the step needs one. After the step, call dpl draft next again.",
      "properties": {
        "kind": {
          "$ref": "#/$defs/NextActionKind"
        },
        "layer": {
          "description": "The layer the step belongs to.",
          "type": [
            "string",
            "null"
          ]
        },
        "scene": {
          "type": [
            "string",
            "null"
          ]
        },
        "shot": {
          "type": [
            "string",
            "null"
          ]
        },
        "finding": {
          "description": "The finding to resolve or the finding a fix is for (dpl.findings/1 id).",
          "type": [
            "string",
            "null"
          ],
          "pattern": "^f-[0-9a-f]{12}$"
        },
        "fix": {
          "description": "The suggestion to apply (its id on the finding).",
          "type": [
            "string",
            "null"
          ]
        },
        "instruction": {
          "description": "In words, what to do; ends with the call to make afterwards.",
          "type": "string"
        },
        "paths": {
          "description": "Plan paths the step writes or changes.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "tool": {
          "description": "The command to run for this step, when it needs one (dpl apply, dpl draft gate, dpl validate); otherwise the command to call after writing.",
          "type": "string"
        }
      },
      "required": [
        "kind",
        "instruction",
        "paths",
        "tool"
      ],
      "additionalProperties": false
    },
    "NextActionKind": {
      "type": "string",
      "description": "What kind of step the next action is.",
      "enum": [
        "fix_plan_error",
        "write_layer",
        "apply_fix",
        "resolve_finding",
        "run_gate"
      ]
    },
    "Recipe": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "summary": {
          "type": "string"
        },
        "use_when": {
          "type": "string"
        },
        "signals": {
          "$ref": "#/$defs/RecipeSignals"
        },
        "slots": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/RecipeSlot"
          }
        },
        "notes": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "sources": {
          "description": "Prior-art register entries the recipe is based on (docs/prior-art/entries/PA-…).",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "shoot_plan": {
          "description": "The Shoot Plan recipe it was ported from or matches, when there is one.",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "id",
        "title",
        "summary",
        "use_when",
        "signals",
        "slots",
        "notes",
        "sources"
      ],
      "additionalProperties": false
    },
    "RecipeRecommendation": {
      "type": "object",
      "description": "Which recipe fits a scene that has no shots yet, and why. The agent chooses; the recipe is advice.",
      "properties": {
        "scene": {
          "type": "string"
        },
        "recipe": {
          "type": "string"
        },
        "why": {
          "description": "The signals that matched, or that nothing matched and the default was chosen.",
          "type": "string"
        },
        "alternatives": {
          "description": "The other recipes, best fit first.",
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "scene",
        "recipe",
        "why",
        "alternatives"
      ],
      "additionalProperties": false
    },
    "RecipeSignals": {
      "type": "object",
      "description": "What makes a recipe fit a scene. Every part is optional; a recipe with none fits only by being the default.",
      "properties": {
        "speakers": {
          "description": "The number of distinct speakers in the scene's lines.",
          "type": [
            "integer",
            "null"
          ]
        },
        "address": {
          "description": "Who the scene's lines are said to: camera when a speaker addresses the lens.",
          "type": [
            "string",
            "null"
          ]
        },
        "keywords": {
          "description": "Words in the scene's purpose or change that point to this kind of scene (lower case, matched as whole words or word starts).",
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "keywords"
      ],
      "additionalProperties": false
    },
    "RecipeSlot": {
      "type": "object",
      "description": "One shot a recipe asks for, as a role and a framing: never words for the film.",
      "properties": {
        "id": {
          "type": "string"
        },
        "role": {
          "description": "A coverage.role term.",
          "type": "string"
        },
        "priority": {
          "description": "A coverage.priority term.",
          "type": "string"
        },
        "size": {
          "description": "A camera.size term.",
          "type": "string"
        },
        "angle": {
          "description": "A camera.angle term.",
          "type": "string"
        },
        "movement": {
          "description": "A camera.movement term.",
          "type": "string"
        },
        "setup": {
          "description": "A set-up label (A), or per_speaker, per_listener, per_step, per_beat, per_topic: a set-up of its own for each.",
          "type": "string"
        },
        "repeat": {
          "description": "How many shots of this slot: once, or one for each per_speaker, per_turn (each line that turns the scene), per_step, per_answer, per_beat or per_topic.",
          "type": "string"
        },
        "job": {
          "description": "What the shot is for, in general terms and with the readiness rules it serves.",
          "type": "string"
        }
      },
      "required": [
        "id",
        "role",
        "priority",
        "size",
        "angle",
        "movement",
        "setup",
        "repeat",
        "job"
      ],
      "additionalProperties": false
    }
  }
}
