tasks: заведена задача о замене правила полноты на last wins
- первый шаг — замер: в 1 022 координатах, где полноту решило превосходство полей, была ли более полная точка более поздней; от исхода ветвится всё - «экспорт — источник правды» ограничено двумя рамками: по дате снапшота и по типам, которых в экспорте нет вовсе (stateOfMind)
This commit is contained in:
@@ -37,6 +37,7 @@
|
||||
- [Устаревание нижнего слоя после экспорта](items/lower-layer-expiry.md) — Нижний слой растёт на ~100 тысяч координат в сутки, а после экспорта Apple он избыточен
|
||||
- [[idea] Выгрузка в parquet отдельной командой](items/parquet-export.md) — Дверь для тяжёлой аналитики снаружи: DuckDB читает и parquet, и файл SQLite — спешить некуда, но и закрывать не нужно
|
||||
- [Заголовки доставки в архиве рядом с телом](items/delivery-headers-in-archive.md) — Заголовки живут только в базе — потеря базы навсегда ломает вывод слоя при пересборке
|
||||
- [Правило полноты против last wins](items/last-wins-over-completeness.md) — Полнота решает 1,2% спорных координат, и неизвестно, была ли более полная точка более поздней — от этого зависит, нужна ли она вообще
|
||||
|
||||
## инфра
|
||||
- [Активный алерт «данных нет N часов»](items/stream-silence-alert.md) — Пропажу потока сейчас замечает человек, а не сервис
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
# Правило полноты против last wins
|
||||
|
||||
- **Секция:** ядро
|
||||
- **Зачем:** Полнота решает 1,2% спорных координат, и неизвестно, была ли более полная точка более поздней — от этого зависит, нужна ли она вообще
|
||||
- **Теги:** goal:merge-robustness, sprint:2026-08-03
|
||||
|
||||
Правило слияния перестаёт зависеть от того, чей набор полей богаче, — либо
|
||||
зависит ровно там, где замер показал, что без этого теряются данные. Сегодня
|
||||
неизвестно, какой из двух случаев верен.
|
||||
|
||||
## Откуда задача
|
||||
|
||||
Владелец предложил 2026-08-04 держаться стратегии **last wins**: экспорт Apple
|
||||
Health — база снапшота, новые доставки HAE затирают предыдущие. Это отменяет
|
||||
`critical`-инвариант `CLAUDE.md` «при столкновении выигрывает более полная
|
||||
точка, а не последняя», и потому меняется не молча, а этой задачей.
|
||||
|
||||
Соседняя задача [tie-break-equal-completeness](tie-break-equal-completeness.md)
|
||||
двигает то же правило в ту же сторону, но осторожнее: она меняет только
|
||||
тай-брейк при **равной** полноте, оставляя саму полноту первичной. Эта задача
|
||||
решает, надо ли снимать и её.
|
||||
|
||||
## Замер — первый шаг, и от него ветвится всё остальное
|
||||
|
||||
Из 84 978 спорных координат живого архива (замер 2026-08-04, `tmp/diag`):
|
||||
|
||||
| | координат |
|
||||
| --- | --- |
|
||||
| решено полнотой | 1 022 (1,2%) |
|
||||
| упало на тай-брейк | 83 956 (98,8%) |
|
||||
|
||||
Вопрос ровно один: **в этих 1 022 случаях более полная точка была более поздней
|
||||
или более ранней?**
|
||||
|
||||
- **Всегда более поздней** — полнота ничего не решает сверх порядка, `last wins`
|
||||
строго проще и ничего не теряет. Ветка полноты удаляется, инвариант в
|
||||
`CLAUDE.md` переписывается.
|
||||
- **Иногда более ранней** — значит HAE присылает обеднённые версии задним
|
||||
числом, и `last wins` будет молча стирать поля. Тогда полнота остаётся, а
|
||||
граница её применения записывается числом: сколько таких случаев, у каких
|
||||
метрик, какие поля пропадали.
|
||||
|
||||
Замер обязан различать **точки метрик** и **сущности** (`workouts`,
|
||||
`stateOfMind`): у сущностей отношение другое — покрытие, код другой
|
||||
(`internal/store/winner.go`), и он не мерялся вовсе.
|
||||
|
||||
Оба исхода — законный результат задачи. Исход «полнота нужна» не считается
|
||||
провалом и не отменяет предложение владельца: он его уточняет границей.
|
||||
|
||||
## Две рамки, без которых «экспорт — источник правды» ломает работающее
|
||||
|
||||
Обе выведены при постановке и в замере не нуждаются:
|
||||
|
||||
1. **По времени.** Экспорт — снапшот на дату выгрузки; доставки HAE после этой
|
||||
даты обязаны его перекрывать, иначе новые данные не доедут. Совместимо с
|
||||
инвариантом «хранилище — свёртка по журналу»: `import(экспорт) +
|
||||
replay(доставки по received_at)`.
|
||||
2. **По типам.** `stateOfMind` в экспорте Apple отсутствует ни одним типом
|
||||
(измерено, `docs/research/apple-health.md`). Для него единственный источник —
|
||||
доставки HAE, и объявить экспорт источником правды для него нельзя.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- для 1 022 координат, где полнота решила исход, названо число: в скольких из
|
||||
них более полная точка была более поздней — оракул: прогон замера на живом
|
||||
архиве, число воспроизводится вторым прогоном
|
||||
- тот же вопрос отвечён отдельно для сущностей (`workouts`, `stateOfMind`) —
|
||||
оракул: тот же прогон, отдельная колонка
|
||||
- правило слияния приведено к исходу замера, и `CLAUDE.md` говорит то же, что
|
||||
делает код — оракул: глазами, сверка формулировки инварианта с реализацией
|
||||
- повторный прогон живого архива даёт тот же отпечаток, живая свёртка равна
|
||||
пересборке — оракул: `task verify:archive` дважды подряд
|
||||
- ни одна метрика не потеряла род из-за изменения правила — оракул:
|
||||
`task verify:archive`, ноль противоречащих часов
|
||||
|
||||
## Рамки
|
||||
|
||||
Схему не трогаем. Отпечаток витрины изменится — пересборка обязательна и
|
||||
делается человеком при остановленном сервисе; подмена файла базы необратима и в
|
||||
задаче не выполняется. Берётся **после**
|
||||
[tie-break-equal-completeness](tie-break-equal-completeness.md): та меняет то же
|
||||
место, и замер до её вливания ответит на вопрос про уже неактуальное правило.
|
||||
|
||||
Связано: находки 10, 47, 49, 53; `docs/architecture.md` → «Разрешение
|
||||
столкновений»; `docs/review.md`, запись 2026-08-04.
|
||||
Reference in New Issue
Block a user