Files
healthlog/docs/backlog/pravilo-sliyaniya-tochek.md
T
av cd7a4c1493 отработаны находки ревью кода
Триаж свёл 62 сырые находки девяти проходов к 33 причинам: 3 блокера,
4 «сейчас», 2 развилки. Все закрыты регрессионными тестами.

- схема точки сна определяется по самой точке, а не по индексу в исходном
  массиве: одна пропущенная точка меняла эпизод и сводку местами
- доставка сворачивается одной транзакцией: частичное состояние было
  недетерминированным (восемь прогонов — семь состояний)
- граница размера на распакованном теле: 400 КиБ gzip разворачивались в
  400 МиБ мимо max_body_mb
- столкновение — расхождение канонических форм, а не байтов; WARN с
  координатами объекта; payload без HTML-экранирования
- выравнивание по местной метке: получасовые зоны уводили часовую выгрузку
  в minute
- доставка из одних суточных сводок больше не отвергается целиком
- единицы не переписываются молча; счётчик считает сохранённые точки
- все выходы Fold логируются, исход пишется на переживающем отмену контексте

Четыре развилки вынесены блокерами в беклог.
2026-08-01 19:02:05 +03:00

4.3 KiB

Правило выбора победителя при столкновении точек

Приоритет: блокеры

Вынуто ревью кода задачи razbor-metrik-v-obekty (профиль deep, находка №7 триажа, severity major). Трогает записанный инвариант «при столкновении выигрывает более полная точка».

Что решить

Как выбирать победителя, когда по одним координатам приехали разные содержимые. Сегодняшнее правило измеримо неверно в двух местах.

Оракул: измерено

Полнота — это счётчик ключей, чьё значение не null, не "" и не пусто. Поэтому {}, [], 0 и false считаются содержательными:

точка {"qty":0,"a":0,"b":0,"c":{},"d":[]}   полнота 5
точка {"date":"…","qty":123.4}              полнота 2

Вторая точка — настоящее измерение — проигрывает первой и стирается безвозвратно. Восстановить можно только из сырого архива, пока он жив.

Тай-брейк при равной полноте — лексикографический порядок канонических форм, то есть исход зависит от самого значения. Для накопительной метрики, которую досчитывают задним числом, это систематическая победа меньшего числа: qty:1 бьёт qty:100. Недосчёт, неотличимый от нормы.

Частота на живом потоке не измерена — стоит прогнать по архиву до решения.

Варианты и цена

(1) Полнота по множеству ключей: побеждает надмножество; несравнимые множества — объединять поля, а не выбирать точку целиком. Цена: средняя — правка спеки, resolve и тестов. Самое честное прочтение инварианта «ничего не теряем молча»: при несравнимых наборах не теряется ничего вообще.

(2) Оставить счёт ключей, но не считать содержательными {}, [], 0, false, "". Цена: малая. Правило остаётся играбельным (точку с лишними непустыми полями никто не мешает прислать), и произвол тай-брейка не решён.

(3) При равной полноте побеждает точка более поздней доставки по received_at. Цена: малая, но спека должна признать зависимость от журнала, и нужен детерминированный порядок внутри одной доставки — а именно там измерены столкновения (31 координата в 21 доставке из 89).

Рекомендация

(1). Объединение полей при несравнимых наборах — единственный вариант, при котором столкновение вообще перестаёт быть выбором «кого потерять».

Что стоит без решения

Правило работает и теперь наблюдаемо: столкновение считается расхождением канонических форм (а не байтов, как было), даёт WARN с координатами объекта и счётчик overwrites. Если правило начнёт терять — это станет видно в логе, а не через месяцы при сверке с экспортом Apple.

Связано: openspec/specs/storage → «Разрешение столкновений по полноте».