ADR-005: Переопределение разрешений
- Статус: Принято
- Дата: 16 апреля 2026
- Автор: CTO Agent (KALA-148)
Контекст
Кастомные роли покрывают стабильную tenant-политику, но production-системам всегда нужны исключения:
- временно выдать одному пользователю доступ к просмотру ролей;
- отозвать рискованное разрешение у пользователя, который в целом попадает под широкую роль;
- добавить один отдел для проекта;
- убрать один отдел из широкой роли.
Заводить новую роль под каждое исключение — это role sprawl. Полноценная таблица разрешений с per-user грантами добавит больше механики, чем сейчас нужно продукту.
Решение
Хранить override-ы прямо в User:
extraPermissions String[]
revokedPermissions String[]
extraDepartmentIds String[]
revokedDepartmentIds String[]
Эффективные разрешения считаются так:
effectivePerm = role.permissions + extraPermissions - revokedPermissions
Эффективные отделы — по тому же шаблону "add-then-remove":
effectiveDept = role.departmentIds + primary department + extraDepartmentIds - revokedDepartmentIds
Revoke применяется последним — значит бьёт и базовую роль, и additions.
Рассмотренные альтернативы
Per-user permission join-таблица
Пока отклонено. Запросы писать проще, но это добавляет write-пути, джоины и сложность UI до того, как permissions станут достаточно мощными, чтобы оправдать это.
Кастомная роль под каждое исключение
Отклонено. Список ролей становится шумным, и размывается сам факт, что изменение — это индивидуальное исключение.
Только deny-override-ы
Отклонено. Продукту нужны и временные grants, и дополнительный department-доступ.
Последствия
Плюсы
- Исключения явно видны в user-записи.
- Эффективный доступ детерминирован и легко объясним.
- Grant- и revoke-API могут писать audit-записи с измененным ключом.
- Revoke даёт простой deny-механизм без полноценного policy engine.
Минусы
- Массивы менее нормализованы, чем join-строки.
- Переименование permission-ключа требует array-миграции.
- Слишком много override-ов на одного пользователя — сигнал, что нужно делать отдельную роль.
Заметки по реализации
POST /users/:id/permissions/grantдобавляет permission и extra department.POST /users/:id/permissions/revokeдобавляет permission revoke и department revoke.- DELETE-маршруты убирают grant- или revoke-записи.
GET /users/:id/effective-accessвозвращает роль, override-ы и вычисленный доступ — для отладки.- Каждая override-мутация пишет audit log entry.