store: при равной полноте точек побеждает пришедшая доставка

- байтовый порядок канонических форм остался тай-брейком только внутри одной
  доставки: на живом корпусе он решал 98,8% спорных координат и системно хранил
  меньшее значение, из-за чего step_count терял род и verify:archive был красным
- правило перестало быть коммутативным осознанно, поэтому порядок свёртки
  приведён к журнальному: проход воркера прекращается на отложенной доставке,
  а свёртка вне порядка журнала пишет WARN
- заведены счётчики PointsHeld и PointsErased — удержание полнотой и
  единственное направление, в котором правило теряет содержание
This commit is contained in:
av
2026-08-04 11:16:24 +03:00
parent ae607f1ceb
commit b278501a6e
44 changed files with 3780 additions and 172 deletions
@@ -31,6 +31,52 @@
слияния, читающее собственную выдачу, повторяет дефект наследования слоя «из
будущего» (`docs/review.md`, 2026-08-01).
## Вопросы
Записаны 2026-08-04 по итогам ревью (профиль `deep`). Работа доведена до
коммита в объявленных границах; эти два решения не мои.
**1. Пересобирать ли живую витрину сейчас.** Новое правило меняет исход на
75 494 координатах (95% — `basal_energy_burned` слоя `raw`), но действует
только вперёд: уже сохранённые часы держат значение, выбранное прежним,
измеримо смещённым правилом, пока витрину не пересоберут. Отпечаток живого
`./data` с новым правилом **не сойдётся** — это ожидаемо и названо в дизайне.
- **(а)** `healthlog reindex` с остановкой сервиса и подменой файла базы сразу
после выкладки. Цена: простой приёма на время прогона (минута на нынешнем
архиве) плюс необратимое действие руками.
- **(б)** отложить до планового окна, приняв расхождение витрины на этот срок.
Цена: до пересборки Read API отдаёт по историческим часам прежние значения, а
сверка отпечатков с пересборкой не сойдётся и будет выглядеть отказом.
- **(в)** не пересобирать вовсе — витрина сойдётся только по тем координатам,
которые переприедут доставками. Цена: смещение остаётся в истории навсегда,
обнаружится сверкой с родным экспортом Apple, то есть месяцами позже.
Рекомендация: **(а)**. Подмена файла базы — необратимое действие человека
(`CLAUDE.md`), выполнить его я не вправе; этим изменением оно и не выполняется.
**2. Не сузить ли тай-брейк там, где он теряет содержание.** Разряд полноты
гаснет, когда значения общих содержательных ключей разошлись, — и тогда
пришедшая точка побеждает, даже если унесёт ключ, которого сама не несёт.
Замер: 2 координаты из 80 129 спорных на живом корпусе, обе — те же, что дают
несравнимые наборы.
- **(а, сделано)** оставить правило и завести счётчик `PointsErased` с `WARN` и
координатами. Цена: событие наблюдается, но не предотвращается; обратимо
пересборкой, пока жив архив.
- **(б)** сузить «побеждает пришедшая» до случая, когда множества
содержательных ключей совпали, а при строгом включении имён оставлять более
полную независимо от происхождения. Цена: правило перестаёт быть чисто
структурным на этом разряде, дельта хранения переписывается, прогон живого
архива надо снимать заново. Проверить обязательно: сохраняется ли при этом
починка `step_count` — по замеру его столкновения идут с одинаковыми
наборами `{date, qty}`, то есть должна сохраниться.
Рекомендация: **(а)** — она уже реализована, потому что не меняет принятого
владельцем правила и восстанавливает наблюдаемость. Переход к (б) остаётся
дешёвым: счётчик скажет, если событие станет массовым.
## Критерии приёмки
- ни одна метрика не теряет род из-за столкновения равной полноты; `step_count`