Como fazer uma IA corrigir falhas de dbt e abrir um PR em rascunho sem dar o controle a ela.
Por Ricardo Ribeiro · Código aberto (Apache-2.0): github.com/ricardoribs/pipeline-guard
São 3 da manhã e um dbt build falha. Alguém acorda, abre o log, lê 400 linhas e descobre que o problema era uma única linha de SQL. A correção leva dois minutos. Achar a causa levou uma hora.
Foi essa hora que eu quis recuperar. Construí o Pipeline Guard, uma ferramenta de código aberto que lê a falha, diagnostica a causa com evidência que dá para conferir, corrige em um ambiente isolado, testa a correção e abre um pull request em rascunho (draft). Uma pessoa revisa e decide.
Neste artigo explico a ideia por trás dele, listo tudo o que ele entrega, mostro como cada etapa funciona e apresento dois casos reais de teste em sandbox.
O problema
Um pipeline de dados é uma sequência de etapas que transforma dados brutos em tabelas prontas para análise. O dbt é a ferramenta que cuida de boa parte dessas transformações, escritas em SQL. Quando uma etapa falha, o time precisa descobrir o que quebrou, onde e por quê.
A reação óbvia é deixar um modelo de linguagem consertar tudo sozinho. O risco é grande: o modelo pode errar a causa, inventar uma explicação convincente, editar um arquivo que não devia ou expor dados e credenciais no meio do caminho. Em dados, um erro silencioso custa mais que uma falha barulhenta.
A pergunta que guiou o projeto foi esta: como aproveitar o modelo sem entregar o controle a ele?
A ideia: separar quem raciocina de quem decide
A resposta foi dividir o trabalho em dois lados.
- O modelo raciocina. Ele lê as evidências, classifica a falha e propõe uma correção.
- O código determinístico decide. Determinístico quer dizer que, para a mesma entrada, a resposta é sempre a mesma, sem improviso. É esse código que valida cada citação do modelo, aplica as regras de elegibilidade, confere a autorização e controla tudo o que pode acontecer no mundo real.
Em uma frase: o modelo propõe, o código decide.
O modelo, por exemplo, não tem nenhuma operação para aprovar um PR, fazer merge, fazer deploy ou rodar de novo o pipeline que falhou. Isso não depende de ele se comportar bem: esse caminho simplesmente não existe no código.
O que o Pipeline Guard entrega
A cada falha, ele entrega um pacote completo e verificável, não só uma resposta de chat. Estas são as dez entregas, na ordem em que acontecem:
- Validação do incidente e da configuração. Antes de coletar qualquer evidência, ele confere se o incidente e o arquivo de configuração do projeto seguem o contrato. Se algo está inválido, a execução para com um código de saída próprio.
- Coleta de evidência somente leitura. Um adaptador de uma lista fechada (hoje
dbtemock) coleta só o que o incidente declarou. No dbt, são os arquivosrun_results.jsonemanifest.json, com checagem de hash. - Sanitização antes do modelo. Campos sensíveis, cabeçalhos de autenticação, cookies, chaves privadas, e-mails, URLs assinadas e formatos conhecidos de token são mascarados antes de qualquer envio ao modelo ou gravação em disco.
- Diagnóstico estruturado com citações. O resultado segue um contrato versionado (
pipeline_diagnosis_v1): causa, família da falha, confiança, status da causa, impacto e as evidências citadas. Se a evidência for insuficiente ou conflitante, a causa viraUNKNOWN, em vez de um palpite. - Decisão de elegibilidade. Código determinístico, e não o modelo, diz se a falha pode seguir para correção.
- Relatório neutro. Saída em JSON (para máquinas) e em Markdown (para pessoas), sem depender de Slack, Airflow, Git ou warehouse.
- Artefatos imutáveis e manifesto. Cada execução guarda resultado, diagnóstico, decisão e relatório, mais um manifesto com status final, versões de contrato, hashes de evidência, horários e duração. Repetir o mesmo incidente reaproveita o resultado final em vez de criar uma duplicata conflitante.
- Correção mínima em workspace isolado. Com autorização verificada, um agente de correção edita só o necessário, dentro de limites de caminho, extensão, número de arquivos e linhas.
- Validação com os comandos do seu projeto. Roda só os comandos que o projeto declarou (no dbt,
depsebuild) e confere o hash do patch. - Push restrito e PR em rascunho. Envia só a branch do incidente, com um refspec estrito, revalida o commit enviado e cria ou reaproveita um PR em rascunho no Bitbucket Cloud ou no GitHub. A descrição traz o incidente, o diagnóstico, as evidências, os arquivos alterados e o resultado dos testes.
O resultado final é versionado e legível por máquina, e vem com códigos de saída estáveis, que qualquer orquestrador sabe interpretar:
| Código de saída | Significado |
|---|---|
| 0 | Diagnóstico validado e com evidência concluído |
| 1 | Falha operacional |
| 2 | Incidente ou configuração do projeto inválidos |
| 3 | Parada segura com causa UNKNOWN: evidência insuficiente ou limite atingido |
O código 3 é um resultado controlado. A ferramenta prefere parar a inferir uma causa que não consegue provar.
Como funciona, passo a passo
- Incidente normalizado. A falha entra em um formato padrão: qual pipeline, qual etapa, qual ambiente, qual commit.
- Evidência rastreável. O adaptador do dbt lê os arquivos
run_results.jsonemanifest.jsonque o próprio dbt gera e confere se os hashes (a impressão digital de cada arquivo) batem com o que o incidente declarou. Cada evidência recebe um identificador e passa por sanitização antes do diagnóstico. - Diagnóstico sem ferramentas. O agente só classifica a falha. Ele não executa nada. Cada citação que ele faz é validada contra as evidências coletadas.
- Elegibilidade. Uma regra fixa decide se o caso pode seguir para correção. Hoje, só falhas do tipo CODE (erro no código), com autorização verificada e ligada ao incidente, ao diagnóstico e ao commit de base.
- Correção em área isolada. Um workspace separado, editando só os caminhos permitidos pela configuração do projeto e rodando só os comandos de validação que o projeto declarou.
- PR em rascunho. Se tudo passou, a ferramenta cria (ou reaproveita) um pull request em rascunho. Quem aprova e faz o merge é uma pessoa.
Dois modos de uso
O mesmo núcleo atende quem investiga uma falha sozinho e quem quer o reparo rodando dentro do orquestrador.
Interativo, no Claude Code
O Pipeline Guard vem como um plugin com quatro skills (conjuntos de instruções que o Claude ativa conforme o que você pede):
pipeline-guard-setup: instala e configura o plugin em um projeto, gerando e validando o arquivo de configuração a partir de um preset.diagnose: investiga uma falha e devolve diagnóstico validado, decisão de elegibilidade e relatório neutro.remediate: aplica uma correção autorizada, valida o patch e envia a branch do incidente.publish-draft-pr: publica ou reaproveita o PR em rascunho depois que a branch foi enviada.
Você pede em linguagem natural, por exemplo: Diagnostique o incidente descrito em ./incidents/failed-run.json. O pedido passa só o caminho do arquivo, nunca o log colado no prompt. Um gancho que falha de forma fechada bloqueia prompts de diagnóstico que contenham dados sensíveis reconhecidos antes de o Claude processá-los.
Sem supervisão, em qualquer orquestrador
Para rodar de madrugada, sem ninguém para responder perguntas, existe um comando único e neutro quanto ao host:
uv run scripts/unattended_cli.py \
--mode unattended \
--config ./config/pipeline-guard.yaml \
--project-root . \
--output-dir /retained/pipeline-guard/output \
--authorization-state-dir /retained/pipeline-guard/authorization-state \
./incident.json
Nesse modo:
- nada é perguntado nem confirmado: a autorização vem de uma política versionada do projeto e de uma identidade de serviço;
- o trabalho acontece em um workspace isolado e gerenciado, que é preservado limpo quando a publicação precisa ser retomada com segurança;
- a saída é um resultado versionado, legível por máquina, com um resumo para pessoas e códigos de saída estáveis;
- o PR em rascunho sai pela própria fronteira de publicação do Pipeline Guard, sem depender de outro plugin;
- há guias para Databricks Jobs, Bitbucket Pipelines e Airflow, sem lógica específica de cada host embutida no código;
- um Dockerfile prepara a execução em contêiner, com usuário sem privilégios e versões fixas das dependências.
Qualquer modelo, configuração por projeto
O Pipeline Guard não prende você a um fornecedor nem a um tipo de projeto.
- Seis perfis de modelo:
anthropic(o padrão),databricks,openai,gemini,ollamaeopenai-compatible. Isso inclui rodar com um modelo local, sem enviar nada para a nuvem. Para o agente que escreve a correção, o endpoint precisa oferecer chamadas de função no formato do Chat Completions. - Ordem de escolha clara: primeiro os argumentos da linha de comando, depois o bloco
runtime.modeldo arquivo de configuração, depois as variáveis de ambiente. - Credenciais por referência: o arquivo aponta para a variável de ambiente, nunca guarda a chave em texto.
runtime:
model:
provider: ollama
name: llama3.3
base_url: http://127.0.0.1:11434/v1
- Presets de projeto:
genericedbt. A skill de setup gera oconfig/pipeline-guard.yamla partir de um deles e valida o resultado. - Política da correção no seu arquivo: você define quais caminhos e extensões podem ser editados, quantos arquivos e linhas no máximo e quais comandos de validação rodam.
- Adaptadores com contrato único: todo adaptador segue o mesmo contrato (
pipeline_adapter_interface), o registro é fechado e os detalhes de cada ferramenta ficam isolados emadapter_details.<nome>. Hoje hádbtemock(este para validar a instalação sem depender de nada externo). - Duas plataformas de PR: Bitbucket Cloud e GitHub.
As travas de segurança
- Diagnóstico sem ferramentas. O agente que diagnostica não lê arquivos, não roda comandos, não acessa rede e não altera o projeto. Só enxerga o incidente validado e as evidências coletadas.
- Toda afirmação cita evidência. Cada ponto relevante do diagnóstico precisa apontar para um identificador de evidência que existe, e um validador externo confere o contrato e as citações.
- Elegibilidade por código. Quem decide se a falha pode seguir para correção é código determinístico, nunca o modelo.
- Autorização amarrada. Ela vale para um incidente, um hash de diagnóstico e um commit de base. Se qualquer um dos três mudar, deixa de valer.
- Agente de correção contido. Recebe só o contexto necessário, usa ferramentas restritas de leitura, escrita e edição e não tem acesso a credenciais de Git ou do provedor. Não publica, não faz merge e não faz deploy. A etapa que escreve a correção se recusa a rodar com privilégio de administrador.
- Redação de dados sensíveis. Antes de chegar ao modelo ou ser gravado, o texto passa por um filtro que mascara campos sensíveis, cabeçalhos de autenticação, cookies, identificadores de sessão, chaves privadas, e-mails, parâmetros de URL assinada, assinaturas de acesso do Azure e formatos conhecidos de token (como os da Atlassian e da DigitalOcean). Os rótulos são reconhecidos em português e em inglês.
- Credenciais só por variável de ambiente. Nunca aparecem em prompt, argumento de comando, log ou resultado.
- Orçamento de execução. Há limite de chamadas de ferramenta, de tempo e de tokens. Execução inválida, com evidência insuficiente ou conflitante, ou sem orçamento, para com segurança.
- Escrita só onde deve. O diagnóstico não escreve em projeto, Git, warehouse nem deploy. A correção só escreve no workspace isolado, envia só a branch autorizada e publica só rascunho, depois de revalidar tudo.
- Publicador estreito. O componente que cria o PR só sabe localizar, criar, reaproveitar ou fechar o rascunho que o próprio Pipeline Guard produziu. Aprovar, revisar, fazer merge ou administrar o repositório: nada disso existe nele.
Dois casos reais de teste
Os dois casos abaixo rodaram em um ambiente de teste (sandbox) no Databricks Jobs, a partir de cenários de referência: falhas plantadas de propósito para exercitar o caminho completo. Não são incidentes de produção. Os PRs foram publicados em um repositório Bitbucket.
Caso 1: o teste invertido
- Falha: o
dbt buildterminou com código de saída 1 na etapadbt_build. - Diagnóstico: um teste falhou porque retornou um resultado quando estava configurado para esperar zero. Família da falha: CODE. Confiança: alta. Causa: confirmada.
- Evidência: o registro de evidência do dbt informava um registro com falha.
- Correção: um arquivo alterado,
tests/scenario_singular_inverted.sql, com 1 linha adicionada e 1 removida. - Validação:
depspassou em 3,9 s ebuildpassou em 5,1 s. Status: todas aprovadas. - Resultado: o PR #20 foi criado como rascunho pelo publicador do Pipeline Guard. As duas pipelines do próprio repositório passaram. Eu revisei e fiz o merge na branch
dev.
Caso 2: a coluna que sumiu
- Falha: o mesmo tipo de erro, na mesma etapa, mas por outro motivo.
- Diagnóstico: a transformação usava um campo que não estava disponível na hora da execução. Família: CODE. Confiança: alta. Causa: confirmada.
- Evidência: a evidência de execução informava que um campo referenciado estava indisponível.
- Correção: um arquivo,
models/scenarios/scenario_missing_column.sql, com 1 linha adicionada e 1 removida. - Validação:
depsem 4,3 s ebuildem 5,2 s, todas aprovadas. - Resultado: o PR #21, também criado como rascunho, revisado e com merge feito por mim.
Um detalhe de que gosto: o commit de base do segundo incidente é exatamente o commit do merge do primeiro. O ciclo funciona em cadeia: a correção aprovada de um caso vira a base do seguinte.
O que os dois casos mostram
| Caso | Tipo da falha | Diagnóstico | Arquivos alterados | Validação | Decisão final |
|---|---|---|---|---|---|
| 1. Teste invertido | CODE | Correto, confiança alta | 1 (1 linha mudou) | Todas aprovadas (cerca de 9 s) | Merge por pessoa |
| 2. Coluna ausente | CODE | Correto, confiança alta | 1 (1 linha mudou) | Todas aprovadas (cerca de 9,5 s) | Merge por pessoa |
Os dois casos percorreram o caminho completo, de ponta a ponta: da falha ao rascunho revisável, sem nenhuma etapa manual no meio, exceto a que deve ser manual, a decisão final.
O que aprendi
- Controle vale mais que inteligência. O ganho não veio de um modelo melhor, e sim de limitar o que o modelo consegue fazer.
- Evidência que se confere muda a conversa. Quando cada afirmação do diagnóstico aponta para um arquivo com hash, revisar fica rápido e o erro do modelo fica fácil de ver.
- O rascunho é a interface certa. Um PR em rascunho dá ao time o que ele já sabe usar: revisão, histórico e testes automáticos. Ninguém precisa aprender uma ferramenta nova.
- Parar é uma resposta válida. Quando a evidência é insuficiente ou conflitante, o diagnóstico vira UNKNOWN e a execução para, em vez de chutar uma causa. Um sistema que sabe dizer que não sabe é mais fácil de confiar.
Teste você mesmo
O código está em github.com/ricardoribs/pipeline-guard, com licença Apache-2.0. Se você usa dbt, o que mais me ajudaria é:
- rodar em um projeto de teste e abrir uma issue com o que quebrou;
- contar qual tipo de falha seu time mais enfrenta, para eu priorizar o próximo adaptador;
- sugerir ou testar o cenário do Airflow.
Estou construindo na área de engenharia de dados com IA generativa e aberto a conversar sobre oportunidades. Pode me encontrar no LinkedIn.