## ADDED Requirements ### Requirement: Перечисление непокрытых секций доставки Разбор SHALL перечислять верхнеуровневые ключи объекта `data` и возвращать вызывающему те из них, которые он не покрывает. Содержимое непокрытой секции MUST NOT удерживаться после того, как разбор прошёл мимо неё: тела доходят до 42 МиБ, и удержание кучи здесь — часть контракта, а не деталь реализации. Покрытым сегодня является ровно один ключ — `metrics`. Разбор и перечисление MUST ходить по одному объявленному множеству покрытых имён: состояние «секция разбирается, но числится непокрытой» невыразимо по построению. Непокрытым ключ считается независимо от того, что лежит внутри: содержимое не интерпретируется, поэтому и о пустоте секции разбор честно ничего не знает. Измерено на живом архиве — пустых секций HAE не присылает ни разу (99 доставок). Список SHALL быть каноничен: имена отсортированы, повторов нет. Порядок ключей в JSON от HAE нестабилен, а значение уезжает в базу и сравнивается между доставками. Отсутствие непокрытых ключей и отсутствие секции `metrics` — разные события, и оба нормальны: половина потока состоит из доставок без метрик вовсе (48 из 99). #### Scenario: Незнакомая секция попадает в список непокрытых - **WHEN** тело содержит `data.workouts` наряду с `data.metrics` - **THEN** разбор возвращает `workouts` в списке непокрытых ключей - **AND** точки секции `metrics` разбираются как обычно #### Scenario: Доставка без метрик разбирается и не теряется - **WHEN** тело содержит только `data.stateOfMind` - **THEN** разбор завершается без ошибки, точек нет - **AND** `stateOfMind` возвращается в списке непокрытых ключей #### Scenario: Доставка из одних метрик непокрытых ключей не даёт - **WHEN** единственный ключ `data` — `metrics` - **THEN** список непокрытых ключей пуст #### Scenario: Один и тот же набор секций даёт один и тот же список - **WHEN** два тела несут те же секции в разном порядке, а одно из них повторяет непокрытый ключ дважды - **THEN** списки непокрытых ключей у них совпадают #### Scenario: Содержимое непокрытой секции не удерживается в памяти - **WHEN** тело в десятки мегабайт состоит преимущественно из непокрытой секции - **THEN** после разбора удержано не больше четырёх размеров тела — та же граница, что и для тела из метрик - **AND** содержимое непокрытой секции в результат разбора не попадает ### Requirement: Границы списка непокрытых секций Список непокрытых ключей MUST быть ограничен — не больше 32 имён и не больше 64 байт на имя: имена приходят из тела, которым отправитель управляет целиком. Срабатывание любой из границ MUST быть видно вызывающему — молчаливое усечение превратило бы список в уверенный, но неполный ответ на вопрос «что останется потерянным, если тело удалить». Число имён сверх предела отдаётся счётчиком. Имя длиннее предела обрезается по границе рун, к обрезанному приписывается маркер `…` — сверх предела, а не внутри него. Обрезка не инъективна, поэтому обрезанное имя сравнению со словарём известных секций не подлежит. Предел длины считается по байтам **декодированного** имени: escape- последовательности JSON к этому моменту уже разобраны. #### Scenario: Ключей больше предела - **WHEN** объект `data` содержит 40 непокрытых ключей - **THEN** список содержит 32 имени - **AND** число отброшенных имён отдано отдельным счётчиком #### Scenario: Имя ключа длиннее предела - **WHEN** непокрытый ключ длиннее 64 байт - **THEN** в списке лежит имя, обрезанное по границе рун, с маркером `…` ### Requirement: Отказ разбора остаётся всё или ничего Разбор SHALL оставаться операцией «всё или ничего»: ошибка, встреченная **после** того, как секция `metrics` уже разобрана (обрезанное тело, мусор в следующем члене), MUST NOT оставлять точки в результате — доставка считается неразобранной целиком. Иначе часть точек оказалась бы в витрине под статусом, по которому доставку никто не подберёт, и свёртка перестала бы быть детерминированной по журналу. Повтор ключа `metrics` в одном объекте `data` SHALL давать объединение секций, а не победу последней: молча терять точки нельзя. #### Scenario: Тело оборвано после секции метрик - **WHEN** тело содержит целую секцию `metrics`, а следующий член `data` оборван - **THEN** разбор завершается ошибкой и точек не отдаёт #### Scenario: Секция метрик встречается дважды - **WHEN** объект `data` содержит два ключа `metrics` - **THEN** точки обеих секций попадают в результат