Files
healthlog/openspec/changes/archive/2026-08-01-nerazobrannye-sekcii-dostavki/specs/parsing/spec.md
T
av 34e5109b6d непокрытые секции доставки видны в статусе разбора
- половина потока (50 доставок из 104) не несёт metrics вовсе и до сих пор
  числилась parsed: ретеншен, поверив статусу, срезал бы тела stateOfMind,
  которых в экспорте Apple нет
- разбор перечисляет верхнеуровневые ключи data, непокрытые проглатываются
  декодированием: тело 40 МиБ из непокрытой секции удерживает 0 МиБ
- статус partial и колонка delivery.uncovered_sections; миграция переводит
  прежние parsed в pending — им верить нельзя
- витрина не изменилась: отпечаток совпал с прогоном до изменения
2026-08-01 21:25:59 +03:00

7.3 KiB
Raw Blame History

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 единственный ключ datametrics
  • 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 точки обеих секций попадают в результат