{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://theamateur.co.uk/schemas/dpl/dpl.findings-1.schema.json",
  "title": "DPL readiness findings (dpl.findings/1)",
  "description": "What `dpl readiness <plan> --format json` writes: what edit-readiness found in a plan, in a form a product can store, show, dismiss and compare. Severity (how much a finding hurts the edit) and confidence (how sure the rule is) are separate on purpose. Every member listed here is always written, or left out when null; nothing else appears.",
  "$comment": "Findings are deterministic: the same plan and rules give the same document byte for byte. `plan_hash` is the SHA-256 of the plan in its standard layout (`dpl format`), so it names the revision the findings are about; `rules_version` names the rule set. A finding's `id` is stable across revisions of the plan, so a product can keep a dismissal against it. `dpl apply` reads this document back, tolerating members it does not know.",
  "type": "object",
  "properties": {
    "format": {
      "type": "string",
      "const": "dpl.findings/1"
    },
    "work_id": {
      "type": "string"
    },
    "plan_hash": {
      "description": "SHA-256 of the plan in its standard layout: the revision the findings are about.",
      "type": "string",
      "pattern": "^[0-9a-f]{64}$"
    },
    "rules_version": {
      "type": "string"
    },
    "summary": {
      "$ref": "#/$defs/FindingsSummary"
    },
    "findings": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Finding"
      }
    },
    "not_assessed": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/NotAssessed"
      }
    },
    "missing_details_hint": {
      "description": "Where there are questions to ask, a pointer to dpl missing; absent otherwise. not_assessed is unchanged.",
      "type": [
        "string",
        "null"
      ]
    }
  },
  "required": [
    "format",
    "work_id",
    "plan_hash",
    "rules_version",
    "summary",
    "findings",
    "not_assessed"
  ],
  "additionalProperties": false,
  "$defs": {
    "Evidence": {
      "type": "object",
      "description": "A fact from the plan that triggered a finding: where it is and what it says.",
      "properties": {
        "path": {
          "type": "string"
        },
        "fact": {
          "type": "string"
        }
      },
      "required": [
        "path",
        "fact"
      ],
      "additionalProperties": false
    },
    "Finding": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^f-[0-9a-f]{12}$"
        },
        "code": {
          "type": "string",
          "pattern": "^(DPL|RDY|REC)-[A-Z0-9-]+$"
        },
        "category": {
          "$ref": "#/$defs/FindingCategory"
        },
        "severity": {
          "$ref": "#/$defs/FindingSeverity"
        },
        "confidence": {
          "$ref": "#/$defs/FindingConfidence"
        },
        "scene": {
          "type": [
            "string",
            "null"
          ]
        },
        "shot": {
          "type": [
            "string",
            "null"
          ]
        },
        "entities": {
          "description": "Every plan id the finding is about (scene, shots, characters, props, lines).",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "message": {
          "type": "string"
        },
        "why": {
          "description": "Why it matters to the edit, tied to an invariant, a convention or the plan's declared intent.",
          "type": "string"
        },
        "intent": {
          "description": "The declared intent the finding protects (a scene purpose, a turning line), when there is one.",
          "type": [
            "string",
            "null"
          ]
        },
        "evidence": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Evidence"
          }
        },
        "suggestions": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Suggestion"
          }
        },
        "disposition": {
          "$ref": "#/$defs/FindingDisposition"
        },
        "override_why": {
          "description": "For a finding that is not open: the reason (the plan's scene accepts, or the remembered disposition's reason).",
          "type": [
            "string",
            "null"
          ]
        },
        "remembered": {
          "description": "For a finding closed by a remembered disposition: who closed it, when, and against which plan revision.",
          "anyOf": [
            {
              "$ref": "#/$defs/RememberedDisposition"
            },
            {
              "type": "null"
            }
          ]
        },
        "reopened_because": {
          "description": "For an open finding that had a remembered disposition whose evidence has since changed: why it is open again.",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "id",
        "code",
        "category",
        "severity",
        "confidence",
        "entities",
        "message",
        "why",
        "evidence",
        "suggestions",
        "disposition"
      ],
      "additionalProperties": false
    },
    "FindingCategory": {
      "type": "string",
      "enum": [
        "integrity",
        "coverage",
        "continuity",
        "screen_direction",
        "eyeline",
        "state",
        "transition",
        "creative_intent",
        "sound_attention",
        "constraint_trade_off",
        "feasibility"
      ]
    },
    "FindingConfidence": {
      "type": "string",
      "enum": [
        "certain",
        "likely",
        "possible"
      ]
    },
    "FindingDisposition": {
      "type": "string",
      "description": "Overridden: the plan's own scene accepts, or a remembered overridden disposition. Accepted and WontFix come only from remembered dispositions (dpl.dispositions/1, dpl readiness --dispositions).",
      "enum": [
        "open",
        "overridden",
        "accepted",
        "wont_fix"
      ]
    },
    "FindingSeverity": {
      "type": "string",
      "enum": [
        "blocker",
        "major",
        "minor",
        "note"
      ]
    },
    "FindingsSummary": {
      "type": "object",
      "description": "MissingQuestions is how many questions dpl missing would put to the human for this plan (additive: 0 where not computed).",
      "properties": {
        "blocker": {
          "type": "integer",
          "minimum": 0
        },
        "major": {
          "type": "integer",
          "minimum": 0
        },
        "minor": {
          "type": "integer",
          "minimum": 0
        },
        "note": {
          "type": "integer",
          "minimum": 0
        },
        "overridden": {
          "type": "integer",
          "minimum": 0
        },
        "safe_fixes": {
          "type": "integer",
          "minimum": 0
        },
        "missing_questions": {
          "type": "integer"
        }
      },
      "required": [
        "blocker",
        "major",
        "minor",
        "note",
        "overridden",
        "safe_fixes",
        "missing_questions"
      ],
      "additionalProperties": false
    },
    "FixValidation": {
      "type": "object",
      "description": "Whether a suggestion was applied to a copy of the plan and checked again with the same rules.",
      "properties": {
        "applied": {
          "type": "boolean"
        },
        "resolves": {
          "type": "boolean"
        },
        "introduces": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "error": {
          "type": [
            "string",
            "null"
          ]
        },
        "safe": {
          "type": "boolean"
        }
      },
      "required": [
        "applied",
        "resolves",
        "introduces",
        "safe"
      ],
      "additionalProperties": false
    },
    "NotAssessed": {
      "type": "object",
      "description": "Scenes whose facts were missing for some checks: not assessed is said, never passed silently.",
      "properties": {
        "scene": {
          "type": "string"
        },
        "missing": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "scene",
        "missing"
      ],
      "additionalProperties": false
    },
    "PlanChange": {
      "type": "object",
      "description": "One typed edit to a plan. Ops: set (a shot's field, dotted, to a value; null removes it), set_scene (the same for a scene), set_plan (a member of the plan itself by its path, such as schedule.days[0].end), insert_shot (a whole shot into a scene; the shots it overlaps are trimmed or split around it), remove_shot, set_join (the kind of join between two shots), add_state_change (into a scene).",
      "properties": {
        "op": {
          "type": "string",
          "enum": [
            "set",
            "set_scene",
            "set_plan",
            "insert_shot",
            "remove_shot",
            "set_join",
            "add_state_change"
          ]
        },
        "target": {
          "type": "string"
        },
        "field": {
          "type": [
            "string",
            "null"
          ]
        },
        "value": {}
      },
      "required": [
        "op",
        "target"
      ],
      "additionalProperties": false
    },
    "RememberedDisposition": {
      "type": "object",
      "description": "Who closed a finding with a remembered disposition, when (as they wrote it), and the plan revision they judged it against.",
      "properties": {
        "by": {
          "type": "string"
        },
        "at": {
          "type": "string"
        },
        "at_plan_hash": {
          "type": "string"
        }
      },
      "required": [
        "by",
        "at",
        "at_plan_hash"
      ],
      "additionalProperties": false
    },
    "Suggestion": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        },
        "summary": {
          "type": "string"
        },
        "trade_off": {
          "type": "string"
        },
        "changes": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/PlanChange"
          }
        },
        "validation": {
          "anyOf": [
            {
              "$ref": "#/$defs/FixValidation"
            },
            {
              "type": "null"
            }
          ]
        }
      },
      "required": [
        "id",
        "summary",
        "trade_off",
        "changes"
      ],
      "additionalProperties": false
    }
  }
}
