Integrações e fronteiras de confiança do Cadência¶
Explica como identidade, conta ativa e privilégios atravessam browser, Next.js, Supabase, workers, Growth, CLI e um futuro servidor MCP.
Por que foi construído assim¶
O Cadência é um sistema multi-conta e concentra memória de marketing e dados dos clientes de cada usuário. Isso torna tenant_id uma decisão de autorização, não um simples parâmetro técnico.
A implementação cresceu por runtimes especializados: Next.js para a interface e rotas síncronas, workers para geração pesada, Growth para cron e dispatch, Lara para WhatsApp e a CLI para operação por agentes. Esses componentes não têm o mesmo modelo de confiança. O browser usa JWT de usuário; grande parte dos runtimes internos usa service_role, PAT administrativo, segredos compartilhados ou SSH.
A documentação antiga condensava tudo em “RLS multi-tenant” e projetava MCP como uma casca futura sobre a mesma biblioteca da CLI. A auditoria do código mostrou duas correções necessárias: RLS não protege chamadas com service_role/PAT, e os transportes privilegiados da CLI não podem virar uma interface pública. A arquitetura passa a separar serviço de negócio de transporte, sem criar uma API REST apenas para encaminhar chamadas MCP.
Stack¶
| Camada | Tecnologia |
|---|---|
| Interface | Next.js 15, React 19, Vercel |
| Identidade | Supabase Auth, sessão/JWT e memberships em user_tenant_roles |
| Dados | Supabase PostgreSQL, RLS, Storage e Realtime |
| Processamento | FastAPI workers no Coolify e Python Growth na VPS Master |
| Operação | cadencia-cli, Supabase Management API/PAT e SSH |
| Integração futura | Servidor MCP com OAuth e ferramentas estreitas |
| Serviços externos | Resend/Svix, Stripe, Lara/Evolution e providers sociais |
Como funciona¶
flowchart TD
classDef component fill:#D1FAE5,stroke:#10B981,color:#111
classDef flow fill:#DBEAFE,stroke:#3B82F6,color:#111
classDef decision fill:#EDE9FE,stroke:#8B5CF6,color:#111
classDef core fill:#FEE2E2,stroke:#EF4444,color:#111
classDef external fill:#FEF3C7,stroke:#F59E0B,color:#111
classDef warning fill:#FEF9C3,stroke:#EAB308,color:#111
subgraph SG_component["Componentes"]
browser["Browser"]
next["Next.js / Vercel"]
mcp["MCP público futuro"]
internal["Runtimes internos"]
end
class browser,next,mcp,internal component
subgraph SG_flow["Fluxo do processo"]
identity["1. Autenticar identidade"]
account["2. Resolver conta ativa"]
authorize["3. Autorizar ação"]
service["4. Executar serviço de negócio"]
persist["5. Persistir ou enfileirar"]
result["6. Responder e auditar"]
end
class identity,account,authorize,service,persist,result flow
subgraph SG_decision["Decisões"]
trusted_tenant["De onde veio o tenant?"]
credential_mode["Qual credencial chega ao Supabase?"]
rls_path["JWT"]
public_surface["A superfície é pública?"]
safe_tool["Sim"]
end
class trusted_tenant,credential_mode,rls_path,public_surface,safe_tool decision
title["Cadência — integrações e fronteiras de confiança"]
class title core
supabase["Supabase"]
class supabase external
subgraph SG_warning["Gotchas / Erros"]
reject_tenant["Rejeitar"]
admin_path["service_role / PAT"]
internal_only["Não / operação interna"]
end
class reject_tenant,admin_path,internal_only warning
identity -->|"sessão/OAuth válida"| account
account -->|"membership válida"| authorize
authorize -->|"ação permitida"| service
service -->|"contrato validado"| persist
persist -->|"commit/claim confirmado"| result
browser -->|"sessão"| identity
mcp -->|"OAuth"| identity
next -->|"resolve"| account
internal -->|"execução interna"| service
persist -->|"dados"| supabase
account -->|"avaliar origem"| trusted_tenant
trusted_tenant -->|"sim"| credential_mode
trusted_tenant -->|"não"| reject_tenant
credential_mode -->|"JWT"| rls_path
credential_mode -->|"service_role/PAT"| admin_path
rls_path -->|"policy validada"| public_surface
admin_path -->|"escopo explícito"| public_surface
public_surface -->|"sim"| safe_tool
public_surface -->|"não"| internal_only
safe_tool -->|"chamar serviço"| service
internal_only -->|"somente interno"| service No app, a conta ativa é resolvida no servidor a partir da sessão, memberships válidas e cookie de preferência revalidado. Grande parte de /api/app/* usa service_role; portanto cada operação precisa aplicar o tenant resolvido em todas as queries relevantes.
O browser também acessa Supabase Auth, polling de geração e Realtime da Lara, além de chamar workers por /api/v1/* via rewrite. Esses caminhos dependem de JWT e RLS. Eles são exceções existentes, não autorização para abrir tabelas ou serviços indiscriminadamente.
Workers e Growth executam depois da fronteira pública e usam privilégios administrativos. Lara recebe do Next um x-tenant-id derivado depois da autenticação. A CLI é uma ferramenta de operador com PAT e SSH. Um MCP futuro fica antes desses runtimes: autentica o usuário, resolve a conta ativa, autoriza uma ferramenta estreita e só então chama um serviço seguro ou enfileira trabalho.
Decisões técnicas¶
Tenant deriva da identidade¶
Body, query string, prompt, argumento de ferramenta ou header externo não concedem acesso a uma conta. O servidor cruza a identidade com user_tenant_roles e resolve a conta ativa antes da operação.
RLS e filtro explícito são regimes diferentes¶
RLS protege operações que chegam ao Supabase com JWT/anon key. service_role e PAT administrativo bypassam RLS; nesses caminhos, autorização server-side e filtro explícito por tenant são a barreira real.
MCP não é middleware universal¶
Não é necessário criar endpoints REST apenas para o MCP chamar. O servidor MCP é uma fronteira pública OAuth e pode invocar serviços de aplicação ou RPCs seguras. Frontend e CLI podem continuar usando transportes adequados a seus contextos; convergem nas regras de negócio, não obrigatoriamente no protocolo.
Transportes de operador não atravessam a fronteira pública¶
run_sql, PAT, service_role genérica, SSH, segredos de cron e triggers internos permanecem fora das ferramentas MCP. Ações mutantes exigem schema estreito, papel autorizado, idempotência, auditoria e confirmação quando houver efeito externo relevante.
Gotchas & armadilhas¶
- Conta ativa divergente nos workers — sem claim de tenant, o middleware atual consulta uma membership com
limit=1; em usuário multi-conta ela pode não ser a conta selecionada no app. Não reutilizar esse fallback. - RLS imaginária com service role — uma policy correta não protege uma query administrativa sem
tenant_id;service_rolebypassa RLS. - Cobertura textual incompleta — o teste de isolamento das API Routes detecta arquivo sem qualquer escopo, mas não prova que todas as queries do arquivo estejam filtradas.
- Auditoria não é autorização —
createAuditedAdminaparece em poucas rotas e opera de forma detectiva/fail-open. Log não substitui guard nem filtro. - CLI parece tenant-scoped, mas a credencial não é — helpers públicos reduzem acidentes;
run_sqle PAT continuam administrativos. - Segredo interno não identifica usuário — trigger de Growth e cron autenticam serviço, não membership ou conta ativa.
Como operar¶
Esta página descreve contratos; não introduz um serviço novo para reiniciar. Para validar documentação e inventário sem executar produção:
# cadencia-app: confirmar superfícies browser e resolução de tenant
rg -n "resolveActiveTenant|createClient|/api/v1|generation_queue|pipeline_status" src
# cadencia-cli: confirmar inventário e transportes privilegiados
cadencia-cli --help
rg -n "run_sql|Management API|SSH" src docs
# cadencia-growth: confirmar entradas internas e uso administrativo
rg -n "service_role|tenant_id|trigger_server" .
Mudanças de autorização exigem testes cross-tenant e multi-conta; busca textual isolada não basta como evidência de segurança.
FAQ¶
Tudo deve passar pelo MCP, inclusive web e CLI?
Não. MCP é um adaptador para harnesses como ChatGPT e Claude. O ponto compartilhável é o serviço de negócio e sua política de autorização; a web pode continuar usando Next/API Routes e a CLI pode continuar como ferramenta operacional.
Um MCP funciona como uma API com endpoints?
Ele expõe ferramentas, recursos e prompts pelo protocolo MCP, não necessariamente endpoints REST de negócio. O servidor continua tendo um transporte HTTP/streamable ou equivalente, mas o contrato consumido pelo modelo é MCP.
Precisamos criar uma API só para o MCP?
Não. Extraia serviços/RPCs seguros quando houver regra hoje presa a uma rota. O MCP pode chamá-los diretamente na camada de aplicação, sem uma volta artificial MCP → REST → mesma lógica.
O agente poderá acessar todo o banco?
Não se a fronteira for construída corretamente. O agente vê apenas ferramentas allowlisted e o servidor resolve identidade, conta ativa e autorização. Credenciais administrativas genéricas não são oferecidas ao modelo.
WebMCP é a mesma coisa?
Não. WebMCP descreve interação estruturada com capacidades expostas no contexto do navegador. O MCP tratado aqui é a integração de serviço entre ChatGPT/Claude e o Cadência.