Documentazione

Multi-tenancy rigorosa

Come betool isola i tuoi dati, segreti ed esecuzioni dalle altre organizzazioni.

Multi-tenancy rigorosa

L'isolamento tra organizzazioni è l'invariante fondante di betool. Ecco come viene garantito, strato per strato.

A livello di database

Le tabelle di business contengono una colonna org_id che collega ogni riga alla propria organizzazione (il più delle volte vincolata da una foreign key a organisation(id)).

Tutte le query lato server passano attraverso metodi store che filtrano sistematicamente in base all'org_id della sessione corrente. Nessun percorso di codice espone una query arbitraria; nessun client può forzare un org_id diverso.

Per le rare risorse condivisibili (tipicamente gli account LLM condivisi a livello di piattaforma), una colonna is_shared BOOLEAN NOT NULL DEFAULT FALSE abilita un opt-in esplicito. Regole:

  • Qualsiasi lettura è autorizzata con WHERE org_id = %s OR is_shared = TRUE.
  • Qualsiasi mutazione di una riga is_shared = TRUE richiede il ruolo hyperadmin (operatore di piattaforma).
  • Qualsiasi mutazione invalida la cache globalmente — nessuna divergenza per singola org.

A livello di storage dei file

I file sono archiviati in uno storage a oggetti (disco locale o bucket cloud a seconda del deployment), in uno spazio proprio di ciascuna organizzazione:

orgs/
└── <org_id>/
    ├── files/<canale>/<exchange_id>/<file>   ← file ricevuti dai canali
    └── fixtures/                             ← fixture di benchmark

Le chiavi sono costruite lato server (nome di file sanificato, anti path-traversal). Qualsiasi lettura di un file per conto di un utente, di un tool o di un agent passa per una validazione che rifiuta una chiave al di fuori dello spazio orgs/<org_id>/files/ dell'organizzazione corrente.

A livello di cache

Le cache applicative (ad esempio la risoluzione dei provider LLM) sono indicizzate per organizzazione. Quando una risorsa is_shared = TRUE viene mutata, la cache è invalidata globalmente — non esiste una finestra durante la quale un'org potrebbe vedere un valore obsoleto.

A livello di segreti

Le credenziali (chiavi LLM, token di autenticazione degli operatori, segreti dei webhook) sono archiviate lato server, associate a ciascuna org. Non vengono mai restituite in chiaro dall'API: le route GET indicano soltanto la loro presenza (ad esempio has_api_key: bool).

A livello di esecuzione

Quando una pipeline viene eseguita:

  • Gli operatori hanno accesso soltanto agli account esterni dichiarati nell'org.
  • I modelli LLM sono risolti nell'ordine own > shared (fallback automatico) — sempre.
  • I file allegati sono leggibili soltanto nello spazio di storage dell'org corrente.

Se un operatore tenta di accedere a una risorsa fuori perimetro, l'accesso viene rifiutato con un errore esplicito — non un timeout silenzioso.

Test pratico

A ogni aggiunta di una nuova risorsa condivisibile, il team risponde alle 4 domande:

  1. Se compare una seconda org di backoffice, il mio codice si comporta correttamente?
  2. Se muto una riga condivisa, tutte le cache per singola org vengono invalidate?
  3. I miei segreti restano strettamente lato server?
  4. La mia GET consente ai picker di altre org di elencare la risorsa senza esporre il segreto?

Se una qualsiasi risposta è no, la risorsa non viene rilasciata.