schema-per-tenant: F0 categorizacao + F1 template/helpers + F2 provisionamento

- docs/F0_categorizacao.md: varredura completa (137 tabelas -> 84 tenant + 53
  global, 66 funcoes, FKs, policies, edge functions) + decisoes Q1-Q4
- F1 (migrations 01-05): tenants.slug, helpers de schema, _tenant_template
  (84 tabelas sem tenant_id, singletons, views __SCHEMA__/__TENANT_ID__),
  clone_tenant_template/drop_tenant_schema, channel_routing, tenant_schemas
- F2 (migration 06): provision_account_tenant/create_clinic_tenant/
  ensure_personal_tenant_for_user clonam schema na mesma transacao
- db.cjs: psqlFile agora usa ON_ERROR_STOP=1 (falha de migration nao passa
  mais como sucesso silencioso)
- blueprint original em novo-rumo.txt; wiki Obsidian atualizada

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-06-12 11:58:46 -03:00
parent b0b636c660
commit 05c6746e33
12 changed files with 1718 additions and 1 deletions
+6
View File
@@ -1642,3 +1642,9 @@ PROXIMA SESSAO (retomar amanha 22/05):
PUSH PENDENTE: 35 commits ahead of origin/main; SSL self-signed
do Gitea exige `git -c http.sslVerify=false push origin main`
+ credenciais (user faz manual).
## [2026-06-12 10:47] session | F0 schema-per-tenant: varredura e categorizacao
Touched: Migracao Schema-per-Tenant, index
## [2026-06-12 11:49] session | F1 schema-per-tenant: template + helpers + clone
Touched: Migracao Schema-per-Tenant
@@ -0,0 +1,53 @@
# Migração Schema-per-Tenant
**Status:** F2 concluída e smoke-testada (2026-06-12). Próximo: F3 (frontend useTenantDb).
## F2 — entregue (migration 20260612000006)
Os 3 únicos pontos de criação de tenant (`provision_account_tenant`, `create_clinic_tenant`, `ensure_personal_tenant_for_user` — este último também acionado pelo trigger de signup `handle_new_user_create_personal_tenant`) agora chamam `clone_tenant_template()` na mesma transação: clone falhou → tenant não nasce. Smoke: ensure_personal criou tenant pessoal `tenant_terapeuta_pessoal` com 84 tabelas + registro, 2ª chamada idempotente, drop limpou tudo. Não há fluxo de exclusão de tenant no sistema (drop_tenant_schema fica pra uso admin/manual).
## F1 — entregue (migrations 2026061200000105 em database-novo/migrations/)
- `tenants.slug` criado + backfill dos 9 + trigger auto-gera/imutável
- Helpers: `tenant_schema_name/for`, `tenant_id_for_schema`, `tenant_schema_checked(p_tenant_id)` (valida `is_tenant_member` — substitui current_tenant_schema do blueprint)
- `_tenant_template`: 84 tabelas sem tenant_id, 6 singletons (`singleton boolean PK/UQ` nas configs 1-linha: company_profiles, email_layout_config, conversation_autoreply_settings/bots/sla_rules, session_reminder_settings), 4 sequences locais, 94 FKs (62 intra + 32 pra public/auth), 6 views com placeholders `__SCHEMA__`/`__TENANT_ID__` em `_views`, seeds de sistema (whitelist 8 lookups)
- `clone_tenant_template(uuid)` → tabelas+seqs+seeds+FKs+views+RLS (policies com tenant_id EMBUTIDO: `is_tenant_member('<uuid>')` + saas_admin_full)+realtime+grants+trigger routing+registro em `tenant_schemas`
- `drop_tenant_schema(uuid)` protegido; `public.channel_routing` (webhook inbound acha tenant do canal) sincronizada por trigger
- Smoke: clone tenant_smoke_f1 → 84 tabelas/168 policies/roundtrip/routing sync/singleton rejeitando 2ª linha → drop limpo
### Gotchas aprendidos na F1
- **`postgres` não é superuser no Supabase** → `session_replication_role` proibido; seeds usam retry-loop de FK (rounds). Vale pro F6 (migração de dados): rodar como `supabase_admin` ou retry-loop.
- **db.cjs aplicava migration sem `ON_ERROR_STOP`** → rollback silencioso reportado como sucesso. Corrigido (psqlFile agora usa `-v ON_ERROR_STOP=1`).
- Linhas operacionais órfãs com tenant_id NULL (intakes/convites/notifs) NÃO são seeds — whitelist explícita.
- Clones F1/F2 ainda SEM triggers de negócio (F6) e fora do PostgREST (F5) — `_meta.triggers_pending=true`.
Migração de multi-tenant RLS-only (tenant_id em cada tabela) para schema físico por tenant (`tenant_<slug>`), seguindo blueprint do projeto irmão (`novo-rumo.txt` na raiz), adaptado.
## Artefatos
- `docs/F0_categorizacao.md` — varredura completa: classificação das 137 tabelas, 66 funções, 6 views, FKs, edge functions, divergências.
- `novo-rumo.txt` (raiz) — blueprint original com lições do projeto irmão.
## Números-chave
- 137 tabelas public → 79 tenant-scoped + 5 em decisão (infra mensageria) + 53 globais
- 66 funções afetadas (blueprint avisava: listas pré-feitas subestimam — era "29" lá, 66 aqui)
- 1 única FK global→tenant problemática: `whatsapp_credits_transactions.conversation_message_id`
- 0 policies de tabelas globais usando funções a refatorar
- 9 tenants (3 clínicas + 6 therapists), volumetria minúscula (<400 linhas/tabela)
## Divergências vs blueprint (decisivas)
1. **Sem `tenants.slug`** — precisa criar coluna ou usar uuid no nome do schema.
2. **Multi-membership**: `profiles.tenant_id` 100% NULL; verdade vive em `tenant_members` (4 users multi-tenant). `current_tenant_schema()` do blueprint não funciona → frontend escolhe schema ([[tenantStore]] já tem `activeTenantId`), segurança via policy com tenant_id embutido por schema + RPCs recebem `p_tenant_id` validado com `is_tenant_member()`.
3. **6/9 tenants são terapeutas individuais** — schema por signup; custo operacional do config.toml do PostgREST cresce com tenants.
4. `email_layout_config.tenant_id` e `email_templates_tenant.tenant_id` apontam pra **auth.users** (legado) — mapear na migração de dados.
5. View `current_tenant_id` é código morto (claim JWT nunca populado).
## Decisões (2026-06-12)
- Q1: **criar `tenants.slug`** → schemas `tenant_<slug>`
- Q2: **todo tenant ganha schema** (clínicas e therapists)
- Q3: **mensageria tenant-scoped** (isolamento máximo, contra rec. global) → crons varrem tenants em loop; webhooks inbound precisam de índice global `channel_routing` (channel_external_id → tenant_id) pra rotear antes de gravar
- Q4: **asaas tenant** (staging `asaas_webhook_events` global roteia)
Total final: **84 tabelas tenant-scoped, 53 globais.**
## Fases (tasks #1#7 na sessão)
F0 categorização ✅ · F1 template+helpers · F2 provisionamento · F3 frontend useTenantDb · F4 edge functions · F5 PostgREST config · F6 rewrite funções + migração dados + drops (lotes, backup antes de cada um)
Relacionados: [[Decisões de Billing da Agenda]], [[Supabase Local]], [[index]]
+1
View File
@@ -30,3 +30,4 @@ _(synthesized answers to questions you've asked, filed back as pages)_
---
*This index is maintained by Claude via `/wiki-brain`. Do not edit by hand unless you know what you're doing.*
- [[Migracao Schema-per-Tenant]] — migração RLS-only → schema físico por tenant (F0 done, aguardando Q1-Q4)