Documentação DATGOV

Arquitetura, fluxo de dados e processo de desenvolvimento, publicados diretamente do repositório — sem edição, sem versão paralela.

DATGOV — BIBLE (arquitetura)

> Fonte de decisão de lançamento: docs/LAUNCH_READY_REPORT.md. Este arquivo

> descreve a arquitetura; detalhes por fase em docs/phases-1-6-implementation.md.

Visão

Evidence-First: toda resposta carrega evidência da fonte oficial (URL, hash,

timestamp). Dados comunitários nunca substituem fonte oficial.

Componentes

| Camada | Tech | Onde |

|---|---|---|

| API | FastAPI (backend/datgov/api.py) | container datgov-backend-1 (127.0.0.1:18000) |

| Roteador de fontes | SourceRouter + circuit breaker + cache (backend/datgov/router.py) | idem |

| Rate limit | RedisRateLimiter fixed-window por IP (TTL armado na 1ª batida) | Redis DB 0 (prod) / DB 1 (staging) |

| Persistência | PostgreSQL 16 (schema datgov: clients, api_keys, api_usage_logs…), RLS | datgov-postgres-1 |

| Evidências | EvidenceStore (SQLite content-addressed) | volume |

| MCP p/ IAs | POST /v1/ai/mcp (JSON-RPC tools/list, tools/call) — 11 tools declaradas, 10 realmente funcionais em produção (get_dou_publications existe mas retorna sempre no_authoritative_result — ver seção DOU/InLabs abaixo, bloqueio de infraestrutura, não decisão de negócio nem falta de handler): get_tcu_irregularities (contas.tcu.gov.br/contrata2RS/api/publico/termos-contratuais, filtro por CNPJ local pois o endpoint não filtra no servidor), find_peers_by_cnae (novo 06/09/2026: busca empresas por CNAE com ranking exato/classe/divisão sobre xooriq_cnpj_active_complete, índice btree em cnae_principal), get_procurements (PNCP /v1/contratos?cnpjOrgao=..., janela 365d), get_sanctions (CEIS+CNEP/Portal da Transparência), get_risk_signals/compare_companies (compostos sobre os dois acima), search_cnpj (XooriqCnpjAdapterxooriq_cnpj_active_complete — Postgres nativo do host, projeto Xooriq, ~9M CNPJs com refresh semanal automatizado; papel read-only datgov_cnpj_ro, least-privilege, só nas 2 tabelas de CNPJ), check_pep, check_international_sanctions, search_candidato_tse. Deploy real dos 2 tools novos (CNAE + DOU) em produção só em 06/09/2026 — antes disso existiam só no git, nunca tinham sido buildados/restartados | via nginx /api/v1/ai/mcp |

| Watchman (sanções intl.) | moov-io/watchman self-hospedado, containers datgov-watchman (porta 18807 em 127.0.0.1 — restrita após achado real do gate fox-shield, era 0.0.0.0 sem auth —, rede datgov_net), services/watchman/docker-compose.yml. INCLUDED_LISTS=us_ofac,un_csl,eu_csl — 26566 entidades reais indexadas | via WatchmanAdapter → domínio sancoes-intl do SourceRouter |

| Monitoramento contínuo | datgov.monitoring_subscriptions (RLS, mesmo padrão enable-sem-policy das demais tabelas) + backend/datgov/monitor.py::run_once() — reconsulta assinaturas ativas, alerta por e-mail (mailer.py) e por WhatsApp (whatsapp.py, corrigido 2026-09-06 — item P1 "3": achado real de que existiam DOIS sistemas ChatCentral; a versão original usava o antigo (Chatwoot, desativado como painel em 2026-09-04); corrigido para chamar POST /api/messages/send do sistema real que fica, Central Chat Atendimento (atendimento.centralfox.online), sempre via template Meta aprovado datgov_alerta_monitoramento — nunca texto livre, que só entrega dentro da janela de 24h. Pendente: CHATCENTRAL_ALERT_PHONE vazio no secret, aguardando um número real de teste do Sr. Fox para o passo final de verificação end-to-end) só quando content_sha256 muda. Domínios cobertos: pep, sancoes-intl, candidatos-tse, contratacoes (PNCP, adicionado 2026-09-04 — antes disso não era possível assinar "avise quando esse órgão publicar contrato novo"; janela de 7 dias por checagem para não gerar ruído de hash por deslocamento de período) | cron externo (systemd timer), sem dependência nova de scheduler |

| Documentação pública | frontend/app/docs/page.tsx — BIBLE.md/WORKFLOW.md renderizados a partir do próprio repo (conversor markdown→HTML próprio, zero dependência nova) | rota /docs no mesmo frontend, sem subdomínio novo |

| Marca | Kit oficial "Conceito 2 aprovado" (favicons, símbolo no header claro/escuro, logo no schema.org Organization). Arquivo completo em brand-assets/DATGOV_Kit_Conceito_2/ (logos SVG/PNG, redes sociais, WhatsApp, app icons, paleta #0D47A1/#0066FF/#00D4FF/#2B2F36/#F7F9FB) | frontend/public/{favicon*,brand/}; paleta do site (Terminal de Evidências, teal/âmbar) mantida — são identidades visuais distintas, não trocadas |

| Billing | Stripe conta PT acct_1TC3S… (mesma conta oficial do ecossistema), modo LIVE desde 06/09/2026 (achado real: a chave configurada em produção sempre foi sk_test_..., ninguém tinha ativado live; chave live encontrada no cofre e ativada); trial 7 dias em toda assinatura; Starter/Equipe/API-MCP com stripe_price_id live recriados e gravados em subscription_plans (preços test antigos nunca existiram no modo live — objetos Stripe não migram entre test/live). Sessão de checkout live testada de ponta a ponta (cs_live_... real, sem completar pagamento). Pay-as-you-use: Price/Meter metered recriados em modo live, mesmo event_name usado por billing.py::run_once() (agregado de api_usage_logs por dia) | webhook /webhooks/stripe emite api_key + e-mail; payment_provider_id guarda o Stripe customer id; achado FoxShield 06/09/2026 corrigido — client_id do metadata agora é validado contra datgov.clients antes de emitir api_key (IDOR) |

| Frontend | Next.js (frontend/), sistema visual "Terminal de Evidências" (app/globals.css, aprovado 2026-08-05) | datgov-frontend-1 (127.0.0.1:13000) |

| Captura de lead | POST /v1/leads → tabela datgov.leads (RLS) | idem backend |

| Cadastro self-service | Chat na home (signup-chat.tsx) → POST /v1/signup (CPF validado, validators.py) → e-mail com senha temp (mailer.py) → POST /v1/auth/client-loginPOST /v1/auth/change-password (forçada, política 8+/maiúscula/minúscula/especial) → GET /v1/auth/mePOST /v1/checkout/create-session | idem backend + frontend/app/dashboard/page.tsx |

| Site IA (S1-S10) | JSON-LD, OG, sitemap/robots (Next.js), llms.txt, data.json, webmcp.js, chat /assistant via gateway LiteLLM | frontend/app/{layout,sitemap,robots}.ts*, frontend/public/, frontend/app/assistant/route.ts |

| OCR | Tesseract (produção) · PaddleOCR (benchmark via GH Actions) | scripts/benchmark_ocr*.py |

| Backup offsite | dump cifrado AES-256 → Cloudflare Workers KV (cron dom 03:20) | scripts/backup_*.sh |

Ambientes

frontend, postgres, redis, nginx, certbot.

datgov-backend:latest, Redis DB 1, DATGOV_RATE_LIMIT alto p/ testes de carga.

Padrões

Inclui smtp.env (cópia do vault foxsites_at_centralfox_online, e-mails

saem como "DATGOV <[email protected]>"), litellm_master_key.txt e

xooriq_cnpj_ro_password.txt (papel datgov_cnpj_ro no Postgres do Xooriq).

docker network connect bridge datgov-backend-1 — necessário pro adapter

xooriq-cnpj alcançar o Postgres nativo do host (172.17.0.1). O Compose não

gerencia esse attach sozinho (limitação real do Docker: a bridge padrão não

aceita alias de rede-escopo, que o Compose sempre tenta criar).

hoje abre conexão PG por request — pool é o item 1 do plano de escala

(docs/load-test-summary.md).

(.github/workflows/ci.yml).

DOU (Diário Oficial da União) via InLabs — código pronto, BLOQUEADO em produção (achado real 06/09/2026)

Status: código implementado e testado como funcional a partir de fora da VPS; não funciona rodando na VPS de produção — bloqueio real de infraestrutura, não decisão de negócio.

Prontidão pra mercado — LGPD, imagem social, headers, auditoria (06/09/2026)

E-mail, Cloudflare e GSC — auditoria e correções (06/09/2026, continuação)

Apps móveis (iOS + Android) via AIFoxApp — reparo de identidade e plano de construção (06/09/2026)

DATGOV — WORKFLOW (operação)

Deploy (VPS, diretório /home/fox/projects/datgov)

```bash

docker build -t datgov-backend:latest -f docker/backend.Dockerfile .

docker build -t datgov-frontend:latest -f docker/frontend.Dockerfile .

docker compose -f docker-compose.prod.yml -p datgov up -d --no-deps --force-recreate backend frontend

curl -sf http://127.0.0.1:18000/v1/health # prod local

curl -sf https://datgov.com.br/api/v1/health # prod público

curl -sf https://staging.datgov.com.br/api/v1/health

```

--force-recreate é obrigatório — sem ele, docker compose up só recria o

container se a config do docker-compose.yml mudou; um docker build novo

com a MESMA config fica rodando na imagem antiga (achado real 2026-08-06:

/v1/auth/me deployado mas invisível — container antigo continuava no ar).

Confirmar sempre: `docker exec datgov-backend-1 python3 -c "from datgov.api

import app; print([r.path for r in app.routes])"` mostra a rota nova.

Migration nova (ex.: datgov.leads, datgov.clients.cpf): aplicar direto no

Postgres de produção antes do deploy do backend — `docker exec -i

datgov-postgres-1 psql -U datgov -d datgov < supabase/migrations/<arquivo>.sql`.

Staging usa a mesma imagem; recriar renomeando o antigo (rollback), nunca rm.

Testes

```bash

cd backend && python -m pytest -q # unit/integr.

k6 run -e RATE=150 -e DURATION=60s load-arrival.js # capacidade (staging!)

```

Carga: usar load-arrival.js (constant-arrival-rate) contra 127.0.0.1:18001;

nunca contra produção. Metas e histórico: docs/load-test-summary.md.

Benchmarks OCR

scripts/prepare_ocr_ground_truth.py).

input limit p/ smoke). Artifacts paddle-benchmark-report-*.

CI

datgov-ci roda em todo push: pytest (backend/) + compileall + build Next.js.

Verificar com gh run list -R PauloFox0105/datgov.

Site IA (S1-S10)

Cadastro self-service (trial 7 dias)

POST /v1/auth/client-loginPOST /v1/auth/change-password (forçada)

GET /v1/auth/mePOST /v1/checkout/create-session (Stripe,

trial_period_days: 7) → webhook ativa e manda a chave de API por e-mail.

111.444.777-35 e um e-mail que você controla de verdade — a senha

temporária só existe no e-mail entregue (Gmail search_threads), não na

resposta da API. Sempre delete from datgov.clients where email=... no

fim do teste.

hardcoded no código — /v1/checkout/create-session recebe o price_id

direto no payload).

com 111.444.777-35/-36). Política de senha: 8+ caracteres, maiúscula,

minúscula, caractere especial.

máx. 2 tentativas. **Nunca envie teste para @centralfox.online

inventado** — o Mailcow rejeita destinatário local inexistente (550

Recipient address rejected); use um e-mail externo real.

Upgrade 2026-08-31 (itens 1-6) — operação

Deployado em produção em 2026-09-02 (commit 8bdd483 em master, merge de feature/sancoes-internacionais). 8 tools MCP confirmadas ao vivo em https://datgov.com.br/api/v1/ai/mcp, /docs HTTP 200, sitemap com 3 URLs, GSC resubmetido, IndexNow pingado (200 em api.indexnow.org/Bing/Yandex). fox_presence_audit.py: 16/16 PASS.

depois docker network connect datgov_net datgov-watchman (rede separada por

padrão do compose — precisa da conexão manual pra o backend alcançar por nome

de container). Checar saúde: `docker inspect -f "{{.State.Health.Status}}"

datgov-watchman deve dar healthy; curl http://localhost:18807/v2/search?name=X&type=person`

deve responder com entities. Pin de versão obrigatório — a tag :latest

do Docker Hub resolveu para v0.31.3 (desatualizada); usar v0.66.0 ou mais

recente, e sempre setar INCLUDED_LISTS explícito (vazio = 0 listas carregadas

nessa versão, não "carrega tudo" como em versões antigas). **Porta 18807 é

127.0.0.1 de propósito** (achado real do gate fox-shield: estava em 0.0.0.0,

exposta sem auth a qualquer IP sem necessidade — o backend fala com o watchman

por nome de container em datgov_net, nunca pela porta do host). Não reabrir

para 0.0.0.0 sem adicionar autenticação antes.

cron/systemd timer (não roda sozinho ainda). Assinaturas ficam em

datgov.monitoring_subscriptions; sem linha ali, run_once() não faz nada.

cron, mesmo padrão do monitor. Só reporta uso de cliente com

payment_provider_id populado (Stripe customer id) — o webhook grava isso

automaticamente após a correção do bug, mas clientes que já assinaram ANTES

da correção ficam com payment_provider_id desatualizado até renovar.

padrão do Docker Compose — secrets.portaldatransparencia_chave (arquivo

em ${DATGOV_SECRETS_DIR}/portaldatransparencia_chave.txt) montado em

/run/secrets/portaldatransparencia_chave no container, lido por

factory._secret("DATGOV_PORTAL_TRANSPARENCIA_CHAVE") (mesmo padrão de

stripe_secret_key/postgres_password). Achado real: a 1ª versão lia de

DATGOV_SECRETS_DIR diretamente (caminho de host), que não existe dentro

do container — nunca teria funcionado em produção. Corrigido antes do

deploy.

bloqueia todo cliente automatizado por WAF Akamai (403, testado com e sem

User-Agent de navegador), mas um navegador humano baixa normal. O Sr. Fox

baixou o zip oficial e forneceu; verificado real com CPF de um registro

público (50 colunas confirmadas, NR_CPF_CANDIDATO na posição 20).

Arquivo em backend/data/consulta_cand_2026.zip (NÃO commitado — dado

atualiza 4x/dia no TSE, ficaria stale; está em .gitignore). Montado no

container via volumes: ./backend/data:/app/data:ro +

DATGOV_TSE_CANDIDATOS_URL=file:///app/data/consulta_cand_2026.zip

(mesmo mecanismo file:// já suportado por BaseAdapter via urllib —

nenhum código novo). Para atualizar: baixar o zip mais recente pelo

navegador e substituir o arquivo no host, sem precisar de rebuild/redeploy.

Nota de correção: official-cache (citado antes como fallback genérico)

tem um bug pré-existente — file://data/raw é um caminho malformado que

nunca funcionou; não é o mecanismo real usado aqui.

do repo em build/request time — atualizar os .md já atualiza a página, sem

passo extra de publicação.

Gate FOXQA + FOXSECURITY pré-deploy (upgrade 2026-08-31)

Antes de autorizar deploy de qualquer branch com mudança de superfície (nova

tool MCP, novo serviço, novo endpoint), rodar os dois gates nesta ordem:

1. FOXQA: foxqa_bootstrap_project (se o projeto ainda não existir),

foxqa_register_suite, rodar a suite real (pytest -q) e

foxqa_record_run com o resultado fresco — nunca um número de memória.

foxqa_log_issue para todo NÃO VERIFICADO/bloqueio real, mesmo os que não

bloqueiam o deploy (ex.: bloqueio externo do TSE).

2. FOXSECURITY: agente fox-shield modo deep (pré-lançamento, OWASP) no

diff real da branch. Achados CRITICAL/HIGH bloqueiam até corrigir e

reverificar de verdade (não só aplicar o patch — testar que o problema

sumiu e que o caminho legítimo continua funcionando).

Achado real desta rodada (2026-09-01): check_pep não fazia quote() no cpf

(HIGH) e watchman exposto em 0.0.0.0 sem auth (MEDIUM) — ambos corrigidos e

reverificados no commit 5f47de1 antes de qualquer autorização de deploy.

Segurança

testada em docs/offsite-backup-evidence.md.