Auditoria @audit — Header Novidades + Placeholders
Data: 2026-04-26 Escopo: especialista-headers/ (edge), header-novidades-especialista/ (UI), hooks useHeaders*, docs HEADER_NOVIDADES.md e CONTENT_SCHEDULING_PATTERN.md. Resultado: ✅ APROVADO — sem achados críticos. Dívida técnica pré-existente catalogada separadamente.
Metodologia
Aplicação dos 12 domínios do AUDIT.md sobre o feature de Header Novidades + Placeholders entregue nas Etapas 1–4.
Verificações automatizadas:
deno checkem todas as Edge Functions tocadasbun run build(frontend)supabase testemespecialista-headers/__tests__/→ 42/42 passingsupabase linter(issues pré-existentes não relacionadas)
Resumo por Domínio
| # | Domínio | Status | Observação |
|---|---|---|---|
| 1 | Logging | ✅ | CUDs cobertos por registrarLog(). Lazy reconciliation é best-effort idempotente — sem registrarLog é o padrão correto para mutações derivadas de leitura. |
| 2 | Incidentes | ✅ | Falhas técnicas usam console.warn não-bloqueante; reads não logam. |
| 3 | RBAC | ✅ | requireRole(['especialista','administrador']) + requireFeatureFlag('header_novidades') + SUB_FLAG_POR_ACAO (controle/programados/configuracoes). Cobertura validada em sub-flags-coverage.test.ts. |
| 4 | RLS / silent failure | ✅ | Updates seguem padrão .update().eq().select(); uso de createSupabaseSystem() documentado nos 3 pontos (busca de adesões e nome do criador) com referência ao ADR. |
| 5 | Edge Function — estrutura | ✅ | CORS em todas as respostas, PUBLIC_ACTIONS antes do auth, formato {success, code, message}, 200 + FEATURE_DISABLED para pausa intencional, 503 reservado para erro real de leitura da flag. |
| 6 | React Query | ✅ | staleTime definido (5–10 min), queryKey por contexto, invalidateQueries cross-cache (novidades ↔ placeholders ↔ ativos), optimistic updates com cancelQueries/rollback no padrão 4-hook. |
| 7 | Toasts (frontend) | ✅ | Zero import from 'sonner', zero alert(), zero error.message cru. Tudo via olpToast + getUserFriendlyError. |
| 8 | Backend error handling | ✅ | Insert/update/delete verificam error; falhas em reconciliação não bloqueiam (best-effort + defesa em profundidade na RLS pública). |
| 9 | React Query cache | ✅ | refetchOnWindowFocus=false declarado em hooks, staleTime compatível com tabela canônica. |
| 10 | Query chunking / N+1 | ✅ | Sem .in() com arrays >100. Reorder usa loop sequencial pequeno (≤ N placeholders/headers, máx ~10 ordens). |
| 10.5 | Batching | ✅ | Tela de gestão é tabbed (lazy mount once); requests separados por aba. Batch_init não justificado para esse volume. |
| 10.6 | N+1 | ✅ | Auto-toggle inverso usa 1 SELECT + 2 UPDATEs em massa + loop pequeno na reatribuição de ordem. |
| 10.7 | Hook stability | ✅ | Funções dos hooks expostas são useMutation/useQuery (já estáveis). Sem useEffect em deps instáveis. |
| 11 | Segurança geral | ✅ | Input validation server-side (Zod no programar/desprogramar), sem service_role no frontend, gates simétricos, ownership por sub-flag. |
| 11.5 | IDOR | ✅ | Mutações restritas a especialista/admin; sem operação cross-escola exposta. Audiência de leitura por escola vinculada (consumo público) já com fail-close. |
| 11.6 | Silent save | ✅ | Hooks expõem mutateAsync/onError para os componentes propagarem olpToast com getUserFriendlyError. |
| 11.7 | Scoping permissões | n/a | Feature não toca usuarios_escola_permissoes. |
| 11.8 | Canary priority | ✅ | Flags header_novidades (gestão) e header_novidades_consumo (consumo) segregadas; sub-flags por aba para controle granular. |
| 12 | Enum / data integrity | ✅ | Tipos RowHeaderNovidade e RowHeaderPlaceholder derivados de Database types. UI usa enums das migrations. |
Pontos Fortes Observados
- Lazy Reconciliation Inversa (placeholders ↔ novidades) — padrão limpo, idempotente, com snapshot persistente em
ativo_antes_auto_off. Cobre as 4 transições sem cron novo nem janela de inconsistência. - Semântica HTTP correta para flags pausadas —
200 + code:'FEATURE_DISABLED'evita retry no client e diferencia pausa intencional de incidente. Reservou503apenas para falha real de leitura da flag. - Defesa em profundidade temporal —
list_activeaplica filtro temporal explícito (publicar_em <= now()ANDdespublicar_em > now()) além da RLS, garantindo que a janela seja respeitada mesmo se a reconciliação atrasar. - Sub-flags por aba — admin pode pausar Controle/Programados/Configurações independentemente sem derrubar o módulo inteiro.
- Validação Zod server-side em
programar_header(rejeitadespublicar_em <= publicar_em).
Recomendações (Backlog — não bloqueantes)
- 🟢 Considerar
batch_initse a tela de gestão evoluir para >3 requests no mount (hoje são 3 separados por tab — lazy mount). - 🟢 Documentar visualmente (diagrama) o ciclo
programado → ativo → desativadonoHEADER_NOVIDADES.md.
Dívida Técnica Pré-existente (Não Relacionada)
Os seguintes itens foram observados durante o saneamento TS mas não são parte da feature auditada:
_shared/supabase-client.ts: incompatibilidade de tiposSchemaNameem alguns casts (as anyaplicado nas funções de auth/password). Solução de fundo seria atualizar tipagem do@supabase/supabase-js.- Linter Supabase reporta 6×
RLS Enabled No Policy(categoria 5 do ADR-011 — tabelasescola_anotacoes,portal-*), 1×Function Search Path Mutable, 1×Extension in Public, 4×Public Bucket Allows Listing(banners/thumbnails — design intencional).
→ Catalogados para PR de saneamento separado.
Conclusão
Feature de Header Novidades + Placeholders aprovada sem achados críticos. Padrões do projeto totalmente respeitados:
- SSOT:
HEADER_NOVIDADES.md+CONTENT_SCHEDULING_PATTERN.md - Memórias internas (Lovable Memory, não expostas no VitePress):
features/header-novidades/architecture,architecture/content-scheduling-pattern
Build limpo, 42/42 testes verdes, frontend sem regressões.
Adendo — Refator pós-feedback (2026-04-26, mesmo dia)
Após validação em produção, o usuário identificou que o auto-toggle inverso (reconciliarPlaceholdersVsNovidades) causava UX confusa: especialista ativava um placeholder na tela de Configurações e o backend o desativava na mesma request porque havia novidades ativas no sistema.
Mudanças aplicadas:
- Removido
reconciliarPlaceholdersVsNovidades()e suas 11 chamadas emespecialista-headers/index.ts. Especialista agora é fonte da verdade absoluta deheaders_placeholders.ativo. - Adicionado helper
escolaTemHeaderVisivel(escolaId)que reaproveita o filtro de adesão delist_active. - Modificado
list_active_placeholderspara aceitarparams.escola_ide suprimir resultado quando a escola tem ≥1 header de novidade visível (decisão de consumo por escola, não mais global). - Frontend:
content-context.tsxenviaescola_ide isola queryKey por escola.CardPlaceholdermigrado paraforwardRef(resolve warning React reportado no console). Banner explicativo da aba Configurações atualizado para refletir o novo modelo. - Coluna deprecada:
headers_placeholders.ativo_antes_auto_offmantida no schema (zero migration agora) — drop em PR de cleanup posterior.
Princípios reforçados:
- SSOT: especialista decide
ativo, backend apenas filtra no consumo. - Audiência por escola: mesma lógica de adesão (
escola_olimpiadasstatus='ativa') entrelist_active(fail-close) elist_active_placeholders(fail-open, intencional — placeholder neutro é menos prejudicial que fallback hardcoded).
Testes: placeholders-crud.test.ts atualizado com 3 novos asserts:
- Helper de reconciliação NÃO pode reaparecer (regressão).
list_active_placeholdersaceitaescola_id.escolaTemHeaderVisivelreaproveita filtro de adesão. Exit 0 em todos os 42+3 testes Deno.