Files
progcode/editorial/agent-rewrites/065.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
18 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 65,
"slug": "editorial-2026-03-mechanism-data-contracts",
"title": "Совместимость схемы — это направление, а не номер версии",
"excerpt": "Как compatibility gate отделяет направление чтения, обязательные поля, объявленные additions и возможности consumer — и почему неопределённость должна останавливать проверку.",
"contentHtml": "<p>Симптом обычно выглядит безобидно: producer выпускает схему v2, consumer видит знакомые поля, а в ревью появляется короткое слово compatible. Затем старый reader получает данные с новым полем, не находит обязательный <code>state</code> или встречает поле другого типа. Ошибка проявляется уже на границе сервисов. Цена — отклонённые сообщения, неверные значения по умолчанию, ручная миграция и спор о том, что именно обещала версия.</p>\n<p>Тезис простой: совместимость нельзя вычислять по номеру версии, пересечению имён или удачному примеру сериализации. Сначала нужно назвать направление, две точки схемы и конкретного consumer. Затем отдельно проверить обязательную поверхность, объявленные additions и capability reader. Если хотя бы одна часть неизвестна, gate должен остановиться. Такой отказ полезнее зелёного статуса без объяснения.</p>\n<h2>Что именно сравнивает gate</h2>\n<p>Назовём baseline старой схемой и candidate новой схемой. В выбранном направлении фиксированный producer создаёт candidate, а фиксированный consumer читает эту форму, опираясь на baseline как на точку отсчёта. Это не единственное возможное направление. Новый reader может читать старые данные, но это уже другой вопрос и другая карточка сравнения.</p>\n<p>Минимальная запись отношения содержит пять значений: <code>direction</code>, <code>family</code>, <code>baselineVersion</code>, <code>candidateVersion</code> и <code>consumerId</code>. <code>family</code> не даёт сравнить случайные JSON-объекты только потому, что у них совпали ключи. Версии закрепляют обе точки. <code>consumerId</code> не позволяет заменить проверяемого reader абстрактным «клиентом». Пустое или изменённое значение даёт <code>stop-implicit-comparison</code>.</p>\n<figure><img src=\"/assets/editorial/2026/data-contracts-2026-compatibility-gate-loop.svg\" alt=\"Цикл compatibility gate: направление, diff обязательных полей, manifest additions и capability consumer\" loading=\"lazy\" /><figcaption>Gate проверяет отношение, форму данных, намерение producer и способность named consumer принять новую поверхность. Красная ветка сохраняет конкретную причину остановки.</figcaption></figure>\n<h2>Три независимые проверки</h2>\n<p>Первая проверка смотрит на обязательную поверхность baseline. Если required-поле исчезло из candidate или сменило тип, старый reader больше не получает обещанную форму. Например, замена <code>state</code> на <code>phase</code> может казаться переименованием с тем же смыслом. Gate не угадывает смысл имён. Для reader поле <code>state</code> отсутствует, поэтому результат — <code>stop-backward-incompatible-schema</code>.</p>\n<p>Вторая проверка смотрит на новые поля. Candidate может сохранить <code>id</code> и <code>state</code>, но добавить <code>priority</code>. Это не разрушает обязательную поверхность. Однако producer должен явно назвать addition в manifest. Скрытое <code>routingHint</code>, появившееся в candidate без записи в manifest, даёт <code>stop-undocumented-schema-field</code>. Gate сначала требует объяснить новую поверхность, а потом спрашивает, принимает ли её reader.</p>\n<p>Третья проверка смотрит на capability consumer. Tolerant reader может разрешать declared additions. Strict reader может отклонять неизвестные поля. Слово optional в схеме producer не меняет policy reader автоматически. Если strict consumer не принимает <code>priority</code>, результат — <code>stop-incompatible-consumer</code>. Gate не удаляет поле на лету и не придумывает adapter. Команда отдельно выбирает изменение reader, разделение формы, задержку candidate или миграцию.</p>\n<div class=\"table-scroll\"><table><caption>Симптом → причина → проверка → действие</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Для двух схем написано compatible, но не указано направление</td><td>Один boolean склеил разные отношения producer и reader</td><td>Проверить <code>direction</code>, family, baseline, candidate и consumerId</td><td>Остановить как <code>stop-implicit-comparison</code> и оформить relation</td></tr><tr><td>В candidate нет обязательного <code>state</code></td><td>Required-поле baseline удалили или переименовали</td><td>Построить field map и сравнить required surface baseline</td><td>Вернуть поле или назвать отдельную migration</td></tr><tr><td>Новый <code>routingHint</code> есть в данных, но нет в описании изменения</td><td>Фактический diff шире declared manifest</td><td>Сверить additions candidate с <code>declaredAddedFields</code></td><td>Остановить как <code>stop-undocumented-schema-field</code></td></tr><tr><td>Tolerant reader проходит, strict reader падает</td><td>Capability зависит от конкретного consumer, а не от версии producer</td><td>Проверить policy declared additions у named reader</td><td>Изменить reader, форму или завести migration</td></tr><tr><td>Отчёт говорит «75% совместимо»</td><td>Агрегат скрыл разные причины и владельцев следующего шага</td><td>Проверить исходный status и reason одного сравнения</td><td>Сохранить конкретный stop-status вместо процента</td></tr></tbody></table></div>\n<h2>Учебный пример с фиксированными схемами</h2>\n<p>Ниже — учебный JavaScript-подобный пример. Он не подключается к registry, сети, файловой системе, CI или production data. <code>fixedCase</code> возвращает заранее известный объект, а <code>review</code> выполняет только описанные проверки. Пример показывает форму решения, но не доказывает совместимость реального формата.</p>\n<pre><code>const baseline = {\n version: '1.0',\n required: { id: 'string', state: 'string' }\n};\n\nconst candidate = {\n version: '2.0',\n required: { id: 'string', phase: 'string' },\n additions: []\n};\n\nconst relation = {\n direction: 'backward',\n family: 'orders',\n baselineVersion: '1.0',\n candidateVersion: '2.0',\n consumerId: 'orders-reader'\n};\n\nconst report = review({ baseline, candidate, relation });\nconsole.log(report);\n// {\n// status: 'stop-backward-incompatible-schema',\n// removedRequiredFields: ['state'],\n// nextAction: 'retain-required-baseline-field-or-name-a-separate-migration'\n// }</code></pre>\n<p>Важна не длина функции, а граница вывода. Gate обнаружил отсутствие <code>state</code>. Он не объявил новый <code>phase</code> эквивалентом, не выдал разрешение на deploy и не выбрал стратегию миграции. Следующий шаг зависит от владельца контракта и требований старого reader.</p>\n<p>Положительный учебный случай тоже ограничен. Если candidate сохраняет <code>id</code> и <code>state</code>, добавляет объявленный <code>priority</code>, а named reader допускает declared additions, gate может вернуть <code>synthetic hand-off</code>. Это означает только то, что фиксированная проверка закончилась положительно. Это не означает, что parser, registry, права, нагрузка и выпуск в реальной системе готовы.</p>\n<h2>Почему порядок проверок имеет значение</h2>\n<p>Если сначала спросить reader, принимает ли он неизвестные поля, tolerant policy может скрыть неописанное изменение producer. Поэтому gate сначала устанавливает отношение, затем проверяет required surface, потом сверяет manifest и только после этого проверяет capability.</p>\n<p>Так распределяется ответственность. Producer называет новую поверхность. Сравнение проверяет буквальную форму. Manifest связывает diff с намерением. Consumer описывает границу принятия. Ни один слой не подменяет другой. Если переставить шаги, зелёный результат может появиться раньше, чем команда поймёт, что именно она выпускает.</p>\n<h2>Порядок действий</h2>\n<ol><li>Выберите одну contract family и зафиксируйте baseline и candidate.</li><li>Назовите направление: какой producer пишет, какой reader читает и относительно какой точки.</li><li>Запишите <code>consumerId</code> и остановите сравнение при неизвестной или неполной связи.</li><li>Постройте карты полей и проверьте исчезновение required-полей и смену их типов.</li><li>Составьте manifest всех новых полей candidate; не выводите намерение из одного sample.</li><li>Сверьте фактические additions с manifest и остановите скрытые поля.</li><li>Проверьте capability именно named consumer: required fields, допустимую версию и policy дополнительных полей.</li><li>Верните один status, reason и next action; положительный результат назовите только synthetic hand-off.</li><li>Для обратного отношения заведите отдельное сравнение, а не расширяйте текущий boolean.</li></ol>\n<h2>Отрицательный путь</h2>\n<p>Нельзя считать gate работающим только по зелёному fixed case. Передайте объект без <code>direction</code>. Ожидайте <code>stop-implicit-comparison</code>. Удалите <code>state</code> из candidate. Ожидайте <code>stop-backward-incompatible-schema</code>. Добавьте <code>routingHint</code> без manifest. Ожидайте <code>stop-undocumented-schema-field</code>. Замените tolerant reader на strict reader. Ожидайте <code>stop-incompatible-consumer</code>.</p>\n<p>Каждый отказ должен сохранять следующий шаг. Неизвестное отношение требует уточнить карточку. Удалённое required-поле требует вернуть поверхность или назвать миграцию. Скрытый addition требует обновить описание изменения или убрать поле. Несовместимый reader требует решения владельца consumer. Общий статус «не прошёл» не даёт команде достаточного действия.</p>\n<h2>Ограничения механизма</h2>\n<p>Этот gate проверяет узкое отношение между фиксированными описаниями. Он не извлекает схемы из registry, не знает все deployment-версии, не проверяет реальные payloads и не подтверждает, что consumer честно описал свои потребности. Он также не решает семантическое изменение: строка <code>state=active</code> может сохранить тип и имя, но начать означать другой бизнес-статус.</p>\n<p>Он не заменяет contract tests, миграцию данных, нагрузочную проверку, security review, SLA и план отката. JSON Schema, JTD и Avro дают полезные понятия для формы и чтения, но не определяют статусы этого gate. Поэтому результат нужно читать узко: механизм сделал одно сравнение явным и остановил неизвестность. Он не управляет релизом.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Для выбранной пары есть заполненные family, direction, baseline, candidate и consumerId. Gate возвращает отдельные результаты для неизвестного отношения, разрушенной required surface, скрытого addition и несовместимого reader. Есть один положительный учебный случай и отрицательные случаи для каждой остановки. Каждый report содержит reason и next action. Положительный report прямо говорит <code>synthetic hand-off</code> и не выдаёт право на deploy.</p>\n<p>Если команда не может воспроизвести эти статусы на фиксированных входах или не знает, какой reader проверяется, критерий не выполнен. Номер версии и зелёный процент не заменяют evidence. Готовность здесь означает, что вопрос о совместимости имеет направление, named участников, отдельную причину и проверяемый следующий шаг.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://avro.apache.org/docs/1.12.0/specification/\" target=\"_blank\" rel=\"noopener noreferrer\">Apache Avro 1.12.0: Specification</a> — официальное описание writer schema, reader schema и schema resolution. Документ не подтверждает статусы этого учебного gate или результат конкретного release.</li><li><a href=\"https://json-schema.org/draft/2020-12/json-schema-validation\" target=\"_blank\" rel=\"noopener noreferrer\">JSON Schema Draft 2020-12: Validation</a> — официальное описание structural validation и ограничений для object properties. Документ не определяет направление producer/consumer и не заменяет compatibility review.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc8927.html\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 8927: JSON Type Definition</a> — нормативное описание required, optional и additional properties в JTD. RFC не даёт migration decision, deployment approval или production evidence.</li></ul>"
}