Перейти к содержимому

Разговоры в PR

Pull request — это уже то место, где происходит разговор о коде. Разговоры в PR делают его поверхностью рабочего пространства Control Center — так же, как чат-мост делает такой поверхностью Slack: упомяните бота GitHub App вашего сервера через @ на PR, ответьте внутри его веток ревью или добавьте метку ai-review — и работа произойдёт в пространстве проверки этого PR, с тем же закреплением авторства и теми же ограничениями, что и всё, набранное в приложении. Комментарий вдобавок проходит проверку на членство; метка — нет, потому что у неё нет автора. Ответы бота возвращаются в GitHub, в ту самую ветку, где был задан вопрос.

Три решения определяют эту возможность, и каждое из них — то же решение, которое чат-мост принял для чата, только пересказанное для хоста кода: в каком направлении идёт соединение, что подтверждает права комментария, пришедшего извне, и за кем закрепляется каждая сторона.

Единственный push-канал для приложения в GitHub — это вебхук, а вебхуку нужен входящий URL — единственное, чего эта поверхность намеренно никогда не требует. Поэтому обнаружение — это обход: примерно раз в минуту один поисковый запрос с псевдонимом на каждую установку приложения спрашивает у GitHub о PR, которые упоминают бота (или его короткий slug), и об открытых PR, несущих метку проверки; у PR, которые уже имеют пространство проверки, читаются ветки комментариев; а всё остальное сравнивается по id комментария с тем, что было увидено в прошлый обход.

Последствия этого решения стоит назвать:

  • Задержка — это интервал обхода, а не событие. Подтверждение 👀 появляется примерно в течение минуты. Ничего срочного на этом не держится.
  • Ровно один раз на комментарий. Идентификаторы комментариев уникальны во всём GitHub; каждый записывается в тот момент, когда признан подходящим, — до того, как по нему что-либо предпринимается. Сбой может потерять обработку комментария, но никогда не запустит её дважды.
  • Простой догоняется, а не воспроизводится заново. Набор увиденного сохраняется, поэтому после перезапуска доставляется то, что пришло во время простоя. Единственное исключение — самый первый обход: сравнивать не с чем, поэтому всё необработанное записывается молча, — оператор, пометивший двадцать PR до появления этого сервера, не должен вернуться к двадцати запущенным проверкам.
  • Метка — это множество, а не событие. Поиск сообщает о наличии метки, поэтому помеченный PR срабатывает один раз и навсегда. Повторное добавление метки после удаления для обхода невидимо; путь к повторному запуску — упоминание.

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

Комментарий в PR приходит, неся немногим больше логина автора, — неаутентифицированная внешняя идентичность, пока не доказано обратное. Бот сопоставляет его с обратным индексом участников рабочего пространства и их подключённых аккаунтов GitHub; индекс перестраивается по TTL, потому что люди входят в систему и получают приглашения, пока сервер работает.

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

  • Неизвестный логин — не привязан или привязан тем, кто не состоит в этом рабочем пространстве, — получает совет войти и убедиться, что он относится к этому рабочему пространству
  • Участник только для чтения (viewer или guest) получает ответ, что его роль не позволяет запускать работу

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

У метки ai-review нет автора, которого можно сопоставить, поэтому по этой лестнице она не поднимается. Её добавление запускает проверку в том рабочем пространстве, которое уже привязало репозиторий.

Каждая сторона разговора несёт свою идентичность, и эта разница — и есть суть возможности:

  • В GitHub бот — это сервер. Каждый комментарий, реакция и ответ, которые публикует мост, уходят от токена установки приложения. Никто ничего не нажимал в клиенте, поэтому ничто не одалживает аккаунт человека — то же правило, которое не даёт вебхукам и опросу работать от человеческих учётных данных.
  • В пространстве вопрос принадлежит вам. Сообщение приписывается участнику, с которым сопоставлен автор, и запуск агента выполняется от его имени: коммиты указывают его соавтором, а регулятор автономии и ограничения действий пространства применяются ровно так же, как к сообщению, набранному в приложении. Записи в хост кода из этого запуска по-прежнему уходят от имени приложения — то же правило, что и в пункте выше, — а не от личного токена участника.

Публикация замечаний проверки обратно в GitHub остаётся решением человека, принятым в Control Center. Упоминание может запустить проверку; публикует её только человек.

Всё, что написано аккаунтом бота, отбрасывается до того, как сработает любой шлюз, — и наш конкретно, и любой суффикс [bot], чтобы бот-ревьюер другого приложения тоже не мог разбудить нашего. Без этого собственные ответы бота приходили бы как новые комментарии и зацикливали бы его навсегда.

Суффикс [bot] — ещё и причина, по которой существует короткая форма. GitHub жёстко закрепляет логин аккаунта приложения как <slug>[bot], не предлагает для него автодополнение и никогда не сопоставляет @slug с приложением, — поэтому приём голого slug — это решение нашего сопоставителя, а для его обнаружения нужна отдельная полоса поиска по необработанному тексту комментариев, а не индекс упоминаний GitHub. Цена названа, а не сглажена: человек, чей логин совпадает со slug приложения, обращается к боту каждым коротким упоминанием самого себя. Отличительные имена приложений делают такое совпадение гипотетическим.

  • Никакой автопубликации. Замечания попадают в пространство проверки; публикация в GitHub — отдельное действие, которое выполняет человек
  • Никакого нечёткого сопоставления. Автор сопоставляется ровно с одним участником или получает отказ — авторства «на глаз» не бывает
  • Никакого вебхука GitHub App. Эта поверхность никогда его не регистрирует, поэтому входящего URL для комментариев в PR не существует. Другие вебхуки продукта (конвейеры, синхронизация тикетов) — отдельная необязательная настройка.