{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://cheapagent.ai/schemas/verdict.v1.json",
  "title": "CheapAgent Verdict v1",
  "description": "Machine-readable decision object emitted by every CheapAgent surface (CLI --json, MCP tools, doc2toon serve, hosted API, web app). The verdict is a decision, not a compression claim: it tells an agent whether converting a document to TOON is worth it before any context is spent. Consumers MUST ignore unknown fields; fields are only ever added (never removed or retyped) within schema_version 1.x. Decision fields (verdict, safe_to_auto_apply) derive exclusively from deterministic inputs: measured character counts, warning codes, and the lossless/valid flags. Token estimates are advisory payload and never decision inputs.",
  "type": "object",
  "additionalProperties": true,
  "required": [
    "schema_version",
    "verdict",
    "safe_to_auto_apply",
    "profile",
    "measured_chars",
    "token_estimates",
    "toon_candidate",
    "warnings",
    "flags",
    "mode",
    "delimiter"
  ],
  "properties": {
    "schema_version": {
      "type": "string",
      "pattern": "^1\\.[0-9]+$",
      "description": "Verdict schema version. Additive optional fields bump the minor (1.0 -> 1.1). Breaking changes are a new schema document (verdict.v2.json), never a mutation of this one."
    },
    "verdict": {
      "description": "The decision. convert: TOON is worth it. keep_markdown: TOON does not improve this input. split_first: structural problems hide the savings; split the document before converting. review: convertible, but warnings need a human look first. refused: a budget target could not be met losslessly and lossy output was not permitted.",
      "enum": ["convert", "keep_markdown", "split_first", "review", "refused"]
    },
    "safe_to_auto_apply": {
      "type": "boolean",
      "description": "True only when an agent may apply the conversion without human review: verdict is convert AND mode is lossless AND flags.lossless AND flags.valid AND measured_chars.savings > 0 AND no warning has severity \"warning\". The mode clause exists because only lossless mode preserves all source blocks by construction; outside it, content coverage is measured heuristically and disclosed via the low_coverage warning rather than trusted for auto-apply. Conservative by design."
    },
    "profile": {
      "type": "object",
      "additionalProperties": true,
      "required": ["name", "title", "source_type", "stats"],
      "properties": {
        "name": {
          "description": "Detected document profile.",
          "enum": ["raw_prose", "definitions", "requirements", "table", "mixed"]
        },
        "title": {
          "type": ["string", "null"],
          "description": "Document title when one was detected."
        },
        "source_type": {
          "enum": ["markdown", "text", "stdin", "paste"]
        },
        "stats": {
          "type": "object",
          "additionalProperties": true,
          "required": ["lines", "headings", "paragraphs", "list_items", "tables", "table_rows", "definitions", "rules"],
          "properties": {
            "lines": { "type": "integer", "minimum": 0 },
            "headings": { "type": "integer", "minimum": 0 },
            "paragraphs": { "type": "integer", "minimum": 0 },
            "list_items": { "type": "integer", "minimum": 0 },
            "tables": { "type": "integer", "minimum": 0 },
            "table_rows": { "type": "integer", "minimum": 0 },
            "definitions": { "type": "integer", "minimum": 0 },
            "rules": { "type": "integer", "minimum": 0 }
          }
        }
      }
    },
    "measured_chars": {
      "type": "object",
      "additionalProperties": true,
      "description": "Deterministic character measurements. These are the decision inputs: identical on every surface regardless of tokenizer.",
      "required": ["source", "toon", "savings", "savings_pct"],
      "properties": {
        "source": { "type": "integer", "minimum": 0 },
        "toon": { "type": "integer", "minimum": 0, "description": "Characters in the TOON candidate. For refused verdicts this measures the shortest lossless candidate that was attempted." },
        "savings": { "type": "integer", "description": "source - toon. Negative when TOON is larger than the source." },
        "savings_pct": { "type": "number", "description": "savings / source * 100. Negative when TOON is larger." }
      }
    },
    "token_estimates": {
      "type": "object",
      "additionalProperties": true,
      "description": "Advisory token estimates. Never decision inputs: estimators differ by surface, so these numbers may differ between CLI and browser for the same document while the verdict stays identical.",
      "required": ["estimator", "source", "toon", "savings", "savings_pct", "ratio_estimates"],
      "properties": {
        "estimator": {
          "type": "string",
          "description": "Identity of the estimator that produced the primary numbers, e.g. \"tokenx@1.3.0\" or \"chars-per-token:4\"."
        },
        "source": { "type": "integer", "minimum": 0 },
        "toon": { "type": "integer", "minimum": 0 },
        "savings": { "type": "integer" },
        "savings_pct": { "type": "number" },
        "ratio_estimates": {
          "type": "array",
          "description": "Fixed chars-per-token ratio projections so consumers can sanity-check the primary estimate against simple ratios.",
          "items": {
            "type": "object",
            "additionalProperties": true,
            "required": ["chars_per_token", "source", "toon", "savings", "savings_pct"],
            "properties": {
              "chars_per_token": { "type": "number", "exclusiveMinimum": 0 },
              "source": { "type": "integer", "minimum": 0 },
              "toon": { "type": "integer", "minimum": 0 },
              "savings": { "type": "integer" },
              "savings_pct": { "type": "number" }
            }
          }
        }
      }
    },
    "toon_candidate": {
      "type": ["string", "null"],
      "description": "The TOON output. Present on convert responses; null on profile responses (measurements only — the candidate is withheld to keep the verdict context-cheap) and on refused verdicts (no output was produced)."
    },
    "warnings": {
      "type": "array",
      "description": "Coded warnings. The code set is open: new codes may appear within 1.x, and consumers MUST tolerate unknown codes, using the severity field to decide how to react. Prose is a rendering of the code, never the data.",
      "items": {
        "type": "object",
        "additionalProperties": true,
        "required": ["code", "severity", "message"],
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable warning identifier. v1 registry: duplicate_rule, vague_rule, long_section, split_candidate, negative_savings, lossy_applied, target_not_reached, low_coverage, budget_refused."
          },
          "severity": { "enum": ["info", "warning"] },
          "message": { "type": "string" },
          "suggestion": { "type": "string" },
          "evidence": { "type": "string" },
          "range": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "line_start": { "type": "integer", "minimum": 1 },
              "line_end": { "type": "integer", "minimum": 1 },
              "char_start": { "type": "integer", "minimum": 0 },
              "char_end": { "type": "integer", "minimum": 0 }
            }
          }
        }
      }
    },
    "flags": {
      "type": "object",
      "additionalProperties": true,
      "required": ["lossless", "valid", "target_reached"],
      "properties": {
        "lossless": { "type": "boolean", "description": "No budget (semantic) compression was applied by the pipeline. In v1 this flag asserts the mode's intent, not independently verified content coverage: only lossless mode preserves all source blocks by construction. Content coverage is separately measured and disclosed via the low_coverage warning. See safe_to_auto_apply." },
        "valid": { "type": "boolean", "description": "The candidate round-trips through the official TOON decoder." },
        "target_reached": { "type": ["boolean", "null"], "description": "Whether the requested budget target was met. Null when no target was requested." }
      }
    },
    "mode": {
      "enum": ["lossless", "record", "budget"]
    },
    "delimiter": {
      "description": "TOON delimiter selected for the candidate. Null when no candidate encoding was selected (refused verdicts).",
      "enum": [",", "\t", "|", null]
    },
    "context_plan": {
      "type": "object",
      "description": "Per-section context plan (docs/context-plan-design.md). Present only on plan surfaces (doc2toon plan --json), which emit schema_version 1.1; profile and convert responses stay 1.0 and never carry it. Sections are the author's own heading-bounded source slices, each measured as a standalone document under the unchanged decision policy — zero new tunable constants. The plan adds a metric; it never restates or replaces the whole-document verdict fields.",
      "additionalProperties": true,
      "required": ["sections", "net", "recommend_hybrid", "reassembly_verified", "safe_to_auto_apply"],
      "properties": {
        "sections": {
          "type": "array",
          "description": "Every section in source order. The [char_start, char_end) ranges partition the source exactly: no gaps, no overlaps, byte-identical on reassembly.",
          "items": {
            "type": "object",
            "additionalProperties": true,
            "required": ["heading", "kind", "range", "profile", "verdict", "action", "measured_chars", "warnings", "safe_to_auto_apply"],
            "properties": {
              "heading": {
                "type": ["string", "null"],
                "description": "Heading text; null for preamble and frontmatter sections."
              },
              "kind": {
                "enum": ["section", "preamble", "frontmatter"]
              },
              "range": {
                "type": "object",
                "description": "Whole-document coordinates of the section's raw slice (1-based lines; [char_start, char_end) character offsets).",
                "additionalProperties": true,
                "required": ["line_start", "line_end", "char_start", "char_end"],
                "properties": {
                  "line_start": { "type": "integer", "minimum": 1 },
                  "line_end": { "type": "integer", "minimum": 1 },
                  "char_start": { "type": "integer", "minimum": 0 },
                  "char_end": { "type": "integer", "minimum": 0 }
                }
              },
              "profile": {
                "description": "Standalone profile of the slice. Null only for frontmatter sections, which are metadata and never measured.",
                "enum": ["raw_prose", "definitions", "requirements", "table", "mixed", null]
              },
              "verdict": {
                "description": "The section's standalone verdict under the unchanged whole-document policy. Null only for frontmatter sections.",
                "enum": ["convert", "keep_markdown", "split_first", "review", "refused", null]
              },
              "action": {
                "description": "convert iff the standalone verdict is convert and the candidate decodes; keep otherwise. Frontmatter is always keep.",
                "enum": ["convert", "keep"]
              },
              "measured_chars": {
                "description": "The section's standalone measurement — carried by every measured section, keep actions included, so each plan row is auditable. Null only for frontmatter sections.",
                "oneOf": [
                  { "type": "null" },
                  {
                    "type": "object",
                    "additionalProperties": true,
                    "required": ["source", "toon", "savings", "savings_pct"],
                    "properties": {
                      "source": { "type": "integer", "minimum": 0 },
                      "toon": { "type": "integer", "minimum": 0 },
                      "savings": { "type": "integer" },
                      "savings_pct": { "type": "number" }
                    }
                  }
                ]
              },
              "warnings": {
                "type": "array",
                "description": "Coded warnings from the section's standalone measurement — same open registry and severity rules as the top-level warnings array. Ranges are offset to whole-document coordinates; a consumer never sees slice-relative positions.",
                "items": {
                  "type": "object",
                  "additionalProperties": true,
                  "required": ["code", "severity", "message"],
                  "properties": {
                    "code": { "type": "string" },
                    "severity": { "enum": ["info", "warning"] },
                    "message": { "type": "string" },
                    "suggestion": { "type": "string" },
                    "evidence": { "type": "string" },
                    "range": {
                      "type": "object",
                      "additionalProperties": true,
                      "properties": {
                        "line_start": { "type": "integer", "minimum": 1 },
                        "line_end": { "type": "integer", "minimum": 1 },
                        "char_start": { "type": "integer", "minimum": 0 },
                        "char_end": { "type": "integer", "minimum": 0 }
                      }
                    }
                  }
                }
              },
              "safe_to_auto_apply": {
                "type": "boolean",
                "description": "Section-level safety under the existing whole-document formula, applied to the standalone measurement."
              }
            }
          }
        },
        "net": {
          "type": "object",
          "description": "Aggregate hybrid measurement. hybrid is the exact character count of the assembled hybrid document, so splice overhead (kept headings, fence markers) is counted, never hidden.",
          "additionalProperties": true,
          "required": ["source", "hybrid", "savings", "savings_pct"],
          "properties": {
            "source": { "type": "integer", "minimum": 0 },
            "hybrid": { "type": "integer", "minimum": 0 },
            "savings": { "type": "integer", "description": "source - hybrid. Negative when the hybrid is larger than the source." },
            "savings_pct": { "type": "number" }
          }
        },
        "recommend_hybrid": {
          "type": "boolean",
          "description": "True when net savings clear the frozen MIN_CONVERT_SAVINGS_PCT band AND at least one section converts — the same 5% band the whole-document policy uses, reused at plan level."
        },
        "reassembly_verified": {
          "type": "boolean",
          "description": "Mechanical verification of the hybrid: kept sections byte-identical, converted candidates decode as embedded, section slices re-stitch to the full document."
        },
        "safe_to_auto_apply": {
          "type": "boolean",
          "description": "recommend_hybrid AND converted-section count > 0 AND every converted section individually safe AND reassembly_verified. Non-vacuous by definition: a plan that converts nothing is never safe to auto-apply."
        }
      }
    }
  }
}
