← Voltar ao blog
Relato de engenharia/ Harness engineering/ 24 de setembro de 2026

Exit Zero

Passei onze semanas construindo guardrails determinísticos em volta de agentes de código e o resto do tempo tentando provar que eles eram falsos. O teste de mutação encontrou um gate que imprimia FALHOU na tela e saía com exit 0, e ele era um dos dois gates que eu estava prestes a ligar num cliente.

00 · Os números emprestados

Trabalho com desenvolvimento de software há quase trinta anos e acompanhei o que a disciplina construiu nesse tempo em método, processo e teste. No fim de 2024 comecei a desenvolver com assistência de IA, e a escala do trabalho mudou. Em 11 de setembro, em um dos produtos que esta plataforma governa, fechei 59 commits e um diff de 207.859 linhas adicionadas.

Vale decompor esse número, porque este relato trata justamente do hábito de decompor. Dessas linhas, 184.770 são 22 snapshots de migração de ORM gerados por ferramenta. O que escrevi de fato naquele dia foram 16.783 linhas de TypeScript (7.762 de código de produção e 9.021 de teste) e cerca de 3.400 de documentação. Artefatos que nenhum humano lê inflaram a manchete em doze vezes.

Volume não indica qualidade, aderência à necessidade do negócio ou software funcionando. Os dados publicados dizem o mesmo, com mais precisão do que os slogans construídos em cima deles.

A METR fez um ensaio controlado randomizado com 16 desenvolvedores open-source experientes, em 246 tarefas reais nos repositórios maduros dos próprios participantes. Antes de começar, eles previram um ganho de 24% de velocidade; ao terminar, estimaram que tinham sido acelerados em 20%. A medição mostrou que ficaram 19% mais lentos. São cerca de 39 pontos entre o percebido e o real, na direção que deveria preocupar quem põe fluxo agêntico em produção: as pessoas mais bem posicionadas para notar o problema não o notaram.

A GitClear analisou mais de 600 milhões de commits e encontrou, de 2023 para 2026, um aumento de 81% nos blocos de código duplicados, o maior nível já registrado (73,0 por milhão de linhas alteradas). No mesmo período, o copiar-e-colar dentro do mesmo commit subiu 41%, as construções que mascaram erro subiram 47% e o churn de duas semanas subiu 15%. Os marcadores de consolidação caíram na mesma janela: chamadas de função entre arquivos −35% e movimentos de linha por refatoração −70%.

Em dois relatórios seguidos, o DORA descreve a IA como amplificador: ela aumenta as forças das organizações de alto desempenho e as disfunções das que já vão mal. Pela leitura do DORA, o retorno depende da qualidade da plataforma interna em volta da ferramenta, e a ferramenta sozinha não o explica.

Somados, esses números dizem algo mais estreito do que "IA para código é ruim": dizem que o modelo não é a variável que decide. Uma pesquisa publicada em julho de 2026 testou isso diretamente. Ben Sghaier e colegas fixaram o modelo e variaram só o harness ao longo de 35 releases sequenciais de uma CLI, contra 50 tarefas estratificadas do SWE-bench Verified, e rastrearam as oscilações de qualidade até mudanças de harness. O título do artigo resume o argumento: Don't Blame the Large Language Model.

Quem desenvolve software se preocupa o tempo todo com processo, arquitetura, design, regra de negócio e contexto, e com a forma de manter tudo isso coerente do primeiro commit até o deploy e a sustentação. Construí um SDLC de IA aos poucos, tentando seguir as práticas clássicas de Engenharia de Software: spec, design, TDD e revisão.

Hoje a plataforma cobra algumas dessas práticas. Entrega sem os canônicos obrigatórios não passa, símbolo exportado sem consumidor é reportado e SQL destrutivo nunca chega ao shell. Outras ela só ensina. TDD existe como uma skill que o agente lê, sem gate que dispare. Revisão de código é um template de CODEOWNERS que entrego a cada projeto, e o meu harness não tem como garantir que ela aconteça. O hook que restringiria push está registrado e testado, mas não está ligado a nada.

Essa lacuna é o assunto deste relato: como eu sei que os guardrails, controles, regras e hooks que estou entregando estão eles mesmos seguindo as regras que defini?

Em resumo: se o harness é o que está sob engenharia, quem testa o harness?

O que este relato é Um diário de engenharia de onze semanas de uma plataforma interna: o que construí, o que medi, o que a medição matou e onde a própria medição errou. Descrevo um método e não faço afirmação de eficácia; os limites estão declarados no §07.

01 · Onde isto se encaixa

O que a taxonomia de Böckeler deixa em aberto

Em abril de 2026, Birgitta Böckeler publicou Harness engineering for coding agent users, que deu ao campo seu vocabulário de trabalho: Agente = Modelo + Harness, em que o harness é tudo o que há num agente além do modelo. Ela divide os controles em dois eixos. Pela direção, os guias orientam antes de o agente agir (feedforward) e os sensores observam depois que ele age, para que se corrija (feedback). Pela execução, os controles computacionais são determinísticos e rápidos (linters, type checkers, hooks de pre-commit), e os inferenciais rodam num modelo e são mais lentos (agentes de revisão, juízes customizados).

Esse 2×2 é um bom mapa e não vou redesenhá-lo. Aqui interessa a lista de pontos que Böckeler deixa explicitamente em aberto no fim do artigo. Quatro deles:

  • não existe equivalente a cobertura de código para avaliar o próprio harness;
  • a coerência continua sem solução à medida que o harness cresce: como impedir que os controles se contradigam;
  • falta ferramenta para configurar e sincronizar controles ao longo do pipeline de entrega;
  • templates de harness se comportam mal em bases legadas e endividadas.

Ao ler o artigo, percebi que talvez eu tenha respondido algumas dessas perguntas. O que tenho são quatro respostas parciais, tiradas de um sistema que está rodando, e o §07 mostra até onde elas vão.

Medindo a lacuna no GitHub

Antes de escrever, varri pela API do GitHub os repositórios mais estrelados da área com uma única pergunta: o harness testa a si mesmo? Montei a amostra em 19 de agosto de 2026 com o cânone visível de harness engineering e refiz a contagem em 24 de setembro, quando os mesmos nove repositórios somavam cerca de 600 mil estrelas.

Amostra e árvores de arquivo de 19/08/2026; estrelas atualizadas em 24/09/2026, via API do GitHub. Nenhum dos nove passou a ter mutação do harness entre as duas datas. "Testes junto a hooks" conta arquivos cujo caminho tem marcador de hook e de teste, uma heurística generosa, que superestima. "Mutação do harness" é qualquer mecanismo que quebre um controle de propósito para provar que algum teste percebe.
RepositórioEstrelasArquivosTestes junto a hooksMutação do harness
obra/superpowers291.2022552nenhuma
github/spec-kit138.7766867nenhuma
ruvnet/ruflo73.2187.11734nenhuma
bmad-code-org/BMAD-METHOD53.4257850nenhuma
SuperClaude-Org/SuperClaude23.9064750nenhuma
diet103/…-infrastructure-showcase10.0291610nenhuma
buildermethods/agent-os5.444310nenhuma
disler/claude-code-hooks-mastery3.9261531nenhuma
karanb192/claude-code-hooks52423621nenhuma

O padrão aparece nas duas pontas da distribuição. O repositório mais estrelado entre os que se apresentam explicitamente como vitrine de infraestrutura (10.029 estrelas, 161 arquivos de hooks, agentes e skills) não tem nenhum teste dos próprios hooks. O mais bem testado da amostra é também o menor, por duas ordens de grandeza: o karanb192/claude-code-hooks entrega um teste unitário por plugin e é o vizinho mais próximo de tudo o que descrevo aqui. Ele tem plugins chamados config-guard ("quem guarda os guardas"), protect-tests (bloqueia o agente de desabilitar teste para ficar verde) e dead-rules-audit (pontua quais das suas regras escritas o modelo de fato segue e sinaliza as cronicamente ignoradas para promoção a hook). São os instintos certos, e o autor chegou a eles de forma independente.

Nenhum repositório da amostra quebra um controle de propósito para conferir se alguma coisa percebe, e também não encontrei isso na literatura do arXiv.

Todos esses repositórios pedem ao agente que obedeça aos controles, e nenhum deles verifica se os controles continuam funcionando.
Trabalho vizinho que vale ler Dois papers acadêmicos de 2026 chegam à mesma tese. O IACDM (Moreira) condiciona o avanço a uma máquina de estados externa ao modelo ("o agente pode pedir avanço, não concedê-lo") e aplica 19 lentes de crítica a 12 projetos; cada lente achou pelo menos um defeito que nenhuma outra achou. O Agentic Harness Engineering (Lin et al.) evolui harnesses automaticamente, pareando cada edição a uma previsão falsificável declarada, e leva o pass@1 do Terminal-Bench 2 de 69,7% para 77,0%. Nenhum dos dois valida que um controle individual dispara.

02 · Mecanismo 1 O registry

Gate que ninguém chama é só um documento

A plataforma é uma camada de SDLC interna distribuída como plugin: 22 agentes, 54 skills, 19 hooks, 33 gates, 467 arquivos markdown e cerca de 7.600 linhas de shell, construída em 140 commits entre 29 de junho e 16 de setembro de 2026. A parte que interessa aqui são os gates, que interceptam chamadas de ferramenta e fronteiras de sessão: guarda de SQL destrutivo, varredura de segredo, restrição de push, checagem de plano-primeiro, runner de lint e cobertura, checagem de canônicos, entre outros.

O primeiro problema que encontrei veio da simples existência dos gates, antes que qualquer um deles falhasse.

A documentação dizia quais gates eram obrigatórios, um catálogo à parte dizia o que cada um checava e um terceiro arquivo dizia em que nível cada um era cobrado. Os três eram prosa mantida à mão, e os três se desalinharam com o tempo, porque nada no sistema ligava a frase "este gate é obrigatório" ao fato de um processo ser invocado.

A correção foi um registry, um único arquivo legível por máquina que é a autoridade sobre quais gates existem. Toda entrada nele precisa responder a três perguntas.

// uma entrada, abreviada
{
  "runner":     "hooks/anti-destrutivo.sh",
  "nivel":      "🔴",  // obrigatório
  "inviolavel": true,
  "gatilho": {
      "tipo":  "EXEC",        // EXEC | PROSA-CMD | orfao
      "onde": ["hooks.json:PreToolUse(Bash)"]
  },
  "corpus":  "stdin JSON tool_input.command | arg $1",
  "harness_cases": [
      "anti-destrutivo/bloqueia-drop-de-tabela",
      "anti-destrutivo/bloqueia-delete-sem-where",
      … mais 10
  ]
}

O que ele roda (runner), onde está armado (gatilho.onde, um evento real num manifesto real) e o que prova que ele morde (harness_cases, casos executáveis e nomeados, ver §03). A cada commit, um checador confere o registry contra a realidade: se um runner está listado e não está armado, está armado e não está listado, ou cita casos que não existem, o commit falha.

O terceiro tipo de gatilho é o que eu defenderia com mais força. orfao quer dizer que o controle existe, está testado e hoje não está ligado a nada. Três das 33 entradas são órfãs. Elas não são bugs e ninguém as escondeu: são dormência declarada, com a razão registrada. Sem esse campo, sobraria um hook parado numa pasta, com cara de cobrado e sem nunca disparar, que é justamente o modo de falha que este arquivo existe para impedir.

33gates no registry
27armados num evento
3invocados por comando documentado
3órfãos declarados

Esta é a minha resposta direta à falta de "ferramenta para configurar e sincronizar controles" que Böckeler aponta. A solução é simples: um JSON e um script de shell que se recusa a deixar o JSON mentir, e todo o valor está nessa recusa.

A regra por baixo Skip ≠ pass: um gate nunca fica verde sem ter checado. Ferramenta ausente conta como falha, nunca como pulo, e "não se aplica" é um veredito à parte, declarado. Um verde falso é pior do que não ter gate, porque é um gate que você parou de vigiar.

03 · Mecanismo 2 O harness

237 casos, uma pasta para cada

Um gate é um script de shell que lê alguma coisa e decide, o que o torna testável como qualquer outra coisa. O formato que sobreviveu ao uso é burro de propósito: uma pasta por caso e um arquivo por asserção.

casos/anti-destrutivo/bloqueia-drop-de-tabela/
  runner         → hooks/anti-destrutivo.sh
  arg            → psql -c '<DDL destrutivo em uma tabela>'
  exit           → 2
  espera_stderr  → DDL destrutivo
  motivo         → guard-rail #1: derrubar tabela nunca passa.

Não há framework de teste, DSL de asserção nem biblioteca de mock. Um runner lê a pasta, executa o script indicado com a entrada dada e compara o código de saída e o stderr. Casos que precisam de sistema de arquivos ganham um bloco {SETUP}, e casos que precisam de stdin usam stdin.json no lugar de arg. São 237 casos em 244 pastas (sete delas são fixtures compartilhadas), e o registry cita 227 deles pelo nome a partir das entradas de gate.

Duas escolhas de projeto importam mais que o formato.

Todo caso carrega um motivo. Esse arquivo guarda o enunciado da regra que está sendo defendida, no vocabulário da própria regra, em vez de uma descrição da asserção. Quando um caso fica vermelho dois meses depois, quem o lê precisa saber se a regra mudou ou se o código quebrou, e a asserção sozinha não informa isso.

O stderr é asserido junto com o código de saída. Tem cara de preciosismo, e eu a considero a restrição mais valiosa do formato, por um motivo simples: um script que quebra num argumento inesperado também sai com código diferente de zero. Se você aferir só o código de saída, "o gate bloqueou o comando perigoso" e "o gate está quebrado" dão o mesmo verde. Sei disso porque cometi exatamente esse erro ao medir, e ele é um dos três erros descritos no §06.

Custo, porque gate lento acaba sendo driblado

A suíte completa roda no caminho de pre-commit. O motor de mutação (§04) fica fora dele porque leva minutos; é um comando de ponto limpo, que rodo depois de mexer num runner e antes de fechar release. O gate mais caro que medi roda em 1,14 s, contra um teto declarado de 60 s. Essa folga existe para o caso de travamento, e não para o caso comum.

Por que não um framework de teste O harness tem de rodar no repositório de um cliente cuja stack ele não controla: Java, .NET, PHP, Node, Bun, Python. Um formato de teste que exige runtime instalado vai ser pulado justamente no repo onde ele importa. Pasta e código de saída são a única coisa universal.
Nota de rodapé, ao vivo O comando no exemplo acima está ofuscado por um motivo literal. Enquanto eu escrevia este artigo, o guarda de SQL destrutivo da própria plataforma bloqueou a gravação do arquivo, porque o parágrafo citava o comando dentro do exemplo de caso de teste. O gate mordeu, e a mensagem de erro dele explicava a diferença entre citar e executar e oferecia um caminho alternativo. Esse é o falso positivo mais barato que existe, porque acusa, explica e mostra a saída em vez de deixar passar calado.

04 · Mecanismo 3 Mutação

Quem testa os testes

237 casos verdes provam que os gates se comportam corretamente em 237 entradas, mas não provam que algum caso perceberia se um gate parasse de funcionar. Essa segunda afirmação precisa de outro mecanismo: quebrar o runner de propósito, de forma mecânica e com uma mudança pequena por vez, e exigir que algum caso fique vermelho. Um mutante que sobrevive indica uma forma real de quebrar aquele gate que nenhum caso detecta.

A lista de operadores é curta de propósito, porque operador demais gera mutante equivalente e o ruído acaba com o mecanismo:

OperadorO que simula
exit 2 → exit 0o gate para de bloquear
exit $FALHA → exit 0a postura para de bloquear
fail=1 → fail=0a falha deixa de contar
if ! … → if …a condição perde a negação
-eq→-ne · -le→-gt · -lt→-gea comparação inverte
grep -q → grep -qvo casamento inverte
block "…" → : "…"a chamada de bloqueio é neutralizada

O operador que joguei fora

O candidato óbvio a décimo primeiro operador era && → ||, e ele foi medido e reprovado. Gerou 35 dos 51 sobreviventes da primeira rodada completa, uma proporção de ruído de 2:1, e quase todos caíram em linhas de guarda de dependência opcional, do tipo [ -f prisma/schema.prisma ] && roda …. Trocar o conectivo nessas linhas só muda o comportamento quando a dependência opcional está no estado oposto, e esse é justamente o estado que nenhum fixture tem. Um relatório que ninguém lê pertence à mesma família de falha de um gate permanentemente amarelo.

O que os mutantes acharam

Foram três classes de problema, e a primeira é a razão de este artigo existir.

Verde falso. O problema estava em duas linhas do orquestrador principal de gates: as atribuições fail=1 de dois gates específicos em postura de bloqueio. Com elas zeradas, o runner imprime ⛔ FALHOU na tela e no stderr e sai com 0. É uma mensagem de reprovação impressa em cima de uma saída de sucesso, dentro do orquestrador que todo o resto chama.

A parte que deveria preocupar você

Esses dois gates eram justamente os dois que estavam na fila para subir de warn para block num cliente, ou seja, o ramo que seria ligado em produção não tinha prova nenhuma por trás. Todos os casos que cobriam aqueles gates estavam verdes porque os exercitavam em postura de aviso, onde o fail=1 nunca é lido.

Um ledger que podia emudecer. Uma comparação invertida no hook de rastreio de commit faz uma condição de deduplicação ficar sempre verdadeira assim que o ledger tem mais de uma entrada. A partir daí, o hook para de registrar commit novo sem avisar, o gate de fechamento de sessão deixa de ver qualquer commit na sessão e a sessão fecha limpa. Esse caso só ficou testável depois que acrescentei uma costura de depuração, pela mesma razão do caso irmão: sair com 0 em silêncio é o que o hook faz quando funciona, e exit 0 sobrevive a qualquer mutação.

Um ramo sem cobertura que era obrigatório no papel. O ramo de checagem de heads de migração de banco estava marcado como obrigatório no catálogo, mas não tinha caso nem entrada na lista de sobreviventes aceitos. Era um sobrevivente silencioso, morando na fresta entre dois ledgers.

O ledger de sobreviventes aceitos e o bug dentro dele

Alguns mutantes sobrevivem legitimamente: equivalentes que mudam uma string em vez de uma comparação, ramos inalcançáveis sem ferramental específico, caminhos que dependem do ambiente. Esses vão para um arquivo de sobreviventes declarados, cada um com classe, razão e data, e o motor imprime a lista inteira a cada execução. Qualquer sobrevivente fora dessa lista reprova a execução. A dívida é permitida desde que listada; o que fica proibido é a dívida silenciosa.

Esse arquivo tinha um defeito próprio, e ele é a coisa mais instrutiva desta seção. As entradas eram chaveadas por runner + número da linha, e números de linha mudam. Medi duas vezes: em 5 de agosto, 2 de 14 entradas já estavam desancoradas, e em 15 de agosto eram 3 de 12. Uma delas apontava para um grep -q de um ramo Java que já não existia no arquivo, ou seja, era uma entrada morta guardando um número que qualquer mutante futuro podia herdar.

O modo de falha é assimétrico, e por isso precisei de duas medições para levá-lo a sério. Uma entrada deslocada reaparece como sobrevivente novo, o que gera ruído visível. Já um sobrevivente real que cai num número herdado é aceito sem aviso. A chave passou a ser runner + operador + trecho de código, o número de linha virou só documentação e o motor avisa quando reancora uma entrada. Errar para o lado do ruído é o único erro barato disponível aqui.

102mutantes mortos
2sobreviventes não declarados
11declarados, classificados, datados
237casos de harness verdes

Rodei o motor de novo para esta versão do texto, em 24 de setembro de 2026, sobre a revisão 1.27.2 e numa cópia do repositório, porque os hooks da sessão que dispara o motor rodam da própria árvore e um hook mutado passaria a valer na sessão durante a rodada. Deu 102 mortos, 11 declarados e dois sobreviventes não declarados, e os dois estão em gates que nasceram depois da primeira versão deste artigo. O gate de observabilidade, de 2 de setembro, continua verde quando o exit 2 vira exit 0. O gate de merge de PR, de 27 de agosto, continua verde quando a comparação que reconhece o timeout (-eq 124) é invertida.

Em 20 de agosto, a mesma rodada tinha dado 98 mortos e zero sobreviventes não declarados, e a árvore que o motor deixou era idêntica byte a byte à que encontrou. O zero não se manteve sozinho: os gates escritos depois dele entraram com casos que passam, e só a mutação mostrou que esses casos não percebem o gate parar de bloquear. Chegar àquele zero de agosto levou duas rodadas e acrescentou quatro casos. A primeira rodada registrou seis mutantes vivos como "pré-existentes e não relacionados" e seguiu em frente, que é o que um time faz quando o mecanismo é novo e a fila é longa. Quando olhei os seis um a um, nenhum era ruído: três eram verdes falsos e três eram âncoras mortas.

Respondendo à pergunta da cobertura Isto é o mais perto que tenho da métrica ausente que Böckeler aponta. Trata-se de uma taxa de morte sobre um conjunto fixo de operadores, e não de cobertura, o que faz dela uma afirmação mais fraca e mais honesta. Ela diz que, para estas dez formas mecânicas de quebrar um controle, alguma coisa percebe, e não diz nada sobre a décima primeira.
Trabalho anterior Teste de mutação é tecnologia dos anos 1970, e a Test Double já publicou sobre usá-lo para manter agentes de código honestos quanto aos testes de produto. O único movimento novo aqui é apontar o motor de mutação para o próprio harness. É um passo pequeno, e em boa medida esse é o ponto.

05 · Mecanismo 4 Precisão medida

A regra que teve de merecer a entrada

A régua da casa diz que regra sem sinal medido não vira gate, e que reprovar é um resultado legítimo. O candidato aqui era o export-sem-import, que acusa símbolo exportado no diff da tarefa sem nenhum consumidor. Ele torna executável uma regra que até então era só uma frase num documento de definition of done: implementado, testado e importado por ninguém é dead code, e dead code não foi entregue.

Antes de escrever o gate, escrevi uma sonda descartável e a rodei sobre o histórico de três repositórios. Registrei o limiar (precisão ≥ 70%) antes de olhar qualquer resultado.

Sonda rodada em 10/08/2026. O repo C devolveu não aplicável, e não limpo, porque é CommonJS e não tem export ES para avaliar. Registrar isso como "limpo" teria sido a mentira mais fácil da medição.
RepoCommitsAvaliaçõesLimpoIncertoAcusaTaxa sobre decidíveis
A (workspace de analytics)400471325117 (25%)277,7%
B (gateway em produção)6913211301713,1%
C (site de marketing)160———n/a

Depois triei à mão, uma a uma, as 17 acusações. Treze eram verdadeiras: o símbolo continuava parado ali, sem consumidor. Quatro eram prematuras, porque o símbolo ganhou consumidor depois, e em três dessas quatro o consumidor chegou no mesmo dia, num commit seguinte da mesma sessão. Depois de uma correção descrita abaixo, zero eram falso positivo.

O número depende do que você chama de unidade

A precisão por commit é 13/17 = 76%. Por tarefa, que é como o gate roda de verdade (aferindo o diff acumulado da sessão em vez de ir commit a commit), ela é 13/14 = 93%, porque as três acusações do mesmo dia nunca disparam. A mesma sonda sobre os mesmos dados dá duas respostas, e elas caem em lados opostos da linha que a indústria usa.

Essa linha vem do Google. O Tricorder, plataforma de análise estática da empresa, impõe um teto: um analisador exibido ao desenvolvedor tem de ficar abaixo de aproximadamente 10% de falso positivo efetivo, porque acima disso os desenvolvedores o descartam e o desligam. A taxa geral do próprio Tricorder fica pouco abaixo de 5%. Contra esse teto, este gate reprova como checagem por commit e passa como checagem por tarefa.

Taxa de falso positivo contra o teto de confiança

A mesma regra, medida sobre as mesmas 17 acusações, em duas unidades de trabalho diferentes.

Taxa de falso positivo de três analisadores contra um teto de 10% O Tricorder fica pouco abaixo de 5 por cento. O gate medido por tarefa fica em 7 por cento. Medido por commit fica em 24 por cento, mais que o dobro do teto de 10 por cento. 0% 10% 20% 30% Teto do Tricorder: 10% Tricorder (Google) Este gate, por tarefa Este gate, por commit <5% 7% 24%

O número do Google é a taxa efetiva de falso positivo publicada para os analisadores do Tricorder; os dois números do gate são o complemento de 93% e 76% de precisão sobre 17 acusações triadas à mão em dois repositórios. Com n pequeno, isto serve de apoio a decisão e não de benchmark.

Quero ser cuidadoso com a conclusão, porque dá para tirar dela uma versão barata e uma verdadeira. A barata seria "escolha a unidade que favorece". A que considero verdadeira é que a unidade de medição é um parâmetro de projeto do gate, e escolhê-la equivale a escolher o limiar. Aqui, agregar por tarefa descreve o que o gate faz de fato, e por isso não conta como truque de relatório: um símbolo que ganha consumidor no commit seguinte da mesma sessão nunca foi defeito.

Três consequências de projeto que saíram da medição

Uma classe de falso positivo que a sonda achou para mim. A versão 2 acusou três símbolos num arquivo de schema, e os três eram falsos: o consumidor faz import * as schema from "./schema" e o ORM lê o módulo inteiro, então o nome do símbolo não aparece em lugar nenhum. É a mesma cegueira do barril export *, com outra sintaxe. A versão 3 trata os dois casos do mesmo jeito: arquivo consumido por namespace vira incerto, nunca acusação. O efeito medido foi de 30 para 27 acusações, de 105 para 117 incertos e de 3 para 0 falsos positivos.

Incerto é veredito de primeira classe, e corresponde a 25% dos casos no repo grande. Ele é reportado como incompleto, nunca como passou. É a regra skip ≠ pass outra vez, e é por isso que o gate nasce em postura de aviso em vez de bloqueio.

Gate novo nasce em warn, sempre. Um gate que vai para bloqueio no primeiro dia fica vermelho o tempo todo, e em uma semana alguém negocia a retirada dele. O gate só se forma quando uma leva de trabalho real passa verde.

Quatro dias depois, o gate foi auditado contra a própria regra

A auditoria achou duas cegueiras, as duas da família que esta plataforma existe para combater: casos que o gate não sabia decidir e em que também não avisava que não soube. O parser de símbolos reconhecia function, class e const/let/var, mas export type e export interface caíam num ramo padrão e sumiam, sem virar achado nem incerto. Além disso, o corpus era git diff HEAD, que não enxerga arquivo untracked, então antes do git add a resposta era "nada a checar". As duas foram corrigidas: a primeira virou incerto declarado e a segunda ampliou o corpus.

O que o gate de fato pegou Um módulo inteiro órfão em produção: um handler HTTP servindo /metrics que nenhum arquivo importava e que, portanto, nunca era executado. Um arquivo de protocolo cujos type-guards só eram consumidos pelo próprio arquivo de teste, testados e cobertos, mas sem nenhuma chamada em código de produção; foram oito na primeira medição e nove, separados em três classes distintas, numa medição posterior e mais afiada. E dois casos em que o arquivo é importado, mas o símbolo acusado não, algo que uma checagem por arquivo perderia por construção.
Custo 3 min 02 s para varrer 400 commits num repositório de 2.377 arquivos, cerca de 0,45 s por diff. No uso normal o gate roda sobre um diff só, e não sobre quatrocentos.

06 · Achados contra si mesma

Duas configurações que nenhum código lia

Em agosto rodei a plataforma contra um levantamento de 97 achados feito por outra pessoa, que fazia dogfood de um pipeline agêntico diferente. A regra era que diferença entre repositórios não conta como defeito: cada item tinha de ser reproduzido rodando o código deste repositório, com comando e saída, ou não contava. Quatro itens passaram por esse filtro. A auditoria refutou, recaracterizou ou confirmou cada um deles e gerou seis achados novos. Por causa de dois desses seis, recomendo a quem for construir algo parecido que comece pelo registry e deixe os gates para depois.

Os níveis de enforcement eram decorativos. Um arquivo de configuração declarava, para cada gate, se ele estava em off, warn ou block. O arquivo era calibrado com clientes e estava documentado, mas até a versão 1.9.0 nenhum código o lia. A postura era editada direto no runner, e o próprio runner admitia isso num comentário. Quem pusesse um gate em block naquele arquivo acreditava ter endurecido o pipeline e não tinha mudado nada.

O arquivo de limiares mentia sobre si mesmo. Um comentário no topo do arquivo de limiares de qualidade afirmava que o gate lia dali os comandos de lint, teste e cobertura. Isso era falso, porque o runner trazia hardcoded os equivalentes do ecossistema npm. A consequência medida foi um repositório de produção 100% Bun (765 testes passando, 49 pulados, nenhum falhando, 95,51% de cobertura de linha) que ficava permanentemente vermelho e não conseguia fechar sessão. O lint falhava por falta de um arquivo de configuração do ESLint, e a análise de dependências falhava por falta de um package-lock.json. Nenhuma das duas falhas diz algo sobre a qualidade do código; o gate estava medindo o ecossistema no lugar do repositório.

Um arquivo de configuração que nenhum código lê acaba virando uma mentira com schema.

Hoje os dois arquivos são lidos por um leitor único, uma função consultada por todo hook e runner, para que a postura seja resolvida num só lugar. O leitor carrega uma allowlist e ignora qualquer chave que não esteja nela, o que impede a configuração local de um cliente de afrouxar um gate inviolável inventando uma entrada. Um mecanismo relacionado trata o problema de legado que Böckeler aponta. O repositório registra em que versão da plataforma passou pelo onboarding, e qualquer gate introduzido depois dessa versão reporta dívida datada em amarelo em vez de bloquear. Assim, um repositório já entregue não fica vermelho só porque a plataforma continuou evoluindo.

A maior parte do que medi, eu matei

Eu não esperava por isso quando comecei, e considero este o argumento mais forte a favor da disciplina: a maioria dos candidatos a regra que passaram por medição neste período não sobreviveu a ela.

Eu queria todas essas regras. Quatro das cinco reprovações vieram de medições rodadas justamente para justificar a construção.
CandidatoCorpusVeredito
Dependência fora do lockfile2 acusações, ambas verdadeirasreprovado: as acusações são verdadeiras, mas apontam drift de lock, que o npm ci já faz falhar. Pegar pacote de fato alucinado exigiria uma chamada de rede por gate.
Guarda de repetição de ferramenta1.985 transcripts · 49.712 chamadasreprovado: a repetição exata dentro da mesma sessão é 1,31% das chamadas, e assinaturas que se repetem ≥3× aparecem em 3,5% das sessões. A triagem manual de 15 casos matou a regra.
Gate anti-adulteração do harness123 commits × 6 superfíciesreprovado: um afrouxamento real em todo o histórico, zero acompanhados de código de produto e zero sem justificativa escrita.
Décimo primeiro operador de mutação51 sobreviventes no 1º runreprovado: 35 deles vinham desse único operador.
Plano de poda de carga de contexto53 skills · 6.047 charsreprovado: sub-skills aninhadas custam zero token, e as candidatas à poda eram justamente as mais referenciadas.
Gate de evidência visual1.683 commits · 7 repos · 180 diasaprovado: 405 commits (24%) tocam UI, e 5 de 7 repos têm frente de UI, de 22% a 67%. A mesma tabela levou à revisão do desenho.

O caso do anti-adulteração merece atenção à parte, porque é a refutação mais limpa que tenho. A hipótese era que um agente, ou um humano com prazo apertado, afrouxaria o harness em silêncio, baixando um limiar, apagando um caso ou enfraquecendo uma asserção. A varredura de seis superfícies em 123 commits achou exatamente um afrouxamento. Ele vinha com uma decisão humana escrita, com razão, custo aceito e data de revisão, e o mesmo commit plantou um caso de harness feito para ficar vermelho quando a regra endurecer de novo. O sinal foi zero: o único evento ligado à hipótese é exatamente o tipo de afrouxamento auditável e declarado que um gate bem desenhado teria de deixar passar.

O gate aprovado ensina na direção oposta. A mesma tabela que justificou construí-lo também destruiu o desenho original: só um dos sete repositórios declara como dirigir um navegador, então o gate, do jeito que estava especificado, responderia "não pude conferir" em toda sessão de quatro repositórios. Um aviso que se repete sem saída vira ruído que as pessoas aprendem a pular, e isso equivale a desligar o gate pela porta dos fundos. Foi essa medição que produziu a válvula de escape: um arquivo de isenção declarada, com causa, dono e prazo, que falha fechado (fail-closed) se a declaração vier pela metade.

Três vezes em que a medição errou

Num único dia de agosto, três medições produziram números confiantes e errados.

  1. Um laço de shell que iterava sobre uma lista concatenada quebrou num nome de diretório com espaço e produziu 594 hits, com a conclusão "esta regra é inviável, vai afogar o time em ruído". A contagem verdadeira era zero, e a regra estava certa.
  2. Contei exit != 0 como "bloqueou", sem considerar que um script invocado fora do manifesto sai com 1 diante de um argumento inesperado. A conclusão foi "as três formas de curinga estão bloqueadas", quando na verdade as três passavam direto.
  3. Um caminho de raiz que não foi resolvido para absoluto fez um gate de portabilidade acusar o próprio nome da plataforma, como se ele não se reconhecesse.
A lição transferível

As três sobreviveram porque o resultado era plausível. Os 594 hits confirmavam que "essa regra vai dar ruído", e o "BLOQUEADO" confirmava que "os hooks funcionam". A medição que concorda com o que você já acreditava é a que você menos confere, e foi exatamente ali que as três estavam.

Ficou uma regra prática: antes de concluir algo a partir de um número, rode a contraprova, um caso que tem de dar o resultado oposto. E um gate que bloqueia precisa imprimir o motivo, porque um código de saída diferente de zero, sozinho, pode significar apenas que o script quebrou.

Sobre medir antes de construir Cada medição acima é um documento escrito, com data, corpus, limiar declarado antes do resultado e uma afirmação explícita do que não foi medido. Esse formato faz a maior parte do trabalho. Uma medição sem limiar declarado de antemão é um argumento procurando um número.
Um achado relacionado, na camada de prompt A plataforma exige um baseline falho documentado antes que qualquer skill nova seja escrita: é preciso observar o agente falhar sem ela, ou a skill não entra. O achado vem do baseline dessa própria regra. Cinco amostras novas e independentes receberam a tarefa de escrever uma skill e de dizer o que fizeram para se certificar de que ela funcionava. Cinco de cinco nunca rodaram baseline e nunca testaram se a skill mudava comportamento. O que chamaram de verificação foi conferir se os caminhos de arquivo citados existiam e se o frontmatter batia com o das skills irmãs. Como o prompt ainda as empurrava na direção de verificar, o resultado é conservador.
A única ferramenta de terceiro medida Um indexador de grafo de código, avaliado sob a tese de que "grep acha nomes, o grafo acha arestas". Os resultados foram mediana de −70% de tool calls em 5 de 5 pares e mediana de −57% de tokens, mas com 1 de 4 pares invertendo o sinal, a n=4. Passou com ressalva declarada e não foi adotado por nada: o documento de medição não mudou um arquivo sequer da plataforma.

07 · Limites declarados

O que isto não demonstra

Pela regra da própria plataforma, dívida é permitida desde que listada, e só a dívida silenciosa é proibida. Aplicando essa regra a este relato:

Não faço afirmação de eficácia aqui, e não teria base para fazer. Não medi se times que usam esta plataforma entregam mais rápido, entregam melhor ou sequer entregam, e não há grupo de controle. Como a METR mostrou que desenvolvedores experientes erraram a própria vazão por 39 pontos, minha impressão subjetiva sobre o valor da plataforma não vale nada, e não vou oferecê-la.

O harness declara os próprios buracos, e eles existem. A cada execução, a suíte reporta três gates sem nenhum caso (sem prova de que mordem) e outros cinco com cobertura parcial, com ramo declarado e não provado. Isso sai impresso no placar em vez de ficar guardado, e a ideia é essa, mas o leitor não deve tomar 237 verdes como 33 gates plenamente provados, ainda mais com dois sobreviventes não declarados na rodada de 24 de setembro.

Todo n deste relato é pequeno: dezessete acusações triadas à mão, dois repositórios com módulos ES e cinco amostras no baseline de skill. Doze projetos já seriam um estudo pequeno, e este é menor. Os números sustentam a afirmação "este mecanismo produziu este sinal neste contexto", o que basta para justificar uma decisão de projeto e não basta para generalizar.

A taxa de morte de mutantes não mede cobertura. Dez operadores formam um conjunto pequeno de propósito, escolhido pela relação sinal-ruído. Mesmo um placar com zero sobrevivente não declarado não diria nada sobre modos de falha que esses dez operadores não conseguem expressar. O verde falso do §04 foi achado pelo fail=1 → fail=0, ou seja, só apareceu porque alguém por acaso escreveu o operador que o expressa.

Tudo aqui é a plataforma de uma pessoa, em dogfood: onze semanas, 140 commits e dois repositórios de cliente nas bordas. Os gates que mais importam, os que estão na fila para sair de warn e virar block, ainda não rodaram em postura de bloqueio para valer. Esse é o maior buraco isolado, e o verde falso do §04 mostra como ele aparece quando você o encontra antes que custe caro.

Parte disto não é inédito, e prefiro dizer isso. Ablação de skill para conferir se ela muda comportamento já é prática padrão, com ferramental público; política fora do modelo é literatura estabelecida; teste de mutação tem cinquenta anos. O que não encontrei publicado foi apontar o motor de mutação para o harness e amarrar cada controle aos casos que provam que ele dispara. Não achar nada em algumas dezenas de buscas não prova que não exista, e se alguém já fez isso, eu gostaria de ter a referência.

O que vale roubar

  1. Escreva quais dos seus controles não estão ligados a nada. Faça isso num campo do próprio arquivo que afirma que seus controles existem, e não numa lista de bugs. Três dos meus 33 estão dormentes, e saber disso vale mais que os três controles.
  2. Afira o stderr além do código de saída. Script quebrado e gate bloqueando produzem o mesmo código diferente de zero, e se você só testa o código, nem a sua suíte nem você conseguem distinguir os dois.
  3. Quebre um controle de propósito esta semana. Troque um fail=1 por fail=0, ou apague uma chamada de block, e veja se alguma coisa fica vermelha. Se nada ficar, você aprendeu a coisa mais útil deste artigo pelo preço de dez minutos.
  4. Declare o limiar antes de olhar o resultado. O meu foi precisão ≥ 70%, escrito antes de rodar a sonda. Sem isso, todo número é post hoc e toda regra sobrevive.
  5. Deixe a medição matar coisas. Cinco dos meus seis candidatos morreram. Se suas medições só aprovam o que você já planejava construir, elas viraram cerimônia, e as três medições erradas do §06 mostram como isso fica visto por dentro.
  6. Gate novo entra em postura de aviso. Um gate que bloqueia desde o primeiro dia fica vermelho o tempo todo, e até sexta alguém negocia a retirada dele.

No fim, nada disto trata de IA. É a lição mais velha da disciplina chegando a um lugar novo: uma asserção que ninguém viu falhar não merece esse nome. Passamos trinta anos aprendendo a desconfiar de código não testado e depois construímos um plano de controle para agentes autônomos com scripts de shell e JSON que ninguém testa. Eu mesmo fiz isso nas primeiras seis semanas.

O harness é código de produção: roda a cada commit, barra cada entrega e, quando falha, falha em silêncio e em verde. Trate-o como tal.

Como este artigo foi escrito

A plataforma, as medições, a triagem manual e todos os achados acima são meus. Redigi o texto com apoio de IA e depois o editei. Um artigo que defende declarar o que você fez de fato, e não o que gostaria de ter feito, não pode abrir exceção para si mesmo.

Reprodutibilidade A plataforma é proprietária e não será aberta. Tudo o que é preciso para reconstruir estes quatro mecanismos está descrito acima: os três campos obrigatórios do registry, o formato completo do caso de harness, os dez operadores de mutação na íntegra e o bug de chaveamento do ledger de sobreviventes com a correção. Nenhum deles passa de algumas centenas de linhas de shell.

08 · Fontes
Relato de engenharia · 24 de setembro de 2026 · Nomes de repositório anonimizados a pedido do autor; todos os números como medidos · Redigido com apoio de IA, editado pelo autor.
Tags:Engenharia de SoftwareAgentes de IATestes de Software

Novos artigos direto no seu e-mail.

Sem spam. Só conteúdo sobre dados, BI e decisões empresariais — quando publicamos.

Pronto para parar de esperar pelo número?

O Entendo responde qualquer pergunta sobre seus dados em 30 segundos — sem SQL, sem ticket, sem analista na fila.