Velocidade de AI sem safety stack vira fragilidade
O aspecto mais perigoso do código gerado por AI não é ele estar sempre errado.
O mais perigoso é que muitas vezes ele parece correto o suficiente para ser mergeado.
É isso que o torna arriscado. Código obviamente quebrado é barrado. O código que parece plausível, passa em alguns happy-path tests e ao mesmo tempo enfraquece silenciosamente uma boundary importante é o que chega à produção.
Se o seu workflow é só prompt, paste, review e merge, cada aumento na velocidade de geração amplia a distância entre a rapidez com que você consegue mudar o sistema e o quanto pode confiar nessa mudança.
A solução não é mais heroísmo em code review. A solução é uma safety stack em camadas que capture diferentes classes de falha em diferentes pontos do ciclo.
Camada 1: Prevention com types, schemas e contracts
O defeito mais barato é aquele que o programa nem consegue representar.
Por isso a primeira camada é prevention.
Nessa camada, você aperta a surface area antes mesmo de o comportamento chegar ao runtime:
- Branded types e phantom types evitam misturar valores estruturalmente parecidos, mas semanticamente diferentes.
- Runtime schemas como Zod protegem as fronteiras onde dados sem tipo entram no sistema.
- Contracts definem preconditions, postconditions e invariants em torno dos caminhos críticos.
Isso importa ainda mais com AI-generated code, porque modelos conseguem produzir implementações superficialmente coerentes que ainda assim cometem category mistakes. Se seus types e schemas são fracos, o modelo tem espaço demais para estar apenas “quase certo”.
Camada 2: Verification com property-based tests
Example tests são úteis, mas fáceis demais de sofrer overfitting, tanto por humanos quanto por modelos.
Um modelo pode gerar uma função e o happy-path test correspondente. No pull request isso parece produtividade. Na prática, o comportamento continua subespecificado.
Property-based testing muda a pergunta. Em vez de perguntar se a função funciona para três exemplos, você pergunta o que precisa continuar verdadeiro para classes inteiras de inputs.
Os melhores pontos de partida geralmente são:
- round trips
- idempotência
- invariantes de ordenação
- monotonicidade
- error behavior correto para invalid input
Aqui a AI ajuda bastante. Modelos conseguem sugerir first drafts de properties a partir de uma assinatura ou doc comment. O humano continua revisando, mas o problema da página em branco fica muito menor.
Camada 3: Assessment com mutation testing
Coverage não é métrica de qualidade. É métrica de execução.
Mutation testing faz a pergunta que realmente importa: se o código mudasse de uma forma defeituosa, porém plausível, seus tests perceberiam?
É por isso que mutation testing fica acima de contracts e property tests. Ele não os substitui. Ele mede se eles realmente têm força.
Isso é especialmente importante com test suites geradas por AI. Modelos conseguem produzir tests que parecem impressionantes, executam muitas linhas e ainda assim verificam muito pouco. Mutation testing expõe essa falsa confiança rapidamente.
A abordagem prática não é rodar full mutation analysis sobre tudo o tempo todo. A abordagem prática é:
- começar pelos módulos críticos
- usar incremental mutation testing no código alterado
- fazer triage rigorosa dos survivors
- elevar thresholds conforme a suite amadurece
Na era da AI, mutation testing é o antídoto contra confiança falsa.
Camada 4: Runtime containment e recovery
Mesmo uma boa stack de verificação não captura tudo.
Por isso a camada externa é runtime containment.
É aqui que crash-only design, deadlines, circuit breakers, leases e capability-based boundaries entram. Quando algo escapa, o sistema deve falhar de forma controlada, em vez de transformar um caminho ruim em um incidente em cascata.
Para muitos times, essa camada começa pequena:
- timeouts explícitos em chamadas externas
- idempotency keys em endpoints que alteram estado
- circuit breakers diante de dependências instáveis
- capability surfaces estreitas para operações sensíveis
O objetivo não é perfeição. O objetivo é blast radius limitado.
Por que as camadas funcionam melhor juntas
Cada camada captura uma classe diferente de falha.
Types e contracts previnem invalid states óbvios. Property-based tests verificam semântica. Mutation testing verifica se os tests têm dentes. Runtime containment lida com aquilo que ainda escapa.
Essa é a ideia central: você não precisa de uma técnica perfeita. Você precisa de várias técnicas imperfeitas que falhem de forma independente.
Como é um rollout prático
A maioria dos times não deveria ligar tudo de uma vez.
A sequência mais prática costuma ser:
- endurecer boundaries em TypeScript ou Rust
- adicionar runtime schemas nos inputs externos
- introduzir contracts em funções críticas
- escrever property tests para serializers, reducers e validators
- ativar incremental mutation testing em módulos de alto risco
- adicionar runtime containment nas dependências que mais falham
Essa sequência funciona porque cada camada fortalece a próxima. Schemas melhores produzem properties melhores. Properties melhores elevam mutation scores. O feedback de mutation testing mostra onde contracts ou profundidade de teste ainda estão fracos.
A mudança real na engenharia da era AI
Os times vencedores não serão os que geram mais código. Serão os que conseguem absorver mais generated code sem reduzir a confiança.
É exatamente isso que a safety stack resolve.
Ela transforma a AI de speed amplifier em reliability amplifier. O modelo ajuda a gerar implementações, tests, contracts e rules. A stack garante que esses artefatos sejam verificados continuamente por sistemas determinísticos, em vez de aceitos apenas porque parecem bons.
Se você leva o uso de AI em production engineering a sério, esse é o padrão. Não mais uma checklist de review. Não mais um prompt dizendo “be careful”.
E sim um sistema em camadas no qual toda mudança gerada precisa sobreviver a prevention, verification, assessment e containment.
É assim que código rápido vira código confiável.