# Публикация пакетов AI-агентом без разрастания токенов выпуска

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

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

## Доступ на чтение реестра не дает права на выпуск

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

Не объединяйте эти вопросы в одну учетную запись только потому, что клиент реестра делает их похожими. Менеджер пакетов может читать настройки из одного и того же файла `.npmrc` для `npm ci`, `npm publish`, `npm dist-tag add` и `npm unpublish`. Такая структура файла удобна клиенту, но не заменяет продуманную модель разрешений.

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

- Чтение: поиск метаданных, загрузка архива, проверка целостности и установка зависимостей.
- Публикация: создание новой неизменяемой версии под одобренным именем пакета.
- Маршрутизация: изменение изменяемых каналов, например `latest`, тегов предварительных выпусков или настроек доступа к реестру.
- Деструктивные действия: отмена публикации, удаление пакета, если реестр это разрешает, и удаление тега выпуска.

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

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

## Широкие токены публикации ломаются в обычных сценариях

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

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

С одним из часто встречающихся сбоев я разбирался не раз:

1. Команда передает агенту токен реестра, чтобы тот мог выполнять проверки выпуска.
2. Конфигурация проекта указывает на рабочий реестр, потому что именно его используют машины разработчиков.
3. Агенту нужно проверить содержимое пакета, и он запускает `npm pack`, что безопасно.
4. В следующей инструкции говорится «протестировать публикацию», и агент запускает `npm publish`, вместо того чтобы публиковать в изолированное место.
5. Команда завершается успешно, потому что токен и имя пакета действительны. Команда замечает проблему только после того, как автоматизация или пользователи видят новую версию.

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

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

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

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

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

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

```sh
npm ci
npm test
npm pack --json
```

`npm pack --json` возвращает структурированную информацию об архиве, который будет создан. Нужные части выглядят примерно так:

```json
[
  {
    "id": "@acme/widget@1.4.0",
    "name": "@acme/widget",
    "version": "1.4.0",
    "filename": "acme-widget-1.4.0.tgz",
    "files": [
      {"path": "README.md", "size": 2400},
      {"path": "dist/index.js", "size": 18420},
      {"path": "package.json", "size": 910}
    ]
  }
]
```

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

Затем отдельно получите состояние реестра. Документация CLI npm описывает `npm view` как способ просматривать метаданные пакета в реестре. Используйте его для конкретных вопросов, а не для получения огромного общего вывода:

```sh
npm view @acme/widget version dist-tags --json
npm view @acme/widget@1.4.0 dist --json
```

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

Составьте запрос на подтверждение из этих фактов. Хороший запрос говорит: опубликовать `@acme/widget@1.4.0` в `registry.example`, не перемещая тег. Плохой запрос говорит: запустить `npm publish`. В первом случае проверяющий сразу заметит ошибку в пространстве имен. Во втором ему придется восстанавливать намерение по команде, которой он может не доверять.

## Временный пакет помогает проверить путь через реестр

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

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

Этот минимальный `package.json` делает тест понятным:

```json
{
  "name": "@acme-release-test/relay-check-2025-04",
  "version": "0.0.1",
  "description": "Temporary registry release-path check",
  "main": "index.js",
  "files": ["index.js", "README.md"],
  "publishConfig": {
    "access": "restricted"
  }
}
```

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

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

```sh
mkdir release-path-check
cd release-path-check
npm init -y
npm install @acme-release-test/relay-check-2025-04@0.0.1
node -e "console.log(require('@acme-release-test/relay-check-2025-04'))"
```

Успешная `npm install` отвечает на более полезный вопрос, чем успешный ответ публикации: может ли чистый клиент найти и получить указанную версию? Если пакет использует exports, объявления типов, исполняемый файл командной строки или скрипт postinstall, в этом чистом каталоге также проверьте соответствующую общедоступную точку входа. То, что реестр сохранил пакет, еще не означает, что клиент сможет им пользоваться.

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

## Содержимое пакета и приемка реестром требуют разных проверок

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

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

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

Проверка со стороны клиента задает третий вопрос: может ли чистый проект установить точный выпуск и запустить его так, как это будут делать пользователи? Именно здесь проявляются отсутствующие peer-зависимости, неправильные записи `exports` и случайная зависимость от файлов рабочей области.

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

## Подтверждение должно соответствовать последствиям вызова

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

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

В запросе для каждого вызова должно быть достаточно сведений, чтобы отклонить действие по правильной причине:

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

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

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

## Считайте теги изменением маршрута для пользователей

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

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

Команды наглядно показывают разницу:

```sh
npm publish --tag candidate
npm view @acme/widget dist-tags --json
npm dist-tag add @acme/widget@1.4.0 latest
```

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

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

## Сохраняйте полезные аудиторские записи после инцидента

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

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

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

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

## Включите временный тест в договор выпуска

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

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

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