# 17. Handoff: состояние проекта после закрытия продуктовых рисков **Дата:** 2026-10-28. **Ветка:** `feature/groups-courses` (последняя история — `git log --oneline`). **Последний коммит перед этой итерацией:** `3aae2b8` (`docs: handoff после закрытия продуктовых рисков`). **Пуш:** не выполнялся. **Рабочее дерево:** чистое. `exports/` и `texput.log` игнорируются и в коммиты не входят. Этот файл — точка восстановления контекста. Если после сжатия контекста непонятно, что уже сделано и с чего продолжать, читать сначала его, затем [16-stage6-report.md](16-stage6-report.md) и [evidence/README.md](evidence/README.md). --- ## 1. Что уже сделано ### 1.1. Доменная модель и роли - Роли `ADMIN`, `TEACHER`, `STUDENT`. - Группы-когорты: `stream`, `course`, `number`, `degree`, `enrollment_year`, `status`; история членства `membership_history`. - Импорт групп Excel v2, перевод/академ/отчисление/выпуск/архив/восстановление. - Удаление студента с усиленным подтверждением (`login` или ФИО). - Вход закрыт для отчисленных, выпущенных, архивных студентов и архивных аккаунтов; архивные/выпущенные группы также блокируют вход. - Ведущие нули номера группы **не являются требованием**: номер числовой, отдельной поддержки `01` нет. Не добавлять `number_label` без нового ТЗ. ### 1.2. Курсы и лабораторные - Курс C «Промышленное программирование на языке C» создаётся bootstrap-ом; все лабораторные и главы переносятся в курс. - `labs.course_id` становится `NOT NULL` в runtime после bootstrap (`CourseBootstrapService`), а не отдельной Flyway-миграцией. - У курса есть человекочитаемый `course_no`; материалы открываются по `/learn/course/{course_no}/...`. - Bootstrap идемпотентен, сериализуется advisory lock, проверяет дубли номеров лабораторных. - Копирование лабораторной в другой курс; перенос между курсами запрещён. - Копия не переносит прогресс, оценки, сдачи, защиты и назначения. ### 1.3. Права преподавателя - Преподаватель своего курса может: - вести лабораторные/уровни/варианты; - работать с ведомостью: оценки, комментарии, публикация, уровни, варианты, распределение, защиты; - привязывать/отвязывать группы курса; - авторствовать материалы курса (главы/разделы/статьи, публикация). - Все проверки скоупа выполняются на сервисах; чужие курс/лаба/студент/группа дают `403` или `404`. - Глобальные материалы и админские функции остаются только у `ADMIN`. ### 1.4. Безопасность (закрытые продуктовые риски) **Legacy-активы** - Файл без записи в `article_assets` больше не публичен: аноним/студент/преподаватель → `404`; админ → доступ. - Инвентаризация: `GET /api/v1/content/legacy-assets` (ADMIN), страница `/admin/legacy-assets` и `scripts/legacy-assets.sh` для работы со стендом. - Старые прямые ссылки на бесхозные файлы у обычных пользователей ломаются осознанно. **JWT и сессия** - Access JWT: **15 минут** (`LMS_ACCESS_TOKEN_TTL`). - Refresh-токен: **7 дней** (`LMS_REFRESH_TOKEN_TTL`), хранится как SHA-256 hash, ротируется при обновлении. - Абсолютный потолок сессии: **30 дней** (`LMS_SESSION_ABSOLUTE_TIMEOUT`); ротация refresh не продлевает `absolute_expires_at` (миграция `V12`). - Web-сессия: **7 дней sliding** (`LMS_SESSION_TIMEOUT`), cookie `LMS_SESSION`; каждый ответ перевыпускает cookie с новым `Max-Age`, но не дольше остатка абсолютного срока; access обновляется автоматически за 2 минуты до истечения. - Logout и смена пароля отзывают refresh; отчисленный/архивный студент, архивный аккаунт и закрытая группа не могут обновить сессию. - Повторное использование старого refresh отзывает все активные сессии аккаунта. - Истёкшие refresh-токены старше 30 дней удаляет ночная задача (`LMS_REFRESH_TOKEN_RETENTION`, `LMS_REFRESH_TOKEN_CLEANUP_CRON`). - Gateway публикует без access-JWT: `/auth/login`, `/auth/setup`, `/auth/refresh`, `/auth/logout`, `/auth/health`, `/auth/jwks.json`, `/content/**`, `/actuator`. - Миграции `identity V11__refresh_tokens.sql` и `V12__refresh_absolute.sql`. ### 1.5. Аудит - `identity.audit_events` (V10), внутренний API `POST /api/v1/internal/audit`, админский `GET /api/v1/audit`. - Пишутся доступы/изменения курсов, групп, материалов, лабораторных, оценки, публикации, защиты, профиль (staff), скачивания, lifecycle, promotion. - `/admin/audit`: фильтр по действию, keyset-курсор `(occurred_at, id)`, «Показать ещё» и «В начало»; offset `page` остался для совместимости. - `V13` добавляет индексы `(occurred_at DESC, id DESC)` и `(action, occurred_at DESC, id DESC)`. ### 1.6. Материалы и активы - Курсовые материалы `/learn/course/{course_no}/...`; публичные глобальные материалы `audience=ALL`; `STUDENTS`/`TEACHERS` требуют роль. - Видимость `article_assets` наследуется от статьи/раздела/главы. - `V12__asset_links_backfill.sql` восстанавливает связи из markdown-ссылок. - Legacy-файлы без ссылок теперь закрыты (см. §1.4). ### 1.7. Повышение и жизненный цикл - Promotion: dry-run, apply, idempotency, rollback, cancel, scheduler 31 августа МСК и catch-up, advisory locks, conflict detection. - Student lifecycle: academic leave/return/expel/archive/restore. - Concurrent apply/rollback покрыты интеграционными тестами. - Apply/rollback на сгенерированных когортах покрыты model-based тестом. ### 1.8. LDD и проверки - Реестр: `docs/evidence/claims.json`. - **51 claim**: 25 P0 + 26 P1; **51/51 proven, P0 gaps = 0**. - **346 автотестов**, 0 failures/errors/skipped. - Mutation testing: - `CourseAccessService` — 75% test strength; - `PromotionPlanner` — 69%. - Browser E2E Playwright: 6 сценариев × Chromium/Firefox/WebKit = **18/18** (admin, teacher, student/anonymous, сдача + mobile, cursor-аудит, инвентаризация активов). - Runtime/load: 100 групп / 1000 студентов, 125 параллельных чтений, 0 ошибок, p95 < 1 c. - CI: - `check` на push/MR: тесты, ktlint, detekt, `evidenceCheck`; - `smoke` на push/MR: preflight + полный стек, health, вход админа, один курс; - `release-evidence` обязателен для тегов, вручную для веток: тесты, миграции, mutation, runtime/load, browser E2E, финальный evidence. - `scripts/ci-preflight.sh` проверяет Java 21, Docker Compose v2, Python 3, Node и Playwright; первый реальный GitLab pipeline ещё не подтверждён. - Полный `./scripts/ldd-release.sh` локально прошёл; browser E2E — 18/18. ### 1.9. Миграции | Сервис | Диапазон | Что важно | |---|---|---| | identity | `V4–V13` | роли, группы, статусы, promotion, история, аудит (`V10`), refresh (`V11`), absolute (`V12`), keyset-индексы (`V13`) | | academic | `V6–V12` | курсы, `labs.course_id`, главы/аудитории, индексы, `article_assets`, backfill | | progress | `V13` | автор комментария/оценки; V13 идемпотентна | - **Progress V10 отсутствует.** Не создавать и не включать `out-of-order`. - Если dev/staging когда-то применял старую feature-миграцию progress V10, удалить только строку истории через `flyway repair`/SQL из `deploy/preflight/progress-v10-v13.sql`, объекты не удалять. - Production/master: progress V11/V12 уже есть, затем V13. - `labs.course_id NOT NULL` — post-bootstrap, не Flyway. ### 1.10. Деплой и backup - `deploy/backup.sh`: БД + submissions + assets, checksums, retention, verify/restore; `deploy.sh up` требует backup. - Ежедневный systemd timer. - Production `.env.prod`: обязательны `LMS_DB_PASSWORD`, `LMS_INTERNAL_TOKEN`; сроки сессии можно переопределить. - `docker-compose.prod.yml` прокидывает: `LMS_ACCESS_TOKEN_TTL`, `LMS_REFRESH_TOKEN_TTL`, `LMS_SESSION_TIMEOUT`, `LMS_SESSION_ABSOLUTE_TIMEOUT`, `LMS_REFRESH_TOKEN_RETENTION`, `LMS_REFRESH_TOKEN_CLEANUP_CRON`, `LMS_INTERNAL_TOKEN`. - Перед деплоем: backup, preflight, затем deploy, bootstrap курса C (если курсов нет), postflight, smoke. - Gateway не маршрутизирует `/api/v1/internal/**`. --- ## 2. Проверки: что и как запускать ```bash # 1. Полный локальный gate ./gradlew test ktlintCheck detekt evidenceCheck # 2. Полный LDD release (долго, нужны Docker/Java/Node + Chromium/Firefox/WebKit) ./scripts/ldd-release.sh # 3. Быстрый smoke (то же, что CI smoke) ./scripts/ldd-smoke.sh # 4. Отдельные проверки ./scripts/ldd-migration-upgrade.sh ./scripts/ldd-mutation.sh ./scripts/ldd-runtime.sh # с LDD_SCALE=1 — 100 групп/1000 студентов ./scripts/ldd-browser.sh # 5. Отчёт cat build/reports/evidence/index.md ``` Последний подтверждённый результат: - `346` тестов, 0 failures; - `51/51` claims; - `ldd-release.sh` — PASS; - smoke — PASS; - browser E2E — 18/18 (Chromium/Firefox/WebKit); - `git diff --check` — чисто. Артефакты: `build/evidence/{runtime,migration,mutation,browser,smoke}.json`. --- ## 3. Что осталось внести / закрыть Это уже не P0-риски, а эксплуатационный backlog. Порядок recommended. 1. **Инвентаризация legacy-активов на реальном хранилище.** Страница `/admin/legacy-assets`, API и `scripts/legacy-assets.sh` готовы. Нужно на production/staging получить список бесхозных файлов, привязать их к статьям или удалить. Старые внешние ссылки на такие файлы уже не работают у обычных пользователей. 2. **Первый реальный CI-прогон.** Конфиг, smoke и `scripts/ci-preflight.sh` готовы, но нужно убедиться, что в GitLab есть shell-runners с тегами `lms-shell` (smoke) и `lms-release` (полный release), Java 21, Docker Compose v2, Python 3, Node и браузерами Chromium/Firefox/WebKit. `check` использует privileged `docker:dind`. Дождаться зелёного push-MR pipeline и первого tag-job `release-evidence`. 3. **Визуальная регрессия и мобильный набор E2E.** Сейчас 6 сценариев × 3 браузера, без screenshot-diff. Добавлять по мере роста UI. 4. **Пакетные сдачи в ведомости.** Детали лабораторных курса уже пакетные, но ведомость запрашивает сдачи по выбранной лабораторной отдельно. Оптимизировать, если появятся медленные курсы. «Контингент»/`next.uniyar.ac.ru` **исключён из плана** решением от 2026-10-28: нет API-спецификации и доступа к внешней системе. При появлении нового ТЗ вернуться к отдельному anti-corruption слою. --- ## 4. Важные gotchas - Не возвращать публичный доступ к legacy-активам. - Не добавлять `number_label`/строковый номер группы: по требованиям номер числовой, ведущего нуля быть не может. - Не делать access JWT снова 30-дневным. 30 дней — только абсолютный потолок сессии. - Gateway обязан пропускать `/api/v1/auth/refresh` и `/api/v1/auth/logout` без access-JWT; иначе автообновление и logout ломаются. - `LMS_ACCESS_TOKEN_TTL`, `LMS_REFRESH_TOKEN_TTL`, `LMS_SESSION_TIMEOUT`, `LMS_SESSION_ABSOLUTE_TIMEOUT`, `LMS_REFRESH_TOKEN_RETENTION` и `LMS_REFRESH_TOKEN_CLEANUP_CRON` должны попадать в environment соответствующих контейнеров; сейчас `docker-compose.prod.yml` это делает. - Session cookie `LMS_SESSION` перевыпускается фильтром на каждом ответе, но `Max-Age` ограничен остатком абсолютного срока; не убирать ни фильтр, ни проверку `AUTHENTICATED_AT`, иначе 30-дневный потолок перестанет работать. - Keyset-аудит использует `(occurred_at, id)`; не менять сортировку на одну `id` или `occurred_at` без пересчёта курсора. Offset `page` — только совместимость. - `article_assets` без связи → 404 для всех, кроме админа. Это намеренно. - `/api/v1/labs/details` — пакетный контракт progress; не заменять его обратно на цикл `/labs/{id}` без load-теста. - WebKit на Fedora требует Docker-образа Playwright; `ldd-browser.sh` сам переключается, не ломать этот fallback. - Не коммитить `exports/`, `texput.log`, `data/`, `backups/`, `.env.prod`. - Не включать Flyway `out-of-order`; progress V10 не существует. - `labs.course_id NOT NULL` применяется bootstrap-сервисом в runtime. - Перед production deploy — backup; без него deploy.sh не продолжит. - Внутренние пути `/api/v1/internal/**` не должны появляться в gateway. - `LMS_INTERNAL_TOKEN` должен быть одинаковым у identity/academic/progress. --- ## 5. Если контекст потерян: порядок восстановления 1. `git status --short --branch` — ожидается чистое дерево на `feature/groups-courses`. 2. Прочитать этот файл, затем `docs/16-stage6-report.md` §7 и `docs/evidence/baseline-2026-10-27.md`. 3. `./gradlew test ktlintCheck detekt evidenceCheck` — должно быть 346 тестов и 51/51 claims. 4. Для release-проверки: `./scripts/ldd-release.sh`. 5. Для быстрой проверки: `./scripts/ldd-smoke.sh` и `./scripts/ci-preflight.sh`. 6. Продолжать backlog из §3, начиная с инвентаризации legacy-активов и первого реального CI-прогона. ## 6. Карта ключевых файлов - `docs/07-groups-courses.md` — ТЗ. - `docs/08-groups-courses-plan.md` — план и статус. - `docs/16-stage6-report.md` — итоговый отчёт, deploy и ограничения. - `docs/evidence/claims.json` — LDD-реестр. - `docs/evidence/baseline-2026-10-27.md` — фактический baseline. - `services/identity-service/.../auth/refresh/` — refresh-токены, absolute, cleanup. - `services/identity-service/.../db/migration/V11__refresh_tokens.sql`, `V12__refresh_absolute.sql`, `V13__audit_keyset_indexes.sql`. - `services/identity-service/.../audit/` — аудит и keyset-курсор. - `services/academic-service/.../content/` — материалы/активы. - `services/academic-service/.../course/CourseGroupService.kt` — bulk-группы. - `services/academic-service/.../lab/LabService.kt` — `details(courseId)`. - `services/progress-service/.../AcademicClient.kt` — пакетный вызов лаб. - `services/progress-service/.../CourseScopeService.kt` — course-scope. - `web/.../session/SessionTokenService.kt`, `SessionLifetime.kt`, `SessionCookieRefreshFilter.kt`. - `web/.../admin/AdminAuditController.kt`, `LegacyAssetsController.kt`. - `services/gateway/.../JwtAuthenticationFilter.kt`. - `scripts/ldd-*.sh`, `scripts/ci-preflight.sh`, `scripts/legacy-assets.sh`. - `deploy/backup.sh`, `deploy/deploy.sh`, `deploy/DEPLOY.md`.