Files
magistr/BUG_FIX_PROGRESS.md
2026-07-13 03:28:18 +03:00

33 KiB
Raw Blame History

Журнал исправления проблем

Обновлено: 2026-07-13 (Europe/Moscow).

Область и неизменяемые данные

  • В работе ровно 33 проблемы: 1 и 334.
  • Проблема № 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 Успешно.

Порядок выполнения

  1. Безопасная production-конфигурация и внешние секреты: № 1 (с зависимой частью № 14).
  2. Аутентификация и границы запроса: № 3, 4, 24, 25, 28.
  3. Высокоприоритетные инварианты расписания: № 58.
  4. Tenant lifecycle и Kubernetes coordination: № 9, 10, 13, 14.
  5. Frontend-сессия и достоверность UI: № 11, 12, 33, 34.
  6. Календарные и DB-инварианты: № 1517, 21, 31, 32.
  7. История, права, масштаб и производительность: № 1823.
  8. Локальный запуск, CI/CD, воспроизводимые артефакты и timezone: № 26, 27, 29, 30.
  9. 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:0003: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 на невалидном legacy REPLACE и 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 и ISO dayOfWeek с датой, наличие 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.

Следующая операция:

  1. проверить актуальные create/update потоки DatabaseController, swap/remove API TenantRoutingDataSource, Flyway и персистенцию tenant-конфигурации;
  2. определить порядок prepare → connection validation → Flyway → persistence → atomic swap, не закрывающий рабочий pool до полной готовности кандидата;
  3. добавить компенсирующий rollback для ошибок персистенции/активации и явные русские ошибки без ложного 200;
  4. покрыть неверные credentials, ошибку миграции и ошибку персистенции тестами, доказывающими сохранность старого подключения;
  5. выполнить полный прогон и AutoUpdateDocs перед изменением статуса № 9.