docs: закрытие продуктовых рисков и новый LDD baseline
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
# 16. Отчёт этапа 6: проверка, hardening и передача
|
||||
|
||||
**Дата:** 2026-10-27.
|
||||
**Дата:** 2026-10-27, обновлено 2026-10-28 после закрытия продуктовых рисков.
|
||||
**Ветка:** `feature/groups-courses`.
|
||||
**Статус:** этап 6 закрыт. Остаточные риски перечислены в разделе 4 и не
|
||||
блокируют передачу.
|
||||
**Статус:** этап 6 закрыт. Продуктовые риски (legacy-активы, срок жизни JWT,
|
||||
сессии, пагинация аудита и N+1 имён групп) закрыты; оставшиеся ограничения
|
||||
перечислены в разделе 4 и не блокируют передачу.
|
||||
|
||||
## 1. Итоговый объём этапов 0–6
|
||||
|
||||
| Этап | Что реализовано | Отчёт |
|
||||
|---|---|---|
|
||||
| 0 | Схема и контракты: identity `V4–V10` (`V10` — аудит, добавлена на этапе 6), academic `V6–V12`, progress `V13`; preflight-скрипты; ADR; контракт профиля студента курса | [10](10-stage0-report.md) |
|
||||
| 0 | Схема и контракты: identity `V4–V11` (`V10` — аудит, `V11` — refresh-токены), 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) |
|
||||
@@ -26,8 +27,11 @@
|
||||
активной группы; админ — все курсы;
|
||||
- студент видит только опубликованные оценки и комментарии;
|
||||
- активы наследуют видимость статьи → раздела → главы: курсовые требуют доступа
|
||||
к курсу, глобальные — аудитории, а старые файлы без привязки остаются
|
||||
публичными (см. раздел 4);
|
||||
к курсу, глобальные — аудитории, а файлы без связи со статьёй по умолчанию
|
||||
недоступны (`404`) всем, кроме админа; админ перечисляет их через
|
||||
`GET /api/v1/content/legacy-assets`;
|
||||
- access-токен живёт 15 минут, refresh — 7 дней с ротацией и отзывом; web-сессия
|
||||
скользящая на 7 дней и сама обновляет access-токен;
|
||||
- преподаватель своего курса может писать: лабораторные/уровни/варианты,
|
||||
ведомость, привязка групп и авторские материалы курса; чужие курс, лаба,
|
||||
студент и группа отклоняются на сервисах;
|
||||
@@ -63,12 +67,13 @@
|
||||
| progress-service | 33 | 0 | 0 | 0 |
|
||||
| web | 69 | 0 | 0 | 0 |
|
||||
| markdown | 5 | 0 | 0 | 0 |
|
||||
| **Итого** | **267** | **0** | **0** | **0** |
|
||||
| **Итого на момент этапа 6** | **267** | **0** | **0** | **0** |
|
||||
|
||||
`ktlintCheck` и `detekt` прошли без замечаний. Отдельных unit-тестов у gateway
|
||||
и `common-http` в текущем дереве нет; маршрутизация gateway проверена
|
||||
конфигурацией и security-матрицей ниже. Тот же набор команд выполняет
|
||||
`.gitlab-ci.yml`.
|
||||
После закрытия рисков актуальный LDD-baseline — **319 тестов, 0 failures**
|
||||
(см. раздел 7). `ktlintCheck` и `detekt` прошли без замечаний. У gateway теперь есть unit-тесты публичных auth-путей и 401; маршрутизация
|
||||
gateway дополнительно проверена конфигурацией и security-матрицей ниже.
|
||||
Тот же набор команд выполняет `.gitlab-ci.yml`; на каждый push/MR добавлен
|
||||
smoke-job, полный release-evidence обязателен для тегов.
|
||||
|
||||
### 2.2. Upgrade-тест миграций
|
||||
|
||||
@@ -81,7 +86,7 @@
|
||||
(`ПИ-11БО`, `ПИ-21БО`), зачисления, семестр, лабораторная, глава/раздел/
|
||||
статья/актив, оценка, комментарий и попытка сдачи.
|
||||
3. Поверх master применены миграции ветки:
|
||||
- identity `V4–V10`;
|
||||
- identity `V4–V11`;
|
||||
- academic `V6–V12`;
|
||||
- progress `V13`.
|
||||
4. Выполнены postflight-запросы и повторное применение `V13`.
|
||||
@@ -93,7 +98,9 @@
|
||||
курс 2, год 2025); статус `ACTIVE`; созданы `promotion_runs`,
|
||||
`promotion_run_items`; зачисления перенесены в `membership_history`
|
||||
действием `ENROLL`; добавлен `accounts.status`; `V10` создаёт
|
||||
`identity.audit_events` без внешних ключей и индексы по `occurred_at`/`action`.
|
||||
`identity.audit_events` без внешних ключей и индексы по `occurred_at`/`action`;
|
||||
`V11` создаёт `identity.refresh_tokens` (SHA-256-хеш, `expires_at`,
|
||||
`revoked_at`, `replaced_by`, индексы по аккаунту и сроку).
|
||||
- **academic:** добавлены `labs.course_id`, `chapters.course_id/audience`,
|
||||
индексы `labs_number_course_uidx`, `chapters_slug_global_uidx`,
|
||||
`chapters_slug_course_uidx`, `article_assets_article_idx`,
|
||||
@@ -119,8 +126,8 @@
|
||||
|
||||
### 2.3. Security-проверки
|
||||
|
||||
Проверено автоматическими тестами (полный прогон 267 тестов) и разбором
|
||||
конфигурации gateway. Запись преподавателя дополнительно покрыта
|
||||
Проверено автоматическими тестами (актуальный прогон — 319 тестов, 0 failures)
|
||||
и разбором конфигурации gateway. Запись преподавателя дополнительно покрыта
|
||||
negative-тестами «свой/чужой курс».
|
||||
|
||||
| Сценарий | Ожидание | Факт |
|
||||
@@ -133,24 +140,28 @@ negative-тестами «свой/чужой курс».
|
||||
| Студент/преподаватель чужого курса → курсовой актив | `403` | `403` |
|
||||
| Преподаватель своего курса → скрытый курсовой актив | доступ | `200`; админ видит любой актив |
|
||||
| Глобальный `STUDENTS`-актив → преподаватель | `403` | `403`; студент получает `200` |
|
||||
| Legacy-актив без связи со статьёй | публично | `200`; остаточный риск, раздел 4 |
|
||||
| Legacy-актив без связи со статьёй | `404` для анонима/студента/преподавателя, файл доступен только админу | `404` всем, кроме `ADMIN`; `GET /api/v1/content/legacy-assets` под `ROLE_ADMIN` перечисляет несвязанные файлы |
|
||||
| Access/refresh | короткий access + ротация refresh | access JWT 15 минут (`LMS_ACCESS_TOKEN_TTL`), refresh 7 дней (`LMS_REFRESH_TOKEN_TTL`); `POST /api/v1/auth/refresh` ротирует пару, повторное использование старого токена отзывает все сессии аккаунта |
|
||||
| Logout / смена пароля | сессии отзываются | `POST /api/v1/auth/logout` отзывает refresh; смена пароля — `revokeAll` |
|
||||
| Архив/отчисление/закрытая группа → refresh | обновление запрещено | `ensureSessionAllowed` при ротации: `EXPELLED`, `GRADUATED`, `ARCHIVED`, архивный аккаунт и `GRADUATED`/`ARCHIVED` группа дают `401` |
|
||||
| Web-сессия | 7 дней sliding, без повторного входа, быстрый отзыв | cookie `LMS_SESSION`, `Max-Age` 7 дней (`LMS_SESSION_TIMEOUT`), `HttpOnly`/`SameSite=Lax`; access обновляется за 2 минуты до истечения, cookie перевыпускается на каждом ответе; статус перепроверяется не реже чем раз в 45 секунд, `active=false` или `401/403` инвалидируют сессию, недоступность identity не разлогинивает всех; выход отзывает refresh |
|
||||
| Админ → курс, профиль, скачивание | полный доступ | доступ к любому курсу и студенту, скачивание любой попытки курса |
|
||||
| Преподаватель своего курса → чтение | курс, группы, профиль, материалы | видит опубликованные и скрытые материалы, всех студентов активной группы курса, неопубликованные оценки и все попытки |
|
||||
| Преподаватель своего курса → чтение | курс, группы, профиль, материалы | видит опубликованные и скрытые материалы, всех студентов активной группы курса, неопубликованные оценки и все попытки; `GET /api/v1/courses/{id}/groups` отдаёт привязанные группы с именами одним вызовом (без N+1) |
|
||||
| Преподаватель своего курса → запись | лабы/уровни/варианты, ведомость, группы, материалы | оценки/комментарии, публикация, уровни/варианты и раздача, защиты (код/reset), привязка групп, авторство материалов; проверки в progress `CourseScopeService` и academic course/content authoring |
|
||||
| Преподаватель чужого курса | `403` | `403` на чтение и запись: материалы, курс по `course_no`, состав группы, профиль, лабы, оценки, группы |
|
||||
| Аудит → админ | чтение журнала | `GET /api/v1/audit?limit=&action=` и `/admin/audit` доступны только `ADMIN` |
|
||||
| Аудит → админ | чтение журнала | `GET /api/v1/audit?page=&limit=&action=` и `/admin/audit` доступны только `ADMIN`; в UI — постраничный переход «Показать ещё» |
|
||||
| Аудит → запись из сервисов | только по токену | `POST /api/v1/internal/audit` с `X-Internal-Token`; без/неверный токен — `403` |
|
||||
| Студент → свой профиль | только своё и опубликованное | скрытые оценки/комментарии отсутствуют; чужой `studentId` — `403` |
|
||||
| Студент → чужой курс/группа | `403` | доступ только к курсам активной группы; архивный курс — `403` |
|
||||
| Чужая сдача / лаба другого курса | `403` | скачивание отклоняется; своя попытка курса скачивается ZIP |
|
||||
| Внутренние API identity | только по токену | неверный/пустой `X-Internal-Token` — `403`, сравнение constant-time; неизвестный id — `404` |
|
||||
| Gateway | внутренние пути не публикуются | маршрутов на `/api/v1/internal/**` нет; gateway требует JWT для всего, кроме `/actuator`, входа/setup/JWKS и публичного `/api/v1/content/**` |
|
||||
| Gateway | внутренние пути не публикуются | маршрутов на `/api/v1/internal/**` нет; gateway требует JWT для всего, кроме `/actuator`, входа/setup/JWKS/refresh/logout и публичного `/api/v1/content/**` |
|
||||
| Gateway → заголовки личности | клиент не подменяет | `X-Account-Id/Login/Role` удаляются и проставляются только после проверки JWT |
|
||||
| Web-сессия | быстрый отзыв доступа | статус перепроверяется не реже чем раз в 45 секунд; `active=false` или `401/403` инвалидируют сессию; недоступность identity не разлогинивает всех |
|
||||
|
||||
Остаточные риски перечислены в разделе 4: legacy-активы без привязки,
|
||||
30-дневный JWT для прямого API, ведущие нули номера группы и отсутствие
|
||||
пагинации/N+1 на больших объёмах.
|
||||
Остаточные ограничения перечислены в разделе 4: offset-пагинация аудита,
|
||||
N+1 в отдельных лабораторных списках и отсутствие абсолютного лимита
|
||||
web-сессии. Продуктовые риски legacy-активов, 30-дневного JWT и ведущих
|
||||
нулей номера группы закрыты или сняты как не относящиеся к требованиям.
|
||||
|
||||
## 3. Чек-лист деплоя
|
||||
|
||||
@@ -166,18 +177,25 @@ negative-тестами «свой/чужой курс».
|
||||
Там же задан `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.
|
||||
- [ ] **CI зелёный.** `.gitlab-ci.yml` на каждом push/MR выполняет
|
||||
`test ktlintCheck detekt evidenceCheck` и job `smoke`
|
||||
(`scripts/ldd-smoke.sh`: полный стек, health, вход админа, один курс).
|
||||
Полный `release-evidence` обязателен для тегов и запускается вручную для
|
||||
веток. `check` использует privileged-сервисы `docker:dind` для Testcontainers;
|
||||
`smoke`/`release-evidence` — shell-runner с Java 21, Docker Compose v2,
|
||||
Python 3, Node и Chromium Playwright.
|
||||
- [ ] Заданы сроки сессии: `LMS_ACCESS_TOKEN_TTL=15m`,
|
||||
`LMS_REFRESH_TOKEN_TTL=7d`, `LMS_SESSION_TIMEOUT=7d` (значения по умолчанию
|
||||
подходят; менять только осознанно).
|
||||
|
||||
### 3.2. Порядок выкладки
|
||||
|
||||
1. **Flyway.** `./deploy/deploy.sh up` собирает образы и запускает
|
||||
`postgres → identity → academic → progress → gateway → web`. Миграции
|
||||
применяются автоматически при старте сервисов:
|
||||
identity `V4–V10` (`V10` — `identity.audit_events`), academic `V6–V12`,
|
||||
progress `V13`. Отдельный ручной запуск Flyway не нужен.
|
||||
identity `V4–V11` (`V10` — `identity.audit_events`, `V11` —
|
||||
`identity.refresh_tokens`), academic `V6–V12`, progress `V13`.
|
||||
Отдельный ручной запуск Flyway не нужен.
|
||||
2. **Bootstrap курса C.** После входа админа открыть `/admin/courses`.
|
||||
Пока курсов нет, показан блок первичной настройки. Bootstrap идемпотентно
|
||||
создаёт/находит курс «Промышленное программирование на языке C»,
|
||||
@@ -194,7 +212,8 @@ negative-тестами «свой/чужой курс».
|
||||
- ровно один курс C;
|
||||
- `comments_without_author` соответствует старым комментариям;
|
||||
- группы без полного разбора отсутствуют;
|
||||
- таблица `identity.audit_events` существует (`V10`).
|
||||
- таблица `identity.audit_events` существует (`V10`);
|
||||
- таблица `identity.refresh_tokens` существует (`V11`).
|
||||
4. **V10 → V13 — только dev/staging.** На production/master записи
|
||||
`progress V10__profile_authors.sql` нет, ничего удалять не нужно. Не путать
|
||||
с academic `V10__course_title_unique.sql` — он относится к другой схеме и
|
||||
@@ -229,7 +248,10 @@ negative-тестами «свой/чужой курс».
|
||||
только опубликованные; чужой курс даёт `403`, аноним уходит на `/login`.
|
||||
- [ ] **Активы:** картинки курсовой статьи открываются у студента своего курса
|
||||
и преподавателя; чужой курс — `403`, скрытая картинка у студента — `404`,
|
||||
публичная `ALL` — без входа; старый файл без привязки — публичен.
|
||||
публичная `ALL` — без входа; файл без привязки к статье — `404` для всех,
|
||||
кроме админа. Админ проверяет инвентаризацию через
|
||||
`GET /api/v1/content/legacy-assets` (или админский UI/curl) и при
|
||||
необходимости привязывает или удаляет файлы.
|
||||
- [ ] **Копирование лабораторной:** админ копирует лабораторную в другой курс;
|
||||
создаётся черновик с уровнями и вариантами; оценки, сдачи, защиты и
|
||||
назначения не копируются; занятый номер даёт `409`.
|
||||
@@ -241,45 +263,48 @@ negative-тестами «свой/чужой курс».
|
||||
- [ ] **Аудит:** `/admin/audit` открывается у админа, показывает события
|
||||
(например, `PROFILE_VIEW`, `SUBMISSION_DOWNLOAD`, `GRADE_SET`,
|
||||
`CONTENT_*`, `TEACHER_*`) с фильтром по действию и временем МСК;
|
||||
«Показать ещё» переходит на следующую страницу (`page`/`limit`);
|
||||
преподаватель/студент получают `403`.
|
||||
- [ ] **Сессия:** после входа cookie `LMS_SESSION` живёт 7 дней; при работе
|
||||
дольше 15 минут страница продолжает работать без повторного входа
|
||||
(access обновляется автоматически). Выход и смена пароля требуют повторного
|
||||
входа; вход отчисленного/архивного студента и refresh закрытой группы
|
||||
отклоняются.
|
||||
- [ ] Проверить логи `web`, `gateway`, `identity`, `academic`, `progress` на
|
||||
`ERROR`/`WARN` после smoke.
|
||||
|
||||
## 4. Известные пробелы и follow-up
|
||||
|
||||
1. **Legacy-активы без привязки.** Видимость связанных активов реализована:
|
||||
`V12` восстанавливает `article_assets` по markdown-ссылкам, а маршрут
|
||||
`/api/v1/content/assets/{name}` проверяет курс/аудиторию и публикацию.
|
||||
Остаточный случай — файлы, на которые нет ссылок ни в одной статье: они
|
||||
остаются публичными. Нужна инвентаризация и привязка либо удаление таких
|
||||
файлов; до этого fallback сохраняется, чтобы не ломать старые ссылки.
|
||||
Детали — в [15](15-stage5-course-materials.md).
|
||||
2. **Старый формат импорта не поддерживается.** Импорт принимает только
|
||||
шаблон v2 (поток/курс/номер/степень/год/ФИО/№/база). Ведущие нули номера
|
||||
группы нормализуются к числу (`01` → `1`), поэтому `ПИ-01БО` и `ПИ-1БО`
|
||||
неразличимы; для разных когорт нужен строковый номер. Ограничение
|
||||
зафиксировано в [11](11-stage1-report.md).
|
||||
3. **JWT живёт до 30 дней.** Web-сессия перепроверяет статус каждые 45 секунд
|
||||
и закрывает UI при отчислении/архиве, но уже выданный JWT для прямого
|
||||
вызова API действует до истечения. Нужны короткий TTL + refresh,
|
||||
denylist/revocation или проверка статуса на каждом API-вызове.
|
||||
4. **Пагинация и N+1.** Журнал аудита ограничен `limit` (максимум 500), но
|
||||
без offset/keyset; часть списков групп, материалов и ведомости отдаётся
|
||||
целиком. На больших курсах нужны пагинация и пакетные выборки вместо
|
||||
запросов в цикле.
|
||||
1. **Старый формат импорта не поддерживается.** Импорт принимает только
|
||||
шаблон v2 (поток/курс/номер/степень/год/ФИО/№/база). Ограничение
|
||||
зафиксировано в [11](11-stage1-report.md). Номер группы — числовой, поэтому
|
||||
отдельного требования к ведущим нулям нет.
|
||||
2. **Пагинация аудита — offset.** `GET /api/v1/audit` поддерживает `page` и
|
||||
`limit` (максимум 500), UI — «Показать ещё». Для очень больших журналов
|
||||
keyset-пагинация была бы устойчивее к вставкам между страницами.
|
||||
3. **N+1 в отдельных списках.** Имена групп курса отдаются одним вызовом
|
||||
(`GET /api/v1/courses/{id}/groups`), но часть лабораторных списков и
|
||||
ведомости на больших объёмах всё ещё собирается по частям. Полный нагрузочный
|
||||
профиль 100 групп / 1000 студентов проходит, однако пакетные выборки для
|
||||
оставшихся экранов — лабораторная деталь, а не блокер передачи.
|
||||
4. **Нет абсолютного лимита web-сессии.** Сессия скользящая (7 дней) и
|
||||
отзывается при выходе, смене пароля, отчислении/архиве и закрытии группы,
|
||||
но при непрерывной активности может жить бессрочно. Если политика требует
|
||||
принудительного повторного входа раз в N дней, нужен абсолютный `max-age`
|
||||
поверх sliding-сессии.
|
||||
|
||||
## 5. Рекомендуемые следующие шаги
|
||||
|
||||
Приоритет — сверху вниз:
|
||||
|
||||
1. **Инвентаризация legacy-активов.** Найти файлы без строк в `article_assets`,
|
||||
привязать их к статьям или удалить; после этого можно убрать публичный
|
||||
fallback для несвязанных файлов.
|
||||
2. **JWT/refresh.** Короткий TTL access-токена + refresh, denylist/revocation
|
||||
или проверка статуса на каждом API-вызове; закрыть остаточный риск
|
||||
отчисленного/архивного пользователя с уже выданным токеном.
|
||||
3. **Пагинация и устранение N+1.** Keyset/offset для аудита, групп, материалов
|
||||
и ведомости; пакетные выборки профилей, комментариев и сдач.
|
||||
1. **Инвентаризация legacy-активов.** Несвязанные файлы уже не публичны; админ
|
||||
получает их список через `GET /api/v1/content/legacy-assets`, после чего
|
||||
привязывает к статьям или удаляет. Это гигиена хранилища, а не открытый
|
||||
риск доступа.
|
||||
2. **Keyset-пагинация и оставшиеся N+1.** Устойчивый курсор для аудита,
|
||||
пакетные выборки для лабораторных списков и ведомости на больших курсах.
|
||||
3. **Абсолютный лимит сессии.** Опционально: принудительный повторный вход
|
||||
раз в N дней поверх 7-дневной sliding-сессии.
|
||||
4. **Внешняя интеграция «Контингент»/next.uniyar.ac.ru.** Отдельный
|
||||
anti-corruption слой, идемпотентная синхронизация, журнал и отчёт дрифта;
|
||||
внешние идентификаторы добавляются миграцией.
|
||||
@@ -288,17 +313,22 @@ negative-тестами «свой/чужой курс».
|
||||
|
||||
Этап 6 закрыт: права преподавателя на запись, аудит, CI, security-матрица,
|
||||
upgrade-тест миграций, видимость активов и deploy-runbook проверены.
|
||||
Остаточные риски (legacy-активы, 30-дневный JWT, ведущие нули номера группы,
|
||||
пагинация/N+1) задокументированы в разделе 4 и не блокируют передачу.
|
||||
Продуктовые риски закрыты: legacy-активы secure-by-default, access/refresh и
|
||||
web-сессия с отзывом, пагинация аудита и bulk-выдача групп. Оставшиеся
|
||||
ограничения (offset-пагинация, отдельные N+1, отсутствие абсолютного лимита
|
||||
сессии) задокументированы в разделе 4 и не блокируют передачу.
|
||||
|
||||
## 7. LDD baseline
|
||||
|
||||
После этапа 6 текущая функциональность проверена через LDD-контур:
|
||||
реестр `docs/evidence/claims.json` (37 claim, 20 P0 / 17 P1), gate
|
||||
`./gradlew evidenceCheck`, upgrade-скрипт, runtime-smoke, mutation testing
|
||||
и браузерный Playwright E2E. Результат: **37/37 claims proven, P0 gaps = 0**,
|
||||
276 тестов; mutation strength `CourseAccessService` 75%,
|
||||
`PromotionPlanner` 69%; browser E2E 4/4; конкурентные apply/rollback и
|
||||
нагрузочный smoke (p95 0.868 c) зелёные. E2E нашёл и закрыл реальный дефект
|
||||
После этапа 6 и закрытия рисков текущая функциональность проверена через
|
||||
LDD-контур: реестр `docs/evidence/claims.json` (**46 claims, 25 P0 / 21 P1**),
|
||||
gate `./gradlew evidenceCheck`, upgrade-скрипт, runtime-smoke, smoke-job,
|
||||
mutation testing и браузерный Playwright E2E. Результат:
|
||||
**46/46 claims proven, P0 gaps = 0, 319 тестов, 0 failures**; mutation strength
|
||||
`CourseAccessService` 75%, `PromotionPlanner` 69%; browser E2E 4/4;
|
||||
конкурентные apply/rollback и масштабный нагрузочный smoke (100 групп /
|
||||
1000 студентов, 125 параллельных чтений, 0 ошибок, p95 < 1 с) зелёные.
|
||||
Smoke-job выполняется на каждом push/MR, полный `release-evidence` обязателен
|
||||
для тегов и доступен вручную для веток. E2E нашёл и закрыл реальный дефект
|
||||
Jackson 3/Kotlin defaults. Подробности и остаточные ограничения —
|
||||
[`docs/evidence/baseline-2026-10-27.md`](evidence/baseline-2026-10-27.md).
|
||||
|
||||
Reference in New Issue
Block a user