Files
healthlog/openspec/changes/archive/2026-08-01-nerazobrannye-sekcii-dostavki/specs/storage/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.9 KiB
Raw Blame History

ADDED Requirements

Requirement: Учёт частично разобранной доставки

Система SHALL отличать доставку, разобранную целиком, от доставки, в теле которой остались непокрытые разбором секции. Доставка с непустым списком непокрытых ключей MUST получать статус partial, а не parsed.

Статусы разбора:

pending  этим разбором ещё не смотрели
parsed   разобрано всё, что в теле было
partial  разобрано покрытое; в теле остались непокрытые секции
failed   разобрать не удалось, точек нет

Источник истины — список непокрытых ключей; статус производен от него и от факта отказа, в порядке failedpartialparsed. Приоритет назван явно, чтобы читатели (ретеншен, статистика) спрашивали статус, а не сравнивали список со строкой.

Список непокрытых ключей SHALL сохраняться рядом с доставкой — именами ключей, без содержимого секций. Он же ответ на вопрос «что останется потерянным, если тело удалить»: для stateOfMind доставки HAE единственный источник, в экспорте Apple его нет (находка 46). Поэтому список MUST сохраняться и при отказе разбора, если разбор успел его собрать: failed с непустым списком — законное состояние.

Запись списка MUST замещать прежнее значение целиком, включая замещение пустым: иначе доставка, все секции которой стали покрытыми, осталась бы partial навсегда.

Список — снимок покрытия на момент свёртки. Задача, которая начинает разбирать секцию, тем же изменением SHALL переводить partial-строки с этим ключом в pending; ретеншену позволено смотреть на partial только при соблюдении этого правила.

Статусы, поставленные разбором, который частичного исхода не различал, доверия не заслуживают: под parsed у них лежат и полностью разобранные доставки, и доставки без метрик вовсе. Такие строки MUST переводиться в pending — «этим разбором ещё не смотрели». Число точек у них до пересвёртки остаётся прежним: оно производно от объектов витрины, которые никуда не делись.

Scenario: Доставка с непокрытой секцией отмечается частичной

  • WHEN разбор доставки вернул непустой список непокрытых ключей
  • THEN parse_status доставки равен partial
  • AND список непокрытых ключей сохранён вместе с доставкой
  • AND точки покрытой секции сохранены как обычно

Scenario: Доставка без непокрытых секций остаётся parsed

  • WHEN разбор доставки не дал непокрытых ключей
  • THEN parse_status равен parsed
  • AND сохранённый список непокрытых ключей пуст

Scenario: Отказ разбора сильнее частичности

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

Scenario: Пересвёртка после того, как секция стала покрытой

  • WHEN доставка со статусом partial сворачивается повторно разбором, который эту секцию уже покрывает
  • THEN её статус становится parsed
  • AND сохранённый список непокрытых ключей пуст

Scenario: Строки прежнего разбора переводятся в неразобранные

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

MODIFIED Requirements

Requirement: Значения точек не попадают в логи

Данные о здоровье чувствительнее токенов. Система MUST NOT писать значения точек и тела доставок в записи лога уровня выше DEBUG.

Непокрытые секции называются в логе именами ключей: имя секции — это форма пакета, а не измерение. Содержимое секции в лог не попадает ни при каком уровне выше DEBUG. Имена идут структурным атрибутом, а не склейкой в текст сообщения: кодировщик экранирует управляющие символы, и имя из чужого тела не разрывает построчный разбор логов.

Частичный разбор уровня записи не повышает: partial — установившееся состояние половины потока (48 доставок из 99), и постоянный WARN обесценил бы уровень. Повышает уровень другое — срабатывание границ списка: тело с сотнями секций или с именем длиннее предела на HAE не похоже вовсе.

Scenario: Разбор доставки логируется без значений

  • WHEN доставка разобрана
  • THEN запись лога содержит счётчики (метрик, точек, объектов) и идентификатор доставки
  • AND не содержит ни значений точек, ни имён устройств

Scenario: Непокрытые секции названы именами ключей

  • WHEN доставка содержит непокрытую секцию
  • THEN запись лога содержит имена непокрытых ключей отдельным атрибутом
  • AND не содержит ничего из содержимого этих секций
  • AND уровень записи из-за одной лишь частичности не повышается

Scenario: Границы списка сработали

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