---
name: project_permissions_rework
description: "Переработка ролевых прав LearnSpace (registry/role_permissions/user_permissions): Phase A+B готовы, Phase C (кастомные роли) остаётся"
metadata:
  node_type: memory
  type: project
  originSessionId: 60467058-b40e-4bd9-9f7f-d1e362e8039a
---

Переработка **ролевой системы прав** (отдельной от content_access — см. [[project_content_access]]).
Это система `registry.js` (ключи прав) + `role_permissions`/`user_permissions` + middleware
`requirePermission` (читает права ЖИВЬЁМ из БД каждый запрос) + админ-вкладка «Доступ · роли»
(`frontend/js/admin/sections/permissions.js`) + модалка прав пользователя (`users.js`, `up-modal`).
План: `plans/permissions-rework/PLAN.md`.

**Phase A + B ЗАВЕРШЕНЫ (2026-06-03), всё на master:**
- **A1** (9ac2a61) — зависимости `requires` в реестре (questions.delete→manage, templates.public→manage,
  courses.interactive→manage, simulations.quiz→access). Право = own AND все requires. UI-каскад.
- **A2** (b0e385b) — lint-тест `backend/tests/permissions-registry.test.js` (ключи requirePermission/perm
  есть в реестре) + метки theory/simulations переформулированы («…доступен роли»).
- **A3** (7d474b4) — история изменений прав: `GET /api/permissions/log` (admin), кнопка на вкладке.
- **A4** (6bd1532) — убран role-level `token_version` bump (серверное применение живое → не нужен
  массовый разлогин роли). User-level bump оставлен.
- **B5** (0a24a66) — группы прав (поле GROUP в реестре → byRole.group), секции в UI + вкл/выкл группы.
- **B6** (b95b639) — массово по классу: `POST /api/permissions/class/:id/bulk` (admin), всем ученикам.
- **B7** (8b495f1) — пресеты-профили (PRESETS.student: full/focus/restricted/reset),
  `GET /api/permissions/presets` + `POST /api/permissions/class/:id/preset`; общий хелпер `applyPermsToClass`.
- **B8** (a250d15) — временные права: миграция **053** (`user_permissions.expires_at`). Резолвер/`/me`/
  `/users/:id` игнорируют просроченные; `seedDefaults` чистит. `setUserPermission(...,days)`. В модалке
  прав пользователя — бейдж «до ДАТА» + кнопка «врем.».

Тесты: `permissions.test.js` 17/17, `permissions-registry.test.js` 2/2. Полный backend-набор в рамках
baseline (3 Auth + флака «intro» chemistry8 под нагрузкой).

**PHASE C (ПРОИЗВОЛЬНЫЕ КАСТОМНЫЕ РОЛИ) — ЗАВЕРШЕНА И ВЛИТА В master** (2026-06-03, fast-forward
до `b4a5b1a`, запушено в origin; ветка `feature/custom-roles` осталась локально, можно удалить).
План: `plans/permissions-rework/PHASE_C_DESIGN.md`.
Модель (без рефактора 111 requireRole): кастомная роль НАСЛЕДУЕТ «базовые роли» (какие встроенные гейты
проходит) + хранит функциональную базу в `users.role` (CHECK ок) + имя в `users.custom_role` + свой набор
прав в role_permissions под именем роли.
- C-1 (054): таблица `roles`(name,label,base_roles,is_builtin) + `auth.effectiveRoles()`; requireRole
  сверяет пересечение с effectiveRoles(customRole||role) — встроенные роли быстрый путь, 111 гейтов не задеты.
- C-2 (055): `users.custom_role` (ADD COLUMN, без пересборки users); `updateRole` принимает кастомную роль
  → база=base_roles[0] + custom_role=имя; `authMiddleware`/`optionalAuth` → req.user.customRole.
- C-3 (056): снят CHECK у role_permissions; `isEnabled(uid,permRole,baseRole,key)` = user→role_permissions
  [customRole]→фолбэк[base]→дефолт(base); getMyPermissions/getUserPermissions: roleMap база+оверлей.
- C-4a: rolesController + `/api/roles` CRUD (admin, inline guards) + засев прав из базы; setPermission
  принимает кастомные роли (ключ по базе, хранит под именем).
- C-4b: UI «Конструктор ролей» в `permissions.js` (#perm-roles в admin.html: создать/настроить права/
  удалить) + выпадающий список ролей у пользователя в `users.js` (optgroup «Кастомные роли»).
Тесты: custom-roles 8/8, roles-api 5/5, permissions 17/17, full backend в рамках baseline.
**ВЛИТО в master 2026-06-03** (push ok, сервер перезапущен на master, /api/health = 200). Кнопка «Права»
в карточке пользователя видна всем, кроме admin (фикс b4a5b1a).
**НЕ делались (исходный дизайн Phase C, не выбраны):** C-10 делегирование учителю, C-11 пер-классовый скоуп прав.

**Заметка от A2-линта:** ряд teacher-прав (`students.invite`, `sessions.reset`, `results.export`,
`schedule.manage`, `templates.public`, `courses.interactive`) и `theory.access` НЕ enforce-ятся через
`requirePermission` на сервере — потенциальные недогейченные точки, проверить отдельно.

**Гочи:** новый роут требует inline-гейт (requireRole/requirePermission), иначе pre-commit route-lint
блокирует (был случай с /class/:id/bulk). Сервер надо перезапускать, чтобы подхватить изменения.
