From 3d24248075d5c12e3e853378c19013f15aff6459 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Wed, 5 Aug 2026 19:09:35 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BF=D1=80=D0=B8=D0=B2?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D0=BD=D0=B0=20=D0=BA=20=D0=BA=D0=B0=D0=BD?= =?UTF-8?q?=D0=BE=D0=BD=D1=83=20av-dev-pm=204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - каждая запись каталога задач получила тип вместо тега kind: и префикса заголовка; секция роадмапа «Разработка» стала «Сопровождением», порядок секций канонический - поправлены протухшие факты: нереализованные маршруты Read API, MCP и `healthlog import`, словарь слоёв в инварианте, семантика гейта по покрытию диффа, периметр перестал дублировать security.md - замер слияния переведён с находки 49 на находку 54, заполнены Purpose спек storage и parsing --- CLAUDE.md | 19 ++-- docs/.pm.json | 2 +- ...26-08-03-kod-ryadom-so-strokoj-reestrom.md | 4 +- docs/adr/README.md | 5 +- docs/architecture.md | 86 ++++++++++++------- docs/database.md | 1 + docs/research/README.md | 4 +- docs/tasks/BACKLOG.md | 74 ++++++++-------- docs/tasks/REJECTED.md | 2 +- docs/tasks/ROADMAP.md | 57 ++++++------ docs/tasks/SPRINT.md | 14 +-- docs/tasks/items/apple-export-import.md | 5 +- docs/tasks/items/config-defaults-data-dir.md | 5 +- .../items/data-migration-row-selection.md | 5 +- docs/tasks/items/day-boundary-timezone.md | 5 +- docs/tasks/items/delivery-header-limits.md | 5 +- .../items/delivery-headers-in-archive.md | 5 +- docs/tasks/items/deploy-rivendell.md | 5 +- docs/tasks/items/deploy.md | 5 +- docs/tasks/items/derived-content-schemas.md | 5 +- docs/tasks/items/drop-heartbeat-series.md | 5 +- docs/tasks/items/entity-size-limits.md | 5 +- .../items/entity-without-parsed-label.md | 5 +- docs/tasks/items/integrity-before-swap.md | 5 +- docs/tasks/items/journal-and-rebuild.md | 3 +- docs/tasks/items/journal-order-on-ingest.md | 5 +- .../items/last-wins-over-completeness.md | 5 +- docs/tasks/items/limits-and-load.md | 3 +- docs/tasks/items/lower-layer-cleanup.md | 3 +- docs/tasks/items/lower-layer-expiry.md | 5 +- docs/tasks/items/mcp-server.md | 5 +- docs/tasks/items/merge-cost-wide-delivery.md | 5 +- docs/tasks/items/merge-counters-in-db.md | 5 +- docs/tasks/items/merge-robustness.md | 3 +- .../items/monthly-manual-sections-pass.md | 5 +- docs/tasks/items/native-export-import.md | 3 +- docs/tasks/items/ndjson-stream.md | 5 +- docs/tasks/items/observability.md | 3 +- docs/tasks/items/openapi-gate-check.md | 5 +- docs/tasks/items/openapi-spec.md | 5 +- docs/tasks/items/overlapping-sources.md | 5 +- docs/tasks/items/parquet-export.md | 5 +- docs/tasks/items/parsing-completeness.md | 3 +- docs/tasks/items/raw-archive-retention.md | 5 +- docs/tasks/items/read-api-partial-bucket.md | 7 +- docs/tasks/items/read-api-points-bucket.md | 7 +- .../items/read-api-points-conditional.md | 7 +- docs/tasks/items/read-api-records.md | 7 +- docs/tasks/items/read-api-response-limit.md | 7 +- docs/tasks/items/read-api-workouts.md | 7 +- docs/tasks/items/read-api.md | 3 +- docs/tasks/items/rebuild-comparison-check.md | 5 +- docs/tasks/items/rebuild-memory-footprint.md | 5 +- .../items/release-rollback-after-migration.md | 5 +- docs/tasks/items/schema-annotations.md | 5 +- docs/tasks/items/sealed-threshold.md | 5 +- docs/tasks/items/self-description.md | 3 +- .../items/shutdown-and-migration-traces.md | 5 +- docs/tasks/items/stats-endpoint.md | 5 +- docs/tasks/items/stream-silence-alert.md | 5 +- docs/tasks/items/swagger-ui.md | 5 +- .../items/token-and-secret-management.md | 5 +- .../tasks/items/workout-identity-on-import.md | 5 +- docs/tasks/items/workout-routes-table.md | 5 +- openspec/specs/parsing/spec.md | 7 +- openspec/specs/storage/spec.md | 15 +++- 66 files changed, 322 insertions(+), 227 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index efc1355..d525c8c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,7 @@ Коллектор данных Apple Health. Принимает выгрузки из Health Auto Export и родного экспорта Apple, хранит их и отдаёт другим моим проектам — через HTTP -API и через MCP. Это **хранилище, а не аналитика**: принять, дедуплицировать, +API и, в планах, через MCP. Это **хранилище, а не аналитика**: принять, дедуплицировать, сохранить, отдать. Не переименовывать поля Apple, не интерпретировать значения. Агрегат считается только в ответе на запрос и только там, где род метрики измерен. @@ -22,7 +22,7 @@ API и через MCP. Это **хранилище, а не аналитика** Go, один статический бинарь (`CGO_ENABLED=0`). SQLite (`modernc.org/sqlite`, чистый Go), `chi`, `sqlx`, `goose` (миграции), `pelletier/go-toml/v2`, -`log/slog`, ULID через `internal/ident`. +`log/slog`, ULID (`github.com/oklog/ulid/v2`) через `internal/ident`. Module path — `git.vakhrushev.me/av/healthlog`. @@ -65,8 +65,10 @@ Module path — `git.vakhrushev.me/av/healthlog`. образ жизни» на языке телефона, а родной экспорт — коды HealthKit, и без словаря эти два источника не сойтись. - **Своей агрегации в хранении нет — есть слои.** `critical`, обратимо - пересборкой. Метрика лежит в той подробности, в какой пришла (`sample`/`raw`/`minute`/`hour`); слой выводится - из выравнивания меток, а не из заголовка HAE — тот врёт. + пересборкой. Метрика лежит в той подробности, в какой пришла + (`sample`/`raw`/`minute`/`hour`/`day`); слой выводится + из выравнивания меток, а не из заголовка HAE — тот врёт. Перечень слоёв один и + лежит в [docs/database.md](docs/database.md), таблица `bucket`. - **Агрегация в ответе — только измеренная.** `critical`, обратимо: ответ не хранится, но потребитель уже принял по нему решение. Род свёртки выводится сверкой слоёв между собой (часовое = сумма минутных → накопительная, = среднее → @@ -110,9 +112,12 @@ Module path — `git.vakhrushev.me/av/healthlog`. - **Исходы:** 0 — зелёный; ненулевой — красный, и до его починки опиниативные проходы ревью **не запускаются**. - **Что красит безусловно:** любой файл из `./data` в индексе, любой токен в - индексе, непокрытая изменённая строка, миграция без правки `docs/database.md`. - Причина одна на все: это ровно те отказы, которые не видны глазами и стоят - необратимо. + индексе, миграция без правки `docs/database.md`. Причина одна на все: это + ровно те отказы, которые не видны глазами и стоят необратимо. Покрытие + изменённых строк задумано тем же классом, но сегодня гейт от него **не + краснеет**: `scripts/diff-coverage.py` всегда возвращает `0`, и шаг печатает + `OK` при любом покрытии — разбор непокрытых строк остаётся человеку или + проходу ревью. Запись 2026-08-04 в [docs/review.md](docs/review.md). - **Чего в гейте намеренно нет и кто обязан это гонять:** `task verify:archive` (минута прогона, данные есть только на этой машине) и `task verify:busy` (около 50 секунд). Гоняет их **человек или оркестратор задачи** diff --git a/docs/.pm.json b/docs/.pm.json index 5d8b431..d8bb38b 100644 --- a/docs/.pm.json +++ b/docs/.pm.json @@ -1,4 +1,4 @@ { - "canon": 3, + "canon": 4, "migrations": "internal/store/migrations" } diff --git a/docs/adr/ADR-2026-08-03-kod-ryadom-so-strokoj-reestrom.md b/docs/adr/ADR-2026-08-03-kod-ryadom-so-strokoj-reestrom.md index e78a29a..8801d5b 100644 --- a/docs/adr/ADR-2026-08-03-kod-ryadom-so-strokoj-reestrom.md +++ b/docs/adr/ADR-2026-08-03-kod-ryadom-so-strokoj-reestrom.md @@ -1,7 +1,7 @@ # Код HealthKit кладётся реестром рядом, а не полем внутри точки -- Дата: 2026-08-03 -- Источник: openspec/changes/archive/2026-08-03-slovar-kategorialnyh-znachenij/design.md +- **Дата:** 2026-08-03 +- **Источник:** openspec/changes/archive/2026-08-03-slovar-kategorialnyh-znachenij/design.md ## Решение diff --git a/docs/adr/README.md b/docs/adr/README.md index b91b13b..8133998 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -28,10 +28,7 @@ ## Записи -Новые сверху. - -| Дата | Запись | Статус | -| --- | --- | --- | +Новые сверху. Все шесть активны — статуса поэтому ни у одной нет. - [ADR-2026-08-04-otvet-tochek-nesyot-rod-i-ego-primenimost](ADR-2026-08-04-otvet-tochek-nesyot-rod-i-ego-primenimost.md) — конверт точек несёт измеренный род, его **применимость к отданному ряду** и diff --git a/docs/architecture.md b/docs/architecture.md index f547253..1480440 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -46,12 +46,14 @@ healthlog принимает выгрузки Apple Health из приложен с переведённой строкой (см. «Категориальные значения»): он приписывается, а не подменяет. - **Своей агрегации в хранении нет — есть слои.** Метрика лежит в тех - разрезах подробности, в которых пришла (`sample`/`raw`/`minute`/`hour`); - переагрегирования при записи не происходит никогда. -- **Агрегация в ответе — только измеренная.** Read API умеет свести метрику к - запрошенной сетке, но род свёртки (сумма или среднее) выведен сверкой слоёв - между собой, а не проставлен вручную. Где род неизвестен, агрегация не - предлагается: отдаются значения как есть. + разрезах подробности, в которых пришла (перечень слоёв — + [database.md](database.md), таблица `bucket`); переагрегирования при записи + не происходит никогда. +- **Агрегация в ответе — только измеренная.** Род свёртки (сумма или среднее) + выведен сверкой слоёв между собой, а не проставлен вручную. Где род + неизвестен, агрегация не предлагается: отдаются значения как есть. Свёртка к + запрошенной сетке объявлена контрактом и **ещё не реализована** — параметр + `bucket` отвергается `400` (задача `read-api-points-bucket`). - **Минимум компонентов** — один процесс, SQLite, файлы. Без очередей и внешних зависимостей. @@ -170,7 +172,7 @@ HRV); у накопительных — только `date`. Поэтому то ``` дыра моложе суток → закроется в течение часа дыра моложе недели → закроется в течение суток -дыра старше недели → не закроется; лечится `healthlog import` +дыра старше недели → не закроется; лечится только `healthlog import` (ещё не написан) ``` Широкие проходы почти бесплатны именно из-за часовых объектов: глубокий проход @@ -225,7 +227,7 @@ capability**, и здесь стоит ссылка, а не пересказ т | `ident` | генерация и разбор ULID | — | | `archive` | сырой архив: запись тела, чтение для reindex, ретеншен | [`storage`](../openspec/specs/storage/spec.md) | | `hae` | разбор формата HAE, канонизация, хеш содержимого | [`parsing`](../openspec/specs/parsing/spec.md) | -| `ingest` | use-case приёма, общий для HTTP и CLI `import` | [`ingest`](../openspec/specs/ingest/spec.md) | +| `ingest` | use-case приёма, общий для HTTP и будущего CLI `import` | [`ingest`](../openspec/specs/ingest/spec.md) | | `fold` | свёртка одной доставки в часовые объекты | [`storage`](../openspec/specs/storage/spec.md), [`uncovered-sections`](../openspec/specs/uncovered-sections/spec.md) | | `replay` | проигрывание журнала в витрину: состав, порядок, отчёт | [`reindex`](../openspec/specs/reindex/spec.md) | | `catalog` | каталог разрезов и измерение рода агрегации | [`catalog`](../openspec/specs/catalog/spec.md) | @@ -235,6 +237,8 @@ capability**, и здесь стоит ссылка, а не пересказ т ## Приём + + ``` запрос → токен → лимит тела, gzip → проверка формы JSON → запись тела в архив → строка в delivery → 200 @@ -264,7 +268,8 @@ capability**, и здесь стоит ссылка, а не пересказ т - **200** — тело сохранено в архив. Дальше даже полный провал разбора (незнакомая метрика, новая форма точки) не меняет ответ: данные уже в безопасности, исход разбора виден в логе, в `delivery.parse_status` и в - `/stats`, а доразобрать их можно командой `reindex`. + `/stats` (маршрут — задача `stats-endpoint`), а доразобрать их можно командой + `reindex`. #### Очередь свёртки — таблица, а не структура в памяти @@ -942,8 +947,11 @@ hour метки выровнены на час heart_rate 00:00:00 #### Разрешение столкновений -По одним координатам приезжают разные содержимые: 2 897 случаев из 444 256 -координат, 0.65% (находка 49). Выигрывает **более полная** точка, и полнота — +По одним координатам приезжают разные содержимые: спорных координат 80 129 из +460 995 (17,4%), и полнота отбрасывает кого-то лишь в 981 из них (1,2%) — +остальное решает тай-брейк ([research/apple-health.md](research/apple-health.md), +находка 54; прежняя оценка «0,65%» из находки 49 считала ключ без слоя). +Выигрывает **более полная** точка, и полнота — это сравнение **множеств** ключей с непустым значением, а не их числа. Число сравнимо всегда и потому отвечает там, где ответа нет: точка @@ -981,9 +989,9 @@ hour метки выровнены на час heart_rate 00:00:00 отдельно (см. ниже). **Несравнимые множества не сливаются, а считаются.** Объединение полей — самая -дорогая часть правила — на живом потоке не потребовалось ни разу (0 из 2 897), -поэтому вместо реализации стоит счётчик и `WARN` с координатами объекта. Если -событие наступит, оно будет видно, а не додумано заранее. +дорогая часть правила — на живом корпусе наступило дважды на 155 доставок +(находка 54), поэтому вместо реализации стоит счётчик и `WARN` с координатами +объекта. Событие видно, а не додумано заранее. **Тай-брейк при равной полноте — пришедшая доставка.** Порядок канонических форм отвергнут замером: он берёт меньшее значение в 96% случаев (находка 49) и @@ -1445,18 +1453,26 @@ MongoDB, и так просилось из слова «перезаписыва ## Read API + + ``` GET /api/v1/metrics каталог: имя, units, род, слои с диапазонами GET /api/v1/metrics/{name}?from&to&layer точки метрики за период (bucket — соседняя задача, пока 400) -GET /api/v1/workouts?from&to заголовки тренировок -GET /api/v1/workouts/{id} тренировка целиком, с маршрутом -GET /api/v1/records/{kind}?from&to прочие секции -GET /api/v1/schema схемы всего, что есть в хранилище -GET /api/v1/metrics/{name}/schema схема и статистика одной метрики -GET /stats последняя доставка, счётчики, тишина по потоку GET /healthz ``` +**Целевая поверхность шире реализованной.** Маршрутов ниже в роутере ещё нет, +и запрос к ним получает `404`: + +``` +GET /api/v1/workouts?from&to заголовки тренировок → read-api-workouts +GET /api/v1/workouts/{id} тренировка целиком, с маршрутом → read-api-workouts +GET /api/v1/records/{kind}?from&to прочие секции → read-api-records +GET /api/v1/schema схемы всего, что есть в хранилище → цель self-description +GET /api/v1/metrics/{name}/schema схема и статистика одной метрики → цель self-description +GET /stats последняя доставка, счётчики, тишина → stats-endpoint +``` + Хранение пачками на контракт не влияет: `GET /metrics/{name}` собирает ответ из часовых объектов, попавших в диапазон, и отдаёт точки. Клиент про объекты не знает — это деталь хранения, а не API. @@ -1687,8 +1703,9 @@ Docker и go-kit; версионирование с конверсией у Kube ### MCP -Поверх Read API — адаптер MCP, чтобы агент подключался без промежуточного -кода. Инструментов ровно два, по числу форм запроса выше, плюс каталог. +Поверх Read API **встанет** адаптер MCP, чтобы агент подключался без +промежуточного кода — кода адаптера сегодня нет, это задача `mcp-server` цели +`read-api`. Инструментов ровно два, по числу форм запроса выше, плюс каталог. Собственной логики в адаптере нет: он переводит вызовы в те же обработчики. **Транспорт — HTTP** (Streamable HTTP), не stdio: сервис живёт на VPS, и агент @@ -1741,18 +1758,18 @@ Docker и go-kit; версионирование с конверсией у Kube ## Аутентификация -Статический токен в заголовке `Authorization: Bearer …`; список допустимых -токенов — в конфиге. HAE умеет слать произвольные заголовки, этого достаточно. - -Токены **раздельные**: на запись (приём) и на чтение. Клиент, читающий -данные, не может писать. MCP пользуется токеном чтения — отдельного контура -у него нет, см. «MCP». - -Наружу открыты два контура: приём (телефон) и чтение вместе с MCP (агенты и -приложения). Оба через Caddy с TLS, оба с разными токенами. +Периметр, модель угроз и разграничение контуров — [security.md](security.md), +разделы «Периметр» и «Что разграничивает доступ»; сегодняшний контур отличается +от целевого, и сказано это там. Здесь важно одно следствие для компоновки: MCP — +эндпоинт того же процесса и того же контура чтения, отдельного контура доступа у +него нет (см. «MCP»). ## Деплой +**Целевая** раскладка; сегодняшний контур — [security.md](security.md), +«Периметр», статус работ — [tasks/ROADMAP.md](tasks/ROADMAP.md), +«Сопровождение». + VPS **rivendell** (Timeweb), доступен всегда. Перед сервисом — **Caddy**, он терминирует TLS; сам сервис слушает plain HTTP. Приём открыт наружу на отдельном поддомене — телефон должен доставать до него из любой сети, иначе @@ -1764,7 +1781,12 @@ VPS **rivendell** (Timeweb), доступен всегда. Перед серв Тома: каталог сырого архива и файл SQLite — на постоянном хранении, конфиг (с токенами) — отдельно, `0600`. -**Откат бинаря поверх новой схемы отказывает на старте.** Версия схемы базы выше + + +**Откат бинаря поверх новой схемы отказывает на старте** — правило нормировано в +[`storage`](../openspec/specs/storage/spec.md), требование «Открытие базы +отказывает при схеме из будущего»; здесь только следствия для деплоя. Версия +схемы базы выше версии, вшитой в бинарь, — отказ, а не повод мигрировать; в контейнере это выглядит циклом перезапуска, и лечится возвратом бинаря вперёд. Версию читает сам goose (`Provider.GetVersions`), а не собственный запрос: имя таблицы учёта и diff --git a/docs/database.md b/docs/database.md index b869cad..2701f04 100644 --- a/docs/database.md +++ b/docs/database.md @@ -240,5 +240,6 @@ Data-миграции у таблицы нет и быть не может: ко | таймаут чтения запроса | 5 мин (`server.read_timeout`) | конфиг; щедро: экспорт истории по мобильной сети | | таймаут отправки ответа | 30 с (`server.write_timeout`) | конфиг; маршрут приёма держит собственный бюджет | | бюджет остановки | 30 с | `cmd/healthlog/serve.go`, `shutdownTimeout` | +| дедлайн свёртки одной доставки | 2 мин | `internal/replay/worker.go`, `foldTimeout`; обстоятельством не считается — не уложившаяся доставка уходит в `failed` | | ретеншен сырого архива | до следующего проверенного экспорта (~2 ГБ за квартал) | правило, а не число; не реализован — задача `raw-archive-retention` | | предела на одну сущность | **нет** | задача `entity-size-limits` | diff --git a/docs/research/README.md b/docs/research/README.md index 7a9eae7..5fb8fe4 100644 --- a/docs/research/README.md +++ b/docs/research/README.md @@ -51,7 +51,7 @@ python3 tmp/research/hl.py workouts тренировки, ряд ## Записи -- [apple-health.md](apple-health.md) — 53 находки на живом потоке Health Auto +- [apple-health.md](apple-health.md) — 54 находки на живом потоке Health Auto Export и на родном экспорте Apple. Записи нумерованы сквозным номером внутри файла, и **на номер ссылаются @@ -64,7 +64,7 @@ python3 tmp/research/hl.py workouts тренировки, ряд | --- | --- | | Форма точки, схемы, типы значений | 4, 21, 38, 39, 44 | | Слой и гранулярность, режимы автоматизации | 5, 6, 13, 19, 20, 23, 33, 41 | -| Идентичность, столкновения, слияние, полнота | 11, 14, 36, 47, 49 | +| Идентичность, столкновения, слияние, полнота | 11, 14, 36, 47, 49, 54 | | Досчёт задним числом и стабильность значений | 3, 10, 30, 48, 51 | | Локализация и категориальные значения | 8, 24, 37, 43 | | Секции потока и их состав | 9, 15, 16, 17, 22, 34, 50, 52 | diff --git a/docs/tasks/BACKLOG.md b/docs/tasks/BACKLOG.md index 5433186..fcf564b 100644 --- a/docs/tasks/BACKLOG.md +++ b/docs/tasks/BACKLOG.md @@ -11,43 +11,43 @@ ## Ядро -- [[idea] Человеческие аннотации поверх выведенных схем](items/schema-annotations.md) — Выведенная схема говорит форму, но не смысл метрики — нужно ли описание сверху, зависит от стабильности формата -- [Проверять целостность собранной витрины до подмены](items/integrity-before-swap.md) — Подмена файла необратима, а годность выхода подтверждена отпечатком на ещё открытом дескрипторе -- [Снизить цену слияния на широкой доставке](items/merge-cost-wide-delivery.md) — 63 МБ на одной координате держат транзакцию 5.15 с при busy_timeout 5 с — соседние доставки уходят в failed -- [Не терять сущность с id и неразобранной меткой](items/entity-without-parsed-label.md) — Тренировка с меткой в неизвестном формате пропадает целиком, а после включения ретеншена окно становится необратимым -- [Не задваивать тренировки при импорте родного экспорта](items/workout-identity-on-import.md) — В export.xml у тренировки нет id — импорт снапшота задвоит тренировки, приехавшие от HAE -- [Импортировать родной экспорт Apple Health](items/apple-export-import.md) — Слой sample пуст: полная история и точные сэмплы лежат в zip-архиве и никуда не едут -- [[idea] Месячный проход по ручным секциям](items/monthly-manual-sections-pass.md) — Симптомы и лекарства заводятся задним числом на недели — недельного глубокого прохода им не хватит -- [[idea] NDJSON-поток для больших выборок Read API](items/ndjson-stream.md) — Выборка нижнего слоя за месяц не влезает в один JSON-ответ — либо поток, либо пагинация -- [Не отбирать строки в data-миграциях по обрезаемым спискам](items/data-migration-row-selection.md) — Миграция 00007 отбирает по uncovered_sections, который обрезается на 32 — следующая покрытая секция унаследует слепую зону -- [[idea] Отказ от heartbeatSeries](items/drop-heartbeat-series.md) — 93% объёма HRV ради данных, которых нет ни в одном планируемом запросе -- [Не держать весь журнал в памяти при пересборке](items/rebuild-memory-footprint.md) — Учёт доставок и список путей архива материализуются целиком: расход растёт вместе с журналом, а у журнала конца нет -- [[idea] Пересекающиеся источники одной метрики](items/overlapping-sources.md) — Сон пишут и часы, и стороннее приложение — сумма по обоим задвоит ночь -- [[idea] Порог sealed: с какого возраста час считается запечатанным](items/sealed-threshold.md) — WARN на изменение старого часа уже пишется, но порог не выбран — ставим по факту, когда накопится статистика досчёта -- [Держать порядок журнала при конкурентных приёмах](items/journal-order-on-ingest.md) — Доставка, свёрнутая раньше своей предшественницы, уходит в failed навсегда, и живая витрина молча расходится с пересборкой -- [Ограничить размер и число заголовков доставки](items/delivery-header-limits.md) — MaxHeaderBytes не задан, в базу заголовки пишутся целиком: дефект спит до деплоя, а просыпается вместе с ним -- [Ограничить размер сущности и считать форму потоково](items/entity-size-limits.md) — Тело 40 МиБ даёт 768 МиБ пика кучи, 63 МиБ держат блокировку 5.019 с — предела на одну сущность нет вовсе -- [[idea] Разворачивание маршрутов тренировок в отдельную таблицу](items/workout-routes-table.md) — Маршрут лежит блобом внутри тренировки — понадобится, только если появится клиент, которому мало отдачи одним пакетом -- [Выводить схемы содержимого из данных](items/derived-content-schemas.md) — Метрик больше сотни и формы точек разные — клиент вынужден угадывать структуру по выборке -- [[idea] Что считать сутками при смене часового пояса](items/day-boundary-timezone.md) — Шаги за день — базовый запрос трекера и игры, но чей это день при перелёте, не решено -- [Сверять живую витрину с пересборкой](items/rebuild-comparison-check.md) — reindex печатает оба отпечатка, но сравнивать их некому — расхождение с журналом молчит -- [Помечать нижний слой устаревшим после экспорта](items/lower-layer-expiry.md) — Нижний слой растёт на ~100 тысяч координат в сутки, а после экспорта Apple он избыточен -- [[idea] Выгрузка в parquet отдельной командой](items/parquet-export.md) — Дверь для тяжёлой аналитики снаружи: DuckDB читает и parquet, и файл SQLite — спешить некуда, но и закрывать не нужно -- [Класть заголовки доставки в архив рядом с телом](items/delivery-headers-in-archive.md) — Заголовки живут только в базе — потеря базы навсегда ломает вывод слоя при пересборке -- [Измерить, нужно ли правило полноты рядом с LWW](items/last-wins-over-completeness.md) — Полнота решает 1,2% спорных координат, и неизвестно, была ли более полная точка более поздней — от этого зависит, нужна ли она вообще -- [Поднять MCP-сервер поверх Read API](items/mcp-server.md) — Агент-медик — первый заказчик проекта, а подключить его сейчас нечем -- [Написать OpenAPI-спеку руками](items/openapi-spec.md) — Потребителей три и один из них агент — контракт должен читаться машиной, а не пересказываться в чате -- [Ловить гейтом расхождение спеки с маршрутами](items/openapi-gate-check.md) — Рукописная спека — источник истины, а расходится она с кодом молча: без гейта решение о рукописной спеке не держится -- [Поднять Swagger UI без внешней сети](items/swagger-ui.md) — Контракт читается машиной, но человеку нечем выполнить запрос к живому сервису из браузера, а внешних CDN в локальной сети нет +- [✨ Проверять целостность собранной витрины до подмены](items/integrity-before-swap.md) — Подмена файла необратима, а годность выхода подтверждена отпечатком на ещё открытом дескрипторе +- [🐞 Снизить цену слияния на широкой доставке](items/merge-cost-wide-delivery.md) — 63 МБ на одной координате держат транзакцию 5.15 с при busy_timeout 5 с — соседние доставки уходят в failed +- [🐞 Не терять сущность с id и неразобранной меткой](items/entity-without-parsed-label.md) — Тренировка с меткой в неизвестном формате пропадает целиком, а после включения ретеншена окно становится необратимым +- [✨ Не задваивать тренировки при импорте родного экспорта](items/workout-identity-on-import.md) — В export.xml у тренировки нет id — импорт снапшота задвоит тренировки, приехавшие от HAE +- [✨ Импортировать родной экспорт Apple Health](items/apple-export-import.md) — Слой sample пуст: полная история и точные сэмплы лежат в zip-архиве и никуда не едут +- [🐞 Не отбирать строки в data-миграциях по обрезаемым спискам](items/data-migration-row-selection.md) — Миграция 00007 отбирает по uncovered_sections, который обрезается на 32 — следующая покрытая секция унаследует слепую зону +- [🧹 Не держать весь журнал в памяти при пересборке](items/rebuild-memory-footprint.md) — Учёт доставок и список путей архива материализуются целиком: расход растёт вместе с журналом, а у журнала конца нет +- [🐞 Держать порядок журнала при конкурентных приёмах](items/journal-order-on-ingest.md) — Доставка, свёрнутая раньше своей предшественницы, уходит в failed навсегда, и живая витрина молча расходится с пересборкой +- [✨ Ограничить размер и число заголовков доставки](items/delivery-header-limits.md) — MaxHeaderBytes не задан, в базу заголовки пишутся целиком: дефект спит до деплоя, а просыпается вместе с ним +- [✨ Ограничить размер сущности и считать форму потоково](items/entity-size-limits.md) — Тело 40 МиБ даёт 768 МиБ пика кучи, 63 МиБ держат блокировку 5.019 с — предела на одну сущность нет вовсе +- [✨ Выводить схемы содержимого из данных](items/derived-content-schemas.md) — Метрик больше сотни и формы точек разные — клиент вынужден угадывать структуру по выборке +- [✨ Сверять живую витрину с пересборкой](items/rebuild-comparison-check.md) — reindex печатает оба отпечатка, но сравнивать их некому — расхождение с журналом молчит +- [✨ Помечать нижний слой устаревшим после экспорта](items/lower-layer-expiry.md) — Нижний слой растёт на ~100 тысяч координат в сутки, а после экспорта Apple он избыточен +- [🐞 Класть заголовки доставки в архив рядом с телом](items/delivery-headers-in-archive.md) — Заголовки живут только в базе — потеря базы навсегда ломает вывод слоя при пересборке +- [✨ Поднять MCP-сервер поверх Read API](items/mcp-server.md) — Агент-медик — первый заказчик проекта, а подключить его сейчас нечем +- [✨ Написать OpenAPI-спеку руками](items/openapi-spec.md) — Потребителей три и один из них агент — контракт должен читаться машиной, а не пересказываться в чате +- [🧹 Ловить гейтом расхождение спеки с маршрутами](items/openapi-gate-check.md) — Рукописная спека — источник истины, а расходится она с кодом молча: без гейта решение о рукописной спеке не держится +- [✨ Поднять Swagger UI без внешней сети](items/swagger-ui.md) — Контракт читается машиной, но человеку нечем выполнить запрос к живому сервису из браузера, а внешних CDN в локальной сети нет +- [🔬 Измерить, нужно ли правило полноты рядом с LWW](items/last-wins-over-completeness.md) — Полнота решает 1,2% спорных координат, и неизвестно, была ли более полная точка более поздней — от этого зависит, нужна ли она вообще +- [🔬 Человеческие аннотации поверх выведенных схем](items/schema-annotations.md) — Выведенная схема говорит форму, но не смысл метрики — нужно ли описание сверху, зависит от стабильности формата +- [🔬 Месячный проход по ручным секциям](items/monthly-manual-sections-pass.md) — Симптомы и лекарства заводятся задним числом на недели — недельного глубокого прохода им не хватит +- [🔬 NDJSON-поток для больших выборок Read API](items/ndjson-stream.md) — Выборка нижнего слоя за месяц не влезает в один JSON-ответ — либо поток, либо пагинация +- [🔬 Отказ от heartbeatSeries](items/drop-heartbeat-series.md) — 93% объёма HRV ради данных, которых нет ни в одном планируемом запросе +- [🔬 Пересекающиеся источники одной метрики](items/overlapping-sources.md) — Сон пишут и часы, и стороннее приложение — сумма по обоим задвоит ночь +- [🔬 Порог sealed: с какого возраста час считается запечатанным](items/sealed-threshold.md) — WARN на изменение старого часа уже пишется, но порог не выбран — ставим по факту, когда накопится статистика досчёта +- [🔬 Разворачивание маршрутов тренировок в отдельную таблицу](items/workout-routes-table.md) — Маршрут лежит блобом внутри тренировки — понадобится, только если появится клиент, которому мало отдачи одним пакетом +- [🔬 Что считать сутками при смене часового пояса](items/day-boundary-timezone.md) — Шаги за день — базовый запрос трекера и игры, но чей это день при перелёте, не решено +- [🔬 Выгрузка в parquet отдельной командой](items/parquet-export.md) — Дверь для тяжёлой аналитики снаружи: DuckDB читает и parquet, и файл SQLite — спешить некуда, но и закрывать не нужно ## Инфра -- [Слать уведомление, когда данных нет N часов](items/stream-silence-alert.md) — Пропажу потока сейчас замечает человек, а не сервис -- [Выложить сервис на rivendell](items/deploy-rivendell.md) — Сервис живёт на рабочей машине — телефон достаёт до него только дома -- [Хранить счётчики слияния вне логов](items/merge-counters-in-db.md) — Единственный след несравнимых наборов — строка WARN в docker-логе с ротацией 3×10 МБ: событие может произойти и не оставить ничего -- [Развести бюджеты остановки и оставить следы миграции в логе](items/shutdown-and-migration-traces.md) — Долгий запрос чтения съедает бюджет остановки, и WARN обвиняет воркер свёртки; миграция молчит и не прерывается SIGTERM -- [Назвать механизм отката релиза после наката миграции](items/release-rollback-after-migration.md) — Страж версии схемы делает возврат старого бинаря отказом, а понизить схему нечем — аварийный путь пришлось бы изобретать в аварии -- [Подчищать сырой архив до последнего проверенного экспорта](items/raw-archive-retention.md) — Архив не подчищается вовсе, а резать его раньше даты проверенного экспорта нельзя — в журнале останется дыра, которую нечем пересобрать -- [Отдавать состояние сервиса маршрутом /stats](items/stats-endpoint.md) — Тихо сломавшаяся автоматизация — главный эксплуатационный риск, а сейчас факт виден только в логах -- [Свести умолчания конфига с рабочей раскладкой данных](items/config-defaults-data-dir.md) — Запуск без конфига заведёт пустую базу в корне рядом с настоящей — тихая ловушка -- [Развести токены контуров и убрать секреты из репозитория](items/token-and-secret-management.md) — Проверка токенов выключена, config.docker.toml коммитится — так нельзя выезжать наружу +- [✨ Слать уведомление, когда данных нет N часов](items/stream-silence-alert.md) — Пропажу потока сейчас замечает человек, а не сервис +- [✨ Выложить сервис на rivendell](items/deploy-rivendell.md) — Сервис живёт на рабочей машине — телефон достаёт до него только дома +- [✨ Хранить счётчики слияния вне логов](items/merge-counters-in-db.md) — Единственный след несравнимых наборов — строка WARN в docker-логе с ротацией 3×10 МБ: событие может произойти и не оставить ничего +- [🐞 Развести бюджеты остановки и оставить следы миграции в логе](items/shutdown-and-migration-traces.md) — Долгий запрос чтения съедает бюджет остановки, и WARN обвиняет воркер свёртки; миграция молчит и не прерывается SIGTERM +- [✨ Назвать механизм отката релиза после наката миграции](items/release-rollback-after-migration.md) — Страж версии схемы делает возврат старого бинаря отказом, а понизить схему нечем — аварийный путь пришлось бы изобретать в аварии +- [✨ Подчищать сырой архив до последнего проверенного экспорта](items/raw-archive-retention.md) — Архив не подчищается вовсе, а резать его раньше даты проверенного экспорта нельзя — в журнале останется дыра, которую нечем пересобрать +- [✨ Отдавать состояние сервиса маршрутом /stats](items/stats-endpoint.md) — Тихо сломавшаяся автоматизация — главный эксплуатационный риск, а сейчас факт виден только в логах +- [🐞 Свести умолчания конфига с рабочей раскладкой данных](items/config-defaults-data-dir.md) — Запуск без конфига заведёт пустую базу в корне рядом с настоящей — тихая ловушка +- [✨ Развести токены контуров и убрать секреты из репозитория](items/token-and-secret-management.md) — Проверка токенов выключена, config.docker.toml коммитится — так нельзя выезжать наружу diff --git a/docs/tasks/REJECTED.md b/docs/tasks/REJECTED.md index 18a66c5..969a0ea 100644 --- a/docs/tasks/REJECTED.md +++ b/docs/tasks/REJECTED.md @@ -8,7 +8,7 @@ - 2026-08-01 `bekap-dannyh` — Резервное копирование ./data. Причина: бекап обеспечивает готовый механизм на сервере пет-проектов — своего заводить не нужно, задача снимается деплоем. Была секция: высокий. - 2026-08-01 `identichnost-epizodnyh-metrik` — Идентичность эпизодных метрик. Причина: решён измерением и prior art: ключ эпизода — метрика+слой+start+end (находка 47), вариант А; вернулся в scope razbor-metrik-v-obekty. Была секция: блокеры. - 2026-08-01 `edinicy-metriki-v-razreze` — Единицы метрики: часть координаты или свойство объекта. Причина: измерено: на 99 доставках единицы не менялись ни у одной из 30 метрик (находка 49 → 48); реализованное правило «сохранённое побеждает + WARN + счётчик» делает событие наблюдаемым. Была секция: блокеры. -- 2026-08-04 `mcp` — [goal] MCP. Причина: поглощена целью read-api («Чтение данных клиентами»): MCP — не направление, а последний шаг того же направления; адаптер переводит вызовы в те же обработчики и собственной логики не несёт. Очередь «Read API перед MCP» стала порядком задач внутри цели. Задача mcp-server жива и перевешена на read-api. Была секция: порядок. +- 2026-08-04 `mcp` — 🎯 MCP. Причина: поглощена целью read-api («Чтение данных клиентами»): MCP — не направление, а последний шаг того же направления; адаптер переводит вызовы в те же обработчики и собственной логики не несёт. Очередь «Read API перед MCP» стала порядком задач внутри цели. Задача mcp-server жива и перевешена на read-api. Была секция: порядок. - 2026-08-04 `read-api-points` — Read API: точки, выбор слоя, свёртка по сетке. Причина: разложена на read-api-envelope-and-points (конверт, точки за период, форма провода, условный запрос), read-api-bucketing (свёртка по сетке, предел размера ответа, порог неполного ведра) и read-api-workouts-and-records (тренировки и записи наружу). Одним заходом не мерджилась: десяток критериев приёмки и три развилки в одном файле. Была секция: ядро. - 2026-08-04 `read-api-envelope-and-points` — Конверт ответа и точки за период. Причина: разложена на read-api-wire-format (форма провода, мерджится первой и трогает только живой каталог), read-api-points-period (точки за период с конвертом) и read-api-points-conditional (условный запрос со scope-etag). Была секция: ядро. - 2026-08-04 `read-api-bucketing` — Свёртка по сетке и предел размера ответа. Причина: разложена на read-api-points-bucket (свёртка по сетке), read-api-partial-bucket (порог неполного ведра и его полярность) и read-api-response-limit (предел размера ответа, общий для всех маршрутов чтения). Была секция: ядро. diff --git a/docs/tasks/ROADMAP.md b/docs/tasks/ROADMAP.md index 7b4cc7c..91279e4 100644 --- a/docs/tasks/ROADMAP.md +++ b/docs/tasks/ROADMAP.md @@ -1,9 +1,37 @@ # Роадмап Что приложение уже умеет и чего ещё не умеет. Цель — возможность приложения, -файл `[goal]` в `items/`; её задачи здесь **не перечисляются** — перечень даёт +файл типа `goal` в `items/`; её задачи здесь **не перечисляются** — перечень даёт `tasks.py list --goal <слаг>`. Очередь значима только в «Запланировано» и -обосновывается прозой рядом. +обосновывается прозой рядом. В «Сопровождении» лежит то, чем держат проект — +выкладка, инструмент, эксплуатация; граница проходит по тому, кто наблюдает: +сообщает ли о состоянии приложение своему пользователю или дежурный смотрит на +сервис снаружи. + +## Запланировано + +Очередь держится на двух зависимостях. **`healthlog import` идёт перед чисткой +нижнего слоя:** пока импорт экспорта не написан, помечать что-либо устаревшим не +на основании чего. **MCP входит в чтение, а не идёт отдельной целью:** адаптер +собственной логики не несёт, он переводит вызовы в те же обработчики, и очередь +осталась порядком задач внутри цели. + +- [🎯 Клиенты читают данные через HTTP и MCP](items/read-api.md) — Данные видны только через sqlite на хосте — ни один из трёх потребителей, включая агента-медика, ничего прочитать не может +- [🎯 Клиент узнаёт форму данных из ответа](items/self-description.md) — Метрик больше сотни и формы точек разные — клиент вынужден угадывать структуру по выборке +- [🎯 История из родного экспорта Apple лежит в хранилище](items/native-export-import.md) — Слой sample пуст: полная история и точные сэмплы лежат в zip-архиве и никуда не едут +- [🎯 Нижний слой чистится после проверенного экспорта](items/lower-layer-cleanup.md) — Нижний слой растёт на ~100 тысяч координат в сутки, а после экспорта Apple он избыточен +- [🎯 Приложение сообщает о своём состоянии](items/observability.md) — Тихо сломавшаяся автоматизация — главный эксплуатационный риск, а сейчас факт виден только в логах + +## Направления + +- [🎯 Исход слияния не зависит от порядка элементов на проводе](items/merge-robustness.md) — Правила слияния держатся на замерах, и каждый новый замер находит зависимость от порядка на проводе +- [🎯 Расхождение витрины с журналом не молчит](items/journal-and-rebuild.md) — Расхождение витрины с журналом сейчас молчит: reindex печатает оба отпечатка, а сравнивать их некому +- [🎯 У каждого входа есть названный предел](items/limits-and-load.md) — Предела на одну сущность нет вовсе: 63 МиБ держат блокировку 5.019 с, соседние доставки уходят в failed +- [🎯 Новая форма от источника не теряется молча](items/parsing-completeness.md) — Пять секций HAE поток ещё не приносил, а ручные секции заводятся задним числом — разбор полон ровно до следующей новой формы + +## Сопровождение + +- [🎯 Сервис доступен телефону из любой сети](items/deploy.md) — Сервис живёт на рабочей машине — телефон достаёт до него только дома, и оба контура открыты ## Готово @@ -24,28 +52,3 @@ [research/apple-health.md](../research/apple-health.md): правило вывода слоя, модель идентичности и формы точки проверены на живом потоке. Возможностью приложения она не была, поэтому строки среди достигнутых целей не занимает. - -## Запланировано - -Очередь держится на двух зависимостях. **`healthlog import` идёт перед чисткой -нижнего слоя:** пока импорт экспорта не написан, помечать что-либо устаревшим не -на основании чего. **MCP входит в чтение, а не идёт отдельной целью:** адаптер -собственной логики не несёт, он переводит вызовы в те же обработчики, и очередь -осталась порядком задач внутри цели. - -- [[goal] Клиенты читают данные через HTTP и MCP](items/read-api.md) — Данные видны только через sqlite на хосте — ни один из трёх потребителей, включая агента-медика, ничего прочитать не может -- [[goal] Клиент узнаёт форму данных из ответа](items/self-description.md) — Метрик больше сотни и формы точек разные — клиент вынужден угадывать структуру по выборке -- [[goal] История из родного экспорта Apple лежит в хранилище](items/native-export-import.md) — Слой sample пуст: полная история и точные сэмплы лежат в zip-архиве и никуда не едут -- [[goal] Нижний слой чистится после проверенного экспорта](items/lower-layer-cleanup.md) — Нижний слой растёт на ~100 тысяч координат в сутки, а после экспорта Apple он избыточен -- [[goal] Приложение сообщает о своём состоянии](items/observability.md) — Тихо сломавшаяся автоматизация — главный эксплуатационный риск, а сейчас факт виден только в логах - -## Направления - -- [[goal] Исход слияния не зависит от порядка элементов на проводе](items/merge-robustness.md) — Правила слияния держатся на замерах, и каждый новый замер находит зависимость от порядка на проводе -- [[goal] Расхождение витрины с журналом не молчит](items/journal-and-rebuild.md) — Расхождение витрины с журналом сейчас молчит: reindex печатает оба отпечатка, а сравнивать их некому -- [[goal] У каждого входа есть названный предел](items/limits-and-load.md) — Предела на одну сущность нет вовсе: 63 МиБ держат блокировку 5.019 с, соседние доставки уходят в failed -- [[goal] Новая форма от источника не теряется молча](items/parsing-completeness.md) — Пять секций HAE поток ещё не приносил, а ручные секции заводятся задним числом — разбор полон ровно до следующей новой формы - -## Разработка - -- [[goal] Сервис доступен телефону из любой сети](items/deploy.md) — Сервис живёт на рабочей машине — телефон достаёт до него только дома, и оба контура открыты diff --git a/docs/tasks/SPRINT.md b/docs/tasks/SPRINT.md index d0c3d34..3e343a0 100644 --- a/docs/tasks/SPRINT.md +++ b/docs/tasks/SPRINT.md @@ -1,6 +1,6 @@ # Спринт -- **Цель:** [[goal] Клиенты читают данные через HTTP и MCP](items/read-api.md) +- **Цель:** [🎯 Клиенты читают данные через HTTP и MCP](items/read-api.md) - **Начат:** 2026-08-04 - **Спринт:** `2026-08-04` @@ -8,9 +8,9 @@ ## Набор -- [Отвечать 304 на повторный запрос точек](items/read-api-points-conditional.md) — Агент опрашивает по расписанию, а каждый повтор стоит полного чтения: на каталоге это 693 мс и +153 МиБ -- [Сворачивать точки по заданной сетке](items/read-api-points-bucket.md) — «Шаги за неделю по дням» — базовый запрос трекера и игры, и сегодня его нечем задать -- [Отличать неполное ведро от полного](items/read-api-partial-bucket.md) — Текущий час неполон всегда, и без порога свёртка отдаёт его наравне с полными — клиент видит провал вместо неизвестности -- [Ограничить размер ответа маршрутов чтения](items/read-api-response-limit.md) — У маршрутов чтения нет ни одного потолка: множители «метрики × окно × точки × одновременные запросы» ничем не ограничены -- [Отдавать тренировки вместе с маршрутом](items/read-api-workouts.md) — Тренировки с маршрутами разобраны и лежат в витрине, а маршрутов чтения нет — сценарий трекера не закрыт -- [Отдавать записи со своим id за период](items/read-api-records.md) — stateOfMind разобран и хранится, но наружу не отдаётся — а восстановить его нечем: в экспорте Apple его нет +- [✨ Отвечать 304 на повторный запрос точек](items/read-api-points-conditional.md) — Агент опрашивает по расписанию, а каждый повтор стоит полного чтения: на каталоге это 693 мс и +153 МиБ +- [✨ Сворачивать точки по заданной сетке](items/read-api-points-bucket.md) — «Шаги за неделю по дням» — базовый запрос трекера и игры, и сегодня его нечем задать +- [✨ Отличать неполное ведро от полного](items/read-api-partial-bucket.md) — Текущий час неполон всегда, и без порога свёртка отдаёт его наравне с полными — клиент видит провал вместо неизвестности +- [✨ Ограничить размер ответа маршрутов чтения](items/read-api-response-limit.md) — У маршрутов чтения нет ни одного потолка: множители «метрики × окно × точки × одновременные запросы» ничем не ограничены +- [✨ Отдавать тренировки вместе с маршрутом](items/read-api-workouts.md) — Тренировки с маршрутами разобраны и лежат в витрине, а маршрутов чтения нет — сценарий трекера не закрыт +- [✨ Отдавать записи со своим id за период](items/read-api-records.md) — stateOfMind разобран и хранится, но наружу не отдаётся — а восстановить его нечем: в экспорте Apple его нет diff --git a/docs/tasks/items/apple-export-import.md b/docs/tasks/items/apple-export-import.md index 317c0b8..b9c50d9 100644 --- a/docs/tasks/items/apple-export-import.md +++ b/docs/tasks/items/apple-export-import.md @@ -1,6 +1,7 @@ -# Импортировать родной экспорт Apple Health +# ✨ Импортировать родной экспорт Apple Health -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** Слой sample пуст: полная история и точные сэмплы лежат в zip-архиве и никуда не едут - **Теги:** goal:native-export-import diff --git a/docs/tasks/items/config-defaults-data-dir.md b/docs/tasks/items/config-defaults-data-dir.md index 65f3525..f911e63 100644 --- a/docs/tasks/items/config-defaults-data-dir.md +++ b/docs/tasks/items/config-defaults-data-dir.md @@ -1,6 +1,7 @@ -# Свести умолчания конфига с рабочей раскладкой данных +# 🐞 Свести умолчания конфига с рабочей раскладкой данных -- **Секция:** Инфра +- **Тип:** fix +- **Категория:** Инфра - **Зачем:** Запуск без конфига заведёт пустую базу в корне рядом с настоящей — тихая ловушка - **Теги:** goal:deploy diff --git a/docs/tasks/items/data-migration-row-selection.md b/docs/tasks/items/data-migration-row-selection.md index d54a7e3..b91e54e 100644 --- a/docs/tasks/items/data-migration-row-selection.md +++ b/docs/tasks/items/data-migration-row-selection.md @@ -1,6 +1,7 @@ -# Не отбирать строки в data-миграциях по обрезаемым спискам +# 🐞 Не отбирать строки в data-миграциях по обрезаемым спискам -- **Секция:** Ядро +- **Тип:** fix +- **Категория:** Ядро - **Зачем:** Миграция 00007 отбирает по uncovered_sections, который обрезается на 32 — следующая покрытая секция унаследует слепую зону - **Теги:** goal:journal-and-rebuild diff --git a/docs/tasks/items/day-boundary-timezone.md b/docs/tasks/items/day-boundary-timezone.md index 360db55..9ba713d 100644 --- a/docs/tasks/items/day-boundary-timezone.md +++ b/docs/tasks/items/day-boundary-timezone.md @@ -1,6 +1,7 @@ -# [idea] Что считать сутками при смене часового пояса +# 🔬 Что считать сутками при смене часового пояса -- **Секция:** Ядро +- **Тип:** research +- **Категория:** Ядро - **Зачем:** Шаги за день — базовый запрос трекера и игры, но чей это день при перелёте, не решено - **Теги:** goal:read-api diff --git a/docs/tasks/items/delivery-header-limits.md b/docs/tasks/items/delivery-header-limits.md index 9bc04d3..1e1143b 100644 --- a/docs/tasks/items/delivery-header-limits.md +++ b/docs/tasks/items/delivery-header-limits.md @@ -1,6 +1,7 @@ -# Ограничить размер и число заголовков доставки +# ✨ Ограничить размер и число заголовков доставки -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** MaxHeaderBytes не задан, в базу заголовки пишутся целиком: дефект спит до деплоя, а просыпается вместе с ним - **Теги:** goal:limits-and-load diff --git a/docs/tasks/items/delivery-headers-in-archive.md b/docs/tasks/items/delivery-headers-in-archive.md index 48c1d96..9ed5385 100644 --- a/docs/tasks/items/delivery-headers-in-archive.md +++ b/docs/tasks/items/delivery-headers-in-archive.md @@ -1,6 +1,7 @@ -# Класть заголовки доставки в архив рядом с телом +# 🐞 Класть заголовки доставки в архив рядом с телом -- **Секция:** Ядро +- **Тип:** fix +- **Категория:** Ядро - **Зачем:** Заголовки живут только в базе — потеря базы навсегда ломает вывод слоя при пересборке - **Теги:** goal:journal-and-rebuild diff --git a/docs/tasks/items/deploy-rivendell.md b/docs/tasks/items/deploy-rivendell.md index adfe970..5dd6813 100644 --- a/docs/tasks/items/deploy-rivendell.md +++ b/docs/tasks/items/deploy-rivendell.md @@ -1,6 +1,7 @@ -# Выложить сервис на rivendell +# ✨ Выложить сервис на rivendell -- **Секция:** Инфра +- **Тип:** feature +- **Категория:** Инфра - **Зачем:** Сервис живёт на рабочей машине — телефон достаёт до него только дома - **Теги:** goal:deploy diff --git a/docs/tasks/items/deploy.md b/docs/tasks/items/deploy.md index 09cf702..80ffafb 100644 --- a/docs/tasks/items/deploy.md +++ b/docs/tasks/items/deploy.md @@ -1,6 +1,7 @@ -# [goal] Сервис доступен телефону из любой сети +# 🎯 Сервис доступен телефону из любой сети -- **Секция:** Разработка +- **Тип:** goal +- **Секция:** Сопровождение - **Зачем:** Сервис живёт на рабочей машине — телефон достаёт до него только дома, и оба контура открыты - **Теги:** decomposed diff --git a/docs/tasks/items/derived-content-schemas.md b/docs/tasks/items/derived-content-schemas.md index 6eea5aa..0cc69d4 100644 --- a/docs/tasks/items/derived-content-schemas.md +++ b/docs/tasks/items/derived-content-schemas.md @@ -1,6 +1,7 @@ -# Выводить схемы содержимого из данных +# ✨ Выводить схемы содержимого из данных -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** Метрик больше сотни и формы точек разные — клиент вынужден угадывать структуру по выборке - **Теги:** goal:self-description diff --git a/docs/tasks/items/drop-heartbeat-series.md b/docs/tasks/items/drop-heartbeat-series.md index cc2b593..70bf434 100644 --- a/docs/tasks/items/drop-heartbeat-series.md +++ b/docs/tasks/items/drop-heartbeat-series.md @@ -1,6 +1,7 @@ -# [idea] Отказ от heartbeatSeries +# 🔬 Отказ от heartbeatSeries -- **Секция:** Ядро +- **Тип:** research +- **Категория:** Ядро - **Зачем:** 93% объёма HRV ради данных, которых нет ни в одном планируемом запросе - **Теги:** goal:lower-layer-cleanup diff --git a/docs/tasks/items/entity-size-limits.md b/docs/tasks/items/entity-size-limits.md index 4824a7b..c242898 100644 --- a/docs/tasks/items/entity-size-limits.md +++ b/docs/tasks/items/entity-size-limits.md @@ -1,6 +1,7 @@ -# Ограничить размер сущности и считать форму потоково +# ✨ Ограничить размер сущности и считать форму потоково -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** Тело 40 МиБ даёт 768 МиБ пика кучи, 63 МиБ держат блокировку 5.019 с — предела на одну сущность нет вовсе - **Теги:** goal:limits-and-load diff --git a/docs/tasks/items/entity-without-parsed-label.md b/docs/tasks/items/entity-without-parsed-label.md index b1318c7..a91a582 100644 --- a/docs/tasks/items/entity-without-parsed-label.md +++ b/docs/tasks/items/entity-without-parsed-label.md @@ -1,6 +1,7 @@ -# Не терять сущность с id и неразобранной меткой +# 🐞 Не терять сущность с id и неразобранной меткой -- **Секция:** Ядро +- **Тип:** fix +- **Категория:** Ядро - **Зачем:** Тренировка с меткой в неизвестном формате пропадает целиком, а после включения ретеншена окно становится необратимым - **Теги:** goal:parsing-completeness diff --git a/docs/tasks/items/integrity-before-swap.md b/docs/tasks/items/integrity-before-swap.md index bf72cc3..4361788 100644 --- a/docs/tasks/items/integrity-before-swap.md +++ b/docs/tasks/items/integrity-before-swap.md @@ -1,6 +1,7 @@ -# Проверять целостность собранной витрины до подмены +# ✨ Проверять целостность собранной витрины до подмены -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** Подмена файла необратима, а годность выхода подтверждена отпечатком на ещё открытом дескрипторе - **Теги:** goal:journal-and-rebuild diff --git a/docs/tasks/items/journal-and-rebuild.md b/docs/tasks/items/journal-and-rebuild.md index 4922324..5c1b329 100644 --- a/docs/tasks/items/journal-and-rebuild.md +++ b/docs/tasks/items/journal-and-rebuild.md @@ -1,5 +1,6 @@ -# [goal] Расхождение витрины с журналом не молчит +# 🎯 Расхождение витрины с журналом не молчит +- **Тип:** goal - **Секция:** Направления - **Зачем:** Расхождение витрины с журналом сейчас молчит: reindex печатает оба отпечатка, а сравнивать их некому - **Теги:** decomposed diff --git a/docs/tasks/items/journal-order-on-ingest.md b/docs/tasks/items/journal-order-on-ingest.md index 3f29e4a..85d5c72 100644 --- a/docs/tasks/items/journal-order-on-ingest.md +++ b/docs/tasks/items/journal-order-on-ingest.md @@ -1,6 +1,7 @@ -# Держать порядок журнала при конкурентных приёмах +# 🐞 Держать порядок журнала при конкурентных приёмах -- **Секция:** Ядро +- **Тип:** fix +- **Категория:** Ядро - **Зачем:** Доставка, свёрнутая раньше своей предшественницы, уходит в failed навсегда, и живая витрина молча расходится с пересборкой - **Теги:** goal:journal-and-rebuild, question diff --git a/docs/tasks/items/last-wins-over-completeness.md b/docs/tasks/items/last-wins-over-completeness.md index 764db16..81e11bb 100644 --- a/docs/tasks/items/last-wins-over-completeness.md +++ b/docs/tasks/items/last-wins-over-completeness.md @@ -1,6 +1,7 @@ -# Измерить, нужно ли правило полноты рядом с LWW +# 🔬 Измерить, нужно ли правило полноты рядом с LWW -- **Секция:** Ядро +- **Тип:** research +- **Категория:** Ядро - **Зачем:** Полнота решает 1,2% спорных координат, и неизвестно, была ли более полная точка более поздней — от этого зависит, нужна ли она вообще - **Теги:** goal:merge-robustness, sprint:2026-08-03 diff --git a/docs/tasks/items/limits-and-load.md b/docs/tasks/items/limits-and-load.md index 8036e2d..07c70ba 100644 --- a/docs/tasks/items/limits-and-load.md +++ b/docs/tasks/items/limits-and-load.md @@ -1,5 +1,6 @@ -# [goal] У каждого входа есть названный предел +# 🎯 У каждого входа есть названный предел +- **Тип:** goal - **Секция:** Направления - **Зачем:** Предела на одну сущность нет вовсе: 63 МиБ держат блокировку 5.019 с, соседние доставки уходят в failed - **Теги:** decomposed diff --git a/docs/tasks/items/lower-layer-cleanup.md b/docs/tasks/items/lower-layer-cleanup.md index 8b83781..2588315 100644 --- a/docs/tasks/items/lower-layer-cleanup.md +++ b/docs/tasks/items/lower-layer-cleanup.md @@ -1,5 +1,6 @@ -# [goal] Нижний слой чистится после проверенного экспорта +# 🎯 Нижний слой чистится после проверенного экспорта +- **Тип:** goal - **Секция:** Запланировано - **Зачем:** Нижний слой растёт на ~100 тысяч координат в сутки, а после экспорта Apple он избыточен - **Теги:** decomposed diff --git a/docs/tasks/items/lower-layer-expiry.md b/docs/tasks/items/lower-layer-expiry.md index e914454..7a82eed 100644 --- a/docs/tasks/items/lower-layer-expiry.md +++ b/docs/tasks/items/lower-layer-expiry.md @@ -1,6 +1,7 @@ -# Помечать нижний слой устаревшим после экспорта +# ✨ Помечать нижний слой устаревшим после экспорта -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** Нижний слой растёт на ~100 тысяч координат в сутки, а после экспорта Apple он избыточен - **Теги:** goal:lower-layer-cleanup diff --git a/docs/tasks/items/mcp-server.md b/docs/tasks/items/mcp-server.md index d2ff93c..906fa63 100644 --- a/docs/tasks/items/mcp-server.md +++ b/docs/tasks/items/mcp-server.md @@ -1,6 +1,7 @@ -# Поднять MCP-сервер поверх Read API +# ✨ Поднять MCP-сервер поверх Read API -- **Секция:** Ядро — набор ограничен HTTP-слоем чтения после дробления; адаптер берётся следующим спринтом по той же цели +- **Тип:** feature +- **Категория:** Ядро — набор ограничен HTTP-слоем чтения после дробления; адаптер берётся следующим спринтом по той же цели - **Зачем:** Агент-медик — первый заказчик проекта, а подключить его сейчас нечем - **Теги:** goal:read-api diff --git a/docs/tasks/items/merge-cost-wide-delivery.md b/docs/tasks/items/merge-cost-wide-delivery.md index 12be732..458ece8 100644 --- a/docs/tasks/items/merge-cost-wide-delivery.md +++ b/docs/tasks/items/merge-cost-wide-delivery.md @@ -1,6 +1,7 @@ -# Снизить цену слияния на широкой доставке +# 🐞 Снизить цену слияния на широкой доставке -- **Секция:** Ядро +- **Тип:** fix +- **Категория:** Ядро - **Зачем:** 63 МБ на одной координате держат транзакцию 5.15 с при busy_timeout 5 с — соседние доставки уходят в failed - **Теги:** goal:limits-and-load diff --git a/docs/tasks/items/merge-counters-in-db.md b/docs/tasks/items/merge-counters-in-db.md index c9a9ad2..260643d 100644 --- a/docs/tasks/items/merge-counters-in-db.md +++ b/docs/tasks/items/merge-counters-in-db.md @@ -1,6 +1,7 @@ -# Хранить счётчики слияния вне логов +# ✨ Хранить счётчики слияния вне логов -- **Секция:** Инфра +- **Тип:** feature +- **Категория:** Инфра - **Зачем:** Единственный след несравнимых наборов — строка WARN в docker-логе с ротацией 3×10 МБ: событие может произойти и не оставить ничего - **Теги:** goal:observability diff --git a/docs/tasks/items/merge-robustness.md b/docs/tasks/items/merge-robustness.md index 18c812a..38e77d9 100644 --- a/docs/tasks/items/merge-robustness.md +++ b/docs/tasks/items/merge-robustness.md @@ -1,5 +1,6 @@ -# [goal] Исход слияния не зависит от порядка элементов на проводе +# 🎯 Исход слияния не зависит от порядка элементов на проводе +- **Тип:** goal - **Секция:** Направления - **Зачем:** Правила слияния держатся на замерах, и каждый новый замер находит зависимость от порядка на проводе - **Теги:** decomposed diff --git a/docs/tasks/items/monthly-manual-sections-pass.md b/docs/tasks/items/monthly-manual-sections-pass.md index 5d248f2..e9dac1e 100644 --- a/docs/tasks/items/monthly-manual-sections-pass.md +++ b/docs/tasks/items/monthly-manual-sections-pass.md @@ -1,6 +1,7 @@ -# [idea] Месячный проход по ручным секциям +# 🔬 Месячный проход по ручным секциям -- **Секция:** Ядро +- **Тип:** research +- **Категория:** Ядро - **Зачем:** Симптомы и лекарства заводятся задним числом на недели — недельного глубокого прохода им не хватит - **Теги:** goal:parsing-completeness diff --git a/docs/tasks/items/native-export-import.md b/docs/tasks/items/native-export-import.md index ab67648..6607cff 100644 --- a/docs/tasks/items/native-export-import.md +++ b/docs/tasks/items/native-export-import.md @@ -1,5 +1,6 @@ -# [goal] История из родного экспорта Apple лежит в хранилище +# 🎯 История из родного экспорта Apple лежит в хранилище +- **Тип:** goal - **Секция:** Запланировано - **Зачем:** Слой sample пуст: полная история и точные сэмплы лежат в zip-архиве и никуда не едут - **Теги:** decomposed diff --git a/docs/tasks/items/ndjson-stream.md b/docs/tasks/items/ndjson-stream.md index 2c9f51b..6382eaa 100644 --- a/docs/tasks/items/ndjson-stream.md +++ b/docs/tasks/items/ndjson-stream.md @@ -1,6 +1,7 @@ -# [idea] NDJSON-поток для больших выборок Read API +# 🔬 NDJSON-поток для больших выборок Read API -- **Секция:** Ядро +- **Тип:** research +- **Категория:** Ядро - **Зачем:** Выборка нижнего слоя за месяц не влезает в один JSON-ответ — либо поток, либо пагинация - **Теги:** goal:read-api diff --git a/docs/tasks/items/observability.md b/docs/tasks/items/observability.md index 0d50ece..7a573ca 100644 --- a/docs/tasks/items/observability.md +++ b/docs/tasks/items/observability.md @@ -1,5 +1,6 @@ -# [goal] Приложение сообщает о своём состоянии +# 🎯 Приложение сообщает о своём состоянии +- **Тип:** goal - **Секция:** Запланировано - **Зачем:** Тихо сломавшаяся автоматизация — главный эксплуатационный риск, а сейчас факт виден только в логах - **Теги:** decomposed diff --git a/docs/tasks/items/openapi-gate-check.md b/docs/tasks/items/openapi-gate-check.md index 49f1784..de5a63c 100644 --- a/docs/tasks/items/openapi-gate-check.md +++ b/docs/tasks/items/openapi-gate-check.md @@ -1,6 +1,7 @@ -# Ловить гейтом расхождение спеки с маршрутами +# 🧹 Ловить гейтом расхождение спеки с маршрутами -- **Секция:** Ядро +- **Тип:** chore +- **Категория:** Ядро - **Зачем:** Рукописная спека — источник истины, а расходится она с кодом молча: без гейта решение о рукописной спеке не держится - **Теги:** goal:read-api, sprint:2026-08-04 diff --git a/docs/tasks/items/openapi-spec.md b/docs/tasks/items/openapi-spec.md index 067e639..6b1c9cc 100644 --- a/docs/tasks/items/openapi-spec.md +++ b/docs/tasks/items/openapi-spec.md @@ -1,6 +1,7 @@ -# Написать OpenAPI-спеку руками +# ✨ Написать OpenAPI-спеку руками -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** Потребителей три и один из них агент — контракт должен читаться машиной, а не пересказываться в чате - **Теги:** goal:read-api, sprint:2026-08-04 diff --git a/docs/tasks/items/overlapping-sources.md b/docs/tasks/items/overlapping-sources.md index e90d0f3..6f4460d 100644 --- a/docs/tasks/items/overlapping-sources.md +++ b/docs/tasks/items/overlapping-sources.md @@ -1,6 +1,7 @@ -# [idea] Пересекающиеся источники одной метрики +# 🔬 Пересекающиеся источники одной метрики -- **Секция:** Ядро +- **Тип:** research +- **Категория:** Ядро - **Зачем:** Сон пишут и часы, и стороннее приложение — сумма по обоим задвоит ночь - **Теги:** goal:read-api diff --git a/docs/tasks/items/parquet-export.md b/docs/tasks/items/parquet-export.md index e6ed94a..958d08e 100644 --- a/docs/tasks/items/parquet-export.md +++ b/docs/tasks/items/parquet-export.md @@ -1,6 +1,7 @@ -# [idea] Выгрузка в parquet отдельной командой +# 🔬 Выгрузка в parquet отдельной командой -- **Секция:** Ядро +- **Тип:** research +- **Категория:** Ядро - **Зачем:** Дверь для тяжёлой аналитики снаружи: DuckDB читает и parquet, и файл SQLite — спешить некуда, но и закрывать не нужно - **Теги:** goal:read-api diff --git a/docs/tasks/items/parsing-completeness.md b/docs/tasks/items/parsing-completeness.md index 5f3be36..8d3e58b 100644 --- a/docs/tasks/items/parsing-completeness.md +++ b/docs/tasks/items/parsing-completeness.md @@ -1,5 +1,6 @@ -# [goal] Новая форма от источника не теряется молча +# 🎯 Новая форма от источника не теряется молча +- **Тип:** goal - **Секция:** Направления - **Зачем:** Пять секций HAE поток ещё не приносил, а ручные секции заводятся задним числом — разбор полон ровно до следующей новой формы - **Теги:** decomposed diff --git a/docs/tasks/items/raw-archive-retention.md b/docs/tasks/items/raw-archive-retention.md index a3707f7..1a0117e 100644 --- a/docs/tasks/items/raw-archive-retention.md +++ b/docs/tasks/items/raw-archive-retention.md @@ -1,6 +1,7 @@ -# Подчищать сырой архив до последнего проверенного экспорта +# ✨ Подчищать сырой архив до последнего проверенного экспорта -- **Секция:** Инфра +- **Тип:** feature +- **Категория:** Инфра - **Зачем:** Архив не подчищается вовсе, а резать его раньше даты проверенного экспорта нельзя — в журнале останется дыра, которую нечем пересобрать - **Теги:** goal:journal-and-rebuild diff --git a/docs/tasks/items/read-api-partial-bucket.md b/docs/tasks/items/read-api-partial-bucket.md index 0c7df6d..41d4471 100644 --- a/docs/tasks/items/read-api-partial-bucket.md +++ b/docs/tasks/items/read-api-partial-bucket.md @@ -1,8 +1,9 @@ -# Отличать неполное ведро от полного +# ✨ Отличать неполное ведро от полного -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** Текущий час неполон всегда, и без порога свёртка отдаёт его наравне с полными — клиент видит провал вместо неизвестности -- **Теги:** goal:read-api, sprint:2026-08-04, kind:feature +- **Теги:** goal:read-api, sprint:2026-08-04 Ведро, в котором известна не вся сетка, отличимо от полного — а полярность порога названа вслух, а не выводится читателем из умолчания. diff --git a/docs/tasks/items/read-api-points-bucket.md b/docs/tasks/items/read-api-points-bucket.md index 11e2a34..149f39e 100644 --- a/docs/tasks/items/read-api-points-bucket.md +++ b/docs/tasks/items/read-api-points-bucket.md @@ -1,8 +1,9 @@ -# Сворачивать точки по заданной сетке +# ✨ Сворачивать точки по заданной сетке -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** «Шаги за неделю по дням» — базовый запрос трекера и игры, и сегодня его нечем задать -- **Теги:** goal:read-api, sprint:2026-08-04, kind:feature +- **Теги:** goal:read-api, sprint:2026-08-04 «Шаги за неделю по дням» отвечаются одним запросом `?from&to&bucket`, и род свёртки берётся измеренным, а не угаданным. diff --git a/docs/tasks/items/read-api-points-conditional.md b/docs/tasks/items/read-api-points-conditional.md index 3c38872..b771583 100644 --- a/docs/tasks/items/read-api-points-conditional.md +++ b/docs/tasks/items/read-api-points-conditional.md @@ -1,8 +1,9 @@ -# Отвечать 304 на повторный запрос точек +# ✨ Отвечать 304 на повторный запрос точек -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** Агент опрашивает по расписанию, а каждый повтор стоит полного чтения: на каталоге это 693 мс и +153 МиБ -- **Теги:** goal:read-api, sprint:2026-08-04, kind:feature +- **Теги:** goal:read-api, sprint:2026-08-04 Повторный опрос точек с той же меткой стоит `304` вместо полного чтения, и метка не может ответить на другой набор данных. diff --git a/docs/tasks/items/read-api-records.md b/docs/tasks/items/read-api-records.md index 2413bdf..8e735a2 100644 --- a/docs/tasks/items/read-api-records.md +++ b/docs/tasks/items/read-api-records.md @@ -1,8 +1,9 @@ -# Отдавать записи со своим id за период +# ✨ Отдавать записи со своим id за период -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** stateOfMind разобран и хранится, но наружу не отдаётся — а восстановить его нечем: в экспорте Apple его нет -- **Теги:** goal:read-api, sprint:2026-08-04, kind:feature +- **Теги:** goal:read-api, sprint:2026-08-04 Записи со своим `id` — сегодня это `stateOfMind` — достаются за период через `GET /records/{kind}`. diff --git a/docs/tasks/items/read-api-response-limit.md b/docs/tasks/items/read-api-response-limit.md index f039bae..de1fe80 100644 --- a/docs/tasks/items/read-api-response-limit.md +++ b/docs/tasks/items/read-api-response-limit.md @@ -1,8 +1,9 @@ -# Ограничить размер ответа маршрутов чтения +# ✨ Ограничить размер ответа маршрутов чтения -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** У маршрутов чтения нет ни одного потолка: множители «метрики × окно × точки × одновременные запросы» ничем не ограничены -- **Теги:** goal:read-api, sprint:2026-08-04, kind:feature +- **Теги:** goal:read-api, sprint:2026-08-04 У маршрутов чтения появляется названный потолок: сетка не задана и ответ не влезает — сервер огрубляет её и **называет** в ответе; сетка задана явно и не diff --git a/docs/tasks/items/read-api-workouts.md b/docs/tasks/items/read-api-workouts.md index b901dc2..dc8f608 100644 --- a/docs/tasks/items/read-api-workouts.md +++ b/docs/tasks/items/read-api-workouts.md @@ -1,8 +1,9 @@ -# Отдавать тренировки вместе с маршрутом +# ✨ Отдавать тренировки вместе с маршрутом -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** Тренировки с маршрутами разобраны и лежат в витрине, а маршрутов чтения нет — сценарий трекера не закрыт -- **Теги:** goal:read-api, sprint:2026-08-04, kind:feature +- **Теги:** goal:read-api, sprint:2026-08-04 Трекер забирает тренировку одним пакетом вместе с маршрутом: `GET /workouts` за период и `GET /workouts/{id}` поштучно. diff --git a/docs/tasks/items/read-api.md b/docs/tasks/items/read-api.md index 6a6e832..0f44e94 100644 --- a/docs/tasks/items/read-api.md +++ b/docs/tasks/items/read-api.md @@ -1,5 +1,6 @@ -# [goal] Клиенты читают данные через HTTP и MCP +# 🎯 Клиенты читают данные через HTTP и MCP +- **Тип:** goal - **Секция:** Запланировано - **Зачем:** Данные видны только через sqlite на хосте — ни один из трёх потребителей, включая агента-медика, ничего прочитать не может - **Теги:** decomposed diff --git a/docs/tasks/items/rebuild-comparison-check.md b/docs/tasks/items/rebuild-comparison-check.md index eac2acb..163fab8 100644 --- a/docs/tasks/items/rebuild-comparison-check.md +++ b/docs/tasks/items/rebuild-comparison-check.md @@ -1,6 +1,7 @@ -# Сверять живую витрину с пересборкой +# ✨ Сверять живую витрину с пересборкой -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** reindex печатает оба отпечатка, но сравнивать их некому — расхождение с журналом молчит - **Теги:** goal:journal-and-rebuild diff --git a/docs/tasks/items/rebuild-memory-footprint.md b/docs/tasks/items/rebuild-memory-footprint.md index 6882246..5dda071 100644 --- a/docs/tasks/items/rebuild-memory-footprint.md +++ b/docs/tasks/items/rebuild-memory-footprint.md @@ -1,6 +1,7 @@ -# Не держать весь журнал в памяти при пересборке +# 🧹 Не держать весь журнал в памяти при пересборке -- **Секция:** Ядро +- **Тип:** chore +- **Категория:** Ядро - **Зачем:** Учёт доставок и список путей архива материализуются целиком: расход растёт вместе с журналом, а у журнала конца нет - **Теги:** goal:journal-and-rebuild diff --git a/docs/tasks/items/release-rollback-after-migration.md b/docs/tasks/items/release-rollback-after-migration.md index d949d17..e6c0587 100644 --- a/docs/tasks/items/release-rollback-after-migration.md +++ b/docs/tasks/items/release-rollback-after-migration.md @@ -1,6 +1,7 @@ -# Назвать механизм отката релиза после наката миграции +# ✨ Назвать механизм отката релиза после наката миграции -- **Секция:** Инфра +- **Тип:** feature +- **Категория:** Инфра - **Зачем:** Страж версии схемы делает возврат старого бинаря отказом, а понизить схему нечем — аварийный путь пришлось бы изобретать в аварии - **Теги:** goal:deploy diff --git a/docs/tasks/items/schema-annotations.md b/docs/tasks/items/schema-annotations.md index 036bb9c..fbf8391 100644 --- a/docs/tasks/items/schema-annotations.md +++ b/docs/tasks/items/schema-annotations.md @@ -1,6 +1,7 @@ -# [idea] Человеческие аннотации поверх выведенных схем +# 🔬 Человеческие аннотации поверх выведенных схем -- **Секция:** Ядро +- **Тип:** research +- **Категория:** Ядро - **Зачем:** Выведенная схема говорит форму, но не смысл метрики — нужно ли описание сверху, зависит от стабильности формата - **Теги:** goal:self-description diff --git a/docs/tasks/items/sealed-threshold.md b/docs/tasks/items/sealed-threshold.md index 904052f..eb68254 100644 --- a/docs/tasks/items/sealed-threshold.md +++ b/docs/tasks/items/sealed-threshold.md @@ -1,6 +1,7 @@ -# [idea] Порог sealed: с какого возраста час считается запечатанным +# 🔬 Порог sealed: с какого возраста час считается запечатанным -- **Секция:** Ядро +- **Тип:** research +- **Категория:** Ядро - **Зачем:** WARN на изменение старого часа уже пишется, но порог не выбран — ставим по факту, когда накопится статистика досчёта - **Теги:** goal:merge-robustness diff --git a/docs/tasks/items/self-description.md b/docs/tasks/items/self-description.md index 809b81f..3360600 100644 --- a/docs/tasks/items/self-description.md +++ b/docs/tasks/items/self-description.md @@ -1,5 +1,6 @@ -# [goal] Клиент узнаёт форму данных из ответа +# 🎯 Клиент узнаёт форму данных из ответа +- **Тип:** goal - **Секция:** Запланировано - **Зачем:** Метрик больше сотни и формы точек разные — клиент вынужден угадывать структуру по выборке - **Теги:** decomposed diff --git a/docs/tasks/items/shutdown-and-migration-traces.md b/docs/tasks/items/shutdown-and-migration-traces.md index f1b2452..3fac179 100644 --- a/docs/tasks/items/shutdown-and-migration-traces.md +++ b/docs/tasks/items/shutdown-and-migration-traces.md @@ -1,6 +1,7 @@ -# Развести бюджеты остановки и оставить следы миграции в логе +# 🐞 Развести бюджеты остановки и оставить следы миграции в логе -- **Секция:** Инфра +- **Тип:** fix +- **Категория:** Инфра - **Зачем:** Долгий запрос чтения съедает бюджет остановки, и WARN обвиняет воркер свёртки; миграция молчит и не прерывается SIGTERM - **Теги:** goal:deploy diff --git a/docs/tasks/items/stats-endpoint.md b/docs/tasks/items/stats-endpoint.md index 16b4bcb..d67bbd0 100644 --- a/docs/tasks/items/stats-endpoint.md +++ b/docs/tasks/items/stats-endpoint.md @@ -1,6 +1,7 @@ -# Отдавать состояние сервиса маршрутом /stats +# ✨ Отдавать состояние сервиса маршрутом /stats -- **Секция:** Инфра +- **Тип:** feature +- **Категория:** Инфра - **Зачем:** Тихо сломавшаяся автоматизация — главный эксплуатационный риск, а сейчас факт виден только в логах - **Теги:** goal:observability diff --git a/docs/tasks/items/stream-silence-alert.md b/docs/tasks/items/stream-silence-alert.md index bf36276..1f1eec9 100644 --- a/docs/tasks/items/stream-silence-alert.md +++ b/docs/tasks/items/stream-silence-alert.md @@ -1,6 +1,7 @@ -# Слать уведомление, когда данных нет N часов +# ✨ Слать уведомление, когда данных нет N часов -- **Секция:** Инфра +- **Тип:** feature +- **Категория:** Инфра - **Зачем:** Пропажу потока сейчас замечает человек, а не сервис - **Теги:** goal:observability diff --git a/docs/tasks/items/swagger-ui.md b/docs/tasks/items/swagger-ui.md index bb25823..cd36e67 100644 --- a/docs/tasks/items/swagger-ui.md +++ b/docs/tasks/items/swagger-ui.md @@ -1,6 +1,7 @@ -# Поднять Swagger UI без внешней сети +# ✨ Поднять Swagger UI без внешней сети -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** Контракт читается машиной, но человеку нечем выполнить запрос к живому сервису из браузера, а внешних CDN в локальной сети нет - **Теги:** goal:read-api, sprint:2026-08-04 diff --git a/docs/tasks/items/token-and-secret-management.md b/docs/tasks/items/token-and-secret-management.md index 21dfa9b..dd821dc 100644 --- a/docs/tasks/items/token-and-secret-management.md +++ b/docs/tasks/items/token-and-secret-management.md @@ -1,6 +1,7 @@ -# Развести токены контуров и убрать секреты из репозитория +# ✨ Развести токены контуров и убрать секреты из репозитория -- **Секция:** Инфра +- **Тип:** feature +- **Категория:** Инфра - **Зачем:** Проверка токенов выключена, config.docker.toml коммитится — так нельзя выезжать наружу - **Теги:** goal:deploy diff --git a/docs/tasks/items/workout-identity-on-import.md b/docs/tasks/items/workout-identity-on-import.md index c3ffe72..5a1bcaa 100644 --- a/docs/tasks/items/workout-identity-on-import.md +++ b/docs/tasks/items/workout-identity-on-import.md @@ -1,6 +1,7 @@ -# Не задваивать тренировки при импорте родного экспорта +# ✨ Не задваивать тренировки при импорте родного экспорта -- **Секция:** Ядро +- **Тип:** feature +- **Категория:** Ядро - **Зачем:** В export.xml у тренировки нет id — импорт снапшота задвоит тренировки, приехавшие от HAE - **Теги:** goal:native-export-import diff --git a/docs/tasks/items/workout-routes-table.md b/docs/tasks/items/workout-routes-table.md index c8cc92e..9bc3e11 100644 --- a/docs/tasks/items/workout-routes-table.md +++ b/docs/tasks/items/workout-routes-table.md @@ -1,6 +1,7 @@ -# [idea] Разворачивание маршрутов тренировок в отдельную таблицу +# 🔬 Разворачивание маршрутов тренировок в отдельную таблицу -- **Секция:** Ядро +- **Тип:** research +- **Категория:** Ядро - **Зачем:** Маршрут лежит блобом внутри тренировки — понадобится, только если появится клиент, которому мало отдачи одним пакетом - **Теги:** goal:read-api diff --git a/openspec/specs/parsing/spec.md b/openspec/specs/parsing/spec.md index a118d3b..a732ec6 100644 --- a/openspec/specs/parsing/spec.md +++ b/openspec/specs/parsing/spec.md @@ -1,7 +1,12 @@ # parsing Specification ## Purpose -TBD - created by archiving change razbor-metrik-v-obekty. Update Purpose after archive. + +Разбор тела Health Auto Export: какие секции покрыты, как читается точка и её +метка времени, как выводится слой доставки и что происходит с непонятым. +Источник истины о формате — не документация HAE, а живые пакеты +(`docs/research/apple-health.md`). + ## Requirements ### Requirement: Разбор секции метрик diff --git a/openspec/specs/storage/spec.md b/openspec/specs/storage/spec.md index 0257c8a..70f15f5 100644 --- a/openspec/specs/storage/spec.md +++ b/openspec/specs/storage/spec.md @@ -1,7 +1,13 @@ # storage Specification ## Purpose -TBD - created by archiving change razbor-metrik-v-obekty. Update Purpose after archive. + +Витрина: как принятые точки и сущности ложатся, адресуются и выбираются между +версиями. Здесь живут идентичность точки по координатам, полнота и тай-брейк, +часовой объект, хранение сущностей с собственным `id`, отпечаток витрины и учёт +разобранности доставки. Правило одно на всё: содержимое хранится дословно, а +состояние остаётся свёрткой журнала в его порядке. + ## Requirements ### Requirement: Идентичность точки по координатам @@ -410,8 +416,11 @@ HTML-экранирования: `&`, `<` и `>` внутри точки обя построчный разбор логов. То же относится к идентификатору сущности: он приходит из чужого тела и ограничен по длине при разборе. -Частичный разбор уровня записи не повышает: `partial` — установившееся состояние -половины потока (53 доставки из 118), и постоянный `WARN` обесценил бы уровень. +Частичный разбор уровня записи не повышает: `partial` — установившееся +состояние, а не сигнал. До покрытия `workouts` и `stateOfMind` частичной +числилась половина потока (53 доставки из 118); после покрытия непокрытая +секция живым потоком не приносилась ни разу на том же корпусе. Постоянный +`WARN` обесценил бы уровень. Повышает уровень другое, и оснований два: - срабатывание границ списка: тело с сотнями секций или с именем длиннее предела