Миграция 053: user_permissions.expires_at (NULL = бессрочно). Резолвер isEnabled
+ /me + /users/:id игнорируют просроченные оверрайды (наследуют роль); seedDefaults
чистит просроченные строки. setUserPermission принимает days → выдаёт право на
срок (datetime('now','+N days')). API отдаёт expiresAt. Клиент: setUserPermission(...,days).
В модалке прав пользователя — бейдж «до ДАТА» + кнопка «врем.» (выдать на N дней).
Тест: срок хранится/отдаётся, просроченное игнорируется и вычищается. Backend pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PRESETS (student): «Полный доступ», «Режим фокуса» (без магазина/испытаний),
«Ограниченный» (+ без лаборатории), «Сбросить к стандарту роли». GET
/api/permissions/presets + POST /api/permissions/class/:id/preset (admin).
Рефактор: общий applyPermsToClass() (карта key→1/0/inherit) — его используют и
bulk, и preset. В блоке «Массово по классу» — кнопки пресетов (с подтверждением).
Тест: список + применение focus/reset + валидация. Backend pass (3 baseline-Auth).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
POST /api/permissions/class/:id/bulk { permission, enabled } (admin, явный
requireRole) — выставляет user_permissions всем ученикам класса (1/0/null=сброс),
точечный token_version bump каждому. Валидация: только студенческие ключи.
Клиент LS.setClassPermission. В админке «Доступ · роли» — блок «Массово по
классу»: выбор класса → у каждого права «включить/выключить всем / сбросить».
Тест: оверрайд всем + сброс + отклонение teacher-ключа. Backend 221 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
registry: карта GROUP (Вопросы / Класс и ученики / Библиотека / Курсы и шаблоны /
Геймификация / Контент / Тесты и активность / Профиль), проброшена в byRole.group.
permissions.js: вкладка «Доступ · роли» рендерит права секциями по группам, у
каждой — «включить все / выключить все» (с подтверждением, если в группе есть
requireConfirmOff). Карточка вынесена в permCard(). Тест: definitions содержат group.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
requirePermission читает права из БД на каждый запрос → серверное применение
живое. Прежний bump token_version при role-level изменении разлогинивал ВСЕХ
пользователей роли из-за одного тумблера. Убрали его: изменение применяется
сразу на сервере, клиент подхватит при следующем /permissions/me. User-level
bump оставлен (точечно одному пользователю — целевое обновление, не массовое).
Тест 3 обновлён: role-level НЕ бампает token_version + значение сохраняется.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GET /api/permissions/log (admin-only) — последние изменения ролевых прав (или
?user_id= для личных оверрайдов) из admin_audit_log; читаемый текст («включил
«X» для роли «учитель»») с резолвом меток через registry. Клиент LS.permissionsLog.
Вкладка «Доступ · роли»: блок «История изменений прав ролей» с кнопкой «Показать».
Тест: admin видит записи, не-админу 403. permissions 13/13.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
registry: поле requires (questions.delete→manage, templates.public→manage,
courses.interactive→manage, simulations.quiz→access), проброшено в byRole.
auth.requirePermission: вынесен isEnabled(); право = own AND все requires
(дочернее не работает без родителя). /me и /users/🆔 effective с учётом
requires + requires в ответе. UI permissions.js: каскад — дочернее с
невыполненной зависимостью неактивно (тумблер заблокирован + «Требует: …»).
Тест зависимости. План: plans/permissions-rework/PLAN.md. Backend 216 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>