# ✨ Подчищать сырой архив до последнего проверенного экспорта

- **Тип:** feature
- **Категория:** Инфра
- **Зачем:** Архив не подчищается вовсе, а резать его раньше даты проверенного экспорта нельзя — в журнале останется дыра, которую нечем пересобрать
- **Теги:** goal:journal-and-rebuild

Срок жизни сырого архива объявлен (14 дней, `storage.raw_retention`), но
удаления нет: архив растёт бесконечно. Пока это 16 МБ и проблемой не является.

**Само правило изменилось.** Экспорт Apple — снапшот всей истории, доставки
после его даты — события поверх снапшота, и состояние всегда пересобираемо
свёрткой. Значит доставки должны жить **до следующего проверенного экспорта**,
а не фиксированные две недели: иначе между концом ретеншена и датой снапшота
образуется дыра в журнале, и пересобрать этот отрезок будет нечем.

Цена измерена: ~23 МБ архива в сутки, то есть ~2 ГБ за квартал между
экспортами. Дёшево за возможность пересобрать что угодно.

Отдельное исключение: `stateOfMind` в экспорт не попадает вовсе (проверено на
свежем архиве). Для него доставки — не хвост журнала, а единственный источник,
и под общее правило удаления он не подпадает.

Включать **после** того, как разбор устоится и пересборка докажет, что
хранилище действительно восстанавливается: иначе страховка исчезнет раньше, чем
перестанет быть нужна.

Готово, когда удаляются только доставки старше последнего проверенного
экспорта, записи `stateOfMind` не трогаются вовсе, а `/stats` показывает
глубину архива и дату снапшота, до которой он подрезан.

Двигает строку «Завершения» цели: «Сырой архив подчищается до последнего проверенного экспорта».

## Предусловие снова открыто

Признак «доставка с непокрытой секцией» появился в change
`2026-08-01-nerazobrannye-sekcii-dostavki` и работал заодно защитой
`stateOfMind`: такие доставки числились `partial`, и ретеншен их не тронул бы.

Change `2026-08-02-trenirovki-i-zapisi` покрыл `stateOfMind` разбором, и защита
исчезла: доставка из одного состояния разума теперь получает `parsed` с пустым
списком непокрытых, то есть **побайтово неотличима** от доставки из метрик — а
метрики восстановимы из экспорта Apple, состояние разума нет (находка 46).
Ретеншен, написанный по правилу «удаляем всё, что не `partial`», сотрёт ровно те
тела, которых в экспорте не существует, и первая же пересборка потеряет историю
состояния разума навсегда.

Значит признак невосстановимости нужен **не производный от «непокрытости»**.
Варианты:

- **Перечень покрытых секций, которых нет в экспорте Apple** рядом с доставкой
  (сегодня — ровно `stateOfMind`). Цена: колонка и строка в свёртке; читается
  так же, как `uncovered_sections`, и одним запросом.
- **Признак у доставки «тело — единственный источник»**, выставляемый разбором.
  Цена та же, но смысл шире и требует решения, что считать единственным
  источником для будущих секций.
- **Никогда не подрезать тела доставок, у которых есть строки в `record`.**
  Цена нулевая по схеме, но неточная: провенанс записи указывает на доставку
  её **текущей** версии, а копий у записи бывает по 26.

Рекомендация — первый вариант: он прямо отвечает на вопрос «что останется
потерянным», как это уже делает `uncovered_sections`, и не требует додумывать
семантику.

Вместе с этим действует правило: задача, которая начинает разбирать секцию, тем
же изменением переводит `partial`-строки с этим ключом в `pending` (так сделала
миграция `00007`). Ретеншену позволено смотреть на `partial` только пока правило
соблюдается.

## Что читать перед удалением тела

Две колонки учётной записи, и обе обязательны:

- `uncovered_sections` — непустой список означает, что в теле есть секции,
  которых разбор не покрывает; удалять нельзя;
- `skipped_entities` — число сущностей с собственным `id`, которые разбор не
  понял. **`NULL` означает «не измерялось» и нулю не равен**: так выглядят
  доставки, свёрнутые разбором, который пропусков не считал, и те, чей разбор не
  досчитал. `NULL` — «не удалять». Прочитать его как ноль значит удалить тело
  тренировки, маршрута которой нет больше нигде: в экспорте Apple его не
  существует.

Правило пришло из задачи «Дозакрыть находки ревью по слиянию сущностей»
(миграция `00008`), где колонка и заведена — без `DEFAULT` именно ради этого
различия.
