- эксперимент против поведения stdlib, драйвера и PRAGMA — обязательный вопрос 8 у ops, с прецедентом «-1 >= -1» и оговоркой про data_version - «не изобретаем ли то, что уже есть в библиотеке» — вопрос 1 у architecture, с перечнем конструкций stdlib - потеряна поимённая сверка с Effective Go и стайлгайдами: класс обратимый, но теперь не покрыт вовсе — записано в журнал ревью - профили: quick 4, standard 6, deep 7–8, design 3
21 KiB
Журнал проскочивших дефектов
Сюда попадает дефект, который прошёл ревью и всплыл позже. Записывается сразу, а не ретроспективно: со временем теряется не сам факт, а причина непоймания — единственное, ради чего журнал существует.
Реализованные задачи, находки ревью и решения сюда не пишутся: у них есть коммит, спека и беклог. Здесь только промахи конвейера.
Форма записи:
## 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 чек-листа равен длине чек-листа, а этих пунктов в чек-листах нет и быть не может. - Что меняем: отчёт ревью обязан перечислять запущенные проходы поимённо и с исходом, а оркестратор задачи — сверять этот перечень с составом профиля до того, как коммитить; непущенный проход идёт в границы покрытия строкой «не запускался», а не отсутствует. Правилом линтера это не выражается, автоматической проверки нет — но пропуск, названный в отчёте, стоит одной строки, а пропуск молчащий стоил семи находок и отдельной задачи на их дозакрытие. Состав проходов и профилей при этом не трогаем: они сработали ровно так, как задуманы, — их просто не позвали.
2026-08-02 — тест на утечку значений в лог краснел от хода часов
- Где:
internal/fold/log_test.go,TestFoldНесравнимыеНаборыДаютWarn - Симптом: гейт задачи про цену читающего маршрута покраснел на чужом
тесте: «в логе оказалось значение точки "5.1"». Значения в логе не было —
подстрока нашлась в метке времени записи (
…T20:23:35.193…содержит5.1). Повторный прогон зелёный. - Причина: утверждение искало секрет в сыром буфере записи, а буфер
содержит служебное поле
timeс долями секунды. Вероятность совпадения для двухсимвольного числа с точкой — около процента на прогон, то есть тест флаки по построению, и краснеет он у того, кто мимо проходил. - Почему не поймали: шаг
flakyгейта гоняет набор дважды подряд — вероятность поймать однопроцентную флаки за два прогона мала, а сам тест выглядит образцовым: он проверяет ровно тот инвариант, который проекту дороже всего («данные о здоровье чувствительнее токенов»). Ни один проход ревью не смотрит на тесты чужих задач. - Что меняем: правило в conventions.md — проверка «в логе
нет значения» разбирает запись и выбрасывает
time, а не ищет в сыром буфере. Гейт не трогаем: два прогона против однопроцентной флаки не помогут, а десять стоили бы дороже самой находки.
2026-08-02 — состав конвейера сужен: 11 проходов до 6–9
Не промах, а решение по итогам пяти задач подряд. Записано здесь, потому что именно здесь лежит цена непоймания: если что-то теперь проскочит, первый вопрос будет «не тот ли это класс, который мы перестали проверять».
- Повод: профиль
deepстоял на всех пяти задачах сессии и гонял 11 проходов на коде плюс 4 на дизайне — порядка полутора миллионов токенов на задачу. Ревью, а не написание кода, стало основной статьёй расхода. - На чём основано: поимённая атрибуция находок надёжна только для
дозапуска трёх проходов на
f8200f7— там оркестратор запускал их сам. В двух циклах, которые вели сабагенты, находки перечислены без указания прохода, и это ограничение вывода названо здесь честно. - Что убрано и почему:
negative— удалён. За сессию ни одной именной находки; блокер про откат релиза он нашёл дублем сops, то есть заплатил триажу дедупликацией. Два его живых вопроса переселены: «хватит ли сигналов владельцу, когда поток оборвётся ночью» — вops, вопрос 7; «что опытный человек отсюда удалил бы» — вarchitecture, вопрос 5.rubric— только вdesign. Его же 14 свойств из design-прогона ложатся приёмочными критериями вtasks.md; судить код по критерию, под который он писался, — корреляция по построению.reimpl— по триггеру «новое правило слияния, идентичности или разбора». Самый дорогой проход конвейера; единственный раз, когда триаж назвал его отсутствие дырой покрытия, — это была задача с новым правилом слияния сущностей, то есть ровно триггерный случай.
- Что переставлено, и это важнее сокращения:
adversaryиopsбыли вdeep-только, аstandardгонял четыре самых слабых generative-прохода. То есть профиль, которым закрывается большинство задач, запускал ровно тех, кто ничего не принёс, и не запускал тех, кто принёс почти всё. Оба переехали вstandard. Это одновременно дешевле и качественнее. - Что чуть не убрали по ошибке:
idiomбыл в списке на удаление как «вкусовщина». Отменено фактом: в задаче про цену читающего маршрута он нашёл, что-1 >= -1читается как «журнал разобран целиком», и воспроизвёл — 1492 тика из 5502. Плюс три эксперимента на дизайнеrazbor-metrik-v-obekty. Вывод, который стоит помнить: этот проход зарабатывает экспериментами против поведения stdlib и драйвера, а не цитатами из гайдов, — и потому у него есть внешний оракул. Оценка «не всплыл поимённо ни разу» была верна по имевшимся данным и неверна по существу. - Что мы сознательно перестали проверять: класс «чего нет в зрелой
реализации такого узла» вне профиля
design, и «пять вопросов второго инженера» как отдельная постановка. Обратимость этого класса высокая: он портит форму кода и полноту наблюдаемости, а не данные. Если проскочит дефект этого класса — запись сюда и пересмотр решения. - Побочная выгода, ради которой стоило резать отдельно: реестр из 6–9 проходов сверяется взглядом. Промах 2026-08-02 (запись выше) был молчащим пропуском трёх проходов из одиннадцати; на коротком списке требование «перечисли запущенные проходы поимённо и с исходом» наконец выполнимо.
2026-08-02 — idiom тоже упразднён, класс переселён
Решение владельца, принятое после того, как оркестратор привёл доводы против удаления (находка на чекпойнте WAL, воспроизведённая: 1492 тика из 5502) и они были выслушаны. Записано отдельной строкой, потому что довод был, и если класс проскочит — искать надо здесь.
- Что переселено, а не выброшено. Проход зарабатывал экспериментами против
поведения stdlib и драйвера, и именно эта способность перенесена поимённо:
- «поведение библиотеки, драйвера и
PRAGMAизмеряется, а не вычитывается из документации; что возвращается в вырожденном случае и отличим ли этот ответ от штатного» — вops, обязательный вопрос 8, вместе с прецедентом-1 >= -1и оговоркой проdata_versionкак свойство соединения; - «не изобретаем ли то, что уже есть в библиотеке» — в
architecture, вопрос 1, с перечнем конструкций stdlib: своя абстракция, повторяющая форму существующей, — находка того же класса, что и второй способ делать одно и то же.
- «поведение библиотеки, драйвера и
- Что действительно потеряно. Поимённая сверка с положениями Effective Go,
Go Code Review Comments, Go Proverbs и стайлгайдов Uber и Google. Различение
«идиоматично» против «распространено» больше не задаётся никем:
architectureспрашивает про форму решения,ops— про поведение под нагрузкой, но ни один не спросит «в Go так не пишут». Класс обратимый — портит форму кода, не данные, — но он теперь не покрыт вовсе, и это надо признавать в границах покрытия, а не считать проверенным. - Итог по конвейеру:
quick4,standard6,deep7–8,design3. Было 11 на коде и 4 на дизайне.