баг-фикс завершён

This commit is contained in:
Zuev
2026-07-19 20:16:12 +03:00
parent bc0e1ab1b4
commit ee876f1acd
43 changed files with 1228 additions and 258 deletions

View File

@@ -21,7 +21,7 @@
| `V2__subgroups_active_unique_name.sql` | `68f5525a50ddba4f8800a63cab3cd0f84779e301c4a1f29a6e0399f97fbdb30b` |
Текущая единая baseline-миграция: `V1__init.sql`, SHA-256
`250cab68c9ab6d8401619447585b47d6122458d1cb00a6e76af308414f89958d`.
`fffedf66e8bf7d25d2d8c1b1a6146b45dd5b7e52233d05a68de8bf62c817f7a7`.
Упоминания V2V7 в исторических записях ниже описывают последовательность разработки до
консолидации и не означают наличие этих файлов в текущем дереве.
@@ -62,7 +62,7 @@
| 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-БД. Оператору остаются RBAC `get/update`, mount без `subPath`, `TENANTS_CONFIG_REQUIRED=true` и HTTP probes в отсутствующем `../k8s`. | 64 целевых теста; полный backend 169/0/0/0; LF-хеши V1V4 совпадают. Kustomize не выполнен: `../k8s` отсутствует. |
| 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 30300 секунд и semantic snapshot fence по `TenantSecretUpdateReceipt`. | 53 целевых теста, 2 PostgreSQL lifecycle-теста; полный backend 200/0/0/0. |
@@ -81,10 +81,10 @@
| 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 процесса; 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. |
| 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 — успешно. |
@@ -333,10 +333,10 @@
- AutoUpdateDocs синхронизировал `docs/API.md`, `docs/ARCHITECTURE.md` и
`docs/INFRASTRUCTURE.md`. На этом этапе ограничение watcher по обновлению URL/credentials
существующего домена было оставлено проблеме № 13 и устранено следующим этапом.
- В текущей рабочей копии отсутствует внешний каталог `../k8s`, поэтому манифесты не менялись
и Kustomize не запускался. Для production обязательны directory mount без `subPath`,
- В текущем внешнем `../k8s` подтверждены directory mount без `subPath`,
`TENANTS_CONFIG_REQUIRED=true`, Role `get/update` для `tenants-secret` и HTTP liveness /
readiness probes; применение и rollout остаются внешними действиями оператора.
readiness probes. Манифесты успешно собираются Kustomize; production rollout остаётся
отдельным действием оператора.
Фактические проверки этапа:
@@ -348,7 +348,7 @@
| Контейнерный полный `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` | Не выполнено: внешний каталог `../k8s` отсутствует в текущем workspace. |
| `kubectl kustomize ../k8s`, production secret-scan и `bash -n ../k8s/deploy.sh` | Успешно в финальном аудите. |
### № 13 — полная и повторяемая синхронизация tenant-конфигурации
@@ -926,14 +926,142 @@
| 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 | Полный прогон после № 3132: 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`, а V2V4
отсутствуют.
- 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` и `334`) имеют реализованную репозиторную часть и
воспроизводимое подтверждение. № 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`; V2V4 и другие дополнительные миграции
отсутствуют. Финальный SHA-256 V1:
`fffedf66e8bf7d25d2d8c1b1a6146b45dd5b7e52233d05a68de8bf62c817f7a7`.
## Точка продолжения
Текущий этап: **№ 29 — единая временная зона бизнес-дат**.
Текущий этап: **реализация и проверка всех разрешённых изменений завершены**.
Следующая операция:
Оставшееся операторское действие владельца:
1. построить graphify-контекст всех `LocalDate.now()`/`LocalDateTime.now()` и frontend
`toISOString()` в бизнес-операциях;
2. внедрить единый `Clock`/`ZoneId` Europe/Moscow без изменения абсолютных timestamp;
3. зафиксировать `TZ`/`user.timezone` в Compose и Kubernetes;
4. проверить границы московской полуночи и date-only форматирование без UTC-сдвига.
1. при готовности production-полномочий выполнить `docs/SECURITY_RUNBOOK.md`: ротацию,
provisioning, rollout и очистку истории для № 1. Эти действия не выполнялись
автоматически и не требуются для завершения репозиторной реализации.