DarkRiDDeR14 мин

Совместимость не решается номером: полевой цикл платформенной команды для API

ПлатформыПрактика

Проблема в поле выглядит как простая координация: у платформенной команды есть новая версия, у нескольких потребителей — разные привычки, и хочется назвать всё compatible одним сообщением. Цена такой экономии — невидимый потребитель обнаруживается после hand-off, а команда чинит не контракт, а следы разных ожиданий: кто-то ожидал поле, кто-то порядок, кто-то секретный режим, а кто-то вообще сравнивал другой тип операции.

Рабочий цикл начинается не с массового уведомления, а с небольшого inventory: один named contract, один named consumer, одна цель сравнения и один status. Сначала отбрасываем несопоставимые family, затем проверяем требуемую поверхность и исключения, после чего передаём только ограниченный результат. Действие: сохранять stop как полезный выход review, а не маскировать его версией или словами «должно работать».

Инвентарь consumer — это модель решения, не список команд

Слово consumer слишком широкое. Для compatibility review важны не владельцы и не названия систем, а наблюдаемые условия использования: какой contract family ожидается, какую version consumer способен читать, какие response fields обязательны и как он трактует errors. В этом пакете все profiles — fixed synthetic literals. Они не обозначают реальные сервисы, пользователей или трассы. Поэтому их можно безопасно сравнить, не создавая видимость, что мы обследовали производство.

У такого inventory есть приятная строгость. fixed-tolerant-reader-v1 относится к family fixed-catalog-read-v1, поддерживает 1.3.0 и требует id с state. fixed-legacy-reader-v1 требует ещё legacyMode; для него результат — incompatible, а не «попробуем». fixed-command-adapter-v1 вообще относится к command family. Его нельзя использовать как плохой пример read compatibility: сравнение прекращается раньше, на сопоставимости объекта.

Замкнутый вертикальный цикл: named contract и named consumer проходят проверку family, surface, guarantee и escape hatch; зелёная ветка ведёт к synthetic hand-off, красные ветки возвращают конкретный stop в inventory.
Цикл не выпускает версию и не меняет API. Он сохраняет причину, по которой следующий review должен продолжить работу или остановиться.
Полевой inventory перед сравнениями
Поле карточкиПочему нужноFixed примерОшибочный заменитель
contract familyне смешать разные операцииfixed-catalog-read-v1одинаковый version string
versionназвать поверхность во времени1.3.0latest или устная договорённость
required fieldsувидеть минимальное чтениеid, stateполный снимок response
error boundaryпонять условие ветвленияfixed-not-foundлюбая ошибка равна отсутствию
escape hatchотделить исключение от общего путиraw-envelope-v1секретный query flag
statusсохранить решение reviewstop-incomparable-consumerобщая фраза compatible

Сначала проверить, что сравниваем один вид контракта

Самая дешёвая проверка — family. Read operation и command adapter могут иметь похожие поля и одинаковую строку версии, но отвечают на разные действия и риски. Если сравнить их как два reader, можно ошибочно объявить набор полей достаточным. Если сравнить их как несовместимые, можно ошибочно потребовать migration там, где связи вообще не было. Поэтому incomparable-consumer — не мягкая форма incompatibility. Это отдельный verdict: основания для сравнения отсутствуют.

Это особенно важно для платформенной абстракции. Чем лучше общий API скрывает детали, тем сильнее соблазн привести разнородных потребителей к одному знаменателю. Но совместимость — не отношение между всеми сущностями с JSON. Это отношение выбранного contract family и конкретной потребности. Сначала нужно назвать ось сравнения, а уже потом обсуждать поля, errors и version. Такая последовательность обычно укорачивает meeting: часть спорных примеров уходит в другой review вместо того, чтобы загрязнять текущий verdict.

Исполняемая остановка для несопоставимого consumer

import { createFixedConsumerCompatibilityCase, reviewFixedConsumerCompatibility, runFixedPlatformApiFixture } from './upgrade-2026-01.mjs';

const item = createFixedConsumerCompatibilityCase('incomparable-consumer-v1');
const result = reviewFixedConsumerCompatibility(item);
const fixture = runFixedPlatformApiFixture();
console.log({ status: result.status, next: result.nextAction, assertions: Object.keys(fixture.assertions).length });
// { status: 'stop-incomparable-consumer', next: 'separate-contract-review-by-family', assertions: 15 }

Фрагмент вызывает только public exports. Он не проверяет настоящий client и не создаёт release note. Его смысл в другом: case получает status до сравнения полей, потому что family не совпадает. Fixture дополнительно подтверждает fail-closed пути для undocumented hatch, implicit guarantee и incompatible reader. Положительный путь в этом модуле заканчивается synthetic contract-review hand-off, поэтому код не может случайно показать изменение API как результат проверки.

Как передать совместимость, не передавая уверенность

Хороший hand-off состоит из четырёх коротких строк: идентификатор contract, version, consumer, status с next action. Например, у compatible fixed reader разрешён только hand-off hand-off-named-consumer-and-fixed-contract. Это не означает «развернуть» или «потребитель доказанно работает». Это значит, что названная пара прошла правила synthetic fixture и следующий reviewer может продолжать с явной областью. Любое более сильное решение потребовало бы данных и прав, которых здесь нет.

Для stop format ещё важнее. Вместо «потребитель старый» сохранить incompatible-consumer и missing field. Вместо «надо договориться» сохранить undocumented-escape-hatch и требование name plus boundary. Вместо «похоже, не подходит» сохранить incomparable-consumer. Точный status удерживает ответственность на объекте контракта, не на человеке и не на эмоциональной оценке команды.

Последовательность полевого review

  1. Назвать единицу. Выбрать одну operation и одну version, не смешивая её с набором соседних endpoint.
  2. Завести карточку consumer. Записать family, supported version, required fields и error boundary без догадки о будущих ожиданиях.
  3. Отсечь несопоставимое. При разных family вернуть отдельный review, не вычисляя совместимость по похожим именам полей.
  4. Проверить surface. Сопоставить нужные поля и declared guarantees с фиксированным contract; отсутствующее поле остаётся incompatibility.
  5. Проверить исключение. Для hatch требовать versioned name и negative boundary; секретный маршрут не проходит review.
  6. Передать ограниченно. Сохранить status, reasons и next action; positive output не меняет API и не заменяет rollout.

Где в цикле живёт обратная совместимость

Обратная совместимость не живёт в одной функции сравнения и не в changelog. Она поддерживается на переходах: public surface объявлена до версии, consumer profile относится к тому же family, exception имеет отдельный предел, а status доступен следующему участнику. SemVer даёт полезную дисциплину именования изменения после того, как public API определён. OpenAPI может описать форму операции. Но ни стандарт, ни document не знают, какой именно synthetic reader держит закрытый parser или нуждается в missing field.

Поэтому цикл намеренно не агрегирует разные verdict в один процент совместимости. Процент скрывает, какой consumer нельзя сравнивать, какой требует absent field и какой использует неоформленный обход. Полевая команда должна сохранить эти разные причины до тех пор, пока не появится отдельное решение: сохранить surface, оформить migration или разделить family. В хорошей документации такой список выглядит менее гладким, зато не превращает неизвестность в командное обязательство.

Неизвестный consumer не равен нулевому риску

Инвентарь почти всегда неполон. Ошибка здесь — подставить вместо отсутствующей карточки удобный verdict: «значит, зависимостей нет». Честнее хранить неизвестность отдельно. Если у потребителя не назван family, его нельзя включить ни в compatible, ни в incompatible список. Если он известен только по устному описанию, нельзя выводить required fields. Такой объект возвращается в очередь исследования, а не в числитель успешных проверок.

Это меняет разговор о гибкости платформы. Команда может выпускать узкий contract, не обещая покрыть каждый будущий случай, но она не должна объявлять неизвестные случаи безопасными. Когда новый reader появляется, ему не требуется оправдывать существование; требуется принести минимальную карточку решения. Затем его можно сравнить по обычному циклу или признать отдельным family. Так abstraction остаётся развиваемой: неизвестный спрос не утаскивает весь API в общий режим, но и не исчезает из истории решения.

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

Inventory не является реестром всех интеграций и не гарантирует отсутствие неизвестных consumer. В пакете нет сетевых вызовов, file reads, production traces, токенов, людей или реальных данных; нет и политики выпуска. Поэтому review нельзя использовать как сертификат compatibility, security или availability. Его результат — дисциплинированная форма вопроса, а не готовая операция над системой.

Следующий практический шаг — выбрать одну неизвестную зависимость и не угадывать её смысл. Создайте отдельную карточку с family, version и минимальным decision, который она должна принять. Если family неизвестна, это уже полезный статус; если поле не описано, это повод оформить контракт; если всё сравнимо, можно передать ограниченный hand-off в независимое ревью. Так платформа сохраняет гибкость без того, чтобы каждый нестандартный запрос навсегда растягивал публичный API.

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

  • Semantic Versioning 2.0.0, exact source commit — версия: Semantic Versioning 2.0.0, commit 7c834b3f3a4940d77ab593bc32583004d6a426a9, 18 June 2013, immutable commit pin. Pinned SemVer 2.0.0 требует заявить precise public API и различает backward-compatible additions и backward-incompatible public changes. Граница: SemVer не создаёт inventory consumer и не устанавливает, что любой version string означает compatibility.
  • OpenAPI Specification v3.1.1 — версия: OpenAPI Specification 3.1.1, 24 October 2024, dated immutable publication. OAS 3.1.1 описывает возможности HTTP API без необходимости читать исходный код или сетевой трафик. Граница: Описание не является evidence поведения конкретного consumer и не выполняет migration или rollout.
  • RFC 9110: HTTP Semantics — версия: RFC 9110, June 2022, immutable RFC publication. RFC 9110 определяет HTTP как uniform interface с request/response semantics и representations. Граница: RFC не задаёт contract family, field tolerance или итог compatibility review прикладного API.