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

6.8 KiB

Журнал проскочивших дефектов

Сюда попадает дефект, который прошёл ревью и всплыл позже. Записывается сразу, а не ретроспективно: со временем теряется не сам факт, а причина непоймания — единственное, ради чего журнал существует.

Реализованные задачи, находки ревью и решения сюда не пишутся: у них есть коммит, спека и беклог. Здесь только промахи конвейера.

Форма записи:

## 2026-08-01 — <краткое последствие>

- **Где:** internal/store/bucket.go:120
- **Симптом:** <как обнаружилось, кем и когда>
- **Почему не поймали:** <какой проход обязан был найти и что ему помешало>
- **Что меняем:** <правило прохода, шаг гейта, конвенция — либо «ничего, цена
  поимки выше цены дефекта»>

Последний пункт важнее остальных. Вывод «ничего не меняем» — законный исход: не всякий дефект стоит того, чтобы усложнять ради него ревью каждой задачи.


2026-08-01 — свёртка не воспроизводилась при пересборке журнала

  • Где: internal/store/delivery.go, LastDerivedLayer
  • Симптом: прогон живого архива (99 доставок) вторым проходом дал 1742 объекта вместо 1737, а координат сна 182 вместо 174. Нашёл тест сходимости на шаге apply — не ревью.
  • Причина: доставка без плотных метрик наследует слой автоматизации. Запрос брал последний выведенный слой вообще, а не последний до этой доставки, поэтому при пересборке доставка наследовала слой «из будущего». Свёртка переставала быть функцией от префикса журнала.
  • Почему не поймали: формулировка «наследует последний надёжно выведенный слой той же автоматизации» звучит однозначно и в спеке, и в дизайне — пропущенное слово «предшествующей» не выглядит пропуском. Проходы specs и architecture сверяли код со спекой и понятиями, а инвариант «import + replay даёт то же состояние» ни один из них не проверял на конкретном правиле: он записан в архитектуре как свойство системы, а не как критерий для каждого узла, читающего состояние.
  • Что меняем: в рубрику healthlog-review-rubric и в проход ops — вопрос «читает ли узел состояние, которое сам же меняет, и остаётся ли он функцией от префикса журнала». Дешевле правила: любой запрос к delivery из свёртки обязан иметь границу по received_at разбираемой доставки. Тест сходимости на живом архиве (internal/fold/replay_test.go) остаётся постоянным — именно он это поймал.

2026-08-02 — прогон живого архива был красным и об этом никто не знал

  • Где: internal/fold/replay_test.go (перенесён в internal/replay/archive_test.go)
  • Симптом: первый же запуск task verify:archive в задаче про пересборку дал координат sleep_analysis 222, измерено 174. Проверено прогоном прежней редакции теста на том же архиве: она даёт ровно те же 222, 2049 объектов и тот же отпечаток — значит тест покраснел не от изменений задачи, а сам, когда архив дорос с 94 доставок до 116.
  • Причина: утверждение было пришпилено к числу, производному от корпуса (174 координаты сна). Корпус растёт с каждой доставкой, то есть константа протухает по расписанию телефона. Проверяемое свойство при этом другое и от размера корпуса не зависит: ключ по интервалу не схлопывает записи до ключа по метке (222 координаты против 218 меток).
  • Почему не поймали: прогон живого архива намеренно не входит в task gate (минута работы, данные есть только на этой машине). У проверки, которую гейт не гоняет, краснота никому не видна — она обнаруживается только следующей задачей, которая до неё дотянется. Ни один проход ревью прогон не запускал: проходы читают код, а не гоняют опциональные команды.
  • Что меняем: утверждение переписано на само свойство (координат строго больше, чем различных меток), измеренные числа остались в t.Logf. Правило общее и годится в конвенции: в проверке на живом корпусе нельзя утверждать число, производное от размера корпуса — утверждать надо инвариант, а число печатать. Гейт при этом не трогаем: цена ежедневной минуты выше цены такой протухшей константы, а после этой задачи прогон стал ещё и единственным, кто проверяет настоящий проигрыватель журнала.