El primer eje, la autoridad: quién tiene derecho a decir que no
El primer eje separa a los motores que dan al servidor la potestad de aceptar o rechazar cada mutación de los que confían en estructuras que convergen sin árbitro, y cada extremo compra garantías distintas.
De los tres ejes que ordenan este nivel, el de la autoridad es el primero porque es el que condiciona a los otros dos y el que menos se puede cambiar después. Formulado como pregunta suena casi trivial: cuando un cliente hace una escritura, ¿existe alguien con potestad para rechazarla? En un extremo del eje la respuesta es sí y tiene nombre propio: hay un servidor que reevalúa cada mutación contra el estado canónico y emite un veredicto que el cliente acata, y Zero de Rocicorp lo dice sin rodeos en su propia documentación. En el otro extremo la respuesta es no, y no por descuido sino por construcción: se eligen estructuras cuya fusión está definida para cualquier par de estados, de modo que ningún estado es rechazable porque no hay nadie facultado para rechazarlo, que es el terreno de los tipos convergentes del bloque anterior. Entre los dos extremos hay un continuo poblado, y esta lección lo recorre entero para dejar claro qué se compra y qué se paga en cada punto, porque la elección no es de gusto arquitectónico sino de qué invariantes tiene tu dominio.
- Describir con precisión el ciclo completo de una mutación bajo un servidor autoritativo, incluida la reubicación de la cola pendiente.
- Entender por qué la ausencia de árbitro no es una carencia sino la condición que permite fusionar sin coordinar.
- Clasificar tus invariantes según sobrevivan o no a la fusión, que es lo que decide de verdad tu posición en el eje.
- Reconocer las posiciones intermedias, incluido el arbitraje de orden sin arbitraje de contenido.
Servidor autoritativo: el derecho a decir que no
En este extremo el cliente no envía el resultado de su escritura sino la intención que la produjo: el nombre de una mutación y sus argumentos, no el estado al que llegó. La distinción parece menor y es la clave entera del modelo. Al recibir una intención, el servidor puede volver a ejecutarla contra el estado canónico, con los permisos reales, el reloj real y la secuencia real, y llegar a una conclusión que el cliente no estaba en condiciones de alcanzar. El cliente había calculado una hipótesis con la información parcial que tenía; el servidor calcula el hecho.
El ciclo completo tiene una pieza que suele pasarse por alto y que hace todo el trabajo fino. Cuando llega la respuesta autoritativa, el cliente no puede limitarse a aplicarla encima de lo que tenía, porque encima de su hipótesis pueden haberse acumulado otras mutaciones locales todavía sin enviar. Lo que hace es descartar su estado especulativo, adoptar el estado canónico recién llegado y reaplicar sobre él la cola pendiente, exactamente igual que una reubicación de commits en un control de versiones. Esa reaplicación es la razón de que las mutaciones tengan que estar escritas como funciones nombradas y reproducibles: van a ejecutarse muchas veces, en el cliente, y una vez, de verdad, en el servidor.
sequenceDiagram participant C as cliente participant S as servidor autoritativo C->>C: aplica la mutacion en local de forma especulativa C->>S: envia nombre y argumentos con un identificador S->>S: reevalua contra el estado canonico y los permisos reales S-->>C: veredicto aceptada o rechazada mas el estado resultante C->>C: descarta lo especulativo y reaplica la cola pendiente
De aquí se deducen dos consecuencias que conviene tener presentes antes de enamorarse del modelo. La primera es que el cliente necesita suficientes datos en local para calcular su hipótesis: si la mutación consulta filas que nunca bajaron, la especulación es imposible y la escritura deja de ser instantánea. Ese acoplamiento entre autoridad y alcance reaparecerá con fuerza en la cuarta lección. La segunda es que el rechazo tiene un coste visible e ineludible: el usuario vio ocurrir algo y va a verlo deshacerse. La reversión no se puede ocultar, solo se puede diseñar, y esa reversión visible es el precio literal del derecho a decir que no.
Convergencia sin árbitro: nadie puede rechazar nada
En el extremo opuesto no hay veredicto porque no hay juez. Las réplicas intercambian estados u operaciones, y la fusión está definida para cualquier par: es conmutativa, asociativa e idempotente, de modo que dos réplicas cualesquiera pueden reconciliarse entre sí sin consultar a una tercera y llegar al mismo sitio con independencia del orden en que se enteren de las cosas. Un servidor, si existe, deja de ser autoridad y pasa a ser un intermediario que almacena y reenvía sin necesidad de entender el contenido. Jazz lleva ese planteamiento al extremo poniendo además cifrado por delante, de modo que el intermediario no solo no juzga: no puede juzgar, porque no ve.
La consecuencia dura de este extremo es que desaparecen las invariantes globales. No hay forma de garantizar que un nombre sea único, que un saldo no baje de cero, que un aforo no se rebase o que exactamente una persona ocupe un puesto, porque garantizar cualquiera de esas cosas exige que alguien vea todas las escrituras candidatas antes de admitir una, y eso es coordinación. El control de acceso también cambia de naturaleza: donde el servidor autoritativo valida, aquí se cifra y se firma. No puedes leer lo que no puedes descifrar y no puedes escribir lo que los demás no aceptarán como firmado por quien corresponde, que es una forma completamente distinta de decir lo mismo y con propiedades muy distintas al revocar.
// Autoritativo: el cliente propone, el servidor dispone
const veredicto = await servidor.ejecutar("transferir", { de, a, importe });
if (veredicto.rechazada) local.revertirYNotificar(veredicto.motivo);
// Sin arbitro: no hay veredicto posible, solo fusion
estadoLocal = fusionar(estadoLocal, estadoRemoto); // definida para todo par
// No existe un lugar donde escribir "saldo insuficiente": nadie tiene los dos saldos a la vez
La pregunta correcta no es cuál de los dos extremos es mejor, porque la respuesta depende enteramente de una propiedad de tu dominio que puedes calcular. Una invariante es cerrada bajo fusión cuando, siendo cierta en dos réplicas por separado, sigue siendo cierta en su fusión. Que un conjunto de etiquetas contenga solo etiquetas válidas lo es. Que una fecha sea posterior a otra suele serlo. Que un contador solo crezca lo es. Que un identificador sea único no lo es. Que un total no supere un tope no lo es. Las invariantes del primer grupo no necesitan árbitro y coordinar por ellas es tirar disponibilidad a la basura; las del segundo no sobreviven a ninguna cantidad de ingenio algebraico y exigen coordinación, aunque sea mínima y localizada.
Cuando una invariante no es cerrada bajo fusión, aún queda una salida antes de rendirse a la coordinación en cada escritura: repartir por adelantado. Si hay mil unidades de un recurso escaso, se asignan doscientas a cada uno de cinco clientes en un momento en que sí hay red, y a partir de ahí cada cliente puede gastar de su reserva sin consultar a nadie, porque la invariante global se mantiene por construcción. La coordinación se paga una vez y por adelantado en lugar de una vez por operación, y la disponibilidad se recupera casi entera. Su límite es igual de claro y hay que aceptarlo: el reparto puede quedar mal calibrado, y un cliente con la reserva agotada tendrá que esperar a la red aunque queden unidades libres en la reserva de otro.
Qué compra y qué paga cada extremo
Autoritativo: compra garantías
Invariantes globales, permisos aplicados en el momento de escribir, validación de servidor y un único lugar donde reside la verdad.
Autoritativo: paga finalidad
Sin red no hay veredicto, solo hipótesis acumuladas. Y la reversión de una hipótesis larga es visible, tardía y difícil de explicar al usuario.
Sin árbitro: compra autonomía
Funciona sin servidor, entre pares, durante meses, y sobrevive a que el proveedor desaparezca. No hay punto único de fallo porque no hay punto único.
Sin árbitro: paga el no
Ninguna invariante global, revocación de permisos difícil de hacer efectiva y un modelo de datos obligado a expresarse con estructuras que conmutan.
Formulado con precisión, el intercambio es este: la potestad de rechazar es una forma de coordinación, y la coordinación es exactamente lo que hay que ceder para seguir disponible durante una partición. No es una metáfora ni una analogía suelta con los teoremas clásicos de sistemas distribuidos; es el mismo enunciado dicho con otras palabras. Un servidor autoritativo aislado de sus clientes tiene solo dos salidas y ambas son pérdidas: negarse a finalizar, con lo que la escritura se queda pendiente indefinidamente, o aceptar a ciegas, con lo que ya no es autoritativo. No hay una tercera puerta esperando a que alguien la descubra.
Conviene además desactivar una lectura moral del eje que circula bastante. El extremo sin árbitro no es el extremo puro ni el extremo virtuoso, del mismo modo que el autoritativo no es el pragmático ni el atrasado. Son dos maneras de repartir un coste fijo, y la que sobra en un dominio es imprescindible en el de al lado. Un editor colaborativo de notas personales no tiene ninguna invariante global que proteger y pagaría un peaje absurdo por un árbitro. Un sistema de reserva de asientos tiene una invariante que no es negociable, y venderla como convergencia elegante no la hace desaparecer: la convierte en una incidencia con dos personas sentadas en la misma butaca.
Un continuo, no un interruptor
Las posiciones intermedias son la mayoría y merecen nombre propio. La primera es la autoridad parcial por dominio: el servidor arbitra solo el puñado de mutaciones que tocan invariantes no cerradas bajo fusión, y deja convergir todo lo demás sin intervenir. Es la posición correcta en casi cualquier aplicación real, porque en un modelo típico la proporción de campos que necesitan árbitro es pequeña y aplicar coordinación a los demás es pagar por un seguro que no cubre nada.
La segunda es la asimetría entre caminos, y es lo que ocurre cuando alguien mira el giro que dio ElectricSQL en 2024 y no sabe dónde colocarlo. Ese modelo hace llegar filas autoritativas de Postgres al cliente sin exigirle estructuras convergentes, mientras que las escrituras suben por tu propia interfaz de servidor, que es autoritativa por definición porque es código tuyo ejecutándose junto a la base. El camino de bajada se comporta como una réplica y el de subida como una petición clásica: no es una posición confusa, es una decisión deliberada de no acoplar ambos caminos al mismo mecanismo.
La tercera, y la más sutil, es el arbitraje de orden sin arbitraje de contenido. Un sistema de registro de eventos como LiveStore mantiene un log determinista de mutaciones y deriva de él el estado en SQLite; el servidor que ordena ese log ejerce autoridad, pero solo sobre la secuencia, no sobre la validez semántica de cada entrada. Es autoridad en un sentido muy débil y a la vez suficiente para recuperar propiedades que la convergencia pura no da, como poder reconstruir el estado exacto de cualquier cliente en cualquier punto y poder razonar sobre un antes y un después que todos comparten.
Casi todo el mundo aborda este eje como una elección de arquitectura y por eso lo aborda mal. Se discute si conviene un servidor autoritativo o un modelo entre pares, se comparan latencias, se invocan preferencias, y la conversación acaba en el sitio de siempre, que es una decisión tomada por afinidad estética y defendida luego con argumentos técnicos reconstruidos a posteriori. La reformulación que ordena todo el asunto es esta: el eje de la autoridad no es una propiedad de tu arquitectura, es una propiedad de tu dominio, y ya estaba decidida antes de que abrieras el editor. Haz el ejercicio de escribir todas las invariantes de tu aplicación, sin filtrar, y clasifícalas en dos montones: las que siguen siendo ciertas después de fusionar dos estados donde eran ciertas por separado, y las que no. Ese reparto no lo eliges tú, viene dado por lo que tu producto significa, y determina el mínimo de coordinación que vas a pagar hagas lo que hagas. Si el segundo montón está vacío, cualquier árbitro que introduzcas es coste puro: estás cambiando disponibilidad por una garantía que ya tenías gratis, y lo peor es que el equipo lo vivirá como prudencia. Si el segundo montón tiene una sola invariante, es un error igual de grave —aunque menos comentado— someter la aplicación entera a un árbitro por culpa de esa única línea, porque la coordinación que esa invariante exige es local y minúscula y no había ninguna necesidad de propagarla al resto. De ahí se sigue la única regla de diseño que este eje admite y que casi nadie aplica: no elijas un punto del eje para tu aplicación, elige un punto por invariante y paga coordinación exactamente donde la matemática la exige y en ningún otro sitio. Y de ahí se sigue también la explicación de por qué este mercado no converge ni va a converger en una respuesta única, por mucho que cada año alguien anuncie que ya se resolvió: lo que se está distribuyendo no son opiniones sobre la mejor arquitectura, sino dominios con repartos distintos entre esos dos montones. Zero puede ser explícitamente autoritativo y Jazz puede llevar la descentralización al extremo sin que ninguno de los dos esté equivocado, porque no están compitiendo por el mismo problema: están sirviendo a poblaciones de invariantes distintas. Cuando alguien te pregunte cuál es mejor, la respuesta técnicamente honesta no es una preferencia sino otra pregunta, y ya sabes cuál.
- Escribe la lista completa de invariantes de tu dominio, incluidas las que nunca has puesto por escrito porque las das por obvias.
- Para cada una, comprueba formalmente si sobrevive a la fusión de dos estados donde era cierta por separado, y anota el contraejemplo cuando no.
- Cuenta qué proporción del modelo cae en cada montón, y compárala con la cantidad de coordinación que tu arquitectura actual impone.
- Para una invariante que no sobreviva a la fusión, diseña el reparto por adelantado que la salvaría y calcula el tamaño de reserva razonable.
- Escribe una mutación como intención reproducible y ejecútala dos veces, en local y en servidor, verificando que la reaplicación de la cola pendiente produce el mismo estado.
- Simula un rechazo tardío de una mutación sobre la que ya se apoyan otras tres y describe exactamente qué ve el usuario en pantalla.