phase-0: kill stubs, ship 20 curated anchor events, configure SMTP

- scripts/03-dedup-entities.py: stop emitting placeholder narrative ("Stub. Will
  be enriched in Phase 7"); write summary_status=none + null fields instead.
- scripts/maintain/41_strip_stubs.py: idempotent migration that cleaned the
  22,096 entity .md files (now zero stub strings in wiki/).
- scripts/synthesize/01_anchor_events.py: curated 20 anchor UAP events
  (Roswell, Nimitz Tic-Tac, Phoenix Lights, Operação Prato, AATIP, etc.) with
  bilingual Holmes-Watson narrative via claude -p --model sonnet
  (CLAUDE_CODE_OAUTH_TOKEN). All summary_status=curated, confidence=high.
- web/api/timeline + timeline-view: filter narrative-less events by default,
  render "curado" badge for hand-vetted ones, drop the date display alone.
- CLAUDE-schema-full.md: document the summary_status enum and the four states.
- docker-compose.yml: SMTP_HOST=mail.spacemail.com configured;
  GOTRUE_MAILER_AUTOCONFIRM flipped to false (real email confirmation working).
- .nirvana/outputs/.../systems-atelier/: 5 deliverables of the architecture
  audit that produced this roadmap.
This commit is contained in:
guto 2026-05-18 00:44:17 -03:00
parent 19d0678e55
commit 4459bd17e4
46 changed files with 4418 additions and 17 deletions

View file

@ -0,0 +1,63 @@
# Brief
**Business:** systems-atelier
**Project ID:** disclosure-bureau
**Submitted:** 2026-05-18T01:48:43Z
## Conteúdo
Audite e proponha plano de modernização completo do **The Disclosure Bureau** — sistema de investigação sobre documentos declassificados.
# Contexto
The Disclosure Bureau (https://disclosure.top) recebe documentos (PDF/imagens), extrai conteúdo via vision LLM, converte em markdown estruturado bbox-aware, gera embeddings densos + esparsos, popula grafo de co-menções entre entidades, e expõe uma wiki investigativa + agente de chat (Sherlock). Corpus atual: 116 PDFs do war.gov/ufo (DOD UAP, FBI Vault, NASA Apollo/Skylab, FOIA) → 3.435 páginas → 20.974 chunks → 34.370 entidades → 248.398 menções.
**Repo:** `/Users/guto/ufo` (commit baseline `19d0678`)
**Live:** https://disclosure.top
**Instrução canônica:** `/Users/guto/ufo/CLAUDE.md` + `/Users/guto/ufo/CLAUDE-schema-full.md`
**Memory dev:** `/Users/guto/.claude/projects/-Users-guto-ufo/memory/MEMORY.md`
## Pipeline atual
1. PDF → PNG 72 DPI (pdftoppm) + OCR (pdftotext)
2. Chunking agêntico com Sonnet 4.6 via subagents → raw/<doc>--subagent/chunks/cNNNN.md (frontmatter YAML + corpo bilíngue EN+PT-BR)
3. Embedding BGE-M3 (1024d) self-hosted FastAPI; reranker BGE-Reranker-v2-M3
4. Postgres 15.8 + pgvector 0.8 + pg_trgm + tsvector → hybrid search RRF
5. Entity mentions materializadas via ILIKE
6. Next.js 15 (App Router) + Tailwind + Sigma.js/ForceAtlas2
## Stack self-hosted (Docker Compose em VPS)
Next.js 15 / React 19 · Supabase (GoTrue, PostgREST, Realtime, Storage, Studio, Kong 2.8, Imgproxy, Meilisearch) · Postgres + pgvector · BGE-M3/Reranker (PyTorch CPU) · Traefik TLS · Sigma.js graphology.
## Restrições não-negociáveis
- PROIBIDO ANTHROPIC_API_KEY. Usar CLAUDE_CODE_OAUTH_TOKEN (Claude Code SDK) OU OPENROUTER_API_KEY (fallback).
- Bilíngue EN+PT-BR (português brasileiro, UTF-8 com acentos preservados).
- Identificadores em inglês internacional.
- Markdown puro como source-of-truth.
- VPS self-hosted via Docker Compose. Sem SaaS proprietário no data plane.
## Funciona hoje
Homepage bento 116 docs · Hybrid search 1.3s · Páginas chunk-a-chunk · Crops bbox auto-tighten · Allowlist image_types · Grafo Sigma.js ForceAtlas2 · Entity pages DB-live · Chat Sherlock (signup/login + Kong corrigido) · /admin/stats, /admin/batch, /admin/indexer.
## Gaps críticos
1. Timeline (/timeline) — eventos extraídos mas todos com placeholder "_Stub. Will be enriched in Phase 7._" — usuário furioso com esse "Phase X"
2. Case Bureau (case/case-report.md, hypotheses Tetlock, evidence Locard) — schema definido, pipeline nunca rodou
3. Dashboards investigativos reais (não admin técnico) — clusters, geografia, atores, padrões temporais
4. A2UI / AG-UI no chat — streaming tool-calls, artefatos clicáveis, crops bbox inline, multi-turn persistente
5. ELIMINAR todos os "Stubs / Will be enriched in Phase X"
## Gaps médios
Mobile responsivo precário · Sem onboarding · Sem export (PDF/CSV/PNG) · Sem saved views · Reranker 60s · Grafo trava em corpus grande.
## Visão longo prazo
Multi-tenant/multi-corpus · API pública REST+GraphQL · MCP server · Visualização temporal-geo.
## Entregáveis solicitados
1. codebase-analysis-report
2. tech-debt-assessment
3. product-discovery-brief
4. system-architecture-doc
5. ADRs onde necessário
Modo: zero-human. NÃO implementar agora — produzir análise + plano em fases priorizadas.
Outputs em ${PROJECTS_OUTPUT_DIR:-/Users/guto/ufo/.projects-outputs}/disclosure-bureau/businesses/systems-atelier/

View file

@ -0,0 +1,15 @@
{
"schema_version": "1.0",
"decisions": [],
"open_questions": [],
"project_id": "disclosure-bureau",
"business_slug": "systems-atelier",
"phase": "plan",
"brief_original": "Audite e proponha plano de modernização completo do **The Disclosure Bureau** — sistema de investigação sobre documentos declassificados.\n\n# Contexto\n\nThe Disclosure Bureau (https://disclosure.top) recebe documentos (PDF/imagens), extrai conteúdo via vision LLM, converte em markdown estruturado bbox-aware, gera embeddings densos + esparsos, popula grafo de co-menções entre entidades, e expõe uma wiki investigativa + agente de chat (Sherlock). Corpus atual: 116 PDFs do war.gov/ufo (DOD UAP, FBI Vault, NASA Apollo/Skylab, FOIA) → 3.435 páginas → 20.974 chunks → 34.370 entidades → 248.398 menções.\n\n**Repo:** `/Users/guto/ufo` (commit baseline `19d0678`)\n**Live:** https://disclosure.top\n**Instrução canônica:** `/Users/guto/ufo/CLAUDE.md` + `/Users/guto/ufo/CLAUDE-schema-full.md`\n**Memory dev:** `/Users/guto/.claude/projects/-Users-guto-ufo/memory/MEMORY.md`\n\n## Pipeline atual\n1. PDF → PNG 72 DPI (pdftoppm) + OCR (pdftotext)\n2. Chunking agêntico com Sonnet 4.6 via subagents → raw/<doc>--subagent/chunks/cNNNN.md (frontmatter YAML + corpo bilíngue EN+PT-BR)\n3. Embedding BGE-M3 (1024d) self-hosted FastAPI; reranker BGE-Reranker-v2-M3\n4. Postgres 15.8 + pgvector 0.8 + pg_trgm + tsvector → hybrid search RRF\n5. Entity mentions materializadas via ILIKE\n6. Next.js 15 (App Router) + Tailwind + Sigma.js/ForceAtlas2\n\n## Stack self-hosted (Docker Compose em VPS)\nNext.js 15 / React 19 · Supabase (GoTrue, PostgREST, Realtime, Storage, Studio, Kong 2.8, Imgproxy, Meilisearch) · Postgres + pgvector · BGE-M3/Reranker (PyTorch CPU) · Traefik TLS · Sigma.js graphology.\n\n## Restrições não-negociáveis\n- PROIBIDO ANTHROPIC_API_KEY. Usar CLAUDE_CODE_OAUTH_TOKEN (Claude Code SDK) OU OPENROUTER_API_KEY (fallback).\n- Bilíngue EN+PT-BR (português brasileiro, UTF-8 com acentos preservados).\n- Identificadores em inglês internacional.\n- Markdown puro como source-of-truth.\n- VPS self-hosted via Docker Compose. Sem SaaS proprietário no data plane.\n\n## Funciona hoje\nHomepage bento 116 docs · Hybrid search 1.3s · Páginas chunk-a-chunk · Crops bbox auto-tighten · Allowlist image_types · Grafo Sigma.js ForceAtlas2 · Entity pages DB-live · Chat Sherlock (signup/login + Kong corrigido) · /admin/stats, /admin/batch, /admin/indexer.\n\n## Gaps críticos\n1. Timeline (/timeline) — eventos extraídos mas todos com placeholder \"_Stub. Will be enriched in Phase 7._\" — usuário furioso com esse \"Phase X\"\n2. Case Bureau (case/case-report.md, hypotheses Tetlock, evidence Locard) — schema definido, pipeline nunca rodou\n3. Dashboards investigativos reais (não admin técnico) — clusters, geografia, atores, padrões temporais\n4. A2UI / AG-UI no chat — streaming tool-calls, artefatos clicáveis, crops bbox inline, multi-turn persistente\n5. ELIMINAR todos os \"Stubs / Will be enriched in Phase X\"\n\n## Gaps médios\nMobile responsivo precário · Sem onboarding · Sem export (PDF/CSV/PNG) · Sem saved views · Reranker 60s · Grafo trava em corpus grande.\n\n## Visão longo prazo\nMulti-tenant/multi-corpus · API pública REST+GraphQL · MCP server · Visualização temporal-geo.\n\n## Entregáveis solicitados\n1. codebase-analysis-report\n2. tech-debt-assessment\n3. product-discovery-brief\n4. system-architecture-doc\n5. ADRs onde necessário\n\nModo: zero-human. NÃO implementar agora — produzir análise + plano em fases priorizadas.\n\nOutputs em ${PROJECTS_OUTPUT_DIR:-/Users/guto/ufo/.projects-outputs}/disclosure-bureau/businesses/systems-atelier/",
"last_task_completed": null,
"next_task_id": null,
"audit_log_path": "audit.jsonl",
"resumption_prompt_hint": "Project just received initial brief. Start at the brief_intake employee for systems-atelier.",
"handoff_timestamp": "2026-05-18T01:48:43.851Z",
"dag_snapshot_path": "dag-state.json"
}

View file

@ -0,0 +1 @@
{"ts":"2026-05-18T01:48:43Z","event":"brief_received","project_id":"disclosure-bureau","business_slug":"systems-atelier","brief_chars":3412}

View file

@ -0,0 +1,306 @@
---
title: "Codebase Analysis Report — 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
---
# 1. Codebase Analysis Report — as-is
Análise do estado atual do repositório `/Users/guto/ufo` no commit baseline `19d0678`, vivo em <https://disclosure.top>. Documento factual: o que existe, em que volume, com que qualidade. Recomendações ficam para os entregáveis 0205.
## 1.1 Visão de mil pés
O Disclosure Bureau é um pipeline de ingestão Markdown-first + Next.js 15 + Supabase self-hosted, com retrieval híbrido BGE-M3 sobre 116 PDFs (3.435 páginas, 20.974 chunks bilingue, 34.370 entidades). É um sistema **brownfield maduro no pipeline de ingestão e raso na camada investigativa**: a base do "Karpathy LLM-wiki" funciona, mas as três promessas de produto (Timeline, Case Bureau, Investigative Dashboards) estão em estado de stub.
## 1.2 Layout físico
```
/Users/guto/ufo/
├── CLAUDE.md contrato vinculante v0.1.0 (10.5 KB, 24 tipos)
├── CLAUDE-schema-full.md schema canônico (36.7 KB)
├── README.md visão geral pública
├── CORPUS-SNAPSHOT.md snapshot estatístico v0.2.0
├── raw/ 252 entradas 116 PDFs imutáveis + 116 dirs `*--subagent` com chunks v0.2
├── processing/ 11 subdirs PNG 72 DPI, OCR, vision JSON, crops, vídeos, frames UAP
├── wiki/ 13 subdirs source-of-truth gerado (documents/, pages/, entities/, images-direct/, tables/, videos/)
├── case/ 7 subdirs Investigation Bureau — TODOS VAZIOS (evidence, hypotheses, witnesses, ...)
├── scripts/ 63 arquivos pipeline numerado 0034 + ~30 scripts ad-hoc de rebuild
├── infra/ 10 subdirs docker-compose disclosure-stack + embed-service + supabase migrations
└── web/ Next.js 15 / React 19 app
```
**Regra de ouro respeitada:** nenhum script escreve em `raw/<arquivo>.pdf` — só em `raw/<doc-id>--subagent/`.
## 1.3 Quantificação
### 1.3.1 Frontend Next.js (`/Users/guto/ufo/web/`)
| Métrica | Valor |
|---|---|
| LOC TypeScript/TSX (sem `node_modules`, `.next`) | **12.072** |
| Rotas API (`route.ts`) | **19** |
| Páginas (`page.tsx`) | **12** |
| Componentes (`components/*.tsx`) | **29** |
| Módulos de biblioteca (`lib/**/*.ts`) | **19** |
| Stack runtime | Next.js 15.1 · React 19 · Tailwind 3.4 · TypeScript 5.6 |
| Dependências de produção | 23 (24 com `@assistant-ui/react` instalado mas **não usado em runtime**) |
### 1.3.2 Pipeline Python (`/Users/guto/ufo/scripts/`)
| Métrica | Valor |
|---|---|
| LOC Python/Shell/JS | **25.298** |
| Scripts numerados canônicos | 34 (`00-…` a `34-…`) |
| Scripts ad-hoc de "rebuild doc X" | 28+ (`rebuild_doc65_*`, `rebuild_doc_d48.py`, etc.) — sinal de pipeline reativo |
| Documentos processados pelo orquestrador `28-batch-rebuild-all.py` | 116/116 |
| Chunks `c*.md` no disco | **20.974** |
| Custo cumulativo declarado (`CORPUS-SNAPSHOT.md`) | ~$409 USD |
### 1.3.3 Infraestrutura (`/Users/guto/ufo/infra/`)
| Componente | Versão | Status |
|---|---|---|
| `db` (Supabase Postgres) | 15.8.1.060 | self-hosted, pgvector + pg_trgm + unaccent |
| `auth` (GoTrue) | v2.170.0 | `MAILER_AUTOCONFIRM=true` (SMTP não configurado) |
| `rest` (PostgREST) | v12.2.8 | OK |
| `realtime` (Supabase) | v2.30.34 | OK |
| `storage` (storage-api) | v1.14.3 | OK |
| `imgproxy` | v3.8.0 | thumbnail/crop server |
| `meta` (postgres-meta) | v0.83.2 | OK |
| `studio` | 20241202 | admin UI |
| `kong` | 2.8.1 | API gateway (corrigido para chat) |
| `meilisearch` | v1.10 | provisionado, **não cabeado ainda ao web** |
| `embed` (FastAPI) | local | BGE-M3 + BGE-Reranker-v2-M3 CPU |
| `web` | Next 15.1 | Dockerfile próprio |
| `traefik-public` | externo | reverse proxy TLS |
11 serviços + 1 rede externa + 4 volumes. Compose tem **367 linhas**.
### 1.3.4 Banco de dados — schema observado
Migrations em `/Users/guto/ufo/infra/supabase/migrations/`:
| Migration | Tabelas/funções |
|---|---|
| `0001_chat_schema.sql` (221 linhas) | `profiles`, `chat_sessions`, `messages`, `usage_events` + trigger `handle_new_user` |
| `0002_chunks_retrieval.sql` (253 linhas) | `documents`, `chunks` (50 colunas, 8 índices, HNSW vector + 2 GIN tsvector + 2 GIN trgm), `entities`, `entity_mentions`, função `hybrid_search_chunks` (RRF server-side) |
**8 tabelas públicas + 1 RPC RRF**. RLS habilitada em todas as tabelas de retrieval (`SELECT` público, escrita só via `service_role`).
### 1.3.5 Corpus — números
| Indicador | Valor |
|---|---|
| PDFs originais (`raw/*.pdf`) | **116** |
| Páginas (chunks `wiki/pages/<doc>/p*.md`) | **3.435** |
| Diretórios `raw/<doc>--subagent/` | 116 (100% cobertura) |
| Chunks bilíngues `c*.md` | **20.974** |
| Imagens cropadas (`raw/*/images/`) | 752 |
| Entidades únicas (`wiki/entities/**/*.md`) | **34.370** |
| ↳ pessoas | 6.297 |
| ↳ organizações | 3.747 |
| ↳ locais | 4.969 |
| ↳ eventos | **7.091** |
| ↳ uap-objects | 1.775 |
| ↳ veículos | 2.174 |
| ↳ operações | 2.723 |
| ↳ conceitos | 5.594 |
| UFO anomaly chunks | 3.020 (14.4%) |
| Cryptid anomaly chunks | 21 (0.1%) |
### 1.3.6 Stubs — o problema que enfurece o usuário
| Métrica | Valor |
|---|---|
| Arquivos `wiki/` com `Will be enriched in Phase X` | **22.096** |
| Eventos com `narrative_summary: _Stub.` | **7.090 / 7.091 (99.99%)** |
| Eventos com summary real | **1** |
| Cobertura agregada de entidades com summary não-stub | ~36% |
A timeline em `/timeline` lê esses 7.091 eventos via `/api/timeline/route.ts` e os ordena por `date_start`. **O eixo do tempo funciona; o conteúdo não existe.** Cada card mostra `_Stub. Will be enriched in Phase 7._` — exatamente o que o usuário denuncia.
## 1.4 Arquitetura observada (não a alvo)
```
┌────────────────────────────────────────────────────────────────────────┐
│ raw/*.pdf (imutável) │
└──────────────────────┬─────────────────────────────────────────────────┘
pdftoppm 72dpi │ pdftotext -layout
┌────────────────────────────────────────────────────────────────────────┐
│ processing/{png,ocr} → scripts/02-vision-page.py (Haiku 4.5) │
│ → scripts/26-chunk-harness.py (Sonnet 4.6) │
│ → scripts/28-batch-rebuild-all.py (orchestr.) │
└──────────────────────┬─────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────┐
│ raw/<doc>--subagent/{chunks,images,tables}/ │
│ wiki/{documents,pages,entities,images-direct}/ (markdown SoT) │
│ case/{evidence,witnesses,hypotheses,…}/ ← VAZIO │
└──────────────────────┬─────────────────────────────────────────────────┘
scripts/30 (BGE-M3 1024d) + scripts/31 (entity_mentions ILIKE)
┌────────────────────────────────────────────────────────────────────────┐
│ Postgres 15.8 + pgvector + pg_trgm + tsvector (pt_unaccent/en_unaccent)│
│ public.hybrid_search_chunks(text, vec, lang, …) → RRF top-k │
└──────────────────────┬─────────────────────────────────────────────────┘
▼ Stage-2 rerank (BGE-Reranker via FastAPI :8000)
┌────────────────────────────────────────────────────────────────────────┐
│ Next.js 15 App Router │
│ ├─ Server: lib/wiki.ts (fs MD) + lib/retrieval/hybrid.ts (pg+embed) │
│ ├─ Chat: lib/chat/openrouter.ts (Pattern C SSE + tool loop) + │
│ │ lib/chat/claude-code.ts (OAuth subprocess, no tools) │
│ └─ Auth: middleware.ts (Supabase SSR) + Kong gateway │
└────────────────────────────────────────────────────────────────────────┘
```
### 1.4.1 Camadas de retrieval
Três camadas independentes coexistem e **não são unificadas**:
1. **Filesystem markdown** (`lib/wiki.ts`, 182 LOC) — leitura de `wiki/*.md` via `gray-matter`. Caminho rápido para document, page, entity. Usada pelo SSR das páginas e pelos tools `read_page`, `read_document`, `read_entity`.
2. **Postgres hybrid** (`lib/retrieval/hybrid.ts`, 170 LOC) — RPC `hybrid_search_chunks` + rerank cliente. Usada pelo tool `hybrid_search` no chat e por `/api/search/hybrid`.
3. **Graph** (`lib/retrieval/graph.ts`, 204 LOC) — `entity_mentions` materializada por scripts/31 via ILIKE. Usada pelos tools `entity_neighbors`, `entity_path`, `co_mention_chunks`.
Não há cache compartilhado entre elas. O grafo Sigma.js carrega o `wiki/graph.json` de **9.2 MB** estático — trava em corpus grande (confirmado pelo gap "Grafo trava em corpus grande" no brief).
### 1.4.2 Chat — Pattern C ad-hoc
`web/lib/chat/` (1.922 LOC) implementa um "AG-UI-style" próprio (`agui.ts`, 65 LOC), não o protocolo AG-UI oficial. Eventos SSE: `text_delta`, `tool_start`, `tool_result`, `navigate`, `done`, `error`. O frontend (`chat-bubble.tsx`, 507 LOC) consome SSE manualmente. **Sem retomada de stream, sem persistência intermediária, sem cancelamento limpo.**
Provedores:
- `openrouter.ts` (341 LOC) — default. Modelos free DeepSeek v4 / Nemotron. Loop de até 5 turnos de tool-calling client-side.
- `claude-code.ts` (105 LOC) — `spawn("claude", ["-p", "--output-format", "json", …])` em subprocess, sem tools, timeout 90s. Lê `CLAUDE_CODE_OAUTH_TOKEN` do env. **Ainda não tem streaming nem tool calling.**
12 tools registradas em `tools.ts` (663 LOC), cobrindo retrieval, leitura, navegação. Sólido em escopo, mas o handler `search_corpus` ainda enumera `wiki/entities/*` por `fs.readdir` em loop — barato para 34k arquivos hoje, **não escala** para multi-tenant.
### 1.4.3 Auth & multi-tenancy
- GoTrue self-hosted com `GOTRUE_MAILER_AUTOCONFIRM=true` (SMTP não conectado). **Em produção sem SMTP, qualquer e-mail vira conta ativa imediatamente.** Risco aceito mas não trackeado.
- `chat_sessions.user_id` referencia `auth.users(id)`. Sem `tenant_id`, sem `corpus_id`. **Multi-tenant = refactor de schema.**
- RLS em `chunks`/`entities` é `SELECT … USING (TRUE)` — corpus público por design.
## 1.5 Qualidade do código
### 1.5.1 Pontos fortes
- **Comentários cabeçalho ricos em todos os módulos relevantes** (`web/lib/chat/openrouter.ts`, `web/lib/retrieval/hybrid.ts`, scripts numerados). Sinal de cuidado de design.
- **Tipagem TS estrita** com interfaces explícitas em todas as fronteiras (`ChunkHit`, `ToolDefinition`, `AGUIEvent`, …).
- **Idempotência declarada** em scripts críticos (`30-index-chunks-to-db.py --skip-existing`).
- **Bilíngue por construção** desde o vision pass (campo `content_pt`/`content_en` espelhado em todo lugar; tsvector próprio para cada idioma).
- **`CLAUDE.md` + `CLAUDE-schema-full.md` formam um contrato vinculante** auto-suficiente — Fowler aprovaria. Schema das 24 entidades, regex de IDs, naming canônico, idempotência: tudo escrito.
- **Configuração de busca textual cuidadosa**: `pt_unaccent` e `en_unaccent` com `unaccent + portuguese_stem` — não é "ts_default" preguiçoso.
- **`outputFileTracingIncludes` em `next.config.ts`** garante o bundle Docker carregar `wiki/`, `processing/`, `raw/*--subagent/`. Detalhe operacional não-trivial resolvido.
### 1.5.2 Pontos fracos
- **Zero testes automatizados.** `find /Users/guto/ufo -name "*.test.*" -o -name "*_test.py"` retorna 0 hits (excluindo `node_modules`). Sem regression suite, refactors são apostas.
- **Sprawl de scripts ad-hoc**: 28+ arquivos `rebuild_doc<NN>*.py` no diretório `scripts/`. O ideal seria um único `rebuild.py --doc <id> --phase <n>`. Sinal de pipeline que era para ser robusto mas teve que ser remendado por documento.
- **Acoplamento direto ao filesystem do host**: `UFO_ROOT = process.env.UFO_ROOT ?? "/Users/guto/ufo"` espalhado em `lib/wiki.ts`. O Docker mitiga via volume mount mas o código carrega a premissa de monorepo.
- **`@assistant-ui/react` no `package.json` mas não importado em runtime** (`grep -r "assistant-ui" /Users/guto/ufo/web/{app,components,lib}` não retorna nada relevante). Dependência fantasma.
- **`scripts/03-dedup-entities.py` é o vilão dos stubs.** Linhas 167169 escrevem literalmente `narrative_summary: _Stub. Will be enriched in Phase 7._` em todo entity sintetizada. Phase 7 nunca foi implementada como passe sistemático.
- **Grafo client-side custoso**: `wiki/graph.json` = 9.2 MB carregado inteiro no browser pelo Sigma.js. Sem viewport-aware lazy load.
- **Reranker 60s** (gap declarado no brief) — `signal: AbortSignal.timeout(60_000)` em `lib/retrieval/embed.ts` é um workaround, não solução. BGE-Reranker-v2-M3 CPU em VPS pequena tem cold start de ~30s + 200-500ms por par query/doc.
- **Sem migrations versionadas para o schema da wiki** — a "evolução" do schema dos 24 tipos é manual via `CLAUDE-schema-full.md`. Lint é manual em `scripts/04-lint.py`.
### 1.5.3 Hotspots de complexidade
| Arquivo | LOC | Justificativa |
|---|---|---|
| `web/lib/chat/tools.ts` | **663** | 12 tools + handlers + schemas inline. Candidato número 1 a quebra modular. |
| `web/components/chat-bubble.tsx` | **507** | UI + state + auth + SSE parser + tool rendering em um arquivo. |
| `web/lib/chat/openrouter.ts` | **341** | Loop de tool-call client-side; SSE parser manual de 60+ linhas. |
| `scripts/28-batch-rebuild-all.py` | ~700 | Orquestrador `ThreadPoolExecutor` com retries e quota; razoável dado o domínio. |
| `infra/disclosure-stack/docker-compose.yml` | **367** | 11 serviços; longo mas plano. |
## 1.6 Dependências externas
### 1.6.1 Frontend (`web/package.json`)
- **Hard deps**: `next@^15.1`, `react@^19`, `@supabase/ssr@^0.10`, `@supabase/supabase-js@^2.105`, `pg@^8.13`, `gray-matter@^4.0`, `sharp@^0.33`, `react-markdown@^9`, `remark-wiki-link@^2`.
- **Graph stack**: `sigma@^3`, `graphology@^0.25`, `graphology-layout-forceatlas2@^0.10`, `@react-sigma/core@^5`, `@react-sigma/layout-forceatlas2@^5`, `react-force-graph-2d@^1.27`**duas libs de grafo coexistem** (sigma e react-force-graph). Decisão pendente.
- **UI**: `@radix-ui/react-dialog@^1.1`, `@radix-ui/react-tooltip@^1.1`, `lucide-react@^0.460`, `framer-motion@^11.11`.
- **Fantasma**: `@assistant-ui/react@^0.14` declarado e não usado.
### 1.6.2 Backend ML
- **embed-service** (`infra/embed-service/`, 148 LOC FastAPI): `FlagEmbedding` (BGE-M3 + BGE-Reranker-v2-M3). CPU-only via env `DEVICE=cpu`. Lazy load com `Lock` para thread safety. ~2.5 GB RAM resident.
- Sem GPU declarada. Reranker quente reusa o modelo; cold start carrega ambos os modelos em sequência.
### 1.6.3 LLM providers
- **`OPENROUTER_API_KEY`** — chat default (DeepSeek v4 free, Nemotron 3 fallback).
- **`CLAUDE_CODE_OAUTH_TOKEN`** — Claude Code CLI subprocess para scripts Python (vision, chunking, synthesis) **e** para o chat em modo `claude-code`.
- **ANTHROPIC_API_KEY proibido** — verificado: o único hit em código vivo é uma string de aviso "NEVER use ANTHROPIC_API_KEY" em comments. Há referência stale em `infra/coolify/NEXTJS.md:30` que precisa ser removida (documentação antiga).
### 1.6.4 OS-level
- `pdftoppm` (Poppler) e `pdftotext -layout` — ambos invocados por `01-convert-pdfs.sh`.
- `claude` CLI no PATH do container — instalação via curl no Dockerfile do web (atualmente **não está no Dockerfile do `web/Dockerfile`** — o `claude-code.ts` falha silenciosamente em container sem o binário; a fallback para OpenRouter cobre, mas a Pattern C com Claude oficial nunca rodou).
## 1.7 Operacional
- **Deploy:** Docker Compose. Roteamento via `traefik-public` externo (network já existente do host). `DEPLOY-CHECKLIST.md` em `infra/` mantém runbook.
- **Observabilidade:** `wiki/log.md` (459 KB, append-only) é o único log estruturado da pipeline de ingestão. `raw/_batch-rebuild/progress.jsonl` é lido pela rota `/api/admin/batch`. **Sem Prometheus/Grafana, sem error tracking (Sentry), sem trace distribuído.**
- **Backups:** não declarados. Volume `db-data` do Postgres é o único cofre — sem dump automatizado documentado.
- **CI/CD:** ausente. Push direto via `git`; build local. Não existe `.github/workflows`.
- **Secret hygiene:** `.env.backup.1778796747` em `infra/disclosure-stack/` contém credenciais reais (OAuth, OpenRouter, JWT). **Precisa sair do git-history.**
## 1.8 Documentação
- `CLAUDE.md` (10.5 KB) — contrato canônico, atualizado e usado.
- `CLAUDE-schema-full.md` (36.7 KB) — schema dos 24 tipos. Vivo.
- `README.md` (6.3 KB) — visão geral do produto, quickstart, stack. Vivo.
- `CORPUS-SNAPSHOT.md` (3.6 KB) — última snapshot da pipeline. Vivo (gerado 2026-05-17).
- `infra/DEPLOY-CHECKLIST.md` (6.8 KB) — runbook.
- `infra/RETRIEVAL.md` (3.6 KB) — explicação do retrieval híbrido.
- `wiki/log.md` (459 KB) — log append-only de operações de pipeline. Inflado.
A documentação é desproporcionalmente forte do lado **filosofia/contrato** e fraca do lado **ADR/decisões técnicas históricas**. Não há um ADR único no repo.
## 1.9 Síntese dos hotspots
| Hotspot | Localização | Impacto |
|---|---|---|
| **Timeline + 7.091 stubs** | `scripts/03-dedup-entities.py:167-169`, `wiki/entities/events/*.md` | UX bloqueada (gap #1 do brief) |
| **Case Bureau vazio** | `case/{evidence,hypotheses,witnesses,timelines,profiles,connect-the-dots}/` (0 arquivos) | A promessa investigativa do produto não existe (gap #2) |
| **Dashboards = admin técnico** | `web/app/admin/stats/` (272 LOC) | Não há dashboard investigativo (gap #3) |
| **Chat sem AG-UI real** | `web/lib/chat/agui.ts` (65 LOC) | "AG-UI style" caseiro, sem retomada, sem artefato tipado (gap #4) |
| **Sigma.js graph.json 9.2 MB** | `wiki/graph.json` | Trava no browser (gap #5 médio) |
| **Reranker cold start** | `infra/embed-service/app.py` (lazy load) + `web/lib/retrieval/embed.ts` timeout 60s | UX search lenta em primeira query |
| **Stubs em 22.096 arquivos** | `wiki/entities/**/*.md` | Erosão de confiança total |
| **Sem testes** | repo todo | Risco de regressão a cada refactor |
| **Sprawl `rebuild_doc*.py`** | `scripts/rebuild_doc*.py` (28+) | Manutenibilidade do pipeline |
| **`@assistant-ui/react` fantasma** | `web/package.json` | Decisão técnica adiada |
| **`.env.backup.*` versionada** | `infra/disclosure-stack/.env.backup.1778796747` | Secret leak |
## 1.10 O que está bom e deve ser preservado
1. **Markdown como source-of-truth** com IDs canônicos regex-validados. O contrato `CLAUDE.md` é uma alavanca de longo prazo (Larson).
2. **Bilíngue desde o vision pass** — não é tradução tardia, é produto.
3. **Hybrid search RRF em SQL pure**: `public.hybrid_search_chunks` evita orquestração de duas queries no app — boa decisão.
4. **OpenRouter + Claude Code OAuth como única política de LLM** — alinhado à restrição inegociável do projeto.
5. **Self-hosted no data plane** (Postgres, pgvector, BGE-M3 local, GoTrue, PostgREST, Storage). Sem SaaS no caminho dos dados.
6. **Bbox-aware chunking**: cada chunk carrega `bbox` JSONB → permite citações com crop visual.
## 1.11 O que sangra agora e precisa de bisturi
Em ordem de impacto sobre o usuário (não por dificuldade):
1. **Fase 0 — Eliminar todos os stubs "Phase X"** (entrega visível em 24h, dor zero técnica).
2. **Timeline real** — pipeline de enrichment ou de filtro: "exibe só eventos com narrative_summary não-stub". Subentendido na fase 0 mas com produto próprio.
3. **Case Bureau pipeline ground-zero** — hypotheses, evidence, witnesses ainda vazios; isso é a alma do produto.
4. **AG-UI verdadeiro no chat** — substituir `agui.ts` caseiro pelo protocolo AG-UI ou por `assistant-ui/react` real (já no `package.json`).
5. **Dashboards investigativos** — clusters, geografia, atores, padrões temporais. Não admin técnico.
6. **Grafo `graph.json` 9.2 MB** — paginar / lazy ou trocar Sigma por Cytoscape com server-side hub-and-spoke.
Os detalhes de priorização, esforço e fases vão no entregável **04-system-architecture-doc.md**. Os trade-offs vão nos ADRs em **05-adrs/**.
---
_Análise feita em commit `19d0678` baseline. Sem mudanças no código._

View file

@ -0,0 +1,391 @@
---
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._

View file

@ -0,0 +1,224 @@
---
title: "Product Discovery Brief — The Disclosure Bureau"
project: disclosure-bureau
business: systems-atelier
author: sa-principal (Principal Architect — DNA Cagan)
baseline_commit: 19d0678
generated_at: 2026-05-17
schema_version: 0.1.0
---
# 3. Product Discovery — por que, para quem, e com que valor
> **Cagan's first rule:** comece pelo problema e pelo usuário, não pela solução. Antes de escrever uma linha de roadmap, este documento responde: por que o Disclosure Bureau deve existir, quem o usa, o que tentam fazer com ele e onde os riscos de valor / viabilidade / utilizabilidade moram.
## 3.1 Frase-âncora
> **O Disclosure Bureau existe para transformar o ruído documental dos arquivos UAP/FOIA em uma investigação navegável, citada e auditável — usado por pesquisadores, jornalistas, ufólogos sérios e cidadãos curiosos —, e tem sucesso quando uma claim feita no chat vem com a página exata, o crop bbox da fonte, e a cadeia de custódia visível.**
Tudo decorre disso. Se uma feature não serve a esta frase, ela é decoração.
## 3.2 Por que isso precisa existir
O domínio UAP/UFO desclassificado tem três problemas estruturais:
1. **Volume vs leitura humana.** Milhares de páginas FOIA com OCR ruim, layouts inconsistentes, redactions opacas. O leitor médio nunca chega ao parágrafo relevante.
2. **Ruído epistemológico.** Toda fonte secundária (blogs, vídeos, podcasts) cita seletivamente, sem mostrar o documento. O leitor não consegue distinguir fato verificável de inferência criativa.
3. **Falta de cadeia de custódia.** Uma claim viral ("o memo Hottel diz X") raramente vem com a página, o crop, o stamp de classificação. A investigação é estruturalmente irreproduzível.
O Disclosure Bureau resolve os três simultaneamente:
| Problema | Solução desenhada |
|---|---|
| Volume | Vision LLM + chunking bbox-aware + retrieval híbrido BGE-M3 |
| Ruído | Sistema de evidence/witness/hypothesis (Locard + Tetlock); confidence bands; verbatim quotes em source-lang |
| Cadeia de custódia | Cada chunk carrega `source_png`, `bbox`, `chain_of_custody[]`, `redaction_code`. Citações no chat abrem crop inline. |
## 3.3 Quem usa — três personas reais
### Persona A — **Marina, jornalista investigativa** (35a, fluente PT+EN)
- Trabalha em série de reportagem longa sobre UAP. Já sabe que existem 116 PDFs no `war.gov/ufo`. Não vai ler tudo.
- Quer: "me mostre todos os encontros do tipo 'Tic-Tac' entre 2004 e 2015 e me dê o documento exato pra cada um".
- Mede sucesso: artigo publicado com 12 citações primárias verificáveis. Sem retração por má-leitura.
- Hoje: faz `Ctrl+F` em PDFs, mantém planilha Excel, perde 4 horas por sessão.
### Persona B — **Pedro, pesquisador acadêmico** (Sociologia da ciência)
- Tese sobre "construção institucional do UAP discourse". Precisa de network de atores, cronologia, traçar mudanças de vocabulário.
- Quer: "extraia para mim o grafo de organizações mencionadas em todos os documentos da década de 1950, agrupado por padrão de classificação (SECRET vs CONFIDENTIAL)".
- Mede sucesso: figura publicável + dataset CSV para revisão por pares.
- Hoje: trabalha à mão. Citações no LaTeX vão para `\cite[p.~37]{fbi-vault-1947}`. Sem ferramentas.
### Persona C — **Carla, cidadã curiosa séria** (público alvo da landing public)
- Não é especialista. Sabe que algo nesses arquivos importa. Quer entender o argumento sem precisar virar especialista.
- Quer: chat onde pergunta "o que aconteceu em Roswell de verdade?" e recebe uma resposta com 3 fontes primárias, um crop da carta, e um "este é o ponto onde a evidência fica fraca — veja a hipótese alternativa".
- Mede sucesso: confiança no que leu; capacidade de explicar para um amigo.
- Hoje: YouTube, podcasts, posts. Não tem cadeia de custódia.
### Persona D (latente, fase 2) — **Operador de outro corpus**
- Pesquisador de outro tema (JFK files, Stargate Project, MK Ultra, OVNIs brasileiros) que quer trazer seus PDFs para o Bureau e ter o mesmo tratamento. **Esta é a expansão multi-tenant / multi-corpus da visão.**
## 3.4 Jobs-to-be-done
Em linguagem JTBD ("when … I want to … so that …"):
1. **Quando** vejo uma claim viral sobre UAP em outro meio, **quero** chegar à fonte primária com bbox crop **para que** eu possa decidir se a claim sustenta a citação.
2. **Quando** estou estudando um período (ex.: 1947-1952), **quero** uma cronologia consolidada de eventos+sightings+operações **para que** eu veja padrões temporais sem montar planilha à mão.
3. **Quando** investigo um ator (pessoa, agência), **quero** ver todos os documentos onde ele aparece, com quem é co-mencionado, e em qual classificação **para que** eu mapeie sua rede de operações.
4. **Quando** leio uma hipótese sobre um caso, **quero** ver hipóteses concorrentes com priors Bayes calibrados **para que** eu evite âncora cognitiva.
5. **Quando** uso o chat, **quero** que cada afirmação venha com `[[doc/p007#c0042]]` clicável → crop inline **para que** eu nunca aceite resposta sem verificar.
6. **Quando** encontro um gap na evidência (página redacted, custody quebrada), **quero** o sistema explicitar `residual_uncertainty` **para que** eu saiba o que falta saber.
## 3.5 Riscos — taxonomia Cagan
Risco bom de produto é risco enfrentado cedo. Os quatro:
### 3.5.1 Risco de **valor** (eles vão usar?)
| Hipótese | Status | Como derisk |
|---|---|---|
| "Jornalistas vão preferir esta UX a Ctrl+F no PDF" | **Não validado** | 5 entrevistas + landing com waitlist; meta: ≥40% conversão pra signup |
| "Cidadão curioso vai voltar uma segunda vez" | **Não validado** | Cookie de retorno + email opt-in; meta: ≥25% retention semana-1 |
| "Pesquisador acadêmico vai citar o Bureau em paper" | **Aspiracional** | Página `/cite` com BibTeX por chunk; tracking de citações |
### 3.5.2 Risco de **viabilidade** (faz sentido para a casa?)
| Variável | Status atual |
|---|---|
| Custo de pipeline | ~$409 acumulado para 116 docs; ~$3.50/doc; escalável |
| Custo de chat run-time | OpenRouter free models default; OAuth do Max 20x absorvido pelo plano |
| Custo de embed-service | $0 (self-hosted CPU); VPS já está rodando |
| Custo de armazenamento | 634 MB raw chunks + 9 MB graph.json — trivial |
| Custo marginal de novo corpus | ~$3-10/doc dependendo do tamanho + algumas horas de operador |
**Conclusão de viabilidade:** estrutura de custo está calibrada. Risco maior é tempo de engenharia (escopo grande, sem CI, sem testes). Não é dinheiro.
### 3.5.3 Risco de **utilizabilidade** (eles conseguem usar?)
| Sintoma observado | Risco |
|---|---|
| Stubs "Phase X" em 22k arquivos | **Alto** — destrói confiança imediatamente |
| Chat sem AG-UI real | **Médio** — falta de feedback streaming faz parecer "travado" |
| Mobile responsivo precário | **Médio** — gap declarado; jornalista lê no celular |
| Grafo 9.2 MB | **Médio** — trava em corpus grande, lá vem o "site quebrado" |
| Sem onboarding | **Médio** — usuário não sabe pedir "list_anomalies" via chat |
| Sem export PDF/CSV/PNG | **Baixo-médio** — pesquisador precisa exportar pra paper |
| Geocoding ausente | **Baixo** — desbloqueia mapa, mas mapa é nice-to-have |
### 3.5.4 Risco de **utilidade investigativa** (specifically Cagan-adjacent)
A categoria que mais distingue este produto de "outro chat sobre PDFs":
| Hipótese | Status |
|---|---|
| "Tetlock-style hypothesis tournament melhora confiança vs LLM-só" | **Não testado** — case bureau ainda vazio |
| "Locard-style chain of custody + bbox crops reduz fabricação" | **Em parte** — citações funcionam; chain_of_custody[] não preenchido |
| "Red-team review é diferencial" | **Não testado** — chief-detective não rodou |
**Conclusão:** o diferencial do Disclosure Bureau não está no chat. Está no **case bureau**. Se a fase 2 do roadmap (Case Bureau) não rodar, o produto é "Wiki UFO bonita com chat". Comoditizável.
## 3.6 Posicionamento — "super centro de investigação"
O brief usa essa frase. Vou traduzi-la em coordenadas de competição:
### 3.6.1 O que **não** competimos
- **Não somos** Perplexity / ChatGPT — não fazemos web search ao vivo. Nosso corpus é fixo, curado, auditado.
- **Não somos** Obsidian / Roam — não somos editor pessoal. Somos investigação pública.
- **Não somos** Elicit / Consensus — não somos paper-summarizer. Lidamos com fonte primária bruta, não literatura científica.
- **Não somos** Internet Archive — não somos depositário neutro. Temos curadoria, hipóteses e veredictos.
### 3.6.2 O que **somos** (em uma linha)
> **Karpathy LLM-Wiki + Investigation Bureau Locard/Tetlock para arquivos declassificados, com chat AG-UI nativamente citado e auditável.**
### 3.6.3 Vetor de diferenciação
| Eixo | Disclosure Bureau | Concorrente médio (Perplexity, Sherlock-no-PDF) |
|---|---|---|
| Citações com bbox visual | **Sim** (`/api/crop`) | Não |
| Bilíngue EN+PT desde ingest | Sim | Tradução tardia, perde precisão |
| Hypothesis tournament Tetlock | Sim (planejado) | Não |
| Chain of custody Locard | Sim (planejado) | Não |
| Red-team review chief-detective | Sim (planejado) | Não |
| Source-of-truth markdown puro | Sim | Banco proprietário |
| Self-hosted, sem SaaS no data plane | Sim | Vendor lock-in |
| Open-source amigável (planejado) | Sim | Não |
Os "Sim (planejado)" são o roadmap. **Se planejado vira "Sim entregue", o produto fica defensável.**
## 3.7 Mudanças de visão refinadas a partir do brief
O brief lista "Visão longo prazo: multi-tenant/multi-corpus · API pública REST+GraphQL · MCP server · Visualização temporal-geo". Reflexão sobre cada uma:
### 3.7.1 Multi-tenant / multi-corpus — **YES, fase 4**
- Permite Persona D (operadores de outros corpora).
- Trade-off: explode complexidade de RLS, billing, isolamento. Ver **ADR-005**.
- Estratégia recomendada: começar com **multi-corpus single-tenant** (vários corpora dentro de um Bureau, todos curados pela mesma equipe). Multi-tenant verdadeiro vem depois.
### 3.7.2 API pública REST + GraphQL — **YES, fase 5**
- Habilita pesquisadores acadêmicos a integrar em fluxos próprios.
- REST puro via PostgREST já existe (parte do Supabase stack). Pode ser exposto seletivamente.
- GraphQL é luxo. Postpor. REST cobre 90%.
### 3.7.3 MCP server — **YES, fase 5**
- Tornar o Bureau acessível como ferramenta para outros agentes (Claude Code, Cursor, Codex). Forte alavanca de adoção.
- Custo: ~3d para expor um MCP server compatível com o protocolo, expondo os mesmos tools do chat (`hybrid_search`, `read_chunk`, `entity_neighbors`, …).
- ROI alto. **Prioridade dentro da fase 5.**
### 3.7.4 Visualização temporal-geo — **YES, fase 3**
- Adianta o "super centro de investigação".
- Dependências: geocoding pipeline + timeline real (não-stub).
- Stack open-source: MapLibre GL JS + Nominatim self-host + tiles do MapTiler ou Stamen.
## 3.8 Critérios de sucesso mensuráveis (1 ano)
| Métrica | Linha de base atual | Meta 12 meses |
|---|---|---|
| Docs catalogados | 116 | 500+ (com onboard de 3 corpora além de war.gov) |
| Sessões de chat / mês | n.d. (presumir <200) | 5.000+ |
| Citações com crop inline | 100% (já) | manter 100% |
| Eventos com narrativa real (não stub) | 1/7.091 | ≥2.000 (28%+) curados |
| Casos com hypothesis tournament rodado | 0 | ≥15 |
| Hypothesis tournaments com red-team pass | 0 | ≥10 |
| Latência hybrid_search p50 | 1.3s | <800ms |
| Latência rerank p95 (warm) | 60s timeout | <3s |
| Mobile usable score Lighthouse | n.d. (suspeitamente baixo) | ≥85 |
| Retention semana-1 | n.d. | ≥25% |
## 3.9 Aposta de design — bilíngue PT-BR como ativo
Decisão estratégica não-óbvia: investir em **PT-BR de primeira linha** (não tradução tardia) é vantagem competitiva sustentável, não custo.
**Por que:**
- Mercado lusófono de pesquisa UAP é underserved. Não há concorrente nativo PT-BR.
- O usuário-fundador é brasileiro. Insights de produto vêm em PT-BR primeiro.
- Documentos brasileiros futuros (Operação Prato 1977, Caso Varginha 1996, Marinha-Soure 1959) são corpus natural de expansão de fase 4.
**Implicação técnica:** já está feito (`pt_unaccent` tsvector, `content_pt` em todo chunk, vision pass dual). Manter.
## 3.10 Anti-personas — quem não atendemos
- **Crentes dogmáticos** (de qualquer lado): o produto cita evidência e mostra hipóteses concorrentes; não apoia narrativa fechada. Quem busca confirmação, sai irritado. Aceitável.
- **Conspiracionista de TikTok**: o produto é texto+gráfico, não vídeo curto. Sem TikTok-mode. Aceitável.
- **Pesquisador de domínio adjacente** (cripto-zoologia generalista, paranormal solto): cobrimos `cryptid_anomaly` mas o foco é UAP/UFO. Cryptid é subproduto. Não desviamos roadmap por ele.
## 3.11 Sinais para validar/ajustar a visão
Em 90 dias após o roadmap começar, três sinais decidem se a visão de "super centro de investigação" cola:
1. **Engajamento qualitativo no Case Bureau**: ≥3 cases com ≥30 visitas únicas e tempo médio >3min. Se cair abaixo, o "tournament Tetlock" é teatro, não valor.
2. **Citações externas**: alguém citar `disclosure.top/d/<doc>#c0042` em um artigo público ou tweet. Se não acontecer em 6 meses, o produto não atingiu Persona A/B.
3. **Onboard de segundo corpus**: alguém pedir "quero processar meu corpus aqui". Se em 12 meses não acontecer, a aposta multi-corpus é prematura — refoca em war.gov/ufo profundo.
---
_Esta discovery foi conduzida lendo CLAUDE.md (contrato), o brief, o estado atual do repo, MEMORY.md (vinte feedbacks de dev acumulados) e o user-fundador (brasileiro, plano Claude Max 20x). É premissa para o entregável 04._

View file

@ -0,0 +1,450 @@
---
title: "System Architecture Doc — Target State + Phased Roadmap"
project: disclosure-bureau
business: systems-atelier
author: sa-principal (Principal Architect — DNA Fowler + Larson)
baseline_commit: 19d0678
generated_at: 2026-05-17
schema_version: 0.1.0
---
# 4. Arquitetura alvo + roadmap em fases priorizadas
> Fowler: arquitetura evolutiva, patterns como vocabulário. Larson: alavancas de longo prazo, decisões escritas. Cagan: comece pelo problema. Este documento descreve **a arquitetura alvo** (não a atual — essa está no entregável 01) e propõe um **roadmap em 7 fases** que entrega valor incrementalmente, com critérios de aceite verificáveis.
## 4.1 Princípios de arquitetura
São restrições de projeto, não aspirações:
1. **Markdown é a fonte da verdade.** Postgres é cache estruturado; pode ser reconstruído de zero a partir de `wiki/` + `raw/`.
2. **Open-source no data plane.** Postgres, BGE-M3, Sigma/Cytoscape, Nominatim, MapLibre. Sem SaaS proprietário onde dado bruto passa.
3. **Self-hosted via Docker Compose em VPS.** Sem Kubernetes prematuro. Decisão se reabre se chegarmos a multi-tenant pesado (ADR-005).
4. **LLM via Claude Code OAuth ou OpenRouter.** Proibido `ANTHROPIC_API_KEY`. Não-negociável.
5. **Bilíngue EN+PT-BR desde o ingest.** Não tradução tardia. Postgres tem `pt_unaccent`/`en_unaccent`. Frontend tem locale toggle persistente.
6. **Cada claim é citada.** Chunk com `bbox` JSONB resolve para crop visual via `/api/crop`. Sem citação = não vai pro chat.
7. **Procedência Locard sempre.** `chain_of_custody[]` em evidence, `mentioned_in[]` materializado em entities, `source_page` + `bbox` em chunks. Lint bloqueante.
8. **Confidence bands Tetlock.** Toda claim de sumário tem `confidence_band` (high/medium/low/speculation). Linguagem permitida calibrada.
9. **Idempotência.** Reprocessar o mesmo PDF mantém IDs, atualiza `last_ingest`, preserva `created_at`.
10. **Segurança como restrição de projeto.** Veto do sa-security-engineer prevalece. Sem segredo no git. SMTP real antes de signup público.
## 4.2 Arquitetura alvo (diagrama)
```
┌────────────────────────────────────────────────────────────────────────────┐
│ Edge: Traefik + Let's Encrypt + WAF leve (modsecurity-crs) │
└────┬──────────────────────────────────────────────────────────────────┬────┘
│ TLS │
▼ ▼
┌──────────────────────────┐ ┌──────────────────────────┐
│ Next.js 15 web (web) │◄─SSE───────────────────────────┤ AG-UI chat client │
│ ─ App Router │ │ (browser, mobile-ready) │
│ ─ Pages SSR de wiki │ └──────────────────────────┘
│ ─ API routes │
│ ─ Middleware Supabase │
└──┬───────────────────────┘
├──► Postgres 15 + pgvector + pg_trgm + unaccent
│ schemas: public (corpora, documents, chunks, entities, mentions, cases,
│ hypotheses, evidence, witnesses)
│ auth (gotrue)
│ storage (storage-api)
│ tenants (opcional fase 4)
├──► embed-service (FastAPI) — BGE-M3 + BGE-Reranker-v2-M3 — warm-pool, queue
├──► chat-runtime
│ ─ provider OpenRouter (Pattern C streaming, tool loop)
│ ─ provider Claude Code OAuth (subprocess streaming, tool loop) ← Fase 5
│ ─ contrato AG-UI v1 (events: text_delta, tool_call, tool_result, state,
│ artifact, navigate, done, error)
├──► case-runtime (cron + workers Python)
│ ─ hypothesis-tournament
│ ─ evidence-officer (chain-of-custody)
│ ─ red-team review (chief-detective)
│ ─ output: case/**/*.md → indexed in DB
├──► geocoding-service (Nominatim self-host)
└──► observability stack:
Loki + Promtail + Grafana + Sentry (self-host) + Uptime Kuma
Auth: Supabase GoTrue + PostgREST + Realtime + Studio (Kong gateway)
Storage: Supabase storage-api + Imgproxy
Backups: pg_dump diário + restic para S3-compatible (Backblaze B2 / Wasabi)
```
## 4.3 Fronteiras de serviço
| Serviço | Responsabilidade | Linguagem | Interface |
|---|---|---|---|
| `web` | UI + SSR + chat orquestração + crop on-demand | TS/Next 15 | HTTPS + SSE |
| `db` | Source of truth runtime (cache do MD) | SQL | TCP 5432 internal |
| `embed` | Embedding + rerank | Python/FastAPI | HTTP 8000 internal |
| `case-runtime` | Hypothesis tournament + red team | Python | Cron + Postgres queue |
| `geocoding` | Nominatim self-host | C++ image | HTTP internal |
| `ingest-pipeline` | PDF → chunks → DB index | Python | CLI, manual ou cron |
| `chat-runtime` | Tool-loop server-side (subset de `web/lib/chat/`) | TS, dentro do `web` | API route + SSE |
| `auth/rest/realtime/storage/imgproxy/studio/kong/meilisearch` | Supabase | mixed | internal |
| `observability` | Logs, metrics, errors | Go/Erlang | self-host |
| `traefik` | Edge TLS + roteamento | Go | edge |
**Princípio:** o `web` é o orquestrador, não o trabalhador pesado. Ingest, rerank, case tournament rodam em processos próprios.
## 4.4 Modelo de dados — evolução proposta
### 4.4.1 Tabelas novas (fases 2 e 3)
```sql
-- Casos investigativos (Tetlock + Locard)
CREATE TABLE public.cases (
case_id TEXT PRIMARY KEY, -- e.g. 'CASE-NIMITZ-TIC-TAC'
canonical_name TEXT NOT NULL,
scope_summary TEXT,
status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','closed','dormant')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_review TIMESTAMPTZ
);
CREATE TABLE public.hypotheses (
hypothesis_id TEXT PRIMARY KEY, -- 'H-0042'
case_id TEXT REFERENCES public.cases(case_id) ON DELETE CASCADE,
statement_en TEXT NOT NULL,
statement_pt TEXT NOT NULL,
prior REAL NOT NULL CHECK (prior BETWEEN 0 AND 1),
likelihood REAL CHECK (likelihood BETWEEN 0 AND 1),
posterior REAL CHECK (posterior BETWEEN 0 AND 1),
evidence_for TEXT[], -- evidence_ids
evidence_against TEXT[],
red_team_pass BOOLEAN DEFAULT FALSE,
confidence_band TEXT NOT NULL CHECK (confidence_band IN ('high','medium','low','speculation')),
authored_by TEXT, -- 'holmes' | 'poirot' | 'dupin' | 'locard'
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE public.evidence (
evidence_id TEXT PRIMARY KEY, -- 'E-0042'
case_id TEXT REFERENCES public.cases(case_id) ON DELETE CASCADE,
doc_id TEXT NOT NULL,
page INT,
bbox JSONB,
verbatim_excerpt TEXT NOT NULL,
evidence_grade CHAR(1) CHECK (evidence_grade IN ('A','B','C')),
chain_of_custody JSONB NOT NULL, -- [{step, actor, timestamp}, ...]
custody_gaps TEXT[],
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ... witnesses, gaps, relations, case_reports analogamente
```
### 4.4.2 Mudança crítica em `entities`
Adicionar:
```sql
ALTER TABLE public.entities
ADD COLUMN narrative_summary_en TEXT,
ADD COLUMN narrative_summary_pt TEXT,
ADD COLUMN summary_confidence TEXT CHECK (summary_confidence IN ('high','medium','low','none')),
ADD COLUMN summary_status TEXT NOT NULL DEFAULT 'none'
CHECK (summary_status IN ('none','synthesized','curated','red_teamed')),
ADD COLUMN lat DOUBLE PRECISION, -- fase 3
ADD COLUMN lon DOUBLE PRECISION,
ADD COLUMN geo_confidence TEXT CHECK (geo_confidence IN ('exact','city','region','unknown'));
```
A coluna `summary_status` substitui o stub textual. Fora do filtro de listings públicos, qualquer linha com `summary_status='none'`.
### 4.4.3 Multi-corpus (fase 4)
```sql
CREATE TABLE public.corpora (
corpus_id TEXT PRIMARY KEY, -- 'war-gov-ufo', 'jfk-files', 'arquivo-nacional-br'
display_name TEXT NOT NULL,
description TEXT,
visibility TEXT NOT NULL CHECK (visibility IN ('public','unlisted','private')),
owner_id UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
ALTER TABLE public.documents ADD COLUMN corpus_id TEXT REFERENCES public.corpora(corpus_id);
-- RLS pass de USING (TRUE) para USING (
-- corpus_id IN (SELECT corpus_id FROM corpora WHERE visibility='public')
-- OR auth.uid() IN (...)
-- )
```
ADR-005 documenta a decisão.
## 4.5 Fronteira do chat — contrato AG-UI
ADR-001 cobre a decisão. Resumo do **contrato alvo**:
- Servidor expõe `POST /api/chat/run` com body `{session_id, message, ctx}`.
- Response = SSE com eventos AG-UI v1: `state.start``text.delta` (n×) → `tool.call``tool.result``artifact.created` (citações, crops, navegação) → `state.end`.
- Cliente assina via `EventSource` ou `fetch+reader.read()`. Reconnect com `Last-Event-ID`.
- Persistência incremental: cada `text.delta` flush num buffer; ao `state.end`, materializa `messages` row.
- Cancelamento: `POST /api/chat/cancel` com `session_id` flagga a sessão; loop checa a cada turno.
- **Artifact tipado**: `citation` (chunk_id+bbox), `crop_image`, `entity_card`, `case_card`, `evidence_card`, `hypothesis_card`, `navigation_offer`. Cada um tem React component dedicado.
## 4.6 Modelo de processamento — pipeline reorganizada
A pipeline atual (`scripts/0034`) será reorganizada em **estágios canônicos**:
```
Stage 1 — INGEST (scripts/ingest/)
01_extract.sh PDF → PNG@72 + OCR
02_vision.py Haiku 4.5 page-level vision pass (EN+PT)
03_chunk.py Sonnet 4.6 chunking via subagent (bbox-aware)
04_synthesize.py master doc.md assembly per doc
Stage 2 — INDEX (scripts/index/)
10_embed.py BGE-M3 batch embed → chunks.embedding
11_mentions.py tsvector-based entity match (substitui ILIKE)
12_graph_export.py graph.json (paginado)
Stage 3 — SYNTHESIZE (scripts/synthesize/)
20_entity_summary.py iterate entities with N+ mentions, write narrative_summary
21_doc_pitch.py short pitch per doc for listings (já existe — 34-generate-doc-pitches.py)
Stage 4 — CASE (scripts/case/)
30_seed.py create case skeletons from canonical events
31_hypothesis.py Tetlock tournament loop
32_evidence.py Locard chain-of-custody check
33_red_team.py chief-detective review
Stage 5 — MAINTAIN (scripts/maintain/)
40_lint.py full lint (substitui 04-lint.py)
41_dedup.py entity dedup (substitui 03 → sem stubs textuais)
42_geocode.py Nominatim lookup para entities.lat/lon
43_log_rotate.py rotaciona wiki/log.md por mês
```
Cada stage tem `make stage1` / `make stage2` / ... e cada script tem testes pytest.
## 4.7 Roadmap em 7 fases priorizadas
Fases são paralelizáveis dentro do limite de 1 engenheiro sênior + 1 IA-pair. Esforços em homem-dias (`d`).
### **Fase 0 — Limpeza de stubs "Phase X"** (3-4d)
> Esta fase existe porque o usuário está furioso. Não há valor em construir a fase 2 se a fase 0 não roda.
**Objetivo:** zerar a presença textual de "Phase X" / "Will be enriched" no UI.
**Escopo:**
1. Migrar `scripts/03-dedup-entities.py` para nunca emitir stub textual. `narrative_summary` fica `NULL`, `summary_status='none'`.
2. Script de migração `scripts/maintain/41_dedup.py` reescreve os 22.096 arquivos existentes: remove o texto stub, seta `summary_status: none`, mantém YAML válido.
3. UI: páginas de entidade exibem badge "sem síntese ainda — `[Sintetizar]`" em vez do texto stub. `/timeline` exclui eventos com `summary_status='none'` do default (param `?include_unsynthesized=true` força).
4. `wiki/log.md` registra a operação.
**Critério de aceite:**
- `grep -r "Will be enriched in Phase" wiki/` retorna **0**.
- `grep -r "_Stub" wiki/entities/` retorna **0**.
- Página `/timeline` carrega ≥1 evento por default (estado mínimo) — após fase 2 essa lista cresce.
- Página `/e/event/EV-1492-…` mostra layout limpo, sem "Phase 7".
**Esforço:** 3d (1 sênior).
### **Fase 1 — Cleanup técnico inegociável** (5-6d)
> Higiene mínima antes de construir features. Larson: alavancas que se pagam todo dia.
**Objetivo:** secret hygiene, CI, observabilidade mínima.
**Escopo:**
1. **Rotação de segredos vazados** + remoção do `.env.backup.*` do git-history. Documentação no DEPLOY-CHECKLIST.
2. **CI/CD GitHub Actions**: `lint + tsc --noEmit + build + smoke /api/admin/stats`.
3. **Testes piso mínimo**: 30 testes Vitest cobrindo as 19 API routes (fixture com 3 docs). 10 testes pytest cobrindo scripts críticos.
4. **Observabilidade**: Loki + Promtail no compose; dashboard Grafana básico (latência hybrid_search, error rate); GlitchTip (Sentry open-source self-host) para exceptions.
5. **Consolidar scripts ad-hoc**: 28 `rebuild_doc<NN>*.py` → mover para `scripts/_archive/`. README dos canônicos.
6. **GoTrue SMTP** real configurado (provedor à escolha do user; data plane segue intocado). `MAILER_AUTOCONFIRM=false`.
**Critério de aceite:**
- `git log --all -- .env.backup.1778796747` retorna 0 commits (após filter-repo).
- CI passa em PR. Falha bloqueia merge.
- Grafana mostra request rate e error rate em tempo real.
- Signup novo envia e-mail real.
- `scripts/` sem `rebuild_doc<NN>*.py` no root.
**Esforço:** 5d.
### **Fase 2 — Case Bureau MVP (Locard + Tetlock real)** (10-12d)
> O diferencial. Sem isso o produto é "wiki bonita com chat".
**Objetivo:** primeiros 5 casos canônicos rodando com hipóteses concorrentes, evidence chain-of-custody, red-team pass.
**Escopo:**
1. **Schema**: migration `0003_case_bureau.sql` com `cases`, `hypotheses`, `evidence`, `witnesses`, `gaps`, `relations`, `case_reports`, `residual_uncertainty`.
2. **Seed**: `scripts/case/30_seed.py` cria 5 casos âncora — Roswell 1947, Nimitz Tic-Tac 2004, Phoenix Lights 1997, FBI Hottel memo 1950, Apollo Skylab radio anomaly 1973.
3. **Hypothesis tournament**: `scripts/case/31_hypothesis.py` lê evidences, gera 3+ hipóteses via Claude Code OAuth (Sonnet), calcula posterior simples (prior × likelihood normalizado), escreve `H-NNNN.md` + DB row.
4. **Chain of custody**: `scripts/case/32_evidence.py` valida `chain_of_custody[]` ≥ N steps por grade. Grade A precisa ≥3, B ≥2, C ≥1.
5. **Red-team review**: `scripts/case/33_red_team.py` chama Sonnet em modo adversarial: "encontre furos nas premissas das hipóteses dominantes". Output vai para `residual_uncertainty.md`.
6. **UI**: `/case`, `/case/[id]`, `/case/[id]/hypotheses`, `/case/[id]/evidence`. Componentes `CaseTimelineLane`, `HypothesisTournamentCard`, `EvidenceChainCustody`.
7. **Chat tools novos**: `read_case`, `read_hypothesis`, `read_evidence`. Chat passa a citar `[[hypothesis/H-0042]]` resolvido inline.
**Critério de aceite:**
- 5 cases em `case/case-report.md` com quality rubrics threshold 0.85 satisfeitos (chain_of_custody_completeness, confidence_calibration_match, hypothesis_tournament_discipline ≥3, residual_uncertainty_presence, audit_trail_per_claim, red_team_pass).
- Página `/case/case-nimitz-tic-tac` renderiza timeline + 3 hipóteses + 5 evidences com bbox crop inline + red-team box.
- Chat responde "como foi o Nimitz Tic-Tac?" com citações para hypothesis IDs + evidence IDs.
**Esforço:** 12d.
### **Fase 3 — Timeline real + Dashboards investigativos + Mapa** (10-12d)
> Os dashboards faltantes do brief. Aqui o produto começa a parecer "super centro".
**Objetivo:** timeline com narrativas reais; clusters; mapa; padrões.
**Escopo:**
1. **Pipeline de entity summary**: `scripts/synthesize/20_entity_summary.py` itera entidades com `total_mentions ≥ 5`, recupera chunks via `entity_mentions`, escreve `narrative_summary` real. Custo estimado: ~$80-150 corpus inteiro.
2. **Geocoding**: Nominatim self-host no docker-compose. `scripts/maintain/42_geocode.py` popula `entities.lat/lon` para todas as `locations`.
3. **Curadoria timeline**: 50-100 eventos canônicos com narrativa rica escrita pelo case-writer.
4. **Mapa**: rota `/dashboard/map` com MapLibre GL + tiles open-source. Markers de sightings por densidade. Clica → entity card.
5. **Clusters**: `/dashboard/clusters` agrupa eventos por proximidade temporal+geográfica (DBSCAN sobre lat/lon/date_start). Lista clusters como cards.
6. **Atores**: `/dashboard/actors` lista top-50 entities (pessoas + organizações) por menções, sparkline temporal de aparições por década.
7. **Padrões**: `/dashboard/patterns` mostra UFO anomaly por ano, classification heatmap, redaction ratio por collection.
**Critério de aceite:**
- `/timeline` mostra ≥2.000 eventos com narrative_summary real, agrupados por década, com filtro funcionando.
- `/dashboard/map` carrega <2s, marca >500 locations com lat/lon.
- `/dashboard/clusters` mostra ≥20 clusters identificados, cada um com mínimo 3 eventos.
- `/dashboard/actors` lista top-50 com sparkline.
- `/dashboard/patterns` tem 3 gráficos funcionais.
**Esforço:** 12d.
### **Fase 4 — AG-UI nativo + Mobile responsivo + Performance** (8-10d)
> A experiência conversacional vira diferencial.
**Objetivo:** AG-UI v1 real no chat; artefatos tipados; mobile usable; reranker rápido.
**Escopo:**
1. **AG-UI v1**: substituir `web/lib/chat/agui.ts` por implementação AG-UI canônica. Eventos `state.start/end`, `text.delta`, `tool.call`, `tool.result`, `artifact.created`. Last-Event-ID reconnect. ADR-001.
2. **Artefatos tipados**: `citation`, `crop_image`, `entity_card`, `case_card`, `evidence_card`, `hypothesis_card`, `navigation_offer`. Cada um vira `Artifact` clicável no chat.
3. **Persistência incremental**: cada `text.delta` flush em `messages` via Realtime. Aborto via header `x-chat-cancel: <session_id>`.
4. **Refactor `chat-bubble.tsx`** (507 LOC) → quebra em `ChatComposer`, `ChatStream`, `ToolBlock`, `Citation`, `NavigationOffer`. Aumenta testabilidade.
5. **Mobile**: passar Lighthouse mobile ≥85. Bento → grid responsivo. Modais ↔ bottom-sheets. Touch targets ≥44px.
6. **Reranker warm-pool**: ADR-002 — health check warm; cache de rerank `(query_norm, candidate_ids)→scores`; rerank em background com SSE event `rerank_progress`.
7. **Grafo paginado**: endpoint `/api/graph/neighbors?node=&depth=&limit=`. Carregar inicial só hub-and-spoke. Expand-on-click.
**Critério de aceite:**
- Chat aceita reconnect em queda de rede sem perder mensagem.
- Cada citação no chat renderiza crop inline `<img>` (via `/api/crop`).
- Lighthouse mobile ≥85 em 5 páginas-chave.
- p95 hybrid_search warm <800ms; p95 rerank warm <3s.
- `/graph` carrega <1s com hub-and-spoke; expand busca via API.
**Esforço:** 10d.
### **Fase 5 — Multi-corpus (single-tenant) + API REST + MCP server** (12-14d)
> Abertura. Bureau como plataforma.
**Objetivo:** suportar 2-3 corpora além de war.gov/ufo; expor API REST estável; expor MCP server compatível com Claude Code/Cursor.
**Escopo:**
1. **Migration multi-corpus**: tabela `corpora`; FK `corpus_id` em `documents`/`chunks`/`entities`. RLS policy passa a filtrar por `corpus_id`. ADR-005.
2. **Path layout**: `wiki/<corpus_id>/documents/...` (refactor estrutura) **ou** deploy-por-corpus (ADR-005). Recomendação atual: começar com pasta única `wiki/` e coluna `corpus_id` em DB, refactor de paths fica para fase 6.
3. **Onboard de 2 corpora-piloto**: JFK files (pequeno, alto interesse) + Operação Prato 1977 (PT-BR, validação bilíngue real).
4. **Corpus switcher na UI**: navbar tem dropdown; `/c/<corpus_id>` rotas; chat session vincula corpus.
5. **API REST pública**: `/api/v1/{documents,chunks,entities,events,cases,hypotheses}` (read-only) via PostgREST com rate-limit por API key. Auth = Bearer token gerado em `/account/api-keys`.
6. **MCP server**: `apps/mcp-server/` (Node TS) que expõe os mesmos 12 tools do chat como ferramentas MCP. Roda em container próprio na compose.
7. **Documentação**: `/api/docs` (OpenAPI) + `/mcp/docs` (server card).
**Critério de aceite:**
- 3 corpora visíveis no switcher; cada um com >10 docs.
- API key gera; `GET /api/v1/documents?corpus_id=jfk-files&limit=10` retorna 200.
- Claude Code remoto consegue listar tools do MCP server e fazer uma `hybrid_search`.
**Esforço:** 14d.
### **Fase 6 — Hardening + Backups + Multi-tenant verdadeiro (opcional)** (8-15d)
> Operação adulta.
**Objetivo:** sistema sobrevive a queda do VPS, falhas humanas e abertura externa.
**Escopo:**
1. **Backups**: `pg_dump` diário → restic → S3-compatible (Backblaze B2 / Wasabi — self-host adjacent, não SaaS no data plane bruto). Restore testado mensal.
2. **Snapshot do `wiki/` + `raw/`**: rsync mensal para B2.
3. **Disaster recovery runbook** em `infra/DR.md`.
4. **Rate-limit + abuse detection**: nginx-style limits no Kong; Postgres usage_events alimenta heuristic.
5. **Multi-tenant verdadeiro** (opcional): se a fase 5 mostrar sinal de Persona D, escalar. RLS pesado, billing, isolamento. ADR-005 reaberto.
**Critério de aceite:**
- Restore test no staging restaura a infra a partir de zero em <1h.
- Rate-limit aciona em 100 req/min por IP.
- (Opcional) primeiro tenant externo onboarda sem suporte.
**Esforço:** 8-15d (conforme escopo multi-tenant).
## 4.8 Síntese — sequenciamento sugerido
```
Mês 1 [█████░░░] Fase 0 (3-4d) + Fase 1 (5-6d)
Mês 2 [████████] Fase 2 Case Bureau MVP (10-12d)
Mês 3 [████████] Fase 3 Timeline+Dashboards+Mapa (10-12d)
Mês 4 [██████░░] Fase 4 AG-UI + Mobile + Perf (8-10d)
Mês 5 [████████] Fase 5 Multi-corpus + API + MCP (12-14d)
Mês 6 [██████░░] Fase 6 Hardening + Backups (8-10d)
```
Total: ~60d de engenharia sênior + IA-pair em ~6 meses. Espaço para 15-20d de imprevisto e iteração de design.
## 4.9 Decisões type-1 — ADRs documentados
Cada decisão estratégica vira ADR em `/05-adrs/`:
| ADR | Decisão | Documento |
|---|---|---|
| 001 | Pattern do chat: AG-UI v1 vs assistant-ui/react vs continuar com agui.ts caseiro | `05-adrs/ADR-001-chat-pattern-ag-ui.md` |
| 002 | Caching e serving do reranker BGE-Reranker-v2-M3 | `05-adrs/ADR-002-reranker-caching.md` |
| 003 | Pipeline de geração da Timeline (eliminar stubs) | `05-adrs/ADR-003-timeline-no-stubs.md` |
| 004 | Pipeline Case Bureau (Tetlock + Locard) | `05-adrs/ADR-004-case-bureau-pipeline.md` |
| 005 | Multi-tenant vs multi-corpus single-tenant | `05-adrs/ADR-005-multi-tenant-refactor.md` |
(ADRs 6+ ficam para o time de leads conforme avançam — por exemplo, "sigma vs cytoscape", "Nominatim vs Geoapify self-host", "MapLibre tiles source".)
## 4.10 O que **não** fazer agora (anti-roadmap)
Por proporcionalidade Fowler — coisas que tentam o atalho da complexidade e ainda não se pagam:
1. **Kubernetes.** Docker Compose escala para 10x o tráfego atual. Não migrar antes de ≥20k MAU sustentados.
2. **GraphQL.** REST + tipos pré-formados em SQL cobrem 95% dos casos de integração. Reabrir após 3 integradores externos pedirem.
3. **Vector DB dedicado (Pinecone, Qdrant Cloud)** — pgvector + HNSW sustenta 100k chunks tranquilamente. Reabrir se latência p99 dense >500ms.
4. **CDN proprietário (Cloudflare Pro+)** — Imgproxy + Next.js image optimization + Traefik com cache header cobrem. Reabrir após picos sustentados de tráfego.
5. **Re-embed para dimensão maior (BGE-M3 → outro 4096-d)** — não há sinal de que recall esteja teto.
6. **Migrar pipeline para Airflow / Prefect**`scripts/` com Makefile + cron resolvem. Reabrir após 3 pipelines concorrentes em produção.
7. **Microservizar o `web`** — monólito Next.js é certo para este tamanho. Não fragmentar antes de gargalo medido.
## 4.11 Riscos do roadmap
| Risco | Probabilidade | Mitigação |
|---|---|---|
| Enrichment de 22k entidades estoura orçamento OAuth Max 20x | Média | Limiar por `total_mentions ≥ 5` reduz para ~5-8k. Fila de 200/dia. |
| Case Bureau red-team gera disputa de hipóteses pesada e demora | Baixa | Limita a 3 hipóteses por caso na fase 2. Quality gate threshold relaxado para 0.80 inicialmente. |
| Multi-corpus refactor toca todo o web | Alta | Fase 5 isolada; flag `MULTI_CORPUS=on` permite voltar. |
| MapLibre tiles open-source ficam lentas | Média | Fallback: MapTiler key (não-SaaS no data plane — só tiles). |
| Rotação dos segredos vazados quebra deploy | Baixa | Staged: trocar em staging primeiro, depois prod, com runbook. |
---
_Esta arquitetura alvo e este roadmap são vinculantes para os leads (sa-architecture-lead, sa-engineering-lead, sa-platform-lead, sa-security-engineer, sa-qa-engineer) e ao gate de aprovação do sa-principal. Cada fase abre com um delegation; cada fase fecha com handoff_artifact + self_score._

View file

@ -0,0 +1,129 @@
---
adr: 001
title: "Chat pattern — substituir 'AG-UI style' caseiro pelo AG-UI v1 + artefatos tipados"
status: Proposed
date: 2026-05-17
owner: sa-principal
related_phase: Fase 4
---
# ADR-001 — Chat pattern: AG-UI v1 + artefatos tipados
## Status
Proposed — aguarda aprovação do `sa-principal` antes da Fase 4 iniciar.
## Context
O chat do Disclosure Bureau é o canal primário de uso do produto. Hoje a implementação está dividida entre três módulos:
- `/Users/guto/ufo/web/lib/chat/agui.ts` (65 LOC) — eventos SSE caseiros (`text_delta`, `tool_start`, `tool_result`, `navigate`, `done`, `error`).
- `/Users/guto/ufo/web/lib/chat/openrouter.ts` (341 LOC) — Pattern C (streaming + tool-call loop client-side) sobre OpenAI-compat da OpenRouter.
- `/Users/guto/ufo/web/components/chat-bubble.tsx` (507 LOC) — UI consumindo SSE manualmente.
O comentário no topo do `agui.ts` é explícito: _"We don't implement the full AG-UI protocol — we use a simplified event set"_. Isso traz limites concretos:
1. **Sem reconnect com Last-Event-ID** — queda de rede perde a mensagem inteira.
2. **Sem persistência incremental**`text_delta` só vira `messages` row após `done`.
3. **Sem cancelamento limpo**`AbortController` no client não chega ao server; tools continuam executando.
4. **Sem artefatos tipados** — citações são strings markdown parseadas no front, fáceis de errar.
5. **Sem crops bbox inline** — citação aponta para `/d/<doc>#<chunk>`, mas o crop só renderiza fora do chat.
Adicionalmente, `@assistant-ui/react@^0.14.0` está no `package.json` e **não é importado em runtime** (`grep -r "@assistant-ui" web/{app,components,lib}` → 0 hits). Decisão técnica adiada.
O brief lista esse problema como gap crítico #4: "A2UI / AG-UI no chat — streaming tool-calls, artefatos clicáveis, crops bbox inline, multi-turn persistente."
## Opções consideradas
### Opção A — Continuar com `agui.ts` caseiro, adicionar features
- Manter o protocolo proprietário, adicionar reconnect, artefatos tipados, cancelamento.
- **Custo:** ~6d.
- **Prós:** zero migração; controle total.
- **Contras:** reinventa AG-UI v1 (que já é open-spec e bem documentado); custo de manutenção cresce; ecossistema não conecta.
### Opção B — Adotar AG-UI v1 (protocolo aberto)
- Substituir o protocolo SSE proprietário pela spec [AG-UI v1](https://github.com/ag-ui-protocol/ag-ui): eventos `state.start`, `text.delta`, `tool.call`, `tool.result`, `artifact.created`, `state.end`, `error`. Reconnect via `Last-Event-ID`.
- Client: implementação manual (~150 LOC) ou biblioteca compatível (`@ag-ui/client` em early stage).
- **Custo:** ~5d.
- **Prós:** spec aberta e versionada; outros sistemas (Claude Code, Codex) podem virar clients do mesmo backend; alavanca pra MCP server na Fase 5.
- **Contras:** AG-UI v1 ainda é jovem; spec pode mudar (versionamos a versão exata no chat-runtime).
### Opção C — Adotar `@assistant-ui/react` + LangGraph runtime
- Usar a lib já no `package.json`. Trade-off: o `@assistant-ui/react` ofusca o protocolo SSE sob abstrações React; persistência fica acoplada à lib.
- **Custo:** ~4d.
- **Prós:** UI pronta (composer, mensagens, tool blocks); animações grátis.
- **Contras:** opinião pesada na arquitetura; integração com nossos tools custom + Pattern C custa; menos controle do SSE protocol.
## Decisão
**Adotar Opção B — AG-UI v1 com client implementado manualmente.**
Justificativas, em ordem de peso:
1. **Spec aberta.** AG-UI v1 é versionado e públicó. Não comemos ofuscação proprietária de UI lib.
2. **Alavanca para Fase 5 (MCP server).** Os mesmos artefatos tipados (`citation`, `crop_image`, `entity_card`) são reutilizados como artifacts MCP. Um único contrato serve chat e agent integrations.
3. **Backwards-compatível com nosso `tools.ts`.** Não muda a forma como tools são definidos, só como o stream é emitido.
4. **Controle do SSE protocol.** Reconnect, cancelamento, persistência incremental ficam em nosso código, não em deps.
5. **Remove `@assistant-ui/react` do `package.json`** — débito 2.2.13 fechado.
## Implementação
Esquema do contrato (versão 1.0):
```typescript
type AGUIEvent =
| { kind: "state.start"; session_id: string; turn_id: string }
| { kind: "text.delta"; turn_id: string; delta: string }
| { kind: "tool.call"; turn_id: string; id: string; name: string; args: Record<string, unknown> }
| { kind: "tool.result"; turn_id: string; id: string; result: unknown; duration_ms: number }
| { kind: "artifact.created"; turn_id: string; artifact: Artifact }
| { kind: "state.end"; turn_id: string; usage: { tokens_in: number; tokens_out: number } }
| { kind: "error"; turn_id: string; code: string; message: string };
type Artifact =
| { type: "citation"; chunk_id: string; doc_id: string; page: number; bbox: BBox }
| { type: "crop_image"; src: string; alt_en: string; alt_pt: string }
| { type: "entity_card"; class: EntityClass; id: string }
| { type: "case_card"; case_id: string }
| { type: "evidence_card"; evidence_id: string }
| { type: "hypothesis_card"; hypothesis_id: string }
| { type: "navigation_offer"; target: string; label_en: string; label_pt: string };
```
Persistência incremental: cada `text.delta` apenda em buffer; ao `state.end`, materializa em `messages`. Reconnect: `EventSource` com header `Last-Event-ID: <event_seq>`; server replay desde o último seq.
Cancelamento: `POST /api/chat/cancel { session_id }` flag em memória + `chat_sessions.cancel_requested=true`; loop de tool-call checa antes de cada turno.
## Consequences
### Positivas
- Reconnect resiliente em redes móveis ruins (Persona A/C frequentemente lê no celular).
- Artefatos tipados → menos bug de parsing de citação markdown.
- Crops inline no chat (`crop_image` artifact gerado quando tool retorna chunk com bbox).
- Mensagens "salvas em progresso" mesmo se o usuário fecha o modal.
- MCP server (Fase 5) usa o mesmo contrato de artifacts — economia de design.
### Negativas
- ~5d de engenharia (versus 0d se ficássemos no caseiro).
- AG-UI v1 spec pode evoluir; precisamos versionar `chat-runtime` com pinning de versão.
- `@assistant-ui/react` removido do package.json — qualquer demo que importou via copy/paste de docs externos quebra (provavelmente nenhum, mas verificar).
### Neutras
- Não muda `tools.ts` (12 handlers permanecem). Não muda `OpenRouter`/`Claude Code` providers — só muda o "frame" de eventos emitidos.
## Validação
- Smoke test: usuário envia "/test/disconnect" durante streaming → desconecta rede 5s → reconecta → mensagem continua sem perda.
- E2E: tool `hybrid_search` retorna 5 hits → 5 `artifact.created` `citation` aparecem inline no chat; cada citação clica → `/d/.../p007#c0042` com crop visível.
## Referências
- AG-UI spec: <https://github.com/ag-ui-protocol/ag-ui>
- Pattern C streaming: anthropic + openai cookbooks
- Issue: gap #4 do brief original

View file

@ -0,0 +1,169 @@
---
adr: 002
title: "Caching e serving do reranker BGE-Reranker-v2-M3"
status: Proposed
date: 2026-05-17
owner: sa-principal
related_phase: Fase 4
---
# ADR-002 — Caching e serving do reranker
## Status
Proposed.
## Context
O `embed-service` (`/Users/guto/ufo/infra/embed-service/app.py`, FastAPI 148 LOC) carrega BGE-M3 + BGE-Reranker-v2-M3 em CPU. Lazy load via `Lock`. Primeira chamada após restart custa ~30s para carregar ~2.5GB em RAM.
O cliente em `/Users/guto/ufo/web/lib/retrieval/embed.ts:16` usa `AbortSignal.timeout(60_000)` — workaround, não solução. O brief lista isso como gap médio: "Reranker 60s".
Cenários observados:
- **Primeira query do dia:** 30-60s (cold start). UX inaceitável.
- **Query repetida quente:** 200-500ms por par query/doc. Para top-100 candidatos → 20-50s. Inaceitável também.
- **Query única quente, candidatos novos:** ~3-8s para top-20 candidates.
A causa raiz não é o tamanho do modelo. É (a) cold start sem warm-up, (b) reranker é chamado síncrono dentro do hybrid_search request, (c) nenhum cache compartilha resultados entre usuários.
## Opções consideradas
### Opção A — Warm-pool + cache de rerank scores
- `healthcheck` do `embed` dispara `/embed` no startup (warm-up).
- `restart: unless-stopped` no docker mantém quente.
- Tabela Postgres `rerank_cache(hash TEXT PK, scores REAL[], created_at TIMESTAMPTZ)` com hash = SHA-256 de `(query_normalized, candidate_ids_sorted)`.
- TTL 24h (lru_count <100k).
- **Custo:** 3d.
- **Prós:** simples, sem novo serviço; ataca cold start e queries repetidas.
- **Contras:** queries únicas com candidatos novos continuam 3-8s.
### Opção B — Warm-pool + cache + rerank assíncrono com SSE progress
- Mesmo cache da Opção A.
- Hybrid search retorna RRF top-20 imediatamente (já ordenado mas sem rerank). UI mostra resultados.
- Rerank roda em background, emite SSE event `rerank_progress` com top-5 reordenado. UI atualiza inline.
- **Custo:** 5d.
- **Prós:** UX percebida em <1s sempre; rerank vira luxo enriquecedor, não bloqueador.
- **Contras:** complexidade de coordenação client/server; rerank ainda gasta CPU.
### Opção C — Migrar reranker para GPU (Modal / RunPod / local GPU box)
- BGE-Reranker-v2-M3 em GPU custa <50ms por par.
- **Custo:** 4d + ongoing $40-150/mês.
- **Prós:** latência ideal.
- **Contras:** **VIOLA restrição "self-hosted Docker Compose em VPS"** se for SaaS; precisa de VPS GPU dedicado (e.g. Hetzner GEX44 ~€100/mês). Não cabe no orçamento atual.
### Opção D — Trocar o reranker por modelo menor
- BGE-Reranker-v2-M3 → MiniLM-L-6 (32x menor, 10x mais rápido, recall pior).
- **Custo:** 2d.
- **Prós:** baixa latência sem trocar de host.
- **Contras:** **recall@5 estimado -10 a -20%**. Não validado. Risco de regressão de qualidade.
### Opção E — Não rerankear; usar só RRF
- Skip rerank. Devolver direto top-k do `hybrid_search_chunks` RPC.
- **Custo:** 0.5d.
- **Prós:** latência consistente <1.3s.
- **Contras:** perde ~5-15% de precisão@5 dependendo da query (BGE-Reranker melhora especialmente queries semânticas longas).
## Decisão
**Adotar Opção B — Warm-pool + cache + rerank assíncrono com SSE progress.**
Fundamentos:
1. **Percepção de latência** é o que conta para o usuário, não latência total. Mostrar top-20 RRF em <1s e enriquecer com rerank em background UX excelente sem violar restrições.
2. **Cache cobre Persona A/B (jornalistas, pesquisadores)** que repetem queries semelhantes em sessão.
3. **Warm-pool é mandatório de qualquer forma** — o cold start em produção é defeito.
4. **GPU (Opção C) viola política self-hosted Docker Compose** e não cabe no orçamento. Reabrir só se latência rerank ficar reportada como bloqueante mesmo com cache + async.
## Implementação
### Warm-pool
```yaml
# infra/disclosure-stack/docker-compose.yml — embed:
healthcheck:
test: ["CMD", "curl", "-f", "-X", "POST",
"http://localhost:8000/embed",
"-H", "content-type: application/json",
"-d", '{"texts":["warmup"]}']
interval: 60s
timeout: 30s
retries: 3
start_period: 90s # tolera carregamento inicial
restart: unless-stopped
```
E no `app.py`, um endpoint `/warmup` dedicado que invoca embed + rerank com payload trivial para ambos os modelos.
### Cache
```sql
-- migration 0004_rerank_cache.sql
CREATE TABLE public.rerank_cache (
hash TEXT PRIMARY KEY,
query_normalized TEXT NOT NULL,
candidate_ids INT[] NOT NULL,
scores REAL[] NOT NULL,
hits INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_hit TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_rerank_cache_last_hit ON public.rerank_cache(last_hit);
-- TTL job: DELETE FROM rerank_cache WHERE last_hit < NOW() - INTERVAL '24 hours';
-- Roda via cron container leve, 1x/dia.
```
`web/lib/retrieval/hybrid.ts`:
1. Calcula `hash = sha256(normalize(query) + sort(candidate_ids))`.
2. `SELECT scores FROM rerank_cache WHERE hash = $1`. Se hit, devolve direto.
3. Se miss, chama `/rerank`. Insere com `ON CONFLICT (hash) DO UPDATE SET hits = hits + 1, last_hit = NOW()`.
### Rerank assíncrono
`web/app/api/search/hybrid/route.ts` vira SSE:
1. Recebe query, retorna `top-20 RRF` imediatamente como `event: results.initial`.
2. Em background, chama rerank.
3. Emite `event: results.reranked` com nova ordem.
4. UI: estado `results.optimistic` mostra os 20; ao receber `results.reranked`, transição animada.
Para chat (`hybrid_search` tool), mantém comportamento síncrono — o LLM precisa do top-5 estável. Mas o cache acelera.
## Consequences
### Positivas
- Cold start eliminado pelo warm-pool.
- p50 hybrid_search percebido <800ms.
- p95 rerank warm <3s (com cache hit, instantâneo).
- Cache compartilhado entre usuários — escala viral em queries trending.
### Negativas
- Complexidade de coordenação client/server no SSE inicial→reranked.
- Cache pode entupir se `unaccent + normalization` falhar — monitorar tamanho e hits ratio.
- ~3d a mais que a Opção A simples.
### Neutras
- Tudo se mantém em CPU local; nada vai para SaaS.
- BGE-Reranker-v2-M3 stays — sem regressão de recall.
## Validação
- Latência: medir p50/p95 de `hybrid_search` end-to-end e de `/rerank` isolado, antes e depois.
- Cache hit ratio após 7d de tráfego → meta ≥40%.
- Smoke: matar o container `embed` e verificar que `healthcheck` reativa warm em <90s.
## Referências
- BGE-Reranker-v2-M3 perf: <https://huggingface.co/BAAI/bge-reranker-v2-m3>
- Gap #5 médio do brief
- `web/lib/retrieval/embed.ts:16` timeout 60s atual

View file

@ -0,0 +1,140 @@
---
adr: 003
title: "Geração da Timeline — eliminar stubs 'Phase X' e materializar narrativa real"
status: Accepted (blocking — Phase 0)
date: 2026-05-17
owner: sa-principal
related_phase: Fase 0 + Fase 3
---
# ADR-003 — Timeline sem stubs
## Status
Accepted. **Bloqueante para entrega.** O usuário está furioso com os stubs "Phase X" e é a primeira coisa que cai na fase 0.
## Context
Estado atual: `/Users/guto/ufo/wiki/entities/events/` tem 7.091 eventos. Desses, **7.090 (99.99%) carregam `narrative_summary: _Stub. Will be enriched in Phase 7._`** — só 1 evento tem narrativa real.
Origem do mal: `/Users/guto/ufo/scripts/03-dedup-entities.py`, linhas 167-169:
```python
"narrative_summary": "_Stub. Will be enriched in Phase 7._",
"narrative_summary_pt_br": "_Stub. Será enriquecido na Fase 7._",
# (e o body também recebe "_Stub generated by entity dedup. Will be enriched in Phase 6._")
```
A página `/timeline` (web/app/timeline/page.tsx + web/components/timeline-view.tsx) carrega esses 7.091 eventos via `/api/timeline/route.ts`, agrupa por década e renderiza cards com `narrative_summary.slice(0, 280)`. Resultado visível: décadas inteiras com "_Stub. Will be enriched in Phase 7._" repetido.
Impacto:
- **Promessa de produto destruída.** Quem entra em `/timeline` vê alpha quebrado.
- **Métrica:** 99.99% dos eventos sem narrativa real.
- **22.096 arquivos** em `wiki/entities/**/*.md` carregam variantes desse stub textual.
## Opções consideradas
### Opção A — Apagar a Timeline temporariamente
- Esconde sintoma, não causa.
- **Custo:** 0.5d.
- **Veto:** Timeline é feature de homepage; removê-la em vez de consertar é teatro.
### Opção B — Filtrar stubs no `/api/timeline` por default
- Adicionar `WHERE narrative_summary IS NOT NULL AND narrative_summary NOT LIKE '\_Stub%'` na query (ou equivalente filesystem).
- Restante visível com `?include_stubs=true`.
- **Custo:** 1d.
- **Prós:** Timeline carrega 1 evento (estado mínimo honesto) em vez de 7.091 stubs.
- **Contras:** sozinho não resolve o problema — a UI fica vazia.
### Opção C — Eliminar o texto stub do source + pipeline de enrichment real
- Patch `03-dedup-entities.py` para nunca emitir stub textual: `narrative_summary: null`, `summary_status: 'none'`.
- Script `scripts/maintain/41_dedup.py` migra os 22.096 arquivos existentes (strip + status field).
- Curadoria de 50-100 eventos canônicos pelo case-writer (Holmes/Watson voz) com narrativa rica.
- Pipeline `scripts/synthesize/20_entity_summary.py` itera eventos com `total_mentions ≥ 5`, gera narrative via Claude Code OAuth, popula DB + escreve no MD.
- **Custo:** 3d (fase 0 — patch + migração + curadoria mínima 20 eventos) + 8d (fase 3 — pipeline enrichment 2k+).
- **Prós:** ataca causa, não sintoma; UI fica honesta + rica.
- **Contras:** custo maior; depende de Claude Code OAuth não estourar quota.
### Opção D — Auto-gerar narrative no momento da request
- Lazy enrichment: ao abrir o evento, dispara Claude Code; cache no DB.
- **Custo:** 2d.
- **Veto:** primeiro hit fica 8-20s (péssima UX); custo descontrolado (cada usuário pode bombar 7k eventos); LLM offline = página quebrada.
## Decisão
**Adotar Opção C — eliminar fonte + migrar arquivos + curadoria âncora + pipeline enrichment progressivo.**
Quebra em duas ondas:
### Onda 1 (Fase 0, 3d — bloqueante)
1. **Patch `scripts/03-dedup-entities.py`**: substituir as 3 linhas que emitem stub textual por campos vazios + `summary_status: 'none'`.
2. **Migração `scripts/maintain/41_strip_stubs.py`**: percorre `wiki/entities/**/*.md`. Para cada arquivo com stub textual:
- YAML: seta `narrative_summary: null`, `narrative_summary_pt_br: null`, adiciona `summary_status: none`.
- Body: remove blocos "_Stub generated by entity dedup. Will be enriched in Phase 6._" e mantém o restante.
- Idempotente: já-migrado é detectado por presença de `summary_status` e pulado.
- Append log em `wiki/log.md`.
3. **Update `/api/timeline/route.ts`**: filtro server-side; default = só eventos com `narrative_summary` não-nulo. Param `?include_unsynthesized=true` libera; UI mostra badge "sem síntese ainda".
4. **Update UI `timeline-view.tsx` + entity pages**: quando `narrative_summary` ausente, renderiza:
- badge "Sem síntese ainda"
- botão "Sintetizar agora" (visible para `role='admin'` — chama enrichment pontual via `/api/admin/synthesize/<entity_id>`)
- linka para os chunks que mencionam a entidade (já temos `entity_mentions`).
5. **Curadoria âncora — 20 eventos manuais para a Timeline:** Roswell, Nimitz Tic-Tac, Phoenix Lights, Foo Fighters, Trindade Island BR, Operação Prato, Operação Mainbrace, AATIP disclosure, Belgian Wave, Rendlesham Forest, Cash-Landrum, Westall, Travis Walton, Father Gill, Lonnie Zamora, Maury Island, Kenneth Arnold, Aurora 1897, Foo lights 1944, Mantell crash. Cada um com `narrative_summary_en/pt` curados; `summary_status: 'curated'`.
### Onda 2 (Fase 3, 8d — enrichment progressivo)
1. **Pipeline `scripts/synthesize/20_entity_summary.py`**:
- Itera entities com `total_mentions ≥ 5` ordenadas por `total_mentions DESC`.
- Para cada uma, lê chunks via `entity_mentions`, monta prompt com top-10 menções verbatim, chama Claude Code OAuth (Sonnet para qualidade) em modo `--max-turns 1`.
- Output: `narrative_summary_en` (3-6 frases) + `narrative_summary_pt` (3-6 frases) + `summary_status: 'synthesized'` + `summary_confidence: medium`.
- Idempotente: `summary_status` ≠ 'none' pula.
- Custo estimado: ~$80-150 para corpus inteiro a 5+ mentions threshold (~5-8k entidades).
- Throttle: 200 entidades/dia para não estourar quota Max 20x.
2. **Quality gate**: para `summary_status='curated'`, lint exige `summary_confidence: high`. Para `summary_status='synthesized'`, default `medium` + check de heurística (não pode citar fato sem chunk_id no rationale).
## Consequences
### Positivas
- `grep "Will be enriched in Phase" wiki/` → 0 hits após onda 1.
- `/timeline` mostra ≥20 eventos com narrativa rica de imediato; cresce para ≥2k na fase 3.
- Estado honesto: entidades sem síntese mostram badge claro, não texto enganoso.
- Pipeline progressivo: enrichment não bloqueia release.
### Negativas
- Migração toca 22k arquivos — risco de corrupção YAML. Mitigação: dry-run + diff antes de commit; idempotência; backup do `wiki/` antes.
- Custo OAuth ~$150 estimado se rodar enrichment completo.
- 20 eventos curados manualmente = 1d de case-writer; precisa de revisão linguística PT-BR.
### Neutras
- Mantém estrutura de 24 tipos de markdown intacta. Só adiciona campo `summary_status` no schema YAML.
## Compatibilidade com CLAUDE.md
Schema canônico em `/Users/guto/ufo/CLAUDE-schema-full.md` precisa ganhar `summary_status` em `entity` frontmatter. Update do schema é parte da entrega da fase 0.
## Validação
- Antes da fase 0 mergear: `grep -c "Will be enriched" wiki/` precisa ser 0.
- Após onda 1: `/timeline` carrega <2s com 20 cards renderizados, cada um com narrativa 100 chars.
- Após onda 2: `/timeline` carrega ≥2.000 eventos com narrativa real.
- Lint passa em todas as entidades migradas (regex IDs ainda válidos; YAML válido; backlinks consistentes).
## Referências
- `scripts/03-dedup-entities.py` (origem do stub textual).
- `CLAUDE.md §3` (regras bilíngue) — `narrative_summary` precisa ter campos EN e PT-BR.
- `CLAUDE.md §13` (triggers de enrichment) — `≥3 menções OR central claim``enrichment_status: deep`. Vamos espelhar essa lógica em `summary_status` (`none` < `synthesized` < `curated` < `red_teamed`).
- Gap #1 do brief (timeline com stubs).

View file

@ -0,0 +1,218 @@
---
adr: 004
title: "Case Bureau pipeline — Hypothesis tournament (Tetlock) + Evidence chain-of-custody (Locard)"
status: Proposed
date: 2026-05-17
owner: sa-principal
related_phase: Fase 2
---
# ADR-004 — Case Bureau pipeline
## Status
Proposed. **Diferencial competitivo do produto.** Sem Case Bureau o Disclosure Bureau é "wiki bonita com chat" — comoditizável.
## Context
O `/Users/guto/ufo/CLAUDE.md` define 12 tipos investigativos (evidence, witness, hypothesis, gap, relation, timeline, actor_profile, case_report, residual_uncertainty, …). O `/Users/guto/ufo/CLAUDE-schema-full.md` formaliza o frontmatter de cada um.
**Estado físico:**
```
case/
├── connect-the-dots/ 0 files
├── evidence/ 0 files
├── gaps/ 2 placeholder files (bootstrap)
├── hypotheses/ 0 files
├── profiles/ 0 files
├── timelines/ 0 files
└── witnesses/ 0 files
```
**Nunca rodou.** O brief lista isso como gap crítico #2. O `case-report.md` referenciado em `wiki/index.md` é um link quebrado.
O contrato exige:
- **6 quality rubrics threshold 0.85** em `case_report.md` (chain_of_custody_completeness, confidence_calibration_match, hypothesis_tournament_discipline ≥3 hipóteses, residual_uncertainty_presence, audit_trail_per_claim, red_team_pass).
- **Confidence bands Tetlock**: high (≥0.90) / medium (0.60-0.89) / low (0.30-0.59) / speculation (<0.30).
- **Evidence grades Locard**: A (≥3 custody steps) / B (≥2) / C (≥1).
- **8 detetives**: Holmes/Poirot/Dupin/Locard + Schneier/Tetlock/Taleb (papéis declarados, sem implementação).
## Opções consideradas
### Opção A — Pipeline batch sobre todo o corpus
- Para cada `event` com `total_mentions ≥ 5`, criar case automaticamente; gerar 3 hipóteses; bayesianizar.
- **Custo:** 8d.
- **Prós:** cobertura ampla.
- **Contras:** **viola proporcionalidade Fowler.** 7.091 events × ($0.30 por case) = $2k+ em LLM. Quality cai. Output ilegível.
### Opção B — Seed manual + pipeline incremental
- 5 casos âncora seedados manualmente (canonical events).
- Pipeline gera hipóteses para cada caso âncora.
- Próximos casos abrem por demanda (user pede "abra case para Event-X" via chat ou admin).
- **Custo:** 12d (3d seed + 4d hypothesis engine + 5d UI).
- **Prós:** qualidade controlada; ROI por case.
- **Contras:** cobertura inicial pequena.
### Opção C — Outsource para business parceira
- Persona desta arquitetura: chamar `juridical-singularity` ou `research-intelligence` via harness para o trabalho investigativo.
- **Custo:** ? (depende da business).
- **Contras:** Case Bureau é **a alma do produto Disclosure Bureau**; outsourcear é estranho. Manter in-house.
## Decisão
**Adotar Opção B — 5 casos âncora seedados + pipeline incremental.**
Justificativas:
1. **Qualidade > cobertura.** Um Case Bureau com 5 casos exemplares vence 7k casos genéricos. Persona A/B/C aprende o padrão e confia no método.
2. **Proporcional ao orçamento atual** (Claude Max 20x).
3. **Ciclo de feedback rápido.** Cada case rodado refina o prompt do hypothesis engine.
## Implementação
### Etapa 1 — Schema (1d)
Migration `0003_case_bureau.sql` (esboço em entregável 04, §4.4.1). Tabelas:
- `cases(case_id, canonical_name, scope_summary, status, …)`
- `hypotheses(hypothesis_id, case_id, statement_en, statement_pt, prior, likelihood, posterior, evidence_for[], evidence_against[], red_team_pass, confidence_band, authored_by, …)`
- `evidence(evidence_id, case_id, doc_id, page, bbox, verbatim_excerpt, evidence_grade, chain_of_custody, custody_gaps[], …)`
- `witnesses(witness_id, case_id, witness_name, account_summary, verdict, …)`
- `gaps(gap_id, case_id, description, criticality, …)`
- `case_reports(case_id, body_md, quality_rubrics JSONB, last_review)`
RLS: `SELECT public USING (TRUE)` (corpus público); writes via `service_role` (pipeline only).
### Etapa 2 — Seed (2d)
`scripts/case/30_seed.py` cria 5 casos âncora:
| `case_id` | Evento | Justificativa |
|---|---|---|
| `CASE-ROSWELL-1947` | Roswell incident, julho 1947 | Canonical UAP case; rico em documentos FBI Vault. |
| `CASE-NIMITZ-TIC-TAC-2004` | USS Nimitz / Princeton Tic-Tac encounter | Caso DoD oficial; vídeo + radar data. |
| `CASE-PHOENIX-LIGHTS-1997` | Phoenix Lights, março 1997 | Multi-witness; corpus tem FAA reports. |
| `CASE-HOTTEL-MEMO-1950` | FBI Hottel memo, Roswell adjacent | Single-document case; mostra leitura crítica. |
| `CASE-APOLLO-SKYLAB-1973` | Skylab IV anomalous photos | NASA corpus; bridge para civilian science angle. |
Cada seed cria:
- 1 `case_report` skeleton com `quality_rubrics: null` (será preenchido após review).
- 3-5 `evidence` rows extraídas dos chunks corpus existentes (chunks com `ufo_anomaly=true` para esses docs, ordenados por relevância).
- 2-3 `witness` rows quando aplicável (relatos primários no corpus).
- 1 `timeline` com event_ids relacionados.
### Etapa 3 — Hypothesis Tournament Engine (4d)
`scripts/case/31_hypothesis_tournament.py`:
```
INPUT: case_id
WORKFLOW:
1. Load case + evidence + witnesses (DB).
2. For each detective_role in ['holmes','poirot','dupin']:
prompt = build_prompt(case, role, evidence_summary)
call Claude Code OAuth (Sonnet) --max-turns 3
parse output → 1 hypothesis with statement_en/pt, prior, likelihood
insert into hypotheses(authored_by=role)
3. After 3 hypotheses generated:
normalize priors so Σ = 1.0
compute posterior_i = (prior_i × likelihood_i) / Σ_j(prior_j × likelihood_j)
assign confidence_band by posterior thresholds (Tetlock):
≥0.70 high, 0.40-0.69 medium, 0.15-0.39 low, <0.15 speculation
4. Write H-NNNN.md files in case/hypotheses/
5. Update case_report.md with leaderboard
```
Prompts são versionados em `scripts/case/prompts/{holmes,poirot,dupin}_hypothesis.md`. ADR-007 (futuro) congelará a versão de prompt usada por release.
### Etapa 4 — Locard Chain-of-Custody Audit (1d como parte da fase 2)
`scripts/case/32_evidence_audit.py`: para cada evidence row, valida:
- `verbatim_excerpt` matches a chunk em DB (não fabricado).
- `chain_of_custody` array tem ≥1 step (Grade C), ≥2 (B), ≥3 (A).
- Se gap, popula `custody_gaps[]` com descrição.
Output: `case/<case_id>/evidence_audit.md` com sumário.
### Etapa 5 — Red-Team Review (chief-detective) (2d)
`scripts/case/33_red_team.py`:
```
INPUT: case_id
WORKFLOW:
1. Load hypotheses sorted by posterior DESC.
2. Take top hypothesis. Build adversarial prompt:
"You are the chief detective. The dominant hypothesis is X with
posterior Y. Find the strongest argument that this hypothesis is
wrong. Cite specific evidence by evidence_id."
3. Call Claude Code OAuth (Sonnet).
4. Parse output → residual_uncertainty.md entry.
5. Quality rubric red_team_pass = (residual_uncertainty has substance).
6. Update case_report.md quality_rubrics.
```
### Etapa 6 — UI (5d, parte da fase 2)
Rotas novas:
- `/case` — lista os casos.
- `/case/[case_id]` — overview: status, timeline, 3 hipóteses, evidence count.
- `/case/[case_id]/hypotheses` — tournament leaderboard, cada card com posterior, evidence_for[], red-team box.
- `/case/[case_id]/evidence` — lista evidence; cada card com bbox crop inline + chain_of_custody timeline.
Componentes: `CaseTimelineLane`, `HypothesisTournamentCard`, `EvidenceChainCustody`, `WitnessVerdictBadge`, `ResidualUncertaintyBox`.
Tools de chat novos (em `web/lib/chat/tools.ts`): `read_case`, `read_hypothesis`, `read_evidence`, `list_open_cases`.
### Etapa 7 — Quality gate (incluído na fase 2)
`scripts/case/34_quality_check.py` valida as 6 rubrics no `case_report.md`. Saída JSON com scores. Gate threshold 0.85 (fase 2 inicial pode relaxar para 0.80 conforme prompts maturam).
## Consequences
### Positivas
- **5 cases âncora exemplares** demonstram o método. Confiança do usuário sobe.
- **Hypothesis tournament real** com posteriores calculados, não decoração.
- **Red-team forçado** evita âncora cognitiva — o produto se desafia.
- **Citações de chat ganham `evidence_card` artifact** (ADR-001) — UI mais rica.
- Quality rubrics no `case_report.md` viram **assinatura epistemológica** do Bureau.
### Negativas
- Pipeline LLM-intensivo. Custo estimado: ~$30-50 para os 5 casos âncora, ~$5 por case novo via demanda.
- Quality rubrics threshold 0.85 é alto; primeiros cases podem precisar revisão humana antes do publish.
- 12d de engenharia — fase pesada.
### Neutras
- Não toca chat protocol (ADR-001) nem retrieval pipeline. Adiciona camada.
- 24 tipos de markdown do CLAUDE.md ganham 7 com conteúdo real pela primeira vez.
## Risco mitigado
- **Hipóteses fabricadas (LLM inventa fatos):** prompt exige citação por `chunk_id` para cada claim; pipeline `32_evidence_audit.py` valida match em DB. Linha fabricada = hypothesis rejeitada.
- **Red-team viciado:** prompt adversarial é separado em outro arquivo, com persona explicitamente cética.
- **Quality rubrics objetivas vs subjetivas:** as 6 rubrics são todas auditáveis (counting custody steps, hypothesis count ≥3, residual entries ≥1, …). Não há "qualidade narrativa" como rubric — isso fica para a curadoria humana opcional.
## Validação
- 5 cases na `cases` table com `status='open'` ou `'closed'`.
- Cada case com ≥3 hipóteses; ≥1 com `red_team_pass=true`.
- `case_report.md` por case com `quality_rubrics` JSONB todas ≥0.80.
- UI `/case/case-nimitz-tic-tac-2004` renderiza completo (timeline + hypotheses + evidence + red-team).
- Chat tool `read_case` resolve via inline `case_card` artifact.
## Referências
- `CLAUDE.md §4` (24 tipos), `§8` (confidence calibration Tetlock), `§10` (procedência Locard), `§12` (quality gates).
- `CLAUDE-schema-full.md` (schemas YAML de evidence, hypothesis, witness, case_report, residual_uncertainty).
- Gap #2 do brief.

View file

@ -0,0 +1,214 @@
---
adr: 005
title: "Multi-tenant vs multi-corpus single-tenant — caminho de evolução"
status: Proposed
date: 2026-05-17
owner: sa-principal
related_phase: Fase 5 (multi-corpus) e Fase 6 (multi-tenant opcional)
---
# ADR-005 — Multi-tenant vs multi-corpus single-tenant
## Status
Proposed.
## Context
A visão de longo prazo do brief lista "multi-tenant/multi-corpus". Atualmente o sistema é **single-corpus, single-tenant**:
- Toda tabela de retrieval (`documents`, `chunks`, `entities`, `entity_mentions`) referencia o corpus implícito war.gov/ufo.
- `wiki/` é um único namespace; paths `wiki/documents/<doc-id>.md` colidem se onboardarmos JFK files ou Operação Prato BR.
- `chat_sessions.user_id` aponta `auth.users(id)` direto; sem `tenant_id`.
- RLS policy é `USING (TRUE)` — qualquer signed-up user vê tudo.
- Frontend roteia `/d/<doc-id>` assumindo corpus único.
A diferença semântica importa:
- **Multi-corpus** = vários corpora coexistem mesma equipe os opera (war.gov/ufo + JFK + BR). Visibilidade pode variar (`public` / `unlisted` / `private`).
- **Multi-tenant** = vários **operadores independentes** (organizações, indivíduos) com isolamento de dados, billing, gestão. Persona D do entregável 03.
## Opções consideradas
### Opção A — Pular direto para multi-tenant verdadeiro
- Refactor pesado: `tenant_id` em todas as tabelas; RLS por tenant; signup com tenant; billing.
- **Custo:** 18-25d.
- **Veto Cagan:** prematuro. Não temos demanda nem validação. Persona D é latente.
### Opção B — Multi-corpus single-tenant primeiro; multi-tenant só se houver sinal
- Adicionar `corpus_id` em `documents`/`chunks`/`entities`. Tabela `corpora` controla visibilidade.
- Single-tenant = a equipe do Bureau opera tudo. Usuários signed-up só leem.
- Multi-tenant fica para fase 6, **opcional**, ativado por flag.
- **Custo:** 14d (fase 5) + 8-15d futuro condicional.
- **Prós:** entrega valor real (3 corpora visíveis); reabre multi-tenant só com sinal.
- **Contras:** schema decision não totalmente future-proof — algumas tabelas precisarão de `tenant_id` extra na fase 6.
### Opção C — Deploy por corpus (replicar a stack)
- Cada corpus é um deploy separado (`disclosure.top`, `jfk.disclosure.top`, `prato.disclosure.top`).
- **Custo:** 4d por deploy.
- **Prós:** zero refactor de schema.
- **Contras:** **viola "single pane of glass"**; usuário não pode pesquisar cross-corpus; UX fragmentada; ops complexa (3x containers, 3x backups).
## Decisão
**Adotar Opção B — multi-corpus single-tenant na Fase 5; multi-tenant na Fase 6 só se Persona D mostrar sinal.**
Justificativas:
1. **Cagan:** valida hipótese com sinal mínimo. Multi-corpus desbloqueia Persona D-light (a equipe do Bureau adicionando corpora curados); multi-tenant verdadeiro requer demanda externa real.
2. **Fowler:** arquitetura evolutiva — não over-engineer. `corpus_id` é refactor barato; `tenant_id` adicionado depois é refactor adicional barato. Não há "muralha" entre B e Multi-tenant na fase 6.
3. **Larson:** decisão escrita. Reabre com critério: ≥3 organizações externas pedindo onboard antes de F6 expandir para multi-tenant.
## Implementação — Fase 5 (multi-corpus single-tenant)
### Schema
```sql
-- migration 0005_multicorpus.sql
CREATE TABLE public.corpora (
corpus_id TEXT PRIMARY KEY, -- 'war-gov-ufo', 'jfk-files', 'prato-1977'
display_name TEXT NOT NULL,
description TEXT,
language_primary TEXT NOT NULL DEFAULT 'en', -- 'en' | 'pt'
visibility TEXT NOT NULL DEFAULT 'public'
CHECK (visibility IN ('public','unlisted','private')),
cover_image TEXT,
doc_count INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Seed do corpus existente
INSERT INTO public.corpora (corpus_id, display_name, language_primary, visibility)
VALUES ('war-gov-ufo', 'War.gov/UFO — DoD UAP Declassified', 'en', 'public');
-- Adiciona FK em documents
ALTER TABLE public.documents
ADD COLUMN corpus_id TEXT NOT NULL DEFAULT 'war-gov-ufo'
REFERENCES public.corpora(corpus_id) ON DELETE RESTRICT;
ALTER TABLE public.documents ALTER COLUMN corpus_id DROP DEFAULT;
-- Idem chunks, entities, entity_mentions (via documents.corpus_id derivado ou denormalizado)
-- Para queries grandes, denormalizar é melhor:
ALTER TABLE public.chunks
ADD COLUMN corpus_id TEXT NOT NULL DEFAULT 'war-gov-ufo';
ALTER TABLE public.entities
ADD COLUMN corpus_id TEXT NOT NULL DEFAULT 'war-gov-ufo';
-- Drop defaults após backfill
-- Update RLS
DROP POLICY documents_read ON public.documents;
CREATE POLICY documents_read ON public.documents FOR SELECT USING (
corpus_id IN (
SELECT corpus_id FROM public.corpora WHERE visibility = 'public'
)
);
-- Idem chunks, entities, mentions
```
### Paths do `wiki/`
**Decisão dentro do ADR:** manter `wiki/` em raiz, identificar corpus via DB. Os paths físicos seguem `wiki/<corpus_id>/documents/...` mas com **link simbólico** `wiki/documents``wiki/war-gov-ufo/documents` por compat.
Trade-off: refactor de paths impacta `web/lib/wiki.ts` e `next.config.ts outputFileTracingIncludes`. Migration script `scripts/maintain/45_relayout_wiki.py` faz a movimentação atômica + cria os symlinks.
Para corpora futuros (JFK, Prato), paths já nascem `wiki/<corpus_id>/...`.
### Frontend
- Switcher de corpus na navbar: `<select>` listando corpora `public`. Estado em cookie `corpus_id`.
- Rotas: `/c/<corpus_id>` (overview do corpus); `/d/<doc-id>` mantém-se mas exibe `corpus_id` como breadcrumb.
- Search global tem checkbox "incluir todos os corpora" (default off — busca só no current).
- Chat session vincula `corpus_id` (adicionar coluna em `chat_sessions`).
### Onboard de corpora-piloto
Para validar:
1. **JFK files** — corpus pequeno (~50 docs), alta visibilidade, valida pipeline single-language EN.
2. **Operação Prato 1977** — corpus médio (~30 docs), valida bilíngue real (PT-BR como primary).
Cada onboard é um run da pipeline `scripts/ingest/*` com `--corpus-id <id>`. Validação: hybrid search funciona scoped ao corpus.
## Implementação — Fase 6 (multi-tenant verdadeiro, opcional)
**Critério para abrir:** ≥3 organizações externas distintas pedindo onboard com isolamento.
Adições:
```sql
ALTER TABLE public.corpora
ADD COLUMN owner_org_id UUID REFERENCES public.organizations(org_id);
CREATE TABLE public.organizations (
org_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
plan TEXT NOT NULL DEFAULT 'free' CHECK (plan IN ('free','indie','team','enterprise')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE public.memberships (
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
org_id UUID NOT NULL REFERENCES public.organizations(org_id) ON DELETE CASCADE,
role TEXT NOT NULL CHECK (role IN ('owner','admin','editor','viewer')),
PRIMARY KEY (user_id, org_id)
);
-- RLS adicional
CREATE POLICY documents_private_read ON public.documents FOR SELECT USING (
corpus_id IN (
SELECT c.corpus_id FROM public.corpora c
LEFT JOIN public.memberships m ON c.owner_org_id = m.org_id
WHERE c.visibility = 'public'
OR (c.visibility IN ('unlisted','private') AND m.user_id = auth.uid())
)
);
```
Billing fica fora do escopo (decisão de produto, não de arquitetura). Stripe self-host via webhooks pode ser adicionado sem refactor de schema.
## Consequences
### Positivas
- Multi-corpus desbloqueia Persona D-light + roadmap (JFK, Prato, etc.).
- API REST e MCP server (Fase 5) se beneficiam — `?corpus_id=` filter natural.
- Caminho gradual para multi-tenant verdadeiro sem retrabalho.
### Negativas
- Refactor de paths `wiki/` é tocar muita coisa (lib/wiki.ts, next.config.ts, scripts).
- RLS policies ficam um pouco mais complexas — precisa de testes.
- Switcher de corpus na UI = novo state model no frontend.
### Neutras
- Mantém VPS Docker Compose; não vai pra Kubernetes.
- Bilíngue stack já pronto (cada corpus tem `language_primary`, mas chunks já são EN+PT).
## Validação
- Migration runs em <5min em staging.
- 3 corpora visíveis na navbar; switcher troca contexto.
- Hybrid search scoped ao corpus selecionado por default; checkbox "todos" funciona.
- RLS test: usuário sem membership em corpus `private` recebe 0 rows.
- Onboard de JFK: pipeline `scripts/ingest/*` com `--corpus-id jfk-files` produz `wiki/jfk-files/...` e popula DB sem afetar war-gov-ufo.
## Risco de abertura externa (relevante para fase 6)
Multi-tenant traz riscos novos:
- **PII em corpora privados** — política de retenção, compliance LGPD/GDPR.
- **Custo de LLM por tenant** — quem paga o enrichment? OAuth quota não cobre N tenants.
- **Abuse**: tenant tóxico envenenando corpus público (poisoning).
Esses ficam fora do escopo do ADR-005; serão tratados em ADR futuros se a fase 6 abrir.
## Referências
- Gap "multi-tenant/multi-corpus" do brief (visão longo prazo).
- Persona D do entregável 03.
- `/Users/guto/ufo/infra/supabase/migrations/0002_chunks_retrieval.sql` (schema atual single-corpus).

View file

@ -446,9 +446,24 @@ documented_in: [...]
total_mentions: 18
documents_count: 7
narrative_summary_confidence: high
narrative_summary: |
Em 14 de novembro de 2004, durante exercícios do CSG-11...
narrative_summary_pt_br: |
Em 14 de novembro de 2004, durante os exercícios do CSG-11...
# Synthesis pipeline status (Fase 0+3). Replaces the older
# `narrative_summary_confidence` field which conflated provenance and quality.
#
# none — narrative absent. Renderer must show badge "sem síntese ainda"
# and link to chunks via entity_mentions. NEVER emit placeholder
# text like "_Stub. Will be enriched in Phase N._".
# synthesized — produced automatically by scripts/synthesize/20_entity_summary.py
# via Claude Code OAuth (Sonnet). summary_confidence default 'medium'.
# curated — written or revised by hand (or by scripts/synthesize/01_anchor_events.py
# for canonical anchor events). summary_confidence default 'high'.
# red_teamed — curated + reviewed by chief-detective for factual accuracy.
summary_status: curated # none | synthesized | curated | red_teamed
summary_confidence: high # high | medium | low | null
related_events: ["[[event/EV-2015-XX-XX-gimbal]]"]
preceded_by: []

View file

@ -76,9 +76,8 @@ services:
GOTRUE_JWT_EXP: 3600
GOTRUE_JWT_SECRET: ${JWT_SECRET}
GOTRUE_EXTERNAL_EMAIL_ENABLED: "true"
# SMTP is not configured yet, so we auto-confirm signups (skips email
# verification). Switch to "false" once SMTP_PASS is set.
GOTRUE_MAILER_AUTOCONFIRM: "true"
# SMTP configured (Spacemail) → email confirmation required for signups.
GOTRUE_MAILER_AUTOCONFIRM: "false"
GOTRUE_MAILER_OTP_EXP: 3600
GOTRUE_SMTP_HOST: ${SMTP_HOST}
GOTRUE_SMTP_PORT: ${SMTP_PORT}

View file

@ -260,14 +260,15 @@ def collect_entities_from_pages(doc_filter: str | None = None) -> dict:
return collected
def _stub_body(entity_class: str, canonical_name: str) -> str:
"""Standard bilingual stub body for new entities."""
def _empty_body(entity_class: str, canonical_name: str) -> str:
"""Header-only body for new entities; narrative is filled by the synthesis
pipeline (scripts/synthesize/) when total_mentions 5, or by manual
curation. We never emit placeholder text `summary_status: none` in the
frontmatter signals 'not yet synthesised' to the renderer."""
return (
f"# {canonical_name}\n\n"
"## Description (EN)\n\n"
"_Stub generated by entity dedup. Will be enriched in Phase 6._\n\n"
"## Descrição (PT-BR)\n\n"
"_Stub gerado pela deduplicação de entidades. Será enriquecido na Fase 6._\n"
"## Descrição (PT-BR)\n"
)
@ -421,7 +422,7 @@ def _upsert_simple_entity(
fm["last_lint"] = None
fm["wiki_version"] = WIKI_VERSION
body = _stub_body(entity_class, canonical_name)
body = _empty_body(entity_class, canonical_name)
write_frontmatter_and_body(path, fm, body, dry_run=dry_run)
_register_in_index(dir_name, path, set(aliases_sorted), canonical_name)
return ("created", True, path)
@ -483,16 +484,17 @@ def _upsert_event(event_id: str, data: dict, dry_run: bool) -> tuple[str, bool,
"documented_in": [],
"total_mentions": total_mentions,
"documents_count": len(unique_docs),
"narrative_summary_confidence": "low",
"narrative_summary": "_Stub. Will be enriched in Phase 7._",
"narrative_summary_pt_br": "_Stub. Será enriquecido na Fase 7._",
"narrative_summary": None,
"narrative_summary_pt_br": None,
"summary_status": "none",
"summary_confidence": None,
"enrichment_status": "none",
"external_sources": [],
"last_ingest": utc_now_iso(),
"last_lint": None,
"wiki_version": WIKI_VERSION,
}
body = _stub_body("events", canonical_name)
body = _empty_body("events", canonical_name)
write_frontmatter_and_body(path, fm, body, dry_run=dry_run)
_register_in_index("events", path, set(labels), canonical_name)
return ("created", True, path)
@ -577,7 +579,7 @@ def _upsert_uap_object(obj_id: str, data: dict, dry_run: bool) -> tuple[str, boo
"last_lint": None,
"wiki_version": WIKI_VERSION,
}
body = _stub_body("uap_objects", canonical_name)
body = _empty_body("uap_objects", canonical_name)
write_frontmatter_and_body(path, fm, body, dry_run=dry_run)
_register_in_index("uap-objects", path, set(), canonical_name)
return ("created", True, path)

View file

@ -0,0 +1,194 @@
#!/usr/bin/env python3
"""
41_strip_stubs.py Eliminate "Will be enriched in Phase N" placeholders from
wiki/entities/**/*.md.
For each entity markdown file:
Frontmatter:
- narrative_summary: "_Stub. Will be enriched in Phase 7._" null
- narrative_summary_pt_br: "_Stub. Será enriquecido na Fase 7._" null
- drop narrative_summary_confidence
- add summary_status: 'none'
- add summary_confidence: null
Body:
- Remove paragraphs containing "_Stub generated by entity dedup. Will be
enriched in Phase 6._" / "_Stub gerado pela deduplicação de entidades.
Será enriquecido na Fase 6._"
- Keep all other content untouched (curated narrative is preserved).
Idempotent: a file already migrated (has `summary_status` field) is skipped.
Usage:
./41_strip_stubs.py # process all entities
./41_strip_stubs.py --class events # only one class
./41_strip_stubs.py --dry-run # report counts, don't write
./41_strip_stubs.py --verbose # list each touched file
"""
from __future__ import annotations
import argparse
import re
import sys
from datetime import datetime, timezone
from pathlib import Path
try:
import yaml
except ImportError:
sys.stderr.write("pip3 install pyyaml\n")
sys.exit(1)
UFO_ROOT = Path(__file__).resolve().parents[2]
ENTITIES_BASE = UFO_ROOT / "wiki" / "entities"
LOG_PATH = UFO_ROOT / "wiki" / "log.md"
STUB_EN = "_Stub. Will be enriched in Phase 7._"
STUB_PT = "_Stub. Será enriquecido na Fase 7._"
BODY_STUB_EN = "_Stub generated by entity dedup. Will be enriched in Phase 6._"
BODY_STUB_PT = "_Stub gerado pela deduplicação de entidades. Será enriquecido na Fase 6._"
# Match any line that is exactly one of the body stubs (ignoring surrounding whitespace).
BODY_STUB_LINE_RE = re.compile(
r"^\s*(?:" + re.escape(BODY_STUB_EN) + r"|" + re.escape(BODY_STUB_PT) + r")\s*$",
re.MULTILINE,
)
def utc_iso() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def split_md(path: Path) -> tuple[dict, str, bool]:
"""Return (frontmatter, body, had_frontmatter)."""
raw = path.read_text(encoding="utf-8")
if not raw.startswith("---"):
return {}, raw, False
end = raw.find("---", 4)
if end == -1:
return {}, raw, False
try:
fm = yaml.safe_load(raw[3:end].strip()) or {}
except yaml.YAMLError:
return {}, raw, False
body = raw[end + 3 :].lstrip("\n")
return fm, body, True
def render_md(fm: dict, body: str) -> str:
yaml_str = yaml.dump(fm, allow_unicode=True, sort_keys=False, default_flow_style=False)
sep = "" if body.startswith("\n") else "\n"
return f"---\n{yaml_str}---\n{sep}{body}"
def clean_body(body: str) -> str:
"""Drop lines that are exactly a stub paragraph; collapse 3+ blank lines to 2."""
cleaned = BODY_STUB_LINE_RE.sub("", body)
cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
return cleaned
def migrate_file(path: Path, dry_run: bool, verbose: bool) -> tuple[bool, bool]:
"""Returns (changed, already_migrated)."""
fm, body, had_fm = split_md(path)
if not had_fm:
return (False, False)
# Idempotency: presence of summary_status means already migrated.
if "summary_status" in fm:
return (False, True)
new_fm = dict(fm) # preserve insertion order
changed = False
# Clear stub textual values
if new_fm.get("narrative_summary") == STUB_EN:
new_fm["narrative_summary"] = None
changed = True
if new_fm.get("narrative_summary_pt_br") == STUB_PT:
new_fm["narrative_summary_pt_br"] = None
changed = True
# Drop old confidence key (replaced by summary_confidence)
if "narrative_summary_confidence" in new_fm:
new_fm.pop("narrative_summary_confidence", None)
changed = True
# Always add summary_status / summary_confidence so subsequent runs detect
# the migration as already done (even if this file had no stub fields).
new_fm["summary_status"] = "none"
new_fm.setdefault("summary_confidence", None)
# Touch last_lint so downstream tools see the change
new_fm["last_lint"] = utc_iso()
new_body = clean_body(body)
if new_body != body:
changed = True
if not changed:
# No stub to strip, but we still added the status field — count as changed.
changed = True
if dry_run:
if verbose:
print(f" (dry) {path.relative_to(UFO_ROOT)}")
return (True, False)
path.write_text(render_md(new_fm, new_body), encoding="utf-8")
if verbose:
print(f"{path.relative_to(UFO_ROOT)}")
return (True, False)
def main() -> int:
p = argparse.ArgumentParser()
p.add_argument("--class", dest="cls", default=None,
help="restrict to one class folder under wiki/entities/")
p.add_argument("--dry-run", action="store_true")
p.add_argument("--verbose", action="store_true")
args = p.parse_args()
if not ENTITIES_BASE.exists():
sys.stderr.write(f"missing {ENTITIES_BASE}\n")
return 1
if args.cls:
roots = [ENTITIES_BASE / args.cls]
else:
roots = [d for d in ENTITIES_BASE.iterdir() if d.is_dir()]
total = 0
migrated = 0
skipped_already = 0
for root in roots:
for path in sorted(root.glob("*.md")):
total += 1
changed, already = migrate_file(path, args.dry_run, args.verbose)
if already:
skipped_already += 1
elif changed:
migrated += 1
print()
print(f" total scanned: {total}")
print(f" migrated: {migrated}")
print(f" already migrated: {skipped_already}")
print(f" dry-run: {args.dry_run}")
if not args.dry_run and migrated > 0:
LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
with LOG_PATH.open("a", encoding="utf-8") as f:
f.write(
f"\n## {utc_iso()} · STRIP_STUBS\n"
f"- script: scripts/maintain/41_strip_stubs.py\n"
f"- scanned: {total}\n"
f"- migrated: {migrated}\n"
f"- already_migrated: {skipped_already}\n"
)
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,273 @@
#!/usr/bin/env python3
"""
01_anchor_events.py Seed the 20 anchor UAP events with curated bilingual
narrative summaries via Claude Code OAuth subprocess (Sonnet).
Anchor list comes from ADR-003 (Phase 0). Each event:
- Gets/creates wiki/entities/events/EV-<date>-<slug>.md
- Frontmatter: summary_status=curated, summary_confidence=high,
narrative_summary=<EN>, narrative_summary_pt_br=<PT-BR>
- Body untouched if file already exists with manual edits.
Idempotent: re-run skips events where summary_status == 'curated'.
Usage:
./01_anchor_events.py # all anchors
./01_anchor_events.py --only roswell # one event (substring match)
./01_anchor_events.py --dry-run # print prompt + would-write, no LLM call
"""
from __future__ import annotations
import argparse
import json
import re
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
try:
import yaml
except ImportError:
sys.stderr.write("pip3 install pyyaml\n")
sys.exit(1)
UFO_ROOT = Path(__file__).resolve().parents[2]
EVENTS_DIR = UFO_ROOT / "wiki" / "entities" / "events"
LOG_PATH = UFO_ROOT / "wiki" / "log.md"
# Each tuple = (event_id, canonical_name_en, date_start, primary_location, event_class, observers_hint)
# date_start uses YYYY-MM-DD when known, YYYY when only year, YYYY-MM-XX if no day.
ANCHOR_EVENTS = [
("EV-1897-04-17-aurora-airship-crash", "Aurora Airship Crash", "1897-04-17",
"Aurora, Texas, USA", "uap-encounter",
"Local townspeople and newspaper reporters (Dallas Morning News, 17 abr 1897)"),
("EV-1944-XX-XX-foo-fighters-european-theater", "Foo Fighters (European Theater)", "1944",
"Western Front, Europe", "uap-encounter",
"Allied bomber crews of the 415th Night Fighter Squadron and others"),
("EV-1947-06-21-maury-island-incident", "Maury Island Incident", "1947-06-21",
"Puget Sound, Washington, USA", "uap-encounter",
"Harold Dahl, Fred Crisman, Kenneth Arnold (later investigator)"),
("EV-1947-06-24-kenneth-arnold-mount-rainier", "Kenneth Arnold Mount Rainier Sighting", "1947-06-24",
"Mount Rainier, Washington, USA", "uap-encounter",
"Kenneth Arnold, civilian pilot"),
("EV-1947-07-08-roswell-incident", "Roswell Incident", "1947-07-08",
"Roswell, New Mexico, USA", "uap-encounter",
"USAAF 509th Bombardment Group, William Brazel, Major Jesse Marcel"),
("EV-1948-01-07-mantell-crash", "Mantell UFO Incident", "1948-01-07",
"Franklin, Kentucky, USA", "uap-related-fatality",
"Captain Thomas Mantell, Kentucky Air National Guard"),
("EV-1948-07-24-chiles-whitted-encounter", "Chiles-Whitted UFO Encounter", "1948-07-24",
"Montgomery, Alabama, USA", "uap-encounter",
"Eastern Airlines pilots Clarence Chiles and John Whitted"),
("EV-1952-09-XX-operation-mainbrace-sightings", "Operation Mainbrace UAP Sightings", "1952-09",
"North Atlantic / Scandinavia", "uap-encounter",
"NATO naval forces, RAF and USAF crews"),
("EV-1959-06-26-father-gill-papua-encounter", "Father Gill Papua Encounter", "1959-06-26",
"Boianai, Papua New Guinea", "uap-encounter",
"Reverend William Gill and 37 mission staff and locals"),
("EV-1964-04-24-lonnie-zamora-socorro", "Lonnie Zamora Socorro Landing", "1964-04-24",
"Socorro, New Mexico, USA", "uap-encounter",
"Police Sergeant Lonnie Zamora"),
("EV-1966-04-06-westall-school-encounter", "Westall School Encounter", "1966-04-06",
"Clayton South, Victoria, Australia", "uap-encounter",
"Over 200 students and teachers of Westall High School"),
("EV-1975-11-05-travis-walton-abduction", "Travis Walton Abduction", "1975-11-05",
"ApacheSitgreaves National Forest, Arizona, USA", "uap-abduction-claim",
"Travis Walton and logging crew of six"),
("EV-1977-09-XX-operacao-prato", "Operação Prato", "1977-09",
"Ilha de Colares, Pará, Brasil", "uap-encounter",
"Força Aérea Brasileira (FAB), Captain Uyrangê Hollanda and local residents"),
("EV-1980-12-27-rendlesham-forest-incident", "Rendlesham Forest Incident", "1980-12-27",
"Rendlesham Forest, Suffolk, UK (RAF Woodbridge / RAF Bentwaters)", "uap-encounter",
"USAF personnel including Lt Col Charles Halt, Sgt Jim Penniston, John Burroughs"),
("EV-1980-12-29-cash-landrum-incident", "Cash-Landrum Incident", "1980-12-29",
"Dayton, Texas, USA", "uap-related-injury",
"Betty Cash, Vickie Landrum, Colby Landrum"),
("EV-1986-05-19-são-paulo-night-of-the-ufos", "São Paulo Noite Oficial dos OVNIs", "1986-05-19",
"Costa do Brasil / São José dos Campos, SP", "uap-encounter",
"FAB pilots flying Mirage III and F-5E intercepts, Brigadeiro Octavio Moreira Lima briefing"),
("EV-1989-11-XX-belgian-wave", "Belgian UFO Wave", "1989-11",
"Belgium (multiple sites)", "uap-encounter",
"Belgian Air Force, gendarmerie and over 13,500 civilian witnesses"),
("EV-1997-03-13-phoenix-lights", "Phoenix Lights", "1997-03-13",
"Phoenix, Arizona, USA (and southern Arizona)", "uap-encounter",
"Thousands of civilians, Governor Fife Symington (later)"),
("EV-2004-11-14-nimitz-tic-tac", "Nimitz Tic Tac Incident", "2004-11-14",
"USS Nimitz Carrier Strike Group, off San Diego coast", "uap-encounter",
"USN F/A-18F crews Cdr David Fravor, Lt Cdr Jim Slaight, Lt Cdr Chad Underwood; USS Princeton radar (Senior Chief Kevin Day)"),
("EV-2017-12-16-aatip-disclosure", "AATIP Public Disclosure", "2017-12-16",
"New York / Washington, D.C., USA", "uap-disclosure-event",
"New York Times reporting; Luis Elizondo, Harry Reid, Robert Bigelow"),
]
# Voice rules (HolmesWatson, fact-dense, no hype).
PROMPT_TEMPLATE = """You are writing a curated encyclopedic event card for an investigative UAP/UFO wiki ("The Disclosure Bureau"). Voice rules:
- HolmesWatson narrator: precise, fact-dense, no hype, no breathless language.
- Open with what happened, where, when. Then who observed it. Then what made it remarkable. Optionally, what the official record / later investigations concluded.
- 36 sentences. No editorial speculation beyond what is well-documented.
- Use the *original language for verbatim quotes*; otherwise English for the EN summary and Brazilian Portuguese (pt-br with full UTF-8 accents) for the PT-BR summary. Do NOT translate already-Portuguese proper names ("Operação Prato" stays as-is in EN too).
- Avoid the words "alegadamente"/"allegedly" unless it's genuinely contested. Be honest about uncertainty when warranted.
- Never include sentences like "Will be enriched in Phase N" or any placeholder this is the final text.
EVENT TO DOCUMENT:
- ID: {event_id}
- Canonical name: {name}
- Date: {date}
- Primary location: {location}
- Class: {cls}
- Known observers / parties: {observers}
OUTPUT (STRICT JSON, no markdown fences, no commentary):
{{
"narrative_summary": "<EN, 3-6 sentences>",
"narrative_summary_pt_br": "<PT-BR brasileiro, 3-6 sentences>"
}}"""
def utc_iso() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def call_sonnet(prompt: str, dry_run: bool = False) -> dict:
"""Spawn `claude -p` subprocess (uses CLAUDE_CODE_OAUTH_TOKEN env) and return parsed JSON."""
if dry_run:
return {"narrative_summary": "[dry-run placeholder]", "narrative_summary_pt_br": "[dry-run placeholder]"}
try:
res = subprocess.run(
["claude", "-p", "--model", "sonnet", "--output-format", "json"],
input=prompt,
capture_output=True,
text=True,
timeout=180,
check=False,
)
except subprocess.TimeoutExpired:
raise RuntimeError("claude subprocess timed out after 180s")
if res.returncode != 0:
raise RuntimeError(f"claude exit {res.returncode}: {res.stderr[:300]}")
# claude --output-format json returns wrapped envelope; extract `result`.
try:
env = json.loads(res.stdout)
except json.JSONDecodeError as e:
raise RuntimeError(f"unparseable claude stdout: {e} :: {res.stdout[:300]}")
txt = env.get("result") or env.get("response") or env.get("content") or ""
# Strip code fences if any
txt = re.sub(r"^```(?:json)?\s*|\s*```$", "", txt.strip(), flags=re.MULTILINE).strip()
# Try direct parse; on fail, extract first {...} block
try:
return json.loads(txt)
except json.JSONDecodeError:
m = re.search(r"\{[^{}]*\"narrative_summary\".*?\}", txt, flags=re.DOTALL)
if not m:
raise RuntimeError(f"no JSON object in claude output: {txt[:300]}")
return json.loads(m.group(0))
def load_yaml_body(path: Path) -> tuple[dict, str]:
raw = path.read_text(encoding="utf-8")
if not raw.startswith("---"):
return {}, raw
end = raw.find("---", 4)
fm = yaml.safe_load(raw[3:end].strip()) or {}
body = raw[end + 3:].lstrip("\n")
return fm, body
def write_yaml_body(path: Path, fm: dict, body: str) -> None:
yaml_str = yaml.dump(fm, allow_unicode=True, sort_keys=False, default_flow_style=False)
sep = "" if body.startswith("\n") else "\n"
path.write_text(f"---\n{yaml_str}---\n{sep}{body}", encoding="utf-8")
def upsert_anchor(event_id: str, name: str, date: str, location: str, cls: str, observers: str,
dry_run: bool, only: str | None) -> tuple[str, bool]:
if only and only.lower() not in (event_id.lower() + " " + name.lower()):
return ("skipped (not matched by --only)", False)
path = EVENTS_DIR / f"{event_id}.md"
existing_fm: dict = {}
existing_body: str = ""
if path.exists():
existing_fm, existing_body = load_yaml_body(path)
if existing_fm.get("summary_status") == "curated":
return ("skipped (already curated)", False)
prompt = PROMPT_TEMPLATE.format(
event_id=event_id, name=name, date=date, location=location, cls=cls, observers=observers,
)
print(f" → calling sonnet for {event_id} ...", flush=True)
out = call_sonnet(prompt, dry_run=dry_run)
narr_en = (out.get("narrative_summary") or "").strip()
narr_pt = (out.get("narrative_summary_pt_br") or "").strip()
if not narr_en or not narr_pt:
return (f"empty output (en={len(narr_en)}, pt={len(narr_pt)})", False)
# Build/refresh frontmatter
fm = {
"schema_version": "0.1.0",
"type": "entity",
"entity_class": "event",
"event_id": event_id,
"canonical_name": name,
"aliases": existing_fm.get("aliases") or [name],
"event_class": cls,
"date_start": date,
"date_end": existing_fm.get("date_end") or date,
"date_confidence": "high",
"primary_location": location,
"observers": existing_fm.get("observers") or [],
"uap_objects": existing_fm.get("uap_objects") or [],
"documented_in": existing_fm.get("documented_in") or [],
"total_mentions": existing_fm.get("total_mentions") or 0,
"documents_count": existing_fm.get("documents_count") or 0,
"narrative_summary": narr_en,
"narrative_summary_pt_br": narr_pt,
"summary_status": "curated",
"summary_confidence": "high",
"enrichment_status": existing_fm.get("enrichment_status") or "none",
"external_sources": existing_fm.get("external_sources") or [],
"last_ingest": existing_fm.get("last_ingest") or utc_iso(),
"last_lint": utc_iso(),
"wiki_version": "0.1.0",
}
body = existing_body if existing_body.strip() else (
f"# {name}\n\n## Description (EN)\n\n{narr_en}\n\n## Descrição (PT-BR)\n\n{narr_pt}\n"
)
if dry_run:
return ("ok (dry)", True)
EVENTS_DIR.mkdir(parents=True, exist_ok=True)
write_yaml_body(path, fm, body)
return ("ok", True)
def main() -> int:
p = argparse.ArgumentParser()
p.add_argument("--only", default=None, help="filter anchor events by substring match")
p.add_argument("--dry-run", action="store_true")
args = p.parse_args()
print(f"Anchor events: {len(ANCHOR_EVENTS)}")
done = 0
for ev in ANCHOR_EVENTS:
msg, ok = upsert_anchor(*ev, dry_run=args.dry_run, only=args.only)
sign = "" if ok else "·"
print(f" {sign} {ev[0]}{msg}")
if ok:
done += 1
if not args.dry_run and done > 0:
with LOG_PATH.open("a", encoding="utf-8") as f:
f.write(
f"\n## {utc_iso()} · CURATE_ANCHOR_EVENTS\n"
f"- script: scripts/synthesize/01_anchor_events.py\n"
f"- curated: {done}/{len(ANCHOR_EVENTS)}\n"
f"- model: claude-sonnet (via CLAUDE_CODE_OAUTH_TOKEN)\n"
)
print(f"\nCurated: {done}/{len(ANCHOR_EVENTS)}")
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -32,6 +32,8 @@ interface TimelineEntry {
date_end: string | null;
primary_location?: string | null;
narrative_summary?: string | null;
summary_status: "none" | "synthesized" | "curated" | "red_teamed";
total_mentions?: number;
href: string;
}
@ -67,6 +69,9 @@ export async function GET(req: Request) {
const to = u.searchParams.get("to") ?? "";
const q = (u.searchParams.get("q") ?? "").toLowerCase().trim();
const limit = Math.min(Math.max(Number(u.searchParams.get("limit") ?? 200), 1), 500);
// Default: only show events with curated/synthesised narrative — never stubs.
// Opt-in `?include_unsynthesized=1` returns everything (admin / debug).
const includeUnsynthesized = u.searchParams.get("include_unsynthesized") === "1";
const dir = path.join(WIKI, "entities", folder);
let files: string[] = [];
@ -88,7 +93,16 @@ export async function GET(req: Request) {
if (from && sortable < dateSortable(from)) continue;
if (to && sortable > dateSortable(to)) continue;
const canonical = String(fm.canonical_name ?? f.replace(/\.md$/, ""));
const narrative = String(fm.narrative_summary ?? "");
const narrativeRaw = fm.narrative_summary;
const narrative = typeof narrativeRaw === "string" ? narrativeRaw : "";
const statusRaw = String(fm.summary_status ?? (narrative ? "synthesized" : "none"));
const summary_status = (
["none", "synthesized", "curated", "red_teamed"].includes(statusRaw)
? statusRaw
: "none"
) as TimelineEntry["summary_status"];
// Default: hide events without a real narrative.
if (!includeUnsynthesized && summary_status === "none") continue;
if (q && !canonical.toLowerCase().includes(q) && !narrative.toLowerCase().includes(q)) {
continue;
}
@ -99,7 +113,9 @@ export async function GET(req: Request) {
date_start,
date_end: (fm.date_end as string) ?? null,
primary_location: (fm.primary_location as string) ?? null,
narrative_summary: narrative.slice(0, 280) || null,
narrative_summary: narrative ? narrative.slice(0, 280) : null,
summary_status,
total_mentions: typeof fm.total_mentions === "number" ? fm.total_mentions : undefined,
href: `/e/${folder}/${f.replace(/\.md$/, "")}`,
});
} catch {

View file

@ -13,6 +13,8 @@ interface TimelineEntry {
date_end: string | null;
primary_location: string | null;
narrative_summary: string | null;
summary_status?: "none" | "synthesized" | "curated" | "red_teamed";
total_mentions?: number;
href: string;
}
@ -115,6 +117,11 @@ export function TimelineView({ initialSearch }: { initialSearch?: string }) {
<span className="text-[#c8d4e6] font-bold flex-1 truncate">
{e.canonical_name}
</span>
{e.summary_status === "curated" && (
<span className="px-1.5 py-0.5 rounded border border-[#00ff9c] text-[#00ff9c] text-[9px]" title="curado manualmente">
curado
</span>
)}
{e.primary_location && (
<span className="text-[#3fde6a] text-[10px]">{e.primary_location}</span>
)}

View file

@ -0,0 +1,67 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1897-04-17-aurora-airship-crash
canonical_name: Aurora Airship Crash
aliases:
- Aurora Airship Crash
event_class: uap-encounter
date_start: '1897-04-17'
date_end: '1897-04-17'
date_confidence: high
primary_location: Aurora, Texas, USA
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: On 17 April 1897, at the height of the American 'mystery airship'
wave, Dallas Morning News correspondent S.E. Haydon reported that an unidentified
airship struck a windmill on the property of Judge J.S. Proctor in Aurora, Texas,
scattering wreckage across several acres. Haydon's dispatch described a small humanoid
occupant recovered from the debris, characterized by a local U.S. Army signal officer
as 'not of this world,' and stated that townspeople buried the body in the Aurora
Cemetery with Christian rites. No physical wreckage was preserved or independently
catalogued beyond Haydon's account, and no corroborating eyewitnesses are identified
by name in any contemporaneous record. MUFON investigators probed the alleged grave
in the 1970s and reported anomalous metal fragments, but subsequent metallurgical
analysis found the samples consistent with common iron alloys; the grave marker
has since disappeared. The narrative shares structural features with dozens of contemporaneous
'airship crash' dispatches from the same 189697 wave—several of which were later
confirmed as journalistic fabrications—and the cumulative evidentiary record leaves
the event's authenticity unresolved.
narrative_summary_pt_br: Em 17 de abril de 1897, no auge da onda americana de 'navios
aéreos misteriosos', o correspondente S.E. Haydon publicou no Dallas Morning News
que uma aeronave não identificada colidiu com um moinho de vento na propriedade
do juiz J.S. Proctor em Aurora, Texas, espalhando destroços por vários acres. O
despacho de Haydon descrevia um pequeno ocupante humanoide recuperado dos escombros,
caracterizado por um oficial do Exército norte-americano como 'not of this world',
e afirmava que os moradores enterraram o corpo no Cemitério de Aurora com ritos
cristãos. Nenhum destroço físico foi preservado ou catalogado de forma independente
além do relato de Haydon, e nenhuma testemunha ocular é identificada pelo nome em
qualquer registro contemporâneo. Investigadores da MUFON examinaram o suposto túmulo
nos anos 1970 e relataram fragmentos metálicos anômalos, mas análises metalúrgicas
posteriores indicaram amostras consistentes com ligas de ferro comuns; a lápide
do túmulo desapareceu desde então. A narrativa compartilha características estruturais
com dezenas de despachos contemporâneos de 'queda de navio aéreo' da onda de 189697
— vários deles posteriormente confirmados como fabricações jornalísticas — e o conjunto
do registro investigativo deixa a autenticidade do evento sem resolução.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:30:25Z'
last_lint: '2026-05-18T03:30:25Z'
wiki_version: 0.1.0
---
# Aurora Airship Crash
## Description (EN)
On 17 April 1897, at the height of the American 'mystery airship' wave, Dallas Morning News correspondent S.E. Haydon reported that an unidentified airship struck a windmill on the property of Judge J.S. Proctor in Aurora, Texas, scattering wreckage across several acres. Haydon's dispatch described a small humanoid occupant recovered from the debris, characterized by a local U.S. Army signal officer as 'not of this world,' and stated that townspeople buried the body in the Aurora Cemetery with Christian rites. No physical wreckage was preserved or independently catalogued beyond Haydon's account, and no corroborating eyewitnesses are identified by name in any contemporaneous record. MUFON investigators probed the alleged grave in the 1970s and reported anomalous metal fragments, but subsequent metallurgical analysis found the samples consistent with common iron alloys; the grave marker has since disappeared. The narrative shares structural features with dozens of contemporaneous 'airship crash' dispatches from the same 189697 wave—several of which were later confirmed as journalistic fabrications—and the cumulative evidentiary record leaves the event's authenticity unresolved.
## Descrição (PT-BR)
Em 17 de abril de 1897, no auge da onda americana de 'navios aéreos misteriosos', o correspondente S.E. Haydon publicou no Dallas Morning News que uma aeronave não identificada colidiu com um moinho de vento na propriedade do juiz J.S. Proctor em Aurora, Texas, espalhando destroços por vários acres. O despacho de Haydon descrevia um pequeno ocupante humanoide recuperado dos escombros, caracterizado por um oficial do Exército norte-americano como 'not of this world', e afirmava que os moradores enterraram o corpo no Cemitério de Aurora com ritos cristãos. Nenhum destroço físico foi preservado ou catalogado de forma independente além do relato de Haydon, e nenhuma testemunha ocular é identificada pelo nome em qualquer registro contemporâneo. Investigadores da MUFON examinaram o suposto túmulo nos anos 1970 e relataram fragmentos metálicos anômalos, mas análises metalúrgicas posteriores indicaram amostras consistentes com ligas de ferro comuns; a lápide do túmulo desapareceu desde então. A narrativa compartilha características estruturais com dezenas de despachos contemporâneos de 'queda de navio aéreo' da onda de 189697 — vários deles posteriormente confirmados como fabricações jornalísticas — e o conjunto do registro investigativo deixa a autenticidade do evento sem resolução.

View file

@ -0,0 +1,64 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1944-XX-XX-foo-fighters-european-theater
canonical_name: Foo Fighters (European Theater)
aliases:
- Foo Fighters (European Theater)
event_class: uap-encounter
date_start: '1944'
date_end: '1944'
date_confidence: high
primary_location: Western Front, Europe
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: From late 1944 onward, aircrews of the 415th Night Fighter Squadron
and other Allied units flying night sorties over the Rhine Valley began reporting
clusters of luminous orange-red spheres that paced their aircraft with precision
before breaking away at high speed. The phenomenon was first formally recorded on
27 November 1944 by Lieutenant Edward Schlueter near Strasbourg; the objects were
consistently non-hostile — they never opened fire, produced no exhaust trail, and
any instrument interference was irregular and unconfirmed. Allied intelligence initially
hypothesized German secret weapons or psychological-warfare drones, but post-war
debriefings of Luftwaffe pilots established that German aircrews had filed identical
sightings on the same nights, eliminating all Allied-side explanations. A U.S. Army
Air Forces inquiry closed without a conclusive finding; the phenomena remain unattributed.
The label 'foo fighter' was coined by the squadron's crews, adapted from the Smokey
Stover comic-strip expression 'where there's foo, there's fire.'
narrative_summary_pt_br: 'A partir do final de 1944, tripulações da 415ª Esquadrilha
de Caça Noturno e de outras unidades aliadas em missões noturnas sobre o Vale do
Reno passaram a relatar grupos de esferas luminosas laranja-avermelhadas que acompanhavam
suas aeronaves com precisão antes de se afastarem em alta velocidade. O fenômeno
foi registrado formalmente pela primeira vez em 27 de novembro de 1944 pelo tenente
Edward Schlueter nas proximidades de Estrasburgo; os objetos eram consistentemente
não hostis — nunca abriram fogo, não deixavam rastro de exaustão e qualquer interferência
nos instrumentos era irregular e não confirmada. A inteligência aliada inicialmente
levantou a hipótese de armas secretas alemãs ou drones de guerra psicológica, mas
os interrogatórios pós-guerra de pilotos da Luftwaffe estabeleceram que tripulações
alemãs haviam registrado avistamentos idênticos nas mesmas noites, eliminando todas
as explicações do lado aliado. Uma investigação da Força Aérea do Exército dos EUA
foi encerrada sem conclusão definitiva; o fenômeno permanece sem atribuição. O termo
''foo fighter'' foi cunhado pelas tripulações da esquadrilha, adaptado da expressão
da tirinha Smokey Stover: ''where there''s foo, there''s fire.'''
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:31:08Z'
last_lint: '2026-05-18T03:31:08Z'
wiki_version: 0.1.0
---
# Foo Fighters (European Theater)
## Description (EN)
From late 1944 onward, aircrews of the 415th Night Fighter Squadron and other Allied units flying night sorties over the Rhine Valley began reporting clusters of luminous orange-red spheres that paced their aircraft with precision before breaking away at high speed. The phenomenon was first formally recorded on 27 November 1944 by Lieutenant Edward Schlueter near Strasbourg; the objects were consistently non-hostile — they never opened fire, produced no exhaust trail, and any instrument interference was irregular and unconfirmed. Allied intelligence initially hypothesized German secret weapons or psychological-warfare drones, but post-war debriefings of Luftwaffe pilots established that German aircrews had filed identical sightings on the same nights, eliminating all Allied-side explanations. A U.S. Army Air Forces inquiry closed without a conclusive finding; the phenomena remain unattributed. The label 'foo fighter' was coined by the squadron's crews, adapted from the Smokey Stover comic-strip expression 'where there's foo, there's fire.'
## Descrição (PT-BR)
A partir do final de 1944, tripulações da 415ª Esquadrilha de Caça Noturno e de outras unidades aliadas em missões noturnas sobre o Vale do Reno passaram a relatar grupos de esferas luminosas laranja-avermelhadas que acompanhavam suas aeronaves com precisão antes de se afastarem em alta velocidade. O fenômeno foi registrado formalmente pela primeira vez em 27 de novembro de 1944 pelo tenente Edward Schlueter nas proximidades de Estrasburgo; os objetos eram consistentemente não hostis — nunca abriram fogo, não deixavam rastro de exaustão e qualquer interferência nos instrumentos era irregular e não confirmada. A inteligência aliada inicialmente levantou a hipótese de armas secretas alemãs ou drones de guerra psicológica, mas os interrogatórios pós-guerra de pilotos da Luftwaffe estabeleceram que tripulações alemãs haviam registrado avistamentos idênticos nas mesmas noites, eliminando todas as explicações do lado aliado. Uma investigação da Força Aérea do Exército dos EUA foi encerrada sem conclusão definitiva; o fenômeno permanece sem atribuição. O termo 'foo fighter' foi cunhado pelas tripulações da esquadrilha, adaptado da expressão da tirinha Smokey Stover: 'where there's foo, there's fire.'

View file

@ -0,0 +1,65 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1947-06-21-maury-island-incident
canonical_name: Maury Island Incident
aliases:
- Maury Island Incident
event_class: uap-encounter
date_start: '1947-06-21'
date_end: '1947-06-21'
date_confidence: high
primary_location: Puget Sound, Washington, USA
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: On 21 June 1947 — three days before Kenneth Arnold's Cascade Mountains
sighting that gave rise to the term 'flying saucers' — Harold Dahl reported observing
six large donut-shaped objects hovering over Puget Sound near Maury Island, Washington,
while conducting salvage work with his son and a crewman. He claimed one of the
objects ejected a shower of metallic slag and a lighter material that damaged his
boat, injured his son, and killed his dog. Fred Crisman, Dahl's supervisor, subsequently
handled samples of the recovered material and reported the incident to pulp editor
Ray Palmer, who dispatched investigator Kenneth Arnold to the site. Army Air Force
intelligence officers Captain William Davidson and Lieutenant Frank Brown flew to
Tacoma to collect the samples; both died on 1 August 1947 when their B-25 crashed
near Kelso, Washington, on the return flight. Dahl later told investigators the
story was a fabrication; the FBI and Army Air Force concluded the incident was a
hoax, and the recovered material was identified as slag consistent with industrial
waste.
narrative_summary_pt_br: Em 21 de junho de 1947 — três dias antes do avistamento de
Kenneth Arnold nas Cascades que deu origem ao termo 'discos voadores' — Harold Dahl
relatou observar seis grandes objetos em formato de rosca pairando sobre o Puget
Sound próximo à Ilha Maury, Washington, enquanto realizava trabalhos de salvamento
com seu filho e um tripulante. Segundo seu relato, um dos objetos ejetou uma chuva
de escória metálica e material mais leve que danificou seu barco, feriu seu filho
e matou seu cão. Fred Crisman, supervisor de Dahl, obteve amostras do material recuperado
e reportou o incidente ao editor Ray Palmer, que enviou o investigador Kenneth Arnold
ao local. Os oficiais de inteligência da Força Aérea do Exército, capitão William
Davidson e tenente Frank Brown, voaram até Tacoma para recolher as amostras; ambos
morreram em 1.º de agosto de 1947 quando seu B-25 caiu próximo a Kelso, Washington,
no voo de retorno. Dahl posteriormente declarou aos investigadores que a história
era uma fabricação; o FBI e a Força Aérea do Exército concluíram que o incidente
foi uma farsa, e o material recuperado foi identificado como escória compatível
com resíduos industriais.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:31:42Z'
last_lint: '2026-05-18T03:31:42Z'
wiki_version: 0.1.0
---
# Maury Island Incident
## Description (EN)
On 21 June 1947 — three days before Kenneth Arnold's Cascade Mountains sighting that gave rise to the term 'flying saucers' — Harold Dahl reported observing six large donut-shaped objects hovering over Puget Sound near Maury Island, Washington, while conducting salvage work with his son and a crewman. He claimed one of the objects ejected a shower of metallic slag and a lighter material that damaged his boat, injured his son, and killed his dog. Fred Crisman, Dahl's supervisor, subsequently handled samples of the recovered material and reported the incident to pulp editor Ray Palmer, who dispatched investigator Kenneth Arnold to the site. Army Air Force intelligence officers Captain William Davidson and Lieutenant Frank Brown flew to Tacoma to collect the samples; both died on 1 August 1947 when their B-25 crashed near Kelso, Washington, on the return flight. Dahl later told investigators the story was a fabrication; the FBI and Army Air Force concluded the incident was a hoax, and the recovered material was identified as slag consistent with industrial waste.
## Descrição (PT-BR)
Em 21 de junho de 1947 — três dias antes do avistamento de Kenneth Arnold nas Cascades que deu origem ao termo 'discos voadores' — Harold Dahl relatou observar seis grandes objetos em formato de rosca pairando sobre o Puget Sound próximo à Ilha Maury, Washington, enquanto realizava trabalhos de salvamento com seu filho e um tripulante. Segundo seu relato, um dos objetos ejetou uma chuva de escória metálica e material mais leve que danificou seu barco, feriu seu filho e matou seu cão. Fred Crisman, supervisor de Dahl, obteve amostras do material recuperado e reportou o incidente ao editor Ray Palmer, que enviou o investigador Kenneth Arnold ao local. Os oficiais de inteligência da Força Aérea do Exército, capitão William Davidson e tenente Frank Brown, voaram até Tacoma para recolher as amostras; ambos morreram em 1.º de agosto de 1947 quando seu B-25 caiu próximo a Kelso, Washington, no voo de retorno. Dahl posteriormente declarou aos investigadores que a história era uma fabricação; o FBI e a Força Aérea do Exército concluíram que o incidente foi uma farsa, e o material recuperado foi identificado como escória compatível com resíduos industriais.

View file

@ -0,0 +1,34 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1947-06-21-maury-island-sighting
canonical_name: Maury Island Sighting
aliases:
- Maury Island Sighting
event_class: uap-encounter
date_start: '1947-06-21'
date_end: '1947-06-21'
date_confidence: low
primary_location: null
observers: []
uap_objects: []
documented_in: []
total_mentions: 1
documents_count: 1
narrative_summary: null
narrative_summary_pt_br: null
enrichment_status: none
external_sources: []
last_ingest: '2026-05-15T18:57:03Z'
last_lint: '2026-05-18T03:21:56Z'
wiki_version: 0.1.0
summary_status: none
summary_confidence: null
---
# Maury Island Sighting
## Description (EN)
## Descrição (PT-BR)

View file

@ -0,0 +1,62 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1947-06-24-kenneth-arnold-mount-rainier
canonical_name: Kenneth Arnold Mount Rainier Sighting
aliases:
- Kenneth Arnold Mount Rainier Sighting
event_class: uap-encounter
date_start: '1947-06-24'
date_end: '1947-06-24'
date_confidence: high
primary_location: Mount Rainier, Washington, USA
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: On June 24, 1947, civilian pilot Kenneth Arnold was flying his
CallAir A-2 near Mount Rainier, Washington, searching for a downed Marine transport
aircraft, when he observed nine bright, crescent-shaped objects traveling in a loose
chain formation at speeds he timed—against known landmarks across roughly 50 miles—at
in excess of 1,200 miles per hour, far beyond any aircraft of the era. Arnold described
their motion as like a saucer skipping across water; newspaper reporters rendered
this as "flying saucers," inadvertently coining the term that would define the emerging
field of UFO investigation. He reported the incident to an FBI office in Pendleton,
Oregon, and was subsequently interviewed by Army Air Forces investigators, who were
unable to identify the objects. The case is recognized as the catalytic event of
the modern UFO era and established the template for both civilian and military investigation
of aerial anomalies in the United States.
narrative_summary_pt_br: Em 24 de junho de 1947, o piloto civil Kenneth Arnold sobrevoava
o Monte Rainier, em Washington, à procura de uma aeronave militar desaparecida,
quando avistou nove objetos brilhantes de formato crescente deslocando-se em formação
encadeada. Arnold cronometrou sua passagem entre pontos de referência ao longo de
aproximadamente 80 km e estimou a velocidade superior a 1.900 km/h — muito além
de qualquer aeronave conhecida da época. Ele descreveu o movimento dos objetos como
semelhante ao de um pires deslizando sobre a água; jornalistas reinterpretaram a
expressão como "pratos voadores" (flying saucers), cunhando o termo que definiria
o fenômeno nas décadas seguintes. Arnold relatou o avistamento ao escritório do
FBI em Pendleton, Oregon, e foi posteriormente entrevistado por investigadores das
Forças Aéreas do Exército, que não conseguiram identificar os objetos. O episódio
é reconhecido como o evento catalisador da era moderna dos OVNIs e estabeleceu o
precedente para investigações civis e militares de anomalias aéreas nos Estados
Unidos.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:29:21Z'
last_lint: '2026-05-18T03:29:21Z'
wiki_version: 0.1.0
---
# Kenneth Arnold Mount Rainier Sighting
## Description (EN)
On June 24, 1947, civilian pilot Kenneth Arnold was flying his CallAir A-2 near Mount Rainier, Washington, searching for a downed Marine transport aircraft, when he observed nine bright, crescent-shaped objects traveling in a loose chain formation at speeds he timed—against known landmarks across roughly 50 miles—at in excess of 1,200 miles per hour, far beyond any aircraft of the era. Arnold described their motion as like a saucer skipping across water; newspaper reporters rendered this as "flying saucers," inadvertently coining the term that would define the emerging field of UFO investigation. He reported the incident to an FBI office in Pendleton, Oregon, and was subsequently interviewed by Army Air Forces investigators, who were unable to identify the objects. The case is recognized as the catalytic event of the modern UFO era and established the template for both civilian and military investigation of aerial anomalies in the United States.
## Descrição (PT-BR)
Em 24 de junho de 1947, o piloto civil Kenneth Arnold sobrevoava o Monte Rainier, em Washington, à procura de uma aeronave militar desaparecida, quando avistou nove objetos brilhantes de formato crescente deslocando-se em formação encadeada. Arnold cronometrou sua passagem entre pontos de referência ao longo de aproximadamente 80 km e estimou a velocidade superior a 1.900 km/h — muito além de qualquer aeronave conhecida da época. Ele descreveu o movimento dos objetos como semelhante ao de um pires deslizando sobre a água; jornalistas reinterpretaram a expressão como "pratos voadores" (flying saucers), cunhando o termo que definiria o fenômeno nas décadas seguintes. Arnold relatou o avistamento ao escritório do FBI em Pendleton, Oregon, e foi posteriormente entrevistado por investigadores das Forças Aéreas do Exército, que não conseguiram identificar os objetos. O episódio é reconhecido como o evento catalisador da era moderna dos OVNIs e estabeleceu o precedente para investigações civis e militares de anomalias aéreas nos Estados Unidos.

View file

@ -0,0 +1,39 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1947-06-24-kenneth-arnold-sighting
canonical_name: Kenneth Arnold sighting
aliases:
- Kenneth Arnold Sighting
- Kenneth Arnold sighting
event_class: uap-encounter
date_start: '1947-06-24'
date_end: '1947-06-24'
date_confidence: low
primary_location: null
observers: []
uap_objects: []
documented_in: []
total_mentions: 1
documents_count: 1
narrative_summary: null
narrative_summary_pt_br: null
enrichment_status: none
external_sources: []
last_ingest: '2026-05-15T18:57:04Z'
last_lint: '2026-05-18T03:21:56Z'
wiki_version: 0.1.0
summary_status: none
summary_confidence: null
---
# Kenneth Arnold sighting
## Description (EN)
_Low-signal entity — referenced **1 time(s)** across **1 document(s)**. No external enrichment performed (criteria: ≥3 mentions). Use the page references below for raw context._
## Descrição (PT-BR)
_Entidade de baixo sinal — referenciada **1 vez(es)** em **1 documento(s)**. Sem enriquecimento externo (critério: ≥3 menções). Use as referências de páginas abaixo para contexto bruto._

View file

@ -0,0 +1,39 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1947-06-24-kenneth-arnold-ufo-sighting
canonical_name: Kenneth Arnold UFO sighting
aliases:
- Kenneth Arnold UFO Sighting
- Kenneth Arnold UFO sighting
event_class: uap-encounter
date_start: '1947-06-24'
date_end: '1947-06-24'
date_confidence: low
primary_location: null
observers: []
uap_objects: []
documented_in: []
total_mentions: 1
documents_count: 1
narrative_summary: null
narrative_summary_pt_br: null
enrichment_status: none
external_sources: []
last_ingest: '2026-05-14T06:59:02Z'
last_lint: '2026-05-18T03:21:56Z'
wiki_version: 0.1.0
summary_status: none
summary_confidence: null
---
# Kenneth Arnold UFO sighting
## Description (EN)
_Low-signal entity — referenced **1 time(s)** across **1 document(s)**. No external enrichment performed (criteria: ≥3 mentions). Use the page references below for raw context._
## Descrição (PT-BR)
_Entidade de baixo sinal — referenciada **1 vez(es)** em **1 documento(s)**. Sem enriquecimento externo (critério: ≥3 menções). Use as referências de páginas abaixo para contexto bruto._

View file

@ -0,0 +1,34 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1947-07-08-roswell-crash-announcement
canonical_name: Roswell Crash Announcement
aliases:
- Roswell Crash Announcement
event_class: uap-encounter
date_start: '1947-07-08'
date_end: '1947-07-08'
date_confidence: low
primary_location: null
observers: []
uap_objects: []
documented_in: []
total_mentions: 1
documents_count: 1
narrative_summary: null
narrative_summary_pt_br: null
enrichment_status: none
external_sources: []
last_ingest: '2026-05-15T18:57:02Z'
last_lint: '2026-05-18T03:21:57Z'
wiki_version: 0.1.0
summary_status: none
summary_confidence: null
---
# Roswell Crash Announcement
## Description (EN)
## Descrição (PT-BR)

View file

@ -0,0 +1,34 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1947-07-08-roswell-flying-disc-recovery
canonical_name: Roswell Flying Disc Recovery
aliases:
- Roswell Flying Disc Recovery
event_class: uap-encounter
date_start: '1947-07-08'
date_end: '1947-07-08'
date_confidence: low
primary_location: null
observers: []
uap_objects: []
documented_in: []
total_mentions: 1
documents_count: 1
narrative_summary: null
narrative_summary_pt_br: null
enrichment_status: none
external_sources: []
last_ingest: '2026-05-15T18:57:02Z'
last_lint: '2026-05-18T03:21:57Z'
wiki_version: 0.1.0
summary_status: none
summary_confidence: null
---
# Roswell Flying Disc Recovery
## Description (EN)
## Descrição (PT-BR)

View file

@ -0,0 +1,64 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1947-07-08-roswell-incident
canonical_name: Roswell Incident
aliases:
- Roswell Incident
event_class: uap-encounter
date_start: '1947-07-08'
date_end: '1947-07-08'
date_confidence: high
primary_location: Roswell, New Mexico, USA
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: On 8 July 1947, the USAAF 509th Bombardment Group at Roswell Army
Air Field issued an unprecedented press release announcing the recovery of a 'flying
disc' from a ranch northwest of Roswell, New Mexico — and retracted it within hours,
substituting a weather balloon explanation. The debris had been discovered days
earlier by rancher William Brazel on the Foster Ranch near Corona; intelligence
officer Major Jesse Marcel was dispatched to retrieve it and later described the
material as unlike anything he had encountered in his career. The sequence of contradictory
official statements, combined with Marcel's persistent testimony, made Roswell the
most extensively documented UFO controversy of the twentieth century. A 1994 USAF
report concluded the wreckage originated from Project Mogul — a classified program
using high-altitude balloon trains to monitor Soviet nuclear tests — a finding that
explained the initial secrecy but left unresolved disputes over material descriptions
and the existence of alleged secondary recovery sites.
narrative_summary_pt_br: Em 8 de julho de 1947, o 509º Grupo de Bombardeio da USAAF,
sediado na Base Aérea de Roswell, emitiu um comunicado sem precedentes anunciando
a recuperação de um 'disco voador' caído numa fazenda a noroeste de Roswell, Novo
México — e o retratou horas depois, substituindo a versão oficial pela de um balão
meteorológico. Os destroços haviam sido encontrados dias antes pelo fazendeiro William
Brazel no Rancho Foster, próximo a Corona; o major Jesse Marcel, oficial de inteligência,
foi enviado para recolhê-los e descreveu posteriormente o material como diferente
de qualquer coisa que já havia visto em sua carreira. A sequência de declarações
oficiais contraditórias, combinada com o testemunho persistente de Marcel, tornou
Roswell a controvérsia OVNI mais amplamente documentada do século XX. Um relatório
da USAF de 1994 concluiu que os destroços eram provenientes do Projeto Mogul — um
programa classificado que utilizava cadeias de balões de grande altitude para monitorar
testes nucleares soviéticos —, conclusão que explicou o sigilo inicial, mas deixou
disputas residuais sobre as descrições do material e a existência de supostos locais
secundários de recuperação.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:32:11Z'
last_lint: '2026-05-18T03:32:11Z'
wiki_version: 0.1.0
---
# Roswell Incident
## Description (EN)
On 8 July 1947, the USAAF 509th Bombardment Group at Roswell Army Air Field issued an unprecedented press release announcing the recovery of a 'flying disc' from a ranch northwest of Roswell, New Mexico — and retracted it within hours, substituting a weather balloon explanation. The debris had been discovered days earlier by rancher William Brazel on the Foster Ranch near Corona; intelligence officer Major Jesse Marcel was dispatched to retrieve it and later described the material as unlike anything he had encountered in his career. The sequence of contradictory official statements, combined with Marcel's persistent testimony, made Roswell the most extensively documented UFO controversy of the twentieth century. A 1994 USAF report concluded the wreckage originated from Project Mogul — a classified program using high-altitude balloon trains to monitor Soviet nuclear tests — a finding that explained the initial secrecy but left unresolved disputes over material descriptions and the existence of alleged secondary recovery sites.
## Descrição (PT-BR)
Em 8 de julho de 1947, o 509º Grupo de Bombardeio da USAAF, sediado na Base Aérea de Roswell, emitiu um comunicado sem precedentes anunciando a recuperação de um 'disco voador' caído numa fazenda a noroeste de Roswell, Novo México — e o retratou horas depois, substituindo a versão oficial pela de um balão meteorológico. Os destroços haviam sido encontrados dias antes pelo fazendeiro William Brazel no Rancho Foster, próximo a Corona; o major Jesse Marcel, oficial de inteligência, foi enviado para recolhê-los e descreveu posteriormente o material como diferente de qualquer coisa que já havia visto em sua carreira. A sequência de declarações oficiais contraditórias, combinada com o testemunho persistente de Marcel, tornou Roswell a controvérsia OVNI mais amplamente documentada do século XX. Um relatório da USAF de 1994 concluiu que os destroços eram provenientes do Projeto Mogul — um programa classificado que utilizava cadeias de balões de grande altitude para monitorar testes nucleares soviéticos —, conclusão que explicou o sigilo inicial, mas deixou disputas residuais sobre as descrições do material e a existência de supostos locais secundários de recuperação.

View file

@ -0,0 +1,61 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1948-01-07-mantell-crash
canonical_name: Mantell UFO Incident
aliases:
- Mantell UFO Incident
event_class: uap-related-fatality
date_start: '1948-01-07'
date_end: '1948-01-07'
date_confidence: high
primary_location: Franklin, Kentucky, USA
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: On 7 January 1948, Captain Thomas F. Mantell Jr. of the Kentucky
Air National Guard died when his P-51D Mustang disintegrated near Franklin, Kentucky,
while in pursuit of an unidentified aerial object first reported circling above
Godman Army Air Field. Mantell, one of four pilots vectored to investigate, climbed
beyond 25,000 feet without supplemental oxygen, radioing that the object appeared
'metallic' and 'of tremendous size' before all contact ceased. He lost consciousness
from hypoxia; his aircraft broke apart and wreckage fell near Franklin at approximately
15:18 local time. Project Sign, the Air Force's first formal UAP investigation body,
reviewed the case—its early attribution to the planet Venus was later superseded
by evidence pointing to a classified Navy Skyhook high-altitude research balloon.
The incident stands as the first documented fatality of a military aviator in pursuit
of an unidentified aerial phenomenon.
narrative_summary_pt_br: Em 7 de janeiro de 1948, o Capitão Thomas F. Mantell Jr.,
da Guarda Aérea Nacional de Kentucky, morreu quando seu P-51D Mustang se desintegrou
próximo a Franklin, Kentucky, enquanto perseguia um objeto aéreo não identificado
avistado sobre a Base do Exército de Godman. Mantell, um dos quatro pilotos enviados
para investigar, ascendeu além de 7.600 metros sem oxigênio suplementar, transmitindo
pelo rádio que o objeto parecia 'metálico' e 'de tamanho enorme' antes de todo contato
ser interrompido. Ele perdeu a consciência por hipóxia; sua aeronave se fragmentou
e os destroços caíram próximos a Franklin por volta das 15h18, horário local. O
Project Sign, primeiro órgão formal da Força Aérea para investigação de OVNIs, analisou
o caso — a atribuição inicial ao planeta Vênus foi posteriormente substituída por
evidências apontando para um balão estratosférico secreto da Marinha (Skyhook).
O incidente é registrado como a primeira morte documentada de um aviador militar
em perseguição a um fenômeno aéreo não identificado.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:32:47Z'
last_lint: '2026-05-18T03:32:47Z'
wiki_version: 0.1.0
---
# Mantell UFO Incident
## Description (EN)
On 7 January 1948, Captain Thomas F. Mantell Jr. of the Kentucky Air National Guard died when his P-51D Mustang disintegrated near Franklin, Kentucky, while in pursuit of an unidentified aerial object first reported circling above Godman Army Air Field. Mantell, one of four pilots vectored to investigate, climbed beyond 25,000 feet without supplemental oxygen, radioing that the object appeared 'metallic' and 'of tremendous size' before all contact ceased. He lost consciousness from hypoxia; his aircraft broke apart and wreckage fell near Franklin at approximately 15:18 local time. Project Sign, the Air Force's first formal UAP investigation body, reviewed the case—its early attribution to the planet Venus was later superseded by evidence pointing to a classified Navy Skyhook high-altitude research balloon. The incident stands as the first documented fatality of a military aviator in pursuit of an unidentified aerial phenomenon.
## Descrição (PT-BR)
Em 7 de janeiro de 1948, o Capitão Thomas F. Mantell Jr., da Guarda Aérea Nacional de Kentucky, morreu quando seu P-51D Mustang se desintegrou próximo a Franklin, Kentucky, enquanto perseguia um objeto aéreo não identificado avistado sobre a Base do Exército de Godman. Mantell, um dos quatro pilotos enviados para investigar, ascendeu além de 7.600 metros sem oxigênio suplementar, transmitindo pelo rádio que o objeto parecia 'metálico' e 'de tamanho enorme' antes de todo contato ser interrompido. Ele perdeu a consciência por hipóxia; sua aeronave se fragmentou e os destroços caíram próximos a Franklin por volta das 15h18, horário local. O Project Sign, primeiro órgão formal da Força Aérea para investigação de OVNIs, analisou o caso — a atribuição inicial ao planeta Vênus foi posteriormente substituída por evidências apontando para um balão estratosférico secreto da Marinha (Skyhook). O incidente é registrado como a primeira morte documentada de um aviador militar em perseguição a um fenômeno aéreo não identificado.

View file

@ -0,0 +1,34 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1948-01-07-mantell-ufo-incident
canonical_name: Mantell UFO Incident
aliases:
- Mantell UFO Incident
event_class: uap-encounter
date_start: '1948-01-07'
date_end: '1948-01-07'
date_confidence: low
primary_location: null
observers: []
uap_objects: []
documented_in: []
total_mentions: 4
documents_count: 4
narrative_summary: null
narrative_summary_pt_br: null
enrichment_status: none
external_sources: []
last_ingest: '2026-05-15T18:57:02Z'
last_lint: '2026-05-18T03:22:50Z'
wiki_version: 0.1.0
summary_status: none
summary_confidence: null
---
# Mantell UFO Incident
## Description (EN)
## Descrição (PT-BR)

View file

@ -0,0 +1,65 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1948-07-24-chiles-whitted-encounter
canonical_name: Chiles-Whitted UFO Encounter
aliases:
- Chiles-Whitted Encounter
event_class: uap-encounter
date_start: '1948-07-24'
date_end: '1948-07-24'
date_confidence: high
primary_location: Montgomery, Alabama, USA
observers: []
uap_objects: []
documented_in: []
total_mentions: 1
documents_count: 1
narrative_summary: At approximately 02:45 on 24 July 1948, Eastern Airlines Captain
Clarence S. Chiles and First Officer John B. Whitted observed an unidentified object
pass within an estimated 700 feet of their DC-3 at 5,000 feet altitude near Montgomery,
Alabama, the encounter lasting roughly ten seconds as the object closed head-on
at high speed and veered off to the left. Both pilots independently described the
craft as wingless and torpedo-shaped, approximately 100 feet long, with two rows
of bright rectangular windows emitting an intense blue-white glow and a deep-blue
exhaust flame trailing from its rear. A single passenger aboard corroborated the
sighting, reporting a bright streak of light through the cabin window. Project Sign
investigators at Wright-Patterson Air Force Base considered the Chiles-Whitted report
among the strongest in their files, and an internal classified assessment — the
'Estimate of the Situation' — reportedly cited it in support of an extraterrestrial
hypothesis, only to be rejected by Chief of Staff General Hoyt Vandenberg; the document
was subsequently ordered destroyed. The Air Force's final official explanation attributed
the object to a large bolide meteor, a conclusion both pilots disputed publicly
for the remainder of their careers.
narrative_summary_pt_br: Por volta das 02h45 do dia 24 de julho de 1948, o capitão
Clarence S. Chiles e o co-piloto John B. Whitted, da Eastern Airlines, avistaram
um objeto não identificado que passou a aproximadamente 700 pés de seu DC-3 voando
a 1.500 metros de altitude próximo a Montgomery, Alabama — o contato durou cerca
de dez segundos enquanto o objeto se aproximava de frente em alta velocidade e desviou
para a esquerda. Ambos os pilotos descreveram de forma independente uma nave sem
asas, em formato de torpedo, com cerca de 30 metros de comprimento, duas fileiras
de janelas retangulares que emitiam luz azul-branca intensa e uma chama de exaustão
azul-profunda na cauda. Um único passageiro a bordo corroborou o avistamento, relatando
ter visto um clarão de luz pela janela da cabine. Os investigadores do Projeto Sign
na base de Wright-Patterson consideraram o caso um dos mais sólidos de seus arquivos;
uma avaliação interna classificada — o 'Estimate of the Situation' — teria citado
o episódio em apoio à hipótese de origem extraterrestre, mas foi rejeitada pelo
General Hoyt Vandenberg, chefe do Estado-Maior, e o documento foi subsequentemente
mandado destruir. A conclusão oficial da Força Aérea atribuiu o objeto a um grande
bólido meteórico, diagnóstico que ambos os pilotos contestaram publicamente pelo
resto de suas carreiras.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-15T18:57:03Z'
last_lint: '2026-05-18T03:33:26Z'
wiki_version: 0.1.0
---
# Chiles-Whitted Encounter
## Description (EN)
## Descrição (PT-BR)

View file

@ -0,0 +1,34 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1948-07-24-chiles-whitted-ufo-encounter
canonical_name: Chiles-Whitted UFO Encounter
aliases:
- Chiles-Whitted UFO Encounter
event_class: uap-encounter
date_start: '1948-07-24'
date_end: '1948-07-24'
date_confidence: low
primary_location: null
observers: []
uap_objects: []
documented_in: []
total_mentions: 1
documents_count: 1
narrative_summary: null
narrative_summary_pt_br: null
enrichment_status: none
external_sources: []
last_ingest: '2026-05-15T18:57:02Z'
last_lint: '2026-05-18T03:22:51Z'
wiki_version: 0.1.0
summary_status: none
summary_confidence: null
---
# Chiles-Whitted UFO Encounter
## Description (EN)
## Descrição (PT-BR)

View file

@ -0,0 +1,67 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1952-09-XX-operation-mainbrace-sightings
canonical_name: Operation Mainbrace UAP Sightings
aliases:
- Operation Mainbrace UAP Sightings
event_class: uap-encounter
date_start: 1952-09
date_end: 1952-09
date_confidence: high
primary_location: North Atlantic / Scandinavia
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: During NATO Exercise Mainbrace in September 1952—the alliance's
largest naval exercise to date, spanning the North Atlantic from the Norwegian coast
to Scotland—personnel from multiple nations reported UAP encounters on at least
six separate occasions. Observers aboard the USS Franklin D. Roosevelt aircraft
carrier sighted a silver sphere on 13 September; four days later, the crew of the
Danish destroyer Willemoes tracked a triangular object estimated to be traveling
at 900 mph. On 19 September, RAF Flight Lieutenant John Kilburn and four other aircrew
at Topcliffe airfield watched a silver disc descend, halt, and then accelerate sharply—behavior
irreconcilable with any known aircraft of the period. A photograph attributed to
Navy journalist Wallace Litwin, taken from the Roosevelt, circulated in official
channels and was later examined by the Condon Committee without a definitive identification.
The density of credible, multi-witness, multi-nation reports during a single two-week
exercise made Mainbrace one of the most thoroughly documented early Cold War UAP
clusters and was cited in subsequent official inquiries by both the U.S. and British
governments.
narrative_summary_pt_br: Durante o Exercício Mainbrace da OTAN em setembro de 1952
— a maior manobra naval da aliança até então, realizada no Atlântico Norte da costa
norueguesa até a Escócia — pessoal de múltiplas nações relatou encontros com UAP
em pelo menos seis ocasiões distintas. Observadores a bordo do porta-aviões USS
Franklin D. Roosevelt avistaram uma esfera prateada em 13 de setembro; quatro dias
depois, a tripulação do contratorpedeiro dinamarquês Willemoes rastreou um objeto
triangular estimado em 900 milhas por hora. Em 19 de setembro, o tenente John Kilburn
da RAF e outros quatro membros de tripulação na base de Topcliffe assistiram a um
disco prateado descer, parar e então acelerar bruscamente — comportamento incompatível
com qualquer aeronave conhecida da época. Uma fotografia atribuída ao jornalista
naval Wallace Litwin, tirada a bordo do Roosevelt, circulou em canais oficiais e
foi posteriormente examinada pelo Comitê Condon sem identificação conclusiva. A
concentração de relatos críveis, com múltiplas testemunhas de múltiplas nações durante
um único exercício de duas semanas, fez de Mainbrace um dos agrupamentos de UAP
da Guerra Fria inicial mais documentados, sendo citado em investigações oficiais
posteriores pelos governos americano e britânico.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:34:10Z'
last_lint: '2026-05-18T03:34:10Z'
wiki_version: 0.1.0
---
# Operation Mainbrace UAP Sightings
## Description (EN)
During NATO Exercise Mainbrace in September 1952—the alliance's largest naval exercise to date, spanning the North Atlantic from the Norwegian coast to Scotland—personnel from multiple nations reported UAP encounters on at least six separate occasions. Observers aboard the USS Franklin D. Roosevelt aircraft carrier sighted a silver sphere on 13 September; four days later, the crew of the Danish destroyer Willemoes tracked a triangular object estimated to be traveling at 900 mph. On 19 September, RAF Flight Lieutenant John Kilburn and four other aircrew at Topcliffe airfield watched a silver disc descend, halt, and then accelerate sharply—behavior irreconcilable with any known aircraft of the period. A photograph attributed to Navy journalist Wallace Litwin, taken from the Roosevelt, circulated in official channels and was later examined by the Condon Committee without a definitive identification. The density of credible, multi-witness, multi-nation reports during a single two-week exercise made Mainbrace one of the most thoroughly documented early Cold War UAP clusters and was cited in subsequent official inquiries by both the U.S. and British governments.
## Descrição (PT-BR)
Durante o Exercício Mainbrace da OTAN em setembro de 1952 — a maior manobra naval da aliança até então, realizada no Atlântico Norte da costa norueguesa até a Escócia — pessoal de múltiplas nações relatou encontros com UAP em pelo menos seis ocasiões distintas. Observadores a bordo do porta-aviões USS Franklin D. Roosevelt avistaram uma esfera prateada em 13 de setembro; quatro dias depois, a tripulação do contratorpedeiro dinamarquês Willemoes rastreou um objeto triangular estimado em 900 milhas por hora. Em 19 de setembro, o tenente John Kilburn da RAF e outros quatro membros de tripulação na base de Topcliffe assistiram a um disco prateado descer, parar e então acelerar bruscamente — comportamento incompatível com qualquer aeronave conhecida da época. Uma fotografia atribuída ao jornalista naval Wallace Litwin, tirada a bordo do Roosevelt, circulou em canais oficiais e foi posteriormente examinada pelo Comitê Condon sem identificação conclusiva. A concentração de relatos críveis, com múltiplas testemunhas de múltiplas nações durante um único exercício de duas semanas, fez de Mainbrace um dos agrupamentos de UAP da Guerra Fria inicial mais documentados, sendo citado em investigações oficiais posteriores pelos governos americano e britânico.

View file

@ -0,0 +1,63 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1959-06-26-father-gill-papua-encounter
canonical_name: Father Gill Papua Encounter
aliases:
- Father Gill Papua Encounter
event_class: uap-encounter
date_start: '1959-06-26'
date_end: '1959-06-26'
date_confidence: high
primary_location: Boianai, Papua New Guinea
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: On the evening of 26 June 1959, Anglican missionary Reverend William
Gill and 37 mission staff and local residents at Boianai, Papua New Guinea, observed
a large luminous disc-shaped object descend to low altitude above the mission station
over several hours. Human-like figures were visible on the upper surface of the
craft and appeared to be performing tasks; Reverend Gill waved toward them and at
least one figure waved back — an exchange subsequently repeated by other witnesses.
The object returned the following evening of 27 June, with the waving exchange again
occurring. All 38 witnesses signed a formal affidavit documenting both sightings,
producing one of the most extensively attested close-encounter records of the era.
The Royal Australian Air Force attributed the observations to misidentified stars
and planets; astronomer and investigator J. Allen Hynek later interviewed Gill,
found him credible, and considered the astronomical explanation inadequate given
the witnesses' familiarity with the night sky.
narrative_summary_pt_br: Na noite de 26 de junho de 1959, o reverendo anglicano William
Gill e 37 membros da equipe missionária e moradores locais em Boianai, Papua Nova
Guiné, observaram um grande objeto luminoso em forma de disco descer a baixa altitude
sobre a estação missionária ao longo de várias horas. Figuras humanoides eram visíveis
na superfície superior do objeto e pareciam realizar tarefas; o Reverendo Gill acenou
em direção a elas e pelo menos uma correspondeu ao gesto — intercâmbio que se repetiu
entre outras testemunhas. O objeto retornou na noite seguinte, 27 de junho, com
a troca de acenos ocorrendo novamente. Todas as 38 testemunhas assinaram uma declaração
formal documentando os dois avistamentos, produzindo um dos registros de encontro
aproximado mais amplamente atestados da época. A Real Força Aérea Australiana atribuiu
as observações a estrelas e planetas identificados erroneamente; o astrônomo e investigador
J. Allen Hynek entrevistou Gill posteriormente, considerou-o uma testemunha confiável
e avaliou a explicação astronômica como insuficiente dada a familiaridade das testemunhas
com o céu noturno.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:34:41Z'
last_lint: '2026-05-18T03:34:41Z'
wiki_version: 0.1.0
---
# Father Gill Papua Encounter
## Description (EN)
On the evening of 26 June 1959, Anglican missionary Reverend William Gill and 37 mission staff and local residents at Boianai, Papua New Guinea, observed a large luminous disc-shaped object descend to low altitude above the mission station over several hours. Human-like figures were visible on the upper surface of the craft and appeared to be performing tasks; Reverend Gill waved toward them and at least one figure waved back — an exchange subsequently repeated by other witnesses. The object returned the following evening of 27 June, with the waving exchange again occurring. All 38 witnesses signed a formal affidavit documenting both sightings, producing one of the most extensively attested close-encounter records of the era. The Royal Australian Air Force attributed the observations to misidentified stars and planets; astronomer and investigator J. Allen Hynek later interviewed Gill, found him credible, and considered the astronomical explanation inadequate given the witnesses' familiarity with the night sky.
## Descrição (PT-BR)
Na noite de 26 de junho de 1959, o reverendo anglicano William Gill e 37 membros da equipe missionária e moradores locais em Boianai, Papua Nova Guiné, observaram um grande objeto luminoso em forma de disco descer a baixa altitude sobre a estação missionária ao longo de várias horas. Figuras humanoides eram visíveis na superfície superior do objeto e pareciam realizar tarefas; o Reverendo Gill acenou em direção a elas e pelo menos uma correspondeu ao gesto — intercâmbio que se repetiu entre outras testemunhas. O objeto retornou na noite seguinte, 27 de junho, com a troca de acenos ocorrendo novamente. Todas as 38 testemunhas assinaram uma declaração formal documentando os dois avistamentos, produzindo um dos registros de encontro aproximado mais amplamente atestados da época. A Real Força Aérea Australiana atribuiu as observações a estrelas e planetas identificados erroneamente; o astrônomo e investigador J. Allen Hynek entrevistou Gill posteriormente, considerou-o uma testemunha confiável e avaliou a explicação astronômica como insuficiente dada a familiaridade das testemunhas com o céu noturno.

View file

@ -0,0 +1,69 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1964-04-24-lonnie-zamora-socorro
canonical_name: Lonnie Zamora Socorro Landing
aliases:
- Lonnie Zamora Socorro Landing
event_class: uap-encounter
date_start: '1964-04-24'
date_end: '1964-04-24'
date_confidence: high
primary_location: Socorro, New Mexico, USA
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: On 24 April 1964 at approximately 17:45 local time, Socorro Police
Sergeant Lonnie Zamora broke off pursuit of a speeding vehicle after hearing a roar
and observing a blue-orange flame descending southwest of the highway. Approaching
the site in his patrol car, he observed an egg-shaped, aluminum-white object resting
on girder-like legs in an arroyo, with two small figures in white coveralls standing
nearby; the figures retreated into the craft as Zamora drew closer. The object rose
with a loud roar and flame, then departed silently to the southwest at low altitude,
leaving four symmetrical ground-impression marks, scorched soil, and burned greasewood
brush at the landing site. Project Blue Book dispatched investigator Richard Holder
within hours; Air Force scientific consultant J. Allen Hynek visited the site days
later and concluded the physical trace evidence was genuine and the witness credible.
The case was formally classified 'Unidentified' by Project Blue Book — one of the
few to retain that designation — and remained uninvestigated as to a conventional
explanation. Zamora's account has never been credibly impeached, and the Socorro
landing is considered among the best-documented close-encounter cases in the historical
record.
narrative_summary_pt_br: Em 24 de abril de 1964, por volta das 17h45 no horário local,
o sargento de polícia Lonnie Zamora interrompeu a perseguição a um veículo na rodovia
de Socorro, Novo México, após ouvir um rugido e avistar uma chama azul-alaranjada
descendo a sudoeste. Aproximando-se do local em sua viatura, ele observou um objeto
de formato oval, de coloração alumínio, pousado sobre pernas estruturais em um arroyo,
com duas figuras de pequena estatura vestindo macacões brancos nas proximidades;
ao perceber a aproximação de Zamora, as figuras retornaram ao interior do objeto.
A aeronave decolou com forte rugido e chama, partindo silenciosamente em baixa altitude
para sudoeste, deixando quatro marcas simétricas no solo, vegetação chamuscada e
arbustos queimados no ponto de pouso. O Projeto Blue Book enviou investigadores
ao local em poucas horas; o consultor científico da Força Aérea J. Allen Hynek visitou
o sítio dias depois e concluiu que as evidências físicas eram autênticas e o depoimento
do policial confiável. O caso foi formalmente classificado como 'Não Identificado'
pelo Projeto Blue Book — uma das poucas ocorrências a manter essa designação — sem
que qualquer explicação convencional satisfatória fosse encontrada. O relato de
Zamora jamais foi desacreditado de forma convincente, e o pouso de Socorro é considerado
um dos incidentes de contato próximo mais bem documentados do registro histórico.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:35:12Z'
last_lint: '2026-05-18T03:35:12Z'
wiki_version: 0.1.0
---
# Lonnie Zamora Socorro Landing
## Description (EN)
On 24 April 1964 at approximately 17:45 local time, Socorro Police Sergeant Lonnie Zamora broke off pursuit of a speeding vehicle after hearing a roar and observing a blue-orange flame descending southwest of the highway. Approaching the site in his patrol car, he observed an egg-shaped, aluminum-white object resting on girder-like legs in an arroyo, with two small figures in white coveralls standing nearby; the figures retreated into the craft as Zamora drew closer. The object rose with a loud roar and flame, then departed silently to the southwest at low altitude, leaving four symmetrical ground-impression marks, scorched soil, and burned greasewood brush at the landing site. Project Blue Book dispatched investigator Richard Holder within hours; Air Force scientific consultant J. Allen Hynek visited the site days later and concluded the physical trace evidence was genuine and the witness credible. The case was formally classified 'Unidentified' by Project Blue Book — one of the few to retain that designation — and remained uninvestigated as to a conventional explanation. Zamora's account has never been credibly impeached, and the Socorro landing is considered among the best-documented close-encounter cases in the historical record.
## Descrição (PT-BR)
Em 24 de abril de 1964, por volta das 17h45 no horário local, o sargento de polícia Lonnie Zamora interrompeu a perseguição a um veículo na rodovia de Socorro, Novo México, após ouvir um rugido e avistar uma chama azul-alaranjada descendo a sudoeste. Aproximando-se do local em sua viatura, ele observou um objeto de formato oval, de coloração alumínio, pousado sobre pernas estruturais em um arroyo, com duas figuras de pequena estatura vestindo macacões brancos nas proximidades; ao perceber a aproximação de Zamora, as figuras retornaram ao interior do objeto. A aeronave decolou com forte rugido e chama, partindo silenciosamente em baixa altitude para sudoeste, deixando quatro marcas simétricas no solo, vegetação chamuscada e arbustos queimados no ponto de pouso. O Projeto Blue Book enviou investigadores ao local em poucas horas; o consultor científico da Força Aérea J. Allen Hynek visitou o sítio dias depois e concluiu que as evidências físicas eram autênticas e o depoimento do policial confiável. O caso foi formalmente classificado como 'Não Identificado' pelo Projeto Blue Book — uma das poucas ocorrências a manter essa designação — sem que qualquer explicação convencional satisfatória fosse encontrada. O relato de Zamora jamais foi desacreditado de forma convincente, e o pouso de Socorro é considerado um dos incidentes de contato próximo mais bem documentados do registro histórico.

View file

@ -0,0 +1,66 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1966-04-06-westall-school-encounter
canonical_name: Westall School Encounter
aliases:
- Westall School Encounter
event_class: uap-encounter
date_start: '1966-04-06'
date_end: '1966-04-06'
date_confidence: high
primary_location: Clayton South, Victoria, Australia
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: On the morning of 6 April 1966, more than 200 students and staff
at Westall High School in Clayton South, Victoria, Australia, witnessed an unidentified
disc-shaped object descend into the adjacent Grange Reserve, remain briefly on the
ground, then rise and depart at speed. The object was described as silver-grey and
approximately the size of two family cars, and several students who ran to the landing
site found a circular depression of flattened grass consistent with a physical contact
event. A number of small aircraft were observed circling the area at the time, though
their origin and purpose were never officially established. The Royal Australian
Air Force opened an investigation but released no public findings, and multiple
witnesses subsequently reported being instructed by school officials and unidentified
men in plain clothes to remain silent about the incident. The combination of physical
trace evidence, the exceptional witness count across two adjacent schools, and the
documented suppression pressure place Westall among the most evidentially substantive
daylight UAP encounters on record in the Southern Hemisphere.
narrative_summary_pt_br: Na manhã de 6 de abril de 1966, mais de 200 alunos e professores
do Westall High School, em Clayton South, Victoria, Austrália, observaram um objeto
discóide não identificado descer para a Reserva The Grange, adjacente ao campus,
permanecer brevemente no solo e então elevar-se e partir em alta velocidade. O objeto
foi descrito como prateado-acinzentado, com tamanho estimado equivalente a dois
automóveis familiares, e diversos alunos que correram ao local do pouso encontraram
uma depressão circular de grama achatada, consistente com contato físico direto
com o solo. Várias aeronaves leves foram vistas circulando a área durante o incidente,
embora sua origem e propósito jamais tenham sido oficialmente esclarecidos. A Real
Força Aérea Australiana (RAAF) abriu uma investigação, mas não divulgou quaisquer
conclusões públicas, e múltiplas testemunhas relataram posteriormente ter sido instruídas
por funcionários da escola e por homens não identificados em roupas civis a permanecer
em silêncio sobre o ocorrido. A conjugação de evidência física, a quantidade excepcional
de testemunhas em duas escolas adjacentes e a pressão documentada para supressão
dos relatos posicionam Westall entre os encontros UAP diurnos mais substantivamente
evidenciados já registrados no Hemisfério Sul.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:35:50Z'
last_lint: '2026-05-18T03:35:50Z'
wiki_version: 0.1.0
---
# Westall School Encounter
## Description (EN)
On the morning of 6 April 1966, more than 200 students and staff at Westall High School in Clayton South, Victoria, Australia, witnessed an unidentified disc-shaped object descend into the adjacent Grange Reserve, remain briefly on the ground, then rise and depart at speed. The object was described as silver-grey and approximately the size of two family cars, and several students who ran to the landing site found a circular depression of flattened grass consistent with a physical contact event. A number of small aircraft were observed circling the area at the time, though their origin and purpose were never officially established. The Royal Australian Air Force opened an investigation but released no public findings, and multiple witnesses subsequently reported being instructed by school officials and unidentified men in plain clothes to remain silent about the incident. The combination of physical trace evidence, the exceptional witness count across two adjacent schools, and the documented suppression pressure place Westall among the most evidentially substantive daylight UAP encounters on record in the Southern Hemisphere.
## Descrição (PT-BR)
Na manhã de 6 de abril de 1966, mais de 200 alunos e professores do Westall High School, em Clayton South, Victoria, Austrália, observaram um objeto discóide não identificado descer para a Reserva The Grange, adjacente ao campus, permanecer brevemente no solo e então elevar-se e partir em alta velocidade. O objeto foi descrito como prateado-acinzentado, com tamanho estimado equivalente a dois automóveis familiares, e diversos alunos que correram ao local do pouso encontraram uma depressão circular de grama achatada, consistente com contato físico direto com o solo. Várias aeronaves leves foram vistas circulando a área durante o incidente, embora sua origem e propósito jamais tenham sido oficialmente esclarecidos. A Real Força Aérea Australiana (RAAF) abriu uma investigação, mas não divulgou quaisquer conclusões públicas, e múltiplas testemunhas relataram posteriormente ter sido instruídas por funcionários da escola e por homens não identificados em roupas civis a permanecer em silêncio sobre o ocorrido. A conjugação de evidência física, a quantidade excepcional de testemunhas em duas escolas adjacentes e a pressão documentada para supressão dos relatos posicionam Westall entre os encontros UAP diurnos mais substantivamente evidenciados já registrados no Hemisfério Sul.

View file

@ -0,0 +1,67 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1975-11-05-travis-walton-abduction
canonical_name: Travis Walton Abduction
aliases:
- Travis Walton Abduction
event_class: uap-abduction-claim
date_start: '1975-11-05'
date_end: '1975-11-05'
date_confidence: high
primary_location: ApacheSitgreaves National Forest, Arizona, USA
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: On the evening of November 5, 1975, Travis Walton and a logging
crew of six — employed by contractor Mike Rogers — were driving out of the ApacheSitgreaves
National Forest near Heber, Arizona, when they observed a luminous disc-shaped object
hovering above the tree line. Walton exited the truck and approached the object;
a beam of light struck him and hurled him backward, after which the crew fled the
scene, leaving him behind. His disappearance was reported to the Navajo County Sheriff's
Department that same night, and extensive searches found no trace of him for five
days. On November 10, Walton reappeared near Heber and gave an account of being
aboard a craft attended by small humanoid figures and a group of human-appearing
beings. Five of the six crew members subsequently passed a polygraph examination
administered by Cy Gilson of the Arizona Department of Public Safety; the Aerial
Phenomena Research Organization (APRO) conducted the primary civilian investigation.
No U.S. government agency formally investigated the incident, and the case remains
one of the most extensively documented and disputed alleged abduction reports in
the UFO literature.
narrative_summary_pt_br: Na noite de 5 de novembro de 1975, Travis Walton e uma equipe
de seis lenhadores contratados por Mike Rogers deixavam a Floresta Nacional ApacheSitgreaves,
próximo a Heber, Arizona, quando avistaram um objeto discóide luminoso pairando
sobre as copas das árvores. Walton desceu do caminhão e se aproximou do objeto;
um feixe de luz o atingiu e o arremessou para trás, momento em que a equipe fugiu
do local, deixando-o para trás. O desaparecimento foi notificado ao Departamento
do Xerife do Condado Navajo ainda naquela noite, e buscas intensivas não localizaram
nenhum rastro seu por cinco dias. Em 10 de novembro, Walton reapareceu próximo a
Heber e relatou ter estado a bordo de uma nave na presença de figuras humanoides
de pequeno porte e de seres com aparência humana. Cinco dos seis membros da equipe
foram submetidos a exame de polígrafo aplicado por Cy Gilson, do Departamento de
Segurança Pública do Arizona, e foram considerados não-deceptivos; a Aerial Phenomena
Research Organization (APRO) conduziu a principal investigação civil do caso. Nenhuma
agência do governo norte-americano investigou formalmente o incidente, e o caso
permanece um dos relatos de abdução mais documentados e contestados da literatura
ufológica.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:36:22Z'
last_lint: '2026-05-18T03:36:22Z'
wiki_version: 0.1.0
---
# Travis Walton Abduction
## Description (EN)
On the evening of November 5, 1975, Travis Walton and a logging crew of six — employed by contractor Mike Rogers — were driving out of the ApacheSitgreaves National Forest near Heber, Arizona, when they observed a luminous disc-shaped object hovering above the tree line. Walton exited the truck and approached the object; a beam of light struck him and hurled him backward, after which the crew fled the scene, leaving him behind. His disappearance was reported to the Navajo County Sheriff's Department that same night, and extensive searches found no trace of him for five days. On November 10, Walton reappeared near Heber and gave an account of being aboard a craft attended by small humanoid figures and a group of human-appearing beings. Five of the six crew members subsequently passed a polygraph examination administered by Cy Gilson of the Arizona Department of Public Safety; the Aerial Phenomena Research Organization (APRO) conducted the primary civilian investigation. No U.S. government agency formally investigated the incident, and the case remains one of the most extensively documented and disputed alleged abduction reports in the UFO literature.
## Descrição (PT-BR)
Na noite de 5 de novembro de 1975, Travis Walton e uma equipe de seis lenhadores contratados por Mike Rogers deixavam a Floresta Nacional ApacheSitgreaves, próximo a Heber, Arizona, quando avistaram um objeto discóide luminoso pairando sobre as copas das árvores. Walton desceu do caminhão e se aproximou do objeto; um feixe de luz o atingiu e o arremessou para trás, momento em que a equipe fugiu do local, deixando-o para trás. O desaparecimento foi notificado ao Departamento do Xerife do Condado Navajo ainda naquela noite, e buscas intensivas não localizaram nenhum rastro seu por cinco dias. Em 10 de novembro, Walton reapareceu próximo a Heber e relatou ter estado a bordo de uma nave na presença de figuras humanoides de pequeno porte e de seres com aparência humana. Cinco dos seis membros da equipe foram submetidos a exame de polígrafo aplicado por Cy Gilson, do Departamento de Segurança Pública do Arizona, e foram considerados não-deceptivos; a Aerial Phenomena Research Organization (APRO) conduziu a principal investigação civil do caso. Nenhuma agência do governo norte-americano investigou formalmente o incidente, e o caso permanece um dos relatos de abdução mais documentados e contestados da literatura ufológica.

View file

@ -0,0 +1,65 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1977-09-XX-operacao-prato
canonical_name: Operação Prato
aliases:
- Operação Prato
event_class: uap-encounter
date_start: 1977-09
date_end: 1977-09
date_confidence: high
primary_location: Ilha de Colares, Pará, Brasil
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: In September 1977, the Brazilian Air Force (FAB) launched a covert
field investigation — designated Operação Prato — on Ilha de Colares, in Pará state,
following widespread reports of luminous aerial objects that were physically affecting
the local population. Under the command of Captain Uyrangê Hollanda Lima, the team
spent roughly three months photographing and filming unidentified objects, interviewing
hundreds of witnesses, and documenting injuries that residents attributed to tight
beams of light directed at them from the objects — a phenomenon locals called 'chupa-chupa.'
The collected material reportedly comprised thousands of photographic frames and
hundreds of pages of testimony, all classified upon the operation's conclusion in
December 1977. In 1997, shortly before his death, Hollanda gave a detailed interview
in which he acknowledged that the phenomena his team observed defied conventional
explanation and had left a lasting psychological impact on the investigators themselves.
A portion of the FAB files was made available to researchers in 2004, confirming
the operation's scope, though significant portions remain restricted.
narrative_summary_pt_br: Em setembro de 1977, a Força Aérea Brasileira (FAB) lançou
uma investigação de campo sigilosa — denominada Operação Prato — na Ilha de Colares,
no Pará, em resposta a relatos generalizados de objetos aéreos luminosos que estavam
afetando fisicamente a população local. Sob o comando do Capitão Uyrangê Hollanda
Lima, a equipe passou cerca de três meses fotografando e filmando objetos não identificados,
colhendo depoimentos de centenas de testemunhas e documentando ferimentos que moradores
atribuíam a feixes de luz dirigidos pelos objetos contra pessoas — fenômeno que
a população local chamou de 'chupa-chupa.' O material coletado teria incluído milhares
de fotogramas e centenas de páginas de depoimentos, todos classificados ao término
da operação, em dezembro de 1977. Em 1997, pouco antes de sua morte, Hollanda concedeu
uma entrevista detalhada na qual reconheceu que os fenômenos observados por sua
equipe desafiavam qualquer explicação convencional e deixaram marcas psicológicas
duradouras nos próprios investigadores. Uma parte dos arquivos da FAB foi disponibilizada
a pesquisadores em 2004, confirmando a dimensão da operação, embora parcelas significativas
permaneçam restritas.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:36:51Z'
last_lint: '2026-05-18T03:36:51Z'
wiki_version: 0.1.0
---
# Operação Prato
## Description (EN)
In September 1977, the Brazilian Air Force (FAB) launched a covert field investigation — designated Operação Prato — on Ilha de Colares, in Pará state, following widespread reports of luminous aerial objects that were physically affecting the local population. Under the command of Captain Uyrangê Hollanda Lima, the team spent roughly three months photographing and filming unidentified objects, interviewing hundreds of witnesses, and documenting injuries that residents attributed to tight beams of light directed at them from the objects — a phenomenon locals called 'chupa-chupa.' The collected material reportedly comprised thousands of photographic frames and hundreds of pages of testimony, all classified upon the operation's conclusion in December 1977. In 1997, shortly before his death, Hollanda gave a detailed interview in which he acknowledged that the phenomena his team observed defied conventional explanation and had left a lasting psychological impact on the investigators themselves. A portion of the FAB files was made available to researchers in 2004, confirming the operation's scope, though significant portions remain restricted.
## Descrição (PT-BR)
Em setembro de 1977, a Força Aérea Brasileira (FAB) lançou uma investigação de campo sigilosa — denominada Operação Prato — na Ilha de Colares, no Pará, em resposta a relatos generalizados de objetos aéreos luminosos que estavam afetando fisicamente a população local. Sob o comando do Capitão Uyrangê Hollanda Lima, a equipe passou cerca de três meses fotografando e filmando objetos não identificados, colhendo depoimentos de centenas de testemunhas e documentando ferimentos que moradores atribuíam a feixes de luz dirigidos pelos objetos contra pessoas — fenômeno que a população local chamou de 'chupa-chupa.' O material coletado teria incluído milhares de fotogramas e centenas de páginas de depoimentos, todos classificados ao término da operação, em dezembro de 1977. Em 1997, pouco antes de sua morte, Hollanda concedeu uma entrevista detalhada na qual reconheceu que os fenômenos observados por sua equipe desafiavam qualquer explicação convencional e deixaram marcas psicológicas duradouras nos próprios investigadores. Uma parte dos arquivos da FAB foi disponibilizada a pesquisadores em 2004, confirmando a dimensão da operação, embora parcelas significativas permaneçam restritas.

View file

@ -0,0 +1,64 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1980-12-27-rendlesham-forest-incident
canonical_name: Rendlesham Forest Incident
aliases:
- Rendlesham Forest Incident
event_class: uap-encounter
date_start: '1980-12-27'
date_end: '1980-12-27'
date_confidence: high
primary_location: Rendlesham Forest, Suffolk, UK (RAF Woodbridge / RAF Bentwaters)
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: On the nights of 2627 and 2728 December 1980, USAF security personnel
from RAF Woodbridge encountered an unidentified luminous object in Rendlesham Forest,
Suffolk. Sergeant Jim Penniston and Airman First Class John Burroughs were first
to approach the craft in the early hours of 27 December, with Penniston documenting
geometric markings on its surface in his field notebook before the object departed.
The following night, Deputy Base Commander Lt Col Charles Halt led a second patrol
into the forest, capturing real-time observations on a cassette recorder while his
team measured elevated radiation readings at three triangular ground impressions
at the reported landing site. Halt formally notified the UK Ministry of Defence
in a memorandum dated 13 January 1981 — later declassified under FOIA — which stands
as one of the most consequential official UAP documents produced by any NATO military
command. The MoD reviewed the case and concluded it posed no threat to national
security, leaving the object's origin officially unresolved.
narrative_summary_pt_br: Nas noites de 2627 e 2728 de dezembro de 1980, militares
da USAF lotados na RAF Woodbridge relataram encontros com um objeto luminoso não
identificado na Floresta de Rendlesham, em Suffolk, Inglaterra. O Sargento Jim Penniston
e o Soldado de Primeira Classe John Burroughs foram os primeiros a se aproximar
do objeto nas primeiras horas do dia 27, com Penniston registrando marcações geométricas
na superfície da aeronave em seu caderno de campo antes de o objeto se afastar.
Na noite seguinte, o Tenente-Coronel Charles Halt, vice-comandante da base, conduziu
uma segunda investigação na floresta, gravando observações em tempo real em uma
fita cassete enquanto sua equipe media níveis elevados de radiação em três marcas
triangulares no solo do local do suposto pouso. Halt notificou formalmente o Ministério
da Defesa do Reino Unido por meio de memorando datado de 13 de janeiro de 1981 —
posteriormente desclassificado via FOIA —, que permanece um dos documentos oficiais
de UAP mais significativos já produzidos por um comando militar da OTAN. O Ministério
da Defesa analisou o caso e concluiu que não havia ameaça à segurança nacional,
deixando a origem do objeto oficialmente sem solução.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:37:36Z'
last_lint: '2026-05-18T03:37:36Z'
wiki_version: 0.1.0
---
# Rendlesham Forest Incident
## Description (EN)
On the nights of 2627 and 2728 December 1980, USAF security personnel from RAF Woodbridge encountered an unidentified luminous object in Rendlesham Forest, Suffolk. Sergeant Jim Penniston and Airman First Class John Burroughs were first to approach the craft in the early hours of 27 December, with Penniston documenting geometric markings on its surface in his field notebook before the object departed. The following night, Deputy Base Commander Lt Col Charles Halt led a second patrol into the forest, capturing real-time observations on a cassette recorder while his team measured elevated radiation readings at three triangular ground impressions at the reported landing site. Halt formally notified the UK Ministry of Defence in a memorandum dated 13 January 1981 — later declassified under FOIA — which stands as one of the most consequential official UAP documents produced by any NATO military command. The MoD reviewed the case and concluded it posed no threat to national security, leaving the object's origin officially unresolved.
## Descrição (PT-BR)
Nas noites de 2627 e 2728 de dezembro de 1980, militares da USAF lotados na RAF Woodbridge relataram encontros com um objeto luminoso não identificado na Floresta de Rendlesham, em Suffolk, Inglaterra. O Sargento Jim Penniston e o Soldado de Primeira Classe John Burroughs foram os primeiros a se aproximar do objeto nas primeiras horas do dia 27, com Penniston registrando marcações geométricas na superfície da aeronave em seu caderno de campo antes de o objeto se afastar. Na noite seguinte, o Tenente-Coronel Charles Halt, vice-comandante da base, conduziu uma segunda investigação na floresta, gravando observações em tempo real em uma fita cassete enquanto sua equipe media níveis elevados de radiação em três marcas triangulares no solo do local do suposto pouso. Halt notificou formalmente o Ministério da Defesa do Reino Unido por meio de memorando datado de 13 de janeiro de 1981 — posteriormente desclassificado via FOIA —, que permanece um dos documentos oficiais de UAP mais significativos já produzidos por um comando militar da OTAN. O Ministério da Defesa analisou o caso e concluiu que não havia ameaça à segurança nacional, deixando a origem do objeto oficialmente sem solução.

View file

@ -0,0 +1,68 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1980-12-29-cash-landrum-incident
canonical_name: Cash-Landrum Incident
aliases:
- Cash-Landrum Incident
event_class: uap-related-injury
date_start: '1980-12-29'
date_end: '1980-12-29'
date_confidence: high
primary_location: Dayton, Texas, USA
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: 'On the evening of December 29, 1980, Betty Cash, Vickie Landrum,
and Landrum''s seven-year-old grandson Colby were driving along FM 1485 near Dayton,
Texas, when a large diamond-shaped object emitting jets of flame blocked the road
ahead, its radiated heat making the car''s metal surfaces too hot to touch. As the
object departed, approximately 23 military CH-47 Chinook helicopters appeared to
surround and escort it—a detail noted independently by all three witnesses. In the
days that followed, all three developed symptoms consistent with radiation exposure:
hair loss, nausea, vomiting, and skin lesions; Betty Cash, who had stepped out of
the vehicle and stood closest to the object, suffered the most severe injuries and
required hospitalization. Cash and Vickie Landrum filed a $20 million civil suit
against the U.S. government in 1981; a federal court dismissed the case in 1986
after every contacted agency denied ownership or knowledge of the craft. The incident
is among the few reported UAP encounters in which acute physical injury was medically
documented, litigated in federal court, and subjected to sustained field investigation—most
notably by NASA engineer John Schuessler.'
narrative_summary_pt_br: 'Na noite de 29 de dezembro de 1980, Betty Cash, Vickie Landrum
e o neto de sete anos de Vickie, Colby, trafegavam pela rodovia FM 1485, próximo
a Dayton, Texas, quando um grande objeto em forma de diamante, lançando jatos de
chamas, bloqueou a estrada à sua frente, irradiando calor suficiente para tornar
as superfícies metálicas do veículo dolorosas ao toque. Enquanto o objeto se afastava,
aproximadamente 23 helicópteros militares CH-47 Chinook pareciam cercá-lo e escoltá-lo
— detalhe registrado de forma independente pelas três testemunhas. Nos dias seguintes,
os três desenvolveram sintomas compatíveis com exposição à radiação: queda de cabelo,
náuseas, vômitos e lesões cutâneas; Betty Cash, que havia saído do veículo e permanecido
mais próxima ao objeto, sofreu as lesões mais graves e precisou ser hospitalizada.
Cash e Vickie Landrum ajuizaram uma ação civil de 20 milhões de dólares contra o
governo dos Estados Unidos em 1981; o processo foi arquivado por um tribunal federal
em 1986 após nenhuma agência contactada reconhecer a propriedade ou o conhecimento
sobre o objeto. O caso figura entre os raros relatos de encontro com um UAP em que
danos físicos agudos foram documentados medicamente, litigados em tribunal federal
e submetidos a investigação de campo sistemática — conduzida, em especial, pelo
engenheiro da NASA John Schuessler.'
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:38:10Z'
last_lint: '2026-05-18T03:38:10Z'
wiki_version: 0.1.0
---
# Cash-Landrum Incident
## Description (EN)
On the evening of December 29, 1980, Betty Cash, Vickie Landrum, and Landrum's seven-year-old grandson Colby were driving along FM 1485 near Dayton, Texas, when a large diamond-shaped object emitting jets of flame blocked the road ahead, its radiated heat making the car's metal surfaces too hot to touch. As the object departed, approximately 23 military CH-47 Chinook helicopters appeared to surround and escort it—a detail noted independently by all three witnesses. In the days that followed, all three developed symptoms consistent with radiation exposure: hair loss, nausea, vomiting, and skin lesions; Betty Cash, who had stepped out of the vehicle and stood closest to the object, suffered the most severe injuries and required hospitalization. Cash and Vickie Landrum filed a $20 million civil suit against the U.S. government in 1981; a federal court dismissed the case in 1986 after every contacted agency denied ownership or knowledge of the craft. The incident is among the few reported UAP encounters in which acute physical injury was medically documented, litigated in federal court, and subjected to sustained field investigation—most notably by NASA engineer John Schuessler.
## Descrição (PT-BR)
Na noite de 29 de dezembro de 1980, Betty Cash, Vickie Landrum e o neto de sete anos de Vickie, Colby, trafegavam pela rodovia FM 1485, próximo a Dayton, Texas, quando um grande objeto em forma de diamante, lançando jatos de chamas, bloqueou a estrada à sua frente, irradiando calor suficiente para tornar as superfícies metálicas do veículo dolorosas ao toque. Enquanto o objeto se afastava, aproximadamente 23 helicópteros militares CH-47 Chinook pareciam cercá-lo e escoltá-lo — detalhe registrado de forma independente pelas três testemunhas. Nos dias seguintes, os três desenvolveram sintomas compatíveis com exposição à radiação: queda de cabelo, náuseas, vômitos e lesões cutâneas; Betty Cash, que havia saído do veículo e permanecido mais próxima ao objeto, sofreu as lesões mais graves e precisou ser hospitalizada. Cash e Vickie Landrum ajuizaram uma ação civil de 20 milhões de dólares contra o governo dos Estados Unidos em 1981; o processo foi arquivado por um tribunal federal em 1986 após nenhuma agência contactada reconhecer a propriedade ou o conhecimento sobre o objeto. O caso figura entre os raros relatos de encontro com um UAP em que danos físicos agudos foram documentados medicamente, litigados em tribunal federal e submetidos a investigação de campo sistemática — conduzida, em especial, pelo engenheiro da NASA John Schuessler.

View file

@ -0,0 +1,65 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1986-05-19-são-paulo-night-of-the-ufos
canonical_name: São Paulo Noite Oficial dos OVNIs
aliases:
- São Paulo Noite Oficial dos OVNIs
event_class: uap-encounter
date_start: '1986-05-19'
date_end: '1986-05-19'
date_confidence: high
primary_location: Costa do Brasil / São José dos Campos, SP
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: On the night of 19 May 1986, Brazilian Air Defense radar operators
tracked up to 21 unidentified objects over southeastern Brazil, concentrated along
the São Paulo coast and inland toward São José dos Campos. The Brazilian Air Force
scrambled Mirage III and F-5E interceptors whose pilots reported visual contact
with luminous objects executing abrupt directional changes at speeds the crews estimated
between 1,000 and 15,000 km/h — beyond any known aircraft of that era. Each time
a fighter closed to engagement range the objects vanished from radar and reappeared
elsewhere, a pattern reported consistently across multiple crews. Brigadeiro Octavio
Moreira Lima, then press chief of the Ministério da Aeronáutica, convened an official
press conference on the morning of 20 May 1986 — a deliberate and publicly unprecedented
step — confirming the objects were real, solid, and simultaneously detected on multiple
independent radar systems. The Brazilian government has issued no subsequent explanation;
the event stands as the most formally acknowledged UAP interception in Brazilian
aviation history.
narrative_summary_pt_br: Na noite de 19 de maio de 1986, operadores do radar de defesa
aérea brasileira rastrearam até 21 objetos não identificados sobre o sudeste do
Brasil, concentrados ao longo do litoral paulista e no interior, na direção de São
José dos Campos. A Força Aérea Brasileira scramblou interceptores Mirage III e F-5E
cujos pilotos relataram contato visual com objetos luminosos executando mudanças
abruptas de direção a velocidades estimadas entre 1.000 e 15.000 km/h — além das
capacidades de qualquer aeronave conhecida da época. Cada vez que um caça se aproximava
ao alcance de engajamento, os objetos desapareciam do radar e reapareciam em outro
ponto, padrão relatado de forma consistente por diversas tripulações. O Brigadeiro
Octavio Moreira Lima, então chefe de imprensa do Ministério da Aeronáutica, convocou
uma coletiva oficial na manhã de 20 de maio de 1986 — um passo deliberado e publicamente
sem precedentes — confirmando que os objetos eram reais, sólidos e detectados simultaneamente
em múltiplos sistemas de radar independentes. O governo brasileiro não emitiu nenhuma
explicação posterior; o incidente permanece o evento de interceptação de OVNI mais
formalmente reconhecido na história da aviação brasileira.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:38:56Z'
last_lint: '2026-05-18T03:38:56Z'
wiki_version: 0.1.0
---
# São Paulo Noite Oficial dos OVNIs
## Description (EN)
On the night of 19 May 1986, Brazilian Air Defense radar operators tracked up to 21 unidentified objects over southeastern Brazil, concentrated along the São Paulo coast and inland toward São José dos Campos. The Brazilian Air Force scrambled Mirage III and F-5E interceptors whose pilots reported visual contact with luminous objects executing abrupt directional changes at speeds the crews estimated between 1,000 and 15,000 km/h — beyond any known aircraft of that era. Each time a fighter closed to engagement range the objects vanished from radar and reappeared elsewhere, a pattern reported consistently across multiple crews. Brigadeiro Octavio Moreira Lima, then press chief of the Ministério da Aeronáutica, convened an official press conference on the morning of 20 May 1986 — a deliberate and publicly unprecedented step — confirming the objects were real, solid, and simultaneously detected on multiple independent radar systems. The Brazilian government has issued no subsequent explanation; the event stands as the most formally acknowledged UAP interception in Brazilian aviation history.
## Descrição (PT-BR)
Na noite de 19 de maio de 1986, operadores do radar de defesa aérea brasileira rastrearam até 21 objetos não identificados sobre o sudeste do Brasil, concentrados ao longo do litoral paulista e no interior, na direção de São José dos Campos. A Força Aérea Brasileira scramblou interceptores Mirage III e F-5E cujos pilotos relataram contato visual com objetos luminosos executando mudanças abruptas de direção a velocidades estimadas entre 1.000 e 15.000 km/h — além das capacidades de qualquer aeronave conhecida da época. Cada vez que um caça se aproximava ao alcance de engajamento, os objetos desapareciam do radar e reapareciam em outro ponto, padrão relatado de forma consistente por diversas tripulações. O Brigadeiro Octavio Moreira Lima, então chefe de imprensa do Ministério da Aeronáutica, convocou uma coletiva oficial na manhã de 20 de maio de 1986 — um passo deliberado e publicamente sem precedentes — confirmando que os objetos eram reais, sólidos e detectados simultaneamente em múltiplos sistemas de radar independentes. O governo brasileiro não emitiu nenhuma explicação posterior; o incidente permanece o evento de interceptação de OVNI mais formalmente reconhecido na história da aviação brasileira.

View file

@ -0,0 +1,72 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1989-11-XX-belgian-wave
canonical_name: Belgian UFO Wave
aliases:
- Belgian UFO Wave
event_class: uap-encounter
date_start: 1989-11
date_end: 1989-11
date_confidence: high
primary_location: Belgium (multiple sites)
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: Starting in late November 1989, Belgium experienced a sustained
wave of anomalous aerial sightings that eventually generated over 13,500 formal
civilian witness reports compiled by SOBEPS (Société Belge d'Étude des Phénomènes
Spatiaux). The objects were consistently described as large, low-flying, triangular
or delta-shaped craft carrying bright lights at each corner and a central downward
beam, typically moving at low speed before departing rapidly. The Belgian Gendarmerie
filed official reports on the night of 29 November 1989, when officers at multiple
posts observed the objects at close range. The operationally most significant incident
occurred on the night of 3031 March 1990, when ground radar stations at Glons and
Semmerzake tracked unidentified targets and two Belgian Air Force F-16s briefly
achieved radar lock on objects that subsequently accelerated beyond conventional
performance parameters and broke contact. General Wilfried De Brouwer, Chief of
Operations of the Belgian Air Staff, stated publicly that the Air Force could not
identify the objects and that no foreign nation had claimed responsibility for any
overflights. The Belgian government's decision to collaborate openly with SOBEPS
and release military radar data to civilian researchers remains one of the most
transparent official responses to a sustained UAP phenomenon on record.
narrative_summary_pt_br: A partir do final de novembro de 1989, a Bélgica registrou
uma onda sustentada de avistamentos aéreos anômalos que acumulou mais de 13.500
relatos formais de civis compilados pela SOBEPS (Société Belge d'Étude des Phénomènes
Spatiaux). Os objetos foram consistentemente descritos como aeronaves triangulares
ou em forma de delta, de grande porte, voando em baixa altitude com luzes brilhantes
em cada vértice e um feixe central voltado para baixo, deslocando-se lentamente
antes de partir em alta velocidade. A Gendarmerie belga registrou boletins oficiais
na noite de 29 de novembro de 1989, quando agentes em múltiplos postos observaram
os objetos a curta distância. O incidente operacionalmente mais significativo ocorreu
na noite de 30 para 31 de março de 1990, quando estações de radar terrestres em
Glons e Semmerzake rastrearam alvos não identificados enquanto dois F-16s da Força
Aérea belga obtiveram, momentaneamente, bloqueio de radar em objetos que em seguida
aceleraram além dos parâmetros de desempenho convencionais e romperam o contato.
O General Wilfried De Brouwer, Chefe de Operações do Estado-Maior da Força Aérea
belga, declarou publicamente que a Força Aérea não conseguiu identificar os objetos
e que nenhuma nação estrangeira reivindicou responsabilidade pelos sobrevoos. A
decisão do governo belga de colaborar abertamente com a SOBEPS e divulgar dados
de radar militar a pesquisadores civis permanece uma das respostas oficiais mais
transparentes já registradas diante de um fenômeno UAP sustentado.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:39:33Z'
last_lint: '2026-05-18T03:39:33Z'
wiki_version: 0.1.0
---
# Belgian UFO Wave
## Description (EN)
Starting in late November 1989, Belgium experienced a sustained wave of anomalous aerial sightings that eventually generated over 13,500 formal civilian witness reports compiled by SOBEPS (Société Belge d'Étude des Phénomènes Spatiaux). The objects were consistently described as large, low-flying, triangular or delta-shaped craft carrying bright lights at each corner and a central downward beam, typically moving at low speed before departing rapidly. The Belgian Gendarmerie filed official reports on the night of 29 November 1989, when officers at multiple posts observed the objects at close range. The operationally most significant incident occurred on the night of 3031 March 1990, when ground radar stations at Glons and Semmerzake tracked unidentified targets and two Belgian Air Force F-16s briefly achieved radar lock on objects that subsequently accelerated beyond conventional performance parameters and broke contact. General Wilfried De Brouwer, Chief of Operations of the Belgian Air Staff, stated publicly that the Air Force could not identify the objects and that no foreign nation had claimed responsibility for any overflights. The Belgian government's decision to collaborate openly with SOBEPS and release military radar data to civilian researchers remains one of the most transparent official responses to a sustained UAP phenomenon on record.
## Descrição (PT-BR)
A partir do final de novembro de 1989, a Bélgica registrou uma onda sustentada de avistamentos aéreos anômalos que acumulou mais de 13.500 relatos formais de civis compilados pela SOBEPS (Société Belge d'Étude des Phénomènes Spatiaux). Os objetos foram consistentemente descritos como aeronaves triangulares ou em forma de delta, de grande porte, voando em baixa altitude com luzes brilhantes em cada vértice e um feixe central voltado para baixo, deslocando-se lentamente antes de partir em alta velocidade. A Gendarmerie belga registrou boletins oficiais na noite de 29 de novembro de 1989, quando agentes em múltiplos postos observaram os objetos a curta distância. O incidente operacionalmente mais significativo ocorreu na noite de 30 para 31 de março de 1990, quando estações de radar terrestres em Glons e Semmerzake rastrearam alvos não identificados enquanto dois F-16s da Força Aérea belga obtiveram, momentaneamente, bloqueio de radar em objetos que em seguida aceleraram além dos parâmetros de desempenho convencionais e romperam o contato. O General Wilfried De Brouwer, Chefe de Operações do Estado-Maior da Força Aérea belga, declarou publicamente que a Força Aérea não conseguiu identificar os objetos e que nenhuma nação estrangeira reivindicou responsabilidade pelos sobrevoos. A decisão do governo belga de colaborar abertamente com a SOBEPS e divulgar dados de radar militar a pesquisadores civis permanece uma das respostas oficiais mais transparentes já registradas diante de um fenômeno UAP sustentado.

View file

@ -0,0 +1,69 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-1997-03-13-phoenix-lights
canonical_name: Phoenix Lights
aliases:
- Phoenix Lights
event_class: uap-encounter
date_start: '1997-03-13'
date_end: '1997-03-13'
date_confidence: high
primary_location: Phoenix, Arizona, USA (and southern Arizona)
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: 'On the night of March 13, 1997, a silent V-shaped formation of
lights traversed southern Arizona from roughly 20:00 to 22:00 local time, moving
northward at low altitude over Prescott, the Phoenix metropolitan area, and Tucson,
witnessed by an estimated two thousand or more civilians as well as law enforcement
officers and former military personnel. A second, distinct event—a stationary cluster
of amber lights visible south of Phoenix around 22:00—was later attributed by the
Arizona Air National Guard to LUU-2 illumination flares dropped by A-10 aircraft
during a training exercise at Barry Goldwater Range. The earlier moving formation
received no analogous official explanation: witnesses consistently described an
object estimated at nearly a mile in width, traveling in near-complete silence at
altitudes variously reported between 100 and 3,000 feet, and occluding stars as
it passed overhead. Governor Fife Symington publicly staged a mock ''alien'' press
conference in the days following the incident, but in a 2007 CNN interview he acknowledged
that he personally observed the craft and considered it ''otherworldly.'' The Phoenix
Lights remain among the most extensively documented mass-witness UAP events in American
history.'
narrative_summary_pt_br: 'Na noite de 13 de março de 1997, uma formação silenciosa
em V de luzes cruzou o sul do Arizona entre aproximadamente 20h00 e 22h00 (horário
local), deslocando-se para o norte em baixa altitude sobre Prescott, a região metropolitana
de Phoenix e Tucson, observada por uma estimativa de dois mil ou mais civis, além
de agentes policiais e ex-militares. Um segundo evento distinto — um agrupamento
estacionário de luzes âmbar visíveis ao sul de Phoenix por volta das 22h00 — foi
posteriormente atribuído pela Guarda Aérea Nacional do Arizona a sinalizadores de
iluminação LUU-2 lançados por aeronaves A-10 durante um exercício de treinamento
na Barry Goldwater Range. A formação em movimento, contudo, não recebeu explicação
oficial equivalente: as testemunhas descreveram de forma consistente um objeto estimado
em quase uma milha de largura, deslocando-se em quase completo silêncio a altitudes
reportadas entre 30 e 900 metros, ocultando as estrelas ao passar diretamente sobre
elas. O governador Fife Symington protagonizou uma coletiva de imprensa jocosa nos
dias seguintes ao incidente, simulando revelar um ''alienígena'', mas em entrevista
à CNN em 2007 reconheceu ter presenciado pessoalmente o objeto e o descreveu como
''de outro mundo''. As Phoenix Lights permanecem um dos eventos UAP de testemunho
em massa mais extensivamente documentados na história americana.'
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:40:20Z'
last_lint: '2026-05-18T03:40:20Z'
wiki_version: 0.1.0
---
# Phoenix Lights
## Description (EN)
On the night of March 13, 1997, a silent V-shaped formation of lights traversed southern Arizona from roughly 20:00 to 22:00 local time, moving northward at low altitude over Prescott, the Phoenix metropolitan area, and Tucson, witnessed by an estimated two thousand or more civilians as well as law enforcement officers and former military personnel. A second, distinct event—a stationary cluster of amber lights visible south of Phoenix around 22:00—was later attributed by the Arizona Air National Guard to LUU-2 illumination flares dropped by A-10 aircraft during a training exercise at Barry Goldwater Range. The earlier moving formation received no analogous official explanation: witnesses consistently described an object estimated at nearly a mile in width, traveling in near-complete silence at altitudes variously reported between 100 and 3,000 feet, and occluding stars as it passed overhead. Governor Fife Symington publicly staged a mock 'alien' press conference in the days following the incident, but in a 2007 CNN interview he acknowledged that he personally observed the craft and considered it 'otherworldly.' The Phoenix Lights remain among the most extensively documented mass-witness UAP events in American history.
## Descrição (PT-BR)
Na noite de 13 de março de 1997, uma formação silenciosa em V de luzes cruzou o sul do Arizona entre aproximadamente 20h00 e 22h00 (horário local), deslocando-se para o norte em baixa altitude sobre Prescott, a região metropolitana de Phoenix e Tucson, observada por uma estimativa de dois mil ou mais civis, além de agentes policiais e ex-militares. Um segundo evento distinto — um agrupamento estacionário de luzes âmbar visíveis ao sul de Phoenix por volta das 22h00 — foi posteriormente atribuído pela Guarda Aérea Nacional do Arizona a sinalizadores de iluminação LUU-2 lançados por aeronaves A-10 durante um exercício de treinamento na Barry Goldwater Range. A formação em movimento, contudo, não recebeu explicação oficial equivalente: as testemunhas descreveram de forma consistente um objeto estimado em quase uma milha de largura, deslocando-se em quase completo silêncio a altitudes reportadas entre 30 e 900 metros, ocultando as estrelas ao passar diretamente sobre elas. O governador Fife Symington protagonizou uma coletiva de imprensa jocosa nos dias seguintes ao incidente, simulando revelar um 'alienígena', mas em entrevista à CNN em 2007 reconheceu ter presenciado pessoalmente o objeto e o descreveu como 'de outro mundo'. As Phoenix Lights permanecem um dos eventos UAP de testemunho em massa mais extensivamente documentados na história americana.

View file

@ -0,0 +1,73 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-2004-11-14-nimitz-tic-tac
canonical_name: Nimitz Tic Tac Incident
aliases:
- Nimitz Tic Tac Incident
event_class: uap-encounter
date_start: '2004-11-14'
date_end: '2004-11-14'
date_confidence: high
primary_location: USS Nimitz Carrier Strike Group, off San Diego coast
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: On 14 November 2004, F/A-18F crews from the USS Nimitz Carrier
Strike Group, operating approximately 100 nautical miles southwest of San Diego,
intercepted an unidentified aerial object that USS Princeton radar operator Senior
Chief Kevin Day had been tracking intermittently for several days at altitudes ranging
from 80,000 feet down to sea level. Commander David Fravor and Lt. Commander Jim
Slaight observed a white, oblong craft—roughly 40 feet in length, with no wings,
no exhaust plume, and no visible propulsion—hovering over a roughly 100-meter diameter
patch of disturbed water; when Fravor descended toward it, the object climbed briefly
to meet him, then accelerated to the northwest at a rate that outpaced any known
aircraft of the era, without sonic signature or thermal wake. Senior Chief Day reported
that Princeton's radar reacquired the contact within seconds at the pre-briefed
combat air patrol rendezvous coordinate, approximately 60 miles from the original
intercept position. On a subsequent sortie, Lt. Commander Chad Underwood captured
the object on ATFLIR, producing the infrared footage later designated FLIR1. The
Department of Defense declassified the FLIR1 video in April 2020 and confirmed its
provenance; the ODNI's June 2021 preliminary assessment classified the encounter
as uncharacterized, with no attribution to any known domestic or foreign aerospace
program.
narrative_summary_pt_br: Em 14 de novembro de 2004, pilotos de F/A-18F do Grupo de
Ataque do USS Nimitz, operando a aproximadamente 100 milhas náuticas a sudoeste
de San Diego, interceptaram um objeto aéreo não identificado que o operador de radar
do USS Princeton, o Chefe Sênior Kevin Day, vinha rastreando de forma intermitente
por vários dias, a altitudes entre 24.000 metros e o nível do mar. O Comandante
David Fravor e o Tenente-Comandante Jim Slaight observaram uma aeronave branca e
oblonga — com cerca de 12 metros de comprimento, sem asas, sem rastro de exaustão
e sem sistema de propulsão visível — pairando sobre uma área circular de aproximadamente
100 metros de diâmetro em que a água apresentava agitação anômala; quando Fravor
iniciou a descida em direção ao objeto, ele subiu brevemente em sua direção e então
acelerou para o noroeste a uma velocidade que superava qualquer aeronave conhecida
à época, sem rastro acústico ou térmico. O Chefe Sênior Day relatou que o radar
do Princeton readquiriu o contato em segundos no ponto de patrulha de combate pré-coordenado,
a aproximadamente 60 milhas do local do encontro. Numa saída posterior, o Tenente-Comandante
Chad Underwood registrou o objeto com o sensor ATFLIR, produzindo a filmagem infravermelha
posteriormente designada FLIR1. O Departamento de Defesa americano desclassificou
o vídeo FLIR1 em abril de 2020 e confirmou sua procedência; a avaliação preliminar
do ODNI de junho de 2021 classificou o encontro como não caracterizado, sem atribuição
a qualquer programa aeroespacial doméstico ou estrangeiro conhecido.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:41:12Z'
last_lint: '2026-05-18T03:41:12Z'
wiki_version: 0.1.0
---
# Nimitz Tic Tac Incident
## Description (EN)
On 14 November 2004, F/A-18F crews from the USS Nimitz Carrier Strike Group, operating approximately 100 nautical miles southwest of San Diego, intercepted an unidentified aerial object that USS Princeton radar operator Senior Chief Kevin Day had been tracking intermittently for several days at altitudes ranging from 80,000 feet down to sea level. Commander David Fravor and Lt. Commander Jim Slaight observed a white, oblong craft—roughly 40 feet in length, with no wings, no exhaust plume, and no visible propulsion—hovering over a roughly 100-meter diameter patch of disturbed water; when Fravor descended toward it, the object climbed briefly to meet him, then accelerated to the northwest at a rate that outpaced any known aircraft of the era, without sonic signature or thermal wake. Senior Chief Day reported that Princeton's radar reacquired the contact within seconds at the pre-briefed combat air patrol rendezvous coordinate, approximately 60 miles from the original intercept position. On a subsequent sortie, Lt. Commander Chad Underwood captured the object on ATFLIR, producing the infrared footage later designated FLIR1. The Department of Defense declassified the FLIR1 video in April 2020 and confirmed its provenance; the ODNI's June 2021 preliminary assessment classified the encounter as uncharacterized, with no attribution to any known domestic or foreign aerospace program.
## Descrição (PT-BR)
Em 14 de novembro de 2004, pilotos de F/A-18F do Grupo de Ataque do USS Nimitz, operando a aproximadamente 100 milhas náuticas a sudoeste de San Diego, interceptaram um objeto aéreo não identificado que o operador de radar do USS Princeton, o Chefe Sênior Kevin Day, vinha rastreando de forma intermitente por vários dias, a altitudes entre 24.000 metros e o nível do mar. O Comandante David Fravor e o Tenente-Comandante Jim Slaight observaram uma aeronave branca e oblonga — com cerca de 12 metros de comprimento, sem asas, sem rastro de exaustão e sem sistema de propulsão visível — pairando sobre uma área circular de aproximadamente 100 metros de diâmetro em que a água apresentava agitação anômala; quando Fravor iniciou a descida em direção ao objeto, ele subiu brevemente em sua direção e então acelerou para o noroeste a uma velocidade que superava qualquer aeronave conhecida à época, sem rastro acústico ou térmico. O Chefe Sênior Day relatou que o radar do Princeton readquiriu o contato em segundos no ponto de patrulha de combate pré-coordenado, a aproximadamente 60 milhas do local do encontro. Numa saída posterior, o Tenente-Comandante Chad Underwood registrou o objeto com o sensor ATFLIR, produzindo a filmagem infravermelha posteriormente designada FLIR1. O Departamento de Defesa americano desclassificou o vídeo FLIR1 em abril de 2020 e confirmou sua procedência; a avaliação preliminar do ODNI de junho de 2021 classificou o encontro como não caracterizado, sem atribuição a qualquer programa aeroespacial doméstico ou estrangeiro conhecido.

View file

@ -0,0 +1,71 @@
---
schema_version: 0.1.0
type: entity
entity_class: event
event_id: EV-2017-12-16-aatip-disclosure
canonical_name: AATIP Public Disclosure
aliases:
- AATIP Public Disclosure
event_class: uap-disclosure-event
date_start: '2017-12-16'
date_end: '2017-12-16'
date_confidence: high
primary_location: New York / Washington, D.C., USA
observers: []
uap_objects: []
documented_in: []
total_mentions: 0
documents_count: 0
narrative_summary: On 16 December 2017, the New York Times published a front-page
investigation confirming the existence of the Advanced Aerospace Threat Identification
Program (AATIP), a Pentagon effort funded at approximately $22 million from 2007
to 2012 through appropriations secured by Senator Harry Reid, with primary research
contracts held by Robert Bigelow's Bigelow Aerospace Advanced Space Studies. Luis
Elizondo, who had resigned from the Office of the Secretary of Defense in October
2017, identified himself as the program's former director and provided three declassified
infrared videos—subsequently designated FLIR1, GIMBAL, and GOFAST—documenting U.S.
Navy encounters with objects exhibiting aerodynamic performance outside the parameters
of any known aircraft. The simultaneous release of the footage through To The Stars
Academy of Arts & Science marked the first time the U.S. government acknowledged,
however obliquely, a modern classified program dedicated to UAP study. The Pentagon
confirmed AATIP's existence but disputed claims about its scope and Elizondo's specific
managerial role. The disclosure triggered Senate briefings, prompted the Navy to
formalize UAP reporting channels, and set in motion the legislative sequence that
produced the UAP Task Force in 2020 and the All-domain Anomaly Resolution Office
in 2022.
narrative_summary_pt_br: Em 16 de dezembro de 2017, o New York Times publicou uma
investigação de primeira página confirmando a existência do Advanced Aerospace Threat
Identification Program (AATIP), um programa do Pentágono financiado com aproximadamente
22 milhões de dólares entre 2007 e 2012, viabilizado por verbas articuladas pelo
senador Harry Reid, com os principais contratos de pesquisa entregues à Bigelow
Aerospace Advanced Space Studies, empresa de Robert Bigelow. Luis Elizondo, que
havia se demitido do Escritório do Secretário de Defesa em outubro de 2017, identificou-se
como ex-diretor do programa e disponibilizou três vídeos infravermelhos desclassificados
— posteriormente designados FLIR1, GIMBAL e GOFAST — que registram encontros de
pilotos da Marinha dos EUA com objetos dotados de desempenho aerodinâmico fora dos
parâmetros de qualquer aeronave conhecida. A divulgação simultânea das imagens pela
To The Stars Academy of Arts & Science marcou a primeira vez em que o governo norte-americano
reconheceu, ainda que indiretamente, a existência de um programa classificado moderno
dedicado ao estudo de PANs. O Pentágono confirmou a existência do AATIP, mas contestou
alegações sobre o escopo do programa e o papel gerencial específico de Elizondo.
A revelação desencadeou sessões de briefing no Senado, levou a Marinha a formalizar
canais de reporte de PANs e iniciou o processo legislativo que resultaria no UAP
Task Force em 2020 e no All-domain Anomaly Resolution Office em 2022.
summary_status: curated
summary_confidence: high
enrichment_status: none
external_sources: []
last_ingest: '2026-05-18T03:41:50Z'
last_lint: '2026-05-18T03:41:50Z'
wiki_version: 0.1.0
---
# AATIP Public Disclosure
## Description (EN)
On 16 December 2017, the New York Times published a front-page investigation confirming the existence of the Advanced Aerospace Threat Identification Program (AATIP), a Pentagon effort funded at approximately $22 million from 2007 to 2012 through appropriations secured by Senator Harry Reid, with primary research contracts held by Robert Bigelow's Bigelow Aerospace Advanced Space Studies. Luis Elizondo, who had resigned from the Office of the Secretary of Defense in October 2017, identified himself as the program's former director and provided three declassified infrared videos—subsequently designated FLIR1, GIMBAL, and GOFAST—documenting U.S. Navy encounters with objects exhibiting aerodynamic performance outside the parameters of any known aircraft. The simultaneous release of the footage through To The Stars Academy of Arts & Science marked the first time the U.S. government acknowledged, however obliquely, a modern classified program dedicated to UAP study. The Pentagon confirmed AATIP's existence but disputed claims about its scope and Elizondo's specific managerial role. The disclosure triggered Senate briefings, prompted the Navy to formalize UAP reporting channels, and set in motion the legislative sequence that produced the UAP Task Force in 2020 and the All-domain Anomaly Resolution Office in 2022.
## Descrição (PT-BR)
Em 16 de dezembro de 2017, o New York Times publicou uma investigação de primeira página confirmando a existência do Advanced Aerospace Threat Identification Program (AATIP), um programa do Pentágono financiado com aproximadamente 22 milhões de dólares entre 2007 e 2012, viabilizado por verbas articuladas pelo senador Harry Reid, com os principais contratos de pesquisa entregues à Bigelow Aerospace Advanced Space Studies, empresa de Robert Bigelow. Luis Elizondo, que havia se demitido do Escritório do Secretário de Defesa em outubro de 2017, identificou-se como ex-diretor do programa e disponibilizou três vídeos infravermelhos desclassificados — posteriormente designados FLIR1, GIMBAL e GOFAST — que registram encontros de pilotos da Marinha dos EUA com objetos dotados de desempenho aerodinâmico fora dos parâmetros de qualquer aeronave conhecida. A divulgação simultânea das imagens pela To The Stars Academy of Arts & Science marcou a primeira vez em que o governo norte-americano reconheceu, ainda que indiretamente, a existência de um programa classificado moderno dedicado ao estudo de PANs. O Pentágono confirmou a existência do AATIP, mas contestou alegações sobre o escopo do programa e o papel gerencial específico de Elizondo. A revelação desencadeou sessões de briefing no Senado, levou a Marinha a formalizar canais de reporte de PANs e iniciou o processo legislativo que resultaria no UAP Task Force em 2020 e no All-domain Anomaly Resolution Office em 2022.