Гейты качества при сборке сайта: четыре проверки, которые не пускают поломку в прод

Автоматическая приёмка сайта: 4 гейта с кодом выхода, 12 этапов, 8 точек согласия. 162 страницы, 204 цели, 0 мёртвых ссылок, контраст 1,13:1 и PNG на 13,9 МБ — что ловит автомат, а что только человек.

Коротко: гейт качества — это не отчёт о проверке, а скрипт, который возвращает ненулевой код выхода и тем самым запрещает публикацию. Разница между ними практическая: семь проверочных скриптов, собранных в студии раньше, печатали красивый лог и оставляли решение человеку, то есть гейтов фактически не было. Сейчас в конвейере четыре гейта (мёртвые ссылки, покрытие локалей, протечка языка, контраст по computed-style), двенадцать этапов сборки и восемь точек, где нужен явный «покажи мне». На живом мультиязычном прогоне это выглядит так: 162 страницы, 204 уникальные цели, 0 мёртвых ссылок, 0 протечек локали, 0 контраст-нарушений — и при этом гейты поймали только 6 из 9 реальных дефектов. Три самых дорогих нашели человек. Ниже — что автоматическая приёмка ловит, что не ловит никогда и как по этому принимать работу у подрядчика.

Чем гейт отличается от проверки, после которой «самим решать»

Проверка выдаёт текст: «найдено 58 подозрений». Гейт выдаёт число, которое интерпретируется однозначно: 0 — прошло, 1 — упало, публикация запрещена, 2 — часть проверок пропущена (например, гейт доступности требует установленный браузерный движок и не может считаться зелёным, если его не запускали). Именно кодом выхода проверяется, что гейт вообще работает: запуск выборочный (run.mjs links a11y), результат складывается в qa/summary.json, а этап конвейера не считается пройденным, пока нет его артефакта по указанному в договоре пути.

Второе отличие — гейт проверяют в обе стороны. Подложенный в фикстуру текст на чужом языке должен быть найден и дать exit 1. Проверка, которая всегда зелёная, опаснее отсутствия проверки: она легитимизирует релиз. Отсюда правило приёмки, которое мы формулируем так: сначала негативный тест, потом доверие к позитивному.

Сколько этапов в конвейере и где на каждом нужен человек

Двенадцать: интейк, разведка по источникам, аудит первоисточника, ТЗ, дизайн-тезис, макет, дизайн-контракт, сборка, гейты, отчёт, публикация, ретро. Из них восемь требуют явного human-in-the-loop — «покажи мне» на разведке, аудите, ТЗ, дизайн-тезисе, макете, дизайн-контракте, отчёте, и отдельное «да» на публикации. У каждого этапа есть артефакт, по которому он проверяется: sources.md с URL и датами, inventory/ с byte-exact снимком оригинала, TZ.md, один вертикальный HTML-артборд макета в 1440 и в 390, DESIGN.md с токенами, извлечёнными из утверждённого макета, qa/ с логами гейтов, отчёт в формате P0/P1/P2.

Есть и контринтуитивное правило автоматизации: этап выносится в отдельного агента только после двух прогонов подряд без единой правки человека — артефакт принят с первого предъявления, гейты зелёные, в журнале по этому этапу пусто. Если правки есть каждый прогон, автоматизировать нельзя: кодируется наш шум, а не устойчивый процесс.

Двенадцать этапов конвейера сборки сайта: восемь точек с показом человеку, девятый этап — гейты с кодом выхода, одиннадцатый — публикация только после явного да

Что именно ловят четыре гейта на 162 страницах

Ниже — реальные прогоны двух проектов студии: мультиязычный сайт на шести локалях (162 индексируемых URL) и миграция legacy-сайта (104 страницы). Порог один для всех: ненулевой код выхода запрещает публикацию.

ГейтЧто ловитЧто измеряетПрогон на 162 стр.Прогон на 104 стр.
linksМёртвые и недоступные внутренние цели, ссылки в чужую локальстраницы / уникальные цели / проблемы162 / 204 / 0104 / 151 / 0
i18n-coverageСтрока интерфейса переведена не во всех локаляхL-объекты / неполные30 / 0514 / 0
locale-leakСимволы и слова чужого языка в разметке страницыстраницы / протечки162 / 0104 / 0
a11yКонтраст по computed-style, видимый фокус, reduced-motionстраницы / нарушения162 / 0— (движок не поднят)

Значение «0» в колонке нарушений — это результат починки, а не отсутствие проверок. В контрасте первичный прогон дал две пары с реальными провалами: подпись на светлой плашке 2,98:1 и прозрачная кнопка 1,13:1 при требуемом пороге 4,5:1 (WCAG 1.4.3). Такие дефекты не видны глазу на дизайне с контрастным фоном, но видны computed-style: гейт считает цвет после наложения слоёв, а не значение из токена.

Почему ложные срабатывания — это настройка, а не провал

Первый прогон гейта протечки языка дал 58 срабатываний на одной турецкой странице. Причина оказалась не в контенте, а в наборе маркеров: немецкий список включал ö и ü, которые легально существуют в турецком алфавите. Наборы символов сделали взаимоисключающими — для турецкого ı, ğ, ş, для немецкого ä, ß. Цена решения честная и зафиксирована в журнале: слова, где из «турецкого» только ç, теперь не ловятся. Шум стоил дороже сигнала, а ложноположительный гейт, который валят руками один раз, после этого перестают читать.

Полезный побочный эффект: разбор ложных срабатываний — это единственный способ услышать, что порог настроен неверно. Если гейт никогда не краснеет на чистом проекте и всегда краснеет на грязном, он ничего не проверяет.

Почему «пропущено» — это не «пройдено»

Код выхода 2 придуман не для красоты: он означает, что часть проверок вообще не запускалась. Так, гейт доступности требует установленный браузерный движок в каждом проекте отдельно, и на одном из двух прогонов в таблице выше он не снимался — поэтому там прочерк, а не ноль. Отличить «зелёный» от «не мерили» без этого кода невозможно, а зелёный отчёт без измерений — это ровно тот документ, под которым подписывают релиз.

Второй сюжет — переносимость готовых гейтов. Проверка мобильного меню когда-то была написана с зашитым русским селектором aria-label*="меню": на англоязычном проекте она молчала, потому что искала строку, которой там нет. Селектор вынесли в конфиг проекта. Третий сюжет — сохраняемость артефактов: в прежних проектах отчёты доступности генерировались скриптом, который не сохранился, и воспроизвести приёмку нечем. Отсюда правило: результат гейта лежит в папке проекта рядом с кодом, а не в переписке, — иначе через месяц никто не докажет, что проверка была.

И открытый долг самого конвейера, который честно держать в журнале: путь к набору проверок нестабилен, он живёт в папке рабочей сессии, поэтому доработки гейтов не возвращаются в мастер, а каждый новый бриф хардкодит адрес. Это тот же класс проблемы, что код, который не мержат в основную ветку: технически работает, но накопление улучшений останавливается.

Что гейты не поймают никогда

На приёмке того же мультиязычного проекта нашли девять дефектов. Шесть поймали гейты, три — только глазами и сверкой с первоисточником. И все три самых дорогих были в третьей группе:

  • Картинки-коллажи рендерились вертикальной полосой. Презентационный атрибут height="1200" перебивал aspect-ratio: 16/9, а файл на самом деле был 1600×900 — из коллажа вырезалась середина. Ни один текстовый гейт этого не видит: ссылка жива, язык тот, alt заполнен.
  • Один PNG весил 13,9 МБ (6000×3375) в сетке из двух колонок. После обработки: JPEG q82 1600×900 — 296 КБ, и каталог медиа с 18 МБ до 4,5 МБ. Файл был доступен по URL, то есть для гейта ссылок всё в порядке.
  • Цену из прайса подписали не тем товаром. В источнике стоимость относилась к одной системе, в макете — к другой, и различались они в разы. Это медицинский и ценовой факт, а по правилу статуса фактов такой не имеет права быть выводом агента — только подтверждённым из первоисточника.

Отсюда два следующих гейта, которых в наборе пока нет: media-weight (файл сверх порога примерно в 400 КБ или шире удвоенной ширины своей колонки — красный, с указанием, где именно он рендерится) и price-facts (каждое число с валютой из контентного дерева обязано существовать в прайсе и в строке FACT-MAP рядом с тем же товаром). Логика простая: гейт проверяет форму, а не достоверность — страница может говорить правильным языком, с достаточным контрастом и рабочей ссылкой, и при этом врать.

Девять дефектов приёмки: шесть поймали автоматические гейты, три самых дорогих — только человек и сверка с первоисточником

Как принимать AI-продукт, если это не сайт

Тот же принцип переносится на продукт целиком. В нашем внутреннем AI-ассистенте приёмка — часть продукта, а не финальный ритуал: скрипт функциональных проверок гоняет реальный pipeline на копии базы и даёт 16/16 PASS, визуальная приёмка в headless Chromium на мобильном окне 390×844 — 33/33 PASS, и обе сотни перепрогоняются после редизайна как регресс. Что это доказывает конкретно: 21 модуль на месте, импорт 13 контактов с повторным прогоном даёт плюс ноль дублей, 38 клиентских GET-запросов не отдают ни одного числового score (пользователь видит объяснение, а не рейтинг), при выключенной модели за весь обход — ноль ответов 5xx, вместо них честный флаг деградации. Статус проверки при этом бывает и незакрытым: знак «не проверено» означает именно это, а не «почти готово». Как это устроено в витрине — кейс с цифрами приёмки.

Сколько стоит внедрить гейты в проект

Отдельной строки «гейты» в прайсе студии нет, и выдуманную цифру здесь давать не буду. Известно другое, по факту работ: четвёртый гейт (доступность) не писали с нуля — его подняли из проверочного скрипта предыдущего проекта, добавили пороги и код выхода, это часы, а не недели. Дороже обходится не внедрение, а его отсутствие: три пропущенных дефекта выше — это 14 МБ в сетке, обрезанный контент и цена, приписанная не тому товару, то есть то, что улетело бы в прод вместе с «зелёным» отчётом. Если считать экономию на вашем проекте — пришлите URL и стек: снимем baseline и покажем, какие проверки у вас валятся прямо сейчас.

Что требовать от подрядчика, если принимаете работу сами

Список, который отличает конвейер от «мы проверили, всё нормально»:

  • Лог гейтов с кодом выхода, а не скриншот списка. Если скрипт всегда возвращает 0 — это не гейт.
  • Негативный тест. Попросите подложить битую ссылку или английский абзац в русскую страницу: проверка обязана упасть.
  • Артефакты по путям из договора: реестр источников с URL и датами, снимок оригинала, ТЗ, макет в двух брейкпоинтах, журнал гейтов.
  • Отчёт с колонкой «чем пойман» по каждому дефекту — сразу видно, где автоматизация заканчивается и начинается человек.
  • Скриншоты 1440 и 390 на каждую спорную секцию: снимок в headless-браузере дешёвый способ зафиксировать состояние, на которое потом можно сослаться.
  • Статусы фактов в контенте: подтверждённый, внешний со ссылкой, заглушка. Выдуманный — не статус, а брак.

Про то, как не слить бюджет на AI-разработку до этапа приёмки, — в статье «Как создать AI MVP и не слить бюджет»; техническую часть сборки (вес, сжатие, первый экран) разбирает аудит скорости по замерам; если нужен не разовый релиз, а выстроенный процесс приёмки — AI-консалтинг и аудит процессов.