En esta página
Propiedad de arquitectura
COADF P-4 se publica íntegro. Al recibir un documento se crea un identificador que viaja con cada paso de procesamiento, cada evaluación de confianza, cada decisión humana y la salida publicada. La traza es de solo inserción, una consulta reconstruye la cadena y la cadena se exporta como JSON para alguien que no construyó el sistema. El esquema publicado de la traza de auditoría nombra los campos: trace_id, timestamp, event_type, actor, input, output, decision, source_hash e immutable.
Dos propiedades la sostienen. Continuidad: cada paso lleva la misma identidad, a través de cada frontera que cruza. Persistencia: el registro de cada paso sobrevive, y nadie puede editarlo después para que la respuesta parezca mejor.
Por qué importa
La pregunta que responde P-4 llega tarde y desde fuera. Meses después de la publicación alguien pregunta de dónde salió un valor, qué lo leyó y quién lo miró. La respuesta tiene que poder reconstruirla una persona que no construyó el sistema, a partir de registros que nadie pudo reescribir entretanto.
Una traza distribuida no es una traza de auditoría de negocio
Es la distinción que más implementaciones difuminan, porque las herramientas se parecen: ambas tienen un identificador, ambas cruzan servicios, ambas muestran pasos en secuencia. Responden a preguntas distintas y prometen cosas distintas.
| Aspecto | Traza distribuida | Traza de auditoría |
|---|---|---|
| Finalidad | Explicar una ejecución: latencia, errores, estructura de llamadas | Establecer qué le ocurrió a una transacción y quién actuó |
| Identidad | Una traza por petición o tarea, transportada en la cabecera W3C traceparent | Un trace_id por transacción de negocio, creado en la recepción |
| Duración | La de la petición; conservación medida en días | Tanto como los registros que justifica |
| Muestreo | El muestreo es normal y está permitido | Sin muestreo: una entrada ausente es un defecto |
| Mutabilidad | Las pipelines filtran, descartan y reescriben por configuración | Solo inserción: una corrección es una entrada nueva |
| Contenido | Spans, tiempos, atributos elegidos para el diagnóstico | Eventos de negocio: entrada, salida, decisión, actor |
La especificación W3C Trace Context define la cabecera traceparent, y los SDK de OpenTelemetry propagan tracecontext y baggage por defecto. Esa identidad es por ejecución. En OpenTelemetry, una traza que no se muestrea no se exporta, y el procesador de filtrado del Collector descarta la telemetría que cumple una condición. Ambos son comportamientos correctos para el diagnóstico y descalificantes para la evidencia.
Las dos se correlacionan: se registra el trace_id de auditoría en cada span como atributo, y el identificador de la traza distribuida en los logs. Ninguna debe convertirse en la otra. Una sola transacción de negocio abarca habitualmente varias trazas distribuidas: una petición de recepción, una tarea de procesamiento asíncrona, una decisión tomada días después, la salida.
La correlación copia el identificador de auditoría en la telemetría, y la telemetría cruza otras fronteras: exportadores, proveedores, reglas de acceso y plazos de conservación que suelen ser más laxos que los del almacén de auditoría. Hay que clasificar el identificador antes de que salga: una referencia opaca, nunca datos personales ni un secreto. Donde el identificador de auditoría tenga significado propio, la correlación se hace mediante una referencia separada y no sensible.
Arquitectura de ejemplo · No normativa
Trazas distribuidas y una sola traza de auditoría
- Correlación mediante el trace_id de auditoría
Descripción textual. Carril superior, trazas distribuidas: cuatro trazas separadas, una para una solicitud de recepción, otra para un trabajo de procesamiento, otra para una decisión tomada días después y otra para una salida. No están conectadas entre sí. Carril inferior, la traza de auditoría: cuatro entradas ilustrativas, recepción, procesamiento, decisión y salida, todas con el mismo trace_id, persistidas por separado de la telemetría y no muestreadas. Una nota en el carril dice que los eventos son ilustrativos, no un flujo de trabajo exigido por COADF. Una línea discontinua une cada traza con la entrada que le corresponde, rotulada como atributo de span que lleva el trace_id de auditoría.
Estrategias de implementación válidas
Crear una sola vez, en la recepción
El identificador se crea donde entra la transacción y en ningún otro sitio. Todo componente posterior lo exige y ninguno lo genera. Un identificador ausente es un error, nunca un motivo para crear uno nuevo, y un valor por defecto en un modelo de mensaje es la forma más común de romper esa regla sin que nadie lo note.
Transportarlo de forma explícita a través de cada frontera
- Dentro del proceso: un argumento de función o el contexto de la petición.
- Entre servicios: un campo del contrato de la petición o del mensaje, donde un esquema puede exigirlo.
- En el trabajo programado: un argumento de la tarea.
- En reposo: una columna en cada fila almacenada.
Las bibliotecas de propagación transportan por su cuenta el contexto de ejecución. La identidad de auditoría forma parte del contrato propio y pertenece a la carga útil.
Persistir, solo inserción
Hay varias realizaciones válidas, y se combinan:
- Persistencia de solo inserción. El repositorio ofrece añadir y leer, y nada más.
- Privilegios de base de datos. El rol de la aplicación tiene
INSERTySELECTsobre la traza, y noUPDATE,DELETEniTRUNCATE. - Triggers que rechazan reescrituras, como defensa en profundidad para las sesiones que sí tienen derechos más amplios, con un trigger a nivel de sentencia para
TRUNCATE, porque en PostgreSQLTRUNCATEno dispara los triggersON DELETE. - Un modelo de eventos inmutable, en el que una corrección es un evento nuevo que sustituye a uno anterior.
- Una convención de sustitución. El valor vigente de un atributo es su última entrada; el historial son todas.
El event sourcing es una manera de obtener un historial de solo inserción, y no la única; véase la nota técnica.
Escribir la entrada con el cambio
La entrada de auditoría se registra en la misma transacción de base de datos que el cambio de estado que describe, o mediante un outbox transaccional, de modo que ninguno de los dos pueda existir sin el otro.
Hacer idempotentes los reintentos
La hora del paso se fija cuando el paso se ejecuta, y en un reintento se reenvía la misma entrada. Las entradas llevan una clave única natural para que una escritura repetida se absorba en lugar de duplicarse. En PostgreSQL, ON CONFLICT DO NOTHING omite una fila que entra en conflicto con una restricción o un índice únicos en lugar de lanzar un error, y justo por eso no puede ser toda la respuesta: un conflicto no siempre es un reintento. La misma clave con otro contenido es un segundo escritor o un error. Hay que comparar la entrada almacenada antes de llamar reintento a la escritura, y fallar de forma visible cuando difiere.
Reconstruir con una consulta, exportar como JSON
Si la reconstrucción necesita a alguien que conozca el sistema, P-4 no se cumple. Se ordena por marca de tiempo, y se dice lo que ese orden no resuelve: las entradas con la misma marca de tiempo no quedan ordenadas por ninguno de los campos publicados.
Lo que un trigger no da
Los privilegios y los triggers son controles dentro de la propia frontera de confianza de la base de datos. En PostgreSQL, un superusuario omite todas las comprobaciones de permisos, y el propietario de la tabla puede desactivar sus triggers. Una tabla de solo inserción es, por tanto, un control fuerte frente a la aplicación y uno débil frente a sus administradores. Hacer detectable la manipulación por parte de personas internas con privilegios necesita un control fuera de la base de datos, y es una propiedad distinta de la persistencia de solo inserción. Ninguna de las dos es, por sí sola, una afirmación jurídica sobre la evidencia.
Modos de fallo
El identificador se regenera a mitad de camino
Un consumidor o un worker crea un identificador nuevo cuando falta el campo, a menudo mediante un valor por defecto en un modelo de mensaje. El resultado son dos mitades de una transacción y ninguna consulta que las una.
La traza existe solo en los logs
Los logs rotan, se muestrean, no tienen estructura y los escribe código que los trata como diagnóstico. Una cadena que solo puede reconstruirse a partir de logs no puede reconstruirse de forma fiable.
Una frontera asíncrona pierde el contexto
Los pools de hilos, los executors y los brokers no transportan nada salvo que algo lo copie. En Python,
asyncio.to_threadestá documentado como propagador del contexto actual;run_in_executorno documenta nada parecido, y la propiato_threadde CPython copia el contexto de forma explícita antes de llamarlo. En Spring, un executor necesita un decorador de tareas que propague el contexto.Las filas almacenadas no pueden relacionarse con su evidencia
No hay
source_hash, o hay un hash de algo distinto de los bytes que realmente se leyeron, así que la misma entrada nunca puede volver a reconocerse.Los reintentos producen entradas ambiguas
Una marca de tiempo tomada en el momento de la inserción convierte cada reintento en una entrada nueva y distinta, y la traza dice entonces que un paso ocurrió dos veces.
Un conflicto descartado como reintento
Una clave única y
ON CONFLICT DO NOTHING, y nada más: una segunda entrada con la misma clave y otra decisión desaparece sin error, y la traza conserva la que llegó primero.Una corrección sobrescribe
Un
UPDATEdestruye lo que se creía antes, y la traza ya no puede explicar por qué una salida anterior decía lo que decía.Una frontera de servicio inicia una traza nueva
Un gateway elimina una cabecera que no conoce, una biblioteca cliente no propaga, una tarea por lotes empieza desde cero. Cada caso es invisible hasta que alguien intenta reconstruir.
El backend de tracing usado como traza de auditoría
Su muestreo, su conservación y su pipeline mutable son adecuados para el diagnóstico e inadecuados para la evidencia.
Relojes de pared usados para ordenar pasos entre hosts
Las marcas de tiempo de máquinas distintas ordenan las entradas solo en la medida en que sus relojes coinciden. Dentro de una transacción, un único escritor, o un orden asignado por uno solo, es más fiable que la hora de pared de varios hosts.
Verificación
Prueba de integración
Pasa cuando: Desde una salida publicada hasta sus fuentes: una consulta devuelve todas las entradas, todas con el mismo
trace_id, con elsource_hashde cada documento leído.Prueba de dientes: Se quita el identificador de un salto asíncrono en una rama de trabajo. La prueba de reconstrucción tiene que fallar, no devolver una cadena más corta que parece completa.
Prueba de integración
Pasa cuando: Una corrección se añade: después existen ambas entradas, y la posterior sustituye a la anterior.
Prueba de dientes: Se reemplaza la inserción por una actualización: la prueba falla.
Prueba de integración
Pasa cuando: Un reintento exacto se absorbe, y la misma clave con otro contenido se rechaza, no se absorbe.
Prueba de dientes: Se toma cada conflicto como reintento sin comparar: falla la prueba que escribe otro contenido bajo la misma clave.
Prueba de integración
Pasa cuando: Contra la base de datos real, el rol de la aplicación no puede actualizar, borrar ni truncar la traza.
Prueba de dientes: Se concede
UPDATEal rol en una base de datos de trabajo: la prueba falla.Prueba de contrato
Pasa cuando: Todo esquema de mensaje que cruza una frontera asíncrona exige el identificador de auditoría.
Prueba de dientes: Se hace opcional el campo: la prueba de contrato falla.
Prueba de extremo a extremo
Pasa cuando: Una petición que se ramifica en una tarea y un mensaje conserva una sola identidad de auditoría, comprobada en la traza persistida y no en los logs.
Evidencia manual
Pasa cuando: Se exporta una cadena como JSON y una persona que no construyó el sistema reconstruye solo a partir de ella la historia de la salida.
Realizaciones alternativas
- Event sourcing. El almacén de eventos es el historial y el estado se deriva de él. Propiedades de auditoría sólidas, y un compromiso de arquitectura grande.
- Captura de cambios de datos. Cambios de filas leídos del log de la base de datos. Útil para la replicación; registra qué cambió, no quién decidió ni por qué, así que no sustituye las entradas de auditoría de negocio.
- Almacenamiento de objetos de escritura única para las cadenas exportadas, donde la conservación tiene que durar más que la base de datos.
- Almacenes de tipo libro mayor o que hacen detectable la manipulación, donde la manipulación por personas internas forma parte del modelo de amenazas.
Limitaciones
- El patrón no decide qué pasos son relevantes para una regulación concreta o un producto concreto.
- La persistencia de solo inserción no equivale a detectabilidad de manipulaciones, y ninguna de las dos es una conclusión jurídica sobre la evidencia.
- No define plazos de conservación.
- Depende de que cada componente respete el contrato. Un componente que no lo hace lo detecta la prueba de reconstrucción; no lo impide.
Fuentes
- W3C: Trace Context, the traceparent header · especificación · W3C Recommendation, Level 1 · Comprobado el 2026-09-11
- OpenTelemetry: SDK environment variables: OTEL_PROPAGATORS · especificación · Specification 1.60.0 · Comprobado el 2026-09-11
- OpenTelemetry: Sampling · documentación oficial · Comprobado el 2026-09-11
- OpenTelemetry: Transforming telemetry · documentación oficial · Comprobado el 2026-09-11
- PostgreSQL Global Development Group: TRUNCATE · documentación oficial · PostgreSQL 18 · Comprobado el 2026-09-11
- PostgreSQL Global Development Group: INSERT: ON CONFLICT · documentación oficial · PostgreSQL 18 · Comprobado el 2026-09-11
- PostgreSQL Global Development Group: Role attributes · documentación oficial · PostgreSQL 18 · Comprobado el 2026-09-11
- PostgreSQL Global Development Group: ALTER TABLE: DISABLE TRIGGER · documentación oficial · PostgreSQL 18 · Comprobado el 2026-09-11
- Python Software Foundation: asyncio: Task and to_thread · documentación del lenguaje · Python 3.14 · Comprobado el 2026-09-11
- CPython: Lib/asyncio/threads.py · repositorio del proyecto · CPython 3.14.7 · Comprobado el 2026-09-11
- Spring Framework: Observability support: context propagation · documentación oficial · Spring Framework 7.0 · Comprobado el 2026-09-11
