Endpoints для пробного запуска, которые не дают AI-агентам программировать вслепую
Endpoints для пробного запуска позволяют AI-агентам для программирования увидеть планируемые изменения, затронутые ресурсы и ошибки проверки до выполнения записей.

AI-агенты для программирования не должны узнавать, допустимо ли изменение, выполняя его. Это ленивый дизайн API, а автономность быстро показывает цену такого подхода. Агент может повторить запрос, выбрать другую ветку и перейти к следующей задаче быстрее, чем оператор успеет восстановить картину случайно изменённых прав, частичной миграции или удаления с неверно заданной областью.
Предварительный просмотр оправдан только тогда, когда он достаточно точно предсказывает конкретное выполнение и позволяет человеку или агенту решить, продолжать ли работу. Ответ «допустимо» не является планом. Diff без каскадного обновления хуже, чем отсутствие diff: он создаёт ложную уверенность.
Цель дизайна проста: принять предлагаемую запись, проверить её относительно текущего состояния и обычных бизнес-правил, вернуть планируемые последствия и ошибки, а затем не позволить выполнить устаревший или изменённый план. Это требует большего внимания, чем добавление dryRun=true. Зато агент сможет исправить неверный запрос до того, как попросит человека об одобрении.
Превью должно описывать точную запись
Endpoints для пробного запуска должны принимать тот же существенный замысел, что и выполнение, и вычислять последствия именно этого замысла. Если POST /memberships выдаёт роль, отправляет приглашение, добавляет участника в платёжную группу и создаёт запись аудита, превью должно сообщать о каждом последствии, которое появилось бы при выполнении.
Команды часто выпускают endpoint «validate», который проверяет форму JSON и обязательные поля. У такого endpoint есть своё место, но он не показывает результат записи. Он не сообщит вызывающей стороне, что запрошенная роль конфликтует с существующей, целевая учётная запись заблокирована или приглашение займёт ограниченное место. Если он делает только это, называйте его проверкой.
Разница важна, потому что агенты принимают успешные вызовы за доказательство допустимости. Ответ, проверяющий только форму, а затем выполнение оставляет агента без информации о частях решения, зависящих от состояния. Полное превью оценивает и запрос, и текущий мир.
Перед проектированием превью сформулируйте контракт выполнения каждой операции записи одним предложением:
Для этих входных данных и наблюдаемой ревизии цели выполнение создаст, изменит, удалит или запустит следующие названные последствия.
Так проявляется расплывчатое поведение. «Изменить настройки проекта» слишком широко. «Изменить retention_days с 30 на 14, пересчитать срок истечения для 18 активных объектов и отклонить объекты под юридическим удержанием» даёт превью конкретный результат, который можно проверить.
Хорошее превью сохраняет семантику операции. Не превращайте массовое удаление в расплывчатое число только потому, что неудобно возвращать настоящий список. Не используйте «может затронуть», если сервис способен определить точные ресурсы. Если набор слишком велик для ответа, верните общее число, ограниченный пример и курсор или ссылку на отчёт, чтобы вызывающая сторона могла изучить полный набор до выполнения.
Плану нужны идентичность, область действия и последствия
Нельзя оценить «изменятся 12 записей», не зная, какие именно записи и как изменятся. Планируемое действие должно показывать входные данные, целевую область и последствия в форме, удобной для программы и человека.
Для обновления одного ресурса часто подходит diff на уровне полей. Для развёртывания плану могут понадобиться образы, окружения, ревизии конфигурации, поведение при перезапуске и проверки работоспособности. Для изменения оплаты нужны старый и новый платёж, дата вступления в силу и информация о том, получит ли клиент уведомление. Подбирайте вывод под предметную область, а не заставляйте каждую операцию помещаться в массив JSON Patch.
Минимально покажите:
- имя операции и явный статус превью;
- стабильный идентификатор каждого затронутого ресурса и его ревизию, если сервис их поддерживает;
- прежние и предлагаемые значения каждого существенного изменения;
- вторичные последствия, например задания, уведомления, изменения доступа или рассчитанные платежи;
- предупреждения, блокирующие выполнение условия и допущения, способные изменить результат.
Понятие «существенный» требует оценки. Сырая временная метка базы данных редко помогает тому, кто одобряет действие. Новый владелец, расширение членства в группе или планируемое удаление, напротив, важны. Сначала показывайте смысловой результат, а подробности низкого уровня оставляйте для случаев, когда они нужны вызывающей стороне.
Превью также должно отделять прямые последствия от производных. Допустим, агент уменьшает квоту хранилища команды. Прямое изменение затрагивает одно поле квоты. Производным результатом может стать блокировка загрузок для трёх существующих проектов. Если спрятать это под общим предупреждением, операция будет выглядеть безопаснее, чем есть. Поместите результат в отдельный массив effects и назовите причину.
Так же точно описывайте неопределённость. Превью может сообщить, что при выполнении будет запрошен внешний налоговый сервис или запланирована отложенная работа. Оно не должно называть окончательную сумму налога, если сервис её ещё не рассчитал. Используйте запись о допущении, где указана зависимость и возможность выполнения без неё.
Проверка должна отделять блокеры от предупреждений
Превью должно точно сообщать агенту, что мешает выполнению, что требует проверки, а что служит только контекстом. Смешивание этих категорий приводит к неудачным повторам и усталости от одобрений.
Блокер означает, что сервис откажется выполнять действие в оценённых условиях. Агенту нужно исправить входные данные, получить недостающие полномочия или остановиться. Предупреждение означает, что выполнение возможно, но разумному оператору стоит изучить последствия. Контекст сообщает информацию, не намекая на опасность.
Возвращайте структурированные ошибки, а не текст, который агенту придётся разбирать. Эта форма намеренно проста:
{
"mode": "preview",
"executable": false,
"validation": [
{
"severity": "error",
"code": "version_conflict",
"path": "/if_match",
"message": "Project prj_184 is at revision 73, not revision 71.",
"blocks_execution": true,
"repair": "Fetch the current project and create a new preview."
},
{
"severity": "warning",
"code": "member_count_change",
"message": "The group will gain 42 members through nested groups.",
"blocks_execution": false
}
]
}
Стабильные коды позволяют агенту выбрать ответ. После version_conflict он может получить текущую ревизию, но после legal_hold_active не может ответственно придумать исправление. message нужен человеку, который проверяет действие. Сохраняйте оба поля.
Не называйте каждое неожиданное условие предупреждением. Если предупреждение всегда требует изменить запрос, это должна быть ошибка. И наоборот, не блокируйте выполнение из-за необычного, но разрешённого условия. Команды превращают каждое предупреждение в блокер из страха что-то пропустить, а затем агенты отправляют превью, которые невозможно завершить без ручной очистки. Интерфейс становится формальным жестом.
Полезная проверка проста: если выполнение получит тот же ввод в том же состоянии, запустится ли оно? Если да, верните предупреждение или контекст. Если нет, верните ошибку. Ошибки авторизации отделяйте от предметной проверки. Они описывают разные проблемы и требуют разных способов исправления.
Пробный запуск не должен писать за спиной вызывающей стороны
Превью должно избегать постоянных внешних эффектов, в том числе тех, которые разработчики считают хозяйственными мелочами. Создание «временной» строки, резервирование товара, увеличение видимой пользователям последовательности, постановка webhook в очередь, отправка письма или обновление времени последнего доступа нарушают ожидание, что запрос безопасен для проверки.
Такая ошибка встречается в зрелых сервисах, потому что код выполнения постепенно обрастает удобными решениями. Обработчик создания может в начале выделить идентификатор, записать ожидающую запись до проверки и вызвать публикацию события до фиксации транзакции. Позже кто-то оборачивает только финальную вставку в if preview. В локальном тесте превью выглядит безвредным, но в рабочей системе всё равно расходует идентификаторы, создаёт поток событий или оставляет мусор.
Рассматривайте выполнение превью как отдельный режим сервиса приложения, а не как условие только в контроллере. Режим может использовать общие функции разбора, авторизации, политик и построения плана. Записи и внешние отправки должны проходить через интерфейсы, которые либо создают предлагаемое последствие, либо отклоняют запрос.
Полезная граница реализации выглядит так:
parse request
-> authorize caller
-> load consistent current state
-> validate business rules
-> build plan
-> preview: return plan
-> execute: apply plan in a transaction, then publish committed effects
Порядок важен. Если база данных поддерживает транзакции, стройте план на основе тех же чтений, которые направляют выполнение. Если зависимость не может участвовать в транзакции, сообщайте о будущем взаимодействии как об отдельном последствии и проектируйте компенсирующее действие на случай ошибки. Внешний вызов не становится транзакционным от одного притворства.
Для записей аудита тоже нужно принять решение. Можно фиксировать сам факт запроса превью. Это разумно, но отправляйте такое событие в явно отдельный путь аудита и убедитесь, что оно не запускает процессы для завершённых изменений. Нельзя помещать «превью выполнено» рядом с «право выдано» и ждать, что последующие потребители сами поймут разницу.
Проверяйте отсутствие изменений, а не только результат. До и после запроса превью убедитесь, что соответствующие таблицы, исходящие очереди, объектное хранилище, тестовые приёмники почты и downstream webhook-получатели не изменились. Модульные тесты редко ловят такую проблему. Интеграционный тест в одноразовом окружении поймает.
Семантика HTTP требует явного контракта
В HTTP нет универсального метода для пробного запуска, и попытка сделать вид, будто он есть, создаёт проблемы совместимости. RFC 9110 определяет GET, HEAD, OPTIONS и TRACE как безопасные методы в смысле того, что клиент не запрашивает изменение состояния. Стандарт не говорит, что POST с параметром запроса безопасен, и не определяет dryRun как стандартный управляющий параметр.
Поэтому разработчик endpoint должен сделать режим видимым и в запросе, и в ответе. POST часто остаётся подходящим выбором: для планирования сложной записи нужен body, а оценка может быть затратной. Важно, чтобы клиенты, логи и люди могли отличить превью от выполнения без догадок.
Для простой операции легко читается явное поле в body:
POST /v1/projects/prj_184/memberships/plan
Content-Type: application/json
{
"subject_id": "usr_92",
"role": "admin",
"if_match": "73"
}
Отдельный endpoint /plan подходит, когда у планирования есть собственный результат, жизненный цикл или права доступа. Он также устраняет частую проблему флагов в query: сгенерированный клиент не передал флаг, прокси проигнорировал его в настройках кэша или вызывающая сторона неверно скопировала URL и выполнила запись. Если вы выбираете один endpoint с полем mode, отклоняйте пропущенные и неизвестные значения в операциях, где случайное выполнение опасно.
Возвращайте тип ответа, который нельзя принять за выполненный ресурс. 201 Created с body в форме ресурса плохо подходит для превью, даже если добавить поле preview: true. Используйте 200 OK для немедленного плана или 202 Accepted, только если само планирование выполняется асинхронно. Добавляйте в body ответа mode: "preview" и явно задавайте тип содержимого, если API использует типизированные media types.
Не кэшируйте превью, если не понимаете все влияющие на него входные данные, включая личность вызывающей стороны и авторизацию. Самое безопасное значение по умолчанию: Cache-Control: no-store. Устаревший план это не просто старая страница. Он может направить агента к записи, которая теперь затрагивает другой набор ресурсов.
Не используйте OPTIONS для этой задачи. RFC 9110 применяет его для описания вариантов взаимодействия, а не для имитации записи с произвольным body. Сервис, который перегружает этот метод, запутает библиотеки, средства безопасности и всех, кто ожидает обычного поведения HTTP.
Выполнение должно доказать, что план всё ещё актуален
Превью может устареть до выполнения. Другой пользователь изменит запись, запланированное задание запустится, право доступа истечёт, а агент изменит запрос после чтения ответа. Это проблема времени между проверкой и использованием, и обнадёживающее превью её не устраняет.
Связывайте план с оценённым запросом, ревизиями прочитанных ресурсов, личностью вызывающей стороны и коротким сроком действия. Сервер может вернуть подписанный непрозрачный plan_token или сохранить план и вернуть его идентификатор. Непрозрачные токены не позволяют клиенту воспринимать план как редактируемое полномочие. Сохранённые планы удобнее для просмотра больших последствий и отзыва одобрения. Подходят оба варианта, если выполнение повторно проверяет нужные условия.
Ответ может содержать:
{
"mode": "preview",
"plan_id": "plan_7f4c",
"expires_at": "2025-06-18T14:05:00Z",
"request_digest": "sha256:...",
"read_revisions": [
{"resource": "projects/prj_184", "revision": "73"}
],
"executable": true
}
При выполнении сервис должен проверить вызывающую сторону, digest, срок действия и ревизии. Затем он должен либо атомарно применить уже одобренный план, либо заново построить план внутри транзакции записи и сравнить его с одобренным. Если эквивалентность гарантировать нельзя, запрос следует отклонить с plan_stale и попросить новое превью.
Не позволяйте агенту предварительно просматривать запрос для одного субъекта, а выполнять plan ID с другим субъектом в body. Ещё лучше, если выполнение принимает только plan ID и ожидаемую ревизию: тогда у сервера не будет второй изменяемой копии запроса, которую нужно согласовывать.
Для некоторых изменений нельзя дать содержательную гарантию. План отправки сообщения может стать неуместным, если адрес получателя изменится мгновением позже. План вызова стороннего сервиса может зависеть от цены, которая изменится до вызова. Указывайте это в результате, проверяйте данные непосредственно перед необратимым действием и требуйте нового решения, если разница важна.
Рабочий процесс агента должен предусматривать остановку перед выполнением
Агент должен воспринимать превью как основание для решения, а не как разрешение автоматически выполнять запись. Ему нужны правила: когда можно выполнить действие, когда следует исправить запрос, а когда необходимо показать план человеку.
Самый надёжный процесс состоит из четырёх действий:
- Отправить предполагаемую запись в режиме превью с ключом идемпотентности и ожидаемыми ревизиями ресурсов.
- Остановиться при наличии блокеров, затем исправить только поля, указанные в ответе, или запросить у человека недостающие намерения.
- Показать планируемые последствия и предупреждения, если операция пересекает границу одобрения команды.
- Выполнить только возвращённый план, пока он актуален, а результат выполнения записать отдельно от превью.
Одобрение должно сосредотачиваться на последствиях, а не на необработанном JSON. Человеку, который решает, выдавать ли доступ, нужно увидеть субъекта, роль, ресурсы, доступные через раскрытие групп, и срок действия. Он не должен восстанавливать последствия из body, заполненного идентификаторами.
Не заставляйте агента делать превью каждого безобидного действия и просить одобрение на каждое предупреждение. Так появится очередь карточек, которые никто не читает. Определите осмысленные границы в приложении: необратимые операции, изменения доступа и денег, внешние сообщения, большие наборы ресурсов и действия, последствия которых сервис помечает как неопределённые. Агент может самостоятельно выполнять небольшие понятные изменения в пределах выданных ему полномочий.
Sallyport может требовать решения человека перед фактическим HTTP- или SSH-вызовом агента, а превью API придаёт этому решению конкретное содержание. Эти два механизма решают разные задачи: один определяет, может ли процесс действовать, другой объясняет, что сделает целевой сервис.
Неудачное массовое изменение показывает, почему сводки недостаточно
Представим, что агенту поручено удалить подрядчиков из группы поддержки production. Он находит фильтр, соответствующий 37 учётным записям, и отправляет превью. Сервис возвращает count: 37, valid: true и общее замечание, что унаследованные членства могут измениться. Оператор одобряет действие, потому что ожидаемый результат кажется обычным.
При выполнении сервис удаляет прямое членство для этих 37 учётных записей. Четыре из них сохраняют доступ через вложенные группы. Ещё шесть теряют отдельное право дежурного, потому что сервис также удаляет связанную привилегию. Задание уведомлений сообщает всем 37 людям об изменении доступа. Теперь оператору нужно выяснять, какие последствия были ожидаемыми, какие скрытыми и описывало ли уведомление фактическое состояние доступа.
Превью было технически правдивым в самом узком смысле. Оно не обещало, что фильтр выбрал только подрядчиков. Но это всё равно был плохой интерфейс: вместо графа членства и списка последствий пользователь получил число.
Лучший ответ группирует результат по последствиям:
{
"mode": "preview",
"operation": "remove_group_members",
"selected": 37,
"effects": [
{"type": "direct_membership_removed", "count": 37},
{"type": "access_retained_via_nested_group", "subjects": ["usr_8", "usr_19", "usr_31", "usr_44"]},
{"type": "on_call_entitlement_removed", "subjects": ["usr_2", "usr_7", "usr_11", "usr_24", "usr_29", "usr_35"]},
{"type": "notification_queued", "count": 37}
],
"validation": [
{
"severity": "warning",
"code": "access_outcome_varies",
"message": "Four selected subjects retain group-derived access."
}
]
}
Для крупных пакетов в ответ можно добавить скачиваемый отчёт или постраничные подробности. Смысл не в том, чтобы заставить человека читать тысячи строк. Нужно сделать видимыми исключительные и необратимые последствия до записи.
Этот пример также показывает ошибочность распространённого совета: «используйте пробные запуски только для разрушительных действий». Команды повторяют его, потому что удаления кажутся опасными, а превью требует инженерных затрат. Но выдача прав, изменение конфигурации или уведомление могут иметь больший радиус последствий, чем удаление. Выбирайте поддержку превью по последствиям и обратимости, а не по HTTP-методу или операции с базой данных.
Тесты должны сравнивать последствия превью и выполнения
Endpoint превью приходит в негодность, если тесты доказывают только то, что он возвращает ответ 200. Его главное обещание состоит в эквивалентности: при одинаковых состоянии и запросе заявленные последствия должны совпадать с выполнением.
Создавайте парные тесты. Подготовьте fixture, отправьте превью, сохраните нормализованный план, восстановите fixture, выполните тот же замысел и сравните журнал выполнения с предсказанным набором последствий. Игнорируйте поля, которые не могут разумно совпасть, например серверные временные метки или сгенерированные correlation ID. Не игнорируйте созданные ресурсы, изменённые значения, опубликованные события, уведомления и исходящие вызовы.
Property-тесты полезны для фильтров и массовых операций. Создайте коллекцию ресурсов с разными состояниями, запросите превью по предикату, выполните его в свежей копии и проверьте совпадение выбранного набора и итогового состояния. Такие тесты находят неприятные случаи, когда запрос планирования соединяет одну таблицу, а запрос записи другую.
Отдельно проверяйте побочные эффекты превью. Используйте поддельные адаптеры для почты, webhook, очередей и платёжных провайдеров, которые завершают тест с ошибкой, если режим превью обращается к ним. Затем проведите хотя бы один интеграционный тест с настоящим уровнем хранения: ORM или триггер могут записать данные, даже если код приложения выглядит чистым.
Наконец, специально проверяйте устаревание. Предварительно просмотрите изменение, измените ресурс другим запросом, затем выполните старый план. Сервис должен отклонить его. Система, которая применяет старый план, потому что diff «всё ещё достаточно похож», рано или поздно перезапишет чужую работу.
Превью это возможность API, а не повод отказаться от контроля
Endpoints превью уменьшают неожиданности. Они не заменяют авторизацию, проверки конкурентности, дизайн транзакций, идемпотентность, журналы аудита или проверку операций, которым она нужна. Вызывающая сторона без полномочий не должна получать подробную карту защищённых ресурсов через запросы превью. Повторный запрос выполнения не должен создавать тот же побочный эффект дважды только потому, что plan token действителен.
Начните с записи, которая сильнее всего навредила команде на репетиции или в production. Перечислите все прямые и косвенные последствия, реализуйте план, который их показывает, и заставьте выполнение отклонять устаревшие планы. Затем напишите парный тест, доказывающий согласованность превью и выполнения. Если вы не можете описать, что сделает запись до её запуска, риск связан не с агентом. Он связан с API.
Вопросы и ответы
Что такое endpoint для пробного запуска?
Пробный запуск оценивает предлагаемое действие и показывает, что произойдёт, не изменяя целевую систему. Полезный ответ содержит предполагаемую операцию, затронутые ресурсы, различия или другой эквивалентный план, результаты проверки и сделанные допущения.
Пробный запуск это то же самое, что API только для чтения?
Нет. Запрос на чтение показывает текущее состояние, а превью вычисляет результат конкретной предлагаемой записи. Если агент хочет изменить настройку, превью должно оценить именно эту настройку и её зависимости, а не просто получить текущую конфигурацию.
Для каких действий агента нужны endpoints для предварительного просмотра?
Используйте пробные запуски перед значимыми изменениями: развёртыванием, изменениями инфраструктуры и прав доступа, миграциями данных, разрушительной очисткой и внешними уведомлениями. Не добавляйте их ради простой операции без побочных эффектов, например создания изолированного черновика.
Должны ли запросы на превью требовать авторизацию?
Превью должно использовать те же границы авторизации, что и выполнение. При желании можно дать отдельное разрешение на планирование без записи. Нельзя раскрывать чувствительные данные о текущем состоянии, скрытые имена ресурсов или подробности обширной инвентаризации только потому, что запрос содержит флаг пробного запуска.
Как показывать ошибки проверки в ответе пробного запуска?
Возвращайте машиночитаемый список ошибок со стабильными кодами, JSON-путями, понятными человеку сообщениями и полем, указывающим, будет ли выполнение заблокировано. Агенту нужны структурированные данные для исправления, а человеку достаточно понятного описания, чтобы оценить разумность операции.
Может ли пробный запуск всё же вызвать побочные эффекты?
Пробный запуск безопасен, если endpoint не создаёт постоянных побочных эффектов и считает скрытые подготовительные записи ошибками. Проверяйте резервирование ресурсов, обновление временных меток, создание записей, расход лимитов, постановку заданий в очередь, письма, webhook-вызовы и записи аудита, которые могут случайно запустить дальнейшие процессы.
Может ли агент выполнить старое превью?
Да, но выполнение нужно привязать к идентификатору плана, ревизии цели, сроку действия и личности исполнителя. При выполнении пересчитайте план или отклоните его, если эти данные изменились. Сохранённое превью без таких проверок создаёт ложное чувство уверенности.
Использовать параметр dryRun в запросе или отдельный endpoint?
Используйте явное поле вроде «mode»: «preview» или отдельный маршрут для превью, если операция достаточно сложна, чтобы заслуживать собственного ресурса. Не полагайтесь на свободный параметр запроса, который библиотеки, кэши или правила прокси могут незаметно отбросить. Из запроса и ответа должно быть сразу понятно, что это превью.
Гарантирует ли аккуратный diff успешное выполнение?
Нет. Различия могут выглядеть безопасно, но операция всё равно может выполняться без нужных прав, конфликтовать с текущей записью, превышать лимит или опираться на устаревшую версию. Надёжное превью проверяет запрос по тем же правилам и против того же текущего состояния, что и выполнение.
Может ли Sallyport делать пробные запуски для API, которые их не поддерживают?
Sallyport может поставить одобрение перед фактическим HTTP- или SSH-действием агента, но целевой сервис всё равно должен предоставлять достоверное превью, если человек должен сначала проверить предполагаемое изменение. Шлюз не может восстановить предметные последствия, о которых API ничего не сообщает.