Plano Mestre — Migração de createSupabaseSystem() para RLS
Status: Em execução. Fases 0–5 ✅ concluídas. Próxima: Fase 6 (
gestao-alunos). Escopo: ~20 Edge Functions autenticadas usandocreateSupabaseSystem()para CRUD padrão.
Objetivo: Eliminar bypass de RLS não justificado, ativando policies que hoje são código morto.
1. Reclassificação Real (Audit Corrigido)
O audit original listou ~20 funções. Após leitura do código real, a situação é diferente:
Grupo A — Dead Code (importam createSupabaseSystem mas NÃO usam)
| Função | Situação Real |
|---|---|
especialista-olimpiadas | Cria supabaseSystem mas todas as queries usam supabase (RLS). Dead code. |
especialista-headers | Idem — cria mas não usa. Dead code. |
especialista-cursos | Idem. |
escola-dashboard | Cria supabaseSystem mas todas as queries usam supabase (RLS). Dead code. |
eventos-calendario | Idem. |
eventos-calendario-trial | Idem. |
Ação: Remover imports/instanciações mortas. Zero risco. Nenhuma policy nova. ✅ Executado na Fase 0.
Grupo A.2 — Reclassificados para Grupo B (audit original errou)
| Função | Plano original dizia | Realidade encontrada |
|---|---|---|
admin-dashboard | Dead code | Usa ativamente — queries em portal_rate_metrics, escolas |
admin-usuarios | Dead code | Usa ativamente — queries em usuario_papeis, usuarios |
admin-usuarios-escola | Dead code | Usa ativamente — queries em papeis, usuario_papeis, usuarios |
Ação: Movidas para Grupo B. Serão tratadas nas fases seguintes com policies RLS.
Grupo B — Uso Real de supabaseSystem para CRUD
| Função | Gravidade | O que usa supabaseSystem para | Tabelas |
|---|---|---|---|
gestao-alunos + helpers | ALTA | 30+ operações CRUD | alunos, turmas, escolas, aluno_turma_historico, aluno_responsaveis, importacao_alunos_sessoes |
gestao-turmas | ALTA | Todo CRUD de turmas + séries | turmas, series_escolares, escolas |
admin-feature-flags | MÉDIA | CRUD de canary groups/flags | canary_groups, canary_group_escolas, canary_group_usuarios, feature_flag_canary |
admin-dashboard | MÉDIA | Queries em portal_rate_metrics, escolas | portal_rate_metrics, escolas |
admin-usuarios | MÉDIA | Queries em usuario_papeis, usuarios | usuario_papeis, usuarios |
admin-usuarios-escola | MÉDIA | CRUD em papeis, usuario_papeis, usuarios | papeis, usuario_papeis, usuarios |
change-password | MÉDIA | SELECT senha_hash + INSERT notificações | usuarios, notificacoes |
set-password | MÉDIA | SELECT/UPDATE senha_hash + INSERT histórico | usuarios, senha_historico |
notificacoes (create_internal, create_batch) | MÉDIA | INSERT notificações + query usuario_papeis | notificacoes, usuario_papeis |
escola-pagamentos | BAIXA | INSERT notificações (complementar) | notificacoes |
coordenador-videos | BAIXA | UPDATE visualizações (1 operação) | cursos_videos |
gestao-resultados (import + cleanup) | MÉDIA | Sessões import + cleanup mural_liberacoes | importacao_resultados_sessoes, configuracoes_fase_nivel, mural_liberacoes |
2. Crítica da Sequência Original
A sequência proposta pelo usuário tinha problemas identificados:
Problema 1: admin-dashboard, admin-usuarios e admin-usuarios-escola são falsos positivos — não usam supabaseSystem de verdade. Gastar 3 ciclos de Cenário B neles é desperdício.
Problema 2: change-password e set-password acessam usuarios.senha_hash — campo que não deve ser acessível via RLS (expõe hash de senha via PostgREST). São casos onde supabaseSystem é possivelmente legítimo, não dívida técnica.
Problema 3: escola-pagamentos e coordenador-videos usam supabaseSystem para 1 operação cada. São micro-correções, não Cenário B completo.
3. Sequência Final Validada
Fase 0 — Limpeza de Dead Code (1 entrega) ✅ CONCLUÍDA
Removidos imports/instanciações mortas de createSupabaseSystem() em 6 funções:
especialista-olimpiadas,especialista-headers,especialista-cursosescola-dashboard,eventos-calendario,eventos-calendario-trial
Nota: 3 funções originalmente listadas (admin-dashboard, admin-usuarios, admin-usuarios-escola) foram reclassificadas como Grupo B após análise real do código revelar uso ativo de supabaseSystem.
Risco: Zero. Removeu código morto. Nenhuma policy nova.
Fase 1 — admin-feature-flags (1 entrega) ✅ CONCLUÍDA
Justificativa de ser primeiro: Admin-only, tabelas de flags já têm SELECT policies, risco operacional baixo, CRUD real com supabaseSystem.
Desafio: Precisa de INSERT/UPDATE/DELETE policies para canary_groups, canary_group_escolas, canary_group_usuarios, feature_flag_canary — admin-only.
Dependências: Nenhuma.
Entregue em 2026-04-07:
- 12 policies RLS admin-only criadas (UPDATE feature_flags; INSERT/UPDATE/DELETE canary_groups; SELECT/INSERT/DELETE canary_group_escolas e canary_group_usuarios; INSERT/DELETE feature_flag_canary)
- Código migrado:
createSupabaseSystem()→createSupabaseClient(req), todas as referênciassupabaseSystem→supabase - Testes de segurança criados em
security.test.ts(4 cenários: coordenador bloqueado 403, admin permitido 200) - Testes de contrato baseline: 5/5 passando
Fase 2 — notificacoes (1 entrega) ✅ CONCLUÍDA
Justificativa: Função mista — list/mark_read já usam RLS. Apenas create_internal e create_batch usam supabaseSystem. Migração parcial e simples.
Desafio: Admin cria notificações para usuários de outras escolas — precisa policy de INSERT com principal_role = 'administrador'.
Dependência: Nenhuma.
Entregue em 2026-04-07:
- 1 policy RLS criada:
notificacoes_admin_insert(FOR INSERT, admin-only, WITH CHECK) - Código migrado: removidas 2 instanciações de
createSupabaseSystem(), ~5 referênciassupabaseSystem→supabase - Import de
createSupabaseSystemremovido do arquivo - Testes de contrato baseline: 5/5 passando
- Testes de segurança criados em
security.test.ts(4 cenários: coordenador bloqueado 403, admin permitido 200)
Fase 3 — escola-pagamentos + coordenador-videos ✅
Entregue em 2026-04-07:
coordenador-videos: removidocreateSupabaseSysteme fallback UPDATE redundante. RPCincrement_video_view(SECURITY DEFINER) chamado viasupabase(RLS client). Zero policies necessárias.escola-pagamentos: migração parcial.recalculate_due_datesmigrado para RLS com 2 policies novas (escola_assinaturas_escola_update_own,escola_faturas_escola_update_own).request_plan_changemantémsupabaseSystem— reclassificado como ADR Categoria 2 (notificação cross-tenant para admins).- 4 testes de contrato criados para
coordenador-videos(CORS, 401, JSON shape, anti-leak). 8/8 testes passando. - Documentação atualizada: ADR, RLS_POLICIES, CATALOG, PLANO.
Fase 4 — change-password + set-password + reset-password ✅ CONCLUÍDA
Entregue em 2026-04-07:
- 4 RPCs SECURITY DEFINER criadas:
get_own_password_hash,update_own_password_hash,get_own_password_history,save_to_password_history - Guard de ownership:
(auth.jwt()->>'sub')::uuid != p_usuario_id(padrão OLP, nãoauth.uid()) set-password: 100% migrado — zerocreateSupabaseSystem. UsacreateSupabaseClient(req)+ RPCs.change-password: parcial — operações de senha via RPCs.supabaseSystemmantido apenas para HIBP fire-and-forget (INSERTnotificacoes— ADR Cat. 2).reset-password: idemchange-password.password-history-helper.ts: refatorado de queries diretas parasupabase.rpc().- Testes existentes: 14/14 passando (4 auth-password + 10 modal-definir-senha).
Fase 5 — gestao-turmas (1 entrega — Cenário B completo) ✅ CONCLUÍDA
Entregue em 2026-04-07:
- Decisão arquitetural: Admin removido do fluxo — turmas são conteúdo pedagógico, fora do escopo admin (menor privilégio). Nenhuma policy admin criada.
- Policy legada
turmas_admin_readonlyremovida (DROP). createSupabaseSystem()eliminado — todas as operações usamcreateSupabaseClient(req)(RLS ativo).listSeriesmigrado paracreateSupabasePublic()(tabela pública).requireRolerestrito a['escola', 'coordenador'].escola_idoverride removido — sempreuser.escola_id(zero-trust).- Validação zero-trust:
.maybeSingle()em INSERT/UPDATE/DELETE com check de resultado vazio → 403. - Testes de contrato endurecidos: 4/4 passando (CORS 204 + credentials, 401 auth guard, Content-Type JSON, anti-leak).
- Documentação atualizada: ADR, RLS_POLICIES, PLANO.
Fase 6 — gestao-alunos (1 entrega — Cenário B completo, maior da lista)
Justificativa: Mesmo padrão school-scoped que gestao-turmas mas 4 arquivos de helpers, 30+ operações, 6+ tabelas.
Dependência: Fase 5 validou o padrão school-scoped sem admin. Avaliar se admin precisa de acesso a alunos (decisão a documentar).
Tabelas: alunos, turmas, escolas, aluno_turma_historico, aluno_responsaveis, importacao_alunos_sessoes.
Fase 7 — gestao-resultados (import + mutations cleanup)
Justificativa: Último. As queries e mutations normais já usam RLS. Apenas import sessions e cleanup usam supabaseSystem.
Tabelas: importacao_resultados_sessoes, configuracoes_fase_nivel, mural_liberacoes.
Nota: Import sessions podem ser reclassificadas como Categoria 2 (fire-and-forget background) no ADR.
4. Template de Entrega por Função (Cenário B)
Cada função com uso real de supabaseSystem segue estas fases:
Fase 1: Raio-X
- Inventário: arquivos, tabelas, operações (SELECT/INSERT/UPDATE/DELETE)
- Policies existentes no banco (query pg_policies)
- Cobertura de testes atual (index.test.ts?)
- Desvios de padrão vs docs/
→ Aprovação
Fase 2: Testes Baseline
- Testes de contrato Deno (CORS, auth guard, response shape)
- Correção de desvios identificados (se houver)
→ Aprovação
Fase 3: Planejamento da Migração
- Quais policies RLS precisam ser criadas (SQL exato)
- Quais linhas mudam de supabaseSystem → supabase
- MIGRATION_SAFETY_PROTOCOL Fase 1
→ Aprovação
Fase 4: TDD — Testes + Migration SQL
- Escrever testes de segurança IDOR (se school-scoped)
- SQL completo das policies
- MIGRATION_SAFETY_PROTOCOL Fase 2 + Checklist
→ Aprovação
Fase 5: Execução
- Aplicar migration
- Alterar código (supabaseSystem → supabase)
- Rodar testes — todos devem passar
- MIGRATION_SAFETY_PROTOCOL Fase 3
Fase 6: @audit + Documentação
- AUDIT_CHECKLIST completo na função
- Atualizar ADR_SERVICE_ROLE_USAGE.md (mover função de "dívida" para "migrado")
- Atualizar RLS_POLICIES.md com as novas policies criadas
- Verificar DATABASE_SCHEMA.md — novas policies ou RPCs exigem atualização?
- Verificar EDGE_FUNCTIONS_CATALOG.md — mudança estrutural na função exige atualização?
- Seguir checklist completo de docs/development/DOCUMENTATION_MAINTENANCE.md5. Dependências entre Funções
Fase 0 (dead code) ← sem dependência
Fase 1 (admin-flags) ← sem dependência
Fase 2 (notificacoes) ← sem dependência
Fase 3 (pagamentos) ← depende de policy INSERT notificacoes (Fase 2)
Fase 3 (coord-videos) ← sem dependência (usa RPC existente)
Fase 4 (passwords) ← sem dependência (RPCs novos)
Fase 5 (turmas) ← sem dependência ✅ concluída (school-scoped, sem admin)
Fase 6 (alunos) ← depende de Fase 5 (valida padrão school-scoped)
Fase 7 (resultados) ← sem dependência técnica6. Critério de Done por Função
Uma função é declarada migrada quando:
- Zero uso de
createSupabaseSystem()fora deregistrarLog()e categorias legítimas do ADR - Policies RLS cobrindo SELECT/INSERT/UPDATE/DELETE para cada tabela acessada
- Testes de contrato Deno passando (CORS, auth, response shape)
- Teste IDOR cross-escola passando (se school-scoped)
@auditsem itens 🔴- ADR_SERVICE_ROLE_USAGE.md atualizado (linha movida de dívida para migrado)
- RLS_POLICIES.md atualizado com novas policies
- DATABASE_SCHEMA.md e EDGE_FUNCTIONS_CATALOG.md verificados e atualizados se necessário
- Checklist de DOCUMENTATION_MAINTENANCE.md seguido
7. Estimativa de Complexidade
| Fase | Função | Complexidade | Policies novas |
|---|---|---|---|
| 0 | 6 funções (dead code) | Trivial | 0 |
| 1 | admin-feature-flags | Média | ~12 (CRUD admin em 4 tabelas) |
| 2 | notificacoes | Baixa | ~4 (INSERT admin + self-access) |
| 3 | pagamentos + videos | Trivial | 0-2 (reusa policies) |
| 4 | passwords | Média | 0 policies, 4 RPCs novos |
| 5 | gestao-turmas | Alta | 0 novas, 1 removida (admin readonly) |
| 6 | gestao-alunos | Muito Alta | ~20+ (6 tabelas, school-scoped + admin) |
| 7 | gestao-resultados | Média | ~6 (sessions + config) |
Referências
- ADR_SERVICE_ROLE_USAGE.md — Categorias de uso legítimo
- RLS_DESIGN_GUIDE.md — Padrões obrigatórios de policies
- RLS_POLICIES.md — Inventário atual
- DEV_WORKFLOW.md — Cenário B (atualização de feature)
- MIGRATION_SAFETY_PROTOCOL.md — Protocolo de 3 fases
- AUDIT_CHECKLIST.md — @audit pós-entrega
- DOCUMENTATION_MAINTENANCE.md — Regras de documentação