## Why Изменение «тренировки и записи с собственным `id`» (`f8200f7`) прошло ревью не полностью: проходы `adversary`, `ops` и архитектурный на коде не запускались. Дозапуск нашёл девять причин, триаж оставил семь — у каждой прогнанный оракул. Две из них необратимы по последствиям: обеднённая версия тренировки затирает маршрут молча (в экспорте Apple маршрута нет, `reindex` проиграет то же поражение), а одно поле не той формы уносит тренировку целиком, причём доставка при этом числится разобранной — то есть ретеншен, решающий «что потеряется, если тело удалить», получит ложное «терять нечего». Остальные пять — молчание там, где обещана детерминированность: откат бинаря поверх новой схемы стартует без слова, победитель внутри доставки зависит от порядка элементов на проводе, провенанс устаревает на каждой из ~26 повторных присылок, канонизация идёт внутри транзакции вопреки собственному комментарию (измерено: 768 МиБ пика, 5.019 с удержания блокировки), а тело доставки в 8 МиБ целиком уезжает в текст ошибки и оттуда в `WARN`. ## What Changes - **Сравнение содержания сущностей получает второй разряд и запрет вырождения формы.** Сегодня `Covers` смотрит только наличие ключа и длину верхнеуровневого массива, поэтому версия-скелет (каждый массив заменён массивом той же длины из `null`, каждый вложенный объект — скаляром) признаётся равной настоящей и выигрывает тай-брейк журнала. Добавляются: ключи без содержания вторым разрядом (как у точек — «иначе ключ с пустым значением исчезает по жребию»), запрет покрывающей версии подменять объект или массив скаляром, и счёт **содержательных элементов** верхнеуровневого массива рядом с его длиной. Предел правила остаётся названным вслух и не закрывается: сокращение **внутри** элемента ряда (точка маршрута без `altitude`) ловится только сверкой с телом в архиве. - **Победитель внутри одной доставки становится функцией множества версий, а не порядка элементов массива.** Стандарт уже записан для точек и для сущностей молча не применён: попарная свёртка частичного порядка с тотальным тай-брейком нетранзитивна — `[A,B,C]` даёт `C`, `[B,C,A]` даёт `A`. Механизм выносится в одного помощника, общего с точками. Счётчик различающихся версий при этом считает по **канонической форме**, а не по байтам: требование постановки («differs при разных байтах») исполнено по форме, потому что порядок ключей у HAE нестабилен и побайтовый счётчик срабатывал бы на норме потока; детерминизм обеспечен тай-брейком по байтам, а не счётчиком. - **Провенанс обновляется при совпавшем хеше.** Сегодня совпадение хеша пропускает запись вместе с провенансом, и в сущности остаётся первая свёрнутая копия, а не победитель по журналу; при возврате содержимого к прежнему живая витрина расходится с `reindex`. - **Одно поле не того ТИПА больше не уносит сущность.** Пять полей заголовка читаются мягко — тем же принципом, который уже записан в коде для `duration`. Граница названа вслух: правило закрывает смену типа значения, но **не** смену формата строки, а наблюдался именно дрейф формата дат — тренировка с незнакомой датой по-прежнему теряется целиком, и закрыть это может только хранение сущности с неразобранной меткой (остаток). Пропуски сущностей при этом становятся видны в **учётной записи доставки**, а не только в логе, причём «не измерялось» отличимо от нуля. - **Счётчик удержанных версий выводится в отчёт пересборки.** Новое правило строже прежнего, а сходимость отпечатка его не проверяет по построению: живой приём и пересборка пользуются одним правилом и одинаково сойдутся на одинаково удержанной версии. - **`store.Open` сверяет версию схемы перед миграцией.** Версия базы выше версии бинаря — отказ, а не повод мигрировать; прецедент записан в `OpenForRead`. - **Канонизация сущности уезжает за транзакцию по-настоящему.** Каноническая форма считается один раз на версию, до входа в транзакцию, и из неё же берётся хеш — сегодня форма считается дважды (в хеше и в отложенном разборе), причём второй раз внутри `inTx`, который повторяется до пяти раз. - **Текст ошибки разбора перестаёт нести значения из тела**: называется тип токена и смещение во входе. - Лог `delivery failed` на приёме отличает занятость базы от прочих причин. - **Не входит:** хранение сущности с `id`, но неразобранной меткой (NULL-метка); пределы на размер одной сущности и секции и потоковый расчёт формы и хеша; принцип «data-миграции не отбирают строки по обрезаемым спискам»; длина очереди `pending` в `/stats`. Всё четыре уходят задачами беклога. ## Capabilities ### New Capabilities Новых нет. Все семь пунктов — уточнения уже записанных правил разбора и хранения; новое понятие ввёл бы второй словарь для того же домена. ### Modified Capabilities - `parsing`: заголовок сущности читается мягко — поле не той формы пропускает само поле, а не сущность; диагностика разбора не несёт значений из тела. - `storage`: содержание сущностей сравнивается с двумя разрядами, запретом вырождения формы и счётом содержательных элементов массива; победитель внутри доставки объявлен функцией множества с тотальным порядком; провенанс обновляется при совпавшем хеше, а метка изменения — нет; учётная запись доставки несёт число пропущенных сущностей, отличая ноль от «не измерялось»; открытие базы сверяет версию схемы; каноническая форма сущности считается вне транзакции и один раз. - `ingest`: лог отказа приёма отличает занятость базы от прочих причин. - `reindex`: число пропущенных сущностей внесено в перечень производных полей, которые пересборка не переносит; отчёт пересборки называет число удержанных версий сущностей. ## Impact - `internal/canon` — `Covers`, форма массива, экспорт хеша по готовой форме. - `internal/store` — `entity.go` (версия сущности, слияние, провенанс), `bucket.go` (комментарий о канонизации), `store.go` (страж версии схемы), новая миграция `00008` (колонка `skipped_entities` у доставки). - `internal/hae` — `entity.go` (мягкий заголовок), `hae.go` (текст ошибок). - `internal/fold`, `internal/ingest` — проброс счётчика пропусков в учёт, различение занятости базы в логе. - `docs/architecture.md`, `docs/database.md`, `docs/conventions.md`, `docs/review-journal.md`. - Оракулы из `tmp/adv/` переезжают обычными тестами в `internal/canon`, `internal/store`, `internal/hae`.