diff --git a/docs/17-handoff.md b/docs/17-handoff.md new file mode 100644 index 0000000..6dde71f --- /dev/null +++ b/docs/17-handoff.md @@ -0,0 +1,291 @@ +# 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`. diff --git a/docs/README.md b/docs/README.md index bf07d9f..0ec952d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,6 +24,7 @@ Preview Mermaid Support) и в IntelliJ IDEA (плагин Mermaid). | [14-stage4-report.md](14-stage4-report.md) | Отчёт этапа 4: повышение, академотпуск, отчисление, архив, восстановление и удаление | | [15-stage5-course-materials.md](15-stage5-course-materials.md) | Отчёт этапа 5: курсовые материалы, публичные `ALL`-главы, видимость активов и secure-by-default legacy-файлы | | [16-stage6-report.md](16-stage6-report.md) | Отчёт этапа 6: права преподавателя на запись, аудит, CI, upgrade-тест миграций, security-матрица, чек-лист деплоя и остаточные ограничения | +| [17-handoff.md](17-handoff.md) | Handoff: что уже внесено, что осталось, как восстановить контекст и прогнать проверки | | [evidence/README.md](evidence/README.md) | LDD: реестр утверждений, evidence gate, runtime- и migration-проверки | | [evidence/baseline-2026-10-27.md](evidence/baseline-2026-10-27.md) | LDD-baseline: 46 claims, 319 тестов, upgrade, runtime/scale, mutation и browser E2E | | [ADR 0001](adr/0001-groups-courses-foundation.md) | Ключевые архитектурные решения: курс C, миграции, доступ, статусы, повышение |