В полевой работе с контрактом данных самая дорогая ошибка — объявить один schema change совместимым «для всех», потому что один consumer прочитал sample. Остальные могут ожидать другой набор полей, запрещать additions или вообще относиться к другой contract family. Цена общего verdict — поздний поиск владельца и ручное исправление уже после того, как решение разошлось между командами. Нужна не длинная рассылка, а матрица, где каждая строка фиксирует одну пару producer и consumer.
Практический цикл строится вокруг простого правила: неизвестный consumer не получает зелёный статус по умолчанию. Сначала карточка называет family, baseline, candidate, direction и capability reader. Затем gate возвращает один из раздельных результатов: hand-off, incompatible consumer, undocumented field, backward break или implicit comparison. Действие для техлида — сохранить именно эту причину рядом с парой, не превращая stop в общий риск без адреса.
Матрица начинается с единицы решения, а не со списка систем
Список интеграций обычно полезен для владения, но слишком широк для compatibility review. Здесь нужна минимальная единица: один fixed producer создаёт один candidate schema, а один fixed consumer принимает или не принимает конкретную границу этой формы. В карточке consumer достаточно нескольких свойств: family, required fields, supported candidate version и policy для declared additions. Все значения в overlay — учебные literals. Они не изображают реальных сервисов, пользователей, сообщений или наблюдаемость.
Такая узость снимает лишнюю претензию к gate. Он не строит полный граф компании и не обещает найти каждый hidden reader. Он даёт команде способ не потерять уже известную пару. Tolerant reader в matrix соглашается на described addition priority; strict reader останавливается на том же candidate; profile с другой family вообще не должен быть включён в это сравнение. Даже отсутствующая карточка лучше воспринимается как work item, а не как доказательство отсутствия риска.
| Пара | Что известно | Вердикт gate | Куда вернуть работу |
|---|---|---|---|
| producer 1.1 → tolerant reader | priority declared, reader принимает additions | synthetic compatibility-review hand-off | в независимый hand-off |
| producer 1.1 → strict reader | priority declared, reader запрещает additions | stop-incompatible-consumer | к границе reader или отдельной migration |
| producer 2.0 → tolerant reader | baseline state удалён | stop-backward-incompatible-schema | к candidate required surface |
| producer 1.1 → unnamed relation | нет direction или consumerId | stop-implicit-comparison | к карточке сравнения |
| producer 1.1 hidden field | routingHint отсутствует в manifest | stop-undocumented-schema-field | к change manifest |
Inventory consumer хранит условия чтения, а не репутацию команды
Профиль consumer не должен звучать как оценка: «старый», «сложный», «привередливый». Такие слова не помогают выполнить проверку. Вместо них нужны наблюдаемые условия. Required fields показывают минимальную поверхность, без которой reader не может принять решение. Policy additions показывает, допускает ли он именно описанные новые поля. Supported candidate version делает временную точку явной. Family не даёт сравнить read contract с другой операцией только потому, что обе стороны используют JSON.
В fixed cases strict reader не является ошибкой. Он говорит понятное правило: declared additions не принимаются. Gate не вправе объявить его плохим участником или изменить его policy. Он должен сохранить stop-incompatible-consumer и следующий шаг. Это полезно и для людей: вместо спора о скорости команды видно, какой контрактный выбор надо сделать. Возможно, producer удержит addition, возможно, consumer расширит границу, возможно, появится отдельная форма. Пока решение не принято, красный status честнее зелёной надежды.
Исполняемый triage для strict consumer
import { createFixedDataContractCase, reviewFixedDataContractCompatibility, runFixedDataContractFixture } from './upgrade-2026-03.mjs';
const item = createFixedDataContractCase('incompatible-consumer-v2');
const report = reviewFixedDataContractCompatibility(item);
const fixture = runFixedDataContractFixture();
console.log({ status: report.status, reason: report.reasons[0], assertions: Object.keys(fixture.assertions).length });
// { status: 'stop-incompatible-consumer', reason: 'incompatible-consumer', assertions: 15 }
Это настоящий запуск public functions данного module. Он берёт named fixed case, а fixture проверяет пять независимых веток. При этом код не пишет в registry, не посылает sample, не читает environment и не получает данные о внешнем consumer. Положительный case в той же fixture заканчивается только hand-off. Такой предел защищает от подмены: успешное упражнение не становится основанием сообщить, что что-то уже опубликовано или работает за границей учебной модели.
Почему gate loop должен возвращаться к карточке, а не к общей очереди
У хорошего stop есть адрес возврата. Backward break возвращается к candidate schema: пропал required baseline field. Undocumented field возвращается к manifest: новая поверхность не была названа. Incompatible consumer возвращается к capability named reader. Implicit comparison возвращается к relation: не задано, что и в какую сторону сравнивают. Если все четыре причины превратить в «нужно договориться», команда вернётся к исходной ручной координации, только с более формальным заголовком.
Поэтому loop в visual не имеет линии deploy. После зелёной ячейки он передаёт limited review hand-off, после красной — конкретное уточнение. Даже hand-off не означает, что gate распоряжается выпуском. Следующий участник может потребовать дополнительные основания, а реальная система может иметь условия вне этой модели. Роль compatibility gate ограничена: сделать вопрос о форме и reader проверяемым, удержать конкретную причину и не потерять границу полномочий.
Один глобальный verdict скрывает разные владельцы решения
Когда у change есть пять consumer, хочется свернуть матрицу в один статус. Делать это можно только после определения цели агрегирования. Для release note достаточно перечислить пары и их состояния. Для приоритизации можно посчитать очереди stop по причинам. Но нельзя присвоить candidate строку compatible, если хотя бы один известный reader требует отдельного решения. Это не бюрократия. Это различие между «какая-то проверка прошла» и «все названные контракты покрыты утверждением».
Отдельно храните incomparable или implicit relation. Нулевая информация о consumer — не tolerance. Профиль другой family — не incompatible, пока не выяснено, существует ли связь. В данном module не создаётся отдельный profile другой family, потому что user story ограничена четырьмя обязательными stop. Но правило остаётся: прежде чем считать поля, нужно подтвердить ось сравнения. Это дешевле, чем строить огромную matrix, где половина ячеек имеет красиво окрашенный, но бессмысленный verdict.
Полевой порядок работы с change
- Завести одну строку. Взять один producer, baseline, candidate и одного consumer вместо массового статуса для схемы.
- Собрать capability. Записать contract family, required fields, policy declared additions и candidate version без догадки о будущих сценариях.
- Определить relation. Указать direction и обе точки schema; незаполненная связь должна остановить review.
- Прогнать structural слой. Проверить required baseline fields, типы и manifest additions раньше consumer policy.
- Сохранить раздельный verdict. Не заменять reason общей фразой; вернуть её в именно ту часть карточки, которая требует решения.
- Передать только область. При зелёном status отдать named pair в следующий review, не объявляя registry, CI или deploy завершёнными.
Граничные данные нужны для чужой проверки правил
Тестовый набор gate обычно соблазняются наполнить красивыми объектами. В этой задаче полезнее обратное: несколько коротких случаев, которые обязаны остановиться. Case с удалённым state не даёт перепутать rename и сохранение required surface. Case с routingHint не даёт tolerant consumer узаконить скрытое поле. Case со strict reader не даёт structural diff выдать за полную совместимость. Case с неявным direction не даёт sample превратить в отношение.
Эти данные фиксированы в памяти, поэтому reviewer может повторить результат без доступа к production. Они не заменяют реальные boundary payload и не дают сигнал о нагрузке, retention, access control или времени доставки. Но именно в их ограничении есть польза для документации: каждый выход виден, каждое поле имеет известное происхождение, а код можно буквально выполнить из article snippet. При расширении gate новый rule обязан принести свой fixed case и своё fail-closed ожидание.
Ограничения и следующий шаг
Изолированный overlay не обслуживает реальный schema registry и не умеет обнаруживать неизвестных consumers. Он не подключается к network, filesystem, Git, CI, clock, telemetry, API или production data. Reference documents ниже объясняют schema vocabulary и reader-writer direction, но не подтверждают output этой матрицы, отсутствие инцидентов или успех какого-либо deployment. Разумно воспринимать её как форму инженерного review, а не как гарантию системы.
Следующий шаг — не масштабировать matrix сразу. Выберите одну известную связку, у которой сегодня есть ручное сообщение о change, и запишите capability reader в четырёх полях. Если relation ещё нельзя назвать, оставьте explicit stop и назначьте владельца уточнения. Если relation читается, добавьте fixed case в локальный набор. Так data contract перестаёт жить только в памяти producer и становится точкой, которую consumer может проверить до следующего deploy.
Проверяемые источники
- RFC 8927: JSON Type Definition — версия: RFC 8927, November 2020, immutable RFC publication. RFC 8927 явно разделяет required properties, optionalProperties и additional properties, поэтому используется как первичный vocabulary для матрицы field boundaries. Граница: Experimental RFC не устанавливает policy конкретного reader, ownership matrix или outcome synthetic gate.
- Apache Avro Specification 1.12.0, exact source commit — версия: Apache Avro 1.12.0, commit 8c27801dc8d42ccc00997f25c0b8f45f8d4a233e, release tag dated 5 August 2024, immutable commit pin. Pinned Avro specification называет writer and reader schemas и описывает resolution, что подтверждает необходимость хранить направление relation рядом с участниками. Граница: Avro source не сообщает о существовании named fixed consumers и не подтверждает release decision.
- JSON Schema Core, draft-bhutton-json-schema-01 — версия: draft-bhutton-json-schema-01, published 10 June 2022, immutable versioned IETF draft. Dated Draft 2020-12 описывает object-property vocabulary, применяемую здесь только для ясного разговора о declared additions. Граница: Specification не даёт единого compatibility verdict для произвольной группы consumer.