{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://codafort.dev/schemas/coda-vet-v1.schema.json",
  "title": "coda-vet/1",
  "description": "Veredito de verificação de uma MUDANÇA (código gerado por IA) — a única modalidade cujo sujeito é o delta, não a base de código. O nome segue a convenção coda-<modalidade>/1 (ADR-0002). O campo `axes` é o invariante epistêmico do formato: um veredito limpo não afirma 'a mudança é segura', afirma 'os eixos que RODARAM não acharam nada' — V1 exige artefato de ingestão, V3/V4 exigem o modo delta (git). Eixo pulado jamais é eixo limpo, e um laudo que omitisse `axes.skipped` afirmaria cobertura que não teve. Determinístico: a mesma mudança produz o mesmo veredito byte-a-byte.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema",
    "blocked",
    "axes",
    "files",
    "blocking",
    "advisory",
    "ingested",
    "contract_breaks",
    "health",
    "diff_only"
  ],
  "properties": {
    "schema": {
      "const": "coda-vet/1"
    },
    "blocked": {
      "type": "boolean",
      "description": "true se há algo bloqueante — o gate reprova (exit ≠ 0)."
    },
    "axes": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "ran",
        "skipped"
      ],
      "description": "O que este veredito PODE afirmar. Serializado sempre — inclusive vazio.",
      "properties": {
        "ran": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "skipped": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "axis",
              "reason"
            ],
            "properties": {
              "axis": {
                "type": "string",
                "minLength": 1
              },
              "reason": {
                "type": "string",
                "minLength": 1,
                "description": "A razão é do usuário, não do log. Eixo pulado sem razão é 'não rodou' silencioso."
              }
            }
          }
        }
      }
    },
    "files": {
      "type": "integer",
      "minimum": 0
    },
    "blocking": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/finding"
      },
      "description": "Findings PROVADOS no escopo (confirmed de segurança no delta). Só eles bloqueiam."
    },
    "advisory": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/finding"
      },
      "description": "Qualidade/suspected — consultivo, nunca bloqueia."
    },
    "ingested": {
      "type": "array",
      "description": "V1 — diagnósticos EXTERNOS (type-check/teste) que o CI rodou, correlacionados ao diff. O codafort não executa nada: só lê e correlaciona.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "tool",
          "file",
          "line",
          "severity",
          "rule",
          "message",
          "blocking"
        ],
        "properties": {
          "tool": {
            "type": "string"
          },
          "file": {
            "type": "string"
          },
          "line": {
            "type": "integer",
            "minimum": 0
          },
          "severity": {
            "type": "string"
          },
          "rule": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "blocking": {
            "type": "boolean"
          }
        }
      }
    },
    "scope": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "files_changed",
        "lines_added",
        "critical_files",
        "wide"
      ],
      "description": "V4 — blast-radius do diff. AUSENTE fora do modo delta (sem base não há o que medir); consultivo, nunca bloqueia.",
      "properties": {
        "files_changed": {
          "type": "integer",
          "minimum": 0
        },
        "lines_added": {
          "type": "integer",
          "minimum": 0
        },
        "critical_files": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "wide": {
          "type": "boolean"
        }
      }
    },
    "contract_breaks": {
      "type": "array",
      "description": "V3 — símbolos PÚBLICOS removidos vs a base (breaking change). BLOQUEIA: é fato re-derivável. Vazio fora do modo delta.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "file",
          "symbol",
          "kind"
        ],
        "properties": {
          "file": {
            "type": "string"
          },
          "symbol": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          }
        }
      }
    },
    "excluded": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Padrões do `.codafort-exclude.yaml` da raiz: o que ficou fora deste veredito. Ausente quando não há exclusão declarada."
    },
    "health": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "functions",
        "avg_cyclomatic",
        "max_cyclomatic",
        "avg_cognitive",
        "max_cognitive",
        "complex_functions",
        "duplicated_lines_density",
        "technical_debt_minutes",
        "code_smells"
      ],
      "description": "Snapshot de manutenibilidade do run — consultivo, nunca gate.",
      "properties": {
        "functions": {
          "type": "integer",
          "minimum": 0
        },
        "avg_cyclomatic": {
          "type": "number",
          "minimum": 0
        },
        "max_cyclomatic": {
          "type": "integer",
          "minimum": 0
        },
        "avg_cognitive": {
          "type": "number",
          "minimum": 0
        },
        "max_cognitive": {
          "type": "integer",
          "minimum": 0
        },
        "complex_functions": {
          "type": "integer",
          "minimum": 0
        },
        "duplicated_lines_density": {
          "type": "number",
          "minimum": 0,
          "maximum": 100
        },
        "technical_debt_minutes": {
          "type": "integer",
          "minimum": 0
        },
        "code_smells": {
          "type": "integer",
          "minimum": 0
        },
        "worst_functions": {
          "type": "array",
          "description": "As piores funções pela complexidade cognitiva (até 5), para o número virar ação. O código de nível de módulo (`<file>`) não entra.",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "file",
              "function",
              "line",
              "cognitive",
              "cyclomatic"
            ],
            "properties": {
              "file": {
                "type": "string"
              },
              "function": {
                "type": "string"
              },
              "line": {
                "type": "integer",
                "minimum": 0
              },
              "cognitive": {
                "type": "integer",
                "minimum": 0
              },
              "cyclomatic": {
                "type": "integer",
                "minimum": 0
              }
            }
          }
        }
      }
    },
    "diff_only": {
      "type": "boolean",
      "description": "true = só o delta (linhas novas vs a base) foi considerado; false = projeto inteiro (fallback sem git, com aviso)."
    }
  },
  "definitions": {
    "finding": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "id",
        "severity",
        "kind",
        "rule",
        "message",
        "file",
        "line",
        "cwe",
        "owasp",
        "confidence",
        "tier",
        "has_fix",
        "is_new"
      ],
      "properties": {
        "id": {
          "type": "string",
          "description": "SF-n da sessão — o mesmo ID que explain/fix consomem."
        },
        "severity": {
          "type": "string"
        },
        "kind": {
          "type": "string"
        },
        "rule": {
          "type": "string"
        },
        "message": {
          "type": "string"
        },
        "file": {
          "type": "string"
        },
        "line": {
          "type": "integer",
          "minimum": 0
        },
        "cwe": {
          "type": "array",
          "items": {
            "type": "integer"
          }
        },
        "owasp": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "confidence": {
          "type": "string"
        },
        "tier": {
          "enum": [
            "confirmed",
            "suspected"
          ],
          "description": "PROVA vs PALPITE — sem isto o consumidor trata advisory como bloqueante."
        },
        "has_fix": {
          "type": "boolean"
        },
        "is_new": {
          "type": "boolean"
        },
        "taint": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "source",
            "source_line",
            "sink",
            "sink_line",
            "var",
            "hops"
          ],
          "properties": {
            "source": {
              "type": "string"
            },
            "source_line": {
              "type": "integer"
            },
            "sink": {
              "type": "string"
            },
            "sink_line": {
              "type": "integer"
            },
            "var": {
              "type": "string"
            },
            "hops": {
              "type": "integer",
              "minimum": 0
            }
          }
        }
      }
    }
  }
}
