{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:cede:schema:feed-object:v0",
  "title": "CEDE feed object, v0",
  "description": "One version of one pinned data snapshot, as a registry object (SPEC.md section 2A). The body of GET /feeds/{id}/versions/{version}: the manifest as committed — what was retrieved, from whom, under what licence, covering what window and what box, at what aggregation level — plus the sha256 of the exact bytes and the path at which those bytes are readable. A snapshot is immutable once published: a refreshed catalogue is a new version, never an edit of this one, which is what lets a result that names this version replay byte for byte.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version",
    "id",
    "version",
    "name",
    "publisher",
    "kind",
    "vintage",
    "sha256",
    "media_type",
    "bytes",
    "aggregation_level",
    "content_servable",
    "links"
  ],
  "properties": {
    "schema_version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" },
    "id": { "type": "string", "minLength": 1 },
    "version": { "type": "string", "minLength": 1 },
    "name": { "type": "string", "minLength": 1 },
    "publisher": { "type": "string", "minLength": 1 },
    "kind": { "type": "string", "minLength": 1 },
    "vintage": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
    "sha256": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$",
      "description": "Digest of the exact bytes, in full. Recomputed from the file on every read: a snapshot that has drifted from this value is refused, never served as though it were the pinned data."
    },
    "media_type": { "type": "string", "minLength": 1 },
    "bytes": { "type": "integer", "minimum": 0 },
    "retrieved_at": {
      "type": "string",
      "description": "When Cede took its copy. Not when the publisher produced the data, and not a claim that re-running query_url today returns these bytes — for a continuously revised catalogue it will not, which is why the copy is pinned here."
    },
    "query_url": {
      "type": "string",
      "minLength": 1,
      "description": "The publisher request this snapshot is the response to, verbatim, so the extract can be reproduced against the publisher and compared with what Cede holds."
    },
    "record_count": { "type": "integer", "minimum": 0 },
    "coverage": {
      "type": "object",
      "description": "What the snapshot actually contains — window, bounding box, thresholds. Work outside it is refused rather than answered from an extract that ends inside the question, which would under-report events that really occurred.",
      "properties": {
        "period_of_record": { "type": "object" },
        "bounding_box": { "type": "object" }
      }
    },
    "licence": {
      "description": "The licence the snapshot is redistributed under, with any required attribution statement.",
      "type": ["object", "string"]
    },
    "aggregation_level": {
      "type": "object",
      "required": ["level"],
      "description": "SPEC.md section 4, P10: feeds are aggregate only — never individual-level records. The level is declared by the manifest and is what decides whether content is served.",
      "properties": {
        "level": { "type": "string", "minLength": 1 },
        "note": { "type": "string" }
      }
    },
    "content_servable": {
      "type": "boolean",
      "description": "Whether links.data serves this snapshot's bytes. False means the declared aggregation level is not one whose content this build serves."
    },
    "notes": {
      "type": "array",
      "description": "What the snapshot is and is not, in the publisher's and the platform's own words. Read them before drawing a conclusion from the data: this is where a reanalysis cell says it is not a gauge, and where a revised catalogue says its bytes are frozen.",
      "items": { "type": "string" }
    },
    "links": {
      "type": "object",
      "additionalProperties": false,
      "required": ["self", "data"],
      "properties": {
        "self": { "type": "string", "minLength": 1 },
        "data": { "type": "string", "minLength": 1 }
      }
    }
  }
}
