{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://theamateur.co.uk/schemas/dpl/dpl.diff-1.schema.json",
  "title": "DPL plan diff (dpl.diff/1)",
  "description": "What `dpl diff <old> <new>` writes: what changed between two versions of a plan, matched by stable ids and not by position. Each entity that differs is added, removed, changed (with the members that differ, their old and new values) or a possible rename (an old id gone and a new id with exactly its content). With both findings documents it also says which open findings are new, resolved or unchanged. Every member listed here is always written, or left out when null; nothing else appears.",
  "$comment": "An entity's `fields[].path` is inside the entity (`camera.size`, `layers[bg].motion.kind`); a list of objects with ids is matched by id, any other list by position. A value larger than 240 characters is replaced by a note of its size. `markdown` is the same account for people.",
  "type": "object",
  "properties": {
    "format": {
      "type": "string",
      "const": "dpl.diff/1"
    },
    "old_plan_hash": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$"
    },
    "new_plan_hash": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$"
    },
    "summary": {
      "$ref": "#/$defs/DiffSummary"
    },
    "entities": {
      "description": "Only entities that differ, in a fixed order: by kind, then the new plan's order, then the removed ones.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/EntityDiff"
      }
    },
    "findings": {
      "description": "Present when both findings documents were given.",
      "anyOf": [
        {
          "$ref": "#/$defs/FindingsChange"
        },
        {
          "type": "null"
        }
      ]
    },
    "markdown": {
      "description": "A short account for people and for an agent's context, the same facts as the lists above.",
      "type": "string"
    }
  },
  "required": [
    "format",
    "old_plan_hash",
    "new_plan_hash",
    "summary",
    "entities",
    "markdown"
  ],
  "additionalProperties": false,
  "$defs": {
    "DiffSummary": {
      "type": "object",
      "properties": {
        "added": {
          "type": "integer",
          "minimum": 0
        },
        "removed": {
          "type": "integer",
          "minimum": 0
        },
        "changed": {
          "type": "integer",
          "minimum": 0
        },
        "possible_renames": {
          "type": "integer",
          "minimum": 0
        },
        "unchanged": {
          "type": "integer",
          "minimum": 0
        }
      },
      "required": [
        "added",
        "removed",
        "changed",
        "possible_renames",
        "unchanged"
      ],
      "additionalProperties": false
    },
    "EntityChange": {
      "type": "string",
      "enum": [
        "added",
        "removed",
        "changed",
        "possible_rename"
      ]
    },
    "EntityDiff": {
      "type": "object",
      "properties": {
        "kind": {
          "$ref": "#/$defs/EntityKind"
        },
        "id": {
          "description": "The entity's id in the new plan (in the old plan, for a removed one).",
          "type": "string"
        },
        "scope": {
          "description": "For a shot: the scene that holds it in the new plan.",
          "type": [
            "string",
            "null"
          ]
        },
        "change": {
          "$ref": "#/$defs/EntityChange"
        },
        "renamed_from": {
          "description": "For a possible rename: the old id.",
          "type": [
            "string",
            "null"
          ]
        },
        "fields": {
          "description": "For a changed entity: each member that differs.",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "$ref": "#/$defs/FieldChange"
          }
        }
      },
      "required": [
        "kind",
        "id",
        "change"
      ],
      "additionalProperties": false
    },
    "EntityKind": {
      "type": "string",
      "description": "The things a plan is made of that have an identity of their own, so that a change to one is reported against it.",
      "enum": [
        "plan",
        "work",
        "character",
        "location",
        "prop",
        "continuity_rule",
        "sequence",
        "person",
        "scene",
        "shot",
        "transition",
        "sound_carrier",
        "speech_line",
        "beat_function",
        "sound_event",
        "sound_bridge",
        "sound_rule",
        "sound_beat",
        "speech_pause",
        "intensity_point",
        "takeover"
      ]
    },
    "FieldChange": {
      "type": "object",
      "description": "One member of an entity that differs. Path is inside the entity (camera.size, layers[bg].motion.kind; a list of objects with ids is matched by id).",
      "properties": {
        "path": {
          "type": "string"
        },
        "change": {
          "$ref": "#/$defs/FieldChangeKind"
        },
        "old": {},
        "new": {}
      },
      "required": [
        "path",
        "change"
      ],
      "additionalProperties": false
    },
    "FieldChangeKind": {
      "type": "string",
      "enum": [
        "added",
        "removed",
        "changed"
      ]
    },
    "FindingBrief": {
      "type": "object",
      "description": "A finding in a few members, for saying what a revision did to the findings.",
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^f-[0-9a-f]{12}$"
        },
        "code": {
          "type": "string",
          "pattern": "^(DPL|RDY|REC)-[A-Z0-9-]+$"
        },
        "severity": {
          "$ref": "#/$defs/FindingSeverity"
        },
        "scene": {
          "type": [
            "string",
            "null"
          ]
        },
        "shot": {
          "type": [
            "string",
            "null"
          ]
        },
        "message": {
          "type": "string"
        },
        "now": {
          "description": "For a resolved finding: gone (the plan no longer has it), or the disposition it now has.",
          "type": [
            "string",
            "null"
          ]
        },
        "reopened_because": {
          "description": "For a new finding that had been decided about and is open again: why.",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "id",
        "code",
        "severity",
        "message"
      ],
      "additionalProperties": false
    },
    "FindingSeverity": {
      "type": "string",
      "enum": [
        "blocker",
        "major",
        "minor",
        "note"
      ]
    },
    "FindingsChange": {
      "type": "object",
      "description": "What the revision did to the open findings, matched by finding id.",
      "properties": {
        "new": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/FindingBrief"
          }
        },
        "resolved": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/FindingBrief"
          }
        },
        "unchanged": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "new",
        "resolved",
        "unchanged"
      ],
      "additionalProperties": false
    }
  }
}
