Files
av 5ae0c5ff81 reindex: пересборка витрины проигрыванием журнала
- `healthlog reindex` собирает витрину из журнала (тела архива + учёт
  доставок) в ОТДЕЛЬНЫЙ файл базы, строго по `(received_at, id)`; рабочую
  базу читает без наката миграций и не трогает вовсе. Подмену делает
  человек при остановленном сервисе: переименование поверх открытого
  дескриптора портит базу молча.
- Журналом считается архив, а не таблица доставок: тело без учётной записи
  заводится заново (метка из ULID, размер и хеш по распакованному телу),
  запись без тела переносится, но не сворачивается. Оракул сходимости
  встроен — два отпечатка и «объектов было/стало»; пустой журнал успехом не
  считается.
- Прогон живого архива переехал на новый пакет: второго проигрывателя
  журнала в проекте не осталось, а его утверждение о ключе сна перестало
  быть константой, протухающей с каждой доставкой.
2026-08-02 09:07:46 +03:00

5.8 KiB

Why

Разбор пишется по реальным данным и будет ошибаться — это норма. Без пересборки ошибка разбора становится потерей данных: исправленный код не применится к тому, что уже разобрано неверно, а точки из объекта не удаляются никогда. Сегодня журнал есть (116 тел в архиве), а кода, который его проигрывает, нет: после миграции 00005 доставки числятся pending, и подобрать их некому.

Оговорка, которую легко прочитать наоборот: пересборка точнее приёма не тем, что видит более длинный ряд. Слой обязан быть функцией префикса журнала, и наследование «от последней доставки вообще» уже ловили дефектом — 1737 объектов против 1742. Точнее она ровно тем, что применяет исправленный разбор к тому, что уже разобрано неверно.

What Changes

  • Новая подкоманда healthlog reindex: собирает витрину из журнала — import(снапшот) + replay(доставки по received_at) — и пишет её в отдельный файл базы, не трогая рабочую. Снапшот в этой дельте пуст: import появится вместе с задачей про родной экспорт Apple, и та встроится сюда же, а не заведёт вторую операцию.
  • Журналом считается архив, а не таблица доставок: тела, у которых учётной записи нет (приём успел записать тело и упал на вставке строки), заводятся заново по имени файла. Это обещание, уже записанное в internal/ingest.
  • Порядок проигрывания — строго (received_at, id), а не порядок обхода каталога: слой наследуется от предшествующей доставки той же автоматизации, и порядок входит в результат.
  • Оракул сходимости встроен в команду: отпечаток пересобранной витрины печатается рядом с отпечатком рабочей, и команда прямо говорит, совпали они или нет. Отпечаток значений точек не раскрывает.
  • Подмена рабочей базы пересобранной остаётся за человеком и в команду не входит: сервис держит открытый дескриптор, и rename поверх него оставил бы процесс писать в отвязанный inode — молча.
  • Границы, названные вслух: sealed в пересобранной витрине пуст (правила его выставления ещё нет), а заголовки доставок берутся из рабочей базы — в архиве их нет вовсе.
  • Прогон живого архива (task verify:archive) переезжает на новый код: сегодня он второй проигрыватель журнала со своим порядком и своим синтезом учёта, и после появления настоящей пересборки зеленел бы, проверяя путь, которым команда не ходит.

Capabilities

New Capabilities

  • reindex: пересборка витрины проигрыванием журнала — состав журнала, порядок, детерминированность, отчёт и его оракул, граница «что не восстанавливается».

Modified Capabilities

Изменённых нет. Правило «тело без учётной записи заводится заново» могло бы показаться правилом учёта, но оно описывает состав журнала при проигрывании и живёт в reindex; дублировать его в storage значило бы завести два места, где сказано одно и то же. Схема БД, слияние точек и вывод слоя не меняются: вся дельта — новый потребитель существующей свёртки.

Impact

  • Новый пакет internal/replay — проигрывание журнала поверх существующего internal/fold; собственного разбора и собственного слияния не заводит.
  • Новый файл cmd/healthlog/reindex.go, строка в main.go.
  • internal/store: перечисление доставок в порядке журнала, очистка витрины, чтение отпечатка (уже есть).
  • internal/ident: время создания из ULID — метка приёма для тела без учётной записи.
  • Схема БД не меняется, миграций нет.
  • README.md (reindex перестаёт быть «в планах»), docs/architecture.md (почему подмена базы не автоматизируется), docs/plan.md, docs/backlog.