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

108 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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`.