Pular para conteúdo

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_role bypassa 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çãocreateAuditedAdmin aparece 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_sql e 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.