Ir al contenido

Conversaciones de PR

Una pull request ya es el lugar donde ocurre la conversación sobre el código. Las conversaciones de PR la convierten en una superficie de un espacio de trabajo de Control Center, igual que un puente de chat convierte Slack en una: menciona con @ al bot de la GitHub App del servidor en una PR, responde dentro de sus hilos de revisión o añade la etiqueta ai-review, y el trabajo ocurre en el espacio de revisión de la PR — bajo la misma atribución y las mismas barandillas que cualquier cosa escrita en la aplicación. Un comentario también está condicionado a la pertenencia; la etiqueta no, porque no tiene autor. Las respuestas del bot vuelven a GitHub, en el mismo hilo en el que se hizo la pregunta.

Tres decisiones dan forma a la función, y cada una es la misma que tomó el puente de chat para el chat, reformulada para un host de código: en qué dirección va la conexión, qué autoriza un comentario que llega de fuera y a quién se atribuye cada lado.

El único canal de envío que GitHub ofrece a una app es un webhook, y un webhook necesita una URL de entrada — justo lo único que esta superficie nunca exige de forma deliberada. Así que el descubrimiento es un barrido: cada minuto o así, una búsqueda con alias por cada instalación de la app pide a GitHub las PR que mencionan al bot (o su slug corto) y las PR abiertas que llevan la etiqueta de revisión; a las PR que ya tienen un espacio de revisión se les leen sus hilos de comentarios, y todo se compara por id de comentario con lo visto en el barrido anterior.

Merece la pena nombrar las consecuencias de este diseño:

  • La latencia es un intervalo de barrido, no un evento. El acuse de recibo 👀 llega en torno a un minuto. Nada urgente depende de él.
  • Exactamente una vez, por comentario. Los id de comentario son únicos en todo GitHub; cada uno se registra en el momento en que se clasifica como apto, antes de que nada actúe sobre él. Un fallo puede perder el procesamiento de un comentario, pero nunca dispararlo dos veces.
  • El tiempo sin conexión se recupera, no se reproduce. El conjunto de lo visto se persiste, así que un reinicio entrega lo que llegó mientras estaba caído. La única excepción es el primer barrido de todos: al no haber memoria con la que comparar, todo lo pendiente se registra en silencio — un operador que etiquetó veinte PR antes de que existiera este servidor no debe volver y encontrarse con veinte revisiones en marcha.
  • La etiqueta es un conjunto, no un evento. La búsqueda informa de la presencia de la etiqueta, así que una PR etiquetada se dispara una sola vez, y nunca más. Volver a añadir una etiqueta tras quitarla es invisible para el barrido; una mención es la vía para volver a ejecutarlo.

La búsqueda se ejecuta una vez por cada cuenta de instalación, con el cliente propio de ese propietario, porque un token de instalación solo ve los repos de su propia instalación — una única credencial para todo el servidor pasaría por alto en silencio todas las PR de las demás instalaciones de la app.

Un comentario de PR llega con poco más que el login de un autor — una identidad externa sin autenticar hasta que se demuestre lo contrario. El bot lo resuelve contra un índice inverso de los miembros del espacio de trabajo y sus cuentas de GitHub conectadas, que se reconstruye según un TTL porque la gente inicia sesión y recibe invitaciones mientras el servidor está en marcha.

La denegación por defecto tiene forma de escalera, y cada peldaño responde en el hilo en el que denegó:

  • A un login desconocido — no vinculado, o vinculado por alguien que no es miembro de este espacio de trabajo — se le dice que inicie sesión y se asegure de pertenecer al espacio
  • A un miembro de solo lectura (viewer o guest) se le dice que su rol no puede iniciar trabajo

Los dos casos de login desconocido comparten una misma respuesta: el índice inverso solo devuelve a un miembro de este espacio de trabajo, así que una cuenta no vinculada y una cuenta vinculada que no es miembro son el mismo fallo de búsqueda. Ninguno de ellos continúa. Un comentario nunca se atribuye a alguien que no lo escribió, y las palabras de un desconocido nunca entran en un espacio de trabajo que no pueden ver.

La etiqueta ai-review no tiene autor que resolver, así que no sube por esta escalera. Añadirla inicia la revisión en el espacio de trabajo que ya tiene vinculado el repositorio.

Cada lado de la conversación lleva una identidad distinta, y esa diferencia es la función:

  • En GitHub, el bot es el servidor. Cada comentario, reacción y respuesta que publica el puente viaja con el token de instalación de la app. Nadie hizo clic en nada en un cliente, así que nada toma prestada la cuenta de una persona — la misma regla que mantiene los webhooks y el sondeo al margen de las credenciales humanas.
  • En el espacio, la pregunta es tuya. El mensaje se atribuye al miembro al que se resolvió el autor, y la ejecución del agente corre en su nombre: los commits lo reconocen como coautor, y el dial de autonomía del espacio y las barandillas de acciones se aplican exactamente igual que a un mensaje escrito en la aplicación. Las escrituras en el host de código que hace la ejecución siguen saliendo como la app — la misma regla que en el punto anterior —, no con el token personal del miembro.

Publicar los hallazgos de la revisión de vuelta en GitHub sigue siendo una decisión de una persona, tomada en Control Center. Una mención puede iniciar una revisión; solo un humano publica una.

La protección contra bucles y el nombre corto

Sección titulada «La protección contra bucles y el nombre corto»

Todo lo que firma una cuenta de bot se descarta antes de que se ejecute cualquier filtro — el nuestro en concreto, y cualquier sufijo [bot], para que el bot de revisión de otra app tampoco pueda despertar al nuestro. Sin esto, las propias respuestas del bot llegarían como comentarios nuevos y lo volverían a disparar indefinidamente.

El sufijo [bot] es también la razón de que exista la forma corta. GitHub fija el login de la cuenta de una app en <slug>[bot], no ofrece autocompletado para él y nunca resuelve @slug hacia la app — así que aceptar el slug sin sufijo es una decisión de nuestro emparejador, y descubrirlo requiere una vía de búsqueda sobre el texto bruto de los comentarios, no el índice de menciones de GitHub. El coste se declara en lugar de disimularse: un usuario humano que comparte el slug de la app menciona al bot cada vez que se menciona a sí mismo en forma corta. Con nombres de app distintivos, la colisión es hipotética.

  • Sin publicación automática. Los hallazgos llegan al espacio de revisión; publicarlos en GitHub es una acción humana aparte
  • Sin identidad difusa. El autor se resuelve a exactamente un miembro o se rechaza — no hay atribución por aproximación
  • Sin webhook de la GitHub App. Esta superficie nunca registra uno, así que no hay URL de entrada que exponer para los comentarios de PR. Los demás webhooks del producto (pipelines, sincronización de tickets) son una configuración aparte y opcional.