19 KiB
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), cookieLMS_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), внутренний APIPOST /api/v1/internal/audit, админскийGET /api/v1/audit.- Пишутся доступы/изменения курсов, групп, материалов, лабораторных, оценки, публикации, защиты, профиль (staff), скачивания, lifecycle, promotion.
/admin/audit: фильтр по действию, keyset-курсор(occurred_at, id), «Показать ещё» и «В начало»; offsetpageостался для совместимости.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. - 51 claim: 25 P0 + 26 P1; 51/51 proven, P0 gaps = 0.
- 348 автотестов, 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
Последний подтверждённый результат:
348тестов, 0 failures;51/51claims;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.
-
Инвентаризация legacy-активов на реальном хранилище. Страница
/admin/legacy-assets, API иscripts/legacy-assets.shготовы. Нужно на production/staging получить список бесхозных файлов, привязать их к статьям или удалить. Старые внешние ссылки на такие файлы уже не работают у обычных пользователей. -
Первый реальный CI-прогон. Развернуть Woodpecker server и Docker-backend агент на отдельной VM по
deploy/WOODPECKER.md, включить репозиторий в Woodpecker и дождаться зелёных workflowcheck/smokeна push-MR иreleaseна теге. Шаги идут в nested dind; VM не имеет доступа к production и production-секретам. -
Визуальная регрессия и мобильный набор E2E. Сейчас 6 сценариев × 3 браузера, без screenshot-diff. Добавлять по мере роста UI.
-
Пакетные сдачи в ведомости. Детали лабораторных курса уже пакетные, но ведомость запрашивает сдачи по выбранной лабораторной отдельно. Оптимизировать, если появятся медленные курсы.
«Контингент»/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без пересчёта курсора. Offsetpage— только совместимость. 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. Если контекст потерян: порядок восстановления
git status --short --branch— ожидается чистое дерево наfeature/groups-courses.- Прочитать этот файл, затем
docs/16-stage6-report.md§7 иdocs/evidence/baseline-2026-10-27.md. ./gradlew test ktlintCheck detekt evidenceCheck— должно быть 348 тестов и 51/51 claims.- Для release-проверки:
./scripts/ldd-release.sh. - Для быстрой проверки:
./scripts/ldd-smoke.shи./scripts/ci-preflight.sh. - Продолжать 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.