{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:cede:schema:monitoring-notification:v0",
  "title": "CEDE monitoring notification, v0",
  "description": "What Cede POSTs to a registered webhook address (SPEC.md section 3.6). The one published document that arrives at a consumer rather than being fetched by one, so it is versioned like every other: a receiver written against this file is an API consumer even though it never calls the API. It carries exactly the four observation fields SPEC.md section 3.6 names as Monitor's output — the feed id, the vintage, the computed index value and the distance to attachment — as they are now and as they were, plus the identities that resolve them and the model that computed them (SPEC.md section 4, P9). It carries nothing that follows from them: no attachment flag, no payout ratio, no limit, and no amount of money in any currency. A notification is a difference between two readings of an index computed by analytics software from pinned public snapshots, delivered to an address the account holder gave Cede. It is never a notice under any contract: Cede is not the calculation agent of record for anything, determines nothing about anybody's obligations, sends no claim notice, no settlement notice and no calculation-agent notice, and moves no money. Delivery is at least once — deduplicate on notification_id.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version",
    "type",
    "notification_id",
    "created_at",
    "webhook_id",
    "monitoring_id",
    "structure_id",
    "model",
    "changed",
    "measurement",
    "previous_measurement",
    "disclosure",
    "links"
  ],
  "properties": {
    "schema_version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" },
    "type": {
      "type": "string",
      "enum": ["measurement_changed"],
      "description": "The only kind of message this build sends, and its name says the whole of what it asserts: a measurement changed. Not that a structure triggered, not that anything is payable, not that anything is due."
    },
    "notification_id": { "type": "string", "minLength": 1, "description": "Stable across every attempt of this notification. Delivery is at-least-once, so deduplicate on this: a receiver that answered after Cede's socket timed out will be sent the same id again." },
    "created_at": { "type": "string", "minLength": 20, "description": "When the change was found, RFC 3339 in UTC. Not when it was delivered — the delivery time is in the account's own delivery log at GET /monitoring/{id}/webhooks." },
    "webhook_id": { "type": "string", "minLength": 1 },
    "monitoring_id": { "type": "string", "minLength": 1 },
    "structure_id": { "type": "string", "minLength": 1 },
    "model": {
      "type": "object",
      "additionalProperties": true,
      "required": ["name", "version"],
      "description": "SPEC.md section 4 P9's label: the model and version that computed the level in `measurement`, and the same label that measurement carries — read off it, so the two cannot disagree. An unattributed level pushed into somebody else's system is the least reproducible number this platform could emit, so it is required here. `previous_measurement` carries its own label, which is the model that produced the earlier reading and need not be this one.",
      "properties": {
        "name": { "type": "string", "minLength": 1 },
        "version": { "type": "string", "minLength": 1 }
      }
    },
    "changed": {
      "type": "array",
      "minItems": 1,
      "description": "Which of the compared fields moved. This is the whole of what the message asserts.",
      "items": { "type": "string", "minLength": 1 }
    },
    "measurement": { "$ref": "#/$defs/measurement", "description": "The measurement now." },
    "previous_measurement": { "$ref": "#/$defs/measurement", "description": "The measurement this registration stood on before, so a receiver holding nothing can still see the move." },
    "disclosure": { "type": "string", "minLength": 1, "description": "The fixed sentence stating what a delivery is and is not. Pinned as a constant in the product (cede.monitor.webhooks.DELIVERY_DISCLOSURE)." },
    "links": {
      "type": "object",
      "additionalProperties": false,
      "required": ["monitoring"],
      "properties": {
        "monitoring": { "type": "string", "minLength": 1, "description": "Where the full observation — the window, the levels, the method and the standing limitations — is read back from, by the account holder with their own key. The notification carries the difference; the record carries the measurement in full." }
      }
    }
  },
  "$defs": {
    "measurement": {
      "type": "object",
      "additionalProperties": false,
      "required": ["feed_id", "vintage", "index_value", "index_unit", "distance_to_attachment", "model"],
      "description": "One reading: the four compared fields, the unit they are in, and the label of the model that produced them. Each reading carries its own label, so previous_measurement is attributed to whatever computed it rather than to the model named at the top of the message.",
      "properties": {
        "feed_id": { "type": "string", "minLength": 1 },
        "model": {
          "type": "object",
          "additionalProperties": true,
          "required": ["name", "version"],
          "description": "SPEC.md section 4, P9: the model and version that produced this reading. Present on each reading rather than once per message, because the two readings need not have come from the same model version — the top-level model is this same label for measurement, and previous_measurement carries the label of what produced the earlier numbers.",
          "properties": {
            "name": { "type": "string", "minLength": 1 },
            "version": { "type": "string", "minLength": 1 }
          }
        },
        "vintage": { "type": "string", "minLength": 1 },
        "index_value": { "type": ["number", "null"], "description": "The index level, in index_unit. Null when no level could be measured — never a zero, which is a measurement." },
        "index_unit": { "type": "string", "minLength": 1 },
        "distance_to_attachment": { "type": ["number", "null"], "description": "The structure's attachment level minus the index value, in index_unit. Positive is short of the attachment, negative is past it. A receiver that needs to know whether the attachment was reached reads this and decides for itself, outside Cede." }
      }
    }
  }
}
