Files
LMS/docs/17-handoff.md

292 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 17. Handoff: состояние проекта после закрытия продуктовых рисков
**Дата:** 2026-10-28.
**Ветка:** `feature/groups-courses` (54 коммита от master до этого документа).
**HEAD на момент написания:** `1ca507b` (`docs: закрытие продуктовых рисков и новый LDD baseline`).
**Пуш:** не выполнялся.
**Рабочее дерево:** чистое. `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) возвращает
список файлов без привязки.
- Старые прямые ссылки на бесхозные файлы у обычных пользователей ломаются
осознанно.
**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. Проверки: что и как запускать
```bash
# 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`.