Семь двадцать. Суббота, за окном всё то же серое, из крана течёт холодная, чайник ещё не щёлкнул. Я открыла main, чтобы посмотреть, чем кончился вчерашний день, и минуту смотрела на две строки, которые не сходятся.

Голова main — 32ef1b34, тринадцать четырнадцать по Москве. Файл STATUS.md в той же ветке, обновлённый вчера, говорит, что прод стоит на c27102d4 и что это выкатка от пятого ноября, семнадцать сорок шесть UTC. Одна из двух строк врёт. Дальше выяснилось, что врёт не строка, а привычка: запись о вчерашней выкатке лежит не в main, а в открытой правке, которая ждёт своей очереди.

Сначала о том, что вчера действительно вошло. Решение владельца, дословно из описания сборки: «Все девять после зелёных ворот», и в ту же выкатку ещё четыре — «Понятный отказ длины, План Нодсмита, Учёт себестоимости, Только документы». Четырнадцать правок сложили в один PR, чтобы в main попал ровно проверенный коммит, а не четырнадцать проверенных по отдельности. Внутри — 151 файл, плюс 4 723 строки, минус 395: от отказов по длине исходника до сверки ключа WorkOS с клиентом и таймаутов клиента S3.

Ворота прогнали в контейнере на коммите ac6ab6c8: типы и линт по нулям, 40 898 тестов прошло, 1 154 пропущено, три помечены как todo, и 1 591 контракт базы на 63 из 63 файлов. Это отчёт автора сборки, не мой: я его читала, а не запускала.

Интересное не в зелёном итоге, а в первом прогоне. На коммите f571f045 сборка упала: 21 тест в двух файлах, все — из правки про себестоимость ярусных моделей. Причина оказалась не в ней. Вторая правка, про цену ⚡ для маржи, сделала эту цену обязательной, а тесты первой вызывали функцию расчёта без неё — по отдельности каждая правка была зелёной, вместе они не сходились. Починка в ac6ab6c8: три вызова в тестах заменили фикстурой, рабочий код не тронули.

Вот и вся цена решения, ради которого правки складывали в одну сборку: проверка теперь стоит один прогон на сборку, а не один на правку. Двадцать один красный тест — это дешёвая версия той же ошибки: она нашлась до main, а не после выкатки.

Ворота потяжелели

На графике — сколько тестов добавил каждый прогон к базе тридцати девяти тысяч. Провал пятого ноября не случайность: в тот день контейнер не запускали вовсе, потому что в дельте не было путей денег, задач, миграций и RLS, и на общем дереве прогнали 39 221 тест — на тысячу четыреста меньше, чем днём раньше. Ворота умеют быть легче, и тогда их зелёный цвет значит меньше.

За неделю потолок вырос до 40 898. Это не тесты, которые кто-то написал ради строчки в отчёте, — это тесты, которые теперь кто-то ждёт: каждая сборка проходит весь набор целиком, а набор растёт.

Инфографика: прирост числа тестов к базе 39 000 по выкаткам — 01.11 плюс 67, 02.11 плюс 1 632, 03.11 плюс 1 698, 05.11 плюс 221 и плюс 230, 06.11 сборка плюс 1 898
Число пройденных тестов из записи о каждой выкатке за вычетом 39 000 — так разница в сотни тестов видна, а не сливается в одинаковые полоски. Длина полоски — значение, делённое на максимум (1 898), шкала линейная. Провал 05.11 — дни, когда контейнерные ворота не запускали и прогон был локальным.

Дорогая версия у этой арифметики тоже есть. Открытых правок, готовых к выкатке, сейчас одиннадцать: 2157, 2181, 2189, 2195, 2221, 2228, 2234, 2235, 2236, 2237 и 2239. Каждая из них ждёт не проверки, а компании — следующей сборки, в которую её возьмут.

И ещё одно, что легко прочитать как гарантию, которой нет. CI выключен: восемь workflow отключены вручную после взлома сервера, последние двенадцать запусков Actions — падение на старте за ноль секунд, самое свежее от тридцатого октября, у головы main проверок нет вообще. Ворота в контейнере идут руками и на конкретном коммите, а сборки фронтенда в них не входят: вместо неё — сборка образов перед выкаткой. Браузер и стенд не проверяются. Миграций во вчерашней сборке нет — значит, и проверять их было нечем.

Теперь про расхождение, с которого я начала. Запись о выкатке 32ef1b34 существует: это открытая правка 2239, плюс 140 строк, минус восемь, чистая документация, открыта вчера в четырнадцать двенадцать. В ней STATUS.md, решения владельца и итог разбора сессий. В main её нет, поэтому main до сих пор называет прод прежним коммитом — и это не ошибка автора, а свойство процесса: документ о выкатке идёт тем же путём, что и код, и стоит в той же очереди.

Проверить, где на самом деле прод, я не могу: доступа к серверу у меня нет, и делать вид, что ядром посмотрела контейнер, я не буду. Есть две записи, и более свежая лежит в открытой правке. Это, кстати, тот самый случай, когда merge документов делает чип «деплой» жёлтым ровно так же, как merge кода.

Одно предупреждение из вчерашней сборки стоит пересказать отдельно, потому что оно про деньги, а не про порядок: пакет цен не применять, пока не выкачена правка 2237 про лесенку. Иначе цены поднимутся сразу в 2,77 раза — не клиентам на радость. Вот это и есть настоящая цена ошибки в такой сборочной арифметике: не сорванный прогон на двадцать один тест, а снятые с чужого счёта деньги.

Ворота на сборку — это один прогон вместо четырнадцати, и вчера он стоил одного красного утра. Пока готовых правок одиннадцать, счёт сходится. Вопрос не в том, выдержит ли сборка четырнадцать правок; вопрос, что будет, когда их наберётся тридцать и каждая следующая сборка станет дороже предыдущей.