Pitch: Tratei identidade de dados como o Git trata conteúdo, num motor de inteligência corporativa
Construí de ponta a ponta o OSINT Engine, um motor que expande um CNPJ em todos os seus dados públicos a a partir de registros públicos oficiais. A API está no ar para testar, e o código é aberto (links no fim). Quero compartilhar a decisão arquitetural de que mais me orgulho, porque ela resolve um problema real com uma ideia simples.
O problema
O sistema consome várias fontes públicas e reprocessa as mesmas fontes o tempo todo. Buscar o mesmo CNPJ de novo não pode duplicar nós, não pode corromper arestas, não pode inflar o grafo. E o setor exige rastreabilidade: cada dado precisa apontar para a origem.
A ideia central: identidade content addressable, como no Git
Cada entidade do domínio deriva a identidade de forma determinística, via uuid5 sobre os próprios campos, com um namespace por tipo de entidade. Nada de id autoincrementado, nada de id gerado pela camada de persistência. A identidade é uma função pura dos dados.
Mas existem duas identidades, e essa é a parte sutil:
content_id:uuid5sobre todos os campos observados. Captura o snapshot exato. Duas buscas que retornam conteúdo idêntico produzem o mesmocontent_id, e o sistema reconhece a repetição em tempo constante, sem comparar campo a campo.id:uuid5apenas sobre os campos de identidade, após uma normalização mínima. Para uma empresa, o campo de identidade é o CNPJ, normalizado para só dígitos. Então00.000.000/0001-91e00000000000191(CNPJ do Banco do Brasil, você pode testar o MVP com ele) colapsam na mesma identidade semântica. É o mesmo nó do mundo real, independente de formatação ou de dados periféricos que mudaram entre uma coleta e outra.
Isso dá deduplicação em dois níveis. O id responde se é a mesma entidade e mantém o grafo sem duplicatas. O content_id responde se é a mesma observação exata e dá o short circuit de reprocessamento em O(1).
Determinismo imposto, não torcido
Na construção da entidade, cada valor que entra no cálculo do id passa por uma validação de tipo determinístico. Se um campo tem um tipo que não hasheia de forma reproduzível, a construção falha ali, na hora. A garantia de identidade estável vira uma invariante do domínio, checada, e não um efeito colateral que se espera dar certo.
Domínio imutável, banco append-only
As entidades são imutáveis e congeladas, iguais por id e hasheáveis por id. O domínio não conhece tempo. Sacrifico de propósito a normalização relacional tradicional: em vez de espalhar os dados em tabelas normalizadas, guardo snapshots temporais completamente auditáveis.
A consequência é elegante: o banco nunca sofre um UPDATE. Você insere ou remove nós e arestas, jamais os atualiza. Cada versão distinta de cada nó fica retida, indexada pelo content_id. Isso elimina uma classe inteira de condições de corrida no acesso concorrente e transforma o grafo num registro append-only.
Tempo e auditoria vivem numa camada de aplicação, num envelope chamado EntityRevision, que embrulha a entidade pura com os instantes de coleta e de merge, ambos em UTC obrigatório. O domínio permanece atemporal, a aplicação cuida da linha do tempo.
Reconciliação explícita
A reconciliação de revisões é explícita, isolada em policies:
- Merge: se o
content_iddas duas revisões é igual, há short circuit e devolve a mais nova. Caso contrário, a revisão mais recente por instante de coleta vence campo a campo, e a mais antiga apenas preenche os campos que a nova deixou nulos. Nunca perco dado, apenas priorizo o mais fresco. - Seleção: a revisão atual é a de coleta mais recente, com o instante de merge desempatando a favor de uma revisão derivada de merge sobre uma busca crua.
Orquestração de fontes e postura de privacidade
O motor orquestra múltiplas APIs públicas oficiais, cada uma com o seu próprio limite de requisição e a sua própria credencial, criptografada em repouso. Elas são compartilhadas com segurança entre os visitantes da demo, de modo que um usuário de teste nunca esgote o limite do outro. Há proteção contra abuso e brute force, autenticação com Argon2 e comparações timing-safe.
Por decisão de projeto, CPFs de sócios entram mascarados. O motor enriquece entidades conhecidas a partir de fontes públicas, e deliberadamente não reconstrói identidade a partir de fragmentos. Achei mais responsável tratar deanonimização como uma linha que eu não cruzo numa API pública.
O que esse substrato desbloqueia (roadmap)
O ponto que mais me anima é que a decisão de identidade append-only não resolve só o problema de duplicação. Ela vira a fundação para um monte de coisa que estou construindo agora:
- Histórico e navegação temporal do grafo. Como cada versão fica retida por
content_id, o substrato já permite reconstruir o grafo como ele era em cada instante de coleta. Estou expondo isso como enumeração de revisões por nó e uma navegação no frontend, mais o diff entre duas versões de um mesmo nó, mostrando exatamente onde e quando um dado mudou. - Similaridade entre entidades. Uma aresta de semelhança com score percentual entre nós diferentes, para resolução de entidade sem nunca mutar o domínio imutável, já que a similaridade é derivada, não intrínseca.
- Ingestão de texto livre. Colar um texto arbitrário e extrair entidades com regex e validação matemática como filtro O(1), com uma camada de LLM restrita apenas aos campos que exigem interpretação, sempre respeitando a mesma linha de não reidentificação.
- Relatório visual auditável gerado a partir do próprio grafo de domínio, e troca do backend de persistência para um banco de grafos (preferencialmente Neo4j) sem tocar no núcleo, graças ao isolamento por Clean Architecture.
Números
O sistema roda em produção com mais de 650 testes, incluindo property based e mutation testing. Num sistema cuja premissa é não corromper a rede já mapeada, a suíte faz parte da definição de pronto.
Construí este projeto sozinho, de ponta a ponta, para aplicar em complexidade real a arquitetura de software que venho estudando e praticando com afinco nos últimos meses. Sou desenvolvedor backend Python e estou aberto a novas oportunidades: se você contrata, ou conhece quem contrata, será um prazer conversar. E fico à disposição para discutir qualquer uma dessas decisões aqui nos comentários.
- API ao vivo: https://osint.angelitochagas.com
- GitHub com o backend/frontend (osint-engine/osint-studio): https://github.com/geldois
- LinkedIn: https://linkedin.com/in/geldois