store: при равной полноте точек побеждает пришедшая доставка
- байтовый порядок канонических форм остался тай-брейком только внутри одной доставки: на живом корпусе он решал 98,8% спорных координат и системно хранил меньшее значение, из-за чего step_count терял род и verify:archive был красным - правило перестало быть коммутативным осознанно, поэтому порядок свёртки приведён к журнальному: проход воркера прекращается на отложенной доставке, а свёртка вне порядка журнала пишет WARN - заведены счётчики PointsHeld и PointsErased — удержание полнотой и единственное направление, в котором правило теряет содержание
This commit is contained in:
@@ -120,19 +120,76 @@
|
||||
порядке журнала — `(received_at, id)`, как он определён capability пересборки.
|
||||
Распараллеливать свёртку MUST NOT.
|
||||
|
||||
Порядок здесь — не удобство отладки, а условие правильности содержимого:
|
||||
тай-брейк при равной полноте точек разрешается в пользу пришедшей доставки, то
|
||||
есть исход слияния есть функция порядка свёртки. Свёрнутая не в порядке журнала
|
||||
доставка возвращает координату к версии, которую источник уже пересчитал, и
|
||||
живая витрина расходится с тем, что даёт пересборка.
|
||||
|
||||
Проход воркера SHALL прекращаться на первой доставке, чей исход свёртки
|
||||
**классифицирован как отложенный** (занятость базы, отмена снаружи), а не
|
||||
перешагивать её. Перешагнув, проход свернул бы её преемниц раньше неё.
|
||||
|
||||
Предикат остановки — именно класс исхода, а не статус доставки. Доставка,
|
||||
оставшаяся `pending` из-за отказа записи самого исхода, курсор двигает и проход
|
||||
не останавливает: иначе проход выбирал бы её бесконечно, свёртка встала бы
|
||||
целиком, а приём продолжал бы отвечать `200`.
|
||||
|
||||
Очередь при этом не встаёт: собственный дедлайн свёртки обстоятельством
|
||||
MUST NOT считаться — доставка, не уложившаяся в бюджет, получает `failed` и
|
||||
очередь освобождает, а занятость базы блокирует запись всем одинаково. Проход
|
||||
возобновляется сигналом приёма или периодическим пробуждением.
|
||||
|
||||
Стоящая голова очереди молчать MUST NOT: у неё нет верхнего предела ожидания, и
|
||||
снаружи она неотличима от здорового потока, потому что приём продолжает отвечать
|
||||
`200`. Метка отставания (см. ниже) SHALL писаться и на выходе прохода по
|
||||
барьеру, а не только на пустой выборке: занятая голова очереди до пустой
|
||||
выборки не пропускает проход НИКОГДА, и метка, привязанная к ней, молчала бы
|
||||
ровно в том состоянии, ради которого заведена. Флаг «первый проход завершён»
|
||||
при этом взводиться MUST NOT — проход до конца очереди не дошёл.
|
||||
|
||||
Достижимая гарантия называется точно: в порядке `(received_at, id)`
|
||||
сворачиваются все доставки, **видимые воркеру** на момент выборки. Доставка,
|
||||
ставшая видимой позже курсора прохода, подбирается следующим проходом;
|
||||
абсолютного порядка при конкурентных приёмах система не обещает.
|
||||
абсолютного порядка при конкурентных приёмах система не обещает, потому что
|
||||
строка учёта становится видимой только после записи тела (измерено 184 мс на
|
||||
62 МиБ).
|
||||
|
||||
Последствие этого предела называется вслух: доставка без плотных метрик,
|
||||
свёрнутая раньше своей предшественницы, слоя не выведет и получит `failed` — то
|
||||
есть её точки в витрину не попадут до пересборки. Живое состояние в этом случае
|
||||
расходится с тем, что даёт `healthlog reindex`. Окно узкое (обе доставки должны
|
||||
приниматься одновременно, и только у автоматизации без плотных метрик), и
|
||||
изменение его сужает, а не открывает: прежде свёртка шла в порядке завершения
|
||||
обработчиков. Устранение предела — отдельный вопрос, оно требует удерживать
|
||||
порядок на самом приёме.
|
||||
Остаточное окно молчать MUST NOT. Перед свёрткой воркер SHALL спрашивать
|
||||
журнал, есть ли доставка **позже** этой по `(received_at, id)`, уже записавшая
|
||||
исход разбора в витрину — то есть в статусе `parsed` или `partial`. Есть —
|
||||
пишется одна запись `WARN` на доставку, с её идентификатором, ожиданием в
|
||||
секундах и без значений точек.
|
||||
|
||||
Спрашивать журнал система SHALL **до** свёртки, а писать запись — **после** и
|
||||
только если свёртка состоялась: после свёртки предикат уже видит саму эту
|
||||
доставку разобранной, а отложенная доставка витрину не трогала, и запись о
|
||||
свёртке вне порядка утверждала бы событие, которого не было, — да ещё
|
||||
повторялась бы каждым проходом, пока голова очереди занята. Это единственное наблюдение, по которому расхождение живой витрины с
|
||||
пересборкой вообще обнаружимо до сверки отпечатков; лечится оно
|
||||
`healthlog reindex`.
|
||||
|
||||
Статус `failed` в предикат входить MUST NOT: такая доставка в витрину ничего не
|
||||
записала, и перестановка относительно неё содержимого не разводит. А
|
||||
`failed` — штатный исход (невыводимый слой), и его учёт превратил бы `WARN` в
|
||||
шум, на который перестают смотреть.
|
||||
|
||||
Пересборка эту проверку выполнять MUST NOT: она идёт в порядке журнала по
|
||||
построению, и её тишина здесь содержательна.
|
||||
|
||||
Проверка эта — **страж окна, а не постоянная часть свёртки**: закрыв порядок на
|
||||
самом приёме, её SHALL снять вместе с окном. Сказано здесь потому, что иначе
|
||||
страж переживёт стерегомое и станет тем, что следующий читатель удалит без
|
||||
объяснения.
|
||||
|
||||
Последствие предела называется вслух: доставка без плотных метрик, свёрнутая
|
||||
раньше своей предшественницы, слоя не выведет и получит `failed` — то есть её
|
||||
точки в витрину не попадут до пересборки. Та же перестановка при равной полноте
|
||||
точек оставляет в витрине версию не той доставки, что стоит в журнале последней.
|
||||
Окно узкое (обе доставки должны приниматься одновременно), и изменение его
|
||||
сужает, а не открывает: прежде проход перешагивал отложенную доставку.
|
||||
Устранение предела — отдельный вопрос, оно требует удерживать порядок на самом
|
||||
приёме.
|
||||
|
||||
Воркер SHALL продвигаться по неразобранным доставкам строго возрастающим
|
||||
курсором в пределах одного прохода. Курсор обязателен для завершимости:
|
||||
@@ -159,6 +216,21 @@
|
||||
- **AND** доставка без плотных метрик наследует слой предшествующей ей по этому
|
||||
порядку доставки той же автоматизации, а не соседа по времени вставки
|
||||
|
||||
#### Scenario: Отложенная доставка держит очередь
|
||||
|
||||
- **GIVEN** в очереди несколько доставок, и свёртка первой из них отложена
|
||||
занятостью базы
|
||||
- **WHEN** воркер делает проход
|
||||
- **THEN** её преемницы в этом проходе не сворачиваются
|
||||
- **AND** следующий проход снова начинает с отложенной доставки
|
||||
|
||||
#### Scenario: Свёртка вне порядка журнала не молчит
|
||||
|
||||
- **GIVEN** доставка стала видимой после того, как её преемница по журналу уже
|
||||
вышла из очереди
|
||||
- **WHEN** воркер сворачивает её
|
||||
- **THEN** система пишет `WARN` с идентификатором доставки и без значений точек
|
||||
|
||||
#### Scenario: Доставка, не записавшая исход, не зацикливает проход
|
||||
|
||||
- **WHEN** свёртка доставки не смогла записать исход разбора и оставила её
|
||||
@@ -170,7 +242,6 @@
|
||||
- **GIVEN** доставка осталась `pending`, и новых доставок не приезжает
|
||||
- **WHEN** наступает очередное периодическое пробуждение
|
||||
- **THEN** воркер пробует свернуть её снова
|
||||
|
||||
### Requirement: Подбор неразобранного при старте — та же операция
|
||||
|
||||
При старте система SHALL сворачивать доставки, числящиеся неразобранными, тем
|
||||
@@ -320,4 +391,3 @@ NOT: факт уходит в `DEBUG`, приём продолжается.
|
||||
- **THEN** отказ логируется на уровне `ERROR` вместе с путём тела в архиве
|
||||
- **AND** запись отличает занятость базы от прочих причин отказа
|
||||
- **AND** тот же отказ по другой причине этого признака не несёт
|
||||
|
||||
|
||||
@@ -26,6 +26,48 @@
|
||||
разбора разошёлся бы с первым молча, а другой предел означал бы, что тело,
|
||||
принятое со `200`, вечно отказывает на каждой пересборке.
|
||||
|
||||
**Условие сходимости SHALL называться требованием, а не оговоркой сценария.**
|
||||
Правило слияния точек разрешает равную полноту в пользу пришедшей доставки, то
|
||||
есть содержимое витрины есть функция **порядка** свёртки, а не только множества
|
||||
доставок. Отпечаток пересборки равен отпечатку накопленной приёмом витрины
|
||||
тогда и только тогда, когда живая свёртка шла в порядке журнала. Прежде от
|
||||
порядка зависел только вывод слоя; теперь от него зависят значения точек, то
|
||||
есть цена нарушения выросла и должна быть названа здесь, а не выведена
|
||||
читателем.
|
||||
|
||||
**Посылка равенства SHALL перечисляться рядом с ним**, а не подразумеваться,
|
||||
потому что оракул, чья посылка не названа, краснеет по причине, к правилу
|
||||
отношения не имеющей, и краснота становится неотличимой от дефекта. Посылок
|
||||
три: живая свёртка шла в порядке журнала; в журнале нет доставок, чью свёртку
|
||||
живой путь провалил, а пересборка проведёт (статус `failed`); за время
|
||||
пересборки новых доставок не приезжало.
|
||||
|
||||
Из этих трёх посылок прогон пересборки SHALL печатать рядом с отпечатком ту,
|
||||
которую он измеряет сам, — число `failed`. Первая посылка прогону
|
||||
**недоступна по построению**: записи «свёртка вне порядка журнала» рождаются
|
||||
только в живом воркере, нигде не хранятся, и пересборке та же спека проверку
|
||||
прямо запрещает. Она проверяется журналом сервиса, и это сказано здесь, чтобы
|
||||
следующий автор не приписал к отпечатку константный ноль, выдав его за
|
||||
подтверждение.
|
||||
|
||||
Равенство «пересборка = приём» SHALL проверяться оракулом, а не рассуждением:
|
||||
журнал, содержащий доставки с равнополными столкновениями, проигранный живым
|
||||
путём (фоновый воркер, в том числе с доставкой, отложенной занятостью базы) и
|
||||
путём пересборки, обязан давать один отпечаток витрины.
|
||||
|
||||
**Место снапшота в порядке журнала этой дельтой не определяется, и это сказано
|
||||
вслух.** Под правилом «побеждает пришедшая» исход столкновения снапшота Apple с
|
||||
точкой HAE зависит от того, какую позицию журнала получит доставка импорта:
|
||||
учтённая сегодняшним временем, она перебила бы все равнополные точки, включая
|
||||
досчитанные задним числом. Стадия снапшота сегодня пуста, поэтому вопрос
|
||||
отложен — но решать его SHALL задача импорта, и явно, а не выбором первого
|
||||
автора.
|
||||
|
||||
Отчёт пересборки SHALL называть два числа про точки — удержанные правилом
|
||||
полноты против пришедшей доставки и потерявшие содержание в пользу пришедшей —
|
||||
рядом с удержанными версиями сущностей и по той же причине: сходимость
|
||||
отпечатка ни одно из них не проверяет по построению.
|
||||
|
||||
#### Scenario: Пересобранная витрина совпадает с накопленной приёмом
|
||||
|
||||
- **GIVEN** рабочая витрина накоплена тем же разбором, приём во время
|
||||
@@ -34,6 +76,14 @@
|
||||
- **WHEN** журнал проигрывается заново с пустой витрины
|
||||
- **THEN** отпечаток пересобранной витрины совпадает с отпечатком накопленной
|
||||
|
||||
#### Scenario: Отложенная занятостью доставка не разводит приём и пересборку
|
||||
|
||||
- **GIVEN** журнал, где одни координаты получают равнополные точки с разными
|
||||
значениями от разных доставок
|
||||
- **AND** свёртка одной из доставок откладывается занятостью базы
|
||||
- **WHEN** тот же журнал проигрывается живым путём и путём пересборки
|
||||
- **THEN** отпечатки витрин совпадают
|
||||
|
||||
#### Scenario: Повторная пересборка ничего не меняет
|
||||
|
||||
- **WHEN** пересборка того же журнала выполняется второй раз
|
||||
@@ -43,7 +93,6 @@
|
||||
|
||||
- **WHEN** в журнале есть доставки со статусом `parsed` и со статусом `pending`
|
||||
- **THEN** проигрываются обе
|
||||
|
||||
### Requirement: Порядок проигрывания задаётся журналом
|
||||
|
||||
Система SHALL проигрывать доставки строго в порядке `(received_at, id)`, а не
|
||||
|
||||
+218
-44
@@ -79,6 +79,14 @@ TBD - created by archiving change razbor-metrik-v-obekty. Update Purpose after a
|
||||
полноты, — 1 916 несут равные наборы и разные значения, где исход решает
|
||||
тай-брейк, а несравнимых наборов ноль.
|
||||
|
||||
Перемер 2026-08-04 на выросшем корпусе (155 доставок, 460 995 координат,
|
||||
настоящий ключ со слоем) уточнил соотношение и сделал тай-брейк главным
|
||||
разрядом правила, а не крайним: спорных координат 80 129, полнота отбрасывает
|
||||
кого-то в 981 из них (1,2%), остальные 79 148 (98,8%) уходят в тай-брейк.
|
||||
Смена тай-брейка меняет исход на 75 494 координатах, из них 71 773 —
|
||||
`basal_energy_burned` слоя `raw`, то есть посекундная развёртка HAE, которую
|
||||
Read API суммировать и так не имеет права.
|
||||
|
||||
Пустым значением MUST считаться `null`, пустая строка, число, равное нулю (в
|
||||
любой записи), пустой объект и пустой массив: поле без содержания не делает
|
||||
точку полнее точки, где этого поля нет вовсе. Пустота MUST определяться по
|
||||
@@ -111,37 +119,92 @@ TBD - created by archiving change razbor-metrik-v-obekty. Update Purpose after a
|
||||
витрины по жребию. Несравнимость на этом разряде исходом MUST NOT быть:
|
||||
лишние ключи там заведомо пусты, объединять в них нечего.
|
||||
|
||||
Если равны и эти множества, а значения различаются, исход MUST быть
|
||||
детерминированным и не зависеть от порядка, в котором доставки дошли до
|
||||
хранилища: свёртка по журналу обязана давать то же состояние, что приём в
|
||||
реальном времени.
|
||||
**Если полнота ответа не дала — равные множества с разными значениями либо
|
||||
несравнимые множества, — победителем SHALL быть точка, пришедшая разбираемой
|
||||
доставкой, а не лежавшая в объекте.** Байтовый порядок канонических форм на
|
||||
этом разряде отвергнут замером: он берёт меньшее значение в 1 847 случаях из
|
||||
1 912 (находка 49), то есть системно хранит версию, которую источник уже
|
||||
пересчитал. Ценой этого выбора час `2026-08-03T07:00Z` метрики `step_count`
|
||||
остался с недосчитанным значением, сверка слоёв объявила метрику мгновенной
|
||||
против 23 согласных часов, и род ушёл в `unknown` — Read API потерял право
|
||||
суммировать шаги.
|
||||
|
||||
Победитель MUST быть функцией **множества** точек координаты, а не порядка их
|
||||
поступления. Попарная свёртка этого не даёт: полнота — частичный порядок,
|
||||
тай-брейк — тотальный, и вместе они образуют нетранзитивное отношение победы
|
||||
(A превосходит B по полноте, B бьёт C тай-брейком, C бьёт A тай-брейком).
|
||||
При таком цикле повторная свёртка одной и той же доставки меняет содержимое
|
||||
объекта, и витрина перестаёт быть функцией журнала. Поэтому система SHALL
|
||||
отбросить кандидатов, превзойдённых по полноте кем-то другим, и выбрать
|
||||
победителя среди оставшихся по тотальному порядку — обе операции зависят
|
||||
только от состава множества.
|
||||
Значение точки в правило входить MUST NOT: «брать бо́льшее» верно для
|
||||
накопительных метрик и неверно для мгновенных, которые источник досчитывает
|
||||
вниз. Род метрики в правило входить MUST NOT тоже — род есть функция витрины, а
|
||||
правило слияния, читающее собственную выдачу, перестаёт быть функцией префикса
|
||||
журнала.
|
||||
|
||||
Сравнение по `received_at` для этого не годится: у сохранённой точки нет
|
||||
провенанса — ни времени приёма, ни идентификатора доставки, — и сравнивать
|
||||
не с чем. Детерминизм обеспечивается свойством самих значений (например,
|
||||
порядком канонических форм), а не порядком событий.
|
||||
**Столкновение ВНУТРИ одной доставки SHALL разрешаться порядком канонических
|
||||
форм**: провенанс у таких точек общий, различать их нечем, а порядок элементов
|
||||
в JSON-массиве от HAE нестабилен. Точка, канонически совпавшая с сохранённой,
|
||||
SHALL считаться пришедшей — иначе сохранённый кандидат проигрывал бы соседу по
|
||||
доставке, которого обязан был обойти по байтам, и «внутри доставки решают
|
||||
байты» нарушалось бы ровно тогда, когда доставка ничего не изменила. Побеждает
|
||||
при этом сохранённое содержимое дословно: хеш не двигается, объект не
|
||||
переписывается. Тем же правилом схлопываются две точки одного тела с совпавшей
|
||||
канонической формой — в витрине остаются байты **встреченной первой**. Выбор
|
||||
между ними ненаблюдаем по построению: различие при совпавшей канонической форме
|
||||
это порядок ключей или последний разряд double, и хеш содержимого объекта
|
||||
считается по канонической форме, а не по байтам.
|
||||
|
||||
Цена нового тай-брейка называется вслух и ограничивается разрядом, на котором
|
||||
он работает. **Когда значения общих содержательных ключей разошлись, полнота
|
||||
ответа не даёт по построению** — «надмножество имён о полноте не говорит
|
||||
ничего», см. выше, — и пришедшая точка побеждает, даже если сохранённая несла
|
||||
сверх того ключи **без содержания** (`Min:0`, `Max:0`). Такие ключи по
|
||||
определению пустоты этого же требования содержания не несут, и второй разряд их
|
||||
бережёт только там, где содержание совпало. Прежний байтовый порядок сохранял
|
||||
их случайно — по тому, что каноническая форма с ключом `Max` сортируется раньше
|
||||
формы с одним `date`, — и рассчитывать на такую защиту было нельзя.
|
||||
|
||||
**Класс входа, на котором правило ведёт себя хуже прежнего, называется здесь.**
|
||||
Две автоматизации, чьи наборы метрик пересекаются, наполняют одну координату
|
||||
разными значениями (находка 14); прежний тай-брейк давал на ней устойчивый
|
||||
исход, новый — чередование по последней доставке, то есть перезапись объекта и
|
||||
`WARN` на каждой доставке. Защита остаётся операционной («наборы метрик между
|
||||
автоматизациями не пересекать»), система её не проверяет, и это записано, чтобы
|
||||
следующий разбор не искал причину заново.
|
||||
|
||||
**Победитель SHALL быть функцией множества точек координаты и их происхождения
|
||||
(доставка или витрина), а не порядка элементов внутри доставки.** Попарная
|
||||
свёртка этого не даёт: полнота — частичный порядок, тай-брейк — тотальный, и
|
||||
вместе они образуют нетранзитивное отношение победы (A превосходит B по
|
||||
полноте, B бьёт C тай-брейком, C бьёт A тай-брейком). При таком цикле повторная
|
||||
свёртка одной и той же доставки меняет содержимое объекта. Поэтому система
|
||||
SHALL отбросить кандидатов, превзойдённых по полноте кем-то другим, и выбрать
|
||||
победителя среди оставшихся по тотальному порядку — сперва происхождение,
|
||||
затем каноническая форма.
|
||||
|
||||
**Правило тем самым есть явная функция порядка журнала, и цена этого называется
|
||||
вслух.** Функцией множества оно быть перестало: «пришедшая побеждает» не
|
||||
коммутативно. Витрина остаётся свёрткой журнала только пока порядок свёртки
|
||||
равен порядку журнала — требование, которое capability приёма обязана
|
||||
обеспечивать, а capability пересборки обязана проверять оракулом. Восстановить
|
||||
коммутативность «для чистоты» MUST NOT: это откатило бы починку молча.
|
||||
|
||||
Сравнение по `received_at` для тай-брейка не годится: у сохранённой точки нет
|
||||
провенанса — ни времени приёма, ни идентификатора доставки, — и сравнивать не с
|
||||
чем. Заводить его это изменение SHALL NOT: колонка провенанса на точку означала
|
||||
бы смену формата содержимого объекта, миграцию и рост нижнего слоя, а
|
||||
«пришедшая побеждает» даёт тот же исход, пока порядок свёртки равен порядку
|
||||
журнала. Запрет этот **бюджетный, а не принципиальный**: он назван решением
|
||||
владельца от 2026-08-04 и снимается тем же порядком. Если окно конкурентного
|
||||
приёма закрыть на приёме не удастся, провенанс (на объект, не на точку)
|
||||
остаётся единственным ходом, и спека обязана это допускать, а не запрещать
|
||||
вечно.
|
||||
|
||||
Если множества **несравнимы** — каждое несёт ключ с непустым значением,
|
||||
которого нет у другого, — система SHALL выбрать победителя тем же
|
||||
детерминированным правилом, что и при равных множествах, и MUST оставить
|
||||
наблюдение: счётчик в итоге разбора доставки, координаты объекта и запись
|
||||
`WARN` без значений точки. Несравнимый набор — частный случай столкновения:
|
||||
счётчик перезаписей растёт вместе с ним, а координаты попадают в оба списка.
|
||||
которого нет у другой, — система SHALL выбрать победителя тем же правилом, что
|
||||
и при равных множествах, и MUST оставить наблюдение: счётчик в итоге разбора
|
||||
доставки, координаты объекта и запись `WARN` без значений точки. Несравнимый
|
||||
набор — частный случай столкновения: счётчик перезаписей растёт вместе с ним, а
|
||||
координаты попадают в оба списка.
|
||||
|
||||
Объединять поля двух точек система SHALL NOT: на живом потоке несравнимых
|
||||
наборов не встретилось ни разу (0 из 2 897 столкновений, при обоих определениях
|
||||
пустоты), и реализация правила, которое никогда не срабатывает, стоила бы
|
||||
больше, чем счётчик, который скажет, если оно наступит.
|
||||
Объединять поля двух точек система SHALL NOT: на живом потоке несравнимые
|
||||
наборы наблюдаются единицами (2 на 155 доставок), и реализация правила, которое
|
||||
почти не срабатывает, стоила бы больше, чем счётчик, который скажет, если оно
|
||||
станет массовым.
|
||||
|
||||
Победителем SHALL оставаться одна из пришедших точек **дословно**: правило
|
||||
выбирает, а не конструирует. Каноническая форма существует только в момент
|
||||
@@ -163,21 +226,52 @@ TBD - created by archiving change razbor-metrik-v-obekty. Update Purpose after a
|
||||
#### Scenario: При равном содержании поля не теряются
|
||||
|
||||
- **WHEN** сохранена точка `{date, qty, Min:0, Max:0}`
|
||||
- **AND** по тем же координатам приезжает точка `{date, qty}` с другим `qty`
|
||||
- **AND** по тем же координатам приезжает точка `{date, qty}` с тем же `qty`
|
||||
- **THEN** остаётся точка с `Min` и `Max`
|
||||
|
||||
#### Scenario: При разошедшихся значениях пустые ключи не удерживают точку
|
||||
|
||||
- **WHEN** сохранена точка `{date, qty:10, Min:0, Max:0}`
|
||||
- **AND** по тем же координатам следующей доставкой приезжает точка
|
||||
`{date, qty:12}`
|
||||
- **THEN** остаётся пришедшая точка, а `Min` и `Max` в витрине не остаются
|
||||
- **AND** счётчик перезаписей растёт
|
||||
|
||||
#### Scenario: Одинаково полные точки с разными значениями
|
||||
|
||||
- **WHEN** по одним координатам приходят две точки с одинаковыми множествами
|
||||
ключей и разными значениями
|
||||
- **THEN** исход определяется детерминированно и не зависит от порядка
|
||||
воспроизведения доставок
|
||||
- **GIVEN** по координатам сохранена точка предыдущей доставки
|
||||
- **WHEN** следующей доставкой приезжает точка с тем же множеством ключей и
|
||||
другим значением
|
||||
- **THEN** в витрине остаётся пришедшая точка
|
||||
|
||||
#### Scenario: Одинаково полные точки внутри одной доставки
|
||||
|
||||
- **WHEN** в одном теле по одним координатам приезжают две точки с одинаковыми
|
||||
множествами ключей и разными значениями
|
||||
- **THEN** остаётся точка с меньшей канонической формой
|
||||
- **AND** исход не зависит от порядка этих точек в массиве
|
||||
|
||||
#### Scenario: Повторная присылка сохранённого содержимого ничего не переписывает
|
||||
|
||||
- **GIVEN** по координатам сохранена точка
|
||||
- **WHEN** следующей доставкой приезжает точка с той же канонической формой и
|
||||
другими байтами
|
||||
- **THEN** в витрине остаются сохранённые байты
|
||||
- **AND** объект не переписывается
|
||||
|
||||
#### Scenario: Полнота сильнее происхождения
|
||||
|
||||
- **GIVEN** по координатам сохранена точка с `Avg`, `Min` и `Max`
|
||||
- **WHEN** следующей доставкой приезжает точка только с `Avg` и теми же
|
||||
значениями общих ключей
|
||||
- **THEN** остаётся сохранённая точка
|
||||
- **AND** счётчик удержаний в итоге разбора доставки растёт
|
||||
|
||||
#### Scenario: Несравнимые множества считаются, а не сливаются
|
||||
|
||||
- **WHEN** по одним координатам приходят две точки, каждая из которых несёт
|
||||
ключ с непустым значением, которого нет у другой
|
||||
- **THEN** остаётся ровно одна точка, выбранная детерминированно
|
||||
- **THEN** остаётся ровно одна точка, выбранная тем же правилом
|
||||
- **AND** счётчик несравнимых наборов в итоге разбора доставки растёт
|
||||
- **AND** система пишет `WARN` с координатами объекта и без значений точки
|
||||
|
||||
@@ -631,13 +725,15 @@ HAE состоят из объектов, а не из чисел.
|
||||
необратимым.
|
||||
|
||||
**Тай-брейк при равных наборах — позиция доставки в журнале `(received_at, id)`,
|
||||
а не порядок свёртки.** «Побеждает приехавшая» было бы функцией порядка
|
||||
свёртки, а он порядку журнала не равен: воркер сворачивает в порядке журнала
|
||||
только среди видимых ему доставок и абсолютного порядка при конкурентных
|
||||
приёмах не обещает. Доставка с более ранней меткой, свёрнутая позже, вернула бы
|
||||
а не порядок свёртки.** Порядок свёртки приведён к порядку журнала требованием
|
||||
capability приёма, но равенство это неполное: строка учёта становится видимой
|
||||
воркеру только после записи тела, и при конкурентном приёме остаётся окно, в
|
||||
котором доставка с более ранней меткой сворачивается позже. Она вернула бы
|
||||
витрину к недосчитанной версии, и пересборка разошлась бы с живым приёмом молча,
|
||||
в содержимом тренировки. Позиция журнала снимает это: исход зависит от журнала,
|
||||
а не от того, кто раньше добрался до базы.
|
||||
в содержимом тренировки. Хранимая позиция журнала снимает это целиком: исход
|
||||
зависит от журнала, а не от того, кто раньше добрался до базы, — то есть у
|
||||
сущности гарантия строго сильнее, чем у точки, и держится она на колонке
|
||||
провенанса, которой у точки нет.
|
||||
|
||||
Ровно поэтому **провенанс сущности SHALL обновляться и тогда, когда хеш
|
||||
совпал**: сохранённая позиция журнала участвует в тай-брейке пункта 4, и если
|
||||
@@ -669,12 +765,17 @@ HAE состоят из объектов, а не из чисел.
|
||||
закоммитила последней: исход снова станет функцией порядка коммитов, а не
|
||||
журнала, причём молча.
|
||||
|
||||
Отличие от точки здесь содержательное: у точки на одних координатах законно
|
||||
встречаются два разных измерения, и предпочитать позднее нет оснований — там
|
||||
исход решает порядок канонических форм. У сущности `id` — идентичность одного
|
||||
объекта HealthKit, и вторая версия есть тот же объект, пересчитанный источником;
|
||||
тай-брейк по канонической форме заморозил бы тренировку на произвольной из
|
||||
версий навсегда, вместе с недосчитанной энергией.
|
||||
Отличие от точки — в **механизме**, а не в намерении, и критерий выбора между
|
||||
ними записан здесь, чтобы третья единица хранения не открывала спор заново. Обе
|
||||
предпочитают позднюю версию: у сущности — по хранимой позиции журнала, у точки —
|
||||
по происхождению кандидата (пришла доставкой или лежала в объекте). Различает их
|
||||
одно: у сущности есть колонка провенанса, у точки её нет и рамка решения
|
||||
владельца заводить её запретила. Отсюда и разная сила гарантии, названная выше.
|
||||
Порядок канонических форм у обеих остался тем же и там же — тай-брейком
|
||||
**внутри одной доставки**, где провенанс общий и различать нечем. Тай-брейк по
|
||||
канонической форме между доставками заморозил бы тренировку на произвольной из
|
||||
версий навсегда, вместе с недосчитанной энергией, а у точки — систематически
|
||||
хранил бы меньшее значение (находка 49).
|
||||
|
||||
Версии одного ключа **внутри одной доставки** позициями не различаются, и
|
||||
победитель среди них SHALL быть **функцией множества версий, а не порядка
|
||||
@@ -1494,3 +1595,76 @@ HealthKit рядом. Значение хранится **дословно**, т
|
||||
- **THEN** в разобранных записях лога нет ни одной наблюдённой строки и ни
|
||||
одного кода
|
||||
- **AND** счётчики значений без кода в записи присутствуют
|
||||
### Requirement: Проигрыш пришедшей точки считается отдельно
|
||||
|
||||
Итог разбора доставки SHALL нести счётчик координат, где пришедшая точка
|
||||
проиграла сохранённой, и координаты первых таких объектов — метрику, слой и
|
||||
час, без значений точек.
|
||||
|
||||
Счётчик SHALL быть отдельным от счётчика перезаписей. Перезаписи считают
|
||||
столкновение в обе стороны (на корпусе 2026-08-04 они сработали бы около 84 000
|
||||
раз), и отличить по ним «оставили пришедшую» от «выбросили пришедшую» нельзя —
|
||||
то есть единственное событие, ради наблюдения за которым правило и переписано,
|
||||
остаётся невидимым.
|
||||
|
||||
Под новым правилом пришедшая точка проигрывает ровно тогда, когда сохранённая
|
||||
**строго полнее**. То есть счётчик меряет одно направление правила полноты — то,
|
||||
в котором оно спорит с журналом; случай «пришедшая строго полнее» им не
|
||||
считается, потому что там полнота и журнал согласны. Молчащий счётчик означает,
|
||||
что правило полноты перестало спорить вовсе, и это событие для разбора, а не
|
||||
отказ.
|
||||
|
||||
Счётчик SHALL быть виден не только в логе свёртки, но и в отчёте пересборки —
|
||||
рядом с удержанными версиями сущностей и по той же причине: сходимость
|
||||
отпечатка правило удержания не проверяет по построению, живой приём и пересборка
|
||||
пользуются одним правилом и одинаково сойдутся на одинаково удержанной точке.
|
||||
|
||||
Значений точек счётчик и его координаты содержать MUST NOT — данные о здоровье
|
||||
чувствительнее токенов.
|
||||
|
||||
#### Scenario: Удержание сохранённой точки видно в итоге доставки
|
||||
|
||||
- **GIVEN** по координатам сохранена точка, строго более полная, чем пришедшая
|
||||
- **WHEN** доставка сворачивается
|
||||
- **THEN** счётчик удержаний растёт
|
||||
- **AND** координаты объекта попадают в список удержаний
|
||||
- **AND** счётчик остаётся нулевым, когда побеждает пришедшая точка
|
||||
|
||||
### Requirement: Потеря содержания пришедшей точкой считается и не молчит
|
||||
|
||||
Система SHALL считать отдельным счётчиком координаты, где победителем оказалась
|
||||
**пришедшая** точка, а у проигравшей был ключ с непустым значением, которого у
|
||||
победительницы нет. Координаты таких объектов SHALL попадать в лог, и система
|
||||
SHALL писать `WARN` — без значений точек.
|
||||
|
||||
Это единственное направление, в котором новое правило способно потерять
|
||||
содержание, и защитить его нечем **по построению**: разряд полноты в этом
|
||||
случае погашен — значения общих содержательных ключей разошлись, и надмножество
|
||||
имён о полноте не говорит ничего. До смены тай-брейка тот же исход случался по
|
||||
жребию байтового порядка и так же молча; разница в том, что теперь он
|
||||
детерминирован, а значит либо не случается вовсе, либо случается всегда.
|
||||
|
||||
Счётчик SHALL быть отдельным и от перезаписей, и от удержаний: перезаписи
|
||||
считают столкновение в обе стороны, удержания — противоположное направление, и
|
||||
ни один из них на вопрос «потеряли ли мы содержание» не отвечает.
|
||||
|
||||
Запрещать такое слияние система SHALL NOT: измерено — 2 координаты из 80 129
|
||||
спорных на живом корпусе, и обе те же, что дают несравнимые наборы. Событие
|
||||
обратимо пересборкой, пока жив архив, поэтому вместо запрета — наблюдение, тем
|
||||
же решением и по той же причине, по какой отложено объединение полей.
|
||||
|
||||
Значений точек ни счётчик, ни координаты, ни запись содержать MUST NOT.
|
||||
|
||||
#### Scenario: Пришедшая точка унесла содержательный ключ сохранённой
|
||||
|
||||
- **GIVEN** сохранена точка `{date, asleep:7.5, rem:1.2}`
|
||||
- **WHEN** следующей доставкой приезжает точка `{date, asleep:7.4}`
|
||||
- **THEN** в витрине остаётся пришедшая точка
|
||||
- **AND** счётчик потери содержания растёт, а счётчик удержаний — нет
|
||||
- **AND** система пишет `WARN` с координатами объекта и без значений точек
|
||||
|
||||
#### Scenario: Пришедшая точка ничего не унесла
|
||||
|
||||
- **WHEN** множества ключей у сохранённой и пришедшей совпадают, а значения
|
||||
различаются
|
||||
- **THEN** счётчик потери содержания не растёт
|
||||
|
||||
Reference in New Issue
Block a user