Strikte Mandantentrennung
Die Isolation zwischen Organisationen ist die grundlegende Invariante von betool. So wird sie Schicht für Schicht durchgesetzt.
Auf Datenbankebene
Die Geschäftstabellen tragen eine Spalte org_id, die jede Zeile ihrer Organisation zuordnet (meist durch einen Fremdschlüssel auf organisation(id) eingeschränkt).
Alle serverseitigen Abfragen laufen über Store-Methoden, die systematisch nach der org_id der aktuellen Sitzung filtern. Kein Codepfad legt eine beliebige Abfrage offen; kein Client kann eine andere org_id erzwingen.
Für die wenigen Ressourcen, die organisationsübergreifend geteilt werden können (typischerweise gemeinsam genutzte LLM-Konten auf Plattformebene), ermöglicht eine Spalte is_shared BOOLEAN NOT NULL DEFAULT FALSE ein explizites Opt-in. Regeln:
- Jeder Lesevorgang ist mit
WHERE org_id = %s OR is_shared = TRUEautorisiert. - Jede Mutation einer Zeile mit
is_shared = TRUEerfordert die Hyperadmin-Rolle (Plattformbetreiber). - Jede Mutation invalidiert den Cache global — keine Divergenz pro Organisation.
Auf Ebene des Dateispeichers
Dateien werden in einem Objektspeicher abgelegt (lokale Festplatte oder Cloud-Bucket je nach Deployment), in einem eigenen Bereich jeder Organisation:
orgs/
└── <org_id>/
├── files/<canal>/<exchange_id>/<fichier> ← von den Kanälen empfangene Dateien
└── fixtures/ ← Benchmark-Fixtures
Die Schlüssel werden serverseitig gebildet (bereinigter Dateiname, Schutz vor Path Traversal). Jedes Lesen einer Datei im Namen eines Benutzers, eines Tools oder eines Agenten durchläuft eine Validierung, die einen Schlüssel außerhalb des Bereichs orgs/<org_id>/files/ der aktuellen Organisation ablehnt.
Auf Cache-Ebene
Die Anwendungs-Caches (zum Beispiel die Auflösung der LLM-Provider) sind nach Organisation indiziert. Wenn eine Ressource mit is_shared = TRUE mutiert wird, wird der Cache global invalidiert — es gibt kein Zeitfenster, in dem eine Organisation einen veralteten Wert sehen könnte.
Auf Geheimnisebene
Zugangsdaten (LLM-Schlüssel, Operator-Auth-Tokens, Webhook-Geheimnisse) werden serverseitig gespeichert und jeder Organisation zugeordnet. Sie werden von der API niemals im Klartext zurückgegeben: GET-Routen zeigen nur an, ob sie vorhanden sind (zum Beispiel has_api_key: bool).
Auf Ausführungsebene
Wenn eine Pipeline läuft:
- Operatoren haben nur Zugriff auf die externen Konten, die innerhalb ihrer Organisation deklariert sind.
- LLM-Modelle werden in der Reihenfolge
own > shared (fallback auto)aufgelöst — immer. - Angehängte Dateien sind nur im Speicherbereich der aktuellen Organisation lesbar.
Versucht ein Operator, auf eine Ressource außerhalb seines Geltungsbereichs zuzugreifen, wird der Zugriff mit einem expliziten Fehler verweigert — kein stiller Timeout.
Praxistest
Wann immer eine neue teilbare Ressource hinzugefügt wird, beantwortet das Team diese 4 Fragen:
- Wenn eine zweite Backoffice-Organisation auftaucht, verhält sich mein Code dann korrekt?
- Wenn ich eine geteilte Zeile mutiere, werden dann alle Caches pro Organisation invalidiert?
- Bleiben meine Geheimnisse strikt serverseitig?
- Erlaubt mein GET den Auswahlmenüs anderer Organisationen, die Ressource aufzulisten, ohne das Geheimnis offenzulegen?
Wenn eine der Antworten Nein lautet, wird die Ressource nicht ausgeliefert.