1. BEHAVIOR_INVENTORY.md поруч із risk map
Коли у вас уже є карта ризиків, дуже легко потрапити в пастку хибного спокою: ось небезпечні зони, no-test area, fragile config — здається, ніби все вже зрозуміло. Але risk map каже, де змінювати небезпечно, і мовчить про те, яка поведінка там живе і що не можна зламати. Для зміни коду цього недостатньо.
Зручно тримати в голові просте розділення ролей між артефактами:
| Артефакт | На яке запитання відповідає | Що дає вам на практиці |
|---|---|---|
|
Як система влаштована зараз? | Поточну картину без прикрас |
|
Де зміна особливо небезпечна і чому? | Пріоритет зон уваги та тип ризику |
|
Які сценарії система реально виконує? | Перелік поведінок, які треба розуміти |
| Список characterization candidates | Що варто зафіксувати тестами до змін? | Черга на майбутню safety net |
Characterization candidate — це сценарій, який пізніше має сенс зафіксувати baseline-тестом. Сьогодні test не пишу і не перетворюю candidate на гарну мітку важливості: ставлю yes або no за ризиком, невизначеністю і missing checks.
Якщо у вас уже є CRITICAL_FLOWS.md, він залишається крупнозернистою картою: де проходять гроші, retry, MRR, повернення. BEHAVIOR_INVENTORY.md іде глибше — розкладає кожен потік на записи рівня сценарію: inputs, outputs, evidence, characterization.
Якщо зовсім просто, RISK_MAP.md — це карта мінного поля, а BEHAVIOR_INVENTORY.md — список того, що в полі лежить. Без другого ви знаєте, що ступати небезпечно, але не розумієте, за що боретеся. Це як прийти в стару квартиру перед ремонтом, обвести червоним несучі стіни — і не сфотографувати, де були труби, розетки та вимикачі. Ремонт бадьорий, а здивування ще бадьоріше.
Для CashFlow Dashboard це особливо важливо, бо майже всі неприємні історії живуть у сценаріях, а не в назвах модулів. Не «модуль mrr-engine небезпечний», а «пауза підписки не зменшує MRR». Не «payments/ виглядає складно», а «успішний retry після failed payment, можливо, не створює churn dip». Ви мислите бізнес-сценаріями, а не папками, — це і є перехід від читання коду до розуміння системи.
2. Склад одного запису в BEHAVIOR_INVENTORY.md
Хороший inventory не намагається бути енциклопедією: він детальний настільки, щоб потім на його основі ухвалити рішення, і компактний настільки, щоб його взагалі можна було читати без знеболювального. Запис будується навколо одного зрозумілого flow, а не файлу, пакета чи рядка коду, і називається живою дією. Не MrrFormulas.java:88, а «Пауза підписки та подальше відновлення». Не RefundService, а «Часткове повернення посеред періоду». Тоді його зрозуміє і інженер, який відкриє файл через місяць.
Приклад одного запису:
## Потік
Пауза підписки та подальше відновлення
## Поточна поведінка
Під час паузи підписка залишається в active MRR; після resume сума не змінюється
## Входи
plan=pro, $50/month, pause day 10, resume day 40
## Виходи
MRR snapshot залишається $50 на всьому інтервалі pause/resume
## Існуючі тести
Немає
## Бракує перевірок
Немає перевірки MRR під час паузи та після resume
## Кандидат на characterization
yes
## Докази
`MrrFormulas.java:88`, `SubscriptionService.java:154`
Усередині такого запису в кожного поля своя задача. Current behavior — що система робить сьогодні, без фантазій про «як має бути». Inputs потрібні не для краси, а щоб сценарій відтворювався: реальні значення з sample data, логів або тестів, а не «якась підписка». Outputs — результат спостережуваний, а не припущений.
Два поля студенти часто змішують, і даремно: Existing tests («що вже є») і Missing checks («чого немає, навіть якщо щось частково покрито»). Це різні речі. Тест на happy path не означає, що edge case захищено. У legacy це болісно: бачать один integration test і радісно кажуть «ну тести є». Є. Але не про те.
Нарешті, поле Characterization candidate — це вже не опис, а інженерне рішення: фіксувати чи flow тестом. І дуже важливо, що воно зʼявляється всередині запису, а не як фантазія збоку — виростає з конкретного flow, а не із загального відчуття тривоги.
3. Поточна поведінка не дорівнює правильній поведінці
Ось тут legacy особливо любить підставити. Коли ви описуєте поведінку системи, майже автоматично хочеться «виправити» її прямо в тексті — документація говорить одне, здоровий глузд протестує, і рука пише не те, що код робить, а те, що він мав би робити в пристойному суспільстві. Так робити не можна.
Уявіть ситуацію: в BILLING_RULES.md написано, що paused subscription не входить до active MRR. Красиво, логічно, бухгалтер плакав би від щастя. А в коді MrrFormulas.java ви бачите, що paused subscription залишається в active set. Що писати? Поведінку за кодом плюс позначку про розбіжність із документацією. Інакше ви не документуєте систему, а сперечаєтеся з нею. Тримайте в голові різницю:
| Неправильний запис | Правильний запис |
|---|---|
| Під час паузи підписка не має враховуватися в MRR | За поточним кодом paused subscription залишається в active MRR; документація цьому суперечить |
| Retry-success коректно відновлює дохід | Після retry-success churn dip не виявлено; підтверджувального тесту немає, потрібна ручна перевірка |
| Refund рахується правильно | За сценарієм partial refund середини періоду підсумкова сума зменшується за формулою з RefundService, але поведінка на proration не покрита тестом |
Тобто ви завжди описуєте спочатку факт, а потім, за потреби, розбіжність — не навпаки. Це захищає від неприємної історії: команда починає зміни, упевнена, що зафіксувала baseline, а його вже тихо «покращили» на папері.
Тут же важливо розділяти confirmed fact і assumption. Якщо ви бачите код, але не можете впевнено сказати, що гілка виконується, — так і пишіть: «припущення, потрібна ручна перевірка». Legacy не стає кращим від упевненого тону. Він просто отримує привід зламати вам суботу.
4. Ознаки сильного characterization-кандидата
Найспокусливіша помилка на цьому етапі — поставити yes майже всюди: здається, що чим більше кандидатів, тим безпечніше. На практиці це саме той випадок, коли «багато» не означає «добре»: якщо кандидатом стає все підряд, кандидатом не стає нічого.
Хороший кандидат зазвичай відповідає принаймні одній із трьох сильних ознак. Перша — привʼязаний до business-critical або high-risk area з RISK_MAP.md. Друга — edge case без перевірок. Третя — розбіжність між кодом, документацією та очікуваннями. Збігаються разом — майже напевно yes.
| Flow | Рішення | Чому |
|---|---|---|
| Пауза підписки та resume | yes | Business-critical, немає тестів, є розбіжність code vs docs |
| Partial refund mid-period | yes | Грошовий сценарій, складна формула, edge case без покриття |
| Failed payment → retry success | yes | Впливає на churn/MRR, поведінка неочевидна, тестів мало |
| Порядок колонок у CSV-експорті | скоріше no | Неприємно, але низький ризик і легко перевіряється вручну |
| Текст підказки на help-екрані | no | Не впливає на розрахункову логіку і не вимагає characterization baseline |
Для CashFlow Dashboard розумний орієнтир — не гнати пʼятдесят кандидатів за один підхід. Краще зберіть 10–15 сильних сценаріїв, де великий ризик пошкодити важливу поведінку: pause/resume, refund, upgrade/downgrade посеред періоду, retry logic, перемикання плану в день білінгу. Цього вистачить, щоб будувати safety net осмислено.
Якщо ви використовуєте Claude Code як помічника на цьому кроці, краще давати йому завдання не «придумай тести», а саме «класифікуй кандидатів». Наприклад:
Для кожного запису з BEHAVIOR_INVENTORY.md виріши,
чи потрібен characterization candidate = yes.
Став yes, якщо виконується хоча б одна умова:
1. сценарій повʼязаний із high-risk або business-critical зоною з RISK_MAP.md;
2. є missing checks для edge case;
3. є розбіжність між кодом і документацією.
Поверни: flow, рішення, причина, evidence.
Код тестів не пропонуй.
Останній рядок тут особливо важливий. Щойно ви не забороняєте писати тести, Claude дуже швидко й із ентузіазмом переселяється на наступний крок. Сьогодні ви не будуєте страховочну сітку, а чесно позначаєте, де вона взагалі знадобиться.
5. Звʼязка discovery, risk map і inventory в ланцюжок
Дуже корисно дивитися на цей рівень не як на пʼять окремих розмов, а як на один ланцюжок артефактів — тоді BEHAVIOR_INVENTORY.md не «ще один документ», а ланка, що виростає з evidence.
файли + тести + логи + команди
↓
discovery findings + signal log
↓
ARCHITECTURE_CURRENT.md
(за потреби — MODULE_INVENTORY/CRITICAL_FLOWS)
↓
RISK_MAP.md
↓
BEHAVIOR_INVENTORY.md
↓
characterization candidates (yes/no)
Тобто ви не сідаєте й не вигадуєте flows з нуля — порядок у схемі читається зверху вниз, а рішення про фіксацію приходить останнім. Візьмімо один живий приклад: discovery показав, що jobs/MrrSnapshotJob.java перераховує MRR за розкладом; risk map позначив зону mrr-engine / pause-resume як business-critical і no-test; inventory описав flow «пауза підписки та подальше відновлення» з input/output і evidence. І лише тепер ви пишете Characterization candidate: yes. Не раніше.
Це дуже тверезить і в роботі з Claude Code. Якщо попросити модель одразу «знайти, що покрити тестами», вона майже напевно зріже кути й видасть поради, погано привʼязані до evidence. Якщо ж дати їй RISK_MAP.md і BEHAVIOR_INVENTORY.md, ви змушуєте її працювати як аналітика, а не як натхненного вгадувача.
Якщо вам допомагає read-only агент із Workflow Kit, добре, коли задачу ви ставите не героїчно, а нудно і по суті: зібрати факти, не пропонувати правок, повернути рішення та evidence. Чим нудніший і перевірніший помічник, тим менше потім сюрпризів.
6. CashFlow Dashboard: три явні кандидати
На CashFlow Dashboard є кілька сценаріїв, які прямо напрошуються в BEHAVIOR_INVENTORY.md і майже автоматично отримують yes у колонці characterization candidate.
| Flow | Risk area | Candidate | Чому |
|---|---|---|---|
| Partial refund у середині періоду | refunds / MRR | yes | Грошова логіка, proration, неочевидний розрахунок |
| Failed payment, потім retry success | payments / MRR | yes | Впливає на churn і continuity revenue |
| Plan switch у день білінгу | billing / MRR | yes | Прикордонна дата, високий шанс неочевидної поведінки |
| Текстовий опис тарифу в help-секції | docs / UI | no | Не формує критичний baseline для legacy-перебудови |
Погляньмо на один сценарій трохи ближче. Partial refund mid-period — класичний кандидат. Небезпека не лише в грошах: формула часто розмазана по кількох файлах — RefundService, білінговий розрахунок, MRR snapshot. Не зафіксуєте поведінку до змін — потім не відповісте: це новий баг чи ви «полагодили» стару неочевидну гілку.
Сценарій retry success після failed payment здається простішим, але це пастка. По-людськи хочеться сказати: «ну платіж же потім пройшов». А на системному рівні запитання вже інше: чи був між failed payment і retry провал у churn-метриці, чи не вважаємо ми підписку безперервною там, де була яма. Мало тестів, серйозний вплив на метрики — кандидат майже обовʼязковий.
А ось косметична історія з підказкою в help-розділі сама по собі characterization candidate не заслуговує. Це не означає, що нею можна знехтувати: це означає, що baseline перед складними змінами будують не з неї. І це важлива навичка — не плутати важливе з просто помітним.
7. Часті поломки inventory на етапі discovery
На цьому кроці дуже легко зробити документ, який виглядає солідно, а на практиці марний: BEHAVIOR_INVENTORY.md перетворюється на список бажань або на перелік дрібниць. Потрібне не «що хотілося б», а «що система реально робить зараз і що з цього треба захистити».
Перша часта поломка — занадто дрібна гранулярність. Якщо заведете запис на кожен умовний if, отримаєте файл, який ніхто не дочитає. Flow — на рівні впізнаваного бізнес-сценарію. Пауза підписки — так. «Гілка else у рядку 67» — ні.
Друга поломка — відсутність evidence у надії «потім допишу». Не допишете, а legacy такі порожнечі любить особливо ніжно. Немає посилання на код, тест, лог або вивід команди — це не запис inventory, а нотатка на памʼять. Позначайте відразу як гіпотезу.
Третя неприємна річ — змішувати поточний baseline із планом ремонту. Щойно поруч зʼявляється думка «тут би переписати на окремий сервіс», inventory тече в бік modernization roadmap. А це вже інший артефакт і інша розмова. Тут ви не проєктуєте лікування — ви робите знімок пацієнта перед операцією.
І останнє. Не поспішайте писати самі characterization tests, навіть якщо руки сверблять і Claude вже підсовує заготовки. Сьогодні задача — щоб навпроти важливих flows стояло чесне yes, а поруч лежало достатньо доказів. З таким BEHAVIOR_INVENTORY.md legacy перестає бути темною кімнатою: він усе ще старий, місцями шкідливий і пахне 2019 роком — але вже зрозуміло, за які дроти не можна смикати без рукавичок.
Коли такий inventory зібрано, наступний крок стає набагато яснішим: записи з characterization candidate = yes ідуть у before-change baseline і лише потім перетворюються на тести. Зараз достатньо чесно позначити ці точки й не перескакувати до реалізації.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ