Aller au contenu

Conversations de PR

Une pull request est déjà le lieu où se tient la conversation sur le code. Les conversations de PR en font une surface d’un espace de travail Control Center, comme une passerelle de chat fait de Slack la sienne : mentionnez avec @ le bot GitHub App du serveur sur une PR, répondez dans ses fils de revue ou ajoutez l’étiquette ai-review, et le travail se déroule dans l’espace de revue de la PR — sous la même attribution et les mêmes garde-fous que tout ce qui est saisi dans l’application. Un commentaire est en outre soumis à l’appartenance ; l’étiquette ne l’est pas, car elle n’a pas d’auteur. Les réponses du bot reviennent sur GitHub, dans le fil où la question a été posée.

Trois décisions façonnent la fonctionnalité, et chacune est la même décision que la passerelle de chat a prise pour le chat, reformulée pour une forge : dans quel sens circule la connexion, ce qui autorise un commentaire venu de l’extérieur et à qui chaque partie est attribuée.

Le seul canal push de GitHub pour une application est le webhook, et un webhook a besoin d’une URL entrante — la seule chose que cette surface ne requiert délibérément jamais. La découverte se fait donc par balayage : environ toutes les minutes, une recherche aliasée par installation de l’application demande à GitHub les PR qui mentionnent le bot (ou son slug court) et les PR ouvertes qui portent l’étiquette de revue ; les PR qui ont déjà un espace de revue voient leurs fils de commentaires lus, et tout est comparé par identifiant de commentaire à ce qui a été vu au balayage précédent.

Les conséquences de conception méritent d’être nommées :

  • La latence est un intervalle de balayage, pas un événement. L’accusé de réception 👀 arrive en une minute environ. Rien d’urgent n’en dépend.
  • Exactement une fois, par commentaire. Les identifiants de commentaire sont uniques sur GitHub ; chacun est enregistré au moment où il est classé comme éligible, avant que quoi que ce soit n’agisse sur lui. Un plantage peut faire perdre le traitement d’un commentaire, jamais le déclencher deux fois.
  • Le temps hors ligne est rattrapé, pas rejoué. L’ensemble des éléments vus est persisté, si bien qu’un redémarrage livre ce qui est arrivé pendant l’arrêt. La seule exception est le tout premier balayage : faute de mémoire à comparer, tout ce qui est en attente est enregistré en silence — un opérateur qui a étiqueté vingt PR avant l’existence de ce serveur ne doit pas retrouver vingt revues en cours.
  • L’étiquette est un ensemble, pas un événement. La recherche signale la présence de l’étiquette, donc une PR étiquetée se déclenche une fois, et une seule. Réajouter une étiquette après l’avoir retirée est invisible pour le balayage ; une mention est la voie de relance.

La recherche s’exécute une fois par compte d’installation, sur le client propre à ce propriétaire, car un jeton d’installation ne voit que les dépôts de sa propre installation — un identifiant unique à l’échelle du serveur manquerait silencieusement toutes les PR relevant des autres installations de l’application.

Un commentaire de PR arrive avec guère plus que l’identifiant d’un auteur — une identité externe non authentifiée jusqu’à preuve du contraire. Le bot le résout au moyen d’un index inversé des membres de l’espace de travail et de leurs comptes GitHub connectés, reconstruit selon un TTL car les gens se connectent et sont invités pendant que le serveur tourne.

Le refus par défaut prend la forme d’une échelle, et chaque barreau répond dans le fil où il a refusé :

  • Un identifiant inconnu — non lié, ou lié par une personne qui n’est pas membre de cet espace de travail — est invité à se connecter et à vérifier qu’il en fait bien partie
  • Un membre en lecture seule (viewer ou guest) est informé que son rôle ne permet pas de lancer du travail

Les deux cas d’identifiant inconnu partagent une seule réponse : l’index inversé ne renvoie qu’un membre de cet espace de travail, si bien qu’un compte non lié et un non-membre lié produisent le même échec. Aucun d’eux ne poursuit. Un commentaire n’est jamais attribué à quelqu’un qui ne l’a pas envoyé, et les propos d’un inconnu n’entrent jamais dans un espace de travail qu’il ne peut pas voir.

L’étiquette ai-review n’a aucun auteur à résoudre, elle ne gravit donc pas cette échelle. L’ajouter lance la revue dans l’espace de travail qui lie déjà le dépôt.

Chaque partie de la conversation porte une identité différente, et cette différence est la fonctionnalité même :

  • Sur GitHub, le bot est le serveur. Chaque commentaire, réaction et réponse publiés par la passerelle utilisent le jeton d’installation de l’application. Personne n’a cliqué quoi que ce soit dans un client, donc rien n’emprunte le compte d’une personne — la même règle qui tient les webhooks et le sondage à l’écart des identifiants humains.
  • Dans l’espace, la question est la vôtre. Le message est attribué au membre auquel l’auteur a été résolu, et l’exécution de l’agent se fait en son nom : les commits les désignent comme coauteurs, et le réglage d’autonomie et les garde-fous d’action de l’espace s’appliquent exactement comme à un message saisi dans l’application. Les écritures vers la forge issues de l’exécution sortent toujours sous l’identité de l’application — même règle que le point ci-dessus — et non avec le jeton personnel du membre.

Publier les constats de revue sur GitHub reste une décision humaine, prise dans Control Center. Une mention peut lancer une revue ; seul un humain en publie une.

Tout ce qui est écrit par un compte de bot est écarté avant l’exécution du moindre contrôle — le nôtre en particulier, et tout suffixe [bot], afin que le bot de revue d’une autre application ne puisse pas non plus réveiller le nôtre. Sans cela, les propres réponses du bot arriveraient comme de nouveaux commentaires et le redéclencheraient sans fin.

Le suffixe [bot] explique aussi pourquoi la forme courte existe. GitHub fixe l’identifiant de connexion d’un compte d’application à <slug>[bot], n’offre aucune autocomplétion pour celui-ci et ne résout jamais @slug vers l’application — accepter le slug seul relève donc d’une décision de notre comparateur, et sa découverte exige une voie de recherche sur le texte brut des commentaires plutôt que sur l’index des mentions de GitHub. Le coût est énoncé plutôt que lissé : un utilisateur humain qui partage le slug de l’application s’adresse au bot chaque fois qu’il se mentionne lui-même sous forme courte. Des noms d’application distinctifs rendent cette collision hypothétique.

  • Aucune publication automatique. Les constats arrivent dans l’espace de revue ; publier sur GitHub est une action humaine distincte
  • Aucune identité approximative. L’auteur se résout en exactement un membre ou est refusé — il n’y a pas d’attribution devinée
  • Aucun webhook de GitHub App. Cette surface n’en enregistre jamais, il n’y a donc aucune URL entrante à exposer pour les commentaires de PR. Les autres webhooks du produit (pipelines, synchronisation des tickets) relèvent d’une configuration distincte et facultative.