Files
LMS/docs/17-handoff.md
Vladislav Kopnin 9412d87fed fix: закрыть P0/P1 расхождения с целевой моделью
- академический отпуск: readOnly, запрет submit/start/finish защиты
- черновики лабораторных скрыты от студентов в списке/деталях/по UUID
- удалено действие «Убрать из группы» из UI и API
- импорт: restoreInactiveStatuses, skipped-отчёт, разблокировка архивного аккаунта
- глобальные материалы STUDENTS/TEACHERS с JWT-фильтрацией и сменой аудитории
- LDD-AUTH-005 переведён на unit-evidence, clean check проходит по P0
- backup: безопасный парсинг .env, verify-latest, кавычки cron
- robots/noindex, комментарии без автора, профиль без черновиков
- docs/18-goal-audit.md; baseline 57 claims / 366 тестов / release PASS
2026-10-29 18:58:21 +03:00

19 KiB
Raw Blame History

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

Дата: 2026-10-28. Ветка: feature/groups-courses. История: git log --oneline; последние изменения — Woodpecker CI и изоляция тестовой среды на отдельной VM. Пуш: не выполнялся. Рабочее дерево: чистое. 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), страница /admin/legacy-assets и scripts/legacy-assets.sh для работы со стендом.
  • Старые прямые ссылки на бесхозные файлы у обычных пользователей ломаются осознанно.
  • Студенческие страницы материалов не показывают админские действия статей (удаление/правка/публикация/перемещение); регрессия закрыта тестом LearnSectionTemplateTest в master-хотфиксе 920d96a.

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.
  • 57 claims: 27 P0 + 30 P1; 57/57 proven, P0 gaps = 0.
  • 366 автотестов, 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: Gitea + Woodpecker 3.x, конфигурация в .woodpecker/:
    • check на push/MR: тесты, ktlint, detekt, evidenceCheck;
    • smoke на push/MR: после check — preflight + полный стек, health, вход админа, один курс;
    • release на тег или вручную: тесты, миграции, mutation, runtime/load, browser E2E, финальный evidence.
    • workflows требуют метку isolation=dedicated-vm; шаги идут в nested dind без хост-Docker сокета, агент — на отдельной VM без production-доступа.
    • scripts/ci-preflight.sh проверяет Java 21, Docker Compose v2, Python 3, Node и Playwright. Установка server/agent — deploy/WOODPECKER.md.
    • Первый реальный Woodpecker 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. Проверки: что и как запускать

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

# 2. Полный LDD release (долго, нужны Docker/Java/Node + Chromium/Firefox/WebKit)
./scripts/ldd-release.sh

# 3. Быстрый smoke (то же, что workflow 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

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

  • 366 тестов, 0 failures;
  • 57/57 claims, P0 gaps = 0;
  • 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-прогон. Развернуть Woodpecker server и Docker-backend агент на отдельной VM по deploy/WOODPECKER.md, включить репозиторий в Woodpecker и дождаться зелёных workflow check/smoke на push-MR и release на теге. Шаги идут в nested dind; VM не имеет доступа к production и production-секретам.

  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 — должно быть 366 тестов и 57 claims, 57/57 proven.
  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/ci-dind.sh, scripts/ci-build-image.sh, scripts/legacy-assets.sh.
  • .woodpecker/{check,smoke,release}.yaml — CI на Gitea + Woodpecker.
  • ci/Dockerfile — прогретый образ lms-ci:1 для изолированного dind.
  • deploy/backup.sh, deploy/deploy.sh, deploy/DEPLOY.md, deploy/WOODPECKER.md.