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

14 KiB
Raw Blame History

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.