{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://castles.bussetech.com/schema/coverage.schema.json",
  "title": "Coverage grid",
  "description": "Negative space as data. One cell per (definition band × jurisdiction). A cell says how far this dataset has actually got, and — crucially — names the register that WOULD settle it. An unexamined cell is a published admission, not an absence: the failure this project exists to answer is a dataset that showed nine castles in Saxony and let the reader assume that was Saxony.",
  "type": "object",
  "required": ["version", "cells"],
  "additionalProperties": false,
  "properties": {
    "version": { "const": 1 },
    "cells": {
      "type": "array",
      "minItems": 1,
      "items": { "$ref": "#/$defs/cell" }
    }
  },
  "$defs": {
    "cell": {
      "type": "object",
      "required": ["definition", "jurisdiction", "state", "note"],
      "additionalProperties": false,
      "properties": {
        "definition": { "type": "string", "description": "Ref: data/definitions.yml id." },
        "jurisdiction": { "type": "string", "description": "ISO 3166-1 alpha-2 with optional subdivision, matching registers.yml." },
        "state": {
          "enum": ["complete", "partial", "surveyed-empty", "unexamined"],
          "description": "`complete` = every structure the bounding register lists has a record here, and the integer is real. `partial` = work has started and the count is a floor, not a total. `surveyed-empty` = looked, found none, and that is a finding. `unexamined` = not started, said out loud."
        },
        "settled_by": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Ref: data/registers.yml ids. The register(s) that would close this cell. An unexamined cell naming its register is a work order; one naming none is a research question."
        },
        "records_held": {
          "type": "integer",
          "minimum": 0,
          "description": "Records currently in this dataset for this cell. Derived and rewritten by scripts/counts.py — never hand-edited, and CI fails on drift."
        },
        "register_count": {
          "type": ["integer", "null"],
          "description": "What the bounding register says the population is, when it publishes a usable figure. The distance between this and records_held IS the completeness claim, stated numerically instead of implied."
        },
        "note": {
          "type": "string",
          "minLength": 1,
          "description": "Plain language: what was done, what was not, and what would finish it. Required on every cell including complete ones."
        }
      }
    }
  }
}
