{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://codafort.dev/schemas/coda-attestation-v1.schema.json",
  "title": "coda-attestation/1",
  "description": "Payload do atestado contra-assinado (Ed25519) sobre o RESULTADO de um scan. Prova integridade e autoria do resultado; NÃO prova ausência de vulnerabilidade. O token no fio é `base64url(payload).base64url(assinatura)`. IMPORTANTE: tokens emitidos antes de 2026-07-29 carregam `kind: \"codafort-attestation/1\"` e seguem válidos — a assinatura cobre estes bytes e não pode ser refeita, então todo verificador DEVE aceitar os dois kinds na leitura.",
  "type": "object",
  "required": [
    "v",
    "kind",
    "commit",
    "engine_version",
    "ruleset_hash",
    "result_digest",
    "standard",
    "verdict",
    "scanned_at",
    "n_files"
  ],
  "properties": {
    "v": {
      "const": 1
    },
    "kind": {
      "enum": [
        "coda-attestation/1",
        "codafort-attestation/1"
      ],
      "description": "Formato corrente e o LEGADO. O segundo só aparece em tokens já emitidos; `create` emite apenas o primeiro."
    },
    "repo_hash": {
      "type": "string",
      "description": "Identidade LEGADA (hash curto do path da raiz — mudava por máquina). Presente só em tokens emitidos antes de 2026-08-18; a emissão atual usa `repo_id`/`repo_id_kind`. O verificador aceita as duas formas para sempre: token emitido não se re-assina."
    },
    "repo_id": {
      "type": "string",
      "description": "Identidade do repositório. `root-commit` = SHA completo do PRIMEIRO commit da história — estável entre máquinas/checkouts e não-reversível a caminho; `path` = hash curto do path (fallback sem git, carimbado em `repo_id_kind`)."
    },
    "repo_id_kind": {
      "enum": [
        "root-commit",
        "path"
      ],
      "description": "Qual identidade este atestado carrega. O fallback é DITO, nunca disfarçado de identidade forte."
    },
    "commit": {
      "type": "string"
    },
    "dirty": {
      "type": [
        "boolean",
        "null"
      ],
      "description": "Working tree sujo significa que o commit declarado NÃO descreve o código atestado. `null` = não-computável, o que não é o mesmo que limpo."
    },
    "engine_version": {
      "type": "string"
    },
    "ruleset_hash": {
      "type": "string",
      "description": "SHA-256 do ruleset efetivo (builtin + camadas locais + knobs)."
    },
    "result_digest": {
      "type": "string",
      "description": "Digest canônico do conjunto de findings — portável entre raízes de checkout."
    },
    "standard": {
      "type": "string"
    },
    "verdict": {
      "enum": [
        "pass",
        "fail"
      ]
    },
    "reduction_version": {
      "type": "integer",
      "minimum": 1,
      "description": "Versão da REGRA que produziu o `verdict` (plan-ledger §L4.3). O par (standard, reduction_version) endereça a redução inteira — simétrico ao que `ruleset_hash` faz pela regra estática. v1 = `blocker>0 | critical>0`, condições FIXAS do padrão, imunes ao gate local do repo. Ausente em tokens emitidos antes de 2026-08-18 (a mesma regra v1 valia, sem carimbo)."
    },
    "supersedes": {
      "type": "string",
      "description": "`attestation_id` do atestado que este SUCEDE (plan-ledger §L4.4) — conclusões reavaliáveis sobre substrato imutável: nunca mutar, sempre suceder. Válido só quando `commit` e `result_digest` são idênticos aos do antecessor (mesmo scan, mais evidência); scan diferente é atestado novo. Ausente quando não há sucessão."
    },
    "scanned_at": {
      "type": "integer"
    },
    "n_files": {
      "type": "integer",
      "minimum": 0
    },
    "n_findings_by_sev": {
      "type": "object"
    },
    "issued_to_hash": {
      "type": "string"
    },
    "attested_at": {
      "type": "integer",
      "description": "Acrescentado pelo vendor na contra-assinatura."
    },
    "attestation_id": {
      "type": "string"
    },
    "kid": {
      "type": "string"
    },
    "deleg": {
      "type": "string",
      "description": "Token de delegação (Att-2, assinatura local air-gapped)."
    },
    "evidence": {
      "type": "array",
      "description": "Contribuições de outras modalidades, ordenadas por `modality` (ordem canônica: o payload é assinado, e ordem instável produziria assinatura instável). Ausente quando não há evidência anexada — nunca presente-e-vazia, para que o payload de quem não usa siga byte-idêntico.",
      "items": {
        "$ref": "#/definitions/evidence"
      }
    },
    "artifacts": {
      "type": "array",
      "minItems": 1,
      "description": "Artefatos CONSTRUÍDOS desta release (§L2 do plan-ledger) — o que faz o atestado ENDEREÇAR a release, não só o commit. A forma é a do `subject[]` do in-toto Statement (mapa alg→hex, sem prefixo), copiável sem transformação. AUSENTE quando não declarado — nunca presente-e-vazio. ⚠ DECLARAÇÃO do emissor, contra-assinada: prova que quem emitiu declarou que este commit produziu estes artefatos — NÃO prova o vínculo build↔fonte (provenance de build, SLSA L2+, fora do escopo).",
      "items": {
        "type": "object",
        "required": [
          "name",
          "digest"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "digest": {
            "type": "object",
            "required": [
              "sha256"
            ],
            "properties": {
              "sha256": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$"
              }
            }
          }
        }
      }
    }
  },
  "definitions": {
    "evidence": {
      "type": "object",
      "required": [
        "schema",
        "modality"
      ],
      "properties": {
        "schema": {
          "const": "coda-evidence/1"
        },
        "modality": {
          "type": "string",
          "description": "src | iast | dast | run | vet — uma forma para todas, para que acrescentar modalidade seja DADO e não mudança de contrato assinado. `vet` é a modalidade cujo SUJEITO é a mudança (verificação de código gerado por IA), não a base de código."
        },
        "granularity": {
          "type": "string",
          "description": "`file-line` (o emissor casa arquivo e linha) ou `cwe` (só a classe da falha, como o DAST, que de fora não enxerga linha). Ausente ou desconhecida DEVE ser lida como a mais grosseira: somar precisões diferentes sem declará-las afirma mais do que se mediu."
        },
        "axes_ran": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Eixos de verificação que realmente rodaram."
        },
        "axes_skipped": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Eixos que NÃO rodaram. Mesma razão de ser do `unreached`: eixo pulado jamais é eixo limpo, e um laudo que omite esta lista afirma cobertura que não teve."
        },
        "confirmed_at_runtime": {
          "type": "integer",
          "minimum": 0
        },
        "sanitized_at_runtime": {
          "type": "integer",
          "minimum": 0
        },
        "unreached": {
          "type": "integer",
          "minimum": 0,
          "description": "Findings que o tráfego NÃO exercitou. Campo de primeira classe: 'não medido' jamais é 'seguro', e um laudo que apaga esta categoria mente por omissão."
        },
        "static_findings": {
          "type": "integer",
          "minimum": 0
        },
        "confirmation_rate": {
          "type": "string"
        },
        "verdict_digest": {
          "type": "string"
        },
        "instrumentation": {
          "type": "object",
          "description": "Fronteira de INSTRUMENTAÇÃO da modalidade (L1.1): o que o agente conseguia ver neste run, em contagens ABSOLUTAS (a doutrina de cobertura da plataforma é piso sobre a contagem, não sobre a fração — a fração cai legitimamente quando a taxonomia cresce). `unreached` diz o que o tráfego não exercitou POR FINDING; isto diz o que o agente nem instrumentava POR SINK. Ausente = não medido, nunca zero.",
          "required": [
            "sinks_covered"
          ],
          "properties": {
            "sinks_covered": {
              "type": "integer",
              "minimum": 0,
              "description": "Sinks vistos por ALGUM agente (união entre runtimes)."
            },
            "by_runtime": {
              "type": "object",
              "additionalProperties": {
                "type": "integer",
                "minimum": 0
              },
              "description": "Contagem por runtime — runtime com ZERO entra no mapa; omiti-lo faria a cobertura parecer melhor do que é."
            }
          }
        },
        "load": {
          "type": "object",
          "required": [
            "requests"
          ],
          "description": "Fronteira de CARGA da medição dinâmica (plan-ledger §L1.2/§L4.5) — só quem GERA a carga pode declará-la (H4), e só o que foi MEDIDO entra. No DAST: `requests` vem do audit log encadeado por SHA-256 (cada request está na cadeia; `verify-audit` reprova adulteração), `duration_s` dos carimbos do scan e `audit_anchor` é a ponta da cadeia — a reprodutibilidade aqui não é um seed, é o log verificável. AUSENTE quando não medido: 'não medido' nunca vira número.",
          "properties": {
            "requests": {
              "type": "integer",
              "minimum": 0
            },
            "duration_s": {
              "type": "integer",
              "minimum": 0
            },
            "audit_anchor": {
              "type": "string",
              "description": "Ponta da cadeia de auditoria — quem recebe pode exigir o log e verificar a carga inteira."
            }
          }
        },
        "verdicts": {
          "type": "array",
          "description": "§D-F3 — os pares (finding, veredito) que o `verdict_digest` resume. OPT-IN (`codatrace evidence --verdicts`): AUSENTE quando não pedido, para o payload de quem não usa seguir byte-idêntico. Presente, é o que permite ao consumidor saber QUAIS findings foram confirmados em runtime — e não apenas quantos —, habilitando força de evidência por-finding e veredito recomputável. Ordem canônica (a mesma linha `id|verdict` que alimenta o digest), portanto indiferente à ordem de observação. Carrega SÓ `id` e `verdict`: o `id` é opaco (SF-n ou vuln_hash) e caminho/linha NÃO viajam — levá-los exportaria a estrutura do repositório.",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "verdict"
            ],
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "description": "Identidade opaca do finding estático (SF-n da sessão do codafort, ou vuln_hash)."
              },
              "verdict": {
                "enum": [
                  "confirmed-at-runtime",
                  "sanitized-at-runtime",
                  "unreached"
                ],
                "description": "Veredito observado. `unreached` é 'não medido', NUNCA 'seguro' — o invariante positivo-só vale aqui como no agregado."
              }
            }
          }
        }
      }
    }
  }
}
