Перейти к основному содержимому

ADR-003: Кастомные роли и разрешения

  • Статус: Принято
  • Дата: 16 апреля 2026
  • Автор: CTO Agent (KALA-141)

Контекст

Исходная авторизационная модель использовала фиксированный Prisma-enum Role:

  • admin;
  • dept_head;
  • approver;
  • employee.

Этого хватало для первых API-гейтов, но не для реальных компаний. Тенантам нужны роли вроде "Legal Reviewer", "Finance Operator" или "Regional Manager" — без ожидания code deploy. Одновременно продукт ещё не требует полноценной many-to-many RBAC с permission-таблицей, иерархией ролей, ресурс-грантами и policy-языком.

Решение

Ввести SystemRole как основную модель:

model SystemRole {
id String
name String
slug String
isSystem Boolean
allDepartments Boolean
departmentIds String[]
permissions String[]
}

Встроенные роли хранятся как строки SystemRole с isSystem: true. Роли, созданные тенантом, имеют isSystem: false. User.roleId указывает на role-строку, а legacy enum остаётся в User.role для совместимости и fallback во время миграции.

Разрешения — строковые ключи из центрального каталога. Wildcard * выдаёт все известные разрешения.

Рассмотренные альтернативы

Оставить только enum

Отклонено. Любая tenant-специфичная роль потребовала бы правок в коде приложения и деплоя для изменения политики.

Many-to-many таблица разрешений

Пока отклонено. Нормализованная permission-таблица с role-permission join — мощная штука, но добавляет миграции, джоины, сложность UI и дрейф политики до того, как продукт действительно этого требует.

Policy DSL в JSON

Отклонено. Policy DSL слишком гибкий для текущего админ-UI и его сложнее аудитить, чем явные permission-ключи.

Последствия

Плюсы

  • Кастомные роли можно создавать и править в runtime.
  • Встроенные роли защищены через isSystem.
  • Permission-проверки остаются простыми: проверка членства в массиве + расширение wildcard.
  • Legacy enum fallback позволяет существующим пользователям и тестам работать во время миграции.

Минусы

  • Permission-ключи должны быть стабильными — они хранятся как строки.
  • Переименование permission требует миграции данных.
  • Нет resource-level policy language; изоляция идёт по оси department scope.

Заметки по реализации

  • src/lib/permission-catalog.ts владеет валидным списком ключей.
  • src/lib/permissions.ts вычисляет capabilities и эффективные permission-наборы.
  • src/routes/roles.ts обрабатывает role CRUD и защищает системные роли.
  • Seed-данные создают четыре встроенные строки SystemRole.