Skip to content

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:

  1. Perda de acesso: coordenador perde permissão de agenda → escola não consegue operar
  2. 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 durante update e update_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.

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:

SecretImpacto se comprometido
OLP_JWT_SECRETForjar sessões de qualquer usuário
OLP_PEPPER_SECRETComprometer hashes de senha
CRON_SECRETExecutar jobs administrativos
E2E_TEST_PASSWORDSenha 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

ComponenteProduçãoStagingPor que importa
Gatewaygateway.olp.digital (Worker)gateway-staging.olp.digital (Worker)Cookie rewrite + CORS + geo headers idênticos
FrontendLovable hosting (olp.digital)Cloudflare Pages (staging.olp.digital)Build Vite idêntico
DNSCloudflare DNSCloudflare DNSSubdomí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 → output dist/
  • Deploy automático via GitHub (branch staging)
  • Preview deploys por branch
  • Zero configuração de servidor

2.4 Custo zero

RecursoFree TierUso estimado staging
Workers100.000 req/dia< 5.000 req/dia
Pages500 builds/mês< 50 builds/mês
DNSIlimitado2 subdomínios

O staging inteiro opera no free tier da Cloudflare sem custo adicional.

2.5 Alternativas descartadas

AlternativaPor que descartada
VercelNão resolve cookies cross-origin (sem Worker no mesmo domínio)
NetlifyMesmo problema de cookies; sem Worker nativo
Lovable hostingNão suporta múltiplos ambientes (produção e staging simultâneos)
Docker localNã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 PagesLovable hosting
VantagemBranch deploys, CI integrado, mesmo domínio do WorkerZero config, preview automático
DesvantagemBuild 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 separadoMesmo projeto (schemas diferentes)
VantagemIsolamento total de secrets, RLS, dadosSem custo extra, schema unificado
DesvantagemGestão de 2 projetos, deploy duplicado de Edge FunctionsRisco 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çãoStatusDecisão
Wasender (WhatsApp)✅ ConfiguradoCredenciais de staging independentes — OTP WhatsApp funcional
MercadoPago❌ Não configuradoPagamentos 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órioWorker no painel Cloudflare
VantagemVersionado, deploy automatizadoEdição rápida, sem build pipeline
DesvantagemPipeline adicional, complexidadeDuplicaçã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 fixosUUIDs gerados
VantagemReferência cruzada direta nos testesIsolamento entre runs
DesvantagemColisão se aplicar seed 2x sem limparTestes 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 secretWhatsApp sandbox
VantagemZero custo, zero latência, CI-friendlyTeste real do fluxo WhatsApp
DesvantagemNão testa envio WhatsApp realCusto, 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 stagingNão configurar
VantagemTestes E2E do portal aluno/responsávelMenos complexidade
DesvantagemMais 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:

  1. pg_dump --schema-only de produção
  2. Aplicar no staging via psql
  3. 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:

SecretMétodo de geração
OLP_JWT_SECRETopenssl rand -base64 64
OLP_PEPPER_SECRETopenssl rand -base64 32
CRON_SECRETopenssl rand -hex 32
E2E_TEST_PASSWORDopenssl rand -hex 16
SUPABASE_SERVICE_ROLE_KEYGerado 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-login
  • curso-thumbnails
  • tutoriais-thumbnails
  • mural-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:

  1. Criar Worker olp-staging-gateway no painel Cloudflare
  2. Copiar código do Worker de produção
  3. Alterar COOKIE_DOMAIN de .olp.digital para .staging.olp.digital (subdomínio isolado para evitar vazamento de cookies para produção)
  4. Alterar SUPABASE_URL para apontar ao projeto staging
  5. 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:

typescript
'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:

  1. Criar projeto Pages conectado ao repo GitHub
  2. Branch de produção: staging (ou main com variáveis de staging)
  3. Build command: bun install && bun run build
  4. Output directory: dist
  5. Variáveis de ambiente:
VariávelValor
VITE_SUPABASE_URLhttps://nrgcajnhpjmwilfcmqyb.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEYAnon key do staging
VITE_SUPABASE_PROJECT_IDnrgcajnhpjmwilfcmqyb
VITE_USE_WORKERtrue
VITE_WORKER_URLhttps://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árioCódigo (CPF/CNPJ)Papel(is)Escola
Dev Admin42970698064administrador— (global)
Dev Especialista63100416066especialista— (global)
Dev Coordenador99407464075coordenadorMonteiro Lobato
Dev Diretor61626069026diretorMonteiro Lobato
Dev Escola77457094000157 (CNPJ)escolaMonteiro Lobato
Dev Testes Multi Role40750810017admin, espec, coord×3, dir, escola, pedag, profTodas as 3 escolas
Dev Coordenador da Nova Era47691226080coordenadorNova 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

BugSeveridadeDetectado emImpacto evitado
20 usuários sem permissões (órfãos)CríticoAuditoria stagingCoordenadores sem acesso à sidebar
gestao-usuarios-escola não protege canary no updateCríticoAuditoria stagingPermissões canary deletadas silenciosamente
gestao-usuarios-escola não protege canary no update_permissoesCríticoAuditoria stagingPermissões canary deletadas em bulk
features_em_manutencao ausente no list da escolaMédioAuditoria stagingFrontend escola sem badges de manutenção
password-history-helper.test.ts regex desatualizadoBaixoCI stagingTeste falhando silenciosamente

5.2 Cobertura de testes

EstágioCenáriosO que valida
Contract~52 funçõesCORS, auth, payloads, status codes
Security~15 cenáriosIDOR, cross-escola, role escalation
E2E~10 fluxosLogin, 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 clienteBug detectado no CI antes do merge
Rollback manual + comunicação com escolaFix 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