{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://law.arxo.io/schema/population-bank.schema.json",
  "title": "Arxo population impact bank 0.1 / 0.2",
  "description": "A pinned population for the population-impact comparison: two sides — case packages with their own pins — rows mapping a row id to a case and a weight, and a single measure. It carries no legal meaning: weights and sums only orchestrate the comparison and are not a legal answer. The runner checks row id uniqueness, non-negative weights, the content hash self-pin, and equal case sets on both sides (rejection POPULATION_BANK_INVALID); the schema only holds the shape. Form 0.2 adds a distribution section — auxiliary rank and size queries, a pinned equivalence scale, and a group count — and provenance, an optional link to the materialization manifest. Auxiliary queries run as separate asks on the basis side.",
  "$comment": "DECISION-0255; SPEC §168.4; DECISION-0264; DECISION-0265 and clarifications",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schemaVersion",
    "bankId",
    "worlds",
    "rows",
    "measure",
    "contentHash"
  ],
  "properties": {
    "schemaVersion": {
      "enum": [
        "law.population.bank/0.1",
        "law.population.bank/0.2"
      ]
    },
    "bankId": {
      "$ref": "#/$defs/Name"
    },
    "worlds": {
      "type": "object",
      "additionalProperties": false,
      "required": ["before", "after"],
      "properties": {
        "before": {
          "$ref": "#/$defs/WorldPin"
        },
        "after": {
          "$ref": "#/$defs/WorldPin"
        }
      }
    },
    "rows": {
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/Row"
      }
    },
    "measure": {
      "$ref": "#/$defs/Measure"
    },
    "contentHash": {
      "description": "Self-pin: sha256 of the bank's canonical bytes without the contentHash field, with rows ordered by the bytes of rowId: permuting the rows in the input does not change the hash.",
      "$comment": "SPEC §208; DECISION-0255 §8",
      "$ref": "#/$defs/Digest"
    },
    "distribution": {
      "$ref": "#/$defs/Distribution"
    },
    "provenance": {
      "$ref": "#/$defs/Provenance"
    }
  },
  "$defs": {
    "Digest": {
      "type": "string",
      "pattern": "^sha256:[0-9a-f]{64}$"
    },
    "Name": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_.-]+$"
    },
    "CaseName": {
      "description": "Name of the case declaration, as recorded in the [[cases]] list of the case package.",
      "$comment": "SPEC §168.4",
      "type": "string",
      "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
    },
    "Weight": {
      "description": "Decimal string not less than zero.",
      "$comment": "DECISION-0255 §6",
      "type": "string",
      "pattern": "^[0-9]+(\\.[0-9]+)?$"
    },
    "WorldPin": {
      "description": "A side of the bank: a case package and its pins. theoryHash and artifactHash are the hashes of the bound world W = root + closure C, the same hashes each row's evaluation manifest records.",
      "$comment": "SPEC §168.4; DECISION-0255 §1; SPEC §209; DECISION-0170",
      "type": "object",
      "additionalProperties": false,
      "required": [
        "package",
        "manifestHash",
        "lockHash",
        "theoryHash",
        "artifactHash",
        "resources"
      ],
      "properties": {
        "package": {
          "description": "Path to the case package's law.toml relative to the bank file's directory.",
          "type": "string",
          "pattern": "^(?!/)(.+/)?law\\.toml$"
        },
        "manifestHash": {
          "description": "sha256 of the exact bytes of law.toml.",
          "$ref": "#/$defs/Digest"
        },
        "lockHash": {
          "description": "sha256 of the exact bytes of law.lock.",
          "$ref": "#/$defs/Digest"
        },
        "theoryHash": {
          "$ref": "#/$defs/Digest"
        },
        "artifactHash": {
          "$ref": "#/$defs/Digest"
        },
        "resources": {
          "description": "Explicit resources of the side (calendars, documents, snapshots) — exactly the resources entries of the pinned law.lock, ordered by id and path.",
          "type": "array",
          "uniqueItems": true,
          "items": {
            "$ref": "lockfile.schema.json#/$defs/LockedResource"
          }
        }
      }
    },
    "Row": {
      "type": "object",
      "additionalProperties": false,
      "required": ["rowId", "case", "weight"],
      "properties": {
        "rowId": {
          "$ref": "#/$defs/Name"
        },
        "case": {
          "$ref": "#/$defs/CaseName"
        },
        "weight": {
          "$ref": "#/$defs/Weight"
        }
      }
    },
    "ShortQueryId": {
      "description": "Short query name: the ask operation completes the full URN in the namespace of the case package and the row's case.",
      "$comment": "SPEC §172",
      "type": "object",
      "properties": {
        "queryId": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_.-]+$"
        }
      }
    },
    "Measure": {
      "description": "A single measure per bank: a collect-all query (distinct: false) over Money literals, or a term query without an aggregate and with the empty-options flag; the stage is printed into the report verbatim. The schema deliberately does not constrain the distinct flag, the empty-options flag in a term query, side resolution, or an explicit non-Money variable type: those are the runner's POPULATION_MEASURE_INVALID rejection, and the bank's shape must not intercept them with a POPULATION_BANK_INVALID rejection.",
      "$comment": "DECISION-0255 §3; SPEC §52.1; SPEC §12; hypotheses H2, H3",
      "type": "object",
      "additionalProperties": false,
      "required": ["name", "stage", "currency", "query"],
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1
        },
        "stage": {
          "type": "string",
          "minLength": 1
        },
        "currency": {
          "type": "string",
          "pattern": "^[A-Z]{3}$"
        },
        "query": {
          "oneOf": [
            {
              "allOf": [
                {
                  "$ref": "query.schema.json#/$defs/CollectQuery"
                },
                {
                  "$ref": "#/$defs/ShortQueryId"
                }
              ]
            },
            {
              "allOf": [
                {
                  "$ref": "query.schema.json#/$defs/TermQuery"
                },
                {
                  "$ref": "#/$defs/ShortQueryId"
                }
              ]
            }
          ]
        }
      }
    },
    "PositiveDecimal": {
      "description": "Decimal string greater than zero (equivalence-scale weight).",
      "type": "string",
      "pattern": "^(0*[1-9][0-9]*(\\.[0-9]+)?|0+\\.[0-9]*[1-9][0-9]*)$"
    },
    "ScalePin": {
      "description": "Equivalence scale, pinned as name + version + source + contentHash; weights — first adult, each subsequent adult, child. contentHash is a self-pin: sha256 of the object's canonical bytes without the contentHash field (checked by the runner, POPULATION_BANK_INVALID). The runner applies it as a statistical operation.",
      "$comment": "DECISION-0265 §1, D-2; SPEC §208",
      "type": "object",
      "additionalProperties": false,
      "required": [
        "name",
        "version",
        "source",
        "contentHash",
        "weights"
      ],
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1
        },
        "version": {
          "type": "string",
          "minLength": 1
        },
        "source": {
          "type": "string",
          "minLength": 1
        },
        "contentHash": {
          "$ref": "#/$defs/Digest"
        },
        "weights": {
          "type": "object",
          "additionalProperties": false,
          "required": ["firstAdult", "otherAdult", "child"],
          "properties": {
            "firstAdult": {
              "$ref": "#/$defs/PositiveDecimal"
            },
            "otherAdult": {
              "$ref": "#/$defs/PositiveDecimal"
            },
            "child": {
              "$ref": "#/$defs/PositiveDecimal"
            }
          }
        }
      }
    },
    "SizeQuery": {
      "description": "A term query with an Integer result of zero or more (number of adults or children); the empty-options flag in the term is a POPULATION_MEASURE_INVALID rejection.",
      "$comment": "DECISION-0255 H3",
      "allOf": [
        {
          "$ref": "query.schema.json#/$defs/TermQuery"
        },
        {
          "$ref": "#/$defs/ShortQueryId"
        }
      ]
    },
    "Distribution": {
      "description": "Deciles for form 0.2 of the impact comparison: groups — number of groups; basis — the ranking side; scale — the pinned scale; rank — the ranking measure (Measure shape, executed as a separate ask on the basis side); size — queries for the number of adults (at least one) and children.",
      "$comment": "DECISION-0265 §§1–3",
      "type": "object",
      "additionalProperties": false,
      "required": [
        "groups",
        "basis",
        "scale",
        "rank",
        "size"
      ],
      "properties": {
        "groups": {
          "type": "integer",
          "minimum": 2
        },
        "basis": {
          "const": "before"
        },
        "scale": {
          "$ref": "#/$defs/ScalePin"
        },
        "rank": {
          "$ref": "#/$defs/Measure"
        },
        "size": {
          "type": "object",
          "additionalProperties": false,
          "required": ["adults", "children"],
          "properties": {
            "adults": {
              "$ref": "#/$defs/SizeQuery"
            },
            "children": {
              "$ref": "#/$defs/SizeQuery"
            }
          }
        }
      }
    },
    "Provenance": {
      "description": "Link to the materialization manifest: the manifest's contentHash and the data class; real data triggers the runner's rejection.",
      "$comment": "DECISION-0264",
      "type": "object",
      "additionalProperties": false,
      "required": ["materializationHash", "dataClass"],
      "properties": {
        "materializationHash": {
          "$ref": "#/$defs/Digest"
        },
        "dataClass": {
          "enum": ["synthetic", "real"]
        }
      }
    }
  },
  "allOf": [
    {
      "if": {
        "properties": {
          "schemaVersion": {
            "const": "law.population.bank/0.2"
          }
        }
      },
      "then": {
        "required": ["distribution"]
      },
      "else": {
        "not": {
          "anyOf": [
            {
              "required": ["distribution"]
            },
            {
              "required": ["provenance"]
            }
          ]
        }
      }
    }
  ]
}
