{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://gxplex.com/api/v1/schema/change-events.json",
  "title": "Change event document",
  "description": "The document served at /api/v1/change-events.json. Items are carried under `items`.",
  "type": "object",
  "allOf": [
    {
      "$ref": "https://gxplex.com/api/v1/schema/envelope.json"
    }
  ],
  "properties": {
    "collection": {
      "const": "change-events"
    },
    "items": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/item"
      },
      "description": "Every change event this document carries."
    }
  },
  "required": [
    "items"
  ],
  "additionalProperties": true,
  "$defs": {
    "item": {
      "type": "object",
      "title": "Change event",
      "description": "One move of a digest under one tracked row between two named observations, as the later record's own `changed` field marks it, with the located spans, the verdict, class, basis and witness of the revision candidate it joins where one is staged, and the entry's rights; `means`: a consumer re-reads the source; nothing here says what the change means.",
      "properties": {
        "licence_class": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "open",
            "restricted",
            "unknown"
          ],
          "description": "What this archive recorded of the terms the source states. Null where no reading exists for this object. See `rights` on the envelope."
        },
        "text_retained": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Whether normalised prose of the document is held here for a quotation to be checked against. Held, never served. Null where no reading exists."
        },
        "quote_allowed": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Whether a quotation from the document appears in this record's own published output. A fact about this record's handling, not a reading of what the publisher permits, and not a grant. Null where no reading exists."
        },
        "snapshot_available": {
          "type": [
            "boolean",
            "null"
          ],
          "description": "Whether a retained snapshot of an observed retrieval is still held here for a digest to be re-checked against. No snapshot is served. Null where no reading exists."
        },
        "rights_of": {
          "type": [
            "string",
            "null"
          ],
          "description": "The tracked entry id the four fields above are the rights OF. Null where the object names no entry."
        },
        "rights_unresolved": {
          "type": [
            "string",
            "null"
          ],
          "description": "Present only where the four fields are null, naming which absence it is: an id this record does not hold, or an object that names no entry."
        },
        "source": {
          "type": "object",
          "description": "The tracked entry this object is about, its state, and where it resolves on this site. `state` is one of `tracked` (this record holds the entry), `not-tracked` (an id is named and this record holds no entry under it) or `no-subject` (the object names no entry, so no lookup applies). `resolves_to` carries an address only where the state is `tracked`; a minted address for an untracked id would 404, and a consumer cannot tell a 404 from a deploy that dropped a file."
        },
        "observed_at": {
          "type": [
            "string",
            "null"
          ],
          "format": "date",
          "description": "The day THIS RECORD observed the fact this object carries, at day granularity. It is never a publisher's own date for an event and never a build clock. Null where no such day was read."
        },
        "observed_at_state": {
          "type": "string",
          "enum": [
            "recorded",
            "not-carried",
            "not-an-observation"
          ],
          "description": "Which of the two absences a null `observed_at` is. `not-carried`: this object is a record about something observable and carries no day for when this record read it. `not-an-observation`: this object is not a record of a retrieval at all — a corpus summary, a coverage term — so no day is missing from it. Opposite facts, one blank."
        },
        "observed_at_read_from": {
          "type": [
            "string",
            "null"
          ],
          "description": "The key on the emitting store `observed_at` was read from, so the value can be checked against the store rather than trusted. Null only where the state is `not-an-observation`, which is the one case with no field to name."
        },
        "distance": {
          "type": [
            "integer",
            "null"
          ],
          "minimum": 0,
          "description": "Distance from the source on the filed scale — 0 primary, 1 official notice, 2 official related record, 3 third-party. Defined for signals only. Null on every other object, and a null is NOT zero: zero is the strongest claim the scale makes."
        },
        "distance_label": {
          "type": [
            "string",
            "null"
          ],
          "description": "The scale's own word for `distance`, read from the one home that holds the scale. Null where no distance was read, and null where the record's own label disagrees with the scale — a disagreement is withheld rather than reconciled, because neither side adjudicates the other."
        },
        "distance_state": {
          "type": "string",
          "enum": [
            "recorded",
            "unmeasured",
            "field-absent",
            "out-of-scale",
            "label-disagrees",
            "not-a-signal"
          ],
          "description": "Which reading produced `distance`. `not-a-signal`: this object has no position on the scale. `field-absent`: the record carries no distance key. `unmeasured`: the key is present and null — the entry declared no address to measure from. `out-of-scale`: the record's value is not a member of the scale, so no value is published. `label-disagrees`: the value IS a member and the record's own label is not the scale's word for it, so the value is published and the LABEL is withheld — the two are different withholdings and one sentence covering both said the value was withheld in a case where it is served."
        },
        "id": {
          "type": "string",
          "description": "observation/<source id>/<day>/<sequence> of the later observation; the id /api/v1/events.json gives the same act."
        },
        "occurred_on": {
          "type": [
            "string",
            "null"
          ],
          "format": "date",
          "description": "The day of the later observation."
        },
        "identifiers": {
          "type": "object",
          "description": "`source_id`; `publisher_identifier` (null, with `publisher_identifier_state` `not-recorded`, until rows hold a publisher's own key); `publisher_origin_host` where the row records one; `answered_url` and `asked_url` of the later observation as stored; `bundle`, the row's observation bundle."
        },
        "digest": {
          "type": "object",
          "description": "`old` and `new`: the sha256 of the normalised text at the earlier and at the later observation."
        },
        "observations": {
          "type": "object",
          "description": "`before` and `after`, each with `key` (day·sequence), `observed_date`, `sequence`, `digest`, `permalink`, and `asked_url` and `answered_url` as that observation stores them, so a move between two addresses of one row is visible. Both are always present: a changed record whose earlier observation cannot be named is excluded and counted in the envelope."
        },
        "spans": {
          "type": "object",
          "description": "`state` (`located`, `words-on-page` or `not-located`), `comparison_verdict` and `refused_by` as the comparison engine returned them, `words_differing`, `words_exact`, `regions` (word offsets [earlier start, earlier words, later start, later words], earlier digest first, or null) and `page`, the comparison page's anchor for the pair of points holding the two digests, which may be earlier observations than this event's own. No publisher word is carried; a row whose licence position is not `open` is `not-located`."
        },
        "candidate_state": {
          "type": "string",
          "enum": [
            "staged",
            "not-staged"
          ],
          "description": "Whether a revision candidate joins this event."
        },
        "candidate": {
          "type": [
            "object",
            "null"
          ],
          "description": "`id` and `url` of the joined candidate, or null."
        },
        "verdict": {
          "type": [
            "object",
            "null"
          ],
          "description": "`placed`, `state`, `cause`, `rule_version`, `statements` (a publisher-stated confirmation's notes with field, surface and day, or null) and `read_from`, as the verdict file holds them for the joined candidate; null where none is staged."
        },
        "class": {
          "type": [
            "string",
            "null"
          ],
          "description": "The verdict's class, as stored; null where none is staged or none is stored."
        },
        "basis": {
          "type": [
            "string",
            "null"
          ],
          "description": "The verdict's basis, byte for byte as the verdict file stores it; null where none is staged or none is stored."
        },
        "witness": {
          "type": [
            "object",
            "null"
          ],
          "description": "The joined candidate's witness fields as /api/v1/candidates.json serves them; null where none is staged."
        },
        "means": {
          "type": "string",
          "description": "a consumer re-reads the source; nothing here says what the change means"
        }
      },
      "required": [
        "id",
        "occurred_on",
        "identifiers",
        "digest",
        "observations",
        "spans",
        "candidate_state",
        "candidate",
        "verdict",
        "class",
        "basis",
        "witness",
        "means",
        "licence_class",
        "text_retained",
        "quote_allowed",
        "snapshot_available",
        "rights_of",
        "source",
        "observed_at",
        "observed_at_state",
        "observed_at_read_from",
        "distance",
        "distance_label",
        "distance_state"
      ],
      "additionalProperties": true
    }
  }
}
