# Los tiempos de espera de aprobación evitan que las aprobaciones antiguas ejecuten acciones nuevas

Una aprobación es un permiso para una acción propuesta concreta en un momento determinado. No es un cupón que un agente pueda canjear cuando le resulte conveniente.

Parece obvio, hasta que un agente pone en cola un despliegue de producción, una eliminación o un comando SSH mientras una persona está ocupada. El revisor ve una solicitud razonable, la aprueba y después el mundo cambia. Una compilación más reciente gana su lugar en la cola. Cambia el conjunto de objetivos. Alguien redirige un alias de entorno. Un incidente modifica las condiciones de seguridad. Si la aprobación antigua todavía puede ejecutarse, el sistema ha convertido una decisión sobre el estado de ayer en autoridad sobre el estado de hoy.

Los tiempos de espera de aprobación solo solucionan una parte del problema, pero es una parte que los equipos suelen dejar abierta. Añade una caducidad breve y estricta a las aprobaciones de acciones cuyo significado pueda cambiar. Después, vincula la aprobación a la acción propuesta exacta y vuelve a comprobar antes de ejecutar las condiciones que pueden haber cambiado. Haz ambas cosas. Por separado, cada control deja un hueco.

## Toda aprobación tiene un presupuesto de vigencia

Cada aprobación consume tiempo mientras espera para ejecutarse. Cuanto más pueda cambiar el objetivo, menor debe ser ese presupuesto.

Una solicitud para reiniciar un trabajador de vista previa desechable puede seguir teniendo sentido durante media hora. Una solicitud para promocionar una versión a producción puede quedar obsoleta en unos minutos si otra versión, una reversión o la respuesta a un incidente pueden cambiar el siguiente paso correcto. Un comando que elimina una instantánea de copia de seguridad identificada debería caducar pronto cuando la lista de instantáneas está sometida a un proceso activo de retención. Un comando SSH contra un alias de host mutable merece la ventana más corta de todas.

El valor predeterminado equivocado es elegir un número grande para no molestar a nadie. Ocho horas parece considerado. En la práctica, permite que las aprobaciones se acumulen durante la comida, la noche o el cambio de turno. Un revisor puede aprobar un despliegue a las 10:02, olvidarlo y descubrir a las 16:40 que un estado mucho más reciente de la cola consumió su clic. La interfaz de aprobación parecía cuidadosa. La ruta de ejecución fue descuidada.

Define la ventana con una pregunta más concreta: ¿durante cuánto tiempo puede esta acción propuesta exacta seguir describiendo fielmente lo que va a ocurrir?

Para la mayoría de los equipos, una política inicial útil podría ser esta:

- Despliegue o reversión de producción: de 5 a 15 minutos.
- Eliminación destructiva: de 2 a 10 minutos, según si el conjunto de objetos está fijado.
- Comando SSH con efectos de escritura: de 2 a 5 minutos.
- Inspección SSH de solo lectura: sin aprobación por acción, o una ventana más larga si tu entorno aún exige una.
- Cambios rutinarios fuera de producción con una compilación fijada: de 15 a 30 minutos.

Son valores operativos iniciales, no constantes universales. Una caducidad de cinco minutos es demasiado larga si un bucle de automatización puede modificar el objetivo cada pocos segundos. Es demasiado corta si un revisor de guardia necesita reunir pruebas antes. La respuesta no es alargar discretamente el plazo para siempre. Proporciona al revisor el contexto que necesita y facilita regenerar la solicitud a partir del estado actual.

GitHub Actions establece aquí una distinción importante: un trabajo de despliegue puede esperar una revisión obligatoria y, cuando recibe la aprobación, un trabajo pendiente puede continuar y obtener acceso a los secretos de su entorno. GitHub también documenta que un trabajo sin aprobar puede fallar después de 30 días. Eso evita que las colas vivan para siempre, pero 30 días no es una ventana de vigencia útil para una aprobación sobre un despliegue cambiante. Tu propio control debe tratar la antigüedad de la aprobación como parte de la autorización, no como simple mantenimiento de la cola.

## Vincula la decisión a la acción, no a una etiqueta

Una aprobación debe cubrir datos concretos de la acción. «Aprobar el despliegue en producción» es una etiqueta. Apenas informa al revisor sobre el objeto que se ejecutará.

Para un despliegue, vincula la decisión al resumen inmutable del artefacto o al identificador de la confirmación, al destino, al plan de migración si existe, a la revisión de configuración y a la operación de lanzamiento. Para una eliminación, vincúlala a una lista inmutable de objetos o a una instantánea del resultado de la consulta, además del modo de eliminación. Para SSH, vincúlala a la identidad verificada del host, el usuario, la plantilla de comando, los argumentos ya expandidos, el directorio de trabajo si corresponde y una descripción limitada de los archivos de entrada.

Esta es la distinción que los equipos confunden con más frecuencia:

- **Aprobar una intención** significa que el revisor acepta un objetivo general, como «eliminar vistas previas obsoletas».
- **Aprobar una acción** significa que el revisor acepta que este ejecutor elimine estos identificadores de objeto, con este comando, antes de este plazo.

La aprobación de una intención tiene su lugar en la gestión de cambios. No puede sustituir a una aprobación de ejecución cuando un agente puede actuar sobre un sistema activo. Si apruebas una intención y después permites que el agente resuelva los objetivos, has delegado en él la parte importante de la decisión.

La Transaction Authorization Cheat Sheet de OWASP lo señala en otro ámbito. Indica que la persona que autoriza una transacción debe identificar y reconocer los datos importantes de la transacción, y advierte que cambiar los datos después de la autorización crea un fallo de tiempo de comprobación frente a tiempo de uso. El ejemplo es financiero, pero la regla se aplica directamente: los datos de aprobación deben protegerse contra modificaciones, y un cambio de datos debe invalidar la autorización existente.

Un resumen preciso proporciona al ejecutor algo concreto con lo que comparar. No calcules un hash de una descripción vaga en lenguaje natural y lo des por terminado. Normaliza los campos que controlan el efecto, serialízalos de forma determinista y calcula el hash de esa forma canónica.

```json
{
  "request_id": "appr_01JX...",
  "action_type": "deploy",
  "action_digest": "sha256:8b1d...",
  "summary": {
    "artifact": "registry.example/app@sha256:4fa2...",
    "environment": "production",
    "operation": "promote",
    "config_revision": "7c0e...",
    "migration": "none"
  },
  "issued_at": "2026-07-22T14:03:00Z",
  "expires_at": "2026-07-22T14:13:00Z",
  "status": "pending"
}
```

El `summary` es para la persona. El `action_digest` es para el ejecutor. Conserva ambos. Las personas necesitan ver datos útiles; los servicios necesitan una comprobación de igualdad exacta. Si el agente cambia aunque sea un campo vinculado, debe enviar una nueva solicitud y obtener una nueva decisión.

## La caducidad y la invalidación resuelven fallos distintos

Un plazo evita que las aprobaciones antiguas permanezcan abiertas. La invalidación elimina una aprobación en cuanto cambian los datos relevantes. Necesitas ambas cosas porque esperar a que termine un temporizador es una mala práctica cuando el sistema ya sabe que la solicitud no coincide con la realidad.

Invalida una aprobación pendiente cuando cambie el resumen de la acción. Esa regla no admite excepciones. Invalídala también cuando cambie una dependencia que afecte a su significado: la revisión prevista del entorno de un despliegue, el conjunto seleccionado para una eliminación, una clave de host, el titular de un bloqueo de lanzamiento o el estado de un ticket de cambio obligatorio.

No invalida la aprobación ante cualquier evento sin relación. Si cada línea de registro, confirmación ajena o cambio inocuo de una métrica acaba con una aprobación, los revisores aprenderán que las solicitudes no son fiables y empezarán a aprobarlas sin examinarlas. La regla debe seguir los datos que cambian el efecto solicitado o las condiciones de seguridad.

Usa tres estados, no dos:

1. `pending` significa que la solicitud exacta todavía puede aprobarse antes del plazo.
2. `approved` significa que un revisor la aprobó, pero el ejecutor aún no la ha consumido.
3. `consumed` significa que una ejecución reclamó la aprobación exactamente una vez.

Añade estados terminales para `expired`, `invalidated`, `rejected` y `failed`. Una solicitud rechazada no debe volver a estar pendiente porque un agente haya reintentado una llamada de red. Una ejecución fallida no debe reutilizar silenciosamente la misma aprobación, salvo que puedas demostrar que la acción nunca comenzó y que no cambió ningún estado relevante. En la mayoría de los sistemas de acciones, es más seguro y más fácil de explicar pedir al agente que vuelva a solicitarla.

El ejecutor debe realizar estas comprobaciones en una sola transacción o en una operación atómica de comparación e intercambio:

```text
if now >= expires_at: reject as expired
if status != approved: reject as unavailable
if stored_digest != supplied_digest: reject as changed
if live_preconditions fail: reject as stale
atomically change status from approved to consumed
execute the action
```

No marques la aprobación como consumida después de que comience la acción. Dos trabajadores pueden competir, observar ambos el estado `approved` y ejecutar los dos. Consúmela primero mediante una transición de estado atómica y registra después que la ejecución comenzó. Si el proceso muere tras el consumo, trata el resultado como desconocido hasta que el ejecutor pueda establecer si llegó al objetivo. Es incómodo. El trabajo destructivo duplicado es peor.

## Las aprobaciones de despliegue deben seguir al artefacto

Una solicitud de despliegue queda obsoleta cuando cambian su artefacto, destino, plan de lanzamiento u orden en la cola. Los nombres de rama y las etiquetas móviles no bastan.

La solicitud de despliegue debe identificar un artefacto inmutable. Puede ser un resumen de imagen, el hash de un paquete de lanzamiento firmado o un registro de compilación inmutable. También debe indicar al revisor si el ejecutor ejecutará migraciones de base de datos, cambiará la configuración de funcionalidades, reiniciará instancias o reemplazará un despliegue anterior. Estos detalles afectan a la aprobación. Ocultarlos tras un botón genérico de «desplegar» invita a aprobar por inercia.

Un contrato sólido de aprobación de despliegue incluye:

- El identificador de compilación inmutable y la revisión de origen.
- El entorno de destino exacto y la identidad de la cuenta o del clúster.
- La operación de lanzamiento, como promocionar, revertir o volver a desplegar.
- Las revisiones de configuración y migración que se aplicarán.
- Un token de concurrencia o una generación de despliegue.

El token de concurrencia importa cuando un cambio posterior sustituye a una solicitud anterior. Supón que la compilación A espera aprobación. La compilación B termina, supera las comprobaciones y se convierte en la versión que ahora quieres publicar. Si la solicitud de A sigue siendo válida, un revisor puede liberar accidentalmente la versión antigua. Cuando B entra en el mismo canal de lanzamiento, invalida la aprobación pendiente de A. No dependas de que los revisores reparen en las marcas de tiempo dentro de una cola activa.

La documentación de despliegues de GitHub separa la protección del entorno de la concurrencia del flujo de trabajo. Sus controles de concurrencia pueden cancelar trabajo pendiente en un grupo, mientras que la aprobación del entorno controla si un trabajo puede continuar. La separación es útil: una política de cola puede decidir qué ejecución es la actual, mientras que una barrera de aprobación puede decidir si esa ejecución exacta puede ejecutarse. Combinarlas sin cuidado produce el fallo clásico en el que la persona correcta aprueba la ejecución equivocada.

El ejecutor debe volver a comprobar todo antes del despliegue. Una comprobación previa práctica podría verificar que el artefacto solicitado todavía existe, que el entorno sigue apuntando a la identidad de destino esperada, que ninguna versión más reciente ocupa el canal y que el plan de migración sigue coincidiendo con el resumen aprobado. Si falla alguna comprobación, marca la solicitud como invalidada y muestra al revisor una solicitud nueva. Nunca sustituyas silenciosamente la compilación A por la B porque A haya sido aprobada. Es otra acción.

Evita un patrón popular pero débil: aprobar una solicitud de incorporación de cambios y tratar esa aprobación como autorización para producción. La revisión de código responde si un cambio propuesto pertenece a la base de código. No responde si esta compilación debe ejecutarse ahora en producción, después del incidente actual y con las migraciones y el estado de destino actuales. Mantén esas decisiones separadas.

## Las solicitudes de eliminación necesitan un conjunto de objetos congelado

Las aprobaciones de eliminación se vuelven peligrosas cuando la solicitud contiene una consulta en lugar de los objetos resueltos. «Eliminar copias de seguridad de más de 30 días» puede describir un conjunto diferente cada minuto.

Al crear la solicitud, resuelve la consulta en identificadores de objeto y registra un marcador de instantánea. La pantalla de aprobación debe mostrar el número, algunos identificadores representativos, la base de retención y el modo exacto de eliminación. El ejecutor debe usar ese conjunto congelado, no volver a ejecutar la consulta amplia después de la aprobación.

Si el conjunto es demasiado grande para mostrarlo completo, proporciona al revisor un identificador estable del manifiesto y un desglose conciso. No reduzcas la solicitud a «eliminar 8.421 elementos» sin ningún límite. Una cantidad no permite saber si la lista incluye el inquilino equivocado, copias actuales o un prefijo inesperado.

Considera esta solicitud:

```json
{
  "action_type": "delete_objects",
  "scope": "archive/preview/",
  "selection": {
    "manifest_digest": "sha256:19e7...",
    "object_count": 184,
    "newest_object_at": "2026-06-19T03:11:00Z",
    "oldest_object_at": "2025-11-02T18:24:00Z"
  },
  "mode": "permanent",
  "expires_at": "2026-07-22T14:08:00Z"
}
```

Durante la ejecución, confirma que el manifiesto sigue existiendo y que cada identificador de objeto sigue resolviendo a la versión esperada. Si el sistema de almacenamiento admite versiones, vincula la eliminación a las versiones y no a los nombres. Los nombres pueden reutilizarse. Un objeto nuevo escrito en la misma ruta después de la aprobación no debe heredar la sentencia de muerte del objeto antiguo.

Un plazo breve es especialmente importante cuando la solicitud se basa en la antigüedad o en un inventario activo. Cuanto más espere, más probable es que un objeto que acaba de cumplir el requisito, un elemento restaurado o un registro reclasificado cambie el conjunto previsto. Si el sistema no puede congelar el conjunto, no debe permitir que una sola aprobación autorice una consulta de eliminación amplia. Pide en su lugar una solicitud reciente y limitada.

La eliminación lógica y la eliminación permanente merecen solicitudes y ventanas de caducidad diferentes. La eliminación lógica puede revertirse, pero no uses esa posibilidad como excusa para aprobar solicitudes vagas. La recuperación suele ser lenta, incompleta o depender de permisos que no controla la persona que solicita la eliminación.

## Las aprobaciones SSH se deterioran más rápido de lo que parece

SSH es especialmente sensible al contexto obsoleto porque los nombres, las sesiones, las variables de entorno y los árboles de trabajo pueden cambiar bajo el mismo texto de comando.

`systemctl restart api` parece específico hasta que preguntas qué máquina lo recibe, a qué apunta `api` allí, qué despliegue está activo y si un incidente posterior cambió el motivo para reiniciar algo. `rm -rf /srv/tmp/job-123` puede ser seguro en un host y catastrófico en otro si cambian un alias, un punto de montaje o una expansión del shell.

Para una acción SSH, aprueba una solicitud de comando estructurada, no una transcripción de terminal. La solicitud debe incluir la identidad verificada del host, el usuario de destino, una plantilla de comando fija, los argumentos permitidos ya expandidos, el directorio de trabajo declarado y cualquier resumen esperado de los archivos de entrada. Si el agente necesita un shell, limita el comando a una carga útil explícita en lugar de autorizar una sesión interactiva abierta.

Una tarjeta de aprobación razonable podría decir:

```text
Host: prod-api-03, host key SHA256:K4f...
User: deploy
Command: /usr/local/bin/release-health --release 2026.07.22.4 --repair-cache
Directory: /srv/api
Effect: writes cache state, may restart one service
Expires: 14:08 UTC
```

Eso tampoco demuestra que el comando sea seguro. Sí proporciona información suficiente para que una persona reconozca lo que está autorizando. Después, el ejecutor vuelve a conectarse, verifica de nuevo la identidad del host, confirma el resumen del comando y lo ejecuta antes del plazo.

Nunca permitas que la aprobación de un comando en `prod-api` autorice su ejecución después de que DNS, el inventario o el mapeo del bastión hayan resuelto esa etiqueta en otro host. Vincula la aprobación a la identidad criptográfica del host cuando la configuración de la conexión lo permita. Si se produce una rotación legítima de la clave del host mientras la aprobación espera, invalida la solicitud. Puede resultar molesto durante el mantenimiento. Es preferible a aprobar un comando para una máquina y enviarlo a otra.

Los comandos de solo lectura merecen una categoría propia. Los equipos suelen exigir aprobación para cada llamada SSH porque tienen un único control y lo aplican en todas partes. El resultado es fatiga de aprobación, y después los revisores hacen clic en comandos que no pueden interpretar. Separa las inspecciones inocuas de las acciones que escriben, reinician, modifican accesos o exponen resultados sensibles. Usa aprobación por llamada cuando los efectos del comando lo justifiquen y mantén la solicitud lo bastante compacta para poder leerla.

## La pantalla de aprobación debe hacer visibles los cambios

Un contrato preciso en el backend no sirve de mucho si el revisor solo ve una frase escrita por el agente. La pantalla debe mostrar los campos que podrían cambiar la respuesta.

Empieza por el efecto: desplegar este resumen en este entorno; eliminar permanentemente este manifiesto fijo; ejecutar este comando en este host verificado. Coloca el plazo donde el revisor lo vea antes de aprobar y muestra la hora en una zona horaria inequívoca. Muestra una cuenta atrás solo como ayuda. La validez la decide la marca de tiempo del servidor del ejecutor.

Cuando cambie una solicitud, no sustituyas el contenido antiguo en el mismo sitio dejando activo el botón de aprobación. Márcala como invalidada. Crea una solicitud nueva con una explicación visible, como «el artefacto ha cambiado» o «ha cambiado el inventario de objetivos». Un revisor que aprobó la versión anterior debe tomar una decisión nueva. Ese clic adicional es precisamente el objetivo.

NIST SP 800-63B-4 describe la intención de autenticación como la intervención de la persona que confirma que pretende autenticarse o volver a autenticarse. La aprobación de acciones necesita la misma disciplina, pero de forma más concreta. Un toque o un clic de confirmación debe expresar la intención de realizar la operación mostrada, no una disposición general a dejar que el agente continúe.

Evita solicitudes que conviertan la presión temporal en una trampa. Una ventana de dos minutos para una eliminación compleja obliga al revisor a elegir entre aprobar a ciegas o dejar que caduque. La solicitud debe estar lista para inspeccionarse antes de llegar al revisor. Usa un plazo corto de ejecución después de que el revisor tenga contexto suficiente, no un plazo apresurado para decidir que castigue una lectura cuidadosa.

Un campo de comentarios puede ayudar cuando el revisor necesita explicar por qué una acción inusual es aceptable. No lo hagas obligatorio para el trabajo normal. El texto obligatorio y rutinario produce comentarios que nadie lee. Exígelo para anulaciones, ampliaciones excepcionales del plazo o acciones que superen un radio de impacto definido.

## El ejecutor es responsable de hacer cumplir las reglas

El sistema que tiene autoridad para ejecutar la acción debe hacer cumplir la caducidad, la vinculación y el consumo único. Una interfaz de flujo de trabajo, un bot de chat o un marco de agentes puede solicitar la aprobación, pero no puede ser el juez final si otro componente puede saltársela.

Por eso una pasarela de acciones es un límite útil. El agente propone una llamada HTTP o un comando SSH. La pasarela comprueba si existe una aprobación vigente, inyecta la credencial cuando corresponde, realiza la operación y devuelve el resultado. El agente nunca necesita una credencial reutilizable que le permita saltarse después la ruta de aprobación.

Sallyport sigue esta estructura para HTTP y SSH: el agente se conecta mediante su adaptador MCP mientras las credenciales permanecen en la bóveda cifrada de la aplicación, y la aplicación ejecuta la acción en lugar de entregar los secretos al agente. Sus claves por llamada son un lugar natural para exigir una confirmación nueva en acciones cuyo contexto cambia rápidamente. La lógica de tiempo de espera y vinculación de la acción aún debe ser explícita en la ruta de la solicitud; una confirmación sin esas comprobaciones puede convertirse en una aprobación obsoleta.

Mantén el estado de aprobación junto al ejecutor o haz que este pueda verificarlo criptográficamente. Un token de aprobación firmado puede funcionar si incluye el identificador de la solicitud, el resumen de la acción, la hora de emisión, la caducidad, la identidad del revisor y un nonce. El ejecutor debe comprobar también la revocación y consumir el nonce una sola vez. Un token firmado que sigue siendo válido después de invalidar la solicitud no es más que una aprobación obsoleta bien firmada.

Trata los relojes con cuidado. Usa el reloj de un servicio fiable para decidir la caducidad, almacena las marcas de tiempo en UTC y rechaza las aprobaciones en el instante de caducidad o después. El reloj local del agente y la cuenta atrás del navegador son ayudas visuales. No son datos de autorización.

## Los registros deben explicar por qué se ejecutó o no una acción

Cuando caduca una aprobación, los equipos necesitan un registro que diga algo más que «denegada». Necesitan saber si el revisor nunca respondió, si la solicitud cambió después de aprobarse, si el ejecutor detectó una condición previa fallida o si otro trabajador ya consumió la aprobación.

Escribe un historial de eventos inmutable que conecte la propuesta, el resumen mostrado, la decisión de aprobación, la invalidación o caducidad, las comprobaciones previas, el intento de ejecución y el resultado. Guarda el resumen de la acción en cada evento. Si se regeneró la solicitud, registra el identificador de la solicitud sustituta sin insinuar que la aprobación anterior se transfirió.

Un formato de evento útil podría ser este:

```json
{
  "event": "approval.invalidated",
  "request_id": "appr_01JX...",
  "action_digest": "sha256:8b1d...",
  "reason": "release_lane_superseded",
  "replaced_by": "appr_01JY...",
  "recorded_at": "2026-07-22T14:06:22Z"
}
```

Registra también los intentos de consumir aprobaciones caducadas. Revelan agentes que reintentan a ciegas, trabajadores con relojes incorrectos y rutas de interfaz que no se actualizaron. También permiten al revisor de un incidente distinguir una solicitud caducada de una acción que realmente llegó a producción.

El diario Sessions y el diario Activity de Sallyport se proyectan desde un registro de auditoría cifrado y encadenado mediante hashes, y su comando `sp audit verify` comprueba esa cadena sin conexión sobre el texto cifrado. Ese tipo de historial solo sirve si el vocabulario de eventos es honesto. Incluye eventos de caducidad, invalidación y fallos de las comprobaciones previas, no solo llamadas correctas que hagan que el panel parezca limpio.

No confundas la auditabilidad con la prevención. Un registro perfecto de una aprobación antigua que se ejecutó contra un estado nuevo es la prueba de un fallo. La comprobación preventiva debe ejecutarse antes de que el ejecutor envíe la solicitud o abra el canal SSH.

## Haz que sustituir solicitudes caducadas resulte sencillo

Las ventanas breves solo funcionan cuando crear una solicitud nueva es más fácil que discutir con una antigua. Si regenerarla exige volver a introducir un número de ticket, reconstruir un comando manualmente y localizar a tres personas, los equipos presionarán para alargar todos los tiempos de espera hasta volverlos inútiles.

El agente debe poder volver a enviar la solicitud a partir del estado actual, pero debe mostrar claramente los datos nuevos. Si solo cambió la caducidad y todos los campos vinculados siguen idénticos, una solicitud nueva puede conservar el mismo resumen legible, pero debe recibir un identificador y un plazo nuevos. Si cambió algún campo de la acción o alguna condición activa, explica qué cambió. No obligues al revisor a comparar hashes opacos.

Trata la ampliación del plazo como una excepción. Si la ofreces, exige que el ejecutor vuelva a ejecutar todas las comprobaciones previas y que el revisor vuelva a ver el resumen actual. Un botón de «ampliar 30 minutos» que renueva el token antiguo es una forma de saltarse la aprobación con una tipografía más agradable.

Empieza midiendo cuatro cosas: con qué frecuencia caducan las aprobaciones, con qué frecuencia se invalidan acciones aprobadas antes de ejecutarse, cuánto esperan las solicitudes y qué tipos de acción generan reintentos repetidos. Esos resultados indican si el plazo es demasiado corto, si la cola es lenta o si el agente crea solicitudes antes de tener entradas estables.

Una aprobación obsoleta debe fallar de forma silenciosa y específica: el ejecutor la rechaza, el registro indica el motivo y el agente solicita una decisión actual si el trabajo sigue teniendo sentido. Esa pequeña negativa es la forma de mantener el control humano ligado a la acción que realmente se ejecuta.
