Pipeline 6815 failed instantly с 'could not find expected : at line 625'
— multi-line shell strings внутри script: - | литерального блока YAML
ломали parser потому что continuation lines не были indented к уровню
блока (lines 625, 627-629 в column 0 — YAML видел их как new key).
Fix: собрать message строкой через printf '%b' с явными \n. Однострочные
shell variables — YAML парсит без проблем независимо от содержимого
внутри quotes.
Раньше release tag создавал v2.X.Y, build images, но values-prod.yaml в
ordinis-infra оставался указывать на старый tag (либо unset → fall to
:latest). Это создавало divergence:
- Auto deploy через trigger-deploy-prod использовал runtime
UPSTREAM_IMAGE_TAG = новый tag → правильный deploy.
- Manual deploy через infra repo (helm upgrade -f values-prod.yaml)
использовал static values → старый tag → split-brain ноды через
:latest cache (см. ordinis-infra MR !10 fix).
Новый job 'propose-prod-image-bump':
- Triggers только на tag pipeline (semver v*.*.*).
- Needs все docker-* jobs (images должны быть в registry).
- Clones ordinis-infra, патчит image.tag в values-prod.yaml через awk
с scope-tracking (только image: block, не writer/readApi/etc).
- Commit + push branch + создаёт MR через GitLab API.
- Reviewer approve → merge → infra pipeline auto-катит deploy-prod.
Git становится единственным источником истины: HEAD ordinis-infra main
всегда отражает что задеплоено на prod.
Setup: новая CI variable INFRA_PROJECT_TOKEN (group-level access token
scope: write_repository, api, role: Developer). Без неё job exits early
с подсказкой — pipeline не блокируется (allow_failure: true).
Failed pipeline 6805: /swagger-ui/index.html в test profile вернул 200
(springdoc enabled по умолчанию через ORDINIS_SWAGGER_UI_ENABLED default
true). Это валидно — Swagger render'ит реальную UI page.
Замена: проверяем generic non-API typo path который точно не имеет
controller'а и не SPA-route'д. Покрывает тот же NoResourceFoundException
branch handler'а независимо от springdoc env config.
GlobalExceptionHandler catch-all Exception → 500 ловил NoResourceFoundException
и NoHandlerFoundException. Симптомы на prod:
- /swagger-ui/index.html при disabled springdoc возвращал 500 (UI всё равно
не рендерился, но spring логировал 'Unhandled exception' на каждый запрос —
спамил alerting и затруднял поиск настоящих ошибок).
- GET /api/v1/typo от integrator'ов давал 500 вместо 404, осложняло
debugging без traceId-correlation.
- Любой stale frontend bundle с переименованным endpoint мог триггерить
false-positive incident alerts.
Fix: explicit @ExceptionHandler для обоих exception типов → 404 not_found.
Логирование DEBUG (не WARN/ERROR) — это ожидаемый 404, не аномалия.
Test: SmokeE2ETest.unknownPath_returns404WithNotFoundCode покрывает оба пути
(/api/v1/* и /swagger-ui/*) → 404 + body.code='not_found'.
Per user feedback: «по кнопке что нового должно открываться модальное
окно с изменениями».
UpdateBanner кнопка «Что нового» (т9n key `updateBanner.docs`) ранее
открывала `/docs/` в новой вкладке — generic documentation page, не
contextual для «что именно в этом обновлении».
Теперь — открывает `WhatsNewModal` (тот же modal что показывает 🎁
button в TopBar) — public changelog с release notes сразу.
## Refactor
- **`WhatsNewModal.tsx` (NEW)** — extracted controlled-mode modal с
release notes. Props: `open`, `onClose`. Internal logic читает
changelog, tracks lastSeen в localStorage, на open mark'ает latest
как seen.
- **`WhatsNewButton.tsx`** — refactored: trigger button + use
`WhatsNewModal`. Поведение unchanged (🎁 icon с unread badge → click → modal).
- **`UpdateBanner.tsx`** — replaced `window.open(DOCS_URL)` на
`setWhatsNewOpen(true)` + render `<WhatsNewModal>`. DOCS_URL constant
removed (больше не используется).
## UX intent
Когда update detected:
1. User видит orange banner «Доступна новая версия v2.16.0»
2. Hover «Что нового» button → opens modal
3. User читает release notes (что добавлено, исправлено) ДО reload
4. Click «Обновить» → reload
Раньше «Что нового» вёл к generic docs page — user не знал ЧТО
изменилось, только что update есть. Modal даёт structured answer.
## Test plan
- [x] `pnpm tsc --noEmit` clean
- [x] `pnpm build` clean
- [x] `pnpm vitest run useChangelog.test` — 11/11 pass (parser logic
unchanged)
- [ ] Manual: симулировать update banner (mock useAppVersion
`updateAvailable=true`), click «Что нового» → modal opens
- [ ] Manual: 🎁 button в TopBar также opens same modal
- [ ] Manual: close modal → red dot disappears на 🎁 button
(localStorage updated)