# Изменения DNS AI-агентами программирования: сделайте ревью безопаснее

Изменения DNS, которые выполняют AI-агенты программирования, должны иметь более строгую форму, чем обычные правки инфраструктуры. Агент не должен представлять «обновить DNS» как одно одобрение. Он должен показывать создание, редактирование или удаление как отдельные операции с разными подтверждениями, вопросами для ревьюера и сценариями обработки ошибок.

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

## У DNS-операций разные риски

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

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

Удаление утверждает, что теперь правильным состоянием считается отсутствие записи. Диапазон возможных сбоев здесь самый широкий, поскольку безопасный результат часто зависит от чего-то за пределами DNS. Возможно, новому endpoint потребуется время, чтобы начать получать трафик. Возможно, другой команде все еще нужна проверочная TXT-запись. Возможно, запись участвует в схеме отказоустойчивого переключения, которую агент не способен вывести из репозитория.

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

Термины DNS тоже требуют аккуратности. Имя владельца DNS может содержать RRset, то есть набор записей с одинаковыми именем и типом. Многие API провайдеров показывают один объект «record», но при обновлении могут заменять весь RRset. Если агент хочет удалить одно из двух значений A, а провайдер заменяет набор целиком, он может случайно удалить оба. Прежде чем определять смысл операции, изучите фактическую модель объектов провайдера.

## Каждая заявка должна объявлять одну необратимую операцию

DNS-операция должна содержать один глагол: `create`, `edit` или `delete`. Не принимайте на границе одобрения расплывчатые методы с именами `apply`, `sync` или `upsert`. Программисту такие имена удобны, а оператору обходятся дорого.

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

Используйте форму запроса, которая сохраняет намерение и дает исполнителю достаточно данных для обнаружения расхождения:

```json
{
  "action": "edit",
  "zone": "example.net",
  "owner": "api.example.net.",
  "type": "A",
  "expected_rrset": {
    "ttl": 300,
    "values": ["198.51.100.24"]
  },
  "proposed_rrset": {
    "ttl": 300,
    "values": ["198.51.100.81"]
  },
  "reason": "Move the API endpoint after the service owner confirmed health checks",
  "verification": [
    "authoritative lookup returns 198.51.100.81",
    "HTTPS health check succeeds at the new endpoint"
  ]
}
```

Для операции create поле `expected_rrset` должно указывать, что RRset отсутствует, либо описывать допустимое существующее состояние, если этого требует модель провайдера. Для удаления `proposed_rrset` должно явно указывать на отсутствие. Не представляйте отсутствие пустой строкой и не пропускайте поле. Неоднозначные данные порождают неоднозначные журналы.

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

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

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

## Для создания нужны подтверждения владения и отсутствия конфликтов

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

До предложения создания агент должен проверить точное имя владельца во всех типах записей. Особенно важно это для CNAME. RFC 1034 говорит, что если в узле есть CNAME RR, других данных там быть не должно. Планировщик, который ищет только существующую CNAME, может предложить добавить CNAME к имени, где уже есть адрес, почта или TXT-данные. Провайдер может отклонить запись или, что хуже, заменить данные по своему специальному правилу.

При ревью создания нужно простым языком показать четыре вещи:

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

Проверка цели зависит от данных. Для записи A или AAAA агент может подтвердить, что адрес принадлежит ожидаемому реестру развертываний, если такой реестр существует. Для CNAME нужно разрешить цель и убедиться, что имя указано полностью. Для MX следует проверить имя почтового узла и приоритет с владельцем почтовой системы. Для TXT-записи, которую использует внешний проверяющий сервис, нужно сохранить точное полученное значение, не «исправляя» пробелы и кавычки по догадке.

Не позволяйте агенту считать новый поддомен безобидным только потому, что он находится внутри знакомого домена. `login`, `auth`, `mta`, `vpn` и `admin` очевидно важны, но значение могут иметь и произвольные имена. Публичное DNS-имя становится интерфейсом, как только от него кто-то начинает зависеть.

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

## При редактировании нужно назвать заменяемое состояние

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

Требуйте предыдущий RRset в каждом запросе на редактирование, включая значения, которые агент собирается сохранить. Рассмотрим A RRset с двумя адресами:

```text
Current:  app.example.net. 60 IN A 198.51.100.10
          app.example.net. 60 IN A 198.51.100.11
Proposed: app.example.net. 60 IN A 198.51.100.11
          app.example.net. 60 IN A 198.51.100.12
```

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

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

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

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

## Для удаления нужна причина отсутствия

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

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

Запрос на удаление должен содержать следующие сведения:

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

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

Отрицательное кэширование усложняет восстановление после удаления. RFC 2308 описывает, как резолверы кэшируют отрицательные ответы, включая NXDOMAIN и ответы об отсутствии данных. Если удалить запись, а затем восстановить ее, некоторые клиенты могут продолжать видеть прежнее отсутствие, пока не истечет срок отрицательного кэша. Это не запрещает удаление. Но план отката должен учитывать период, когда авторитетный DNS уже исправлен, а пользователи все еще получают кэшированную ошибку.

Операция delete не должна молча расширяться, если провайдер не позволяет удалить отдельное значение. Исполнитель должен сообщить, что провайдер требует заменить весь RRset, и запросить новую явную операцию. Такое дополнительное одобрение дешевле объяснений, почему якобы неиспользуемая адресная запись исчезла.

## Карточкам ревью нужен контекст, а не только разница зоны

Обычная разница дает ревьюеру данные, но часто скрывает последствия. Карточка должна начинаться с короткой фразы, которая называет операцию и ожидаемое поведение: «Удалить устаревшую TXT-запись проверки для `verify.example.net`; заказчик подтвердил завершение внешней проверки». Затем покажите точные записи.

Без отдельного экрана в карточке должны быть видны: глагол операции, зона, имя владельца, тип, старое состояние, предлагаемое состояние, TTL, заказчик, идентификатор процесса агента, причина и план проверки. Если запись может затронуть весь RRset, укажите это в первой строке.

Используйте прямые формулировки. «Заменить оба значения A» лучше, чем «синхронизировать набор записей». «Удалить эту MX-запись» лучше, чем «применить желаемую конфигурацию». Ревьюеры быстрее принимают решения, когда система использует рабочие глаголы, а не абстракции.

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

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

### Пример ревью

Предположим, агент должен перевести `api.example.net` на новый адрес. Плохая карточка говорит: «Обновить DNS для API». Ревьюер не может понять, заменит ли провайдер RRset из двух элементов.

Понятное предложение на редактирование выглядит так: «Заменить `198.51.100.24` на `198.51.100.81` в A RRset для `api.example.net`; сохранить `198.51.100.25`; оставить TTL 300». Затем карточка показывает полные текущий и предлагаемый RRset, называет владельца сервиса и сообщает, что после записи агент запросит авторитетный сервер имен и запустит одобренную проверку работоспособности.

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

## DNS-кэши делают время частью одобрения

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

Поэтому в одобрении нужно указывать желаемую точку проверки. «Авторитетные серверы возвращают новый ответ» подтверждает запись. «Публичный резолвер возвращает новый ответ» подтверждает часть распространения изменения. «Приложение обслуживает трафик по новому адресу» подтверждает нужный пользователям результат. Это разные проверки.

Используйте команды, которые показывают источник ответа. Замените имена и адрес на фактический авторитетный сервер зоны:

```sh
dig @ns1.example.net api.example.net A +noall +answer

; expected answer shape
api.example.net. 300 IN A 198.51.100.81

dig @1.1.1.1 api.example.net A +noall +answer

; resolver answer can retain an older value with a lower remaining TTL
api.example.net. 117 IN A 198.51.100.24
```

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

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

Изменения делегирования DNS требуют еще большей осторожности. Редактирование NS-, glue-, DS-записей и других данных, связанных с делегированием, может повлиять на разрешение имен за пределами дочерней зоны. Агент должен отнести их к отдельному классу изменений и потребовать ревьюера, который отвечает за связь родительской и дочерней зон. Не включайте их в обычный процесс работы с записями хостов.

## Широкие DNS-права - плохой обходной путь

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

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

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

Здесь особенно опасны общие endpoint с `upsert`. Они позволяют агенту объединить создание, редактирование и удаление в одном теле запроса, часто с настройками провайдера по умолчанию, которые ревьюер не видит. Определите отдельные методы исполнителя и пусть каждый проверяет обязательные подтверждения своей операции. Метод удаления должен отклонять тело с состоянием замены. Метод редактирования должен отклонять запрос без ожидаемого состояния.

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

## Устаревший план может удалить не ту рабочую запись

Типичный сбой начинается с того, что агент читает DNS в начале долгой задачи программирования. Он видит у `cdn.example.net` одну цель CNAME и планирует заменить ее после развертывания. Пока агент работает, дежурный инженер меняет цель, чтобы перенаправить трафик во время инцидента.

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

Повторное чтение перед записью предотвращает такой сбой. Исполнитель читает RRset, сравнивает его с `expected_rrset` операции и отказывается от записи, если цель дежурного инженера отличается. В журнале нужно сохранить оба значения и сообщить ревьюеру, что запись изменил другой участник. Не пытайтесь автоматически объединять изменения.

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

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

## Журналы должны сохранять именно то, что одобрил ревьюер

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

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

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

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

Первое практическое изменение небольшое: запретите общие DNS-записи на границе одобрения. Требуйте для каждого запроса create, edit или delete, для редактирования и удаления запрашивайте полный ожидаемый RRset, а перед выполнением отклоняйте расхождения. Одно это ограничение делает предложенную агентом DNS-работу достаточно понятной, чтобы человек заметил действительно важные ошибки.
