- профиль приведён к docs/09: variant/level, updatedBy/name, лимит попыток - V14: NOT NULL и полная уникальность когорты, preflight legacy-групп - возврат группы из архива и история членства на странице группы - пагинация групп и плоского списка материалов - пакетные сдачи ведомости одним запросом - XLSX-preflight импорта и audit-тесты ростера - LDD: 66 claims, 398 тестов, release PASS
186 lines
14 KiB
Markdown
186 lines
14 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. Сводка
|
||
|
||
| Область | Статус | Что осталось |
|
||
|---|---|---|
|
||
| Роли, группы-когорты, статусы, история | 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`.
|