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

9.0 KiB

1. Полнота как множество (internal/canon)

  • 1.1 isEmpty: null, "", число, равное нулю (0, 0.0, 0e0, -0), {}, []; false — НЕ пустота. Считается по литералу, БЕЗ материализации значения: разбор целиком разворачивал heartbeatSeries в []any на каждое сравнение (замер ревью: 410 мс на точку 16.5 МБ)
  • 1.2 Тип Fullness (Equal/Superset/Subset/Incomparable, нумерация с единицы, String()) и RelateFullness(a, b []byte) Fullness; source в множества не входит; содержимое, не разбирающееся в объект, даёт пустое множество
  • 1.3 Второй разряд: при равенстве множеств содержательных ключей и совпадении их значений сравниваются множества всех ключей; несравнимость на втором разряде исходом не является
  • 1.6 (по ревью) Надмножество побеждает только при совпадении значений общих содержательных ключей — иначе точка без единого измерения вытесняла измерение ({qty:0.001,p1:null,p2:null} против {qty:72.5})
  • 1.7 (по ревью) Less тотален: при отказе канонизации сравниваются исходные байты. Прежде на паре из двух неразбираемых значений Less в обе стороны давал false, и победителем оказывался просто второй аргумент
  • 1.8 (по ревью) Разбор точки вынесен в Fields/Analyze и переиспользуется: сравнений квадратично по числу кандидатов
  • 1.4 Completeness удалить — второй меры полноты в проекте не остаётся
  • 1.5 Тесты canon по приёмочным критериям ниже

2. Разрешение столкновения (internal/store)

  • 2.1 resolve выбирает победителя из МНОЖЕСТВА кандидатов координаты: отбрасываются превзойдённые по полноте, среди оставшихся — минимум по каноническому порядку
  • 2.4 (по ревью, критическое) Попарная свёртка заменена на выбор из множества. Полнота — частичный порядок, тай-брейк — тотальный; вместе они давали нетранзитивное отношение победы, из-за которого одна и та же доставка, свёрнутая дважды, давала два состояния витрины поочерёдно
  • 2.5 (по ревью) Имя метрики в координате столкновения обрезается: оно приходит из тела дословно, предел приёма 64 МиБ, и без обрезки одна доставка порождала WARN-строку в десятки мегабайт
  • 2.2 MergeStats: счётчик Incomparable и координаты IncomparableAt (потолок тот же, что у Collisions); несравнимость — частный случай столкновения, Overwrites растёт вместе с ней
  • 2.3 Тесты store: регрессия «пять полей без содержания не стирают измерение», «надмножество побеждает», «при равном содержании поля не теряются», «несравнимые наборы считаются и дают координаты», «повторная свёртка не меняет состояние», «исход не зависит от перестановки (6 перестановок × вместе/порознь)», «падинг не вытесняет измерение»

3. Наблюдаемость (internal/fold)

  • 3.1 Пробросить счётчик и координаты в Stats; признак идёт атрибутом всегда, независимо от выбранной ветви
  • 3.2 Ветвь WARN в logResult выше ветви перезаписей, без значений точек
  • 3.3 Тест на уровень, сообщение и набор атрибутов

4. Проверка на живых данных

  • 4.1 task gate зелёный
  • 4.2 task verify:archive проходит и печатает отпечаток содержимого витрины
  • 4.5 (по ревью) Отпечаток включает units и sealed, а поля переменной длины идут с длиной впереди: разделитель | допустим внутри имени метрики, и две разошедшиеся витрины давали один отпечаток
  • 4.3 Отпечаток совпадает с прежним: 05db720966e47bd5670bfe6b022ddf4a2194dc946aedf93db926b3e944f226d6 (объектов 1737) — перепроверен после всех правок ревью: содержимое витрины не изменилось ни на одной координате. Сверка сделана временным возвратом прежнего формата отпечатка, иначе сравнивать было бы нечего. Отпечаток в новом формате — ba40c1d9bc8e6acfe953b5a207c2f8278d8bfbac116e2f1bee42484854c57fd6
  • 4.4 Счётчик несравнимых наборов на всём архиве равен нулю (проверяет посылку «объединять поля не нужно»)

5. Документация

  • 5.1 docs/architecture.md: правило полноты — множество ключей, а не их число; заодно строка «последние пришедшие данные всегда актуализируют картину», противоречащая правилу слияния
  • 5.2 docs/local-research.md, находка 49: замер под расширенным определением пустоты (несравнимых по-прежнему ноль) и наблюдение про нулевые значения как настоящие измерения
  • 5.3 Комментарии у изменённых функций отражают основание, а не только механику

Приёмочные критерии (из ревью предложения, профиль design)

  • П1 RelateFullness тотальна и не паникует: nil, пустой срез, усечённый JSON, не-объект ([], "s", 123, null), объект с тысячами ключей
  • П2 исход не зависит от порядка — проверен не только на паре, но и на всех шести перестановках тройки, и при разбиении на разные доставки. На паре критерий выполнялся и у дефектной реализации: сломано было именно на трёх
  • П3 resolve(a,a) возвращает a дословно; повторная доставка часа не растит счётчики и не пишет WARN
  • П4 победитель — одна из входных точек дословно: ни объединения полей, ни возврата канонической формы
  • П5 отношение согласовано: Equal устойчива к порядку ключей и пробелам, Superset(a,b) ⟺ Subset(b,a), Incomparable симметрична
  • П6 совпадение канонических форм влечёт Equal; пустота и хеш считают числа одним кодом
  • П7 таблица записей пустоты: 0/0.0/0e0/-0/{}/{ }/[]/""/null пусты, false/" "/"0"/[null]/{"a":null} — нет
  • П8 заданный исход на вырожденном входе: невалидный JSON, не-объект, {} против непустой точки
  • П9 счётчик Incomparable растёт и после того, как список координат упёрся в потолок
  • П10 атрибуты WARN — только счётчики и координаты объекта: ни значений, ни имён полей точки