Artículos en profundidad sobre la tecnología que da forma al futuro.

IRCv3: lecciones de diseño de protocolos retrocompatibles

IRCv3 moderniza un protocolo de 1988 sin romperlo. Negociación de capacidades, etiquetas de mensaje y evolución retrocompatible de protocolos.

Una antigua máquina de telégrafo conectada por un cable luminoso a un smartphone moderno sobre un escritorio
El truco de IRCv3: el formato de cable de 1988 sigue funcionando, pero los clientes modernos obtienen una experiencia moderna.

En 1988, un estudiante universitario finlandés escribió un protocolo de chat que cabía en un puñado de páginas. Casi cuatro décadas después, ese mismo protocolo, IRC, sigue transportando millones de mensajes al día, y el grupo de trabajo de IRCv3 nos está enseñando en silencio a evolucionar un protocolo sin dejar la base instalada hecha cenizas. Si alguna vez has intentado versionar una API de la que dependen desconocidos, IRCv3 es el caso de estudio que deberías robar.

He construido bots y clientes de prueba para IRC de forma intermitente durante años, y lo que sigue sorprendiéndome es lo moderna que puede sentirse la experiencia cuando ambos extremos hablan las extensiones más nuevas. Historial reproducido desde el servidor. Autenticación real. Marcas de tiempo que sobreviven a un bouncer. Y, aun así, un cliente escrito en 1995 puede conectarse al mismo servidor y charlar sin problemas. Esa combinación no ocurre por casualidad: es el resultado de un conjunto de decisiones de diseño deliberadas y de algunos errores dolorosos que la comunidad tuvo que sortear.

El pecado original: capacidades que no puedes consultar

El protocolo IRC base, tal como se codificó en la RFC 1459 y después en la RFC 2812, no tenía ningún mecanismo para que un cliente preguntara a un servidor qué soporta. Ninguno. Te conectabas, enviabas NICK y USER, y luego adivinabas. ¿Quieres saber si el servidor soporta un modo o una extensión concreta? Envíalo y a ver si recibes un error. Esto suena pintoresco hasta que has mantenido una librería cliente que tiene que tratar casos especiales para cinco implementaciones de servidor distintas, cada una interpretando de forma ligeramente diferente las partes ambiguas de la especificación.

El modo de fallo aquí no es teórico. Los desarrolladores escribían clientes que detectaban funciones lanzando comandos y analizando numéricos de error: un desastre frágil que se rompía cada vez que un servidor cambiaba una cadena de error. Los bots se unían a canales con suposiciones erróneas y llenaban el chat de fallos. Esta es la brecha entre especificación e implementación en su hábitat natural: cuando la especificación no ofrece un mecanismo de descubrimiento, cada implementador inventa el suyo, y mal.

La solución de IRCv3 fue la negociación de capacidades, y es tan simple que resulta casi vergonzosa. Durante el registro de la conexión, el cliente envía CAP LS y el servidor responde con una lista de capacidades con nombre que soporta, como sasl, server-time o message-tags. Después, el cliente envía CAP REQ con exactamente el subconjunto que quiere, el servidor confirma, y ambos lados continúan con un entendimiento compartido y explícito de lo que soporta la sesión. Un cliente heredado que nunca envía CAP simplemente obtiene el comportamiento antiguo. Un servidor heredado que no entiende CAP devuelve un error que el cliente puede ignorar con seguridad y usar como vuelta atrás.

-> CAP LS 302
<- CAP * LS :sasl=PLAIN,EXTERNAL server-time message-tags echo-message
-> CAP REQ :sasl server-time
<- CAP * ACK :sasl server-time
-> AUTHENTICATE PLAIN
<- AUTHENTICATE +
-> AUTHENTICATE dXNlcgB1c2VyAHBhc3N3b3Jk
<- 900 jane jane!jane@host jane :You are now logged in

La lección se generaliza mucho más allá de IRC. Si diseñas cualquier protocolo o API hoy, construye el mecanismo de negociación en la versión uno, aunque todavía no haya nada que negociar. El coste es un solo intercambio de mensajes; la recompensa es que cada función futura sea opcional en lugar de un cambio que rompa compatibilidad.

Lección: la extensibilidad vence a los comandos nuevos

La segunda gran idea de IRCv3 son las etiquetas de mensaje, y nació de un error real: la gente seguía intentando añadir funciones definiendo comandos nuevos o metiendo semántica en parámetros de mensajes existentes. El formato de cable de IRC es famosamente austero: un prefijo, un comando y una lista de parámetros finales. No había dónde colgar metadatos. Así que cuando alguien quería marcas de tiempo en el historial reproducido, o indicar qué cuenta había enviado un mensaje, la respuesta de toda la vida era inventar un numérico nuevo o retorcer el texto final. Ambos enfoques rompen clientes antiguos o obligan a cada analizador a crecer con casos especiales.

La solución fue añadir una sección nueva y opcional al principio del mensaje: @clave=valor;clave2=valor2 antes del prefijo. Y lo crucial: los servidores solo envían etiquetas a los clientes que negociaron la capacidad message-tags, así que los clientes antiguos nunca ven un byte que no puedan analizar. Así es, en el cable, un mensaje con marca de tiempo:

@time=2024-03-14T09:26:00.000Z;account=jane :jane!jane@host PRIVMSG #rust :borrow checker finally clicked for me

Es el mismo movimiento arquitectónico que las cabeceras HTTP, los metadatos de gRPC o la tolerancia a campos desconocidos de protobuf: reservar un punto de extensión genérico y las funciones dejan de requerir cirugía en el protocolo. La extensión server-time, que resuelve el viejo problema del bouncer en el que todos los mensajes reproducidos parecen haber llegado justo ahora, es literalmente una etiqueta. La propuesta +reply, que permite a los clientes encadenar respuestas a mensajes concretos, es otra etiqueta. Ningún comando nuevo. Ningún numérico nuevo. Ningún día D de cambio de versión.

Yo mismo he cometido el error contrario. Hace años, en un sistema interno de pub/sub, añadimos una función sobrecargando un campo JSON con un prefijo mágico, porque cambiar el formato del envelope «era demasiado trabajo». Ocho meses después manteníamos una expresión regular en tres lenguajes para desempaquetar la magia, y el parser de un equipo trataba la cadena mágica como datos literales, lo que provocó un incidente memorable. Un campo de metadatos simple y explícito habría costado un día. La cadena mágica nos costó trimestres. Las etiquetas de mensaje de IRCv3 son lo que ese sistema debería haber tenido desde el principio.

Sobres en una cinta transportadora, algunos con pequeñas pegatinas de metadatos de colores
Etiquetas de mensaje: metadatos opcionales adjuntos a cada mensaje, invisibles para los clientes que nunca los pidieron.

Lección: acabar con la contraseña en texto plano

Pregunta a cualquiera que usara un cliente IRC en los 2000 cómo funcionaba el inicio de sesión y obtendrás la misma respuesta: escribías /msg NickServ IDENTIFY contraseña. Tus credenciales salían como un mensaje de chat normal, visibles en los registros, y a veces se pegaban en un canal público cuando tu cliente fallaba con el comando, un rito de iniciación tan común que se convirtió en meme. La autenticación se había añadido a posteriori en la capa de aplicación porque el protocolo no tenía ningún concepto de ella.

IRCv3 lo arregló como es debido estandarizando la autenticación SASL durante el registro. En lugar de charlar con un bot después de conectarte, el cliente negocia la capacidad sasl y se autentica como parte del handshake, antes de tener un nick que cualquiera pueda ver. El servidor sabe quién eres desde el primer mensaje, y eso se propaga a todo lo demás: la extensión account-tag puede adjuntar el nombre de tu cuenta a cada mensaje, los bots pueden tomar decisiones de permisos sin sus propias heurísticas para analizar NickServ, y los clientes pueden construir flujos de registro que no implican leer un muro de texto NOTICE.

La lección general: cuando un apaño se vuelve universal, deja de ser un apaño y se convierte en tu protocolo de facto, sin ningún cuidado de diseño. NickServ no aparecía en ninguna RFC, pero moldeó dos décadas de comportamiento de los clientes, incluidos sus modos de fallo. Estandarizar lo que la gente ya hacía (autenticarse al conectar) con un mecanismo real (SASL, que incluye PLAIN, EXTERNAL para certificados de cliente y espacio para más) convirtió el folclore en infraestructura. Si tus usuarios hacen todos el mismo baile torpe para conseguir algo, ese es tu próximo punto de especificación, no una pregunta frecuente de soporte.

Lección: el historial es un problema de protocolo, no de cliente

Durante casi toda la vida de IRC, el protocolo fue estrictamente en directo: si tu cliente no estaba conectado cuando se enviaba un mensaje, ese mensaje nunca existió para ti. La respuesta de la comunidad fue el bouncer: un proxy permanentemente conectado (ZNC es el clásico) que vivía en un VPS, mantenía tu sesión y reproducía lo que te habías perdido. Los bouncers funcionaban, pero eran otra pieza de infraestructura que mantener, otra credencial que gestionar, y la experiencia de reproducción era terrible: todo llegaba con la hora de reproducción y desordenado respecto a lo que habías visto en directo.

IRCv3 atacó esto por capas. Primero, server-time hizo utilizable el historial reproducido al transportar la marca de tiempo real de recepción. Después, el borrador chathistory dio a los clientes una forma estándar de pedir rangos de historial, y servidores como Ergo empezaron a almacenar el historial de los canales directamente. El resultado, como han descubierto muchas personas al montar un pequeño servidor Ergo para sus amigos, es sorprendente: conectas desde el móvil tras una semana sin conexión y el servidor reproduce todo con marcas de tiempo correctas, sin bouncer ni mantenimiento de VPS. Es el mayor salto de calidad de vida que ha tenido IRC, y vino del protocolo, no de un truco ingenioso en el cliente.

Hay un contraargumento que merece tomarse en serio, y los usuarios diarios de IRC lo hacen: la mayor parte de la torpeza percibida de IRC está en el cliente, y hoy puedes tener una buena experiencia con un cliente decente y un bouncer. Es cierto, pero ese argumento demuestra el punto desde el otro lado. Cada autor de cliente reimplementó por separado la unión del historial, el seguimiento de menciones y la deduplicación en la reproducción, con errores sutilmente distintos. Trasladar la parte difícil al protocolo y al servidor significa que se implementa una vez, correctamente, y todos los clientes se benefician. Tomamos esta misma decisión constantemente en el desarrollo web: llevar los invariantes a la plataforma (piensa en cuánto código de aplicación desapareció cuando los navegadores incorporaron fetch nativo, o cuando TLS se volvió obligatorio) en lugar de dejar que mil bases de código lo resuelvan a medias.

Lección: las funciones opcionales crean realidades divergentes

Ahora viene el error que IRCv3 todavía no ha resuelto del todo, y es el más relevante para cualquiera que diseñe sistemas extensibles. Como cada extensión es opcional y se negocia por separado, dos usuarios del mismo canal pueden literalmente ver conversaciones distintas. Un cliente recibe mensajes con etiquetas +reply y los muestra como respuestas encadenadas; otro muestra esos mismos mensajes como líneas planas, sin contexto. Un usuario ve las reacciones con emoji como glifos ordenados junto a un mensaje; otro no ve nada, o un respaldo críptico. La matriz de soporte de IRCv3 es una cuadrícula de síes y noes, y tu experiencia depende de en qué columna te encuentres.

Este es el lado oscuro de la degradación elegante. La compatibilidad hacia atrás garantiza que nada se rompa, pero nada en el modelo garantiza coherencia. Slack y Discord, pensemos lo que pensemos de ellos, nunca tienen este problema: la plataforma controla ambos extremos con tanta firmeza que una reacción es una reacción para todos. IRCv3 eligió la evolución frente al control, y el coste es un panorama de funciones permanentemente desigual, donde los borradores a veces siguen siendo borradores durante años.

Si estás construyendo algo extensible, presupuéstalo desde el primer día. Algunas medidas que IRCv3 y ecosistemas similares han adoptado:

  • Diseña las degradaciones de forma deliberada. Una etiqueta de respuesta debería caer en una línea citada legible, no desaparecer. Si la alternativa no sirve, la función no está lista para lanzarse como opcional.
  • Publica la matriz de soporte y mantenla honesta. Las tablas de clientes y servidores de IRCv3 permiten a los usuarios elegir con información; el silencio genera culpas dirigidas al software equivocado.
  • Define una base común y promuévela. Las versiones numeradas (3.1, 3.2) agrupan capacidades para que los clientes apunten a «soporte 3.2» en lugar de once banderas individuales. Dale a tu ecosistema un suelo hacia el que converger.
  • Caduca tus borradores. Un borrador que llega a los servidores pero nunca se finaliza (el historial de canales ha coqueteado con ese destino) es peor que no tener borrador, porque bifurca el comportamiento sin comprometerse con nada.

Vimos una versión de esto en el artículo sobre por qué la edición colaborativa es más difícil de lo que parece: en cuanto los participantes pueden discrepar sobre la forma del estado compartido, necesitas reglas de convergencia explícitas, no solo buenas intenciones. Hacer opcionales las capacidades por defecto es una buena estrategia, pero la semántica visible para el usuario necesita un plan para las personas que quedan al otro lado de la matriz.

Dos ventanas estilizadas mostrando el mismo flujo, una rica en hilos y símbolos, la otra sencilla
Las extensiones opcionales significan que dos usuarios pueden seguir el mismo canal y ver conversaciones distintas.

Qué significa esto para tu próxima API o protocolo

Quitando la nostalgia del chat, IRCv3 es un manual compacto para evolucionar cualquier cosa con un formato de cable. Este es el checklist que daría a cualquier equipo que empiece un protocolo, un esquema de eventos o incluso una API REST pública:

  1. Envía la negociación primero. Intercambio de capacidades en la versión uno, aunque la lista esté vacía. Añadir el descubrimiento después es diez veces más difícil.
  2. Reserva un punto de extensión. Etiquetas, cabeceras, mapas de metadatos: una ranura genérica de la que puedan colgar funciones futuras sin tocar la gramática central.
  3. Haz que el comportamiento nuevo sea opcional por conexión. Los clientes antiguos deben recibir exactamente el mismo comportamiento antiguo, byte a byte. Los servidores de IRCv3 nunca envían una etiqueta a un cliente que no la pidió.
  4. Estandariza el folclore. Cualquier apaño en el que tus usuarios hayan convergido (autenticación al estilo NickServ, reproducción al estilo bouncer) es una especificación que espera escribirse como es debido.
  5. Planifica la matriz de soporte. Las funciones opcionales divergen; decide qué aspecto tienen las experiencias degradadas y publica quién soporta qué.
  6. Agrupa el progreso en hitos con nombre. Las banderas individuales son para implementadores; las versiones numeradas son para las personas.

Lo que de verdad me parece impresionante es la contención. IRCv3 no persiguió el cifrado de extremo a extremo, un rediseño de la federación ni ninguna reescritura grandiosa. Eligió los problemas pequeños y afilados (descubrimiento, metadatos, autenticación, tiempo) y los resolvió de maneras que un cliente de 1995 puede ignorar. Por eso IRC es prácticamente el único superviviente de su generación de protocolos de internet que sigue funcionando en gran medida tal como fue diseñado, y por eso hoy un proyecto de fin de semana puede dar a un grupo de amigos sincronización de historial, TLS y cuentas reales en un VPS de 5 dólares.

La compatibilidad hacia atrás no es la ausencia de cambios. Es la disciplina de hacer que cada cambio sea algo que el pasado puede ignorar sin peligro.

La próxima vez que te tiente versionar una API cambiando la ruta de /v1 a /v2 y obligando a todos los consumidores a migrar, recuerda el protocolo que permitió que un cliente de la era de módem y una app móvil moderna compartieran el mismo canal. Negocia capacidades. Adjunta metadatos en lugar de inventar comandos. Mantén sólido el suelo y deja que el techo suba a su propio ritmo. IRCv3 demuestra que puedes tener un protocolo que respeta a sus mayores y aun así aprende trucos nuevos, y si un sistema de chat de 1988 puede lograrlo, tu microservicio de tres años no tiene excusa.