Multi-tenancy rigorosa
L'isolamento tra organizzazioni è l'invariante fondante di betool. Ecco come viene garantito, strato per strato.
A livello di database
Ogni tabella di business contiene una colonna org_id (UUID), indicizzata e 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 il ristretto numero di risorse che possono essere condivise tra organizzazioni (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 = TRUErichiede il ruolo hyperadmin (operatore di piattaforma). - Qualsiasi mutazione invalida la cache globalmente — nessuna divergenza per singola org.
A livello di filesystem
Ogni org ha la propria radice di storage:
<data_root>/
└── orgs/
└── <org_id>/
├── fixtures/
├── uploads/
├── knowledge/
└── exchanges/
Gli helper del server (org_*_dir(org_id)) risolvono questi percorsi. Nessun codice di business può scrivere al di fuori della radice di un'organizzazione.
A livello di cache
Tutte le cache applicative (risoluzione del provider LLM, contesti correnti, file analizzati) sono per singola org. 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 nel vault di ciascuna org. Non vengono mai restituite in chiaro dall'API: le route GET restituiscono soltanto has_api_key: bool.
Nel piano Enterprise, il vault può essere collegato a HashiCorp Vault, AWS Secrets Manager o Azure Key Vault.
A livello di esecuzione
Quando una pipeline viene eseguita:
- Gli operatori hanno accesso soltanto agli account esterni dichiarati all'interno della loro org.
- I modelli LLM sono risolti nell'ordine
own > shared (fallback automatico)— sempre. - I file allegati sono leggibili soltanto sotto la radice dell'org corrente.
Se un operatore tenta di accedere a una risorsa al di fuori del proprio ambito, l'errore è esplicito (OrgScopeViolation) — non un timeout silenzioso.
Checklist pratica
Ogni volta che viene aggiunta una nuova risorsa condivisibile, il team risponde a queste 4 domande:
- Se compare una seconda org di backoffice, il mio codice si comporta correttamente?
- Se muto una riga condivisa, tutte le cache per singola org vengono invalidate?
- I miei segreti restano rigorosamente lato server?
- 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.