Recetas de medidas#
Esta página reúne dos patrones frecuentes al escribir medidas en Oracle que resuelven problemas del álgebra y de la mutación sin necesidad de operadores nuevos:
- Pares no orientados sin contarlos dos veces: por qué usar
<en lugar de!=al unir una relación consigo misma, para no inflar el conteo de testigos y permitir que la mutación compruebe el umbral. - Contar distintos por grupo con doble
agrupar: cómo calcular un recuento de valores distintos (DISTINCT) agrupados por clave, colapsando filas repetidas sin un agregador especial.
No hay ninguna salida inventada: la suite del repositorio arma el proyecto de esta página desde una carpeta vacía, corre cada comando y comprueba lo que ves acá.
El proyecto de ejemplo#
Arrancamos desde una carpeta limpia e inicializamos el proyecto. Desactivamos el catálogo base en oracle.json para concentrarnos exclusivamente en las dos reglas de dominio:
oracle init .
Proyecto Oracle inicializado en .:
· catalogos/
· corpus/
· diferencial/
· relaciones/
· oracle.json
Próximos pasos:
1. Creá un caso: oracle caso <grupo/id>
2. Creá una medida: oracle nueva <dominio.nombre>
3. Verificá todo: oracle test
{
"esquema": "oracle.proyecto/v1",
"catalogo_base": false,
"perfiles": []
}
1. Pares no orientados: la clave ordenable <#
El problema: != infla el conteo y deja vivos mutantes#
Al comparar elementos de una misma relación entre sí para detectar colisiones o incompatibilidades —por ejemplo, dos servicios que reclaman el mismo puerto de red, dos barcos superpuestos o dos reuniones encimadas en la misma sala—, la intuición suele llevar a una auto-unión con desigualdad:
de servicio a
unir servicio b
donde a.nombre != b.nombre y a.puerto == b.puerto
El problema no se nota al juzgar, pero rompe la mutación de medidas:
- Si el servicio
apiy el serviciowebcomparten el puerto 8080, la unión produce dos filas de testigos:(api, web)y(web, api). - El valor mínimo de una colisión es 2.
- Cuando
oracle testataca la medida con el mutadoraflojar_umbral(sustituyendo el umbral<= 0por<= 1), el defecto sigue teniendo valor 2, que es> 1: el mutante sigue dando ROJO. - El mutante se comporta igual que la medida original sobre el caso de prueba, por lo que sobrevive y la verificación falla.
La solución: imponer orden con <#
Si la clave es ordenable (un identificador alfanumérico, un timestamp o un número de secuencia), reemplazar != por < impone una orientación estricta:
de servicio a
unir servicio b
donde a.nombre < b.nombre y a.puerto == b.puerto
- El par no orientado
{api, web}se evalúa una sola vez:"api" < "web"es verdadero, mientras que"web" < "api"es falso. - El valor de la colisión es exactamente 1.
- Ante el mutador
aflojar_umbral(<= 1), el valor 1 ahora pasa en verde (1 <= 1). - La inversión de veredicto sobre el caso rojo mata al mutante.
La medida copiable#
medida red.puerto_exclusivo:
de servicio a
unir servicio b
donde a.nombre < b.nombre y a.puerto == b.puerto
resumen contar(1)
umbral <= 0 segun contrato porque "dos servicios no pueden compartir el mismo puerto de escucha"
ambito universal
alcance "compara servicios de a pares por puerto. NO ve si el puerto está fuera de rango"
Los casos: rojo y verde#
Un caso rojo que presenta dos servicios en colisión y espera que la regla lo atrape:
caso 001-puerto-duplicado:
fecha: "2026-09-26"
origen:
repo: "ejemplo/recetas"
commit: "sin-commit"
procedencia: construida
titulo: "dos servicios escuchan en el mismo puerto"
etiqueta: falso_verde
sintoma:
Tanto web como api intentan enlazar el puerto 8080.
como_se_detecto: persona
medida: red.puerto_exclusivo
evidencia:
servicio: nombre, puerto
"api", 8080
"web", 8080
leccion:
Un solo par en colision genera exactamente un testigo gracias a <.
Y un caso verde donde cada servicio tiene su propio puerto:
caso 002-puertos-distintos:
fecha: "2026-09-26"
origen:
repo: "ejemplo/recetas"
commit: "sin-commit"
procedencia: construida
titulo: "servicios con puertos independientes"
etiqueta: verde_correcto
sintoma:
Cada servicio escucha en su propio puerto sin conflicto.
como_se_detecto: persona
medida: red.puerto_exclusivo
evidencia:
servicio: nombre, puerto
"api", 8080
"web", 9090
leccion:
Servicios bien configurados no disparan la infraccion.
2. Contar distintos por grupo: doble agrupar#
El problema: no existe contar_distintos#
El álgebra de Oracle no incluye un agregado contar_distintos ni una función escalar distinto(). Esta omisión es deliberada: el núcleo no incorpora operadores hasta que al menos dos medidas independientes lo requieran como primitiva irreducible.
Además, en evidencia real (métricas de réplicas, trazas de auditoría o eventos de despliegue), una relación suele traer filas repetidas con la misma clave. Un simple contar(1) contaría filas totales, inflando la cuenta con eventos duplicados en lugar de medir entidades distintas.
La solución: encadenar dos pasos agrupar#
El álgebra relacional de Oracle permite encadenar pasos agrupar. Cada uno cumple un rol:
- Primer
agrupar(deduplicar): Se agrupa por la clave del grupo (d.nodo) y por la clave del elemento (d.servicio), sin declarar agregados. En Oracle, unagrupar:sin agregados colapsa todas las filas idénticas en una sola por combinación de claves (equivalente a unDISTINCT). - Segundo
agrupar(contar): Se agrupa únicamente por la clave del grupo (nodo), calculandoagregado distintos = contar(1). Dado que el paso previo dejó una fila por elemento único, este conteo refleja con exactitud la cantidad de elementos distintos por grupo. - Filtro y umbral: Con
donde distintos > 2se aíslan los grupos que superan la cota permitida, yresumen contar(1)totaliza las infracciones.
La medida copiable#
En este ejemplo, la regla exige que ningún nodo del clúster aloje más de dos tipos de servicios distintos:
medida red.servicios_por_nodo:
de despliegue d
agrupar:
clave nodo = d.nodo
clave servicio = d.servicio
agrupar:
clave nodo = nodo
agregado distintos = contar(1)
donde distintos > 2
resumen contar(1)
umbral <= 0 segun contrato porque "ningún nodo puede alojar más de dos servicios distintos"
ambito universal
alcance "cuenta servicios distintos por nodo. NO ve el consumo de memoria ni CPU"
Los casos: rojo y verde#
Un caso rojo con filas repetidas donde un nodo aloja tres servicios distintos (web, api, db), violando el límite:
caso 003-nodo-con-tres-servicios:
fecha: "2026-09-26"
origen:
repo: "ejemplo/recetas"
commit: "sin-commit"
procedencia: construida
titulo: "un nodo supera el maximo de servicios distintos"
etiqueta: falso_verde
sintoma:
El nodo n1 aloja tres servicios distintos a pesar de tener filas duplicadas.
como_se_detecto: persona
medida: red.servicios_por_nodo
evidencia:
despliegue: nodo, servicio
"n1", "web"
"n1", "web"
"n1", "api"
"n1", "db"
leccion:
Doble agrupar deduplica web y cuenta 3 servicios distintos, superando el limite.
Y un caso verde con réplicas duplicadas que confirman que las repeticiones no inflan la cuenta de distintos:
caso 004-nodo-dentro-del-limite:
fecha: "2026-09-26"
origen:
repo: "ejemplo/recetas"
commit: "sin-commit"
procedencia: construida
titulo: "nodo con dos servicios distintos y replicas repetidas"
etiqueta: verde_correcto
sintoma:
El nodo n1 aloja dos servicios con varias instancias cada uno.
como_se_detecto: persona
medida: red.servicios_por_nodo
evidencia:
despliegue: nodo, servicio
"n1", "web"
"n1", "web"
"n1", "api"
"n2", "db"
leccion:
Las replicas duplicadas no inflan la cuenta de distintos.
3. Verificación con oracle test#
Con ambas medidas y sus cuatro casos en el proyecto, ejecutamos la verificación completa:
oracle test
UNITARIOS: salteados (sólo aplican al propio Oracle)
CORPUS OK · 4 casos · esquema, evidencia L0 y trazabilidad en regla
SINTAXIS OK · 2 medidas · 0 macros · 4 casos · 0 relaciones
catálogo: 2 medidas · corpus: 4 casos
ROJO 001-puerto-duplicado red.puerto_exclusivo (valor 1)
verde 002-puertos-distintos red.puerto_exclusivo (valor 0)
ROJO 003-nodo-con-tres-servicios red.servicios_por_nodo (valor 1)
verde 004-nodo-dentro-del-limite red.servicios_por_nodo (valor 0)
defectos que se pusieron rojos: 2 · verdes correctos: 2 · sin evidencia esperada: 0 · huecos declarados: 0
nivel meta — el marco medido con sus propias medidas:
ACEPTACIÓN ✓ — 2 defectos en rojo, 0 sin evidencia esperada, 2 verdes correctos, 0 huecos declarados sin tapar
DIFERENCIAL: salteado (el proyecto no tiene fixtures en diferencial/ todavía)
mutantes de medida (medida × mutador): 30 · murieron 30 · sobrevivieron 0
con 30 mutadores: 6 de quien escribió el lenguaje y 24 de otro autor (ver https://github.com/Segtem/oracle/blob/main/docs/decisiones/DECISION-011-LOS-MUTADORES-TIENEN-AUTOR.md)
de los muertos: 24 por conducta (invirtió el veredicto, cambió testigos o cambió el valor) · 6 rechazados por el álgebra sin evaluar
detecciones evaluadas (mutante × caso): 60
sin políticas meta activas — se informa sólo el resultado operativo
MUTACIÓN DE CÓDIGO: salteada (sólo aplica al propio Oracle)
ALCANCE: verificación de medidas contra casos guardados del corpus.
PRODUCTO: sin nueva medición; la aceptación no reejecuta los comandos de origen ni el producto. El resultado no certifica su estado actual.
VEREDICTO: VERDE (todas las verificaciones aplicables en regla)
Tanto la sintaxis como el corpus y la aceptación pasan en verde. Además, la suite de mutación ejecuta los 30 mutadores del motor: los 30 mueren y ninguno sobrevive, demostrando que ambos patrones fijan estrictamente el comportamiento esperado sin relajar el rigor del catálogo.
Esta página se genera desde docs/recetas.md.