{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://xleitstelle.usercontent.opencode.de/xmas-core/rules/migration/rules.schema.json",
  "$defs": {
    "CodeTable": {
      "additionalProperties": false,
      "description": "How codes change: each listed code becomes its value, an unlisted one is kept or dropped.\n\nOne class for both places a table is written. A hop's rule for an enumeration is one,\nand the engine rewrites every property of that enumeration through it wherever it\nappears; a `map` operation is one rule's table for one attribute, optionally moving the\nresult into another. A table value is one of:\n\n- **a code**, which takes the old one's place;\n- **a list of codes**, for an old code the new version covers with several, which take\n  its place together;\n- **`null`** (or `[]`), for a code with no successor, which goes the way an unlisted code\n  does under `unmapped: drop`.\n\nA table that replaces any code by several keeps **every code once** - `BP_Wegerecht`'s\n1000 and 2000 are 2000, 2500 and 2000 otherwise. A table that does not keeps a list's\n**length**, so a parallel detail list stays in step with it: two codes that became the\nsame one are both kept, since collapsing them would hand the second code's detail to\nwhatever came after it. That is also why the loader refuses `paired` beside the first\nkind.\n\nA removed code is recorded as gone, and every entry that moves is recorded where it went.",
      "properties": {
        "map": {
          "additionalProperties": true,
          "title": "Map",
          "type": "object"
        },
        "unmapped": {
          "default": "keep",
          "enum": [
            "keep",
            "drop"
          ],
          "title": "Unmapped",
          "type": "string"
        }
      },
      "required": [
        "map"
      ],
      "title": "CodeTable",
      "type": "object"
    },
    "Drop": {
      "additionalProperties": false,
      "description": "Remove attributes the target version does not have.\n\nLosing a value is the one thing a migration does that a reader needs told about, so a\ndrop **reports by default** - once per attribute that actually held something, never for\none that was already absent. That also makes it the way to report what an earlier\noperation *left*: a `move` with `if_unset` or a `map` that did not match leaves its\nsource in place, and the drop after it reports exactly those.\n\n`warn: null` silences it, for the drop of a value that is not lost: one a `pre:`/`post:`\noperation has already moved somewhere else.",
      "properties": {
        "op": {
          "const": "drop",
          "title": "Op",
          "type": "string"
        },
        "comment": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "A note for whoever reads the rule file. Never read when the rule runs.",
          "title": "Comment"
        },
        "when": {
          "anyOf": [
            {
              "$ref": "#/$defs/When"
            },
            {
              "items": {
                "$ref": "#/$defs/When"
              },
              "type": "array"
            }
          ],
          "description": "Guards, ANDed. A single clause may be written without the surrounding list.",
          "title": "When"
        },
        "then": {
          "description": "Operations that depend on this one, and run under the same guard.\n\nThe alternative is for each of them to test what this operation wrote, which is how a\nrule used to say \"if the retype above fired\" - by reading `featuretype` as though it\nwere an attribute, once per operation. Sharing the guard also evaluates it **once**,\nbefore this operation runs: one rule guards three operations on a value the third of\nthem overwrites, and was only correct because that one came last.",
          "items": {
            "discriminator": {
              "mapping": {
                "drop": "#/$defs/Drop",
                "fold": "#/$defs/Fold",
                "map": "#/$defs/Map",
                "move": "#/$defs/Move",
                "remove": "#/$defs/Remove",
                "retype": "#/$defs/Retype",
                "set": "#/$defs/Set",
                "warn": "#/$defs/Warn"
              },
              "propertyName": "op"
            },
            "oneOf": [
              {
                "$ref": "#/$defs/Retype"
              },
              {
                "$ref": "#/$defs/Set"
              },
              {
                "$ref": "#/$defs/Drop"
              },
              {
                "$ref": "#/$defs/Move"
              },
              {
                "$ref": "#/$defs/Map"
              },
              {
                "$ref": "#/$defs/Fold"
              },
              {
                "$ref": "#/$defs/Warn"
              },
              {
                "$ref": "#/$defs/Remove"
              }
            ]
          },
          "title": "Then",
          "type": "array"
        },
        "warn": {
          "anyOf": [
            {
              "enum": [
                "ATTRIBUTE_DROPPED",
                "OBJECT_NOT_SUPPORTED",
                "VALUE_NOT_MAPPED",
                "OBJECT_DROPPED",
                "RP_PLAN_NOT_SUPPORTED",
                "MANUAL_CHECK_REQUIRED",
                "LP_OBJECT_NOT_TRANSFORMABLE",
                "PROPERTY_NOT_TRANSFORMABLE",
                "INVALID_GEOMETRY",
                "MISSING_DATES",
                "MISSING_REQUIRED_ATTRIBUTE",
                "NO_ATTRIBUTE_OR_MATCHING_CODE_FOUND",
                "FEATURE_NOT_MAPPABLE",
                "BEREICH_SKIPPED_PARENT_PLAN",
                "FEATURE_SKIPPED_NO_MAPPING",
                "ART_MAPPING_REMOVED",
                "MULTIPLE_DIENTZURDARSTELLUNG_REFERENCE",
                "REPLACE_ID",
                "JSONFG_SRID_DETECTION_FALLBACK"
              ],
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": "ATTRIBUT_ÜBERSPRUNGEN"
        },
        "message": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Message"
        },
        "attrs": {
          "items": {
            "type": "string"
          },
          "minItems": 1,
          "title": "Attrs",
          "type": "array"
        }
      },
      "required": [
        "op",
        "attrs"
      ],
      "title": "Drop",
      "type": "object"
    },
    "Fold": {
      "additionalProperties": false,
      "description": "Collect several attributes into the nested object(s) the target version introduced.\n\nA fold builds as many objects as its **longest** source has entries; a scalar source\ncounts as one. Each source contributes its entry `i` to object `i`, a **scalar** source\ncontributes to object `[0]` alone, and a source that runs out leaves the field unset.\nWhere 5.x kept a code list and a parallel detail list side by side, the target pairs each\ncode with its detail in one value, and pairing them by position is what those two lists\nalways meant - a shared scalar beside them, a roof pitch over several roof shapes, is\nthe same operation with one source that has nothing to pair past the first.\n\nHow many objects there are is therefore the data's answer, and whether `to` holds a list\nof them is the target's, filled in by the loader. A field inside an object is written as\nthe value itself; the engine wraps it where the target declares the field 0..*.\nReports `to` when it built something.",
      "properties": {
        "op": {
          "const": "fold",
          "title": "Op",
          "type": "string"
        },
        "comment": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "A note for whoever reads the rule file. Never read when the rule runs.",
          "title": "Comment"
        },
        "when": {
          "anyOf": [
            {
              "$ref": "#/$defs/When"
            },
            {
              "items": {
                "$ref": "#/$defs/When"
              },
              "type": "array"
            }
          ],
          "description": "Guards, ANDed. A single clause may be written without the surrounding list.",
          "title": "When"
        },
        "then": {
          "description": "Operations that depend on this one, and run under the same guard.\n\nThe alternative is for each of them to test what this operation wrote, which is how a\nrule used to say \"if the retype above fired\" - by reading `featuretype` as though it\nwere an attribute, once per operation. Sharing the guard also evaluates it **once**,\nbefore this operation runs: one rule guards three operations on a value the third of\nthem overwrites, and was only correct because that one came last.",
          "items": {
            "discriminator": {
              "mapping": {
                "drop": "#/$defs/Drop",
                "fold": "#/$defs/Fold",
                "map": "#/$defs/Map",
                "move": "#/$defs/Move",
                "remove": "#/$defs/Remove",
                "retype": "#/$defs/Retype",
                "set": "#/$defs/Set",
                "warn": "#/$defs/Warn"
              },
              "propertyName": "op"
            },
            "oneOf": [
              {
                "$ref": "#/$defs/Retype"
              },
              {
                "$ref": "#/$defs/Set"
              },
              {
                "$ref": "#/$defs/Drop"
              },
              {
                "$ref": "#/$defs/Move"
              },
              {
                "$ref": "#/$defs/Map"
              },
              {
                "$ref": "#/$defs/Fold"
              },
              {
                "$ref": "#/$defs/Warn"
              },
              {
                "$ref": "#/$defs/Remove"
              }
            ]
          },
          "title": "Then",
          "type": "array"
        },
        "warn": {
          "anyOf": [
            {
              "enum": [
                "ATTRIBUTE_DROPPED",
                "OBJECT_NOT_SUPPORTED",
                "VALUE_NOT_MAPPED",
                "OBJECT_DROPPED",
                "RP_PLAN_NOT_SUPPORTED",
                "MANUAL_CHECK_REQUIRED",
                "LP_OBJECT_NOT_TRANSFORMABLE",
                "PROPERTY_NOT_TRANSFORMABLE",
                "INVALID_GEOMETRY",
                "MISSING_DATES",
                "MISSING_REQUIRED_ATTRIBUTE",
                "NO_ATTRIBUTE_OR_MATCHING_CODE_FOUND",
                "FEATURE_NOT_MAPPABLE",
                "BEREICH_SKIPPED_PARENT_PLAN",
                "FEATURE_SKIPPED_NO_MAPPING",
                "ART_MAPPING_REMOVED",
                "MULTIPLE_DIENTZURDARSTELLUNG_REFERENCE",
                "REPLACE_ID",
                "JSONFG_SRID_DETECTION_FALLBACK"
              ],
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Reported once for each attribute the operation says it acted on; see each operation.\n\nOne field on every operation rather than a pair per occasion: what an operation reports\nabout is what it *did*, so a guard that has to predict it - and got it wrong, reporting a\nvalue that was never there - is not needed."
        },
        "message": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Message"
        },
        "to": {
          "title": "To",
          "type": "string"
        },
        "attrs": {
          "additionalProperties": {
            "type": "string"
          },
          "description": "Source attribute -> its field name inside the object. Two sources may name the same\nfield, whose stream is then their values in the order they are written.",
          "minProperties": 1,
          "title": "Attrs",
          "type": "object"
        }
      },
      "required": [
        "op",
        "to",
        "attrs"
      ],
      "title": "Fold",
      "type": "object"
    },
    "Map": {
      "additionalProperties": false,
      "description": "Rewrite one attribute's codes through a table, in place or into another attribute.\n\nDistinct from a hop's rule for an enumeration only in reach: that rewrites every value of\nthe enumeration wherever it appears, this one attribute of one rule.",
      "properties": {
        "op": {
          "const": "map",
          "title": "Op",
          "type": "string"
        },
        "comment": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "A note for whoever reads the rule file. Never read when the rule runs.",
          "title": "Comment"
        },
        "when": {
          "anyOf": [
            {
              "$ref": "#/$defs/When"
            },
            {
              "items": {
                "$ref": "#/$defs/When"
              },
              "type": "array"
            }
          ],
          "description": "Guards, ANDed. A single clause may be written without the surrounding list.",
          "title": "When"
        },
        "then": {
          "description": "Operations that depend on this one, and run under the same guard.\n\nThe alternative is for each of them to test what this operation wrote, which is how a\nrule used to say \"if the retype above fired\" - by reading `featuretype` as though it\nwere an attribute, once per operation. Sharing the guard also evaluates it **once**,\nbefore this operation runs: one rule guards three operations on a value the third of\nthem overwrites, and was only correct because that one came last.",
          "items": {
            "discriminator": {
              "mapping": {
                "drop": "#/$defs/Drop",
                "fold": "#/$defs/Fold",
                "map": "#/$defs/Map",
                "move": "#/$defs/Move",
                "remove": "#/$defs/Remove",
                "retype": "#/$defs/Retype",
                "set": "#/$defs/Set",
                "warn": "#/$defs/Warn"
              },
              "propertyName": "op"
            },
            "oneOf": [
              {
                "$ref": "#/$defs/Retype"
              },
              {
                "$ref": "#/$defs/Set"
              },
              {
                "$ref": "#/$defs/Drop"
              },
              {
                "$ref": "#/$defs/Move"
              },
              {
                "$ref": "#/$defs/Map"
              },
              {
                "$ref": "#/$defs/Fold"
              },
              {
                "$ref": "#/$defs/Warn"
              },
              {
                "$ref": "#/$defs/Remove"
              }
            ]
          },
          "title": "Then",
          "type": "array"
        },
        "warn": {
          "anyOf": [
            {
              "enum": [
                "ATTRIBUTE_DROPPED",
                "OBJECT_NOT_SUPPORTED",
                "VALUE_NOT_MAPPED",
                "OBJECT_DROPPED",
                "RP_PLAN_NOT_SUPPORTED",
                "MANUAL_CHECK_REQUIRED",
                "LP_OBJECT_NOT_TRANSFORMABLE",
                "PROPERTY_NOT_TRANSFORMABLE",
                "INVALID_GEOMETRY",
                "MISSING_DATES",
                "MISSING_REQUIRED_ATTRIBUTE",
                "NO_ATTRIBUTE_OR_MATCHING_CODE_FOUND",
                "FEATURE_NOT_MAPPABLE",
                "BEREICH_SKIPPED_PARENT_PLAN",
                "FEATURE_SKIPPED_NO_MAPPING",
                "ART_MAPPING_REMOVED",
                "MULTIPLE_DIENTZURDARSTELLUNG_REFERENCE",
                "REPLACE_ID",
                "JSONFG_SRID_DETECTION_FALLBACK"
              ],
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Reported once for each attribute the operation says it acted on; see each operation.\n\nOne field on every operation rather than a pair per occasion: what an operation reports\nabout is what it *did*, so a guard that has to predict it - and got it wrong, reporting a\nvalue that was never there - is not needed."
        },
        "message": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Message"
        },
        "map": {
          "additionalProperties": true,
          "title": "Map",
          "type": "object"
        },
        "unmapped": {
          "default": "keep",
          "enum": [
            "keep",
            "drop"
          ],
          "title": "Unmapped",
          "type": "string"
        },
        "from": {
          "title": "From",
          "type": "string"
        },
        "to": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "To"
        },
        "paired": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "A parallel detail list whose entry at a code's index goes with it.\n\nA code replaced by nothing takes its detail with it. The loader refuses a code replaced\nby several, since it has no single position left to pair with.",
          "title": "Paired"
        }
      },
      "required": [
        "op",
        "map",
        "from"
      ],
      "title": "Map",
      "type": "object"
    },
    "Move": {
      "additionalProperties": false,
      "description": "Move values from one or more attributes to another.\n\nWhat the move does with them is the target's answer, not the rule's: into a **list**\nthey are appended after whatever `to` already holds, and into a **single** value the\nfirst one wins and the rest are lost. That one distinction is what `rename`, `merge` and\n`take_first` were three operations for. Reports the source whose entries were lost.",
      "properties": {
        "op": {
          "const": "move",
          "title": "Op",
          "type": "string"
        },
        "comment": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "A note for whoever reads the rule file. Never read when the rule runs.",
          "title": "Comment"
        },
        "when": {
          "anyOf": [
            {
              "$ref": "#/$defs/When"
            },
            {
              "items": {
                "$ref": "#/$defs/When"
              },
              "type": "array"
            }
          ],
          "description": "Guards, ANDed. A single clause may be written without the surrounding list.",
          "title": "When"
        },
        "then": {
          "description": "Operations that depend on this one, and run under the same guard.\n\nThe alternative is for each of them to test what this operation wrote, which is how a\nrule used to say \"if the retype above fired\" - by reading `featuretype` as though it\nwere an attribute, once per operation. Sharing the guard also evaluates it **once**,\nbefore this operation runs: one rule guards three operations on a value the third of\nthem overwrites, and was only correct because that one came last.",
          "items": {
            "discriminator": {
              "mapping": {
                "drop": "#/$defs/Drop",
                "fold": "#/$defs/Fold",
                "map": "#/$defs/Map",
                "move": "#/$defs/Move",
                "remove": "#/$defs/Remove",
                "retype": "#/$defs/Retype",
                "set": "#/$defs/Set",
                "warn": "#/$defs/Warn"
              },
              "propertyName": "op"
            },
            "oneOf": [
              {
                "$ref": "#/$defs/Retype"
              },
              {
                "$ref": "#/$defs/Set"
              },
              {
                "$ref": "#/$defs/Drop"
              },
              {
                "$ref": "#/$defs/Move"
              },
              {
                "$ref": "#/$defs/Map"
              },
              {
                "$ref": "#/$defs/Fold"
              },
              {
                "$ref": "#/$defs/Warn"
              },
              {
                "$ref": "#/$defs/Remove"
              }
            ]
          },
          "title": "Then",
          "type": "array"
        },
        "warn": {
          "anyOf": [
            {
              "enum": [
                "ATTRIBUTE_DROPPED",
                "OBJECT_NOT_SUPPORTED",
                "VALUE_NOT_MAPPED",
                "OBJECT_DROPPED",
                "RP_PLAN_NOT_SUPPORTED",
                "MANUAL_CHECK_REQUIRED",
                "LP_OBJECT_NOT_TRANSFORMABLE",
                "PROPERTY_NOT_TRANSFORMABLE",
                "INVALID_GEOMETRY",
                "MISSING_DATES",
                "MISSING_REQUIRED_ATTRIBUTE",
                "NO_ATTRIBUTE_OR_MATCHING_CODE_FOUND",
                "FEATURE_NOT_MAPPABLE",
                "BEREICH_SKIPPED_PARENT_PLAN",
                "FEATURE_SKIPPED_NO_MAPPING",
                "ART_MAPPING_REMOVED",
                "MULTIPLE_DIENTZURDARSTELLUNG_REFERENCE",
                "REPLACE_ID",
                "JSONFG_SRID_DETECTION_FALLBACK"
              ],
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Reported once for each attribute the operation says it acted on; see each operation.\n\nOne field on every operation rather than a pair per occasion: what an operation reports\nabout is what it *did*, so a guard that has to predict it - and got it wrong, reporting a\nvalue that was never there - is not needed."
        },
        "message": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Message"
        },
        "from": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            {
              "additionalProperties": true,
              "type": "object"
            }
          ],
          "minLength": 1,
          "title": "From"
        },
        "to": {
          "title": "To",
          "type": "string"
        },
        "set": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "The field every moved value carries, with its value read from the `from:` table.\n\nWritten with `item[field]`, because what a `set:` moves is a **data type** -\n`XP_ExterneReferenz` - and the engine dumps every data-typed property to a mapping before\nany rule runs. That is how attributes that each named one kind of document become one\nreference list tagged by `typ`.",
          "title": "Set"
        },
        "if_unset": {
          "default": false,
          "description": "Leave an occupied `to` alone, and the source where it is.\n\nThe source is not consumed, so a `drop` after the move reports the value that did not\nmove - which is what a version that folded two attributes into one and kept the more\nspecific of them needs said.",
          "title": "If Unset",
          "type": "boolean"
        }
      },
      "required": [
        "op",
        "from",
        "to"
      ],
      "title": "Move",
      "type": "object"
    },
    "Pull": {
      "additionalProperties": false,
      "description": "Copy something from the features `via` reaches back onto the feature that owns it.\n\n`from: id` copies the reached feature's own id, which is how a role pointing at it is\nfilled in. An `attrs:` group is filled as one: only when the feature holds none of it,\nand all of it from the same reached feature.",
      "properties": {
        "op": {
          "const": "pull",
          "title": "Op",
          "type": "string"
        },
        "comment": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "A note for whoever reads the rule file. Never read when the operation runs.",
          "title": "Comment"
        },
        "each": {
          "description": "Start from features of this class, subclasses included.\n\nNot spelled `on`: YAML 1.1 reads a bare `on` as the boolean true, so it would never\narrive as a key at all.",
          "title": "Each",
          "type": "string"
        },
        "via": {
          "description": "Association roles to follow, in order. A chain, not a choice - two roles that get the\nsame treatment are two operations, because `[a, b]` cannot mean both and be readable.",
          "items": {
            "type": "string"
          },
          "minItems": 1,
          "title": "Via",
          "type": "array"
        },
        "when": {
          "anyOf": [
            {
              "$ref": "#/$defs/When"
            },
            {
              "items": {
                "$ref": "#/$defs/When"
              },
              "type": "array"
            }
          ],
          "description": "Guards on the feature the roles reached, ANDed.",
          "title": "When"
        },
        "from": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "From"
        },
        "to": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "To"
        },
        "attrs": {
          "anyOf": [
            {
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Shorthand for several attributes that keep their name on both sides.",
          "title": "Attrs"
        },
        "with": {
          "additionalProperties": true,
          "description": "Fixed fields added to each copied data type, rebuilt as the target role's type.",
          "title": "With",
          "type": "object"
        }
      },
      "required": [
        "op",
        "each",
        "via"
      ],
      "title": "Pull",
      "type": "object"
    },
    "Push": {
      "additionalProperties": false,
      "description": "Write onto the features `via` reaches, from the feature that owns the reference.",
      "properties": {
        "op": {
          "const": "push",
          "title": "Op",
          "type": "string"
        },
        "comment": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "A note for whoever reads the rule file. Never read when the operation runs.",
          "title": "Comment"
        },
        "each": {
          "description": "Start from features of this class, subclasses included.\n\nNot spelled `on`: YAML 1.1 reads a bare `on` as the boolean true, so it would never\narrive as a key at all.",
          "title": "Each",
          "type": "string"
        },
        "via": {
          "description": "Association roles to follow, in order. A chain, not a choice - two roles that get the\nsame treatment are two operations, because `[a, b]` cannot mean both and be readable.",
          "items": {
            "type": "string"
          },
          "minItems": 1,
          "title": "Via",
          "type": "array"
        },
        "when": {
          "anyOf": [
            {
              "$ref": "#/$defs/When"
            },
            {
              "items": {
                "$ref": "#/$defs/When"
              },
              "type": "array"
            }
          ],
          "description": "Guards on the feature the roles reached, ANDed.",
          "title": "When"
        },
        "to": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "The role to write the owning feature's id to, which is how an inverse role is filled in.",
          "title": "To"
        },
        "set": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Fixed values, for a role whose meaning moved onto the referenced feature.",
          "title": "Set"
        }
      },
      "required": [
        "op",
        "each",
        "via"
      ],
      "title": "Push",
      "type": "object"
    },
    "Remove": {
      "additionalProperties": false,
      "description": "Drop the whole feature: the target version has no equivalent.\n\nA requirement the target places on a value is a guard on this: a geometry of a type the\ntarget class has no place for is `when: {attr: position, geom: [...], not: true}`. A\nrequirement that only *reports* is not an operation at all - an attribute the target\ndeclares 1..1 already fails validation, which says so.",
      "properties": {
        "op": {
          "const": "remove",
          "title": "Op",
          "type": "string"
        },
        "comment": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "A note for whoever reads the rule file. Never read when the rule runs.",
          "title": "Comment"
        },
        "when": {
          "anyOf": [
            {
              "$ref": "#/$defs/When"
            },
            {
              "items": {
                "$ref": "#/$defs/When"
              },
              "type": "array"
            }
          ],
          "description": "Guards, ANDed. A single clause may be written without the surrounding list.",
          "title": "When"
        },
        "then": {
          "description": "Operations that depend on this one, and run under the same guard.\n\nThe alternative is for each of them to test what this operation wrote, which is how a\nrule used to say \"if the retype above fired\" - by reading `featuretype` as though it\nwere an attribute, once per operation. Sharing the guard also evaluates it **once**,\nbefore this operation runs: one rule guards three operations on a value the third of\nthem overwrites, and was only correct because that one came last.",
          "items": {
            "discriminator": {
              "mapping": {
                "drop": "#/$defs/Drop",
                "fold": "#/$defs/Fold",
                "map": "#/$defs/Map",
                "move": "#/$defs/Move",
                "remove": "#/$defs/Remove",
                "retype": "#/$defs/Retype",
                "set": "#/$defs/Set",
                "warn": "#/$defs/Warn"
              },
              "propertyName": "op"
            },
            "oneOf": [
              {
                "$ref": "#/$defs/Retype"
              },
              {
                "$ref": "#/$defs/Set"
              },
              {
                "$ref": "#/$defs/Drop"
              },
              {
                "$ref": "#/$defs/Move"
              },
              {
                "$ref": "#/$defs/Map"
              },
              {
                "$ref": "#/$defs/Fold"
              },
              {
                "$ref": "#/$defs/Warn"
              },
              {
                "$ref": "#/$defs/Remove"
              }
            ]
          },
          "title": "Then",
          "type": "array"
        },
        "warn": {
          "anyOf": [
            {
              "enum": [
                "ATTRIBUTE_DROPPED",
                "OBJECT_NOT_SUPPORTED",
                "VALUE_NOT_MAPPED",
                "OBJECT_DROPPED",
                "RP_PLAN_NOT_SUPPORTED",
                "MANUAL_CHECK_REQUIRED",
                "LP_OBJECT_NOT_TRANSFORMABLE",
                "PROPERTY_NOT_TRANSFORMABLE",
                "INVALID_GEOMETRY",
                "MISSING_DATES",
                "MISSING_REQUIRED_ATTRIBUTE",
                "NO_ATTRIBUTE_OR_MATCHING_CODE_FOUND",
                "FEATURE_NOT_MAPPABLE",
                "BEREICH_SKIPPED_PARENT_PLAN",
                "FEATURE_SKIPPED_NO_MAPPING",
                "ART_MAPPING_REMOVED",
                "MULTIPLE_DIENTZURDARSTELLUNG_REFERENCE",
                "REPLACE_ID",
                "JSONFG_SRID_DETECTION_FALLBACK"
              ],
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": "OBJEKT_ÜBERSPRUNGEN",
          "description": "`null` removes the feature without reporting, for one that is carried by another."
        },
        "message": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Message"
        },
        "property": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "The attribute that is the cause, checked at load like every other name a rule reads.\nLeft out when the class itself is the cause, and the report then names the feature\ntype.",
          "title": "Property"
        },
        "abort": {
          "default": false,
          "description": "Stop the whole migration: the document holds something with no mapping at all.",
          "title": "Abort",
          "type": "boolean"
        }
      },
      "required": [
        "op"
      ],
      "title": "Remove",
      "type": "object"
    },
    "Retype": {
      "additionalProperties": false,
      "description": "Write the feature out as a different feature type, or one per plan type.",
      "properties": {
        "op": {
          "const": "retype",
          "title": "Op",
          "type": "string"
        },
        "comment": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "A note for whoever reads the rule file. Never read when the rule runs.",
          "title": "Comment"
        },
        "when": {
          "anyOf": [
            {
              "$ref": "#/$defs/When"
            },
            {
              "items": {
                "$ref": "#/$defs/When"
              },
              "type": "array"
            }
          ],
          "description": "Guards, ANDed. A single clause may be written without the surrounding list.",
          "title": "When"
        },
        "then": {
          "description": "Operations that depend on this one, and run under the same guard.\n\nThe alternative is for each of them to test what this operation wrote, which is how a\nrule used to say \"if the retype above fired\" - by reading `featuretype` as though it\nwere an attribute, once per operation. Sharing the guard also evaluates it **once**,\nbefore this operation runs: one rule guards three operations on a value the third of\nthem overwrites, and was only correct because that one came last.",
          "items": {
            "discriminator": {
              "mapping": {
                "drop": "#/$defs/Drop",
                "fold": "#/$defs/Fold",
                "map": "#/$defs/Map",
                "move": "#/$defs/Move",
                "remove": "#/$defs/Remove",
                "retype": "#/$defs/Retype",
                "set": "#/$defs/Set",
                "warn": "#/$defs/Warn"
              },
              "propertyName": "op"
            },
            "oneOf": [
              {
                "$ref": "#/$defs/Retype"
              },
              {
                "$ref": "#/$defs/Set"
              },
              {
                "$ref": "#/$defs/Drop"
              },
              {
                "$ref": "#/$defs/Move"
              },
              {
                "$ref": "#/$defs/Map"
              },
              {
                "$ref": "#/$defs/Fold"
              },
              {
                "$ref": "#/$defs/Warn"
              },
              {
                "$ref": "#/$defs/Remove"
              }
            ]
          },
          "title": "Then",
          "type": "array"
        },
        "warn": {
          "anyOf": [
            {
              "enum": [
                "ATTRIBUTE_DROPPED",
                "OBJECT_NOT_SUPPORTED",
                "VALUE_NOT_MAPPED",
                "OBJECT_DROPPED",
                "RP_PLAN_NOT_SUPPORTED",
                "MANUAL_CHECK_REQUIRED",
                "LP_OBJECT_NOT_TRANSFORMABLE",
                "PROPERTY_NOT_TRANSFORMABLE",
                "INVALID_GEOMETRY",
                "MISSING_DATES",
                "MISSING_REQUIRED_ATTRIBUTE",
                "NO_ATTRIBUTE_OR_MATCHING_CODE_FOUND",
                "FEATURE_NOT_MAPPABLE",
                "BEREICH_SKIPPED_PARENT_PLAN",
                "FEATURE_SKIPPED_NO_MAPPING",
                "ART_MAPPING_REMOVED",
                "MULTIPLE_DIENTZURDARSTELLUNG_REFERENCE",
                "REPLACE_ID",
                "JSONFG_SRID_DETECTION_FALLBACK"
              ],
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Reported once for each attribute the operation says it acted on; see each operation.\n\nOne field on every operation rather than a pair per occasion: what an operation reports\nabout is what it *did*, so a guard that has to predict it - and got it wrong, reporting a\nvalue that was never there - is not needed."
        },
        "message": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Message"
        },
        "to": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "To"
        },
        "by_plan": {
          "anyOf": [
            {
              "additionalProperties": {
                "type": "string"
              },
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "A target per plan type (``BP``, ``FP``, ...), for a class the target split by plan.",
          "title": "By Plan"
        }
      },
      "required": [
        "op"
      ],
      "title": "Retype",
      "type": "object"
    },
    "Set": {
      "additionalProperties": false,
      "description": "Set an attribute: to a fixed value, another attribute's value, or a value per plan type.\n\nReports the attribute it set, for a value that is a guess a human should confirm.",
      "properties": {
        "op": {
          "const": "set",
          "title": "Op",
          "type": "string"
        },
        "comment": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "A note for whoever reads the rule file. Never read when the rule runs.",
          "title": "Comment"
        },
        "when": {
          "anyOf": [
            {
              "$ref": "#/$defs/When"
            },
            {
              "items": {
                "$ref": "#/$defs/When"
              },
              "type": "array"
            }
          ],
          "description": "Guards, ANDed. A single clause may be written without the surrounding list.",
          "title": "When"
        },
        "then": {
          "description": "Operations that depend on this one, and run under the same guard.\n\nThe alternative is for each of them to test what this operation wrote, which is how a\nrule used to say \"if the retype above fired\" - by reading `featuretype` as though it\nwere an attribute, once per operation. Sharing the guard also evaluates it **once**,\nbefore this operation runs: one rule guards three operations on a value the third of\nthem overwrites, and was only correct because that one came last.",
          "items": {
            "discriminator": {
              "mapping": {
                "drop": "#/$defs/Drop",
                "fold": "#/$defs/Fold",
                "map": "#/$defs/Map",
                "move": "#/$defs/Move",
                "remove": "#/$defs/Remove",
                "retype": "#/$defs/Retype",
                "set": "#/$defs/Set",
                "warn": "#/$defs/Warn"
              },
              "propertyName": "op"
            },
            "oneOf": [
              {
                "$ref": "#/$defs/Retype"
              },
              {
                "$ref": "#/$defs/Set"
              },
              {
                "$ref": "#/$defs/Drop"
              },
              {
                "$ref": "#/$defs/Move"
              },
              {
                "$ref": "#/$defs/Map"
              },
              {
                "$ref": "#/$defs/Fold"
              },
              {
                "$ref": "#/$defs/Warn"
              },
              {
                "$ref": "#/$defs/Remove"
              }
            ]
          },
          "title": "Then",
          "type": "array"
        },
        "warn": {
          "anyOf": [
            {
              "enum": [
                "ATTRIBUTE_DROPPED",
                "OBJECT_NOT_SUPPORTED",
                "VALUE_NOT_MAPPED",
                "OBJECT_DROPPED",
                "RP_PLAN_NOT_SUPPORTED",
                "MANUAL_CHECK_REQUIRED",
                "LP_OBJECT_NOT_TRANSFORMABLE",
                "PROPERTY_NOT_TRANSFORMABLE",
                "INVALID_GEOMETRY",
                "MISSING_DATES",
                "MISSING_REQUIRED_ATTRIBUTE",
                "NO_ATTRIBUTE_OR_MATCHING_CODE_FOUND",
                "FEATURE_NOT_MAPPABLE",
                "BEREICH_SKIPPED_PARENT_PLAN",
                "FEATURE_SKIPPED_NO_MAPPING",
                "ART_MAPPING_REMOVED",
                "MULTIPLE_DIENTZURDARSTELLUNG_REFERENCE",
                "REPLACE_ID",
                "JSONFG_SRID_DETECTION_FALLBACK"
              ],
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Reported once for each attribute the operation says it acted on; see each operation.\n\nOne field on every operation rather than a pair per occasion: what an operation reports\nabout is what it *did*, so a guard that has to predict it - and got it wrong, reporting a\nvalue that was never there - is not needed."
        },
        "message": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Message"
        },
        "attr": {
          "title": "Attr",
          "type": "string"
        },
        "value": {
          "default": null,
          "title": "Value"
        },
        "from": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Copy this attribute's value, falling back to `value` when it is absent.",
          "title": "From"
        },
        "by_plan": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "A value per plan type (``BP``, ``FP``, ...), for the few that differ by plan.",
          "title": "By Plan"
        },
        "if_unset": {
          "default": false,
          "description": "Leave an attribute that already holds a value alone.",
          "title": "If Unset",
          "type": "boolean"
        }
      },
      "required": [
        "op",
        "attr"
      ],
      "title": "Set",
      "type": "object"
    },
    "Warn": {
      "additionalProperties": false,
      "description": "Report something about the feature without changing it.",
      "properties": {
        "op": {
          "const": "warn",
          "title": "Op",
          "type": "string"
        },
        "comment": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "A note for whoever reads the rule file. Never read when the rule runs.",
          "title": "Comment"
        },
        "when": {
          "anyOf": [
            {
              "$ref": "#/$defs/When"
            },
            {
              "items": {
                "$ref": "#/$defs/When"
              },
              "type": "array"
            }
          ],
          "description": "Guards, ANDed. A single clause may be written without the surrounding list.",
          "title": "When"
        },
        "then": {
          "description": "Operations that depend on this one, and run under the same guard.\n\nThe alternative is for each of them to test what this operation wrote, which is how a\nrule used to say \"if the retype above fired\" - by reading `featuretype` as though it\nwere an attribute, once per operation. Sharing the guard also evaluates it **once**,\nbefore this operation runs: one rule guards three operations on a value the third of\nthem overwrites, and was only correct because that one came last.",
          "items": {
            "discriminator": {
              "mapping": {
                "drop": "#/$defs/Drop",
                "fold": "#/$defs/Fold",
                "map": "#/$defs/Map",
                "move": "#/$defs/Move",
                "remove": "#/$defs/Remove",
                "retype": "#/$defs/Retype",
                "set": "#/$defs/Set",
                "warn": "#/$defs/Warn"
              },
              "propertyName": "op"
            },
            "oneOf": [
              {
                "$ref": "#/$defs/Retype"
              },
              {
                "$ref": "#/$defs/Set"
              },
              {
                "$ref": "#/$defs/Drop"
              },
              {
                "$ref": "#/$defs/Move"
              },
              {
                "$ref": "#/$defs/Map"
              },
              {
                "$ref": "#/$defs/Fold"
              },
              {
                "$ref": "#/$defs/Warn"
              },
              {
                "$ref": "#/$defs/Remove"
              }
            ]
          },
          "title": "Then",
          "type": "array"
        },
        "warn": {
          "anyOf": [
            {
              "enum": [
                "ATTRIBUTE_DROPPED",
                "OBJECT_NOT_SUPPORTED",
                "VALUE_NOT_MAPPED",
                "OBJECT_DROPPED",
                "RP_PLAN_NOT_SUPPORTED",
                "MANUAL_CHECK_REQUIRED",
                "LP_OBJECT_NOT_TRANSFORMABLE",
                "PROPERTY_NOT_TRANSFORMABLE",
                "INVALID_GEOMETRY",
                "MISSING_DATES",
                "MISSING_REQUIRED_ATTRIBUTE",
                "NO_ATTRIBUTE_OR_MATCHING_CODE_FOUND",
                "FEATURE_NOT_MAPPABLE",
                "BEREICH_SKIPPED_PARENT_PLAN",
                "FEATURE_SKIPPED_NO_MAPPING",
                "ART_MAPPING_REMOVED",
                "MULTIPLE_DIENTZURDARSTELLUNG_REFERENCE",
                "REPLACE_ID",
                "JSONFG_SRID_DETECTION_FALLBACK"
              ],
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": "MANUELLE_ÜBERPRÜFUNG_ERFORDERLICH"
        },
        "message": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Message"
        },
        "property": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Property"
        }
      },
      "required": [
        "op"
      ],
      "title": "Warn",
      "type": "object"
    },
    "When": {
      "additionalProperties": false,
      "description": "A guard on a single operation: one predicate over one or more attributes.\n\nRoughly a seventh of the rules are one or two operations behind a condition, and this is\nwhat keeps them data. A clause names **one subject** - an attribute, a list of them, or a\nclass - and carries **at most one predicate** over it. Naming no predicate is the common\ncase and means the attribute has a value:\n\n    when: {attr: refText}                               # present\n    when: {attr: refText, not: true}                    # absent\n    when: {attr: art, in: [...], not: true}             # none of these values\n    when: {attr: position, geom: [XP_Liniengeometrie]}  # a value of this geometry type\n    when: {attrs: [a, b]}                               # both present\n    when: {attrs: [a, b], any: true}                    # at least one present\n    when: {attrs: [a, b], any: true, not: true}         # at least one absent\n\nPresence is `is not None`, so an attribute stated as an explicit `false` counts as\npresent - which is the whole reason it is not spelled as a value test.\n\n`not:` negates the predicate **per attribute** and `any:` swaps the quantifier, which is\nwhat lets six earlier keywords (`set`, `not_in`, `all_set`, `any_set`, `all_unset`,\n`any_unset`) collapse into two modifiers. An operation may carry a list of clauses, which\nare ANDed, and that is the whole of it: no `or`, no nesting. A conjunction is still a\ntable row; the moment a guard can be arbitrarily composed the rule file stops being\nsomething anyone can read.",
      "properties": {
        "attr": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Attr"
        },
        "attrs": {
          "anyOf": [
            {
              "items": {
                "type": "string"
              },
              "minItems": 1,
              "type": "array"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Attrs"
        },
        "is_a": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Class predicate; only a collection operation can evaluate it, checked at load.",
          "title": "Is A"
        },
        "is": {
          "default": null,
          "title": "Is"
        },
        "in": {
          "anyOf": [
            {
              "items": {},
              "type": "array"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "In"
        },
        "contains": {
          "default": null,
          "title": "Contains"
        },
        "geom": {
          "anyOf": [
            {
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Geometry types by their UML name (`XP_Flaechengeometrie`), as the source version's\nproperties declare them.\n\nNot the model classes: the generated models spell a geometry type out as the union it\nadmits (`Polygon | MultiPolygon`) and keep its name only as the property's typename.",
          "title": "Geom"
        },
        "not": {
          "default": false,
          "description": "Negate the predicate, for each attribute separately.",
          "title": "Not",
          "type": "boolean"
        },
        "any": {
          "default": false,
          "description": "Quantify `attrs` with `any` rather than `all`.",
          "title": "Any",
          "type": "boolean"
        }
      },
      "title": "When",
      "type": "object"
    },
    "_Migration": {
      "additionalProperties": false,
      "description": "One hop of the rule file, before it is checked against the appschemas.",
      "properties": {
        "from": {
          "title": "From",
          "type": "string"
        },
        "to": {
          "title": "To",
          "type": "string"
        },
        "comment": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "A note on the hop as a whole, for whoever reads the rule file.",
          "title": "Comment"
        },
        "rules": {
          "additionalProperties": {
            "oneOf": [
              {
                "items": {
                  "discriminator": {
                    "mapping": {
                      "drop": "#/$defs/Drop",
                      "fold": "#/$defs/Fold",
                      "map": "#/$defs/Map",
                      "move": "#/$defs/Move",
                      "remove": "#/$defs/Remove",
                      "retype": "#/$defs/Retype",
                      "set": "#/$defs/Set",
                      "warn": "#/$defs/Warn"
                    },
                    "propertyName": "op"
                  },
                  "oneOf": [
                    {
                      "$ref": "#/$defs/Retype"
                    },
                    {
                      "$ref": "#/$defs/Set"
                    },
                    {
                      "$ref": "#/$defs/Drop"
                    },
                    {
                      "$ref": "#/$defs/Move"
                    },
                    {
                      "$ref": "#/$defs/Map"
                    },
                    {
                      "$ref": "#/$defs/Fold"
                    },
                    {
                      "$ref": "#/$defs/Warn"
                    },
                    {
                      "$ref": "#/$defs/Remove"
                    }
                  ]
                },
                "type": "array"
              },
              {
                "$ref": "#/$defs/CodeTable"
              }
            ]
          },
          "description": "Keyed by class. A **feature type or data type** takes a list of operations - the\ndispatch walks the inheritance chain of either, and a rule on a data type reaches it\nwherever it is nested. An **enumeration** takes a code table.",
          "title": "Rules",
          "type": "object"
        },
        "pre": {
          "items": {
            "discriminator": {
              "mapping": {
                "pull": "#/$defs/Pull",
                "push": "#/$defs/Push"
              },
              "propertyName": "op"
            },
            "oneOf": [
              {
                "$ref": "#/$defs/Pull"
              },
              {
                "$ref": "#/$defs/Push"
              }
            ]
          },
          "title": "Pre",
          "type": "array"
        },
        "post": {
          "items": {
            "discriminator": {
              "mapping": {
                "pull": "#/$defs/Pull",
                "push": "#/$defs/Push"
              },
              "propertyName": "op"
            },
            "oneOf": [
              {
                "$ref": "#/$defs/Pull"
              },
              {
                "$ref": "#/$defs/Push"
              }
            ]
          },
          "title": "Post",
          "type": "array"
        }
      },
      "required": [
        "from",
        "to"
      ],
      "title": "Migration",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "The rule file: every hop, and the templates they share.\n\nDumped with `by_alias=True, exclude_unset=True`, it is the rule file's canonical form:\nfields in declaration order (`when` and `then` last), what was written and nothing else,\nrules and migrations sorted. `scripts/normalize_migration_rules.py` writes that back as YAML.",
  "properties": {
    "templates": {
      "additionalProperties": true,
      "description": "Only anchors to alias; what a template holds is checked wherever it is used.",
      "title": "Templates",
      "type": "object"
    },
    "migrations": {
      "items": {
        "$ref": "#/$defs/_Migration"
      },
      "title": "Migrations",
      "type": "array"
    }
  },
  "required": [
    "migrations"
  ],
  "title": "XPlanung migration rules",
  "type": "object"
}
