{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://codafort.dev/schemas/codafort-interaction-v1.schema.json",
  "title": "codafort-interaction/1",
  "description": "O uso do codafort por um agente (ou por uma pessoa) num repositório conectado à plataforma — o canal do dev do plan-platform-sync. É artefato de esteira, fora da convenção coda-<modalidade>/1 do ADR-0002, como codafort-gate/1: uma interação não é análise, é uso da ferramenta. Cinco invariantes moram no formato. (1) Identidade DECLARADA, nunca inferida: `agent_kind` vem do clientInfo do MCP ou de --agent/CODAFORT_AGENT, e `unknown` é resposta legítima. (2) Argumento não viaja: `args_digest` é sha256 dos argumentos, porque caminho de arquivo do cliente não sai da máquina dele. (3) `findings_touched` é contagem, não lista. (4) `session.id` é gerado pelo cliente e estável durante a sessão — é por ele que envelopes sucessivos da mesma sessão MCP caem na mesma linha, e é `commit_sha` que casa a sessão com o run (a atribuição do placar). (5) Proposta não é decisão: `proposals` é o que o agente (ou a pessoa, na CLI) propõe sobre um finding, e a plataforma só a LISTA — quem a transforma em estado é uma pessoa com papel, pela triagem.",
  "type": "object",
  "additionalProperties": false,
  "required": ["schema", "tool", "session", "interactions"],
  "properties": {
    "schema": { "const": "codafort-interaction/1" },
    "tool": {
      "type": "object",
      "additionalProperties": false,
      "required": ["name", "version"],
      "properties": {
        "name": { "type": "string", "minLength": 1, "maxLength": 40 },
        "version": { "type": "string", "minLength": 1, "maxLength": 40 }
      }
    },
    "repository": {
      "type": "object",
      "additionalProperties": false,
      "description": "Onde a interação aconteceu. `commit_sha` é o HEAD no momento dela: a sessão fica com o do último envelope recebido.",
      "properties": {
        "repository_name": { "type": "string", "minLength": 1, "maxLength": 200 },
        "branch": { "type": "string", "minLength": 1, "maxLength": 200 },
        "commit_sha": { "type": "string", "pattern": "^[0-9a-f]{7,64}$" }
      }
    },
    "session": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "agent_kind", "surface", "started_at"],
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
          "description": "Gerado pelo cliente. Na plataforma vira id POR TENANT (derivado de organização + este valor), então não colide nem vaza entre tenants."
        },
        "agent_kind": { "type": "string", "minLength": 1, "maxLength": 80 },
        "agent_version": { "type": ["string", "null"], "maxLength": 80 },
        "surface": { "enum": ["cli", "mcp"] },
        "started_at": { "type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$" },
        "ended_at": { "type": ["string", "null"], "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$" }
      }
    },
    "interactions": {
      "type": "array",
      "minItems": 1,
      "maxItems": 1000,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["verb", "at"],
        "properties": {
          "verb": { "type": "string", "minLength": 1, "maxLength": 40 },
          "args_digest": { "type": ["string", "null"], "pattern": "^sha256:[0-9a-f]{64}$" },
          "findings_touched": { "type": "integer", "minimum": 0 },
          "outcome": { "enum": ["ok", "error"] },
          "duration_ms": { "type": "integer", "minimum": 0 },
          "at": { "type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$" }
        }
      }
    },
    "proposals": {
      "type": "array",
      "maxItems": 100,
      "description": "Falso-positivo ou risco aceito PROPOSTO sobre um finding — nunca aplicado pela plataforma. `vuln_hash` identifica o finding sem caminho de arquivo; `rationale` é o texto que quem propôs escreveu.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["vuln_hash", "kind"],
        "properties": {
          "vuln_hash": { "type": "string", "minLength": 1, "maxLength": 200 },
          "kind": { "enum": ["false_positive", "accepted_risk"] },
          "rationale": { "type": ["string", "null"], "maxLength": 500 }
        }
      }
    }
  }
}
