# Deploy Checklist — Retrieval Layer Activation

Runbook end-to-end para ativar pgvector + BGE-M3 + reranker no Disclosure Bureau VPS.

> Assume: VPS já tem disclosure-stack (Supabase + Next.js + Meilisearch) rodando, e o batch `scripts/28-batch-rebuild-all.py` já produziu chunks em `raw/<doc>--subagent/` (parcial ou completo).

## 0. Pré-condições

```bash
# checa que chunks existem
ls -d /Users/guto/ufo/raw/*--subagent | wc -l   # esperado ≥ 1

# checa que .env tem POSTGRES_PASSWORD + DATABASE_URL + EMBED_SERVICE_URL
grep -E "POSTGRES_PASSWORD|DATABASE_URL|EMBED_SERVICE_URL" infra/disclosure-stack/.env
```

Se o `.env` ainda não tem as novas linhas, copia do `.env.example`:

```
DATABASE_URL=postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres
EMBED_SERVICE_URL=http://embed:8000
```

## 1. Aplicar migração 02 (pgvector + chunks schema)

Sobe o stack — bootstrap.sh já aplica as migrações automaticamente (vê linhas 86-92):

```bash
cd infra/disclosure-stack
./scripts/deploy.sh        # rsync + docker compose up; aplica 00-init, 01-chat, 02-chunks
```

Se quiser aplicar só a 02 manualmente:

```bash
./scripts/ssh.sh
cd /data/disclosure
docker exec -i disclosure-db psql -U postgres < migrations/02-chunks-retrieval.sql
```

**Verificar:**

```bash
docker exec -i disclosure-db psql -U postgres -c "\dx" | grep vector
# extensão `vector` instalada ✓

docker exec -i disclosure-db psql -U postgres -c "\dt public.*"
# documents, chunks, entities, entity_mentions ✓

docker exec -i disclosure-db psql -U postgres -c "\df public.hybrid_search_chunks"
# função RPC ✓
```

## 2. Subir o embed-service (BGE-M3 + reranker)

Já está no `docker-compose.yml`. Primeiro build leva 5-10 min (baixa torch CPU + FlagEmbedding). Primeira request leva mais 5-8 s pra carregar modelos.

```bash
./scripts/ssh.sh
cd /data/disclosure
docker compose build embed
docker compose up -d embed
docker compose logs -f embed
```

**Verificar (de dentro do VPS via internal network):**

```bash
docker exec disclosure-embed curl -s http://localhost:8000/health
# {"status":"ok","embed_loaded":false,"rerank_loaded":false}

docker exec disclosure-embed curl -s -X POST http://localhost:8000/embed \
  -H 'content-type: application/json' \
  -d '{"texts":["UAP sobre Kansas em 1950"]}'
# primeira call: ~5s (model load). retorna {model,dim:1024,embeddings:[[...]]}
```

## 3. Indexar chunks → Postgres

```bash
./scripts/ssh.sh
cd /data/disclosure

# instalar deps Python (se ainda não)
pip3 install psycopg[binary] pyyaml requests

# pegar a senha do postgres pra montar DATABASE_URL local
source /data/disclosure/.env

# rodar indexer dentro de um container que tem rede internal
docker run --rm \
  --network disclosure-internal \
  -v /data/ufo:/data/ufo:ro \
  -e DATABASE_URL="postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres" \
  -e EMBED_SERVICE_URL=http://embed:8000 \
  python:3.11-slim \
  bash -c "pip install -q psycopg[binary] pyyaml requests && \
           python3 /data/ufo/scripts/30-index-chunks-to-db.py --skip-existing"
```

**Verificar:**

```bash
docker exec -i disclosure-db psql -U postgres -c \
  "SELECT COUNT(*) FROM public.chunks WHERE embedding IS NOT NULL;"
# esperado: total = soma de chunks em raw/*--subagent/chunks/
```

## 4. Materializar `entity_mentions`

```bash
docker run --rm \
  --network disclosure-internal \
  -v /data/ufo:/data/ufo:ro \
  -e DATABASE_URL="postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres" \
  python:3.11-slim \
  bash -c "pip install -q psycopg[binary] pyyaml && \
           python3 /data/ufo/scripts/31-populate-entity-mentions.py"

# tempo: ~30min para 34k entidades
```

**Verificar:**

```bash
docker exec -i disclosure-db psql -U postgres -c \
  "SELECT COUNT(*) FROM public.entity_mentions;"
# esperado: dezenas de milhares
```

## 5. Sync `mentioned_in[]` → markdown (opcional, fecha loop)

Esta é a única etapa que ESCREVE em `wiki/`. Use `--dry-run` primeiro.

```bash
docker run --rm \
  --network disclosure-internal \
  -v /data/ufo:/data/ufo \
  -e DATABASE_URL="postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres" \
  python:3.11-slim \
  bash -c "pip install -q psycopg[binary] pyyaml && \
           python3 /data/ufo/scripts/32-sync-mentioned-in-yaml.py"
```

## 6. Configurar Next.js para usar a DB

No `disclosure-stack/.env`:

```
DATABASE_URL=postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres
EMBED_SERVICE_URL=http://embed:8000
```

Reiniciar o container web:

```bash
docker compose restart web
```

## 7. Smoke test end-to-end

Acessar pelas URLs do Traefik:

| URL | Esperado |
|---|---|
| `https://app.disclosure.top/admin/stats` | dashboard com counts da DB |
| `https://app.disclosure.top/admin/indexer` | "✓ retrieval operacional" |
| `https://app.disclosure.top/search?q=Olathe` | hits semânticos |
| `https://app.disclosure.top/graph` | force graph com co-mentions |
| `https://app.disclosure.top/timeline` | eventos por década |
| `https://app.disclosure.top/d/doc-342-.../v2` | chunks renderizados inline |
| `https://app.disclosure.top/d/doc-342-.../v2/p001` | single page view |
| `Cmd+K` em qualquer página | command palette com hybrid_search |
| chat: "qual foi a forma dos objetos em Olathe?" | resposta cita `[[doc-id/p001#c0008]]` que vira card com crop |

## 8. Manutenção periódica

```bash
# compactar progress.jsonl (após várias retries do batch)
python3 scripts/33-compact-progress-log.py

# reindexar docs novos que apareceram no disco mas ainda não estão na DB
python3 scripts/30-index-chunks-to-db.py --skip-existing

# regenerar entity_mentions quando 31 novos chunks são adicionados
python3 scripts/31-populate-entity-mentions.py --reset
```

## 9. Troubleshooting

| Sintoma | Causa | Fix |
|---|---|---|
| `/api/search/hybrid` 503 | embed-service down | `docker compose logs embed` |
| `/api/admin/indexer` mostra "db_error" | DATABASE_URL errada ou DB parado | `docker compose logs db` |
| chat retorna `"retrieval_unavailable"` | DB ou embed-service inacessíveis | restart no compose |
| `/graph` vazio | entity_mentions não populado | rodar `31-populate-entity-mentions.py` |
| `/timeline` vazio | events sem `date_start` no frontmatter | revisar wiki/entities/events/ |
| bbox crop 500 | PNG faltando em processing/ | rodar `01-convert-pdfs.sh` |
| Anthropic 429 no batch | quota Max 20x (5h window) | esperar reset; orchestrator agora aborta cedo |

## 10. Custos recorrentes

- **Embedding (BGE-M3 self-host)**: $0
- **Reranker (BGE-Reranker-v2-M3 self-host)**: $0
- **Postgres + pgvector**: já incluso no plano VPS
- **LLM (chat agent)**: OpenRouter — deepseek-v4-flash:free ($0) ou paid model conforme tier
- **Re-rebuild de docs**: só quando schema mudar (Anthropic API ou Claude Code Max quota)
