{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://codafort.dev/schemas/coda-profile-v1.schema.json",
  "title": "coda-profile/1",
  "description": "Envelope da modalidade PERFIL (emitido pelo codacrash, RF-300..390). É modalidade PRÓPRIA e não uma sub-modalidade de crash — aqui não há falha: o exit code é 0 e o artefato descreve ONDE o tempo foi gasto. A separação é do owner (2026-07-29) e tem consequência prática: um consumidor que trata perfil como crash inventaria severidade onde só há peso. Determinístico: top-N já sai ordenado por peso desc e nome asc, então a mesma amostragem produz o mesmo envelope. PUBLICADO em 2026-09-13 a partir do emissor real (`codacrash/crates/cli/src/report/profile.rs`), que escreve o JSON à mão.",
  "type": "object",
  "required": ["schema", "samples", "total_weight", "frames", "top_self", "top_total", "risks"],
  "properties": {
    "schema": { "const": "coda-profile/1" },
    "tool": {
      "type": "object",
      "description": "Proveniência. **Ausente nos envelopes de hoje**, como no `coda-crash/1`: opcional para que o contrato descreva a realidade sem travar a correção.",
      "required": ["name", "version"],
      "properties": {
        "name": { "type": "string", "minLength": 1 },
        "version": { "type": "string", "minLength": 1 }
      }
    },
    "samples": { "type": "integer", "description": "Quantas amostras entraram. É o denominador de tudo o que vem depois — um top-N sobre 30 amostras não é o mesmo objeto que um top-N sobre 30 mil." },
    "total_weight": { "type": "integer", "description": "Peso total (amostras × custo), na unidade do formato de entrada." },
    "frames": { "type": "integer", "description": "Frames distintos vistos." },
    "top_self": {
      "type": "array",
      "description": "Hotspots por tempo PRÓPRIO — onde a CPU esteve, não quem chamou.",
      "items": { "$ref": "#/definitions/hot" }
    },
    "top_total": {
      "type": "array",
      "description": "Hotspots por tempo TOTAL (próprio + descendentes) — quem é responsável pelo custo, ainda que não o gaste.",
      "items": { "$ref": "#/definitions/hot" }
    },
    "risks": {
      "type": "array",
      "description": "Correlação de SEGURANÇA sobre o perfil (RF-370): hotspot quente que casa um padrão de risco conhecido (ReDoS, cripto fraca, hashing-DoS), com piso de self-time. É o que liga perfil ao Finding canônico — e é positivo-só: ausência de risco aqui não é prova de ausência de risco.",
      "items": {
        "type": "object",
        "required": ["category", "function", "cwe", "self_pct"],
        "properties": {
          "category": { "type": "string" },
          "function": { "type": "string" },
          "cwe": { "type": "integer" },
          "self_pct": { "type": "number", "description": "Fração (0..1), não percentual." }
        }
      }
    },
    "diff": {
      "type": "array",
      "description": "Comparação com um perfil BASELINE (`--compare`, RF-360). **Presente só quando houve baseline** — ausência do campo significa 'não comparado', jamais 'sem diferença'. Entradas com |delta| desprezível são omitidas pelo emissor.",
      "items": {
        "type": "object",
        "required": ["fn", "before_pct", "after_pct", "delta_pct"],
        "properties": {
          "fn": { "type": "string" },
          "before_pct": { "type": "number" },
          "after_pct": { "type": "number" },
          "delta_pct": { "type": "number", "description": "`after_pct - before_pct`, em fração. Negativo = ficou mais barato." }
        }
      }
    }
  },
  "definitions": {
    "hot": {
      "type": "object",
      "required": ["fn", "weight", "pct"],
      "properties": {
        "fn": { "type": "string", "description": "Função, como a simbolização a resolveu. Sem símbolo, o endereço — nunca um nome inventado." },
        "weight": { "type": "integer" },
        "pct": { "type": "number", "description": "Fração (0..1) do peso total." }
      }
    }
  }
}
