Isolamento, escala e governança — Gita Agents¶
Documento estratégico e operacional. Responde três perguntas críticas: (1) qual a garantia real de que clientes não se misturam, (2) quantos clientes o sistema aguenta hoje, e (3) como governar isso em escala. Escrito em 2026-04-21 após auditoria profunda de código e capacidade.
Contexto¶
Após validar 13 fluxos end-to-end com 100% de sucesso na bateria v15, Junior priorizou investir em estrutura de gestão e garantias de isolamento antes de expandir pra mais clientes. A Gita opera hoje 3 tenants no mesmo engine multi-tenant. Um incidente em 18/04/2026 (lead da Janaina recebendo resposta de outro cliente) foi corrigido, mas a auditoria mostrou que o isolamento atual depende inteiramente de código + disciplina operacional, sem travas estruturais no banco.
Objetivo deste doc: documentar o plano em 4 fases pra transformar o sistema num case à prova de mistura e pronto pra 30-50 clientes simultâneos sem cirurgia estrutural.
1. Estado atual de isolamento¶
1.1 O que já protege¶
| Proteção | Onde | Status |
|---|---|---|
Sticky routing filtrado por client_name |
router-classifier.ts:88-125 |
✅ ativo desde fix 18/04/2026 |
| Histórico cross-agent filtrado por tenant | history.ts:46-52 (JOIN com ai_agents.client_name) |
✅ ativo |
Chaves Redis sempre com agent_id |
debounce.ts, filters.ts (block_ia, whitelist, CRM cache) |
✅ ativo |
slug UNIQUE global |
supabase/migrations/001_multi_tenant_schema.sql |
✅ ativo |
UNIQUE(agent_id, remote_jid) em leads |
mesmo schema | ✅ ativo |
| FK com CASCADE DELETE (agent_id) | mesmo schema | ✅ ativo |
Audit script audit:tenants |
scripts/audit-tenant-isolation.ts |
⚠️ checa consistência de client_name, não checa duplicação de config |
| Cross-product blocking dentro do cluster Gita | router-classifier.ts:204-235 (productOf() + isVagasPriority) |
✅ validado v15 |
1.2 Riscos reais de mistura (sem proteção estrutural)¶
| ID | Cenário | Impacto | Probabilidade | Fix na Fase 1 |
|---|---|---|---|---|
| R1 | Agente com client_name="" ou NULL |
Queries eq('client_name', ...) falham silenciosamente; lead órfão pode ser servido por outro tenant |
Alta (admin distraído) | ✅ CHECK constraint + validação no admin |
| R2 | Dois tenants com mesmo crm_config.account_id |
Label pausar-ia pausa agente errado; chatwoot/inbound-message grava mensagem em agente do tenant errado |
Média (copiar-colar) | ✅ Audit v2 detecta + endpoint fail-closed |
| R3 | Mesmo notification_clickup_channel_id em 2 tenants |
Reportar do cliente A aparece no canal de B | Média | ✅ Audit v2 detecta |
| R4 | Mesmo token Uazapi em 2 agentes | Webhook do cliente A processado no contexto de B sem detecção | Baixa mas catastrófica | ✅ Audit v2 detecta + source validation |
| R5 | Webhook Uazapi cai no slug errado (typo ou manipulação) | Mensagem processada no agente errado | Baixa | ✅ Webhook valida BaseUrl contra provider_config.api_url |
| R6 | Mesmo remote_jid em múltiplos tenants (cross-tenant leads) |
Auditoria loga mas não bloqueia; comportamento legítimo em alguns casos (ex: seu próprio 1770), mas router não emite alerta pra fora | Já acontece | ✅ Evento router.cross_tenant_detected vira alerta ClickUp |
1.3 Proteções estruturais ausentes (visão sistêmica)¶
- Sem RLS no Supabase: qualquer query fora de padrão pode vazar. Confiamos 100% no code review e audit script.
client_nameé string livre: sem tabelaclientsdedicada com FK, typos criam tenants fantasmas.- Sem validação de source em webhooks: normalizer confia no slug na URL.
- Sem constraint de unicidade em configs externas (
account_id, tokens, channel_ids).
2. Capacidade atual e bottlenecks¶
2.1 Uso real hoje¶
- Janaina Ortiga: 🟢 produção (~50 conversas/dia · 4 agentes)
- Gita Agentes (cluster): 🟡 validação (9 agentes, volume baixo)
- Denise Ramos: 🟡 validação
Total ativo: ~1.5 clientes em uso real.
2.2 Bottlenecks em ordem de aparição¶
| Bottleneck | Aperto em | Mitigação | Custo |
|---|---|---|---|
| Gemini Flash rate limit (15 RPM) | ~10-15 clientes simultâneos ativos | Upgrade Pro (300 RPM) ou múltiplas API keys | $ |
| Uazapi instância compartilhada (cluster Gita) | ~30-50 msgs/seg | Instância dedicada por cliente ou upgrade de plano | $$ |
| Supabase Pro writes (~200 reqs/seg) | ~100 clientes volume Janaina | Upgrade Team ($599/mo) | $$$ |
| Follow-up loop serial | ~10k leads elegíveis num ciclo | Paralelização + distributed lock real no lead | eng |
| Engine single-thread Node | ~200 eventos/seg | 2-4 réplicas horizontais | eng |
2.3 Capacidade estimada¶
- Sem investimento novo: 10-15 clientes com volume Janaina sem degradação.
- Fase 1 + Fase 2: 30-50 clientes (proteções + visibilidade, sem mudança de infra).
- Fase 3 (2 réplicas + Gemini Pro + follow-up paralelo): 100-150 clientes.
- Fase 4 (RLS +
clientstable + template CLI): 300+ clientes com onboarding de 30min.
2.4 Custo marginal¶
| Componente | Por cliente / mês |
|---|---|
| Uazapi (instância dedicada) | $100-300 |
| Gemini Flash | $0.20-0.50 |
| Gemini Pro (se usado) | $2-5 |
| Supabase (rateado) | $5-15 |
| Redis (rateado) | $1-2 |
| Whisper | $0.60 |
| Total | $130-370 |
Uazapi domina. Possibilidade de reduzir: avaliar consolidar instâncias por cluster ou negociar volume.
2.5 Pontos de contenção não-técnicos¶
- Onboarding manual: 2-4h por cliente (3 hardcodes + migration SQL + config admin).
- Edição de prompts: admin UI com cache 60s (OK) ou SQL direto.
- Sem alertas automáticos: hoje review manual de logs.
- Sem SLO/SLA formalizado.
3. Plano em 4 fases¶
Fase 1 — Isolamento à prova (1-2 dias, esta semana)¶
Objetivo: zero tolerância a mistura. Travas estruturais + audit expansivo + validações de origem.
| Item | Descrição | Arquivo |
|---|---|---|
| F1.1 | Migration: CHECK (client_name IS NOT NULL AND length(trim(client_name)) > 0) em ai_agents |
supabase/migrations/xxx_tenant_isolation.sql |
| F1.2 | Audit v2: detecta duplicação de crm_config->>account_id, provider_config->>token, notification_clickup_channel_id |
scripts/audit-tenant-isolation.ts |
| F1.3 | Webhook Uazapi valida BaseUrl (do payload) contra provider_config.api_url do agente |
packages/engine/src/pipeline/normalizer.ts ou middleware index.ts |
| F1.4 | Endpoint chatwoot/inbound-message: fail-closed se retornar agentes de >1 tenant |
packages/engine/src/index.ts handler |
| F1.5 | Evento router.cross_tenant_detected dispara notificação ClickUp (tool reportar interna) |
packages/engine/src/pipeline/router-classifier.ts |
| F1.6 | npm run audit:all no pre-push hook + CI workflow (se houver GitHub Actions) ou documentado em runbook de deploy |
.husky/pre-push ou runbook-novo-cliente.md |
Critério de sucesso: auditoria rodando verde em CI. Tentativa de criar agente com client_name vazio é barrada no banco. Dois agentes com mesmo account_id gera alerta.
Fase 2 — Observabilidade operacional (2-3 dias, próxima semana)¶
Objetivo: métrica, dashboard e alerta. Sem observar, não dá pra gerir.
| Item | Descrição |
|---|---|
| F2.1 | Endpoint /metrics (Prometheus format): Gemini RPM por agente, DB writes/s, msgs/s por tenant, follow-up queue size, latência p50/p95 de pipeline |
| F2.2 | Dashboard no admin: últimas 24h × 7d × 30d — leads novos, qualificações, agendamentos, reportars, erros por agente |
| F2.3 | Alertas ClickUp: erro/min > 5, deploy travado >10min, Gemini 429, router.cross_tenant_detected, audit falhando |
| F2.4 | Cron diário: npm run audit:all + report no ClickUp #Comercial (ou novo #Infra) |
| F2.5 | Funnel por estágio: captação → qualificado (fit) → agendado → converted, por cliente |
Critério de sucesso: Junior consegue ver saúde dos 3 tenants num dashboard. Alerta dispara antes do primeiro cliente reclamar.
Fase 3 — Capacidade (quando chegar a 5-10 clientes ativos)¶
| Item | Descrição |
|---|---|
| F3.1 | 2 réplicas engine + distributed lock no lead (não só na função de follow-up) |
| F3.2 | Upgrade Gemini → Pro (300 RPM) ou múltiplas keys com round-robin por agente |
| F3.3 | Avaliar Supabase Team (ou otimizar agent_logs com retenção mais curta + agregação) |
| F3.4 | Plano Uazapi: instância dedicada por cliente vs. compartilhada por cluster (decidir com base em volume) |
Fase 4 — Governança estrutural (próximos 2-3 meses)¶
| Item | Descrição |
|---|---|
| F4.1 | Tabela clients com FK em ai_agents.client_id (substitui client_name string). Migration preserva dados atuais. |
| F4.2 | RLS Supabase com JWT por tenant (service_role só pro admin interno). |
| F4.3 | Template CLI: npm run new-client -- --name='X' --slug='y' --segment='z' gera agente + migration + seed. Onboarding: 2-4h → 30min. |
| F4.4 | Runbook de incidente: SLO (99% uptime engine, <5min response alert), SLA de resposta (quem é chamado), post-mortem template. |
| F4.5 | Rotação de credenciais: calendar (anual), tokens Uazapi/Chatwoot/ClickUp (semestral). |
4. SLO / SLA operacional (proposta inicial)¶
Quando hoje não temos nada formal. Proposta pra discussão:
| Métrica | Alvo |
|---|---|
| Uptime engine | 99.5% mensal (~3.5h downtime/mês tolerável) |
| Latência de resposta (webhook → send) | p95 < 30s (inclui Gemini + Whisper) |
| Taxa de erro no pipeline | < 0.5% |
| Tempo de detecção de incidente | < 5min (via alerta automático) |
Audit cross_tenant_detected |
0 alertas/semana (exceto cross-tenant conhecido/legítimo) |
| Onboarding novo cliente | ≤ 2h (Fase 4: ≤ 30min) |
5. Checklist operacional pós-implementação¶
Diário (automatizado):
- [ ] Cron npm run audit:all roda 08:00 e posta em ClickUp
- [ ] Dashboard de métricas consultável no admin
Por deploy (pre-push hook + CI):
- [ ] npm run audit:all retorna 0 violations
- [ ] Testes: npm run test:v15 (subset dry-run) passa
- [ ] Build verde
Por novo cliente:
- [ ] Validações bloquearam client_name vazio
- [ ] Auditoria de duplicação (account_id, token, channel_id) passa
- [ ] Test dry-run dos fluxos do novo cliente antes de liberar tráfego real
Mensal: - [ ] Revisar custo marginal por cliente (Uazapi, Gemini, Supabase) - [ ] Revisar taxa de erro e latência por tenant - [ ] Rotação de tokens se agendada
Decisões arquiteturais em aberto¶
- Uazapi: instância dedicada vs cluster compartilhado? Janaina tem dedicada, cluster Gita compartilha entre 4 produtos. Decidir padrão antes de 4º cliente.
clientstable vsclient_namestring: migração pode ser gradual (adicionar FK nullable) ou big-bang (migration com UPDATE massivo). Decidir na Fase 4.- RLS granularidade: por
client_idapenas ou por role (admin_global, admin_tenant, viewer)? Discutir antes de abrir admin pra stakeholders de cliente. - Onboarding template: CLI vs admin UI vs script SQL auditável. CLI mais alinhado com versionamento no git.
Validação recente¶
- Bateria v15 (2026-04-21): 13/13 ✅ — valida isolamento cross-product dentro do cluster Gita + exceção
isVagasPriority - Audit tenant-isolation v2 (2026-04-21): 1 FAIL detectado — token Uazapi compartilhado cross-tenant (ver abaixo)
- Incidente 18/04/2026: corrigido, sticky agora filtra por
client_name
Achados operacionais da auditoria v2 (2026-04-21) — AÇÃO NECESSÁRIA¶
🔴 FAIL — Token Uazapi compartilhado cross-tenant¶
3 agentes ativos compartilham a MESMA instância Uazapi (grupogita.uazapi.com + token 13ef1f63...):
| Slug | Tenant (client_name) |
is_active |
|---|---|---|
moleiro |
Adriano Moleiro | true (legado — não está em CLIENTES.md) |
oraculo-pinzon |
Edison Pinzon | true (legado — não está em CLIENTES.md) |
gita-captacao |
Gita Agentes | true (em produção) |
Risco: se alguém apontar webhook da instância Uazapi 556191502969 pra /webhook/whatsapp/moleiro ou /webhook/whatsapp/oraculo-pinzon (por typo ou config antiga), mensagens destinadas ao cluster Gita Agentes seriam processadas no contexto do tenant errado. A validação de origem que acabamos de adicionar (webhook.source_mismatch_token) não pega esse caso porque os 3 agentes aceitam o mesmo token.
Ação imediata recomendada (decisão do Junior):
Opção A — DESATIVAR Moleiro e Pinzon se não estão em uso:
UPDATE ai_agents SET is_active = false WHERE slug IN ('moleiro', 'oraculo-pinzon');
556191502969 aponta apenas pra gita-router.
Opção B — TOKEN DEDICADO por tenant se houver intenção de manter Moleiro/Pinzon rodando:
- Criar instâncias Uazapi dedicadas pra cada
- Atualizar provider_config.token e api_url de cada agente
- Rotacionar webhooks
Recomendação: Opção A (desativar). Evidência: CLIENTES.md lista 3 clientes (Janaina, Gita Agentes, Denise), sem menção a Moleiro/Pinzon. Provável histórico de testes antigos.
⚠️ WARN — Canal ClickUp compartilhado entre Janaina Ortiga e Gita Agentes¶
Todos os 13 agentes dos dois tenants reportam pro mesmo canal ClickUp (8cgqa6x-12233).
Impacto operacional: time comercial unificado recebe tudo no mesmo fluxo. Pode ser intencional pra equipe pequena, mas dificulta crescer operação por cliente.
Decisão: provavelmente manter por enquanto (operação pequena). Revisitar quando tiver 5+ clientes ativos — aí cada um pode ter canal dedicado.
Próximo incidente esperado (se não mitigado): cliente novo com client_name typo ou account_id Chatwoot duplicado. Fase 1 elimina ambos.