feat: разбор и хранение тренировок и состояния разума

- секции `workouts` и `stateOfMind` покрыты разбором: тренировка лежит одной
  строкой вместе с маршрутом и внутренними рядами, запись — по ключу `род + id`;
  миграция 00007 заводит обе таблицы и возвращает в очередь `partial`-доставки
  с этими ключами
- сущность заменяется целиком, но условно: приехавшая побеждает, если не теряет
  содержания сохранённой (множество ключей и длины верхнеуровневых массивов), а
  при равном содержании выигрывает версия из более поздней доставки ЖУРНАЛА —
  «побеждает приехавшая» было бы функцией порядка свёртки, и живая витрина
  расходилась бы с пересборкой молча
- отпечаток витрины покрывает тренировки и записи и снимается одним снимком
  базы; отчёт `reindex` считает «было и стало» по каждой единице хранения
This commit is contained in:
av
2026-08-02 13:05:16 +03:00
parent c28de9796e
commit f8200f7f80
47 changed files with 5817 additions and 301 deletions
+32 -8
View File
@@ -26,14 +26,38 @@
экспорта, записи `stateOfMind` не трогаются вовсе, а `/stats` показывает
глубину архива и дату снапшота, до которой он подрезан.
## Предусловие снято
## Предусловие снова открыто
Признак, без которого ретеншен был опасен, готов: доставка с непокрытой секцией
имеет статус `partial` и список непокрытых ключей
(change `2026-08-01-nerazobrannye-sekcii-dostavki`). Ретеншен обязан спрашивать
статус, а не считать `parsed` разрешением: тело `stateOfMind` восстановить
неоткуда — в экспорте Apple секции нет.
Признак «доставка с непокрытой секцией» появился в 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`. Ретеншену
позволено смотреть на `partial` только пока правило соблюдается.
же изменением переводит `partial`-строки с этим ключом в `pending` (так сделала
миграция `00007`). Ретеншену позволено смотреть на `partial` только пока правило
соблюдается.