{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://apigenie.pl/schemas/apigenie-evolve-report.json",
  "title": "apigenie evolve plan --format json",
  "description": "The report of `apigenie evolve plan --format json` (experimental): the plan, the checks it passed, the server notes and the files written. Adding members keeps formatVersion; removing a member or changing its meaning or type increments it.",
  "type": "object",
  "required": ["formatVersion", "result", "plan", "checks", "serverNotes", "files"],
  "properties": {
    "formatVersion": {
      "description": "The version of this format.",
      "const": 1
    },
    "result": {
      "description": "`planned` when every breaking change was placed in a wave (exit status 0), `residue` when some could not be (exit status 1), `failed` when a check failed and nothing was written (exit status 2).",
      "enum": ["planned", "residue", "failed"]
    },
    "plan": { "$ref": "#/$defs/plan" },
    "checks": {
      "description": "The three checks, in order: the expand wave is non-breaking against the old document, the contract wave has only scheduled removals (and the changes the units and the residue own) against the expand wave, the contract wave equals the target.",
      "type": "array",
      "items": {
        "type": "object",
        "required": ["id", "status"],
        "properties": {
          "id": { "enum": ["expand-non-breaking", "contract-scheduled-only", "contract-equals-target"] },
          "status": { "enum": ["ok", "failed"] },
          "detail": { "type": "string" }
        }
      }
    },
    "serverNotes": {
      "description": "What the implementation has to do that the documents can't say: emit or accept both names, apply a default.",
      "type": "array",
      "items": {
        "type": "object",
        "required": ["unit", "note"],
        "properties": {
          "unit": { "type": "string" },
          "note": { "type": "string" }
        }
      }
    },
    "files": {
      "description": "The files written: the plan and the two wave documents; empty when a check failed.",
      "type": "array",
      "items": { "type": "string" }
    }
  },
  "$defs": {
    "plan": {
      "description": "The same object as plan.yaml.",
      "type": "object",
      "required": ["apigenie-evolve", "from", "to", "today", "window", "waves", "units", "residue"],
      "properties": {
        "apigenie-evolve": { "const": 1 },
        "from": { "$ref": "#/$defs/document" },
        "to": { "$ref": "#/$defs/document" },
        "today": { "type": "string", "format": "date" },
        "window": { "type": "string", "description": "As given: days (90d) or weeks (12w)." },
        "waves": {
          "type": "array",
          "minItems": 2,
          "maxItems": 2,
          "items": { "$ref": "#/$defs/wave" }
        },
        "units": {
          "type": "object",
          "additionalProperties": { "$ref": "#/$defs/unit" }
        },
        "residue": {
          "type": "array",
          "items": { "$ref": "#/$defs/residue" }
        },
        "renameCandidates": {
          "description": "Removed and added members of one object that were not paired as a rename; a hint decides.",
          "type": "array",
          "items": {
            "type": "object",
            "required": ["container", "removed", "added"],
            "properties": {
              "container": { "type": "string" },
              "removed": { "type": "array", "items": { "type": "string" } },
              "added": { "type": "array", "items": { "type": "string" } }
            }
          }
        }
      }
    },
    "document": {
      "type": "object",
      "required": ["path"],
      "properties": {
        "path": { "type": "string" },
        "version": { "type": "string", "description": "info.version; absent when empty." }
      }
    },
    "wave": {
      "type": "object",
      "required": ["id", "file", "units"],
      "properties": {
        "id": { "enum": ["expand", "contract"] },
        "file": { "type": "string" },
        "version": { "type": "string", "description": "The suggested version; absent when the input's version is not semantic." },
        "notBefore": { "type": "string", "format": "date", "description": "The contract wave: the day the window closes." },
        "units": { "type": "array", "items": { "type": "string" } }
      }
    },
    "unit": {
      "type": "object",
      "required": ["recipe", "from", "sunset"],
      "properties": {
        "recipe": {
          "enum": [
            "remove-operation", "move-path", "remove-response-property", "rename-response-property",
            "rename-request-property", "rename-request-parameter", "remove-request-parameter",
            "require-request-property", "require-request-parameter", "remove-request-enum-value"
          ]
        },
        "from": { "type": "string", "description": "`GET /path` for an operation, otherwise the location of the element." },
        "to": { "type": "string", "description": "The new element of a rename or a move." },
        "value": { "type": "string", "description": "The enum value of remove-request-enum-value." },
        "detected": { "type": "boolean", "description": "Renames: detected by the planner (true) or supplied by a hint (false)." },
        "sunset": { "type": "string", "format": "date" },
        "reason": { "type": "string" },
        "server": { "type": "string", "description": "The server note." },
        "sites": { "type": "array", "items": { "type": "string" }, "description": "Every location the differ reported the change at, when there are several." }
      }
    },
    "residue": {
      "type": "object",
      "required": ["id", "location", "change", "why"],
      "properties": {
        "id": { "type": "string" },
        "location": { "type": "string" },
        "change": { "type": "string" },
        "why": { "type": "string" }
      }
    }
  }
}
