Files
LMS/docs/18-goal-audit.md
Vladislav Kopnin 82394cc688 feat: закрыть остаточные P1/P2 по профилю, когорте и ведомости
- профиль приведён к docs/09: variant/level, updatedBy/name, лимит попыток
- V14: NOT NULL и полная уникальность когорты, preflight legacy-групп
- возврат группы из архива и история членства на странице группы
- пагинация групп и плоского списка материалов
- пакетные сдачи ведомости одним запросом
- XLSX-preflight импорта и audit-тесты ростера
- LDD: 66 claims, 398 тестов, release PASS
2026-10-29 20:51:27 +03:00

186 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`.