# Investigar registros de auditoría de API cuando los registros del agente entran en conflicto

Un proveedor de API afirma que una solicitud cambió datos de producción. El registro del agente indica que nunca llegó tan lejos. Ambas afirmaciones pueden ser ciertas, y tratar cualquiera de los dos registros como un veredicto es la forma en que los equipos convierten una discrepancia contenida en una mala respuesta a un incidente.

Investiga la acción como una cadena de observaciones. Determina quién inició la ejecución, qué intentó hacer el agente, qué cruzó el límite de las credenciales, qué aceptó el proveedor y qué cambió después. Las marcas de tiempo ayudan a ordenar la cadena. Los identificadores de solicitud la conectan. Los resultados y el estado indican si tuvo importancia. Los eventos ausentes también son evidencia, pero solo después de descartar las formas habituales en que desaparecen los registros.

He visto a personas empezar con una hoja de cálculo y ordenar inmediatamente por hora. Es al revés. Una marca de tiempo suele ser el campo de unión más débil de todos. Empieza con identificadores estables y exportaciones inmutables, y usa el tiempo después para comprobar si la secuencia propuesta tiene sentido.

## Conserva los registros antes de que alguien actualice un panel

Captura las pruebas originales antes de filtrar, reintentar, revocar el acceso o pedir al equipo de soporte del proveedor que investigue. Los paneles interactivos cambian, se ejecutan trabajos de retención y un reintento puede crear el segundo evento que confunde al primero.

Abre una carpeta del caso con un identificador de caso y recopila exportaciones sin procesar, no solo capturas de pantalla. Incluye el registro de sesión del agente, los registros individuales de acciones, la exportación de auditoría del proveedor, los registros de la aplicación del sistema afectado y cualquier registro de salida disponible para el equipo. Registra la hora de recopilación en UTC, quién la hizo, la cuenta o el rol utilizado y el filtro empleado para generar cada exportación.

Calcula el hash de cada archivo después de recopilarlo. Basta con un comando de shell si tu sistema operativo ofrece una utilidad estándar SHA-256:

```
$ shasum -a 256 provider-events.json agent-activity.json
81b5777b8416320fe26cb8a8dddb6a9e736fab4f5e7aa5812bf6afeffc5f4e82  provider-events.json
a1e98c01992b51104fbc8c5fcbaa78e65db31f1edb3e546f4c14d0e6d3673ba  agent-activity.json
```

Pon los hashes en una nota sencilla del caso. El hash no demuestra que la exportación del proveedor fuera completa. Demuestra que tu copia de trabajo no ha cambiado silenciosamente desde la recopilación. Son afirmaciones distintas, y los informes de incidentes suelen mezclarlas.

No conviertas el JSON en una hoja de cálculo como primera copia. La normalización puede descartar campos duplicados, el orden de las matrices, los segundos fraccionarios, los valores vacíos y el cuerpo exacto de la solicitud que después explica una discrepancia. Conserva una exportación intacta y crea un archivo de trabajo analizado por separado.

Si la discrepancia pudiera estar relacionada con una filtración de credenciales o un uso no autorizado, contiene el acceso de una forma que conserve la secuencia. Revoca una sesión activa del agente o bloquea la ruta de acción si puedes. Evita rotar la credencial del proveedor antes de recopilar sus registros de auditoría recientes, salvo que un abuso activo exija una rotación inmediata. A veces rotar la credencial es necesario, pero puede borrar la única vía restante para atribuir la acción.

## Un identificador de solicitud tiene prioridad sobre una marca de tiempo

Relaciona los registros mediante identificadores que sobrevivan a los límites: un identificador de solicitud del proveedor, un identificador de correlación proporcionado por el cliente, una clave de idempotencia, un identificador de objeto devuelto por la escritura y un identificador de traza si el proveedor documenta uno. Conserva todos los identificadores, porque el proveedor puede mostrar identificadores distintos en cabeceras, eventos de auditoría, exportaciones de soporte y cuerpos de error.

El mejor caso es sencillo. El registro de acción indica que el agente llamó a `POST /v1/invoices`; las cabeceras de respuesta contienen `x-request-id: req_72M...`; la exportación del proveedor incluye `req_72M...`; y la factura creada tiene `inv_4P...`. Ahora tienes una unión entre intención, entrega, procesamiento del proveedor y estado persistente.

Los casos difíciles son más habituales. Un proveedor puede asignar un identificador de solicitud solo después de analizar la solicitud. Un fallo de TLS no tendrá identificador de solicitud del proveedor porque la solicitud nunca llegó a la aplicación. Una pasarela puede generar un identificador y el servicio posterior otro. Una API asíncrona puede devolver un identificador de trabajo y escribir el objeto solicitado varios minutos después. Registra qué límite emitió cada identificador en lugar de reducirlos todos a una única columna `request_id`.

Usa una tabla de conciliación que haga visible la incertidumbre:

| Campo | Registro de acción local | Registro del proveedor | Sistema afectado |
| --- | --- | --- | --- |
| Identificador de correlación del cliente | `run-18-call-42` | `run-18-call-42` | ausente |
| Identificador de solicitud del proveedor | `req_72M...` en la respuesta | `req_72M...` | ausente |
| Método y ruta | `POST /v1/invoices` | `POST /v1/invoices` | factura creada |
| Resultado | `504 timeout` | `202 accepted` | trabajo `job_91...` completado |
| Hora del evento | `10:04:03.219Z` | `10:04:03Z` | `10:04:11.802Z` |

Esta tabla revela un fallo conocido: el cliente agotó el tiempo de espera, pero el proveedor aceptó la escritura y la procesó después de que el cliente abandonara. Sería incorrecto llamar «fallida» a la acción por el resultado del cliente. También sería incorrecto llamar al registro del proveedor «prueba de que el agente tenía intención de hacerlo». La evidencia indica que el agente envió una solicitud que el proveedor aceptó y que el cliente no recibió una respuesta a tiempo.

Si el proveedor permite una clave de idempotencia para las escrituras, úsala. El borrador de Internet de la IETF sobre Idempotency-Key describe bien el objetivo práctico: un cliente reintenta una operación HTTP insegura sin crear accidentalmente el mismo efecto dos veces. El comportamiento específico varía según el proveedor, así que lee su documentación sobre retención y reglas de coincidencia. No supongas que basta con que coincida el endpoint.

Para las API que aceptan cabeceras personalizadas, genera un identificador de correlación antes de la llamada y envíalo en una cabecera documentada, como `X-Client-Request-ID`. Guárdalo con el evento local. Nunca pongas secretos, prompts, datos de usuarios ni tokens sin procesar en este identificador. Un valor seguro no tiene significado fuera del caso, por ejemplo `case-2025-041-run7-call18`.

## El tiempo puede refutar una historia, pero rara vez la demuestra

Usa las marcas de tiempo para delimitar eventos y detectar órdenes imposibles. No las uses como campo principal de identidad salvo que ninguna fuente tenga un identificador mejor.

RFC 3339 define un perfil común de marcas de tiempo de Internet y recomienda la forma UTC en mayúsculas terminada en `Z`, como `2025-03-08T10:04:03.219Z`. Conserva la cadena original incluso después de analizarla. La diferencia entre `10:04:03Z` y `10:04:03.219Z` importa cuando una fuente redondea a segundos y otra informa de milisegundos.

Crea cuatro campos de tiempo para cada evento relevante:

- la marca de tiempo de la fuente exactamente como se exportó
- la marca de tiempo normalizada en UTC
- el tipo de evento, como enviado, aceptado, completado o registrado
- el propietario del reloj, como Mac local, extremo del proveedor, trabajador del proveedor o base de datos

Una marca de tiempo del extremo del proveedor puede preceder a una marca local de «respuesta recibida» sin que exista una contradicción. La marca de finalización de un trabajador del proveedor puede ser posterior a la salida del proceso del agente. Un reloj local desviado puede hacer que una acción parezca anterior al inicio de la sesión. Son mecanismos normales, no pruebas de manipulación.

Construye un intervalo alrededor de un ancla conocida, normalmente un identificador de solicitud o el inicio de una sesión. Empieza con un intervalo suficientemente estrecho para evitar uniones accidentales. Amplíalo solo cuando puedas indicar el motivo: el proveedor solo registra segundos, la operación es asíncrona o mediste la desviación del reloj frente a una referencia fiable. Escribe el intervalo elegido en la nota del caso. «Buscamos aproximadamente alrededor de esa hora» no es un método.

Ten cuidado con la hora de ingestión del registro. Muchos sistemas muestran `event_time` y `created_at`. El primero describe cuándo ocurrió el evento según el sistema que lo emitió. El segundo puede describir cuándo un agregador lo recibió o indexó. Una llegada tardía no significa una ejecución tardía. Si un evento aparece después de que comenzara un incidente, inspecciona ambos campos antes de construir una historia.

Una prueba de ordenación útil solo pregunta si la historia propuesta es posible. Un evento del proveedor a las 10:04:03, junto con un envío local a las 10:04:03.219, puede ser posible si los relojes difieren o el proveedor redondea hacia abajo. Una finalización declarada a las 10:02 cuando el proveedor dice que aceptó el trabajo a las 10:04 no es posible, salvo que hayas mezclado dos eventos o entendido mal el campo.

## Separa intentado, entregado, aceptado y completado

Los equipos suelen comprimir cuatro estados distintos en la palabra «llamó». Ese atajo provoca la mayoría de las disputas sobre registros.

Un agente puede intentar una acción al construir una solicitud. Un componente local puede entregar bytes a un endpoint remoto. El proveedor puede aceptar la solicitud. Un trabajador posterior puede completar el efecto. Cada etapa tiene un registro y un modo de fallo diferentes.

La especificación HTTP Semantics, RFC 9110, deja claro que el código de estado es una afirmación sobre la respuesta del servidor, no un historial completo de la experiencia del cliente. Un `202 Accepted` indica explícitamente que el procesamiento se aceptó, pero aún no se completó. Un `204 No Content` indica que el servidor completó la solicitud correctamente, pero por sí solo no explica todos los efectos posteriores. Una interrupción de red puede no producir ninguna respuesta HTTP aunque el servidor haya procesado la solicitud.

Clasifica cada evento en disputa con un estado como estos:

- **Solo intentado**: existe un registro de acción local, pero ninguna evidencia demuestra que se entregara por la red.
- **Entregado, resultado desconocido**: la solicitud salió del límite local, pero el cliente no recibió una respuesta fiable y el proveedor aún no tiene un registro consultable.
- **Aceptado, efecto pendiente**: el proveedor devolvió una aceptación o una referencia de trabajo, pero todavía no hay un estado completado.
- **Completado**: un resultado del proveedor y un cambio de estado observado coinciden.
- **Contradicho**: las fuentes hacen afirmaciones que no pueden ser ciertas a la vez después de tener en cuenta el significado de sus campos.

«Resultado desconocido» es una conclusión legítima. No lo rebautices como fallo solo porque el agente recibió una excepción. En una operación de escritura, esa excepción debería detener los reintentos automáticos, salvo que un mecanismo de idempotencia o una comprobación posterior haga seguro el reintento.

El error inverso es igual de grave: una respuesta `200` no significa que se produjera el resultado de negocio previsto. Un endpoint puede devolver éxito para una solicitud sintácticamente válida mientras una validación posterior, un trabajo asíncrono o una dependencia posterior rechaza el cambio previsto. Inspecciona el objeto devuelto, el estado del trabajo o el evento del sistema de destino que el contrato de la API define como señal de finalización.

## Los eventos ausentes necesitan una explicación delimitada

La ausencia de un registro puede significar que la solicitud nunca ocurrió, pero también que consultaste el servicio equivocado, usaste el ámbito de cuenta incorrecto, buscaste en el nivel de retención equivocado o esperabas un registro que el proveedor nunca promete emitir.

Analiza los eventos ausentes en un orden fijo. Primero, confirma la cuenta, el proyecto, la región, el entorno y el producto de API exactos. Los proveedores suelen separar las vistas de auditoría por uno o varios de estos campos. Después, busca con todos los identificadores, y luego con un intervalo de tiempo y un endpoint documentados. Comprueba si el proveedor registra solicitudes aceptadas, rechazadas, llamadas del plano de datos, llamadas del plano de control o solo acciones administrativas. Revisa después la retención y el retraso de las exportaciones. Por último, pregunta si un proxy, un SDK o una cola asíncrona crea un evento del proveedor diferente del que esperabas.

Conviene recordar un fallo concreto. Un agente envía `POST /exports` y recibe una interrupción de conexión. El equipo busca el identificador local del cliente en los registros de auditoría del proveedor y no encuentra nada. Reintenta y después recibe dos notificaciones de finalización de exportación.

La primera solicitud llegó a un endpoint regional de ingestión. La pantalla de auditoría que consultaron solo mostraba eventos del plano de control. El proveedor registró el trabajo con un identificador de exportación generado, no con la cabecera del cliente, y el servicio de trabajos lo completó después de la interrupción. Nada de esta secuencia requería actividad maliciosa. El duplicado se produjo al reintentar una escritura antes de comprobar si existía una clave de idempotencia, un endpoint de consulta del trabajo o un marcador de nivel de negocio.

Este fallo también muestra por qué la ausencia debe expresarse con cuidado. Di «La exportación que recopilamos no contiene ningún evento del plano de datos que coincida con este intervalo», en lugar de «El proveedor no tiene ningún registro». La primera afirmación identifica la evidencia y su límite. La segunda hace una afirmación que a menudo no puedes respaldar.

Si se esperaba un registro pero está ausente, conserva los parámetros de consulta y captura la documentación del proveedor que indique la cobertura esperada de eventos. Una solicitud de soporte sin el identificador exacto de solicitud, el ámbito de cuenta, el intervalo UTC, el endpoint y los hashes de las pruebas hará perder días.

## Los resultados necesitan una inspección que vaya más allá de los códigos de estado

Compara la intención declarada de la solicitud con el contenido de la respuesta y el efecto observable. Los códigos de estado informan sobre un intercambio de protocolo. No indican si la solicitud tenía el ámbito correcto, si el proveedor aplicó un valor predeterminado o si el agente envió un identificador obsoleto.

Para cada acción, captura estos campos cuando la API los exponga: método HTTP, ruta normalizada, identificador de solicitud, clave de idempotencia, identidad del actor o de la credencial, código de estado, hash del cuerpo de respuesta, identificador del objeto devuelto y cualquier identificador de trabajo asíncrono. Redacta las credenciales y los datos sensibles del contenido antes de compartirlos de forma amplia, pero conserva un original protegido si la política lo permite.

Un hash del cuerpo de respuesta ayuda a distinguir dos registros `200` superficialmente idénticos. Calcúlalo sobre los bytes sin procesar de la respuesta antes de embellecerla. Si la API devuelve JSON y el orden de los campos cambia entre capas, conserva tanto los bytes originales como una copia analizada y canónica. No afirmes que códigos de estado iguales significan respuestas iguales.

Después, consulta el recurso que debería existir o haber cambiado. En una operación de creación, recupera el identificador del objeto devuelto y compara su creador, hora de creación y atributos. En una actualización, recupera una versión, revisión o entrada de auditoría si el servicio la ofrece. En un borrado, comprueba que el objeto esté ausente y que un registro de auditoría del proveedor atribuya el borrado a la misma credencial.

Aquí es donde las credenciales amplias perjudican las investigaciones. Si muchas herramientas comparten un token de API, el proveedor a menudo puede decirte que el token actuó, pero no qué proceso local o qué persona inició la acción. Trata la identidad de la credencial como un marcador de límite, no como la identidad del actor.

## Un registro de pasarela solo sirve si registra el límite

Una pasarela de acciones ofrece un punto de observación claro entre un agente y la operación con credenciales. Debe registrar el proceso o la ejecución que invoca, el estado de autorización aprobado, la operación solicitada, el resultado devuelto al agente y suficientes identificadores para relacionar los registros del proveedor. No debe entregar la credencial al agente y luego llamar a la telemetría local resultante un registro de auditoría.

Sallyport mantiene las credenciales de API y SSH en su bóveda cifrada, ejecuta la acción por sí mismo y devuelve el resultado al agente, no el secreto. Sus diarios Sessions y Activity se proyectan desde un registro de auditoría cifrado, encadenado mediante hashes y ciego a escritura, lo que ofrece al investigador registros tanto del nivel de ejecución como del nivel de llamada. `sp audit verify` puede verificar esa cadena sin conexión sobre el texto cifrado, sin necesitar la clave de la bóveda.

Ese diseño cubre una carencia concreta. Un registro del proveedor puede identificar una credencial y una solicitud de API. No indica qué proceso del agente recibió permiso para usar esa credencial ni demuestra que el agente nunca vio el secreto. Un registro de auditoría local solo puede responder en parte a esa pregunta cuando el límite de las credenciales está dentro del componente que genera el registro.

No exageres lo que demuestra un registro de pasarela. No puede informar de una solicitud que la haya evitado ni convertir una API ambigua del proveedor en una API precisa. Te ofrece un lugar más sólido para comparar pruebas y un punto desde el que revocar una ejecución conocida del agente mientras continúa la investigación.

## Redacta el hallazgo como afirmaciones con pruebas y límites

Un buen hallazgo permite que otro ingeniero reproduzca tu razonamiento sin heredar tus suposiciones. Escribe afirmaciones separadas sobre la invocación, el permiso, la entrega de la solicitud, el procesamiento del proveedor y el efecto observado. Adjunta los identificadores, las marcas de tiempo, los archivos de origen y el significado de los campos que respaldan cada afirmación.

Usa un lenguaje que corresponda al nivel de confianza. «El diario de acciones registra que el proceso X solicitó `POST /v1/invoices` a esta hora». «La exportación del proveedor contiene una solicitud con el mismo identificador de solicitud del proveedor». «La factura existe y sus atributos coinciden con la respuesta registrada». Son afirmaciones comprobables. «El agente causó definitivamente la factura» solo puede estar justificado si las relaciones y el límite de las credenciales lo respaldan.

Cuando los registros no coincidan, deja la discrepancia visible en el informe final. No promedies las marcas de tiempo ni descartes la fuente incómoda. Expón la explicación más probable, las alternativas que descartaste y las pruebas que aún faltan. Si no puedes establecer si una escritura se completó, regístrala como desconocida y corrige la ruta de la API antes de permitir reintentos automáticos.

El cambio práctico después de un incidente suele ser pequeño y poco llamativo: exigir un identificador de correlación, conservar la clase correcta de eventos del proveedor, preservar las marcas de tiempo UTC con fracciones y usar idempotencia para las escrituras. Estos controles convierten la próxima discrepancia de una discusión forense en una conciliación breve.
