disclosure-bureau/docs/adrs/ADR-004-auth-and-rls-evolution.md
Luiz Gustavo eaf282c535
Some checks failed
CI / Web — typecheck + lint + build (push) Failing after 40s
CI / Scripts — Python smoke (push) Failing after 3s
CI / Web — npm audit (push) Failing after 29s
CI / Retrieval — golden set (Recall@5 + MRR) (push) Failing after 3s
W2: rerank opt-in, analyze_image_region tool, RAG eval, graph cleanup, ADRs
- TD#8 hybrid.ts: rerank_strategy {always|when_top_k_gt|never} + threshold
  (default skips rerank for top_k ≤ 15; chat tool uses threshold 10)
- O11 vision.ts + tools.ts: analyze_image_region tool — sharp-crops the
  bbox, claude CLI reads the temp PNG via Read tool, Sonnet vision answers
- TD#12 /graph: SigmaGraph replaces ForceGraphCanvas; react-force-graph-2d
  uninstalled (-37 transitive deps); force-graph-canvas.tsx deleted
- TD#27 messages/route.ts gatherContext slice sizes via CTX_* env vars
- TD#22 tests/rag/: golden.yaml (15 queries) + run.py (Recall@k + MRR +
  negative-pass rate) + baseline.json + CI job in .forgejo/workflows/ci.yml
- docs/adrs/: ADR-001..005 published from systems-atelier deliverables

Verified live on disclosure.top: top_k=5 path skips rerank (6.7s embed-only,
was 12-15s with rerank); rerank=always still available on demand.
First RAG baseline: Recall@5 = 0.2083, MRR = 0.25, Negative pass = 1.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 19:20:09 -03:00

106 lines
4.5 KiB
Markdown

---
adr: ADR-004
title: Auth model evolution — manter Supabase GoTrue + RLS publico-read; novas tabelas case/* write-only por investigator role
status: accepted
date: 2026-05-23
deciders: sa-principal, sa-security-engineer, sa-platform-lead
project: disclosure-bureau
---
## Context
Modelo de auth atual:
- **GoTrue email magic-link**, SMTP Spacemail, `MAILER_AUTOCONFIRM=false`.
- **profiles.role ∈ {user, admin, suspended}**, com `budget_cap_usd`, `daily_quota`.
- **Sessoes `is_public=true`** sao readable por anon (compartilhaveis via share_token UUID).
- **RLS publico-read** em chunks/entities/documents/entity_mentions/relations.
- **service_role key em env do container web** (necessario para usage_events INSERT bypass RLS + admin tasks). F5 do security audit.
Decisao de fronteira na W3: novas tabelas (`hypotheses`, `evidence`, `contradictions`, `witnesses`, `gaps`, `residual_uncertainties`, `investigation_jobs`) — quem escreve?
## Options
1. **service_role escreve tudo** (mesmo padrao atual). Investigator-runtime usa service_role.
2. **Role intermediario `investigator`** com permissoes minimas. Investigator-runtime usa esse role; web nao.
3. **Investigator usa Postgres direto sem RLS**: bypass desde o nivel de conexao.
## Decision
**Opcao 2: Role `investigator` granular.**
Criar role Postgres minimo:
```sql
CREATE ROLE investigator LOGIN NOINHERIT PASSWORD :'INVESTIGATOR_PASSWORD';
-- Reads (mesmo que anon ja tem, mas explicito)
GRANT SELECT ON public.chunks, public.entities, public.entity_mentions,
public.relations, public.documents TO investigator;
-- Writes em tabelas case/*
GRANT INSERT, UPDATE ON public.hypotheses, public.evidence, public.contradictions,
public.witnesses, public.gaps, public.residual_uncertainties,
public.investigation_jobs TO investigator;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO investigator;
-- Negar tudo o resto
REVOKE ALL ON public.profiles, public.chat_sessions, public.messages,
public.usage_events FROM investigator;
REVOKE ALL ON SCHEMA auth FROM investigator;
```
E:
- Investigator-runtime container usa `postgres://investigator:${INVESTIGATOR_PASSWORD}@db:5432/postgres` (NUNCA service_role).
- Web service continua com `postgres://postgres:...` (acesso amplo necessario para createServiceClient nos pontos especificos).
- Em W5, **avaliar reducao de uso de service_role no web** criando role `web_service` similar com permissoes ainda menores que `postgres`.
**RLS nas novas tabelas:**
```sql
ALTER TABLE public.hypotheses ENABLE ROW LEVEL SECURITY;
CREATE POLICY hypotheses_public_read ON public.hypotheses FOR SELECT USING (TRUE);
GRANT SELECT ON public.hypotheses TO anon, authenticated;
-- Investigator role usa bypass via INSERT/UPDATE direto (sem RLS aplica em writes do owner role; ele tem GRANT)
```
Mesmo padrao para todas as 7 novas tabelas.
**Modificacoes nas existentes:**
- `relations` ganha RLS (F4). Hoje sem.
- `messages.citations JSONB` continua mas adicionar coluna `hypothesis_id REFERENCES public.hypotheses(hypothesis_id)` se chat citar hipoteses geradas.
**Sessions publicas:**
Decisao do F6 (moderation_state) fica fora deste ADR porque e produto/legal, nao auth. Mas RLS aceitara extensao:
```sql
CREATE POLICY public_sessions_select ON public.chat_sessions
FOR SELECT USING (
auth.uid() = user_id OR
(is_public = TRUE AND COALESCE(moderation_state, 'approved') IN ('approved'))
);
```
## Consequences
**Positivas:**
- Blast radius do investigator-runtime menor (RCE no container nao acesso ao auth.users / profiles).
- Auditabilidade granular: cada INSERT em hypotheses/evidence tem `created_by = '<detective>@detective'` + role Postgres `investigator`.
- Aderencia a least-privilege.
**Negativas:**
- 1 password adicional para gerir (`INVESTIGATOR_PASSWORD`). Mitigacao: docker secrets em W5.
- Investigator nao pode ler `messages` (intencional) — se algum dia detetive precisar contexto de sessao do user, precisa de hand-off explicito (ex: chat passar `userTurn` no `payload` do job).
## Verification
- `\du investigator` confirma role + permissoes.
- Teste manual: investigator role tenta `SELECT FROM auth.users` -> erro permission denied.
- Teste manual: investigator role faz `INSERT INTO hypotheses` -> sucesso.
- Service_role uses count auditavel: grep `createServiceClient` no `web/`.
## References
- `security-audit-report.md` F4, F5, F6
- `agentic-layer-spec.md` secao 3.2 (container env)
- `infra/disclosure-stack/init-db.sql` (roles bootstrap atual)