---
title: "Tech Debt Assessment — The Disclosure Bureau"
project: disclosure-bureau
business: systems-atelier
author: sa-principal (Principal Architect)
baseline_commit: 19d0678
generated_at: 2026-05-17
schema_version: 0.1.0
---

# 2. Tech Debt Assessment — priorizado

Cada débito carrega: **severidade** (`high` / `med` / `low`), **categoria** (UX | Pipeline | Arch | Sec | Ops | DevEx), **localização concreta no repo**, **esforço estimado em homem-dias** (`d`) por engenheiro sênior, e **bloqueio percebido** (o que ele impede).

A precificação é baseada na escala observada (116 docs, 20.974 chunks, 34.370 entidades) e na ausência de testes — qualquer refactor exige escrever o teste antes.

## 2.1 Sumário

| # | Severidade | Débito | Esforço |
|---|---|---|---|
| 1 | **HIGH** | Stubs "Phase X" em 22.096 arquivos de wiki | 3d (saneamento) + 8d (enrichment real, opcional) |
| 2 | **HIGH** | Case Bureau (Tetlock+Locard) nunca rodou | 12d |
| 3 | **HIGH** | Timeline mostra 7.090 eventos com `_Stub. Will be enriched in Phase 7._` | 4d (filtra+narrar) ou rola na fase 0 |
| 4 | **HIGH** | Chat "AG-UI style" caseiro, sem padrão | 5d (adotar AG-UI ou assistant-ui) |
| 5 | **HIGH** | Dashboards investigativos inexistentes (só /admin/stats técnico) | 8d |
| 6 | **HIGH** | Zero testes automatizados | 4d (smoke + retrieval golden set) + ongoing |
| 7 | **MED** | Reranker cold start ~30s + 60s timeout client-side | 3d (warm-pool + queue) |
| 8 | **MED** | Sigma.js consome `graph.json` 9.2 MB inteiro | 4d (server-side neighbors) |
| 9 | **MED** | `entity_mentions` materializada por ILIKE (pode duplicar/perder) | 3d |
| 10 | **MED** | 28+ scripts `rebuild_doc<NN>*.py` ad-hoc | 4d (unificar em `rebuild.py --doc`) |
| 11 | **MED** | GoTrue com `MAILER_AUTOCONFIRM=true` sem SMTP | 1d (configurar SMTP) ou 0.5d (gate por allowlist temporária) |
| 12 | **MED** | `.env.backup.1778796747` com credenciais reais commitada | 0.5d + rotação dos segredos |
| 13 | **MED** | `@assistant-ui/react` no `package.json` e não usado | 1d (decisão + remoção ou adoção) |
| 14 | **MED** | Schema sem `tenant_id`/`corpus_id` (multi-tenant futuro) | 4d (schema) + 6d (UI/refactor) |
| 15 | **LOW** | `wiki/log.md` 459 KB monolítico | 1d (rotacionar) |
| 16 | **LOW** | Dois libs de grafo coexistem (sigma + react-force-graph) | 1d (escolher uma, remover outra) |
| 17 | **LOW** | `outputFileTracingIncludes` cobre paths absolutos do dev | 1d (consolidar via env) |
| 18 | **LOW** | Falta CI/CD | 2d (GitHub Actions: lint + build + smoke) |
| 19 | **LOW** | Sem observabilidade (logs, metrics, error tracking) | 3d (Loki + Grafana + Sentry self-hosted) |
| 20 | **LOW** | Documentação ADR ausente | coberto neste deliverable + onging |

**Total estimado:** ~75 homem-dias para zerar o débito high/med a profundidade média. Quanto disso fica em fases vai no entregável 04.

---

## 2.2 Detalhamento

### 2.2.1 [HIGH] Stubs "Phase X" — erosão de confiança total

**Fonte do mal:** `scripts/03-dedup-entities.py`, linhas 167-169, escreve literalmente:

```python
"narrative_summary": "_Stub. Will be enriched in Phase 7._",
"narrative_summary_pt_br": "_Stub. Será enriquecido na Fase 7._",
"_Stub generated by entity dedup. Will be enriched in Phase 6._\n\n"
```

**Distribuição atual:**

- 22.096 arquivos em `wiki/entities/**/*.md` carregam "Will be enriched"
- 7.090 / 7.091 events com `narrative_summary: _Stub.`
- ~64% das 34.370 entidades sem narrativa real

**Por que fere o usuário:** o produto promete "wiki investigativa". O leitor entra em qualquer entidade não-trivial (`/e/event/EV-1492-…`) e lê _"Será enriquecido na Fase 7"_. A mensagem comunica: **isto é alpha incompleto**. O usuário declarou furor explícito no brief.

**Esforço:**

- **Saneamento básico — 3d.** Reescrever `03-dedup-entities.py` para nunca emitir stub textual; entidades sem narrativa exibem badge "sem síntese ainda" no UI e ficam fora dos listings públicos por default. Rodar um script `scripts/35-strip-stubs.py` que substitui o texto pelo campo vazio + booleano `needs_synthesis: true`.
- **Enrichment real — 8d (opcional, pode ser fila contínua).** Pipeline `scripts/36-enrich-entities.py` que itera entidades com `total_mentions ≥ N`, recupera chunks via `entity_mentions`, sintetiza com Claude Code OAuth, escreve `narrative_summary` real. Custo: ~$80-150 dependendo do limiar.

**Categoria:** UX + Pipeline. **Bloqueia:** Timeline, Entity pages, Case Bureau.

---

### 2.2.2 [HIGH] Case Bureau — schema definido, pipeline jamais rodou

**Estado físico:**

```
case/
├── connect-the-dots/   0 files
├── evidence/           0 files
├── gaps/               (only 2 placeholder gaps from initial bootstrap)
├── hypotheses/         0 files
├── profiles/           0 files
├── timelines/          0 files
└── witnesses/          0 files
```

**O que o CLAUDE.md promete:** 12 tipos investigativos (evidence, witness, hypothesis, gap, relation, timeline, actor_profile, case_report, residual_uncertainty, …) com chain-of-custody Locard, hypothesis tournament Tetlock, ≥3 hipóteses por caso, posteriores bayesianos, 6 quality rubrics threshold 0.85.

**O que existe:** nada. Os 8 detetives (Holmes/Poirot/Dupin/Locard + Schneier/Tetlock/Taleb) são copy do README; não há agente rodando, não há output.

**Esforço — 12d quebrado em 3 etapas:**

1. **3d — Define-and-seed.** Escrever `scripts/40-case-seed.py` que cria 5 casos âncora (Roswell, Nimitz Tic-Tac, Phoenix Lights, FBI Vault Hottel memo, NASA Apollo radio anomaly). Cada caso = 1 timeline, 3-5 evidence cards com `verbatim_excerpt` + `source_page` + `bbox`, 2-3 witness_analysis com `verdict`, ≥3 hipóteses concorrentes com priors Tetlock.
2. **4d — Hypothesis engine.** `scripts/41-hypothesis-tournament.py` lê evidências, gera 3+ hipóteses via LLM, calcula posterior via Bayes simples (prior × likelihood), ranqueia, escreve `case/hypotheses/H-NNNN.md` + atualiza `case-report.md`. Modelo: Claude Code OAuth, Sonnet para case-writer / red-team review.
3. **5d — UI surface.** Novas rotas Next.js: `/case`, `/case/[id]`, `/case/[id]/hypotheses`, `/case/[id]/evidence`. Componentes: `CaseTimelineLane`, `HypothesisTournamentCard`, `EvidenceChainCustody`, `WitnessVerdictBadge`. Linka para `[[evidence/E-0042]]` resolvido pelo router.

**Categoria:** Arch + UX + Pipeline. **Bloqueia:** posicionamento como "super centro de investigação" (visão do brief).

---

### 2.2.3 [HIGH] Timeline — funciona mas mostra 99.99% de stubs

**Localização:**

- `web/app/api/timeline/route.ts` (119 LOC) — lê `wiki/entities/events/*.md`, ordena por `date_start`. Funciona.
- `web/components/timeline-view.tsx` (143 LOC) — agrupa por década, lista cards. Funciona.
- **Dados:** 7.090 dos 7.091 events com `narrative_summary: _Stub.`.

**O bug não é a Timeline; é o conteúdo.** Mas o usuário vê a Timeline. Daí ela vira o sintoma.

**Esforço — 4d (curto-prazo) ou rola junto com 2.2.1:**

1. **1d** — Filtro server-side em `/api/timeline`: por default só devolve eventos com `narrative_summary` ausente do regex `/^_Stub/`. Query param `?include_stubs=true` para o caso real (ainda 1 evento; vai aumentar conforme enrichment roda).
2. **1d** — UI: separar "Eventos consolidados" (com narrativa) e "Eventos catalogados" (count agregado por década) em duas faixas da Timeline.
3. **2d** — Camada de canonização: 50-100 eventos curados manualmente (Roswell, Nimitz, Phoenix, Aurora, Foo-fighters, Operation Mainbrace, AATIP, etc.) com narrativa rica escrita pelo case-writer. Esses ficam pinned no topo de cada década.

**Categoria:** UX. **Bloqueia:** primeira impressão da feature.

---

### 2.2.4 [HIGH] Chat — "AG-UI style" caseiro

**Localização:**

- `web/lib/chat/agui.ts` (65 LOC) — eventos `text_delta`, `tool_start`, `tool_result`, `navigate`, `done`, `error`.
- `web/lib/chat/openrouter.ts` (341 LOC) — SSE parser + loop de tool-call manual.
- `web/components/chat-bubble.tsx` (507 LOC) — UI consumindo SSE manualmente.

**Limites:**

- Sem retomada de stream em queda de conexão.
- Sem persistência intermediária — se o usuário fecha o modal no meio, o assistant message é perdido.
- Sem cancelamento limpo (abort do `fetch` não chega ao backend, que segue executando tools).
- Sem artefatos tipados — citações são strings markdown parseadas no client.
- Sem crops bbox inline no chat (citação aponta para `/d/<doc>#<chunk>` mas o crop só renderiza fora do chat).

**Esforço — 5d:**

1. **2d** — Decidir entre adotar [AG-UI](https://github.com/ag-ui-protocol/ag-ui) puro (mais alinhado ao trace "/state/run" do protocolo) **ou** adotar `@assistant-ui/react` (já no `package.json`, ainda não usado). ADR-001 cobre.
2. **2d** — Refactor `chat-bubble.tsx` → componentes pequenos (`ChatComposer`, `ChatStream`, `ToolBlock`, `CitationCrop`, `NavigationOffer`). Cada citação `chunk_id` vira `<Citation/>` com bbox crop inline via `/api/crop`.
3. **1d** — Persistência incremental: cada `text_delta` flush no `messages.content` via Realtime, não ao final. Aborto via `AbortController` chega ao loop server-side via header `x-chat-cancel: <session_id>`.

**Categoria:** UX + Arch. **Bloqueia:** experiência conversacional sólida.

---

### 2.2.5 [HIGH] Dashboards investigativos inexistentes

**Estado:** existe `/admin/stats` que mostra contadores técnicos (docs/chunks/entities/redactions). Não existe **dashboard investigativo**.

**Falta:**

- **Clusters** de eventos por proximidade temporal+geográfica (KMeans sobre `date_start` + `primary_location` geocoded).
- **Mapa**: choropleth/scatter geográfico de sightings (precisa geocode em `primary_location`).
- **Atores recorrentes**: top-50 pessoas/organizações por menções, com sparkline temporal.
- **Padrões**: redaction-heavy ratio por collection; UFO anomaly heatmap por ano; cryptid hotspots.
- **Connect-the-dots view**: para um caso aberto, qual o subgrafo mínimo de entidades conectadas.

**Esforço — 8d:**

1. **2d** — Geocoding pipeline (`scripts/42-geocode-locations.py`) usando Nominatim self-host (Docker) — sem SaaS. Persistir lat/lon em `entities` via colunas `lat`, `lon`, `geo_confidence`.
2. **3d** — Rotas e componentes Next: `/dashboard/clusters`, `/dashboard/map`, `/dashboard/actors`, `/dashboard/patterns`. Lib: `react-simple-maps` ou MapLibre GL (open-source, self-hosted tiles).
3. **3d** — Conteúdo: queries Postgres dedicadas (views materializadas atualizadas em incremental), componentes de gráfico (Recharts ou Visx). Linka tudo de volta para entities/chunks.

**Categoria:** UX + Arch. **Bloqueia:** posicionamento "super centro de investigação".

---

### 2.2.6 [HIGH] Zero testes automatizados

`find /Users/guto/ufo -name "*.test.*" -o -name "*_test.py" -o -name "*.spec.*"` → 0 hits (excluindo `node_modules`).

**Risco:**

- Refactors em `scripts/03-dedup-entities.py` (vilão dos stubs) podem corromper 22k arquivos.
- Mudança no schema do chat (`agui.ts`) sem regression test quebra o SSE no browser.
- Migration `0003_*` futura sem teste pode dropar coluna usada por tool handler.

**Esforço — 4d para um piso mínimo:**

1. **1d** — Vitest no `web/`, smoke de cada `route.ts` (`/api/timeline`, `/api/search/hybrid`, `/api/chunk`, …). Fixture: subset de 3 docs com 10 chunks.
2. **1d** — Golden retrieval set: 30 queries PT+EN com top-5 chunks esperados; CI valida recall@5 ≥ 0.7.
3. **1d** — pytest para scripts críticos (`03-dedup-entities`, `30-index-chunks-to-db`, `04-lint`). Dataset: 1 PDF de 3 páginas com fixture YAML pronta.
4. **1d** — `.github/workflows/ci.yml`: `pnpm lint && pnpm build && pytest && curl /api/admin/stats`.

**Categoria:** DevEx + Quality. **Bloqueia:** velocidade de mudança segura.

---

### 2.2.7 [MED] Reranker cold start + timeout 60s

**Localização:** `web/lib/retrieval/embed.ts:16` (`signal: AbortSignal.timeout(60_000)`). `infra/embed-service/app.py` faz lazy load com `Lock`; primeira chamada após reinício baixa ~2.5 GB de modelo.

**Sintoma:** primeira query do dia leva 30-60s. Brief lista isso como gap médio.

**Esforço — 3d:**

1. **1d** — Warm-pool: container do embed-service ganha um `healthcheck` que dispara `/embed` com `["warmup"]` no startup. `restart: unless-stopped` mantém quente.
2. **1d** — Mover rerank para fila assíncrona com SSE event `rerank_progress` (`text_delta` no chat enquanto roda). Pré-render de top-20 RRF, rerank atualiza inline.
3. **1d** — Cache de rerank: hash `(query_norm, candidate_ids)` → score, TTL 24h. Postgres tabela `rerank_cache(hash TEXT PK, scores REAL[], created_at)`.

**Categoria:** Performance. **Bloqueia:** UX de busca.

---

### 2.2.8 [MED] Sigma.js `graph.json` 9.2 MB

**Localização:** `wiki/graph.json` (9.2 MB), carregado por `web/components/sigma-graph-client.tsx` e `force-graph-canvas.tsx` no cliente.

**Esforço — 4d:**

1. **2d** — Endpoint server-side `/api/graph/neighbors?node=<id>&depth=1&limit=50`: BFS no `entity_mentions` retorna só vizinhança da query, ~30 KB.
2. **1d** — Carregar inicial: hub-and-spoke dos 100 entities com `total_mentions > N` (≤200 KB), expand-on-click busca vizinhos.
3. **1d** — Decidir uma lib: ADR-006 (sigma vs react-force-graph). Remover a outra.

**Categoria:** Performance + DevEx. **Bloqueia:** /graph escalar para mais corpora.

---

### 2.2.9 [MED] `entity_mentions` via ILIKE

**Localização:** `scripts/31-populate-entity-mentions.py`. A materialização do grafo usa ILIKE de `canonical_name` e `aliases` contra `content_en/pt` — frágil para nomes ambíguos ("Hoover", "Park") e perde menções com variações ortográficas.

**Esforço — 3d:**

1. **1d** — Substituir ILIKE por `tsvector` match com `pt_unaccent/en_unaccent`, ranqueado.
2. **1d** — Adicionar coluna `mention_confidence REAL` baseado em (alias match exato vs canonical match vs fuzzy via pg_trgm `%`).
3. **1d** — Pipeline diff: nova execução compara contagens com versão anterior e reporta variações > 10% como red flag.

**Categoria:** Pipeline + Quality. **Bloqueia:** grafo confiável.

---

### 2.2.10 [MED] Sprawl `rebuild_doc<NN>*.py`

**Lista parcial:** `rebuild_doc65.py`, `rebuild_doc65_v2.py`, `rebuild_doc65_full.py`, `rebuild_doc65_gemini.py`, `rebuild_doc65_page_rebuilder.py`, `rebuild_doc65_s2_v2.py`, `rebuild_doc65_s8.py`, `rebuild_doc65_section2..8.py`, `rebuild_doc65_serial130_resume.py`, `rebuild_doc65_suba_final.py`, `rebuild_doc_d48.py`, `rebuild_doc_section3.py`, `rebuild_doc255.py`, `rebuild_doc38.py`, … 28+ arquivos.

**Esforço — 4d:**

1. **2d** — Consolidar em `scripts/rebuild.py --doc <doc-id> --phase <vision|chunk|enrich|index>` com flags por seção. Parâmetros que hoje viram nomes de arquivo viram args.
2. **1d** — Mover os 28 arquivos para `scripts/_archive/` antes de remover (preservar histórico de hot-fix).
3. **1d** — README dos scripts canônicos: cada `NN-...` ganha purpose, inputs, outputs.

**Categoria:** DevEx. **Bloqueia:** novos engenheiros entendendo o pipeline.

---

### 2.2.11 [MED] GoTrue `MAILER_AUTOCONFIRM=true` sem SMTP

**Localização:** `infra/disclosure-stack/docker-compose.yml:81`.

**Risco:** qualquer e-mail (inclusive throwaway) vira conta ativa. Sem rate-limit no signup. Em produção pública, isso é vetor de spam/abuso e infla `chat_sessions` com lixo.

**Esforço:**

- **0.5d** — allowlist temporária: trigger `BEFORE INSERT ON auth.users` rejeita domínios fora de `('disclosure.top', 'plataformainfraredmed.org', allowed_domains_table)`.
- **1d** — Configurar SMTP (Postmark / Mailgun / SES — escolha de provedor é decisão de operação, não data plane). `GOTRUE_MAILER_AUTOCONFIRM=false`. Tela de "confirme seu e-mail" no signup.

**Categoria:** Sec + Ops. **Bloqueia:** abertura pública controlada.

---

### 2.2.12 [MED] `.env.backup.*` versionado com credenciais

**Localização:** `infra/disclosure-stack/.env.backup.1778796747` contém `CLAUDE_CODE_OAUTH_TOKEN`, `OPENROUTER_API_KEY`, `POSTGRES_PASSWORD`, `JWT_SECRET`.

**Esforço — 0.5d + rotação:**

1. Adicionar `.env.backup.*` ao `.gitignore`.
2. Remover do histórico via `git filter-repo` ou `bfg-repo-cleaner` (operação destrutiva — requer alinhamento com user).
3. **Rotacionar TODOS os segredos vazados** (OAuth token, OpenRouter key, JWT secret, postgres password). Sem essa rotação, o passo 1+2 é teatro.
4. Documentar a rotação no `infra/DEPLOY-CHECKLIST.md`.

**Categoria:** Sec. **Bloqueia:** higiene básica de produção.

> **NOTA:** Este débito tem veto implícito de segurança. Não é só "ruim" — é tóxico. Deve ser fixado **antes** de abrir o produto para sign-ups externos.

---

### 2.2.13 [MED] `@assistant-ui/react` fantasma

**Localização:** `web/package.json:13` declara `"@assistant-ui/react": "^0.14.0"`. `grep -r "@assistant-ui" web/{app,components,lib}` retorna **0 hits** em código importado.

**Esforço — 1d:**

- Decisão técnica em ADR-001. Se a escolha for adotar, planeja-se o uso. Se não, remove do `package.json` e do `package-lock.json`.

**Categoria:** DevEx. **Bloqueia:** clareza de decisão de chat.

---

### 2.2.14 [MED] Schema sem `tenant_id` / `corpus_id`

**Localização:** todas as tabelas em `infra/supabase/migrations/0002_chunks_retrieval.sql` (`documents`, `chunks`, `entities`, `entity_mentions`) e em `0001_chat_schema.sql` (`chat_sessions`, `messages`).

**Estado:** monolítico para um único corpus (war.gov/ufo). A visão de longo prazo é multi-tenant / multi-corpus (gap declarado no brief: "Multi-tenant/multi-corpus").

**Esforço — 4d (schema) + 6d (UI + refactor):**

1. **2d** — Migration `0003_multitenant.sql`: `corpora(corpus_id TEXT PK, ...)`, `documents` ganha FK `corpus_id`. RLS policy passa de `USING (TRUE)` para `USING (corpus_id IN (current_user_corpora()))`.
2. **2d** — Wiki paths: `wiki/<corpus_id>/documents/...` (refactor estrutura) **ou** rotear via env `CORPUS_ID` (single-corpus por deploy — mais simples; perde "single pane of glass").
3. **6d (UI)** — Routes Next ganham `[corpusId]` segment. Chat sessions vinculam corpus. Switcher de corpus na navbar para usuários com mais de um.

Trade-offs em **ADR-005**.

**Categoria:** Arch. **Bloqueia:** visão de longo prazo.

---

### 2.2.15 [LOW] `wiki/log.md` 459 KB monolítico

`wiki/log.md` é append-only desde o início e está em 459 KB. Eventualmente passa do tamanho razoável de um arquivo MD navegado por humano.

**Esforço — 1d:**

- Rotacionar por mês: `wiki/log.md` (current) + `wiki/log/<YYYY-MM>.md` (rotated). Script `scripts/33-compact-progress-log.py` já existe — adaptar para rotação.

---

### 2.2.16 [LOW] Duas libs de grafo

**Localização:** `web/package.json` declara `sigma`, `graphology`, `@react-sigma/*` **e** `react-force-graph-2d`.

**Esforço — 1d:** decisão + remoção da não-escolhida (ADR-006).

---

### 2.2.17 [LOW] `outputFileTracingIncludes` com paths relativos absolutos

`web/next.config.ts` usa `"../wiki/**"`, `"../processing/**"` — assume o layout do monorepo. Em Docker, `WORKDIR /app/web` e o COPY ajusta, mas o desenvolvedor que clona não-monorepo quebra.

**Esforço — 1d:** envelopar com `process.env.UFO_ROOT_TRACE || "../"`.

---

### 2.2.18 [LOW] Sem CI/CD

Esforço — 2d:

1. **1d** — `.github/workflows/ci.yml`: lint + build + smoke.
2. **1d** — Deploy: SSH para VPS, `docker-compose pull && up -d web` orquestrado por workflow `release.yml` em push de tag.

---

### 2.2.19 [LOW] Sem observabilidade

Esforço — 3d:

1. **1d** — Loki + Promtail recebem stdout dos serviços compose.
2. **1d** — Grafana dashboard básico: latência hybrid_search, error rate, signup rate.
3. **1d** — Sentry self-hosted (ou GlitchTip open-source) capturando exceptions.

---

### 2.2.20 [LOW] ADRs ausentes

Este deliverable inicia a prática. Continuação como hábito: cada decisão type-1 (stack, contrato público, fronteira de serviço) escreve um ADR. Pasta `docs/adr/`. Convenção `NNNN-titulo.md`.

---

## 2.3 Mapa débito × arquivos concretos

| Débito | Arquivo principal |
|---|---|
| Stubs Phase X | `scripts/03-dedup-entities.py:167-169` + `wiki/entities/**/*.md` |
| Case Bureau vazio | `case/{evidence,hypotheses,witnesses,timelines,profiles,connect-the-dots}/` |
| Timeline stubs | `web/app/api/timeline/route.ts` + `wiki/entities/events/EV-*.md` |
| Chat AG-UI caseiro | `web/lib/chat/agui.ts` (65 LOC) |
| Dashboards | `web/app/admin/stats/page.tsx` (único existente) |
| Testes | repo todo |
| Reranker cold | `infra/embed-service/app.py` + `web/lib/retrieval/embed.ts:16` |
| Grafo 9.2 MB | `wiki/graph.json` + `web/components/sigma-graph-client.tsx` |
| Entity mentions ILIKE | `scripts/31-populate-entity-mentions.py` |
| Sprawl rebuild | `scripts/rebuild_doc*.py` (28+) |
| SMTP / autoconfirm | `infra/disclosure-stack/docker-compose.yml:81` |
| .env backup vazado | `infra/disclosure-stack/.env.backup.1778796747` |
| assistant-ui fantasma | `web/package.json:13` |
| Multi-tenant | todas as migrations |
| log.md gigante | `wiki/log.md` |
| Sigma vs force-graph | `web/package.json` |
| Trace includes | `web/next.config.ts:6-13` |
| CI/CD | (ausente) `.github/workflows/` |
| Observabilidade | (ausente) |
| ADRs | `docs/adr/` (a criar) |

---

_Esforços calibrados para 1 engenheiro sênior + 1 IA-pair (Claude Code OAuth ou Cursor) em ritmo zero-human, com a arquitetura alvo do entregável 04 como roadmap._
