# Устаревшая документация API: как безопасно тестировать действия агентов

Устаревшая документация API - это проблема безопасности агентов, а не просто редакционная недоработка. Человек может прочитать старый пример, усомниться и спросить коллегу. Автономный программирующий агент часто превращает тот же пример в запрос, а затем использует ответ как подтверждение, что все сделал правильно.

Это меняет требования. Если документация объясняет агенту, как вызвать API, обновить токен, удалить запись или обратиться к рабочему хосту, относитесь к тексту как к исполняемому вводу. Тестируйте его вместе с самим инструментом. Страница могла быть точной в момент публикации, но теперь не соответствовать работающему сервису и направить агента к опасному действию, даже если API функционирует именно так, как задумали его текущие разработчики.

Самое опасное расхождение редко приводит к явной ошибке. Ошибка 404 заметна. А вот запрос, который по-прежнему возвращает 200, но выбирает больше ресурсов, использует изменившееся значение по умолчанию или обходит ожидаемое подтверждение, может остаться незамеченным. Именно такое несоответствие оставляет аккуратный журнал активности и создает большие проблемы.

## Документация становится частью контура управления агентом

Агент использует документацию, чтобы выбирать операции, заполнять параметры, интерпретировать ответы и решать, нужно ли повторять запрос. Поэтому примеры, справочные таблицы, руководства по аутентификации и заметки о миграции входят в его контур управления. Код сервиса может быть правильным, но этот контур все равно подскажет агенту неправильный способ работы.

Команды часто проводят искусственную границу между описанием инструмента и руководством. Описание инструмента говорит `deleteProject(project_id)`. Руководство объясняет, откуда взять идентификатор проекта, есть ли пробный запуск, удаляются ли связанные объекты и что делать после ошибки авторизации. Агенту нужны оба источника. Если один из них содержит неверные сведения, действие может оказаться неправильным.

Поэтому устаревший пример отличается от опечатки в абзаце. Представьте старую инструкцию, в которой сказано, что отсутствие параметра `scope` означает «текущий проект». Позже backend меняется, и то же отсутствие параметра начинает означать «все проекты, доступные этим учетным данным». Endpoint по-прежнему работает. Пример по-прежнему разбирается. Агент, следующий старому руководству, теперь может применить якобы локальное изменение ко всей учетной записи.

Документация также определяет уверенность агента. Конкретные фрагменты кода обычно важнее расплывчатого предупреждения в соседнем тексте. Если на одной странице сказано «используйте минимальные привилегии», а на другой приведен пример bearer-токена с доступом ко всей учетной записи, на практике победит пример. Агенты стремятся выбрать путь, который приводит к результату.

Считайте документацией, влияющей на действия, следующие материалы:

- Примеры запросов и команд
- Таблицы параметров с описанием значений по умолчанию и допустимых вариантов
- Инструкции по аутентификации, учетным данным и настройке среды
- Рекомендации по повторам, пагинации, идемпотентности и обработке ошибок
- Инструкции по миграции и устаревшим функциям, которые объясняют, какая операция заменяет другую

Полезно различать **синтаксическое расхождение** и **расхождение смысла**. При синтаксическом расхождении пример перестает работать, потому что изменилось поле или путь. При расхождении смысла пример остается допустимым, но начинает влиять на другие объекты. Синтаксическая ошибка ставит автора в неловкое положение. Расхождение смысла может повредить данным, привести к расходам, раскрыть записи или расширить доступ. Тесты должны обнаруживать оба вида проблем.

## Успешный запрос все равно может доказать, что пример неверен

Тест документации, который проверяет только коды состояния, ловит простые ошибки и пропускает опасные. Успешный HTTP-ответ говорит, что сервер принял запрос. Он ничего не говорит о том, был ли выбран нужный объект, возник ли описанный побочный эффект и соблюдена ли заявленная граница.

Представим, что справочная страница публикует такой запрос:

```bash
curl -sS -X POST "$API_URL/v1/exports" \\
  -H "Authorization: Bearer $TOKEN" \\
  -H "Content-Type: application/json" \\
  -d '{"project":"demo","include_archived":false}'
```

Простой тест проверяет `202 Accepted` и объявляет победу. Но он не замечает несколько важных изменений:

- Сервис молча переименовал `project` в `project_id` и считает старое поле отсутствующим.
- `include_archived` превратился из способа исключить архивные данные в игнорируемое поле для совместимости.
- Учетные данные получили доступ ко всей учетной записи, поэтому `demo` указывает на проект другого клиента.
- Endpoint по-прежнему ставит задачу в очередь, но теперь экспортирует вложения, которые, согласно руководству, должны быть исключены.

Тест должен проверять результат и состояние сервиса, а не только статус. В одноразовой учетной записи создайте одну активную и одну архивную запись. Отправьте описанный запрос. Дождитесь выполнения задачи. Убедитесь, что артефакт содержит активную запись, не содержит архивную и сохраняет ожидаемый идентификатор проекта. Если сервис не предоставляет достаточно данных для такой проверки, документация тоже не может безопасно обещать это поведение.

RFC 9110 определяет коды состояния HTTP как результат обработки запроса. В нем не утверждается, что успешный статус доказывает соответствие намерению вызывающей стороны. Это очевидно, но команды по-прежнему строят проверки документации по схеме `curl` плюс `grep 200`. Используйте семантику HTTP для проверок протокола, а затем добавляйте проверки реального результата.

Хороший тест называет утверждение, которое он проверяет. `export_excludes_archived_records` полезен. `docs_example_returns_success` в основном сообщает, что кто-то отправил запрос.

## Для примеров нужны контрактные тесты, а не проверка скриншотов

Скопировать пример со страницы документации в терминал во время проверки перед выпуском лучше, чем ничего. Но такой подход плохо масштабируется, не оставляет надежных доказательств и сосредоточен на успешном сценарии. Кроме того, люди часто исправляют команду локально и забывают поправить страницу.

Храните исполняемые примеры в структурированном исходном файле, создавайте из него отображаемый фрагмент и запускайте тот же источник в CI. Можно использовать примеры OpenAPI, извлечение кода из Markdown или отдельный каталог с тестовыми данными. Механизм не так важен, как одно свойство: команда, которую видит читатель, должна совпадать с командой, которую запускает тест.

Не поддерживайте молча отдельную «тестовую версию», в которой URL безопаснее, области действия уже или заголовки полнее, чем в опубликованном примере. Такое разделение создает приятный зеленый статус сборки, пока публичные инструкции устаревают. Параметризуйте только значения, которые действительно отличаются в разных средах, например базовый URL, тестовые учетные данные и идентификаторы тестовых данных. Метод, путь, структуру тела и важные для безопасности флаги оставляйте одинаковыми.

Небольшой shell-тест задает нужный шаблон:

```bash
set -euo pipefail

project_id="docs-check-$RANDOM"
response=$(curl -sS -X POST "$API_URL/v1/projects" \\
  -H "Authorization: Bearer $DOCS_TEST_TOKEN" \\
  -H "Content-Type: application/json" \\
  -d "{\"id\":\"$project_id\",\"name\":\"Documentation check\"}")

printf '%s' "$response" | jq -e \\
  --arg id "$project_id" \\
  '.id == $id and .name == "Documentation check" and .archived == false'
```

Ожидаемый результат `jq -e` - логическое значение JSON `true`; при несовпадении команда завершится с ненулевым кодом. Важен не синтаксис shell. Утверждение фиксирует содержание прозы: API создает проект с переданным идентификатором, сохраняет указанное имя и не архивирует его по умолчанию.

Добавьте отдельную проверку отображаемой документации. Если извлекатель Markdown берет со страницы блок с пометкой `bash`, тест должен запускать этот блок после подстановки разрешенных переменных среды. Если документация создается из описания OpenAPI, тестируйте сгенерированный пример, а не похожую команду, скопированную вручную.

Проверка скриншотов по-прежнему полезна для оценки читаемости. Она не доказывает работоспособность. Рецензент довольно часто пропускает отсутствующий заголовок, особенно когда на странице несколько похожих примеров. Машины не устают сравнивать поле с тестовыми данными.

## Проверки работающего сервиса должны охватывать значения по умолчанию и ошибки

Большинство опасных изменений API касается значений по умолчанию, границ авторизации и обработки ошибок. Успешные сценарии не затрагивают ни одну из этих областей, потому что их проще писать и поддерживать в рабочем состоянии.

Проверяйте случаи с пропущенными параметрами, которые может создать агент. Агенты часто формируют тело запроса условно, поэтому необязательное поле может исчезнуть, если предыдущий поиск не вернул значение. Для каждого описанного необязательного параметра решите, безопасно ли его пропустить, нужно ли отклонить запрос или результат будет существенно иным. Затем напрямую проверьте заявленное поведение.

Для операции с полем `dry_run` в изолированной среде выполните как минимум такие проверки:

1. `dry_run: true` возвращает план и не меняет тестовые данные.
2. `dry_run: false` выполняет заявленное изменение только для указанного объекта.
3. При отсутствии `dry_run` запрос отклоняется или используется описанное значение по умолчанию.
4. Токен с недостаточной областью доступа отклоняется до внесения любых изменений.
5. Повторный описанный запрос ведет себя так, как сказано в инструкции об идемпотентности.

Третий случай выявляет распространенный источник случайного ущерба. Команда сервиса меняет значение по умолчанию в пользу интерактивных пользователей, а документация по-прежнему предполагает старое поведение. Человеческий интерфейс может показать экран подтверждения. У клиента API такого экрана нет.

Примеры ошибок тоже нужно тестировать. В документации часто написано «повторите запрос при 429», но не указано, содержит ли ответ `Retry-After`, безопасно ли повторять операцию и нужен ли токен идемпотентности. Такая рекомендация может превратить кратковременное ограничение частоты запросов в двойные счета, повторные развертывания или многократный отзыв доступа.

Проверяйте именно инструкции для ошибки. Создайте ограничение частоты в тестовом сервисе или контролируемой среде. Убедитесь, что описанный клиент читает указанный заголовок, ждет нужное время и отправляет тот же идентификатор идемпотентности, если API его поддерживает. Если сервис не может предсказуемо создать такую ошибку, опишите неопределенность вместо уверенного рецепта.

У ключевого слова `default` в OpenAPI есть связанная с этим ловушка. В описаниях JSON Schema и OpenAPI объявленное значение по умолчанию обычно сообщает, что инструменты могут предполагать или показывать. Оно не заставляет каждый сервер автоматически применять это значение. Проверьте развернутый сервис с пропущенным полем. Значение по умолчанию в схеме и значение по умолчанию на сервере остаются разными утверждениями, пока тест не свяжет их.

## Для разрушительных сценариев нужны одноразовые тестовые данные

Не тестируйте разрушительные примеры в общей staging-учетной записи и не объявляйте это безопасным. В общих средах накапливаются старые тестовые данные, ручные эксперименты и учетные данные с непонятной областью доступа. Рано или поздно тест документации выберет не тот объект или команда очистки выйдет за ожидаемые границы.

Используйте отдельный тестовый клиент или учетную запись с учетными данными, которым доступна только тестовая область. Создавайте каждый объект с уникальной меткой запуска. Перед изменением находите его по этой метке. После теста проверяйте получившееся состояние, а не считайте, что API выполнил обещанное только на основании ответа.

Пример удаления должен показывать полный жизненный цикл:

```text
create fixture: docs-delete-<run-id>
read fixture: confirm owner=test-suite and run_id=<run-id>
delete fixture: send the rendered documentation request
read fixture: expect the documented absence or tombstone state
list nearby fixtures: confirm unrelated fixtures remain
```

Последняя проверка важна. Тест удаления, который подтверждает только исчезновение выбранного объекта, не обнаружит слишком широкий селектор. Я видел, как команды принимали endpoint массового удаления, потому что один тестовый объект исчезал как ожидалось, хотя endpoint также удалял все ресурсы с похожим префиксом. Тесту был нужен намеренно похожий соседний объект, который должен был сохраниться.

Не учите агентов использовать удобные селекторы вроде `latest`, `all`, пустого фильтра или понятного человеку имени, если есть неизменяемый идентификатор. В учебнике такие селекторы кажутся удобными, но становятся опасными, когда агент выполняет рецепт в занятой учетной записи. Если операции действительно нужен широкий селектор, укажите область действия в теле запроса или аргументах команды, где ее сможет увидеть проверяющий. Не прячьте ее в серверном значении по умолчанию.

Для необратимых операций публикуйте предварительное чтение и заставляйте пример использовать его результат. Сначала получите объект и проверьте его неизменяемый идентификатор и важное состояние, затем отправьте запрос на изменение. Это добавляет трения. Такое трение дешевле объяснений, почему агент удалил объект, который случайно имел то же отображаемое имя.

## Тесты инструментов должны сравнивать смысл, а не только схемы

Проверка схемы необходима, но схемы обычно точнее описывают форму данных, чем последствия. Тело запроса может соответствовать всем ограничениям типов и при этом направлять действие в неправильную среду или выполнять его с лишними привилегиями.

Стройте проверки вокруг четырех вопросов: кого затронуло действие? Какое состояние изменилось? Какое состояние не изменилось? Какая личность авторизовала действие? Эти вопросы подходят для HTTP API, SSH-команд и внутренних инструментов.

Для HTTP сохраняйте идентификатор запроса, если сервис его предоставляет, затем запрашивайте получившийся ресурс или запись аудита в тестовой среде. Сопоставляйте идентификатор запроса, исполнителя, цель и изменение. Для SSH запускайте команды на одноразовом хосте, сохраняйте код завершения и вывод, а затем отдельной проверочной командой изучайте состояние хоста. Не поручайте команде действия самой проверять свою работу.

Полезные тестовые данные должны содержать контраст. Если вы проверяете команду, которая должна перезапустить один сервис, создайте другой сервис, который обязан остаться запущенным. Если вы проверяете запрос только к одному репозиторию, добавьте второй репозиторий, видимый тем же учетным данным, но не затрагиваемый запросом. Без контраста широкое действие может выглядеть правильным.

Именно здесь многие команды неправильно используют контрактное тестирование. Инструменты контрактов, управляемых потребителем, могут подтвердить, что поставщик принимает форму запроса и возвращает ожидаемые поля. Они не определяют, выбран ли правильный рабочий аккаунт, приобрела ли опция `force` новый смысл или распространилось ли удаление за пределы описанного объекта. Оставьте контрактный тест. Добавьте проверку результата с тестовыми данными, которые обнаруживают выход за границы.

Описание инструмента тоже нужно тестировать. Если инструмент предоставляет `environment`, не указывайте `production` как допустимое значение, пока тест не подтвердит, что оно направляет запрос на описанный хост и использует заявленный путь авторизации. Агенты используют описания для заполнения аргументов. Устаревшее описание - это всего лишь текстовая версия устаревшего примера API.

## Агенту нужны подтверждение актуальности и безопасный путь отказа

Агент не должен считать документацию актуальной только потому, что она находится в репозитории или внутреннем портале. Дайте ему машиночитаемое подтверждение, связанное с операцией, которую он планирует вызвать.

Простого манифеста может быть достаточно:

```json
{
  "operation": "POST /v1/exports",
  "documentation_source": "docs/api/exports.md#creating-an-export",
  "verified_in": "isolated-test-tenant",
  "verification_commit": "<commit-id>",
  "assertions": [
    "returns an export job",
    "omits archived fixtures when include_archived is false",
    "rejects a token without export scope"
  ],
  "review_required_when": ["production", "include_archived=true"]
}
```

Идентификатор коммита сам по себе не подтверждает надежность. Он позволяет проверить документацию и исходный код теста, на которых основаны результаты. Если процесс выпуска требует ограничения по возрасту проверки, храните время проверки в собственных записях сборки, но не притворяйтесь, будто временная отметка делает старое поведение безопасным. Развертывание сервиса может сделать вчерашний тест неактуальным.

Правило для агента должно быть простым. Если для запрошенной операции нет успешно пройденной проверки развернутого интерфейса, агент должен либо выполнить неизменяющую состояние предварительную проверку в разрешенной тестовой среде, либо попросить человека подтвердить конкретное действие. Ему нельзя импровизировать на основании соседнего endpoint.

Разделяйте понятия «неизвестно» и «безопасно». Агенты склонны заполнять пробелы, потому что выполнение задачи получает положительную обратную связь. Дизайн инструментов должен считать воздержание успешным результатом, если подтверждений нет. Возвращайте причину вроде `documentation example has no verified outcome test for this operation`. Такое сообщение дает разработчику конкретную цель для исправления, а не расплывчатый отказ.

Не пытайтесь решить проблему длинным файлом политик, который перечисляет все рискованные формулировки во всех документах. Текст будет меняться быстрее правил. Связывайте конкретную операцию с конкретным тестом и передавайте агенту результат.

## Контрольные этапы перед выпуском работают только тогда, когда блокируют вводящую в заблуждение страницу

Программа проверки документации не работает, если создает отчеты, на которые никто не обязан реагировать. Проверка должна блокировать публикацию или хотя бы доступ агента к затронутому примеру, когда меняется контракт сервиса.

Свяжите проверки с изменениями в спецификации API, обработчиках маршрутов, middleware аутентификации, сборщиках запросов SDK и исходных файлах документации. Изменение в одной из этих областей должно запускать соответствующие тесты примеров. Если тест не пройден, у команды есть три честных варианта: вернуть старое поведение, обновить документацию и тесты под новое поведение или пометить операцию недоступной для агентов до успешной проверки.

Ручная проверка по-прежнему полезна для принятия решений, но рекомендовать только ее неправильно. Она популярна, потому что кажется дешевой и сохраняет быстрый процесс публикации. Но она требует, чтобы проверяющий мысленно воспроизвел работу сервиса, учетных данных, значений по умолчанию и переходов состояния по одному лишь тексту. Люди не могут надежно делать это на каждом обычном выпуске.

Делайте ошибки понятными. Полезный отчет называет страницу, блок кода, операцию, тестовые данные, наблюдаемый ответ и нарушенное утверждение. «Интеграция документации не прошла» заставляет искать причину. «В exports.md на строке 42 сказано, что архивные записи исключаются, но артефакт экспорта содержит fixture archived-run-817» сразу указывает владельцу, что исправить.

Не ослабляйте тест только потому, что изменение сервиса сделало его неудобным. Сначала решите, было ли старое обещание полезным. Если да, верните его или явно опишите новое ограничение. Если обещание было опасным, удалите пример, а не сохраняйте его с более мягкой формулировкой. Агент обычно следует оставшейся команде.

Для версионируемой документации нужна та же дисциплина. Страница для старой версии API может точно описывать старое развертывание, но все равно вводить агента в заблуждение, если он обращается к текущему базовому URL. Указывайте версию в пути endpoint, URL сервера или метаданных инструмента, чтобы агент мог связать ее с запросом. Заголовок «v1» где-то в верхней части страницы - слабое подтверждение.

## Авторизация уменьшает масштаб ущерба, но не исправляет плохие инструкции

Подтверждение и изоляция учетных данных по-прежнему важны, потому что проверки документации могут пропустить дефекты. Они уменьшают ущерб, если агент выберет неправильную операцию. Но они не превращают устаревшую инструкцию в правильную.

Разделяйте эти две задачи. Проверка документации отвечает на вопрос: «Описывает ли этот пример работающий сервис и его последствия?» Авторизация действия отвечает на вопрос: «Можно ли этому агенту выполнить этот вызов сейчас?» Смешение задач приводит к путанице. Пользователь может одобрить вызов, потому что агент сказал, что экспортирует один проект, а устаревший пример на самом деле экспортирует все проекты, видимые этим учетным данным.

Для действий агентов через HTTP и SSH Sallyport хранит учетные данные отдельно от агента и может потребовать от человека авторизовать сессию или использование конкретных учетных данных. Это полезная последняя граница, если проверка документации не нашла надежных подтверждений или последствия действия требуют участия человека.

На экране подтверждения должны быть указаны операция, цель и область действия в понятных человеку терминах. `POST /v1/exports` недостаточно, если в теле запроса есть `include_archived=true` или селектор для всей учетной записи. Если слой авторизации не может показать значимую область действия, сузьте интерфейс инструмента так, чтобы он мог это сделать.

Записи аудита дают материал для улучшения тестов. Если человек отменил запуск или поставил действие под сомнение, изучите точный запрос, источник документации, на который ссылался агент, и имевшиеся у него подтверждения. Не превращайте такую проверку в поиск виноватого. Используйте ее, чтобы добавить недостающие тестовые данные, утверждение или условие отказа.

## Начните с примера, о котором вероятнее всего придется пожалеть

Не начинайте с самого аккуратного GET-запроса в справочнике. Начните с примера, который может удалить, опубликовать, обновить, выдать доступ, списать деньги или обратиться к рабочему хосту. Дайте ему изолированные тестовые данные, запустите точно отображаемую команду и проверьте как ожидаемое изменение, так и близкое изменение, которого произойти не должно.

Затем свяжите этот тест с источником документации и сделайте сбой заметным до публикации или использования агентом. Эта работа менее эффектна, чем создание нового описания инструмента, но она убирает опасное предположение: будто страница безопасна только потому, что когда-то прошла проверку.

Работающий сервис меняется. Его документация будет меняться медленнее, если не заставить их встретиться в тесте. Сделайте такую встречу частью выпуска, прежде чем агент превратит старую фразу в действие.
