Por que usuários de staging nunca chegam à produção: ambientes isolados para auth
Ambientes na Nulvless são armazenamentos separados, não uma coluna. Veja o que isso impede e o que passa de um ambiente para outro.
Nesta página
A maioria dos produtos modela ambientes como uma coluna. Usuários, sessões e
configurações ficam nas mesmas tabelas com um environment_id ao lado, e cada
consulta lembra de filtrar por ele. Isso funciona até uma consulta esquecer.
Na Nulvless, um ambiente é uma cópia completa do projeto, com seus próprios usuários, sessões, identidades, provedores, templates e chaves. Este post explica por que escolhemos isso, quanto custa e o que passa entre ambientes no lugar dos dados.
Uma coluna é uma promessa que toda consulta precisa cumprir
Quando os ambientes dividem o armazenamento, o isolamento é uma propriedade do seu código, não dos seus dados. Um filtro faltando em um script de suporte, um endpoint de admin que faz join pela chave errada ou um cache indexado só pelo id do usuário bastam para mostrar uma conta de teste em produção ou mandar para um cliente real um e-mail vindo de staging.
Esses bugs são raros, e é isso que os torna caros. Eles não aparecem nos testes, porque os testes rodam em um ambiente por vez.
Um armazenamento separado torna o bug impossível
Cada ambiente responde na sua própria URL de emissor:
organization
└── project · acme
├── environment · main → acme.nulvless.co
├── environment · staging → acme-staging.nulvless.co
└── environment · develop → acme-develop.nulvless.coUm usuário criado em develop não existe em produção, e nunca vai existir,
porque nunca foi gravado naquele armazenamento. Não há filtro para esquecer.
A URL do emissor também é o primeiro argumento de createClient, então apontar
um app para outro ambiente é trocar uma URL e uma chave. As chaves também são
por ambiente: uma chave publicável de staging não é aceita por produção.
O que passa: configuração, como diff
Dados nunca passam entre ambientes. Configuração passa, mas só quando você promove, e uma promoção começa como um diff, não como uma escrita.
A tela Compare recebe uma origem e um destino e lista cada configuração que difere: regras de cadastro, métodos de login, política de senha, MFA, tokens, provedores sociais, modo sudo, proteção contra ataques, o OAuth server, rate limits e notificações. Cada linha mostra o valor atual do destino e o valor que está chegando. Ler essa lista é a revisão.
Por baixo, o diff tem duas seções, auth e communications, cada uma uma lista
de field, before e after. O agrupamento que você vê no console é só
apresentação. A unidade que passa é um campo de configuração.
O que uma promoção deixa para trás de propósito
Algumas coisas nunca vão junto em um merge, e nenhuma delas é erro:
- Credenciais e segredos. Chaves de API de provedores, senhas de SMTP e segredos de clientes OAuth são configurados à mão no destino. Uma chave de staging nunca deve virar chave de produção por acidente.
- Usuários, sessões e identidades. A promoção move configuração, nunca dados.
- Edge functions. Uma função pertence ao projeto, não a um ambiente, então não tem lugar em um diff de ambiente.
A primeira é a que mais surpreende. Um provedor pode ser promovido como ativo enquanto as credenciais ainda faltam no destino, e aí ele falha na hora de entregar, não na hora do merge. Por isso a tela Compare avisa, e por isso o fluxo recomendado configura as credenciais uma vez, antes da primeira promoção que precisar delas.
Aplicando
O merge é protegido pelo modo sudo: ao confirmar, a pessoa precisa provar que ainda está no teclado, e essa confirmação vale por uma hora, então uma sequência de promoções pergunta uma vez só.
Depois o merge roda como um pipeline e gera um deployment, com cada etapa cronometrada: inicialização, autenticação, comunicações. Uma execução típica termina em cerca de três segundos.
Um merge não pode ser desfeito, então o histórico importa. Cada merge registra quem pediu, um log de eventos com horário e as linhas exatas de antes e depois que foram revisadas. Meses depois, esse registro responde "quem mudou isso, e qual era o valor antes?" sem que ninguém precise ter anotado.
O custo, e por que aceitamos
Armazenamentos separados não são de graça. Um ambiente novo começa vazio: sem usuários, sem provedores, sem nada da configuração que você montou em outro lugar. Essa é a ideia, e também a primeira surpresa da maioria das equipes.
Achamos que essa troca é a certa para identidade. Uma tabela compartilhada economiza alguns cliques na configuração e deixa uma classe de incidente aberta para sempre. Um armazenamento separado custa uma promoção deliberada, e em troca o pior bug de ambiente vira um que não pode acontecer.
O formato que recomendamos é simples: mude as coisas em develop, compare com
staging e leia o diff, e promova para main por último. Se o diff ficar
grande, promova com mais frequência. O
guia de ambientes cobre cada etapa em detalhe.
