- Правило покрытия получило второй разряд (условный, как у точек), запрет вырождения формы и счёт содержательных элементов ряда: скелет из скаляров и ряд из null больше не затирают маршрут. Победитель внутри доставки стал функцией множества версий — общим помощником с точками, — а провенанс поднимается и при совпавшем хеше, иначе отложенная доставка возвращала витрину к прежнему содержимому. - Одно поле не того типа больше не уносит сущность, а пропуски видны в учётной записи доставки (миграция 00008, NULL = «не измерялось»); каноническая форма считается один раз и вне транзакции; откат бинаря поверх новой схемы отказывает на старте; текст ошибки разбора не несёт значений из тела. - Ревью кода профилем deep (девять проходов) нашло две регрессии и обе закрыты: безусловный второй разряд запирал законный досчёт навсегда, а выбор победителя был квадратичен по числу присланных версий одного ключа.
113 lines
11 KiB
Markdown
113 lines
11 KiB
Markdown
# Журнал проскочивших дефектов
|
|
|
|
Сюда попадает дефект, который **прошёл ревью и всплыл позже**. Записывается
|
|
сразу, а не ретроспективно: со временем теряется не сам факт, а причина
|
|
непоймания — единственное, ради чего журнал существует.
|
|
|
|
Реализованные задачи, находки ревью и решения сюда не пишутся: у них есть
|
|
коммит, спека и беклог. Здесь только промахи конвейера.
|
|
|
|
Форма записи:
|
|
|
|
```
|
|
## 2026-08-01 — <краткое последствие>
|
|
|
|
- **Где:** internal/store/bucket.go:120
|
|
- **Симптом:** <как обнаружилось, кем и когда>
|
|
- **Почему не поймали:** <какой проход обязан был найти и что ему помешало>
|
|
- **Что меняем:** <правило прохода, шаг гейта, конвенция — либо «ничего, цена
|
|
поимки выше цены дефекта»>
|
|
```
|
|
|
|
Последний пункт важнее остальных. Вывод «ничего не меняем» — законный исход:
|
|
не всякий дефект стоит того, чтобы усложнять ради него ревью каждой задачи.
|
|
|
|
---
|
|
|
|
## 2026-08-01 — свёртка не воспроизводилась при пересборке журнала
|
|
|
|
- **Где:** `internal/store/delivery.go`, `LastDerivedLayer`
|
|
- **Симптом:** прогон живого архива (99 доставок) вторым проходом дал 1742
|
|
объекта вместо 1737, а координат сна 182 вместо 174. Нашёл тест сходимости
|
|
на шаге apply — не ревью.
|
|
- **Причина:** доставка без плотных метрик наследует слой автоматизации.
|
|
Запрос брал последний выведенный слой **вообще**, а не последний до этой
|
|
доставки, поэтому при пересборке доставка наследовала слой «из будущего».
|
|
Свёртка переставала быть функцией от префикса журнала.
|
|
- **Почему не поймали:** формулировка «наследует последний надёжно выведенный
|
|
слой той же автоматизации» звучит однозначно и в спеке, и в дизайне —
|
|
пропущенное слово «предшествующей» не выглядит пропуском. Проходы `specs` и
|
|
`architecture` сверяли код со спекой и понятиями, а инвариант
|
|
«`import + replay` даёт то же состояние» ни один из них не проверял на
|
|
конкретном правиле: он записан в архитектуре как свойство системы, а не как
|
|
критерий для каждого узла, читающего состояние.
|
|
- **Что меняем:** в рубрику `healthlog-review-rubric` и в проход `ops` — вопрос
|
|
«читает ли узел состояние, которое сам же меняет, и остаётся ли он функцией
|
|
от префикса журнала». Дешевле правила: любой запрос к `delivery` из свёртки
|
|
обязан иметь границу по `received_at` разбираемой доставки. Тест сходимости
|
|
на живом архиве (`internal/fold/replay_test.go`) остаётся постоянным —
|
|
именно он это поймал.
|
|
|
|
## 2026-08-02 — прогон живого архива был красным и об этом никто не знал
|
|
|
|
- **Где:** `internal/fold/replay_test.go` (перенесён в `internal/replay/archive_test.go`)
|
|
- **Симптом:** первый же запуск `task verify:archive` в задаче про пересборку
|
|
дал `координат sleep_analysis 222, измерено 174`. Проверено прогоном прежней
|
|
редакции теста на том же архиве: она даёт ровно те же 222, 2049 объектов и тот
|
|
же отпечаток — значит тест покраснел не от изменений задачи, а сам, когда
|
|
архив дорос с 94 доставок до 116.
|
|
- **Причина:** утверждение было пришпилено к **числу, производному от корпуса**
|
|
(174 координаты сна). Корпус растёт с каждой доставкой, то есть константа
|
|
протухает по расписанию телефона. Проверяемое свойство при этом другое и от
|
|
размера корпуса не зависит: ключ по интервалу не схлопывает записи до ключа
|
|
по метке (222 координаты против 218 меток).
|
|
- **Почему не поймали:** прогон живого архива намеренно не входит в `task gate`
|
|
(минута работы, данные есть только на этой машине). У проверки, которую гейт
|
|
не гоняет, краснота никому не видна — она обнаруживается только следующей
|
|
задачей, которая до неё дотянется. Ни один проход ревью прогон не запускал:
|
|
проходы читают код, а не гоняют опциональные команды.
|
|
- **Что меняем:** утверждение переписано на само свойство (координат строго
|
|
больше, чем различных меток), измеренные числа остались в `t.Logf`. Правило
|
|
общее и годится в конвенции: **в проверке на живом корпусе нельзя утверждать
|
|
число, производное от размера корпуса** — утверждать надо инвариант, а число
|
|
печатать. Гейт при этом не трогаем: цена ежедневной минуты выше цены такой
|
|
протухшей константы, а после этой задачи прогон стал ещё и единственным, кто
|
|
проверяет настоящий проигрыватель журнала.
|
|
|
|
## 2026-08-02 — чекпоинт кода прошёл без трёх проходов, и ровно они нашли всё
|
|
|
|
- **Где:** конвейер, а не код: коммит `f8200f7` («тренировки и записи с
|
|
собственным `id`»), шаг 7 скилла `healthlog-task-pipeline`, профиль `deep`.
|
|
- **Симптом:** изменение было закоммичено и заархивировано как прошедшее ревью.
|
|
Дозапуск трёх пропущенных проходов на **уже закоммиченном** коде дал девять
|
|
причин, семь из которых пошли в работу с прогнанными оракулами: скелет из
|
|
`null` затирает маршрут молча и необратимо; одно поле не той формы уносит
|
|
тренировку, а доставка при этом числится разобранной; откат бинаря поверх
|
|
новой схемы стартует без слова; победитель внутри доставки зависит от порядка
|
|
элементов на проводе; провенанс устаревает на каждой повторной присылке;
|
|
канонизация идёт внутри транзакции вопреки собственному комментарию (768 МиБ
|
|
пика, 5.019 с удержания блокировки); тело в 8 МиБ целиком уезжает в текст
|
|
ошибки и оттуда в `WARN`.
|
|
- **Причина:** сабагент, проводивший задачу, на чекпоинте кода запустил не все
|
|
проходы профиля `deep` — не отработали `adversary`, `ops` и архитектурный.
|
|
Отчёт триажа при этом был выпущен и выглядел полным: он агрегирует то, что
|
|
ему подали, и о непоступивших проходах не знает. Секция границ покрытия
|
|
обязана была это назвать, но она заполняется тем же триажем — то есть
|
|
единственный, кто мог заметить пропуск, узнаёт о нём из того же источника,
|
|
который его допустил.
|
|
- **Почему не поймали:** пропуск прохода **не отличим от прохода без находок**.
|
|
Гейт зелёный, спеки сошлись, applicative-проходы отработали — снаружи это
|
|
выглядит как чистое ревью. Все семь находок принадлежат ровно тем классам,
|
|
которые applicative-проходы не достают по построению: враждебно
|
|
сконструированный вход (`adversary`), поведение под откатом и конкуренцией
|
|
(`ops`), второй способ делать уже сделанное (архитектура). Recall чек-листа
|
|
равен длине чек-листа, а этих пунктов в чек-листах нет и быть не может.
|
|
- **Что меняем:** отчёт ревью обязан перечислять запущенные проходы **поимённо
|
|
и с исходом**, а оркестратор задачи — сверять этот перечень с составом
|
|
профиля до того, как коммитить; непущенный проход идёт в границы покрытия
|
|
строкой «не запускался», а не отсутствует. Правилом линтера это не
|
|
выражается, автоматической проверки нет — но пропуск, названный в отчёте,
|
|
стоит одной строки, а пропуск молчащий стоил семи находок и отдельной задачи
|
|
на их дозакрытие. Состав проходов и профилей при этом не трогаем: они
|
|
сработали ровно так, как задуманы, — их просто не позвали.
|