docs: права преподавателя, аудит, CI и итоговые проверки
This commit is contained in:
@@ -2,23 +2,22 @@
|
||||
|
||||
**Дата:** 2026-10-27.
|
||||
**Ветка:** `feature/groups-courses`.
|
||||
**Статус:** проверка этапа 6 выполнена; этап 6 закрыт **не полностью**.
|
||||
Миграции, security-матрица, upgrade-тест, видимость активов и deploy-runbook
|
||||
проверены. Открытыми остаются права преподавателя на запись, аудит и CI — см.
|
||||
раздел 4.
|
||||
**Статус:** этап 6 закрыт. Остаточные риски перечислены в разделе 4 и не
|
||||
блокируют передачу.
|
||||
|
||||
## 1. Итоговый объём этапов 0–5
|
||||
## 1. Итоговый объём этапов 0–6
|
||||
|
||||
| Этап | Что реализовано | Отчёт |
|
||||
|---|---|---|
|
||||
| 0 | Схема и контракты: identity `V4–V9`, academic `V6–V12`, progress `V13`; preflight-скрипты; ADR; контракт профиля студента курса | [10](10-stage0-report.md) |
|
||||
| 0 | Схема и контракты: identity `V4–V10` (`V10` — аудит, добавлена на этапе 6), academic `V6–V12`, progress `V13`; preflight-скрипты; ADR; контракт профиля студента курса | [10](10-stage0-report.md) |
|
||||
| 1 | Роли `ADMIN/TEACHER/STUDENT`, группы-когорты и парсер имени, статусы студентов и аккаунтов, `membership_history`, импорт v2 | [11](11-stage1-report.md) |
|
||||
| 2 | Курсы, `course_no` для браузерных URL, история преподавателей и групп, bootstrap курса C, привязка лабораторных к курсу, копирование лабораторных, админ-UI | [12](12-stage2-report.md) |
|
||||
| 3 | Аккаунты преподавателей, курсовая область видимости, внутренние API identity с `LMS_INTERNAL_TOKEN`, кабинет преподавателя, профиль студента курса и скачивание всех попыток | [13](13-stage3-report.md) |
|
||||
| 4 | Повышение (dry-run/apply/rollback, планировщик 31 августа МСК), академотпуск, отчисление, выпуск, архив/восстановление, усиленное удаление, блокировка входа | [14](14-stage4-report.md) |
|
||||
| 5 | Курсовые материалы `/learn/course/{course_no}/...`, публичные глобальные материалы `ALL`, скрытые материалы для преподавателя, перепроверка статуса web-сессии | [15](15-stage5-course-materials.md) |
|
||||
| 6 | Права преподавателя на запись (лабораторные, ведомость, группы, материалы), центральный аудит `identity.audit_events`, CI merge gate, hardening и deploy-runbook | этот отчёт |
|
||||
|
||||
Ключевые инварианты, действующие после этапов 0–5:
|
||||
Ключевые инварианты, действующие после этапов 0–6:
|
||||
|
||||
- все лабораторные принадлежат курсу; существующие лабораторные, главы и
|
||||
активные группы перенесены в курс C;
|
||||
@@ -29,9 +28,15 @@
|
||||
- активы наследуют видимость статьи → раздела → главы: курсовые требуют доступа
|
||||
к курсу, глобальные — аудитории, а старые файлы без привязки остаются
|
||||
публичными (см. раздел 4);
|
||||
- преподаватель своего курса может писать: лабораторные/уровни/варианты,
|
||||
ведомость, привязка групп и авторские материалы курса; чужие курс, лаба,
|
||||
студент и группа отклоняются на сервисах;
|
||||
- вход закрыт для отчисленных, выпущенных и архивных студентов, а также для
|
||||
архивных аккаунтов; web дополнительно перепроверяет статус;
|
||||
- повышение и жизненный цикл пишут историю; применение повышения идемпотентно.
|
||||
- повышение и жизненный цикл пишут историю; применение повышения идемпотентно;
|
||||
- чувствительные действия пишутся в центральный `identity.audit_events`,
|
||||
админ смотрит журнал на `/admin/audit`;
|
||||
- CI на каждый push/MR прогоняет `test ktlintCheck detekt`.
|
||||
|
||||
## 2. Проверка
|
||||
|
||||
@@ -40,15 +45,11 @@
|
||||
Выполнено 2026-10-27:
|
||||
|
||||
```bash
|
||||
./gradlew test ktlintCheck detekt
|
||||
./gradlew --no-daemon test ktlintCheck detekt
|
||||
```
|
||||
|
||||
Результат: `BUILD SUCCESSFUL`, код возврата `0`, все модули зелёные.
|
||||
|
||||
Счётчики включают новые тесты видимости активов: `academic-service` +9
|
||||
(`ContentAssetAccessIntegrationTest`, импорт активов), `web` +2
|
||||
(`ApiClientContentAssetTest`). Итог — **183 теста, 0 падений**.
|
||||
|
||||
Для получения актуальных счётчиков тесты запущены принудительно:
|
||||
|
||||
```bash
|
||||
@@ -57,16 +58,17 @@
|
||||
|
||||
| Модуль | Тестов | Failures | Errors | Skipped |
|
||||
|---|---:|---:|---:|---:|
|
||||
| identity-service | 62 | 0 | 0 | 0 |
|
||||
| academic-service | 62 | 0 | 0 | 0 |
|
||||
| web | 42 | 0 | 0 | 0 |
|
||||
| progress-service | 12 | 0 | 0 | 0 |
|
||||
| identity-service | 72 | 0 | 0 | 0 |
|
||||
| academic-service | 88 | 0 | 0 | 0 |
|
||||
| progress-service | 33 | 0 | 0 | 0 |
|
||||
| web | 69 | 0 | 0 | 0 |
|
||||
| markdown | 5 | 0 | 0 | 0 |
|
||||
| **Итого** | **183** | **0** | **0** | **0** |
|
||||
| **Итого** | **267** | **0** | **0** | **0** |
|
||||
|
||||
`ktlintCheck` и `detekt` прошли без замечаний. Отдельных unit-тестов у gateway
|
||||
и `common-http` в текущем дереве нет; маршрутизация gateway проверена
|
||||
конфигурацией и security-матрицей ниже.
|
||||
конфигурацией и security-матрицей ниже. Тот же набор команд выполняет
|
||||
`.gitlab-ci.yml`.
|
||||
|
||||
### 2.2. Upgrade-тест миграций
|
||||
|
||||
@@ -79,7 +81,7 @@
|
||||
(`ПИ-11БО`, `ПИ-21БО`), зачисления, семестр, лабораторная, глава/раздел/
|
||||
статья/актив, оценка, комментарий и попытка сдачи.
|
||||
3. Поверх master применены миграции ветки:
|
||||
- identity `V4–V9`;
|
||||
- identity `V4–V10`;
|
||||
- academic `V6–V12`;
|
||||
- progress `V13`.
|
||||
4. Выполнены postflight-запросы и повторное применение `V13`.
|
||||
@@ -90,7 +92,8 @@
|
||||
(`ПИ-11БО` → поток `ПИ`, курс 1, номер 1, `БО`, год набора 2026; `ПИ-21БО` →
|
||||
курс 2, год 2025); статус `ACTIVE`; созданы `promotion_runs`,
|
||||
`promotion_run_items`; зачисления перенесены в `membership_history`
|
||||
действием `ENROLL`; добавлен `accounts.status`.
|
||||
действием `ENROLL`; добавлен `accounts.status`; `V10` создаёт
|
||||
`identity.audit_events` без внешних ключей и индексы по `occurred_at`/`action`.
|
||||
- **academic:** добавлены `labs.course_id`, `chapters.course_id/audience`,
|
||||
индексы `labs_number_course_uidx`, `chapters_slug_global_uidx`,
|
||||
`chapters_slug_course_uidx`, `article_assets_article_idx`,
|
||||
@@ -116,8 +119,9 @@
|
||||
|
||||
### 2.3. Security-проверки
|
||||
|
||||
Проверено автоматическими тестами (полный прогон 183 теста) и разбором
|
||||
конфигурации gateway.
|
||||
Проверено автоматическими тестами (полный прогон 267 тестов) и разбором
|
||||
конфигурации gateway. Запись преподавателя дополнительно покрыта
|
||||
negative-тестами «свой/чужой курс».
|
||||
|
||||
| Сценарий | Ожидание | Факт |
|
||||
|---|---|---|
|
||||
@@ -131,8 +135,11 @@
|
||||
| Глобальный `STUDENTS`-актив → преподаватель | `403` | `403`; студент получает `200` |
|
||||
| Legacy-актив без связи со статьёй | публично | `200`; остаточный риск, раздел 4 |
|
||||
| Админ → курс, профиль, скачивание | полный доступ | доступ к любому курсу и студенту, скачивание любой попытки курса |
|
||||
| Преподаватель своего курса | курс, группы, профиль, материалы | видит опубликованные и скрытые материалы, всех студентов активной группы курса, неопубликованные оценки и все попытки |
|
||||
| Преподаватель чужого курса | `403` | `403` на материалы, курс по `course_no`, состав группы и профиль |
|
||||
| Преподаватель своего курса → чтение | курс, группы, профиль, материалы | видит опубликованные и скрытые материалы, всех студентов активной группы курса, неопубликованные оценки и все попытки |
|
||||
| Преподаватель своего курса → запись | лабы/уровни/варианты, ведомость, группы, материалы | оценки/комментарии, публикация, уровни/варианты и раздача, защиты (код/reset), привязка групп, авторство материалов; проверки в progress `CourseScopeService` и academic course/content authoring |
|
||||
| Преподаватель чужого курса | `403` | `403` на чтение и запись: материалы, курс по `course_no`, состав группы, профиль, лабы, оценки, группы |
|
||||
| Аудит → админ | чтение журнала | `GET /api/v1/audit?limit=&action=` и `/admin/audit` доступны только `ADMIN` |
|
||||
| Аудит → запись из сервисов | только по токену | `POST /api/v1/internal/audit` с `X-Internal-Token`; без/неверный токен — `403` |
|
||||
| Студент → свой профиль | только своё и опубликованное | скрытые оценки/комментарии отсутствуют; чужой `studentId` — `403` |
|
||||
| Студент → чужой курс/группа | `403` | доступ только к курсам активной группы; архивный курс — `403` |
|
||||
| Чужая сдача / лаба другого курса | `403` | скачивание отклоняется; своя попытка курса скачивается ZIP |
|
||||
@@ -142,7 +149,8 @@
|
||||
| Web-сессия | быстрый отзыв доступа | статус перепроверяется не реже чем раз в 45 секунд; `active=false` или `401/403` инвалидируют сессию; недоступность identity не разлогинивает всех |
|
||||
|
||||
Остаточные риски перечислены в разделе 4: legacy-активы без привязки,
|
||||
права преподавателя на запись и 30-дневный JWT для прямого API.
|
||||
30-дневный JWT для прямого API, ведущие нули номера группы и отсутствие
|
||||
пагинации/N+1 на больших объёмах.
|
||||
|
||||
## 3. Чек-лист деплоя
|
||||
|
||||
@@ -158,14 +166,18 @@
|
||||
Там же задан `LMS_DB_PASSWORD`; `.env.prod` не коммитится.
|
||||
- [ ] Проверить, что на сервере нет незакоммиченных изменений и выполнен
|
||||
`git pull` нужной ветки/тега.
|
||||
- [ ] **CI зелёный.** `.gitlab-ci.yml` выполняет
|
||||
`./gradlew --no-daemon test ktlintCheck detekt`. Runner должен разрешать
|
||||
privileged-сервисы `docker:dind`: Testcontainers поднимает PostgreSQL 18
|
||||
через Docker-in-Docker.
|
||||
|
||||
### 3.2. Порядок выкладки
|
||||
|
||||
1. **Flyway.** `./deploy/deploy.sh up` собирает образы и запускает
|
||||
`postgres → identity → academic → progress → gateway → web`. Миграции
|
||||
применяются автоматически при старте сервисов:
|
||||
identity `V4–V9`, academic `V6–V12`, progress `V13`.
|
||||
Отдельный ручной запуск Flyway не нужен.
|
||||
identity `V4–V10` (`V10` — `identity.audit_events`), academic `V6–V12`,
|
||||
progress `V13`. Отдельный ручной запуск Flyway не нужен.
|
||||
2. **Bootstrap курса C.** После входа админа открыть `/admin/courses`.
|
||||
Пока курсов нет, показан блок первичной настройки. Bootstrap идемпотентно
|
||||
создаёт/находит курс «Промышленное программирование на языке C»,
|
||||
@@ -181,7 +193,8 @@
|
||||
- `chapters_without_course = 0` (если все текущие материалы курсовые);
|
||||
- ровно один курс C;
|
||||
- `comments_without_author` соответствует старым комментариям;
|
||||
- группы без полного разбора отсутствуют.
|
||||
- группы без полного разбора отсутствуют;
|
||||
- таблица `identity.audit_events` существует (`V10`).
|
||||
4. **V10 → V13 — только dev/staging.** На production/master записи
|
||||
`progress V10__profile_authors.sql` нет, ничего удалять не нужно. Не путать
|
||||
с academic `V10__course_title_unique.sql` — он относится к другой схеме и
|
||||
@@ -204,8 +217,12 @@
|
||||
отдаёт `UP`.
|
||||
- [ ] **Админ:** вход, `/admin/courses` (курс C и счётчики), `/admin/groups`,
|
||||
`/admin/labs`, `/admin/teachers`, `/admin/promotion`.
|
||||
- [ ] **Преподаватель:** вход, `/teacher`, карточка своего курса, состав группы,
|
||||
профиль студента, скачивание попытки, курсовые материалы.
|
||||
- [ ] **Преподаватель:** вход, `/teacher`, карточка своего курса (имена групп,
|
||||
привязка/отвязка группы по `available-groups`), ведомость
|
||||
`/admin/groups/{groupId}?courseId=...` без админских контролов, оценка и
|
||||
публикация, уровень/вариант и раздача, защита (код/reset), профиль студента,
|
||||
скачивание попытки и ZIP группы, авторство материалов
|
||||
`/teacher/courses/{id}/content`.
|
||||
- [ ] **Студент:** вход, дашборд, лабораторная, сдача/история, курсовые
|
||||
материалы; видны только опубликованные оценки и комментарии.
|
||||
- [ ] **Материалы курса:** админ и преподаватель видят скрытые статьи, студент —
|
||||
@@ -221,6 +238,10 @@
|
||||
- [ ] **Повышение:** `/admin/promotion` строит dry-run за год, данные не
|
||||
меняются; применение — только после проверки плана; откат доступен для
|
||||
завершённого запуска. На production не применять план «на всякий случай».
|
||||
- [ ] **Аудит:** `/admin/audit` открывается у админа, показывает события
|
||||
(например, `PROFILE_VIEW`, `SUBMISSION_DOWNLOAD`, `GRADE_SET`,
|
||||
`CONTENT_*`, `TEACHER_*`) с фильтром по действию и временем МСК;
|
||||
преподаватель/студент получают `403`.
|
||||
- [ ] Проверить логи `web`, `gateway`, `identity`, `academic`, `progress` на
|
||||
`ERROR`/`WARN` после smoke.
|
||||
|
||||
@@ -233,59 +254,39 @@
|
||||
остаются публичными. Нужна инвентаризация и привязка либо удаление таких
|
||||
файлов; до этого fallback сохраняется, чтобы не ломать старые ссылки.
|
||||
Детали — в [15](15-stage5-course-materials.md).
|
||||
2. **Права преподавателя в основном read-only.** Кабинет преподавателя даёт
|
||||
обзор курса, групп, лабораторных и профилей. Авторство лабораторных и
|
||||
материалов, распределение вариантов, выставление оценок, комментарии,
|
||||
коды защиты и действия защиты остаются admin-only в UI/API. ТЗ
|
||||
([07](07-groups-courses.md), раздел ролей) ожидает преподавательский
|
||||
self-service; это главный функциональный разрыв.
|
||||
3. **Названия групп в кабинете преподавателя.** На странице курса группа
|
||||
показывается UUID: academic отдаёт `groupId`, а имя группы не входит в
|
||||
контракт карточки курса. Нужен batch-запрос имён в identity или расширение
|
||||
DTO. Тест `TeacherGroupTemplateTest` фиксирует fallback на id.
|
||||
4. **Аудит чувствительных действий.** Есть `membership_history` (переводы,
|
||||
академ, отчисление, архив, повышение) и журнал `promotion_runs/items`.
|
||||
Отдельного audit-log для скачиваний файлов, просмотра профилей,
|
||||
назначений преподавателей и публикаций нет. Нужна таблица `audit_events`
|
||||
и запись из сервисов/BFF.
|
||||
5. **Старый формат импорта не поддерживается.** Импорт принимает только
|
||||
2. **Старый формат импорта не поддерживается.** Импорт принимает только
|
||||
шаблон v2 (поток/курс/номер/степень/год/ФИО/№/база). Ведущие нули номера
|
||||
группы нормализуются к числу (`01` → `1`), поэтому `ПИ-01БО` и `ПИ-1БО`
|
||||
неразличимы; для разных когорт нужен строковый номер. Ограничение
|
||||
зафиксировано в [11](11-stage1-report.md).
|
||||
6. **JWT живёт до 30 дней.** Web-сессия перепроверяет статус каждые 45 секунд
|
||||
3. **JWT живёт до 30 дней.** Web-сессия перепроверяет статус каждые 45 секунд
|
||||
и закрывает UI при отчислении/архиве, но уже выданный JWT для прямого
|
||||
вызова API действует до истечения. Нужны короткий TTL + refresh,
|
||||
denylist/revocation или проверка статуса на каждом API-вызове.
|
||||
7. **Нет CI.** В репозитории нет pipeline (`.github` и аналогов); гейтом
|
||||
служит локальный `./gradlew test ktlintCheck detekt`. Это не защищает
|
||||
ветку от merge без прогона и не проверяет upgrade-тест автоматически.
|
||||
4. **Пагинация и N+1.** Журнал аудита ограничен `limit` (максимум 500), но
|
||||
без offset/keyset; часть списков групп, материалов и ведомости отдаётся
|
||||
целиком. На больших курсах нужны пагинация и пакетные выборки вместо
|
||||
запросов в цикле.
|
||||
|
||||
## 5. Рекомендуемые следующие шаги
|
||||
|
||||
Приоритет — сверху вниз:
|
||||
|
||||
1. **Права преподавателя на запись.** Дать преподавателю своего курса
|
||||
authoring лабораторных/материалов, распределение вариантов, выставление
|
||||
оценок и комментариев, коды и действия защиты; закрыть negative-тестами
|
||||
«свой/чужой курс». Это закрывает основной разрыв с ТЗ.
|
||||
2. **Инвентаризация legacy-активов.** Найти файлы без строк в `article_assets`,
|
||||
1. **Инвентаризация legacy-активов.** Найти файлы без строк в `article_assets`,
|
||||
привязать их к статьям или удалить; после этого можно убрать публичный
|
||||
fallback для несвязанных файлов.
|
||||
3. **UI авторства контента.** Выбор курса и аудитории при создании глав/
|
||||
статей, чтобы преподаватель и админ не работали только через импорт;
|
||||
сейчас глобальные главы создаются с `ALL`, курсовые — без аудитории.
|
||||
4. **Аудит.** Ввести `audit_events` для скачиваний, профилей, назначений,
|
||||
публикаций, повышения и жизненного цикла; экран просмотра для админа.
|
||||
5. **CI.** Пайплайн на каждый push/MR: `test ktlintCheck detekt`, отдельный
|
||||
шаг upgrade-теста миграций на PostgreSQL 18, сборка `bootJar`; запрет
|
||||
merge при красном статусе.
|
||||
2. **JWT/refresh.** Короткий TTL access-токена + refresh, denylist/revocation
|
||||
или проверка статуса на каждом API-вызове; закрыть остаточный риск
|
||||
отчисленного/архивного пользователя с уже выданным токеном.
|
||||
3. **Пагинация и устранение N+1.** Keyset/offset для аудита, групп, материалов
|
||||
и ведомости; пакетные выборки профилей, комментариев и сдач.
|
||||
4. **Внешняя интеграция «Контингент»/next.uniyar.ac.ru.** Отдельный
|
||||
anti-corruption слой, идемпотентная синхронизация, журнал и отчёт дрифта;
|
||||
внешние идентификаторы добавляются миграцией.
|
||||
|
||||
## 6. Итог
|
||||
|
||||
Этапы 0–5 реализованы и проверены. Security-матрица, upgrade-тест миграций,
|
||||
видимость активов и deploy-runbook этапа 6 зелёные; отчёт и чек-лист передачи
|
||||
готовы. Этап 6 **нельзя считать полностью завершённым** до закрытия прав
|
||||
преподавателя и CI. Видимость связанных активов реализована; публичными
|
||||
остаются только старые файлы без привязки к статье (см. раздел 4), а
|
||||
преподаватели пока работают в режиме просмотра.
|
||||
Этап 6 закрыт: права преподавателя на запись, аудит, CI, security-матрица,
|
||||
upgrade-тест миграций, видимость активов и deploy-runbook проверены.
|
||||
Остаточные риски (legacy-активы, 30-дневный JWT, ведущие нули номера группы,
|
||||
пагинация/N+1) задокументированы в разделе 4 и не блокируют передачу.
|
||||
|
||||
Reference in New Issue
Block a user