# Автоматизация релизов GitHub с AI-агентами и подтверждением

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

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

## Дайте агенту рабочее место для релиза, а не права на весь репозиторий

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

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

Записывайте разрешенные действия как глаголы с фиксированными входными данными. Это полезнее, чем просить агента «быть осторожным».

- `inspect_release_range(base_tag, head_sha)` возвращает коммиты и запросы на слияние
- `build_candidate(head_sha)` возвращает артефакты и дайджесты SHA-256
- `create_draft(version, target_sha, notes)` возвращает идентификатор черновика
- `upload_asset(draft_id, filename, digest)` загружает один проверенный файл
- `request_publish(draft_id)` создает запрос на подтверждение

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

Избегайте аварийного обходного пути вроде `github_api(method, path, body)`. Команды добавляют его для редкой операции, которая не вписалась в первый дизайн. Рано или поздно агент воспользуется им, потому что языковые модели выбирают кратчайший путь через интерфейс. Каждый неограниченный метод запроса превращает узкие полномочия обратно в общий токен репозитория.

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

## Черновик релиза - это объект для проверки, а не опубликованный релиз

Черновик релиза GitHub позволяет собрать публичную информацию, не делая ее публичной. Проверяющий получает конкретный объект: версию, целевой коммит, созданные заметки, прикрепленные файлы, контрольные суммы и статус предварительного выпуска. Сообщения «готово к публикации v2.4.0» недостаточно.

В REST-документации GitHub создание релиза с `draft: true` отделено от его публикации, которая выполняется изменением состояния черновика. GitHub также указывает, что `make_latest` влияет на то, какой релиз будет показан как последний. Эти поля могут выглядеть обычными метаданными, но они влияют на пользователей и инструменты. Считайте их решениями о публикации, а не значениями по умолчанию, которые агент должен угадывать.

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

Используйте структурированную запись подтверждения, например такую:

```json
{
  "operation": "publish_release",
  "repository": "acme/widgets",
  "draft_id": "123456",
  "version": "v2.4.0",
  "target_sha": "8b0c5f7c2d6c4e1a9f1a3b0e5e92d7a4c6f80c11",
  "prerelease": false,
  "make_latest": "true",
  "assets": [
    {"name": "widgets-v2.4.0.tar.gz", "sha256": "b3e1..."},
    {"name": "widgets-v2.4.0.tar.gz.sha256", "sha256": "f18a..."}
  ],
  "notes_digest": "c921..."
}
```

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

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

## Свяжите версию с коммитом, а артефакты с версией

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

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

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

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

```sh
git fetch --tags origin
git rev-parse "v2.4.0^{}"
git rev-parse HEAD
shasum -a 256 dist/widgets-v2.4.0.tar.gz
```

После создания тега первые два идентификатора объектов должны совпадать, а `HEAD` должен быть проверенным SHA, если сборка выполнялась в этой копии репозитория. Команда проверки контрольной суммы выведет строку такого вида:

```text
b3e1f2...  dist/widgets-v2.4.0.tar.gz
```

Внесите этот точный дайджест в запись подтверждения. Не просите проверяющего доверять имени файла. В файле с именем `widgets-v2.4.0.tar.gz` может оказаться отладочная сборка, старая сборка или бинарный файл для другой архитектуры.

Документация GitHub Actions предупреждает, что `pull_request_target` выполняется в контексте базового репозитория и при неосторожном использовании может предоставить непроверенному коду из запроса на слияние права записи или секреты. Тот же вывод относится к агенту релиза: не собирайте распространяемый артефакт из непроверенного кода в задании, которое может публиковать. Отделите проверку непроверенного кода от контролируемой сборки, получающей полномочия на релиз.

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

## Заметки к релизу, созданные агентом, должен отредактировать человек

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

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

Я отклоняю популярную инструкцию «напиши polished release notes по git log». Она популярна, потому что сразу дает готовый документ. Но она ошибочна: сообщения коммитов описывают намерение при реализации, а не обязательно поведение выпущенного продукта. Коммит с названием «fix auth» мог исправить тестовый стенд, изменить текст ошибки или закрыть серьезную уязвимость. В заметках нужно описать реальный эффект для пользователя.

Перед публикацией попросите проверяющего ответить на несколько простых вопросов:

- Содержит ли целевой коммит все заявленные здесь изменения?
- Остался ли в диапазоне какой-либо удаленный или отмененный запрос на слияние?
- Нужно ли пользователям изменить конфигурацию, данные, разрешения или клиенты?
- Требуют ли формулировки о безопасности проверки людьми, которые расследовали проблему?
- Это предварительный выпуск и должен ли GitHub показывать его как последний?

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

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

## Подтверждение должно происходить на границе необратимого действия

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

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

Используйте отдельное подтверждение для следующих операций:

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

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

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

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

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

## Не помещайте учетные данные в запрос и окружение агента

Агент, напрямую владеющий токеном GitHub, может обойти тщательно спроектированный интерфейс подтверждения через команду curl, измененный файл рабочего процесса или забытый вами вызов инструмента. Инструкции в запросе не закрывают эту дыру.

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

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

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

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

## Откат - это отдельное решение для исправления, а не кнопка удаления

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

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

Человек должен отдельно подтверждать действия по откату, поскольку их последствия отличаются от публикации. В запросе нужно указать, что изменится, а что останется видимым. Формулировка «удалить релиз v2.4.0» слишком слаба. Формулировка «удалить публичную запись и артефакты релиза v2.4.0; тег v2.4.0 останется у SHA X; уже скачанные файлы отозвать нельзя» показывает, что именно утверждает проверяющий.

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

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

## Журнал аудита должен восстанавливать действие, а не историю вокруг него

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

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

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

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

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

## Шлюз релиза должен закрываться при расхождении фактов

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

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

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

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