{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:cede:schema:risk-object:v0",
  "title": "CEDE canonical risk object, v0",
  "description": "The canonical, machine-readable risk object defined in SPEC.md section 2. One object describes one risk transfer structure (or candidate structure) end to end. SPEC.md section 2 is normative for the semantics of every field; this file encodes those semantics as constraints, and where the two disagree SPEC.md wins and this file has a bug. Two constraints SPEC states cannot be expressed in JSON Schema and are enforced separately by schema/validate.py: period.expiry must be strictly after period.inception, and period.timezone must name a real IANA zone. See schema/README.md for the field-by-field derivation of what is required and what is optional.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version",
    "status",
    "exposure"
  ],
  "properties": {
    "id": {
      "$ref": "#/$defs/object_id",
      "description": "Server-assigned identity of the object. Immutable. Optional in this schema because the assignment happens server-side: a candidate object that has not been persisted yet has no id. Every object the platform has stored carries one."
    },
    "schema_version": {
      "type": "string",
      "pattern": "^0\\.[0-9]+\\.[0-9]+$",
      "description": "Version of the published schema this object conforms to. Explicit on every object (SPEC section 2). Constrained to the 0.x line here: a major bump is a new schema file, not an edit of this one."
    },
    "status": {
      "type": "string",
      "enum": [
        "draft",
        "analysed",
        "priced",
        "recorded_in_force"
      ],
      "description": "Lifecycle of the object itself, never a transaction status. The perimeter (SPEC section 4) forbids any state that asserts an executed transaction. recorded_in_force records a fact the user asserts about a transaction executed entirely outside Cede (SPEC section 3.6). New values arrive by schema version bump."
    },
    "exposure": {
      "$ref": "#/$defs/exposure"
    },
    "peril": {
      "$ref": "#/$defs/peril"
    },
    "financial_structure": {
      "$ref": "#/$defs/financial_structure"
    },
    "trigger": {
      "$ref": "#/$defs/trigger"
    },
    "period": {
      "$ref": "#/$defs/period"
    },
    "jurisdiction": {
      "$ref": "#/$defs/jurisdiction"
    },
    "counterparties": {
      "$ref": "#/$defs/counterparties"
    },
    "provenance": {
      "$ref": "#/$defs/provenance"
    }
  },
  "allOf": [
    {
      "title": "An analysed object names its peril",
      "description": "SPEC section 3.2: Analyse runs hazard lookups, event footprints and accumulation against the object. There is nothing to look up without peril.code, so every status past draft carries a peril block.",
      "if": {
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "enum": [
              "analysed",
              "priced",
              "recorded_in_force"
            ]
          }
        }
      },
      "then": {
        "required": [
          "peril"
        ]
      }
    },
    {
      "title": "A priced object carries its technical premium",
      "description": "SPEC section 3.3: Price writes a technical price into financial_structure.premium with a pointer to the run. SPEC section 4 P3: every price output is labelled technical and carries its assumption set.",
      "if": {
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "enum": [
              "priced",
              "recorded_in_force"
            ]
          }
        }
      },
      "then": {
        "required": [
          "financial_structure"
        ],
        "properties": {
          "financial_structure": {
            "required": [
              "premium"
            ]
          }
        }
      }
    },
    {
      "title": "A recorded structure is fully specified",
      "description": "SPEC section 3.5 takes a structure with a fully specified trigger; SPEC section 3.6 monitors a recorded structure against the live feeds behind that trigger, over a period. Neither is possible without trigger and period.",
      "if": {
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "const": "recorded_in_force"
          }
        }
      },
      "then": {
        "required": [
          "trigger",
          "period"
        ]
      }
    },
    {
      "title": "Indemnity-style structures attach at a monetary retention",
      "description": "SPEC section 2, financial structure: attachment is a monetary retention for indemnity-style structures and an index threshold for parametric ones, cross-referencing the trigger. So a wording trigger forces the monetary form.",
      "if": {
        "required": [
          "trigger"
        ],
        "properties": {
          "trigger": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "wording"
              }
            }
          }
        }
      },
      "then": {
        "properties": {
          "financial_structure": {
            "properties": {
              "attachment": {
                "$ref": "#/$defs/money"
              }
            }
          }
        }
      }
    }
  ],
  "$defs": {
    "object_id": {
      "type": "string",
      "pattern": "^([0-9A-HJKMNP-TV-Z]{26}|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$",
      "description": "A ULID (26 characters, Crockford base32) or a UUID."
    },
    "model_identifier": {
      "type": "string",
      "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$",
      "description": "The name or version of a model, as an identifier and never as prose. The pattern is the constraint that matters: these are the only fields on an object whose value originates with a model provider, and bounding them to identifier characters is what stops one from carrying a generated sentence (adr/ADR-0016 constraint 2)."
    },
    "currency_code": {
      "type": "string",
      "pattern": "^[A-Z]{3}$",
      "description": "ISO 4217 alphabetic currency code."
    },
    "territory_code": {
      "type": "string",
      "pattern": "^[A-Z]{2}(-[A-Z0-9]{1,3})?$",
      "description": "ISO 3166-1 alpha-2 country code, optionally with an ISO 3166-2 subdivision suffix."
    },
    "timestamp": {
      "type": "string",
      "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}([.][0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$",
      "description": "RFC 3339 date-time with an explicit UTC offset."
    },
    "iana_timezone": {
      "type": "string",
      "pattern": "^[A-Za-z][A-Za-z0-9_+-]*(/[A-Za-z0-9_+-]+)*$",
      "description": "IANA time zone name, e.g. Europe/London. Existence in the tz database is checked by schema/validate.py, which JSON Schema cannot do."
    },
    "money": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "amount",
        "currency"
      ],
      "description": "A currency-tagged amount. SPEC section 2 tags every monetary value with its currency.",
      "properties": {
        "amount": {
          "type": "number",
          "minimum": 0
        },
        "currency": {
          "$ref": "#/$defs/currency_code"
        }
      }
    },
    "exposure_amount": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "amount"
      ],
      "description": "An insured value as the source file gave it. Currency-tagged whenever the file states one anywhere (a cell, a column header, a units banner, or the single currency the rest of the file declares); untagged when it states none, because SPEC section 2 makes source_fidelity the guarantee that nothing is silently dropped and inferring a currency from an address would be enrichment (SPEC section 3.1). See adr/ADR-0029. financial_structure amounts keep the stricter money form: those are figures Cede computes, not cells a broker typed.",
      "properties": {
        "amount": {
          "type": "number",
          "minimum": 0
        },
        "currency": {
          "$ref": "#/$defs/currency_code"
        }
      }
    },
    "index_level": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "value",
        "unit"
      ],
      "description": "A level of a parametric index, in the index's own unit. The alternative form of financial_structure.attachment for parametric structures.",
      "properties": {
        "value": {
          "type": "number"
        },
        "unit": {
          "type": "string",
          "minLength": 1
        },
        "index_ref": {
          "type": "string",
          "minLength": 1,
          "description": "Name of the trigger index this level reads against."
        }
      }
    },
    "peril_code": {
      "type": "string",
      "enum": [
        "earthquake",
        "tropical_cyclone",
        "flood",
        "wildfire",
        "severe_convective_storm",
        "weather_station",
        "cloud_outage",
        "grid_outage"
      ],
      "description": "Canonical peril taxonomy (SPEC section 2, peril). tropical_cyclone covers named-storm structures. Grows only via schema version bump."
    },
    "geometry": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "type",
        "coordinates"
      ],
      "description": "GeoJSON-shaped geometry. Coordinate order is longitude, latitude.",
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "Point",
            "Polygon",
            "MultiPolygon"
          ]
        },
        "coordinates": {
          "type": "array"
        }
      }
    },
    "exposure_values": {
      "type": "object",
      "additionalProperties": false,
      "minProperties": 1,
      "description": "Insured values by coverage part, currency-tagged wherever the source file states a currency (SPEC section 2, exposure). A file that states none anywhere yields untagged amounts rather than none at all, recorded as such in source_fidelity: adr/ADR-0029.",
      "properties": {
        "building": {
          "$ref": "#/$defs/exposure_amount"
        },
        "contents": {
          "$ref": "#/$defs/exposure_amount"
        },
        "business_interruption": {
          "$ref": "#/$defs/exposure_amount"
        },
        "other": {
          "$ref": "#/$defs/exposure_amount"
        }
      }
    },
    "location": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "geocode"
      ],
      "anyOf": [
        {
          "title": "coordinates supplied",
          "required": [
            "latitude",
            "longitude"
          ]
        },
        {
          "title": "address as given, not yet resolved to coordinates",
          "required": [
            "address_as_given"
          ]
        }
      ],
      "description": "A location: coordinates, geocoding resolution and confidence, address as given, occupancy and construction attributes as available (SPEC section 2, exposure). A location must say where it is in one of the two ways a source file can say it — coordinates, or the address as given — and it always carries a geocode block stating which. Coordinates are not required because Ingest never geocodes: SPEC section 3.1 puts enrichment out of its scope, so a schedule of addressed buildings must be recordable as the locations it is rather than as an aggregate (adr/ADR-0006-ungeocoded-locations.md).",
      "properties": {
        "ref": {
          "type": "string",
          "minLength": 1,
          "description": "Identifier for this location as carried in the source file."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "description": "Site name as carried in the source file, e.g. 'Distribution Ctr East'. Optional and purely descriptive: it is how humans on both sides of a submission refer to the location, and dropping it would lose the source's own words for the sake of a field list (adr/ADR-0006-ungeocoded-locations.md)."
        },
        "address_as_given": {
          "type": "string",
          "description": "The address exactly as it appeared in the source, before normalisation."
        },
        "latitude": {
          "type": "number",
          "minimum": -90,
          "maximum": 90
        },
        "longitude": {
          "type": "number",
          "minimum": -180,
          "maximum": 180
        },
        "geocode": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "resolution"
          ],
          "properties": {
            "resolution": {
              "type": "string",
              "enum": [
                "rooftop",
                "street",
                "postal_code",
                "locality",
                "admin_area",
                "centroid",
                "as_supplied",
                "unknown"
              ]
            },
            "confidence": {
              "type": "number",
              "minimum": 0,
              "maximum": 1
            }
          }
        },
        "occupancy": {
          "type": "string"
        },
        "construction": {
          "type": "string"
        },
        "year_built": {
          "type": "integer"
        },
        "storeys": {
          "type": "integer",
          "minimum": 0
        },
        "values": {
          "$ref": "#/$defs/exposure_values"
        }
      }
    },
    "source_fidelity": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "unmapped_columns",
        "guessed_units",
        "ambiguous_rows"
      ],
      "description": "The honesty channel (SPEC section 2, exposure): everything Ingest could not confidently normalise. All three lists are mandatory so that 'nothing was guessed' has to be stated as an empty list rather than inferred from a missing key — nothing is silently dropped.",
      "properties": {
        "unmapped_columns": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "column"
            ],
            "properties": {
              "column": {
                "type": "string",
                "minLength": 1
              },
              "sheet": {
                "type": "string"
              },
              "note": {
                "type": "string"
              }
            }
          }
        },
        "guessed_units": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "field",
              "assumed_unit"
            ],
            "properties": {
              "field": {
                "type": "string",
                "minLength": 1
              },
              "assumed_unit": {
                "type": "string",
                "minLength": 1
              },
              "note": {
                "type": "string"
              }
            }
          }
        },
        "ambiguous_rows": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "row_ref",
              "reason"
            ],
            "properties": {
              "row_ref": {
                "type": "string",
                "minLength": 1
              },
              "reason": {
                "type": "string",
                "minLength": 1
              }
            }
          }
        },
        "inferred_mappings": {
          "type": "array",
          "description": "Columns whose canonical field was INFERRED rather than read from the source's own vocabulary — today, by an AI-assisted mapping pass over the columns the deterministic mapper left in unmapped_columns (adr/ADR-0016, adr/ADR-0017). Optional and absent unless something was actually inferred, so an object carrying none is byte-identical to the deterministic one. Every field here is a closed value: column is the source file's own header, adopted_mapping is an enum member of the mapper's vocabulary, and the two model identifiers are pattern-bounded. There is deliberately no free-text member — no model-composed string may enter a response field (ADR-0016 constraint 2), and a note field would be exactly the hole that rule closes.",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "column",
              "adopted_mapping",
              "model",
              "model_version"
            ],
            "properties": {
              "column": {
                "type": "string",
                "minLength": 1
              },
              "adopted_mapping": {
                "type": "string",
                "description": "The canonical field the column was read as. A closed enum: this is what makes an adopted proposal an identifier rather than text. It grows only with the mapper's own vocabulary, and tests/unit/test_ingest_assist.py pins the two together.",
                "enum": [
                  "address",
                  "city",
                  "construction",
                  "country",
                  "geometry",
                  "latitude",
                  "longitude",
                  "occupancy",
                  "peril",
                  "postcode",
                  "ref",
                  "site_name",
                  "state",
                  "value_bi",
                  "value_building",
                  "value_contents",
                  "value_total",
                  "year_built"
                ]
              },
              "model": {
                "$ref": "#/$defs/model_identifier"
              },
              "model_version": {
                "$ref": "#/$defs/model_identifier"
              }
            }
          }
        }
      }
    },
    "exposure": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "kind",
        "source_fidelity"
      ],
      "description": "What is at risk (SPEC section 2, exposure). locations is optional: SPEC records an open question about how index and revenue exposures without locations are validated, and this schema does not invent an answer.",
      "properties": {
        "kind": {
          "type": "string",
          "enum": [
            "location_schedule",
            "sov",
            "bordereau_derived",
            "index",
            "revenue_stream",
            "other"
          ]
        },
        "locations": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/location"
          }
        },
        "values": {
          "$ref": "#/$defs/exposure_values"
        },
        "currency": {
          "$ref": "#/$defs/currency_code",
          "description": "Default currency for the exposure where per-value currency is absent."
        },
        "source_fidelity": {
          "$ref": "#/$defs/source_fidelity"
        }
      }
    },
    "peril": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "code"
      ],
      "description": "What can happen to the exposure (SPEC section 2, peril).",
      "properties": {
        "code": {
          "$ref": "#/$defs/peril_code"
        },
        "region": {
          "type": "object",
          "additionalProperties": false,
          "minProperties": 1,
          "description": "Geographic scoping of the peril, distinct from the exposure's locations.",
          "properties": {
            "basin": {
              "type": "string",
              "minLength": 1
            },
            "countries": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/territory_code"
              },
              "uniqueItems": true
            },
            "bounding_geometry": {
              "$ref": "#/$defs/geometry"
            },
            "description": {
              "type": "string"
            }
          }
        },
        "secondary": {
          "type": "array",
          "uniqueItems": true,
          "items": {
            "$ref": "#/$defs/peril_code"
          },
          "description": "Secondary or consequent perils considered in analysis, if any."
        }
      }
    },
    "financial_structure": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "limit",
        "attachment"
      ],
      "description": "Limit, attachment, premium (SPEC section 2, financial structure). limit and attachment are the defining pair of a structure; premium, layers and reinstatements are produced later by Price and Structure and are optional here.",
      "properties": {
        "limit": {
          "$ref": "#/$defs/money",
          "description": "Maximum payout of the structure."
        },
        "attachment": {
          "description": "Point at which the structure attaches: a monetary retention for indemnity-style structures, an index threshold for parametric ones.",
          "oneOf": [
            {
              "$ref": "#/$defs/money"
            },
            {
              "$ref": "#/$defs/index_level"
            }
          ]
        },
        "premium": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "basis",
            "amount",
            "currency",
            "assumptions_ref"
          ],
          "description": "Technical premium as computed by Price (SPEC section 3.3). Analytical output only: it is never a price at which anyone has agreed to transact. basis is a required constant so that the SPEC section 4 P3 label cannot be omitted, and assumptions_ref is the mandatory pointer to the assumption set that produced the number.",
          "properties": {
            "basis": {
              "const": "technical"
            },
            "amount": {
              "type": "number",
              "minimum": 0
            },
            "currency": {
              "$ref": "#/$defs/currency_code"
            },
            "assumptions_ref": {
              "type": "string",
              "minLength": 1,
              "description": "Pointer to the complete assumption set (model versions, data vintages, event set, loadings, currency basis) that produced this amount."
            },
            "price_run_ref": {
              "type": "string",
              "minLength": 1,
              "description": "Identifier of the price run recorded in provenance."
            }
          }
        },
        "layers": {
          "type": "array",
          "minItems": 1,
          "description": "For layered programmes. Produced by Structure (SPEC section 3.4).",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "limit",
              "attachment",
              "share"
            ],
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1
              },
              "limit": {
                "$ref": "#/$defs/money"
              },
              "attachment": {
                "oneOf": [
                  {
                    "$ref": "#/$defs/money"
                  },
                  {
                    "$ref": "#/$defs/index_level"
                  }
                ]
              },
              "share": {
                "type": "number",
                "minimum": 0,
                "maximum": 1,
                "description": "Participation in the layer, as a fraction of 1."
              }
            }
          }
        },
        "reinstatements": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "count",
            "cost_basis"
          ],
          "description": "Number and cost basis, if applicable.",
          "properties": {
            "count": {
              "oneOf": [
                {
                  "type": "integer",
                  "minimum": 0
                },
                {
                  "const": "unlimited"
                }
              ]
            },
            "cost_basis": {
              "type": "string",
              "enum": [
                "free",
                "pro_rata_time",
                "pro_rata_amount",
                "pro_rata_time_and_amount",
                "fixed_percentage"
              ]
            },
            "percentage": {
              "type": "number",
              "minimum": 0
            }
          }
        }
      }
    },
    "trigger": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "type"
      ],
      "description": "Parametric trigger, or wording reference for indemnity-style structures. SPEC section 2 states that exactly one of the two branches is populated; the oneOf below is that rule.",
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "parametric_index",
            "parametric_cat_in_a_box",
            "wording"
          ]
        },
        "index": {
          "$ref": "#/$defs/index_definition"
        },
        "data_sources": {
          "type": "array",
          "minItems": 1,
          "description": "The public feeds the index reads, each pinned to a version or vintage.",
          "items": {
            "$ref": "#/$defs/data_source"
          }
        },
        "wording_ref": {
          "$ref": "#/$defs/wording_ref"
        }
      },
      "oneOf": [
        {
          "title": "parametric branch",
          "properties": {
            "type": {
              "enum": [
                "parametric_index",
                "parametric_cat_in_a_box"
              ]
            }
          },
          "required": [
            "index",
            "data_sources"
          ],
          "not": {
            "required": [
              "wording_ref"
            ]
          }
        },
        {
          "title": "wording branch",
          "properties": {
            "type": {
              "const": "wording"
            }
          },
          "required": [
            "wording_ref"
          ],
          "allOf": [
            {
              "not": {
                "required": [
                  "index"
                ]
              }
            },
            {
              "not": {
                "required": [
                  "data_sources"
                ]
              }
            }
          ]
        }
      ]
    },
    "index_definition": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "name",
        "version",
        "measurement",
        "aggregation_window",
        "thresholds",
        "payout_function"
      ],
      "description": "The index definition as versioned code (SPEC section 2, trigger or wording): data sources (held as trigger.data_sources), measurement, aggregation window, thresholds and payout function. Deterministic: same inputs, same payout.",
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1
        },
        "version": {
          "type": "string",
          "minLength": 1
        },
        "description": {
          "type": "string"
        },
        "measurement": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "variable",
            "unit"
          ],
          "properties": {
            "variable": {
              "type": "string",
              "minLength": 1,
              "description": "The measured quantity, named exactly as the feed named in data_sources publishes it: daily_precipitation_mm, daily_maximum_wind_speed_mph, catalogue_preferred_magnitude. Any non-empty string validates here — a variable no pinned feed publishes is refused by the verb that would read it, not by this schema — so docs/feeds.md and GET /primitives are what say which one to write."
            },
            "unit": {
              "type": "string",
              "minLength": 1
            },
            "statistic": {
              "type": "string",
              "enum": [
                "max",
                "min",
                "mean",
                "sum",
                "count",
                "peak",
                "threshold_exceedance"
              ]
            }
          }
        },
        "aggregation_window": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "duration"
          ],
          "properties": {
            "duration": {
              "type": "string",
              "pattern": "^P([0-9]+[YMWD])*([T]([0-9]+[HMS])+)?$",
              "description": "ISO 8601 duration, e.g. P1D, PT72H."
            },
            "alignment": {
              "type": "string",
              "enum": [
                "calendar_day",
                "rolling",
                "event"
              ]
            }
          }
        },
        "thresholds": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "level"
            ],
            "properties": {
              "label": {
                "type": "string",
                "minLength": 1
              },
              "level": {
                "type": "number"
              },
              "unit": {
                "type": "string",
                "minLength": 1
              }
            }
          }
        },
        "payout_function": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "type"
          ],
          "properties": {
            "type": {
              "type": "string",
              "enum": [
                "binary",
                "step",
                "linear",
                "piecewise_linear"
              ]
            },
            "points": {
              "type": "array",
              "minItems": 1,
              "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "level",
                  "payout_ratio"
                ],
                "properties": {
                  "level": {
                    "type": "number"
                  },
                  "payout_ratio": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  }
                }
              }
            },
            "maximum_payout_ratio": {
              "type": "number",
              "minimum": 0,
              "maximum": 1
            }
          }
        }
      }
    },
    "data_source": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "id",
        "version"
      ],
      "description": "A public feed the index reads, pinned to a version. vintage records the as-of date of that pinned version where the publisher dates its releases.",
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1
        },
        "name": {
          "type": "string"
        },
        "kind": {
          "type": "string",
          "enum": [
            "quake_catalogue",
            "storm_track_set",
            "station_network",
            "footprint_provider",
            "outage_feed",
            "other"
          ]
        },
        "version": {
          "type": "string",
          "minLength": 1
        },
        "vintage": {
          "type": "string",
          "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
        }
      }
    },
    "wording_ref": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "id",
        "version"
      ],
      "description": "Reference to a wording held as versioned text, plus clause-level metadata. Cede stores and versions wordings; it never executes them as contracts.",
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1
        },
        "version": {
          "type": "string",
          "minLength": 1
        },
        "title": {
          "type": "string"
        },
        "clauses": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "ref"
            ],
            "properties": {
              "ref": {
                "type": "string",
                "minLength": 1
              },
              "title": {
                "type": "string"
              },
              "category": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "period": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "inception",
        "expiry",
        "timezone"
      ],
      "description": "Inception and expiry of the cover period being modelled. expiry must be after inception — a comparison between two sibling instance values, which JSON Schema cannot express, so schema/validate.py enforces it.",
      "properties": {
        "inception": {
          "$ref": "#/$defs/timestamp"
        },
        "expiry": {
          "$ref": "#/$defs/timestamp"
        },
        "timezone": {
          "$ref": "#/$defs/iana_timezone",
          "description": "Zone in which day boundaries are evaluated for the trigger."
        }
      }
    },
    "jurisdiction": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "governing_law"
      ],
      "description": "Governing law and territorial scope. Informational to Cede — Cede gives no legal advice and executes nothing.",
      "properties": {
        "governing_law": {
          "$ref": "#/$defs/territory_code",
          "description": "Law the draft slip contemplates."
        },
        "territories": {
          "type": "array",
          "minItems": 1,
          "uniqueItems": true,
          "items": {
            "$ref": "#/$defs/territory_code"
          }
        }
      }
    },
    "counterparties": {
      "type": "array",
      "description": "Who is on each side. Descriptive only — recording a party on an object creates no obligation and no relationship through Cede.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "role",
          "name"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "cedent",
              "protection_buyer",
              "risk_taker",
              "broker_of_record",
              "calculation_agent",
              "other"
            ]
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "description": "Legal name as given."
          },
          "identifiers": {
            "type": "object",
            "additionalProperties": false,
            "description": "LEI or similar where supplied. Never verified as KYC by Cede; Cede is not a money-touching party.",
            "properties": {
              "lei": {
                "type": "string",
                "pattern": "^[A-Z0-9]{18}[0-9]{2}$"
              },
              "other": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "provenance": {
      "type": "object",
      "additionalProperties": false,
      "description": "How this object came to exist: source files, ingest run, model runs, prior object versions. Append-only — entries are added, never rewritten, which is a storage rule rather than a shape rule and so is not expressible here.",
      "properties": {
        "source_files": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "name"
            ],
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1
              },
              "sha256": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$"
              },
              "media_type": {
                "type": "string"
              },
              "received_at": {
                "$ref": "#/$defs/timestamp"
              }
            }
          }
        },
        "ingest_run": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "run_id"
          ],
          "properties": {
            "run_id": {
              "type": "string",
              "minLength": 1
            },
            "completed_at": {
              "$ref": "#/$defs/timestamp"
            }
          }
        },
        "model_runs": {
          "type": "array",
          "description": "Every result is labelled with the model and version that produced it (SPEC section 4, P9), so both are required on each entry.",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "run_id",
              "model",
              "model_version"
            ],
            "properties": {
              "run_id": {
                "type": "string",
                "minLength": 1
              },
              "verb": {
                "type": "string",
                "enum": [
                  "analyse",
                  "price",
                  "structure",
                  "backtest",
                  "monitor",
                  "package",
                  "model_run"
                ],
                "description": "What produced this run: one of the seven verbs (SPEC.md section 3), or model_run for a run of a registry model composed in the builder (SPEC.md section 2A). Additive growth of this list is not a breaking change — a reader that does not know a value sees a run it cannot classify, never a document it cannot parse."
              },
              "model": {
                "type": "string",
                "minLength": 1
              },
              "model_version": {
                "type": "string",
                "minLength": 1
              },
              "completed_at": {
                "$ref": "#/$defs/timestamp"
              }
            }
          }
        },
        "analysis_runs": {
          "type": "array",
          "description": "Every analyse run recorded against this object (SPEC.md section 3.2), oldest first. Append-only, like the rest of provenance: a later run adds an entry and rewrites none. The full artifacts travel in the job that produced them — this is the bounded record that survives on the object, so a price read months later can be read against what the pinned data said at the time. Each artifact entry carries its assumptions and the vintage of every snapshot it read, which is section 3.2's requirement that assumptions and data vintages are exposed on every artifact.",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "run_id",
              "artifacts"
            ],
            "properties": {
              "run_id": {
                "type": "string",
                "minLength": 1
              },
              "completed_at": {
                "$ref": "#/$defs/timestamp"
              },
              "links": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "job"
                ],
                "description": "Where the full artifacts are: the job this run ran as. A link rather than an embedded copy, because one hazard document per location would grow the object without limit.",
                "properties": {
                  "job": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              },
              "artifacts": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "analysis",
                    "summary",
                    "assumptions",
                    "data_vintages"
                  ],
                  "properties": {
                    "analysis": {
                      "type": "string",
                      "minLength": 1,
                      "description": "The named analysis that produced this artifact, as the caller asked for it."
                    },
                    "summary": {
                      "type": "string",
                      "minLength": 1,
                      "description": "One line stating what the artifact found and which snapshots it found it in. Never a view of risk and never a price."
                    },
                    "assumptions": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "method"
                      ],
                      "properties": {
                        "method": {
                          "type": "string",
                          "minLength": 1
                        },
                        "notes": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "minLength": 1
                          }
                        }
                      }
                    },
                    "data_vintages": {
                      "type": "array",
                      "minItems": 1,
                      "description": "Every pinned snapshot this artifact was computed from. A vintage is never edited in place, so these four values are enough to fetch the exact bytes again at /feeds/{id}/versions/{version}/data and check the digest.",
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "id",
                          "version",
                          "vintage",
                          "sha256"
                        ],
                        "properties": {
                          "id": {
                            "type": "string",
                            "minLength": 1
                          },
                          "version": {
                            "type": "string",
                            "minLength": 1
                          },
                          "vintage": {
                            "type": "string",
                            "minLength": 1
                          },
                          "sha256": {
                            "type": "string",
                            "pattern": "^[0-9a-f]{64}$"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "prior_versions": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "object_id"
            ],
            "properties": {
              "object_id": {
                "$ref": "#/$defs/object_id"
              },
              "superseded_at": {
                "$ref": "#/$defs/timestamp"
              }
            }
          }
        },
        "assist_run": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "model",
            "model_version",
            "adopted_digest"
          ],
          "description": "The AI-assisted pass that contributed to this object, if one did (adr/ADR-0016 constraint 3, adr/ADR-0017). Present only when at least one proposal was ADOPTED: a pass whose every proposal was refused leaves the deterministic object untouched, so there is nothing to record and nothing recorded. adopted_digest content-addresses the model, its version and every adopted (column, field) pair, and the same digest is carried in ingest_run.run_id — which is how two runs that produce different objects are kept from sharing an indistinguishable run record.",
          "properties": {
            "model": {
              "$ref": "#/$defs/model_identifier"
            },
            "model_version": {
              "$ref": "#/$defs/model_identifier"
            },
            "adopted_digest": {
              "type": "string",
              "pattern": "^[0-9a-f]{64}$"
            },
            "completed_at": {
              "$ref": "#/$defs/timestamp"
            }
          }
        }
      }
    }
  }
}
