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 Wasender e MercadoPago não configurados
| Configurar em staging | Não configurar | |
|---|---|---|
| Vantagem | Testar fluxo WhatsApp e pagamento completo | Simplicidade, sem custo |
| Desvantagem | Complexidade de sessão WhatsApp separada | Testes de mural OTP não rodam em CI |
| Decisão | ❌ Não configurar (por enquanto) |
Justificativa: O login direto via E2E_TEST_PASSWORD permite testes de autenticação em CI sem WhatsApp. Pagamentos não são alvo dos testes automatizados atuais. Quando necessário, será configurado com credenciais de sandbox dos provedores.
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 sem OLP_PORTAL_SECRET
| 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). Configurar em staging requer seed adicional e um login direto para o portal. 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.olp.digital(mesmo domínio — staging é subdomínio) - 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 | Papel(is) | Escola |
|---|---|---|
| Admin Dev | admin | — |
| Diretor Dev | diretor | Escola A |
| Coordenador Dev | coordenador | Escola A |
| Professor Dev | professor | Escola A |
| Gestor Dev | gestor_escola | Escola A |
| Multi-Role Dev | coordenador + gestor_escola | Escola B |
| Escola B Dev | diretor | Escola B |
Todos compartilham o secret E2E_TEST_PASSWORD para login via e2e-login.
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