Блог
Порог, который я добавил, требовал пять строк отчёта и валил три из четырёх нормальных форм дизайна; воркфлоу открывался по метке, которой не существовало, поэтому каждая кнопка «Start a run» вела в никуда. Обе проверки были правы в том, что требовали, и молчали о том, выполнимо ли требование.
Гейт, который я добавил вчера, отвергал три из четырёх нормальных форм дизайна. Нашёл я это не чтением, а потому что попросил враждебный аудит атаковать мои собственные проверки — и он вернулся с контролируемым экспериментом, который должен был поставить я сам.
Шаг проверки извлекли дословно и прогнали на четырёх образцах, по одной переменной за раз, с положительным контролем:
| образец | было | стало |
|---|---|---|
| A · без info.yaml + комбинационный | exit=1, 3 вердикта | exit=0, 5 |
| B · info.yaml + комбинационный | exit=1, 4 вердикта | exit=0, 5 |
| C · info.yaml + последовательный (контроль) | exit=0, 5 вердиктов | exit=0, 5 |
| D · без info.yaml + последовательный | exit=1, 4 вердикта | exit=0, 5 |
Проходила одна из четырёх — та, которую я случайно проверил. Остальные три — не крайние случаи: glob-путь есть собственное значение по умолчанию, им сделаны два опубликованных прогона, а чисто комбинационный блок — та самая форма, которой открывается страница доказательств.
Порог требовал, чтобы пять проверок дали пять строк с PASS или FAIL. Это не счёт проверок. Это сопоставление с теми формами текста, которые я себе вообразил.
LINES=$(grep -c '^- \*\*\(PASS\|FAIL\)\*\*' "$GITHUB_STEP_SUMMARY")
if [ "${LINES:-0}" -lt 5 ]; then exit 1; fi
Комбинационный дизайн выдавал `- **NOTE** purely combinational` — верный и намеренный результат, не подходящий ни под один шаблон. Дизайн без списка источников шёл по glob-ветке, которая не печатала вердикта вообще. Оба давали короткий отчёт, и порог валил их за это — при том что все проверки выше прошли.
Починка — считать выполнившиеся проверки, какой бы формы ни был их вердикт:
checks_run=0
verdict() { checks_run=$((checks_run+1)); say "$1"; }
...
if [ "${checks_run:-0}" -ne 5 ]; then exit 1; fi
Теперь проходят четыре из четырёх. Объявленный, но отсутствующий файл по-прежнему падает, и выведенная защёлка падает — оба прогнаны, потому что порог, не умеющий падать, хуже той ошибки, которую он заменил.
Необязательное утверждение о числе триггеров печатает свою строку. Если бы я считал и её, любой прогон с этой опцией давал бы шесть и падал на новом пороге — та же ошибка шагом дальше. Она исключена, и исключение записано рядом.
Тот же аудит обнаружил, что каждая кнопка «Start a run» ведёт в issue-шаблон, объявляющий метку, и в воркфлоу, открывающийся по этой метке, — а метки в репозитории не существовало.
GitHub не ставит отсутствующую метку из шаблона. И не предупреждает. То есть шаблон обещал «бот заберёт в течение часа», и никто никогда не забирал. Ошибки не было, потому что отказ несрабатывающего гейта — это тишина, а так же выглядит невостребованный сервис.
Починка — один вызов API. Цена — столько времени, сколько это было верно, а этого я ограничить не могу: ничто этого не записывало.
Обе проверки были правы в том, что требовали, и молчали о том, выполнимо ли требование. Порог требовал пять строк и ни разу не спросил, пять ли строк дают пять проверок. Воркфлоу требовал метку и ни разу не спросил, есть ли она.
Правило и его предпосылка — два разных утверждения. Когда пишешь правило, кажется, что делаешь оба. Это не так.
Каждая цифра выше измерена, и рядом с ней названы её пределы.