хранилище описано как свёртка по журналу событий
- экспорт Apple это снапшот всей истории, доставки после его даты — события поверх; состояние пересобирается как import(экспорт) + replay(доставки) - отсюда ретеншен архива меняется с произвольных 14 дней на «до следующего проверенного экспорта» (~2 ГБ за квартал, измерено), а свёртка обязана быть детерминированной — воспроизведение строго по received_at - названы границы модели: stateOfMind в экспорт не попадает вовсе, а верхние слои за периоды с удалёнными доставками не воскресают и досчитываться не должны — каталог обязан говорить это честно
This commit is contained in:
@@ -34,8 +34,9 @@ description: Конвейер ревью изменений healthlog — дет
|
|||||||
Инварианты, нарушение которых — по умолчанию `critical` (подробно —
|
Инварианты, нарушение которых — по умолчанию `critical` (подробно —
|
||||||
`CLAUDE.md`, `docs/architecture.md`):
|
`CLAUDE.md`, `docs/architecture.md`):
|
||||||
|
|
||||||
- **Точка хранится дословно.** Всё, что теряет поле внутри точки, необратимо:
|
- **Точка хранится дословно.** Хранилище — свёртка по журналу
|
||||||
сырой архив живёт 14 дней, дальше истина — сами точки.
|
(`import(экспорт) + replay(доставки)`), поэтому разобранное пересобираемо, а
|
||||||
|
вот не принятое — нет: доставка мимо архива теряется навсегда.
|
||||||
- **Идентичность по координатам** (`метрика + слой + метка`). `source` в ключ
|
- **Идентичность по координатам** (`метрика + слой + метка`). `source` в ключ
|
||||||
не входит. Неверное правило слияния портит историю молча — заметить это
|
не входит. Неверное правило слияния портит историю молча — заметить это
|
||||||
можно только сверкой с родным экспортом Apple, то есть месяцами позже.
|
можно только сверкой с родным экспортом Apple, то есть месяцами позже.
|
||||||
|
|||||||
@@ -24,13 +24,15 @@ healthlog — хранилище данных о здоровье, у котор
|
|||||||
(`task up` / `task restart`), данные в `./data`. Перезапуск на пару секунд
|
(`task up` / `task restart`), данные в `./data`. Перезапуск на пару секунд
|
||||||
безопасен — дыру закроют средний и глубокий проходы синхронизации; а вот
|
безопасен — дыру закроют средний и глубокий проходы синхронизации; а вот
|
||||||
сломанный приём, оставленный работать, теряет данные необратимо.
|
сломанный приём, оставленный работать, теряет данные необратимо.
|
||||||
- **Потерянная точка не восстанавливается.** Сырой архив живёт 14 дней. Любая
|
- **Потерянная доставка не восстанавливается.** Тело, не попавшее в архив, в
|
||||||
правка разбора, слияния или вывода слоя — это `deep`-профиль ревью, без
|
журнал не попадает вовсе: телефон его не перешлёт. Разобранное же всегда
|
||||||
исключений.
|
пересобираемо свёрткой, поэтому цена ошибки разбора и цена ошибки приёма
|
||||||
|
различаются на порядок. Любая правка разбора, слияния или вывода слоя — это
|
||||||
|
`deep`-профиль ревью, без исключений.
|
||||||
- **Данные чувствительны.** Ничего из `./data` не попадает ни в git, ни в
|
- **Данные чувствительны.** Ничего из `./data` не попадает ни в git, ни в
|
||||||
логи выше `DEBUG`, ни в вывод агента. Гейт проверяет первое механически
|
логи выше `DEBUG`, ни в вывод агента. Гейт проверяет первое механически
|
||||||
(`no-health-data`), остальное — на тебе.
|
(`no-health-data`), остальное — на тебе.
|
||||||
- **Разведка уже проведена.** `docs/local-research.md` — 41 находка на живом
|
- **Разведка уже проведена.** `docs/local-research.md` — 46 находок на живом
|
||||||
потоке, половина расходится с документацией HAE. Проверь там, прежде чем
|
потоке, половина расходится с документацией HAE. Проверь там, прежде чем
|
||||||
строить догадку о формате: скорее всего вопрос уже закрыт измерением.
|
строить догадку о формате: скорее всего вопрос уже закрыт измерением.
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,15 @@ Module path — `git.vakhrushev.me/av/healthlog`.
|
|||||||
## Инварианты
|
## Инварианты
|
||||||
|
|
||||||
- **Точки хранятся дословно.** Часовой объект держит точки ровно в том виде,
|
- **Точки хранятся дословно.** Часовой объект держит точки ровно в том виде,
|
||||||
в каком их прислал HAE. На этом инварианте держится всё остальное: сырой
|
в каком их прислал HAE. Начнём что-то отбрасывать внутри точки — потеряем
|
||||||
архив живёт лишь 14 дней, дальше истина — сами объекты. Начнём что-то
|
безвозвратно.
|
||||||
отбрасывать внутри точки — срок хранения архива станет сроком жизни данных.
|
- **Хранилище — свёртка по журналу.** Экспорт Apple это снапшот всей истории,
|
||||||
|
доставки HAE после его даты — события поверх. Состояние всегда пересобираемо:
|
||||||
|
`import(экспорт) + replay(доставки по received_at)`. Поэтому сырой архив
|
||||||
|
живёт до следующего проверенного экспорта (~2 ГБ за квартал), а свёртка
|
||||||
|
обязана быть детерминированной. Что не восстанавливается — `stateOfMind`
|
||||||
|
(его в экспорте нет) и верхние слои за периоды с удалёнными доставками;
|
||||||
|
каталог обязан говорить об этом честно, а не досчитывать молча.
|
||||||
- **Сохранили — значит приняли.** Код ответа отражает доставку, а не разбор:
|
- **Сохранили — значит приняли.** Код ответа отражает доставку, а не разбор:
|
||||||
битый JSON — 400, непонятое содержимое — 200.
|
битый JSON — 400, непонятое содержимое — 200.
|
||||||
- **Ничего не теряем молча.** Идентичность — координаты
|
- **Ничего не теряем молча.** Идентичность — координаты
|
||||||
@@ -82,7 +88,8 @@ Module path — `git.vakhrushev.me/av/healthlog`.
|
|||||||
запускаются.
|
запускаются.
|
||||||
|
|
||||||
**Поток не останавливается.** Телефон шлёт непрерывно и молча. Сломанный приём,
|
**Поток не останавливается.** Телефон шлёт непрерывно и молча. Сломанный приём,
|
||||||
оставленный работать, теряет данные необратимо — сырой архив живёт 14 дней.
|
оставленный работать, теряет данные необратимо: доставка, не попавшая в
|
||||||
|
архив, в журнал не попадает вовсе — телефон её не перешлёт.
|
||||||
Ничего из `./data` не попадает ни в git, ни в логи выше `DEBUG`, ни в вывод
|
Ничего из `./data` не попадает ни в git, ни в логи выше `DEBUG`, ни в вывод
|
||||||
агента.
|
агента.
|
||||||
|
|
||||||
|
|||||||
@@ -34,22 +34,22 @@ healthlog делает это один раз. Телефон шлёт данн
|
|||||||
## Как устроено
|
## Как устроено
|
||||||
|
|
||||||
```
|
```
|
||||||
iPhone ──HTTPS POST──► healthlog ──► сырой архив (.json.gz, 14 дней)
|
экспорт Apple ────────┐ снапшот всей истории, раз в 2–3 месяца
|
||||||
|
▼
|
||||||
|
iPhone ──HTTPS POST──► healthlog ──► журнал доставок (.json.gz)
|
||||||
│ │
|
│ │
|
||||||
│ └── страховка разбора, не склад
|
│ └── события поверх снапшота
|
||||||
▼
|
▼
|
||||||
SQLite ──┬──► HTTP read API ──► мои приложения
|
SQLite ──┬──► HTTP read API ──► мои приложения
|
||||||
(точки по │
|
(свёртка по │
|
||||||
слоям) └──► MCP ───────────► агенты
|
журналу) └──► MCP ───────────► агенты
|
||||||
▲
|
|
||||||
экспорт Apple ──────────────┘ нижний слой, раз в 2–3 месяца
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Приём сначала кладёт тело запроса на диск как есть и только потом разбирает.
|
Приём сначала кладёт тело запроса на диск как есть и только потом разбирает.
|
||||||
Значит, ошибка в нашем разборе не может привести к потере данных: хранилище
|
Значит, ошибка в разборе не теряет данные: состояние всегда пересобирается
|
||||||
пересобирается из архива командой `healthlog reindex`. Архив при этом
|
свёрткой `import(экспорт) + replay(доставки)`. Отсюда и главный инвариант —
|
||||||
недолговечен — дальше истина в самих точках, и потому точки хранятся
|
точки хранятся дословно: журнал, из которого что-то выброшено, перестаёт быть
|
||||||
дословно.
|
журналом.
|
||||||
|
|
||||||
Подробности — [docs/architecture.md](docs/architecture.md).
|
Подробности — [docs/architecture.md](docs/architecture.md).
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ iPhone ──HTTPS POST──► healthlog ──► сырой архив (.jso
|
|||||||
сырой архив. Разбора, хранилища и read API ещё нет — план в
|
сырой архив. Разбора, хранилища и read API ещё нет — план в
|
||||||
[docs/plan.md](docs/plan.md).
|
[docs/plan.md](docs/plan.md).
|
||||||
|
|
||||||
Разведка формата закончена: 41 находка на живом потоке, половина расходится с
|
Разведка формата закончена: 46 находок на живом потоке, половина расходится с
|
||||||
документацией Health Auto Export — [docs/local-research.md](docs/local-research.md).
|
документацией Health Auto Export — [docs/local-research.md](docs/local-research.md).
|
||||||
|
|
||||||
## Команды
|
## Команды
|
||||||
|
|||||||
+60
-12
@@ -15,11 +15,12 @@ healthlog принимает выгрузки Apple Health из приложен
|
|||||||
в каком их прислал HAE — без переименований, пересчётов и отбрасывания
|
в каком их прислал HAE — без переименований, пересчётов и отбрасывания
|
||||||
незнакомых полей. Поэтому хранилище само по себе является полной копией
|
незнакомых полей. Поэтому хранилище само по себе является полной копией
|
||||||
данных, а не производной выжимкой.
|
данных, а не производной выжимкой.
|
||||||
- **Сырой архив — страховка разбора, а не вечный склад.** Тело запроса ложится
|
- **Хранилище — свёртка по журналу, а не единственная копия.** Экспорт Apple
|
||||||
на диск до разбора и живёт ограниченный срок (по умолчанию две недели):
|
это снапшот всей истории, доставки HAE после его даты — события поверх
|
||||||
этого хватает, чтобы пережить ошибку в нашем разборе и пересобрать
|
снапшота. Состояние всегда пересобираемо: `import(экспорт) + replay(доставки)`.
|
||||||
хранилище (`healthlog reindex`). Дальше источником истины остаются часовые
|
Отсюда срок жизни сырого архива — до следующего проверенного экспорта, а не
|
||||||
объекты — прямое следствие пункта выше, см. «Хранилище».
|
произвольные две недели. Исключение названо вслух: `stateOfMind` в экспорт не
|
||||||
|
попадает, см. «Хранилище».
|
||||||
- **Сохранили — значит приняли.** Код ответа отражает доставку, а не разбор
|
- **Сохранили — значит приняли.** Код ответа отражает доставку, а не разбор
|
||||||
(см. «Приём»).
|
(см. «Приём»).
|
||||||
- **Ничего не теряем молча.** Идентичность — устойчивые координаты
|
- **Ничего не теряем молча.** Идентичность — устойчивые координаты
|
||||||
@@ -236,16 +237,63 @@ HRV); у накопительных — только `date`. Поэтому то
|
|||||||
|
|
||||||
## Хранилище
|
## Хранилище
|
||||||
|
|
||||||
### Сырой архив — короткая страховка
|
### Сырой архив и восстановление состояния
|
||||||
|
|
||||||
`raw/ГГГГ/ММ/ДД/<ulid>.json.gz` — тело запроса как пришло, не редактируется.
|
`raw/ГГГГ/ММ/ДД/<ulid>.json.gz` — тело запроса как пришло, не редактируется.
|
||||||
Живёт **ограниченный срок** (`storage.raw_retention`, по умолчанию 14 дней),
|
|
||||||
после чего удаляется.
|
|
||||||
|
|
||||||
Смысл срока: архив нужен, чтобы пережить ошибку в **нашем** разборе и
|
Два источника вместе образуют **полный журнал событий**, а хранилище —
|
||||||
пересобрать хранилище (`healthlog reindex`). Двух недель на это заведомо
|
свёртку по нему:
|
||||||
хватает. Вечно хранить его незачем — часовые объекты держат те же точки
|
|
||||||
дословно, так что архив дублировал бы данные, а не страховал их.
|
```
|
||||||
|
состояние = import(последний проверенный экспорт) ← снапшот всей истории
|
||||||
|
+ replay(доставки после его даты) ← хвост событий
|
||||||
|
```
|
||||||
|
|
||||||
|
Экспорт Apple — не просто «источник истины для нижнего слоя», а снапшот: он
|
||||||
|
содержит всю историю целиком (3.6 млн записей с 2019 года). Доставки HAE после
|
||||||
|
его даты — события поверх снапшота. Значит любое повреждение хранилища,
|
||||||
|
включая ошибку в нашем разборе любой давности, лечится пересборкой, а не
|
||||||
|
восстановлением из бекапа.
|
||||||
|
|
||||||
|
Отсюда три следствия, каждое из которых меняет реализацию.
|
||||||
|
|
||||||
|
**Срок жизни архива определяется циклом экспорта, а не календарём.** Прежние
|
||||||
|
14 дней были произвольным числом. Правильное правило: доставки хранятся **до
|
||||||
|
следующего проверенного экспорта**, иначе в журнале появится дыра между концом
|
||||||
|
ретеншена и датой снапшота. Цена измерена: поток даёт ~23 МБ архива в сутки,
|
||||||
|
то есть ~2 ГБ за квартал между экспортами. Это дёшево за возможность
|
||||||
|
пересобрать что угодно.
|
||||||
|
|
||||||
|
**Свёртка обязана быть детерминированной.** Проигрывание должно давать то же
|
||||||
|
состояние, что и приём в реальном времени. Слияние «выигрывает более полная
|
||||||
|
точка» коммутативно и порядка не требует; но когда две одинаково полные точки
|
||||||
|
несут разные значения, исход решает порядок — поэтому воспроизведение идёт
|
||||||
|
строго по `received_at`, а не по порядку файлов в каталоге.
|
||||||
|
|
||||||
|
**`reindex` и `import` — одна операция, а не две.** Восстановление это импорт
|
||||||
|
снапшота плюс проигрывание хвоста; отдельной «пересборки из архива» не
|
||||||
|
существует, она просто вырожденный случай с пустым снапшотом.
|
||||||
|
|
||||||
|
#### Что не восстанавливается, и это сказано вслух
|
||||||
|
|
||||||
|
Модель почти полна, но не полностью — умолчать об этом опаснее, чем признать.
|
||||||
|
|
||||||
|
**`stateOfMind` в экспорте отсутствует вовсе.** Проверено на свежем экспорте:
|
||||||
|
ни одного типа со словом `StateOfMind` (есть только `MindfulSession` — это
|
||||||
|
минуты осознанности, другое). Состояние разума живёт **только** в доставках
|
||||||
|
HAE. Значит для него доставки не хвост журнала, а единственный источник: либо
|
||||||
|
они не удаляются никогда, либо его история держится на самих сохранённых
|
||||||
|
записях и восстановлению не подлежит.
|
||||||
|
|
||||||
|
**Верхние слои за периоды с удалёнными доставками.** После проигрывания
|
||||||
|
снапшота у старого периода будет только слой `sample`; `minute` и `hour` за
|
||||||
|
него не воскреснут. Посчитать их вниз из `sample` технически можно — и нельзя
|
||||||
|
по инварианту: это была бы **наша** агрегация под видом присланной.
|
||||||
|
|
||||||
|
Поэтому правило: **восстановление не обязано быть побайтным, оно обязано быть
|
||||||
|
честным.** Каталог разрезов показывает, какие слои есть за какой период; после
|
||||||
|
пересборки старый период честно объявляет один слой вместо трёх, а не
|
||||||
|
притворяется, что ничего не изменилось.
|
||||||
|
|
||||||
### Устаревание нижнего слоя
|
### Устаревание нижнего слоя
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
## высокий
|
## высокий
|
||||||
- [Разбор метрик в часовые объекты](razbor-metrik-v-obekty.md) — Доставки копятся непрозрачными телами — точек в хранилище нет вовсе, всё остальное упирается в это
|
- [Разбор метрик в часовые объекты](razbor-metrik-v-obekty.md) — Доставки копятся непрозрачными телами — точек в хранилище нет вовсе, всё остальное упирается в это
|
||||||
- [Тренировки и секции с собственными id](trenirovki-i-zapisi.md) — Тренировки с геотреком и состояние разума приходят, но не разбираются — без них не закрыть ни трекер, ни агента-медика
|
- [Тренировки и секции с собственными id](trenirovki-i-zapisi.md) — Тренировки с геотреком и состояние разума приходят, но не разбираются — без них не закрыть ни трекер, ни агента-медика
|
||||||
- [Пересборка хранилища из сырого архива](reindex-iz-arhiva.md) — Разбор будет ошибаться, а окно на исправление — 14 дней жизни архива
|
- [Пересборка хранилища из сырого архива](reindex-iz-arhiva.md) — Ошибка разбора без пересборки становится потерей данных — исправленный код не применится к уже разобранному
|
||||||
- [Измеренный род агрегации и каталог разрезов](rod-agregacii-i-katalog.md) — Без рода метрики свёртка в ответе неотличима от угадывания — а суммировать нижний слой значит завысить втрое
|
- [Измеренный род агрегации и каталог разрезов](rod-agregacii-i-katalog.md) — Без рода метрики свёртка в ответе неотличима от угадывания — а суммировать нижний слой значит завысить втрое
|
||||||
- [Read API: точки, выбор слоя, свёртка по сетке](read-api-tochki.md) — Данные видны только через sqlite на хосте — ни один из трёх потребителей ничего прочитать не может
|
- [Read API: точки, выбор слоя, свёртка по сетке](read-api-tochki.md) — Данные видны только через sqlite на хосте — ни один из трёх потребителей ничего прочитать не может
|
||||||
- [OpenAPI-спека и Swagger UI](openapi-swagger.md) — Потребителей три и один из них агент — контракт должен читаться машиной, а не пересказываться в чате
|
- [OpenAPI-спека и Swagger UI](openapi-swagger.md) — Потребителей три и один из них агент — контракт должен читаться машиной, а не пересказываться в чате
|
||||||
|
|||||||
@@ -3,14 +3,26 @@
|
|||||||
**Приоритет:** высокий
|
**Приоритет:** высокий
|
||||||
|
|
||||||
Разбор пишется по реальным данным и будет ошибаться — это норма, а не риск.
|
Разбор пишется по реальным данным и будет ошибаться — это норма, а не риск.
|
||||||
Риск в другом: сырой архив живёт 14 дней, и окно на исправление ошибки равно
|
Риск в другом: без пересборки ошибка разбора становится потерей данных —
|
||||||
этому сроку. Без `reindex` ошибка разбора становится потерей данных.
|
исправленный код не применится к тому, что уже разобрано неверно.
|
||||||
|
|
||||||
Пересчёт по всей истории сразу ещё и **точнее** приёма: вывод слоя и род
|
Пересчёт по всей истории сразу ещё и **точнее** приёма: вывод слоя и род
|
||||||
агрегации на полном ряду доставок надёжнее, чем на одной.
|
агрегации на полном ряду доставок надёжнее, чем на одной.
|
||||||
|
|
||||||
Готово, когда `healthlog reindex` пересобирает хранилище с нуля из `data/raw`
|
Проектировать это надо сразу как **свёртку по журналу**, а не как разовую
|
||||||
и результат совпадает с накопленным приёмом.
|
утилиту: состояние есть `import(снапшот экспорта) + replay(доставки после его
|
||||||
|
даты)`, и пересборка из архива — вырожденный случай с пустым снапшотом. Тогда
|
||||||
|
`reindex` и `import` окажутся одной операцией с разным входом, а не двумя
|
||||||
|
похожими.
|
||||||
|
|
||||||
|
Отсюда требование, которое легко упустить: **свёртка обязана быть
|
||||||
|
детерминированной.** Проигрывание должно давать то же состояние, что приём в
|
||||||
|
реальном времени. Слияние «выигрывает более полная точка» коммутативно, но две
|
||||||
|
одинаково полные точки с разными значениями разрешает порядок — значит
|
||||||
|
воспроизведение идёт строго по `received_at`, а не по порядку файлов в каталоге.
|
||||||
|
|
||||||
|
Готово, когда пересборка с нуля даёт состояние, совпадающее с накопленным
|
||||||
|
приёмом, и повторный прогон ничего не меняет.
|
||||||
|
|
||||||
Связано: план шаг 3, `docs/architecture.md` → «Сырой архив».
|
Связано: план шаг 3, `docs/architecture.md` → «Сырой архив».
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,24 @@
|
|||||||
Срок жизни сырого архива объявлен (14 дней, `storage.raw_retention`), но
|
Срок жизни сырого архива объявлен (14 дней, `storage.raw_retention`), но
|
||||||
удаления нет: архив растёт бесконечно. Пока это 16 МБ и проблемой не является.
|
удаления нет: архив растёт бесконечно. Пока это 16 МБ и проблемой не является.
|
||||||
|
|
||||||
Включать **после** того, как разбор устоится и `reindex` докажет, что
|
**Само правило изменилось.** Экспорт Apple — снапшот всей истории, доставки
|
||||||
хранилище действительно пересобирается: иначе страховка исчезнет раньше, чем
|
после его даты — события поверх снапшота, и состояние всегда пересобираемо
|
||||||
|
свёрткой. Значит доставки должны жить **до следующего проверенного экспорта**,
|
||||||
|
а не фиксированные две недели: иначе между концом ретеншена и датой снапшота
|
||||||
|
образуется дыра в журнале, и пересобрать этот отрезок будет нечем.
|
||||||
|
|
||||||
|
Цена измерена: ~23 МБ архива в сутки, то есть ~2 ГБ за квартал между
|
||||||
|
экспортами. Дёшево за возможность пересобрать что угодно.
|
||||||
|
|
||||||
|
Отдельное исключение: `stateOfMind` в экспорт не попадает вовсе (проверено на
|
||||||
|
свежем архиве). Для него доставки — не хвост журнала, а единственный источник,
|
||||||
|
и под общее правило удаления он не подпадает.
|
||||||
|
|
||||||
|
Включать **после** того, как разбор устоится и пересборка докажет, что
|
||||||
|
хранилище действительно восстанавливается: иначе страховка исчезнет раньше, чем
|
||||||
перестанет быть нужна.
|
перестанет быть нужна.
|
||||||
|
|
||||||
Готово, когда старые тела удаляются по расписанию, а `/stats` показывает
|
Готово, когда удаляются только доставки старше последнего проверенного
|
||||||
глубину архива в днях.
|
экспорта, записи `stateOfMind` не трогаются вовсе, а `/stats` показывает
|
||||||
|
глубину архива и дату снапшота, до которой он подрезан.
|
||||||
|
|
||||||
|
|||||||
@@ -1427,6 +1427,38 @@ RFC3339 Z 20 data.stateOfMind[].end = 2026-07-31T18:03:51
|
|||||||
То есть правило «экспорт говорит кодами» верно для типов записей и категориальных
|
То есть правило «экспорт говорит кодами» верно для типов записей и категориальных
|
||||||
значений, но не для всего документа.
|
значений, но не для всего документа.
|
||||||
|
|
||||||
|
## 46. `stateOfMind` в экспорт не попадает — единственная дыра в журнале
|
||||||
|
|
||||||
|
Экспорт плюс доставки после его даты образуют полный журнал событий: состояние
|
||||||
|
пересобирается свёрткой `import(снапшот) + replay(хвост)`. Проверка показала
|
||||||
|
ровно одно исключение.
|
||||||
|
|
||||||
|
В свежем экспорте **нет ни одного типа со словом `StateOfMind`**:
|
||||||
|
|
||||||
|
```
|
||||||
|
grep -oiE 'type="[^"]*(mind|mood|emotion)[^"]*"' → только
|
||||||
|
type="HKCategoryTypeIdentifierMindfulSession" ← минуты осознанности, другое
|
||||||
|
type="HKWorkoutEventTypeMotionPaused|Resumed" ← совпадение по подстроке
|
||||||
|
```
|
||||||
|
|
||||||
|
При этом HAE состояние разума шлёт исправно, и шлёт правильно — стабильными
|
||||||
|
кодами HealthKit (`momentary_emotion`, `slightly_pleasant`, `drained`), в
|
||||||
|
отличие от переведённых фаз сна (находка 37).
|
||||||
|
|
||||||
|
**Следствие:** для `stateOfMind` доставки HAE — не хвост журнала, а
|
||||||
|
единственный источник. Под общее правило ретеншена он не подпадает: удалив
|
||||||
|
доставки, мы потеряем возможность восстановить его историю навсегда.
|
||||||
|
|
||||||
|
### Цена журнала
|
||||||
|
|
||||||
|
Поток даёт **~23 МБ сырого архива в сутки** (89 доставок за 16.4 часа дали
|
||||||
|
15.6 МБ). При экспорте раз в 2–3 месяца это ~2 ГБ между снапшотами — дёшево за
|
||||||
|
возможность пересобрать хранилище с любой точки.
|
||||||
|
|
||||||
|
Прежние 14 дней ретеншена были произвольным числом; правильный срок — до
|
||||||
|
следующего проверенного экспорта, иначе между концом ретеншена и датой
|
||||||
|
снапшота в журнале образуется дыра.
|
||||||
|
|
||||||
## Инструмент
|
## Инструмент
|
||||||
|
|
||||||
Разбор ведётся скриптом `tmp/research/hl.py` (Python 3, только стандартная
|
Разбор ведётся скриптом `tmp/research/hl.py` (Python 3, только стандартная
|
||||||
|
|||||||
+4
-4
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
Разведка закончена: правило вывода слоя, модель идентичности и формы точки
|
Разведка закончена: правило вывода слоя, модель идентичности и формы точки
|
||||||
проверены на живом потоке, выводы — в
|
проверены на живом потоке, выводы — в
|
||||||
[local-research.md](local-research.md), 41 находка.
|
[local-research.md](local-research.md), 46 находок.
|
||||||
|
|
||||||
## Шаги
|
## Шаги
|
||||||
|
|
||||||
@@ -73,9 +73,9 @@
|
|||||||
- ~~Пометка локализованных полей в схемах.~~ **Переросло в шаг 3:** одной
|
- ~~Пометка локализованных полей в схемах.~~ **Переросло в шаг 3:** одной
|
||||||
пометки мало, нужен словарь кодов, иначе не сойтись с родным экспортом
|
пометки мало, нужен словарь кодов, иначе не сойтись с родным экспортом
|
||||||
(находка 37).
|
(находка 37).
|
||||||
- **Ретеншен сырого архива** (`storage.raw_retention`, 14 дней) — удаление
|
- **Ретеншен сырого архива** — удаление доставок старше последнего
|
||||||
старых тел. Пока архив не подчищается; включить после того, как разбор
|
проверенного экспорта (не фиксированный срок: журнал не должен рваться).
|
||||||
устоится, иначе страховка исчезнет раньше, чем перестанет быть нужна.
|
Пока архив не подчищается; включить после того, как разбор устоится.
|
||||||
- **Порог `sealed`** — с какого возраста час считается запечатанным. Ставим по
|
- **Порог `sealed`** — с какого возраста час считается запечатанным. Ставим по
|
||||||
факту: сначала пишем `WARN` на изменение старых объектов и смотрим, какая
|
факту: сначала пишем `WARN` на изменение старых объектов и смотрим, какая
|
||||||
глубина досчёта встречается в жизни (наблюдалось до 22 минут, находка 10).
|
глубина досчёта встречается в жизни (наблюдалось до 22 минут, находка 10).
|
||||||
|
|||||||
Reference in New Issue
Block a user