Saltar al contenido
GUREN AI Docs

David · Data Architect

Consultas seguras

Las validaciones que pasa cada consulta antes de tocar tus datos: solo lectura, filtros obligatorios, límites, idempotencia y registro.

Actualizado el

Antes de que una consulta toque tus datos pasa por varias capas de control. La primera y más importante no es de software: el usuario con el que David se conecta solo puede leer.

#Las capas de control

CapaQué hace
Usuario de solo lecturaTu base de datos rechaza cualquier escritura, aunque todo lo demás fallara. Lo crea tu equipo con los permisos mínimos.
Solo SELECTSe rechaza cualquier instrucción que modifique datos o estructura: INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE.
Filtros obligatoriosCada consulta lleva el filtro de empresa o de unidad que corresponda al usuario que pregunta.
Reglas de la capa semánticaUniones solo por relaciones definidas y agregación en el grano correcto. Ver duplicación silenciosa.
LímitesTiempo máximo de ejecución y cantidad máxima de filas devueltas, para no afectar a tus sistemas.
RegistroQuién preguntó, cuándo, qué consulta se ejecutó y qué devolvió.

#Validación de la consulta

El diseño de David valida cada consulta como un árbol sintáctico (AST): en vez de buscar palabras en un texto, analiza la estructura de la consulta y puede reescribirla, por ejemplo para agregar el filtro obligatorio. Este es un ejemplo de referencia de esa validación:

guardrail.py (ejemplo de referencia)
import sqlglot
from sqlglot import exp

PROHIBIDAS = (exp.Insert, exp.Update, exp.Delete, exp.Drop, exp.Alter)

def validar(sql: str, columna_tenant: str, tenant_id: str, dialecto: str = "postgres") -> str:
    arbol = sqlglot.parse_one(sql, read=dialecto)

    # 1. Solo lectura: cualquier nodo que modifique datos detiene la ejecución
    if not isinstance(arbol, exp.Select) or any(arbol.find_all(*PROHIBIDAS)):
        raise PermissionError("Solo se permiten consultas de lectura")

    # 2. Uniones con condición explícita (sin productos cartesianos)
    for union in arbol.find_all(exp.Join):
        if not union.args.get("on") and not union.args.get("using"):
            raise ValueError("Unión sin condición: rechazada")

    # 3. Filtro obligatorio de empresa
    arbol = arbol.where(f"{columna_tenant} = '{tenant_id}'")

    return arbol.sql(dialect=dialecto)

#Idempotencia

Cuando una operación registra algo (por ejemplo, marcar una partida como conciliada en un sistema intermedio), lleva una clave de idempotencia. Si la red falla y la solicitud se reintenta, la operación no se duplica.

demo pública · POST /api/david/simulate
POST https://guren.cl/api/david/simulate
Content-Type: application/json
Idempotency-Key: 6f1c2a8e-ejemplo

{
  "system": "SAP_S4HANA",
  "table": "BKPF_ACCDOC",
  "query": "SELECT doc_id, account, debit, credit FROM BKPF_ACCDOC WHERE BUKRS = 'CL01'"
}

Si la consulta intenta modificar datos, la demo responde 422 con el motivo del rechazo. Ver Para desarrolladores.

#Qué ve cada persona

Los permisos se definen por rol: un gerente de sucursal ve su sucursal; finanzas ve la empresa completa. Si tu empresa ya administra roles en su proveedor de identidad (Google Workspace, Microsoft Entra ID u otro), los usamos como base en el diseño.