добавлен словарь категориальных значений HAE → коды HealthKit
- фазы сна, контекст пульса и имена тренировок попадают в реестр `category_value` (миграция 00010): строка хранится дословно, выведенный код лежит рядом отдельной записью, а не полем внутри точки - словарь и синонимы кодов живут в бинаре (`internal/healthkit`); локаль из `Accept-Language` сужает поиск, но в ключ реестра не входит — заголовков в сыром архиве нет - наблюдение входит в отпечаток витрины, выведенный код — нет: он производная от словаря, а не от журнала
This commit is contained in:
@@ -1335,3 +1335,162 @@ MUST NOT, а закрывать базу по выходу одной из дв
|
||||
- **THEN** база не закрывается, а запись лога называет этап, не указывая
|
||||
виновной горутины
|
||||
|
||||
### Requirement: Реестр наблюдённых категориальных значений
|
||||
|
||||
Хранилище SHALL держать реестр категориальных значений, которые приносил поток:
|
||||
одна строка на тройку `(метрика или секция, поле, значение)`, с выведенным кодом
|
||||
HealthKit рядом. Значение хранится **дословно**, тем же текстом, каким пришло.
|
||||
|
||||
Локаль в ключ MUST NOT входить. Она живёт в заголовке доставки, которого нет в
|
||||
сыром архиве, — ключ с локалью сделал бы состояние функцией от того, уцелела ли
|
||||
строка учёта: усыновлённая доставка положила бы вторую строку с пустой локалью.
|
||||
Язык, на котором приехала строка, восстанавливается по доставке провенанса.
|
||||
|
||||
Реестр SHALL нести выведенный код и провенанс **первой** встречи — метку приёма
|
||||
и идентификатор доставки, в которой строка появилась впервые по порядку журнала.
|
||||
Первая встреча отвечает на вопрос «когда сменился язык телефона»; счётчик встреч
|
||||
не заводится вовсе — он не идемпотентен при повторной свёртке той же доставки, а
|
||||
значит сделал бы состояние зависящим от числа прогонов.
|
||||
|
||||
Пустой код — законное состояние строки: он означает «словарь этой строки не
|
||||
знает», и перечень таких строк есть заявка на пополнение словаря.
|
||||
|
||||
Словарь, по которому выводится код, SHALL жить в бинаре, а не в таблице базы и
|
||||
не в строках миграции. Таблица, наполняемая руками, стала бы входом, которого
|
||||
нет в журнале, и `import + replay` перестал бы задавать состояние однозначно;
|
||||
данные, засеянные миграцией, живут в двух местах сразу и расходятся с бинарём
|
||||
молча после первой же правки словаря.
|
||||
|
||||
Строки реестра SHALL писаться **той же транзакцией**, что и точки доставки.
|
||||
Доставка — единица свёртки; частичное состояние «точки легли, реестр нет»
|
||||
повторная свёртка не чинит: хеш содержимого сойдётся, и объекты переписываться
|
||||
не станут.
|
||||
|
||||
Обслуживания у реестра нет: строки не удаляются. Строка, единственные доставки
|
||||
которой выпали из журнала подрезкой архива, переживёт их в рабочей витрине и не
|
||||
появится в пересобранной. Это тот же класс, что «верхние слои за периоды с
|
||||
удалёнными доставками», и он MUST называться, а не досчитываться.
|
||||
|
||||
#### Scenario: Фаза сна попадает в реестр с кодом
|
||||
|
||||
- **WHEN** свёрнута доставка с фазой сна «Во сне» и локалью `ru`
|
||||
- **THEN** в реестре есть строка `sleep_analysis / value / «Во сне»` с кодом
|
||||
`HKCategoryValueSleepAnalysisAsleepUnspecified`
|
||||
- **AND** содержимое точки в часовом объекте не изменилось
|
||||
|
||||
#### Scenario: Та же строка без заголовка локали даёт ту же строку реестра
|
||||
|
||||
- **WHEN** та же строка приехала доставкой без `Accept-Language`
|
||||
- **THEN** второй строки в реестре не появляется
|
||||
|
||||
#### Scenario: Строка без кода в реестре видна
|
||||
|
||||
- **WHEN** свёрнута доставка с `heart_rate.context`, которого словарь не знает
|
||||
- **THEN** в реестре есть строка с этим значением и пустым кодом
|
||||
|
||||
#### Scenario: Отказ слияния не оставляет строк реестра
|
||||
|
||||
- **WHEN** слияние доставки отказало
|
||||
- **THEN** реестр не содержит значений этой доставки
|
||||
|
||||
### Requirement: Реестр детерминирован по журналу
|
||||
|
||||
Повторная свёртка той же доставки MUST оставлять реестр без изменений, а
|
||||
провенанс первой встречи MUST быть **минимумом** по порядку журнала
|
||||
(`received_at`, затем идентификатор доставки), а не значением последней записи.
|
||||
|
||||
Проигрывание журнала в любом порядке доставок, дающем тот же префикс, SHALL
|
||||
приводить реестр к тому же состоянию. Реестр — свёртка по журналу, как и всё
|
||||
остальное в витрине.
|
||||
|
||||
Выведенный код SHALL обновляться при каждой встрече строки: словарь живёт в
|
||||
бинаре, и пересборка обязана давать код по текущему словарю, а не по тому,
|
||||
который действовал при первой свёртке. Строка, переставшая приезжать, держит код
|
||||
прежнего словаря до пересборки — это осознанная цена материализации, и на
|
||||
сходимость она не влияет, потому что код в отпечаток не входит.
|
||||
|
||||
#### Scenario: Повторная свёртка ничего не меняет
|
||||
|
||||
- **WHEN** одна и та же доставка свёрнута дважды
|
||||
- **THEN** строки реестра и их провенанс совпадают с состоянием после первой
|
||||
свёртки
|
||||
|
||||
#### Scenario: Провенанс — самая ранняя доставка
|
||||
|
||||
- **WHEN** одна строка приехала сперва поздней доставкой, затем ранней
|
||||
- **THEN** провенансом остаётся ранняя по журналу
|
||||
|
||||
#### Scenario: Порядок свёртки на реестр не влияет
|
||||
|
||||
- **WHEN** три доставки свёрнуты во всех шести порядках
|
||||
- **THEN** реестр и его раздел отпечатка совпадают у всех шести прогонов
|
||||
|
||||
#### Scenario: Пересборка даёт тот же реестр
|
||||
|
||||
- **WHEN** журнал проигран в свежую витрину
|
||||
- **THEN** реестр пересобранной витрины совпадает с реестром исходной
|
||||
|
||||
### Requirement: Отпечаток покрывает наблюдение реестра, но не выведенный код
|
||||
|
||||
Отпечаток витрины SHALL покрывать реестр категориальных значений отдельным
|
||||
разделом: ключ и провенанс первой встречи. Иначе недетерминированная запись
|
||||
реестра прошла бы мимо единственного оракула сходимости.
|
||||
|
||||
Выведенный код в отпечаток входить MUST NOT. Ключ и провенанс — функция журнала;
|
||||
код — функция журнала **и версии словаря в бинаре**. Включённый в отпечаток, он
|
||||
заставил бы всякое пополнение словаря давать расхождение при побайтно совпавшем
|
||||
журнале, а пополнение объявлено рабочим циклом. Человек, принимающий по
|
||||
отпечатку необратимое решение о подмене базы, читал бы это как дефект.
|
||||
Правильность вывода кода проверяется тестами словаря — это другой вопрос, и
|
||||
смешение обесценило бы оракул.
|
||||
|
||||
Раздел SHALL быть отличим от прочих признаком впереди строки, а поля переменной
|
||||
длины SHALL идти с длиной впереди: два разных состояния витрины не имеют права
|
||||
дать один отпечаток.
|
||||
|
||||
Цена названа вслух: у витрины, свёрнутой до этого изменения, реестр пуст, и
|
||||
первая же сверка отпечатков после выкатки покажет расхождение. Это законное
|
||||
расхождение, а не дефект; отчёт пересборки обязан назвать его ожидаемым классом
|
||||
и напечатать счётчик строк реестра.
|
||||
|
||||
#### Scenario: Разошедшееся наблюдение меняет отпечаток
|
||||
|
||||
- **WHEN** у двух витрин совпадают объекты и сущности, но в реестре одной есть
|
||||
строка, которой нет в другой
|
||||
- **THEN** отпечатки различны
|
||||
|
||||
#### Scenario: Расхождение только по коду отпечаток не двигает
|
||||
|
||||
- **WHEN** две витрины несут те же строки реестра с тем же провенансом, но
|
||||
разными кодами
|
||||
- **THEN** отпечатки совпадают
|
||||
|
||||
#### Scenario: Значение с разделителем границу поля не подделывает
|
||||
|
||||
- **WHEN** значение содержит признак раздела, цифры и нулевой байт
|
||||
- **THEN** отпечаток отличается от отпечатка витрины с другим разбиением тех же
|
||||
байтов по полям
|
||||
|
||||
#### Scenario: Пустой реестр отпечаток не ломает
|
||||
|
||||
- **WHEN** в витрине нет ни одной строки реестра
|
||||
- **THEN** отпечаток считается и совпадает с отпечатком такой же витрины без
|
||||
реестра
|
||||
|
||||
### Requirement: Значения категориальных строк не попадают в логи
|
||||
|
||||
Сами наблюдённые строки и выведенные коды MUST NOT попадать в записи лога выше
|
||||
`DEBUG`; в журнал SHALL уходить только счётчики — сколько значений наблюдалось и
|
||||
сколько осталось без кода. Основание: строки категориальных значений — данные о
|
||||
здоровье, контекст пульса и фаза сна описывают человека не меньше, чем число.
|
||||
|
||||
Проверка отсутствия строки в логе SHALL разбирать запись и сравнивать значения
|
||||
полей, а не искать подстроку в сыром буфере: служебная метка времени содержит
|
||||
произвольные цифры, и поиск по буферу делает тест флаки по построению.
|
||||
|
||||
#### Scenario: Свёртка доставки со сном не пишет строк в лог
|
||||
|
||||
- **WHEN** свёрнута доставка с фазами сна и контекстом пульса
|
||||
- **THEN** в разобранных записях лога нет ни одной наблюдённой строки и ни
|
||||
одного кода
|
||||
- **AND** счётчики значений без кода в записи присутствуют
|
||||
|
||||
Reference in New Issue
Block a user