feat: закрыть остаточный backlog
- keyset-пагинация аудита с курсором (occurred_at, id) и UI «Показать ещё» - пакетный /api/v1/labs/details и переход progress на один запрос - абсолютный потолок сессии 30 дней (identity V12 + web), очистка refresh - админская инвентаризация legacy-активов (/admin/legacy-assets, скрипт) - E2E в Chromium/Firefox/WebKit (18/18), CI preflight - LDD: 51 claim, 346 тестов, docs и handoff обновлены
This commit is contained in:
@@ -435,7 +435,7 @@
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/16-stage6-report.md, §2.2",
|
||||
"claim": "Схема master + синтетические данные → миграции identity V4–V11, academic V6–V12, progress V13; V10 audit и V11 refresh создаются, V13 идемпотентна.",
|
||||
"claim": "Схема master + синтетические данные → миграции identity V4–V13, academic V6–V12, progress V13; V10 audit, V11 refresh, V12 absolute-expiry и V13 keyset-индексы создаются, V13 идемпотентна.",
|
||||
"oracle": "Успешное применение и postflight-наличие таблиц/колонок/индексов",
|
||||
"runtime": [
|
||||
"migration.master_upgrade"
|
||||
@@ -488,16 +488,16 @@
|
||||
},
|
||||
{
|
||||
"id": "LDD-BROWSER-001",
|
||||
"title": "Браузерный E2E админа, преподавателя и студента, включая сдачу и mobile",
|
||||
"title": "Браузерный E2E в Chromium, Firefox и WebKit",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/evidence/README.md",
|
||||
"claim": "Playwright на реальном стеке: admin видит курс и аудит; teacher открывает курс/ведомость/профиль, создаёт главу и видит mobile-вид; student сдаёт файл; аноним и студент не попадают в админку.",
|
||||
"claim": "6 сценариев (админ, преподаватель, студент/аноним, сдача+mobile, cursor-аудит, инвентаризация активов) проходят в Chromium, Firefox и WebKit: 18/18 без unexpected.",
|
||||
"oracle": "Playwright assertions на DOM/URL",
|
||||
"runtime": [
|
||||
"browser.e2e"
|
||||
],
|
||||
"limitations": "4 сценария, один Chromium; нет визуальной регрессии и Safari/Firefox."
|
||||
"limitations": "Нет визуальной регрессии и полного набора мобильных страниц; WebKit на Fedora запускается через Docker-образ Playwright."
|
||||
},
|
||||
{
|
||||
"id": "LDD-RUNTIME-002",
|
||||
@@ -613,7 +613,7 @@
|
||||
"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 дней; абсолютного верхнего предела сессии нет."
|
||||
"limitations": "Cookie и серверный timeout 7 дней; поверх них действует абсолютный потолок 30 дней (LDD-AUTH-006)."
|
||||
},
|
||||
{
|
||||
"id": "LDD-AUDIT-004",
|
||||
@@ -621,13 +621,13 @@
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/evidence/README.md",
|
||||
"claim": "Audit API отдаёт страницы limit/page без пересечений и с устойчивым порядком; UI показывает «Показать ещё».",
|
||||
"claim": "Audit API поддерживает offset-страницы `page`/`limit` для совместимости; UI по умолчанию продолжает журнал keyset-курсором.",
|
||||
"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; при частых вставках возможны сдвиги."
|
||||
"limitations": "Offset-режим остаётся только для старых ссылок; основной путь — курсор (LDD-AUDIT-005)."
|
||||
},
|
||||
{
|
||||
"id": "LDD-PERF-001",
|
||||
@@ -641,7 +641,7 @@
|
||||
"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": "Детали лабораторных профиля всё ещё подтягиваются по одной; это следующий шаг."
|
||||
"limitations": "Детали лабораторных теперь пакетные (LDD-PERF-002); отдельные запросы на лабораторную в ведомости ещё остаются."
|
||||
},
|
||||
{
|
||||
"id": "LDD-AUTH-004",
|
||||
@@ -669,6 +669,84 @@
|
||||
"runtime.auth_refresh"
|
||||
],
|
||||
"limitations": "Проверено на одноразовом стеке; TTL не истекает в рамках smoke."
|
||||
},
|
||||
{
|
||||
"id": "LDD-AUTH-006",
|
||||
"title": "Абсолютный потолок сессии 30 дней",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/16-stage6-report.md, §4",
|
||||
"claim": "Ротация refresh не продлевает absolute_expires_at; после 30 дней refresh отклоняется, а web-сессия принудительно завершается и cookie не живёт дольше остатка.",
|
||||
"oracle": "absolute_expires_at в цепочке ротаций, 401 на refresh, redirect /login?expired=absolute, Max-Age cookie",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.identity.RefreshTokenIntegrationTest#refresh past the absolute session expiry is rejected with unauthorized()",
|
||||
"ru.ysu.lms.identity.RefreshTokenIntegrationTest#rotation keeps the absolute session expiry unchanged()",
|
||||
"ru.ysu.lms.web.session.AuthInterceptorTest#absolute expired session redirects to absolute login without identity call()",
|
||||
"ru.ysu.lms.web.session.SessionCookieRefreshFilterTest#cookie max age is capped by remaining absolute lifetime()",
|
||||
"ru.ysu.lms.web.session.SessionCookieRefreshFilterTest#absolute expired session receives zero max age cookie()",
|
||||
"ru.ysu.lms.web.auth.AuthPageControllerTest#login stores authenticated at timestamp()"
|
||||
],
|
||||
"limitations": "Срок настраивается LMS_SESSION_ABSOLUTE_TIMEOUT; абсолютный отсчёт начинается с логина, а не с создания web-сессии."
|
||||
},
|
||||
{
|
||||
"id": "LDD-AUTH-007",
|
||||
"title": "Очистка истёкших refresh-токенов",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/16-stage6-report.md, §4",
|
||||
"claim": "Ночная очистка удаляет токены, у которых sliding- или absolute-срок истёк раньше retention (30 дней), и сохраняет свежие/недавно отозванные токены для detection reuse.",
|
||||
"oracle": "cleanupExpired(now) и количество удалённых записей",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.identity.RefreshTokenIntegrationTest#cleanup deletes only tokens expired beyond retention()"
|
||||
],
|
||||
"limitations": "Расписание задаётся LMS_REFRESH_TOKEN_CLEANUP_CRON; в тесте вызывается метод напрямую, без ожидания cron."
|
||||
},
|
||||
{
|
||||
"id": "LDD-AUDIT-005",
|
||||
"title": "Keyset-пагинация аудита",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/16-stage6-report.md, §2.7",
|
||||
"claim": "Audit API принимает непрозрачный cursor (occurredAt|id), отдаёт строго следующие записи без пересечений и сохраняет фильтр по action; битый курсор — 400; UI ведёт «Показать ещё» по курсору.",
|
||||
"oracle": "Страницы AuditIntegrationTest и cursor-ссылка шаблона",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.identity.audit.AuditIntegrationTest#cursor pagination walks all events newest first()",
|
||||
"ru.ysu.lms.identity.audit.AuditIntegrationTest#cursor pagination respects the action filter()",
|
||||
"ru.ysu.lms.identity.audit.AuditIntegrationTest#malformed audit cursor is rejected with bad request()",
|
||||
"ru.ysu.lms.identity.audit.AuditIntegrationTest#cursor from the newest event returns an empty page()",
|
||||
"ru.ysu.lms.web.admin.AdminAuditTemplateTest#cursor mode links next page with cursor and resets to start()"
|
||||
],
|
||||
"limitations": "Offset page остаётся для обратной совместимости; keyset-курсор не подписан, но не содержит секретов."
|
||||
},
|
||||
{
|
||||
"id": "LDD-PERF-002",
|
||||
"title": "Пакетные детали лабораторных без N+1",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/16-stage6-report.md, §2.7",
|
||||
"claim": "Academic отдаёт все лабораторные курса с уровнями и вариантами одним запросом /api/v1/labs/details; progress делает ровно один HTTP-вызов вместо запроса на каждую лабораторную.",
|
||||
"oracle": "LabDetailsIntegrationTest и счётчик запросов AcademicClientBatchLabsTest",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.academic.lab.LabDetailsIntegrationTest#teacher gets all course labs with levels and variants in one call()",
|
||||
"ru.ysu.lms.academic.lab.LabDetailsIntegrationTest#student of the course receives lab details()",
|
||||
"ru.ysu.lms.academic.lab.LabDetailsIntegrationTest#foreign teacher is forbidden for lab details()",
|
||||
"ru.ysu.lms.progress.AcademicClientBatchLabsTest#getCourseLabs loads all labs with levels and variants in one request()"
|
||||
],
|
||||
"limitations": "Ведомость всё ещё запрашивает сдачи по выбранной лабораторной; это не N+1 по студентам."
|
||||
},
|
||||
{
|
||||
"id": "LDD-ASSET-003",
|
||||
"title": "Админская инвентаризация legacy-активов",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/15-stage5-course-materials.md",
|
||||
"claim": "Админ открывает /admin/legacy-assets, видит список файлов без статьи, ссылку на файл и пустое состояние; страница не показывает ошибку при пустом списке.",
|
||||
"oracle": "LegacyAssetsTemplateTest и HTTP-страница",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.web.admin.LegacyAssetsTemplateTest#renders orphan assets with links and admin-only explanation()",
|
||||
"ru.ysu.lms.web.admin.LegacyAssetsTemplateTest#renders empty legacy assets state()"
|
||||
],
|
||||
"limitations": "Удаление файлов остаётся ручной операцией на стороне academic-service; UI только инвентаризирует."
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user