# RLS e multi-tenant

Todo app Fayz é multi-tenant: vários clientes (tenants) compartilham o mesmo banco. O isolamento entre eles **não** é garantido no código da aplicação, e **não** vem de separar schemas — é garantido **no banco**, com Row Level Security (RLS) do Postgres. O RLS **é** a fronteira de isolamento do modelo: uma linha de um tenant é fisicamente invisível para os outros, mesmo que a aplicação erre.

## A regra única: `tenant_id` + RLS

A regra é a mesma em todas as camadas do [modelo de dados](/pt-BR/docs/dados/modelo) — core, tabelas `plg_` de plugin e as tabelas do seu app: toda tabela carrega uma coluna `tenant_id` e tem uma policy de RLS chaveada nela. A forma **canônica** escopa a linha aos tenants a que o usuário logado pertence:

```sql
CREATE POLICY tenant_isolation ON <tabela>
  USING (tenant_id IN ( /* tenants do usuário logado */ ));
```

O conjunto de tenants do usuário vem de uma função no schema `public`, resolvida a partir do vínculo do usuário com cada tenant. A policy vale para SELECT, INSERT, UPDATE e DELETE. Resultado: uma query nunca enxerga uma linha de um tenant ao qual o usuário não pertence — o Postgres filtra antes de a aplicação ver.

{% callout type="info" %}
O **conceito** — policy escopada por `tenant_id` ao conjunto de tenants do usuário — é estável. O **nome exato** da função e o texto SQL literal dependem da wave de pacotes instalada e podem mudar de rótulo; trate o SQL acima como a forma, não como uma assinatura fixa.
{% /callout %}

## Website e admin: mesmas tabelas, papéis diferentes

O isolamento por tenant é só o primeiro eixo. O segundo é **quem, dentro do tenant, vê o quê** — e ele também mora no RLS, sobre as **mesmas tabelas**. O lado do site e o lado do admin não têm tabelas separadas (`public_*`/`admin_*`); têm **policies e papéis diferentes** sobre a mesma tabela: o `staff` enxerga as linhas do tenant, o cliente final enxerga só as próprias. É por isso que o Fayz nunca duplica tabelas por superfície — uma tabela, várias visões, todas garantidas pelo banco.

## Canônico vs. deferido

Nem todo plugin escreve a policy inline, e isso é intencional:

- **canônico** — a policy escreve a forma canônica (o `tenant_id` escopado ao conjunto de tenants do usuário) direto na migration do plugin.
- **deferido** — o plugin habilita RLS (`ENABLE ROW LEVEL SECURITY`) mas **não** cria a policy; a tabela (com coluna `tenant_id`, tipo `BASE TABLE`, sem prefixo `_`) é capturada por um bloco de auto-detecção do app, que emite **exatamente** a policy canônica no momento do apply.

Um plugin deferido, portanto, **aterrissa como canônico** num banco real — não é uma divergência, só um deferimento de *onde o texto da policy mora*. As duas formas produzem o mesmo isolamento. Qualquer outra forma (`divergent`, `no-rls`) é tratada como problema a padronizar antes de mergear.

## As tabelas do seu app também isolam

As tabelas que o seu app ou incubator cria não são exceção. Uma tabela de clientes ou de agendamentos que você adicione **precisa** carregar `tenant_id` e ter RLS. É fácil esquecer, porque essas tabelas são suas; a regra vale igual para elas — e para as tabelas `plg_` dos plugins.

## Migrations são append-only depois de aplicadas

Um detalhe operacional que evita corrupção silenciosa: **uma migration que já rodou contra qualquer banco real é imutável.** Corrija com uma migration *nova*, nunca editando o arquivo — o arquivo divergiria do banco em silêncio.

A exceção é antes do primeiro apply: um app greenfield, ou o schema-fonte de um plugin que ainda não rodou, pode ser editado no lugar (é até preferível — um apply novo deve já produzir a forma final). A regra de bolso: *já tocou um banco vivo? → migration nova. Nunca aplicou? → edita no lugar.*

{% callout type="tip" %}
Você quase nunca escreve essas policies à mão. O `fayz db apply` emite a forma canônica por você, e o padrão de tabelas do modelo (core, `plg_`, suas tabelas) já garante que toda tabela nova entre isolada. O que esta página te dá é o *porquê* — para você reconhecer uma tabela mal isolada quando vir uma.
{% /callout %}

---

Próximo: como o usuário logado é resolvido em [Auth — visão geral](/pt-BR/docs/auth/visao-geral).
