Saltar al contenido
oracle

13 · De un producto nuevo a su primera medida observable#

Un recorrido breve, ejecutable y reproducible para conectar un producto con Oracle y juzgarlo con una medida pertinente de sus reglas, sin rituales innecesarios.

Entorno y preparación verificados#

Los comandos se ejecutan en una carpeta temporal con el Oracle de este repositorio. Un test reconstruye el ejemplo y comprueba cada salida.

Primero creá el proyecto:

oracle init ejemplo/primer-valor
Proyecto Oracle inicializado en ./ejemplo/primer-valor:
  · 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

Las salidas siguientes provienen de esa ejecución. Los hechos exportados se guardan en la carpeta temporal del ejemplo.


1. El objetivo de este recorrido#

Cuando alguien se acerca a Oracle para medir un desarrollo —especialmente en iteraciones guiadas por agentes o LLMs— surgen con frecuencia dos confusiones habituales:

  1. Confundir la verificación de medidas con la certificación del producto. Correr oracle test y ver una pantalla verde significa únicamente que el catálogo y el corpus son consistentes entre sí; no significa que el código fuente de la aplicación haya sido ejecutado ni que sus reglas de negocio funcionen.
  2. Confundir el tracker con el motor de medición. El subsistema oracle tarea es un gestor documental local en Git. Es completamente optativo: no hace falta inicializar tareas ni escribir bitácoras para formular medidas sobre un producto.

Este documento presenta la ruta mínima de primer valor:

  1. Elegir una regla concreta del producto.
  2. Extraer hechos observables estructurados (nivel L0).
  3. Escribir una medida y casos de ambas polaridades en el catálogo propio.
  4. Validar el catálogo con oracle test.
  5. Juzgar una corrida real del producto con oracle juzgar.

El ejemplo completo y reproducible se encuentra en el repositorio bajo ejemplo/primer-valor/.


2. Paso 1: Elegir una regla y extraer hechos#

En el juego de Batalla Naval, una regla elemental de colocación establece:

Regla de colocación: Toda celda ocupada por un barco debe ubicarse dentro de la cuadrícula de 10×10 (coordenadas de fila y columna entre 0 y 9).

El sensor del producto#

Oracle no inspecciona el DOM ni adivina el estado interno del juego: el producto debe emitir hechos observables estructurados en formato JSON (una tabla o relación de filas).

En nuestro ejemplo, el producto ejemplo/primer-valor/colocador.py expone la opción --exportar <archivo> para volcar las celdas ocupadas por la flota:

{
  "celda_ocupada": [
    {"barco": "fragata", "fila": 1, "columna": 2},
    {"barco": "fragata", "fila": 1, "columna": 3},
    {"barco": "fragata", "fila": 1, "columna": 4},
    {"barco": "destructor", "fila": 8, "columna": 5},
    {"barco": "destructor", "fila": 9, "columna": 5}
  ]
}

Cada hecho es un dato puro: no hay juicios de valor en el JSON, sólo hechos del mundo (nivel L0).


3. Paso 2: Crear el proyecto y enunciar la regla en una medida#

Un proyecto Oracle se inicializa con oracle init <directorio> o creando catalogos/, corpus/, diferencial/ y un archivo oracle.json:

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

[!TIP] Si tu proyecto recién comienza y busca verificar reglas de negocio propias, podés fijar "catalogo_base": false para concentrarte exclusivamente en tus medidas de dominio sin evaluar políticas de proceso universales heredadas.

La medida: catalogos/colocacion/colocacion.dentro_del_tablero.oracle#

En Oracle, las medidas se enuncian buscando lo que ofende (el defecto), no lo que está bien:

medida colocacion.dentro_del_tablero:
    de celda_ocupada c
    donde c.fila < 0 o c.fila > 9 o c.columna < 0 o c.columna > 9
    resumen contar(1)
    umbral <= 0 segun contrato porque "el tablero es de 10x10 (coordenadas 0 a 9); cualquier casilla fuera de ese rango corrompe el estado del juego"
    requiere celda_ocupada
    ambito universal
    alcance "valida que las celdas reportadas estén en el rango 0..9. NO ve si los buques tienen la longitud declarada ni si se solapan entre sí"

Los cuatro componentes esenciales:


4. Paso 3: Fijar la medida con casos de ambas polaridades en el corpus#

Una medida sin casos es una intención sin comprobar: podría estar invertida y pasar sin ser detectada. El corpus en corpus/ necesita al menos dos casos:

Caso 1: Polaridad negativa (falso_verde esperado ROJO)#

Archivo corpus/colocacion/001-desborde-tablero.caso:

caso 001-desborde-tablero:
    fecha: "2026-09-21"
    origen:
        repo: "Segtem/oracle/ejemplo/primer-valor"
        commit: "sin-commit"
    procedencia: construida
    titulo: "Buque colocado parcialmente fuera del tablero (fila 10)"
    etiqueta: falso_verde
    sintoma:
        Un buque destructor colocado verticalmente en fila 9 desborda hacia la fila 10, fuera de la cuadrícula 10x10.
    como_se_detecto: persona
    medida: colocacion.dentro_del_tablero
    evidencia:
        celda_ocupada: barco, fila, columna
            "destructor", 9, 5
            "destructor", 10, 5
    leccion:
        La medida debe detectar la celda fuera de rango (10, 5) y ponerse en rojo para evitar desbordes de memoria o estado inválido.

Caso 2: Polaridad positiva (verde_correcto esperado VERDE)#

Archivo corpus/colocacion/002-colocacion-valida.caso:

caso 002-colocacion-valida:
    fecha: "2026-09-21"
    origen:
        repo: "Segtem/oracle/ejemplo/primer-valor"
        commit: "sin-commit"
    procedencia: construida
    titulo: "Flota colocada enteramente dentro de la cuadrícula 10x10"
    etiqueta: verde_correcto
    sintoma:
        Despliegue reglamentario de buques con todas sus celdas dentro del rango válido de 0 a 9.
    como_se_detecto: persona
    medida: colocacion.dentro_del_tablero
    evidencia:
        celda_ocupada: barco, fila, columna
            "fragata", 1, 2
            "fragata", 1, 3
            "fragata", 1, 4
            "destructor", 8, 5
            "destructor", 9, 5
    leccion:
        Una colocación reglamentaria debe dar 0 testigos ofensivos y resultar verde; fija la medida contra mutaciones que eliminen el filtro.

Casos adicionales necesarios#

Los dos casos anteriores no alcanzan: dejaban seis mutantes vivos. El corpus incluye también fila -1, columna -1, columna 10 y una relación vacía (celda_ocupada: sin filas). Este último queda ROJO aunque el valor sea 0 porque incumple requiere. Los seis casos son construidos; sin-commit evita atribuirles un commit ficticio.

El directorio obligatorio diferencial/ se conserva vacío. No contiene fixtures: esta ruta mínima no aporta una comparación con un evaluador independiente y oracle test lo informa como salteado.

5. Paso 4: Validar el catálogo con oracle test#

Corremos oracle test para verificar que las medidas del catálogo compilen, satisfagan los contratos sintácticos y queden fijadas contra mutación:

caso 003-fila-negativa:
    fecha: "2026-09-21"
    origen:
        repo: "Segtem/oracle/ejemplo/primer-valor"
        commit: "sin-commit"
    procedencia: construida
    titulo: "Fila justo por debajo del tablero"
    etiqueta: falso_verde
    sintoma:
        Fila justo por debajo del tablero; la medida debe rechazar esta evidencia.
    como_se_detecto: mutacion
    medida: colocacion.dentro_del_tablero
    evidencia:
        celda_ocupada: barco, fila, columna
            "borde", -1, 5
    leccion:
        Cada borde y la falta de evidencia deben quedar fijados por el corpus.
caso 004-columna-negativa:
    fecha: "2026-09-21"
    origen:
        repo: "Segtem/oracle/ejemplo/primer-valor"
        commit: "sin-commit"
    procedencia: construida
    titulo: "Columna justo por debajo del tablero"
    etiqueta: falso_verde
    sintoma:
        Columna justo por debajo del tablero; la medida debe rechazar esta evidencia.
    como_se_detecto: mutacion
    medida: colocacion.dentro_del_tablero
    evidencia:
        celda_ocupada: barco, fila, columna
            "borde", 5, -1
    leccion:
        Cada borde y la falta de evidencia deben quedar fijados por el corpus.
caso 005-columna-desbordada:
    fecha: "2026-09-21"
    origen:
        repo: "Segtem/oracle/ejemplo/primer-valor"
        commit: "sin-commit"
    procedencia: construida
    titulo: "Columna justo por encima del tablero"
    etiqueta: falso_verde
    sintoma:
        Columna justo por encima del tablero; la medida debe rechazar esta evidencia.
    como_se_detecto: mutacion
    medida: colocacion.dentro_del_tablero
    evidencia:
        celda_ocupada: barco, fila, columna
            "borde", 5, 10
    leccion:
        Cada borde y la falta de evidencia deben quedar fijados por el corpus.
caso 006-sin-celdas:
    fecha: "2026-09-21"
    origen:
        repo: "Segtem/oracle/ejemplo/primer-valor"
        commit: "sin-commit"
    procedencia: construida
    titulo: "Sensor sin celdas observadas"
    etiqueta: falso_verde
    espera: sin_evidencia
    sintoma:
        Sensor sin celdas observadas; la medida debe rechazar esta evidencia.
    como_se_detecto: mutacion
    medida: colocacion.dentro_del_tablero
    evidencia:
        celda_ocupada:
    leccion:
        Cada borde y la falta de evidencia deben quedar fijados por el corpus.
"""Lógica mínima de colocación de buques para Batalla Naval.

Este script representa el producto bajo análisis: coloca buques en un tablero
de 10x10 y extrae los hechos observables (nivel L0) en formato JSON para que
Oracle pueda juzgarlos de forma independiente.
"""

from __future__ import annotations

import argparse
import json
import sys
from dataclasses import asdict, dataclass


@dataclass(frozen=True)
class CeldaOcupada:
    barco: str
    fila: int
    columna: int


def colocar_buque(
    barco: str,
    fila: int,
    columna: int,
    longitud: int,
    orientacion: str,
    permitir_desborde: bool = False,
) -> list[CeldaOcupada]:
    """Coloca un buque de cierta longitud a partir de (fila, columna).

    orientacion: 'H' (horizontal) o 'V' (vertical).
    Si permitir_desborde es True, omite la validación interna simulando un bug.
    """
    celdas: list[CeldaOcupada] = []
    for i in range(longitud):
        r = fila + (i if orientacion == "V" else 0)
        c = columna + (i if orientacion == "H" else 0)
        if not permitir_desborde:
            if not (0 <= r < 10 and 0 <= c < 10):
                raise ValueError(
                    f"Colocación inválida: {barco} excede el tablero en ({r}, {c})"
                )
        celdas.append(CeldaOcupada(barco=barco, fila=r, columna=c))
    return celdas


def generar_despliegue(con_defecto: bool = False) -> dict[str, list[dict]]:
    """Genera una disposición de buques y retorna los hechos en formato Oracle."""
    flota: list[CeldaOcupada] = []

    # Buque 1: Fragata (3 celdas), posición (1, 2) Horizontal
    flota.extend(colocar_buque("fragata", 1, 2, 3, "H"))

    # Buque 2: Destructor (2 celdas)
    if con_defecto:
        # DEFECTO: colocado en fila 9 Vertical, ocupando celdas (9, 5) y (10, 5).
        # La celda (10, 5) queda fuera de la cuadrícula de 10x10 (índices 0 a 9).
        flota.extend(colocar_buque("destructor", 9, 5, 2, "V", permitir_desborde=True))
    else:
        # CORREGIDO: colocado en fila 8 Vertical, ocupando celdas (8, 5) y (9, 5).
        flota.extend(colocar_buque("destructor", 8, 5, 2, "V", permitir_desborde=False))

    return {
        "celda_ocupada": [asdict(c) for c in flota]
    }


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(
        description="Colocador de buques con exportación de hechos para Oracle."
    )
    parser.add_argument(
        "--defecto",
        action="store_true",
        help="Simular defecto de colocación con desborde del tablero.",
    )
    parser.add_argument(
        "--exportar",
        type=str,
        metavar="RUTA",
        help="Ruta donde escribir el archivo JSON de hechos.",
    )
    args = parser.parse_args(argv)

    hechos = generar_despliegue(con_defecto=args.defecto)
    salida = json.dumps(hechos, indent=2, ensure_ascii=False)

    if args.exportar:
        with open(args.exportar, "w", encoding="utf-8") as f:
            f.write(salida + "\n")
        print(f"Hechos exportados a {args.exportar}")
    else:
        print(salida)
    return 0


if __name__ == "__main__":
    sys.exit(main())
oracle test --proyecto ejemplo/primer-valor

Salida real registrada de 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-desborde-tablero                   colocacion.dentro_del_tablero  (valor 1)
  verde 002-colocacion-valida                  colocacion.dentro_del_tablero  (valor 0)
  ROJO  003-fila-negativa                      colocacion.dentro_del_tablero  (valor 1)
  ROJO  004-columna-negativa                   colocacion.dentro_del_tablero  (valor 1)
  ROJO  005-columna-desbordada                 colocacion.dentro_del_tablero  (valor 1)
  SIN EVIDENCIA 006-sin-celdas                colocacion.dentro_del_tablero  («celda_ocupada» 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)

[!IMPORTANT] Este veredicto verde indica que el catálogo satisface las verificaciones aplicables y los casos presentes. No certifica en absoluto el estado del código del juego en este momento.


6. Paso 5: Juzgar una corrida real con oracle juzgar#

Ahora sí conectamos el producto vivo con Oracle.

Escenario A: Corrida con defecto#

El colocador tiene un bug y posiciona un destructor en la fila 9 vertical, desbordando hacia la fila 10:

python3 ejemplo/primer-valor/colocador.py --defecto --exportar hechos-defecto.json
oracle juzgar --proyecto ejemplo/primer-valor --con hechos-defecto.json

Salida real registrada (exit code 1):

Hechos exportados a hechos-defecto.json
✗ colocacion.dentro_del_tablero                       1 (<= 0)
      → c={'barco': 'destructor', 'fila': 10, 'columna': 5}

VEREDICTO: 1 de 1 medidas en rojo

Oracle rechaza la corrida y señala exactamente el testigo infractor: fila: 10.

Escenario B: Corrida corregida#

Arreglamos el defecto en el producto (el destructor se ubica en la fila 8 vertical, ocupando filas 8 y 9):

python3 ejemplo/primer-valor/colocador.py --exportar hechos-corregido.json
oracle juzgar --proyecto ejemplo/primer-valor --con hechos-corregido.json

Salida real registrada (exit code 0):

Hechos exportados a hechos-corregido.json
✓ colocacion.dentro_del_tablero                       0 (<= 0)

VEREDICTO: verde en 1 medidas. SIN MIRAR:
  · colocacion.dentro_del_tablero: valida que las celdas reportadas estén en el rango 0..9. NO ve si los buques tienen la longitud declarada ni si se solapan entre sí

La corrida pasa limpiamente y el reporte final imprime de forma transparente el alcance declarado: qué fue lo que no se miró.


7. Clarificaciones fundamentales#

Evidencia guardada vs. Evidencia regenerada#

¿Cuándo basta un assert y cuándo aporta Oracle?#

El tracker es una herramienta independiente#

La gestión de tareas (oracle tarea init, oracle tarea nueva, etc.) no es un requisito previo para usar medidas ni para juzgar hechos. Podés medir cualquier producto sin inicializar tareas/.

El tutorial completo es una opción para profundizar; el tracker y ese tutorial son independientes de este recorrido.

Esta página se genera desde docs/13-primer-valor.md.