Documentation

Multi-tenant strict

Comment betool isole vos données, vos secrets et vos exécutions des autres organisations.

Multi-tenant strict

L'isolation entre organisations est l'invariant structurant de betool. Voici comment il est appliqué, couche par couche.

Au niveau de la base de données

Les tables métier portent une colonne org_id qui rattache chaque ligne à son organisation (le plus souvent contrainte par clé étrangère vers organisation(id)).

Toutes les requêtes serveur passent par des store methods qui filtrent systématiquement par l'org_id de la session courante. Aucun chemin n'expose une requête arbitraire ; aucun client ne peut forcer un autre org_id.

Pour les ressources rares mutualisables (typiquement les comptes LLM partagés au niveau plateforme), une colonne is_shared BOOLEAN NOT NULL DEFAULT FALSE permet l'opt-in explicite. Règles :

  • Toute lecture autorisée à WHERE org_id = %s OR is_shared = TRUE.
  • Toute mutation d'une row is_shared = TRUE exige le rôle hyperadmin (opérateur plateforme).
  • Toute mutation invalide le cache globalement — pas de divergence per-org.

Au niveau du stockage de fichiers

Les fichiers sont rangés dans un stockage objet (disque local ou bucket cloud selon le déploiement), sous un espace propre à chaque organisation :

orgs/
└── <org_id>/
    ├── files/<canal>/<exchange_id>/<fichier>   ← fichiers reçus par les canaux
    └── fixtures/                               ← fixtures de benchmark

Les clés sont construites côté serveur (nom de fichier assaini, anti path-traversal). Toute lecture d'un fichier au nom d'un utilisateur, d'un tool ou d'un agent passe par une validation qui refuse une clé hors de l'espace orgs/<org_id>/files/ de l'organisation courante.

Au niveau des caches

Les caches applicatifs (par exemple la résolution des providers LLM) sont indexés par organisation. Quand une ressource is_shared = TRUE est mutée, le cache est invalidé globalement — pas de fenêtre où une org verrait l'ancienne valeur.

Au niveau des secrets

Les credentials (clés LLM, tokens d'auth opérateur, secrets webhook) sont stockés côté serveur, rattachés à chaque org. Ils ne sont jamais retournés en clair par l'API : les routes GET n'indiquent que leur présence (par exemple has_api_key: bool).

Au niveau de l'exécution

Quand un pipeline s'exécute :

  • Les opérateurs n'ont accès qu'aux comptes externes déclarés dans l'org.
  • Les modèles LLM utilisés sont résolus dans l'ordre own > shared (fallback auto) — toujours.
  • Les fichiers attachés ne sont lisibles que dans l'espace de stockage de l'org courante.

Si un opérateur tente d'accéder à une ressource hors-périmètre, l'accès est refusé avec une erreur explicite — pas un timeout silencieux.

Test pratique

À chaque ajout d'une nouvelle ressource mutualisable, l'équipe répond aux 4 questions :

  1. Si une 2ᵉ org backoffice apparaît, mon code se comporte-t-il bien ?
  2. Si je modifie une row shared, tous les caches per-org sont-ils invalidés ?
  3. Mes secrets restent strictement serveur-side ?
  4. Mon GET permet-il aux pickers d'autres orgs de lister la ressource, sans exposer le secret ?

Si une réponse est non, la ressource n'est pas livrée.