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

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

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

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

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

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

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

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

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

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

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

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