# 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. Сводка | Область | Статус | Что осталось | |---|---|---| | Роли, группы-когорты, статусы, история | partial | Финальные `NOT NULL`/полная уникальность когорты; группа не восстанавливается из архива; история членства не показана в UI | | Академический отпуск | **исправлено** | Вход read-only, сдача/защиты заблокированы на progress-service | | Черновики лабораторных | **исправлено** | Студент не видит черновик в списке/деталях/по UUID | | «Убрать из группы» | **исправлено** | Действие удалено из UI и API; остались перевод/академ/отчисление/архив | | Импорт v2 | partial | Есть флаг восстановления неактивных; row-level отчёт для DB-конфликтов неполный; legacy-группы с NULL-полями | | Курсы, преподаватели, группы курса | implemented | — | | Материалы и активы | implemented | Глобальные `STUDENTS`/`TEACHERS` теперь работают; импорт глобальных глав всё ещё создаёт только `ALL` | | Профиль студента | partial | Браузерный URL использует UUID вместо `courseNo`; часть полей контракта 09 отличается; нет лимита/пагинации попыток; нет SQL-теста «без N+1» | | Повышение | partial | Догон при старте ищет только текущий год, не предыдущий; нет теста apply-конфликта | | Аудит | partial | Добавлены `GROUP_CREATE`, `STUDENT_CREATE`, `GROUP_IMPORT`, `GROUP_ENROLL`; отдельных тестов на эти события пока нет | | Backup/deploy | partial | `deploy.sh` теперь проверяет последний backup; restore на staging не подтверждён; первый деплой без backup — документированное исключение | | CI | partial | Конфигурация Woodpecker и локальные прогоны есть; первый реальный pipeline на VM не подтверждён | | Производительность | partial | Keyset-аудит, пакетные лабы и группы курса есть; списки материалов/групп без пагинации; сдачи в ведомости по выбранной лабе | | LDD | implemented | 58 claims (27 P0 / 31 P1), 58/58 proven после release; `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. **Импорт: ошибки до записи.** - Занятый логин и неактивная целевая группа возвращают `applied=false` с номером строки и не создают частичных записей. - Тесты: `GroupServiceIntegrationTest#import reports occupied login...`, `#import reports inactive target group...`. ## 3. Остаточные отличия от цели ### P1 - **Профиль: контракт.** Браузерные маршруты используют UUID курса, а docs/09 обещает `courseNo`; часть полей `variant/level/grade` отличается от контракта. Нужно либо привести код к контракту, либо официально обновить контракт. - **Backup restore на staging** не подтверждён артефактом/логом. - **Первый реальный CI pipeline** на Woodpecker VM не подтверждён. ### Осознанные границы - **Автоматический догон повышения за предыдущий календарный год не делаем.** Планировщик не может безопасно восстановить историческое состояние групп: текущие `course` уже могут быть изменены. Догон в том же году после 31 августа работает; для прошлого года админ запускает dry-run с явным годом и проверяет план вручную. Это защищает от повторного повышения по устаревшему состоянию. ### P2 - **Импорт: гонка и XLSX-отчёт.** Preflight закрывает известные конфликты, но гонка с параллельной вставкой логина всё ещё даёт общий 409; XLSX-строки новых preflight-тестов не покрыты (CSV покрыт). - Когорта: поля всё ещё nullable, уникальность частичная; нужен preflight и финальный `SET NOT NULL`/полный unique index после ручного разбора legacy. - Нет восстановления группы из архива (сейчас архив односторонний). - Нет пагинации списков материалов и групп (аудит и профиль уже лучше). - Попытки сдачи в профиле не ограничены/не пагинированы. - Сдачи в ведомости запрашиваются по выбранной лабораторной, а не пакетно. - Нет визуальной регрессии и полного мобильного набора E2E. - Нет тестов на audit-события `GROUP_CREATE/STUDENT_CREATE/GROUP_IMPORT/GROUP_ENROLL`. - 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`, **58 claims** (27 P0 / 31 P1). - `./gradlew test ktlintCheck detekt evidenceCheck` — зелёный; P0 gaps = 0. - `./scripts/ldd-release.sh` — PASS: **58/58 claims**, 368 тестов, mutation `CourseAccessService` 75%, `PromotionPlanner` 69%, browser E2E 18/18, runtime/load p95 < 1 с. ## 6. Следующий порядок работ 1. Закрыть P1: row-level ошибки импорта, догон повышения, контракт профиля, staging restore, первый CI pipeline. 2. Закрыть P2: финальная миграция когорты, unarchive, пагинация, audit-тесты, визуальная регрессия. 3. Прогнать полный release и обновить `evidence/baseline-*`.