Saltar al contenido
oracle

Cómo funciona Oracle#

Esta página cuenta qué hace Oracle, en qué orden y qué contesta en cada caso. No hay ninguna salida inventada: un test del repositorio arma el proyecto de esta página desde una carpeta vacía, corre cada comando y compara lo que sale con lo que ves acá. Si Oracle cambia y la página no, el test se pone rojo.

El proyecto es chico a propósito: una sola regla de la batalla naval —ningún barco fuera del tablero de 10×10— para mirar de cerca cada pieza. La guía Tu primer juego con un LLM arma el juego completo; esta página explica la máquina.

oracle --version
oracle 0.31.1
  álgebra:  1.0   (qué SIGNIFICA una medida)
  sintaxis: 0.8   (cómo se ESCRIBE)
  corriendo desde: …/oracle

Tres versiones, y son tres cosas distintas. El álgebra versiona lo que una medida significa: si sube la mayor, una medida que ya tenías puede dar otro resultado. La sintaxis versiona cómo se escribe un .oracle o un .caso. La distribución es el paquete que instalás, y cambia también cuando se arregla una herramienta sin tocar el lenguaje.

Las tres piezas#

Todo en Oracle es uno de estos tres archivos:

piezaarchivoqué es
evidenciaun .jsonlo que pasó, en filas planas. La escribe un sensor de tu proyecto, no Oracle
medidaun .oracle en catalogos/una regla: qué filas cuentan como infracción y cuántas se toleran
casoun .caso en corpus/una evidencia guardada y lo que la medida tiene que decir sobre ella

Y dos verbos:

Oracle no corre tu juego, no abre la red y no llama a ningún modelo. Juzga lo que el sensor le entrega, y cada veredicto dice qué no miró.

El proyecto#

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

init crea catalogos/, corpus/, diferencial/, relaciones/ y oracle.json. Para esta página apagamos el catálogo base: así lo único que se mide son nuestras reglas y cada salida es corta. En un proyecto real conviene dejarlo prendido, porque trae las políticas que vigilan tu catálogo (que toda medida declare qué no ve, de dónde sale cada umbral, etc.).

{
  "esquema": "oracle.proyecto/v1",
  "catalogo_base": false,
  "perfiles": []
}

Una medida, línea por línea#

ninguno-requiere flota.casillas_dentro_del_tablero:
    de casilla c
    donde c.fila < 0 o c.fila > 9 o c.columna < 0 o c.columna > 9
    umbral <= 0 segun contrato porque "el tablero es de 10x10: filas y columnas van de 0 a 9"
    requiere casilla
    ambito universal
    alcance "mira cada casilla ocupada que el juego anotó. NO ve si los barcos se tocan ni si la flota está completa"
líneaqué dicequé pasa si falta
ninguno-requiere flota.casillas_dentro_del_tablero:la forma de la regla y su id. ninguno = «no tiene que haber ninguna infracción»; -requiere = «y tiene que haber algo que mirar»el id es obligatorio: dominio.nombre, en ASCII
de casilla cde qué relación de la evidencia salen las filas, y con qué alias se nombransin fuente no hay medida
donde …qué fila cuenta como infracción. Tiene que dar true o false: un número o un texto es errorsin filtro, toda casilla sería una infracción
umbral <= 0 segun contrato porque "…"cuántas infracciones se toleran, de dónde salió ese número (contrato, medicion, convencion o tanteo) y por quées obligatorio: una regla sin umbral no decide nada
requiere casillala relación que tiene que traer filas. Si viene vacía, el veredicto es SIN EVIDENCIAcon ninguno a secas, cero filas son cero infracciones: verde
ambito universaldónde obliga la regla: universal (siempre) o del_origen (sólo en el proyecto que la escribió)se asume sin_declarar
alcance "…"qué no ve la medida. Oracle lo repite al final de cada verdees obligatorio: un verde sin alcance promete de más

Una macro como ninguno-requiere es sólo una forma corta. Oracle la expande a la forma canónica, que es un dato JSON: así se guarda, se compara y se muta.

Ver la forma canónica de esta medida
oracle medida expandir catalogos/flota/flota.casillas_dentro_del_tablero.oracle
[
 "medida",
 "flota.casillas_dentro_del_tablero",
 [
  "desde",
  [
   "de",
   "casilla",
   "c"
  ],
  [
   "donde",
   [
    "o",
    [
     "<",
     [
      "campo",
      "c",
      "fila"
     ],
     0
    ],
    [
     ">",
     [
      "campo",
      "c",
      "fila"
     ],
     9
    ],
    [
     "<",
     [
      "campo",
      "c",
      "columna"
     ],
     0
    ],
    [
     ">",
     [
      "campo",
      "c",
      "columna"
     ],
     9
    ]
   ]
  ]
 ],
 [
  "resumen",
  "contar",
  1
 ],
 [
  "umbral",
  "<=",
  0,
  "el tablero es de 10x10: filas y columnas van de 0 a 9",
  "contrato"
 ],
 [
  "requiere",
  "casilla"
 ],
 [
  "ambito",
  "universal"
 ],
 [
  "alcance",
  "mira cada casilla ocupada que el juego anotó. NO ve si los barcos se tocan ni si la flota está completa"
 ]
]

ninguno se vuelve una tubería (desde: de dónde salen las filas y cómo se filtran), un resumen (contar las filas que quedaron) y un umbral (<= 0). Toda medida tiene esa forma.

Qué pasa cuando juzgás#

oracle juzgar hace siempre lo mismo, en este orden:

  1. Carga el catálogo. Cada medida se valida al leerla: vocabularios cerrados, alcance obligatorio, macros completas. Una medida mal escrita no se evalúa a medias: la corrida no empieza.
  2. Lee la evidencia. Un objeto JSON: cada clave es una relación y su valor, una lista de filas.
  3. Valida la evidencia entera, antes de medir nada. Un null se rechaza, y las claves de unicidad se comprueban en todas las relaciones, también en las que ninguna medida usa.
  4. Mide cada regla. Si una relación de requiere viene vacía, esa medida sale SIN EVIDENCIA y no se mide. Si no, corre la tubería: las filas que sobreviven son los testigos, el resumen las cuenta y el umbral decide.
  5. Aplica las sombras. Una medida en sombra se mide igual, pero su rojo no hace fallar la corrida mientras no supere su cota. Un SIN EVIDENCIA no se perdona nunca.
  6. Da el veredicto y un código de salida: 0 si todo pasó; 1 si hubo un rojo, un SIN EVIDENCIA o una medida propia que no se pudo aplicar; 2 si no se pudo juzgar (evidencia inválida, error del álgebra, uso incorrecto).

Veámoslo con cinco evidencias.

{
  "casilla": [
    {"barco": "b1", "fila": 3, "columna": 4},
    {"barco": "b1", "fila": 3, "columna": 5}
  ]
}
{
  "casilla": [
    {"barco": "b1", "fila": 9, "columna": 4},
    {"barco": "b1", "fila": 10, "columna": 4}
  ]
}
{
  "casilla": []
}
{
  "casilla": [
    {"barco": "b1", "fila": null, "columna": 4}
  ]
}
{
  "casilla": [
    ["clave", ["barco", "fila", "columna"]],
    {"barco": "b1", "fila": 3, "columna": 4},
    {"barco": "b1", "fila": 3, "columna": 4}
  ]
}

Verde: y lo que no miró#

oracle juzgar --con evidencia/en-regla.json
✓ flota.casillas_dentro_del_tablero                   0 (<= 0)

VEREDICTO: verde en 1 medidas. SIN MIRAR:
  · flota.casillas_dentro_del_tablero: mira cada casilla ocupada que el juego anotó. NO ve si los barcos se tocan ni si la flota está completa

0 (<= 0): cero infracciones contra un umbral de cero. Un verde nunca termina ahí: siempre enumera lo que no se miró, que es el alcance de cada medida.

Rojo: con el testigo exacto#

oracle juzgar --con evidencia/fuera.json
✗ flota.casillas_dentro_del_tablero                   1 (<= 0)
      → c={'barco': 'b1', 'fila': 10, 'columna': 4}

VEREDICTO: 1 de 1 medidas en rojo

El testigo es la fila que hizo fallar la regla, tal como vino en la evidencia. La casilla de la fila 9 no aparece: está dentro del tablero. Con el testigo sabés qué corregir sin adivinar.

Sin evidencia: no es verde ni rojo#

oracle juzgar --con evidencia/vacia.json
⊘ flota.casillas_dentro_del_tablero            SIN EVIDENCIA («casilla» vacía; no se midió)

VEREDICTO: 1 de 1 sin evidencia (no se midieron)

La partida no anotó ninguna casilla. Eso no prueba que los barcos estén bien: prueba que el sensor no miró. Oracle no lo cuenta como verde, y tampoco como un rojo del juego: lo nombra aparte, y la corrida falla igual.

Pensalo. ¿Y si la regla no tuviera requiere?

Probalo

La misma regla con ninguno a secas:

ninguno flota.casillas_dentro_sin_requiere:
    de casilla c
    donde c.fila < 0 o c.fila > 9 o c.columna < 0 o c.columna > 9
    umbral <= 0 segun contrato porque "la misma regla, escrita sin exigir que haya casillas"
    ambito universal
    alcance "la misma regla que flota.casillas_dentro_del_tablero; existe para mostrar qué pasa sin requiere"
oracle juzgar --con evidencia/vacia.json
⊘ flota.casillas_dentro_del_tablero            SIN EVIDENCIA («casilla» vacía; no se midió)
✓ flota.casillas_dentro_sin_requiere                  0 (<= 0)

VEREDICTO: 1 de 2 sin evidencia (no se midieron)

Sobre la misma evidencia vacía, la versión sin requiere dice verde: cero filas, cero infracciones. Es la trampa más común de un catálogo, y por eso existe -requiere: si la relación es el universo que se evalúa, usá la variante -requiere. Más abajo, oracle test va a atrapar esta medida floja por su cuenta.

Evidencia inválida: no se juzga#

oracle juzgar --con evidencia/con-null.json
ERROR AL EVALUAR — «flota.casillas_dentro_del_tablero»: la relación «casilla» trae null explícito en la fila 0, campo «fila»
ERROR AL EVALUAR — «flota.casillas_dentro_sin_requiere»: la relación «casilla» trae null explícito en la fila 0, campo «fila»

Un null no dice si la casilla estaba bien o mal: cada comparación lo interpretaría a su manera. Oracle lo rechaza antes de medir. Si tu sensor no pudo decidir un dato, que lo diga con un campo aparte (fila_decidible: false), no con un hueco.

Una relación puede declarar su clave de unicidad poniendo ["clave", [campos…]] como primera fila. Oracle la comprueba en toda la evidencia antes de medir:

oracle juzgar --con evidencia/repetida.json
ERROR AL EVALUAR — «flota.casillas_dentro_del_tablero»: la relación «casilla» declara la clave (barco, fila, columna) y la fila 1 la repite: ya la traía la fila 0 — {'barco': 'b1', 'fila': 3, 'columna': 4}
ERROR AL EVALUAR — «flota.casillas_dentro_sin_requiere»: la relación «casilla» declara la clave (barco, fila, columna) y la fila 1 la repite: ya la traía la fila 0 — {'barco': 'b1', 'fila': 3, 'columna': 4}

Sin la cabecera, dos filas iguales son dos hechos: Oracle no deduplica, porque no puede inventar una identidad que tu dominio no declaró.

Qué comprueba oracle test#

juzgar mira una partida. test mira tus reglas: ¿se ponen rojas cuando deben y verdes cuando deben? ¿Y se nota si alguien las rompe?

oracle test --rapido
UNITARIOS: salteados (sólo aplican al propio Oracle)

CORPUS OK · 0 casos · esquema, evidencia L0 y trazabilidad en regla

MEDIDAS SIN CASOS ✗ — 2 medida(s) propias sin ejercitar:
  · flota.casillas_dentro_del_tablero: escribí un caso rojo y uno verde que ejerzan esta medida
  · flota.casillas_dentro_sin_requiere: escribí un caso rojo y uno verde que ejerzan esta medida

SINTAXIS OK · 2 medidas · 0 macros · 0 casos · 0 relaciones

ACEPTACIÓN ✗ — sin casos en el corpus: un catálogo con medidas no puede verificarse sin casos

DIFERENCIAL: salteado (el proyecto no tiene fixtures en diferencial/ todavía)

MUTACIÓN: salteada por --rapido

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.
OMISIONES: mutación de medidas (--rapido)
VEREDICTO: ROJO (falló: medidas sin casos, aceptación)

Dos medidas y ningún caso: nada respalda a esas reglas. Un caso es una evidencia guardada con su etiqueta, que dice qué tiene que contestar la medida:

etiquetala medida tiene que dar
falso_verderojo: la evidencia trae un defecto que la regla no puede dejar pasar
verde_correctoverde: todo en regla, y la regla lo reconoce

Para esta regla hacen falta seis: un rojo por cada una de las cuatro condiciones del donde, un verde en los cuatro bordes y una partida sin casillas. Y uno más, contra la medida floja.

Ver los siete casos
caso 001-fila-desbordada:
    fecha: "2026-09-25"
    origen:
        repo: "ejemplo/como-funciona"
        commit: "sin-commit"
    procedencia: construida
    titulo: "una casilla en la fila 10"
    etiqueta: falso_verde
    sintoma:
        El juego anotó un barco en la fila 10, fuera del tablero de 0 a 9.
    como_se_detecto: persona
    medida: flota.casillas_dentro_del_tablero
    evidencia:
        casilla: barco, fila, columna
            "b1", 9, 4
            "b1", 10, 4

    leccion:
        El borde de abajo se prueba con la primera fila que ya no existe.
caso 002-fila-negativa:
    fecha: "2026-09-25"
    origen:
        repo: "ejemplo/como-funciona"
        commit: "sin-commit"
    procedencia: construida
    titulo: "una casilla en la fila -1"
    etiqueta: falso_verde
    sintoma:
        El juego anotó un barco en la fila -1.
    como_se_detecto: persona
    medida: flota.casillas_dentro_del_tablero
    evidencia:
        casilla: barco, fila, columna
            "b1", -1, 4

    leccion:
        Sin este caso, borrar la condición c.fila < 0 no se nota.
caso 003-columna-desbordada:
    fecha: "2026-09-25"
    origen:
        repo: "ejemplo/como-funciona"
        commit: "sin-commit"
    procedencia: construida
    titulo: "una casilla en la columna 10"
    etiqueta: falso_verde
    sintoma:
        El juego anotó un barco en la columna 10.
    como_se_detecto: persona
    medida: flota.casillas_dentro_del_tablero
    evidencia:
        casilla: barco, fila, columna
            "b1", 4, 10

    leccion:
        Cada una de las cuatro condiciones necesita su propio caso rojo.
caso 004-columna-negativa:
    fecha: "2026-09-25"
    origen:
        repo: "ejemplo/como-funciona"
        commit: "sin-commit"
    procedencia: construida
    titulo: "una casilla en la columna -1"
    etiqueta: falso_verde
    sintoma:
        El juego anotó un barco en la columna -1.
    como_se_detecto: persona
    medida: flota.casillas_dentro_del_tablero
    evidencia:
        casilla: barco, fila, columna
            "b1", 4, -1

    leccion:
        Cada una de las cuatro condiciones necesita su propio caso rojo.
caso 005-las-cuatro-esquinas:
    fecha: "2026-09-25"
    origen:
        repo: "ejemplo/como-funciona"
        commit: "sin-commit"
    procedencia: construida
    titulo: "las cuatro esquinas del tablero"
    etiqueta: verde_correcto
    sintoma:
        Casillas en 0 y en 9, filas y columnas: todas válidas.
    como_se_detecto: persona
    medida: flota.casillas_dentro_del_tablero
    evidencia:
        casilla: barco, fila, columna
            "b1", 0, 0
            "b2", 0, 9
            "b3", 9, 0
            "b4", 9, 9

    leccion:
        Sin un verde en el borde, cambiar > 9 por >= 9 no se nota.
caso 006-partida-sin-casillas:
    fecha: "2026-09-25"
    origen:
        repo: "ejemplo/como-funciona"
        commit: "sin-commit"
    procedencia: construida
    titulo: "el juego no anotó ninguna casilla"
    etiqueta: falso_verde
    espera: sin_evidencia
    sintoma:
        La partida terminó sin una sola casilla de barco anotada.
    como_se_detecto: persona
    medida: flota.casillas_dentro_del_tablero
    evidencia:
        casilla:

    leccion:
        Cero casillas no es un tablero en regla: es un sensor que no miró.
caso 007-sin-requiere-sin-casillas:
    fecha: "2026-09-25"
    origen:
        repo: "ejemplo/como-funciona"
        commit: "sin-commit"
    procedencia: construida
    titulo: "el juego no anotó ninguna casilla"
    etiqueta: falso_verde
    sintoma:
        La partida terminó sin una sola casilla de barco anotada.
    como_se_detecto: persona
    medida: flota.casillas_dentro_sin_requiere
    evidencia:
        casilla:

    leccion:
        Con ninguno a secas, cero casillas cuenta cero infracciones y sale verde.
oracle test --rapido
UNITARIOS: salteados (sólo aplican al propio Oracle)

CORPUS OK · 7 casos · esquema, evidencia L0 y trazabilidad en regla

SINTAXIS OK · 2 medidas · 0 macros · 7 casos · 0 relaciones

catálogo: 2 medidas · corpus: 7 casos

  ROJO  001-fila-desbordada                    flota.casillas_dentro_del_tablero  (valor 1)
  ROJO  002-fila-negativa                      flota.casillas_dentro_del_tablero  (valor 1)
  ROJO  003-columna-desbordada                 flota.casillas_dentro_del_tablero  (valor 1)
  ROJO  004-columna-negativa                   flota.casillas_dentro_del_tablero  (valor 1)
  verde 005-las-cuatro-esquinas                flota.casillas_dentro_del_tablero  (valor 0)
  SIN EVIDENCIA 006-partida-sin-casillas      flota.casillas_dentro_del_tablero  («casilla» vacía)
  FALLA 007-sin-requiere-sin-casillas          flota.casillas_dentro_sin_requiere  (valor 0; salió verde, se esperaba ROJO medido)

defectos que se pusieron rojos: 4 · verdes correctos: 1 · sin evidencia esperada: 1 · huecos declarados: 0

nivel meta — el marco medido con sus propias medidas:

ACEPTACIÓN ✗ — 1 problema(s)
  · 007-sin-requiere-sin-casillas: se esperaba ROJO medido con flota.casillas_dentro_sin_requiere

DIFERENCIAL: salteado (el proyecto no tiene fixtures en diferencial/ todavía)

MUTACIÓN: salteada por --rapido

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.
OMISIONES: mutación de medidas (--rapido)
VEREDICTO: ROJO (falló: aceptación)

El caso 007 dice que una partida sin casillas es un defecto, y la medida sin requiere la deja pasar en verde. oracle test no la perdona: la nombra, dice qué esperaba y se pone rojo. La salida es borrar la medida floja, porque la otra ya dice lo mismo, y bien:

rm -f catalogos/flota/flota.casillas_dentro_sin_requiere.oracle
rm -f corpus/flota/007-sin-requiere-sin-casillas.caso
oracle test
UNITARIOS: salteados (sólo aplican al propio Oracle)

CORPUS OK · 6 casos · esquema, evidencia L0 y trazabilidad en regla

SINTAXIS OK · 1 medidas · 0 macros · 6 casos · 0 relaciones

catálogo: 1 medidas · corpus: 6 casos

  ROJO  001-fila-desbordada                    flota.casillas_dentro_del_tablero  (valor 1)
  ROJO  002-fila-negativa                      flota.casillas_dentro_del_tablero  (valor 1)
  ROJO  003-columna-desbordada                 flota.casillas_dentro_del_tablero  (valor 1)
  ROJO  004-columna-negativa                   flota.casillas_dentro_del_tablero  (valor 1)
  verde 005-las-cuatro-esquinas                flota.casillas_dentro_del_tablero  (valor 0)
  SIN EVIDENCIA 006-partida-sin-casillas      flota.casillas_dentro_del_tablero  («casilla» vacía)

defectos que se pusieron rojos: 4 · verdes correctos: 1 · sin evidencia esperada: 1 · huecos declarados: 0

nivel meta — el marco medido con sus propias medidas:

ACEPTACIÓN ✓ — 4 defectos en rojo, 1 sin evidencia esperada, 1 verdes correctos, 0 huecos declarados sin tapar

DIFERENCIAL: salteado (el proyecto no tiene fixtures en diferencial/ todavía)

mutantes de medida (medida × mutador): 20 · murieron 20 · 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: 20 por conducta (invirtió el veredicto, cambió testigos o cambió el valor) · 0 rechazados por el álgebra sin evaluar
detecciones evaluadas (mutante × caso): 120

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)

Las capas, en orden#

oracle test corre estas capas una detrás de otra. Con --rapido saltea la mutación; con --todo agrega la mutación de código, que sólo existe dentro del propio Oracle.

capaqué compruebase pone roja si…
estructuraque existan catalogos/, corpus/ y diferencial/falta alguna
andamiolos casos que creó oracle nuevaalguno sigue sin evidencia real
catálogoque cada medida se pueda leeruna medida está mal escrita
corpusel esquema de cada caso y su evidenciaun caso está mal formado
medidas sin casosque cada medida propia tenga al menos un caso, también con --rapidouna medida no tiene ninguno
sintaxisque cada medida y cada caso se impriman y se vuelvan a leer igualesla ida y vuelta cambia algo
aceptaciónque cada caso salga como pide su etiqueta; y, con el catálogo base, las políticas que vigilan tu catálogoun caso sale al revés
diferenciallos fixtures de diferencial/: veredictos guardados contra una implementación independienteun veredicto cambió
mutación de medidasataca cada medida con cada mutador (afloja el umbral, quita un filtro, invierte una comparación…) y exige que algún caso lo notesobrevive un mutante

El veredicto final es uno de tres: VERDE, ROJO (y qué capas fallaron) o SIN MEDICIÓN (no hay medidas ni casos: no hay nada que decir, y no es verde). Un VERDE con capas salteadas lo dice en el mismo renglón: VERDE (se salteó: mutación de medidas (--rapido)).

Por qué la mutación#

Un caso rojo no alcanza. El mutador alejar_limite_de_defecto cambia c.fila < 0 por c.fila < -1: la regla sigue pareciendo razonable, pero ya no ve la fila -1. De los siete casos, sólo el 002 lo nota, porque es el único con una casilla en la fila -1; sin él, ese mutante sobreviviría y oracle test se pondría rojo. La mutación prueba que tus casos sostienen cada parte de tu regla, no sólo que la ejercitan. Lo cuenta en detalle Por qué la mutación.

Lo que Oracle no hace#

La referencia completa del lenguaje está en la especificación y en el manual, que se genera de las mismas declaraciones que usa Oracle.

Esta página se genera desde docs/como-funciona.md.