Por que Staging? — Motivações, Decisões e Trade-offs
Documento de decisão arquitetural (ADR) do ambiente de staging. Para setup técnico, ver STAGING_SETUP.md. Para valores de referência, ver STAGING_REFERENCE.md.
Última atualização: 2026-04-05
1. Motivação — Por que precisamos de staging
O staging não foi criado por "boas práticas" genéricas. Nasceu de problemas reais que bloqueavam o desenvolvimento seguro da plataforma:
1.1 Impossibilidade de testar permissões em produção
O sistema de permissões OLP tem 3 camadas interdependentes:
usuarios_escola_permissoes— permissões CRUD por escola- Feature Flags — flags globais que habilitam/desabilitam áreas inteiras
- Canary Releases — permissões temporárias liberadas para grupos de teste
Bugs nesse ecossistema causam dois cenários igualmente graves:
- Perda de acesso: coordenador perde permissão de agenda → escola não consegue operar
- Acesso indevido: gestor escola consegue alterar permissões canary-protegidas → violação do princípio de menor privilégio
Testar essas interações com escolas reais é inaceitável. Um erro no CRUD de permissões afeta diretamente a operação diária da escola.
Exemplo real: A auditoria de abril/2026 detectou que
gestao-usuarios-escola(endpoint da escola) não protegia permissões canary duranteupdateeupdate_permissoes. Um gestor escola poderia, ao salvar a matriz de permissões, deletar silenciosamente permissões canary-protegidas. Esse bug foi detectado em staging antes de afetar qualquer escola real.
1.2 Autenticação cookie-based cross-origin
O fluxo de autenticação OLP é não-trivial:
Frontend (olp.digital)
→ Cloudflare Worker (gateway.olp.digital)
→ Supabase Edge Function (*.supabase.co)
→ JWT assinado com OLP_JWT_SECRET
→ Cookie HttpOnly olp_auth (Domain=.olp.digital)Esse fluxo envolve:
- 3 origens diferentes (frontend, gateway, Supabase)
- Reescrita de cookies no Worker (Set-Cookie com Domain correto)
- CORS com credentials (nunca
Access-Control-Allow-Origin: *) - Geolocalização injetada via headers Cloudflare
É impossível validar esse fluxo completo com mocks ou testes unitários. A única forma de garantir que cookies são setados, propagados e validados corretamente é um ambiente end-to-end real com as mesmas camadas de produção.
1.3 Pipeline de CI/CD bloqueado
Antes do staging, o pipeline de CI era limitado a:
lint → build → ✅ (fim)Faltavam os estágios críticos:
- Testes de contrato: validar que as 52+ Edge Functions respondem corretamente (CORS, auth, payloads)
- Testes de segurança: validar isolamento cross-escola (IDOR), que um gestor da Escola A não acessa dados da Escola B
- Testes E2E: validar fluxos completos (login → navegação → ação → logout) via Playwright
Sem um ambiente real, esses testes simplesmente não tinham onde rodar.
1.4 Isolamento de secrets
A plataforma utiliza secrets sensíveis que impactam diretamente segurança e integridade:
| Secret | Impacto se comprometido |
|---|---|
OLP_JWT_SECRET | Forjar sessões de qualquer usuário |
OLP_PEPPER_SECRET | Comprometer hashes de senha |
CRON_SECRET | Executar jobs administrativos |
E2E_TEST_PASSWORD | Senha dos usuários de teste para login direto sem OTP (só existe em staging) |
Testar com os secrets de produção significaria que qualquer vazamento no CI (logs, artefatos) comprometeria o ambiente real. Staging usa secrets exclusivos e independentes — um vazamento em staging não afeta produção.
1.5 Validação de migrations antes de produção
Migrations SQL críticas — como o seed de permissões para os 20 usuários órfãos detectados na auditoria — precisam ser validadas antes de aplicar em produção. Em staging, uma migration mal escrita causa rollback local; em produção, causa downtime real.
2. Por que Cloudflare (e não Vercel, Netlify, etc.)
A escolha de Cloudflare não foi preferência — foi necessidade técnica de paridade com produção.
2.1 Paridade de infraestrutura
| Componente | Produção | Staging | Por que importa |
|---|---|---|---|
| Gateway | gateway.olp.digital (Worker) | gateway-staging.olp.digital (Worker) | Cookie rewrite + CORS + geo headers idênticos |
| Frontend | Lovable hosting (olp.digital) | Cloudflare Pages (staging.olp.digital) | Build Vite idêntico |
| DNS | Cloudflare DNS | Cloudflare DNS | Subdomínios sem mexer no registrador (GoDaddy) |
Se usássemos Vercel ou Netlify para o frontend de staging, o fluxo de cookies não funcionaria — o Worker precisa estar no mesmo domínio (.olp.digital) para reescrever cookies com Domain=.olp.digital.
2.2 DNS já no Cloudflare
O domínio olp.digital já tem DNS gerenciado pela Cloudflare. Criar subdomínios (staging.olp.digital, gateway-staging.olp.digital) é uma operação de 30 segundos no painel — sem tocar no registrador externo (GoDaddy), sem propagação DNS demorada.
2.3 Cloudflare Pages — simplicidade
Cloudflare Pages integra-se ao mesmo painel onde o Worker já existe:
- Build:
bun install && bun run build→ outputdist/ - Deploy automático via GitHub (branch
staging) - Preview deploys por branch
- Zero configuração de servidor
2.4 Custo zero
| Recurso | Free Tier | Uso estimado staging |
|---|---|---|
| Workers | 100.000 req/dia | < 5.000 req/dia |
| Pages | 500 builds/mês | < 50 builds/mês |
| DNS | Ilimitado | 2 subdomínios |
O staging inteiro opera no free tier da Cloudflare sem custo adicional.
2.5 Alternativas descartadas
| Alternativa | Por que descartada |
|---|---|
| Vercel | Não resolve cookies cross-origin (sem Worker no mesmo domínio) |
| Netlify | Mesmo problema de cookies; sem Worker nativo |
| Lovable hosting | Não suporta múltiplos ambientes (produção e staging simultâneos) |
| Docker local | Não replica Cloudflare Worker; sem CI integrado |
Supabase local (supabase start) | Não replica Edge Functions em Deno Deploy; sem CORS real |
3. Trade-offs conscientes
Cada decisão abaixo foi tomada com plena consciência dos custos e benefícios:
3.1 Frontend em Cloudflare Pages vs Lovable hosting
| Cloudflare Pages | Lovable hosting | |
|---|---|---|
| Vantagem | Branch deploys, CI integrado, mesmo domínio do Worker | Zero config, preview automático |
| Desvantagem | Build manual (GitHub Actions) | Apenas 1 ambiente por projeto |
| Decisão | ✅ Pages para staging | ✅ Lovable para produção |
Justificativa: Lovable hosting não suporta múltiplos ambientes. Para manter produção em olp.digital via Lovable e ter staging em staging.olp.digital, Cloudflare Pages é a única opção que mantém paridade de domínio.
3.2 Supabase em projeto separado
| Projeto separado | Mesmo projeto (schemas diferentes) | |
|---|---|---|
| Vantagem | Isolamento total de secrets, RLS, dados | Sem custo extra, schema unificado |
| Desvantagem | Gestão de 2 projetos, deploy duplicado de Edge Functions | Risco de cross-contamination de dados e secrets |
| Decisão | ✅ Projeto separado |
Justificativa: O OLP_JWT_SECRET deve ser exclusivo por ambiente. Se staging e produção compartilharem o mesmo projeto, um JWT gerado em staging poderia ser válido em produção — violação crítica de segurança.
3.3 Integrações externas — Wasender e MercadoPago
| Integração | Status | Decisão |
|---|---|---|
| Wasender (WhatsApp) | ✅ Configurado | Credenciais de staging independentes — OTP WhatsApp funcional |
| MercadoPago | ❌ Não configurado | Pagamentos não são alvo dos testes CI |
Justificativa Wasender: Configurado com credenciais de staging para que o fluxo de OTP via WhatsApp funcione quando necessário (ex: testes manuais do mural). O CI usa E2E_TEST_PASSWORD para login direto sem OTP.
Justificativa MercadoPago: Pagamentos não são alvo dos testes automatizados atuais. Quando necessário, será configurado com credenciais de sandbox.
3.4 Worker com código duplicado (não versionado)
| Worker no repositório | Worker no painel Cloudflare | |
|---|---|---|
| Vantagem | Versionado, deploy automatizado | Edição rápida, sem build pipeline |
| Desvantagem | Pipeline adicional, complexidade | Duplicação manual entre prod e staging |
| Decisão | ❌ Painel Cloudflare (dívida técnica aceita) |
Justificativa: O Worker tem < 200 linhas e muda raramente (última alteração significativa: fev/2026). O custo de montar um pipeline de deploy para Worker não se justifica pelo ritmo atual de mudanças. Quando o Worker crescer em complexidade, migraremos para Wrangler + GitHub Actions.
3.5 Dados de seed com UUIDs fixos
| UUIDs fixos | UUIDs gerados | |
|---|---|---|
| Vantagem | Referência cruzada direta nos testes | Isolamento entre runs |
| Desvantagem | Colisão se aplicar seed 2x sem limpar | Testes precisam de lookup |
| Decisão | ✅ UUIDs fixos |
Justificativa: Staging é ambiente isolado — UUIDs fixos simplificam enormemente os testes (assertions diretas, sem queries prévias). O seed é idempotente (usa ON CONFLICT ou verifica existência antes de inserir).
3.6 Login direto para testes (sem OTP WhatsApp)
| Login direto via secret | WhatsApp sandbox | |
|---|---|---|
| Vantagem | Zero custo, zero latência, CI-friendly | Teste real do fluxo WhatsApp |
| Desvantagem | Não testa envio WhatsApp real | Custo, rate limits, latência |
| Decisão | ✅ Login direto via secret |
Justificativa: O E2E_TEST_PASSWORD é a senha real atribuída aos usuários de teste do staging. A Edge Function e2e-login valida o header X-E2E-Key contra esse secret — é uma autenticação legítima, apenas sem o passo de OTP WhatsApp (que é funcional em staging via Wasender, mas desnecessário para CI). Em produção, o secret não existe, tornando o endpoint inacessível. Zero risco de escalação.
3.7 Portal (mural) sem seed data
| Configurar portal em staging | Não configurar | |
|---|---|---|
| Vantagem | Testes E2E do portal aluno/responsável | Menos complexidade |
| Desvantagem | Mais seed data (mural, liberações) | Fluxo portal não testado em CI |
| Decisão | ❌ Dívida técnica aceita (P3) |
Justificativa: O portal do aluno tem fluxo de autenticação separado (cookie olp_mural) e dados específicos (snapshots, liberações). O JWT do portal usa o mesmo OLP_JWT_SECRET (unificado por exigência do PostgREST/RLS — ver AUTHENTICATION.md), então não há secret pendente. O que falta é seed data (mural_dados_publicados, mural_liberacoes, escola_mural_config com slugs). Será implementado quando os testes E2E do portal forem priorizados.
4. O que tivemos que fazer — Setup completo
Cada passo do setup teve uma razão técnica específica. Para comandos detalhados, ver STAGING_SETUP.md.
4.1 Criar projeto Supabase separado
Por quê: Isolamento de OLP_JWT_SECRET. Se staging e produção compartilharem o mesmo secret, um token de staging poderia autenticar em produção.
O que fizemos: Novo projeto no Supabase Dashboard → região South America (São Paulo) para latência consistente com produção.
Resultado: Project Ref nrgcajnhpjmwilfcmqyb — completamente independente de produção (mjvuzsizjlcalyfmbquy).
4.2 Clonar schema + sanitizar dados
Por quê: O schema precisa ser idêntico à produção (tabelas, RLS policies, triggers, enums). Mas dados reais violam LGPD — nunca copiar dados de alunos, telefones ou CPFs.
O que fizemos:
pg_dump --schema-onlyde produção- Aplicar no staging via
psql - Seed com dados fictícios (7 usuários de teste, 3 escolas)
4.3 Regenerar todos os secrets
Por quê: Cada ambiente deve ter secrets exclusivos. Um secret compartilhado entre ambientes anula o isolamento.
O que fizemos: Gerar novos valores para todos os 14 secrets:
| Secret | Método de geração |
|---|---|
OLP_JWT_SECRET | openssl rand -base64 64 |
OLP_PEPPER_SECRET | openssl rand -base64 32 |
CRON_SECRET | openssl rand -hex 32 |
E2E_TEST_PASSWORD | openssl rand -hex 16 |
SUPABASE_SERVICE_ROLE_KEY | Gerado pelo Supabase (automático) |
4.4 Replicar Storage Buckets
Por quê: Edge Functions que fazem upload (banners, thumbnails) falham se o bucket não existir.
O que fizemos: Criar os 4 buckets públicos no staging:
banners-logincurso-thumbnailstutoriais-thumbnailsmural-imagens
4.5 Deployar 52+ Edge Functions
Por quê: Testes de contrato validam cada Edge Function individualmente. Todas precisam estar deployadas.
O que fizemos: supabase functions deploy --project-ref nrgcajnhpjmwilfcmqyb para cada função. No CI, automatizado via GitHub Actions no estágio deploy-staging.
4.6 Clonar Cloudflare Worker Gateway
Por quê: O frontend staging precisa de um gateway no mesmo domínio (.olp.digital) para que cookies Domain=.olp.digital funcionem cross-origin.
O que fizemos:
- Criar Worker
olp-staging-gatewayno painel Cloudflare - Copiar código do Worker de produção
- Alterar
COOKIE_DOMAINde.olp.digitalpara.staging.olp.digital(subdomínio isolado para evitar vazamento de cookies para produção) - Alterar
SUPABASE_URLpara apontar ao projeto staging - Rota:
gateway-staging.olp.digital/*
4.7 Atualizar CORS
Por quê: Edge Functions validam Origin contra uma allowlist. Staging tem origens novas que precisam ser permitidas.
O que fizemos: Adicionar em cors-helpers.ts:
'https://staging.olp.digital',
'https://gateway-staging.olp.digital',As regras dinâmicas (*.olp.digital) já cobriam, mas origens explícitas garantem clareza.
4.8 Configurar Cloudflare Pages
Por quê: Frontend staging precisa de hosting com deploy automatizado e variáveis de ambiente do staging.
O que fizemos:
- Criar projeto Pages conectado ao repo GitHub
- Branch de produção:
staging(oumaincom variáveis de staging) - Build command:
bun install && bun run build - Output directory:
dist - Variáveis de ambiente:
| Variável | Valor |
|---|---|
VITE_SUPABASE_URL | https://nrgcajnhpjmwilfcmqyb.supabase.co |
VITE_SUPABASE_PUBLISHABLE_KEY | Anon key do staging |
VITE_SUPABASE_PROJECT_ID | nrgcajnhpjmwilfcmqyb |
VITE_USE_WORKER | true |
VITE_WORKER_URL | https://gateway-staging.olp.digital |
4.9 Pipeline CI/CD — 5 estágios
Por quê: Garantir que mudanças só chegam a produção após validação completa em staging.
O que fizemos: GitHub Actions com 5 estágios sequenciais:
┌─────────────────┐
│ 1. lint-and-build│ ← Validação de código (ESLint + TypeScript + Vite build)
└────────┬────────┘
│ ✅
┌────────▼────────┐
│ 2. deploy-staging│ ← supabase db push + functions deploy (Staging Ref)
└────────┬────────┘
│ ✅
┌────────▼────────┐
│ 3. contract-tests│ ← Vitest: smoke test sequencial de cada Edge Function
└────────┬────────┘
│ ✅
┌────────▼────────┐
│ 4. security-tests│ ← Vitest: IDOR cross-escola, isolamento de dados
└────────┬────────┘
│ ✅
┌────────▼────────┐
│ 5. e2e │ ← Playwright: fluxos completos (login → ação → logout)
└─────────────────┘Cada estágio só executa se o anterior passou. Um teste de segurança falhando bloqueia o E2E e, consequentemente, o merge.
4.10 Seed de usuários de teste
Por quê: Testes precisam de usuários com papéis e permissões pré-definidos para validar RBAC.
O que fizemos: 7 usuários Dev cobrindo 11 papéis:
| Usuário | Código (CPF/CNPJ) | Papel(is) | Escola |
|---|---|---|---|
| Dev Admin | 42970698064 | administrador | — (global) |
| Dev Especialista | 63100416066 | especialista | — (global) |
| Dev Coordenador | 99407464075 | coordenador | Monteiro Lobato |
| Dev Diretor | 61626069026 | diretor | Monteiro Lobato |
| Dev Escola | 77457094000157 (CNPJ) | escola | Monteiro Lobato |
| Dev Testes Multi Role | 40750810017 | admin, espec, coord×3, dir, escola, pedag, prof | Todas as 3 escolas |
| Dev Coordenador da Nova Era | 47691226080 | coordenador | Nova Era |
Todos compartilham o secret E2E_TEST_PASSWORD para login via e2e-login. Para detalhes completos dos vínculos, ver seed_test_users.sql.
5. Resultados alcançados
O staging transformou o pipeline de CI de superficial para bloqueador de bugs reais:
5.1 Bugs detectados proativamente
| Bug | Severidade | Detectado em | Impacto evitado |
|---|---|---|---|
| 20 usuários sem permissões (órfãos) | Crítico | Auditoria staging | Coordenadores sem acesso à sidebar |
gestao-usuarios-escola não protege canary no update | Crítico | Auditoria staging | Permissões canary deletadas silenciosamente |
gestao-usuarios-escola não protege canary no update_permissoes | Crítico | Auditoria staging | Permissões canary deletadas em bulk |
features_em_manutencao ausente no list da escola | Médio | Auditoria staging | Frontend escola sem badges de manutenção |
password-history-helper.test.ts regex desatualizado | Baixo | CI staging | Teste falhando silenciosamente |
5.2 Cobertura de testes
| Estágio | Cenários | O que valida |
|---|---|---|
| Contract | ~52 funções | CORS, auth, payloads, status codes |
| Security | ~15 cenários | IDOR, cross-escola, role escalation |
| E2E | ~10 fluxos | Login, navegação, CRUD, logout |
| Total | ~77 cenários | — |
5.3 Tempo de feedback
| Antes (sem staging) | Depois (com staging) |
|---|---|
| Bug detectado em produção pelo cliente | Bug detectado no CI antes do merge |
| Rollback manual + comunicação com escola | Fix no PR, re-run do CI |
| ~2h de impacto mínimo | ~0 impacto em produção |
6. Diagrama de arquitetura — Staging vs Produção
┌─────────────────────────────────────────────────────────┐
│ PRODUÇÃO │
│ │
Usuário Real │ ┌──────────────┐ ┌──────────────────┐ │
───────────────► │ │ olp.digital │───►│ gateway.olp.digital│ │
│ │ (Lovable Host)│ │ (CF Worker) │ │
│ └──────────────┘ └────────┬─────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ mjvuzsizjlcalyfmbquy │ │
│ │ (Supabase Produção) │ │
│ │ OLP_JWT_SECRET = A │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ STAGING │
│ │
CI / Dev │ ┌────────────────────┐ ┌─────────────────────────┐ │
───────────────► │ │ staging.olp.digital │──►│gateway-staging.olp.digital│ │
│ │ (CF Pages) │ │(CF Worker) │ │
│ └────────────────────┘ └───────────┬──────────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ nrgcajnhpjmwilfcmqyb │ │
│ │ (Supabase Staging) │ │
│ │ OLP_JWT_SECRET = B │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Pontos-chave:
┌────────────────────────────────────────────────────────────────────────┐
│ • Secrets A ≠ B — JWT de staging NUNCA funciona em produção │
│ • Mesmo domínio (.olp.digital) — cookies funcionam cross-origin │
│ • Worker staging = clone do Worker produção (apenas URLs diferentes) │
│ • CF Pages staging ≠ Lovable hosting produção (necessidade técnica) │
│ • E2E_TEST_PASSWORD só existe em staging — login direto impossível em prod │
└────────────────────────────────────────────────────────────────────────┘7. Referências
- STAGING_SETUP.md — Checklist de setup técnico
- STAGING_REFERENCE.md — Valores e URLs de referência rápida
- seed_test_users.sql — SQL dos usuários de teste
- DEPLOYMENT.md — Deploy de produção
- AUTHENTICATION.md — Fluxo de autenticação
- CLOUDFLARE_WORKER_GATEWAY.md — Gateway de produção