33 KiB
33 KiB
Журнал исправления проблем
Обновлено: 2026-07-13 (Europe/Moscow).
Область и неизменяемые данные
- В работе ровно 33 проблемы:
1и3–34. - Проблема № 2 исключена: демонстрационные учётные записи, seed-данные и документация по ним не изменяются.
BUG_REPORT.mdне редактируется.- Существующие Flyway-миграции не редактируются.
Исходное рабочее дерево перед началом реализации было чистым (git status --short без вывода).
Контрольные суммы исходных миграций:
| Файл | SHA-256 |
|---|---|
V1__init.sql |
404f65f270dd87f4db996495fc970456c4fd0f762e7c32638199dffaa812ea7b |
V2__subgroups_active_unique_name.sql |
68f5525a50ddba4f8800a63cab3cd0f84779e301c4a1f29a6e0399f97fbdb30b |
Исходная линия проверок
| Проверка | Результат до изменений |
|---|---|
mvn -f backend/pom.xml test |
Maven на хосте отсутствует. Контейнерный эквивалент maven:3.9-eclipse-temurin-17 завершён успешно: 30 тестов, 0 failures, 0 errors. |
find frontend ... node --check |
Успешно. |
docker compose config --quiet |
Успешно. |
kubectl kustomize ../k8s >/dev/null |
Успешно. |
bash -n ../k8s/deploy.sh |
Успешно. |
git diff --check |
Успешно. |
Порядок выполнения
- Безопасная production-конфигурация и внешние секреты: № 1 (с зависимой частью № 14).
- Аутентификация и границы запроса: № 3, 4, 24, 25, 28.
- Высокоприоритетные инварианты расписания: № 5–8.
- Tenant lifecycle и Kubernetes coordination: № 9, 10, 13, 14.
- Frontend-сессия и достоверность UI: № 11, 12, 33, 34.
- Календарные и DB-инварианты: № 15–17, 21, 31, 32.
- История, права, масштаб и производительность: № 18–23.
- Локальный запуск, CI/CD, воспроизводимые артефакты и timezone: № 26, 27, 29, 30.
- AutoUpdateDocs, полный регрессионный прогон и финальный аудит 33 пунктов.
Чек-лист
Статусы меняются только после реализации и воспроизводимой проверки.
| № | Статус | Подтверждённая первопричина | Решение и основные файлы | Проверка |
|---|---|---|---|---|
| 1 | требуется внешнее действие | В коде и production YAML были встроенные/placeholder JWT и DB credentials; tenant и OTel credentials находились в отслеживаемой конфигурации; Secure-cookie не был обязательным для production. | Встроенный JWT default удалён; добавлен fail-fast validator; production использует только внешние app-secret, tenants-secret, otel-postgres-secret; создан docs/SECURITY_RUNBOOK.md. Репозиторная часть исправлена. Оператору остаются ротация, provisioning, rollout и очистка истории. |
11 JWT unit/context tests; общий backend: 45/0/0; scripts/check-production-secrets.sh; Kustomize и shell syntax — успешно. |
| 3 | исправлено и проверено | Ротация refresh-токена выполняла read-check-write без блокировки/условного update. | RefreshTokenService.rotate() использует блокирующий findByTokenHashForUpdate() с PESSIMISTIC_WRITE и JOIN FETCH внутри транзакции. |
Два синхронных запроса к Spring proxy и PostgreSQL: один успех, один отказ, один активный новый токен. Полный backend: 46/0/0/0. |
| 4 | исправлено и проверено | AuthContext устанавливался до role-check и сохранялся в servlet-потоке, когда preHandle=false не приводил к afterCompletion. |
AuthorizationInterceptor очищает контекст в начале каждого запроса и устанавливает пользователя только после успешной проверки ролей; финальная очистка сохранена. |
Один MockMvc-поток: 403, публичный запрос, разрешённый запрос и очистка после завершения. Полный backend: 47/0/0/0. |
| 5 | исправлено и проверено | Override сохранялся без доказательства исходной пары, action-specific полей и ресурсных конфликтов. | ScheduleOverrideService строит base/effective day, валидирует матрицу и фактическое изменение, проверяет ресурсы и сериализует CRUD через PostgreSQL advisory locks; V3 закрепляет структурные инварианты. |
27 целевых unit/MockMvc/PostgreSQL tests; полный backend: 74/0/0/0. |
| 6 | исправлено и проверено | Teacher-only поиск загружал только базовые правила исходного преподавателя до применения override. | Один snapshot overrides; релевантные newTeacher-замены достраивают exact base occurrences по уникальным датам, затем применяются overrides, финальный teacher-фильтр и deduplicate. |
5 новых A→B/phantom/cache/dedup/regression тестов; узкий прогон 10/0/0/0, полный backend 79/0/0/0. |
| 7 | исправлено и проверено | Контроллер сравнивал новые слоты только с сохранёнными правилами, не проверял роль преподавателя и enum формата, а нечётные часы приводили к перерасходу генератора. | ScheduleRuleService валидирует весь кандидат до мутации, попарно проверяет дубли/ресурсы, требует TEACHER, Очно/Онлайн и чётные часы; строка семестра сериализует запись. V4 закрепляет чётность и точный дубль в БД. |
23 service/MockMvc/Flyway/PostgreSQL tests; полный backend: 102/0/0/0. |
| 8 | исправлено и проверено | saveGrid удалял данные до полной валидации и ловил исключение внутри transactional controller-метода, поэтому Spring коммитил delete и уже обработанные строки. |
AcademicCalendarGridService полностью валидирует и строит replacement до delete, атомарно заменяет строки и очищает кэш через afterCommit; контроллер стал HTTP-адаптером. |
8 unit + 1 PostgreSQL proxy test; fingerprint старой сетки после отказа неизменен; полный backend 111/0/0/0. |
| 9 | в работе | При update старый pool закрывается до создания нового; Hikari допускает ленивый нерабочий pool, Flyway проглатывает ошибку, а отказ Secret-персистенции оставляет локальное состояние изменённым. | Реализуются prepare/validate/migrate кандидата, persistence до atomic swap и компенсация Secret при ошибке активации. | Готовятся unit и PostgreSQL lifecycle tests для credentials/migration/persistence/swap. |
| 10 | не начато | Два pod перезаписывают целый tenant-документ без resourceVersion; TCP probes не отражают готовность tenant-БД. |
Optimistic locking с retry; отдельные liveness/readiness semantics. | Конкурентные PATCH и health tests; Kustomize-проверка probes. |
| 11 | не начато | Sunday вычисляется повторным мутированием даты; ошибки запросов превращаются в пустые результаты и зелёный статус. | Чистая local-date утилита и три явных состояния проверки. | 2026-07-02, переход года, частичная/полная ошибка API. |
| 12 | не начато | Access JWT хранится в Web Storage; origin исполняет удалённые модули; CSP допускает несовместимые сценарии. | Access JWT только в памяти, восстановление через HttpOnly refresh-cookie; локальные pinned assets; строгий CSP и удаление inline JS. | Login/refresh/logout/reload tests; статический аудит CSP/storage/imports. |
| 13 | не начато | Watcher сравнивает только домены и фиксирует hash до успешной полной синхронизации. | Нормализованное сравнение полного TenantConfig, hash после успеха, bounded backoff. |
Изменение URL/credentials и retry после ошибки. |
| 14 | исправлено и проверено | Kubernetes HTTP-клиент имел trust-all TLS fallback. | KubernetesTenantSecretUpdater загружает service-account CA, строит PKIX trust store, включает HTTPS hostname verification и не имеет небезопасного fallback. |
Mock HTTPS: доверенный CA принят; чужой CA, неверный hostname и отсутствующий CA отклонены. Backend 45/0/0. |
| 15 | не начато | Не проверяются start < end и пересечения; duration доверяется клиенту; используемый DEFAULT scope можно изменить. |
Централизованный валидатор, backend duration, DB/concurrency protection. | Overlap/adjacent/scope/concurrency tests. |
| 16 | не начато | Create/update проверяют лишь порядок дат, но не пересечения и принадлежность семестра году. | Единый calendar validator и PostgreSQL range/exclusion constraints. | Create/update + конкурентные PostgreSQL tests. |
| 17 | не начато | Изменение измерений группы/календаря оставляет несовместимые assignments/grid/subjects. | Отклонять несовместимое изменение транзакционно с 409; контролировать courseCount. |
Assignment сохранён после отказа; старшие строки не остаются. |
| 18 | не начато | Бизнес-решения смешивают users.department_id с датированными assignments и фильтруют архивные записи по текущему состоянию. |
Date-aware resolver кафедры и единое использование истории. | Будущий перевод, прошлый период, архивный преподаватель, совместительство. |
| 19 | не начато | Workload принимает произвольный departmentId для роли DEPARTMENT. |
Принудительный scope из AuthContext; глобальный доступ только разрешённым ролям. |
Межкафедральный запрос кафедры не раскрывает данные. |
| 20 | не начато | Агрегаты переиспользуют интерактивный широкий поиск с лимитом 50 групп. | Специализированная batch/aggregate генерация без UI-лимита. | 51 и 100 активных групп. |
| 21 | не начато | Импорт ищет дисциплину по глобальному имени и меняет её владельца. | 409 для чужой записи, идемпотентность своей; scoped uniqueness при миграции. |
Повторный свой импорт и совпадение чужой кафедры. |
| 22 | не начато | Видимые EDUCATION_OFFICE экраны вызывают backend endpoints с ADMIN-only правами. | Единая role matrix для backend/frontend; согласовать read/write capabilities. | Интеграционный сценарий календаря и форм обучения без штатного 403. |
| 23 | не начато | Генератор повторно запрашивает semester/assignment/calendar day на каждой комбинации. | Батч-загрузка и request-scoped lookup maps. | Query-count test на 120 датах и десятках групп. |
| 24 | не начато | Истёкшие/отозванные refresh rows не удаляются. | Tenant-aware scheduled cleanup с retention и безопасностью двух pod. | Active/fresh rows сохраняются; cleanup идемпотентен и конкурентно безопасен. |
| 25 | не начато | Нет общего mapping DB constraints; контроллеры возвращают клиенту getMessage(). |
Централизованный sanitizer/mapping 400/409, убрать raw exception body. |
Известный/неизвестный constraint без JDBC details. |
| 26 | не начато | Compose требует внешнюю сеть, не публикует HTTP, рассогласует DB/JWT и не имеет named volume. | Самодостаточная сеть/reverse proxy, согласованные env и volume. | Чистый docker compose up -d --build, HTTP / и /api. |
| 27 | не начато | Pipeline собирает/деплоит без обязательных тестов и для tag перезапускает mutable main. |
Test gates, concurrency, immutable tag/digest rollout, rollback check. | Workflow lint/static inspection и shell tests. |
| 28 | не начато | Login не имеет shared rate limit; IP безусловно доверяет forwarded header. | PostgreSQL-backed tenant+trusted-IP+username limiter, lockout/audit, 429/Retry-After. |
Multi-instance integration tests, отсутствие user enumeration. |
| 29 | не начато | Business dates зависят от timezone процесса; frontend date-only строится через UTC. | Инъекция Clock/Europe/Moscow, UTC timestamps, локальное форматирование date-only, TZ контейнеров. |
Boundary tests 00:00–03:00 и первые дни месяца. |
| 30 | не начато | Образы/скачиваемые инструменты используют mutable tags/latest без checksum/SBOM/scan. | Pin versions+digests, SHA verification, SBOM и image scan gates. | Static scan production refs и CI configuration. |
| 31 | не начато | Group create/update допускает неположительные значения и уменьшение ниже активных подгрупп. | Общий validator, locking, новые CHECK constraints. | Create/update/concurrency и migration tests. |
| 32 | не начато | between(start,end) > 120 разрешает 121 включительную дату. |
Проверять between + 1 <= 120 в обоих сервисах. |
Границы 120/121. |
| 33 | не начато | Несколько e.message вставляются как HTML; парольные поля имеют type=text. |
textContent/экранирование; type=password и autocomplete. |
DOM/XSS static/unit test и HTML assertions. |
| 34 | не начато | В затронутых UI/log paths остаются английские метки/логи и raw exception text. | Русификация и безопасные сообщения на границе API/UI. | Поиск английских production сообщений и regression tests. |
Завершённые этапы
№ 1 и зависимая № 14
JwtPropertiesбольше не содержит секрет по умолчанию; production startup отклоняет пустое, короткое, известное legacy/placeholder значение иJWT_REFRESH_COOKIE_SECURE=false.../k8s/config.yamlбольше не создаёт Secret с литеральными значениями.../k8s/backend.yamlмонтирует tenant-конфигурацию изtenants-secret.../k8s/otel-collector.yamlиспользует только${env:...}ссылки иotel-postgres-secret.KubernetesTenantSecretUpdaterзаменилConfigMapUpdater, использует base64-поле Kubernetes Secret, service-account CA и hostname verification.../k8s/deploy.shпроверяет обязательные Secrets/ключи до rollout без вывода значений.- Созданы
docs/SECURITY_RUNBOOK.mdиscripts/check-production-secrets.sh; документация синхронизирована через AutoUpdateDocs.
Фактические проверки этапа:
| Команда | Результат |
|---|---|
Контейнерный mvn test |
45 тестов, 0 failures, 0 errors, BUILD SUCCESS. |
bash scripts/check-production-secrets.sh |
Успешно. |
kubectl kustomize ../k8s >/dev/null |
Успешно. |
bash -n ../k8s/deploy.sh |
Успешно. |
git diff --check |
Успешно. |
| Повторные SHA-256 V1/V2 | Совпадают с исходными. |
Изменённые вне Git-корня файлы: ../k8s/config.yaml, backend.yaml,
otel-collector.yaml, rbac.yaml, deploy.sh, README.md. Их состояние проверяется
отдельно от git diff.
№ 3 — атомарная ротация refresh-токена
- Репозиторий захватывает строку исходного токена через
PESSIMISTIC_WRITEи загружает пользователя в том же запросе. RefreshTokenService.rotate()выполняет проверку активности, отзыв исходного токена и создание следующего токена в одной транзакции после захвата блокировки.- Добавлен интеграционный тест с двумя синхронными потоками, Spring transactional proxy, Flyway V1/V2 и реальным PostgreSQL 16.3 через Testcontainers.
- Одновременная ротация подтверждает ровно один успешный результат, один отказ, две строки в БД и ровно один активный следующий токен.
docs/ARCHITECTURE.mdиdocs/API.mdсинхронизированы через AutoUpdateDocs.
Фактические проверки этапа:
| Команда | Результат |
|---|---|
Контейнерный узкий mvn -Dtest=RefreshTokenServiceTest,RefreshTokenServiceConcurrencyIntegrationTest test |
5 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
Контейнерный полный mvn test |
46 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
git diff --check |
Успешно. |
№ 4 — границы AuthContext
AuthorizationInterceptor.preHandle()очищаетAuthContextдо проверки URI, метода, публичного endpoint и bearer-токена.- Аутентифицированный пользователь помещается в
ThreadLocalтолько после успешной проверки@RequireRoles; ответы401и403оставляют контекст пустым. afterCompletion()продолжает очищать контекст после успешно допущенного запроса.- Регрессионный MockMvc-тест выполняет на одном JUnit-потоке запрещённый защищённый, публичный и разрешённый защищённый запросы и проверяет контекст внутри обработчиков и после завершения.
docs/ARCHITECTURE.mdсинхронизирован через AutoUpdateDocs.
Фактические проверки этапа:
| Команда | Результат |
|---|---|
Контейнерный mvn -Dtest=AuthorizationInterceptorTest test |
1 тест, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
Контейнерный полный mvn test |
47 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
git diff --check |
Успешно. |
№ 5 — безопасные точечные изменения расписания
ScheduleOverrideControllerстал тонким HTTP-адаптером, а CRUD перенесён в публичные транзакционные методыScheduleOverrideService.- Перед сохранением строится реальная базовая пара на дату с учётом семестра, календаря,
чётности, lifecycle и остатка часов; несуществующая occurrence возвращает
400. - Матрица
CANCEL/MOVE/REPLACE, формат и action-specific фактическое изменение проверяются до мутации managed entity. - Сохранённые overrides и кандидат применяются общей логикой
ScheduleQueryService; полуоткрытые интервалы проверяются по преподавателю, аудитории, целой группе и подгруппам. Конфликт возвращается как русский409 Conflict. - PostgreSQL transaction advisory lock сериализует изменения даты между pod. Update/delete
дополнительно блокируют ID; старая и новая даты update захватываются в стабильном порядке,
после чего строка перечитывается с
PESSIMISTIC_WRITE. - Добавлена
V3__schedule_override_invariants.sql: preflight legacy-строк, CHECK для payload каждого действия и формата. Миграционный тест подтверждает полный rollback на невалидном legacyREPLACEи clean upgrade до V3. docs/API.md,docs/BUSINESS_LOGIC.md,docs/DATABASE.mdиdocs/ARCHITECTURE.mdсинхронизированы через AutoUpdateDocs.
Фактические проверки этапа:
| Команда | Результат |
|---|---|
| Целевые контейнерные Maven-прогоны № 5 | 22 unit/MockMvc/PostgreSQL теста, затем 24 усиленных unit/MockMvc теста; оба прогона без failures/errors/skipped. Все 27 тестов № 5 входят в полный прогон. |
Контейнерный полный mvn test |
74 теста, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
git diff --check |
Успешно. |
| Повторные SHA-256 V1/V2 | Совпадают с исходными. |
| SHA-256 новой V3 | eb5315b58a2bad95b0ad36718052788168be0d8f347649c5c98782949865357b. |
№ 6 — замена преподавателя в результирующем расписании
ScheduleQueryService.search()читает overrides диапазона один раз и переиспользует один снимок для достраивания и применения изменений.- Teacher-only путь по-прежнему строит базовое расписание преподавателя напрямую. Только
при наличии overrides с совпадающим
newTeacherон группирует целевые слоты по дате и один раз строит полный базовый день каждой релевантной даты. - Из дня добавляются только occurrences с точным
baseRuleSlotId + lessonDate; отсутствующая базовая пара не создаёт фантомное занятие. - Overrides применяются до окончательного teacher-фильтра: новый преподаватель видит замену, исходный — нет. Итоговая дедупликация удаляет совпавшие базовые и достроенные фрагменты.
CANCELиMOVEпродолжают применяться из того же snapshot; при отсутствии релевантной заменыgroupRepository.findAll()не вызывается.docs/API.md,docs/BUSINESS_LOGIC.mdиdocs/ARCHITECTURE.mdсинхронизированы через AutoUpdateDocs.
Фактические проверки этапа:
| Команда | Результат |
|---|---|
Узкий контейнерный Maven-прогон трёх ScheduleQueryService*Test |
10 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
Контейнерный полный mvn test |
79 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
git diff --check |
Успешно. |
№ 7 — полная валидация правил расписания
ScheduleRuleAdminControllerстал тонким HTTP-адаптером; create/update/archive перенесены в публичные транзакционные методыScheduleRuleService.- До обращений к справочникам сервис проверяет обязательные поля, неотрицательность и чётность каждого лимита часов, положительную сумму, недели начала и структуру payload. Ноль разрешён для неиспользуемого типа занятия.
- Слот принимает только активного пользователя с ролью
TEACHER, базовый временной слот и форматОчно/Онлайн; отсутствующая вложенная ссылка возвращает безопасный400. - Кандидат строится отдельно от managed entity. Точные дубли и ресурсные конфликты слотов
нового payload проверяются попарно до сохранения;
ODDиEVENразрешены одновременно, аBOTHпересекается с ними. Конфликтный update не изменяет существующее правило. - Создание первым DB-запросом блокирует строку семестра. Update блокирует строку правила и
старый/новый семестры в стабильном порядке; реальный PostgreSQL-тест подтверждает, что из
двух параллельных create проходит один, а второй после commit видит
409 Conflict. V4__schedule_rule_even_hours.sqlвыполняет русский preflight нечётных legacy-часов и точных дублей слотов, затем добавляет провалидированный CHECK чётности и UNIQUE точного payload слота. При legacy-нарушении V4 полностью откатывается и остаётся версия V3.docs/API.md,docs/BUSINESS_LOGIC.md,docs/DATABASE.mdиdocs/ARCHITECTURE.mdсинхронизированы через AutoUpdateDocs.
Фактические проверки этапа:
| Команда | Результат |
|---|---|
| Целевые service/MockMvc/Flyway/PostgreSQL прогоны № 7 | 23 теста, 0 failures, 0 errors, 0 skipped. |
Контейнерный полный mvn test |
102 теста, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
git diff --check |
Успешно. |
| Повторные SHA-256 V1/V2/V3 | Совпадают с зафиксированными значениями. |
| SHA-256 новой V4 | 211babace0f1620cac2186b42b6d3039c8ee8518284473b49da0a4d47186e490. |
№ 8 — атомарная замена календарной сетки
AcademicCalendarController.saveGrid()больше не содержит транзакцию, bulk delete, построчное сохранение и внутренний catch; endpoint делегирует публичномуAcademicCalendarGridService.replaceGrid().- Сервис до первого write полностью валидирует непустой payload:
null-строки, границы курса и учебного года, согласованностьweekNumberи ISOdayOfWeekс датой, наличие activity type и уникальность(courseNumber, date). - Все
AcademicCalendarDayстроятся в памяти доdeleteByCalendarId. Валидный набор сохраняется какdelete → flush → saveAll → flushв одной транзакции; исключения выходят за Spring proxy и приводят к rollback. - Очистка кэша зарегистрирована как
afterCommit, поэтому не выполняется при rollback и не создаёт окно повторного кэширования старой сетки между flush и commit. - PostgreSQL-тест сравнивает count+MD5 старой seed-сетки: payload с корректной первой и ошибочной второй строкой оставляет fingerprint неизменным и не добавляет первую строку; следующий валидный запрос атомарно оставляет ровно две новые строки.
docs/API.md,docs/BUSINESS_LOGIC.mdиdocs/ARCHITECTURE.mdсинхронизированы через AutoUpdateDocs.
Фактические проверки этапа:
| Команда | Результат |
|---|---|
Unit AcademicCalendarGridServiceTest |
8 тестов, 0 failures, 0 errors, 0 skipped. |
PostgreSQL AcademicCalendarGridServiceIntegrationTest |
1 тест, 0 failures, 0 errors, 0 skipped; rollback/fingerprint и after-commit подтверждены. |
Контейнерный полный mvn test |
111 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
git diff --check |
Успешно. |
Точка продолжения
Текущий этап: № 9 — безопасная замена tenant DataSource.
Следующая операция:
- проверить актуальные create/update потоки
DatabaseController, swap/remove APITenantRoutingDataSource, Flyway и персистенцию tenant-конфигурации; - определить порядок prepare → connection validation → Flyway → persistence → atomic swap, не закрывающий рабочий pool до полной готовности кандидата;
- добавить компенсирующий rollback для ошибок персистенции/активации и явные русские
ошибки без ложного
200; - покрыть неверные credentials, ошибку миграции и ошибку персистенции тестами, доказывающими сохранность старого подключения;
- выполнить полный прогон и AutoUpdateDocs перед изменением статуса № 9.