Banco de dados

Deixe um agente ler de um banco Postgres externo, com a multi-tenência do próprio cliente aplicada pelo banco, não pelo modelo.

A tool db_query deixa um agente rodar uma consulta SQL somente-leitura contra um banco Postgres externo configurado pelo operador: dados do próprio cliente, não o armazenamento interno do Pepe. Só Postgres. Se esse banco tem a própria multi-tenência (uma coluna no estilo company_id separando os próprios clientes daquele cliente), o Pepe amarra o valor de tenant confiável à conexão e nunca deixa o modelo ver ou definir esse valor: o isolamento de verdade é aplicado pelo próprio Postgres, via Row-Level Security, não por algo que o código do Pepe decide em tempo de execução.

Por que o modelo nunca vê o valor do tenant

Um argumento de tool que o modelo preenche pode sair errado, por engano, ou porque uma página ou documento que o agente leu disse para usar um valor diferente. Isso não é uma falha de redação, é um vazamento real de dados entre clientes. Por isso o schema da tool db_query não tem nenhum parâmetro de tenant/company_id: o modelo só passa connection (um nome) e query (SQL somente-leitura). O valor do tenant vem da configuração que o operador definiu, resolvido do lado do servidor, e aplicado a toda consulta daquela conexão automaticamente.

Configurando Row-Level Security (faça isso primeiro)

Essa é a parte que o Pepe não consegue fazer por você: o próprio banco do operador precisa de um role dedicado e sem privilégios, e uma política. Rode algo assim uma vez, na mão, no banco alvo:

CREATE ROLE pepe_ro LOGIN PASSWORD '...' NOBYPASSRLS;
GRANT SELECT ON orders, invoices TO pepe_ro; -- as tabelas que o agente deve ler

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
  USING (company_id = current_setting('app.pepe_tenant_id', true)::text);

Duas coisas importam aqui:

Uma tabela sem política de RLS não está protegida por essa funcionalidade de jeito nenhum. O db_query roda do mesmo jeito contra toda tabela de uma conexão; se uma tabela específica está de fato isolada depende inteiramente de se aquela tabela tem uma política que funciona. Isso é deliberado, não uma brecha para tapar no Pepe: tentar aplicar isolamento de tenant reescrevendo ou validando SQL arbitrário escrito pelo agente no código da aplicação não dá para fazer de forma confiável (uma cláusula WITH, um JOIN, um agregado podem contrabandear uma leitura passando por uma checagem no nível de texto). Row-Level Security é o único mecanismo que realmente se sustenta não importa como a consulta foi escrita, porque atua dentro do próprio motor de banco de dados, não sobre o texto da consulta.

Adicionando uma conexão

A página Bancos de dados do dashboard lista as conexões, mostra se cada uma tem escopo de tenant, e tem um formulário para adicionar ou remover uma; o campo de senha nunca vem preenchido nem é reexibido depois de salvo. A mesma coisa pelo CLI:

pepe db add clientes_prod --host db.internal --port 5432 --database billing \
  --user pepe_ro --password ${DB_CLIENTES_PROD_PASSWORD} \
  --tenant-column company_id --tenant-mode fixed --tenant-value acme-inc

pepe db list
pepe db remove clientes_prod

Uma conexão sem --tenant-column (ou com o campo “Coluna de tenant” vazio no dashboard) não tem escopo: tudo bem para um banco de um único cliente, sem nada para isolar. Uma com coluna de tenant também precisa de um modo:

Um agente também pode gerenciar conexões de uma conversa com a tool manage_db (as mesmas ações add/list/remove), e consultar com db_query uma vez que tenha as duas tools. As duas são tools de risco: não estão no conjunto sempre-seguro, e passam pelo aviso de permissão comum como qualquer outra tool que alcança para fora.

O que o agente vê

Um resultado de db_query volta embrulhado no mesmo marcador de conteúdo não confiável que um resultado de fetch_url carrega: é conteúdo de fora da conversa, tratado do mesmo jeito. A tool em si é só para Postgres; não tem equivalente para MySQL, SQLite ou qualquer outro motor, já que Row-Level Security (e a garantia de falha fechada acima) é específica do Postgres.