docs: закрытие продуктовых рисков и новый LDD baseline

This commit is contained in:
2026-10-28 13:26:43 +03:00
parent 71fa3284a0
commit 9dcba8ce0c
16 changed files with 584 additions and 212 deletions

View File

@@ -1,19 +1,19 @@
# LDD baseline: 2026-10-27/28
Первый и второй LDD-циклы проверки текущей функциональности LMS.
Добавлены реестр claims, gate `evidenceCheck`, runtime/migration/mutation
скрипты и браузерный E2E на Playwright. В ходе проверки найден и исправлен
реальный дефект Jackson 3.
Три LDD-цикла проверки текущей функциональности LMS; последний выполнен
2026-10-28 после закрытия продуктовых рисков. Добавлены реестр claims, gate
`evidenceCheck`, runtime/migration/mutation/smoke-скрипты и браузерный E2E на
Playwright. В ходе проверки найден и исправлен реальный дефект Jackson 3.
## 1. Что проверялось
Реестр: `docs/evidence/claims.json`, **37 утверждений**.
Реестр: `docs/evidence/claims.json`, **44 утверждения**.
| Критичность | Claims | Proven | Gaps |
|---|---:|---:|---:|
| P0 | 20 | 20 | 0 |
| P1 | 17 | 17 | 0 |
| **Итого** | **37** | **37** | **0** |
| P0 | 25 | 25 | 0 |
| P1 | 21 | 21 | 0 |
| **Итого** | **46** | **46** | **0** |
Домены:
@@ -22,12 +22,14 @@
- повышение: dry-run, apply, идемпотентность, rollback, cancel;
- жизненный цикл: академ, возврат, отчисление, архив, восстановление, вход;
- профиль студента и скачивание сдач;
- материалы и активы;
- миграция V13 и полный upgrade с master-схемы;
- аудит: токен, admin-only, сохранность после удаления, representative-хуки;
- группы курса, авторство материалов, ведомость, UI/контракты, импорт,
- материалы и активы, включая secure-by-default legacy-файлы;
- миграции V11 и V13, полный upgrade с master-схемы;
- аудит: токен, admin-only, сохранность после удаления, representative-хуки,
пагинация;
- аутентификация: короткий access-токен, ротация/отзыв refresh, web-сессия;
- группы курса без N+1, авторство материалов, ведомость, UI/контракты, импорт,
копирование лабораторных;
- runtime-smoke, mutation testing, браузерный E2E.
- runtime-smoke, масштабный load, mutation testing, браузерный E2E и CI smoke.
## 2. Команды и результаты
@@ -37,7 +39,7 @@
./gradlew test --rerun-tasks
```
Результат: **276 тестов, 0 failures, 0 errors, 0 skipped**.
Результат: **319 тестов, 0 failures, 0 errors, 0 skipped**.
`ktlintCheck` и `detekt` — зелёные.
### 2.2. LDD gate
@@ -46,7 +48,7 @@
./gradlew evidenceCheck
```
Результат: **37/37 claims proven, P0 gaps = 0**.
Результат: **46/46 claims proven, P0 gaps = 0**.
Отчёт: `build/reports/evidence/index.md`.
Gate устроен так:
@@ -65,11 +67,11 @@ Gate устроен так:
Сценарий: одноразовая PostgreSQL 18, master identity `V1–V3`, academic
`V1–V5`, progress `V1–V9,V11,V12`, синтетические данные, затем ветка:
identity `V4–V10`, academic `V6–V12`, progress `V13`.
identity `V4–V11`, academic `V6–V12`, progress `V13`.
Проверено:
- `identity.audit_events` создана;
- `identity.audit_events` (`V10`) и `identity.refresh_tokens` (`V11`) созданы;
- `students`, `groups`, `membership_history` сохранились;
- `labs.course_id`, `chapters.course_id`, `article_assets` созданы;
- `grade_comments.author_account_id`, `lab_grades.updated_by_account_id` созданы;
@@ -78,10 +80,11 @@ identity `V4–V10`, academic `V6–V12`, progress `V13`.
Результат: **PASS**, evidence: `build/evidence/migration.json`.
### 2.4. Runtime-smoke полного стека
### 2.4. Runtime-smoke полного стека и CI smoke
```bash
./scripts/ldd-runtime.sh
./scripts/ldd-smoke.sh # то же, что CI job smoke
```
Сценарий: одноразовая БД, `bootJar`, запуск identity/academic/progress/gateway/web,
@@ -99,9 +102,68 @@ identity `V4–V10`, academic `V6–V12`, progress `V13`.
- web `/login` отдаёт `200`, публичное дерево материалов доступно, курсовые
материалы анонимно — `401`.
Результат: **PASS**, evidence: `build/evidence/runtime.json`.
`ldd-smoke.sh` выполняет тот же полный стек, health, вход админа и один
доменный сценарий; это обязательный job `.gitlab-ci.yml` на каждом push/MR,
поэтому базовый runtime-контур проверяется до merge, а не только перед релизом.
### 2.5. Mutation testing
Результат: **PASS**, evidence: `build/evidence/runtime.json` и
`build/evidence/smoke.json`.
### 2.5. Legacy-активы: secure-by-default
Проверено автоматическими тестами доступа к активам:
- файл без строки в `article_assets` отдаёт `404` анониму, студенту и
преподавателю (существование не раскрывается);
- админ может скачать такой файл;
- `GET /api/v1/content/legacy-assets` защищён `ROLE_ADMIN` и перечисляет
несвязанные файлы для привязки или удаления;
- связанный актив по-прежнему наследует видимость статьи → раздела → главы:
публичная `ALL` — без входа, курсовая — только участникам курса, скрытая
для студента — `404`.
Старые внешние ссылки на несвязанные файлы у обычных пользователей перестают
работать. Это осознанное изменение поведения: безопасность важнее совместимости
с «осиротевшими» активами.
Результат: **PASS**, claims `LDD-ASSET-001`, `LDD-ASSET-002`.
### 2.6. Аутентификация, refresh и web-сессия
- identity `V11__refresh_tokens.sql` хранит только SHA-256-хеш refresh-токена,
срок, отзыв и цепочку замен;
- access JWT — 15 минут (`LMS_ACCESS_TOKEN_TTL`), refresh — 7 дней
(`LMS_REFRESH_TOKEN_TTL`);
- `POST /api/v1/auth/refresh` ротирует пару; повторное использование старого
токена отзывает все сессии аккаунта;
- logout и смена пароля отзывают refresh; отчисленный, выпущенный, архивный
студент, архивный аккаунт и закрытая группа не могут обновить сессию;
- web-сессия — 7 дней sliding (`LMS_SESSION_TIMEOUT`), cookie `LMS_SESSION`
(`HttpOnly`, `SameSite=Lax`, `Max-Age` перевыпускается на каждом ответе);
- access обновляется автоматически за 2 минуты до истечения, поэтому активная
вкладка не требует входа каждые 15 минут; logout отзывает refresh.
Прямой API-клиент должен сам использовать `POST /api/v1/auth/refresh`;
автоматического обновления вне web BFF нет.
Результат: **PASS**, claims `LDD-AUTH-001`, `LDD-AUTH-002`, `LDD-AUTH-003`.
### 2.7. Пагинация, N+1 и масштаб
- `GET /api/v1/audit` принимает `page` и `limit` (максимум 500); в UI есть
«Показать ещё». Это offset-пагинация, а не keyset.
- `GET /api/v1/courses/{id}/groups` возвращает привязанные группы с именами
одним вызовом; страница преподавателя не делает N+1 по именам групп.
- Полный load использует seed на **100 групп / 1000 студентов** (SQL, только
release-контур) и **125 параллельных чтений** курса, лаб, профиля, групп и
аудита: **0 ошибок, p95 < 1 с** (порог p95 < 2 с).
Остаются offset-пагинация аудита и N+1 в отдельных лабораторных
списках/ведомости — это лабораторные детали, не продуктовые риски доступа.
Результат: **PASS**, claims `LDD-AUDIT-004`, `LDD-PERF-001`, `LDD-RUNTIME-003`.
### 2.8. Mutation testing
```bash
./scripts/ldd-mutation.sh
@@ -119,7 +181,7 @@ PIT запускается через CLI с JUnit 4 harness: JUnit5-плаги
Остаток — эквивалентные мутации, Kotlin-интринсики и ветки, не входящие в
текущий P0-контур.
### 2.6. Браузерный E2E
### 2.9. Браузерный E2E
```bash
./scripts/ldd-browser.sh
@@ -136,25 +198,27 @@ Playwright/Chromium на полном стеке, одноразовая БД:
Результат: **4/4 passed**, evidence: `build/evidence/browser.json`.
Дополнительно проверены сдача файла студентом и mobile-размер teacher-страницы.
### 2.7. Конкурентные apply/rollback
### 2.10. Конкурентные apply/rollback
Добавлен `PromotionConcurrencyIntegrationTest` (без `@Transactional`, два потока,
`PromotionConcurrencyIntegrationTest` (без `@Transactional`, два потока,
advisory lock): два одновременных apply идемпотентны и пишут history один раз;
два одновременных rollback дают ровно один успех и один `ConflictException`,
состояние восстанавливается один раз.
### 2.8. Нагрузочный smoke
`ldd-runtime.sh` выполняет 100 чтений (10 workers) курса, лаб, профиля и аудита:
**0 ошибок, avg 0.141 c, p95 0.868 c** (порог p95 < 2 c).
### 2.9. Release gate
### 2.11. Release gate и CI
`./scripts/ldd-release.sh` последовательно запускает тесты/линтеры, upgrade,
mutation, runtime и browser E2E, затем финальный `evidenceCheck`.
В `.gitlab-ci.yml` добавлен ручной job `release-evidence` с Chromium/Node.
mutation, runtime + scale и browser E2E, затем финальный `evidenceCheck`.
### 2.10. Найденный дефект
В `.gitlab-ci.yml`:
- `check` — `test ktlintCheck detekt evidenceCheck` на push/MR;
- `smoke` — `ldd-smoke.sh` (полный стек, health, вход админа, один курс)
на каждом push/MR;
- `release-evidence` — полный `ldd-release.sh` с Chromium/Node: обязателен для
тегов, для веток запускается вручную.
### 2.12. Найденный дефект
Первый browser E2E упал на создании студента: API вернул `400`, потому что
Jackson 3 не применял Kotlin-модуль, и DTO с default-значениями требовали все
@@ -166,14 +230,21 @@ optional json fields`.
## 3. Что это доказывает
- Критичные P0-инварианты доступа, повышения, жизненного цикла, профиля,
материалов, миграции и аудита имеют зелёные автоматические свидетельства.
материалов, миграции, аудита и аутентификации имеют зелёные автоматические
свидетельства.
- Приложение собирается, мигрирует с master-схемы и поднимается полным
стеком на чистой БД.
- Ключевой доменный поток «админ → группа → курс → преподаватель → лаба»
проходит через реальные HTTP/gateway/БД и браузер.
- Чужой преподаватель не получает доступ к курсу/лабе/аудиту на реальном стеке.
- Legacy-актив без привязки не утекает обычным пользователям, а админ может
провести инвентаризацию.
- Refresh-токены ротируются и отзываются; закрытый статус блокирует продление
сессии, а web-сессия переживает 15-минутный access-токен.
- Аудит листается по страницам, а группы курса отдаются одним вызовом без N+1.
- Аудит пишется и читается; upgrade не теряет синтетические данные.
- Мутационная прочность критичных компонентов измерена, а не заявлена.
- Масштабный load на 100 группах / 1000 студентов проходит без ошибок.
- Реальный дефект DTO/Jackson найден именно E2E-контуром и закрыт тестом.
## 4. Чего это не доказывает
@@ -186,24 +257,32 @@ optional json fields`.
3. **Браузерный E2E — один Chromium и 4 сценария.** Нет визуальной регрессии,
Safari/Firefox и полного мобильного набора страниц.
4. **Нагрузочный smoke — не benchmark.** Одна машина, синтетические данные,
без SLA production.
5. **Legacy-активы без `article_assets` остаются публичными.**
6. **JWT живёт до 30 дней.** Web перепроверяет статус, прямой API — нет.
7. **Ведущие нули номера группы нормализуются** (`01` → `1`).
8. **Нет пагинации/keyset** для части списков и N+1 в отдельных экранах.
9. **Runtime/mutation/browser E2E не входят в unit-CI**; это release-скрипты.
без SLA production; 125 чтений и p95 < 1 с — ориентир, а не гарантия.
5. **Offset-пагинация аудита.** Вставки между страницами могут сдвигать
выдачу; keyset-курсор не реализован.
6. **N+1 в отдельных лабораторных списках/ведомости.** Имена групп курса и
аудит исправлены, но не все большие списки собраны пакетно.
7. **Нет абсолютного лимита web-сессии.** Sliding-сессия может продлеваться
бесконечно при непрерывной активности; отзыв по статусу при этом работает.
8. **Полный release-evidence не на каждом коммите.** На push/MR идёт smoke,
а mutation/browser/scale — для тегов или вручную.
9. **Внешняя интеграция «Контингент» не проверялась** — она остаётся заделом.
10. **Номер группы числовой.** Отдельного требования к ведущим нулям нет;
`01` и `1` — одно значение, это не открытый риск.
## 5. Вывод
Текущая функциональность прошла LDD-контур: **37/37 claims proven, P0 gaps = 0**,
273 теста, upgrade, mutation baseline и браузерный E2E зелёные. Это
достаточное основание продолжать доработки, но не «формальное доказательство
всей системы».
Текущая функциональность прошла LDD-контур: **46/46 claims proven, P0 gaps = 0**,
**319 тестов**, upgrade, mutation baseline, browser E2E, smoke и масштабный load
зелёные. Это достаточное основание продолжать доработки, но не «формальное
доказательство всей системы».
Следующий цикл LDD:
1. расширить browser E2E на публикацию оценки и остальные мобильные страницы;
2. добавить нагрузочный профиль с реалистичным объёмом групп/студентов;
3. закрыть известные продуктовые риски: legacy-активы, JWT 30 дней, ведущие
нули, пагинация/N+1;
4. сделать release-evidence job обязательным для тегов.
1. почистить инвентарь legacy-активов через `GET /api/v1/content/legacy-assets`
(файлы уже не публичны, это гигиена хранилища);
2. добавить keyset-пагинацию аудита и убрать оставшийся N+1 в лабораторных
списках/ведомости;
3. при необходимости ввести абсолютный лимит web-сессии;
4. расширить browser E2E на публикацию оценки и остальные мобильные страницы;
5. подготовить внешнюю интеграцию «Контингент»/`next.uniyar.ac.ru`.