docs: закрытие продуктовых рисков и новый LDD baseline
This commit is contained in:
@@ -12,8 +12,9 @@
|
||||
`./gradlew evidenceCheck`.
|
||||
- **P1** — важное поведение/UX/контракт. Пробел показывается в отчёте, но не
|
||||
блокирует сборку.
|
||||
- **runtime** — проверка на реально поднятом стеке. Выполняется отдельным
|
||||
скриптом; в unit-CI может отсутствовать, но обязательна перед релизом.
|
||||
- **runtime** — проверка на реально поднятом стеке. Быстрый smoke
|
||||
(`scripts/ldd-smoke.sh`) выполняется в CI на каждом push/MR; полный
|
||||
runtime/scale — в `release-evidence` и обязателен перед релизом.
|
||||
|
||||
## Реестр
|
||||
|
||||
@@ -39,19 +40,22 @@
|
||||
# 2. Гейт свидетельств (P0 без evidence = fail)
|
||||
./gradlew evidenceCheck
|
||||
|
||||
# 3. Полный runtime-smoke на одноразовой БД
|
||||
# 3. Быстрый smoke полного стека (то же, что CI job smoke)
|
||||
./scripts/ldd-smoke.sh
|
||||
|
||||
# 4. Полный runtime-smoke + scale load на одноразовой БД
|
||||
./scripts/ldd-runtime.sh
|
||||
|
||||
# 4. Проверка upgrade с master-схемы
|
||||
# 5. Проверка upgrade с master-схемы
|
||||
./scripts/ldd-migration-upgrade.sh
|
||||
|
||||
# 5. Mutation testing критичных P0-компонентов
|
||||
# 6. Mutation testing критичных P0-компонентов
|
||||
./scripts/ldd-mutation.sh
|
||||
|
||||
# 6. Браузерный E2E (Playwright + Chromium)
|
||||
# 7. Браузерный E2E (Playwright + Chromium)
|
||||
./scripts/ldd-browser.sh
|
||||
|
||||
# 7. Полный release gate
|
||||
# 8. Полный release gate
|
||||
./scripts/ldd-release.sh
|
||||
```
|
||||
|
||||
@@ -74,26 +78,36 @@ Platform 6 из Spring Boot 4.
|
||||
доказательства.
|
||||
6. При переименовании теста обновляется `claims.json`; устаревшая ссылка —
|
||||
это gap, а не «забыли».
|
||||
7. Runtime-свидетельства обязательны перед релизом; CI проверяет unit/
|
||||
integration-слой.
|
||||
7. Runtime-свидетельства обязательны перед релизом; CI на каждом push/MR
|
||||
выполняет unit/integration-слой и быстрый smoke, полный release gate —
|
||||
для тегов (вручную для веток).
|
||||
|
||||
## Текущий baseline
|
||||
|
||||
Первый LDD-цикл выполнен 2026-10-27/28:
|
||||
LDD-циклы выполнены 2026-10-27/28; последний — после закрытия продуктовых
|
||||
рисков:
|
||||
|
||||
- **276 автоматических тестов**, 0 failures/errors;
|
||||
- **37 claims**: 20 P0 + 17 P1. P0 — доступы, повышение, жизненный цикл,
|
||||
профиль/скачивание, материалы/активы, миграция V13, аудит; P1 — группы,
|
||||
авторство, UI, контракты, импорт, копирование лаб, runtime, mutation и
|
||||
browser E2E;
|
||||
- **319 автоматических тестов**, 0 failures/errors;
|
||||
- **46 claims**: 25 P0 + 21 P1, **46/46 proven, P0 gaps = 0**. P0 — доступы,
|
||||
повышение, жизненный цикл, профиль/скачивание, материалы/активы
|
||||
(включая secure-by-default legacy-файлы), миграция V13, аудит, refresh-токены
|
||||
и их отзыв; P1 — группы, авторство, UI, контракты, импорт, копирование лаб,
|
||||
пагинация аудита, bulk-группы курса, runtime, mutation и browser E2E;
|
||||
- mutation testing: `CourseAccessService` 75% test strength,
|
||||
`PromotionPlanner` 69%;
|
||||
- Playwright: 4 сценария (admin, teacher, student/anonymous, сдача+mobile), все зелёные;
|
||||
- runtime-smoke, master-upgrade, mutation и browser E2E вынесены в отдельные
|
||||
скрипты.
|
||||
- Playwright: 4 сценария (admin, teacher, student/anonymous, сдача+mobile),
|
||||
все зелёные;
|
||||
- масштабный load: 100 групп / 1000 студентов, 125 параллельных чтений,
|
||||
0 ошибок, p95 < 1 с (порог p95 < 2 с);
|
||||
- CI: job `smoke` (`ldd-smoke.sh`) на каждом push/MR; полный
|
||||
`release-evidence` (`ldd-release.sh`) обязателен для тегов и ручной для
|
||||
веток.
|
||||
|
||||
В ходе E2E найден и исправлен реальный дефект: Jackson 3 не применял
|
||||
Kotlin-модуль, поэтому DTO с default-значениями требовали все поля JSON
|
||||
(например, создание студента без `firstName`/`basis` давало 400).
|
||||
|
||||
Результаты и остаточные ограничения — в `baseline-2026-10-27.md`.
|
||||
Остаточные ограничения: offset-пагинация аудита, N+1 в отдельных лабораторных
|
||||
списках/ведомости, отсутствие абсолютного лимита web-сессии. Legacy-активы,
|
||||
срок жизни JWT и «ведущие нули» больше не являются открытыми рисками.
|
||||
Результаты — в `baseline-2026-10-27.md`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -232,14 +232,14 @@
|
||||
"criticality": "P0",
|
||||
"status": "active",
|
||||
"requirement": "docs/07-groups-courses.md, §6",
|
||||
"claim": "Скрытый курсовой актив не отдаётся студенту, но доступен преподавателю; чужой курс получает 403; legacy без связи публичен.",
|
||||
"claim": "Скрытый курсовой актив не отдаётся студенту, но доступен преподавателю; чужой курс получает 403; публичный глобальный ALL-актив доступен без входа.",
|
||||
"oracle": "HTTP-статус и содержимое файла",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.academic.content.ContentAssetAccessIntegrationTest#hidden course asset is not served to student but is served to teacher()",
|
||||
"ru.ysu.lms.academic.content.ContentAssetAccessIntegrationTest#foreign teacher and student are forbidden for course asset()",
|
||||
"ru.ysu.lms.academic.content.ContentAssetAccessIntegrationTest#legacy asset without article association stays public()"
|
||||
"ru.ysu.lms.academic.content.ContentAssetAccessIntegrationTest#published global ALL asset stays public()"
|
||||
],
|
||||
"limitations": "Legacy-файлы без article_assets остаются публичными — отдельный остаточный риск."
|
||||
"limitations": "Legacy-файлы без article_assets теперь закрыты отдельным claim LDD-ASSET-002."
|
||||
},
|
||||
{
|
||||
"id": "LDD-MIG-001",
|
||||
@@ -365,7 +365,7 @@
|
||||
"ru.ysu.lms.identity.roster.StudentImportParserTest#parses new template and splits full name()",
|
||||
"ru.ysu.lms.identity.roster.StudentImportParserTest#reports duplicate student number()"
|
||||
],
|
||||
"limitations": "Старый формат не поддерживается; ведущие нули нормализуются."
|
||||
"limitations": "Старый формат импорта не поддерживается; номер группы числовой, отдельного требования к ведущим нулям нет."
|
||||
},
|
||||
{
|
||||
"id": "LDD-LAB-002",
|
||||
@@ -435,7 +435,7 @@
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/16-stage6-report.md, §2.2",
|
||||
"claim": "Схема master + синтетические данные → миграции identity V4–V10, academic V6–V12, progress V13; V10 audit создаётся, V13 идемпотентна.",
|
||||
"claim": "Схема master + синтетические данные → миграции identity V4–V11, academic V6–V12, progress V13; V10 audit и V11 refresh создаются, V13 идемпотентна.",
|
||||
"oracle": "Успешное применение и postflight-наличие таблиц/колонок/индексов",
|
||||
"runtime": [
|
||||
"migration.master_upgrade"
|
||||
@@ -538,6 +538,137 @@
|
||||
"ru.ysu.lms.identity.promotion.PromotionApplyRollbackPropertyTest#generated cohorts apply to plan and rollback to before-state()"
|
||||
],
|
||||
"limitations": "10 детерминированных сценариев в одной транзакции; не заменяет property-генератор по всем полям."
|
||||
},
|
||||
{
|
||||
"id": "LDD-RUNTIME-003",
|
||||
"title": "Масштабный load: 100 групп и 1000 студентов",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/evidence/README.md",
|
||||
"claim": "После seed 100 групп/1000 студентов 125 параллельных чтений курса, лаб, профиля, групп и аудита дают 0 ошибок и p95 < 2 c.",
|
||||
"oracle": "runtime.scale_seed + runtime.load_gradebook_profile",
|
||||
"runtime": [
|
||||
"runtime.scale_seed",
|
||||
"runtime.load_gradebook_profile"
|
||||
],
|
||||
"limitations": "Синтетические данные в одноразовой БД; это smoke, не SLA production."
|
||||
},
|
||||
{
|
||||
"id": "LDD-ASSET-002",
|
||||
"title": "Файлы без привязки к статье не публичны",
|
||||
"criticality": "P0",
|
||||
"status": "active",
|
||||
"requirement": "docs/15-stage5-course-materials.md",
|
||||
"claim": "Аноним, студент и преподаватель получают 404 для legacy-актива без article_assets; админ получает файл; endpoint legacy-assets перечисляет такие файлы до привязки.",
|
||||
"oracle": "HTTP 404/200 и список unlinkedAssetNames",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.academic.content.ContentAssetAccessIntegrationTest#legacy asset without article association is not public()",
|
||||
"ru.ysu.lms.academic.content.ContentAssetAccessIntegrationTest#unlinkedAssetNames lists stored file until it is linked()"
|
||||
],
|
||||
"limitations": "Старые внешние ссылки на бесхозные файлы перестают работать у обычных пользователей — осознанный security-компромисс."
|
||||
},
|
||||
{
|
||||
"id": "LDD-AUTH-001",
|
||||
"title": "Короткий access-токен и ротация refresh",
|
||||
"criticality": "P0",
|
||||
"status": "active",
|
||||
"requirement": "docs/16-stage6-report.md, §4",
|
||||
"claim": "Login выдаёт access ≤15 минут и refresh; refresh ротируется, старый refresh отклоняется, повторное использование отзывает активные сессии.",
|
||||
"oracle": "TTL JWT, ответы refresh, отзыв сессий",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.identity.RefreshTokenIntegrationTest#login returns short lived access token and refresh token()",
|
||||
"ru.ysu.lms.identity.RefreshTokenIntegrationTest#refresh rotates the token and old token is rejected()",
|
||||
"ru.ysu.lms.identity.RefreshTokenIntegrationTest#reuse of a rotated token revokes every active session()"
|
||||
],
|
||||
"limitations": "Refresh-токен хранится как SHA-256 отпечаток; очистка истёкших записей выполняется позже."
|
||||
},
|
||||
{
|
||||
"id": "LDD-AUTH-002",
|
||||
"title": "Отзыв refresh при logout/смене пароля и закрытом статусе",
|
||||
"criticality": "P0",
|
||||
"status": "active",
|
||||
"requirement": "docs/16-stage6-report.md, §4",
|
||||
"claim": "Logout и смена пароля отзывают refresh; отчисленный/архивный студент, архивный аккаунт и закрытая группа не могут обновить сессию.",
|
||||
"oracle": "401 на refresh после отзыва/закрытия",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.identity.RefreshTokenIntegrationTest#logout revokes the refresh token idempotently()",
|
||||
"ru.ysu.lms.identity.RefreshTokenIntegrationTest#password change revokes refresh tokens()",
|
||||
"ru.ysu.lms.identity.RefreshTokenIntegrationTest#expelled student cannot refresh()",
|
||||
"ru.ysu.lms.identity.RefreshTokenIntegrationTest#archived account cannot refresh()",
|
||||
"ru.ysu.lms.identity.RefreshTokenIntegrationTest#student in archived group cannot refresh()"
|
||||
],
|
||||
"limitations": "Access-токен живёт до 15 минут после отзыва refresh — это принятый компромисс."
|
||||
},
|
||||
{
|
||||
"id": "LDD-AUTH-003",
|
||||
"title": "Web-сессия 7 дней sliding и автообновление",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/evidence/README.md",
|
||||
"claim": "Web хранит refresh и expiry, обновляет access за 2 минуты до истечения, продлевает cookie на каждом ответе; logout отзывает refresh; identity outage не разлогинивает всех.",
|
||||
"oracle": "SessionTokenService/AuthPageController/фильтр cookie",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.web.session.SessionTokenServiceTest#token expiring soon is refreshed and session is updated()",
|
||||
"ru.ysu.lms.web.session.SessionTokenServiceTest#identity outage fails open()",
|
||||
"ru.ysu.lms.web.session.SessionCookieRefreshFilterTest#refreshes cookie max age on every existing session()",
|
||||
"ru.ysu.lms.web.auth.AuthPageControllerTest#logout revokes refresh token and invalidates session()"
|
||||
],
|
||||
"limitations": "Cookie и серверный timeout оба 7 дней; абсолютного верхнего предела сессии нет."
|
||||
},
|
||||
{
|
||||
"id": "LDD-AUDIT-004",
|
||||
"title": "Пагинация журнала аудита",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/evidence/README.md",
|
||||
"claim": "Audit API отдаёт страницы limit/page без пересечений и с устойчивым порядком; UI показывает «Показать ещё».",
|
||||
"oracle": "Страницы AuditIntegrationTest и ссылки шаблона",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.identity.audit.AuditIntegrationTest#pagination returns disjoint pages newest first()",
|
||||
"ru.ysu.lms.web.admin.AdminAuditTemplateTest#renders audit events with labels and filter()"
|
||||
],
|
||||
"limitations": "Offset-пагинация, не keyset; при частых вставках возможны сдвиги."
|
||||
},
|
||||
{
|
||||
"id": "LDD-PERF-001",
|
||||
"title": "Bulk-группы курса без N+1",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/evidence/README.md",
|
||||
"claim": "Страница преподавателя получает названия всех привязанных групп одним запросом; чужой преподаватель получает 403.",
|
||||
"oracle": "Один вызов courseGroups и отсутствие courseGroupStudents на страницу",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.academic.course.CourseGroupTeacherAccessTest#teacher gets attached groups with names in one call()",
|
||||
"ru.ysu.lms.web.teacher.TeacherControllerCourseGroupsTest#loads group names with a single call instead of per-group requests()"
|
||||
],
|
||||
"limitations": "Детали лабораторных профиля всё ещё подтягиваются по одной; это следующий шаг."
|
||||
},
|
||||
{
|
||||
"id": "LDD-AUTH-004",
|
||||
"title": "Gateway пропускает refresh/logout без access-токена",
|
||||
"criticality": "P0",
|
||||
"status": "active",
|
||||
"requirement": "docs/16-stage6-report.md, §4",
|
||||
"claim": "POST /api/v1/auth/refresh и /api/v1/auth/logout проходят через gateway без access-JWT, иначе автообновление и отзыв сессии невозможны.",
|
||||
"oracle": "GatewayFilterChain вызван для public path; protected path без токена — 401",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.gateway.security.JwtAuthenticationFilterTest#refresh and logout are public()",
|
||||
"ru.ysu.lms.gateway.security.JwtAuthenticationFilterTest#protected route without bearer token is rejected()"
|
||||
],
|
||||
"limitations": "Проверена маршрутизация gateway; интеграционный E2E с истёкшим access-токеном добавлен через web-тесты."
|
||||
},
|
||||
{
|
||||
"id": "LDD-AUTH-005",
|
||||
"title": "Refresh/logout работают через реальный gateway",
|
||||
"criticality": "P0",
|
||||
"status": "active",
|
||||
"requirement": "docs/16-stage6-report.md, §4",
|
||||
"claim": "Через gateway refresh без access-токена выдаёт новую пару, новый access работает, logout отзывает refresh, повторный refresh — 401.",
|
||||
"oracle": "runtime.auth_refresh",
|
||||
"runtime": [
|
||||
"runtime.auth_refresh"
|
||||
],
|
||||
"limitations": "Проверено на одноразовом стеке; TTL не истекает в рамках smoke."
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user