Dois eixos de isolamento, não um
“Multi-tenant” nesta plataforma significa duas coisas diferentes, e é fácil confundi-las:- Isolamento entre parceiros — a Algar não pode ver, nem por acidente, dado de um segundo parceiro que venha a integrar depois. Esse é um problema horizontal: a mesma API, os mesmos produtos, múltiplos consumidores institucionais.
- Isolamento entre produtos — o dado de uma criança no HistorinhAI não pode ter caminho de acesso, nem teórico, através de um bug ou de uma credencial comprometida no projeto que serve NovelAI/NovelAudio. Esse é um problema vertical: sensibilidade de dado diferente exige raio de explosão (blast radius) diferente, independente de quantos parceiros existam.
Isolamento entre produtos: por que a decisão foi diferente para cada caso
- NovelAI + NovelAudio: projeto compartilhado
- HistorinhAI: projeto isolado
NovelAI e NovelAudio vivem no mesmo projeto Supabase (“Novel”) desde o schema inicial. Isso
não é um atalho — é a modelagem correta para o caso: os dois produtos servem o mesmo
catálogo (a mesma novela, os mesmos capítulos, os mesmos finais — um em texto, outro
narrado), frequentemente para o mesmo leitor (a mesma pessoa pode assinar os dois), e o
conteúdo sensível em jogo é preferência de leitura de um adulto — dado pessoal, mas de baixa
severidade se um bug de isolamento vazasse entre as duas superfícies do mesmo projeto.A tabela
profiles marca a origem com uma coluna app_source (novelai ou novelaudio); a
tabela subscriptions tem uma chave composta (user_id, app), porque os dois produtos vendem
planos diferentes para a mesma pessoa. RLS continua isolando usuário de usuário normalmente —
o que não existe é isolamento de infraestrutura entre os dois produtos, porque não faz
sentido operacional pagar esse custo por dois produtos que compartilham 100% do catálogo e a
mesma base de leitores.Trade-off aceito: menos overhead operacional (uma migration, um Auth, um Storage, uma
fatura) em troca de isolamento mais fraco entre os dois produtos — aceitável porque o dado em
jogo tem o mesmo nível de sensibilidade nos dois lados.partner_id: isolando parceiros, não produtos
O eixo de parceiro é ortogonal ao eixo de produto descrito acima, e vive em uma camada diferente:
o projeto Supabase “Platform” (dedicado à API de parceiro, descrito em
Modelo de autenticação) guarda partners e
partner_credentials — estruturalmente preparado para múltiplos parceiros desde o schema
inicial. O entitlement em si (a coluna partner_id junto do subscriber_id) vive do lado de
cada produto, na tabela partner_subscribers do respectivo projeto — não em uma tabela do
Platform.
O isolamento entre parceiros não é feito por policy de RLS comparando partner_id: as
tabelas do domínio de parceiro têm RLS habilitado com zero policies — nenhum acesso para
anon/authenticated; só as Edge Functions do Partner Gateway, via service_role, leem e
escrevem nelas. O filtro por parceiro acontece dentro dessas functions, sempre a partir do JWT de
serviço verificado, nunca de um valor enviado na requisição:
Um segundo parceiro, sem mudança de schema
Quando um parceiro além da Algar quiser integrar — outra operadora, um banco, um varejista — o modelo já está preparado para isso, porque o desenho não codificou “Algar” em nenhum lugar do schema:- Nova linha em
partners(projeto Platform) — nome, escopos permitidos, status. - Novo
partner_credentials—client_id/hash doclient_secretpara esse parceiro trocar por um JWT de serviço em/oauth2/token. - Cada entitlement, cada evento de billing usage, cada webhook desse parceiro nasce com o
partner_iddele — as mesmas tabelas, as mesmas policies, sem alteração de estrutura.
partner_id
mais o produto referenciado no entitlement já expressam essa combinação. O desenho descrito nesta
página (ver Modelo de autenticação) já nasce pronto para
múltiplos parceiros, mesmo antes do segundo parceiro existir.