Files
healthlog/openspec/changes/archive/2026-08-02-trenirovki-i-zapisi/specs/reindex/spec.md
T
av f8200f7f80 feat: разбор и хранение тренировок и состояния разума
- секции `workouts` и `stateOfMind` покрыты разбором: тренировка лежит одной
  строкой вместе с маршрутом и внутренними рядами, запись — по ключу `род + id`;
  миграция 00007 заводит обе таблицы и возвращает в очередь `partial`-доставки
  с этими ключами
- сущность заменяется целиком, но условно: приехавшая побеждает, если не теряет
  содержания сохранённой (множество ключей и длины верхнеуровневых массивов), а
  при равном содержании выигрывает версия из более поздней доставки ЖУРНАЛА —
  «побеждает приехавшая» было бы функцией порядка свёртки, и живая витрина
  расходилась бы с пересборкой молча
- отпечаток витрины покрывает тренировки и записи и снимается одним снимком
  базы; отчёт `reindex` считает «было и стало» по каждой единице хранения
2026-08-02 13:05:16 +03:00

11 KiB

MODIFIED Requirements

Requirement: Отчёт, оракул и исход команды

Система SHALL завершать пересборку отчётом, который несёт счётчики (проиграно, свёрнуто, отказов по классам, тел без учётной записи, строк без тела, пропущенных файлов, повторов, объектов до и после) и два отпечатка — рабочей витрины и пересобранной, — с прямым ответом, совпали они или нет.

Счётчики «до и после» SHALL покрывать каждую единицу хранения витрины: часовые объекты, тренировки и записи. Отпечаток отвечает «да/нет» за витрину целиком, поэтому единица, которой нет в счётчиках, делает расхождение безадресным: человек увидит «не совпало» при неизменившемся числе объектов и не отличит появление двадцати семи тренировок от пропажи двух.

Отказы SHALL считаться по классам: слой не выводится, содержимое не разбирается, работа отложена по обстоятельствам, всё прочее. Невыведенный слой есть в каждом журнале и штатен; общий счётчик отправлял бы человека искать дефект там, где его нет. Отдельно называть человеку следует только нештатные отказы.

Отложенная доставка (занятость базы, отмена работы снаружи) SHALL считаться нештатной для пересборки, хотя для фоновой свёртки она штатна: пересборка идёт в свежий файл при единственном писателе, и такая доставка в собранной витрине просто отсутствует — вместе с теми, кто наследовал от неё слой. Классы при этом общие с фоновой свёрткой: второй классификатор разошёлся бы с первым молча.

Число объектов «было и стало» SHALL печататься рядом с отпечатками: отпечатки отвечают «да/нет», а решение о подмене необратимо, и по «да/нет» нельзя судить о направлении расхождения. Именно пара чисел — 1737 против 1742 — поймала прошлый дефект наследования слоя.

Отпечаток здесь оракул, а не украшение: число объектов к правилу разрешения столкновений нечувствительно — на координате всегда ровно одна точка, и правило выбирает, какая, а не сколько. «Объектов столько же» совпало бы и при заведомо сломанном правиле.

Отпечаток рабочей витрины SHALL сниматься до начала проигрывания, а число доставок в рабочей базе — до и после. Ненулевая разница SHALL называться в отчёте, и при ней процедура подмены печататься MUST NOT: доставки, приехавшие за время прогона, есть в рабочей базе и в архиве, но не в собранном файле, и подмена стёрла бы их учёт вместе с заголовками, которых в архиве нет.

Величины, которые не снимались, отчёт печатать MUST NOT. При отмене отпечаток пересобранной витрины и число доставок после прогона не измеряются вовсе — печатать их сравнение значило бы выдать неизмеренное за измеренное, причём в единственном оракуле задачи. Ожидаемые классы расхождения (новые доставки за время прогона, непереносимый признак запечатанного часа, исправленный разбор, покрытая разбором новая секция) SHALL называться отдельно от самого факта расхождения.

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

Исход команды. Расхождение отпечатков отказом быть MUST NOT: после исправления разбора оно ожидаемо и есть сам смысл пересборки. Отказ отдельной доставки отказом команды тоже MUST NOT быть: доставка, слой которой не выводится, — штатный исход.

Отказом команды SHALL быть: пустой журнал, отсутствие хотя бы одной свёрнутой доставки, отмена и любая ошибка окружения. Пустая витрина совпадает по отпечатку с пустой витриной, поэтому прогон по пустому журналу выглядит идеальной сходимостью — а все умолчания подыгрывают такому запуску: конфига может не быть вовсе, и тогда пути указывают в рабочий каталог процесса. Человек, выполнивший напечатанную процедуру, заменил бы витрину пустой.

Отчёт значений точек, имён метрик, имён устройств и содержимого тел содержать MUST NOT: отпечаток берёт содержимое хешем. Ограничение относится к отчёту в стандартном выводе; лог свёртки живёт по правилам спеки хранения, где координаты столкновения (метрика, слой, час) разрешены явно.

Отчёт идёт в стандартный вывод человеческим текстом. Прогресс длинного прогона SHALL идти в поток ошибок, а не смешиваться с отчётом: прогон на полном архиве молчит минутами, и зависший неотличим от идущего.

Scenario: Отчёт сравнивает отпечатки

  • WHEN пересборка завершилась
  • THEN отчёт содержит отпечаток рабочей витрины и отпечаток пересобранной
  • AND прямо называет, совпали они или нет
  • AND называет, изменилось ли число доставок в рабочей базе за время прогона

Scenario: Счётчики покрывают все единицы хранения

  • WHEN пересборка завершилась
  • THEN отчёт печатает «до и после» отдельно для часовых объектов, тренировок и записей

Scenario: Расхождение отпечатков не является отказом

  • WHEN отпечаток пересобранной витрины отличается от рабочей, и при этом хотя бы одна доставка свёрнута
  • THEN команда завершается успешно, а расхождение названо в отчёте

Scenario: Пустой журнал — отказ, а не идеальная сходимость

  • WHEN в архиве не нашлось ни одного тела
  • THEN команда завершается ненулевым кодом
  • AND процедуры подмены не печатает

Scenario: Ни одна доставка не свернулась

  • WHEN журнал непуст, но свернуть не удалось ни одной доставки
  • THEN команда завершается ненулевым кодом
  • AND процедуры подмены не печатает

Scenario: Приезд доставок за время прогона отменяет подмену

  • WHEN число доставок в рабочей базе за время прогона изменилось
  • THEN отчёт называет разницу
  • AND процедуры подмены не печатает

Scenario: Отчёт после отмены не сравнивает неизмеренного

  • WHEN прогон отменён
  • THEN отчёт не содержит ни ответа о совпадении отпечатков, ни разницы числа доставок

Scenario: Рабочей базы нет вовсе

  • WHEN файла рабочей базы не существует
  • THEN пересборка идёт по одним подобранным телам
  • AND отчёт называет, что сверять не с чем и что заголовки доставок не восстанавливаются

Scenario: Отчёт не раскрывает данных о здоровье

  • WHEN отчёт напечатан
  • THEN он не содержит ни значений точек, ни имён метрик, ни имён устройств