1068 lines
120 KiB
Markdown
1068 lines
120 KiB
Markdown
# Журнал исправления проблем
|
||
|
||
Обновлено: 2026-07-19 (Europe/Moscow).
|
||
|
||
## Область и неизменяемые данные
|
||
|
||
- В работе ровно 33 проблемы: `1` и `3–34`.
|
||
- Проблема № 2 исключена: демонстрационные учётные записи, seed-данные и документация по ним не изменяются.
|
||
- `BUG_REPORT.md` не редактируется.
|
||
- По прямому разрешению владельца проекта миграции V2–V7 удалены, а их итоговая схема
|
||
объединена в `V1__init.sql`: проект находится на этапе разработки, клиентских tenant-БД нет.
|
||
Для этой редакции требуется полный сброс локальной БД.
|
||
|
||
Исходное рабочее дерево перед началом реализации было чистым (`git status --short` без вывода).
|
||
|
||
Контрольные суммы исходных миграций до разрешённой консолидации (историческая справка):
|
||
|
||
| Файл | SHA-256 |
|
||
|---|---|
|
||
| `V1__init.sql` | `404f65f270dd87f4db996495fc970456c4fd0f762e7c32638199dffaa812ea7b` |
|
||
| `V2__subgroups_active_unique_name.sql` | `68f5525a50ddba4f8800a63cab3cd0f84779e301c4a1f29a6e0399f97fbdb30b` |
|
||
|
||
Текущая единая baseline-миграция: `V1__init.sql`, SHA-256
|
||
`fffedf66e8bf7d25d2d8c1b1a6146b45dd5b7e52233d05a68de8bf62c817f7a7`.
|
||
Упоминания V2–V7 в исторических записях ниже описывают последовательность разработки до
|
||
консолидации и не означают наличие этих файлов в текущем дереве.
|
||
|
||
## Исходная линия проверок
|
||
|
||
| Проверка | Результат до изменений |
|
||
|---|---|
|
||
| `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. Высокоприоритетные инварианты расписания: № 5–8.
|
||
4. Tenant lifecycle и Kubernetes coordination: № 9, 10, 13, 14.
|
||
5. Frontend-сессия и достоверность UI: № 11, 12, 33, 34.
|
||
6. Календарные и DB-инварианты: № 15–17, 21, 31, 32.
|
||
7. История, права, масштаб и производительность: № 18–23.
|
||
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; структурные CHECK включены в V1. | 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`, `Очно`/`Онлайн` и чётные часы; строка семестра сериализует запись. Чётность и точный дубль закреплены в V1. | 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 проглатывал ошибку, а отказ persistence мог сочетаться с изменённым локальным состоянием. | `TenantLifecycleService` выполняет prepare → connection validation → Flyway → Secret persistence → atomic snapshot swap; candidate закрывается при отказе, прежний route сохраняется, Secret компенсируется, старый pool закрывается после drain. | 18 целевых unit/MockMvc/PostgreSQL tests; create/update, credentials, invalid connection, Flyway, persistence, swap, HTTP `503` и in-flight connection. Полный backend: 132/0/0/0. |
|
||
| 10 | исправлено и проверено | Два pod перезаписывали целый tenant-документ без `resourceVersion`; TCP probes не отражали готовность tenant-БД. | Backend применяет доменные upsert/remove через GET → условный PUT по `resourceVersion`, ограниченный retry и безопасную компенсацию; Actuator разделяет process-only liveness и readiness обязательных tenant-БД. Kubernetes содержит RBAC `get/update`, directory mount без `subPath`, required tenant-config и HTTP probes. | 64 целевых теста; полный backend 277/0/0/0; Kustomize, secret-scan и синтаксис deploy-скрипта прошли. |
|
||
| 11 | исправлено и проверено | Воскресенье вычислялось повторным мутированием одной даты, date-only строился через UTC, а ошибки кафедральных запросов превращались в пустой успешный результат. | `dashboard-conflicts.js` формирует локальный диапазон на отдельных объектах, загружает кафедры через `Promise.allSettled` и fail-closed различает `COMPLETE`/`PARTIAL`/`NOT_RUN`; зелёная карточка разрешена только для полного результата без конфликтов. | 12 `node:test`: 02.07.2026 00:15 Europe/Moscow, переход года, полный/частичный/нулевой успех и строгий UI-контракт; `npm run check` и синтаксис 25 JS/MJS-файлов — успешно. |
|
||
| 12 | исправлено и проверено | Четыре frontend-клиента хранили access JWT и профиль в Web Storage; login/admin исполняли OTel с `esm.sh`, а inline JS/styles не позволяли включить строгий CSP. | Общий `auth-session.js` держит access JWT только в памяти и восстанавливает его через HttpOnly refresh-cookie; OTel собирается из exact npm-зависимостей в same-origin bundle; Apache выдаёт CSP без `unsafe-inline`/`unsafe-eval`. | 21 frontend test, `npm audit` 0 vulnerabilities, Docker build и live HTTP/CSP/bundle check — успешно. |
|
||
| 13 | исправлено и проверено | Watcher сравнивал только домены, подтверждал hash до полного успеха, а API после мутации мог принять запаздывающую mounted-проекцию за актуальную. | Полное нормализованное сравнение, prepare/verify/Flyway/atomic swap без повторной записи Secret, hash-after-success, retry 30–300 секунд и semantic snapshot fence по `TenantSecretUpdateReceipt`. | 53 целевых теста, 2 PostgreSQL lifecycle-теста; полный backend 200/0/0/0. |
|
||
| 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 | исправлено и проверено | CRUD слотов жил в контроллере, проверял только номер пары и принимал клиентскую длительность; DB не запрещала overlap и перенос используемого базового слота. | Транзакционный `TimeSlotService` блокирует сетки, валидирует границы, вычисляет duration и отклоняет overlap; V1 содержит duration CHECK, GiST exclusion и триггеры привязки правил к `DEFAULT`; UI показывает расчётное readonly-поле. | 6 unit; общий backend без Testcontainers 209/0/0/0; чистая V1 и конкурентные DB-сценарии проверены на PostgreSQL 16.3. |
|
||
| 16 | исправлено и проверено | CRUD проверял только порядок дат; update не проверял duplicate title/type, годы и семестры пересекались, а семестр мог выходить за год. | `AcademicPeriodService` централизует inclusive-range validation и блокирует родительский год; V1 содержит GiST exclusion constraints и взаимные триггеры границ года/семестра. | 8 unit; общий backend без Testcontainers 209/0/0/0; чистая V1, adjacent/overlap/bounds и конкурентные годы проверены на PostgreSQL 16.3. |
|
||
| 17 | исправлено и проверено | Изменение измерений группы/календаря оставляло несовместимые assignments/grid/subjects. | `AcademicStructureService` транзакционно блокирует группу/график, перевалидирует назначения, курсы, сетку и дисциплины; V1 дублирует инварианты конкурентно безопасными триггерами. Несовместимое изменение даёт русский `409` без мутации данных. | 7 unit; общий backend без Testcontainers 209/0/0/0; чистая V1, DB-отказы и гонка двух транзакций проверены на PostgreSQL 16.3. |
|
||
| 18 | исправлено и проверено | Бизнес-решения смешивали `users.department_id` с датированными assignments и фильтровали архивные записи по текущему состоянию. | `TeacherDepartmentService` централизует разрешение кафедры на дату; списки, права и нагрузка используют историю, будущий перевод не меняет текущую принадлежность; V1 запрещает overlap основных периодов. | 5 service + 7 controller tests; полный backend 216/0/0/0; чистая V1, overlap и конкурентная запись проверены на PostgreSQL 16.3. |
|
||
| 19 | исправлено и проверено | Workload принимал произвольный `departmentId` или глобальный scope для роли `DEPARTMENT`. | Все пять workload-сценариев вычисляют scope через `AuthContext`: кафедра получает только свою выборку, чужой ID даёт русский `403`; глобальный scope сохранён уполномоченным ролям. | 5 controller tests; полный backend 219/0/0/0; compose и diff-check успешны. |
|
||
| 20 | исправлено и проверено | Workload и free-classrooms переиспользовали интерактивный широкий поиск, который отклонял tenant с более чем 50 активными группами. | Добавлен узкий `searchForAggregation`: общие validation/overrides/filter/dedup сохранены, UI-лимит отключён только для агрегатов; обычный search не изменён. | 51 и 100 активных групп обработаны полностью; целевые 9/0/0/0, полный backend 221/0/0/0. |
|
||
| 21 | исправлено и проверено | Импорт находил дисциплину по глобальному имени и без проверки менял её `department_id`. | Транзакционный `SubjectImportService` сохраняет глобальное владение: свой повтор обновляет тот же ID, чужой даёт `409`; V1 содержит case-insensitive уникальность названия. | 4 unit; полный backend 225/0/0/0; чистая V1, регистровый дубль и конкурентная вставка проверены на PostgreSQL 16.3. |
|
||
| 22 | исправлено и проверено | Календарный график учебного отдела получал `403` на specialties/profiles, а видимый CRUD форм обучения был ADMIN-only. | EDUCATION_OFFICE получила read-only specialities/profiles и CRUD education forms; frontend admin/settings используют единый `role-capabilities.js`. | 2 MockMvc role-сценария через реальный interceptor; frontend role test; полный backend 227/0/0/0, npm check успешен. |
|
||
| 23 | исправлено и проверено | Генератор искал semester внутри дат и правил, а assignment/calendar day и эффективную сетку времени — внутри комбинаций дат и групп. | `AcademicDateService.ScheduleSnapshot`, batch-репозитории и `buildScheduleForGroups()` загружают данные один раз на запрос; lookup-карты используются во всех внутренних циклах. | 120 дат, 30 групп и 20 правил: ровно 7 repository reads, 30 корректных занятий; полный backend 228/0/0/0. |
|
||
| 24 | исправлено и проверено | Login/refresh добавляли строки, а revoke/rotate только ставили `revoked_at`; cleanup отсутствовал. | `RefreshTokenCleanupJob` обходит snapshot tenant-БД, удаляет старые строки ограниченными транзакционными пачками с `FOR UPDATE SKIP LOCKED`; retention и расписание настраиваются, cleanup-индексы включены в V1. | 3 unit; полный backend 231/0/0/0; точная V1 и две конкурентные PostgreSQL-транзакции удалили разные пачки, fresh/active сохранены, повтор удалил 0. |
|
||
| 25 | исправлено и проверено | Не было общего mapping DB constraints; несколько контроллеров включали `Exception.getMessage()` в HTTP-body. | `GlobalExceptionHandler` и `DatabaseConstraintViolationMapper` централизованно преобразуют CHECK в безопасный русский `400`, UNIQUE/FK/GiST и неизвестные нарушения — в `409`; raw exception body удалён. | 4 новых теста известных и неизвестных constraints без JDBC/SQL details; полный backend 235/0/0/0. |
|
||
| 26 | исправлено и проверено | Compose требовал внешнюю сеть, не публиковал HTTP, рассогласовывал DB/JWT и использовал anonymous volume. Live startup также выявил два неоднозначных Spring-конструктора. | Внутренняя bridge-сеть, Apache reverse proxy и `HTTP_PORT`; согласованные datasource/JWT env, healthchecks, named volume; production-конструкторы явно помечены для DI. | `up --build --wait`; PostgreSQL/backend/frontend healthy, `/` и liveness `200`, API `401`, named volume; полный backend 235/0/0/0. |
|
||
| 27 | исправлено и проверено | Pipeline собирал и деплоил без тестов, а release tag только перезапускал Deployment на mutable `:main`. | Обязательный checks job, production concurrency lock, SHA/release build tags и deploy только digest из build outputs; общий shell rollback обоих Deployment. | Workflow YAML/static scan, shell success/failure/rollback, production-secret scan и Kustomize — успешно. |
|
||
| 28 | исправлено и проверено | Login не имел shared rate limit, различал архивного пользователя и безусловно доверял forwarded header. | Транзакционный PostgreSQL limiter по tenant + NFKC username + проверенному IP; прогрессивная блокировка, audit/cleanup, единый `401`, `429`/`Retry-After` и trusted proxy chain. | 12 целевых unit/PostgreSQL tests; два service instance дали ровно один `401` и один `429`, общий failure count 2; V1 и cleanup проверены. |
|
||
| 29 | исправлено и проверено | Business dates зависели от timezone процесса, абсолютные моменты смешивались с `LocalDateTime`, frontend date-only местами строился через UTC. | `BusinessTimeService` с `Clock`/`Europe/Moscow`; абсолютные моменты переведены на `Instant`/`TIMESTAMPTZ`/UTC; frontend использует локальные компоненты; Compose и Kubernetes фиксируют бизнес-зону и timezone JVM. | Boundary tests московской полуночи, frontend date-only, Kustomize и полный backend PostgreSQL/Testcontainers-прогон 277/0/0/0 прошли. |
|
||
| 30 | исправлено и проверено | Образы/скачиваемые инструменты использовали mutable refs либо не имели общего контроля digest/SBOM/scan. | Docker/Compose/Testcontainers, OTel Collector и CI images закреплены tag+digest, Actions — commit SHA; checksum, provenance, SBOM и Trivy gate проверяются скриптом. | Shell-регрессии, Workflow/Compose YAML, repo и полный `K8S_DIR=../k8s` artifact-scan прошли. |
|
||
| 31 | исправлено и проверено | Group create/update допускал неположительные значения и уменьшение ниже активных подгрупп; параллельные изменения могли вместе превысить размер группы. | Общий validator create/update, блокировка родительской группы, положительные CHECK и перекрёстные триггеры непосредственно в V1. | 11 service tests и 4 PostgreSQL migration/concurrency tests; полный backend 277/0/0/0. |
|
||
| 32 | исправлено и проверено | `between(start,end) > 120` разрешал 121 включительную дату. | Оба сервиса считают включительные даты как `between + 1` и разрешают не больше 120. | Границы 120/121 в query/generator tests; полный backend 277/0/0/0. |
|
||
| 33 | исправлено и проверено | Несколько `e.message` вставлялись как HTML; парольные поля имели `type=text`. | Ошибки вкладок, БД и заявок рендерятся фиксированным текстом через `textContent`/`replaceChildren`; поля создания пользователя, tenant и одобрения заявки используют `type=password` и `autocomplete=new-password`. | 3 новых static regression tests; полный frontend: 24 проверки, 0 ошибок; CSP-хэши синхронизированы. |
|
||
| 34 | исправлено и проверено | В затронутых UI/log paths оставались английские метки/логи и raw exception text. | Статусы БД и пользовательские ошибки русифицированы; начальная tenant-конфигурация, маршрутизация и interceptor пишут русские production-сообщения с безопасным типом исключения. | Static-проверка исходных английских сообщений; полный backend 200/0/0/0; live Docker HTTP/CSP — успешно. |
|
||
|
||
## Завершённые этапы
|
||
|
||
### № 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
|
||
|
||
- `DatabaseController` делегирует create/update/delete в единый `TenantLifecycleService` и
|
||
возвращает lifecycle-ошибки как русский безопасный `503`, а не ложный `200` или raw
|
||
JDBC/Flyway message.
|
||
- Новый Hikari candidate не публикуется в маршрутизации до явного `getConnection()` /
|
||
`isValid(5)`, успешного Flyway и подтверждённой доменной мутации в актуальном
|
||
`tenants-secret`.
|
||
- `TenantRoutingDataSource` одной volatile-публикацией заменяет неизменяемый снимок
|
||
`TenantConfig + DataSource`; прежняя конфигурация и pool остаются рабочими при ошибке
|
||
credentials, миграции, persistence или доатомарной активации.
|
||
- Неуспешная persistence не меняет локальный route. Неопределённый результат записи сначала
|
||
сверяется повторным чтением и не даёт права на опасный откат. Если swap/remove отказывает
|
||
после подтверждённой записи, прежний снимок восстанавливается только при точном совпадении
|
||
`resourceVersion`; более новое изменение другого pod не перезаписывается.
|
||
- После успешного swap старый pool не закрывается синхронно: `RetiredTenantPoolService`
|
||
прекращает новые маршруты, сохраняет уже выданные соединения и закрывает Hikari после
|
||
drain либо grace timeout.
|
||
- Unit-тесты дополнены отдельной create-веткой, восстановлением снимка без нового domain,
|
||
`Connection.isValid(false)` и MockMvc-проверкой `503` без технических деталей.
|
||
- PostgreSQL 16.3/Testcontainers-тест подтверждает сохранность старого route при неверном
|
||
пароле, повреждённом Flyway checksum и отказе persistence, работоспособность in-flight
|
||
соединения во время swap и последующий drain старого pool.
|
||
- `docs/API.md`, `docs/ARCHITECTURE.md` и `docs/INFRASTRUCTURE.md` синхронизированы через
|
||
AutoUpdateDocs; исправлено устаревшее описание порядка «in-memory до Secret».
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| Контейнерный целевой `mvn -Dtest=TenantLifecycleServiceTest,TenantLifecyclePostgreSqlIntegrationTest,TenantRoutingDataSourceTest,DatabaseControllerTest test` | 18 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| Контейнерный полный `mvn test` | 132 теста, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| Повторные LF-нормализованные SHA-256 V1/V2/V3/V4 | Совпадают с ранее зафиксированными значениями. |
|
||
|
||
### № 10 — межподовая координация tenant-конфигурации и health probes
|
||
|
||
- Введён `TenantConfigStore` с явными операциями upsert/remove. Kubernetes-реализация перед
|
||
каждой мутацией читает актуальный `tenants-secret`, нормализует домены и отправляет полный
|
||
объект условным `PUT` с прочитанным `metadata.resourceVersion`.
|
||
- `409`, `408`, `429`, `5xx` и транспортные ошибки обрабатываются ограниченно: максимум три
|
||
попытки с возрастающей задержкой и повторным применением только собственной доменной
|
||
мутации к свежему состоянию. Идемпотентная операция не выполняет `PUT`.
|
||
- Успех записи и компенсации больше не определяется одним HTTP 2xx: backend разбирает
|
||
возвращённый Secret, сравнивает `tenants.json` с ожидаемым и использует `resourceVersion`
|
||
только проверенного объекта. Пустой, неполный или неоднозначный ответ сверяется повторным
|
||
`GET` без выдачи квитанции, разрешающей небезопасный откат.
|
||
- Компенсация выполняется только для подтверждённой собственной версии. Если текущий Secret
|
||
уже получил другой `resourceVersion`, откат прекращается и не затирает изменение другого
|
||
pod. Отсутствующий `data.tenants.json`, повторяющиеся домены и Kubernetes mode без
|
||
ServiceAccount token обрабатываются fail-closed.
|
||
- Добавлен Spring Boot Actuator. `/actuator/health/liveness` зависит только от жизнеспособности
|
||
процесса; `/actuator/health/readiness` требует непустой набор обязательных tenant, успешную
|
||
миграцию и свежую успешную проверку каждого соединения. H2-заглушка, ошибка обязательного
|
||
конфига, migration failure, timeout, недоступность или просроченная проверка дают `503`.
|
||
- `TenantDatabaseHealthMonitor` проверяет tenant-БД ограниченно-параллельно в фоне; endpoint
|
||
читает только атомарный кэш. Общий deadline отменяет лишь незавершённые задачи, уже
|
||
завершившиеся результаты не теряются и получают фактическое время проверки. Scheduler
|
||
имеет два потока, поэтому долгий health-pass не блокирует watcher.
|
||
- `TenantDataSourceConfig` и watcher поддерживают `TENANTS_CONFIG_REQUIRED=true`: отсутствие,
|
||
пустой список или ошибка чтения production-файла фиксируют configuration failure, а H2 не
|
||
маскирует отказ. Actuator endpoints исключены из tenant-interceptor и не раскрывают
|
||
components, домены, JDBC URL или credentials.
|
||
- AutoUpdateDocs синхронизировал `docs/API.md`, `docs/ARCHITECTURE.md` и
|
||
`docs/INFRASTRUCTURE.md`. На этом этапе ограничение watcher по обновлению URL/credentials
|
||
существующего домена было оставлено проблеме № 13 и устранено следующим этапом.
|
||
- В текущем внешнем `../k8s` подтверждены directory mount без `subPath`,
|
||
`TENANTS_CONFIG_REQUIRED=true`, Role `get/update` для `tenants-secret` и HTTP liveness /
|
||
readiness probes. Манифесты успешно собираются Kustomize; production rollout остаётся
|
||
отдельным действием оператора.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `KubernetesTenantSecretUpdaterTest` | 21 тест, 0 failures, 0 errors, 0 skipped; два конкурентных updater, CAS conflict/retry, 408/5xx reconciliation, проверка 2xx и безопасная компенсация. |
|
||
| Health/startup target (`TenantDataSourceConfigTest`, registry/monitor/indicator, Actuator, MVC exclusion) | 19 тестов, 0 failures, 0 errors, 0 skipped. |
|
||
| Lifecycle/controller/watcher target | 24 теста, 0 failures, 0 errors, 0 skipped; PostgreSQL lifecycle отдельно повторён успешно после единичной Docker-сетевой флуктуации. |
|
||
| Контейнерный полный `mvn test` | 169 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS; 40 suites. |
|
||
| `git diff --check` | Успешно. |
|
||
| Повторные LF-нормализованные SHA-256 V1/V2/V3/V4 | Совпадают с ранее зафиксированными значениями. |
|
||
| `kubectl kustomize ../k8s`, production secret-scan и `bash -n ../k8s/deploy.sh` | Успешно в финальном аудите. |
|
||
|
||
### № 13 — полная и повторяемая синхронизация tenant-конфигурации
|
||
|
||
- `TenantLifecycleService.synchronizeFromPersistedConfig()` валидирует весь входной список,
|
||
отклоняет нормализованные дубли до изменения runtime/readiness и сравнивает `name`, `domain`,
|
||
`url`, `username`, `password`. Новый или изменённый tenant проходит candidate prepare,
|
||
проверку соединения, Flyway и атомарный swap; persisted-путь не вызывает `TenantConfigStore`.
|
||
- Hash mounted `tenants.json` становится применённым только после успешного разбора и полной
|
||
lifecycle-синхронизации. Ошибка сохраняет прежний hash и повторяется с экспоненциальной
|
||
задержкой 30–300 секунд; новая ревизия обходит backoff. Required missing/read/empty также
|
||
наблюдаемы через readiness и русский лог без credentials, hash или fingerprint.
|
||
- Перед `POST`/`DELETE` watcher под общим reentrant monitor применяет ещё не обработанный
|
||
semantic baseline. Ошибка чтения/разбора/sync возвращает безопасный `503` до начала мутации.
|
||
`TenantLifecycleMutationResult` возвращает controller-у immutable-квитанцию персистенции,
|
||
но не раскрывает её в HTTP.
|
||
- Snapshot fence ставится только для реально сохранённой изменённой версии
|
||
(`persisted && changed`) и сравнивает нормализованные `previous/committed` snapshots, а не
|
||
сырые JSON-хеши. Поэтому H0/H1 не откатывают две быстрые мутации до H2, форматирование JSON
|
||
не влияет на решение, merged snapshot другого pod применяется, а local/no-op receipt ничего
|
||
не блокирует.
|
||
- Ошибка публикации readiness после успешного swap больше не помечает активный новый pool как
|
||
`FAILED`: повтор проверяет действующее соединение и восстанавливает readiness без второго
|
||
Flyway/swap. Старый pool передаётся на drain сразу после успешной атомарной публикации.
|
||
- PostgreSQL 16.3/Testcontainers подтверждает сохранность прежнего route при неверных
|
||
credentials и Flyway checksum, успешную замену на валидную БД, работу in-flight соединения,
|
||
drain старого pool и отсутствие повторной записи Secret в persisted-пути.
|
||
- Windows/Docker portability TLS-теста стабилизирована явным `https://localhost:<port>`,
|
||
совпадающим с SAN тестового сертификата; production hostname verification не ослаблялась.
|
||
- AutoUpdateDocs повторно синхронизировал `docs/API.md`, `docs/ARCHITECTURE.md` и
|
||
`docs/INFRASTRUCTURE.md` с финальной receipt/snapshot-схемой.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `TenantConfigWatcherTest,TenantLifecycleServiceTest,DatabaseControllerTest,TenantRoutingDataSourceTest` | 53 теста, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| `TenantLifecyclePostgreSqlIntegrationTest` | 2 теста, 0 failures, 0 errors, 0 skipped; реальный PostgreSQL 16.3/Testcontainers. |
|
||
| `KubernetesTenantSecretUpdaterTest` | 21 тест, 0 failures, 0 errors, 0 skipped; TLS, CAS, reconciliation и компенсация. |
|
||
| Полный `mvn -Dapi.version=1.44 test` | 200 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| `git diff --check` | Успешно. |
|
||
| Повторные LF-нормализованные SHA-256 V1/V2/V3/V4 | Совпадают с ранее зафиксированными значениями; миграционные файлы не изменялись. |
|
||
|
||
### № 11 — достоверный статус проверки конфликтов на дашборде
|
||
|
||
- `formatLocalDate()` формирует `YYYY-MM-DD` из локальных компонентов `Date` без
|
||
`toISOString()`. `currentWeekDateRange()` создаёт отдельный понедельник и вычисляет
|
||
воскресенье как `понедельник + 6 дней`, поэтому начало месяца и переход года не мутируют
|
||
исходную дату повторно.
|
||
- Расписания кафедр загружаются независимо через `Promise.allSettled`. Результат хранит число
|
||
ответивших кафедр и одно из состояний: `COMPLETE`, `PARTIAL` или `NOT_RUN`; технические
|
||
причины отказов не входят в UI-модель.
|
||
- `conflictCheckPresentation()` проверяет диапазон, количество конфликтов и согласованность
|
||
счётчиков fail-closed. Зелёная карточка разрешена только для `COMPLETE`, когда проверены все
|
||
кафедры и конфликтов нет. `PARTIAL` всегда показывает предупреждение и не скрывает найденные
|
||
по доступной части конфликты, а полный отказ отображается как «Проверка не выполнена».
|
||
- Карточки используют русские безопасные тексты, экранирование, явные success/warning/error
|
||
состояния, период и число проверенных кафедр. Контейнер получил live-region и `aria-busy`,
|
||
кнопка — `type="button"`; после ошибки начальной загрузки «Перепроверить» повторно получает
|
||
справочники, а не анализирует пустой список.
|
||
- Тот же локальный formatter устранит сдвиг даты сегодняшнего расписания до 03:00 по Москве.
|
||
Остальная часть общей timezone-проблемы остаётся в объёме № 29.
|
||
- Добавлены dependency-free тесты на встроенном `node:test` и воспроизводимые npm-команды.
|
||
AutoUpdateDocs синхронизировал структуру, контракт Red Zone и команды проверки в
|
||
`docs/FRONTEND.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `npm test` в `frontend/` | 12 тестов, 12 passed, 0 failed; локальная полночь `2026-07-02 00:15 Europe/Moscow`, диапазон `2026-06-29…2026-07-05`, переход года и состояния отказов. |
|
||
| `npm run check` в `frontend/` | Синтаксис `dashboard-conflicts.js` и `dashboard.js`, затем 12 тестов — успешно. |
|
||
| Рекурсивный `node --check` для `frontend/**/*.js` и `frontend/**/*.mjs` | 25 файлов, ошибок нет. |
|
||
| Полный backend-регресс | Backend на этапе № 11 не изменялся; последний полный прогон этапа № 13 остаётся зелёным: 200/0/0/0. |
|
||
| `git diff --check` | Успешно. |
|
||
| Защищённые файлы | `BUG_REPORT.md` и существующие Flyway-миграции не изменялись. |
|
||
|
||
### № 12 — access JWT только в памяти и same-origin JavaScript
|
||
|
||
- Добавлен единый `frontend/auth-session.js` для login, admin/settings, teacher и student.
|
||
Access JWT, роль, кафедра и ID пользователя существуют только в памяти ES-модуля;
|
||
legacy-ключи удаляются из `localStorage` и `sessionStorage`, но больше не читаются и не
|
||
записываются. После navigation/reload новый документ восстанавливает профиль через
|
||
`POST /api/auth/refresh` и `HttpOnly` cookie.
|
||
- `fetchWithAuth()` добавляет bearer-токен из памяти, дедуплицирует параллельные refresh-
|
||
запросы одним Promise и повторяет исходный запрос не более одного раза. Logout отзывает
|
||
серверную cookie-сессию и очищает память даже при сетевой ошибке.
|
||
- Inline CSS/JS кабинетов преподавателя и студента вынесены в `style.css`/`app.js`; inline-
|
||
редиректы кафедры и учебного отдела удалены. Admin/settings запускают UI только после
|
||
успешного async session bootstrap и проверяют роль из общего профиля.
|
||
- Удалён `admin/js/otel.js` с runtime-import из `esm.sh`. OpenTelemetry 2.9.0/0.220.0 и
|
||
auto-instrumentations 0.65.0 зафиксированы exact-версиями в `package-lock.json`, собираются
|
||
esbuild в `/vendor/otel.js` на первом этапе Dockerfile и загружаются только с текущего
|
||
origin. `npm audit` не обнаруживает уязвимостей.
|
||
- Apache подключает `security.conf`: `script-src 'self'`, `script-src-attr 'none'`,
|
||
`style-src 'self'`, exact SHA-256 для статических style-атрибутов, `connect-src 'self'`,
|
||
запрет frame/object и дополнительные защитные заголовки. `unsafe-inline`, `unsafe-eval`,
|
||
удалённые JS imports и динамические inline-стили отсутствуют; тест проверяет полный набор
|
||
CSP-хэшей. Базовые frontend-образы зафиксированы digest; остальные пункты № 30 остаются в
|
||
его собственном объёме.
|
||
- AutoUpdateDocs синхронизировал `docs/FRONTEND.md`, `docs/INFRASTRUCTURE.md`,
|
||
`docs/ARCHITECTURE.md`, `docs/API.md` и устаревший пример в `docs/DEVELOPMENT.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `npm run check` в `frontend/` | 21 тест: 12 dashboard + 5 auth session + 4 CSP/security; синтаксис критических модулей и все тесты успешны. |
|
||
| `npm audit --audit-level=moderate` | `found 0 vulnerabilities`. |
|
||
| Локальный `npm run build:vendor` | Same-origin bundle `dist/vendor/otel.js`, 327471 байт; удалённых imports нет. |
|
||
| `docker build -t magistr-frontend-codex:bug12 frontend` | Multi-stage npm/esbuild → Apache завершён успешно. |
|
||
| Live container HTTP check | `/` и `/vendor/otel.js` вернули `200`; CSP и дополнительные headers присутствуют, `unsafe-inline`/`unsafe-eval` отсутствуют. Временный контейнер остановлен и удалён. |
|
||
| `git diff --check` | Успешно. |
|
||
| Защищённые файлы | `BUG_REPORT.md` и существующие Flyway-миграции не изменялись. |
|
||
|
||
### № 33 и № 34 — безопасный DOM, скрытые пароли и языковой регламент
|
||
|
||
- Для ошибок загрузки SPA-вкладок, статуса/списка tenant-БД и заявок преподавателей добавлены
|
||
общие `renderStatusMessage()`/`renderTableMessage()`. Они создают DOM-узлы, записывают
|
||
фиксированный русский текст через `textContent` и атомарно заменяют содержимое через
|
||
`replaceChildren`; `exception.message` больше не попадает в эти UI-границы.
|
||
- Поля пароля при создании пользователя, настройке tenant-БД и одобрении заявки преподавателя
|
||
используют `type="password"` и `autocomplete="new-password"`. Поля логина помечены
|
||
`autocomplete="username"`.
|
||
- Метки доступности БД `Online`/`Offline` заменены на `Доступно`/`Недоступно`. Затронутые
|
||
production-логи `TenantDataSourceConfig`, `TenantRoutingDataSource` и `TenantInterceptor`
|
||
переведены на русский; вместо текста исключения фиксируется только безопасный технический
|
||
тип, а стек остаётся в отдельной русскоязычной записи `DEBUG`. Англоязычные frontend-логи
|
||
загрузки оборудования, форм обучения и подгрупп также русифицированы; raw `error.message`
|
||
больше не дописывается в собственные browser-console сообщения.
|
||
- CSP пересчитан после удаления двух неиспользуемых style-атрибутов. Статический тест требует
|
||
точного равенства множества SHA-256-хэшей реальным атрибутам и не допускает ослабления
|
||
`unsafe-inline`/`unsafe-eval`.
|
||
- AutoUpdateDocs синхронизировал `docs/FRONTEND.md`, `docs/ARCHITECTURE.md` и
|
||
`docs/LOGGING.md` с безопасным DOM, парольными полями и правилами журналирования.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `npm run check` в `frontend/` | 24 проверки: 12 dashboard + 5 auth session + 7 security; все успешны. |
|
||
| `npm audit --audit-level=moderate` | `found 0 vulnerabilities`. |
|
||
| Полный контейнерный `mvn -Dapi.version=1.44 test` | 200 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| `docker build -t magistr-frontend-codex:bug12-34 frontend` | Multi-stage npm/esbuild → Apache завершён успешно. |
|
||
| Live container HTTP check | `/` и `/vendor/otel.js` вернули `200`; актуальный CSP и дополнительные headers присутствуют. Временный контейнер остановлен и удалён. |
|
||
| `git diff --check` | Успешно. |
|
||
| Защищённые файлы | `BUG_REPORT.md` и Flyway V1–V4 не изменялись; SHA-256 совпадают с ранее зафиксированными значениями. |
|
||
|
||
### № 15 — инварианты временных слотов
|
||
|
||
- CRUD слотов перенесён из `TimeSlotAdminController` в публичный транзакционный
|
||
`TimeSlotService`. Сервис проверяет положительный номер пары, существование сетки,
|
||
`startTime < endTime`, длительность не меньше минуты и вычисляет `durationMinutes` только
|
||
по границам времени, игнорируя одноимённое поле входного DTO.
|
||
- Создание блокирует строку целевой сетки, update — строку слота и старую/новую сетки в
|
||
стабильном порядке. Внутри сетки запрещены повторный номер и пересечение по условию
|
||
`start < other.end AND end > other.start`; касающиеся границами интервалы разрешены.
|
||
- Используемый правилом слот нельзя перенести из `DEFAULT`; удаление используемого слота
|
||
также отклоняется до SQL-ошибки. Конфликт номера или интервала возвращается как русский
|
||
`409 Conflict` через общий `ScheduleConflictException`.
|
||
- Раздел инвариантов временных слотов в `V1__init.sql` проверяет seed, добавляет
|
||
`btree_gist`, CHECK вычисленной длительности, GiST exclusion constraint с
|
||
полуоткрытыми интервалами и триггеры, сериализующие создание правила с переносом слота
|
||
или изменением режима сетки.
|
||
- В настройках поле минут стало `readonly`, всегда пересчитывается при изменении границ и
|
||
больше не отправляется как доверенное значение. AutoUpdateDocs синхронизировал
|
||
`docs/API.md`, `docs/BUSINESS_LOGIC.md`, `docs/DATABASE.md` и `docs/FRONTEND.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| Контейнерный `mvn -Dtest=TimeSlotServiceTest test` без Docker socket | 6 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| Контейнерный `mvn -Dtest=!*IntegrationTest test` без Docker socket | 194 теста, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| `TimeSlotInvariantMigrationIntegrationTest` | 2 PostgreSQL-сценария базовой V1 добавлены и успешно скомпилированы; запуск Testcontainers из Maven-контейнера среда отклонила из-за запрета передачи Docker socket. |
|
||
| Изолированный PostgreSQL 16.3 без томов | Единая V1 применилась с нуля; соседние слоты приняты; overlap (`23P01`), неверная длительность (`23514`) и три нарушения базовой сетки (`23514`) отклонены. |
|
||
| Две параллельные пересекающиеся транзакции | Ровно один `COMMIT`; вторая операция отклонена `ex_time_slots_scope_no_overlap`. Одноразовый контейнер удалён. |
|
||
| `npm run check` в `frontend/` | 24 проверки, все успешны. |
|
||
| `git diff --check` | Успешно. |
|
||
| Консолидация Flyway | По прямому разрешению владельца V2–V7 удалены, их итоговая схема включена в V1. |
|
||
|
||
### № 16 — инварианты учебных годов и семестров
|
||
|
||
- CRUD годов и семестров перенесён из `AcademicCalendarAdminController` в публичный
|
||
транзакционный `AcademicPeriodService`. Названия годов нормализуются до duplicate-check,
|
||
даты обязательны и упорядочены, а календарные диапазоны трактуются как включительные.
|
||
- Учебные годы не могут пересекаться. Семестр должен полностью находиться внутри своего
|
||
года, не пересекаться с другим семестром этого года и иметь уникальный тип. Update года
|
||
отклоняет границы, которые исключат любой сохранённый семестр; update повторно проверяет
|
||
title/type и возвращает безопасный русский `409`, а не JDBC `500`.
|
||
- Semester create/update блокируют строку родительского года; update затем перечитывает
|
||
строку семестра с `PESSIMISTIC_WRITE`. Инвалидация кэша расписания регистрируется только
|
||
после успешного commit.
|
||
- Раздел учебных периодов в `V1__init.sql` проверяет seed, добавляет GiST exclusion
|
||
constraints годов и семестров с
|
||
`daterange(..., '[]')` и два триггера. Блокировка родительской строки в триггере закрывает
|
||
гонку между изменением года и конкурентной записью семестра.
|
||
- AutoUpdateDocs синхронизировал `docs/API.md`, `docs/BUSINESS_LOGIC.md`,
|
||
`docs/DATABASE.md` и `docs/ARCHITECTURE.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| Контейнерный `mvn -Dtest=AcademicPeriodServiceTest test` без Docker socket | 8 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| Контейнерный `mvn -Dtest=!*IntegrationTest test` без Docker socket | 202 теста, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| `AcademicPeriodInvariantMigrationIntegrationTest` | 2 PostgreSQL-сценария базовой V1 добавлены и успешно скомпилированы; Testcontainers-прогон из Maven-контейнера недоступен из-за запрета передачи Docker socket. |
|
||
| Изолированный PostgreSQL 16.3 без томов | Единая V1 применилась с нуля; соседние периоды приняты; overlap года/семестра (`23P01`), выход семестра и сужение года (`23514`) отклонены. |
|
||
| Две параллельные пересекающиеся транзакции годов | Ровно один `COMMIT`; вторая операция отклонена `ex_academic_years_no_overlap`. Одноразовый контейнер удалён. |
|
||
| `git diff --check` | Успешно. |
|
||
| Текущая baseline V1 | SHA-256 `5f9326b545d14a8afb3c75de9e59ea9a35b36ed02fe5a8a822e8430fc3e972cd`. |
|
||
|
||
### № 17 — совместимость сохранённых календарных назначений и сеток
|
||
|
||
- Добавлен публичный транзакционный `AcademicStructureService`. Обновление графика
|
||
блокирует его строку и до мутации проверяет все назначения, старшие курсы сетки,
|
||
дисциплины старших семестров и даты сетки относительно нового учебного года.
|
||
- Обновление группы блокирует её строку и повторно проверяет год начала обучения,
|
||
специальность, профиль и форму для всех назначений. При несовместимости
|
||
`ScheduleConflictException` преобразуется в русский `409 Conflict`; сущность и назначения
|
||
остаются без изменений.
|
||
- Сохранение назначения блокирует группу, график, учебный год и существующую запись,
|
||
проверяет совпадение измерений и вычисленный курс. Инвалидация кэша выполняется только
|
||
после commit.
|
||
- V1 содержит триггеры совместимости назначения, обратной защиты группы и графика,
|
||
размерности строк сетки и дисциплин, а также защиты зависимостей при изменении границ
|
||
учебного года. Seed назначения теперь сразу связывает календарь по году, специальности,
|
||
профилю и форме обучения.
|
||
- Добавлены 7 unit-тестов сервиса и 2 Testcontainers-сценария базовой схемы, включая гонку
|
||
изменения группы с созданием назначения.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| Контейнерный `mvn -Dtest=AcademicStructureServiceTest test` | 7 тестов, 0 failures, 0 errors, BUILD SUCCESS. |
|
||
| Контейнерный `mvn -Dtest=!*IntegrationTest test` | 209 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| Контейнерный `mvn -DskipTests test` | Все 46 test source-файлов, включая DB-интеграционные, успешно скомпилированы. |
|
||
| Чистая PostgreSQL 16.3 без томов | Единая V1 применилась; 0 несовместимых seed-назначений; созданы 4 ключевых constraint и 5 dependency-trigger. |
|
||
| Прямые DB-изменения | Изменения формы графика/группы, уменьшение `course_count`, строка курса 5 и дисциплина семестра 9 отклонены с `23514`; назначение сохранено. |
|
||
| Две конкурентные транзакции | Ровно один `COMMIT`; несовместимое назначение отклонено с `23514`, итоговое состояние согласовано. |
|
||
| `git diff --check` | Успешно. |
|
||
|
||
### № 18 — историческая модель кафедр преподавателя
|
||
|
||
- Добавлен публичный транзакционный `TeacherDepartmentService`. Он разрешает основную и
|
||
дополнительную кафедру на целевую дату, пакетно загружает назначения за диапазон и
|
||
возвращает исторических преподавателей по периоду действия пользователя и назначения.
|
||
- `UserController`, `DepartmentWorkspaceController`, `TeacherSubjectController` и
|
||
`WorkloadController` больше не используют `users.department_id` как источник бизнес-
|
||
решений. Legacy-поле остаётся зеркалом основной кафедры на текущую дату.
|
||
- Будущий перевод закрывает прежний основной период днём перед `validFrom`, но сохраняет
|
||
текущую принадлежность до вступления новой записи в силу. Перевод на текущую или прошлую
|
||
дату синхронизирует legacy-зеркало. Пользователь и история блокируются до изменения.
|
||
- Нагрузка определяет кафедру отдельно на дату каждого занятия: сначала по основной, затем
|
||
по дополнительной связи. Перевод внутри диапазона разделяет агрегат одного преподавателя
|
||
между кафедрами, а прошлый отчёт не зависит от его текущей кафедры.
|
||
- V1 содержит GiST exclusion constraint
|
||
`ex_teacher_primary_department_no_overlap`, запрещающий пересечение любых основных
|
||
периодов одного преподавателя, включая конкурентную запись из нескольких pod.
|
||
- AutoUpdateDocs синхронизировал `docs/API.md`, `docs/BUSINESS_LOGIC.md`,
|
||
`docs/DATABASE.md` и `docs/ARCHITECTURE.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| Контейнерный `mvn -Dtest=!*IntegrationTest test` без Docker socket | 216 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS; все 49 test source-файлов скомпилированы. |
|
||
| Целевые service/controller tests | 12 тестов: будущий и текущий перевод, архивная история, приоритет основной связи, fallback на дополнительную, доступ к дисциплине и разделение нагрузки; все успешны. |
|
||
| Чистая PostgreSQL 16.3 без томов | Точная текущая V1 применилась с нуля; constraint `ex_teacher_primary_department_no_overlap` создан. |
|
||
| Прямые DB-изменения | Соседние основные периоды и дополнительное назначение приняты; пересекающийся основной период отклонён с `23P01`. |
|
||
| Две конкурентные транзакции | Ровно один `COMMIT`; вторая пересекающаяся основная запись отклонена с `23P01`. |
|
||
| `npm run check` в `frontend/` | 3 test suite и синтаксические проверки завершены успешно. |
|
||
| `docker compose config --quiet` | Успешно. |
|
||
| `git diff --check` | Успешно. |
|
||
| Текущая baseline V1 | SHA-256 `5f9326b545d14a8afb3c75de9e59ea9a35b36ed02fe5a8a822e8430fc3e972cd`. |
|
||
|
||
### № 19 — ограничение кафедрального доступа к workload
|
||
|
||
- `WorkloadController` вычисляет разрешённый `departmentId` до любого вызова
|
||
`ScheduleQueryService`. Для роли `DEPARTMENT` отсутствие фильтра означает кафедру из
|
||
`AuthContext`, а несовпадающий ID отклоняется с русским `403 Forbidden`.
|
||
- Единое правило применено к отчётам преподавателей, аудиторий, кафедр, временных слотов и
|
||
свободным аудиториям. Последний endpoint не принимает `departmentId`, но его внутренняя
|
||
выборка для кафедры также больше не является глобальной.
|
||
- `ADMIN`, `EDUCATION_OFFICE` и `SCHEDULE_VIEWER` сохраняют явно документированное право на
|
||
глобальный workload или выбор конкретной кафедры. Отсутствующий `AuthContext` обрабатывается
|
||
fail-closed с русским `401`.
|
||
- AutoUpdateDocs синхронизировал `docs/API.md`, `docs/BUSINESS_LOGIC.md` и
|
||
`docs/ARCHITECTURE.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| Контейнерный `mvn -Dtest=WorkloadControllerTest test` без Docker socket | 5 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| Межкафедральный сценарий | Чужой `departmentId` даёт `403` до вызова поиска; отсутствие ID и все пять endpoints используют кафедру из `AuthContext`. |
|
||
| Привилегированный сценарий | `EDUCATION_OFFICE` сохраняет глобальный scope без `departmentId`. |
|
||
| Контейнерный `mvn -Dtest=!*IntegrationTest test` без Docker socket | 219 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| `docker compose config --quiet` | Успешно. |
|
||
| `git diff --check` | Успешно. |
|
||
|
||
### № 20 — workload без интерактивного лимита 50 групп
|
||
|
||
- В `ScheduleQueryService` выделен узкий публичный `searchForAggregation(departmentId,
|
||
timeSlotId, startDate, endDate)`. Он не принимает остальные UI-фильтры и поэтому не
|
||
становится вторым общим поисковым API.
|
||
- Обычный `search()` продолжает отклонять широкий пользовательский запрос, если тот затрагивает
|
||
больше 50 активных групп. Оба пути используют единый внутренний алгоритм проверки диапазона,
|
||
выбора групп, генерации, применения одного снимка overrides, фильтрации и дедупликации.
|
||
- Все workload-отчёты и free-classrooms переведены на aggregate-путь. Кафедральный scope из
|
||
№19 вычисляется до генерации и сохранён без изменений.
|
||
- Параметризованный тест доказывает полный вызов генератора для 51 и 100 активных групп;
|
||
отдельный regression-test сохраняет отказ интерактивного поиска на 51-й группе.
|
||
- AutoUpdateDocs синхронизировал `docs/API.md`, `docs/BUSINESS_LOGIC.md` и
|
||
`docs/ARCHITECTURE.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| Контейнерный `mvn -Dtest=ScheduleQueryServiceTest,WorkloadControllerTest test` | 9 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| Aggregate 51/100 групп | Все активные группы переданы генератору, overrides прочитаны один раз, исключение лимита не возникает. |
|
||
| Интерактивный regression | Обычный широкий `search()` с 51 активной группой по-прежнему отклонён с просьбой уточнить scope. |
|
||
| Контейнерный `mvn -Dtest=!*IntegrationTest test` без Docker socket | 221 тест, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| `docker compose config --quiet` | Успешно. |
|
||
| `git diff --check` | Успешно. |
|
||
|
||
### № 21 — безопасный импорт дисциплин кафедры
|
||
|
||
- Импорт вынесен из `DepartmentWorkspaceController` в транзакционный
|
||
`SubjectImportService`. Весь payload нормализуется и дедуплицируется по названию без учёта
|
||
регистра до мутации; повтор внутри payload обрабатывается один раз.
|
||
- Глобальная уникальность названия, уже заявленная в бизнес-документации, сохранена. Если
|
||
запись принадлежит текущей кафедре, повторный импорт обновляет её код, восстанавливает из
|
||
архива и сохраняет прежний ID. Совпадение с другой кафедрой даёт русский `409 Conflict`,
|
||
не меняя владельца или код чужой записи.
|
||
- В V1 прежний case-sensitive `UNIQUE(name)` заменён функциональным уникальным индексом
|
||
`uq_subjects_name_ci` на `lower(name)`. Он закрывает регистровые дубли и гонку импортов
|
||
разных кафедр; admin-create также проверяет имя без учёта регистра.
|
||
- Ошибка уникальности при `saveAllAndFlush` преобразуется в безопасный доменный конфликт.
|
||
Новых миграционных файлов не создано.
|
||
- AutoUpdateDocs синхронизировал `docs/API.md`, `docs/BUSINESS_LOGIC.md`,
|
||
`docs/DATABASE.md` и `docs/ARCHITECTURE.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| Контейнерный `mvn -Dtest=SubjectImportServiceTest,DepartmentWorkspaceControllerTest test` | 7 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| Повторный собственный импорт | Два последовательных импорта возвращают тот же объект/ID, обновляют код и восстанавливают архивную запись. |
|
||
| Совпадение чужой кафедры | Получен доменный `409`; чужие `departmentId` и код не изменены, `saveAllAndFlush` не вызван. |
|
||
| Контейнерный `mvn -Dtest=!*IntegrationTest test` без Docker socket | 225 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS; 51 test source-файл скомпилирован. |
|
||
| Чистая PostgreSQL 16.3 без томов | Точная текущая V1 применилась с нуля; `информатика` отклонена как дубль seed `Информатика` по `uq_subjects_name_ci` (`23505`). |
|
||
| Две конкурентные транзакции | Первая запись `Теория систем` закоммичена; параллельная `теория систем` после ожидания отклонена `uq_subjects_name_ci` (`23505`). |
|
||
| `docker compose config --quiet` | Успешно. |
|
||
| `git diff --check` | Успешно. |
|
||
| Текущая baseline V1 | SHA-256 `5f9326b545d14a8afb3c75de9e59ea9a35b36ed02fe5a8a822e8430fc3e972cd`. |
|
||
|
||
### № 22 — согласованная матрица прав учебного отдела
|
||
|
||
- `SpecialityController` сохранил class-level `ADMIN` для всех write-операций, но обязательные
|
||
GET специальностей, общего списка профилей и профилей специальности явно разрешены
|
||
`EDUCATION_OFFICE`.
|
||
- `EducationFormController` разрешает `ADMIN` и `EDUCATION_OFFICE` создавать и удалять формы
|
||
обучения. Широкий read-only GET для остальных ранее разрешённых ролей сохранён method-level
|
||
аннотацией; защита используемых группами/графиками форм не ослаблена.
|
||
- Дубли матриц `ROLE_NAVIGATION` и `ROLE_TABS` удалены. Основной admin SPA и settings SPA
|
||
используют единый `frontend/admin/js/role-capabilities.js`, где для учебного отдела явно
|
||
перечислены календарный график, временные слоты и формы обучения.
|
||
- MockMvc-тест проходит через реальный `AuthorizationInterceptor`: GET specialties/profiles и
|
||
GET/POST/DELETE education forms возвращают `200`, а POST специальности остаётся `403`.
|
||
- Frontend-тест импортирует матрицу как ES-модуль, проверяет capabilities учебного отдела и
|
||
отсутствие локальных дубликатов в двух entrypoint-файлах.
|
||
- AutoUpdateDocs синхронизировал `docs/API.md`, `docs/BUSINESS_LOGIC.md`,
|
||
`docs/FRONTEND.md` и `docs/ARCHITECTURE.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| Контейнерный `mvn -Dtest=EducationOfficeRoleMatrixTest test` | 2 теста, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| MockMvc role matrix | Штатная инициализация справочников календаря и CRUD форм обучения не получают `403`; изменение специальности учебным отделом отклонено. |
|
||
| `npm run check` в `frontend/` | Синтаксис entrypoints и 3 test suite, включая единую role matrix, завершены успешно. |
|
||
| Контейнерный `mvn -Dtest=!*IntegrationTest test` без Docker socket | 227 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS; 52 test source-файла скомпилированы. |
|
||
| `docker compose config --quiet` | Успешно. |
|
||
| `git diff --check` | Успешно. |
|
||
|
||
### № 23 — batch-снимок генерации расписания
|
||
|
||
- `ScheduleQueryService` больше не вызывает генератор отдельно для каждой группы: обычный
|
||
поиск, workload-агрегаты и построение полного базового дня передают весь набор групп в
|
||
`ScheduleGeneratorService.buildScheduleForGroups()`.
|
||
- `AcademicDateService.ScheduleSnapshot` одним набором запросов загружает пересекающиеся
|
||
семестры, назначения календарей групп и `academic_calendar_days` от начала затронутого
|
||
семестра до конца диапазона. Внутренние проверки semester/assignment/activity работают по
|
||
неизменяемым lookup-картам.
|
||
- Правила группы и преподавателя получили batch-запросы по набору семестров. Генератор
|
||
индексирует их по `groupId + semesterId`, поэтому репозиторий не вызывается из циклов дат,
|
||
правил и групп.
|
||
- Ручные назначения сетки звонков, weekday scopes и временные слоты также читаются один раз
|
||
на диапазон; фактический слот выбирается по `date/scope/orderNumber` без дополнительных
|
||
запросов на каждое занятие.
|
||
- `ScheduleGenerationQueryCountTest` обрабатывает 120 включительных дат, 30 групп и 20
|
||
правил. Тест измеряет ровно 7 чтений репозиториев, запрещает старые point-методы и
|
||
одновременно подтверждает 30 корректных занятий с эффективной weekday-сеткой.
|
||
- AutoUpdateDocs синхронизировал `docs/BUSINESS_LOGIC.md` и `docs/ARCHITECTURE.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `ScheduleGenerationQueryCountTest` | 1 тест, 0 failures, 0 errors, 0 skipped; 120 дат, 30 групп, 20 правил, 7 repository reads. |
|
||
| `ScheduleQueryServiceTest,ScheduleQueryServiceTeacherOverrideTest` | 9 тестов, 0 failures, 0 errors, 0 skipped; batch-путь и overrides сохранены. |
|
||
| Контейнерный `mvn -Dtest=!*IntegrationTest test` без Docker socket | 228 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS; 53 test source-файла скомпилированы. |
|
||
|
||
### № 24 — tenant-aware cleanup refresh-сессий
|
||
|
||
- `RefreshTokenCleanupJob` получает атомарный snapshot доменов из
|
||
`TenantRoutingDataSource`, устанавливает `TenantContext` до вызова repository proxy и
|
||
очищает каждую tenant-БД отдельно. Исходный tenant потока восстанавливается в `finally`.
|
||
- По умолчанию истёкшие и отозванные строки сохраняются 30 дней для аудита. Retention,
|
||
включение job, размер пачки, максимум пачек, interval и initial delay задаются через
|
||
`JWT_REFRESH_CLEANUP_*`.
|
||
- Native batch-delete выбирает только записи с `expires_at < cutoff` либо старым
|
||
`revoked_at`, использует `FOR UPDATE SKIP LOCKED` и отдельную транзакцию repository-метода.
|
||
Два pod разбирают непересекающиеся строки; повторная очистка безопасно возвращает 0.
|
||
- Локальный `AtomicBoolean` не допускает наложения двух запусков внутри одного pod, а лимит
|
||
пачек ограничивает длительность одного прохода. Ошибка одной tenant-БД логируется по-русски
|
||
без технического текста и не останавливает очистку остальных.
|
||
- В V1 добавлены частичный индекс по `revoked_at` и индекс по `expires_at`; новых файлов
|
||
миграций не создавалось. Текущий SHA-256 V1:
|
||
`5f9326b545d14a8afb3c75de9e59ea9a35b36ed02fe5a8a822e8430fc3e972cd`.
|
||
- AutoUpdateDocs синхронизировал `docs/DATABASE.md`, `docs/ARCHITECTURE.md` и
|
||
`docs/INFRASTRUCTURE.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `RefreshTokenCleanupJobTest,RefreshTokenServiceTest` | 7 тестов, 0 failures, 0 errors, 0 skipped; два job, retention, idempotency, overlap guard и disabled mode. |
|
||
| Контейнерный `mvn -Dtest=!*IntegrationTest test` без Docker socket | 231 тест, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS; 54 test source-файла скомпилированы. |
|
||
| Точная V1 на чистой PostgreSQL 16.3 | Схема и два cleanup-индекса созданы без ошибок. |
|
||
| Две конкурентные PostgreSQL-транзакции | При незавершённой первой транзакции обе удалили разные пачки по 3 строки; повтор удалил 0, fresh-expired/fresh-revoked/active сохранены. |
|
||
| Testcontainers через Maven-контейнер | Не запускался: среда отклонила проброс `/var/run/docker.sock`; PostgreSQL-специфичный SQL проверен безопасным отдельным контейнером без socket mount. |
|
||
|
||
### № 25 — безопасное преобразование DB/JDBC-ошибок
|
||
|
||
- `GlobalExceptionHandler` перехватывает `DataIntegrityViolationException` до общего `500`
|
||
и делегирует определение безопасного ответа в `DatabaseConstraintViolationMapper`.
|
||
- Именованные CHECK-ограничения текущей V1 преобразуются в русские `400`; UNIQUE, FK и
|
||
GiST exclusion constraints — в русские `409`. Неизвестный SQLState класса integrity
|
||
получает обобщённый `409` без текста драйвера.
|
||
- Технические `constraint` и SQLState доступны только в структурированном журнале.
|
||
JDBC/SQL message, stack trace, запросы и значения параметров в HTTP JSON не включаются.
|
||
- Локальные `catch Exception`, которые формировали тело ответа через `getMessage()`, удалены:
|
||
persistence-ошибки доходят до общего handler, а контролируемые доменные сообщения сохранены.
|
||
- AutoUpdateDocs синхронизировал `docs/API.md`, `docs/ARCHITECTURE.md` и
|
||
`docs/DEVELOPMENT.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `GlobalExceptionHandlerDataIntegrityTest` | 4 теста, 0 failures, 0 errors, 0 skipped; известные CHECK/UNIQUE и неизвестные нарушения не раскрывают constraint, JDBC URL, SQL или пароль. |
|
||
| Связанные controller tests | `ScheduleOverrideControllerTest` и `ScheduleRuleAdminControllerTest`: 5 тестов, 0 failures, 0 errors. |
|
||
| Контейнерный `mvn -Dtest=!*IntegrationTest test` без Docker socket | 235 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS; 55 test source-файлов скомпилированы. |
|
||
| `docker compose config --quiet` | Успешно. |
|
||
| `git diff --check` | Успешно. |
|
||
| Каталог Flyway | Содержит только `V1__init.sql`; SHA-256 не изменился: `5f9326b545d14a8afb3c75de9e59ea9a35b36ed02fe5a8a822e8430fc3e972cd`. |
|
||
|
||
### № 26 — самодостаточный локальный Docker Compose
|
||
|
||
- Compose больше не требует заранее созданную внешнюю сеть: bridge-сеть `magistr`, DNS
|
||
сервисов и именованный том `postgres_data` создаются самим проектом.
|
||
- Frontend публикует `${HTTP_PORT:-80}` и через Apache `mod_proxy` направляет same-origin
|
||
`/api` и `/actuator/health` во внутренний backend. Исходный `Host` сохраняется, поэтому
|
||
локальный запрос выбирает tenant `default`; backend и PostgreSQL наружу не публикуются.
|
||
- `${POSTGRES_DB:-app_db}` одновременно задаёт создаваемую БД и
|
||
`SPRING_DATASOURCE_URL`. Backend получает обязательные `POSTGRES_PASSWORD`, `JWT_SECRET`,
|
||
JWT TTL и cleanup-параметры из `.env`; безопасный шаблон добавлен в `.env.example`.
|
||
- PostgreSQL закреплён на версии 16.3 и хранит данные в named volume. Backend имеет
|
||
liveness healthcheck, а frontend запускается после его готовности; `up --wait` теперь
|
||
подтверждает не только старт процесса, но и рабочий Spring context.
|
||
- Live startup обнаружил, что Spring не мог выбрать production-конструкторы
|
||
`TenantDatabaseHealthMonitor` и `RefreshTokenCleanupJob`, хотя unit-тесты создавали их
|
||
вручную. Оба конструктора явно помечены для injection; backend стабильно выходит в
|
||
`Started Application`.
|
||
- Скачивание mutable OpenTelemetry `latest`, блокировавшее чистую сборку, заменено
|
||
фиксированным Maven-артефактом `2.28.1` с обязательной SHA-256 проверкой. Локальный OTel
|
||
SDK по умолчанию отключён, так как Collector не входит в Compose.
|
||
- AutoUpdateDocs синхронизировал `docs/README.md` и `docs/INFRASTRUCTURE.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `docker compose up -d --build --wait` | PostgreSQL, backend healthcheck и frontend готовы; внутренняя сеть и `magistr_postgres_data` созданы автоматически. |
|
||
| Live HTTP через frontend proxy | `/` → `200`, `/actuator/health/liveness` → `200`, защищённый `/api/departments` → ожидаемый `401`. |
|
||
| Публикация порта | Порт 80 уже занят пользовательским Caddy; эквивалентная проверка успешно выполнена через документированный `HTTP_PORT=18080`, чужой сервис не останавливался. |
|
||
| PostgreSQL mount | Docker inspect: named volume `magistr_postgres_data` смонтирован в `/var/lib/postgresql/data`; backend и DB не имеют host ports. |
|
||
| Java Agent supply chain | Maven Central `opentelemetry-javaagent:2.28.1`, SHA-256 `faa89bdeebf9b1f52be4a4374689176717b02a59df2d8f8b6eb9aa39f9292589`, build check `OK`. |
|
||
| Контейнерный `mvn -Dtest=!*IntegrationTest test` без Docker socket | 235 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| `docker compose config --quiet` и `git diff --check` | Успешно. |
|
||
| Каталог Flyway | По-прежнему содержит только `V1__init.sql`; новых миграций нет. |
|
||
|
||
### № 27 — обязательные CI/CD gates и immutable rollout
|
||
|
||
- `.gitea/workflows/docker-build.yaml` получил обязательный `checks` job: полный backend
|
||
non-integration suite, frontend `npm ci && npm run check`, Compose validation и shell-тест
|
||
immutable deployment. Build/push jobs имеют `needs: checks`, поэтому непроверенный commit
|
||
не публикуется и не разворачивается.
|
||
- Metadata публикует полный `sha-<commit>` и release tag без `latest`. Оба build job отдают
|
||
registry digest, а deploy формирует ссылки `image@sha256:...`; release tag больше не
|
||
перезапускает старый `:main`.
|
||
- Workflow-wide `magistr-production` concurrency lock сериализует production-запуски.
|
||
`kubectl v1.33.12` загружается с официального CDN и проходит проверку закреплённого
|
||
SHA-256 до установки.
|
||
- `scripts/deploy-images.sh` отклоняет любые tag-only ссылки, сохраняет текущие backend и
|
||
frontend image refs, применяет оба новых digest и ждёт rollout. Ошибка `set image` или
|
||
readiness запускает восстановление обоих предыдущих образов и повторную проверку.
|
||
- `scripts/test-deploy-images.sh` использует fake kubectl для трёх сценариев: tag отклонён,
|
||
успешные digests применены, failed backend rollout вернул оба предыдущих digest.
|
||
- Во внешних `../k8s/backend.yaml` и `frontend.yaml` удалены `:main`; нулевой digest-sentinel
|
||
не может случайно выбрать mutable image. `../k8s/deploy.sh` требует два реальных digest и
|
||
подставляет их локально до apply. Backend probes переведены на HTTP liveness/readiness,
|
||
а ConfigMap теперь задаёт корректные `TENANTS_CONFIG_PATH` и
|
||
`TENANTS_CONFIG_REQUIRED=true`.
|
||
- AutoUpdateDocs синхронизировал `docs/INFRASTRUCTURE.md` и `../k8s/README.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `bash scripts/test-deploy-images.sh` | Mutable tag отклонён; success rollout завершён; failed rollout восстановил backend и frontend, тест завершён успешно. |
|
||
| Workflow static validation | Ruby YAML parser — успешно; отсутствуют `:main`, `stable.txt` и `rollout restart`; build jobs зависят от checks и экспортируют digest. |
|
||
| `bash -n` | `scripts/deploy-images.sh`, `scripts/test-deploy-images.sh` и `../k8s/deploy.sh` синтаксически корректны. |
|
||
| `K8S_DIR=../k8s bash scripts/check-production-secrets.sh` | Успешно. |
|
||
| `kubectl kustomize ../k8s >/dev/null` | Успешно. |
|
||
| `docker compose config --quiet` и `git diff --check` | Успешно. |
|
||
| Изменённые внешние файлы | `../k8s/backend.yaml`, `frontend.yaml`, `config.yaml`, `deploy.sh`, `README.md`; они находятся вне Git-корня `magistr` и отдельно проверены. |
|
||
| Production rollout | Не выполнялся: это внешняя необратимая операция, требующая отдельных полномочий и реальных build digests. |
|
||
|
||
### № 28 — общий PostgreSQL rate limit входа
|
||
|
||
- `LoginRateLimitService` выполняет создание/блокировку строки, bcrypt-проверку, изменение
|
||
счётчика и запись аудита в одной транзакции. Ключ состоит из tenant, NFKC-нормализованного
|
||
username в нижнем регистре и проверенного IP. `INSERT ... ON CONFLICT DO NOTHING` вместе
|
||
с `PESSIMISTIC_WRITE` сериализует первые и последующие попытки на разных backend-pod.
|
||
- Пять отказов за 15 минут по умолчанию дают блокировку на 60 секунд. Следующая ошибка после
|
||
окончания блокировки удваивает срок до максимума 15 минут. Controller возвращает `429`
|
||
с округлённым вверх `Retry-After`; успешный вход удаляет состояние.
|
||
- Неизвестный, архивный и ошибочный пользователь проходят одинаковый bcrypt-путь и получают
|
||
одинаковый `401`. В production-журнал попадает только SHA-256 fingerprint нормализованного
|
||
имени; пароль и признак существования пользователя не сохраняются и не логируются.
|
||
- `ClientIpResolver` игнорирует `X-Forwarded-For` от недоверенного непосредственного
|
||
источника, ограничивает длину/число hops и разбирает допустимую цепочку справа налево.
|
||
Compose доверяет внутренней Docker-сети, K3s ConfigMap — стандартной pod-сети Traefik;
|
||
нестандартный CIDR документирован как обязательная настройка оператора.
|
||
- В V1 добавлены `auth_login_rate_limits`, `auth_login_attempt_audit`, уникальный ключ,
|
||
CHECK и cleanup-индексы. `LoginAttemptAuditCleanupJob` раз в сутки tenant-aware пачками
|
||
`FOR UPDATE SKIP LOCKED` удаляет audit и неактивные счётчики старше 90 дней. Новых файлов
|
||
миграций не создано; текущий SHA-256 V1:
|
||
`250cab68c9ab6d8401619447585b47d6122458d1cb00a6e76af308414f89958d`.
|
||
- AutoUpdateDocs синхронизировал `docs/API.md`, `docs/ARCHITECTURE.md`,
|
||
`docs/DATABASE.md`, `docs/INFRASTRUCTURE.md` и внешний `../k8s/README.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `ClientIpResolverTest`, `LoginRateLimitServiceTest`, `AuthControllerTest` | 10 тестов, 0 failures; trusted chain, spoofed header, uniform reject, progressive block и `Retry-After`. |
|
||
| `LoginRateLimitConcurrencyIntegrationTest` | 2 теста, 0 failures; две отдельные service instance одной PostgreSQL-БД атомарно получили `REJECTED` + `BLOCKED`, failure count 2; cleanup сохранил fresh/active rows. |
|
||
| Полный контейнерный `mvn test` | 266 тестов: 263 успешны; все 12 тестов № 28 прошли. Три ранее добавленных конкурентных migration-теста нестабильны: два получили допустимый PostgreSQL `40P01` вместо ожидаемого только `23P01`, один запуск не воспроизвёл гонку. Стабилизация оставлена в финальном регрессионном аудите. |
|
||
| Чистая PostgreSQL 16.3 | Единственная V1 многократно применена Flyway без ошибок; обе новые таблицы и индексы созданы. |
|
||
| Compose, Kustomize, secret-scan, `git diff --check` | Успешно. |
|
||
| Изменённые внешние файлы | `../k8s/config.yaml`, `../k8s/README.md`; каталог вне Git-корня и проверен отдельно. |
|
||
|
||
### № 29 — единая временная зона бизнес-дат
|
||
|
||
- Добавлен `BusinessTimeService`: production использует UTC `Clock`, а календарную дату
|
||
вычисляет в настраиваемой зоне `BUSINESS_TIME_ZONE` с безопасным default
|
||
`Europe/Moscow`. Контроллеры пользователей, групп, кабинета кафедры, заявок и дисциплин,
|
||
генератор расписания и история кафедр получают текущую бизнес-дату через этот сервис.
|
||
- Абсолютные моменты refresh/login-аудита, lifecycle, overrides, заявок, комментариев и
|
||
назначений переведены с `LocalDateTime` на `Instant`. Hibernate и Jackson настроены на
|
||
UTC; все соответствующие колонки единственной `V1__init.sql` используют `TIMESTAMPTZ`.
|
||
- `formatLocalDate()` и календарная арифметика frontend используют локальные компоненты
|
||
`Date`; `toISOString().slice(0, 10)` для date-only в frontend больше не используется.
|
||
- Compose передаёт `BUSINESS_TIME_ZONE`, `TZ` и
|
||
`JAVA_TOOL_OPTIONS=-Duser.timezone=...`; `.env.example` содержит явные значения.
|
||
Kubernetes `app-config` содержит те же production-поля, поэтому backend pod не зависит от
|
||
timezone node или базового образа.
|
||
- AutoUpdateDocs синхронизировал `docs/API.md`, `docs/ARCHITECTURE.md`,
|
||
`docs/DATABASE.md`, `docs/DEVELOPMENT.md`, `docs/FRONTEND.md` и
|
||
`docs/INFRASTRUCTURE.md`.
|
||
- Новых миграционных файлов не создано; каталог Flyway содержит только `V1__init.sql`,
|
||
текущий SHA-256 после завершения № 31:
|
||
`fffedf66e8bf7d25d2d8c1b1a6146b45dd5b7e52233d05a68de8bf62c817f7a7`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `BusinessTimeServiceTest` | В первом полном прогоне обе границы московской полуночи прошли. |
|
||
| `npm test`, `node --check` | Все 4 frontend test-suite и изменённые JS-файлы прошли. |
|
||
| Static time scan | В production Java нет `LocalDate.now()`/`LocalDateTime.now()` без явной зоны; во frontend нет date-only через `toISOString`; в V1 и документации нет `TIMESTAMP` без timezone. |
|
||
| Compose и diff | `docker compose config --quiet` с обязательными секретами и `git diff --check` успешны. |
|
||
| Kubernetes timezone | Kustomize-рендер содержит `BUSINESS_TIME_ZONE=Europe/Moscow`, `TZ=Europe/Moscow` и `JAVA_TOOL_OPTIONS=-Duser.timezone=Europe/Moscow`. |
|
||
| Первый полный `mvn test` | 269 тестов: 263 успешны. После прогона исправлены новое lifecycle-ожидание, JDBC binding `Instant` и три ранее нестабильных ожидания PostgreSQL `40P01`; также усилена блокировка календарного assignment. |
|
||
| Повторный Maven/Testcontainers | Полный прогон после № 31–32: 277 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
|
||
### № 30 — воспроизводимые и проверяемые артефакты
|
||
|
||
- Backend build/runtime, PostgreSQL Compose и все PostgreSQL Testcontainers закреплены
|
||
одновременно точной версией и manifest digest. Ранее закреплённые frontend base images
|
||
сохранены.
|
||
- Все сторонние Actions в Gitea workflow закреплены полными 40-символьными commit SHA;
|
||
Maven, Node.js и Trivy запускаются только по `tag@sha256:...`.
|
||
- OpenTelemetry Java Agent и `kubectl` имеют точные версии и обязательную SHA-256 проверку до
|
||
использования.
|
||
- Оба build job публикуют BuildKit provenance `mode=max` и SBOM-attestation. Отдельный
|
||
`security-scan` проверяет backend/frontend digests закреплённым Trivy и блокирует deploy при
|
||
исправимых `HIGH`/`CRITICAL` уязвимостях.
|
||
- Добавлены `scripts/check-artifact-pinning.sh` и его регрессионный shell-тест. Проверка
|
||
контролирует Dockerfile, Compose, Testcontainers, Actions, checksum, SBOM/provenance, scan
|
||
gate и зависимость deploy от scan. Опциональный `K8S_DIR` расширяет проверку на внешние
|
||
production-манифесты.
|
||
- AutoUpdateDocs синхронизировал `docs/DATABASE.md`, `docs/DEVELOPMENT.md`,
|
||
`docs/INFRASTRUCTURE.md` и `DEVOPS.md`.
|
||
- Внешний `../k8s/otel-collector.yaml` использует официальный Collector Contrib
|
||
`0.153.0@sha256:93aad750175cbf1a973ae1c5886c3371f4d800f61be25cdd26870b8441ffe9fa`.
|
||
Версия подтверждена официальным релизом OpenTelemetry, manifest digest получен напрямую
|
||
из Docker Hub Registry API.
|
||
- Новых Flyway-файлов не создано; в каталоге миграций остаётся только `V1__init.sql` с
|
||
SHA-256 `fffedf66e8bf7d25d2d8c1b1a6146b45dd5b7e52233d05a68de8bf62c817f7a7`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `bash scripts/check-artifact-pinning.sh` | Успешно: repo image refs, Actions, checksum, SBOM и scan gates закреплены. |
|
||
| `bash scripts/test-artifact-pinning.sh` | Успешно: незакреплённый base image и удалённая SBOM гарантированно отклоняются. |
|
||
| `bash scripts/test-deploy-images.sh` | Успешно: immutable refs, rollout и парный rollback. |
|
||
| Workflow YAML, Compose config, `git diff --check` | Успешно. |
|
||
| `K8S_DIR=../k8s bash scripts/check-artifact-pinning.sh` | Успешно: все production-образы имеют полный digest. |
|
||
| `kubectl kustomize ../k8s` | Успешно; рендер содержит закреплённый OTel Collector. |
|
||
|
||
### № 31 — инварианты численности групп и подгрупп
|
||
|
||
- Создание и изменение групп теперь проходят через общий validator
|
||
`AcademicStructureService`: численность, год начала обучения и все идентификаторы должны
|
||
быть положительными, обязательные поля не могут отсутствовать.
|
||
- При обновлении сервис блокирует группу и запрещает уменьшать `groupSize` ниже суммы
|
||
численностей активных подгрупп. Создание, обновление и архивация подгрупп также блокируют
|
||
строку родительской группы до завершения транзакции.
|
||
- `subgroups.student_capacity` стал обязательным. В V1 добавлены CHECK-ограничения
|
||
положительной численности группы, года начала обучения и численности подгруппы.
|
||
- Два триггера V1 защищают перекрёстный инвариант при прямых и конкурентных записях:
|
||
сумма активных подгрупп не может превышать размер группы. Ошибка сопоставляется с
|
||
безопасным русским ответом через `DatabaseConstraintViolationMapper`.
|
||
- Новых миграций не создано: вся итоговая схема находится в `V1__init.sql`, а V2–V4
|
||
отсутствуют.
|
||
- AutoUpdateDocs синхронизировал `docs/API.md`, `docs/ARCHITECTURE.md`,
|
||
`docs/BUSINESS_LOGIC.md` и `docs/DATABASE.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `AcademicStructureServiceTest` | 11 тестов: общий create/update validator, положительные значения и запрет уменьшения ниже подгрупп прошли. |
|
||
| `AcademicStructureInvariantIntegrationTest` | 4 PostgreSQL-теста: CHECK, уменьшение группы и гонка уменьшения с добавлением подгруппы прошли; инвариант сохранён. |
|
||
| Полный контейнерный `mvn test` | 277 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
| Каталог Flyway | Ровно один `V1__init.sql`, SHA-256 `fffedf66e8bf7d25d2d8c1b1a6146b45dd5b7e52233d05a68de8bf62c817f7a7`. |
|
||
|
||
### № 32 — точный включительный лимит диапазона расписания
|
||
|
||
- `ScheduleQueryService` и `ScheduleGeneratorService` одинаково вычисляют количество
|
||
календарных дат как `ChronoUnit.DAYS.between(startDate, endDate) + 1`.
|
||
- Диапазон из 120 дат разрешён, а из 121 даты возвращает русскую ошибку валидации.
|
||
- AutoUpdateDocs уточнил включительную семантику лимита в `docs/API.md`,
|
||
`docs/ARCHITECTURE.md` и `docs/BUSINESS_LOGIC.md`.
|
||
|
||
Фактические проверки этапа:
|
||
|
||
| Команда | Результат |
|
||
|---|---|
|
||
| `ScheduleQueryServiceTest.rangeLimitCountsBothBoundaryDates` | Граница 120 разрешена, 121 отклонена. |
|
||
| `ScheduleGenerationQueryCountTest` | Существующая генерация 120 дат сохранена, запрос 121 даты отклонён до генерации. |
|
||
| Полный контейнерный `mvn test` | 277 тестов, 0 failures, 0 errors, 0 skipped, BUILD SUCCESS. |
|
||
|
||
## Финальный аудит
|
||
|
||
- Все 33 проблемы из области (`1` и `3–34`) имеют реализованную репозиторную часть и
|
||
воспроизводимое подтверждение. № 2 не изменялась, `BUG_REPORT.md` сохранён как исходный
|
||
реестр.
|
||
- Полностью закрыты и проверены 32 пункта. Для № 1 остаются только операторские ротация,
|
||
provisioning, rollout и очистка истории, которые исходный prompt прямо запрещает
|
||
выполнять без отдельных production-полномочий.
|
||
- Полный backend PostgreSQL/Testcontainers-прогон: 277 тестов, 0 failures, 0 errors,
|
||
0 skipped. Четыре frontend test-suite, `npm run check`, Compose validation,
|
||
Workflow YAML, artifact/deploy shell-тесты и `git diff --check` прошли.
|
||
- `kubectl kustomize ../k8s`, production secret-scan, синтаксис deploy-скрипта и расширенный
|
||
artifact-scan прошли. В production-манифестах отсутствуют mutable `latest` и `:main`.
|
||
- Каталог Flyway содержит ровно `V1__init.sql`; V2–V4 и другие дополнительные миграции
|
||
отсутствуют. Финальный SHA-256 V1:
|
||
`fffedf66e8bf7d25d2d8c1b1a6146b45dd5b7e52233d05a68de8bf62c817f7a7`.
|
||
|
||
## Точка продолжения
|
||
|
||
Текущий этап: **реализация и проверка всех разрешённых изменений завершены**.
|
||
|
||
Оставшееся операторское действие владельца:
|
||
|
||
1. при готовности production-полномочий выполнить `docs/SECURITY_RUNBOOK.md`: ротацию,
|
||
provisioning, rollout и очистку истории для № 1. Эти действия не выполнялись
|
||
автоматически и не требуются для завершения репозиторной реализации.
|