# Полномочия агента после сна и пробуждения Mac

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

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

## Сон бывает разным

macOS различает сон системы и сон дисплея. Это важно, потому что темный экран не доказывает, что агент остановился. Apple предоставляет отдельные уведомления NSWorkspace для `willSleep`, `didWake`, `screensDidSleep` и `screensDidWake`. Уведомления о сне и пробуждении не содержат пользовательских данных, которые объясняли бы приложению причину перехода.

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

В проекте нужно использовать четыре отдельных факта:

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

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

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

## Блокировка экрана должна завершать интерактивные полномочия

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

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

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

```text
authorization_denied
reason: authority_epoch_changed
required: unlock_vault_and_approve_session
```

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

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

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

## Пробуждение начинает новую эпоху полномочий

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

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

Минимальная запись авторизации может выглядеть так:

```json
{
  "session_nonce": "6a018d62-2e94-4d4a-9e79-1f4e4b5ca501",
  "process_id": 84172,
  "process_start_marker": "2026-07-22T14:03:18Z",
  "signing_authority": "approved-agent-binary",
  "authority_epoch": 27,
  "approved_at": "2026-07-22T14:04:01Z",
  "per_call_approval": false
}
```

Идентификатор процесса является лишь одним из полей. macOS может повторно использовать PID после завершения процесса, поэтому отдельный PID становится опасным, если шлюз забывает старую запись, а позже снова видит тот же номер. Связывайте одобрение со свежим nonce и наблюдаемым экземпляром процесса. Используйте полномочия подписи кода как сигнал идентичности исполняемого файла, но все равно требуйте нового одобрения сессии для каждого нового запуска процесса.

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

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

## Закрытие крышки требует отдельного теста

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

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

Проведите этот эксперимент с агентом, которому выданы безвредные полномочия, например запись маркера в тестовый API или безопасная команда на одноразовом SSH-хосте:

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

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

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

## Сон дисплея сам по себе плохо подходит для отзыва

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

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

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

Практическая таблица не даст этому превратиться в набор непроверенных представлений:

| Переход | Локальная работа агента | Текущее одобрение сессии | Действие через хранилище |\n| --- | --- | --- | --- |\n| Дисплей засыпает | Может продолжаться | Зависит от вашей политики | Обычно приостановить или разрешить только действия с низким риском |\n| Экран блокируется | Может продолжаться | Завершить | Отклонять до нового одобрения |\n| Система начинает засыпать | Естественным образом приостановить | Немедленно завершить | Отклонять новые вызовы |\n| Система просыпается на экране блокировки | Может локально продолжиться | Остается завершенным | Отклонять до разблокировки и одобрения |\n| Пользователь разблокирует систему | Может продолжаться | Все еще завершено | Требовать новое одобрение сессии |\n
Разблокировка Mac возвращает пользователю рабочий стол. Она не должна незаметно восстанавливать прежние полномочия агента. Разблокировка компьютера и одобрение внешнего действия связаны, но отвечают на разные вопросы.

## Ночные запуски требуют отдельного соглашения

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

Сделайте соглашение о задаче настолько узким, чтобы его можно было объяснить одним предложением. «Запустить тесты и подготовить pull request» понятно. «Сделать все необходимое для завершения задачи» не является соглашением, это пустой чек.

Для автономной работы разделяйте локальные и внешние действия. Разрешайте первые, если они ограничены: анализ репозитория, изменения в ветке, запуск тестов и создание артефактов. Для чувствительных внешних эффектов требуйте нового одобрения человека, включая изменение production API, развертывание, публикацию пакета, запись в общей базе данных или SSH-доступ к важной машине.

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

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

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

## Журналы должны доказывать отказ, а не просто активность

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

macOS дает полезную отправную точку для расследования событий питания:

```sh
pmset -g log | grep -E 'Sleep|Wake|DarkWake|Display'
```

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

Ваш собственный журнал должен отвечать на другой набор вопросов:

```text
14:20:16.402 session_approved process=84172 epoch=27 authority=approved-agent-binary
14:22:04.118 power_will_sleep epoch=27
14:22:04.119 authority_revoked old_epoch=27 new_epoch=28 reason=system_sleep
14:25:38.771 power_did_wake epoch=28
14:25:44.025 action_denied process=84172 request=ssh.exec reason=authority_epoch_changed
```

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

Защищенный от изменений журнал аудита дает еще одно преимущество: он позволяет убедиться постфактум, что последовательность событий и действий не была отредактирована, чтобы выглядеть лучше. Sallyport строит журналы Sessions и Activity на основе зашифрованного журнала аудита, недоступного для записи и связанного цепочкой хешей, а `sp audit verify` проверяет эту цепочку офлайн поверх шифротекста. Для такого теста это полезно: успешная проверка говорит, что записанная последовательность не была незаметно переписана. Но она не оправдывает слабую политику отзыва.

## На границе возникают состояния гонки

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

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

Простая схема выглядит так:

```text
receive request
identify process and session nonce
read current authority epoch
compare request grant epoch to current epoch
check vault gate
check per-call approval when required
inject credential and execute action
append result to audit log
```

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

Не пытайтесь решить каждый крайний случай задержкой сна. Уведомление Apple `willSleep` допускает короткую задержку для обработки, но блокировка сна ноутбука ради завершения действий агента меняет приоритеты местами. Машина выходит из интерактивного состояния. Отзовите доступ, запишите прерывание и предоставьте пользователю решить, что возобновлять.

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

## Тестируйте переходы, которые действительно выполняют пользователи

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

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

Проверьте как минимум следующие случаи на каждой поддерживаемой конфигурации Mac:

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

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

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

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

## Сначала опишите результаты политики, затем обеспечьте их выполнение

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

Для большинства интерактивных AI-агентов для программирования политика должна звучать так:

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

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

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