docs: handoff после закрытия продуктовых рисков

This commit is contained in:
2026-10-28 14:32:41 +03:00
parent 9dcba8ce0c
commit d45137220c
2 changed files with 292 additions and 0 deletions

291
docs/17-handoff.md Normal file
View File

@@ -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`.

View File

@@ -24,6 +24,7 @@ Preview Mermaid Support) и в IntelliJ IDEA (плагин Mermaid).
| [14-stage4-report.md](14-stage4-report.md) | Отчёт этапа 4: повышение, академотпуск, отчисление, архив, восстановление и удаление | | [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-файлы | | [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-матрица, чек-лист деплоя и остаточные ограничения | | [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/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 | | [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, миграции, доступ, статусы, повышение | | [ADR 0001](adr/0001-groups-courses-foundation.md) | Ключевые архитектурные решения: курс C, миграции, доступ, статусы, повышение |