
A IA Traduziu 30 Anos de COBOL com Perfeição. Depois, Corrompeu o Banco de Dados.
Era uma noite de terça-feira, e eu encarava um stack trace que não fazia sentido algum.
Estávamos trabalhando com uma equipe de serviços financeiros que tentava migrar um módulo de transações central de COBOL para Java. A IA tinha feito seu trabalho — ou assim pensávamos. O código Java gerado era limpo, bem estruturado, compilava sem um único erro. Os testes unitários passaram. Todos na chamada estavam cautelosamente otimistas. Então implantaram no ambiente de teste, e a primeira transferência bancária corrompeu o banco de dados.
O bug não estava no Java. O Java estava sintaticamente perfeito. O bug estava naquilo que a IA nunca viu.
Uma variável chamada TRN-LIMIT — definida não no arquivo-fonte que a IA traduziu, mas em um COPYBOOK incluído milhares de linhas antes na cadeia de execução — continha uma cláusula REDEFINES. Trata-se de uma construção COBOL em que o mesmo endereço de memória é interpretado como dois tipos de dados diferentes dependendo de um flag definido em um módulo totalmente diferente. A IA viu TRN-LIMIT como um campo numérico simples. Não era. Era um packed decimal disfarçado de inteiro dependendo do contexto de execução. A IA alucinou uma definição padrão, e a aplicação Java escreveu dados binários corrompidos em uma coluna do banco de dados.
Naquela noite, sentado em uma sala de reunião com minha equipe dissecando o que deu errado, percebi algo que remodelaria tudo o que construímos na Veriprajna: a IA não falhou porque era burra. Ela falhou porque era cega.
O problema de US$ 1,52 trilhão sobre o qual ninguém quer falar
Eis a realidade incômoda da economia global em 2025: 43% dos sistemas bancários ainda rodam em COBOL, e esses sistemas processam 95% de todas as transações em caixas eletrônicos. O software que faz as empresas da Fortune 500 funcionarem? Cerca de 70% dele foi escrito há mais de duas décadas. Só nos EUA, a dívida técnica inchou para um valor estimado de US$ 1,52 trilhão.
E as pessoas que escreveram esse código estão se aposentando. Não é "talvez se aposentem algum dia" — elas estão saindo agora, levando décadas de conhecimento institucional com elas. Enquanto isso, 80% dos orçamentos federais de TI vão para manter os sistemas legados vivos, deixando mal 20% para qualquer coisa nova.
Já me sentei diante de CTOs que descrevem sua situação de modernização da mesma forma que você descreveria uma casa com uma fundação desmoronando: você sabe que precisa consertá-la, sabe que esperar piora as coisas, mas cada empreiteiro que tentou tornou tudo mais caro sem de fato resolver o problema.
Os números confirmam isso. Entre 70% e 80% dos projetos de modernização de sistemas legados não atingem seus objetivos. Isso já era verdade antes de a IA generativa entrar em cena.
Por que todo mundo achou que o GPT poderia resolver isso?
Eu entendo. De verdade. Quando o GPT-4 foi lançado, o mercado de consultoria de software entrou em frenesi. De repente, toda empresa tinha um "acelerador de migração COBOL" — que, se você olhasse por baixo do capô, era um wrapper fininho em torno de um modelo de fundação. Cole seu parágrafo COBOL, receba um método Java de volta. Mágica.
Meu cofundador e eu passamos semanas avaliando essas ferramentas. Alimentávamos elas com código legado real de ambientes de clientes e conferíamos o resultado. A sintaxe estava quase sempre correta. O código compilava. E então falhava de maneiras incrivelmente difíceis de diagnosticar, porque a forma do código parecia certa mesmo quando o significado estava errado.
O bug mais perigoso não é aquele que derruba seu sistema. É aquele que corrompe silenciosamente seus dados por seis meses antes de alguém perceber.
O problema é arquitetural, e se resume a como os grandes modelos de linguagem processam informação. Os LLMs usam um mecanismo de atenção para ponderar a importância de diferentes partes de sua entrada. Os modelos modernos ostentam janelas de contexto de até um milhão de tokens. Mas pesquisas demonstraram um fenômeno chamado efeito "Perdido no Meio": os LLMs exibem uma curva de desempenho em formato de U, recuperando bem as informações no início e no fim de um prompt, mas degradando significativamente para qualquer coisa no meio.
Em um projeto de modernização, um único programa COBOL pode ter milhares de linhas, referenciando copybooks que, por sua vez, também têm milhares de linhas. Se a definição de MAX-TRANSACTION-LIMIT estiver no meio desse contexto massivo, é estatisticamente provável que a IA a ignore. E quando ela ignora algo, não para e pergunta. Ela alucina. Inventa uma definição plausível e segue em frente.
O que acontece quando você trata código como texto?

Esse é o erro central que vejo todo o ecossistema de "wrappers de IA" cometendo, e é a discussão que eu tinha repetidamente com um potencial investidor no início. Ele olhou para a nossa abordagem — construir grafos de conhecimento de repositórios de código — e disse: "Por que não usar simplesmente uma janela de contexto maior? O GPT-5 vai resolver isso."
Abri um programa COBOL no meu laptop. "Encontre para mim a definição de ACCOUNT-BALANCE," eu disse.
Ele procurou no arquivo. Não conseguiu encontrar. Porque não estava naquele arquivo. Estava em um copybook, incluído por meio de uma instrução na linha 47, que por sua vez referenciava uma data division compartilhada mantida por uma equipe completamente diferente.
"Agora imagine que você é um LLM", eu disse. "Você está fazendo uma busca por similaridade de vetores por código relacionado a 'processamento de pagamentos'. Você vai encontrar cinco trechos que mencionam a palavra 'pagamento'. Vai deixar passar completamente o arquivo chamado GlobalVarDef.cbl que define a alíquota de imposto usada pela lógica de pagamento — porque esse arquivo nunca menciona a palavra 'pagamento' em lugar nenhum."
A geração aumentada por recuperação padrão, ou RAG — a técnica que a maioria das ferramentas de IA para código usa para adicionar conhecimento aos LLMs — recupera contexto com base em similaridade textual. Ela converte código em vetores e encontra vetores similares. Isso funciona lindamente para chatbots de FAQ. É catastroficamente insuficiente para código.
Código não é linguagem natural. "O gato sentou no tapete" significa mais ou menos a mesma coisa independentemente do que você leu cinquenta páginas atrás. Mas x = y + 1 significa nada a menos que você conheça as definições, os tipos e os estados atuais de x e y — que podem estar definidos em um arquivo diferente, em um módulo diferente ou herdados de uma classe pai.
Escrevi em profundidade sobre esse problema estrutural na versão interativa da nossa pesquisa. A versão curta: software não é texto. Software é um grafo.
A noite em que paramos de construir um wrapper melhor
Houve um momento — lembro-me dele com clareza — em que minha equipe debatia nossa arquitetura. Tínhamos dois caminhos. Caminho um: construir um pipeline de RAG mais inteligente. Melhor fragmentação, melhores embeddings, melhores prompts. Iterar na abordagem de wrapper até que funcionasse bem o suficiente. Caminho dois: descartar por completo o paradigma baseado em texto e tratar código como aquilo que ele realmente é — um sistema relacional de lógica.
O caminho um era mais rápido. O caminho um era o que os investidores entendiam. O caminho um tinha uma dúzia de concorrentes já comprovando a demanda de mercado.
Minha engenheira principal foi até um quadro branco e desenhou um programa COBOL como um grafo. Nós para variáveis, funções, copybooks, tabelas de banco de dados. Arestas para CALLS, READS, UPDATES_TABLE, IMPORTS_COPYBOOK. Então ela traçou uma cadeia de dependências: o Módulo A chama o Módulo B, que modifica a Variável X, que é lida pelo Módulo C em um diretório completamente diferente.
"Peça a uma busca vetorial para encontrar essa cadeia", ela disse.
Ninguém conseguiu.
Aquela foi a noite em que nos comprometemos a construir o que hoje chamamos de Grafo de Conhecimento Ciente do Repositório — um banco de dados de grafo unificado que combina a estrutura estática do código (árvores de sintaxe abstrata, grafos de chamadas) com o significado semântico da lógica de negócio (documentação, comentários, intenção das variáveis). Não íamos construir um tradutor melhor. Íamos construir um mapa.
Como transformar trinta anos de COBOL em um mapa?

O processo tem quatro fases, e vou poupá-lo dos detalhes de implementação — você pode encontrá-los no nosso mergulho técnico completo. Mas os conceitos importam, porque explicam por que essa abordagem funciona onde os wrappers falham.
Primeiro, analisamos o código de forma estrutural, não textual. Os pipelines de RAG padrão usam "divisão ingênua" — eles cortam um arquivo a cada 500 tokens, muitas vezes separando a assinatura de uma função do seu corpo. Usamos parsers como o Tree-sitter para gerar Árvores de Sintaxe Abstrata, que respeitam os limites lógicos do código. Uma função é tratada como uma unidade completa de lógica, não como um trecho aleatório de texto.
Segundo, extraímos entidades e relacionamentos. Classes, parágrafos, variáveis, tabelas de banco de dados, endpoints de API — estes se tornam nós. As arestas entre eles — CALLS, UPDATES_TABLE, DEFINES_VARIABLE — tornam-se o tecido conectivo. Agora podemos consultar o grafo: "Mostre-me cada parágrafo que atualiza o campo CUSTOMER-ID." Resultados exatos, instantaneamente. Tente fazer isso com grep.
Terceiro — e é aqui que fica interessante — resolvemos símbolos em todo o repositório. Um parser padrão enxerga ACCT-NUM no Arquivo A e ACCT-NUM no Arquivo B como duas strings diferentes. Nosso sistema determina que ambas se referem à mesma entrada em um copybook compartilhado e as mescla em um único nó. Também mesclamos documentação com código: se um documento de requisitos em PDF descreve a "User API" e o código contém uma classe chamada UserAPI, o sistema conecta intenção com implementação.
Quarto, calculamos o fechamento transitivo. Lembra da falha do banco? A depende de B, B depende de C, e a IA viu A mas deixou passar C. Nosso grafo percorre profundamente — de A para B para C — para identificar a definição raiz de cada variável. Quando a IA gera código para o Módulo A, ela importa as definições corretas do Módulo C, mesmo que o Módulo C esteja em um diretório ou repositório inteiramente diferente.
Por que o RAG padrão falha na migração de código?
As pessoas sempre contestam isso. "RAG funciona bem para código", dirão. "É só usar embeddings melhores."
Deixe-me apresentar três cenários em que a busca por similaridade de vetores desmorona completamente:
Um desenvolvedor renomeia Account para Acct. A similaridade semântica cai, mesmo que a lógica seja idêntica. Uma função chamada FNC-001 realiza o cálculo de juros mas não contém nenhum comentário — buscar por "cálculo de juros" nunca a encontrará. E a falha mais comum: o RAG vetorial recupera um teste unitário e um comentário de UI que mencionam "pagamento", mas deixa passar a lógica de negócio central porque os nomes das variáveis não correspondem aos termos da consulta.
A recuperação baseada em grafo não pergunta "qual texto parece similar?". Ela pergunta "o que está logicamente conectado?" — e essa distinção é a diferença entre código que compila e código que funciona.
Aquilo que chamamos de GraphRAG opera sobre estrutura, não similaridade. Quando alguém pede "refatore a lógica de pagamento", o sistema usa busca vetorial para encontrar o ponto de entrada — digamos, o parágrafo ProcessPayment. Mas então, em vez de parar, ela percorre as arestas do grafo. Ela traz as sub-rotinas via arestas CALLS, as definições de variáveis via arestas READS, os copybooks via arestas INCLUDES. Esses trechos conectados podem ser textualmente dissimilares, mas são logicamente inseparáveis.
Pesquisas mostram que o GraphRAG supera significativamente o RAG vetorial em raciocínio multi-hop — conectando fatos separados por vários passos. Em software, quase todo bug sério é uma falha de raciocínio multi-hop. Se eu alterar a lógica da taxa de juros no Módulo A, quais telas de relatório no Módulo Z quebram? O RAG vetorial não consegue responder isso. O grafo consegue, porque percorre a cadeia de chamadas de função que as conecta.
O problema do GOTO (ou: por que o COBOL faz a IA alucinar loops)

Quero contar sobre um desafio técnico específico que quase nos derrotou, porque ele ilustra por que esse trabalho é muito mais difícil do que as pessoas imaginam.
O COBOL tem uma instrução GOTO. O Java não tem. GOTO permite que a execução do programa salte para qualquer lugar — para frente, para trás, para o meio de outro bloco. Ele cria o "código espaguete" sobre o qual todo professor de ciência da computação te alerta. Traduzir GOTO não é um problema de sintaxe. É um problema de topologia.
Vimos três ferramentas comerciais de IA diferentes tentarem traduzir um módulo COBOL com uso intenso de GOTO. Uma gerou uma chamada de função recursiva que teria causado um StackOverflowError em produção. Outra produziu um loop while(true) sem condição de saída. A terceira — minha favorita — simplesmente inventou um fluxo de controle que não existia no código original. Parecia plausível. Estava completamente errado.
Nossa abordagem: mapear os destinos do GOTO como arestas em um Grafo de Fluxo de Controle. Depois, usar reconhecimento de padrões no grafo. Um GOTO que salta de volta para um rótulo anterior? Isso é um loop. Um GOTO que pula um bloco? Isso é uma condicional. Um GOTO para um parágrafo de saída? Isso é uma instrução de retorno. A IA, guiada pela estrutura do grafo, refatora esses saltos em loops while, blocos if/else, ou instruções break/continue.
Sem o grafo, a IA está adivinhando. Com o grafo, ela está fazendo engenharia.
A diferença entre um chatbot e um agente
Não construímos chatbots. Preciso ser claro sobre isso, porque o mercado está inundado de ferramentas que permitem "conversar com sua base de código", e elas não são a mesma coisa.
Um chatbot pega sua pergunta, envia para o GPT-4 e retorna o que quer que venha de volta. Se a saída estiver errada, você a depura manualmente. Esse é o fluxo de trabalho de todo wrapper de IA no mercado.
O que implantamos são agentes autônomos que planejam, executam e se autocorrigem. O agente analisa a AST do arquivo COBOL alvo, identifica dependências, consulta o grafo de conhecimento, gera código Java e então o compila em um sandbox. Se o compilador lançar um erro — "variável não encontrada" — o agente lê o erro, consulta o grafo em busca da dependência faltante e regenera. Depois, executa testes unitários derivados dos traces de execução originais do COBOL para verificar a equivalência comportamental.
Esse loop de compilar-corrigir transfere o ônus da validação do humano para o sistema. Mas — e isso importa enormemente em setores regulados — o grafo de conhecimento oferece total interpretabilidade. Um desenvolvedor pode ver exatamente por que a IA tomou cada decisão: "A IA importou com.bank.logic porque encontrou uma dependência de COPYBOOK-X." Não "confie em mim, eu sou IA". Em vez disso: aqui está a cadeia de citações para esta lógica.
No setor bancário, cada linha de código precisa ser auditável. Você não pode implantar uma caixa-preta que "provavelmente" acertou. Você precisa de um sistema capaz de mostrar seu raciocínio.
E o código morto?
Uma coisa que me surpreendeu: os sistemas legados estão cheios de código que ninguém mais usa. Promoções antigas, produtos descontinuados, rotinas de depuração de 1997. Uma IA baseada em texto migra tudo o que recebe — ela não consegue distinguir código ativo de código morto.
Nosso grafo de chamadas identifica nós inalcançáveis — parágrafos ou arquivos sem arestas de entrada, ou seja, nada os chama. Sinalizamos esse código morto para exclusão antes de a migração começar. Em nossa experiência, isso normalmente reduz a base de código em 20-30%. Isso não é uma otimização menor. É eliminar um quarto do trabalho e um quarto da superfície de ataque.
"Janelas de contexto maiores não vão resolver isso?"
Ainda recebo essa pergunta constantemente. A suposição é que, se o GPT-5 ou o Claude 4 conseguirem lidar com dez milhões de tokens, o problema do "Perdido no Meio" desaparece.
Não vai. E eis o porquê.
Mesmo que a degradação da atenção melhore — e ela vai — você ainda está fazendo recuperação de texto. Você ainda está buscando strings similares em vez de percorrer conexões lógicas. Uma janela de contexto de um milhão de tokens não ajuda se a variável de que você precisa está definida em um arquivo que compartilha zero palavras-chave com o arquivo que você está traduzindo. O problema não é o tamanho da janela. O problema é que a janela está olhando para a coisa errada.
A outra objeção que ouço: "Grafos de conhecimento são caros de construir." São mesmo. Analisar um repositório inteiro, resolver símbolos, calcular o fechamento transitivo — é um investimento inicial significativo. Mas considere a alternativa. A migração manual de um grande sistema COBOL custa dezenas de milhões de dólares e leva anos. A migração com IA baseada em wrapper custa menos no início, mas gera um fluxo constante de bugs induzidos por alucinação que exigem uma depuração humana cara. A abordagem baseada em grafo tem um custo de configuração mais alto e um custo de retrabalho drasticamente menor. Dados da McKinsey sugerem que a GenAI pode reduzir as tarefas de codificação em 50%, mas apenas se implantada corretamente. Vimos melhorias de 2x a 3x na produtividade dos desenvolvedores em comparação com as ferramentas de IA padrão, especificamente porque os desenvolvedores param de gastar horas caçando onde uma variável está definida.
O mapa é o ativo
Eis o que eu gostaria de ter entendido no início: o grafo de conhecimento não é apenas uma ferramenta para migração. É um ativo permanente.
Uma vez que sua base de código existe como um grafo, ela permanece uma representação viva do seu sistema. À medida que o novo código Java evolui, o grafo se atualiza. Você obtém documentação automatizada que está sempre atualizada. Você obtém detecção de desvio arquitetural — o sistema o alerta se um novo código violar as regras de modularidade que você definiu. Você obtém análise de impacto sob demanda: "Se eu alterar este método, o que quebra?"
Modernização não é um evento único. É um ciclo de vida. As organizações que a tratam como um projeto — com uma data de início e uma data de término — são as que acabam de volta ao ponto de partida em cinco anos, afogadas em uma nova geração de dívida técnica.
Código não é texto
A lição à qual eu sempre retorno — aquela daquela noite de terça-feira encarando um stack trace — é enganosamente simples: código não é texto, e as ferramentas que o tratam como texto produzirão resultados que parecem certos e se comportam de forma errada.
Toda a economia de "wrappers de IA" é construída sobre um erro de categoria. Ela pressupõe que, como os LLMs são extraordinários no processamento de linguagem, devem ser extraordinários no processamento de código. Mas código não é linguagem. Código é um grafo — um sistema denso e interconectado de dependências, fluxos de dados e mudanças de estado que existe em múltiplas dimensões simultaneamente. Tentar modernizá-lo com ferramentas baseadas em texto é como navegar por uma cidade usando uma lista de nomes de ruas, mas sem mapa. Você ficará "perdido no meio".
Nós construímos o mapa. E ele funciona — não porque somos mais inteligentes do que as equipes que constroem modelos de fundação, mas porque fizemos uma pergunta diferente. Elas perguntaram: "Como fazemos a IA entender melhor o texto?" Nós perguntamos: "E se o problema não for texto de forma alguma?"
O futuro da modernização de sistemas legados não é um modelo de linguagem maior. É um sistema que entende software da forma como o software realmente funciona — como estrutura, não como strings.
Essa é a aposta que fizemos na Veriprajna. Todos os dias, mais uma organização descobre que seu Java gerado por IA compila lindamente e falha catastroficamente. Todos os dias, a lacuna entre a tradução sintática e a compreensão semântica fica mais cara de ignorar. As organizações que fecharem essa lacuna não vão apenas modernizar seu código. Elas finalmente vão entendê-lo — muitas delas pela primeira vez.