{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "persona-panel-sidecar",
  "title": "persona-panel sidecar",
  "description": "Sidecar JSON written by the persona-panel skill (issue #457 AC6). Captures invoked personas, per-persona structured outputs, and the consolidation/voting result. No $ref or $defs — single-instance AJV-loader pattern per CLAUDE.md.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version",
    "run_id",
    "iso_timestamp",
    "target",
    "personas_invoked",
    "outputs",
    "consolidation"
  ],
  "properties": {
    "schema_version": {
      "type": "integer",
      "const": 1,
      "description": "Sidecar schema version. Bump only on breaking changes; additive evolution stays at 1."
    },
    "run_id": {
      "type": "string",
      "pattern": "^[a-z0-9-]{1,64}$",
      "description": "Stable run identifier (lowercase alnum + hyphens), max 64 chars."
    },
    "iso_timestamp": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}(\\.\\d+)?Z?$",
      "description": "Filename-safe timestamp: standard ISO 8601 with colons in the time component replaced by hyphens (e.g. 2026-05-19T08-22-49Z, NOT 2026-05-19T08:22:49Z). Do not Date.parse() directly — the colons-to-hyphens substitution makes it unparseable as standard ISO 8601. See W4 architect MED-2."
    },
    "target": {
      "type": "string",
      "maxLength": 4096,
      "description": "What was reviewed — file path, PRD ID, MR URL, etc."
    },
    "mode": {
      "type": "string",
      "enum": ["voting-quorum", "hard-gate-threshold", "coordinator-summary"],
      "description": "Consolidation strategy requested by the caller. Optional — defaults to whatever the skill chose."
    },
    "personas_invoked": {
      "type": "array",
      "minItems": 1,
      "maxItems": 20,
      "description": "Roster of personas the panel actually dispatched, with cost/timing metadata.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "version",
          "model",
          "prompt_hash",
          "timestamp_start",
          "timestamp_end"
        ],
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-z0-9-]{1,64}$"
          },
          "version": {
            "type": "string",
            "minLength": 1
          },
          "model": {
            "type": "string",
            "maxLength": 128
          },
          "prompt_hash": {
            "type": "string",
            "pattern": "^[a-f0-9]{16}$",
            "description": "First 16 hex chars of the SHA-256 of the persona prompt. Detects silent prompt drift."
          },
          "timestamp_start": {
            "type": "string"
          },
          "timestamp_end": {
            "type": "string"
          },
          "token_usage": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "input": { "type": "integer", "minimum": 0 },
              "output": { "type": "integer", "minimum": 0 },
              "cache_read": { "type": "integer", "minimum": 0 },
              "cache_creation": { "type": "integer", "minimum": 0 }
            }
          }
        }
      }
    },
    "outputs": {
      "type": "array",
      "minItems": 1,
      "description": "Per-persona structured verdicts. One entry per item in personas_invoked.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["persona_name", "mode", "ok"],
        "properties": {
          "persona_name": {
            "type": "string",
            "pattern": "^[a-z0-9-]{1,64}$"
          },
          "mode": {
            "type": "string",
            "enum": [
              "validated",
              "parse-error",
              "compile-error",
              "validation-failed",
              "dispatch-error",
              "timeout"
            ],
            "description": "Outcome mode — `validated` means the persona returned schema-conforming output; the rest are failure discriminators."
          },
          "ok": {
            "type": "boolean"
          },
          "verdict": {
            "type": "string",
            "enum": ["pass", "fail", "warn"]
          },
          "rationale": {
            "type": "string",
            "maxLength": 4096
          },
          "recommendations": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "string",
              "maxLength": 1024
            }
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "derived_sources": {
            "type": "array",
            "maxItems": 50,
            "description": "Grounding-review v1 (#730 Epic H): sources this persona independently re-derived via Read/Grep/Glob when dispatched with groundingMode='re-derive', instead of trusting a source list already present in the target. Optional — absent when groundingMode='off' (default).",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": ["path"],
              "properties": {
                "path": {
                  "type": "string",
                  "maxLength": 4096
                },
                "supports_claim": {
                  "type": "string",
                  "maxLength": 1024
                }
              }
            }
          }
        }
      }
    },
    "consolidation": {
      "type": "object",
      "additionalProperties": false,
      "required": ["mode_used", "final_verdict"],
      "description": "Aggregated panel decision.",
      "properties": {
        "mode_used": {
          "type": "string",
          "maxLength": 64
        },
        "final_verdict": {
          "type": "string",
          "enum": ["PROCEED", "PROCEED_WITH_FOLLOWUPS", "BLOCKED", "REQUIRES_COORDINATOR"]
        },
        "votes": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "pass": { "type": "integer", "minimum": 0 },
            "fail": { "type": "integer", "minimum": 0 },
            "warn": { "type": "integer", "minimum": 0 },
            "error": { "type": "integer", "minimum": 0 },
            "total": { "type": "integer", "minimum": 0 }
          }
        },
        "threshold": {
          "type": "string",
          "maxLength": 64
        },
        "threshold_met": {
          "type": "boolean"
        },
        "dissenting_personas": {
          "type": "array",
          "maxItems": 20,
          "items": { "type": "string" }
        },
        "tie_break_applied": {
          "type": "boolean"
        },
        "aggregator_warning": {
          "type": "string",
          "maxLength": 512
        },
        "notes": {
          "type": "array",
          "maxItems": 20,
          "items": {
            "type": "string",
            "maxLength": 512
          }
        },
        "grounding_diff": {
          "type": "object",
          "additionalProperties": false,
          "description": "Grounding-review v1 (#730 Epic H): advisory diff between the target's claimed sources and what personas independently re-derived. Shape of scripts/lib/persona-panel/consolidator.mjs::diffGroundingSources() return value. Signal only — never influences final_verdict.",
          "properties": {
            "unconfirmed_author_sources": {
              "type": "array",
              "maxItems": 100,
              "items": { "type": "string", "maxLength": 4096 }
            },
            "newly_derived": {
              "type": "array",
              "maxItems": 100,
              "items": { "type": "string", "maxLength": 4096 }
            },
            "personas_reporting": {
              "type": "integer",
              "minimum": 0
            }
          }
        }
      }
    }
  }
}
