Una tabla de reglas, dos que aplican al mismo caso y un motor que se queda con la primera del rango más alto. Funciona durante meses. Después la misma situación muestra un motivo, y al rato otro, sin que nadie haya tocado nada.

La fecha de creación iguala, no desempata

El orden más natural para una tabla de reglas es la fecha de creación. Parece un criterio total, y no lo es.

La documentación de PostgreSQL es explícita: now() y CURRENT_TIMESTAMP devuelven el inicio de la transacción en curso, no el momento de cada fila. Una siembra que inserta de una vez todas las reglas de un cliente las deja a todas con el mismo instante, al microsegundo. Y ahí el ORDER BY deja de ordenar: el manual advierte que un orden solo está garantizado si se pide explícitamente, y que las expresiones posteriores ordenan lo que empata en las anteriores. Sin ninguna, las devuelve como le convenga, que suele ser su orden físico en disco.

Primero se nota en el motivo, no en la decisión

Mientras las dos reglas empatadas digan lo mismo, la decisión no cambia: si las dos prohíben, el caso se frena igual. Lo que varía es cuál queda registrada: el motivo que lee quien atiende y la regla de la bitácora.

Un motivo que cambia sin causa es una explicación que nadie puede auditar, y auditar es lo que se le pide a un sistema que decide por obligación legal, como el que programa la Ley 2300 en el código en vez de pedírselo al modelo. Y el día que el empate cae entre dos reglas con efecto distinto, una que prohíbe y otra que pide aprobación, lo que cambia es la decisión.

Repartir las fechas no arregla la siembra siguiente

La corrección de reflejo es separar cada fila unos microsegundos y seguir ordenando por ahí. Arregla lo que ya existe y deja intacta la causa: la próxima siembra vuelve a insertarlas todas en una transacción.

La salida es un orden total que no dependa del reloj: un orden declarado para el catálogo de reglas y el identificador como último criterio. Da lo mismo en cada entorno y en cada consulta.

Y una prohibición no se gana por orden de llegada

Un motor que se queda con la primera regla que aplica implementa lo que el estándar XACML de OASIS llama first-applicable. Cuando hay una prohibición en juego, el algoritmo que corresponde es otro: en deny-overrides, si una sola regla evalúa a Deny, el resultado combinado es Deny, pase lo que pase con las demás.

En código propio: la prohibición se evalúa aparte y por sí sola, con los mismos hechos, y si se cumple manda. Deja de importar quién ganó el desempate.

Lo que se lleva cualquiera con una tabla de reglas: ordena por algo único de verdad, y que un veto no compita por orden de llegada. Esto sale de operar agentes con clientes reales, en casos, y de cómo tratamos datos y accesos, en seguridad.