154 lines
12 KiB
Markdown
154 lines
12 KiB
Markdown
# 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-*`.
|