# Как локальное и вышестоящее одобрение меняют контроль над агентом

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

Для ИИ-агента, вызывающего API, локальное и вышестоящее одобрение отвечают на разные вопросы. Локальный барьер решает, может ли этот процесс агента использовать учётные данные или отправить подготовленный запрос. Вышестоящий барьер решает, должен ли удалённый сервис применить запрошенное изменение к собственному ресурсу. Когда API уже просит подтверждение, правильный подход редко сводится к «выбрать более строгий вариант». Выбирайте барьер, который владеет проверяемым фактом, и связывайте его свидетельства с остальными данными о действии.

Это различие важно, потому что за аккуратным экраном одобрения может скрываться большой разрыв в авторизации. Проверяющий может одобрить локальный запрос «развернуть релиз 184», а вышестоящий сервис получит многоразовый токен и другую полезную нагрузку. Или вышестоящий сервис зафиксирует одобрение развёртывания, не зная, что запрос собрал недоверенный процесс агента. Обе записи могут быть правдивыми и всё равно не объяснять действие.

## Локальные и вышестоящие барьеры авторизуют разные вещи

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

Локальный барьер находится рядом с агентом, хранилищем учётных данных, исполнителем команд или шлюзом исходящих действий. Он может проверить исполняемый файл, подпись кода, родительский процесс, начало сеанса, выбранные учётные данные, целевой хост, метод запроса и предлагаемые аргументы. Он также может не передавать секрет агенту и выполнить вызов сам. Это убедительный ответ на вопрос: «Должен ли этот локальный процесс сейчас использовать эту возможность?»

Вышестоящий барьер находится у провайдера API или на связанной с ним плоскости управления. Он может проверить текущую версию ресурса, членство в организации, защищённую среду, политику сервиса, состояние конфликтов и удалённую идентичность проверяющего. Это убедительный ответ на вопрос: «Должно ли именно это удалённое изменение произойти при текущем состоянии сервиса?»

NIST Special Publication 800-207 описывает точку принятия политических решений и точку их исполнения, а также рекомендует переносить исполнение ближе к ресурсу, чтобы неявная зона доверия оставалась небольшой. Этот принцип поддерживает вышестоящее исполнение для фактов, известных только владельцу ресурса. Но локальный барьер он не делает лишним. Сервис не может проверить идентичность локального процесса, если клиент не передаст и не привяжет её, а большинство вызовов с bearer-токеном определяют владельца токена, а не процесс, который инициировал вызов.

OpenSSH хорошо показывает локальный случай. В руководстве OpenBSD для ssh-add сказано, что параметр -c требует подтверждения до того, как агент использует добавленную идентичность. Удалённый SSH-сервер всё равно применяет собственные правила авторизации после этой подписи. Один барьер спрашивает, может ли локальный клиент использовать ключ. Другой спрашивает, соответствует ли получившаяся аутентификация политике сервера. Называть любой из них дубликатом значит не видеть границу, которую защищает каждый.

Практическое правило простое: одобрение должен исполнять компонент, способный предотвратить описываемое действие. Локальное уведомление, которое не может удержать учётные данные или не дать выполнить вызов, лишь создаёт видимость защиты. Вышестоящий комментарий, записанный после изменения, это проверка, а не авторизация.

## Матрица начинается с проверяемого факта

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

<table>
<thead>
<tr><th>Критерий</th><th>Локальное одобрение</th><th>Вышестоящее одобрение</th><th>Следствие для архитектуры</th></tr>
</thead>
<tbody>
<tr><td>Задержка</td><td>Обычно одно локальное взаимодействие плюс время вызова</td><td>Включает сеть, очередь провайдера, уведомление и время проверяющего</td><td>Для частых обратимых вызовов выбирайте локальный вариант, если локального контекста достаточно</td></tr>
<tr><td>Контекст агента</td><td>Может привязать процесс, сеанс, полномочия исполняемого файла, вызов инструмента и локального пользователя</td><td>Обычно видит токен, приложение, рабочую нагрузку или сервисную учётную запись</td><td>Оставляйте локальный барьер, когда происхождение процесса меняет решение</td></tr>
<tr><td>Контекст ресурса</td><td>Видит только полученное или переданное состояние, которое может устареть</td><td>Может проверить текущую версию, правила защиты, владельца и конфликты</td><td>Используйте вышестоящую проверку для изменений, безопасность которых зависит от актуального удалённого состояния</td></tr>
<tr><td>Идентичность человека</td><td>Может привязать человека, присутствующего у устройства</td><td>Может привязать учётную запись организации, роль в команде или разделение обязанностей</td><td>Используйте домен идентичности, которому принадлежит ответственность, или сохраняйте оба</td></tr>
<tr><td>Раскрытие учётных данных</td><td>Может держать секрет вне агента и выдавать только действие</td><td>Часто начинается после того, как клиент уже получил пригодные учётные данные</td><td>Локальное исполнение необходимо, если хранение секрета входит в модель угроз</td></tr>
<tr><td>Сбой сервиса</td><td>Может локально отклонить запрос и сохранить ожидающее намерение</td><td>Не может одобрять, пока сервис или его плоскость одобрения недоступны</td><td>Определите срок действия и отмену. Никогда не считайте сбой согласием</td></tr>
<tr><td>Локальный сбой</td><td>Блокирует защищённые вызовы на этом устройстве</td><td>Может остаться доступным через другой доверенный клиент</td><td>Решите, разрешены ли альтернативные клиенты или они обходят защиту</td></tr>
<tr><td>Детализация аудита</td><td>Может фиксировать запросы, отказы, завершение процессов и попытки вызова</td><td>Может фиксировать принятые запросы и авторитетные изменения ресурса</td><td>Связывайте обе записи стабильным идентификатором действия</td></tr>
<tr><td>Граница вмешательства</td><td>Скомпрометированный хост может атаковать локальные записи или интерфейс</td><td>Провайдер контролирует удалённую запись</td><td>Не требуйте от одного журнала доказывать события за пределами его границы доверия</td></tr>
<tr><td>Охват</td><td>Может единообразно оборачивать множество API</td><td>Покрывает только операции, которые провайдер открыл своему барьеру</td><td>Прежде чем полагаться только на вышестоящее одобрение, составьте список незащищённых путей</td></tr>
</tbody>
</table>

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

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

## Задержка включает ожидание, истечение срока и восстановление человеком

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

Когда один человек наблюдает за агентом на Mac, локальная карточка может появиться, пока контекст запроса ещё свеж. Проверяющий может ответить сразу, а шлюз отправит вызов без второго раунда уведомлений. Это подходит для таких действий, как создание обычного pull request, запрос к защищённому внутреннему API или выполнение известной SSH-команды во время сеанса с участием человека, если последствия действия видны локально.

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

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

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

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

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

## Контекст определяет, может ли проверяющий судить о действии

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

Локальная сторона должна показать, кто запрашивает действие, в понятных человеку терминах: идентичность исполняемого файла, полномочия подписи, если они доступны, родительский сеанс, название инструмента, псевдоним учётных данных, назначение, операцию и читаемую сводку полезной нагрузки. Она должна отличать данные, наблюдаемые шлюзом, от текста, который предоставил агент. Объяснение агента вроде «безопасная очистка» не служит доказательством.

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

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

```json
{
  "action_id": "act_01JQ7M6F4R2K",
  "session_id": "ses_01JQ7KZ9J1AA",
  "caller": {
    "executable": "/usr/local/bin/agent",
    "signing_authority": "Developer ID Application: Example Team"
  },
  "target": {
    "service": "deploy-api",
    "resource": "production/payments",
    "version": "184"
  },
  "request": {
    "method": "POST",
    "operation": "promote",
    "body_sha256": "98b0...e42c"
  },
  "approval": {
    "scope": "single_action",
    "expires_at": "2026-07-24T14:05:00Z"
  }
}
```

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

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

## У действия агента три участника, а не один

У действия агента есть как минимум три идентичности: процесс, который его предложил, субъект учётных данных, который его выполнил, и человек, который его одобрил. Если свести их к одному полю участника, получатся привлекательные журналы, отвечающие не на тот вопрос.

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

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

Сохраняйте все три идентичности и указывайте способ привязки.

<table>
<thead>
<tr><th>Идентичность</th><th>Пример свидетельств</th><th>На какой вопрос отвечает</th></tr>
</thead>
<tbody>
<tr><td>Инициатор</td><td>Подпись процесса, хеш исполняемого файла, ID сеанса</td><td>Какой запущенный код сделал запрос?</td></tr>
<tr><td>Исполнитель</td><td>Субъект API, отпечаток публичного ключа SSH, сеанс роли</td><td>Какие полномочия выполнили вызов?</td></tr>
<tr><td>Проверяющий</td><td>Локальное присутствие пользователя или учётная запись организации на вышестоящей стороне</td><td>Какой человек принял какой риск?</td></tr>
</tbody>
</table>

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

Не называйте агента проверяющим, когда кнопку нажал человек. Не называйте человека вызывающей стороной API, когда запрос выполнила сервисная учётная запись. Фиксируйте делегирование напрямую: инициатор P запросил действие A, проверяющий H авторизовал область S, исполнитель E применил результат R.

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

## После сбоя должно остаться одно устойчивое состояние

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

Используйте явные состояния, например created, locally_approved, submitted, upstream_pending, executing, succeeded, denied, expired и unknown. Конечные состояния должны оставаться конечными. Сохраняйте каждый переход вместе с дайджестом действия и временем. После локального перезапуска можно продолжить наблюдение за ожидающим вышестоящим запросом, но нельзя создавать новое изменение, если это не разрешают правила повторных попыток.

Безопасные правила при сбоях строги:

1. Если локальный барьер недоступен, защищённые действия не покидают устройство.
2. Если плоскость вышестоящего одобрения недоступна, действия, которым она нужна, остаются в ожидании или истекают.
3. Если одобрение прошло, но статус выполнения неизвестен, перед повторной попыткой ищите действие по идентификатору действия или ключу идемпотентности.
4. Если ресурс изменился, пока ожидалось одобрение, отмените решение или попросите вышестоящий сервис пересмотреть его.
5. Если проверяющий отклоняет или отзывает решение, отмените всё ожидающее выполнение и зафиксируйте, дошла ли отмена до сервиса.

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

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

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

## Повторные попытки не должны тратить одно одобрение дважды

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

RFC 9110 определяет идемпотентный метод по предполагаемому эффекту повторения идентичного запроса. В нём сказано, что клиенты не должны автоматически повторять неидемпотентный запрос, если не знают, что его семантика идемпотентна, или не могут установить, что исходный запрос не был применён. Одобрение не меняет это правило. Согласие человека на один платёж, одно развёртывание или одно удаление не означает согласия на неизвестное число попыток.

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

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

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

1. Создайте одно одобренное действие с фиксированными action_id, дайджестом и ключом идемпотентности.
2. Отправьте его, затем разорвите соединение клиента после отправки тела запроса.
3. Перезапустите локальный агент и дайте восстановлению проверить сохранённое состояние.
4. Убедитесь, что до любой повторной попытки он обращается к провайдеру.
5. Подтвердите одно авторитетное изменение ресурса и одну связанную цепочку записей о попытках.

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

Для API с конечной точкой подтверждения изучите её семантику. Двухшаговая схема «подготовить, затем подтвердить» безопасна только тогда, когда подтверждение один раз использует неизменный серверный объект или применяет ключ идемпотентности. Если confirm просто повторяет поля, переданные клиентом, это может быть ещё одна конечная точка изменения с успокаивающим названием.

## Полный аудит связывает намерение, решение, попытку и результат

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

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

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

Для первой реализации достаточно такой формы события:

```json
{
  "event_id": "evt_01JQ7N2AZ8S4",
  "action_id": "act_01JQ7M6F4R2K",
  "phase": "upstream_result",
  "observed_by": "local_gateway",
  "principal": "service-account:deploy-agent",
  "approver": "org-user:release-reviewer",
  "payload_sha256": "98b0...e42c",
  "provider_event_id": "dep_91358",
  "outcome": "succeeded",
  "recorded_at": "2026-07-24T14:02:18Z"
}
```

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

Документация AWS CloudTrail показывает сторону провайдера в свидетельствах об идентичности: его события IAM Identity Center могут указывать, пришёл ли запрос от пользователя, роли, федеративного пользователя или другого сервиса, а некоторые события включают идентификаторы Identity Center. Это полезные авторитетные сведения, но они всё равно не раскрывают, какой локальный процесс агента сформировал запрос, если вы не передаёте корреляционные данные и не сохраняете локальную запись.

Проверяйте пути обхода так же строго, как основной путь. Прямое использование учётных данных, альтернативные профили CLI, незащищённые конечные точки API, обход администратора и повторное использование одобренного серверного объекта могут сделать безупречный журнал одобрений бессмысленным. В заявлении об охвате нужно указать, какие каналы контролирует барьер, а не утверждать, что «все действия одобрены», не составив список каналов.

## Два барьера полезны, только если остаются независимыми

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

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

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

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

Sallyport выполняет эту локальную роль для HTTP API и SSH-действий: агент подключается через его MCP shim, секреты остаются в зашифрованном хранилище, а приложение выполняет действие. Его авторизация на сеанс и необязательное одобрение при каждом использовании не заменяют защищённую среду или организационного проверяющего у провайдера API. Они добавляют свидетельства об использовании процесса и учётных данных на локальной стороне.

Перед выпуском схемы с двумя барьерами напишите по одному предложению для каждого: «Этот барьер предотвращает X, потому что только он наблюдает Y и исполняет Z». Если в предложениях названы одни и те же X, Y и Z, объедините барьеры. Если какой-либо компонент не способен остановить описываемое действие, исправьте точку исполнения, прежде чем улучшать его запрос.

## Разбор развёртывания показывает недостающие связи

Рассмотрим автономного агента для программирования, который продвигает релиз 184 в production-среду, API которой уже требует проверки. Безопасный поток сохраняет одно намерение через локальное одобрение, вышестоящее ожидание, выполнение и сверку.

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

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

Теперь разорвите соединение после вышестоящего одобрения. Локальная сторона не должна возвращать успех и не должна сразу отправлять ещё одно продвижение. Она сохраняет состояние unknown, запрашивает у провайдера данные по ID ожидающего развёртывания или ключу идемпотентности и фиксирует авторитетный результат. Если провайдер сообщает об успехе, локальный журнал закрывает существующее действие. Если у провайдера нет записи, шлюз может повторить запрос в рамках исходного контракта идемпотентности, пока одобрение остаётся действительным.

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

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

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