Files
healthlog/openspec/changes/archive/2026-08-04-tie-break-equal-completeness/proposal.md
T
av b278501a6e store: при равной полноте точек побеждает пришедшая доставка
- байтовый порядок канонических форм остался тай-брейком только внутри одной
  доставки: на живом корпусе он решал 98,8% спорных координат и системно хранил
  меньшее значение, из-за чего step_count терял род и verify:archive был красным
- правило перестало быть коммутативным осознанно, поэтому порядок свёртки
  приведён к журнальному: проход воркера прекращается на отложенной доставке,
  а свёртка вне порядка журнала пишет WARN
- заведены счётчики PointsHeld и PointsErased — удержание полнотой и
  единственное направление, в котором правило теряет содержание
2026-08-04 11:16:24 +03:00

74 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Why
Тай-брейк при равной полноте точек сегодня — порядок канонических форм, и он
решает **98,8% спорных координат** (83 956 из 84 978 при 453 171 координате):
полнота отвечает лишь в 1,2% случаев. Лексикографический порядок системно берёт
меньшее значение (1 847 из 1 912, находка 49), то есть хранит **устаревшую**
версию точки там, где HAE досчитывает задним числом (находка 10).
У дефекта появился независимый оракул: `task verify:archive` покраснел на
`master` без единого коммита, с ростом корпуса. Час `2026-08-03T07:00Z` метрики
`step_count` получил четыре доставки с двумя различными значениями; победило
меньшее, оно же приехавшее первым, и сверка слоёв объявила метрику мгновенной
против 23 согласных часов. `step_count` ушёл в `unknown` — Read API больше не
имеет права суммировать шаги.
Решение владельца от 2026-08-04: **при равной полноте побеждает пришедшая
точка**, а не сохранённая. Значение при этом не интерпретируется — правило
остаётся структурным.
## What Changes
- **BREAKING (содержимое витрины):** при равной полноте побеждает точка,
пришедшая **этой** доставкой, а не сохранённая. Отпечаток витрины обязан
измениться; состояние восстанавливается пересборкой из архива.
- Порядок канонических форм остаётся тай-брейком **внутри одной доставки**, где
провенанс общий и различать нечем.
- **Правило слияния точек перестаёт быть функцией множества и становится явной
функцией порядка журнала.** Отсюда — плата, которую изменение обязано
внести целиком: порядок свёртки приводится к порядку журнала.
- Фоновый воркер **прекращает проход на первой отложенной доставке**, а не
перешагивает её. Иначе занятость базы переставляет доставки местами, и живая
витрина расходится с пересборкой молча.
- Свёртка доставки, у которой в журнале уже есть свёрнутая преемница, пишет
`WARN`: остаточное окно (конкурентный приём делает строку учёта видимой
не в порядке меток) закрыть без изменения приёма нельзя, но молчать о нём
нельзя тем более.
- Заводится счётчик «пришедшая точка проиграла сохранённой» — отдельно от
общего `MergeStats.Overwrites`, который считает столкновения в обе стороны и
различить их не даёт.
- Схема не трогается: колонок провенанса на точку не заводится, формат
`payload` не меняется, миграции нет.
## Capabilities
### New Capabilities
Новых нет.
### Modified Capabilities
- `storage`: тай-брейк при равной полноте точек — пришедшая побеждает
сохранённую; порядок канонических форм остаётся только внутри доставки.
Требование «победитель есть функция множества точек» заменяется на «функция
множества точек и позиции в журнале». Новый счётчик удержаний.
- `ingest`: проход воркера прекращается на первой отложенной доставке; свёртка
вне порядка журнала становится наблюдаемой.
- `reindex`: посылка сходимости («приём шёл последовательно») из оговорки
сценария становится названным условием требования — теперь от порядка свёртки
зависит не только вывод слоя, но и содержимое точек.
## Impact
- `internal/store/bucket.go``mergePoints`, `resolve`, `candidate`,
`pointLess`, `MergeStats`.
- `internal/store/delivery.go` — запрос «есть ли свёрнутая доставка позже этой».
- `internal/replay/worker.go` — барьер на отложенной доставке, `WARN` о свёртке
вне порядка журнала.
- `internal/fold/fold.go` — проброс нового счётчика в итог свёртки и в лог.
- Витрина в `./data`: отпечаток изменится. Пересборка — действие человека при
остановленном сервисе, этим изменением не выполняется.
- Оракулы: `task verify:archive` (ноль противоречащих часов, `step_count`
снова накопительная), тест сходимости «живой приём = пересборка» с отложенной
доставкой.