# 18. Сверка цели и фактического состояния **Дата:** 2026-10-29. **Ветка:** `feature/groups-courses`. **Источники цели:** `07-groups-courses.md`, `08-groups-courses-plan.md`, `09-teacher-student-profile.md`, `16-stage6-report.md`, `17-handoff.md`. Документ фиксирует, где приложение уже соответствует целевой модели, где остались отличия и какие решения приняты по спорным местам. Это не отчёт «всё готово»: статус `partial` означает реальный незакрытый пункт. ## 1. Сводка | Область | Статус | Что осталось | |---|---|---| | Роли, группы-когорты, статусы, история | implemented | Финальные ограничения когорты, возврат из архива и блок истории членства на странице группы | | Академический отпуск | **исправлено** | Вход read-only, сдача/защиты заблокированы на progress-service | | Черновики лабораторных | **исправлено** | Студент не видит черновик в списке/деталях/по UUID | | «Убрать из группы» | **исправлено** | Действие удалено из UI и API; остались перевод/академ/отчисление/архив | | Импорт v2 | partial | Флаг восстановления и row-level отчёт для CSV/XLSX есть; гонка с параллельной вставкой логина всё ещё даёт общий 409 | | Курсы, преподаватели, группы курса | implemented | — | | Материалы и активы | implemented | Глобальные `STUDENTS`/`TEACHERS` теперь работают; импорт глобальных глав всё ещё создаёт только `ALL` | | Профиль студента | implemented | Контракт 09 (`variant/level/updatedBy/name`), лимит попыток и SQL-тест «без N+1» сделаны | | Повышение | partial | Догон при старте ищет только текущий год, не предыдущий; нет теста apply-конфликта | | Аудит | implemented | `GROUP_CREATE/STUDENT_CREATE/GROUP_IMPORT/GROUP_ENROLL/GROUP_ARCHIVE/GROUP_RESTORE` проверены интеграционным тестом | | Backup/deploy | partial | `deploy.sh` теперь проверяет последний backup; restore на staging не подтверждён; первый деплой без backup — документированное исключение | | CI | partial | Конфигурация Woodpecker и локальные прогоны есть; первый реальный pipeline на VM не подтверждён | | Производительность | partial | Keyset-аудит, пакетные лабы/группы/сдачи, пагинация групп и плоского списка материалов, лимит попыток профиля; остаются visual regression и SLA-нагрузка | | LDD | implemented | 66 claims (27 P0 / 39 P1); `check` доказывает P0/unit, runtime P1 — release | | Legacy-активы | partial | Код и UI готовы; инвентаризация на production/staging не выполнена | | «Контингент» | out of scope | Исключён решением 2026-10-28 | ## 2. Что исправлено в этой итерации 1. **Read-only академотпуск.** - `AuthResponse`/`MeResponse` содержат `readOnly`; web-сессия хранит флаг. - `StudentWriteGuard` в progress-service блокирует submit, start и finish защиты. - UI скрывает сдачу/защиту и показывает объяснение. - Тесты: `StudentLifecycleIntegrationTest#academic leave login is read only`, `SubmissionServiceTest#academic leave student cannot submit`, `DefenseServiceAcademicLeaveTest#academic leave student cannot start or finish defense`. 2. **Черновики лабораторных скрыты от студентов.** - `CourseAccessService.requireLabAccess` отдаёт `404` студенту для черновика. - `GET /api/v1/labs` и `/labs/details` фильтруют черновики для студента. - Тесты: `LabDetailsIntegrationTest#student sees published labs but not drafts in list details or by id`, `CourseAccessServiceTest#student cannot access a draft lab of own course`. 3. **Удалено действие «Убрать из группы».** - Удалены endpoint, web-маршрут, API-клиент, кнопка и сервисный метод. - Тесты шаблонов проверяют отсутствие действия. 4. **Импорт больше не воскрешает неактивных студентов без флага.** - `restoreInactiveStatuses` (по умолчанию `false`), `ImportResult.skipped`. - Без флага сохраняются `EXPELLED/GRADUATED/ARCHIVED/ACADEMIC_LEAVE`, строка попадает в отчёт. - С флагом студент активируется, архивный аккаунт разблокируется, пишется `IMPORT`. - Тесты: `GroupServiceIntegrationTest#import does not resurrect...`, `#import restores expelled student...`, `#import preserves academic leave...`, `#import still updates an active student`, шаблонные тесты отчёта. 5. **Глобальные материалы `STUDENTS`/`TEACHERS`.** - Дерево и статьи фильтруются по роли JWT; аноним — только `ALL`. - Админ выбирает аудиторию при создании и может изменить её. - Тесты: `GlobalAudienceAccessIntegrationTest` (5 сценариев). 6. **LDD gate на чистом checkout.** - `LDD-AUTH-005` теперь P0 с unit-тестом gateway; полный refresh/handshake вынесен в P1 `LDD-AUTH-008` (runtime). - `evidenceCheck` без `build/evidence` проходит по P0; runtime P1 закрываются в release. 7. **Backup/env.** - `LMS_REFRESH_TOKEN_CLEANUP_CRON` в примерах закавычен. - `backup.sh` больше не `source`-ит `.env.prod` (не исполняет cron как команду). - `deploy.sh up` после backup вызывает `verify-latest`. 8. **Robots/noindex.** - `robots.txt`; `X-Robots-Tag: noindex, nofollow` для admin/teacher/student/login/api/курсовых материалов. 9. **Профиль.** - Старые комментарии без автора показывают «автор не указан». - Студенческий профиль не показывает черновики лабораторных. 10. **Браузерные URL курсов — `courseNo`.** Страницы преподавателя, админа, материалы и профиль студента принимают человеческий номер курса; REST API продолжает работать с UUID. 11. **Импорт: ошибки до записи.** - Занятый логин и неактивная целевая группа возвращают `applied=false` с номером строки и не создают частичных записей. - Тесты: `GroupServiceIntegrationTest#import reports occupied login...`, `#import reports inactive target group...`, XLSX-варианты `#xlsx import reports occupied login before writing()`, `#xlsx import reports inactive target group before writing()`. 12. **Профиль приведён к контракту 09.** - `variant.id`, `level.id/code`, `grade.updatedBy`, `author.name`. - ФИО автора берётся из identity (fallback — логин). - `submissionLimit` (по умолчанию 50) и `submissionsTotal`; `attemptsCount` остаётся полным. - SQL-тест: 10 лабораторных — не более 15 запросов. - Тесты: `StudentProfileIntegrationTest`, `ProfileDtoJsonTest`, `StudentProfileTemplateTest`, `InternalControllerIntegrationTest`. 13. **Финальные ограничения когорты.** - `V14__cohort_constraints.sql`: `NOT NULL` для 5 полей, полный unique index, строгие check-констрейнты; legacy-строки блокируют миграцию с явной ошибкой. - `scripts/ldd-migration-upgrade.sh` проверяет `cohort_not_null=5` и `cohort_unique_full=1` на пути master→branch. 14. **Возврат группы из архива.** - `POST /api/v1/groups/{id}/restore`, кнопка на странице группы, аудит `GROUP_RESTORE`; повторный возврат активной группы — 400. 15. **Пагинация и пакетные сдачи.** - `GET /api/v1/groups?page=&limit=` и страница `/admin/groups`. - `GET /api/v1/admin/content/articles?offset=&limit=`. - `SubmissionService.latestPerStudentByLab` — один запрос на все лабы ведомости вместо запроса на каждую. 16. **Аудит ростера.** - Интеграционный тест проверяет шесть событий и актора. 17. **История членства на странице группы.** - `GET /api/v1/groups/{id}/history` отдаёт студента, действие, откуда/куда и причину; шаблон показывает последние 200 событий в МСК. - Тесты: `GroupServiceIntegrationTest`, `AdminStage4TemplateTest`. ## 3. Остаточные отличия от цели ### P1 - **Backup restore на staging** не подтверждён артефактом/логом. - **Первый реальный CI pipeline** на Woodpecker VM не подтверждён. ### Осознанные границы - **Автоматический догон повышения за предыдущий календарный год не делаем.** Планировщик не может безопасно восстановить историческое состояние групп: текущие `course` уже могут быть изменены. Догон в том же году после 31 августа работает; для прошлого года админ запускает dry-run с явным годом и проверяет план вручную. Это защищает от повторного повышения по устаревшему состоянию. ### P2 - **Импорт: гонка.** Preflight закрывает известные конфликты, но гонка с параллельной вставкой логина всё ещё даёт общий 409 вместо строки отчёта. - **Визуальная регрессия.** Playwright проверяет сценарии и мобильный viewport, но screenshot-diff нет. - Mutation testing не 100%, нагрузочный smoke не SLA. ### Операционные - Инвентаризация legacy-активов на реальном хранилище. - Установка/проверка daily backup timer на сервере. - Первый push-MR и tag pipeline в Woodpecker. - Restore на staging. ## 4. Принятые решения - `ACADEMIC_LEAVE`: блокировать только сдачу и защиты; чтение и скачивание своих файлов разрешены. - «Убрать из группы»: действие удалено полностью. - Импорт терминальных/академических статусов: только по явному флагу `restoreInactiveStatuses`. - Глобальные материалы: `ALL/STUDENTS/TEACHERS` поддерживаются; `STUDENTS` видит студент, `TEACHERS` — преподаватель, аноним — только `ALL`. - «Контингент» остаётся вне scope. ## 5. LDD - Реестр: `docs/evidence/claims.json`, **66 claims** (27 P0 / 39 P1). - `./gradlew test ktlintCheck detekt evidenceCheck` — зелёный; P0 gaps = 0. - `./scripts/ldd-release.sh` — PASS: **66/66 claims**, 398 тестов, mutation `CourseAccessService` 75%, `PromotionPlanner` 69%, browser E2E 18/18, runtime/load p95 < 1 с. ## 6. Следующий порядок работ 1. Остались операционные P1: restore на staging и первый реальный Woodpecker pipeline. 2. P2: гонка импорта, визуальная регрессия, повышение mutation-покрытия и SLA-нагрузка. 3. Перед production: ручной разбор legacy-когорт по SQL из `deploy/DEPLOY.md`, затем backup и деплой. 4. После каждого изменения прогонять `./scripts/ldd-release.sh` и обновлять `docs/evidence/baseline-2026-10-29.md`.