Воркспейсы, участники и токены
Как устроена тенантность в облаке botruntime: воркспейс как граница доступа, роли участников и виды токенов.
Воркспейс — это граница тенантности. Всё, что создаёт бот, живёт внутри воркспейса: диалоги, сообщения, события, файлы, строки таблиц, метеринг. Воркспейс платформа выводит из токена, а не из заголовков запроса. Рядом в том же воркспейсе — боты, участники с ролями и выпущенные ключи.
Тенант не выбирается клиентом. Переключиться на чужой воркспейс, подставив чужой идентификатор в путь, тело или заголовок, нельзя.
Отсюда мультитенантность из коробки. Облако обслуживает много воркспейсов сразу, и код доступа к данным не пересекает их границу: scope приходит из аутентификации, а не из ввода.
Воркспейс как граница
Каждый запрос к API проходит один из двух швов аутентификации. Оба резолвят
Authorization: Bearer … в scope тенанта: тройку (воркспейс, владелец, бот). Дальше все обращения к БД фильтруются этим scope.
Главный инвариант: тенант берётся из ключа, никогда из клиентских заголовков.
Заголовки x-workspace-id или x-bot-id лишь выбирают сущность среди
доступных ключу. Принадлежность перепроверяется по членству в базе. Подставили
чужой workspaceId — получите 404, а не доступ. Существование чужого тенанта
платформа не раскрывает.
Анонимного доступа нет. Отсутствующий, неверный или отозванный токен — всегда
401. Платформа никогда молча не подставляет «владельца по умолчанию». Сбой
инфраструктуры (например, недоступная БД при проверке ключа) отдаётся как 5xx,
а не маскируется под 401.
Участники и роли
Участник — это человек-аккаунт в воркспейсе с одной из трёх ролей. Роль платформа
резолвит по членству и отдаёт в GET /v1/admin/me вместе со списком воркспейсов.
| Роль | Назначение |
|---|---|
owner | Создатель воркспейса. Единственный управляет составом участников. Строка владельца неизменяема. |
admin | Пишет конфигурацию: интеграции, config-переменные, строки таблиц; читает аудит. |
member | Читает данные бота: диалоги, события, файлы, таблицы, логи, аналитику. |
Матрица прав проверяется на сервере — до хендлера или внутри него.
| Действие | member | admin | owner |
|---|---|---|---|
| Читать диалоги, сообщения, события, трейсы, файлы, таблицы, логи, аналитику | ✓ | ✓ | ✓ |
| Читать имена config-переменных (без значений) | ✓ | ✓ | ✓ |
| Читать журнал аудита воркспейса | — | ✓ | ✓ |
| Ставить, снимать и подключать интеграции | — | ✓ | ✓ |
| Писать и удалять config-переменные (секреты) | — | ✓ | ✓ |
| Вставлять и удалять строки таблиц из консоли | — | ✓ | ✓ |
| Добавлять, менять роль и удалять участников | — | — | ✓ |
Роль owner неотчуждаема: её нельзя понизить, а самого владельца — удалить из
воркспейса. Так воркспейс никогда не остаётся без владельца. Добавить участника
можно только по e-mail уже вошедшего в платформу человека — приглашений «в
никуда» по непроверенному адресу нет.
Создать воркспейс может любой аккаунт: POST /v1/admin/workspaces. Создатель
сразу становится owner.
Две оси доступа
Аутентификация идёт по двум параллельным осям. Они не пересекаются и не подменяют друг друга.
- Человеческий вход. Человек входит в консоль через внешний OIDC-провайдер.
Консоль получает session-JWT и ходит на human-дерево API (
/v1/admin/me,/v1/admin/workspaces/*). Всё скоупится членством человека в воркспейсах. - Машинный доступ. CLI
brt, боты и сервисы предъявляют api-ключ или личный токен. Это отдельная ветка middleware, scope выводится из самого ключа.
Ветку платформа выбирает по форме токена, без угадывания. Префикс
brt_pat_ — это PAT-путь. Всё прочее без префикса идёт на верификацию
session-JWT. Двусмысленности нет: session-JWT это compact JWS, он начинается с
eyJ и префикс нести не может.
Таксономия токенов
| Токен | Форма | Носитель | Scope | Где выпускается |
|---|---|---|---|---|
| Сессия консоли | compact JWS (eyJ…) | человек | аккаунт → его воркспейсы и роли | внешний OIDC-провайдер при входе в консоль |
| Личный токен (PAT) | brt_pat_… | человек-аккаунт | на human-дереве — как владелец аккаунта; на машинной шине с x-workspace-id — воркспейс | страница «Аккаунт» в консоли или профиль brt |
| Ключ бота | bf_… | конкретный бот | (воркспейс, владелец, бот) | provision при первом деплое или POST /v1/admin/bots/baks |
| Ключ интеграции | bf_… | интеграция | воркспейс, BotID=0 | POST /v1/admin/integrations/iaks |
Все токены хранятся в базе только как sha256-хеш. Сырое значение ключа или PAT показывается ровно один раз, в ответе на создание, и невосстановимо. Потеряли — выпускайте новый и отзывайте старый. Мягкий отзыв виден следующему же запросу.
Секреты не передаются через аргументы командной строки. В brt значения
задаются только через stdin или файл:
printf '%s' "$MY_SECRET" | brt secret set STRIPE_KEYТак секрет не оседает в истории shell, в списке процессов и в логах.
Как резолвится тенант из токена
- Ключ бота (
bf_, per-bot) несёт ненулевойBotIDи работает на всех маршрутах, включая привязанные к конкретному боту. - Личный токен (
brt_pat_) на human-дереве действует как аккаунт-владелец. На машинной шине с заголовкомx-workspace-idстановится воркспейс-скоупным принципалом (BotID=0). Заголовок лишь выбирает воркспейс среди тех, где аккаунт состоит; членство перепроверяется — не член получает403, отсутствие заголовка —400. - Ключ интеграции (
bf_, iak) воркспейс-скоупный и несётBotID=0. Он работает на воркспейс-широких поверхностях (реестр, определения интеграций), но на любом маршруте конкретного бота получает честный403. Ключ интеграции не может «просочиться» под видом ключа бота.
403 this API key has no bot scope — преднамеренный гейт на краю API, а не
побочный сбой. Ключ без bot-scope режется до хендлера, а не где-то в глубине
data-plane.
Проверка через API
Личный токен на human-дереве возвращает ваш аккаунт и все воркспейсы с ролями.
curl https://botruntime.ru/v1/admin/me \
-H "Authorization: Bearer brt_pat_2f6b…c91a"{
"account": {
"id": "42",
"email": "ops@example.com"
},
"workspaces": [
{ "id": "7", "name": "Acme", "handle": "acme", "role": "owner" },
{ "id": "9", "name": "Beta", "handle": "beta", "role": "member" }
]
}Тот же PAT на машинной шине требует x-workspace-id, чтобы выбрать воркспейс из
списка:
curl "https://botruntime.ru/v1/admin/workspaces/7/usages?type=ai_spend" \
-H "Authorization: Bearer brt_pat_2f6b…c91a" \
-H "x-workspace-id: 7"Метеринг и квоты
Платформа измеряет потребление на уровне воркспейса. Платёжного движка в ней нет: учёт есть, биллинг — вне области платформы.
- Метеринг. Расход пишется в append-only леджер
meter_event. Основной учитываемый юнит —llm_cost_micro_usd: стоимость LLM в микро-долларах, целочисленные деньги, без float. Расход агрегируется по месяцам и разбивается по ботам (GET /v1/admin/usages/…,/workspaces/{id}/usages/by-bot). - Квоты. У воркспейса есть лимиты — например,
ai_spend, потолок трат на LLM в USD/месяц. Дефолты консервативны; override пишется черезPUT /v1/admin/quotas/ai-spend.
Не привязывайте стоимость к отдельному ответу бота. Расход учитывается леджером агрегатов (usages), а не как цена за реплику. Хранилище файлов считается как текущий STOCK (сумма размеров), а не по записанным байтам.
У ботов нет собственных ключей LLM-провайдера: модельные вызовы идут через шлюз платформы, где и живут лимиты, метеринг и трейсинг. Трейсы всегда metadata-only: содержимое сообщений, prompt/output и user/session/message IDs не записываются и не экспортируются.