Строгая мультитенантность
Изоляция между организациями — основополагающий инвариант betool. Вот как она обеспечивается, слой за слоем.
На уровне базы данных
Каждая бизнес-таблица содержит столбец org_id (UUID), индексированный и ограниченный внешним ключом на organisation(id).
Все серверные запросы проходят через методы хранилища (store), которые систематически фильтруют по org_id текущей сессии. Ни один путь исполнения кода не предоставляет произвольный запрос; ни один клиент не может навязать другой org_id.
Для небольшого числа ресурсов, которые могут совместно использоваться несколькими организациями (как правило, общие учётные записи LLM на уровне платформы), столбец is_shared BOOLEAN NOT NULL DEFAULT FALSE включает явное согласие (opt-in). Правила:
- Любое чтение разрешено с
WHERE org_id = %s OR is_shared = TRUE. - Любое изменение строки с
is_shared = TRUEтребует роли hyperadmin (оператора платформы). - Любое изменение инвалидирует кэш глобально — без расхождений по организациям.
На уровне файловой системы
У каждой организации есть собственный корень хранилища:
<data_root>/
└── orgs/
└── <org_id>/
├── fixtures/
├── uploads/
├── knowledge/
└── exchanges/
Серверные вспомогательные функции (org_*_dir(org_id)) разрешают эти пути. Ни один бизнес-код не может писать за пределами корня организации.
На уровне кэша
Все кэши приложения (разрешение провайдера LLM, текущие контексты, разобранные файлы) раздельны по организациям. При изменении ресурса с is_shared = TRUE кэш инвалидируется глобально — нет окна, в течение которого организация могла бы увидеть устаревшее значение.
На уровне секретов
Учётные данные (ключи LLM, токены аутентификации operator, секреты webhook) хранятся в хранилище каждой организации. Они никогда не возвращаются в открытом виде API: маршруты GET возвращают только has_api_key: bool.
В тарифе Enterprise хранилище может быть подключено к HashiCorp Vault, AWS Secrets Manager или Azure Key Vault.
На уровне исполнения
При запуске pipeline:
- operator имеют доступ только к внешним учётным записям, объявленным в своей организации.
- Модели LLM разрешаются в порядке
own > shared (автоматический fallback)— всегда. - Прикреплённые файлы читаемы только в пределах корня текущей организации.
Если operator пытается получить доступ к ресурсу вне своей области, ошибка является явной (OrgScopeViolation) — а не молчаливым таймаутом.
Практический чек-лист
Всякий раз при добавлении нового совместно используемого ресурса команда отвечает на эти 4 вопроса:
- Если появится вторая организация backoffice, будет ли мой код вести себя корректно?
- Если я изменяю общую строку, инвалидируются ли все кэши по организациям?
- Остаются ли мои секреты строго на стороне сервера?
- Позволяет ли мой GET picker-ам других организаций перечислить ресурс, не раскрывая секрет?
Если хотя бы один ответ отрицательный, ресурс не выпускается.