Skip to content

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

  1. Backend (Edge Functions, shared helpers) → onde erros são mais críticos
  2. Frontend (hooks, componentes, lib/) → qualidade e UX
  3. 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ívelSignificadoAção
🔴 CríticoSegurança, perda de dados, falha silenciosaBloqueia entrega
🟡 AtençãoDesvio de padrão, debt técnicoCorrigir na sprint
🟢 RecomendaçãoMelhoria opcionalBacklog

PARTE A — QUALIDADE DE CÓDIGO

1. Logging

O que verificar

  • [ ] Todo Create/Update/Delete chama registrarLog() com req
  • [ ] Reads NÃO logam (nunca registrarLog em SELECT)
  • [ ] Ação segue formato <modulo>.<operacao> lowercase
  • [ ] Diff usa gerarAlteracoes(antes, depois) para updates
  • [ ] Ação cadastrada em src/constants/log-actions.ts
  • [ ] req passado 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.ts

Erros recorrentes

  • req faltando no registrarLog() → geolocalização não funciona
  • Ação não cadastrada em log-actions.ts → filtro de logs quebrado
  • gerarAlteracoes nã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.errorActions ou acoesExtras)
  • [ ] Fluxo normal do usuário NÃO está na lista de incidentes
  • [ ] detalhes do log tem campo de erro acessível por extrairErroIncidente

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
  • [ ] useMyPermissions filtra sidebar corretamente
  • [ ] Novas permissões adicionadas ao enum permissao_area se necessário
  • [ ] Ownership check: usuário só acessa dados da sua escola (escola_id do JWT)
  • [ ] 🔴 Permissões escopadas por papel_id: toda leitura/escrita em usuarios_escola_permissoes e usuarios_escola_sub_permissoes DEVE filtrar por papel_id — nunca só usuario_id + escola_id
  • [ ] 🔴 Seed de permissões por vínculo: ao criar vínculo, seedPermissionsForRole recebe papel_id explícito
  • [ ] 🔴 Remoção de permissões isolada: ao remover vínculo, removePermissionsForRole deleta APENAS permissões do papel_id removido — 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_permissoes está 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âmetro

Erros 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_usuario sem papel_id → retorna permissões do primeiro vínculo encontrado, não do badge selecionado
  • update_permissoes_usuario sem papel_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(), verificar data.length === 0 ou usar .select() para confirmar
  • [ ] Sem uso de service_role onde createSupabaseClient(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ÍTICO

Erros recorrentes

  • .update({}).eq('id', x) sem .select() → RLS bloqueia silenciosamente, retorna { data: null, error: null }
  • createClient inline em edge function → bypassa padrão de segurança
  • createSupabaseSystem() usado para operações de usuário → bypassa RLS

5. Edge Function — Estrutura

O que verificar

  • [ ] verify_jwt = false no config.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 (nunca fetch direto)

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

  • catch block retorna Response sem getCorsHeaders(req) → CORS error no browser
  • Frontend usa fetch() direto → não passa cookies, auth falha
  • extractAuthenticatedUser chamado antes de actions públicas → público não funciona

6. React Query — Migração

O que verificar

  • [ ] useQuery com staleTime definido (nunca default 0)
  • [ ] queryKey inclui todos os filtros/parâmetros relevantes
  • [ ] Mutations usam invalidateQueries no onSuccess
  • [ ] 🔴 Sem anti-padrão useState([]) + useEffect para fetch
  • [ ] enabled usado quando query depende de parâmetro que pode ser undefined
  • [ ] 🔴 enabled NÃ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 → 🔴 deadlock

Tabela de staleTime por categoria

CategoriastaleTimeExemplos
Dashboards5 minadmin-dashboard-metrics
Listas CRUD5 minalunos, turmas, usuários
Configurações10 minescola-dados, mural-config
Dados quase estáticos30 minolimpiadas, escolas-admin, papéis
Feature flags10 minfeature-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' — usar olpToast de @/components/ui/use-olp-toast
  • [ ] 🔴 Sem error.message cru em toasts — usar getUserFriendlyError(error)
  • [ ] 🔴 Sem alert() — usar olpToast
  • [ ] Mensagens de erro genéricas para o usuário (sem stack traces, sem constraint names)
  • [ ] Erros técnicos logados via console.error para 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 exibir

Erros recorrentes

  • toast.error('Erro', { description: error.message }) → vaza constraint name do Postgres
  • import { 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/delete verifica { 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
  • [ ] detalhes do 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 throw

Template de verificação

typescript
// ✅ 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

  • [ ] staleTime definido e compatível com a tabela da seção 6
  • [ ] refetchOnWindowFocus não está true sem motivo (React Query v5 default é true)
  • [ ] Queries com enabled: false quando parâmetros são undefined/null
  • [ ] Sem queries duplicadas (mesmo dado buscado por hooks diferentes)
  • [ ] placeholderData ou initialData usado 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 dado

10. 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.all para 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_init

Overhead 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

TelaActionConsolida
Resultadosbatch_init_resultadoslistagem + stats + stats_agregadas
Muralbatch_initlistagem + olimpíadas + liberações + config

10.6 N+1 Query Detection

O que verificar

  • [ ] 🟡 Sem .from('tabela').select() dentro de for/map/forEach em Edge Functions
  • [ ] Substituído por .in('campo', arrayDeIds) com queryInChunks se >100 IDs
  • [ ] Promise.all usado para queries independentes (não await sequencial 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

typescript
// ❌ 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 useEffect DEVEM ter useCallback
  • [ ] Dependências do useCallback são estáveis (queryClient, mutation, refetch — não dados reativos)
  • [ ] useEffect de mount redundante removido quando React Query já auto-carrega
  • [ ] Sem useEffect com 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 → remover

Padrã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_role key no frontend
  • [ ] Rate-limits em endpoints públicos sensíveis (OTP, cadastro, lookup)
  • [ ] Ownership check: escola_id do 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, lookup

11.5. IDOR e Escalação de Privilégios

O que verificar

  • [ ] 🔴 Mutações (UPDATE/DELETE) filtram por escola_id do JWT — não apenas por id do 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 requireRole com 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 id no body → backend usa apenas .eq("id", id) sem verificar escola_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.success e mostram olpToast
  • [ ] 🔴 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 dado

Erros 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_permissoes inclui papel_id — sem exceção
  • [ ] 🔴 Todo INSERT em usuarios_escola_sub_permissoes inclui papel_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_id do 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 → /me nã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árioAdmin vêEscola vê
Feature globalmente ONCheckbox editávelCheckbox editável
Feature globalmente OFF + canary ONCheckbox checked + disabled + badge "Canary"Invisível
Feature globalmente OFF sem canaryCheckbox unchecked + disabled + badge "Manutenção"Badge "Em manutenção"

O que verificar

  • [ ] 🔴 get_permissoes_usuario retorna canary_permissions[] e features_em_manutencao[]
  • [ ] 🔴 update_permissoes_usuario nunca deleta/sobrescreve permissões protegidas por canary
  • [ ] 🔴 Frontend admin: items canary são checked + disabled com 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 de feature-gate.ts e 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 PermissoesUsuarioData

12. 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 correspondente

Erros recorrentes

  • Frontend envia "evento" mas enum do banco é tipo_atividade_cronograma com 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/invokeEdge para 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 componentes

14. Nomenclatura

O que verificar

  • [ ] Banco: português snake_case
  • [ ] Edge Functions: português kebab-case com prefixo de domínio
  • [ ] React: inglês PascalCase, hooks use+Domínio
  • [ ] Labels UI: português
  • [ ] Logs: modulo.operacao lowercase

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 verificar

Ref: 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:

DocumentoVerificação
REACT_QUERY_CACHE.md §8Lista de hooks legados/migrados está atualizada?
CODING_STANDARDS.mdTodos os padrões em uso estão documentados?
EDGE_FUNCTIONS_CATALOG.mdTodas as edge functions existentes estão listadas?
DATABASE_SCHEMA.mdTabelas novas estão documentadas? Enums corretos? Functions listadas?
AUDIT_LOG.mdAções de log novas estão catalogadas?
NAVIGATION_AND_ROUTING.mdRotas e permissões atualizadas?
INCIDENT_POLICY.mdLista de ações monitoradas atualizada?
NOTIFICATIONS_MAP.mdNotificaçõ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 §8

Resultado 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çãoPattern de buscaOndeSeveridade
Toast direto Sonnerfrom 'sonner'src/components/🔴
error.message crudescription: (err|error)\.messagesrc/🔴
alert()alert\(src/components/🔴
createClient inlinecreateClient\(supabase/functions/ (exceto supabase-client.ts)🔴
createSupabaseSystem indevidocreateSupabaseSystemsupabase/functions/ (exceto logging/auth/cron)🔴
service_role no frontendSERVICE_ROLEsrc/🔴
fetch direto p/ edgefetch\(.*functionssrc/🔴
Função de hook sem useCallbackreturn { funções sem useCallbacksrc/hooks/🔴
enabled com dependência circularenabled:.*!== null onde dado vem da própria querysrc/hooks/🔴
Permissão sem papel_idusuarios_escola_permissoes sem .eq('papel_id')supabase/functions/🔴
Save silencioso (sem toast)catch.*return.*success: false sem olpToastsrc/components/admin-🔴
invokeAction sem tratar .successinvokeAction( sem verificar resultadosrc/components/🔴
useState+useEffect legadouseState\(\[\]\)src/hooks/🟡
staleTime faltandouseQuery\(\{ sem staleTimesrc/hooks/🟡
queryKey incompletaqueryKey:src/hooks/🟡
Mutation sem invalidaçãouseMutation\(\{ sem invalidateQueriessrc/hooks/🟡
.in() sem chunking\.in\( com array >100supabase/functions/🟡
SELECT * desnecessário\.select\(\)supabase/functions/🟡
N+1 query em loopfor.*await.*\.from\(supabase/functions/🟡
queryFn que engole erroreturn [] dentro de queryFnsrc/🟡
3+ useQuery para mesmo backendmúltiplos useQuery no mesmo hook/componentesrc/🟡
useEffect redundante com React QueryuseEffect chamando refetch de useQuerysrc/components/🟡
CUD sem registrarLog\.insert\(|\.update\(|\.delete\( sem registrarLogsupabase/functions/🟡
req faltando em logregistrarLog sem reqsupabase/functions/🟡
Componente >400 linhascontagem de linhassrc/components/🟡
Import relativo profundo\.\./\.\./\.\.src/🟢
Enum hardcoded'evento'|'prova'src/components/🟡
Ação sem cadastroações em registrarLog vs log-actions.tscruzamento🟡
Doc desatualizadacruzamento código vs docs/vários🟡

Referências

  • docs/operations/INCIDENT_POLICY.md — Política de incidentes
  • docs/security/AUDIT_LOG.md — Catálogo de ações de log
  • docs/development/CODING_STANDARDS.md — Padrões de código
  • docs/development/NAMING_CONVENTIONS.md — Nomenclatura
  • docs/development/REACT_QUERY_CACHE.md — React Query e cache
  • docs/development/TESTING_MASTER.md — Estratégia de testes (documento mestre)
  • docs/architecture/EDGE_FUNCTIONS_CATALOG.md — Catálogo de Edge Functions
  • docs/architecture/DATABASE_SCHEMA.md — Schema do banco
  • docs/development/PROBLEM_SOLVING.md — Protocolo de resolução de problemas
  • src/lib/error-helpers.ts — Helper de erros amigáveis
  • supabase/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)

ArquivoCenáriosTipo
_shared/__tests__/permissions.test.ts8Unitário Deno — resolveMenuForUser
_shared/__tests__/feature-gate.test.ts13Unitário Deno — requireFeatureFlag
_shared/__tests__/feature-gate-canary.test.ts5Unitário Deno — checkCanaryAccess isolado
_shared/__tests__/usuarios-helpers-seed.test.ts5Unitário Deno — seed/remove com papel_id
_shared/usuarios-helpers.test.ts7Unitário Deno — normalizePapelVinculos
admin-usuarios-escola/__tests__/canary-priority.test.ts3Contrato HTTP — canary no CRUD
gestao-usuarios-escola/__tests__/papel-id-scoping.test.ts4Contrato HTTP — papel_id scoping
src/components/__tests__/AdminPermissoesGrid.test.tsx6Componente Vitest — canary/manutenção UI
src/hooks/__tests__/useAdminUsuariosEscola.canary.test.ts2Tipo Vitest — PermissoesUsuarioData
src/hooks/__tests__/useAdminUsuariosEscola.multipapel.test.ts2Isolamento multi-papel

Regras de cobertura mínima

  1. resolveMenuForUser — qualquer mudança DEVE ter teste unitário correspondente
  2. checkCanaryAccess — cenários edge (grupo inativo, múltiplos grupos) obrigatórios
  3. seedPermissionsForRole — verificar papel_id em todos os registros inseridos
  4. AdminPermissoesGrid — canary read-only + manutenção badge obrigatórios
  5. 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_SEED inclui coordenador, diretor E escola