# Чем откатывать релиз после наката миграции **Секция:** инфра · **Хук:** Решено: копия файла базы перед накатом. Страж версии схемы делает возврат бинаря отказом, а понизить схему нечем · **Теги:** goal:deploy, question **Решение принято владельцем 2026-08-02: вариант (2) — копия файла базы перед накатом.** Entrypoint контейнера копирует файл базы рядом до старта бинаря, откат = подмена файла. `Down`-блоки миграций при этом честно называются декорацией для локальной разработки, а не аварийным путём: ни один из них не исполнялся ни разу. Осталось решить при взятии — сколько копий держим и где. Задача естественно склеивается с [деплоем](deploy-rivendell.md). Ниже — исходная постановка блокера, она же ТЗ. Вынуто ревью кода задачи «Дозакрыть находки ревью по слиянию сущностей» (проходы `ops` и `negative`, профиль `deep`). ## Что именно решить Та задача перенесла в `store.Open` стража версии схемы: база новее бинаря — отказ на старте. Решение принято владельцем и здесь не пересматривается. Но у него есть следствие, которое до сих пор нигде не было записано: **после того как новый бинарь накатил миграцию, возврат старого бинаря приёма не чинит.** Он теперь отказывается стартовать, а понизить схему нечем: - подкоманды миграции у бинаря нет (`serve`, `reindex`, `healthcheck`); - `goose` CLI в образ не кладётся; - блоки `-- +goose Down` в миграциях написаны, но ни один тест их не исполняет, и на рабочей базе они не выполнялись ни разу (`DROP COLUMN` в SQLite через `modernc.org/sqlite` не проверялся вовсе); - `restart: unless-stopped` превращает отказ в цикл перезапуска, а телефон всё это время шлёт в закрытый порт и **не перешлёт** потом. То есть аварийный путь придётся изобретать в момент аварии, при остановленном приёме. Цена простоя для метрик закрывается широким и глубоким проходами синхронизации; для `stateOfMind` не закрывается ничем — у него доставки HAE единственный источник. ## Вопросы 1. **Подкоманда `healthlog migrate --down-to N`.** Цена: новая поверхность CLI плюс тест на `Down` каждой миграции (сейчас их нет, и `DROP COLUMN` в SQLite ведёт себя не так, как в постгресе). Зато откат становится операцией, а не импровизацией. 2. **Копия файла базы перед накатом** — entrypoint контейнера делает `cp` рядом, откат = подмена файла. Цена: место (база растёт), плюс правило «сколько копий держим». Зато не требует ни кода, ни доверия к `Down`, а база производна от архива — потеря копии не смертельна. 3. **`goose` CLI в образ.** Цена: образ перестаёт быть одним статическим бинарём, появляется вторая точка, знающая про схему. 4. **Ничего, но записать вслух**: «понижение схемы не поддерживается, лечение — только выкатка вперёд». Цена: в аварии выбора нет. ## Рекомендация (2) плюс уже сделанная запись из (4). Копия файла — единственный вариант, который не требует доверять непроверенному коду ровно в тот момент, когда проверять некогда; а `Down`-блоки при этом честно называются декорацией для локальной разработки. ## Что стоит, пока решения нет Ничего: страж работает, и это правильно. Стоит только аварийный сценарий — он существует ровно в том виде, в каком описан выше. Строка «понижение схемы не поддерживается» уже записана в `docs/architecture.md` (раздел «Деплой»).