# Endpoints de rollback para despliegues autónomos sin colisiones

Los agentes de despliegue autónomos necesitan permiso para fallar de forma segura, no permiso para adivinar. El rollback peligroso rara vez es un comando que falla. Es un comando que tiene éxito después de que el entorno haya cambiado y restaura un artefacto antiguo sobre una versión que el agente no creó.

Una acción de rollback segura identifica un intento de despliegue concreto, nombra una versión anterior concreta y se niega a actuar cuando su visión del estado actual ha quedado obsoleta. Trata el rollback como una transición de estado protegida, no como un atajo hacia «la última versión buena». Esa diferencia determina si un agente repara su propio trabajo o borra el de otra persona.

## Un rollback debe pertenecer a un único intento de despliegue

Los endpoints de rollback deben vincular la reversión con el intento de despliegue que provocó el fallo sospechado. Un número de versión no basta para hacerlo.

Los equipos suelen guardar una secuencia como `1.8.4`, `1.8.5` y `1.8.6`, y después exponen una operación que dice «despliega 1.8.4 en producción». Un agente despliega `1.8.5`, recibe una alerta y llama a esa operación. Mientras tanto, un ingeniero ha desplegado la versión urgente `1.8.6`. El endpoint de rollback acepta la solicitud porque `1.8.4` existe. Ahora producción ejecuta un artefacto anterior a ambos cambios. La API hizo exactamente lo que se le pidió, y ese es el problema.

Mantén separadas estas identidades:

- Una **versión** es un paquete inmutable de código, referencias de configuración y metadatos. Asígnale un ID duradero y un digest del artefacto.
- Un **intento de despliegue** es una solicitud para poner una versión en un objetivo con nombre. Tiene un ID de despliegue, un actor y un ciclo de vida.
- El **estado del objetivo** es lo que el entorno ejecuta en ese momento. Incluye una revisión o generación que cambia con cada transición aceptada.
- La **intención de rollback** indica que el intento de despliegue `D` puede restaurar la versión `R` del objetivo solo si `D` sigue siendo el propietario del estado actual.

El campo que suele omitirse es el vínculo de propiedad. Cuando el despliegue `dep_842` promociona la versión `rel_105`, registra que `dep_842` creó la generación actual del objetivo `gen_913`. Un rollback vinculado a `dep_842` solo puede continuar mientras `gen_913` siga siendo la actual. Si un despliegue posterior crea `gen_914`, el servicio debe rechazar la solicitud antigua.

No infieras la propiedad comparando marcas de tiempo. El orden de los relojes falla con los reintentos, los trabajadores en cola, las reparaciones manuales y cualquier sistema que permita a un usuario seleccionar una versión anterior. Guarda la relación directa cuando aceptes la promoción.

Esto también responde a una pregunta operativa incómoda: ¿puede un agente revertir el despliegue de otro agente? Normalmente, no. Una autoridad independiente puede autorizar explícitamente esa intervención, pero la capacidad normal de rollback debería cubrir solo las acciones iniciadas por quien llama. La autoridad amplia parece cómoda hasta que dos ciclos de despliegue reaccionan ante el mismo incidente.

## La procedencia inmutable permite conocer la versión anterior

El servicio debe capturar la procedencia del rollback antes de cambiar el objetivo, porque después de una promoción la palabra «anterior» se vuelve ambigua. Consultar el historial de versiones después de una alerta es una receta para seleccionar lo que casualmente estaba junto a otra entrada en una lista.

Cuando un servicio de despliegue acepta una promoción, debe crear un registro que incluya la versión estable anterior elegida en ese momento. Esa versión puede diferir del evento inmediatamente anterior. Por ejemplo, un canary puede promocionar `rel_105` después de que `rel_103` siga siendo la base estable, mientras `rel_104` fue un experimento cancelado. El objetivo correcto de recuperación puede ser `rel_103`, no la fila situada justo antes de `rel_105`.

Un registro mínimo podría ser así:

```json
{
  "deployment_id": "dep_842",
  "environment": "production",
  "release_id": "rel_105",
  "artifact_digest": "sha256:8b2c...",
  "source_revision": "4f1d9c7",
  "config_digest": "sha256:1a06...",
  "prior_release_id": "rel_103",
  "created_target_generation": "gen_913",
  "migration_set_id": "mig_77",
  "actor_id": "agent-run-27"
}
```

`prior_release_id` es una decisión, no un campo de conveniencia. Tu controlador de promociones debe elegirlo con reglas que los operadores puedan inspeccionar: la última versión estable verificada para ese objetivo, quizá con configuración y requisitos de migración compatibles. La operación de rollback consume esa decisión guardada. No la recalcula porque la tabla del historial haya cambiado.

La identidad del artefacto necesita algo más que una etiqueta de versión legible. Las etiquetas pueden cambiar de destino. Las etiquetas de compilación pueden reutilizarse por accidente. Un rollback debe desplegar la referencia inmutable del artefacto, basada en su contenido o equivalente, registrada en el intento original. Si tu registro permite que una etiqueta apunte a bytes diferentes más adelante, el ID de la versión debe resolverse al digest capturado en el momento de la promoción.

La configuración merece la misma disciplina. Revertir los bytes de la aplicación y dejar una bandera de funcionalidad modificada, una referencia a un secreto de ejecución, una política de descarga de imágenes o un ajuste de recursos puede crear un sistema que nunca existió en las pruebas. No tienes que duplicar todos los valores en el registro del despliegue, pero sí registrar una revisión o un digest de configuración inmutable y definir si el rollback la restaura.

El estado de la base de datos marca un límite independiente. Una versión que solo añade columnas que aceptan valores nulos suele permitir revertir la aplicación. Una versión que elimina una columna, reescribe valores o cambia significados puede no permitirlo. No prometas un «rollback completo» genérico si el planificador de versiones no puede demostrar la compatibilidad. Marca el despliegue como reversible a nivel de aplicación, reversible a nivel de tráfico o necesitado de reparación. Una negativa resulta menos embarazosa que ejecutar código antiguo contra un esquema que no puede entender.

## El endpoint necesita la versión actual esperada

Una solicitud de rollback debe llevar tanto la versión que quiere restaurar como el estado activo que espera reemplazar. Sin esa condición previa, el endpoint no puede distinguir una recuperación válida de una instrucción obsoleta.

Usa un contrato de solicitud como este:

```http
POST /v1/environments/production/rollbacks
Idempotency-Key: 7e4cd1ee-62cb-4efa-985f-4ee0b77d577b
Content-Type: application/json

{
  "origin_deployment_id": "dep_842",
  "expected_current_release_id": "rel_105",
  "expected_target_generation": "gen_913",
  "restore_release_id": "rel_103",
  "reason": "error rate exceeded release threshold",
  "approval_id": "apr_551"
}
```

El servicio debe obtener `restore_release_id` de `origin_deployment_id` cuando sea posible y después comparar el valor enviado con `prior_release_id`. Mantener ambos campos en la solicitud ayuda a los auditores a ver la intención declarada por el agente, pero prevalece el registro del servidor. Nunca permitas que quien llama convierta su propio despliegue en una licencia para elegir cualquier artefacto histórico.

Cuando tenga éxito, devuelve el nuevo intento de despliegue y la nueva generación del objetivo. No devuelvas una respuesta vaga de `accepted` si el sistema puede reservar la transición de forma síncrona.

```json
{
  "rollback_deployment_id": "dep_849",
  "reverted_deployment_id": "dep_842",
  "previous_release_id": "rel_105",
  "current_release_id": "rel_103",
  "target_generation": "gen_914",
  "status": "running"
}
```

Si el objetivo activo ya no coincide, devuelve una respuesta de conflicto. El cuerpo debe incluir información suficiente para que un agente informe de lo ocurrido, pero no tanta autoridad como para improvisar una acción nueva.

```http
HTTP/1.1 409 Conflict
Content-Type: application/json

{
  "error": "stale_rollback",
  "origin_deployment_id": "dep_842",
  "expected_target_generation": "gen_913",
  "observed_target_generation": "gen_914",
  "observed_release_id": "rel_106"
}
```

Un 409 es un resultado de seguridad exitoso. Enseña al agente que debe detenerse ante este resultado, adjuntar la respuesta a su registro del incidente y solicitar una decisión nueva. No le des una instrucción alternativa como «intenta de nuevo sin la generación esperada». Esa alternativa convierte tu protección en una representación.

Algunos equipos usan un encabezado HTTP `If-Match` con un ETag en lugar de un campo JSON. Funciona si el ETag representa el estado del objetivo y cambia en cada transición. El mecanismo importa menos que la invariante: el comando debe nombrar el estado que puede reemplazar.

## La serialización evita que dos solicitudes válidas colisionen

Una comprobación de condición previa por sí sola no puede proteger un objetivo si dos trabajadores pueden superarla antes de que cualquiera confirme el cambio. El servicio de despliegue debe serializar los cambios del mismo entorno y usar una operación atómica de comparación e intercambio en el almacén de estados.

Supón que el objetivo actual es `(rel_105, gen_913)`. Un agente envía un rollback y un operador envía `rel_106`. Ambas solicitudes leen `gen_913`. Si el servicio comprueba el estado en la memoria de la aplicación y después escribe sin condiciones, ambas llamadas pueden declarar éxito. La última escritura gana y tu registro de auditoría informa de un estado que quizá nunca existió para los usuarios.

Pon la comparación y la mutación en una sola transacción o en una escritura condicional. Una implementación relacional podría usar este patrón:

```sql
UPDATE environment_targets
SET release_id = :restore_release_id,
    generation = generation + 1,
    active_deployment_id = :rollback_deployment_id,
    updated_at = CURRENT_TIMESTAMP
WHERE environment = :environment
  AND generation = :expected_generation
  AND release_id = :expected_release_id;
```

El servicio comprueba el número de filas afectadas. Una fila modificada reserva la transición de estado. Cero filas significa conflicto. Después debe leer el objetivo actual y devolver los valores observados en la respuesta 409.

Una cola no sustituye esta condición. Las colas reducen la probabilidad de trabajo simultáneo, pero la entrega duplicada, una ruta manual fuera de la cola o un reintento del trabajador todavía pueden producir comandos enfrentados. Mantén la escritura condicional donde vive el estado.

La idempotencia resuelve otro fallo. Un agente puede perder la respuesta después de que el servicio acepte el rollback. Si reintenta con la misma clave de idempotencia, el servicio debe devolver el despliegue de rollback y el estado originales. No debe reservar otra generación ni iniciar una segunda ejecución.

Limita la idempotencia al autor y al endpoint, registra un digest del cuerpo de la solicitud y rechaza una clave reutilizada con un cuerpo diferente. De lo contrario, un cliente defectuoso puede asociar una intención nueva con una solicitud anterior al reutilizar un identificador.

## Un rollback puede conservar un estado defectuoso de las dependencias

El rollback de una aplicación y la recuperación del entorno son operaciones distintas. Un endpoint que despliega código antiguo no puede hacer automáticamente compatibles de nuevo todas las dependencias.

He visto la versión predecible de este fallo: la versión `rel_105` introdujo código que escribe un nuevo valor de enumeración. Después, una migración endureció una restricción de la base de datos para permitir solo los valores nuevos. La versión falló por un motivo no relacionado y el operador restauró `rel_103`. El código antiguo escribió el valor anterior, la base de datos lo rechazó y el incidente creció porque el rollback parecía completo en el panel de despliegues.

El endpoint no provocó el cambio de esquema, pero su respuesta de éxito hizo una afirmación falsa. Evita esa afirmación exigiendo metadatos de la versión que describan la compatibilidad en términos concretos. Como mínimo, registra si la versión restaurada puede leer los datos actuales, escribir datos actuales y funcionar con la revisión de configuración del objetivo.

La gestión del tráfico tiene su propia trampa. Un rollback de canary normalmente debería cambiar solo la distribución de tráfico que ese canary controla. Si una versión independiente ha ajustado el grupo estable o si otro controlador ha cambiado una regla de enrutamiento, un endpoint de rollback que escriba un documento de enrutamiento completo puede borrar esos cambios. Usa versiones a nivel de recurso o aplica parches solo a los campos de distribución que el despliegue reservó.

El mismo principio se aplica a la infraestructura. Si una versión creó una cola, un bucket, un rol o una regla de firewall que un trabajo posterior adoptó, eliminarlo durante la reversión puede perjudicar a otro servicio. La limpieza requiere un registro de propiedad del recurso y una comprobación de que ningún despliegue posterior lo haya reclamado. Si no puedes demostrar esa propiedad, deja el recurso en su sitio y crea una tarea de reparación.

Para las operaciones irreversibles, elige una reparación posterior. El agente puede desactivar una bandera de funcionalidad, desviar el tráfico o desplegar una versión correctiva. A los operadores no les gusta esa respuesta porque «rollback» parece más rápido, pero una reversión impecable que destruye datos posteriores cuesta más tiempo que un plan de reparación.

## Los agentes necesitan autoridad limitada y un punto de detención visible

Un agente autónomo debe recibir la autoridad mínima necesaria para completar el despliegue que tiene asignado. No necesita credenciales de nube sin restricciones, un shell general con acceso a producción ni un endpoint que acepte IDs de versión arbitrarios.

Entrega al agente un identificador de despliegue cuando empiece una versión. Ese identificador puede autorizar lecturas de estado, comprobaciones de salud, cambios de tráfico dentro de la distribución del despliegue y un rollback que nombre el despliegue original. Haz que caduque cuando el despliegue alcance un estado terminal o cuando una persona revoque la ejecución. El servicio de despliegue debe aplicar aun así la propiedad en el servidor, porque un identificador puede copiarse o un cliente puede funcionar mal.

La aprobación humana debe producirse antes del límite irreversible, no después de que el agente haya preparado un comando irreversible. Una política razonable solicita aprobación cuando un agente inicia un despliegue en producción y después le permite revertir exactamente ese despliegue mientras se mantenga la condición de propiedad. Si el agente encuentra una versión posterior, necesita una aprobación nueva para cualquier intervención. Es un buen momento para detenerse porque alguien cambió la situación.

Sallyport puede mantener las credenciales HTTP y SSH fuera del proceso del agente mientras una persona aprueba la ejecución del agente o marca una credencial para solicitar aprobación en cada uso. Ese control ayuda a proteger el flujo de acciones, pero el servicio de rollback todavía necesita sus propias comprobaciones de versión y generación. La custodia de credenciales no puede definir la propiedad del despliegue.

Evita nombres de capacidades como `production:rollback:any`. Invitan a quien llama a elegir el alcance durante la ejecución. Prefiere una capacidad emitida por el servidor y vinculada a `dep_842`, al entorno `production` y a la ruta de rollback específica. Si el agente solicita un objetivo no relacionado, la capa de autorización debe rechazarlo antes de que el controlador de despliegue evalúe la solicitud.

Registra la identidad del proceso o de la carga de trabajo del agente en cada solicitud. Una persona debe poder responder quién inició `dep_842`, qué código firmó o autenticó a quien llamó, qué aprobación lo cubría y si alguien revocó el acceso antes de que terminara la ejecución. Las cuentas de automatización anónimas convierten cada incidente en un trabajo de arqueología.

## La verificación debe probar la versión restaurada, no la solicitud

Un rollback solo termina cuando el objetivo ejecuta la versión prevista y el servicio verifica las condiciones que justificaron la recuperación. HTTP 202, una salida de comando exitosa o un evento del controlador que diga «aplicado» no demuestra que la versión antigua atienda el tráfico correctamente.

Define la verificación según el modo de fallo real del despliegue. Si la latencia o los errores activaron el rollback, observa el servicio restaurado mediante el mismo recorrido de medición después de que reciba tráfico. Si una versión de trabajadores consumía trabajos malformados, verifica la versión del trabajador y una carga controlada. Si un error de configuración provocaba fallos de arranque, inspecciona las instancias listas y la revisión de configuración que cargaron.

Mantén una ventana de observación limitada y registra su resultado. El endpoint puede informar `verifying`, después `succeeded`, `failed` o `needs_operator`. No esperes indefinidamente una métrica que quizá no esté disponible. Un límite de tiempo debe producir un resultado explícito de inconcluso, seguido de una decisión del operador.

El evento de auditoría debe conectar todas las etapas: la alerta o regla que solicitó la reversión, el despliegue de origen, el estado esperado, la reserva condicional, los eventos de ejecución, las pruebas de salud, el estado final y cualquier revocación. Los eventos necesitan controles de orden e integridad porque una cronología de despliegue amigable no basta durante una disputa.

Sallyport registra las ejecuciones de agentes y las acciones individuales en un registro de auditoría cifrado y encadenado mediante hashes, y `sp audit verify` comprueba esa cadena sin conexión y sin una clave de la bóveda. Usa ese tipo de prueba para demostrar que un agente solicitó una acción, pero conserva el registro propio de transición de estado y verificación del servicio de despliegue como la fuente autorizada de lo que cambió.

## Los comandos de rollback conocidos necesitan una capa más segura

`kubectl rollout undo` resulta útil para un operador que trabaja directamente con un Deployment de Kubernetes, pero no constituye un contrato completo para la recuperación autónoma. Kubernetes documenta que `kubectl rollout undo` revierte a la revisión anterior del despliegue, a menos que quien llama proporcione `--to-revision`. Ese valor predeterminado tiene sentido durante un diagnóstico práctico. No demuestra que la revisión anterior pertenezca a la ejecución fallida del agente.

Un Deployment de Kubernetes registra el historial de revisiones en ReplicaSets, mientras que `revisionHistoryLimit` controla cuánto historial conserva Kubernetes. Ese historial es un artefacto del controlador, no tu registro empresarial de la base aprobada de una versión, su compatibilidad de configuración o la propiedad del agente. Cuando se elimina parte del historial, «undo» también puede no encontrar la revisión que espera un proceso de despliegue externo.

No entregues a un agente una credencial general de `kubectl` y llames a ese comando tu endpoint de rollback. Coloca un controlador o servicio de despliegue entre el agente y el clúster. El servicio debe resolver el registro del despliegue de origen, comparar la generación activa del objetivo, reservar el cambio de estado e invocar la plataforma subyacente solo después de superar esas comprobaciones.

La misma crítica se aplica a los comandos de proveedores de nube que dicen «despliega la revisión X» o a los controles de CI que dicen «vuelve a ejecutar la versión anterior». Operan sobre un recurso de la plataforma. No saben si una versión está relacionada con el incidente actual del agente a menos que tu plano de control les proporcione ese contexto.

Mantén también limitado el comando de la plataforma. Si el servicio puede aplicar un parche a una revisión de una carga de trabajo identificada, evita concederle permisos de mutación en todo el clúster. Un wrapper que conserve credenciales amplias solo ha ocultado el peligro detrás de otra API.

## Crea un registro de versiones antes de automatizar la recuperación

Puedes introducir un rollback protegido sin sustituir todos los sistemas de despliegue. Empieza por hacer que el registro de versiones sea la fuente autorizada para un objetivo de producción y después obliga a que las rutas humanas y de los agentes pasen por la misma transición condicional.

Una secuencia práctica de implantación tiene cinco partes:

1. Asigna IDs inmutables a las versiones y a los intentos de despliegue, y registra la versión aprobada anterior y la generación del objetivo en el momento de la promoción.
2. Añade un endpoint que requiera `origin_deployment_id`, la versión esperada, la generación esperada y una clave de idempotencia.
3. Haz que la actualización del objetivo sea condicional en la base de datos o en el almacén de control, y devuelve 409 ante cualquier discrepancia.
4. Clasifica cada versión según la reversibilidad de la aplicación, la configuración, los datos y el tráfico antes de promocionarla.
5. Exige pruebas de verificación antes de que el controlador marque un rollback como completado.

Ejecuta este contrato en modo de informe antes de permitir que los agentes lo ejecuten. Deja que el servicio calcule qué restauraría y si rechazaría la solicitud. Compara esas decisiones con las acciones reales de los incidentes. Así descubrirás registros de procedencia incompletos y rutas manuales ocultas sin dar a la automatización la posibilidad de sobrescribir producción.

Después haz que el rechazo sea algo normal. Un rollback obsoleto debe crear un elemento de incidente comprensible con la versión y la generación observadas, no un fallo misterioso que anime a alguien a evitar el endpoint. El endpoint gana confianza cuando rechaza de forma constante una solicitud insegura, incluidas las solicitudes de las personas que lo construyeron.

El primer campo que debes añadir no es `force`. Es `expected_target_generation`. Cuando tus despliegues transporten ese dato y conserven la base original, un agente podrá revertir su propia versión con un límite claro. Hasta entonces, el rollback autónomo no es más que un comando de despliegue antiguo apuntando a un objetivo que se mueve.
