Files
av 7a7594e3e7 полнота точки — множество ключей, победитель — функция множества точек
- отношение победы было нетранзитивным: полнота (частичный порядок) плюс
  тай-брейк (тотальный) в попарной свёртке давали цикл, из-за которого одна
  и та же доставка меняла содержимое объекта при каждой пересборке
- надмножество побеждает только при совпадении значений общих содержательных
  ключей: иначе точка без единого измерения вытесняла измерение
- Less стал тотальным, isEmpty не материализует значение, имя метрики в
  координате столкновения обрезается, отпечаток витрины включает units и sealed
- на живом архиве строгий no-op: 1737 объектов, содержимое совпало побайтово
2026-08-01 21:05:43 +03:00

13 KiB
Raw Blame History

MODIFIED Requirements

Requirement: Разрешение столкновений по полноте

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

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

Измерено (находка 49): настоящих столкновений 2 897 из 444 256 координат (0.65%); из них 981 различаются набором полей — это и есть область правила полноты, — 1 916 несут равные наборы и разные значения, где исход решает тай-брейк, а несравнимых наборов ноль.

Пустым значением MUST считаться null, пустая строка, число, равное нулю (в любой записи), пустой объект и пустой массив: поле без содержания не делает точку полнее точки, где этого поля нет вовсе. Пустота MUST определяться по разобранному значению, а не по байтам: 0, 0.0, 0e0, -0 и { } — та же пустота, что 0 и {}.

false пустотой MUST NOT считаться: для булева поля это одно из двух значений, а не отсутствие содержания (isIndoor: false — тренировка на улице).

Поле source в множество не входит — оно нестабильно и переписывается задним числом (находка 36), так что о полноте измерения ничего не говорит.

Содержимое, которое не разбирается как JSON-объект, SHALL нести пустое множество ключей: так оно проигрывает любой точке с содержанием и не загрязняет наблюдение о несравнимых наборах.

Надмножество побеждает только тогда, когда оно несёт то же содержание: значения ключей, содержательных у обеих точек, MUST совпадать (с точностью до канонической формы). Иначе точки несут разные измерения, и надмножество имён о полноте не говорит ничего — такая пара MUST разрешаться как равнополная. Без этого условия точка {date, qty:0.001, p1:null, p2:null} вытесняла бы {date, qty:72.5}, то есть точка, где ни одно значение не измерение, стирала бы измерение — ровно то, ради отрицания чего правило переписано.

Если множества ключей с непустым значением равны и значения совпали, система SHALL сравнить множества всех ключей, кроме source, и оставить строгое надмножество. Без этого разряда правило теряло бы поля там, где заведено их беречь: точка {date, qty:10, Min:0, Max:0} и точка {date, qty:10} несут одинаковое содержание, и Min с Max исчезли бы из витрины по жребию. Несравнимость на этом разряде исходом MUST NOT быть: лишние ключи там заведомо пусты, объединять в них нечего.

Если равны и эти множества, а значения различаются, исход MUST быть детерминированным и не зависеть от порядка, в котором доставки дошли до хранилища: свёртка по журналу обязана давать то же состояние, что приём в реальном времени.

Победитель MUST быть функцией множества точек координаты, а не порядка их поступления. Попарная свёртка этого не даёт: полнота — частичный порядок, тай-брейк — тотальный, и вместе они образуют нетранзитивное отношение победы (A превосходит B по полноте, B бьёт C тай-брейком, C бьёт A тай-брейком). При таком цикле повторная свёртка одной и той же доставки меняет содержимое объекта, и витрина перестаёт быть функцией журнала. Поэтому система SHALL отбросить кандидатов, превзойдённых по полноте кем-то другим, и выбрать победителя среди оставшихся по тотальному порядку — обе операции зависят только от состава множества.

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

Если множества несравнимы — каждое несёт ключ с непустым значением, которого нет у другого, — система SHALL выбрать победителя тем же детерминированным правилом, что и при равных множествах, и MUST оставить наблюдение: счётчик в итоге разбора доставки, координаты объекта и запись WARN без значений точки. Несравнимый набор — частный случай столкновения: счётчик перезаписей растёт вместе с ним, а координаты попадают в оба списка.

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

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

Scenario: Бедная точка не стирает поля богатой

  • WHEN сохранена точка с Avg, Min, Max и context
  • AND по тем же координатам приезжает точка только с Avg, Min и Max
  • THEN сохранённая точка остаётся с context

Scenario: Поля без содержания полноты не добавляют

  • WHEN сохранена точка с пятью полями, значения которых 0, {} и []
  • AND по тем же координатам приезжает точка с date и ненулевым qty
  • THEN остаётся точка с date и qty

Scenario: При равном содержании поля не теряются

  • WHEN сохранена точка {date, qty, Min:0, Max:0}
  • AND по тем же координатам приезжает точка {date, qty} с другим qty
  • THEN остаётся точка с Min и Max

Scenario: Одинаково полные точки с разными значениями

  • WHEN по одним координатам приходят две точки с одинаковыми множествами ключей и разными значениями
  • THEN исход определяется детерминированно и не зависит от порядка воспроизведения доставок

Scenario: Несравнимые множества считаются, а не сливаются

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

Scenario: Содержимое, которое не разбирается в объект

  • WHEN по координатам сталкиваются точка с непустыми полями и содержимое, не разбирающееся как JSON-объект
  • THEN остаётся точка с полями
  • AND счётчик несравнимых наборов не растёт

Столкновением SHALL считаться расхождение канонических форм, а не байтов. Байты нестабильны — ради этого канонизация и заведена: из 81 952 повторно приехавших точек 67 534 различаются лишь порядком ключей, ещё 63% — последним разрядом double. Побайтовое сравнение давало бы тысячи ложных срабатываний на каждом глубоком проходе, и настоящий отказ правила стал бы неотличим от нормы.

Scenario: Столкновение с различием содержимого оставляет след

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

Scenario: Дребезг сериализации столкновением не считается

  • WHEN та же точка приезжает с другим порядком ключей или отличаясь последним разрядом числа
  • THEN счётчик перезаписей не растёт и WARN не пишется

Без этого следа допущение «меньше полей не значит новее» не получит ни одного наблюдения, а отказ правила будет неотличим от нормальной работы до сверки с экспортом Apple — то есть месяцами.