Documentación

Multi-tenancy estricto

Cómo betool aísla tus datos, secretos y ejecuciones de las demás organizaciones.

Multi-tenancy estricto

El aislamiento entre organizaciones es el invariante fundacional de betool. Así se aplica, capa por capa.

A nivel de base de datos

Las tablas de negocio llevan una columna org_id que vincula cada fila a su organización (casi siempre restringida por una clave foránea a organisation(id)).

Todas las consultas del lado servidor pasan por métodos de store que filtran sistemáticamente por el org_id de la sesión actual. Ningún camino de código expone una consulta arbitraria; ningún cliente puede forzar un org_id distinto.

Para el pequeño número de recursos que pueden compartirse entre organizaciones (normalmente cuentas de LLM compartidas a nivel de plataforma), una columna is_shared BOOLEAN NOT NULL DEFAULT FALSE habilita un opt-in explícito. Reglas:

  • Cualquier lectura se autoriza con WHERE org_id = %s OR is_shared = TRUE.
  • Cualquier mutación de una fila is_shared = TRUE requiere el rol de hyperadmin (operador de plataforma).
  • Cualquier mutación invalida la caché globalmente — sin divergencia por organización.

A nivel de almacenamiento de archivos

Los archivos se guardan en un almacenamiento de objetos (disco local o bucket cloud según el despliegue), bajo un espacio propio de cada organización:

orgs/
└── <org_id>/
    ├── files/<canal>/<exchange_id>/<archivo>   ← archivos recibidos por los canales
    └── fixtures/                               ← fixtures de benchmark

Las claves se construyen del lado del servidor (nombre de archivo saneado, anti path-traversal). Toda lectura de un archivo en nombre de un usuario, de una herramienta o de un agente pasa por una validación que rechaza una clave fuera del espacio orgs/<org_id>/files/ de la organización actual.

A nivel de caché

Las cachés de la aplicación (por ejemplo, la resolución de los proveedores de LLM) están indexadas por organización. Cuando se muta un recurso is_shared = TRUE, la caché se invalida globalmente — no hay ninguna ventana durante la cual una organización pueda ver un valor obsoleto.

A nivel de secretos

Las credenciales (claves de LLM, tokens de autenticación de operadores, secretos de webhook) se almacenan del lado del servidor, vinculadas a cada organización. Nunca se devuelven en texto plano por la API: las rutas GET solo indican su presencia (por ejemplo has_api_key: bool).

A nivel de ejecución

Cuando se ejecuta un pipeline:

  • Los operadores solo tienen acceso a las cuentas externas declaradas dentro de su organización.
  • Los modelos de LLM se resuelven en el orden own > shared (fallback auto) — siempre.
  • Los archivos adjuntos solo son legibles en el espacio de almacenamiento de la organización actual.

Si un operador intenta acceder a un recurso fuera de su ámbito, el acceso se rechaza con un error explícito — no un timeout silencioso.

Prueba práctica

Cada vez que se añade un nuevo recurso compartible, el equipo responde a estas 4 preguntas:

  1. Si aparece una segunda organización de backoffice, ¿mi código se comporta correctamente?
  2. Si muto una fila compartida, ¿se invalidan todas las cachés por organización?
  3. ¿Mis secretos permanecen estrictamente del lado servidor?
  4. ¿Mi GET permite que los pickers de otras organizaciones listen el recurso sin exponer el secreto?

Si alguna respuesta es no, el recurso no se despliega.