{
  "$defs": {
    "ActId": {
      "description": "An act, scoped to the corpus that knows how to fetch it.\n\nIdentity is `(corpus, key)` only. `display_name` is a label that rides along for output, so\nthe same act named by two callers, one of which knows its title and one of which does not,\nis the same act in equality, in hashing and in every set and dict key downstream.",
      "properties": {
        "corpus": {
          "description": "Adapter namespace, e.g. 'eu' or 'toy'.",
          "minLength": 1,
          "title": "Corpus",
          "type": "string"
        },
        "display_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Human title, for output only; never parsed, never identity.",
          "title": "Display Name"
        },
        "key": {
          "description": "Opaque identifier within that corpus.",
          "minLength": 1,
          "title": "Key",
          "type": "string"
        }
      },
      "required": [
        "corpus",
        "key"
      ],
      "title": "ActId",
      "type": "object"
    },
    "ApplicabilityUnchanged": {
      "description": "The change does not move the date this provision applies from.",
      "properties": {
        "kind": {
          "const": "unchanged",
          "default": "unchanged",
          "title": "Kind",
          "type": "string"
        }
      },
      "title": "ApplicabilityUnchanged",
      "type": "object"
    },
    "ApplicabilityUnknown": {
      "description": "The applicability date could not be read deterministically, which is the honest answer.\n\nAttaching a date to a provision set is prose, with exceptions and conditions. Many\nunknowns is the correct outcome; the model never fills one in.",
      "properties": {
        "kind": {
          "const": "unknown",
          "default": "unknown",
          "title": "Kind",
          "type": "string"
        },
        "reason": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Reason"
        }
      },
      "title": "ApplicabilityUnknown",
      "type": "object"
    },
    "Change": {
      "description": "One provision that changed between two versions of one act.\n\nThe unit is the top-level provision; `changed_within` carries the finer coordinates\nthe diff could localise, and `dates_removed`/`dates_added` the machine-readable dates that\nmoved. Both are detail attached to the unit, never a second unit of their own.\n\n`amending_acts` is attribution, not classification: it says which acts a signal names as\namending this unit. The structural diff carries no such fact, so the field is filled by\ncorroboration and is empty when no signal names one.",
      "properties": {
        "after": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Verbatim; None if deleted.",
          "title": "After"
        },
        "amending_acts": {
          "default": [],
          "description": "Acts a signal names as amending this unit; empty when none names one.",
          "items": {
            "$ref": "#/$defs/ActId"
          },
          "title": "Amending Acts",
          "type": "array"
        },
        "applies_from": {
          "anyOf": [
            {
              "format": "date",
              "type": "string"
            },
            {
              "$ref": "#/$defs/ApplicabilityUnknown"
            },
            {
              "$ref": "#/$defs/ApplicabilityUnchanged"
            }
          ],
          "default": {
            "kind": "unknown",
            "reason": null
          },
          "title": "Applies From"
        },
        "before": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Verbatim; None if inserted.",
          "title": "Before"
        },
        "change_type": {
          "$ref": "#/$defs/ChangeType"
        },
        "changed_within": {
          "default": [],
          "description": "Sub-provision coordinates whose own text differs: detail, not the unit.",
          "items": {
            "$ref": "#/$defs/ProvisionLocation"
          },
          "title": "Changed Within",
          "type": "array"
        },
        "dates_added": {
          "default": [],
          "description": "Machine-readable dates the provision carries after and not before.",
          "items": {
            "format": "date",
            "type": "string"
          },
          "title": "Dates Added",
          "type": "array"
        },
        "dates_removed": {
          "default": [],
          "description": "Machine-readable dates the provision carried before and not after.",
          "items": {
            "format": "date",
            "type": "string"
          },
          "title": "Dates Removed",
          "type": "array"
        },
        "dispute_reason": {
          "anyOf": [
            {
              "$ref": "#/$defs/DisputeReason"
            },
            {
              "type": "null"
            }
          ],
          "description": "Which signal disagreed and how; None exactly when not disputed. Derived from `signals`, never stored apart from them.",
          "readOnly": true
        },
        "disputed": {
          "default": false,
          "description": "Signals disagree. Derived from `signals`, not free.",
          "title": "Disputed",
          "type": "boolean"
        },
        "heading": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Heading"
        },
        "in_force": {
          "anyOf": [
            {
              "format": "date",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "In Force"
        },
        "previous_location": {
          "anyOf": [
            {
              "$ref": "#/$defs/ProvisionLocation"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "The location a renumbered provision had before."
        },
        "provision": {
          "$ref": "#/$defs/ProvisionRef"
        },
        "signals": {
          "$ref": "#/$defs/SignalSet",
          "default": {
            "corpus_metadata": {
              "change_types": [],
              "detail": null,
              "status": "unavailable"
            },
            "instruction_parse": {
              "change_types": [],
              "detail": null,
              "status": "unavailable"
            },
            "structural_diff": {
              "change_types": [],
              "detail": null,
              "status": "unavailable"
            }
          }
        },
        "table_entries": {
          "anyOf": [
            {
              "$ref": "#/$defs/EntryDelta"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "The keyed diff of the unit's table entries. None means not read: no profiled table in the unit on either side, or a document written before schema 1.3. An empty delta means read, and no keyed entry changed."
        }
      },
      "required": [
        "change_type",
        "provision",
        "dispute_reason"
      ],
      "title": "Change",
      "type": "object"
    },
    "ChangeType": {
      "description": "How a provision changed. Assigned by the diff and its classification, never by a model.\n\n`RENUMBERED` and `DEFERRED` are carried from the first commit so the schema never\nmigrates, even though no trace has yet produced a renumbering: none occurred in the seven\nend-to-end traces under `scripts/validation/results/`, and no role code plausibly meaning\n\"renumbered\" appears in the 540 annotations they inventory.",
      "enum": [
        "INSERTED",
        "MODIFIED",
        "DELETED",
        "RENUMBERED",
        "DEFERRED"
      ],
      "title": "ChangeType",
      "type": "string"
    },
    "Citation": {
      "description": "A resolvable reference to one provision of one version of one act.",
      "properties": {
        "label": {
          "description": "Display form, e.g. 'Art. 5(1)(bb), v2'.",
          "minLength": 1,
          "title": "Label",
          "type": "string"
        },
        "ref": {
          "$ref": "#/$defs/ProvisionRef"
        },
        "url": {
          "description": "Where a reader sees the provision.",
          "minLength": 1,
          "title": "Url",
          "type": "string"
        }
      },
      "required": [
        "ref",
        "url",
        "label"
      ],
      "title": "Citation",
      "type": "object"
    },
    "DisputeReason": {
      "description": "Which signal disagreed and how, read off one disputed change's `SignalSet` alone.\n\nA reading of stored verdicts, never a judgement about the law. `SignalSet.reason` assigns\nexactly one to every disputed set and none to any other, evaluated in the order declared.",
      "enum": [
        "kind_mismatch",
        "textless_both_others",
        "textless_metadata_only",
        "textless_instruction_only",
        "both_others_silent",
        "metadata_silent",
        "instruction_silent"
      ],
      "title": "DisputeReason",
      "type": "string"
    },
    "EmittedSentence": {
      "description": "One sentence that shipped, with its citations resolved to clickable references.",
      "properties": {
        "citations": {
          "default": [],
          "items": {
            "$ref": "#/$defs/Citation"
          },
          "title": "Citations",
          "type": "array"
        },
        "fallback": {
          "default": false,
          "description": "Written by the gate as a verbatim quotation, not by the model.",
          "title": "Fallback",
          "type": "boolean"
        },
        "text": {
          "minLength": 1,
          "title": "Text",
          "type": "string"
        }
      },
      "required": [
        "text"
      ],
      "title": "EmittedSentence",
      "type": "object"
    },
    "EntryChange": {
      "description": "One keyed entry that differs between two versions, with both sides verbatim.\n\n`typography_only` labels an amendment whose cells are equal once spacing, dash forms and\naccents are set aside. It is a label and never a reason to drop the change: a character\nswap in legal text is a difference, and only whitespace is exempt from being one.",
      "properties": {
        "after": {
          "anyOf": [
            {
              "$ref": "#/$defs/TableEntry"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "The newer entry; None if removed."
        },
        "before": {
          "anyOf": [
            {
              "$ref": "#/$defs/TableEntry"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "The older entry; None if added."
        },
        "container": {
          "$ref": "#/$defs/ProvisionLocation",
          "description": "The located node holding the table."
        },
        "key": {
          "description": "The key both sides are paired on.",
          "title": "Key",
          "type": "string"
        },
        "kind": {
          "enum": [
            "added",
            "removed",
            "amended"
          ],
          "title": "Kind",
          "type": "string"
        },
        "occurrence": {
          "default": 1,
          "description": "Which repeat of the key, when a table prints it twice.",
          "minimum": 1,
          "title": "Occurrence",
          "type": "integer"
        },
        "table": {
          "description": "1-based ordinal among every table in the container.",
          "minimum": 1,
          "title": "Table",
          "type": "integer"
        },
        "typography_only": {
          "default": false,
          "description": "Amended only: equal once spacing, dash forms and accents are set aside.",
          "title": "Typography Only",
          "type": "boolean"
        }
      },
      "required": [
        "kind",
        "container",
        "table",
        "key"
      ],
      "title": "EntryChange",
      "type": "object"
    },
    "EntryDelta": {
      "description": "The keyed diff of one unit's table entries between two versions, with what it skipped.\n\nAn empty `changes` means the unit was read on both sides and no keyed entry moved; the\nabsence of a delta altogether (`None` on the change) means the unit was never read. The\ntwo must never be confused, which is why both are stated rather than one implied.",
      "properties": {
        "changes": {
          "default": [],
          "description": "Sorted by table position, then natural key order, then occurrence.",
          "items": {
            "$ref": "#/$defs/EntryChange"
          },
          "title": "Changes",
          "type": "array"
        },
        "gaps_after": {
          "$ref": "#/$defs/EntryGaps",
          "default": {
            "failed_check_digits": 0,
            "repeated_keys": [],
            "unkeyed_rows": 0,
            "unrecognised_tables": 0
          },
          "description": "The newer side's gaps."
        },
        "gaps_before": {
          "$ref": "#/$defs/EntryGaps",
          "default": {
            "failed_check_digits": 0,
            "repeated_keys": [],
            "unkeyed_rows": 0,
            "unrecognised_tables": 0
          },
          "description": "The older side's gaps."
        },
        "tables": {
          "default": [],
          "description": "The unit's profiled tables, the newer side's where both have one, in order.",
          "items": {
            "$ref": "#/$defs/EntryTable"
          },
          "title": "Tables",
          "type": "array"
        },
        "unchanged": {
          "default": 0,
          "description": "Paired entries equal verbatim.",
          "minimum": 0,
          "title": "Unchanged",
          "type": "integer"
        },
        "unpaired": {
          "default": [],
          "description": "Keys printed a different number of times on each side; not diffed.",
          "items": {
            "$ref": "#/$defs/KeyRef"
          },
          "title": "Unpaired",
          "type": "array"
        },
        "whitespace_only": {
          "default": 0,
          "description": "Paired entries equal once whitespace collapses: not changes.",
          "minimum": 0,
          "title": "Whitespace Only",
          "type": "integer"
        }
      },
      "title": "EntryDelta",
      "type": "object"
    },
    "EntryGaps": {
      "description": "What a reading could not key, read or trust, counted rather than hidden.",
      "properties": {
        "failed_check_digits": {
          "default": 0,
          "minimum": 0,
          "title": "Failed Check Digits",
          "type": "integer"
        },
        "repeated_keys": {
          "default": [],
          "description": "Every key one table prints more than once.",
          "items": {
            "$ref": "#/$defs/KeyRef"
          },
          "title": "Repeated Keys",
          "type": "array"
        },
        "unkeyed_rows": {
          "default": 0,
          "minimum": 0,
          "title": "Unkeyed Rows",
          "type": "integer"
        },
        "unrecognised_tables": {
          "default": 0,
          "minimum": 0,
          "title": "Unrecognised Tables",
          "type": "integer"
        }
      },
      "title": "EntryGaps",
      "type": "object"
    },
    "EntryTable": {
      "description": "A table whose rows are read as entries: how its key is named, and its header rows.",
      "properties": {
        "container": {
          "$ref": "#/$defs/ProvisionLocation",
          "description": "The deepest located node holding the table, as the tree places it."
        },
        "header": {
          "default": [],
          "description": "The header rows, cells verbatim; `None` where a span covers.",
          "items": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            },
            "type": "array"
          },
          "title": "Header",
          "type": "array"
        },
        "key_label": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "description": "How a key is named in prose, or `None` when the key is the entry's name.",
          "title": "Key Label"
        },
        "table": {
          "description": "1-based ordinal among every table in the container.",
          "minimum": 1,
          "title": "Table",
          "type": "integer"
        },
        "unit": {
          "$ref": "#/$defs/ProvisionLocation",
          "description": "The top-level unit the table sits in."
        }
      },
      "required": [
        "unit",
        "container",
        "table",
        "key_label"
      ],
      "title": "EntryTable",
      "type": "object"
    },
    "GateOutcome": {
      "description": "How one change left the gate. Every change leaves it — that is the point.\n\n`RETRY_REQUESTED` is transient: it exists only between the two halves of the one retry\ncycle, and no run may finish holding one.",
      "enum": [
        "passed",
        "passed_on_retry",
        "fallback",
        "retry_requested",
        "unexplained"
      ],
      "title": "GateOutcome",
      "type": "string"
    },
    "Identifier": {
      "description": "One identifier a row carries, in the canonical form of its scheme.",
      "properties": {
        "scheme": {
          "description": "The corpus-defined scheme name, lower case.",
          "title": "Scheme",
          "type": "string"
        },
        "value": {
          "description": "The canonical spelling of the value under its scheme.",
          "title": "Value",
          "type": "string"
        }
      },
      "required": [
        "scheme",
        "value"
      ],
      "title": "Identifier",
      "type": "object"
    },
    "KeyRef": {
      "description": "One key of one table, named without its rows.",
      "properties": {
        "container": {
          "$ref": "#/$defs/ProvisionLocation",
          "description": "The located node holding the table."
        },
        "key": {
          "description": "The key as the entry carries it.",
          "title": "Key",
          "type": "string"
        },
        "table": {
          "description": "1-based ordinal among every table in the container.",
          "minimum": 1,
          "title": "Table",
          "type": "integer"
        }
      },
      "required": [
        "container",
        "table",
        "key"
      ],
      "title": "KeyRef",
      "type": "object"
    },
    "ProvisionLocation": {
      "type": "string"
    },
    "ProvisionRef": {
      "description": "A provision in a specific version of a specific act.",
      "properties": {
        "act": {
          "$ref": "#/$defs/ActId"
        },
        "location": {
          "$ref": "#/$defs/ProvisionLocation"
        },
        "version": {
          "title": "Version",
          "type": "string"
        }
      },
      "required": [
        "act",
        "version",
        "location"
      ],
      "title": "ProvisionRef",
      "type": "object"
    },
    "SignalObservation": {
      "description": "One signal's verdict on one change, with whatever detail that signal carries.\n\n`change_types` is what this signal said the change *was*, at the top-level unit\ngranularity: a set, because one signal can name a unit more than once and in more than one\nway (the AI Act's Article 5 carries both inserted points and replaced ones). Two signals\ndisagree on kind when their sets do not intersect; a signal that names no kind never disagrees.",
      "properties": {
        "change_types": {
          "default": [],
          "items": {
            "$ref": "#/$defs/ChangeType"
          },
          "title": "Change Types",
          "type": "array"
        },
        "detail": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Detail"
        },
        "status": {
          "$ref": "#/$defs/SignalStatus",
          "default": "unavailable"
        }
      },
      "title": "SignalObservation",
      "type": "object"
    },
    "SignalSet": {
      "description": "The three verdicts on one change.\n\nThe structural diff is the primary shipped signal, and the only one carrying actual text.\nCorpus metadata is the evaluation reference set, and it is *not* an oracle: a blanket\namendment is annotated once and its 30 landing sites are not enumerated (measured\n2026-08-05).",
      "properties": {
        "corpus_metadata": {
          "$ref": "#/$defs/SignalObservation",
          "default": {
            "change_types": [],
            "detail": null,
            "status": "unavailable"
          }
        },
        "instruction_parse": {
          "$ref": "#/$defs/SignalObservation",
          "default": {
            "change_types": [],
            "detail": null,
            "status": "unavailable"
          }
        },
        "structural_diff": {
          "$ref": "#/$defs/SignalObservation",
          "default": {
            "change_types": [],
            "detail": null,
            "status": "unavailable"
          }
        }
      },
      "title": "SignalSet",
      "type": "object"
    },
    "SignalStatus": {
      "description": "Whether a signal saw this change, did not see it, or could not have.\n\n`UNAVAILABLE` is not a disagreement: a corpus that publishes no modification metadata\ncannot dissent. Only `OBSERVED` against `ABSENT` is a dispute.",
      "enum": [
        "observed",
        "absent",
        "unavailable"
      ],
      "title": "SignalStatus",
      "type": "string"
    },
    "TableEntry": {
      "description": "A keyed group of rows, with its identifiers and its cells verbatim.",
      "properties": {
        "container": {
          "$ref": "#/$defs/ProvisionLocation",
          "description": "The deepest located node holding the table, as the tree places it."
        },
        "failed_check_digits": {
          "default": 0,
          "description": "Identifier-shaped values in identifier columns that fail.",
          "minimum": 0,
          "title": "Failed Check Digits",
          "type": "integer"
        },
        "group": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Verbatim heading of the innermost block the rows sit in.",
          "title": "Group"
        },
        "identifiers": {
          "default": [],
          "description": "Sorted, deduplicated, and valid under the scheme's check.",
          "items": {
            "$ref": "#/$defs/Identifier"
          },
          "title": "Identifiers",
          "type": "array"
        },
        "key": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "description": "The row key by the adapter's rule; `None` is an unkeyed row, kept and counted.",
          "title": "Key"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "First line of the name cell; a leading entry number and its `.` dropped.",
          "title": "Name"
        },
        "occurrence": {
          "default": 1,
          "description": "1-based among this table's entries sharing this key.",
          "minimum": 1,
          "title": "Occurrence",
          "type": "integer"
        },
        "rows": {
          "description": "Every row of the entry, cells verbatim; `None` where a span covers.",
          "items": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            },
            "type": "array"
          },
          "title": "Rows",
          "type": "array"
        },
        "table": {
          "description": "1-based ordinal among every table in the container.",
          "minimum": 1,
          "title": "Table",
          "type": "integer"
        },
        "unit": {
          "$ref": "#/$defs/ProvisionLocation",
          "description": "The top-level unit the table sits in."
        }
      },
      "required": [
        "unit",
        "container",
        "table",
        "key",
        "rows"
      ],
      "title": "TableEntry",
      "type": "object"
    }
  },
  "$id": "change.schema.json",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "description": "One change as a reader receives it: the facts, then whatever prose survived the gate.",
  "properties": {
    "applicability_note": {
      "anyOf": [
        {
          "$ref": "#/$defs/EmittedSentence"
        },
        {
          "type": "null"
        }
      ],
      "default": null
    },
    "change": {
      "$ref": "#/$defs/Change"
    },
    "outcome": {
      "$ref": "#/$defs/GateOutcome"
    },
    "sentences": {
      "default": [],
      "items": {
        "$ref": "#/$defs/EmittedSentence"
      },
      "title": "Sentences",
      "type": "array"
    },
    "unexplained": {
      "default": "",
      "description": "Why there are no sentences, when there are none.",
      "title": "Unexplained",
      "type": "string"
    },
    "unexplained_kind": {
      "default": "",
      "description": "Which counted kind that reason was, empty when there are sentences.",
      "title": "Unexplained Kind",
      "type": "string"
    }
  },
  "required": [
    "change",
    "outcome"
  ],
  "title": "change",
  "type": "object"
}
