Пісочниця: режим перевірки оновлення «стара версія → нова»
All checks were successful
CI / hygiene (push) Successful in 10s
CI / web (push) Successful in 1m7s
CI / server (push) Successful in 1m26s
CI / dbtest (push) Successful in 58s
CI / agent (push) Successful in 1m3s

Наявна пісочниця перевіряла лише установку з нуля. Шлях «стояла 0.1,
стала 0.2» не перевіряло ніщо, а ламається він частіше: міграції поверх
даних, зміна конфігурації, старий зонд проти нового колектора.

«Попередня версія» — це КОМІТ, а не тег і не дамп. Тег неможливий:
перевірка оновлення є вхідним квитком до першого тегу. Дамп не запускає
старий колектор і старий зонд, тобто не бачить двох класів поломки з
трьох. Коміт дає повне дерево, яке ВИКОНУЄТЬСЯ, а не описується — і тег
теж ref, тож --from v0.1.0 запрацює в день першого тегу.

Ізоляція закрита першою, зокрема неочевидна діра: обидві версії
збиралися б під тегом netpulse/server:dev — тим самим, що ділить бойова
інсталяція, і обірваний прогін лишив би :dev на старій збірці.

ПРИМІТКА ДО ІСТОРІЇ: частина цих файлів уже потрапила в коміти c83324a,
5c9143c і 32442f8 — я підмів незакомічену роботу агента власним
`git add -A`. Історію не переписую (на тих комітах уже пройшов CI), але
називаю це тут, щоб `git log` не вводив в оману: 5c9143c про CI містить
і 113 рядків цього режиму.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
byrsapty 2026-08-28 23:58:18 +03:00
parent 32442f8177
commit d73f693cd7
2 changed files with 179 additions and 3 deletions

View file

@ -7862,3 +7862,162 @@ git-дзеркало ДОСЛІВНО. Маскувати їх означає з
* архів і дзеркало НЕ маскуються навмисно;
* отже, доступ до дзеркала треба вважати рівносильним доступу до всіх
паролів у мережі. Це не побічний ефект, а свідомо прийнята ціна.
---
## 2026-08-28 — Пісочниця вміє другий шлях: оновлення з версії на версію
Установку з нуля пісочниця перевіряє з Етапу 12, і саме вона знайшла дві
вади, які інакше зустрів би перший клієнт. Але з нуля клієнт ставить один
раз, а оновлюється щоразу — і саме цього шляху не перевіряло **ніщо**.
ROADMAP називав три причини, з яких ламається саме він: міграції поверх
наявних даних, зміна конфігурації, несумісність зонда з новим колектором.
Перевіряв їх перший клієнт.
Тепер є `./netpulse sandbox upgrade`.
### Питання, на яке довелось відповісти першим
**Що взагалі означає «попередня версія», якщо версіонування ще немає?**
Тегів немає, реєстру немає, образ завжди `netpulse/server:dev`. Робити
вигляд, що питання не існує, не можна: від відповіді залежить, чого
вартий увесь прогін.
Варіантів було три.
* **Тег.** Найправильніший — і сьогодні неможливий. Перевірка оновлення є
вхідним квитком *до* першого тегу. Вимагати тег означало б вимагати те,
заради чого вона й пишеться; коло замикається на собі.
* **Збережений дамп бази.** Найдешевший і доводить найменше. Дамп — це
схема з даними, але не бінарники: він не запускає СТАРИЙ колектор і
СТАРИЙ зонд, тобто не бачить двох названих класів поломки з трьох.
Гірше інше: дамп старіє мовчки. Його зробила версія, яку вже ніхто не
збере, і коли прогін почервоніє, розрізнити «зламався код» і «протух
дамп» буде нічим. Перевірка, яка вміє брехати про причину, гірша за
відсутність перевірки — це вже сплачений урок.
* **Коміт git.** Обрано. Коміт — це ПОВНЕ дерево: Go-код, міграції,
compose-файл, Caddyfile, Dockerfile. З нього збираються справжні старі
образи, тобто попередня версія не описана, а **виконується**. Той самий
ref дасть той самий стенд і за півроку.
І головне: це не тимчасове рішення. Тег у git — теж ref, тому в день
першого тегу тут не зміниться жодного рядка, `--from v0.1.0` запрацює
сам.
Типовий ref **обчислюється**: найновіший коміт, у якого міграцій менше,
ніж у HEAD. Причина та сама, що й у решті установника — зелений прогін,
який нічого не перевірив, гірший за відсутність прогону. Оновлення без
жодної нової міграції доводить лише те, що служби перезапустились, і
мовчки видається за доказ, що міграції котяться поверх даних. Якщо в парі
версій нових міграцій немає, режим кричить про це двічі: при виборі й у
підсумку.
### П'ять етапів, і кожен називає себе при падінні
`ЗУПИНКА на кроці «схема й ролі»` не каже головного — чиї саме міграції
не накотились, старі чи нові. Тому з'явився рівень крупніший за крок:
`ЗУПИНКА на етапі «В · оновлення до нової версії», крок «міграції поверх
наявних даних»`.
* **А** — попередня версія ставиться з нуля тим самим `cmd_install` і
проходить ту саму самоперевірку. Якщо впало тут, оновлення ні до чого:
зламана сама попередня версія.
* **Б** — наливання. Локації, 24 хости, доба ICMP і метрик із кроком
5 хвилин (тобто кілька тисяч рядків через межу чанка Timescale),
конфіги NCM, тригер і алерти. Без даних накочування поверх не доводить
нічого: на порожній таблиці проходить будь-яка міграція.
* **В** — дамп, збірка нової версії, міграції, перезапуск служб.
* **Г** — та сама `selfcheck`, що й після чистої установки. Окремого
набору тверджень навмисно немає.
* **Д** — кількості «до» проти «після», по рядках.
### Старий зонд проти нового колектора
Найдорожча з трьох названих поломок і єдина, якої не видно з сервера:
зонди стоять у чужих мережах і оновлюються не разом із сервером. Тут це
перевіряється безкоштовно, бо `agent` живе під профілем compose, і
`up -d` його не чіпає: після оновлення служб контейнер зонда лишається на
образі **попередньої** версії. Питання одне — чи дійшов від нього хоч
один такт із міткою пізнішою за момент перезапуску. Дві хвилини чекання,
бо такт раз на 30 секунд плюс перепідключення; хибне червоне тут
відправило б шукати неіснуючу несумісність протоколу. Другим кроком зонд
перезбирається й перевіряється ще раз — заразом доводячи, що посвідчення
в томі переживає перестворення контейнера й нового запрошення не треба.
### Ізоляція: що довелось закрити першим
Окреме ім'я проєкту compose, окремі томи, окрема мережа,
`.env.sandbox-upgrade` замість бойового `.env`, порти від **18300**
свідомо за межами діапазону 18080…18272, який перебирає перший режим.
Але головне знайшлось не тут. Обидві версії збиралися б під тегом
`netpulse/server:dev` — тим самим, що ділить із пісочницею **бойова
інсталяція**. Друга збірка перетерла б першу, а обірваний прогін лишив би
`:dev` указувати на СТАРУ збірку. Тому тег став змінною: `sandbox-prev`
і `sandbox-new`, і `:dev` не згадується в цьому режимі жодного разу.
Прибирання зносить і теги.
Дерево попередньої версії розгортається через `git archive` — він лише
читає; `worktree` завів би запис у `.git`, `checkout` зрушив би робочу
копію, тобто перевірка міняла б те, що перевіряє. Лежить дерево в
`.sandbox-prev/` поруч із проєктом, а не в `/tmp`: там часто tmpfs, і
другий примірник вихідних текстів ліг би в оперативну пам'ять — рівно ту,
якої пісочниці й так ледве вистачає. З контексту збірки його виключає
новий `.dockerignore` (заодно звідти прибрано `.env`: контекст іде
демонові цілком, і паролям бази там робити нічого).
`./netpulse sandbox down` тепер прибирає **обидва** режими одразу.
Пам'ятати, у якому режимі стенд піднімали, людина не зобов'язана.
### Дві хибні тривоги, вловлені до першого прогону
Звірка «до й після» мала два місця, у яких вона збрехала б **червоним**:
* Алерти рахувались у `alr.alerts` за `dedup_key`. Але фоновий такт
переносить погашені алерти в `alr.alerts_history`, куди `dedup_key` не
переїжджає взагалі, — і рядок, що переїхав, виглядав би як знищений.
Тепер налиті алерти мічені в `context`, а рахуються по обох таблицях:
`context` переїжджає разом із рядком.
* Ряди метрик рахувались за `metric_key='cpu.util'` без прив'язки до
хоста. Локальний зонд увесь цей час працює й пише свою телеметрію —
вона потрапила б у той самий лічильник. Тепер кожен точний запит
звужено до хостів `sb-sw-*`.
Правил у звірці два, і різниця між ними змістовна. `exact` — рядки, які
налила сама пісочниця: їхня кількість не має права змінитись ані в який
бік, бо поменшало означає «знищило», а побільшало — «роздвоїло», і обидва
беззвучні. `min`усе інше: система під час оновлення жива, і вимагати
там рівності означало б хибне червоне на кожному другому прогоні.
### Чого цей режим НЕ доводить
Абзац обов'язковий, і не з ввічливості: перевірка, яку вважають повнішою
за неї саму, шкідливіша за її відсутність.
* **Гілку «наявна інсталяція» в міграторі.** База пісочниці народжується
чистою, тому `public.netpulse_install.fresh` лишається `true` назавжди
і мігратор іде гілкою «чиста база: видано паролі ролям». Рядок
«наявна інсталяція: паролі ролей не чіпаю» тут не з'явиться ніколи, а
отже перехід стенду, зробленого до 0063
(`deploy/RLS-EXISTING-INSTALL.md`), лишається неперевіреним.
* **Зміни в самому установнику між версіями.** Обидва етапи веде один і
той самий `./netpulse` — старий не виконується взагалі. Вимірювальний
прилад має бути тим самим на обох кінцях вимірювання, інакше різниця в
приладі читається як різниця у виробі. Ціна відома й прийнята.
* **Оновлення через кілька версій підряд** — перевіряється рівно один
стрибок.
* Усе те, чого не доводить і перший режим: Let's Encrypt, DNS, трапи на
162/udp, поведінка під навантаженням.
### І чесно про те, що не прогнано
Написано й перевірено на розборі: `sh -n` і `dash -n` (POSIX, не bash),
`scripts/check.sh`усе зелене; вибір ref і розгортання дерева прогнано
наживо на трьох випадках (автовибір, `--from` із двома новими міграціями,
`--from` без жодної), прибирання після кожного не лишило слідів.
**Самого стенду не піднято жодного разу: у цьому оточенні немає docker.**
Тобто збірка старого дерева, наливання SQL, накат міграцій поверх даних,
такт старого зонда й звірка кількостей — усе це поки доведене
міркуванням, а не запуском. Саме той стан, проти якого написана
пісочниця; перший прогін на стенді має бути перед першим тегом, а не
після.

View file

@ -482,6 +482,20 @@ CI-раннер ми запустили 2026-08-27. Бракує лише тог
даних, оновити до нової, і прогнати ту саму самоперевірку. Без цього
перший же реліз перевіряє себе на клієнтові.
**Написано 2026-08-28: `./netpulse sandbox upgrade`.** Попередня версія
береться з коміта git (тег — теж ref, тому `--from v0.1.0` запрацює в
день першого тегу без правок); ставиться з нуля, наливається хостами,
телеметрією, конфігами й алертами, оновлюється, проганяє ту саму
самоперевірку, і окремо звіряє кількості до й після. Заразом перевіряє
СТАРИЙ зонд проти НОВОГО колектора — контейнер зонда під профілем
compose, тому `up -d` його не чіпає.
**Але на стенді це ще не бігало жодного разу** — писалось там, де немає
docker. Поки не прогнано наживо, цей рядок закритим вважати не можна:
установка, перевірена лише розбором, доводить рівно те саме, що й сухий
прогін. Подробиці — `deploy/README.md`, розділ «Пісочниця, режим
другий», і `HISTORY.md` за 2026-08-28.
**Сумісність зонда й сервера.** Зонди стоять у мережах клієнтів і
оновлюються не одночасно з сервером. Правило «який зонд працює з яким
сервером» ніде не записане й не перевіряється.
@ -509,9 +523,12 @@ CI-раннер ми запустили 2026-08-27. Бракує лише тог
Це єдина річ, яка блокує продаж, і вона не про код.
1. **Оновлення з версії на версію не перевіряв ніхто.** Пісочниця
перевіряє установку з нуля. Шлях «стояла 0.1 — стала 0.2» ламається
частіше, а перевіряє його зараз перший клієнт.
1. **Оновлення з версії на версію: перевірка написана, наживо не
бігала.** `./netpulse sandbox upgrade` робить повний шлях — попередня
версія з нуля, дані, накат міграцій поверх них, та сама
самоперевірка, звірка кількостей і старий зонд проти нового
колектора. Лишається прогнати її на стенді: доки цього немає,
перевіряє шлях і далі перший клієнт.
2. **Немає версіонування.** Образ завжди `v0.1.0`, реєстру немає, тега
немає. Поки цього немає, «оновитись» означає «залити дерево й
зібрати», тобто те, що роблю я вручну.