DarkRiDDeR13 мин

Смысл поля изменился после релиза: как собрать доказательство до отката

ТестированиеAPI

Проблема после релиза может выглядеть обманчиво спокойно: consumer получает валидный ответ и всё же теряет пользовательский сценарий. Учебная картина проста: provider отдаёт 200, id, state: "active" и renewalAt: null; schema допускает этот JSON. Экран продления раньше трактовал active как готовность показать дату. В этот раз он не может завершить действие. Это пример для разбора, не отчёт об инциденте, но именно так выглядит опасный класс расхождений.

Цена спешки — откат по чужому предположению. Если в первые минуты написать «provider сломал contract», можно вернуть полезное изменение или пропустить consumer, который уже зависит от нового значения. Если написать «всё валидно», можно оставить пользователя в тупике. Нужна короткая запись доказательств: что наблюдали, что пока только интерпретируем, какая проверка ещё не была выполнена и кто вправе остановить выпуск. Тогда аварийное решение становится обратимым, а не громким.

Сначала разделите наблюдение, гипотезу и решение

Наблюдение — это то, что действительно можно показать: версия consumer, request, полученный response, версия provider, время и источник лога. В учебном пакете этих данных нет, поэтому нельзя выдумывать их под пример. Гипотеза — объяснение: provider расширил значение active, а consumer не зафиксировал более узкое условие. Решение — отдельный выбор: остановить выпуск, включить fallback, добавить явное поле или подготовить обратимую миграцию. Смешивать эти строки опасно: гипотеза легко превращается в «доказанный дефект».

Такое разделение помогает и в спокойном release review. Contract не должен храниться как безымянный JSON в репозитории. Ему нужен consumer, provider, scenario, версия и владелец следующей проверки. Provider state особенно важен: фраза «верните active» недостаточна, пока не ясно, в каком бизнес-состоянии дата обязана быть доступна. Это защищает от теста, который случайно проходит на удобных данных и не представляет нужный сценарий.

Минимальная запись разбора расхождения
СтрокаЧто записатьПример для учебного случаяЧто не следует выводить
Наблюдениеадресуемый response и версии, если они естьsynthetic active + nullчто это произошло в production
Schema resultкакая версия schema и какие правила провереныid, enum state, nullable renewalAtчто consumer поведение сохранено
Semantic expectationscenario и решение consumerэкран продления ждёт actionable dateчто правило глобально для домена
Provider verificationcontract, provider state, версия и результат исполненияnot-executed в этом sidecarчто достаточно одного JSON из лога
Release decisionвладелец, срок и безопасное действиене выпускать без нужного evidenceчто rollback уже безопасен

Соберите доказательство в правильном порядке

Первым делом сохраните исходный contract и не переписывайте его под текущий ответ. Иначе расследование теряет точку сравнения. Затем подтвердите schema match отдельным инструментом или review документa: required, enum, nullable, статус и media type. Если JSON структурно не соответствует, это уже достаточная причина исправлять форму. Если соответствует, не закрывайте задачу: переходите к тому условию, из-за которого consumer принял неверное решение.

Semantic expectation удобно записать как одно предложение с отрицательной веткой: «в scenario renewal-screen-needs-an-actionable-date consumer не показывает действие, если state: active пришёл без календарно валидной ISO-даты renewalAt позже согласованного времени отсчёта». В fixture время отсчёта фиксировано и synthetic; в реальном change его определяют владельцы сценария. Это не навязывает provider реализацию и не объявляет null недопустимым для всех запросов. Оно только делает различие видимым. Дальше владельцы продукта и API могут решить, нужна ли новая семантика, новое поле или иной сценарий интерфейса.

Исполнимый synthetic разбор не выдаёт себя за инцидент

Команда может локально прогнать небольшой fixture, чтобы убедиться, что сама запись различает форму и смысл. Он принимает только явно помеченный synthetic response, хранит в памяти пять случаев и никуда не отправляет данные. Для active + null и даты до synthetic reference time отчёт даёт schema PASS и semantic FAIL; для несуществующей даты schema тоже FAIL. Поле actualProviderVerification остаётся not-executed. Это полезно для ревью текста, но не для решения о реальном выпуске.

node web/scripts/upgrade-2023-07.mjs --verify-fixture

# PASS означает только: synthetic in-memory contract различил
# форму JSON и одну semantic expectation.
# PASS не означает: Pact запускался, consumer/provider были подняты,
# сеть наблюдалась или production-совместимость доказана.

Отдельно fixture проверяет rollback учебного плана. Он возвращает только идентификатор baseline contract и semantic rule; он не отменяет deploy, не меняет provider и не публикует verification result. Такая нарочитая неполнота важна в аварийной ситуации: если rollback нельзя описать без фразы «потом разберёмся с версиями», то он ещё не готов. Сначала называют версии и обратимые действия, затем дают команду на изменение.

Шлюз выпуска для contract change: consumer scenario создаёт версионный артефакт, provider state и точная версия provider проходят отдельную verification, после чего владелец принимает release decision. Отсутствующий результат ведёт к остановке, а не к зелёному статусу.
Схема показывает порядок доказательств и точки остановки. Она не запускает Pact, broker, CI, сеть или provider и не является фактическим журналом выпуска.

Когда остановить выпуск, а когда не трогать provider

Остановить выпуск разумно, когда неизвестна связка «какой consumer — какой contract — какая версия provider — какой результат». Это не наказание за неполный тест, а признание отсутствующего evidence. Нельзя безопасно заменять эту связку скриншотом успешного ответа или фразой «раньше работало». Если же semantic expectation оказался ошибочным и provider всегда имел более широкий смысл, изменение может быть на стороне consumer. Но это решение требует подтверждения владельца домена, а не вывода из nullable-поля.

Не стоит автоматически откатывать provider только потому, что один старый consumer не понял новое значение. Иногда безопаснее добавить новое явное поле, временно поддержать оба значения или обновить consumer раньше переключения. Выбор зависит от данных, версий клиентов, срока совместимости и возможности наблюдать переход. Contract tests помогают увидеть границу заранее; они не решают за команду, насколько дорого поддерживать два поведения.

Маршрут: симптом → причина → проверка → действие

  1. Симптом. Зафиксируйте один пользовательский эффект и один response. Не добавляйте в первую запись предположение о виновной команде.
  2. Причина. Сформулируйте гипотезу о расхождении смысла: какое поле provider расширил, а какое условие consumer трактовал уже.
  3. Проверка формы. Проверьте OAS/schema, статус, media type, required, enum и nullable. Отдельно сохраните версию документа и версию provider, если она известна.
  4. Проверка сценария. Назовите consumer, provider state, конкретный request и decision интерфейса. Затем подготовьте реальную provider verification; synthetic fixture может проверить только ясность модели.
  5. Действие. До результата выберите обратимый путь: остановка выкладки, явный fallback или сохранение старого поведения. Не объявляйте production compatibility по schema PASS.
  6. Следующий контроль. После решения сохраните result с версиями и добавьте scenario в contract inventory. Иначе следующий release снова начнёт расследование с нуля.

Как выглядит достаточный provider verification evidence

Для provider verification нужно больше, чем правильный contract file. Нужны точная версия contract, запуск provider в контролируемом состоянии, исполнение interactions, результат PASS/FAIL и связь с версией provider. В типичном Pact-процессе consumer test формирует контракт, provider проверяет его у себя, а результат публикуется для решения о выпуске. В этой цепочке каждая версия отвечает на свой вопрос; пропуск одной нельзя заполнить фразой «JSON совпал».

Перед выпуском полезно также посмотреть, не относится ли scenario к скрытой зависимости: auth, feature flag, миграции или downstream provider. Pact-документация советует не stub-ить слой до извлечения и проверки request body, иначе verifier может принять произвольный input. Эта оговорка не означает, что любой проект обязан использовать именно Pact. Она показывает общий принцип: verification должна проходить через ту границу, на которой действительно принимается контрактное решение.

Ограничения, rollback и следующий шаг

Здесь нет production response, сети, browser trace, CI run, Pact Broker, consumer client, provider process, информации об auth или результата настоящей schema validation. Все service names, версии и данные имеют marker synthetic и существуют только в памяти Node. Содержимое статьи не утверждает, что nullable-поле где-либо изменилось, и не даёт разрешения выпускать или откатывать чужую систему.

Следующий практический шаг — выбрать один реальный change, сохранить его исходный contract, добавить semantic expectation только для использующего его consumer и заранее согласовать rollback. В rollback checklist должны быть версии клиентов, правило переключения, способ вернуть старое поведение, владелец решения и проверка после изменения. Если хотя бы один пункт неизвестен, это не повод имитировать зелёную verification; это повод сузить change до обратимого шага.

Историческая граница июля 2023

К июлю 2023 OAS 3.1.0 уже фиксировал разделение между описанием интерфейса и прикладной семантикой, а официальная документация Pact уже описывала consumer-driven contracts и проверку provider до deploy. Пример использует эти идеи без заявления о версии конкретной библиотеки, фактическом запуске или доступе к чьему-либо broker.

Проверяемые источники

  • OpenAPI Specification v3.1.0, 15 февраля 2021 — версионная первичная спецификация, доступная до июля 2023. OAS описывает интерфейс HTTP API и типы; для части свойств она прямо оставляет прикладную семантику потребляющему приложению.
  • Pact Docs: Introduction to contract testing, обновлено 30 августа 2022 — официальная документация Pact, исторически доступная до июля 2023. Она различает статическую schema/specification и contract by example, который формируется выполнением consumer tests.
  • Pact Docs: Verifying Pacts, обновлено 11 августа 2022 — официальная документация Pact, исторически доступная до июля 2023. В ней provider verification привязан к локальному provider или CI, состояниям provider и опубликованному результату; этот sidecar такого запуска не выполняет.