Estado: ✅ Aceptada Fecha: 2026-05-08 Contexto que la motiva: cierre del trío AI-native (Fase 5) en ROADMAP.md. Implementación entregada en src/bin/gabysql-mcp.rs.

🧭 Contexto

Cuando un agente LLM puede escribir en una base, aparece un problema operacional nuevo: ¿qué hizo, cuándo, y por qué?

El log de errores del server (stderr) y el WAL del motor responden el qué (qué SQL se ejecutó) pero no el por qué (qué pidió el usuario al agente, qué identidad tenía el agente, qué razonamiento llevó a esa escritura). Para auditar workflows con agentes hace falta más metadata, y meter eso en el motor implica:

Es exactamente la misma trampa que ADR-0010 y ADR-0011 ya esquivaron: meter en el motor cosas que pertenecen al gateway.

Restricciones del proyecto:

💡 Decisión

Implementar audit log enriquecido en el gateway MCP, no en el motor. Concretamente:

  1. Nuevo flag --audit-log <ruta> (también GABYSQL_AUDIT_LOG). Si no se pasa, no hay log — overhead cero, semántica idéntica al gateway pre-ADR.
  2. Cuando el log está activo, cada llamada a una tool mutadora (gabysql_execute, gabysql_integrity_check) anexa una línea JSON al archivo (formato JSONL: una entrada por línea, append-only).
  3. La entrada captura:
    • ts_unix: epoch seconds.
    • tool: nombre de la tool MCP.
    • db: archivo .db afectado (o null en single-db).
    • sql: la sentencia SQL ejecutada.
    • reason: el “por qué” semántico que el agente pasa como argumento opcional de la tool. Es el campo central de esta ADR — convierte el log en algo distinto a un log de SQL.
    • client: clientInfo (name + version) capturado en initialize. Identifica qué agente hizo qué.
    • ok: si la llamada al motor tuvo éxito.
    • error: mensaje de error si ok=false.
  4. Nueva tool gabysql_audit_tail(n) que devuelve las últimas N entradas. Permite que el propio agente revise sus acciones (“¿qué he escrito en esta DB en las últimas horas?”). Si el log no está activo, la tool devuelve {"enabled":false,"entries":[]} sin error.
  5. Las lecturas (gabysql_query, gabysql_describe_database, gabysql_list_databases, gabysql_vector_search) no se loguean por defecto. El criterio: el audit log existe para responder “¿qué se escribió y por qué?”, no “¿qué se leyó?”. Logguear lecturas multiplicaría el volumen sin proporción al valor.
  6. El append es best-effort: si escribir al archivo falla (permisos, disco lleno), se loguea a stderr y la tool continúa devolviendo lo que el motor le dio. La alternativa (rechazar la tool si no se puede auditar) es más estricta pero implica que un disco lleno bloquea todas las escrituras, lo cual es peor para la operación.

Resultado: una traza completa de la actividad de los agentes sin que el motor cambie en una sola línea.

🔄 Alternativas consideradas

Tabla de sistema en el motor (ej. __gabysql_audit)

Logging structurado en el gabysql-server (no en el gateway)

Sidecar (proceso aparte) escuchando los tools/call

JSONL append-only en el gateway, opt-in por flag (decisión)

📊 Consecuencias

Positivas

Negativas

Neutras

🚪 Condiciones de salida

Esta ADR queda complementada (no superseded) por una ADR futura cuando ocurra alguno de:

Hasta entonces, JSONL append-only en el gateway es el balance correcto.

🔗 Referencias