feat: закрыть остаточные P1/P2 по профилю, когорте и ведомости
- профиль приведён к docs/09: variant/level, updatedBy/name, лимит попыток - V14: NOT NULL и полная уникальность когорты, preflight legacy-групп - возврат группы из архива и история членства на странице группы - пагинация групп и плоского списка материалов - пакетные сдачи ведомости одним запросом - XLSX-preflight импорта и audit-тесты ростера - LDD: 66 claims, 398 тестов, release PASS
This commit is contained in:
@@ -211,11 +211,11 @@
|
||||
|
||||
Номера уточняются при реализации, чтобы не конфликтовать с максимумами master
|
||||
(identity V3, **academic V5**, progress V12). Максимумы этой ветки:
|
||||
identity V13, **academic V12**, progress V13.
|
||||
identity V14, **academic V12**, progress V13.
|
||||
|
||||
| Сервис | Миграции | Содержание |
|
||||
|---|---|---|
|
||||
| identity | V4–V13 | роли, поля групп, статусы студентов, `membership_history`, `promotion_runs/items`, `accounts.status`; `V10` — `audit_events`; `V11` — `refresh_tokens` (хеш, срок, отзыв, цепочка замен); `V12` — `absolute_expires_at`; `V13` — keyset-индексы аудита |
|
||||
| identity | V4–V14 | роли, поля групп, статусы студентов, `membership_history`, `promotion_runs/items`, `accounts.status`; `V10` — `audit_events`; `V11` — `refresh_tokens` (хеш, срок, отзыв, цепочка замен); `V12` — `absolute_expires_at`; `V13` — keyset-индексы аудита; `V14` — NOT NULL/уникальность когорты |
|
||||
| academic | V6–V12 + post-bootstrap step | курсы, преподаватели, группы курса, `labs.course_id` (nullable), материалы курса/аудитории, частичные индексы, активы; `V10` — уникальный индекс `lower(title)` по курсам; `V11` — backfill `audience='ALL'` для глобальных глав; `V12` — backfill связей `article_assets` из markdown-ссылок; `SET NOT NULL` — post-bootstrap шаг вне Flyway |
|
||||
| progress | V13 | `grade_comments.author_account_id`, `lab_grades.updated_by_account_id`, индекс `lab_grades(lab_id)`; аудит реализован в identity `V10` |
|
||||
|
||||
@@ -336,24 +336,29 @@ identity V13, **academic V12**, progress V13.
|
||||
- **Этап 6 — выполнен.** Права преподавателя на запись (лабораторные,
|
||||
ведомость, группы, материалы), центральный аудит `identity.audit_events`
|
||||
с `/admin/audit`, CI merge gate и полный hardening. Проверены прогон
|
||||
тестов/линтеров (**385**, 0 failures), upgrade миграций с master-схемы
|
||||
(identity `V4–V13`, academic `V6–V12`, progress `V13`), security-матрица,
|
||||
gateway, видимость активов и deploy-runbook. LDD baseline: **59 claims
|
||||
(27 P0 / 32 P1), P0 gaps = 0**, browser E2E 18/18 (3 браузера). Отчёт:
|
||||
тестов/линтеров (**398**, 0 failures), upgrade миграций с master-схемы
|
||||
(identity `V4–V14`, academic `V6–V12`, progress `V13`), security-матрица,
|
||||
gateway, видимость активов и deploy-runbook. LDD baseline: **66 claims
|
||||
(27 P0 / 39 P1), P0 gaps = 0**, browser E2E 18/18 (3 браузера). Отчёт:
|
||||
[16-stage6-report.md](16-stage6-report.md).
|
||||
|
||||
### 9.1. Известные пробелы после этапа 6
|
||||
|
||||
- **Импорт.** Старый формат не поддерживается, принимается только шаблон v2.
|
||||
Номер группы — числовой; отдельного требования к ведущим нулям нет.
|
||||
CSV/XLSX preflight покрыт тестами; гонка с параллельной вставкой логина
|
||||
всё ещё может дать общий 409.
|
||||
- **Пагинация и N+1.** Аудит продолжается keyset-курсором `(occurred_at, id)`
|
||||
с offset-fallback для старых ссылок; `GET /api/v1/labs/details` отдаёт все
|
||||
лабораторные курса с уровнями и вариантами одним запросом; группы курса —
|
||||
одним вызовом. Ведомость по-прежнему запрашивает сдачи по выбранной
|
||||
лабораторной — это не N+1 по студентам.
|
||||
с offset-fallback; `GET /api/v1/labs/details` отдаёт все лабораторные курса
|
||||
одним запросом; группы курса — одним вызовом. Сдачи ведомости собираются
|
||||
одним запросом на все выбранные лабы; группы и плоский список материалов
|
||||
поддерживают `limit`/`offset`. Страница группы и профиль имеют собственные
|
||||
лимиты; дерево материалов по-прежнему загружается целиком для навигации.
|
||||
- **Legacy-активы.** Файлы без статьи уже не публичны; админская страница
|
||||
`/admin/legacy-assets`, API и `scripts/legacy-assets.sh` готовы. Остаётся
|
||||
прогнать инвентаризацию на production/staging и привязать или удалить файлы.
|
||||
- **Визуальная регрессия.** Playwright покрывает сценарии и mobile, но
|
||||
screenshot-diff отсутствует.
|
||||
- **Сессии.** Web-сессия скользящая 7 дней, абсолютный потолок 30 дней
|
||||
(`LMS_SESSION_ABSOLUTE_TIMEOUT`); истёкшие refresh-токены чистит ночная
|
||||
задача (`LMS_REFRESH_TOKEN_RETENTION`).
|
||||
@@ -378,7 +383,7 @@ identity V13, **academic V12**, progress V13.
|
||||
- Access/refresh-токены и web-сессия работают по срокам 15 минут / 7 дней /
|
||||
7 дней sliding с абсолютным потолком 30 дней; logout, смена пароля и
|
||||
закрытие доступа отзывают refresh.
|
||||
- Security-матрица и миграционный upgrade-тест зелёные (identity до `V13`).
|
||||
- Security-матрица и миграционный upgrade-тест зелёные (identity до `V14`).
|
||||
- CI (Gitea + Woodpecker) на каждом push/MR прогоняет
|
||||
`test ktlintCheck detekt evidenceCheck` и smoke полного стека; полный
|
||||
`release` обязателен для тегов.
|
||||
|
||||
@@ -151,7 +151,9 @@ Authorization: Bearer <JWT>
|
||||
`grade.generalComment`; отдельные комментарии — массив `comments`.
|
||||
- `author` может быть `null`, если автор не сохранён (старые данные).
|
||||
- `averageScore` — среднее по выставленным оценкам, `null` если оценок нет.
|
||||
- `submissions` отсортированы по `seq`; файлы — имена без пути.
|
||||
- `submissions` — последние попытки по убыванию `seq`, по умолчанию не более
|
||||
50 (`submissionLimit`, максимум 500); `submissionsTotal` — всего попыток,
|
||||
`attemptsCount` — тоже полное число. Файлы — имена без пути.
|
||||
- `downloadUrl` относительный; web-BFF добавляет авторизацию.
|
||||
|
||||
## 4. Скачивание попытки
|
||||
@@ -244,7 +246,9 @@ GET /api/v1/internal/courses/{courseId}/labs
|
||||
- защиты — `defense_sessions` по `student_id` и `lab_id IN (...)`;
|
||||
- публикации — `grade_publications`/`grade_publication_students` по группе и лабам.
|
||||
- Список лаб курса кэшируется на время запроса.
|
||||
- Для больших курсов — пагинация попыток (по умолчанию все, но с лимитом).
|
||||
- Для больших курсов — лимит попыток: `submissionLimit` (по умолчанию 50,
|
||||
максимум 500) и `submissionsTotal` для индикатора «показаны последние N»;
|
||||
`attemptsCount` не зависит от лимита.
|
||||
- Индексы: `submissions(student_id, lab_id, seq)` уже уникален;
|
||||
`grade_comments(student_id, lab_id)` уже есть; `lab_grades(lab_id)` добавлен;
|
||||
`defense_sessions(student_id, lab_id)` уникален.
|
||||
@@ -273,7 +277,9 @@ GET /api/v1/internal/courses/{courseId}/labs
|
||||
- Аноним — `401`.
|
||||
- Скачивание своей попытки — `200`; чужой — `403`.
|
||||
- Студент не видит неопубликованные оценки/комментарии.
|
||||
- Все попытки возвращаются, а не только последняя.
|
||||
- Все попытки возвращаются (в пределах `submissionLimit`), а не только последняя;
|
||||
`submissionsTotal`/`attemptsCount` показывают полное число.
|
||||
- Профиль на 10 лабораторных делает не более 15 SQL-запросов (без N+1).
|
||||
- Комментарии содержат автора или `null`.
|
||||
- Запрос не делает N+1 (проверка по числу SQL-запросов в интеграционном тесте).
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
| markdown | 5 | 0 | 0 | 0 |
|
||||
| **Итого на момент этапа 6** | **385** | **0** | **0** | **0** |
|
||||
|
||||
После закрытия рисков актуальный LDD-baseline — **385 тестов, 0 failures**
|
||||
На момент этапа 6 LDD-baseline — **385 тестов, 0 failures**
|
||||
(см. раздел 7). `ktlintCheck` и `detekt` прошли без замечаний. У gateway теперь есть unit-тесты публичных auth-путей и 401; маршрутизация
|
||||
gateway дополнительно проверена конфигурацией и security-матрицей ниже.
|
||||
Тот же набор команд выполняет `.woodpecker/` (Gitea + Woodpecker 3.x); на
|
||||
@@ -133,7 +133,7 @@ gateway дополнительно проверена конфигурацией
|
||||
|
||||
### 2.3. Security-проверки
|
||||
|
||||
Проверено автоматическими тестами (актуальный прогон — 385 тестов, 0 failures)
|
||||
Проверено автоматическими тестами (прогон этапа 6 — 385 тестов, 0 failures)
|
||||
и разбором конфигурации gateway. Запись преподавателя дополнительно покрыта
|
||||
negative-тестами «свой/чужой курс».
|
||||
|
||||
@@ -347,7 +347,7 @@ web-сессия с отзывом и абсолютным потолком, key
|
||||
## 7. LDD baseline
|
||||
|
||||
После этапа 6 и закрытия рисков текущая функциональность проверена через
|
||||
LDD-контур: реестр `docs/evidence/claims.json` (**59 claims, 27 P0 / 32 P1**),
|
||||
LDD-контур на момент этапа 6: реестр `docs/evidence/claims.json` (**59 claims, 27 P0 / 32 P1**),
|
||||
gate `./gradlew evidenceCheck`, upgrade-скрипт, runtime-smoke, smoke-workflow,
|
||||
mutation testing и браузерный Playwright E2E. Результат:
|
||||
**59/59 claims proven, P0 gaps = 0, 385 тестов, 0 failures**; mutation strength
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 17. Handoff: состояние проекта после закрытия продуктовых рисков
|
||||
|
||||
**Дата:** 2026-10-28.
|
||||
**Дата:** 2026-10-29.
|
||||
**Ветка:** `feature/groups-courses`.
|
||||
**История:** `git log --oneline`; последние изменения — Woodpecker CI и
|
||||
изоляция тестовой среды на отдельной VM.
|
||||
@@ -20,8 +20,10 @@
|
||||
|
||||
- Роли `ADMIN`, `TEACHER`, `STUDENT`.
|
||||
- Группы-когорты: `stream`, `course`, `number`, `degree`, `enrollment_year`,
|
||||
`status`; история членства `membership_history`.
|
||||
- Импорт групп Excel v2, перевод/академ/отчисление/выпуск/архив/восстановление.
|
||||
`status`; история членства `membership_history`. `V14` делает поля когорты
|
||||
`NOT NULL` и уникальность полной; архивная группа возвращается из архива.
|
||||
- Импорт групп Excel v2 (CSV/XLSX с row-level preflight), перевод/академ/
|
||||
отчисление/выпуск/архив/восстановление.
|
||||
- Удаление студента с усиленным подтверждением (`login` или ФИО).
|
||||
- Вход закрыт для отчисленных, выпущенных, архивных студентов и архивных
|
||||
аккаунтов; архивные/выпущенные группы также блокируют вход.
|
||||
@@ -116,8 +118,8 @@
|
||||
### 1.8. LDD и проверки
|
||||
|
||||
- Реестр: `docs/evidence/claims.json`.
|
||||
- **59 claims**: 27 P0 + 32 P1; **59/59 proven, P0 gaps = 0**.
|
||||
- **385 автотестов**, 0 failures/errors/skipped.
|
||||
- **66 claims**: 27 P0 + 39 P1; **66/66 proven, P0 gaps = 0**.
|
||||
- **398 автотестов**, 0 failures/errors/skipped.
|
||||
- Mutation testing:
|
||||
- `CourseAccessService` — 75% test strength;
|
||||
- `PromotionPlanner` — 69%.
|
||||
@@ -143,7 +145,7 @@
|
||||
|
||||
| Сервис | Диапазон | Что важно |
|
||||
|---|---|---|
|
||||
| identity | `V4–V13` | роли, группы, статусы, promotion, история, аудит (`V10`), refresh (`V11`), absolute (`V12`), keyset-индексы (`V13`) |
|
||||
| identity | `V4–V14` | роли, группы, статусы, promotion, история, аудит (`V10`), refresh (`V11`), absolute (`V12`), keyset-индексы (`V13`), финальные ограничения когорты (`V14`) |
|
||||
| academic | `V6–V12` | курсы, `labs.course_id`, главы/аудитории, индексы, `article_assets`, backfill |
|
||||
| progress | `V13` | автор комментария/оценки; V13 идемпотентна |
|
||||
|
||||
@@ -153,6 +155,8 @@
|
||||
`deploy/preflight/progress-v10-v13.sql`, объекты не удалять.
|
||||
- Production/master: progress V11/V12 уже есть, затем V13.
|
||||
- `labs.course_id NOT NULL` — post-bootstrap, не Flyway.
|
||||
- `V14` намеренно падает на legacy-группах с NULL-когортой; SQL для проверки —
|
||||
в `deploy/DEPLOY.md`.
|
||||
|
||||
### 1.10. Деплой и backup
|
||||
|
||||
@@ -194,8 +198,8 @@ cat build/reports/evidence/index.md
|
||||
```
|
||||
|
||||
Последний подтверждённый результат:
|
||||
- `385` тестов, 0 failures;
|
||||
- `59/59` claims, P0 gaps = 0;
|
||||
- `398` тестов, 0 failures;
|
||||
- `66/66` claims, P0 gaps = 0;
|
||||
- `ldd-release.sh` — PASS;
|
||||
- smoke — PASS;
|
||||
- browser E2E — 18/18 (Chromium/Firefox/WebKit);
|
||||
@@ -225,10 +229,12 @@ cat build/reports/evidence/index.md
|
||||
Сейчас 6 сценариев × 3 браузера, без screenshot-diff. Добавлять по мере
|
||||
роста UI.
|
||||
|
||||
4. **Пакетные сдачи в ведомости.**
|
||||
Детали лабораторных курса уже пакетные, но ведомость запрашивает сдачи по
|
||||
выбранной лабораторной отдельно. Оптимизировать, если появятся медленные
|
||||
курсы.
|
||||
4. **Гонка импорта.**
|
||||
Preflight закрывает известные конфликты, но параллельная вставка логина
|
||||
всё ещё даёт общий 409.
|
||||
|
||||
5. **Restore на staging.**
|
||||
Backup/restore-скрипты готовы; нужен реальный прогон на стенде с логом.
|
||||
|
||||
«Контингент»/`next.uniyar.ac.ru` **исключён из плана** решением от 2026-10-28:
|
||||
нет API-спецификации и доступа к внешней системе. При появлении нового ТЗ
|
||||
@@ -274,9 +280,9 @@ cat build/reports/evidence/index.md
|
||||
1. `git status --short --branch` — ожидается чистое дерево на
|
||||
`feature/groups-courses`.
|
||||
2. Прочитать этот файл, затем `docs/16-stage6-report.md` §7 и
|
||||
`docs/evidence/baseline-2026-10-27.md`.
|
||||
3. `./gradlew test ktlintCheck detekt evidenceCheck` — должно быть 385 тестов
|
||||
и 59 claims, 59/59 proven.
|
||||
`docs/evidence/baseline-2026-10-29.md`.
|
||||
3. `./gradlew test ktlintCheck detekt evidenceCheck` — должно быть 398 тестов
|
||||
и 66 claims, 66/66 proven.
|
||||
4. Для release-проверки: `./scripts/ldd-release.sh`.
|
||||
5. Для быстрой проверки: `./scripts/ldd-smoke.sh` и `./scripts/ci-preflight.sh`.
|
||||
6. Продолжать backlog из §3, начиная с инвентаризации legacy-активов и
|
||||
@@ -288,16 +294,20 @@ cat build/reports/evidence/index.md
|
||||
- `docs/08-groups-courses-plan.md` — план и статус.
|
||||
- `docs/16-stage6-report.md` — итоговый отчёт, deploy и ограничения.
|
||||
- `docs/evidence/claims.json` — LDD-реестр.
|
||||
- `docs/evidence/baseline-2026-10-27.md` — фактический baseline.
|
||||
- `docs/evidence/baseline-2026-10-29.md` — фактический baseline.
|
||||
- `services/identity-service/.../auth/refresh/` — refresh-токены, absolute,
|
||||
cleanup.
|
||||
- `services/identity-service/.../db/migration/V11__refresh_tokens.sql`,
|
||||
`V12__refresh_absolute.sql`, `V13__audit_keyset_indexes.sql`.
|
||||
`V12__refresh_absolute.sql`, `V13__audit_keyset_indexes.sql`,
|
||||
`V14__cohort_constraints.sql`.
|
||||
- `services/identity-service/.../audit/` — аудит и keyset-курсор.
|
||||
- `services/academic-service/.../content/` — материалы/активы.
|
||||
- `services/academic-service/.../course/CourseGroupService.kt` — bulk-группы.
|
||||
- `services/academic-service/.../lab/LabService.kt` — `details(courseId)`.
|
||||
- `services/progress-service/.../AcademicClient.kt` — пакетный вызов лаб.
|
||||
- `services/progress-service/.../SubmissionService.kt` — пакетные сдачи
|
||||
ведомости; `profile/StudentProfileService.kt` — контракт профиля и лимит
|
||||
попыток.
|
||||
- `services/progress-service/.../CourseScopeService.kt` — course-scope.
|
||||
- `web/.../session/SessionTokenService.kt`, `SessionLifetime.kt`,
|
||||
`SessionCookieRefreshFilter.kt`.
|
||||
|
||||
@@ -13,20 +13,20 @@
|
||||
|
||||
| Область | Статус | Что осталось |
|
||||
|---|---|---|
|
||||
| Роли, группы-когорты, статусы, история | partial | Финальные `NOT NULL`/полная уникальность когорты; группа не восстанавливается из архива; история членства не показана в UI |
|
||||
| Роли, группы-когорты, статусы, история | implemented | Финальные ограничения когорты, возврат из архива и блок истории членства на странице группы |
|
||||
| Академический отпуск | **исправлено** | Вход read-only, сдача/защиты заблокированы на progress-service |
|
||||
| Черновики лабораторных | **исправлено** | Студент не видит черновик в списке/деталях/по UUID |
|
||||
| «Убрать из группы» | **исправлено** | Действие удалено из UI и API; остались перевод/академ/отчисление/архив |
|
||||
| Импорт v2 | partial | Есть флаг восстановления неактивных; row-level отчёт для DB-конфликтов неполный; legacy-группы с NULL-полями |
|
||||
| Импорт v2 | partial | Флаг восстановления и row-level отчёт для CSV/XLSX есть; гонка с параллельной вставкой логина всё ещё даёт общий 409 |
|
||||
| Курсы, преподаватели, группы курса | implemented | — |
|
||||
| Материалы и активы | implemented | Глобальные `STUDENTS`/`TEACHERS` теперь работают; импорт глобальных глав всё ещё создаёт только `ALL` |
|
||||
| Профиль студента | partial | URL уже `courseNo`; часть полей контракта 09 отличается; нет лимита/пагинации попыток; нет SQL-теста «без N+1» |
|
||||
| Профиль студента | implemented | Контракт 09 (`variant/level/updatedBy/name`), лимит попыток и SQL-тест «без N+1» сделаны |
|
||||
| Повышение | partial | Догон при старте ищет только текущий год, не предыдущий; нет теста apply-конфликта |
|
||||
| Аудит | partial | Добавлены `GROUP_CREATE`, `STUDENT_CREATE`, `GROUP_IMPORT`, `GROUP_ENROLL`; отдельных тестов на эти события пока нет |
|
||||
| Аудит | 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-аудит, пакетные лабы и группы курса есть; списки материалов/групп без пагинации; сдачи в ведомости по выбранной лабе |
|
||||
| LDD | implemented | 59 claims (27 P0 / 32 P1), 59/59 proven после release; `check` доказывает P0/unit, runtime P1 — release |
|
||||
| Производительность | 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 |
|
||||
|
||||
@@ -87,15 +87,47 @@
|
||||
- Занятый логин и неактивная целевая группа возвращают `applied=false`
|
||||
с номером строки и не создают частичных записей.
|
||||
- Тесты: `GroupServiceIntegrationTest#import reports occupied login...`,
|
||||
`#import reports inactive target group...`.
|
||||
`#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
|
||||
|
||||
- **Профиль: поля контракта.** URL уже используют `courseNo`, но часть полей
|
||||
`variant/level/grade` и авторы комментариев отличаются от docs/09; нужен
|
||||
либо код к контракту, либо официальное обновление контракта.
|
||||
- **Backup restore на staging** не подтверждён артефактом/логом.
|
||||
- **Первый реальный CI pipeline** на Woodpecker VM не подтверждён.
|
||||
|
||||
@@ -109,17 +141,10 @@
|
||||
|
||||
### 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`.
|
||||
- **Импорт: гонка.** Preflight закрывает известные конфликты, но гонка с
|
||||
параллельной вставкой логина всё ещё даёт общий 409 вместо строки отчёта.
|
||||
- **Визуальная регрессия.** Playwright проверяет сценарии и мобильный viewport,
|
||||
но screenshot-diff нет.
|
||||
- Mutation testing не 100%, нагрузочный smoke не SLA.
|
||||
|
||||
### Операционные
|
||||
@@ -142,16 +167,19 @@
|
||||
|
||||
## 5. LDD
|
||||
|
||||
- Реестр: `docs/evidence/claims.json`, **59 claims** (27 P0 / 32 P1).
|
||||
- Реестр: `docs/evidence/claims.json`, **66 claims** (27 P0 / 39 P1).
|
||||
- `./gradlew test ktlintCheck detekt evidenceCheck` — зелёный; P0 gaps = 0.
|
||||
- `./scripts/ldd-release.sh` — PASS: **59/59 claims**, 385 тестов, mutation
|
||||
- `./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: row-level ошибки импорта, догон повышения, контракт профиля,
|
||||
staging restore, первый CI pipeline.
|
||||
2. Закрыть P2: финальная миграция когорты, unarchive, пагинация, audit-тесты,
|
||||
визуальная регрессия.
|
||||
3. Прогнать полный release и обновить `evidence/baseline-*`.
|
||||
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`.
|
||||
|
||||
@@ -27,7 +27,7 @@ Preview Mermaid Support) и в IntelliJ IDEA (плагин Mermaid).
|
||||
| [17-handoff.md](17-handoff.md) | Handoff: что уже внесено, что осталось, как восстановить контекст и прогнать проверки |
|
||||
| [18-goal-audit.md](18-goal-audit.md) | Сверка целевой модели из docs/07–09 с фактическим кодом: статусы, остаточные отличия, решения |
|
||||
| [evidence/README.md](evidence/README.md) | LDD: реестр утверждений, evidence gate, runtime- и migration-проверки |
|
||||
| [evidence/baseline-2026-10-27.md](evidence/baseline-2026-10-27.md) | LDD-baseline: 59 claims, 385 тестов, upgrade, runtime/scale, mutation и browser E2E |
|
||||
| [evidence/baseline-2026-10-29.md](evidence/baseline-2026-10-29.md) | LDD-baseline: 66 claims, 398 тестов, upgrade, runtime/scale, mutation и browser E2E |
|
||||
| [ADR 0001](adr/0001-groups-courses-foundation.md) | Ключевые архитектурные решения: курс C, миграции, доступ, статусы, повышение |
|
||||
|
||||
## Кратко о системе
|
||||
|
||||
@@ -87,10 +87,10 @@ Platform 6 из Spring Boot 4.
|
||||
LDD-циклы выполнены 2026-10-27/28; последний — после закрытия продуктовых
|
||||
рисков:
|
||||
|
||||
- **385 автоматических тестов**, 0 failures/errors;
|
||||
- **59 claims**: 27 P0 + 32 P1, **59/59 proven, P0 gaps = 0**. P0 — доступы,
|
||||
- **398 автоматических тестов**, 0 failures/errors;
|
||||
- **66 claims**: 27 P0 + 39 P1, **66/66 proven, P0 gaps = 0**. P0 — доступы,
|
||||
повышение, жизненный цикл, профиль/скачивание, материалы/активы
|
||||
(включая secure-by-default legacy-файлы), миграции V11–V13, аудит,
|
||||
(включая secure-by-default legacy-файлы), миграции V11–V14, аудит,
|
||||
refresh-токены и их отзыв; P1 — группы, авторство, UI, контракты, импорт,
|
||||
копирование лаб, keyset-пагинация, пакетные детали лабораторных,
|
||||
абсолютный потолок сессии, runtime, mutation и browser E2E;
|
||||
@@ -114,4 +114,4 @@ Kotlin-модуль, поэтому DTO с default-значениями треб
|
||||
реальных runner'ах и отсутствие визуальной регрессии. Legacy-активы, срок
|
||||
access-токена, пагинация, N+1 деталей лабораторных, абсолютный лимит сессии и
|
||||
«ведущие нули» больше не являются открытыми рисками.
|
||||
Результаты — в `baseline-2026-10-27.md`.
|
||||
Результаты — в `baseline-2026-10-29.md`; предыдущий срез — `baseline-2026-10-27.md`.
|
||||
|
||||
44
docs/evidence/baseline-2026-10-29.md
Normal file
44
docs/evidence/baseline-2026-10-29.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# LDD baseline: 2026-10-29
|
||||
|
||||
Актуальный прогон после закрытия P1/P2-остатка: контракт профиля, финальные
|
||||
ограничения когорты, возврат группы из архива, пагинация, пакетные сдачи,
|
||||
row-level XLSX-импорт и audit-события ростера.
|
||||
|
||||
## Результат
|
||||
|
||||
| Проверка | Результат |
|
||||
|---|---|
|
||||
| JUnit/интеграционные тесты | **398**, 0 failures/errors/skipped |
|
||||
| ktlint + detekt | зелёные |
|
||||
| Claims | **66** (27 P0 / 39 P1), **66/66 proven**, P0 gaps = 0 |
|
||||
| Migration master→branch | PASS; identity `V4–V14`, повторные V13/V14 |
|
||||
| Mutation `CourseAccessService` | 75% test strength (порог 70%) |
|
||||
| Mutation `PromotionPlanner` | 69% test strength (порог 60%) |
|
||||
| Browser E2E | 18/18 (Chromium/Firefox/WebKit) |
|
||||
| Load smoke | 125 запросов, 0 ошибок, p95 0.942 с (порог < 1 с) |
|
||||
|
||||
## Что добавлено в этой итерации
|
||||
|
||||
- профиль соответствует `docs/09`: `variant.id`, `level.id/code`,
|
||||
`grade.updatedBy`, `author.name`; `submissionLimit`/`submissionsTotal` и
|
||||
SQL-тест «без N+1» на 10 лабораторных;
|
||||
- `V14__cohort_constraints.sql`: `NOT NULL` для пяти полей когорты, полный
|
||||
unique index и строгие check-ограничения; legacy-строки останавливают
|
||||
миграцию с явной ошибкой;
|
||||
- возврат группы из архива (`POST /api/v1/groups/{id}/restore`) с аудитом;
|
||||
- пагинация `/admin/groups` и плоского списка материалов;
|
||||
- `latestPerStudentByLab` — один запрос сдач на все лабораторные ведомости;
|
||||
- XLSX-тесты preflight-ошибок импорта (занятый логин, неактивная группа);
|
||||
- аудит `GROUP_CREATE/STUDENT_CREATE/GROUP_ENROLL/GROUP_IMPORT/GROUP_ARCHIVE/GROUP_RESTORE`;
|
||||
- история членства на странице группы (последние 200 событий).
|
||||
|
||||
## Остаточные ограничения
|
||||
|
||||
- гонка импорта с параллельной вставкой логина всё ещё даёт общий 409;
|
||||
- screenshot-diff/визуальной регрессии нет;
|
||||
- mutation testing не 100%, нагрузочный smoke не является SLA;
|
||||
- restore на staging, первый реальный Woodpecker pipeline, инвентаризация
|
||||
legacy-активов и daily backup timer требуют доступа к стенду/серверу.
|
||||
|
||||
Артефакты прогона: `build/evidence/{runtime,migration,mutation,browser,smoke}.json`,
|
||||
отчёт gate — `build/reports/evidence/index.md`.
|
||||
@@ -435,12 +435,12 @@
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/16-stage6-report.md, §2.2",
|
||||
"claim": "Схема master + синтетические данные → миграции identity V4–V13, academic V6–V12, progress V13; V10 audit, V11 refresh, V12 absolute-expiry и V13 keyset-индексы создаются, V13 идемпотентна.",
|
||||
"oracle": "Успешное применение и postflight-наличие таблиц/колонок/индексов",
|
||||
"claim": "Схема master + синтетические данные → миграции identity V4–V14, academic V6–V12, progress V13; V10 audit, V11 refresh, V12 absolute-expiry, V13 keyset-индексы, V14 NOT NULL/unique когорты; V13/V14 идемпотентны.",
|
||||
"oracle": "postflight checks = required; повторные V13/V14 не падают",
|
||||
"runtime": [
|
||||
"migration.master_upgrade"
|
||||
],
|
||||
"limitations": "Запускается отдельным скриптом на одноразовой БД; не входит в unit-CI."
|
||||
"limitations": "V14 намеренно блокирует legacy-группы с неразобранной когортой."
|
||||
},
|
||||
{
|
||||
"id": "LDD-RUNTIME-001",
|
||||
@@ -872,6 +872,108 @@
|
||||
"ru.ysu.lms.web.profile.StudentProfileTemplateTest#renders student profile with labs, comments and downloads()"
|
||||
],
|
||||
"limitations": "Внутренние REST-контракты остаются UUID; form payload создания/копирования лабораторной тоже использует UUID."
|
||||
},
|
||||
{
|
||||
"id": "LDD-PROFILE-002",
|
||||
"title": "Профиль соответствует контракту docs/09 и ограничивает попытки",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/09-teacher-student-profile.md, §3, §7",
|
||||
"claim": "Профиль отдаёт variant.id, level.id/code, grade.updatedBy и author.name; число попыток отражается полностью, а список ограничивается параметром submissionLimit; профиль не делает запрос на каждую лабу.",
|
||||
"oracle": "JSON-поля контракта; attemptsCount/submissionsTotal; число SQL-запросов не растёт с числом лабораторных",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.progress.StudentProfileIntegrationTest#teacher sees unpublished grade, authors and all submission attempts()",
|
||||
"ru.ysu.lms.progress.StudentProfileIntegrationTest#profile limits submission attempts but reports total()",
|
||||
"ru.ysu.lms.progress.StudentProfileIntegrationTest#profile uses one query per data kind instead of per lab()",
|
||||
"ru.ysu.lms.web.profile.ProfileDtoJsonTest#deserializes profile contract()",
|
||||
"ru.ysu.lms.web.profile.StudentProfileTemplateTest#renders student profile with labs, comments and downloads()",
|
||||
"ru.ysu.lms.identity.internal.InternalControllerIntegrationTest#lookup includes student full name and leaves staff name empty()"
|
||||
],
|
||||
"limitations": "По умолчанию возвращаются последние 50 попыток; UI показывает, сколько скрыто."
|
||||
},
|
||||
{
|
||||
"id": "LDD-GROUP-001",
|
||||
"title": "Группа возвращается из архива",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/07-groups-courses.md, §8.3",
|
||||
"claim": "Архивную группу можно вернуть в ACTIVE; повторный возврат активной группы отклоняется, состав и история не меняются.",
|
||||
"oracle": "status меняется ARCHIVED→ACTIVE; второй restore — 400; число студентов и история сохраняются",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.identity.roster.GroupServiceIntegrationTest#restores archived group and rejects repeated restore()",
|
||||
"ru.ysu.lms.identity.audit.AuditIntegrationTest#roster operations record group and student audit events()"
|
||||
],
|
||||
"limitations": "Возврат выполняется со страницы группы; отдельного массового восстановления нет."
|
||||
},
|
||||
{
|
||||
"id": "LDD-GROUP-002",
|
||||
"title": "Финальные ограничения когорты",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/07-groups-courses.md, §8.2; docs/08-groups-courses-plan.md, этап 1",
|
||||
"claim": "Все поля когорты NOT NULL, уникальность полная (без partial WHERE), legacy-строки блокируют миграцию с явной ошибкой.",
|
||||
"oracle": "information_schema.is_nullable=NO для 5 полей; groups_cohort_unique без WHERE; master→branch migration PASS",
|
||||
"evidence": [],
|
||||
"runtime": [
|
||||
"migration.master_upgrade"
|
||||
],
|
||||
"limitations": "Перед деплоем на реальных данных нужен ручной разбор legacy-групп; запрос для проверки есть в V14."
|
||||
},
|
||||
{
|
||||
"id": "LDD-IMPORT-004",
|
||||
"title": "XLSX-импорт отчитывается по строкам до записи",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/08-groups-courses-plan.md, этап 1; docs/18 §3",
|
||||
"claim": "Для XLSX занятый логин и неактивная целевая группа возвращают applied=false, номер строки и не создают частичных записей.",
|
||||
"oracle": "ImportResult.errors.row=2, applied=false; студент не создан",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.identity.roster.GroupServiceIntegrationTest#xlsx import reports occupied login before writing()",
|
||||
"ru.ysu.lms.identity.roster.GroupServiceIntegrationTest#xlsx import reports inactive target group before writing()"
|
||||
],
|
||||
"limitations": "Гонка с параллельной вставкой логина вне preflight всё ещё может дать общий 409."
|
||||
},
|
||||
{
|
||||
"id": "LDD-AUDIT-006",
|
||||
"title": "Операции с группами и студентами пишут аудит",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/07-groups-courses.md, §9; docs/08-groups-courses-plan.md, этап 1",
|
||||
"claim": "GROUP_CREATE, STUDENT_CREATE, GROUP_ENROLL, GROUP_IMPORT, GROUP_ARCHIVE и GROUP_RESTORE попадают в журнал с актором и деталями.",
|
||||
"oracle": "audit_events содержит все шесть action; actorAccountId/actorLogin заполнены",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.identity.audit.AuditIntegrationTest#roster operations record group and student audit events()"
|
||||
],
|
||||
"limitations": "Проверяются успешные операции; отказы валидации аудит не пишут."
|
||||
},
|
||||
{
|
||||
"id": "LDD-PERF-003",
|
||||
"title": "Пакетные сдачи и пагинация списков",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/07-groups-courses.md, §10; docs/18 §3",
|
||||
"claim": "Ведомость получает последние сдачи по всем выбранным лабам одним запросом; группы и плоский список материалов поддерживают limit/offset.",
|
||||
"oracle": "одна выборка submissions на набор labId; страницы групп/материалов не пересекаются и не теряют записи",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.progress.SubmissionBatchIntegrationTest#latest submissions are grouped by lab in one call()",
|
||||
"ru.ysu.lms.identity.roster.GroupServiceIntegrationTest#lists groups page by page and returns all without limit()",
|
||||
"ru.ysu.lms.academic.content.CourseContentIntegrationTest#admin article list supports offset and limit()"
|
||||
],
|
||||
"limitations": "Дерево курса по-прежнему загружается целиком для навигации; пагинируются плоские списки."
|
||||
},
|
||||
{
|
||||
"id": "LDD-GROUP-003",
|
||||
"title": "История членства видна на странице группы",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/07-groups-courses.md, §3; docs/18 §3",
|
||||
"claim": "Админ видит последние события membership_history по группе: студент, действие, откуда/куда, причина и время МСК.",
|
||||
"oracle": "GET /api/v1/groups/{id}/history отдаёт studentName/fromGroupName/toGroupName; шаблон показывает блок истории",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.identity.roster.GroupServiceIntegrationTest#imports students and records membership history()",
|
||||
"ru.ysu.lms.web.admin.AdminStage4TemplateTest#renders membership history and restore action for archived group()"
|
||||
],
|
||||
"limitations": "Показаны последние 200 событий; фильтра/пагинации по истории нет."
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user