Quando auditei o CI do meu próprio SaaS, testei os gates antes do código. Um deles, um limite de cobertura no front-end de 95% de linhas, 95% de funções, 90% de branches e 95% de statements, nunca tinha sido avaliado: o job de CI rodava os testes sem a flag --coverage. Corrigi o job e defini o piso em 91/93/88/91, um limite que o pipeline agora aplica de fato, então ele falha no momento em que a cobertura cai abaixo disso. O comentário no pipeline agora registra o motivo: Previously ran without --coverage = decorative.
Isso é dívida de verificação em uma linha de YAML. Escrever código ficou barato. Saber que o código está certo, não. Eu construo o MailHive com um workflow assistido por IA, então o volume de código que precisa ser verificado é alto, e uma verificação que só parece verificação é pior do que nenhuma, porque você para de se preocupar.
O Que É, de Fato, Dívida de Verificação
A Sonar chama a distância entre desconfiar do código gerado por IA e realmente verificá-lo de verification gap, e diz que essa lacuna cria o que o CTO da Amazon, Werner Vogels, chamou de verification debt, a dívida de verificação. Dívida técnica é um atalho que você sabe que tomou. Dívida de verificação é código pelo qual ninguém consegue responder. Ele pode ser bem estruturado, passar numa revisão rápida e ainda assim estar errado.
A pesquisa da Sonar com mais de 1.100 desenvolvedores, publicada em janeiro de 2026, colocou números nessa lacuna: 96% não confiam totalmente que o código gerado por IA está funcionalmente correto, e só 48% dizem que sempre o verificam antes de commitar. 38% dizem que revisá-lo dá mais trabalho do que revisar o código de um colega. A Sonar vende ferramentas de qualidade de código, então leia esses números como uma direção.
Sua Sensação de Velocidade Não É Evidência
O ensaio randomizado de 2025 do METR é o melhor alerta que conheço. 16 desenvolvedores experientes de open source trabalharam em 246 issues reais em repositórios que conheciam. Com IA liberada, levaram 19% mais tempo, e depois ainda acreditavam que a IA os tinha deixado 20% mais rápidos. O METR hoje considera esse resultado desatualizado, e o follow-up de fevereiro de 2026 diz que os dados mais novos são um sinal pouco confiável, principalmente porque os desenvolvedores passaram cada vez mais a não participar quando isso significava trabalhar sem IA. As estimativas mais novas pendem para um ganho de velocidade, com bastante incerteza. Então não cite os 19% como o estado atual das ferramentas de IA. O achado que fica é a lacuna entre percepção e medição. Se sentir rápido não prova nada.
Duas Descobertas ao Auditar Meus Próprios Guardrails
Nenhuma delas era exótica, e as duas pareciam normais num dia comum. Encontrei ambas auditando os próprios guardrails, não o código que eles protegem.
- Limites de cobertura que nunca eram avaliados. Os números estavam no vitest.config.ts e o CI não rodava cobertura. Corrigido fazendo o script de CI rodar com a cobertura ligada.
- Um linter que só rodava localmente. O ESLint rodava num hook de pre-push, que o --no-verify pula, então nenhum pipeline avaliava a regra de fronteira de módulos nem o no-explicit-any. Movi isso para um job de CI que faz o lint dos dois apps.
As duas pareciam cobertas. O que faltava era a prova de que podiam falhar.
Cobertura Diz o Que Rodou, Não o Que Foi Verificado
Mesmo um gate de cobertura funcionando responde à pergunta errada. Ele diz que uma linha executou. Não diz que um teste perceberia se aquela linha estivesse errada. Aqui vai um exemplo pequeno e ilustrativo, que não vem de nenhum codebase real, e que chega a 100% de cobertura:
export function isOverLimit(spent: number, limit: number): boolean {
return spent > limit;
}
it('blocks over the limit', () => expect(isOverLimit(150, 100)).toBe(true));
it('allows under the limit', () => expect(isOverLimit(50, 100)).toBe(false));
// Change > to >= and both tests still pass.
// Nothing checks spent === limit, which is the case the business cares about.Mutation testing encontra exatamente isso. Uma ferramenta como o Stryker, que é open source, altera seu código em pequenos pontos e roda os testes de novo. Se os testes continuam passando, aquele mutante sobreviveu e você encontrou uma verificação que não existe. No service que guarda o estado dos emails do MailHive, a primeira execução produziu uma lista precisa de mutantes sobreviventes, cada um uma mudança específica que os testes não perceberiam. Um relatório de cobertura, mesmo com um piso alto, nunca vai te dar essa lista, e cada item aponta para o próximo teste que vale a pena escrever.
Não comece transformando isso em gate de merge. Rodadas de mutação são lentas e alguns mutantes sobreviventes são inofensivos. O meu é um job manual, que também pode rodar agendado, com escopo de um único service, não consegue bloquear um merge, e o score é um sinal para ler. Amplie o escopo conforme for endurecendo cada fatia.
Teste as Próprias Proteções
Uma regra pode estar na config e configurada de um jeito que nunca reporta uma violação. Uma regra que nunca falha parece idêntica a um codebase que nunca a quebra. Por isso o job de lint do MailHive roda um canário de guardrails: um script que planta uma violação proposital por regra importante e falha se o ESLint não a reportar. Um canário cobre as regras que ele planta, então qualquer proteção fora do ESLint, como um grep por testes focados, precisa da sua própria violação plantada.
Decida o Que Cada Verificação Pode Fazer
O erro sobre o qual eu mais alertaria é tratar um revisor de IA como gate. O MailHive tem um job de revisão por IA nos merge requests que posta uma nota com os achados, e ele nunca bloqueia um merge. Isso é proposital. Verificações determinísticas podem bloquear, porque quando falham você vê por que e consegue reproduzir. A opinião de um modelo é um leitor a mais, útil e às vezes afiado, e não tem poder de veto nem a palavra final.
Essa divisão é a mesma sobre a qual escrevi para ambientes regulados: instruções são um pedido, e o pipeline é o que faz cumprir. Se você também usa Copilot ou Claude Code com instruções de projeto, mantenha a mesma linha. Instruções reduzem o código ruim que chega. Elas não verificam nada.
O Que Fazer Esta Semana
Tudo abaixo é gratuito e funciona num runner de CI comum. Faça nesta ordem, porque o primeiro passo é o que expõe os outros.
- Prove que cada gate consegue falhar. Abra uma branch descartável, quebre uma coisa por gate (um teste falhando, uma queda de cobertura, um erro de lint, um secret) e veja o pipeline ficar vermelho. Qualquer gate que continua verde é decoração. Depois automatize os que der com um canário.
- Garanta que toda verificação que importa também rode no CI. Hooks locais são uma cortesia para você mesmo. Eles podem ser pulados, por design.
- Falhe em testes focados. No Vitest, confira que o allowOnly não está ligado no CI. No Jest, adicione um grep, e plante uma violação para provar que o grep roda no diretório certo.
- Ligue a regra que você vem evitando, usando uma baseline. As bulk suppressions do ESLint deixam as violações existentes de nível error onde estão, contadas por arquivo e por regra, para que as novas falhem. Você impede a dívida de crescer hoje e paga depois.
- Rode mutation testing uma vez no seu módulo mais arriscado. Escolha o código de dinheiro, permissões ou estado, leia os sobreviventes e escreva os testes que eles apontam.
- Mantenha uma pequena suíte de smoke tests end-to-end com Playwright para os fluxos que nunca podem quebrar. Pequena o suficiente para as pessoas não começarem a ignorá-la.
- Dimensione a revisão humana pelo risco, não pelo diff. Um arquivo de teste gerado e uma mudança numa verificação de autorização não merecem a mesma atenção, mesmo com o mesmo número de linhas.
Onde os Gates Param
Mesmo quando todos os gates funcionam, cada um tem um limite. Mutation testing é lento, então eu o restrinjo primeiro às fatias de maior risco e amplio conforme elas se consolidam. Uma suíte end-to-end que precisa de secrets protegidos muitas vezes não consegue rodar em todo merge request, então decida explicitamente quais fluxos precisam passar antes de um release. E nenhum gate consegue te dizer que uma regra de negócio é a regra de negócio errada. Isso ainda exige uma pessoa que conheça o produto.
Um gate que você nunca viu falhar é uma suposição.
Referências
- Sonar. Verification gap press release, January 2026. https://www.sonarsource.com/company/press-releases/sonar-data-reveals-critical-verification-gap-in-ai-coding/
- METR. Early-2025 AI and experienced open source developers study. https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/
- METR. February 2026 update on the experiment design. https://metr.org/blog/2026-02-24-uplift-update/
- StrykerJS mutation testing. https://stryker-mutator.io/docs/stryker-js/introduction/
- ESLint bulk suppressions. https://eslint.org/docs/latest/use/suppressions
- Vitest allowOnly. https://vitest.dev/config/allowonly
- Vitest coverage configuration. https://vitest.dev/config/coverage