Files
LMS/docs/17-handoff.md

17 KiB
Raw Blame History

17. Handoff: состояние проекта после закрытия продуктовых рисков

Дата: 2026-10-28. Ветка: feature/groups-courses (54 коммита от master до этого документа). HEAD на момент написания: 1ca507b (docs: закрытие продуктовых рисков и новый LDD baseline). Пуш: не выполнялся. Рабочее дерево: чистое. exports/ и texput.log игнорируются и в коммиты не входят.

Этот файл — точка восстановления контекста. Если после сжатия контекста непонятно, что уже сделано и с чего продолжать, читать сначала его, затем 16-stage6-report.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) возвращает список файлов без привязки.
  • Старые прямые ссылки на бесхозные файлы у обычных пользователей ломаются осознанно.

JWT и сессия

  • Access JWT: 15 минут (LMS_ACCESS_TOKEN_TTL).
  • Refresh-токен: 7 дней (LMS_REFRESH_TOKEN_TTL), хранится как SHA-256 hash, ротируется при обновлении.
  • Web-сессия: 7 дней sliding (LMS_SESSION_TIMEOUT), cookie LMS_SESSION; каждый ответ перевыпускает cookie с новым Max-Age; access обновляется автоматически за 2 минуты до истечения.
  • Logout и смена пароля отзывают refresh; отчисленный/архивный студент, архивный аккаунт и закрытая группа не могут обновить сессию.
  • Повторное использование старого refresh отзывает все активные сессии аккаунта.
  • Gateway публикует без access-JWT: /auth/login, /auth/setup, /auth/refresh, /auth/logout, /auth/health, /auth/jwks.json, /content/**, /actuator.
  • Миграция identity V11__refresh_tokens.sql.

1.5. Аудит

  • identity.audit_events (V10), внутренний API POST /api/v1/internal/audit, админский GET /api/v1/audit.
  • Пишутся доступы/изменения курсов, групп, материалов, лабораторных, оценки, публикации, защиты, профиль (staff), скачивания, lifecycle, promotion.
  • /admin/audit: фильтр по действию, страницы page/limit и «Показать ещё».
  • Пагинация пока offset, не keyset.

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.
  • 46 claims: 25 P0 + 21 P1; 46/46 proven, P0 gaps = 0.
  • 319 автотестов, 0 failures/errors/skipped.
  • Mutation testing:
    • CourseAccessService — 75% test strength;
    • PromotionPlanner — 69%.
  • Browser E2E Playwright: 4/4 (admin, teacher, student/anonymous, сдача + mobile).
  • Runtime/load: 100 групп / 1000 студентов, 125 параллельных чтений, 0 ошибок, p95 < 1 c.
  • CI:
    • check на push/MR: тесты, ktlint, detekt, evidenceCheck;
    • smoke на push/MR: полный стек, health, вход админа, один курс;
    • release-evidence обязателен для тегов, вручную для веток: тесты, миграции, mutation, runtime/load, browser E2E, финальный evidence.
  • Полный ./scripts/ldd-release.sh локально прошёл.

1.9. Миграции

Сервис Диапазон Что важно
identity V4–V11 роли, группы, статусы, promotion, история, аудит (V10), refresh (V11)
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_INTERNAL_TOKEN.
  • Перед деплоем: backup, preflight, затем deploy, bootstrap курса C (если курсов нет), postflight, smoke.
  • Gateway не маршрутизирует /api/v1/internal/**.

2. Проверки: что и как запускать

# 1. Полный локальный gate
./gradlew test ktlintCheck detekt evidenceCheck

# 2. Полный LDD release (долго, нужны Docker/Java/Node/Chromium)
./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

Последний подтверждённый результат:

  • 319 тестов, 0 failures;
  • 46/46 claims;
  • ldd-release.sh — PASS;
  • smoke — PASS;
  • git diff --check — чисто.

Артефакты: build/evidence/{runtime,migration,mutation,browser,smoke}.json.


3. Что осталось внести / закрыть

Это уже не текущие P0-риски, а следующий backlog. Порядок recommended.

  1. Инвентаризация legacy-активов на реальном хранилище. Endpoint /api/v1/content/legacy-assets готов. Нужно на production/staging получить список бесхозных файлов, привязать их к статьям или удалить; после этого можно решить, удалять ли админский fallback. Старые внешние ссылки на такие файлы уже не работают у обычных пользователей.

  2. Keyset-пагинация аудита. Сейчас page/limit offset. Для очень больших журналов возможны сдвиги при вставках между страницами. Нужен курсор (occurred_at, id).

  3. Оставшиеся N+1.

    • AcademicClient.getCourseLabs подтягивает детали лабораторных по одной;
    • часть данных ведомости/профиля на больших курсах собирается по частям. Имена групп курса уже bulk; аудит paginated. Нужны пакетные эндпоинты или join-выборки, затем повторный load-тест.
  4. Абсолютный лимит web-сессии. Сессия sliding 7 дней и отзывается, но при непрерывной активности может жить бессрочно. Если политика требует повторный вход раз в N дней — добавить абсолютный max-age/absolute timeout.

  5. Очистка refresh_tokens. Истёкшие записи сейчас не удаляются. Добавить scheduled cleanup (например, раз в сутки) и метрику/аудит.

  6. CI на реальном runner. Конфиг готов, но нужно убедиться, что в GitLab есть shell-runners с тегами lms-shell (smoke) и lms-release (полный release), Java 21, Docker Compose v2, Python 3, Node/Chromium. check использует privileged docker:dind для Testcontainers. Проверить первый pipeline на push и первый tag-job.

  7. Browser E2E: расширение. Сейчас 4 сценария, один Chromium. Нет Safari/Firefox, визуальной регрессии и полного мобильного набора. Добавлять по мере роста UI.

  8. Внешняя интеграция «Контингент»/next.uniyar.ac.ru. Отдельный anti-corruption слой, идемпотентная синхронизация, журнал, отчёт дрифта, внешние идентификаторы. В текущий объём не входила.

  9. Release-evidence job на теге. В .gitlab-ci.yml он обязателен для тегов и ручной для веток, но реальный прогон в CI ещё не подтверждён — см. п. 6.


4. Важные gotchas

  • Не возвращать публичный доступ к legacy-активам.
  • Не добавлять number_label/строковый номер группы: по требованиям номер числовой, ведущего нуля быть не может.
  • Не делать access JWT снова 30-дневным. 30 дней — только refresh/web session.
  • Gateway обязан пропускать /api/v1/auth/refresh и /api/v1/auth/logout без access-JWT; иначе автообновление и logout ломаются.
  • LMS_ACCESS_TOKEN_TTL, LMS_REFRESH_TOKEN_TTL, LMS_SESSION_TIMEOUT должны попадать в environment соответствующих контейнеров; сейчас docker-compose.prod.yml это делает.
  • Session cookie LMS_SESSION перевыпускается фильтром на каждом ответе; не убирать его, иначе 7-дневная sliding-сессия станет абсолютной.
  • article_assets без связи → 404 для всех, кроме админа. Это намеренно.
  • Не коммитить 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 — должно быть 319 тестов и 46/46 claims.
  4. Для release-проверки: ./scripts/ldd-release.sh.
  5. Для быстрой проверки: ./scripts/ldd-smoke.sh.
  6. Продолжать backlog из §3, начиная с инвентаризации legacy-активов и keyset-пагинации.

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-токены.
  • services/identity-service/.../db/migration/V11__refresh_tokens.sql.
  • services/identity-service/.../audit/ — аудит.
  • services/academic-service/.../content/ — материалы/активы.
  • services/academic-service/.../course/CourseGroupService.kt — bulk-группы.
  • services/progress-service/.../CourseScopeService.kt — course-scope.
  • web/.../session/SessionTokenService.kt, SessionCookieRefreshFilter.kt.
  • web/.../admin/AdminAuditController.kt.
  • services/gateway/.../JwtAuthenticationFilter.kt.
  • scripts/ldd-*.sh.
  • deploy/backup.sh, deploy/deploy.sh, deploy/DEPLOY.md.