--- 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'` + 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)