Pular para conteúdo

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 tabela clients dedicada 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 + clients table + 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

  1. Uazapi: instância dedicada vs cluster compartilhado? Janaina tem dedicada, cluster Gita compartilha entre 4 produtos. Decidir padrão antes de 4º cliente.
  2. clients table vs client_name string: migração pode ser gradual (adicionar FK nullable) ou big-bang (migration com UPDATE massivo). Decidir na Fase 4.
  3. RLS granularidade: por client_id apenas ou por role (admin_global, admin_tenant, viewer)? Discutir antes de abrir admin pra stakeholders de cliente.
  4. 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');
E garantir que o webhook da instância Uazapi 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.