Узнайте, как проверить, что ваши guard-рейлы действительно работают: тест на ложный отказ, heartbeat и чек-листы для ревьюеров и мониторинга.
Две недели назад я насчитал 204 guard-рейла в своих репозиториях и обнаружил, что 89% из них никогда не показывали, что могут упасть. Я исправил это для партии проверок. А сегодня вечером я подал отчёт о потере данных — 1000 файлов, которые на самом деле не терялись, — и никто, включая меня, не имел числа, чтобы это проверить.
В прошлый раз я писал, что из автоматических проверок, которые делают выводы, только одна из девяти могла доказать, что способна упасть. Это началось с твита @dev_michael: «ИИ не сделал меня худшим кодером, он сделал меня худшим ревьюером».
Поэтому я сделал очевидную следующую работу: заставил партию guard-рейлов доказать, что они могут падать. Это исправление реально, и я опишу его ниже, потому что оно стоит одного вечера и работает.
Но этого оказалось недостаточно, и я узнал об этом унизительным способом.
Каждый ревьюер — ручная проверка, LLM-судья, второй агент, оценивающий первого — получает один заведомо плохой случай, пропущенный через живой путь. Не юнит-тест рядом с пайплайном, а та же точка входа, которую использует реальная работа.
Наш бенчмарк-харнесс прогоняет три гейта для каждого случая, и случай, пропустивший любой из них, не запускается вовсе:
Третий гейт — самый ценный. Он ловил реальные поломки, включая дважды — в guard-рейлах, написанных на той же неделе для ловли именно этого класса проблем.
На этой неделе я продвинулся дальше, потому что харнесс настолько хорош, насколько хороши кейсы в нём, а придумывание кейсов — это то, где у всех заканчивается воображение.
@shreyasht убил свой проект по оптимизации токенов, обнаружив, что его лучший результат — 97% экономии — пришёл из запуска, который вообще не делал работы: агент задал уточняющий вопрос, остановился, и дашборд короновал его. Метрика, которая не наказывает за бездействие, в итоге его поощряет. Его фикс — одно предложение: измеряй за решённую задачу, а не за задачу вообще.
Проблема воображения имеет дешёвый ответ, и он лежал в нашей базе данных: история инцидентов — это тестовый набор.
Мы взяли десять реальных записанных сбоев — тех, где есть поле «что пошло не так», написанное раздражённым человеком, — и попросили модель превратить каждый в исполняемую проверку. Десять уроков — десять исполняемых кейсов, тридцать гейтов, все тридцать прошли. Ноль отброшенных.
Одна вещь удивила меня: первая попытка провалила фильтр внутренностей, и провалилась потому, что пример, который мы передали модели, содержал название продукта в комментарии. Модель скопировала его точно. Фильтр поймал это. Guard-рейл, который я написал для паранойи о человеческой небрежности, поймал машину, которая была послушной вместо этого.
@wrobeltomasz описал эту же дисциплину независимо в комментариях, пока черновик лежал неопубликованным: определить проверки, затем запустить их «в режиме симуляции, чтобы подтвердить, что они действительно могут реагировать на невалидный ввод» — перенося верификацию «из статистики в README в реальную устойчивость системы». Два человека, которые никогда не встречались, — один и тот же гейт, одна и та же причина. Обычно это значит, что паттерн реален, а не является личной причудой.
Вот что доказывает всё вышесказанное: в день, когда я подключил это, тот ревьюер мог сказать «нет».
Это ничего не говорит о сегодняшнем дне.
@mk023 сказал корректирующую фразу в треде под моим прошлым постом и разрешил процитировать её здесь:
«Не просто тестируй, что guard может упасть — тестируй, что он всё ещё охраняет то, что ты думаешь, он охраняет».
Это завершает модель. Зелёная проверка может лгать тремя ортогональными способами: фальсифицируемость — может ли она вообще стать красной; живучесть — может ли она стать красной против сегодняшней системы; и цель — защищает ли она всё ещё ту границу, которая важна. Часть первая дала мне только первое. Фраза Марко — это два других, и остальная часть поста — это то, как я на собственном опыте убедился, что он прав.
@james_anderson_h в треде об ИИ-воркспейсах, которые поставляются с «guard-рейлами — гейтами одобрения, аудит-трейлом, вторым агентом, проверяющим первого», сформулировал проблему лучше, чем мой вопрос: большинство инструментов дают вам аудит-лог, а не доказательство, что вето всё ещё срабатывает.
«Проверка, которую никто никогда не видел падающей, неотличима от проверки, которая одобряет всё. Две дают идентичные логи вплоть до дня, когда штамп пропустит то, что причинит вам боль».
Мой ответ ему был маленькой вещью, которую я теперь считаю самой полезной идеей в этом посте: heartbeat вето.
Покажите одну дату. «Последний раз, когда этот ревьюер отказал: 2 дня назад». Первоклассно, видимо, рядом с числом аптайма. Заведомо плохой случай запускается по расписанию; если дата последнего отказа стареет за расписание, сама устарелость — это тревога. Никакого копания в логах, никакого доверия дашборду вендора, никакой археологии. Одна дата, которую любой прочитает с одного взгляда — и отсутствие свежего красного наконец выглядит так, как оно есть.
Джеймс назвал принцип под этим лучше, чем я: «Тишина и здоровье выглядят одинаково, если только вы намеренно не создадите состояние „давно не проверялось“». Это весь баг в одной строке. Большинство систем моделируют pass и fail и ничего больше. Вето, которое тихо умерло, читается ровно как вето, которому просто нечего было отклонять. Heartbeat — это недостающее третье состояние, носимое снаружи, где аудитор увидит его без вашего разрешения.
Я собирался опубликовать раздел выше как концовку. Затем я провёл вечер, доказывая точку на себе, и история лучше теории.
Я пошёл проверить долгоиграющую задачу сбора данных, распределённую на две машины. Я подключился к одной, поискал рабочую директорию — не нашёл, поискал процесс — не нашёл. Поэтому я сообщил: данные потеряны, примерно тысяча собранных элементов пропала, и я подал тикет с этим утверждением.
Я подключился не к той машине.
Нумерация, которую я использовал для выбора, не означает того, что я предполагал, — факт, записанный в моих собственных инструкциях к проекту, жирным шрифтом, с примером. Я читал это раньше. Всё равно сделал.
Ничего не потерялось. Сбор данных лежал там, где должен был быть, и вторая задача тихо работала на нём в тот самый момент.
Теперь важная часть, потому что «я совершил глупую ошибку» — это не статья.
Почему ложный отчёт прожил так долго?
Потому что не было ничего, что ему противоречило. Ни дашборда, ни счётчика, ни файла с тремя числами. Чтобы проверить моё утверждение, нужно было зайти на две машины и вручную пересчитать файлы — именно поэтому никто не делал этого и за два дня до этого.
И когда я наконец пересчитал, числа обнажили то, чего никто не замечал: две машины работали с одним списком с разных концов, встретились в середине дни назад и с тех пор повторно собрали 332 элемента, которые уже были у обеих.
Не сломано. Не тревожно. Просто тихо расточительно — в системе без видимого числа, перед которым можно быть тихо расточительным.
Зелёная проверка, которую никто не может оспорить, и красная тревога, которую никто не может оспорить, — это один и тот же баг в разной одежде:
Оба происходят от одной недостающей вещи: числа, которое посторонний может прочитать без вашего участия.
Heartbeat вето — это число для ревьюера. Три счётчика в файле — это число для фоновой задачи. Ни то, ни другое не гениально. Оба отсутствуют почти везде, включая — до этой недели — кодбазу человека, который пишет об этом профессионально.
@bert_sk_shim_cb93b1 попал в тот же класс с противоположной стороны, пока это писалось, и назвал часть, которую я упустил. Его проверка логина обращалась к эндпоинту, ограниченному другим методом аутентификации, поэтому она возвращала «нет имени пользователя» независимо от всего — всегда негатив вместо всегда зелёного. Затем фраза, которая переосмыслила весь этот пост для меня:
«Красный результат обычно лечат, а не расследуют, поэтому я попросил кого-то снова залогиниться, что было ненужно, и если бы тайминг был чуть другим, я бы записал это как фикс и оставил сломанную проверку».
Это асимметрия. Зелёный приглашает к самоуспокоенности, но красный приглашает к действию — а действие ощущается как решение. Сломанная всегда-красная проверка получает обходной манёвр, выполненный перед ней, и обходной манёвр получает признание. Он чуть не записал ненужный логин как фикс и оставил инструмент, который ему лгал.
Всегда-негатив прячется лучше, чем всегда-зелёный, и это направление почти никто не отслеживает.
Его фикс обобщается дальше, чем обе наши истории: разделите наличие и значение. Операционная версия, к которой я пришёл с тех пор, — одна строка: печатайте то, что вы прочитали, прежде чем печатать то, что вы заключили.
«Нет имени пользователя на /whoami (схема аутентификации B)» — это баг-репорт. «Не залогинен» — это слух со статус-кодом. Моя ложная тревога умерла бы за тридцать секунд, если бы моя собственная проверка сказала, какая машина ответила, а не только то, что я заключил об этом.
@pm25coder применил тот же ход на другом уровне, пока я писал: их харнесс теперь добавляет каждое событие тихой предохранительной сети в append-only файл, который переживает перезапуски и ротацию логов, так что «срабатывала ли предохранительная сеть когда-либо?» стало поиском, а не археологией. Доказательство, которое переживает процесс, его создавший. Это вся идея, и её стоит украсть.
И Джон Грин применяет самую жёсткую версию этой дисциплины к оценкам моделей: он перезапустил свой экзамен пять раз и наблюдал, как его опубликованный победитель испарился — поведение, решившее ранжирование, воспроизвелось ноль раз из пяти. Его фраза остаётся со мной:
«Экзамен снова поймал своего автора — не в ключе ответов, не в оценщике, а в том, как уверенно я читал один прогон».
Вставьте рядом с любым пайплайном, который утверждает, что имеет guard-рейлы:
60-секундная версия, если вы ничего больше не сделаете: откройте самую старую зелёную проверку в вашем пайплайне и спросите, когда она последний раз становилась красной. Если ответ требует поиска по логам — вы нашли одну. Если вы вообще не можете ответить — вы нашли большую.
Три гейта доказывают, что проверка могла упасть на случае, который кто-то придумал. Они ничего не говорят о случаях, которые никто не придумал, и сбор собственной истории инцидентов — который я рекомендую — имеет встроенное смещение: эти случаи происходят из той же системы, которая породила сбои, поэтому они могут быть систематически проще реальности. Если ваши собранные случаи проходят с заметно большей частотой, чем придуманные, это находка, а не победа. Сообщайте о них отдельно.
У heartbeat тоже есть режим отказа: запланированный заведомо плохой случай может стать ритуалом, который всегда проходит, и тогда свежая дата успокаивает, а не информирует. Честное смягчение — ротация: периодически меняйте заведомо плохой случай. И отделяйте наличие от значения — всегда печатайте, что вы прочитали, прежде чем делать вывод. Это превращает ложную тревогу в баг-репорт, а не в слух.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →