Checklist de Auditoria (@audit) — v3.0
Checklist completo para auditoria de código. Invocar com a palavra-chave
@audit— Última atualização: 2026-04-05
Metodologia de Execução
Ordem de auditoria
- Backend (Edge Functions, shared helpers) → onde erros são mais críticos
- Frontend (hooks, componentes, lib/) → qualidade e UX
- Documentação (docs/) → fidelidade ao código real
Escopo
- Auditar o domínio em trabalho + domínios adjacentes impactados
- Para auditoria completa: varrer todos os 16 domínios
- Cada domínio tem "Como buscar" com patterns exatos para
search_files/grep
Severidade
| Nível | Significado | Ação |
|---|---|---|
| 🔴 Crítico | Segurança, perda de dados, falha silenciosa | Bloqueia entrega |
| 🟡 Atenção | Desvio de padrão, debt técnico | Corrigir na sprint |
| 🟢 Recomendação | Melhoria opcional | Backlog |
PARTE A — QUALIDADE DE CÓDIGO
1. Logging
O que verificar
- [ ] Todo Create/Update/Delete chama
registrarLog()comreq - [ ] Reads NÃO logam (nunca
registrarLogem SELECT) - [ ] Ação segue formato
<modulo>.<operacao>lowercase - [ ] Diff usa
gerarAlteracoes(antes, depois)para updates - [ ] Ação cadastrada em
src/constants/log-actions.ts - [ ]
reqpassado como parâmetro (geolocalização via Cloudflare headers)
Como buscar
# Verificar que CUD tem logging
search_files: "\.insert\(|\.update\(|\.delete\(" em supabase/functions/
→ Para cada match, verificar se há registrarLog() na mesma action
# Verificar reads sem logging indevido
search_files: "registrarLog" em supabase/functions/
→ Confirmar que nenhum está em ação de leitura (list, get, buscar)
# Ações sem cadastro
search_files: "registrarLog.*acao:" em supabase/functions/
→ Extrair valores de ação e cruzar com src/constants/log-actions.tsErros recorrentes
reqfaltando noregistrarLog()→ geolocalização não funciona- Ação não cadastrada em
log-actions.ts→ filtro de logs quebrado gerarAlteracoesnão usado em updates → diff vazio no log
2. Incidentes
O que verificar
- [ ] Falhas técnicas (não de input do usuário) têm ação
.erro/.falha_* - [ ] Ação de erro está na lista monitorada (
CRON_JOBS_REGISTRY.errorActionsouacoesExtras) - [ ] Fluxo normal do usuário NÃO está na lista de incidentes
- [ ]
detalhesdo log tem campo de erro acessível porextrairErroIncidente
Como buscar
# Ações de erro não monitoradas
search_files: "\.erro|\.falha_|\.critico" em supabase/functions/
→ Cruzar com lista em admin-incidentes.tsx (acoesExtras + CRON_JOBS_REGISTRY)
# Fluxo normal classificado como incidente (falso positivo)
→ Verificar lista monitorada: ações como "escola.create" NÃO devem estar láRef: docs/operations/INCIDENT_POLICY.md
3. RBAC e Permissões
O que verificar
- [ ]
requireRole()antes de operações sensíveis com papel correto - [ ] Papéis sem tela (pedagogico, professor, marketing) bloqueados em 4 camadas
- [ ]
useMyPermissionsfiltra sidebar corretamente - [ ] Novas permissões adicionadas ao enum
permissao_arease necessário - [ ] Ownership check: usuário só acessa dados da sua escola (
escola_iddo JWT) - [ ] 🔴 Permissões escopadas por
papel_id: toda leitura/escrita emusuarios_escola_permissoeseusuarios_escola_sub_permissoesDEVE filtrar porpapel_id— nunca sóusuario_id + escola_id - [ ] 🔴 Seed de permissões por vínculo: ao criar vínculo,
seedPermissionsForRolerecebepapel_idexplícito - [ ] 🔴 Remoção de permissões isolada: ao remover vínculo,
removePermissionsForRoledeleta APENAS permissões dopapel_idremovido — nunca de outro papel na mesma escola - [ ] Permissões mínimas obrigatórias: coordenador→
agenda, diretor→painel_geral - [ ] Lazy seed de sub-permissões: se
usuarios_escola_sub_permissoesestá vazio para o par (usuario_id, escola_id, papel_id), backend ativa todas por padrão
Como buscar
# Edge functions sem requireRole
search_files: "case '" em supabase/functions/ (actions de switch)
→ Para cada action CUD, verificar se há requireRole() antes
# Papéis bloqueados
search_files: "pedagogico|professor|marketing" em supabase/functions/
→ Devem aparecer apenas em bloqueios, nunca como papéis permitidos
# Permissões sem escopo papel_id (CRÍTICO desde Abr/2026)
search_files: "usuarios_escola_permissoes" em supabase/functions/
→ Todo SELECT/INSERT/DELETE DEVE ter .eq('papel_id', ...)
→ Ausência de papel_id = colisão entre papéis na mesma escola
# Seed/remove sem papel_id
search_files: "seedPermissionsForRole|removePermissionsForRole" em supabase/functions/
→ Ambos DEVEM receber papel_id como parâmetroErros recorrentes
- Permissões escopadas só por
usuario_id + escola_id→ diretor e coordenador na mesma escola colidem (Caso 7 de PROBLEM_SOLVING.md) get_permissoes_usuariosempapel_id→ retorna permissões do primeiro vínculo encontrado, não do badge selecionadoupdate_permissoes_usuariosempapel_id→ salva contra papel errado, UI silenciosa- Frontend passa
papel_id: ''no mapeamento de badges → backend não encontra vínculo, falha silenciosa
4. RLS — Silent Failure
O que verificar
- [ ] Tabelas novas têm policies de SELECT, INSERT, UPDATE, DELETE
- [ ] 🔴 Silent failure tratada: após
.update()e.delete(), verificardata.length === 0ou usar.select()para confirmar - [ ] Sem uso de
service_roleondecreateSupabaseClient(req)basta - [ ]
createSupabaseSystem()usado apenas para logging, auth, crons
Como buscar
# Updates/deletes sem verificação de resultado
search_files: "\.update\(|\.delete\(" em supabase/functions/
→ Para cada match, verificar se há .select() encadeado OU verificação de count/data
# Uso indevido de createSupabaseSystem
search_files: "createSupabaseSystem" em supabase/functions/
→ Deve aparecer APENAS em logging-helper.ts, auth-helpers.ts, cron jobs
→ Qualquer outro uso é 🔴 CRÍTICO
# Inline createClient (PROIBIDO)
search_files: "createClient\(" em supabase/functions/
→ Deve aparecer APENAS em supabase-client.ts (o shared helper)
→ Qualquer outro arquivo é 🔴 CRÍTICOErros recorrentes
.update({}).eq('id', x)sem.select()→ RLS bloqueia silenciosamente, retorna{ data: null, error: null }createClientinline em edge function → bypassa padrão de segurançacreateSupabaseSystem()usado para operações de usuário → bypassa RLS
5. Edge Function — Estrutura
O que verificar
- [ ]
verify_jwt = falsenoconfig.toml(auth é via cookie, não JWT do Supabase) - [ ]
handleCorsPrelight(req)no início - [ ]
getCorsHeaders(req)em TODAS as respostas (incluindo erros e catch) - [ ] Ações públicas ANTES do auth check
- [ ] Ações autenticadas DEPOIS do
extractAuthenticatedUser(req) - [ ] Formato de resposta:
{ success, data }ou{ success, message } - [ ] Frontend usa
invokeAction/invokeEdge(nuncafetchdireto)
Como buscar
# Respostas sem CORS headers (especialmente em catch)
search_files: "new Response\(" em supabase/functions/
→ Cada Response DEVE ter getCorsHeaders(req) nos headers
# Frontend com fetch direto para edge functions
search_files: "fetch\(.*supabase.*functions" em src/
→ Deve ser ZERO (usar invokeAction/invokeEdge)
# invokeAction não usado
search_files: "supabase\.functions\.invoke" em src/
→ Deve ser ZERO (usar invokeAction/invokeEdge de edge-function.ts)Erros recorrentes
catchblock retornaResponsesemgetCorsHeaders(req)→ CORS error no browser- Frontend usa
fetch()direto → não passa cookies, auth falha extractAuthenticatedUserchamado antes de actions públicas → público não funciona
6. React Query — Migração
O que verificar
- [ ]
useQuerycomstaleTimedefinido (nunca default 0) - [ ]
queryKeyinclui todos os filtros/parâmetros relevantes - [ ] Mutations usam
invalidateQueriesnoonSuccess - [ ] 🔴 Sem anti-padrão
useState([]) + useEffectpara fetch - [ ]
enabledusado quando query depende de parâmetro que pode ser undefined - [ ] 🔴
enabledNÃO depende de dado que só vem do resultado da própria query (deadlock circular)
Como buscar
# Anti-padrão useState + useEffect para fetch (LEGADO)
search_files: "useState\(\[\]\)" em src/hooks/
→ Se acompanhado de useEffect com invokeAction/invokeEdge → hook legado, migrar
# staleTime faltando
search_files: "useQuery\(" em src/hooks/
→ Cada useQuery DEVE ter staleTime explícito
# queryKey incompleta
search_files: "queryKey:" em src/hooks/
→ Verificar que inclui todos os parâmetros de filtro (escolaId, faseId, etc.)
# Mutation sem invalidação
search_files: "useMutation\(" em src/hooks/
→ Cada mutation DEVE ter onSuccess com invalidateQueries
# Deadlock circular em enabled
search_files: "enabled:" em src/hooks/
→ Se enabled depende de estado que só é setado no onSuccess/data da MESMA query → 🔴 deadlockTabela de staleTime por categoria
| Categoria | staleTime | Exemplos |
|---|---|---|
| Dashboards | 5 min | admin-dashboard-metrics |
| Listas CRUD | 5 min | alunos, turmas, usuários |
| Configurações | 10 min | escola-dados, mural-config |
| Dados quase estáticos | 30 min | olimpiadas, escolas-admin, papéis |
| Feature flags | 10 min | feature-flags |
Hooks legados restantes (rastrear migração)
Consultar docs/development/REACT_QUERY_CACHE.md §8 para lista atualizada.
7. Error Handling — Toasts (Frontend)
O que verificar
- [ ] 🔴 Sem
import { toast } from 'sonner'— usarolpToastde@/components/ui/use-olp-toast - [ ] 🔴 Sem
error.messagecru em toasts — usargetUserFriendlyError(error) - [ ] 🔴 Sem
alert()— usarolpToast - [ ] Mensagens de erro genéricas para o usuário (sem stack traces, sem constraint names)
- [ ] Erros técnicos logados via
console.errorpara debug
Como buscar
# Import direto do Sonner (PROIBIDO)
search_files: "from 'sonner'" em src/components/
→ Deve ser ZERO (exceto sonner.tsx que é o wrapper)
# error.message cru em toast
search_files: "description: (err|error)\.message" em src/
→ Deve ser ZERO — usar getUserFriendlyError(error)
# alert() no código
search_files: "alert\(" em src/components/
→ Deve ser ZERO
# Toast sem getUserFriendlyError em catch blocks
search_files: "catch.*\{" em src/components/ e src/hooks/
→ Verificar que erros passam por getUserFriendlyError antes de exibirErros recorrentes
toast.error('Erro', { description: error.message })→ vaza constraint name do Postgresimport { toast } from 'sonner'→ perde padronização de estilo/ícone- Enum leak: backend retorna
"duplicate key"e frontend exibe literalmente - Try/catch sem toast → erro silencioso, usuário não sabe o que aconteceu
8. Error Handling — Backend
O que verificar
- [ ] Todo
insert/update/deleteverifica{ error }no retorno - [ ] Erros de operações críticas (que barram o fluxo) retornam HTTP 400/500
- [ ] Erros de operações complementares (logging, cache) logam mas NÃO barram
- [ ]
detalhesdo log de erro tem informação suficiente para debug
Como buscar
# Insert/update/delete sem verificação de erro
search_files: "\.insert\(|\.update\(|\.delete\(" em supabase/functions/
→ Cada operação DEVE ter: const { data, error } = await ... seguido de if (error)
# Erros complementares que barram fluxo (falso bloqueio)
→ Verificar se falha de registrarLog ou cache geo bloqueia a response principal
→ Deve ser APENAS console.error, não throwTemplate de verificação
// ✅ CORRETO — Operação crítica
const { data, error } = await supabase.from('tabela').insert(dados).select();
if (error) {
await registrarLog(supabaseSystem, { ... acao: 'modulo.erro', detalhes: { error: error.message } }, req);
return new Response(JSON.stringify({ success: false, message: 'Erro ao criar registro' }),
{ status: 400, headers: getCorsHeaders(req) });
}
// ✅ CORRETO — Operação complementar (não barra)
try {
await registrarLog(supabaseSystem, { ... }, req);
} catch (logErr) {
console.error('Falha ao registrar log:', logErr);
// NÃO retorna erro — operação principal já foi concluída
}PARTE B — PERFORMANCE
9. React Query Cache
O que verificar
- [ ]
staleTimedefinido e compatível com a tabela da seção 6 - [ ]
refetchOnWindowFocusnão estátruesem motivo (React Query v5 default étrue) - [ ] Queries com
enabled: falsequando parâmetros são undefined/null - [ ] Sem queries duplicadas (mesmo dado buscado por hooks diferentes)
- [ ]
placeholderDataouinitialDatausado quando faz sentido (UX de loading)
Como buscar
# Queries sem staleTime
search_files: "useQuery\(\{" em src/hooks/
→ Verificar presença de staleTime em cada uma
# Queries que recarregam na troca de aba sem necessidade
search_files: "refetchOnWindowFocus" em src/hooks/
→ Verificar se faz sentido para o tipo de dado10. Query Chunking
O que verificar
- [ ]
.in()com arrays que podem ter >100 itens usa chunking - [ ] Queries com
SELECT *desnecessário (buscar apenas campos usados) - [ ] Paginação implementada em listagens grandes (>100 itens esperados)
Como buscar
# .in() sem chunking
search_files: "\.in\(" em supabase/functions/
→ Verificar se o array fonte pode crescer além de 100
# SELECT * desnecessário
search_files: "\.select\(\)" em supabase/functions/ (select sem campos)
→ Preferir .select('campo1, campo2') para reduzir payload
# Listagens sem paginação
search_files: "\.select\(" em supabase/functions/ com action "list" ou "listar"
→ Verificar se há .range() ou .limit()10.5 Batching de Requisições (batch_init)
O que verificar
- [ ] Telas com 3+ requests paralelos no mount avaliam consolidação em
batch_init - [ ] Backend usa
Promise.allpara paralelizar queries internas (não sequenciais) - [ ] Resposta única com todos os dados necessários:
{ olimpiadas, stats, config } - [ ] Overhead repetido de auth/feature-flag/CORS eliminado
Como buscar
# Componentes com 3+ useQuery para o mesmo backend
search_files: "useQuery\(" em src/hooks/
→ Se um hook tem 3+ useQuery para a mesma Edge Function → candidato a batch_init
# Edge Functions sem batch_init em telas pesadas
→ Verificar telas: Resultados, Mural, Dashboard, Mensagens
→ Se fazem 3+ requests separados → implementar batch_initOverhead por request
Auth check + feature flag check + CORS = ~100-300ms de overhead cada. Com 4 requests paralelos = ~400-1200ms desperdiçados. batch_init reduz para ~100-300ms total.
Exemplos implementados
| Tela | Action | Consolida |
|---|---|---|
| Resultados | batch_init_resultados | listagem + stats + stats_agregadas |
| Mural | batch_init | listagem + olimpíadas + liberações + config |
10.6 N+1 Query Detection
O que verificar
- [ ] 🟡 Sem
.from('tabela').select()dentro defor/map/forEachem Edge Functions - [ ] Substituído por
.in('campo', arrayDeIds)comqueryInChunksse >100 IDs - [ ]
Promise.allusado para queries independentes (nãoawaitsequencial em loop)
Como buscar
# N+1 em loop
search_files: "for.*await.*\.from\(" em supabase/functions/
→ Cada match é provável N+1 — substituir por .in() consolidado
# await sequencial em loop (variante)
search_files: "for \(const.*of.*\).*\{" em supabase/functions/
→ Se contém await dentro do loop → candidato a Promise.all ou .in()Exemplo de correção
// ❌ N+1 — O(n) queries
for (const olp of olimpiadas) {
const { data } = await supabase.from('fases').select().eq('olimpiada_id', olp.id);
}
// ✅ Consolidado — O(1) query
const ids = olimpiadas.map(o => o.id);
const { data } = await supabase.from('fases').select().in('olimpiada_id', ids);10.7 Hook Stability (useCallback)
O que verificar
- [ ] 🔴 Funções retornadas por hooks custom que podem ser usadas como deps de
useEffectDEVEM teruseCallback - [ ] Dependências do
useCallbacksão estáveis (queryClient, mutation, refetch — não dados reativos) - [ ]
useEffectde mount redundante removido quando React Query já auto-carrega - [ ] Sem
useEffectcom funções instáveis como dependências
Como buscar
# Funções retornadas por hook sem useCallback
search_files: "return \{" em src/hooks/
→ Para cada hook, verificar se funções no retorno têm useCallback
→ Se não → 🔴 CRÍTICO (causa loop infinito)
# useEffect com funções de hook como deps
search_files: "useEffect\(" em src/components/
→ Se deps incluem funções vindas de hooks → verificar se são estáveis (useCallback)
→ Se não → loop infinito garantido
# useEffect redundante (React Query já auto-carrega)
→ Se useEffect chama função que é essencialmente um refetch do useQuery → removerPadrão de Detecção de Loop Infinito
Network tab mostrando avalanche de requests idênticos com timestamps crescentes em <1 segundo = loop infinito de re-renders. Causa mais comum: função de hook sem useCallback usada como dep de useEffect.
PARTE C — SEGURANÇA
11. Segurança Geral
O que verificar
- [ ] 🔴 Input validation no backend (Zero Trust — não confiar no frontend)
- [ ] 🔴 Sem SQL raw / queries inline (usar Supabase client)
- [ ] 🔴 Sem dados sensíveis expostos no frontend (CPF mascarado, etc.)
- [ ] 🔴 Sem
service_rolekey no frontend - [ ] Rate-limits em endpoints públicos sensíveis (OTP, cadastro, lookup)
- [ ] Ownership check:
escola_iddo JWT validado contra recurso acessado - [ ] Tokens/OTPs com expiração verificada
Como buscar
# service_role no frontend (CRÍTICO)
search_files: "SERVICE_ROLE" em src/
→ Deve ser ZERO
# SQL inline
search_files: "\.rpc\(|sql`|query\(" em supabase/functions/
→ Avaliar se há SQL injection risk
# Dados sensíveis sem máscara
search_files: "cpf|telefone|email" em src/components/
→ Verificar se há mascaramento visual (ex: ***.***.***-XX)
# Endpoints públicos sem rate limit
→ Verificar send-otp, verify-otp, cadastro-escola-publica, lookup11.5. IDOR e Escalação de Privilégios
O que verificar
- [ ] 🔴 Mutações (UPDATE/DELETE) filtram por
escola_iddo JWT — não apenas poriddo body - [ ] 🔴 Deletes de entidades relacionadas são escopados à escola (ex: ao remover usuário, só remove papéis da escola do JWT)
- [ ] 🔴 Leitura de dados sensíveis valida ownership antes de retornar (ex:
inscricao.escola_id === user.escola_id) - [ ] 🔴 Operações admin-only não são acessíveis por papéis de escola (verificar
requireRolecom papéis corretos) - [ ] 🔴 Updates/deletes validam resultado com
.select().maybeSingle()— RLS pode bloquear silenciosamente - [ ] Parâmetros de busca (
searchTerm) são sanitizados antes de interpolar em.or()/.ilike()PostgREST
Como buscar
# Mutações sem escola_id guard
search_files: "\.update\(|\.delete\(" em supabase/functions/
→ Verificar se há .eq("escola_id", user.escola_id) ou ownership check equivalente
# searchTerm sem sanitização (Filter Injection)
search_files: "\.or\(`|\.ilike\." em supabase/functions/
→ Verificar se variável interpolada passou por .replace(/[%_(),.*!]/g, '')
# Deletes cross-escola
search_files: "\.delete\(\)" em supabase/functions/
→ Verificar se deletes de papéis/permissões filtram por escola_id
# RLS silent fail (update/delete sem validação)
search_files: "\.update\(.*\)\.eq\(" em supabase/functions/
→ Verificar se resultado é validado (não é apenas { error } mas também data check)Erros recorrentes
- Frontend envia
idno body → backend usa apenas.eq("id", id)sem verificarescola_id→ IDOR permite acesso cross-escola .delete().eq("papel_id", id)sem.eq("escola_id")→ remove papéis de TODAS as escolas (escalação).update({}).eq("id", id)retorna{ error: null }mesmo quando RLS bloqueou → falha silenciosa- Permissões deletadas sem filtro por
papel_id→ ao salvar permissões de um papel, apaga permissões de outro papel na mesma escola
11.6. Falha Silenciosa em Operações de UI (Silent Save)
O que verificar
- [ ] 🔴 Toda operação de save exibe toast de sucesso OU erro — sem caminho silencioso
- [ ] 🔴 Wrappers de invokeAction em componentes admin verificam
response.successe mostramolpToast - [ ] 🔴 Catch blocks NÃO engolem erros — erros devem propagar para toast ou re-throw
- [ ] Pós-save rehydration: após save com sucesso, o estado da UI é recarregado (invalidateQueries, refetch, ou reset de baseline)
- [ ] Baseline de permissões: botão "Salvar" só aparece quando há diff vs estado carregado do servidor
Como buscar
# Wrappers silenciosos (catch sem toast)
search_files: "catch.*\{" em src/components/admin-
→ Se catch retorna { success: false } sem olpToast → 🔴 CRÍTICO
# invokeAction sem tratamento de resposta
search_files: "invokeAction\(" em src/components/
→ Se resultado não é verificado (.success) → 🔴 CRÍTICO
# Save sem rehydration
search_files: "Salvar permissões|salvarPermissoes" em src/components/
→ Após save, deve haver invalidateQueries/refetch do mesmo dadoErros recorrentes
try { await invokeAction(...); return { success: true }; } catch { return { success: false }; }→ sem toast = usuário não sabe se funcionou- Save bem-sucedido sem invalidar cache → UI mostra dado antigo até reload manual
- Botão "Salvar" sempre visível (sem diff vs baseline) → confunde sobre o que mudou
11.7. Scoping de papel_id em gestao-usuarios-escola
O que verificar
- [ ] 🔴 Todo INSERT em
usuarios_escola_permissoesincluipapel_id— sem exceção - [ ] 🔴 Todo INSERT em
usuarios_escola_sub_permissoesincluipapel_id— sem exceção - [ ] 🔴 Todo DELETE em permissões usa
.eq("papel_id", ...)para escopo correto - [ ] 🔴 Todo SELECT de listagem filtra ou agrupa por
papel_iddo vínculo ativo - [ ] Seed de sub_permissoes: após criar/atualizar permissões, sub_permissões são semeadas via
seedSubPermissoesForRole - [ ]
papel_idé NOT NULL nas tabelas de permissões (constraint de banco ativa desde Abril/2026)
Arquivos afetados
supabase/functions/gestao-usuarios-escola/index.ts — 6 pontos de escrita
supabase/functions/_shared/gestao-usuarios-helpers.ts — 1 ponto de escrita (create)
supabase/functions/_shared/permissions.ts — resolveMenuForUser (leitura scoped)Erros recorrentes
- INSERT sem papel_id → registro órfão →
/menão encontra → AccessBlockedScreen - DELETE sem papel_id → apaga permissões de TODOS os papéis → colisão multi-papel
- Unique index com NULL → duplicatas ilimitadas (corrigido com NOT NULL constraint)
11.8. Canary Priority no CRUD de Permissões
Regras de negócio
| Cenário | Admin vê | Escola vê |
|---|---|---|
| Feature globalmente ON | Checkbox editável | Checkbox editável |
| Feature globalmente OFF + canary ON | Checkbox checked + disabled + badge "Canary" | Invisível |
| Feature globalmente OFF sem canary | Checkbox unchecked + disabled + badge "Manutenção" | Badge "Em manutenção" |
O que verificar
- [ ] 🔴
get_permissoes_usuarioretornacanary_permissions[]efeatures_em_manutencao[] - [ ] 🔴
update_permissoes_usuarionunca deleta/sobrescreve permissões protegidas por canary - [ ] 🔴 Frontend admin: items canary são
checked + disabledcom badge "Canary" - [ ] 🔴 Frontend escola: items canary ficam completamente invisíveis (não renderizados)
- [ ] 🔴 Frontend escola: items em manutenção mostram badge "Em manutenção"
- [ ]
temAlteracao()ignora items canary na comparação de diff - [ ]
marcarTodas()/desmarcarTodas()preservam items canary - [ ] Save payload exclui items canary (backend os preserva independentemente)
- [ ]
checkCanaryAccessé exportada defeature-gate.tse reutilizada no CRUD
Arquivos afetados
supabase/functions/admin-usuarios-escola/index.ts — get_permissoes_usuario + update_permissoes_usuario
supabase/functions/_shared/feature-gate.ts — checkCanaryAccess exportada
src/components/admin-permissoes-grid.tsx — renderização read-only + badges
src/hooks/useAdminUsuariosEscola.ts — tipo PermissoesUsuarioData12. Enum / Data Integrity
O que verificar
- [ ] Valores enviados pelo frontend correspondem exatamente ao enum do banco
- [ ] Selects/dropdowns usam valores do enum (não strings hardcoded)
- [ ] Tipos TypeScript do Supabase (
types.ts) alinhados com código
Como buscar
# Enums usados no código vs banco
search_files: "tipo_atividade|status_escola|status_assinatura|tipo_escola" em src/
→ Cruzar valores com enums em src/integrations/supabase/types.ts
# Strings hardcoded que deveriam ser enum
search_files: "'evento'|'prova'|'atividade'" em src/components/
→ Verificar se valor existe no enum correspondenteErros recorrentes
- Frontend envia
"evento"mas enum do banco étipo_atividade_cronogramacom valor"atividade"→ insert falha silenciosamente com RLS - Select com opções hardcoded que não refletem enum atualizado → opções faltando ou inválidas
PARTE D — MANUTENIBILIDADE
13. Padrões de Código
O que verificar
- [ ]
invokeAction/invokeEdgepara chamadas de edge function (nunca fetch direto) - [ ] Helpers centralizados (não duplicar lógica entre componentes)
- [ ] Tipos exportados de arquivos dedicados (
types/,helpers.ts) - [ ] Componentes >400 linhas extraídos em diretório por feature
- [ ] Imports seguem padrão
@/(nunca../../../)
Como buscar
# Fetch direto para edge functions
search_files: "fetch\(.*functions" em src/
→ Deve ser ZERO
# Componentes grandes
→ Verificar arquivos em src/components/ com >400 linhas
→ Candidatos para extração em diretório por feature
# Imports relativos profundos
search_files: "\.\./\.\./\.\." em src/
→ Substituir por @/ aliases
# Lógica duplicada
→ Funções de formatação (data, moeda, CPF) devem estar em lib/
→ Verificar se há formatDate/formatCurrency duplicado entre componentes14. Nomenclatura
O que verificar
- [ ] Banco: português
snake_case - [ ] Edge Functions: português
kebab-casecom prefixo de domínio - [ ] React: inglês
PascalCase, hooksuse+Domínio - [ ] Labels UI: português
- [ ] Logs:
modulo.operacaolowercase
Ref: docs/development/NAMING_CONVENTIONS.md
15. Testes
O que verificar
- [ ] Funções puras com lógica complexa têm testes unitários
- [ ] Hooks críticos têm testes de integração
- [ ] Fluxos E2E para funcionalidades de alto risco
- [ ] Testes existentes ainda passam após mudanças
Como buscar
# Funções sem teste
→ Listar arquivos em src/lib/ e verificar se há __tests__/ correspondente
→ Priorizar: helpers de cálculo, validação, formatação
# Testes quebrados
→ Rodar vitest para verificarRef: docs/development/TESTING_MASTER.md
PARTE E — DOCUMENTAÇÃO VIVA
16. Fidelidade da Documentação
O que verificar
Cada documento abaixo deve refletir fielmente o estado atual do código:
| Documento | Verificação |
|---|---|
REACT_QUERY_CACHE.md §8 | Lista de hooks legados/migrados está atualizada? |
CODING_STANDARDS.md | Todos os padrões em uso estão documentados? |
EDGE_FUNCTIONS_CATALOG.md | Todas as edge functions existentes estão listadas? |
DATABASE_SCHEMA.md | Tabelas novas estão documentadas? Enums corretos? Functions listadas? |
AUDIT_LOG.md | Ações de log novas estão catalogadas? |
NAVIGATION_AND_ROUTING.md | Rotas e permissões atualizadas? |
INCIDENT_POLICY.md | Lista de ações monitoradas atualizada? |
NOTIFICATIONS_MAP.md | Notificações novas mapeadas? |
🔴 Itens OBRIGATÓRIOS em toda entrega com mudança de schema ou Edge Function
- [ ] DATABASE_SCHEMA.md atualizado: Se criou/alterou tabela, enum ou database function → atualizar contagem e detalhamento
- [ ] EDGE_FUNCTIONS_CATALOG.md atualizado: Se criou nova Edge Function → adicionar entrada + mapa Function↔Hook
- [ ] Enums verificados: Valores na doc conferem com banco real (rodar query de enums se necessário)
- [ ] Database Functions verificadas: Functions novas documentadas com tipo e descrição
Contexto: Em Abr/2026, auditoria revelou 19 tabelas, 10 enums e 9 functions ausentes da documentação — acumulados por meses de entregas sem atualização. Este checklist obrigatório previne reincidência.
Como verificar
# Edge functions não catalogadas
→ Listar diretórios em supabase/functions/
→ Cruzar com docs/architecture/EDGE_FUNCTIONS_CATALOG.md
# Ações de log não documentadas
search_files: "acao:" em supabase/functions/
→ Cruzar com docs/security/AUDIT_LOG.md e src/constants/log-actions.ts
# Tabelas não documentadas
→ Comparar tabelas em types.ts com docs/architecture/DATABASE_SCHEMA.md
# Hooks legados rastreados
search_files: "useState\(\[\]\)" em src/hooks/
→ Cruzar com lista em REACT_QUERY_CACHE.md §8Resultado esperado
Ao final do @audit, reportar com severidade:
1. ✅ Conformes
Itens verificados que estão OK — listar por domínio.
2. 🟡 Atenção
Desvios menores que não bloqueiam mas devem ser corrigidos na sprint.
3. 🔴 Não-conformes
Itens críticos (segurança, perda de dados, falha silenciosa) que bloqueiam entrega.
4. 📝 Recomendações
Melhorias opcionais para backlog.
Apêndice: Tabela de Pesquisa Avançada
Referência rápida de patterns para busca sistemática de violações:
| Violação | Pattern de busca | Onde | Severidade |
|---|---|---|---|
| Toast direto Sonner | from 'sonner' | src/components/ | 🔴 |
| error.message cru | description: (err|error)\.message | src/ | 🔴 |
| alert() | alert\( | src/components/ | 🔴 |
| createClient inline | createClient\( | supabase/functions/ (exceto supabase-client.ts) | 🔴 |
| createSupabaseSystem indevido | createSupabaseSystem | supabase/functions/ (exceto logging/auth/cron) | 🔴 |
| service_role no frontend | SERVICE_ROLE | src/ | 🔴 |
| fetch direto p/ edge | fetch\(.*functions | src/ | 🔴 |
| Função de hook sem useCallback | return { funções sem useCallback | src/hooks/ | 🔴 |
| enabled com dependência circular | enabled:.*!== null onde dado vem da própria query | src/hooks/ | 🔴 |
| Permissão sem papel_id | usuarios_escola_permissoes sem .eq('papel_id') | supabase/functions/ | 🔴 |
| Save silencioso (sem toast) | catch.*return.*success: false sem olpToast | src/components/admin- | 🔴 |
| invokeAction sem tratar .success | invokeAction( sem verificar resultado | src/components/ | 🔴 |
| useState+useEffect legado | useState\(\[\]\) | src/hooks/ | 🟡 |
| staleTime faltando | useQuery\(\{ sem staleTime | src/hooks/ | 🟡 |
| queryKey incompleta | queryKey: | src/hooks/ | 🟡 |
| Mutation sem invalidação | useMutation\(\{ sem invalidateQueries | src/hooks/ | 🟡 |
| .in() sem chunking | \.in\( com array >100 | supabase/functions/ | 🟡 |
| SELECT * desnecessário | \.select\(\) | supabase/functions/ | 🟡 |
| N+1 query em loop | for.*await.*\.from\( | supabase/functions/ | 🟡 |
| queryFn que engole erro | return [] dentro de queryFn | src/ | 🟡 |
| 3+ useQuery para mesmo backend | múltiplos useQuery no mesmo hook/componente | src/ | 🟡 |
| useEffect redundante com React Query | useEffect chamando refetch de useQuery | src/components/ | 🟡 |
| CUD sem registrarLog | \.insert\(|\.update\(|\.delete\( sem registrarLog | supabase/functions/ | 🟡 |
| req faltando em log | registrarLog sem req | supabase/functions/ | 🟡 |
| Componente >400 linhas | contagem de linhas | src/components/ | 🟡 |
| Import relativo profundo | \.\./\.\./\.\. | src/ | 🟢 |
| Enum hardcoded | 'evento'|'prova' | src/components/ | 🟡 |
| Ação sem cadastro | ações em registrarLog vs log-actions.ts | cruzamento | 🟡 |
| Doc desatualizada | cruzamento código vs docs/ | vários | 🟡 |
Referências
docs/operations/INCIDENT_POLICY.md— Política de incidentesdocs/security/AUDIT_LOG.md— Catálogo de ações de logdocs/development/CODING_STANDARDS.md— Padrões de códigodocs/development/NAMING_CONVENTIONS.md— Nomenclaturadocs/development/REACT_QUERY_CACHE.md— React Query e cachedocs/development/TESTING_MASTER.md— Estratégia de testes (documento mestre)docs/architecture/EDGE_FUNCTIONS_CATALOG.md— Catálogo de Edge Functionsdocs/architecture/DATABASE_SCHEMA.md— Schema do bancodocs/development/PROBLEM_SOLVING.md— Protocolo de resolução de problemassrc/lib/error-helpers.ts— Helper de erros amigáveissupabase/functions/_shared/supabase-client.ts— Clientes Supabase
§12 — Cobertura de Testes: Permissões, Canary e Feature Flags
Adicionado: 2026-04-05 | Contexto: Auditoria completa do ecossistema
Inventário de testes (pós-auditoria)
| Arquivo | Cenários | Tipo |
|---|---|---|
_shared/__tests__/permissions.test.ts | 8 | Unitário Deno — resolveMenuForUser |
_shared/__tests__/feature-gate.test.ts | 13 | Unitário Deno — requireFeatureFlag |
_shared/__tests__/feature-gate-canary.test.ts | 5 | Unitário Deno — checkCanaryAccess isolado |
_shared/__tests__/usuarios-helpers-seed.test.ts | 5 | Unitário Deno — seed/remove com papel_id |
_shared/usuarios-helpers.test.ts | 7 | Unitário Deno — normalizePapelVinculos |
admin-usuarios-escola/__tests__/canary-priority.test.ts | 3 | Contrato HTTP — canary no CRUD |
gestao-usuarios-escola/__tests__/papel-id-scoping.test.ts | 4 | Contrato HTTP — papel_id scoping |
src/components/__tests__/AdminPermissoesGrid.test.tsx | 6 | Componente Vitest — canary/manutenção UI |
src/hooks/__tests__/useAdminUsuariosEscola.canary.test.ts | 2 | Tipo Vitest — PermissoesUsuarioData |
src/hooks/__tests__/useAdminUsuariosEscola.multipapel.test.ts | 2 | Isolamento multi-papel |
Regras de cobertura mínima
- resolveMenuForUser — qualquer mudança DEVE ter teste unitário correspondente
- checkCanaryAccess — cenários edge (grupo inativo, múltiplos grupos) obrigatórios
- seedPermissionsForRole — verificar papel_id em todos os registros inseridos
- AdminPermissoesGrid — canary read-only + manutenção badge obrigatórios
- Canary priority — update_permissoes_usuario NUNCA remove permissões canary
Integridade de dados
- Migration de seed executada: 20 vínculos órfãos corrigidos (0 restantes)
- Sub-permissões populadas: 390 registros (antes: 0)
- Total permissões: 246 (antes: 148)
- Verificação:
PAPEIS_COM_SEEDinclui coordenador, diretor E escola