{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://raw.githubusercontent.com/BourbonDog/amicus/main/schemas/council-run.schema.json",
  "title": "amicus council-run manifest",
  "description": "Headless council run manifest (run.json, spec 2026-07-19 §4). Authored ahead of the engine (v4.0 Plan B) — the run-state writer must satisfy this schema.",
  "type": "object",
  "required": ["schemaVersion", "type", "runId", "status", "stages", "bench", "chair"],
  "properties": {
    "schemaVersion": { "const": 2 },
    "type": { "const": "council-run" },
    "runId": { "type": "string" },
    "status": { "type": "string" },
    "stages": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["name", "status"],
        "properties": {
          "name": { "type": "string" },
          "status": { "type": "string" },
          "startedAt": { "type": ["string", "null"] },
          "completedAt": { "type": ["string", "null"] },
          "waveId": { "type": "string" },
          "waveIds": { "type": "array", "items": { "type": "string" } },
          "taskIds": { "type": "array", "items": { "type": "string" } }
        }
      }
    },
    "bench": { "type": "array", "items": { "type": "string" } },
    "chair": { "type": "string" },
    "critic": { "type": ["string", "null"] },
    "lenses": { "type": ["array", "null"], "items": { "type": "string" } },
    "droppedMembers": {
      "description": "Council preset members dropped during resolution before this run started (v4.5 Wave 2): an alias that no longer resolves, or a resolved id absent from a non-empty cached model catalog (a catalog-unknown/offline member is never dropped this way). Present only when at least one member was actually dropped; absent — never an empty array — otherwise. `bench` above already reflects the survivors only; this is purely an additional observability signal so a scripted/MCP caller can detect a shrunken bench without diffing `bench` against the preset's nominal member list.",
      "type": "array",
      "items": {
        "type": "object",
        "required": ["member", "reason"],
        "properties": {
          "member": { "type": "string" },
          "reason": { "type": "string" }
        }
      }
    },
    "labelMap": { "type": ["object", "null"], "additionalProperties": { "type": "string" } },
    "seats": {
      "description": "v4.8: derived seat table, one entry per bench position. Bench-only — the synthesized 'claude' review is never a seat.",
      "type": ["array", "null"],
      "items": {
        "type": "object",
        "required": ["id", "alias", "role", "position"],
        "properties": {
          "id": { "type": "string" },
          "alias": { "type": "string" },
          "role": { "type": "string" },
          "lens": { "type": ["string", "null"] },
          "position": { "type": "integer" }
        }
      }
    },
    "criticSeat": {
      "description": "v4.8: seat id of the critic, or null.",
      "type": ["string", "null"]
    },
    "options": { "type": "object" },
    "pack": { "type": "object" },
    "tag": { "type": "string" },
    "template": { "type": "object" },
    "usage": { "type": "object" },
    "exitCode": { "type": ["number", "null"] },
    "budgetRefusals": {
      "description": "Waves the --max-cost ceiling refused at pre-flight (v4.4). Present only once at least one wave was refused. The run CONTINUES with a partial bench and exits degraded (2) — it is never rolled back and never aborted.",
      "type": "array",
      "items": {
        "type": "object",
        "required": ["waveId", "models"],
        "properties": {
          "waveId": { "type": ["string", "null"] },
          "models": { "type": "array", "items": { "type": "string" } },
          "reason": { "type": "string" },
          "at": { "type": "string" }
        }
      }
    },
    "sharedServer": {
      "description": "The run acquired its single shared OpenCode server and threaded it through every wave (v4.4.1 Task 0.5). Present only on a successful acquisition; mutually exclusive with sharedServerUnavailable. `goPid` is the shared server's process id — no wave writes a goPid into its own metadata.json while riding an injected server, so this field is the POSITIVE, direct evidence that one server served the run, replacing the goPid-absence inference.",
      "type": "object",
      "required": ["acquired"],
      "properties": {
        "acquired": { "const": true },
        "at": { "type": "string" },
        "goPid": { "type": ["number", "null"] },
        "models": { "type": "number" }
      }
    },
    "sharedServerUnavailable": {
      "description": "The run could not start its single shared OpenCode server and fell back to one server per wave (v4.4.1 Task 0.5). Present only when the acquisition failed. The run CONTINUES — this is never fatal — but the per-wave configuration is the one that races on OpenCode's SQLite, so results may be degraded.",
      "type": "object",
      "required": ["error"],
      "properties": {
        "error": { "type": "string" },
        "at": { "type": "string" }
      }
    },
    "degrades": {
      "description": "v4.6 Plan 1: every degrade/heal the sink announced — additive, written by run-degrade.js.",
      "type": "array",
      "items": {
        "type": "object",
        "required": ["kind", "channel", "what", "why", "effect"],
        "properties": {
          "kind": { "enum": ["degrade", "heal"] },
          "channel": { "type": "string" },
          "what": { "type": "string" },
          "why": { "type": "string" },
          "effect": { "type": "string" },
          "remedy": { "type": "string" },
          "data": {
            "type": "object",
            "description": "v4.6 Plan 2: optional structured facts for derivations (e.g. dead-leg {seat,status,reason}, dead-wave {waveId,models,reason}). Prose fields stay the human surface."
          }
        }
      }
    },
    "chairAttempts": {
      "description": "v4.6.2 PR5 (LC-5): one entry per resolved chair fallback-walk attempt (ch1/ch2/ch3 — the ch4 VERDICT-line repair is not an attempt, see classifyChairAttempt), checkpointed incrementally so a mid-walk kill preserves the attempts already resolved. Additive; absent on chairless (cost-skipped) runs and on every pre-PR5 run.",
      "type": "array",
      "items": {
        "type": "object",
        "required": ["waveId", "model", "outcome"],
        "properties": {
          "waveId": { "type": "string" },
          "model": { "type": "string" },
          "outcome": { "enum": ["completed", "error", "timeout", "no-output"] },
          "reason": { "type": ["string", "null"] }
        }
      }
    },
    "debate": {
      "type": "object",
      "properties": {
        "enabled": { "type": "boolean" },
        "outcome": { "enum": ["ran", "nothing-to-debate", "skipped-cost-ceiling"] },
        "contested": { "type": "integer" }, "disputed": { "type": "integer" },
        "defended": { "type": "integer" }, "amended": { "type": "integer" },
        "withdrawn": { "type": "integer" }, "noResponse": { "type": "integer" },
        "revoteJudges": { "type": "integer" }, "revoteApplied": { "type": "integer" },
        "verdictChanges": { "type": "integer" }
      },
      "required": ["enabled"],
      "additionalProperties": false
    }
  }
}
