добавлен словарь категориальных значений HAE → коды HealthKit
- фазы сна, контекст пульса и имена тренировок попадают в реестр `category_value` (миграция 00010): строка хранится дословно, выведенный код лежит рядом отдельной записью, а не полем внутри точки - словарь и синонимы кодов живут в бинаре (`internal/healthkit`); локаль из `Accept-Language` сужает поиск, но в ключ реестра не входит — заголовков в сыром архиве нет - наблюдение входит в отпечаток витрины, выведенный код — нет: он производная от словаря, а не от журнала
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# Код HealthKit кладётся реестром рядом, а не полем внутри точки
|
||||
|
||||
- Дата: 2026-08-03
|
||||
- Источник: openspec/changes/archive/2026-08-03-slovar-kategorialnyh-znachenij/design.md
|
||||
|
||||
## Решение
|
||||
|
||||
Стабильный код HealthKit для локализованной строки хранится **отдельной строкой
|
||||
таблицы `category_value`** с ключом `(метрика, поле, значение)`, а не полем
|
||||
`value_code` внутри точки, как рисовал `architecture.md`. Словарь и таблица
|
||||
синонимов живут в бинаре (`internal/healthkit`), а не в базе. Наблюдение входит
|
||||
в отпечаток витрины, выведенный код — **нет**.
|
||||
|
||||
## Почему
|
||||
|
||||
Рассматривались три формы, и отвергнутые названы вместе с ценой.
|
||||
|
||||
**Поле внутри точки** — отвергнуто. Цитата источника: «Точка хранится
|
||||
**исходными байтами**; дописать в неё ключ можно только пересериализацией, а она
|
||||
теряет литерал (`1.0` → `1`, целые больше 2^53 сдвигаются, невалидный UTF-8 →
|
||||
U+FFFD) — ровно то, от чего `Point.Raw` защищает. Побайтовая врезка в чужой
|
||||
JSON — фокус, а не решение. Параллельный массив кодов в `bucket` завёл бы
|
||||
производную величину в путь слияния и хеширования: правило полноты, тай-брейк и
|
||||
`content_hash` пришлось бы учить носить код, не давая ему влиять на исход.
|
||||
Правка на поверхности `critical`-инвариантов ради нуля новых сведений — код есть
|
||||
**функция** от того, что уже лежит».
|
||||
|
||||
**Код нигде не хранится, выводится на чтении** — отвергнуто по одной причине:
|
||||
«тогда код недостижим ничем, кроме бинаря. Владелец сегодня читает витрину
|
||||
`sqlite` на хосте (`Read API` ещё нет), а вся задача затевается против того, что
|
||||
„клиент угадывает словарь“. Реестр без кода сообщает только „такая строка
|
||||
была“ — это половина ответа».
|
||||
|
||||
**Словарь в базе, а не в бинаре** — отвергнуто: «словарь стал бы входом,
|
||||
которого нет в журнале, и `import + replay` перестал бы задавать состояние
|
||||
однозначно. `stateOfMind` уже единственная дыра в журнале; вторую заводить
|
||||
незачем».
|
||||
|
||||
**Код вне отпечатка** — обратная сторона того же решения: «Ключ и провенанс —
|
||||
функция журнала; `code` — функция журнала **и версии словаря в бинаре**. Включи
|
||||
его в отпечаток, и он перестал бы отвечать на свой единственный вопрос („дал ли
|
||||
повтор журнала то же состояние“) ровно тогда, когда его задают: всякое
|
||||
пополнение словаря — а оно объявлено рабочим циклом — давало бы расхождение при
|
||||
побайтно совпавшем журнале, и человек, принимающий необратимое решение о
|
||||
подмене базы, читал бы это как дефект».
|
||||
|
||||
Prior art: FHIR `ConceptMap` (отображение «чужая система значений → своя») и
|
||||
`CodeSystem` с `replaced-by` для устаревших имён — те же два отношения,
|
||||
разведённые по разным сущностям. Форма взята, реализация FHIR отвергнута ценой.
|
||||
|
||||
## Последствия
|
||||
|
||||
- `+` Инвариант «точки хранятся дословно» не тронут вовсе: точка не меняется ни
|
||||
байтом, обратное преобразование возможно всегда.
|
||||
- `+` Пути слияния, тай-брейка и `content_hash` не знают о кодах — правки на
|
||||
поверхности `critical`-инвариантов не потребовалось.
|
||||
- `+` Пополнение словаря меняет десяток строк реестра, а не каждый объект с
|
||||
фазами сна; отпечаток при этом не двигается, потому что код в него не входит.
|
||||
- `−` Потребитель обязан делать соединение по `(метрика, поле, значение)` вместо
|
||||
чтения одного поля. Форма ответа Read API это скроет, когда он появится.
|
||||
- `−` Код в базе отстаёт от словаря в бинаре для строк, переставших приезжать.
|
||||
Лечится пересборкой; на сходимость не влияет.
|
||||
- `−` Ключ реестра зафиксирован миграцией `00010`: смена формы ключа стоит
|
||||
второй миграции и пересборки.
|
||||
+5
-2
@@ -33,8 +33,11 @@
|
||||
| Дата | Запись | Статус |
|
||||
| --- | --- | --- |
|
||||
|
||||
Записей пока нет: каталог заведён переездом на канон 2026-08-03. Сырьё для
|
||||
промоута накоплено — девять архивных изменений в
|
||||
- [ADR-2026-08-03-kod-ryadom-so-strokoj-reestrom](ADR-2026-08-03-kod-ryadom-so-strokoj-reestrom.md)
|
||||
— код HealthKit кладётся реестром рядом со строкой, а не полем внутри точки;
|
||||
словарь живёт в бинаре, выведенный код в отпечаток витрины не входит.
|
||||
|
||||
Сырьё для промоута накоплено — архивные изменения в
|
||||
`openspec/changes/archive/`, из них решения с дорогим откатом и намеренные
|
||||
отказы есть как минимум в `2026-08-01-polnota-tochki-mnozhestvom-klyuchey`
|
||||
(идентичность точки и тай-брейк), `2026-08-02-reindex-iz-arhiva` (подмену базы
|
||||
|
||||
Reference in New Issue
Block a user