Задача Егора 2ч.

This commit is contained in:
2026-07-17 01:27:02 +03:00
parent 3f685d3e22
commit 3d798c13e3
20 changed files with 2003 additions and 275 deletions

View File

@@ -1,6 +1,6 @@
# Журнал исправления проблем
Обновлено: 2026-07-15 (Europe/Moscow).
Обновлено: 2026-07-17 (Europe/Moscow).
## Область и неизменяемые данные
@@ -56,9 +56,9 @@
| 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` отсутствует. |
| 11 | не начато | Sunday вычисляется повторным мутированием даты; ошибки запросов превращаются в пустые результаты и зелёный статус. | Чистая local-date утилита и три явных состояния проверки. | 2026-07-02, переход года, частичная/полная ошибка API. |
| 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 | не начато | Access JWT хранится в Web Storage; origin исполняет удалённые модули; CSP допускает несовместимые сценарии. | Access JWT только в памяти, восстановление через HttpOnly refresh-cookie; локальные pinned assets; строгий CSP и удаление inline JS. | Login/refresh/logout/reload tests; статический аудит CSP/storage/imports. |
| 13 | не начато | Watcher сравнивает только домены и фиксирует hash до успешной полной синхронизации. | Нормализованное сравнение полного `TenantConfig`, hash после успеха, bounded backoff. | Изменение URL/credentials и retry после ошибки. |
| 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. |
| 14 | исправлено и проверено | Kubernetes HTTP-клиент имел trust-all TLS fallback. | `KubernetesTenantSecretUpdater` загружает service-account CA, строит PKIX trust store, включает HTTPS hostname verification и не имеет небезопасного fallback. | Mock HTTPS: доверенный CA принят; чужой CA, неверный hostname и отсутствующий CA отклонены. Backend 45/0/0. |
| 15 | не начато | Не проверяются `start < end` и пересечения; duration доверяется клиенту; используемый DEFAULT scope можно изменить. | Централизованный валидатор, backend duration, DB/concurrency protection. | Overlap/adjacent/scope/concurrency tests. |
| 16 | не начато | Create/update проверяют лишь порядок дат, но не пересечения и принадлежность семестра году. | Единый calendar validator и PostgreSQL range/exclusion constraints. | Create/update + конкурентные PostgreSQL tests. |
@@ -324,8 +324,8 @@
маскирует отказ. Actuator endpoints исключены из tenant-interceptor и не раскрывают
components, домены, JDBC URL или credentials.
- AutoUpdateDocs синхронизировал `docs/API.md`, `docs/ARCHITECTURE.md` и
`docs/INFRASTRUCTURE.md`. Ограничение watcher по обновлению URL/credentials существующего
домена явно оставлено проблеме № 13.
`docs/INFRASTRUCTURE.md`. На этом этапе ограничение watcher по обновлению URL/credentials
существующего домена было оставлено проблеме № 13 и устранено следующим этапом.
- В текущей рабочей копии отсутствует внешний каталог `../k8s`, поэтому манифесты не менялись
и Kustomize не запускался. Для production обязательны directory mount без `subPath`,
`TENANTS_CONFIG_REQUIRED=true`, Role `get/update` для `tenants-secret` и HTTP liveness /
@@ -343,18 +343,92 @@
| Повторные LF-нормализованные SHA-256 V1/V2/V3/V4 | Совпадают с ранее зафиксированными значениями. |
| `kubectl kustomize ../k8s` | Не выполнено: внешний каталог `../k8s` отсутствует в текущем workspace. |
### № 13 — полная и повторяемая синхронизация tenant-конфигурации
- `TenantLifecycleService.synchronizeFromPersistedConfig()` валидирует весь входной список,
отклоняет нормализованные дубли до изменения runtime/readiness и сравнивает `name`, `domain`,
`url`, `username`, `password`. Новый или изменённый tenant проходит candidate prepare,
проверку соединения, Flyway и атомарный swap; persisted-путь не вызывает `TenantConfigStore`.
- Hash mounted `tenants.json` становится применённым только после успешного разбора и полной
lifecycle-синхронизации. Ошибка сохраняет прежний hash и повторяется с экспоненциальной
задержкой 30300 секунд; новая ревизия обходит 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-миграции не изменялись. |
## Точка продолжения
Текущий этап: **№ 13полная синхронизация изменённых tenant credentials/URL watcher-ом**.
Текущий этап: **№ 12access JWT и удалённый JavaScript в одном origin**.
Следующая операция:
1. сравнивать полную нормализованную активную конфигурацию с persisted snapshot, а не только
набор доменов;
2. при изменении URL/username/password выполнять prepare → verify → Flyway → atomic swap без
повторной записи Secret;
3. считать hash применённым только после полной успешной синхронизации и не подтверждать
произвольное содержимое mount через `refreshHash()` после API-мутации;
4. добавить bounded retry/backoff и тесты изменения credentials, восстановления прежнего
файла после ошибки и межподового merged snapshot;
5. выполнить полный прогон и AutoUpdateDocs перед изменением статуса № 13.
1. инвентаризировать `localStorage`/`sessionStorage`, refresh-cookie, удалённые `import()` и
inline-скрипты во всех frontend entrypoints и HTTP/CSP-конфигурации;
2. выбрать единый контракт access JWT только в памяти с восстановлением через HttpOnly
refresh-cookie, не оставляя токен в Web Storage после login/refresh/logout/reload;
3. локализовать и зафиксировать внешние runtime-зависимости, затем ввести CSP без
`unsafe-inline`/`unsafe-eval` и устранить несовместимые inline-вставки;
4. покрыть login/refresh/logout/reload тестами и выполнить статический аудит storage/imports/CSP
перед AutoUpdateDocs и изменением статуса № 12.