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:

  1. endurecer boundaries em TypeScript ou Rust
  2. adicionar runtime schemas nos inputs externos
  3. introduzir contracts em funções críticas
  4. escrever property tests para serializers, reducers e validators
  5. ativar incremental mutation testing em módulos de alto risco
  6. 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.