1. Зелена збірка — це не сертифікат
Коли ви лише починаєте працювати з міграціями, дуже хочеться вважати зелений build гарною новиною достатнього масштабу. Проєкт зібрався, тести не розлетілися, IDE не червона — хочеться видихнути. У міграції ця спокуса найнебезпечніша. Зелений build каже рівно одне: «цей код у цьому середовищі зміг перетворитися на виконуваний артефакт». Нижня межа адекватності, а не сертифікат збереженої поведінки.
Паритет поведінки (feature parity) означає зовсім інше: за узгодженим набором сценаріїв зовнішня, спостережувана поведінка залишилася тією самою. Що всередині javax* стало jakarta* — користувачеві й бізнесу байдуже. Їм важливо, що GET /reports/monthly повертає той самий статус, форму відповіді, значення, дати й суми — і не починає говорити на діалекті «ну майже як раніше».
Дуже типовий приклад у міграціях на Spring Boot 3.x: проєкт компілюється, але з відповіді зникає поле, яке раніше приходило як null. Frontend перестає показувати колонку, аналітик вирішує, що система «нічого не знає» про метрику. Код не впав. Поведінка змінилася.
Зручно тримати в голові таку таблицю:
| Слабкий аргумент | Що він насправді означає | Сильний аргумент |
|---|---|---|
пройшов |
код зібрався | ті самі сценарії дали ті самі спостережувані результати |
| «локально відкривається» | один ручний запуск не впав | smoke, integration та contract checks підтверджують поведінку в pilot-розрізі |
| «Claude пише, що все нормально» | гіпотеза на основі аналізу | логи, тести, diff результатів і звіт із доказами |
| один тест зелений | один датчик не спрацював | набір датчиків покрив узгоджені сценарії |
Саме тому сьогодні ми будемо дивитися на validation як на систему доказів, а не як на втішний ритуал «прогоню ще тести, раптом стане спокійніше».
2. Порівнюємо спостережувану поведінку
Перед запуском перевірок корисно зупинитися й дуже чесно відповісти на запитання: що саме ви порівнюєте між старим і новим стеком? Якщо відповідь звучить як «ну загалом усе» — ви вже потрапили в туман. Порівнювати треба конкретний набір зовнішніх ефектів pilot-розрізу.
Для нашого поточного контексту — CashFlow Dashboard, pilot у модулі reports — спостережувана поведінка це статус відповіді, JSON-структура, порядок і формат грошових значень, обробка порожніх даних, коректність періоду в звітах, рендер PDF-вивантаження на тестових прикладах. А імена імпортів, оновлені анотації, внутрішні бін-конфігурації, пакет-серіалізатор у паритет не входять — внутрішня кухня.
Щоб порівняння було чесним, стара й нова версія мають проходити одні й ті самі перевірки на тих самих даних. Різні seed-дані — і ви порівнюєте не стек, а погоду. Ось чому characterization suite, яку ви збирали раніше на етапі модернізації, тут раптово стає золотом: вона вже фіксує поточну поведінку — прогоніть той самий набір датчиків на новому стеку.
# Старий стек
git switch main
./gradlew test --tests 'reports.MonthlyRevenue*' # 12 тестів, усі пройшли
# Новий стек
git switch migration/boot3-pilot
./gradlew test --tests 'reports.MonthlyRevenue*' # 12 тестів, усі пройшли
Це вже краще, ніж просто «я запустив усе на новій гілці». Але ще сильніше працює порівняння конкретного результату:
# Знімаємо baseline зі старого стеку
git switch main
curl -s "http://localhost:8080/reports/monthly?period=2025-04" | jq -S . > /tmp/old.json
# Знімаємо результат із нового стеку
git switch migration/boot3-pilot
curl -s "http://localhost:8080/reports/monthly?period=2025-04" | jq -S . > /tmp/new.json
diff /tmp/old.json /tmp/new.json # порожній вивід = відмінностей немає
Це дуже земна, майже нудна перевірка. І саме тому вона цінна. Міграції взагалі люблять нудних людей: тих, хто робить однакові виміри до і після.
flowchart LR
A[Базова точка на старому стеку] --> B[Ті самі перевірки на новому стеку]
B --> C{Поведінка збіглася?}
C -- так --> D[Go або Continue pilot]
C -- частково --> E[Виправити і повторити перевірку]
C -- ні --> F[No-go або Rollback]
Якщо ви тримаєте цю схему в голові, validation перестає бути полюванням на баги й стає зрозумілим порівняльним експериментом.
3. Ланцюжок перевірок: датчики з доказовою силою
Коли мова заходить про validation, легко захопитися крайнощами. Один обмежується build і локальним запуском, інший робить із одного endpointʼа мінікосмодром. Перевірок має вистачати на pilot scope, але не топити вас у шумі. Для migration-пілота зручно мислити не «списком тестів», а ланцюжком доказів: кожен шар відповідає на своє запитання.
| Перевірка | На яке запитання відповідає | Чим підтверджуємо |
|---|---|---|
|
проєкт узагалі збирається на новому стеку? | лог збирання |
| цільові тести | чи не зламалася локальна бізнес-логіка в pilot-розрізі? | результати unit/characterization tests |
| integration / contract | чи збереглися зовнішні контракти й зв’язки модулів? | integration suite, JSON diff, API checks |
| smoke | чи живий критичний шлях у середовищі, близькому до реального? | HTTP 200/4xx/5xx, базовий користувацький сценарій |
| manual check | чи не змінилася річ, яку автоматикою поки не зловити? | ручна звірка PDF, таблиці, візуального звіту |
Зверніть увагу: manual check тут не «тестів не вистачило, сподіваємося на удачу». Є типи виходів, які дешевше й чесніше перевірити очима: три sample PDF — писати заради них систему візуального регресу занадто, а звірити вручну нормальною інженерною угодою.
Дуже важливо не перетворювати AI-згенеровані тести на автоматичний доказ: якщо тест закріплює неправильну поведінку, зелений прогін доводить лише, що ви елегантно автоматизували помилку. Читайте AI-тест як звичайний код: які входи, які assertions, чому цю поведінку вважають коректною.
4. Хмарне ревʼю
Сюди ж логічно підключити хмарне ревʼю — як додатковий аналізатор сумісності. Міграційні PR-и зазвичай великі: десятки або сотні файлів із переписаними шляхами імпортів (javax.* → jakarta.*), замінами застарілих API, змінами конфігурації та оновленнями версій у build-файлах.
На такому обсязі diff локальна самоперевірка й звичайний reviewer subagent легко пропускають регресії сумісності — забуті вилучення застарілих API, зміни, що ламають транзитивні залежності, анти-патерни конкретного фреймворку в новій версії та відсутні оновлення конфігурації під нові defaults. Хмарне ревʼю (умовно /ultrareview або аналог — імʼя й доступність змінюються від версії до версії) доречне тут саме як додатковий шар поверх вашого деталізованого validation-ланцюжка.
Що воно робить на міграційному PR. Кілька агентів проходять по diff із фокусом на ламаючі зміни з migration guide; reviewer-agents із безпеки та з фреймворку вже знають патерни цільової версії. Ревʼю помічає API, які ще компілюються, але в наступній версії вилучені; перевіряє, що конфіги оновлені під нові defaults (властивості application.yml у Spring Boot 3 переїхали з javax.persistence на jakarta.persistence); ловить регресії тестів — коли вони зелені, але дьоргані вже застарілий API. На виході — розмічений за серйозністю diff, що доповнює детерміновану перевірку (build, tests, lint, CI) на семантичному рівні.
За дозволами все звично: хмарному агенту потрібен доступ лише на читання до міграційної гілки, без деплою в продакшн, без керування секретами й без запису в загальну інфраструктуру — це узгоджується з обмежувальними дозволами для агента міграції з попередньої теми. І, як ми вже домовилися раніше в курсі про витік чутливих даних через хмарні поверхні: якщо в коді лежать секрети, персональні дані клієнтів або конфіги з продакшн-доступами — цей цикл не для хмари. Ревʼю не скасовує доказів паритету: build, tests, integration, smoke і ручні перевірки все одно обовʼязкові.
На практиці корисно ставити собі трохи занудне, зате рятівне запитання: «Якщо цей баг досі існує, чи може мій набір перевірок його не помітити?» Відповідь «так» — validation suite ще не готова, навіть якщо IDE вже втомилася на вас дивитися.
5. Структура MIGRATION_VALIDATION_REPORT.md
Гарний validation report — це не красиво оформлена переможна промова, а документ, за яким інша людина відновить, що перевірили, чим це підтверджується, які діри залишилися й яке рішення ухвалили. Якщо за звітом не можна зрозуміти, що було зроблено, це не report, а терапевтична нотатка автора для власного спокою.
Зручний каркас:
# MIGRATION_VALIDATION_REPORT.md
Дата: 2026-05-24
Відповідальний: @dashboard-tech-lead
Pilot slice: reports/monthly
Підстава: MIGRATION_PLAN.md, ROLLBACK.md, COMPATIBILITY_MATRIX.md
## Прогнані перевірки
## Докази паритету фіч
## Збої та фікси
## Відомі обмеження
## Залишкові ризики
## Рекомендація
Цей шаблон короткий, але закриває головне. Угорі корисно вказати дату, відповідального, pilot slice і посилання на артефакти — це здається дрібницею, доки через місяць ви не відкриваєте три схожі файли й не гадатимете, який із них про поточний pilot.
Checks run відповідає на запитання «що саме прогнали» — конкретно, а не «ми все перевірили». У секції Feature-parity evidence ви вже не перелічуєте кроки, а показуєте, що саме збіглося.
Failures and fixes особливо корисна, бо робить звіт чесним: у гарного pilot-розрізу бувають проміжні падіння, і ви їх не ховаєте, а фіксуєте, як розбіжність було знайдено й закрито.
А ось дві секції, які новачки люблять недолюблювати, бо вони заважають красиво перемогти: Known limitations і Remaining risks. Вони дуже різні, і плутати їх не можна.
Known limitations — це свідомі межі поточного розрізу. CSV-експорт не входив у pilot; перевірені monthly reports, quarterly залишено на наступний розріз; PDF порівняно лише на трьох еталонних кейсах. Не ризик, а межа роботи.
Remaining risks — це те, що може вплинути на рішення, але поки не доведено до кінця. Staging не відтворює production-навантаження кінця місяця; один зовнішній клієнт замокано; реальні дані за грудень, де historically були timezone-аномалії, не прогнані. Зона невизначеності, яку reviewer має бачити.
Саме з цих секцій і випливає Recommendation. Не поетична — однозначна.
6. Claude як reviewer, а не як суддя
Коли у вас уже є готовий report, дуже спокусливо попросити Claude: «Ну, оціни, все добре чи ні». Тут важливо не віддати йому роль судді. Памʼять на шаблони й око на пробіли в нього непогані, але істина не в ньому, а в логах, тестах, diff результатів і заздалегідь узгоджених критеріях. Найкорисніший режим — reviewer: цифровий колега, який уміє бути неприємно уважним, коли ви втомилися й готові написати «ну начебто норм».
Такий запит працює набагато краще за абстрактне «перевір міграцію»:
Перевір мій MIGRATION_VALIDATION_REPORT.md.
Знайди прогалини:
- чи всі сценарії з pilot scope покриті;
- чи всюди є evidence, а не тільки слова;
- чи явно розділені known limitations і remaining risks;
- чи однозначна recommendation.
Файли не змінюй.
Нових перевірок без обґрунтування не вигадуй.
Якщо у вас уже є Workflow Kit і reviewer-agent, логіка та сама: нехай агент читає report, MIGRATION_PLAN.md, ROLLBACK.md і за потреби логи. Але фінальне рішення щодо recommendation ухвалюєте ви, а не система, що сама себе затверджує: самозатвердження — улюблений вид спорту поганих міграцій.
І ще одна фраза, яку корисно буквально запамʼятати: лог збирання важливіший за гарне пояснення Claude. Claude пише, що проблему усунено, а integration suite й далі червона — отже, не усунено. Все інше — літературний жанр.
7. Вибір рішення за підсумками pilot
Найважливіший момент наприкінці — обрати формальне рішення. Тут часто починається магія мови: «ну загалом можна рухатися», «нібито непогано», «мабуть, ок» — потім не зрозумієш, що ви вирішили. Тому корисно обмежити себе чотирма варіантами.
| Recommendation | Коли обираємо | Що це означає на практиці |
|---|---|---|
|
pilot довів parity за agreed scope, ризики прийнятні | можна масштабувати підхід або вважати цей розріз успішним |
|
базовий розріз успішний, але охоплення ще занадто вузьке для загального рішення | розширюємо pilot на сусідню безпечну ділянку |
|
у поточному вигляді pilot не готовий до продовження, але відкат не обовʼязковий | не масштабуємо, доопрацьовуємо гіпотезу або план |
|
спрацювала abort condition або порушено критичний контракт | відкочуємося до попереднього working state |
Тут є тонка, але важлива різниця між no-go і rollback. No-go не завжди означає, що все палає: інколи pilot просто не набрав доказів, або знайшовся дефект, який дешевше виправити на гілці без гучних рухів. Продовжувати не можна, але дослідження й виправлення всередині pilot-гілки — можна.
Rollback — це вже дія: повернення системи в попередній робочий стан за правилами з ROLLBACK.md. На staging це вимкнення feature flag, повернення версії залежності, відкат гілки. У production — окремий вид поваги до реальності.
На розрізах рівня CashFlow Dashboard дуже частим підсумком буде саме continue pilot. Це не боягузтво: розріз підтвердив підхід, але ще недостатній, щоб оголошувати перемогу по модулю.
8. Валідація на CashFlow Dashboard
Тепер зберімо все разом. На минулій лекції ви обрали pilot slice reports/monthly: read-only endpoint, є integration tests, не зачіпає billing logic напряму, і на ньому дешево ловити несумісності Boot 3.x + Java 21. ROLLBACK.md готовий, abort conditions прописані, worktree ізольовано. Після міграції та прогону перевірок фрагмент звіту міг би вийти таким:
## Прогнані перевірки
- `./gradlew clean build` на новому стеку — успішно
- `./gradlew test --tests 'reports.*'` — 47/47
- integration suite `reports-it` — 12/12
- smoke: `GET /reports/monthly?period=2025-04` у staging — 200
- ручна звірка 3 PDF-звітів із baseline
## Докази паритету фіч
- diff JSON old/new — порожній
- characterization suite зелена на старому і новому стеку
## Збої та фікси
- після міграції змінилася поведінка серіалізації `null`-полів
- виправлено через конфігурацію Jackson, перевірки повторено
## Рекомендація
continue pilot
Чому тут не go? Бо pilot покриває лише monthly reports. CSV-експорт не входив в охоплення. Квартальний сценарій не перевірявся. Production-навантаження кінця місяця не відтворено. Для цього розрізу доказів досить, для всього блоку reports — ще ні. Саме тому continue pilot тут чесніше за переможний марш.
Якщо захочете додати другу пару очей, можна попросити Claude перевірити саме повноту звіту, а не саму реальність:
Перевір цей фрагмент MIGRATION_VALIDATION_REPORT.md.
Скажи, чи достатньо evidence для recommendation = continue pilot.
Особливо перевір, чи не змішані known limitations і remaining risks.
Нічого не переписуй, поверни лише зауваження.
І ось у цей момент pilot перестає бути «ми, здається, оновили Spring Boot» і стає інженерним артефактом: baseline, ті самі перевірки до і після, ланцюжок документів, відповідальний, формальне рішення. А отже, є головне, заради чого варто займатися міграціями професійно: не лише рух уперед, а й доказ того, що ви не зламали те, що обіцяли зберегти.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ