Инженерная практика с агентом · · 15 мин чтения
Совместимость API, когда клиент и сервер обновляются не одновременно
Матрица версий, неизвестные состояния, сохранённая очередь, capabilities и порядок выпуска вместо проверки одной новой пары.

Обновление сервера не обновляет всех его клиентов. Мобильное приложение остаётся установленным неделями, локальный runtime продолжает работать старым процессом, worker завершает задание, подготовленное до deployment. Изменение проходит тест на двух новейших сторонах и всё равно ломает настоящий путь. Совместимость отвечает на другой вопрос: какие сочетания версий и capabilities остаются полезными, пока система меняется по частям? Ответ требует не только правильного JSON, но и сохранения смысла статусов, допустимых действий и пользовательской работы, переживающей перерыв между установкой одного компонента и запуском другого в реальном окружении команды.
Практикум использует вымышленный task-status API, добавляющий подробный progress object при сохранении прежнего status. Это пример, не описание выпущенного wire contract GrantTap и не предложение нового provider. Реальный полезный урок продукта — независимые lifecycles приложения, MCP и coding-provider. Они разобраны в главе обновлений. Через compatibility matrix, failure fixtures и staged verification подготовим изменение, которое работает за пределами нового checkout. Описанные проверки необходимо выполнить: сами примеры не означают, что все существующие клиенты уже испытаны, старые версии бесконечно поддерживаются или сохранённая на диске новая версия немедленно стала исполняемым runtime каждого подключённого компьютера.
Сначала поддерживаемые сочетания
Перечислите client/server versions, которые продукт действительно поддерживает. Учтите задержку mobile update, offline компьютера и временный old server при rollback. Не делайте каждую историческую версию вечным обязательством, но определите support window. Агент должен знать, что работает полностью, что честно деградирует, что отклоняется. Без решения tests покрывают случайно установленную пару. Это особенно заметно при разработке через одного агента: у него новая схема, новый generated client и свежая база, поэтому локальный успех кажется всеобщим. Реальные пользователи остаются на других версиях, и их комбинации нужно включить в принятую область поддержки намеренно.
В упражнении есть old/old, old client/new server, new client/old server и new/new. При rolling deployment добавьте mixed instances. Каждой паре задайте expected behavior. Это договорённость, определяющая implementation, fixtures и release order, а не декоративная таблица. Один integration success устанавливает свою пару, не остальные. Если продукт не обещает new client с old server, он всё равно должен понятно объяснять несовместимость. Нельзя оставлять пользователя с вечной загрузкой, которую невозможно отличить от отсутствующей сети. Явная граница поддержки позволяет и агенту выбрать правильный тест, и человеку понять, какое обновление требуется для конкретной возможности.

Сохраняйте смысл вместе с parsing
Валидный JSON способен изменить значение. Прежний complete мог означать завершение операции, а новый сервер выставляет его после принятия. Типы совпали, смысл сломан. Google AIP-180 различает source, wire и semantic compatibility. Это полезная рамка ревью, не автоматический закон любого транспорта проекта. Для собственного API определите реальные гарантии и ожидаемое поведение потребителей. Если старый клиент разрешает следующее действие при complete, новый смысл может создать конфликтующую операцию, хотя decoder не выдаст ни одной ошибки и все типовые проверки структуры ответа будут успешно пройдены на обеих сторонах.
Запишите смысл важных states рядом со schema. Accepted, running и finished остаются различимыми. Новый progress добавляет detail, а старый status сохраняет поддержанное значение. Нельзя улучшать новый экран ослаблением прежней гарантии сервера. Попросите агента связать поля с пользовательским решением. Это выявляет hidden break лучше diff типов: old client всё разберёт и выберет неверное действие. При необходимости введите отдельное представление нового состояния с ясным fallback. Но такое решение требует реализации и проверки обеих сторон; добавление ещё одного enum value без понимания старого decoder лишь переносит ту же проблему в другой участок ответа.
Добавление не автоматически безвредно
Optional field часто проще удаления required, но real decoders по-разному обрабатывают unknown data. Одни отвергают лишнее, другие сохраняют или игнорируют. Проверяйте настоящего клиента вместо общей веры в гибкость JSON. Новое поле также меняет payload size и может раскрывать информацию неподходящему consumer. Compatibility относится к поведению чтения и решения, не одному validator. Если объект большой, учитывайте ограниченные каналы и cache. Успешный маленький sample не устанавливает, что полный новый ответ проходит прежние лимиты памяти, размера сообщения или локального хранения, которые использовались в поддерживаемой версии клиента до этого изменения.
Передайте old-client fixture с progress и проверьте established status и next action. Затем дайте new client response без progress. Fallback явен: ограниченный detail либо понятное требование upgrade для этой возможности. Не придумывайте проценты и не считайте missing нулевым progress. Это создаёт ложную информацию. Человек должен видеть, что источник не предоставляет измерение, а не что измерение равно нулю. У такого состояния могут быть нормальные действия: проверить coarse status, открыть доступный результат или обновить нужный компонент. Оно не должно по умолчанию запускать задачу повторно только потому, что новый экран не получил подробность, которой прежний сервер никогда не обещал.
Missing, null и empty различаются
Отсутствующее поле, null и пустой object могут означать разное. Старый server без progress может не поддерживать capability; empty object нового — быть malformed. Null может означать отсутствие current run, если таков контракт. Не сворачивайте их удобным default, стирающим нужную информацию. Особенно опасно такое преобразование на границе хранения: после записи в cache невозможно понять, прислал ли источник явное значение или клиент сам заменил отсутствие. Если смысл важен для будущего решения, representation должна сохранить различие либо документированно перевести его в отдельное состояние без выдуманного измерения и ложного подтверждения исполнения.
Создайте отдельные fixtures и expectations, пройдите public decode/display path. При unknown progress status и controls остаются согласованными. Глава capabilities полезна как пример разделения доступности и подтверждённого использования. Missing detail не означает, что операция не запускалась; present field не доказывает успех independently. Если схема допускает null только при определённом status, проверьте и неверное сочетание. Декодер может принять его структурно, но UI должен отреагировать по контракту. Корректная обработка malformed response не обязана угадывать истинный исход: bounded error и сохранение идентичности исходной операции позволяют получить новое наблюдение без опасного повторения действия.
Подготовьтесь к новым enum values
Сервер может добавить неизвестное old client состояние. До deployment решите fallback. Unknown presentation сохраняет identity и поддержанный refresh без выдуманного terminal success. Подход зависит от эффекта. Mapping всех неизвестных значений в complete может разрешить конфликт, пока первоначальная операция ещё выполняется. Mapping в failure тоже не обязательно верен: пользователь может повторить уже принятую работу. Нужен отдельный смысл «источник сообщил неподдерживаемое состояние». Это не красивый final outcome, но честное основание ограничить действия и предложить подходящий путь проверки либо обновления вместо догадки о том, что произошло за пределами клиента.
Испытайте synthetic future state старой fixture и полный journey. При обещанной graceful support клиент не должен crash и выбирать unrelated action. Проверьте отправку enum обратно: read и write compatibility различаются. Если безопасный fallback невозможен, ограниченный version requirement корректен. Выдуманная совместимость не становится улучшением от отсутствия error. Для review назовите, какие actions остаются доступны в unknown state. Можно разрешить чтение уже сохранённого результата и запретить новую mutation, если это соответствует контракту. Конкретный набор определяется вашим продуктом, а не универсальным правилом, будто любой неизвестный статус должен вести к одной и той же кнопке Retry.
Запросы и ответы меняются независимо
New server принимает old request — это не доказательство приёма new request старым. Рассмотрите направления отдельно. Новый parameter, required field и изменённый default требуют проверки. Default, выбирающий другую цель или scope, особенно важен. Агент не должен угадывать его ради видимости valid request и выполнять другое намерение пользователя. Если новая операция не представима в прежнем контракте, лучше честно сообщить недоступность. Нельзя превратить отсутствие поддержки в альтернативное действие без явного решения. Такой fallback выглядит удобным только до первого случая, когда человек ожидает проверку, а сервер вместо неё создаёт ресурс по старым правилам.
В fixture запустите new client со старым server и проверьте response вместе с actual side effect controlled backend. HTTP success может скрыть игнорирование parameter и исполнение прежней операции. Acceptance относится к agreed behavior. Если feature требует capability, проверьте её до предложения действия и укажите upgrade path. Это особенно важно для Send now, отмены и изменения политики: дополнительный аргумент может менять не оформление, а способ исполнения. Поддерживаемый отказ должен прийти до эффекта. После неизвестного исхода клиент сохраняет operation identity и сначала проверяет её, вместо отправки другого запроса с новым ID и надеждой, что старая сторона поймёт его иначе.
Version не заменяет capability
Version описывает установку, но не всю config, permissions и provider support. При наличии discovery используйте его настоящий contract. Не придумывайте negotiation protocol в UI без server implementation. Cached capability имеет scope/lifetime и источник, чтобы ответ одного компьютера не включал feature другого route. Если источник временно недоступен, не превращайте старое наблюдение в свежее подтверждение. Можно показать сохранённое состояние с понятной давностью и ограничить действие, требующее текущего знания. Такое разделение особенно полезно при нескольких computers одной Task, где одинаковая версия пакета не гарантирует одинаковую настройку и доступ к нужному инструменту провайдера.
Devices различает running/installed MCP и confirmation maintenance. Package на диске не доказывает restart старого process. В упражнении обновите capability после поддержанного reconnect/restart. Старая observation может быть stale, package-manager success не подтверждает новые semantics каждого соединения. Сохраните выбранный target при возвращении из управления компьютером. Если пользователь проверял одну машину, нельзя незаметно использовать receipt другой для включения возможности в текущем чате. Правильная связь между наблюдением и источником важнее общей зелёной метки аккаунта, потому что реальная операция отправится по конкретному маршруту execution и встретит именно его исполняемое состояние.
Unsupported нужно объяснять
Если old server не поддерживает rich progress, определите полезный остаток. New client может показывать coarse status без detail с понятным требованием upgrade. Для другой mutation нужен отказ. Важно соответствие ожиданию. Unsupported parameter нельзя молча игнорировать с success другой операции. Внешне приятный fallback с неправильным эффектом — дефект корректности. У человека должна остаться возможность понять, что доступно сейчас и что изменит обновление. Не заставляйте его устанавливать всё подряд: назовите нужный слой, например активный runtime выбранного компьютера, и подтвердите фактическое изменение после штатного действия вместо вывода о готовности по одному завершению скачивания пакета.
Проверьте known unavailable и failed discovery. Это разные основания: известный предел и неизвестная support. Для каждого нужен bounded next step. Глава providers показывает неодинаковое реализованное coverage. В вашем API поддержка также следует implementation, не имени агента и connected badge. Если legacy consumer не может показать подробный reason, используйте его поддержанный error format. Формально новый понятный объект ошибки бесполезен, если старый клиент сам не умеет его декодировать. Поэтому несовместимость сообщают по тому пути, который остаётся понятным получателю, а новый detail добавляют только при известной поддержке или в согласованной совместимой форме ответа.

Проверьте сохранённые и queued данные
Cached responses, queued operations и persisted documents переживают upgrade. Новый decoder встретит старое даже после обновления всех server instances. Определите read, migration и invalidation. Unsent work отличается от disposable cache. Очистка всего прячет баг и уничтожает полезное. Агент не должен решать decoding failure удалением local storage без понимания содержания. Для каждого класса данных назовите retention и recovery. История может оставаться читаемой в прежнем формате, очередь может требовать пересмотра перед отправкой, временный вычисленный cache — безопасного сброса. Общее слово данные не делает эти состояния одинаковыми и не оправдывает универсальное destructive действие.
Сохраните old response и queued request как fixtures, откройте new client path. Проверьте identity, target и action. При conversion испытайте interruption/repetition по storage model. Malformed record может требовать recoverable error, legitimate older — supported path. Глава offline storage поясняет различие cache history и current computer state. Если очередь создана старым клиентом, новый не должен автоматически приписывать ей новый смысл optional fields. Сначала установите первоначальное намерение. Там, где преобразование неоднозначно, сохраните запись и предложите конкретный пересмотр, а не выполняйте mutation по догадке только ради того, чтобы после обновления список очереди выглядел пустым и завершённым.
Сохраняйте operation identity при повторах
Rollout меняет instance follow-up request. Existing operation должна оставаться той же по поддержанной routing/storage model. Missing status нельзя лечить create с новым ID. Определите inspection на переходе; это infrastructure concern, не только schema. Агент находит owner layer вместо patch одного client error. Если состояние хранится локально у процесса, простая балансировка между instances может не обеспечивать нужную маршрутизацию. Это архитектурное ограничение следует проверить и объяснить, не обещая восстановление, которого фактическая топология не поддерживает. Наличие двух healthy серверов само по себе не устанавливает доступность операции с любого из них после принятия.
Проверьте sequence: одну version принимает запрос, другая supported отвечает на status. Сохраните unknown там, где source не устанавливает outcome. Испытайте duplicate по настоящему idempotency contract. Новый key не защищает old server, который его не реализует. Глава доставки разделяет receipts и activity; вашему service тоже нужны acceptance, recovery и final outcome на deployment boundary. Отдельно рассмотрите отмену: поздний cancel может прийти в другой version и должен относиться к исходной операции. Если действие уже завершилось, честный ответ о завершении лучше выдуманного success отмены, который оставит клиента с неправильным представлением о наличии созданного результата и следующих допустимых действиях.
Держите fixtures независимыми
Если latest server генерирует все samples, а latest client — expected, compatibility test способен проверять только согласие двух новых реализаций. Сохраните representative inputs и observable behavior прежнего контракта с synthetic provenance. Generated SDK types отличаются от runtime decode и decisions. Compilation не устанавливает wire semantics. Старые fixtures полезны именно своей независимостью от текущей генерации. Изменять их вместе с реализацией можно лишь при осознанном изменении support contract, иначе агент легко «исправит» падающий тест обновлением expected и потеряет единственное свидетельство того, на какое прежнее поведение всё ещё рассчитывает поддерживаемый клиент приложения.
Выполните пары support matrix. Controlled server сужает client behavior, real supported server даёт integration evidence. Назовите слой. Недоступный old binary оставляет pair unverified с объяснением. Mock old response не является живой проверкой old application. Packet показывает direct evidence и assumptions. Если часть fixtures относится к ошибкам, включите их в матрицу наравне с успехом. Старый клиент может корректно показывать новый success, но зависать при знакомом отказе, который сервер начал передавать в другой форме. Именно такие случаи часто проявляются в реальной работе после изменения авторизации или ограничений и не видны в единственном счастливом demonstration сценарии разработки.
Выбирайте rollout order по матрице
Server часто сначала добавляет compatible response, затем client использует его, но это не универсальный закон. New server, ломающий old client, нельзя выкатывать первым при такой support promise. New client, несовместимый с old server, требует gate или соответствующего порядка. Пусть plan следует наблюдённым отношениям, а не лозунгу. Иногда требуется промежуточная совместимая версия или временная ограниченная возможность. Такой дополнительный этап оправдан, если он делает переход проверяемым. Не прячьте зависимость в устной инструкции «обновить всё одновременно»: независимые устройства, магазины приложений и offline машины обычно не дают реальной гарантии мгновенного синхронного переключения всех участников системы.
Учтите mixed instances и задержку install-to-run. Runtime требует штатного restart, mobile update достигает пользователей не сразу. Сохраните previous compatible artifact и опишите rollback для уже обновлённых clients. Source revert не покрывает persisted data и new requests автоматически. Recovery combinations требуют того же внимания, что forward rollout. Если после отката new client теряет detail, это может быть допустимой деградацией, а если меняет смысл mutation — нет. Различие должно быть известно до выпуска. Тогда решение о восстановлении не принимает случайно новую несовместимость под видом возвращения к проверенной прежней системе, которой часть клиентов уже больше не соответствует.
Проверяйте journey после активации
После разрешённого rollout установите serving version и пройдите affected journey. Создайте safe fixture operation, посмотрите coarse/rich status supported clients и совпадение controls с state. Health endpoint подтверждает базовую доступность, не весь contract. Deployment acceptance, serving identity и behavior — отдельные observations одного release. Если используется несколько instances, назовите охват проверки. Один удачный ответ не доказывает все маршруты. Полезно подтвердить тот переход, ради которого менялся API: например, старый клиент продолжает читать окончательный статус, а новый получает подробность без изменения момента, когда операция считается действительно завершённой и позволяет следующее действие пользователя.
В GrantTap сверяйте execution и route результата. Local candidate test не устанавливает external serving state. При препятствии завершите reviewable change и назовите dependency. Глава evidence различает reported result и observed effect. В summary нужны pair, environment и реально пройденное behavior, не «все клиенты синхронны». Если публикация не входила в scope, скажите, что контракт подготовлен и испытан на стенде. Если входила, свяжите serving check с нужным артефактом. Такой итог позволяет следующему человеку принять конкретное решение и продолжить работу, не восстанавливая по общим словам, какой именно сервер и клиент агент фактически проверил в своей последней успешной команде.
Завершайте старую поддержку намеренно
Support window может закончиться с явным решением и отражением в product behavior. Определите сообщение upgrade и сохранение local work. Не оставляйте old client ждать непонятный response бесконечно. Bounded supported error лучше silent break как transient network. Цель — понятный переход, не вечная совместимость любой ценой. Если действие больше невозможно, пользователь должен это узнать до новой mutation, сохранив доступ к собственному черновику или истории там, где это предусмотрено. Завершение поддержки не является разрешением удалять его данные либо автоматически переносить их в другой проект только потому, что старый маршрут больше не принимает новые операции.
Оставьте tests текущего набора и удаляйте obsolete fixtures с основанием. Следующий агент должен найти актуальный contract. Теперь Task точно просит API change с version matrix, stored-data cases и staged verification. Самый свежий local test больше не подменяет всю deployed system. Команда объясняет рабочие combinations, честные degradations и требующие upgrade случаи со свидетельствами. В этом практическая польза: независимые lifecycles перестают быть неожиданностью, а совместимость становится конкретной инженерной работой. Она помогает выпустить полезное изменение без ложного обещания одновременного обновления всех устройств и без догадки, будто успешный parsing автоматически сохраняет прежний пользовательский смысл операции.