{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://intentius.io/sql-yodeler/schemas/v1/plan.schema.json",
  "title": "yodel plan <env> --json",
  "description": "Version 1. A project with migrations prints the versioned plan (mode \"versioned\"); a project without prints the declarative plan, chant's diff with the environment and dialect it is of. Within a version, fields are only added; a reader ignores fields it does not know.",
  "oneOf": [
    {
      "$ref": "#/$defs/versioned"
    },
    {
      "$ref": "#/$defs/declarative"
    }
  ],
  "$defs": {
    "declarative": {
      "description": "chant's diff (chant sql plan --json) with environment, dialect and server added. chant owns the diff's fields: ones beyond those listed may appear.",
      "type": "object",
      "required": [
        "environment",
        "dialect",
        "changes",
        "hints"
      ],
      "properties": {
        "environment": {
          "type": "string"
        },
        "dialect": {
          "$ref": "common.schema.json#/$defs/dialect"
        },
        "server": {
          "$ref": "common.schema.json#/$defs/server"
        },
        "changes": {
          "type": "array",
          "items": {
            "$ref": "common.schema.json#/$defs/change"
          }
        },
        "hints": {
          "$ref": "common.schema.json#/$defs/stringList"
        },
        "rebuilds": {
          "description": "ClickHouse: the changes that need a ClickHouseRebuildOp.",
          "type": "array",
          "items": {
            "$ref": "common.schema.json#/$defs/change"
          }
        },
        "rebuildOps": {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        "refused": {
          "description": "Postgres: the changes that need a PostgresMigrationOp.",
          "type": "array",
          "items": {
            "$ref": "common.schema.json#/$defs/change"
          }
        },
        "migrationOps": {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        "access": {
          "description": "Whether access control is managed in the environment (environments.<env>.access in yodel.config.ts, else sql.profiles.<env>.access) and the plan's access changes: on Postgres policies, row-level security, roles and privileges; on ClickHouse users, roles, row policies and grants. byHand marks a revoke of a privilege no declaration grants: one granted by hand (or whose declaration was deleted), which the apply takes away. On ClickHouse every revoke is one.",
          "type": "object",
          "additionalProperties": false,
          "required": ["managed", "source", "changes"],
          "properties": {
            "managed": { "type": "boolean" },
            "source": { "type": "string" },
            "changes": {
              "type": "array",
              "items": {
                "type": "object",
                "additionalProperties": false,
                "required": ["object", "rule", "class", "field"],
                "properties": {
                  "object": { "type": "string" },
                  "rule": { "type": "string" },
                  "class": { "type": "string" },
                  "field": { "type": "string" },
                  "before": {},
                  "after": {},
                  "byHand": { "const": true }
                }
              }
            }
          }
        },
        "mode": false
      }
    },
    "stepChange": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "rule",
        "class",
        "field"
      ],
      "properties": {
        "rule": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "field": {
          "type": "string"
        },
        "destructive": {
          "const": true
        }
      }
    },
    "step": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "index",
        "kind",
        "object",
        "name",
        "done"
      ],
      "properties": {
        "index": {
          "type": "integer",
          "minimum": 0
        },
        "kind": {
          "enum": [
            "statement",
            "op",
            "manual"
          ]
        },
        "object": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "rule": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "destructive": {
          "const": true
        },
        "changes": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/stepChange"
          }
        },
        "sql": {
          "description": "A statement step's SQL, with passwords, secrets and tokens redacted.",
          "type": "string"
        },
        "detail": {
          "type": "string"
        },
        "op": {
          "type": "string"
        },
        "applyRuns": {
          "description": "An Op or manual step: whether yodel apply runs it.",
          "type": "boolean"
        },
        "refused": {
          "description": "Why yodel apply does not run this step.",
          "type": "string"
        },
        "done": {
          "description": "It succeeded in an earlier run and is not sent again.",
          "type": "boolean"
        }
      }
    },
    "moved": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "part",
          "message"
        ],
        "properties": {
          "part": {
            "enum": [
              "environment",
              "history",
              "pending",
              "live"
            ]
          },
          "message": {
            "type": "string"
          }
        }
      }
    },
    "versioned": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "environment",
        "dialect",
        "mode",
        "applied",
        "pending",
        "problems",
        "notes"
      ],
      "properties": {
        "environment": {
          "type": "string"
        },
        "dialect": {
          "$ref": "common.schema.json#/$defs/dialect"
        },
        "mode": {
          "const": "versioned"
        },
        "server": {
          "$ref": "common.schema.json#/$defs/server"
        },
        "history": {
          "description": "<database>.history, and on ClickHouse the topology and where it came from.",
          "type": "string"
        },
        "applied": {
          "description": "How many migrations the history records applied.",
          "type": "integer",
          "minimum": 0
        },
        "pending": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "outOfOrder",
              "steps"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "outOfOrder": {
                "type": "boolean"
              },
              "resumeAt": {
                "type": "integer"
              },
              "checks": {
                "type": "array",
                "items": {
                  "$ref": "common.schema.json#/$defs/check"
                }
              },
              "steps": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/step"
                }
              }
            }
          }
        },
        "problems": {
          "description": "Directory problems, checksum mismatches, out-of-order migrations.",
          "$ref": "common.schema.json#/$defs/stringList"
        },
        "refusal": {
          "$ref": "common.schema.json#/$defs/refusal"
        },
        "planDigest": {
          "description": "The digest the migrations Op's gate binds; absent when apply would refuse or has nothing to do.",
          "type": "string"
        },
        "policy": {
          "$ref": "common.schema.json#/$defs/policy"
        },
        "op": {
          "description": "The migrations Op.",
          "type": "string"
        },
        "gate": {
          "description": "The Op's gate.",
          "type": "string"
        },
        "approve": {
          "description": "The command a person runs to approve this plan. An agent prints it and never runs it.",
          "type": "string"
        },
        "chantApprove": { "description": "chant's own command for the same approval (chant approve <op> <gate> --plan <digest>), for scripts; a person runs approve.", "type": "string" },
        "approvalMode": {
          "description": "Which approvals of the migrations Op's gate count, as yodel.config.ts at the base commit has it, when not ledger: sealed counts only an approval sealed by a key the signers file at base lists for its approver.",
          "enum": ["sealed"]
        },
        "approval": {
          "description": "The newest approval of the gate in chant's ledger.",
          "type": "object",
          "additionalProperties": false,
          "required": [
            "by",
            "at",
            "holds",
            "sameDigest"
          ],
          "properties": {
            "digest": {
              "type": "string"
            },
            "by": {
              "type": "string"
            },
            "at": {
              "type": "string"
            },
            "holds": {
              "type": "boolean"
            },
            "sameDigest": {
              "type": "boolean"
            },
            "askedAgain": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "at"
              ],
              "properties": {
                "at": {
                  "type": "string"
                },
                "digest": {
                  "type": "string"
                }
              }
            },
            "moved": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "confirmed",
                "changes"
              ],
              "properties": {
                "confirmed": {
                  "type": "boolean"
                },
                "changes": {
                  "$ref": "#/$defs/moved"
                }
              }
            },
            "uncounted": {
              "description": "The gate's approval mode does not count it (under sealed, it is not sealed by a signer at the base commit), and why.",
              "type": "string"
            }
          }
        },
        "opMissing": {
          "description": "The project declares no migrations Op for the environment: the first line of how to declare one.",
          "type": "string"
        },
        "wave": {
          "description": "The environment's wave of the apply pipeline, when yodel.config.ts declares waves and names it.",
          "type": "object",
          "additionalProperties": false,
          "required": [
            "wave",
            "gate",
            "base",
            "baseSource",
            "destructiveSteps",
            "waits",
            "planDigest",
            "digest",
            "approve",
            "promotions",
            "blocked"
          ],
          "properties": {
            "wave": {
              "description": "1-based, in yodel.config.ts waves.",
              "type": "integer",
              "minimum": 1
            },
            "gate": {
              "description": "The gate policy at the base commit.",
              "enum": [
                "always",
                "on-destructive",
                "never"
              ]
            },
            "base": {
              "type": "string"
            },
            "baseSource": {
              "type": "string"
            },
            "destructiveSteps": {
              "type": "array",
              "items": {
                "$ref": "apply.schema.json#/$defs/destructiveStep"
              }
            },
            "waits": {
              "description": "Under that gate, the wave waits for an approval of this plan.",
              "type": "boolean"
            },
            "planDigest": {
              "description": "The wave plan digest.",
              "type": "string"
            },
            "digest": {
              "description": "The set digest the wave's gate binds.",
              "type": "string"
            },
            "approve": {
              "description": "The yodel approve command for the wave, for a person to run at a terminal.",
              "type": "string"
            },
            "chantApprove": { "description": "chant's own command for the same approval (chant approve <op> <gate> --plan <digest>), for scripts; a person runs approve.", "type": "string" },
            "approval": {
              "description": "Which approvals of the wave's digest count, as yodel.config.ts at the base commit has the wave, when not ledger.",
              "enum": ["pr-review", "sealed"]
            },
            "promotions": {
              "description": "Each environment this one requires (waves[].requires) and what its history records of each pending migration.",
              "type": "array",
              "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "env",
                  "from",
                  "migrations"
                ],
                "properties": {
                  "env": {
                    "type": "string"
                  },
                  "from": {
                    "description": "Where the requirement is declared: yodel.config.ts, at the base commit, or both.",
                    "type": "string"
                  },
                  "migrations": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "id",
                        "checksum",
                        "ran"
                      ],
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "checksum": {
                          "type": "string"
                        },
                        "ran": {
                          "enum": [
                            "applied",
                            "not applied",
                            "applied with another checksum"
                          ]
                        },
                        "recorded": {
                          "description": "The checksum the required environment recorded, when it differs.",
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            },
            "blocked": {
              "description": "A pending migration has not run where it must first: the wave refuses it until it has.",
              "type": "boolean"
            }
          }
        },
        "sinceLast": {
          "description": "The pull request comment this plan replaces showed another digest: what moved since.",
          "type": "object",
          "additionalProperties": false,
          "required": [
            "digest",
            "confirmed",
            "changes"
          ],
          "properties": {
            "digest": {
              "type": "string"
            },
            "confirmed": {
              "type": "boolean"
            },
            "changes": {
              "$ref": "#/$defs/moved"
            }
          }
        },
        "notes": {
          "$ref": "common.schema.json#/$defs/stringList"
        }
      }
    }
  }
}
