Articoli approfonditi sulla tecnologia che plasma il futuro.

IRCv3: lezioni di design di protocolli retrocompatibili

IRCv3 ammoderna un protocollo del 1988 senza romperlo. Lezioni su negoziazione delle capability, message tags ed evoluzione retrocompatibile dei protocolli.

Una vecchia macchina telegrafica collegata con un cavo luminoso a uno smartphone moderno su una scrivania
Il trucco di IRCv3: il formato di rete del 1988 funziona ancora, ma i client moderni ottengono un'esperienza moderna.

Nel 1988, uno studente universitario finlandese scrisse un protocollo di chat che stava in poche pagine. Quasi quattro decenni dopo, quello stesso protocollo — IRC — trasporta ancora milioni di messaggi al giorno, e il working group di IRCv3 sta silenziosamente insegnando al resto di noi come far evolvere un protocollo senza far saltare in aria la base installata. Se hai mai provato a versionare un'API da cui dipendono degli sconosciuti, IRCv3 è il caso di studio da cui dovresti copiare.

Ho costruito bot e client di prova per IRC in modo più o meno continuo per anni, e la cosa che continua a sorprendermi è quanto l'esperienza possa sembrare moderna quando entrambe le parti parlano le estensioni più recenti. Cronologia riprodotta dal server. Autenticazione vera. Timestamp dei messaggi che sopravvivono a un bouncer. Eppure un client scritto nel 1995 può ancora connettersi allo stesso server e chiacchierare. Questa combinazione non nasce per caso: è il risultato di una serie di decisioni di design deliberate e di alcuni errori dolorosi che la comunità ha dovuto aggirare.

Il peccato originale: capability di cui non puoi chiedere

Il protocollo IRC di base, codificato nell'RFC 1459 e poi nell'RFC 2812, non aveva alcun meccanismo con cui un client potesse chiedere a un server cosa supporta. Nessuno. Ti connettevi, inviavi NICK e USER, e poi tiravi a indovinare. Vuoi sapere se il server supporta una certa modalità o estensione? La invii e vedi se ottieni un errore. Sembra un'ingenuità finché non hai mantenuto una libreria client che deve gestire casi speciali per cinque diverse implementazioni di server, ognuna delle quali interpreta in modo leggermente diverso le parti ambigue della specifica.

Il problema non è teorico. Gli sviluppatori scrivevano client che sondavano le funzionalità lanciando comandi e analizzando i codici numerici di errore — un pasticcio fragile che si rompeva ogni volta che un server cambiava una stringa d'errore. I bot entravano nei canali con presupposti sbagliati e inondavano di fallimenti. Questo è il gap tra specifica e implementazione nel suo habitat naturale: quando la specifica non ti offre un meccanismo di discovery, ogni implementatore ne inventa uno proprio, e male.

La soluzione di IRCv3 è stata la negoziazione delle capability, ed è così semplice da risultare quasi imbarazzante. Durante la registrazione della connessione, il client invia CAP LS e il server risponde con un elenco di capability con nome che supporta — cose come sasl, server-time o message-tags. Il client poi invia CAP REQ con esattamente il sottoinsieme che vuole, il server conferma, e entrambe le parti procedono con una comprensione condivisa ed esplicita di ciò che la sessione supporta. Un client legacy che non invia mai CAP ottiene semplicemente il vecchio comportamento. Un server legacy che non capisce CAP restituisce un errore che il client può ignorare in sicurezza e a cui può ripiegare.

-> 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 lezione va ben oltre IRC. Se oggi progetti un protocollo o un'API, costruisci il meccanismo di negoziazione già nella prima versione, anche se non c'è ancora nulla da negoziare. Il costo è uno scambio di messaggi; il vantaggio è che ogni funzionalità futura diventa opt-in invece di essere una modifica che rompe tutto.

Lezione: l'estensibilità batte i nuovi comandi

La seconda grande idea di IRCv3 sono i message tags, e nasce da un errore reale: la gente continuava ad aggiungere funzionalità definendo nuovi comandi o infilando semantica nei parametri dei messaggi esistenti. Il formato di rete IRC è notoriamente essenziale — un prefisso, un comando e un elenco di parametri finali. Non c'era un posto dove appendere dei metadati. Quindi, quando qualcuno voleva dei timestamp sulla cronologia riprodotta, o indicare quale account aveva inviato un messaggio, la risposta old-school era inventare un nuovo numerico o rovinare il testo finale. Entrambi gli approcci rompono i client più vecchi o costringono ogni parser a crescere di casi speciali.

La soluzione è stata aggiungere una nuova sezione opzionale all'inizio del messaggio: @key=value;key2=value2 prima del prefisso. Fondamentale: i server inviano i tag solo ai client che hanno negoziato la capability message-tags, quindi i client vecchi non vedono mai un byte che non sanno analizzare. Ecco come appare davvero sulla rete un messaggio con timestamp:

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

È la stessa mossa architetturale di header HTTP, dei metadati gRPC o della tolleranza ai campi sconosciuti di protobuf: riservi un punto di estensione generico e le funzionalità smettono di richiedere interventi chirurgici sul protocollo. L'estensione server-time, che risolve il vecchio problema del bouncer in cui tutti i messaggi riprodotti sembrano appena arrivati, è letteralmente un solo tag. La bozza +reply, che permette ai client di collegare le risposte a messaggi specifici, è un altro tag. Nessun nuovo comando. Nessun nuovo numerico. Nessun flag day per la versione.

Ho fatto l'errore opposto io stesso. Anni fa, in un sistema pub/sub interno, abbiamo aggiunto una funzionalità sovraccaricando un campo JSON con un prefisso magico, perché cambiare il formato dell'envelope « era troppo lavoro ». Otto mesi dopo mantenevamo una regex in tre linguaggi per scartare la magia, e il parser di un team trattava la stringa magica come dato letterale, il che ha prodotto un incidente memorabile. Un campo di metadati stupido ed esplicito sarebbe costato un giorno. La stringa magica ci è costata trimestri. I message tags di IRCv3 sono ciò che quel sistema avrebbe dovuto avere fin dall'inizio.

Buste su un nastro trasportatore, alcune con piccoli adesivi colorati di metadati
Message tags: metadati opzionali allegati a ogni messaggio, invisibili ai client che non li hanno richiesti.

Lezione: eliminare il workaround della password in chiaro

Chiedi a chiunque usasse un client IRC negli anni 2000 come funzionava il login e otterrai la stessa risposta: si digitava /msg NickServ IDENTIFY password. Le credenziali viaggiavano come un normale messaggio di chat, finivano nei log e a volte venivano incollate in un canale pubblico quando il client sbagliava il comando — un rito di passaggio così comune da diventare un meme. L'autenticazione era stata aggiunta a posteriori a livello applicativo perché il protocollo non aveva alcun concetto del genere.

IRCv3 ha risolto il problema in modo serio standardizzando l'autenticazione SASL durante la registrazione. Invece di chattare con un bot dopo la connessione, il client negozia la capability sasl e si autentica come parte dell'handshake, prima di avere un nickname visibile a chiunque. Il server sa chi sei dal primo messaggio, e questo a cascata semplifica tutto: l'estensione account-tag può allegare il nome dell'account a ogni messaggio, i bot possono prendere decisioni sui permessi senza euristiche per leggere NickServ, e i client possono costruire flussi di registrazione che non richiedono di leggere un muro di testo NOTICE.

La lezione più ampia: quando un workaround diventa universale, smette di essere un workaround e diventa il tuo protocollo de facto — senza nessuna cura progettuale. NickServ non era in nessun RFC, eppure ha plasmato due decenni di comportamento dei client, compresi i suoi fallimenti. Standardizzare ciò che la gente già faceva (autenticarsi alla connessione) con un meccanismo vero (SASL, che include PLAIN, EXTERNAL per i certificati client e spazio per altro) ha trasformato il folklore in infrastruttura. Se i tuoi utenti fanno tutti la stessa danza scomoda per ottenere qualcosa, quello è il tuo prossimo punto di specifica, non una FAQ di supporto.

Lezione: la cronologia è un problema di protocollo, non di client

Per gran parte della vita di IRC il protocollo è stato strettamente live: se il tuo client non era connesso quando veniva inviato un messaggio, quel messaggio per te non esisteva. La risposta della comunità è stata il bouncer — un proxy sempre connesso (ZNC è l'esempio classico) che girava su un VPS, teneva aperta la tua sessione e riproponeva ciò che ti eri perso. I bouncer funzionavano, ma erano un'altra infrastruttura da gestire, un'altra credenziale da custodire, e l'esperienza di replay era pessima: tutto arrivava con l'orario del replay, fuori ordine rispetto a ciò che avevi visto in diretta.

IRCv3 ha attaccato il problema a strati. Prima server-time ha reso utile la cronologia riprodotta, portando il timestamp reale di ricezione. Poi la bozza chathistory ha dato ai client un modo standard per richiedere intervalli di cronologia, e server come Ergo hanno iniziato a memorizzare direttamente la cronologia dei canali. Il risultato, come hanno scoperto in molti tirando su un piccolo server Ergo per gli amici, è sorprendente: ti connetti dal telefono dopo una settimana offline e il server ti riproduce tutto con i timestamp corretti, senza bouncer, senza dover fare da babysitter a un VPS. È il salto di qualità più grande che IRC abbia mai avuto, e viene dal protocollo, non da un trucco intelligente nel client.

Esiste un controargomento che merita di essere preso sul serio, e lo fanno gli utenti IRC di tutti i giorni: gran parte della presunta inefficienza di IRC sta nel client, e oggi puoi avere una buona esperienza con un client decente e un bouncer. Vero — ma questo argomento conferma il punto visto dall'altra parte. Ogni autore di client ha reimplementato in modo indipendente la ricostruzione dello scrollback, il tracciamento delle menzioni e la deduplicazione del replay, ognuno con bug leggermente diversi. Spostare la parte difficile nel protocollo e nel server significa implementarla una volta sola, correttamente, e far beneficiare ogni client. Facciamo la stessa scelta continuamente nello sviluppo web: spingere gli invarianti nella piattaforma (pensa a quanto codice applicativo è sparito quando i browser hanno introdotto fetch nativo, o quando TLS è diventato lo standard minimo) invece di lasciare che mille codebase lo risolvano a metà.

Lezione: le funzionalità opzionali creano realtà divergenti

Ora veniamo all'errore che IRCv3 non ha ancora risolto del tutto — ed è quello più rilevante per chiunque progetti sistemi estensibili. Poiché ogni estensione è opzionale e negoziata in modo indipendente, due utenti dello stesso canale possono letteralmente vedere conversazioni diverse. Un client mostra i messaggi con tag +reply come risposte in thread; un altro mostra quegli stessi messaggi come righe piatte, senza contesto. Un utente vede le reazioni emoji come glifi ordinati attaccati a un messaggio; un altro non vede nulla, o un fallback criptico. La matrice di supporto dei client IRCv3 è una griglia di sì e no, e la tua esperienza dipende da quale colonna ti tocca.

Questo è il lato oscuro della degradazione graduale. La retrocompatibilità garantisce che nulla si rompa, ma nel modello nulla garantisce la coerenza. Slack e Discord, qualunque altra cosa si pensi di loro, non hanno mai questo problema: la piattaforma controlla entrambe le estremità abbastanza strettamente da far sì che una reazione sia una reazione per tutti. IRCv3 ha scelto l'evoluzione al posto del controllo, e il costo è un panorama di funzionalità permanentemente disomogeneo, dove le bozze a volte restano bozze per anni.

Se stai costruendo qualcosa di estensibile, mettilo in conto fin dal primo giorno. Alcune mitigazioni su cui IRCv3 e gli ecosistemi simili sono arrivati:

  • Progetta le degradazioni in modo deliberato. Un tag di risposta dovrebbe ripiegare su una citazione leggibile, non sparire. Se il fallback non è utilizzabile, la funzionalità non è pronta per essere rilasciata come opzionale.
  • Pubblica la matrice di supporto e mantienila onesta. Le tabelle dei client e dei server di IRCv3 permettono agli utenti di scegliere consapevolmente; il silenzio alimenta le accuse rivolte al software sbagliato.
  • Definisci una baseline e promuovila. Le release numerate (3.1, 3.2) raggruppano le capability così che i client possano puntare al « supporto 3.2 » invece di undici flag individuali. Dai al tuo ecosistema un minimo comune verso cui convergere.
  • Fai scadere le tue bozze. Una bozza che viene distribuita nei server ma non viene mai finalizzata — la cronologia dei canali ci è andata vicina — è peggio di nessuna bozza, perché biforca il comportamento senza impegnarsi.

Abbiamo visto una versione di questo nell'articolo su perché la modifica collaborativa è più difficile di quanto pensi: nel momento in cui i partecipanti possono non essere d'accordo sulla forma dello stato condiviso, servono regole di convergenza esplicite, non solo buone intenzioni. Rendere le funzionalità opzionali di default è una buona strategia per le capability, ma la semantica visibile all'utente richiede un piano per chi si trova dall'altra parte della matrice.

Due finestre stilizzate che mostrano lo stesso flusso, una ricca di thread e simboli, l'altra semplice
Le estensioni opzionali fanno sì che due utenti possano guardare lo stesso canale e vedere conversazioni diverse.

Cosa significa per la tua prossima API o il tuo prossimo protocollo

Togli la nostalgia della chat e IRCv3 diventa un playbook compatto per far evolvere qualunque cosa abbia un formato di rete. Ecco la checklist che consegnerei a qualsiasi team che inizia un protocollo, uno schema di eventi o anche un'API REST pubblica:

  1. Spedisci prima la negoziazione. Lo scambio di capability già nella prima versione, anche con una lista vuota. Aggiungere la discovery a posteriori è dieci volte più difficile.
  2. Riserva un punto di estensione. Tag, header, mappe di metadati — uno slot generico a cui le funzionalità future possano appendersi senza toccare la grammatica di base.
  3. Rendi i nuovi comportamenti opt-in per connessione. I client vecchi devono ricevere esattamente lo stesso comportamento di prima, byte per byte. I server IRCv3 non inviano mai un tag a un client che non l'ha richiesto.
  4. Standardizza il folklore. Qualunque hack su cui i tuoi utenti hanno convergito (autenticazione in stile NickServ, replay in stile bouncer) è una specifica che aspetta di essere scritta come si deve.
  5. Pianifica la matrice di supporto. Le funzionalità opzionali divergono; decidi come appaiono le esperienze degradate e pubblica chi supporta cosa.
  6. Raggruppa i progressi in milestone con nome. I singoli flag servono agli implementatori; le release numerate servono alle persone.

Quello che trovo davvero impressionante è la sobrietà. IRCv3 non ha inseguito la crittografia end-to-end, un ripensamento della federazione o una riscrittura generale. Ha scelto i problemi piccoli e precisi — discovery, metadati, autenticazione, tempo — e li ha risolti in modi che un client del 1995 può ignorare. Per questo IRC è praticamente l'unico sopravvissuto della sua generazione di protocolli internet ancora in funzione in gran parte come progettato, ed è per questo che oggi un progetto del fine settimana può dare a un gruppo di amici sincronizzazione della cronologia, TLS e account veri su un VPS da 5 dollari.

La retrocompatibilità non è l'assenza di cambiamento. È la disciplina di rendere ogni cambiamento qualcosa che il passato può ignorare in sicurezza.

La prossima volta che ti viene la tentazione di versionare un'API spostando il percorso da /v1 a /v2 e costringendo ogni consumer a migrare, ricordati del protocollo che ha permesso a un client dell'era dial-up e a un'app mobile moderna di condividere lo stesso canale. Negozia le capability. Allega metadati invece di inventare comandi. Mantieni solido il pavimento e lascia che il soffitto salga con i propri tempi. IRCv3 dimostra che si può avere un protocollo che rispetta i suoi anziani e impara comunque nuovi trucchi — e se un sistema di chat del 1988 riesce in questo, il tuo microservizio di tre anni non ha scuse.