Files
healthlog/docs/backlog/otkat-reliza-posle-migracii.md
T
av 8331328134 Дозакрыты находки ревью по слиянию сущностей
- Правило покрытия получило второй разряд (условный, как у точек), запрет
  вырождения формы и счёт содержательных элементов ряда: скелет из скаляров и
  ряд из null больше не затирают маршрут. Победитель внутри доставки стал
  функцией множества версий — общим помощником с точками, — а провенанс
  поднимается и при совпавшем хеше, иначе отложенная доставка возвращала витрину
  к прежнему содержимому.
- Одно поле не того типа больше не уносит сущность, а пропуски видны в учётной
  записи доставки (миграция 00008, NULL = «не измерялось»); каноническая форма
  считается один раз и вне транзакции; откат бинаря поверх новой схемы отказывает
  на старте; текст ошибки разбора не несёт значений из тела.
- Ревью кода профилем deep (девять проходов) нашло две регрессии и обе закрыты:
  безусловный второй разряд запирал законный досчёт навсегда, а выбор победителя
  был квадратичен по числу присланных версий одного ключа.
2026-08-02 16:38:18 +03:00

4.3 KiB

Чем откатывать релиз после наката миграции

Приоритет: блокеры

Вынуто ревью кода задачи «Дозакрыть находки ревью по слиянию сущностей» (проходы 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 (раздел «Деплой»).