{
  "schema_version": 1,
  "diagram_type": "architecture",
  "meta": {
    "title": "A poda do histórico e o que precisa ficar",
    "subtitle": "issues #261/#3201: o que sai, o que tem dono e o leitor técnico do traço do turno",
    "output": "retencao-de-historico.html",
    "quality_profile": "standard"
  },
  "lanes": [
    { "id": "humano", "label": "O que o operador vê" },
    { "id": "relogio", "label": "Relógio" },
    { "id": "borda", "label": "Borda HTTP (cron)" },
    { "id": "regra", "label": "Regra pura" },
    { "id": "banco", "label": "Banco (onde a regra de verdade mora)" },
    { "id": "volta", "label": "O laço de retorno" }
  ],
  "mainPath": ["scheduler", "rota", "podar", "fnpoda", "fila", "trilha"],
  "nodes": [
    { "id": "envs", "lane": "humano", "col": 1, "type": "frontend", "label": "JOB_QUEUE_RETENTION_DAYS / AUDIT_LOG_RETENTION_DAYS / AI_TELEMETRY_RETENTION_DAYS", "sublabel": "opcionais; os defaults valem sem editar .env (doutrina de packaging)" },
    { "id": "runbook", "lane": "humano", "col": 2, "type": "frontend", "label": "docs/runbooks/custo-e-cota-do-supabase.md §4", "sublabel": "comandos: medir tamanho, ver a última poda, separar 'nada vencido' de 'cron parado'" },
    { "id": "painel", "lane": "humano", "col": 3, "type": "frontend", "label": "Painel de auditoria (/admin/audit)", "sublabel": "retention.sweep_run entra no filtro por vir de AUDIT_ACTIONS" },

    { "id": "scheduler", "lane": "relogio", "col": 1, "type": "service", "label": "scheduler (crond) — 40 4 * * *", "sublabel": "docker/scheduler/entrypoint.sh; timeout 120s" },

    { "id": "rota", "lane": "borda", "col": 2, "type": "api", "label": "GET/POST /api/v1/cron/data-retention", "sublabel": "Bearer fail-closed; só relógio e laço — não decide o que é podável" },

    { "id": "politica", "lane": "regra", "col": 1, "type": "service", "label": "lib/retencao/politica.ts", "sublabel": "interpretarRetencao — lixo vira padrão COM aviso; nunca lança (env.ts derrubaria o app)" },
    { "id": "podar", "lane": "regra", "col": 2, "type": "service", "label": "podarHistorico() — lotes de 1000, teto de 20", "sublabel": "para no lote incompleto; declara fila_tem_resto quando sobrou trabalho" },
    { "id": "efeito", "lane": "regra", "col": 3, "type": "service", "label": "houveEfeito()", "sublabel": "rodada que não apagou nada não ocupa linha de auditoria" },

    { "id": "fnpoda", "lane": "banco", "col": 1, "type": "database", "label": "fn_podar_fila_de_jobs (definer)", "sublabel": "terminais done/failed/dead · piso 7d no corpo · anti-join ANTES do limit" },
    { "id": "fnexpurgo", "lane": "banco", "col": 2, "type": "database", "label": "fn_expurgar_auditoria_vencida (definer)", "sublabel": "sem seletor de linha · piso 90d no corpo · revogada de anon/authenticated" },
    { "id": "fila", "lane": "banco", "col": 3, "type": "database", "label": "job_queue", "sublabel": "pending/running NUNCA saem — têm trabalho dentro" },
    { "id": "auditlog", "lane": "banco", "col": 4, "type": "database", "label": "api_audit_log", "sublabel": "append-only no SCHEMA: anon, authenticated e service_role sem UPDATE/DELETE/TRUNCATE (0258)" },
    { "id": "cascata", "lane": "banco", "col": 5, "type": "database", "label": "send_ledger / before_send_traces", "sublabel": "on delete cascade — parte do conserto, e os consumidores falham FECHADO" },
    { "id": "avisos", "lane": "banco", "col": 6, "type": "database", "label": "agent_inbox_items (job_dead, status='open')", "sublabel": "o aviso aberto É o dono: o job que ele aponta não é podado" },

    { "id": "trilha", "lane": "volta", "col": 2, "type": "service", "label": "audit('retention.sweep_run')", "sublabel": "contagem + retenção em vigor; linha nova demais para o próprio expurgo alcançar" },
    { "id": "log", "lane": "volta", "col": 1, "type": "service", "label": "logger.warn / logger.error", "sublabel": "knob ajustado e falha de RPC saem alto — nunca a frase tranquilizadora" },
    { "id": "fnia", "lane": "banco", "col": 7, "type": "database", "label": "fn_expurgar_telemetria_de_ia_vencida / _ritmo_de_envio_vencido / _copias_enviadas_vencidas / _checkpoints_superados (definer, 0587)", "sublabel": "sem seletor de linha · piso no corpo · último envio do número, janela do anti-repetição e último checkpoint da fronteira ficam" },
    { "id": "tabelasia", "lane": "banco", "col": 8, "type": "database", "label": "llm_calls · metrics · skill_activations · ai_router_decisions · pacing_ledger · outbound_copies · lead_checkpoints", "sublabel": "append-only da IA; event_log fica FORA (idempotência dos gatilhos de tempo)" },
    { "id": "fntraco", "lane": "banco", "col": 7, "type": "database", "label": "fn_expurgar_traco_do_turno_vencido (9021)", "sublabel": "service_role · piso 100 · mesmo knob da telemetria · só idade e lote" },
    { "id": "tracos", "lane": "banco", "col": 8, "type": "database", "label": "agent_turn_traces (0721)", "sublabel": "gravado por turno; created_at define a retenção; legado pode conter erro livre" },
    { "id": "leitortraco", "lane": "borda", "col": 5, "type": "api", "label": "GET /api/v1/ai/turn-traces", "sublabel": "cookie manager+ · organização da sessão · datas UTC e instante do corte · API parcial, tela na issue3201" },
    { "id": "recibotraco", "lane": "regra", "col": 6, "type": "service", "label": "recibo-do-traco.ts", "sublabel": "projeção fechada: sem error_message, argumentos ou JSON arbitrário; códigos técnicos conhecidos" }
  ],
  "edges": [
    { "id": "e1", "from": "scheduler", "to": "rota", "label": "curl com INTERNAL_SECRET" },
    { "id": "e2", "from": "envs", "to": "politica", "label": "valor cru" },
    { "id": "e3", "from": "politica", "to": "podar", "label": "dias + aviso" },
    { "id": "e4", "from": "rota", "to": "podar", "label": "invoca a regra" },
    { "id": "e5", "from": "podar", "to": "fnpoda", "label": "rpc em lote" },
    { "id": "e6", "from": "podar", "to": "fnexpurgo", "label": "rpc em lote" },
    { "id": "e7", "from": "fnpoda", "to": "fila", "label": "delete dos terminais velhos" },
    { "id": "e8", "from": "avisos", "to": "fnpoda", "label": "aviso aberto ⇒ não pode podar" },
    { "id": "e9", "from": "fila", "to": "cascata", "label": "FK on delete cascade" },
    { "id": "e10", "from": "fnexpurgo", "to": "auditlog", "label": "delete do que passou da retenção" },
    { "id": "e11", "from": "podar", "to": "efeito", "label": "resultado" },
    { "id": "e12", "from": "efeito", "to": "trilha", "label": "só quando apagou algo" },
    { "id": "e13", "from": "trilha", "to": "auditlog", "label": "insert (a erosão fica registrada)" },
    { "id": "e14", "from": "auditlog", "to": "painel", "label": "select do admin" },
    { "id": "e15", "from": "podar", "to": "log", "label": "avisos de configuração" },
    { "id": "e16", "from": "log", "to": "runbook", "label": "o que o operador procura" },
    { "id": "e17", "from": "runbook", "to": "envs", "label": "qual alavanca puxar" },
    { "id": "e18", "from": "rota", "to": "trilha", "label": "request_id correlaciona" },
    { "id": "e19", "from": "podar", "to": "fnia", "label": "rpc em lote" },
    { "id": "e20", "from": "fnia", "to": "tabelasia", "label": "delete do que passou do prazo e já não tem leitor" },
    { "id": "e21", "from": "fila", "to": "fnia", "label": "job pending/running ⇒ o checkpoint que ele lê fica" },
    { "id": "e22", "from": "podar", "to": "fntraco", "label": "dreno em lotes com o knob existente" },
    { "id": "e23", "from": "fntraco", "to": "tracos", "label": "apaga created_at anterior ao corte" },
    { "id": "e24", "from": "fntraco", "to": "auditlog", "label": "a erosão fica registrada quando houve efeito" },
    { "id": "e25", "from": "politica", "to": "leitortraco", "label": "mesmo prazo; pedido vencido não reabre o corte" },
    { "id": "e26", "from": "tracos", "to": "leitortraco", "label": "RLS e filtro explícito de organização" },
    { "id": "e27", "from": "leitortraco", "to": "recibotraco", "label": "serializa só o recibo técnico" },
    { "id": "e28", "from": "recibotraco", "to": "runbook", "label": "contrato de API parcial documentado; sem tela alegada" }
  ]
}
