Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/sh
# ── Bot anti-vazamento (local) ───────────────────────────────────────────────
# Roda automaticamente a cada `git commit`. Bloqueia o commit se detectar:
# 1. arquivo de ambiente (.env / .env.local / .env.*) — exceto .env.example
# 2. a chave da API do Portal da Transparência no conteúdo
# 3. padrões genéricos de segredo (api key / token / senha com valor real)
# Última linha de defesa ANTES de o segredo sair da sua máquina.
#
# Ative uma vez com: git config core.hooksPath .githooks
# Pular num commit específico (use com cuidado): git commit --no-verify

fail=0

# 1) Arquivos de ambiente staged (exceto .example)
for f in $(git diff --cached --name-only --diff-filter=ACM); do
case "$f" in
*.example) : ;;
*.env|*.env.*|.env|.env.*)
echo "🚫 '$f' é um arquivo de ambiente e NÃO deve ser commitado."
echo " Mantenha segredos em .env.local (já ignorado pelo git)."
fail=1
;;
esac
done

# Conteúdo que está sendo adicionado (só linhas novas)
added="$(git diff --cached -U0 --diff-filter=ACM | grep '^+' | grep -v '^+++')"

# 2) Chave do Portal da Transparência (PORTAL_TRANSPARENCIA_API_KEY = hex)
if printf '%s' "$added" | grep -iqE "portal_transparencia_api_key[[:space:]]*[:=][[:space:]]*['\"]?[0-9a-fA-F]{16,}"; then
echo "🚫 Detectada a CHAVE DA API DO PORTAL DA TRANSPARÊNCIA no commit."
echo " Essa chave é pessoal (sua conta gov.br) — nunca pode ir pro GitHub."
fail=1
fi

# 3) Segredo genérico com valor real (ignora placeholders e process.env)
if printf '%s' "$added" \
| grep -ivE "(your[_-]?|sua[_-]?|seu[_-]?|example|placeholder|dummy|fake|sample|xxx+|<[^>]+>|chave[_-]?aqui|process\.env\.)" \
| grep -iqE "\b(api[_-]?key|apikey|secret|token|password|passwd|senha|client[_-]?secret)\b[[:space:]]*[:=][[:space:]]*['\"]?[A-Za-z0-9_./+=-]{12,}"; then
echo "⚠️ Possível segredo (api key / token / senha) com valor real no commit."
echo " Confira. Se for placeholder, ajuste o texto; se for real, remova."
fail=1
fi

if [ "$fail" -ne 0 ]; then
echo ""
echo "Commit BLOQUEADO para proteger informação sensível. 🛡️"
echo "Se tiver MUITA certeza de que é um falso positivo: git commit --no-verify"
exit 1
fi

exit 0
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,19 @@

- name: Build
run: pnpm -r build

secrets:
name: secrets
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # histórico completo para o gitleaks varrer todos os commits

- name: Gitleaks (varredura de segredos)
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Grátis para repositórios públicos e pessoais (não precisa de licença).
GITLEAKS_CONFIG: .gitleaks.toml

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment on lines +46 to +59
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Agentes / config local (não versionar — ver AGENTS.md)
.claude/
.claudian/

# Pasta criada por engano na raiz (o app web real é apps/web)
/web/

# Dependências
node_modules/
Expand Down
38 changes: 38 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Configuração do gitleaks — o "bot" anti-vazamento do DeOlho.
# Estende as regras padrão (cobrem AWS, GitHub, Google, Stripe, JWT, etc.)
# e adiciona regras específicas para o nosso contexto (chave do Portal da
# Transparência e arquivos de ambiente).
title = "DeOlho — proteção anti-segredo"

[extend]
useDefault = true

# ── Regras específicas do projeto ────────────────────────────────────────────

[[rules]]
id = "portal-transparencia-api-key"
description = "Chave da API do Portal da Transparência (conta gov.br)"
# Pega PORTAL_TRANSPARENCIA_API_KEY = <hex de 16+ chars> (formato da chave do portal)
regex = '''(?i)portal_transparencia_api_key\s*[:=]\s*['"]?[0-9a-f]{16,}['"]?'''
keywords = ["portal_transparencia_api_key"]

[[rules]]
id = "generic-env-secret"
description = "Atribuição de segredo (api key, token, senha) com valor real"
regex = '''(?i)\b(api[_-]?key|apikey|secret|token|password|passwd|senha|client[_-]?secret)\b\s*[:=]\s*['"]?[a-z0-9_\-./+=]{12,}['"]?'''
keywords = ["api_key","apikey","secret","token","password","passwd","senha"]

# ── Allowlist: o que NÃO é vazamento (placeholders, exemplos, docs) ───────────

[allowlist]
description = "Placeholders e arquivos de exemplo — não são segredos reais"
paths = [
'''\.env\.example$''',
'''\.gitleaks\.toml$''',
'''(^|/)CONTRIBUTING\.md$''',
'''(^|/)\.github/''',
]
regexes = [
'''(?i)(your[_-]?|sua[_-]?|seu[_-]?|change[_-]?me|example|placeholder|dummy|fake|test|sample|xxx+|<[^>]+>|chave[_-]?aqui|cole[_-]?aqui|\.\.\.)''',
'''(?i)process\.env\.''',
]
189 changes: 138 additions & 51 deletions .planning/HANDOFF.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,143 @@
# Handoff

Última atualização: 2026-05-24
Ultima atualizacao: 2026-05-30

## Estado atual

- Repositório canônico: `https://github.com/pmarcura/DeOlho`
- Branch base: `main` (agora COM a fundação M0 — reconciliado)
- Branch de trabalho atual: `claude/issue-22-scaffold-tecnico`
- Milestone atual: `M0 - Fundação aberta`
- Issue em andamento: [#22 - Scaffold técnico Next/Postgres/Drizzle](https://github.com/pmarcura/DeOlho/issues/22)
- Próxima issue de UI: [#24 - Design system shadcn social-cívico](https://github.com/pmarcura/DeOlho/issues/24)

## O que foi entregue nesta sessão (2026-05-24)

### 1. Reconciliação do repositório
- O branch `codex/issue-7-m0-fundacao` tinha 6 commits locais não enviados (risco de perda) e estava 12 commits à frente do `main`. Tudo foi enviado ao `origin` e o `main` recebeu fast-forward com toda a fundação M0.
- `main` agora contém: docs OSS, design system, `apps/web` (Next 16 + shadcn + `/financas` mock), `packages/collectors`, `packages/ui`.

### 2. Saneamento do monorepo (commit `chore: unificar monorepo em pnpm`)
- Package manager unificado em **pnpm** (root tinha npm workspaces + `apps/web` tinha pnpm aninhado — inconsistente).
- Removidos: `package-lock.json` (raiz e collectors), `apps/web/pnpm-workspace.yaml`, `apps/web/pnpm-lock.yaml`.
- Criados: `pnpm-workspace.yaml` na raiz, lockfile único.
- `.gitignore` expandido (`.env`, build outputs, `.obsidian`, editores).

### 3. Fundação de dados — `@deolho/db` (commit `feat(db): fundação de dados`)
Pacote novo `packages/db` (Drizzle ORM + driver `postgres`) com o núcleo transversal que precisa existir desde o dia 1 (caro de retrofitar — `research/FEATURES.md`):
- `sources` — catálogo de fontes; `limitacoes` alimenta TRUST-05.
- `raw_records` — evidência verbatim (JSONB) + proveniência + versionamento append-only por `content_hash` (TRUST-02/03, DATA-04).
- `entities` + `entity_references` — IDs canônicos + ponte de resolução.
- `contracts` + `contract_events` — unidade do MVP + linha do tempo (CONT-04).
- Tipagem de confiança (`trust_type`) embutida (TRUST-01); busca FTS portuguesa (tsvector gerado) + fuzzy `pg_trgm` (CONT-01).
- Migration `0000` gerada + extensões `pg_trgm`/`unaccent`/`vector`.
- `docker-compose.yml` (pgvector) na raiz; scripts `seed`/`check`; `.env.example`.

## Verificações executadas
- `pnpm --filter @deolho/db typecheck` → OK (0 erros).
- `drizzle-kit generate` → migration `0000_square_talon.sql` gerada corretamente.
- `pnpm --filter web build` → OK (Next 16.2.6 Turbopack; 3 rotas estáticas).

## Pendências conhecidas (bloqueios)
- **Migrations ao vivo + seed + check**: precisam de um Postgres acessível.
Docker **não está instalado** nesta máquina. Caminhos:
1. Instalar Docker Desktop → `docker compose up -d` → `pnpm --filter @deolho/db migrate && seed && check`.
2. OU usar Postgres gerenciado (Supabase/Neon, free tier — já trazem pg_trgm/unaccent/pgvector): pôr a connection string em `packages/db/.env` e rodar migrate/seed/check.
- **Conectores**: o registry de MCP está vazio neste ambiente — **GitHub MCP e Supabase/Postgres MCP não estão disponíveis para adicionar**. Alternativas: `gh` CLI (não instalado) para issues/PRs; `DATABASE_URL` direto (não precisa de MCP para migrar). `Apify`, `Claude Preview` e `Claude in Chrome` estão conectados e prontos. Bright Data depende da CLI `bdata` (não instalada — skill `brightdata` guia o install).

## Próximo passo recomendado
1. Prover um Postgres (Docker ou Supabase/Neon) e rodar `migrate → seed → check` para validar a fundação ao vivo.
2. **Fatia vertical PNCP**: adaptar `packages/collectors/src/adapters/pncp.ts` para persistir em `@deolho/db` (raw_records → contracts + entities), e criar a primeira página viva de contrato em `apps/web` consumindo o banco (substituindo o mock de Americana). Requer `transpilePackages: ['@deolho/db']` no `next.config.ts` e ler `node_modules/next/dist/docs/` antes (Next 16 tem breaking changes — ver `apps/web/AGENTS.md`).
3. Consertar o adapter `querido-diario` (recebe HTML em vez de JSON — `totalRegistros: 0`).

## Decisões a registrar (candidatas a ADR)
- Package manager: **pnpm** como padrão do monorepo.
- Modelo de dados: padrão **typed-core + raw JSONB** com proveniência por campo, tipagem de confiança e IDs canônicos desde o início.
- `pg-boss` (jobs) e colunas de embedding pgvector ficam para fases posteriores; a extensão `vector` já é criada.
- Repositorio local: `D:\DeOlho`
- Branch atual: `codex/fundacao-civica-eventos`
- Observacao Git: `git pull --ff-only` nao executa nesta branch porque ela ainda nao tem upstream configurado.
- Territorio da entrega: Americana/SP primeiro.
- Escopo trabalhado: fundacao civica com eventos, evidencias, vinculos documentados, fluxos financeiros e cobertura de fontes.

## Entregue nesta sessao

### Banco canonico

- Criadas as tabelas canonicas:
- `civic_events`
- `evidence`
- `entity_relationships`
- `money_flows`
- `source_coverage`
- Adicionados enums para categorias civicas, tipos de relacionamento, tipos de fluxo financeiro e status de cobertura.
- Migration gerada: `packages/db/migrations/0004_awesome_wallop.sql`.
- `sources` atualizado para incluir `camara-americana`, totalizando 10 fontes no catalogo.

### PNCP

- Corrigido o transform de PNCP no web com type guard seguro para diferenciar compra/contrato.
- `/financas` agora usa PNCP real quando ha snapshot local e fallback marcado como demonstrativo/sintetico.
- Mapper PNCP adaptado ao payload real da API:
- fornecedor via `niFornecedor`/`nomeRazaoSocialFornecedor`;
- numero via `numeroContrato`, `numeroContratoEmpenho` ou `numeroControlePNCP`;
- URL publica do contrato PNCP derivada de `numeroControlePNCP`.
- Validacao local:
- 627 contratos retornados na coleta completa;
- 1.118 raw records locais acumulados por causa da primeira tentativa interrompida + recoleta completa;
- 578 contratos distintos por `numeroControlePNCP`;
- 578 eventos;
- 578 evidencias;
- 578 fluxos `contratado`;
- 578 vinculos documentados orgao-fornecedor.
- Segunda execucao do mapper PNCP manteve contagens estaveis.

### TCE-SP

- Corrigido adapter para a API oficial atual:
- `/api/json/despesas/americana/{exercicio}/{mes}`
- `/api/json/receitas/americana/{exercicio}/{mes}`
- Ingestao L0 em lote (`ingestMany`) para suportar volume real.
- Mapper TCE-SP cria eventos/evidencias/fluxos para:
- `empenhado`
- `liquidado`
- `pago`
- `anulado`
- `receita_arrecadada`
- Valores zero e negativos tambem viram fluxo quando publicados pela fonte.
- Pessoa fisica comum nao vira entidade publica: documento de credor so e usado quando for CNPJ completo.
- Validacao local:
- 124.795 despesas cruas;
- 4.074 receitas cruas;
- 124.794 eventos de pagamento;
- 4.071 eventos de receita;
- 128.865 evidencias TCE-SP;
- nenhum raw TCE-SP ficou sem evento/evidencia/fluxo esperado;
- segunda execucao do mapper TCE-SP retornou 0 pendencias.

### Diario, atomos e evidencias

- Extração de atomos agora suporta segmentacao documental com `indefinido`.
- Eventos do Diario gerados a partir dos atomos:
- 273 eventos/evidencias;
- 24 contratos publicados;
- 249 atos publicados.
- Extracao de territorio adicionada para rua, bairro, escola, UBS, praca, secretaria, orgao e equipamento publico quando aparecem no texto.
- Mapper de mencoes Diario->entidade mantem a regra conservadora: so liga CNPJ encontrado no PDF a empresa ja conhecida.

### Fontes e cobertura

`source_coverage` validado com 12 linhas:

- `pncp/contrato`: `fresh`
- `pncp/compra`: `partial`
- `tce-sp/despesa`: `fresh`
- `tce-sp/receita`: `fresh`
- `diario-americana/gazeta`: `fresh`
- `querido-diario/gazeta`: `no_data`
- `transparencia-americana/execucao-orcamentaria`: `unavailable`
- `camara-americana/atividade-legislativa`: `no_data`
- `cgu-transparencia/ceis`: `partial` em rodada limitada
- `cgu-transparencia/cnep`: `partial` em rodada limitada
- `receita-cnpj/cnpj-qsa`: `fresh`
- `tse/doacao-eleitoral`: `pending`

### Produto e linguagem publica

- Removida linguagem publica de "familia no poder".
- Substituida por "vinculos documentados" e "mencoes publicas".
- Mantida a regra: nada de parentesco por sobrenome, nada de score de corrupcao, nada de acusacao automatica.
- Sinais/limitacoes aparecem como limitacao de fonte, nao como fato.

## Verificacoes executadas

- `docker compose up -d db` -> Postgres/pgvector healthy.
- `pnpm --filter @deolho/db migrate` -> OK.
- `pnpm --filter @deolho/db seed` -> OK, 10 fontes.
- `pnpm --filter @deolho/db check` -> OK.
- `pnpm --filter @deolho/collectors collect:pncp` -> contratos coletados; licitacoes ficaram parciais por exigencia de modalidade do endpoint.
- `pnpm --filter @deolho/collectors map:pncp` -> OK e idempotente.
- `pnpm --filter @deolho/collectors collect:tce` -> OK.
- `pnpm --filter @deolho/collectors map:tce` -> OK e idempotente.
- `pnpm --filter @deolho/collectors map:diario-atoms` -> OK.
- `pnpm --filter @deolho/collectors collect:diario` -> OK, `no_data`.
- `pnpm --filter @deolho/collectors collect:diario-americana` -> OK.
- `pnpm --filter @deolho/collectors collect:transparencia` -> OK, `unavailable`.
- `pnpm --filter @deolho/collectors collect:tse` -> OK, `pending`.
- `pnpm --filter @deolho/collectors collect:camara` -> OK, `no_data`.
- `pnpm --filter @deolho/collectors collect:ceis -- --max-pages=1` -> OK, `partial`.
- `pnpm --filter @deolho/collectors exec tsx src/adapters/ceis-cnep.ts cnep --max-pages=1` -> OK, `partial`.
- `pnpm --filter @deolho/collectors enrich:socios` -> OK, 323 socios gravados como atributo de empresa.
- `pnpm -r typecheck` -> OK.
- `pnpm -r lint` -> OK.
- `pnpm -r build` -> OK.
- `git diff --check` -> OK; apenas avisos CRLF do Windows.

## Pendencias conhecidas

- PNCP compras/licitacoes ainda precisa iterar modalidades corretamente; cobertura esta marcada como `partial`.
- Camara Municipal ainda precisa de mapeadores dedicados para projetos, indicacoes, requerimentos, votacoes e sessoes; cobertura inicial esta `no_data` porque o seletor generico nao encontrou itens.
- Transparencia Americana segue `unavailable` para execucao detalhada enquanto a secao SIAFIC estiver indisponivel no portal.
- TSE esta `pending`: a fonte e bulk ZIP e exige entrega focada para download, descompactacao e filtro por Americana/pessoas publicas.
- CEIS/CNEP foi validado com `--max-pages=1`; rodada completa deve ir para job com rate limit.
- Mapper de mencoes do Diario tentou 2 PDFs recentes e recebeu `fetch failed`; precisa reavaliar download/headers se essa ligacao for prioridade imediata.
- A branch contem `.claudian/` e `web/` untracked locais; nao foram alterados nem regularizados por esta entrega.

## Proxima issue recomendada

Fazer a fatia de produto "linha do tempo civica":

1. API/read model para `civic_events` + `evidence` + `money_flows` + `source_coverage`.
2. Tela de evento com fonte, trecho, data de publicacao/coleta, link original e limitacoes.
3. Filtros por rua/bairro/orgao/fornecedor/tipo de gasto.
4. Mapeadores dedicados da Camara para indicacoes/requerimentos de zeladoria.
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ pnpm -r typecheck # tipos TypeScript
pnpm -r build # compila tudo
```

### Proteção anti-vazamento de segredos 🛡️

Ative o hook que bloqueia commits de chaves/API/`.env` **antes** de saírem da
sua máquina (faça uma vez, ao clonar):

```bash
git config core.hooksPath .githooks
```

Nunca commite chaves de API, tokens ou arquivos `.env`. Guarde segredos em
`.env.local` (já ignorado pelo git). O CI também roda o **gitleaks** em todo PR —
se um segredo passar, o merge é bloqueado.

| Pasta | O que é |
|---|---|
| `apps/web` | Interface (Next.js) — o feed cívico que o cidadão vê |
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/app/explorar/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,11 @@ export default async function ExplorarPage() {
)}
</section>

{/* Pessoas no poder — REAL (agentes públicos citados) */}
{/* Agentes públicos citados — REAL */}
<section className="mb-6">
<h2 className="text-sm font-semibold mb-2 px-1 flex items-center gap-1.5">
<Users className="w-4 h-4 text-foreground/70" aria-hidden />
quem está no poder
agentes públicos citados
</h2>
{topPessoas.length === 0 ? (
<EmptyState icone="🗳️" titulo="Sem pessoas detectadas" descricao="O extrator captura agentes públicos citados em nomeações, exonerações e assinaturas." />
Expand Down Expand Up @@ -213,7 +213,7 @@ export default async function ExplorarPage() {
href="/familias"
className="inline-flex items-center justify-center w-full h-10 mt-3 rounded-full bg-foreground/5 text-foreground/80 text-sm font-medium hover:bg-foreground/10"
>
sobrenomes no poder
sobrenomes em atos oficiais
</Link>
</>
)}
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/app/familia/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ export default async function FamiliaPage({ params }: PageProps) {
<Info className="w-4 h-4 text-amber-700 shrink-0 mt-0.5" aria-hidden />
<p className="text-[12px] text-amber-900/90 leading-relaxed">
Sobrenome igual <strong>não comprova parentesco</strong>. Esta é uma visão de
coocorrência em atos oficiais — pra investigar, nunca uma conclusão.
coocorrência em atos oficiais — para leitura contextual, nunca uma conclusão.
Vínculos familiares só aparecem quando houver evidência documental própria.
</p>
</div>

Expand Down
Loading
Loading