fix: закрыть P0/P1 расхождения с целевой моделью

- академический отпуск: readOnly, запрет submit/start/finish защиты
- черновики лабораторных скрыты от студентов в списке/деталях/по UUID
- удалено действие «Убрать из группы» из UI и API
- импорт: restoreInactiveStatuses, skipped-отчёт, разблокировка архивного аккаунта
- глобальные материалы STUDENTS/TEACHERS с JWT-фильтрацией и сменой аудитории
- LDD-AUTH-005 переведён на unit-evidence, clean check проходит по P0
- backup: безопасный парсинг .env, verify-latest, кавычки cron
- robots/noindex, комментарии без автора, профиль без черновиков
- docs/18-goal-audit.md; baseline 57 claims / 366 тестов / release PASS
This commit is contained in:
2026-10-29 18:58:21 +03:00
parent 9cb4d0d263
commit 9412d87fed
63 changed files with 1281 additions and 162 deletions

141
docs/18-goal-audit.md Normal file
View File

@@ -0,0 +1,141 @@
# 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 | 57 claims (27 P0 / 30 P1), 57/57 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. **Профиль.**
- Старые комментарии без автора показывают «автор не указан».
- Студенческий профиль не показывает черновики лабораторных.
## 3. Остаточные отличия от цели
### P1
- **Импорт: ошибки БД.** Ошибки lookup/конфликта всё ещё могут выйти одним
flash-сообщением без номера строки; нужен row-level отчёт и для них.
- **Догон повышения.** `ensureScheduledDryRun` до 31 августа возвращает `null`
и не создаёт план за предыдущий год; после долгого простоя в январе
пропущенный август не догоняется.
- **Профиль: контракт.** Браузерные маршруты используют UUID курса, а docs/09
обещает `courseNo`; часть полей `variant/level/grade` отличается от контракта.
Нужно либо привести код к контракту, либо официально обновить контракт.
- **Backup restore на staging** не подтверждён артефактом/логом.
- **Первый реальный CI pipeline** на Woodpecker VM не подтверждён.
### P2
- Когорта: поля всё ещё 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`, **57 claims** (27 P0 / 30 P1).
- `./gradlew test ktlintCheck detekt evidenceCheck` — зелёный; P0 gaps = 0.
- `./scripts/ldd-release.sh` — PASS: **57/57 claims**, 366 тестов, 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-*`.