беклог: задача на дозакрытие находок ревью сущностей
- три пропущенных прохода дозапущены, триаж оставил семь причин - обе развилки разобраны по уже записанным решениям проекта: второй разряд Covers как у Relate, мягкое чтение полей как у Duration - уточнена формулировка в architecture.md про цену покрытия секции
This commit is contained in:
@@ -831,8 +831,12 @@ value_code "HKCategoryValueSleepAnalysisAsleepREM" ← выведено по с
|
||||
только `stateOfMind`, а `ecg`, `symptoms`, `cycleTracking`, `medications` и
|
||||
`heartRateNotifications` остаются непокрытыми **намеренно**: живой поток не
|
||||
приносил их ни разу, их формы никто не видел, а полнота покрытия HealthKit ради
|
||||
полноты целью проекта не является. Модель под них заложена — новая секция
|
||||
добавляется одной строкой в множество покрытых имён, а не миграцией.
|
||||
полноты целью проекта не является. Модель под них заложена: **миграции схемы**
|
||||
новая секция не требует — она добавляется строкой в множество покрытых имён.
|
||||
Но не одной: правило «покрыли секцию — пересверните» (выше) требует ещё и
|
||||
data-миграции, переводящей уже принятые `partial`-доставки с этим ключом в
|
||||
`pending`, а после появления ретеншена — строки в перечне невосстановимого.
|
||||
Три места, и первое из них — не самое важное.
|
||||
|
||||
Ключ записи — **пара**, а не один `id`: собственный `id` наблюдался живьём
|
||||
только у `stateOfMind`, где он UUID, и короткий несквозной идентификатор в двух
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
- [Порядок журнала при конкурентных приёмах](poryadok-zhurnala-na-priyome.md) — доставка, свёрнутая раньше своей предшественницы, уходит в failed навсегда — живое состояние расходится с reindex
|
||||
|
||||
## высокий
|
||||
- [Дозакрыть находки ревью по слиянию сущностей](dozakryt-nahodki-sushchnostej.md) — Скелет из null затирает маршрут необратимо, а откат бинаря поверх новой схемы проходит молча: семь находок с прогнанными оракулами
|
||||
- [Измеренный род агрегации и каталог разрезов](rod-agregacii-i-katalog.md) — Без рода метрики свёртка в ответе неотличима от угадывания — а суммировать нижний слой значит завысить втрое
|
||||
- [Read API: точки, выбор слоя, свёртка по сетке](read-api-tochki.md) — Данные видны только через sqlite на хосте — ни один из трёх потребителей ничего прочитать не может
|
||||
- [OpenAPI-спека и Swagger UI](openapi-swagger.md) — Потребителей три и один из них агент — контракт должен читаться машиной, а не пересказываться в чате
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
# Дозакрыть находки ревью по слиянию сущностей
|
||||
|
||||
**Приоритет:** высокий
|
||||
|
||||
Задача «Тренировки и секции с собственными id» (`f8200f7`) прошла ревью не
|
||||
полностью: проходы `adversary`, `ops` и архитектурный на коде не запускались.
|
||||
Дозапуск принёс девять причин, триаж оставил семь. Оба заявленных `critical`
|
||||
понижены до `major` с названной причиной — вход недостижим из штатного потока
|
||||
HAE (корпус в 118 доставок такого не производил), — но остались в работе:
|
||||
потеря маршрута необратима, а `reindex` проигрывает то же поражение.
|
||||
|
||||
Отчёт триажа с прогнанными оракулами — `tmp/triage-late.md`, оракулы —
|
||||
`tmp/adv/*.go`. Каждый пункт ниже имеет падающий тест; правка считается
|
||||
сделанной, когда соответствующий оракул зеленеет **и** переезжает из `tmp/` в
|
||||
обычные тесты пакета.
|
||||
|
||||
## Что делать
|
||||
|
||||
**1. Скелет не затирает маршрут.** `canon.Fields.Covers` проверяет только
|
||||
наличие ключа и длину верхнеуровневого массива, поэтому версия, где каждый
|
||||
массив заменён массивом той же длины из `null`, а каждое не-массивное значение
|
||||
— скаляром, признаётся равной настоящей и по тай-брейку журнала замещает её.
|
||||
Оракул: `-run 'Скелет|Маршрут|ДвеВерсии'`.
|
||||
|
||||
Решение принято, новое не проектируем: применяем к сущностям **тот же
|
||||
стандарт, что записан для точек** — второй разряд сравнения, как у
|
||||
`canon.Relate` («иначе ключ с пустым значением исчезает по жребию»). Плюс
|
||||
запрет вырождения формы: покрывающая версия не может подменить объект или
|
||||
массив скаляром — проверка по верхнему уровню, стоимость O(ключей).
|
||||
Поэлементная содержательность массивов **отвергнута ценой**: пункт 4 измерил
|
||||
768 МиБ пика на канонизации, полный обход маршрута на каждое сравнение эту цену
|
||||
умножит. Остающийся предел — порча *внутри* элемента ряда (точка маршрута без
|
||||
`altitude`) — не закрывается ничем, кроме сверки с телом в архиве, и должен
|
||||
быть записан в `architecture.md` рядом с описанием `Covers` так же прямо, как
|
||||
он записан в комментарии кода.
|
||||
|
||||
Отдельно: `pickWithinDelivery` при равном содержании и **разных байтах** обязан
|
||||
считать `differs=true` — сейчас две версии одного `id` в одном теле дают
|
||||
`удержано=0` и молчащий счётчик.
|
||||
|
||||
**2. Одно поле не той формы не уносит сущность.** `entityHead` держит
|
||||
`ID`/`Name`/`Date`/`Start`/`End` типизированными строками, поэтому смена типа
|
||||
любого из пяти роняет `json.Unmarshal` целиком, а доставка при этом получает
|
||||
`parsed` с пустым списком непокрытого. Достижимо из реального потока: дрейф
|
||||
формата дат у HAE задокументирован. Оракул: `-run ОдноПоле`.
|
||||
|
||||
Читать пять полей через `json.RawMessage` и извлекать мягко — это буквально
|
||||
принцип, уже записанный в коде для `Duration` («нечисловое значение — это
|
||||
пропуск ОДНОГО поля, а не сломанная сущность»). Плюс пропуски обязаны быть
|
||||
видны в **учётной записи** доставки, а не только в логе: ретеншен решает по
|
||||
базе, и сегодня он получит ответ «терять нечего». Хранение сущности с
|
||||
неразобранной меткой (NULL) в эту задачу не входит — см. остаток ниже.
|
||||
|
||||
**3. Откат бинаря не проходит молча.** `store.Open` мигрирует безусловно и не
|
||||
сверяет версию схемы, поэтому старый бинарь успешно стартует поверх схемы 7,
|
||||
молча игнорирует незнакомые секции и помечает доставки разобранными. Оракул
|
||||
прогнан живьём: `-run СтарыйБинарь`. Перенести в `Open` страж из
|
||||
`OpenForRead` — прецедент записан там же: «расхождение версий — отказ, а не
|
||||
повод мигрировать».
|
||||
|
||||
**4. Канонизация — за транзакцию, по-настоящему.** Комментарий
|
||||
`bucket.go:143-146` утверждает, что канонизация вынесена наружу; фактически
|
||||
`analyze()` вызывается из `compareEntities` **внутри** `inTx`, который открывает
|
||||
`immediate` и повторяет до пяти раз, а кеш `analyze()` пишется в **копию**
|
||||
элемента среза и не переживает даже одной попытки. Измерено: тело 40 МиБ → пик
|
||||
768.3 МиБ; 63 МиБ → блокировка удерживается 5.019 с при `busy_timeout` 5000, то
|
||||
есть конкурентный `CreateDelivery` исчерпывает повторы и приём отвечает 500 по
|
||||
доставке, тело которой уже на диске.
|
||||
|
||||
В этой задаче: вынести `analyze()` наружу по-настоящему, кешировать в срезе, а
|
||||
не в копии, и различать в логе `delivery failed` занятость базы (`store.ErrBusy`
|
||||
уже выделен доменной ошибкой) от прочих причин. Пределы на размер сущности и
|
||||
потоковый расчёт хеша — остатком.
|
||||
|
||||
**5. Значения из тела не попадают в текст ошибки.** `fmt.Errorf("… встречено
|
||||
%v", tok)` подставляет токен целиком: тело 8 МиБ даёт текст ошибки 8 МиБ,
|
||||
который уходит атрибутом `error` на уровень `WARN`. Инвариант «тела запросов
|
||||
только на `DEBUG` и с обрезкой» нарушен буквально. Называть тип токена и
|
||||
`dec.InputOffset()`. Оракул: `-run Тело`. Дефект в базе диффа, не внесён
|
||||
разбором сущностей.
|
||||
|
||||
**6. Победитель внутри доставки — функция множества, а не порядка.**
|
||||
Попарная свёртка частичного порядка с тотальным тай-брейком нетранзитивна:
|
||||
`[A,B,C]` даёт `C`, `[B,C,A]` даёт `A`. Стандарт «победитель — функция множества
|
||||
точек, а не порядка» записан в `architecture.md` для точек и для сущностей
|
||||
молча не применён. Собрать версии ключа, отбросить строго покрытые, среди
|
||||
оставшихся взять минимум канонической формы. Оракул: `-run ПорядокВнутри`.
|
||||
|
||||
**7. Провенанс обновляется при равных хешах.** Совпал хеш — запись
|
||||
пропускается вместе с провенансом, и в `delivery_id`/`delivery_received_at`
|
||||
остаётся первая свёрнутая копия, а не победитель по журналу. Провенанс
|
||||
устаревает на каждой из ~26 повторных присылок гарантированно; расхождение
|
||||
живой витрины с `reindex` латентно (требует возврата содержимого к прежнему —
|
||||
корпус такого не производил), но нарушает записанный инвариант детерминизма.
|
||||
Сравнивать позиции в журнале и обновлять провенанс. Оракул: `-run Порядок`.
|
||||
|
||||
## Что уходит остатком
|
||||
|
||||
- хранение сущности с `id`, но неразобранной меткой (NULL-метка): требует схемы
|
||||
и правил чтения, а после пункта 2 случай становится редким;
|
||||
- пределы на размер одной сущности и суммарный размер секции, потоковый расчёт
|
||||
канонической формы и хеша — заводится задачей вместе с условием из пункта 4;
|
||||
- принцип «data-миграции не отбирают строки по спискам, которые где-то
|
||||
обрезаются» (миграция `00007` отбирает по обрезаемому на 32
|
||||
`uncovered_sections`; для неё дефект пустой — HAE шлёт одну секцию за
|
||||
доставку, — но следующая покрытая секция унаследует слепую зону);
|
||||
- длина очереди `pending` в `/stats` **и без WARN**: после миграции, переводящей
|
||||
доставки в `pending`, отставание по конструкции не WARN-ится (`startupDone`),
|
||||
и бэклог идёт молча при зелёном `/healthz` — строка уходит в
|
||||
[наблюдаемость](stats-nablyudaemost.md).
|
||||
|
||||
## Кандидаты в конвенции
|
||||
|
||||
- текст ошибки разбора не содержит значений из тела — только тип токена и
|
||||
смещение;
|
||||
- тест перестановок правила слияния обязан включать версию с содержимым, равным
|
||||
одной из уже присланных: тест трёх версий с разными хешами ветку равенства не
|
||||
посещает ни разу.
|
||||
|
||||
Готово, когда все семь оракулов зелены, живут обычными тестами пакетов, а
|
||||
`task verify:archive` сходится.
|
||||
|
||||
Связано: `internal/canon`, `internal/store/entity.go`, `internal/hae/entity.go`,
|
||||
`docs/review-journal.md` (пропуск проходов на чекпоинте — отклонение процесса,
|
||||
ему там место).
|
||||
Reference in New Issue
Block a user