IA & contexto

Ontology-Driven Design: e se a ontologia virasse a rede de segurança do código gerado por IA?

Quando um agente de IA escreve o código, os testes passam, mas o negócio pode sair do eixo sem ninguém perceber: dois nomes para o mesmo conceito, uma regra contrariada, uma noção abandonada que volta. Uma ontologia extraída do código, confrontada com a intenção antes de cada desenvolvimento e depois com o código produzido, deixa essa deriva visível e separa a evolução intencional do erro.

Info

Escrito originalmente em francês. Traduzido por IA — o sentido foi preservado, não a prosa.

Capa do livro Le Contexte
Na continuidade do livro “Le Contexte”

Este artigo dá continuidade ao que escrevi no meu livro Le Contexte. Para lê-lo, baixe-o ou compre-o na Amazon. A edição da Amazon está disponível apenas em francês; se você quiser uma versão em outro idioma, entre em contato comigo.

O TDD usa os testes para orientar o desenvolvimento. E se a gente também usasse uma ontologia para preservar a coerência de negócio de um software? É o caminho que estou explorando com o Claude Code: partir do código para construir uma ontologia e, aos poucos, inverter o processo para que essa ontologia oriente, verifique e acompanhe as evoluções do software.

O código funciona. Mas ele ainda respeita o negócio?

Com agentes de IA como o Claude Code, entramos num período em que produzir código fica cada vez menos difícil.

Eu descrevo uma funcionalidade. O agente explora o código existente, altera várias classes, adiciona funções, adapta interfaces, escreve testes. Poucos minutos depois, ele me apresenta o trabalho.

O código compila. Os testes passam.

Mas uma pergunta continua no ar: esse código novo ainda respeita a lógica de negócio da minha aplicação?

Um agente pode produzir uma implementação tecnicamente correta e, ao mesmo tempo, introduzir uma incoerência no modelo de domínio. Ele pode usar dois nomes para o mesmo conceito, trazer de volta uma noção abandonada ou criar uma regra que contradiz as que já existem.

O problema fica especialmente interessante quando a aplicação cresce e os agentes de IA passam a fazer uma parte cada vez maior das alterações.

Como dar a eles um referencial de negócio comum? Como detectar uma deriva? E como evitar que esse referencial vire só mais uma documentação, que acaba não correspondendo mais ao software?

Foi a partir dessas perguntas que comecei a experimentar, nos meus projetos pessoais com o Claude Code, uma abordagem que vou chamar aqui de Ontology-Driven Design (ODD).

Para ilustrar o processo, vou usar o exemplo de um ERP fictício.

Primeira etapa: extrair a ontologia do código

Meu ponto de partida era bem simples.

Considero o código-fonte a fonte da verdade sobre o comportamento que está de fato implementado. Uma especificação descreve uma intenção; o código mostra o que a aplicação realmente faz.

Então pedi ao Claude Code que explorasse o código da minha aplicação e extraísse dele uma representação do domínio de negócio num arquivo Markdown: ontology.md.

Esse arquivo descreve, entre outras coisas:

  • os conceitos de negócio: Cliente, Pedido de venda, Orçamento, Fatura, Nota de crédito…;
  • suas propriedades e relações;
  • as regras de negócio e os invariantes;
  • os estados e suas transições;
  • o vocabulário usado para designar esses conceitos.

No nosso ERP fictício, um pedido de venda está associado a um cliente. Ele tem um ou mais itens. Ele pode ter origem num orçamento. Ele pode ser validado, cancelado ou faturado.

Essas informações não estão necessariamente descritas no mesmo lugar do código. Algumas regras são explícitas, outras estão espalhadas por várias funções.

O Claude Code consegue procurá-las e tentar reconstituí-las.

O resultado não é uma especificação perfeita. É uma interpretação estruturada do negócio a partir da sua implementação, com os limites que isso implica: uma regra implícita pode escapar, e um comportamento histórico pode ser tomado, por engano, por uma regra intencional.

Mas eu obtenho algo interessante: uma representação do domínio que não depende dos detalhes técnicos da implementação.

O arquivo ontology.md vira um ponto de referência para entender o software.

Segunda etapa: verificar o código novo em relação à ontologia

Com essa ontologia em mãos, pedi ao Claude Code que a usasse sempre que desenvolvesse novas funcionalidades.

A ideia era comparar as alterações feitas com os conceitos e as regras do ontology.md.

Imagine, por exemplo, que a ontologia diga que uma fatura validada não pode mais ser alterada.

Se o Claude Code adicionar uma funcionalidade que permite mudar os itens dela depois da validação, ele precisa apontar essa contradição.

Da mesma forma, se a ontologia distingue claramente um orçamento de um pedido, o agente não deve introduzir uma função que mistura as responsabilidades desses dois conceitos sem um motivo explícito.

É uma primeira rede de segurança.

Mas ela tem um defeito.

A verificação acontece depois que o código foi produzido.

O agente já fez o trabalho dele. Talvez tenha criado várias classes, adicionado métodos e montado testes em cima de uma interpretação errada do negócio.

A gente consegue detectar o problema, mas depois precisa voltar ao código produzido.

Em outras palavras, eu tinha acrescentado uma verificação a posteriori. É útil, mas não basta.

Terceira etapa: tomar o Test-Driven Development como inspiração

Foi aí que pensei no TDD.

O princípio do Test-Driven Development é conhecido: antes de implementar um comportamento, você escreve um teste que expressa o que espera. O teste falha, você escreve o código necessário para ele passar e depois melhora a implementação mantendo essa rede de segurança.

Por que não aplicar uma lógica parecida à ontologia?

Então pedi ao Claude Code que mudasse o processo dele.

Antes de codificar, ele precisa primeiro confrontar a funcionalidade pedida com a ontologia existente.

A ordem das operações muda.

Vamos a uma solicitação nova:

"Quero permitir a criação de um pedido de venda sem passar obrigatoriamente por um orçamento."

Se a ontologia diz que um pedido de venda precisa obrigatoriamente vir de um orçamento, o agente detecta uma contradição antes de mexer no código.

Mas essa contradição não é necessariamente um erro na solicitação.

Ela pode corresponder a uma evolução perfeitamente legítima do negócio.

O agente precisa, então, reconhecer que a intenção do desenvolvedor altera uma regra existente, propor a evolução dessa regra e identificar as consequências para os conceitos relacionados.

Depois ele pode desenvolver a funcionalidade levando em conta o modelo de domínio revisado.

O processo vira:

  1. Entender a funcionalidade pedida.
  2. Identificar os conceitos e as regras da ontologia envolvidos.
  3. Apontar as possíveis contradições entre a solicitação e o modelo existente.
  4. Resolver essas contradições, distinguindo evolução intencional de erro de concepção.
  5. Desenvolver a funcionalidade.
  6. Verificar se o código produzido é coerente com o modelo de domínio esperado.

Não é TDD no sentido estrito: uma ontologia não é uma suíte de testes automatizados, e uma análise feita por LLM não oferece as mesmas garantias que uma asserção determinística.

Mas a filosofia é parecida: definir as expectativas e as restrições antes de implementar, em vez de descobrir as incoerências depois.

Nessa etapa, a ontologia começa a ter um papel ativo no desenvolvimento.

E isso faz surgir um problema novo.

Quarta etapa: uma ontologia que precisa evoluir junto com o código

Se a ontologia vira um referencial para o desenvolvimento, como mantê-la atualizada?

A última coisa que eu quero é criar mais um documento que alguém vai ter que lembrar de alterar à mão a cada evolução da aplicação.

Seria voltar ao problema inicial: uma documentação que fica dessincronizada do código.

A ideia seguinte, então, é introduzir uma parte mecânica no processo.

Um script analisa as mudanças do código-fonte, principalmente a partir do Git, e identifica os elementos adicionados, alterados ou removidos:

  • funções e métodos;
  • classes e interfaces;
  • módulos e pacotes;
  • assinaturas e contratos de API;
  • ferramentas, recursos e contratos expostos via MCP;
  • relações e comportamentos de negócio identificáveis nas alterações.

O objetivo não é reconstruir a ontologia a partir dos detalhes técnicos da aplicação.

Pelo contrário: quero continuar independente das escolhas de implementação. Uma arquitetura hexagonal, o Domain-Driven Design ou qualquer outra organização do código (mesmo bem bagunçada) não devem impor sua estrutura à ontologia.

A ontologia representa o domínio de negócio, não a arquitetura técnica.

Uma classe não é necessariamente um conceito de negócio. Um pacote novo pode ser só uma reorganização técnica. Uma função pode implementar uma regra de negócio sem ser, ela mesma, um conceito novo.

Por isso é preciso separar duas operações.

A primeira é mecânica: determinar com precisão o que mudou.

A segunda é semântica: entender se essas mudanças introduzem, alteram ou contradizem conceitos de negócio.

É aqui que o LLM entra.

O script produz um inventário das alterações. O LLM confronta esse inventário com a ontologia atual e com as diretrizes que deram origem ao desenvolvimento.

Aí várias situações podem aparecer.

Surge um conceito de negócio novo. O agente pode propor ou preparar a inclusão dele na ontologia, com suas relações e as regras associadas.

Um conceito existente muda de significado. É preciso determinar se é uma evolução intencional ou uma deriva.

Dois conceitos ficam incoerentes entre si. O agente aponta a contradição, com as referências ao código envolvido.

O vocabulário diverge. Dois nomes designam a mesma realidade de negócio, ou um nome existente passa a ser usado para outra noção.

A mudança é puramente técnica. Nenhuma alteração na ontologia é necessária.

Um ponto continua essencial: o código não deve prevalecer automaticamente.

Se um agente introduz um erro, não quero que um segundo agente altere a ontologia para tornar esse erro aceitável.

O triângulo: intenção, código e ontologia

É aqui, eu acho, que está a verdadeira evolução do conceito.

Comparar o código com a ontologia não basta.

É preciso confrontar três elementos:

A intenção: o que o desenvolvedor pediu ao Claude Code.

O código: o que foi de fato implementado.

A ontologia: os conceitos e as regras de negócio de referência.

É a comparação desses três elementos que permite entender a natureza de uma divergência.

Vamos voltar ao nosso ERP fictício.

A ontologia diz que um pedido de venda vem obrigatoriamente de um orçamento.

O desenvolvedor pede explicitamente que o orçamento passe a ser opcional.

O Claude Code altera as funções envolvidas e introduz a possibilidade de criar um pedido diretamente.

A ontologia e o código novo agora estão em contradição. Mas o código respeita a intenção.

O resultado certo, portanto, não é recusar a alteração por princípio. É propor a evolução correspondente da ontologia e, depois, procurar os outros pontos do software que ainda pressupõem que o orçamento é obrigatório.

Por outro lado, se o Claude Code tivesse removido essa obrigação sem que nenhuma diretriz pedisse isso, a divergência seria suspeita.

A diferença é fundamental.

Uma incoerência nem sempre é um bug. Ela pode sinalizar uma transformação intencional do negócio.

O papel do sistema não é impedir a mudança, e sim torná-la explícita, coerente e rastreável.

Rumo a um verdadeiro ciclo de desenvolvimento orientado pela ontologia

Agora imagino um processo que atua em três momentos.

Antes do desenvolvimento: entender o negócio

Quando recebe uma nova funcionalidade, o Claude Code identifica os conceitos da ontologia envolvidos.

Ele não precisa carregar a ontologia inteira. Pode selecionar os conceitos, as relações e as regras úteis para a solicitação.

Depois verifica se a funcionalidade é compatível com esse modelo, ou aponta as evoluções necessárias.

Assim o desenvolvimento parte de um entendimento explícito do domínio.

Durante e depois do desenvolvimento: detectar as derivas

Um analisador mecânico identifica as alterações no código-fonte.

Ferramentas como o Tree-sitter permitem, por exemplo, analisar a estrutura sintática dessas alterações e identificar os elementos alterados (é meu próximo projeto). As evoluções dos contratos de API e das interfaces MCP também informam sobre as capacidades expostas pela aplicação.

O LLM se apoia nesses elementos para procurar incoerências com a ontologia.

Essa operação poderia ser integrada ao fluxo de trabalho do Claude Code por meio de hooks, por exemplo depois de uma alteração ou no fim de uma tarefa.

Não se trata de pedir a um LLM que releia às cegas a aplicação inteira a cada mudança, e sim de concentrar a análise nos elementos impactados.

Antes da integração: verificar a coerência

Uma última verificação poderia acontecer antes do merge do código.

Ela geraria um relatório indicando:

  • os conceitos novos ou alterados;
  • as regras de negócio potencialmente impactadas;
  • as incoerências detectadas;
  • as alterações propostas na ontologia;
  • as decisões que exigem validação humana.

O objetivo é ter uma verificação reproduzível, precisa o bastante para ser útil, sem transformar cada alteração do código numa revisão interminável do modelo de domínio.

Manter o vínculo entre os conceitos e a sua implementação

Uma melhoria complementar seria manter a rastreabilidade entre a ontologia e o código.

Um conceito como Invoice poderia ser associado a várias classes, funções, eventos ou operações de API que participam da sua implementação.

Essas associações não definem o conceito. Elas indicam onde ele está implementado.

Essa distinção permitiria responder a perguntas interessantes.

Se eu alterar o conceito de fatura, quais partes do código correm o risco de ser afetadas?

Se uma função de negócio desaparecer, qual regra da ontologia pode deixar de estar implementada?

Se surgir uma nova operação de API, ela expõe um conceito existente ou revela uma noção nova?

E se uma classe mudar de nome, é um simples refactoring ou uma alteração real do domínio?

Esse mapeamento poderia ser mantido aos poucos, à medida que o código evolui.

Mesmo assim, ele deveria guardar as provas de cada associação: arquivos, símbolos e mudanças observadas. Uma relação deduzida pelo LLM não deve ser confundida com uma correspondência comprovada.

Dos invariantes da ontologia aos testes automatizados

Outro caminho é tornar executável uma parte da ontologia.

Algumas regras de negócio podem ser expressas como invariantes.

Por exemplo:

  • uma fatura validada não pode mais ser alterada;
  • uma fatura precisa ter pelo menos um item;
  • uma nota de crédito referencia a fatura que ela corrige;
  • um pedido cancelado não pode ser enviado.

Algumas dessas regras poderiam servir para gerar testes automatizados, ou para verificar se os testes existentes as cobrem.

Teríamos então dois níveis complementares.

O primeiro seria semântico: o LLM examina a coerência entre as intenções, os conceitos e o código.

O segundo seria determinístico: testes e restrições executáveis verificam os comportamentos que dá para formalizar.

A ontologia, portanto, não substituiria nem os testes unitários nem os testes de integração.

Ela poderia ajudar a explicar por que certos comportamentos precisam ser testados, e a identificar regras de negócio que ainda não têm nenhuma verificação.

Uma ontologia viva, mas não uma ontologia oportunista

Esse mecanismo traz um risco grande.

Se a ontologia muda sempre para acompanhar o código, ela deixa de ser uma rede de segurança. Vira uma simples documentação regenerada o tempo todo.

Por outro lado, se ela proíbe qualquer evolução que contradiga as regras atuais, ela impede o produto de mudar.

Por isso é preciso gerir de forma explícita as evoluções da ontologia.

Eu distingo três situações.

A evolução é intencional e clara. O sistema pode preparar uma alteração versionada da ontologia, ou até aplicá-la num branch de trabalho antes da validação.

A alteração parece incoerente com a intenção. O sistema propõe corrigir o código em vez de atualizar a ontologia.

A intenção é ambígua. O sistema pede uma decisão em vez de inventar uma nova regra de negócio.

Assim, a ontologia vira um contrato que evolui entre a intenção e a implementação.

Isso também pressupõe manter um histórico, por exemplo com um pequeno registro de decisões inspirado nos ADR. Um conceito renomeado não deve virar dois conceitos independentes. Uma regra removida precisa poder ser ligada à decisão que justificou o seu fim.

A evolução da ontologia precisa ser tão rastreável quanto a do código.

Medir a coerência em vez de contar linhas de código

Essa abordagem também poderia gerar alguns indicadores interessantes.

A cobertura da ontologia mediria a proporção dos conceitos de negócio identificados no código que têm uma representação na ontologia.

A rastreabilidade mediria quantos conceitos têm referências verificáveis para a sua implementação.

O número de incoerências confirmadas e não resolvidas daria uma ideia da deriva entre o código e o modelo.

Por fim, a proporção de invariantes associados a testes executáveis poderia ajudar a medir a solidez da rede de segurança.

Esses números não seriam indicadores de qualidade absolutos. O valor deles depende inteiramente da capacidade do sistema de separar os verdadeiros conceitos de negócio do ruído técnico.

Mas eles poderiam tornar observável algo que hoje continua em grande parte implícito: a coerência conceitual de um software ao longo das suas evoluções.

E se o processo funcionasse nos dois sentidos?

No começo, meu processo era unidirecional.

O código produzia a ontologia.

Depois acrescentei uma verificação no sentido inverso: a ontologia servia para verificar o código novo.

Inspirado no TDD, introduzi uma etapa prévia: confrontar as intenções com a ontologia antes de começar a desenvolver.

A próxima evolução é automatizar a detecção e a interpretação das mudanças, para manter essa coerência ao longo do tempo.

Mas agora vejo uma possibilidade a mais.

O código poderia fazer a ontologia evoluir, e a ontologia poderia orientar as evoluções do código.

Se uma alteração no código faz surgir um conceito de negócio novo, o sistema propõe integrá-lo à ontologia.

Se uma solicitação implica a evolução de um conceito existente, a ontologia permite identificar as regras e as implementações potencialmente envolvidas antes mesmo de começar.

O referencial deixa de ser apenas produzido pelo software. Ele participa da sua transformação.

É esse ciclo que me interessa.

Uma ideia antiga, um uso a reinventar

Seria errado apresentar o Ontology-Driven Design como uma ideia totalmente nova.

O uso de ontologias para projetar aplicações existe há muito tempo. Em 2005, Pavel Hruby já descrevia uma abordagem de Ontology-Based Domain-Driven Design.

Mais recentemente, trabalhos como o ODDToolkit exploraram a geração de artefatos de software a partir de ontologias formais.

A minha exploração é um pouco diferente.

Não busco necessariamente gerar o software a partir de uma ontologia. Também não quero impor OWL, RDF ou qualquer outro formalismo para começar.

O que busco é usar uma ontologia de negócio como referencial vivo de coerência para agentes de IA que alteram o código-fonte.

A nuance é importante.

O modelo de domínio não substitui a implementação. Ele não pretende descrever toda a complexidade de um software. Sozinho, ele não garante a ausência de bugs.

Ele oferece um quadro de referência explícito para evitar que cada agente, a cada nova tarefa, reconstrua a sua própria interpretação do negócio.

O que ainda falta demonstrar

Ainda estou numa fase de experimentação pessoal.

Comecei gerando uma ontologia a partir do código e, depois, pedindo ao Claude Code que verificasse as mudanças com base nela. Em seguida, mudei as diretrizes para levar essa verificação para antes do desenvolvimento.

O ciclo mecânico de sincronização, a rastreabilidade sistemática e as verificações automatizadas são as próximas frentes de trabalho.

Muitas perguntas continuam em aberto.

O analisador vai conseguir detectar uma alteração real de negócio quando nenhum nome de classe muda? Quantos falsos positivos o LLM vai produzir? O JEV vai conseguir atender a essa necessidade? Isso é outro projeto, que fica para um dia. Como evitar sobrecarregar a ontologia com detalhes técnicos? Com que frequência é preciso revisar as correspondências entre conceitos e código? E, principalmente, o ganho de coerência vai justificar o custo de execução e de manutenção do mecanismo?

Vai ser preciso pôr essa abordagem à prova em evoluções reais de software, com mudanças de negócio intencionais, incoerências introduzidas de propósito e refactorings puramente técnicos.

Portanto, não afirmo ter definido um novo método de desenvolvimento validado.

Descrevo uma direção que estou experimentando.

O negócio como rede de segurança

As práticas de desenvolvimento foram multiplicando as proteções aos poucos.

Os testes unitários protegem comportamentos. Os testes de integração protegem interações. A checagem de tipos e as ferramentas de análise estática detectam certas categorias de erros. As regras de arquitetura ajudam a preservar a estrutura do software.

Mas quando agentes de IA produzem uma parte cada vez maior do código, outra pergunta passa a importar:

Quem protege a coerência do modelo de domínio que esses agentes manipulam?

O DDD traz há muito tempo uma parte da resposta, com a linguagem ubíqua, os modelos de domínio e as fronteiras de contexto. A arquitetura hexagonal ajuda a isolar o negócio das preocupações técnicas.

O Ontology-Driven Design que estou explorando tentaria acrescentar a esses princípios um mecanismo operacional: tornar o modelo de domínio explícito, confrontá-lo com as intenções de desenvolvimento e verificar continuamente a sua coerência com o código.

No fundo, meu objetivo não é produzir mais documentação.

É permitir que o Claude Code desenvolva mais rápido sem reinventar o negócio em silêncio a cada funcionalidade.

O código continua sendo a verdade do comportamento implementado. A ontologia vira a referência do sentido de negócio. A intenção explica por que esse sentido pode mudar.

E é do confronto entre os três que pode nascer uma nova rede de segurança para o desenvolvimento assistido por IA.