Skip to content

Staging — Referência Rápida ​

Documento de referência preenchido com os valores reais do ambiente de staging. Para as motivações e decisões por trás do staging, ver STAGING_WHY.md. Para o guia de setup inicial, ver STAGING_SETUP.md.

Última atualização: 2026-04-05


1. Visão Geral da Infraestrutura ​

text
 PRODUÇÃO                                    STAGING
 ════════                                    ═══════

 olp.digital                                 staging.olp.digital
      │                                           │
      ▼                                           ▼
 ┌──────────────────┐                       ┌──────────────────┐
 │ Lovable Hosting  │                       │ Cloudflare Pages │
 │ (frontend)       │                       │ Branch: main     │
 └────────┬─────────┘                       └────────┬─────────┘
          │                                          │
          ▼                                          ▼
 ┌──────────────────┐                       ┌──────────────────┐
 │ CF Worker        │                       │ CF Worker        │
 │ gateway.olp      │                       │ gateway-staging  │
 │ .digital         │                       │ .olp.digital     │
 │                  │                       │                  │
 │ COOKIE_DOMAIN:   │                       │ COOKIE_DOMAIN:   │
 │ .olp.digital     │                       │ .staging.olp     │
 │                  │                       │   .digital       │
 └────────┬─────────┘                       └────────┬─────────┘
          │                                          │
          ▼                                          ▼
 ┌──────────────────┐                       ┌──────────────────┐
 │ Supabase PROD    │                       │ Supabase STAGING │
 │ mjvuzsizjlca...  │                       │ nrgcajnhpjmw...  │
 │                  │                       │                  │
 │ JWT_SECRET: A    │  ← DIFERENTES →       │ JWT_SECRET: B    │
 │ PEPPER: X        │                       │ PEPPER: Y        │
 │ 52+ Edge Funcs   │                       │ 52+ Edge Funcs   │
 └──────────────────┘                       └──────────────────┘

 ⚠️ Token assinado com JWT_SECRET A NÃO funciona no ambiente B (e vice-versa)

2. URLs e Endpoints ​

RecursoURL
Frontendhttps://staging.olp.digital
Worker Gatewayhttps://gateway-staging.olp.digital
Supabase APIhttps://nrgcajnhpjmwilfcmqyb.supabase.co
Edge Functionshttps://nrgcajnhpjmwilfcmqyb.supabase.co/functions/v1/<nome>
Supabase Dashboardhttps://supabase.com/dashboard/project/nrgcajnhpjmwilfcmqyb
Portal Monteiro Lobatohttps://staging.olp.digital/escola/monteiro-lobato
Portal Nova Erahttps://staging.olp.digital/escola/nova-era

3. Supabase Staging — Identificadores ​

ItemValor
Project Refnrgcajnhpjmwilfcmqyb
Região(mesma de produção)
Anon KeyConfigurada no Cloudflare Pages e GitHub Secrets

⚠️ Nunca commitar o service_role_key no código. Está apenas nos GitHub Secrets.


4. Secrets — Supabase Staging ​

Secrets configurados via Supabase Dashboard → Settings → Edge Functions → Secrets.

SecretStatusNota
OLP_JWT_SECRET✅ ConfiguradoPróprio do staging (≠ produção)
OLP_PEPPER_SECRET✅ ConfiguradoPróprio do staging (≠ produção)
E2E_TEST_PASSWORD✅ ConfiguradoMesmo valor no GitHub Actions
CRON_SECRET✅ ConfiguradoPróprio do staging (≠ produção)
WASENDER_API_KEY✅ ConfiguradoCredenciais de staging
WASENDER_WEBHOOK_SECRET✅ ConfiguradoCredenciais de staging
NTFY_TOPIC✅ ConfiguradoTópico diferente de produção
SUPABASE_URL✅ AutoPreenchido automaticamente
SUPABASE_ANON_KEY✅ AutoPreenchido automaticamente
SUPABASE_SERVICE_ROLE_KEY✅ AutoPreenchido automaticamente
SUPABASE_DB_URL✅ AutoPreenchido automaticamente
SUPABASE_PUBLISHABLE_KEY✅ AutoMesmo valor do anon key
OLP_PORTAL_SECRETN/APortal usa OLP_JWT_SECRET (unificado). Não existe no código — ver AUTHENTICATION.md
TWILIO_*❌ DescontinuadoTwilio removido — Wasender é o provedor exclusivo
MP_ACCESS_TOKEN❌ PendenteMercadoPago não configurado

Regra de isolamento ​

text
CORRETO:
  OLP_JWT_SECRET do staging ≠ OLP_JWT_SECRET da produção
  OLP_PEPPER_SECRET do staging ≠ OLP_PEPPER_SECRET da produção
  CRON_SECRET do staging ≠ CRON_SECRET da produção
  NTFY_TOPIC do staging ≠ NTFY_TOPIC da produção

ERRADO:
  Qualquer secret de segurança igual entre staging e produção

5. Rotação de Secrets ​

Quando rotacionar ​

  • Suspeita de comprometimento
  • Rotação periódica (recomendado: a cada 90 dias para secrets de segurança)
  • Mudança de pessoal com acesso

Processo de rotação ​

bash
# 1. Gerar novo valor
NEW_SECRET=$(openssl rand -hex 32)

# 2. Atualizar no Supabase
supabase secrets set --project-ref nrgcajnhpjmwilfcmqyb \
  OLP_JWT_SECRET="$NEW_SECRET"

# 3. Redeploy de TODAS as Edge Functions que usam o secret
#    (o redeploy faz as funções lerem o novo valor do env)
supabase functions deploy --project-ref nrgcajnhpjmwilfcmqyb

# 4. Validar
curl -X POST "https://nrgcajnhpjmwilfcmqyb.supabase.co/functions/v1/auth-diagnostics" \
  -H "Content-Type: application/json" \
  -d '{"action": "check"}'

Dependências de cada secret ​

SecretEdge Functions que dependemImpacto da rotação
OLP_JWT_SECRETTodas que usam auth-helpers.ts (extractAuthenticatedUser) — ~45 funçõesTodas as sessões existentes são invalidadas (JWT assinado com secret antigo falha verificação)
OLP_PEPPER_SECRETsend-otp, verify-otp (hash do OTP)OTPs pendentes falham verificação — novos OTPs funcionam normalmente
CRON_SECRETfaturamento-cron, maintenance-cron, healthcheck-cronCrons autenticados via header X-Cron-Secret param de funcionar até atualizar o caller (Supabase cron ou externo)
E2E_TEST_PASSWORDe2e-loginTestes CI falham até atualizar o GitHub Secret correspondente
WASENDER_API_KEYsend-otp (envio via WaSender)Envio de mensagens WhatsApp falha

Ordem recomendada para rotação do OLP_JWT_SECRET ​

  1. Gerar novo valor
  2. supabase secrets set com novo valor
  3. Redeploy de todas as Edge Functions
  4. Nota: usuários logados serão deslogados (cookie olp_auth contém JWT inválido)
  5. Validar com auth-diagnostics ou login de teste via e2e-login

6. Impacto dos Secrets Pendentes ​

Secret pendenteFuncionalidade quebrada no stagingTestes afetadosWorkaround
Seed data do muralPortal mural sem dados (mural_liberacoes, mural_dados_publicados, escola_mural_config)E2E de mural não rodam no CINenhum — testes de mural estão desabilitados
TWILIO_*(Descontinuado — Wasender é o provedor exclusivo)N/AN/A
MP_ACCESS_TOKENmercadopago-preference retorna erro ao criar preferência de pagamento; mercadopago-webhook não valida assinaturaFluxo de pagamento completo não testávelNenhum — pagamentos não são alvo dos testes CI

7. Mecanismo do e2e-login ​

O login direto para testes automatizados funciona por ausência intencional de secret em produção — sem flags de ambiente, sem código condicional. O E2E_TEST_PASSWORD é a senha real dos usuários de teste, e o e2e-login autentica legitimamente, apenas pulando o envio de OTP WhatsApp.

Como funciona ​

text
1. Edge Function `e2e-login` registrada em config.toml com verify_jwt = false
2. Recebe: { cpf_or_code, papel? } + header X-E2E-Key
3. Valida: X-E2E-Key === Deno.env.get("E2E_TEST_PASSWORD")
4. Se válido: busca usuário → gera JWT → seta cookie olp_auth → retorna dados

Por que é seguro ​

text
PRODUÇÃO:
  E2E_TEST_PASSWORD não está configurado
  → Deno.env.get("E2E_TEST_PASSWORD") retorna undefined
  → "qualquer-valor" === undefined → false
  → 401 Unauthorized (SEMPRE)

STAGING:
  E2E_TEST_PASSWORD está configurado
  → header deve conter o valor exato
  → 200 OK + JWT + cookie

Não é uma flag de ambiente. A segurança não depende de NODE_ENV=production ou qualquer condicional. Depende da ausência física do secret no ambiente de produção. Mesmo que alguém descubra o nome do header e da Edge Function, sem o secret correto a resposta é sempre 401.


8. Secrets — GitHub Actions ​

Repository secrets configurados em Settings → Secrets and variables → Actions.

SecretStatusUso
SUPABASE_ACCESS_TOKEN✅Token pessoal do Supabase CLI
STAGING_PROJECT_REF✅nrgcajnhpjmwilfcmqyb
STAGING_SUPABASE_URL✅https://nrgcajnhpjmwilfcmqyb.supabase.co
STAGING_SUPABASE_ANON_KEY✅Anon key do projeto staging
E2E_TEST_PASSWORD✅Mesmo valor do Supabase staging
SUPABASE_SERVICE_ROLE_KEY✅Para operações administrativas no CI
NTFY_TOPIC_URL✅Notificações de falha do CI (hash ou URL completa)

Como o CI usa os secrets ​

yaml
env:
  VITE_SUPABASE_URL: ${{ secrets.STAGING_SUPABASE_URL }}
  VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.STAGING_SUPABASE_ANON_KEY }}
  E2E_TEST_PASSWORD: ${{ secrets.E2E_TEST_PASSWORD }}

9. Frontend — Cloudflare Pages ​

ItemValor
PlataformaCloudflare Pages
Branchmain
Build Commandbun install && bun run build
Output Directorydist
Domíniostaging.olp.digital (CNAME no Cloudflare DNS)

Variáveis de ambiente (Cloudflare Pages Dashboard) ​

VariávelValor
VITE_SUPABASE_PROJECT_IDnrgcajnhpjmwilfcmqyb
VITE_SUPABASE_PUBLISHABLE_KEY(anon key do staging)
VITE_SUPABASE_URLhttps://nrgcajnhpjmwilfcmqyb.supabase.co
VITE_USE_WORKERtrue
VITE_WORKER_URLhttps://gateway-staging.olp.digital

10. Worker Gateway — Cloudflare ​

ItemValor
Nomeolp-gateway-staging
URLhttps://gateway-staging.olp.digital
COOKIE_DOMAIN.olp.digital (cobre staging.olp.digital via domínio pai)
SUPABASE_URLhttps://nrgcajnhpjmwilfcmqyb.supabase.co

O Worker usa o mesmo código do gateway de produção (ver docs/architecture/CLOUDFLARE_WORKER_CODE.md). As únicas diferenças são as variáveis de ambiente (SUPABASE_URL e SUPABASE_ANON_KEY) configuradas no Cloudflare Dashboard.

Funcionalidades do Worker ​

SeçãoDescrição
CORSValida origins permitidos (localhost, lovable, olp.digital)
CookiesReescreve olp_auth, olp_portal, olp_mural com Domain=.olp.digital
Burst limiter150 req/5s por IP (portal-escola)
Redirect /pagar/staging.olp.digital/pagar/{token} → Edge Function pagar-redirect
Geo headersInjeta X-Geo-City, X-Geo-Region, X-Geo-Country
SegurançaHSTS, X-Frame-Options, CSP

Como atualizar ​

  1. Copiar o código de docs/architecture/CLOUDFLARE_WORKER_CODE.md
  2. Colar em Cloudflare Dashboard → Workers & Pages → olp-gateway-staging → Edit Code
  3. Verificar variáveis de ambiente (SUPABASE_URL, SUPABASE_ANON_KEY)
  4. Deploy

11. Dados de Teste ​

Ver seed_test_users.sql para referência completa.

Escolas ​

NomeSlug (mural)Uso
Escola Municipal Monteiro Lobatomonteiro-lobatoEscola principal
Colegio Particular Nova Eranova-eraIDOR cross-escola
Rede Educacional Futuro—Multi-role

Usuários (7 ativos) ​

NomeCódigoPapelEscola
Dev Admin42970698064administradorGlobal
Dev Especialista63100416066especialistaGlobal
Dev Coordenador99407464075coordenadorMonteiro Lobato
Dev Diretor61626069026diretorMonteiro Lobato
Dev Escola77457094000157escolaMonteiro Lobato
Dev Testes Multi Role407508100179+ papéisTodas
Dev Coordenador da Nova Era47691226080coordenadorNova Era

Multi-Role detalhamento ​

O usuário 40750810017 possui: administrador, especialista, coordenador (×3 escolas), diretor, escola, pedagogico, professor.


12. Pipeline CI ​

text
lint-and-build → unit-tests   ┐
                → deno-tests   ├→ deploy-staging → contract-tests → security-tests → e2e
                → deploy-staging┘    (smoke)         (IDOR)             (Playwright)

O que cada job precisa ​

JobSecretsDados no banco
unit-tests—— (sem rede)
deno-tests—— (testes black-box mockam ou usam env de prod read-only)
deploy-stagingSUPABASE_ACCESS_TOKEN, STAGING_PROJECT_REF—
contract-testsSTAGING_SUPABASE_URL, STAGING_SUPABASE_ANON_KEY, E2E_TEST_PASSWORD7 usuários com CPFs exatos
security-testsIdemCoord Monteiro + Coord Nova Era
e2eIdem + BASE_URL=staging.olp.digitalAdmin, Coordenador, Edge Functions deployadas

13. Storage Buckets ​

BucketPúblicoStatus
banners-login✅Criado
curso-thumbnails✅Criado
tutoriais-thumbnails✅Criado
mural-imagens✅Criado

14. Diferenças Staging vs Produção ​

AspectoProduçãoStagingImpacto no staging
Supabase RefmjvuzsizjlcalyfmbquynrgcajnhpjmwilfcmqybDados e sessões completamente isolados
Domínioolp.digitalstaging.olp.digitalCookies não vazam entre ambientes
FrontendLovable hostingCloudflare PagesDeploy via CF Pages Dashboard ou CI
Gateway Workergateway.olp.digitalgateway-staging.olp.digitalReescrita de cookies no domínio staging
WhatsApp (Wasender)Ativo✅ ConfiguradoProvedor exclusivo de mensagens
Pagamentos (MP)Ativo❌ Não configuradoFluxo de pagamento/fatura não testável
Notificações (WaSender)Ativo✅ ConfiguradoWhatsApp funcional para testes
NTFYTópico de produçãoTópico separadoAlertas de CI não poluem produção
E2E_TEST_PASSWORD❌ Não existe✅ ConfiguradoSenha dos usuários de teste para login direto (sem OTP)
e2e-loginRetorna 401 sempre✅ FuncionalVer §7 — Mecanismo do e2e-login
Portal (mural)Ativo (via OLP_JWT_SECRET unificado)❌ Sem seed data do muralTestes E2E de portal desabilitados

15. Troubleshooting ​

ErroCausaFix
404 "Usuário não encontrado"Usuário de teste não criadoCriar via UI com CPF exato
500 em authCatch block da EF não mapeia erroCorrigir a função
401 com cookie válidoOLP_JWT_SECRET não bateVerificar secret no Supabase staging
CORS bloqueadoOrigem não em ALLOWED_ORIGINSVerificar cors-helpers.ts
Cookie não setadoWorker não reescreve DomainVerificar COOKIE_DOMAIN no Worker
Edge Function timeoutCold startAumentar timeout ou warmup
CI falha "env not set"Secret faltando no GitHubAdicionar em Settings → Secrets

16. Referências ​


17. Escolas de teste — Sandboxes técnicas ​

[SANDBOX-PERF] Resultados (Onda A · Resultados Perf) ​

ItemValor
escola_idaa000000-0000-0000-0000-000000900001
olimpiada_idaa000000-0000-0000-0000-000000900002
Seedseed_resultados_perf.sql
Função resetpublic.fn_sandbox_resultados_reset() (service_role only)
Consumido porsupabase/functions/_shared/__tests__/fn_resultados_stats.contract.test.ts (6 cenários)
Consumido porsupabase/functions/_shared/__tests__/fn_resultados_stats.stress.test.ts (54 combos)
Script localbun run test:contract:sandbox (roda contract + stress)
CIjob deno-tests em .github/workflows/ci.yml (auto-skip se secret faltar)
Secret CI obrigatórioSTAGING_SUPABASE_SERVICE_ROLE_KEY em GitHub → Settings → Secrets → Actions
  • Dataset determinístico (6 inscrições × 2 fases) que valida fn_resultados_stats contra 5 cenários (C1..C5) + sanity + matriz de 54 combinações (fase × nível × whitelist × confirmação).
  • O stress test recomputa a estatística em JS a partir de um fixture in-memory que espelha o seed e compara row-a-row com a RPC. Substitui a janela de 24h de observação de PARIDADE_DRIFT em prod (ver .lovable/plan.md — Gate 3 acelerado).
  • A escola tem excluir_financeiro=true para não aparecer em relatórios de billing.
  • O reset é idempotente e não recria escola/olimpíada/níveis/fases/alunos (estáticos) — apenas inscrições + resultados.
  • NUNCA aplicar em produção. Aplicar apenas no Supabase nrgcajnhpjmwilfcmqyb. Há guard assertNot mjvuzsizjlca nos testes.

18. Stress test do importador de alunos (8k+ linhas) ​

Geração determinística de uma planilha XLSX no formato exato do importador (supabase/functions/_shared/alunos-import.ts + SYSTEM_FIELDS) para subir manualmente em staging e validar o pipeline completo sob volume.

Quando usar ​

  • Validar que o importador aguenta ≥ 8k alunos sem timeout/OOM.
  • Reproduzir em staging o cap silencioso de 1000 do PostgREST (ver mem://architecture/large-dataset-handling-standard) — sintoma do incidente Nível S/J Canguru 2026-06-04.
  • Popular a sandbox com volume real antes de promover a Entrega 2 (paginação server-side + RPC como SoT). Sem 8k alunos a paridade SQL↔JS de fn_resultados_stats roda contra dataset pequeno e perde sinal.

Como rodar ​

bash
# default: 8000 alunos, 80 turmas, seed=42 → /mnt/documents/alunos-stress-8000.xlsx
bun run stress:alunos

# parametrizado:
bun run stress:alunos --count 12000 --turmas 100 --seed 7 --out ./alunos-12k.xlsx

Flags suportadas:

FlagDefaultObservação
--count8000Quantidade de linhas (alunos).
--turmas80Quantidade de turmas distintas (auto-criação no import).
--seed42Determinismo: mesma seed → mesmo arquivo.
--dup-rate0.10Fração de matrículas propositalmente repetidas (exercita branch de conflito).
--missing-cpf-rate0.05Fração de linhas sem CPF (campo opcional).
--out/mnt/documents/alunos-stress-<count>.xlsxCaminho de saída.

Convenção para cleanup determinístico ​

Cada execução marca os registros com:

  • matrícula: prefixo STRESS-<batchTag>- (ex.: STRESS-X1T8H16-000123)
  • email: domínio @stress.olp.test

Isso permite reverter sem afetar dados reais via cleanup_alunos_stress.sql.

Procedimento ponta a ponta (staging) ​

  1. Gerar a planilha com bun run stress:alunos.
  2. Subir na UI: logar em staging como coordenador da escola SANDBOX → Gestão de Alunos → Importar → selecionar o XLSX.
  3. Acompanhar: a iniciar-importacao-alunos despacha trabalho em background (EdgeRuntime.waitUntil, batches de 50). Progresso em importacao_alunos_sessoes.progresso.
  4. Medir:
    • Tempo total (esperado: ~3-6 min para 8k em batches de 50).
    • SELECT count(*) FROM alunos WHERE matricula LIKE 'STRESS-%' vs total mostrado na listagem de coordenador. Divergência = cap de 1000 ativo na UI.
    • Logs de erro: alunos-import edge logs (esperado: ~10% das linhas falhando por matrícula duplicada — esse é o branch que estamos exercitando).
  5. Encadear stress de resultados: depois que alunos + turmas existirem, rodar inscrições em massa e o seed seed_resultados_perf.sql adaptado para popular resultados_aluno. Em seguida, bun run test:contract:sandbox valida paridade contra volume real.
  6. Validar visualmente o dashboard de resultados procurando o sintoma "X no SQL e não na UI" — caso 2026-06-04.

Cleanup ​

sql
-- Conferir antes:
SELECT count(*) FROM public.alunos
 WHERE matricula LIKE 'STRESS-%' OR email LIKE '%@stress.olp.test';

-- Rodar:
\i docs/staging/cleanup_alunos_stress.sql

O script remove em ordem: resultados_aluno → inscricoes_olimpiada → aluno_turma_historico → aluno_responsaveis → alunos → sessões de import sintéticas → turmas órfãs criadas nos últimos 30 dias.

Garantias do gerador ​

  • Colunas alinhadas com SYSTEM_FIELDS (src/components/importacao-alunos/types.ts): nome_completo, data_nascimento (DD/MM/YYYY), sexo (Masculino/Feminino), matricula, cor_raca (Branca/Parda/Preta/Amarela/Indígena/Não declarada), cpf (com DV válido), email, telefone (vazio), turma (formato "8º Ano - EF - A" reconhecido pelo turma-matching).
  • CPFs com dígito verificador válido (algoritmo oficial), embora o backend valide apenas length === 11.
  • Datas de nascimento coerentes com a série (idade típica ± 1 ano).
  • Determinismo via mulberry32 — mesma seed produz arquivo byte-idêntico.

Por que NÃO esperar a Entrega 2 ​

Ver .lovable/plan.md (plano aprovado): a paginação server-side e a promoção do RPC como SoT eliminam drifts que só aparecem com volume. Subir 8k antes garante antes/depois comparável; subir só depois cega a auditoria.