Ir al contenido

Auditoría

La auditoría es una tabla, organtia.auditoria, con una fila por cada operación que escribe o que lo intenta. No es un diario de conversaciones ni un registro de accesos: es la lista de lo que se hizo, quién lo pidió y con qué resultado.

Se escribe una fila cada vez que una herramienta o una pantalla modifica datos:

Origen Ejemplos de herramienta
Herramientas del CEO recordar_hecho, crear_objetivo, actualizar_objetivo, registrar_decision, solicitar_puesto, solicitar_recurso, guardar_plan_estrategico, proponer_campana, crear_pieza_comunicacion, otorgar_acceso, revocar_acceso, marcar_hecho_obsoleto
Aprobaciones desde la CLI cli:puesto_activo, cli:puesto_rechazado, cli:decision_aprobada, cli:solicitud_aprobado, cli:aprobar_plan, cli:conceder_autonomia, cli:revocar_autonomia, cli:crear_proyecto, cli:crear_tarifa, cli:crear_canal, cli:registrar_suscriptor, cli:directrices_puesto, cli:puesto_cesado
Pantallas de la oficina oficina:puesto_editado, oficina:puesto_desde_perfil, oficina:puesto_ajustado, oficina:recurso_puesto, oficina:recurso_borrado, oficina:memoria_puesto, oficina:memoria_borrada
Configuración ui:instrucciones, cada vez que se guardan o se restauran las instrucciones del CEO

Y una fila con resultado = 'error' cuando una de esas operaciones falla. El fallo se anota en una transacción aparte: si la auditoría no se puede escribir, el error original no se tapa ni se convierte en otro distinto.

  • Las lecturas. Consultar la memoria, listar puestos, abrir una ficha: nada de eso deja fila.
  • Las entradas y salidas de la puerta. El login correcto deja una línea en el registro del proceso (Entrada en la oficina: pedro) y actualiza acceso_usuarios.ultimo_acceso, pero no escribe en la auditoría.
  • Las conversaciones. Los mensajes y las respuestas viven en agno.agno_runs. La auditoría guarda lo que se escribió en las tablas de negocio, no el texto que se habló.
  • Los fallos del proveedor del modelo. Una caída de red o una cuota agotada no genera fila.

Hay tres tablas que se parecen y no son esta:

Tabla Qué guarda
organtia.conexion_ejecuciones Cada llamada a una aplicación externa (conector): acción, URL, código HTTP, duración, error.
organtia.ejecuciones Órdenes de ejecutar pruebas de un proyecto, con su salida y su código de salida.
agno.agno_runs Los turnos de conversación de cada agente, con sus métricas de tokens.
Campo Tipo Qué significa
id bigserial Número de la operación, en orden de inserción.
ocurrido_en timestamptz Momento del registro, con zona horaria. La CLI lo muestra recortado a 19 caracteres (2026-11-06 09:12:33).
actor text Quién la pidió, según lo que el código sabe de él. Por defecto adrian.
herramienta text Qué operación fue. Es el nombre de la herramienta o el prefijo de la pantalla.
entidad text Sobre qué tipo de cosa se actuó: hecho, objetivo, puesto, proyecto, plan, decision, solicitud_recurso, campana, pieza_comunicacion, tarifa, canal, suscriptor, acceso, autonomia, instrucciones, puesto_recurso, puesto_memoria…
entidad_id text El identificador de esa cosa (el id de la fila afectada), en texto.
resultado text ok o error. La base de datos no admite otro valor.
detalle jsonb Lo que hace falta para entender la operación. Ver abajo.
sesion_id text La sesión desde la que se hizo, si la había. Puede ser null.

El valor no es una identidad autenticada en todos los casos; es lo que el código atribuye:

Valor Cuándo aparece
adrian Operaciones de herramientas del CEO. Es el valor por defecto de la tabla y del repositorio.
El nombre del propietario (OWNER_NAME, por defecto Pedro) Ediciones hechas desde la oficina y aprobaciones con nombre: fichas de puesto, recursos, memoria, proyectos, tarifas.
propietario Solo el guardado de las instrucciones del CEO (ui:instrucciones).
Valor Significado
api:<usuario> Petición HTTP hecha por esa persona a través de la puerta (por ejemplo api:pedro).
Un identificador declarado por el cliente El runner de pruebas manda el suyo en la cabecera X-Sesion; se limpia y se recorta a 80 caracteres.
servicio:reloj Llamada autenticada con INTERNAL_SERVICE_TOKEN por el reloj de los crones.
cron:<nombre> Turno lanzado por un cron; el nombre es el del cron.
Sesión de la CLI El identificador de la sesión de conversación abierta en la terminal.
null Operaciones que no nacen de una conversación: guardar instrucciones, editar una ficha.

En una escritura correcta, el objeto trae la prueba de que se releyó lo escrito:

{
"verificado": true,
"campos_verificados": ["estado", "titulo"],
"huella": {"contenido": "9f2c..."}
}
  • verificado es siempre true en las filas ok: el repositorio inserta, relee y solo entonces confirma.
  • campos_verificados lista los campos que se compararon con lo enviado.
  • huella es el sha256 de los campos de texto largos (más de 40 caracteres), recortado a sus 16 primeros caracteres hexadecimales, para poder comprobar que el contenido no cambió sin guardar el texto entero en el registro.

En una fila de error, el detalle es más corto y lleva el mensaje, recortado a 1000 caracteres:

{"error": "ErrorVerificacion: el valor releído de «estado» no coincide con el enviado"}

Algunas operaciones añaden claves propias al detalle. No cuentes con una forma fija: mira las claves antes de leer un campo concreto.

En la CLI, que es la vía prevista:

/auditoria # últimas 15
/auditoria 50 # últimas 50

La tabla que imprime tiene cinco columnas: fecha, actor, herramienta, resultado e id (este último es entidad_id, o — si no lo hay). El máximo que devuelve es 200 filas, aunque pidas más.

En la base de datos, en lectura:

Ventana de terminal
docker compose exec -T db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "
SELECT ocurrido_en, actor, herramienta, entidad, entidad_id, resultado
FROM organtia.auditoria
ORDER BY ocurrido_en DESC
LIMIT 20"'

Consultas que suelen servir:

-- Todo lo que pasó en una sesión concreta
SELECT ocurrido_en, herramienta, resultado, detalle
FROM organtia.auditoria
WHERE sesion_id = 'api:pedro'
ORDER BY ocurrido_en DESC;
-- Solo los fallos de los últimos siete días
SELECT ocurrido_en, actor, herramienta, detalle->>'error' AS error
FROM organtia.auditoria
WHERE resultado = 'error' AND ocurrido_en > now() - interval '7 days'
ORDER BY ocurrido_en DESC;
-- Qué se tocó de un puesto
SELECT ocurrido_en, herramienta, actor
FROM organtia.auditoria
WHERE entidad = 'puesto' AND entidad_id = '12'
ORDER BY ocurrido_en DESC;

No hay vista web de auditoría. El menú de la oficina no tiene una pantalla para esto: se consulta por CLI o por SQL.

El rol de la aplicación (APP_DB_USER, por defecto organtia_app) tiene SELECT e INSERT sobre la tabla, y le están revocados UPDATE, DELETE y TRUNCATE. Es decir: la propia aplicación no puede reescribir ni borrar lo que ya registró, ni siquiera con un error de programación.

No hay purga automática ni retención configurable: la tabla crece y no se vacía sola. En una instalación con mucho uso, es una de las que más pesa; si algún día hay que recortarla, se hace a mano y con una copia de seguridad antes.

Un ejemplo completo, y lo que dice cada trozo:

2026-11-06 09:12:33 | Pedro | oficina:puesto_editado | ok | 12
  • Ocurrió el 6 de noviembre de 2026 a las 09:12:33, hora del servidor.
  • Lo pidió el propietario desde la oficina.
  • Fue una edición de ficha de puesto.
  • Salió bien y se releyó el resultado.
  • El puesto afectado es el 12.

Si esa misma fila dijera error, el detalle contendría el mensaje y sabrías que la ficha no se guardó, aunque la interfaz hubiera mostrado el aviso de otra manera.