- каждая запись каталога задач получила тип вместо тега kind: и префикса заголовка; секция роадмапа «Разработка» стала «Сопровождением», порядок секций канонический - поправлены протухшие факты: нереализованные маршруты Read API, MCP и `healthlog import`, словарь слоёв в инварианте, семантика гейта по покрытию диффа, периметр перестал дублировать security.md - замер слияния переведён с находки 49 на находку 54, заполнены Purpose спек storage и parsing
86 lines
7.1 KiB
Markdown
86 lines
7.1 KiB
Markdown
# ✨ Подчищать сырой архив до последнего проверенного экспорта
|
||
|
||
- **Тип:** 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` именно ради этого
|
||
различия.
|