Files
fn_registry/.claude/CLAUDE.md
T
egutierrez 54950af0eb docs: actualizar CLAUDE.md con apps/, proposals, assertions y bucle reactivo
Documenta la nueva estructura apps/ para aplicaciones ejecutables,
el sistema de proposals, executions/assertions, motor de evaluación SQL,
bucle reactivo (EJECUTAR→EVALUAR→REACCIONAR→PROPONER) y CLI completo
para fn ops assertion/execution y fn proposal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:14:35 +01:00

27 KiB

fn-registry

Registry personal de codigo reutilizable con busqueda FTS. Diseñado para composicion funcional y agentes.

El sistema tiene dos bases de datos SQLite:

  • registry.db — conocimiento estatico: que funciones y tipos existen, como se componen, y que mejoras se proponen. Vive en la raiz del repositorio. Regenerable con fn index (excepto proposals).
  • operations.db — conocimiento dinamico por proyecto: que entities existen, como se relacionan, que ejecuciones se han hecho, y que calidad tienen los datos. Vive en cada proyecto que usa el registry.

Arquitectura: dos BDs, un bucle

┌─────────────────────────────────────────────────────────────────────┐
│                         registry.db (central)                       │
│  ┌──────────┐   ┌──────────┐   ┌──────────┐                        │
│  │ functions │   │  types   │   │proposals │                        │
│  │ (120+)   │   │  (31+)   │   │(auto/man)│                        │
│  └──────────┘   └──────────┘   └──────────┘                        │
│       ↑ busca funciones            ↑ crea proposals                 │
└───────┼────────────────────────────┼────────────────────────────────┘
        │                            │
┌───────┼────────────────────────────┼────────────────────────────────┐
│       ↓ usa en relaciones          │ reactive loop                  │
│                     operations.db (por proyecto)                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────────────┐   │
│  │ entities │→ │relations │  │executions│→ │assertions        │   │
│  │          │  │          │  │          │  │  → assertion_results│  │
│  └──────────┘  └──────────┘  └──────────┘  └──────────────────┘   │
│  ┌──────────────┐  ┌──────────────┐                                 │
│  │relation_inputs│  │types_snapshot│                                 │
│  └──────────────┘  └──────────────┘                                 │
└─────────────────────────────────────────────────────────────────────┘

Bucle autonomo: EJECUTAR → EVALUAR → REACCIONAR → PROPONER

  1. Un pipeline se ejecuta → se registra en executions
  2. Las assertions activas de la entity se evaluan automaticamente
  3. Si critical falla → entity pasa a corrupted + se crea proposal en registry
  4. Si warning falla → entity pasa a stale
  5. El humano revisa proposals y decide si implementar mejoras

Explorar el registry (USAR SIEMPRE)

La BD SQLite registry.db es tu mapa del repositorio. Antes de escribir codigo, SIEMPRE consultala para saber que existe, evitar duplicados y descubrir funciones reutilizables.

# Buscar funciones por texto libre (FTS5)
sqlite3 registry.db "SELECT id, kind, purity, description FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'slice') ORDER BY name;"

# Listar todas las funciones de un dominio
sqlite3 registry.db "SELECT id, purity, signature FROM functions WHERE domain = 'finance' ORDER BY name;"

# Listar solo puras de un dominio
sqlite3 registry.db "SELECT id, signature FROM functions WHERE domain = 'core' AND purity = 'pure' ORDER BY name;"

# Listar solo impuras
sqlite3 registry.db "SELECT id, domain, error_type FROM functions WHERE purity = 'impure' ORDER BY domain, name;"

# Buscar tipos por dominio
sqlite3 registry.db "SELECT id, algebraic, description FROM types WHERE domain = 'cybersecurity';"

# Ver que funciones usa un pipeline o funcion compuesta
sqlite3 registry.db "SELECT id, uses_functions, uses_types FROM functions WHERE uses_functions != '[]';"

# Ver funciones que dependen de un tipo concreto
sqlite3 registry.db "SELECT id FROM functions WHERE uses_types LIKE '%ohlcv_go_finance%';"

# Buscar por tag
sqlite3 registry.db "SELECT id, tags FROM functions WHERE tags LIKE '%generic%';"

# Contar entradas por dominio
sqlite3 registry.db "SELECT domain, COUNT(*) FROM functions GROUP BY domain;"

# Ver todo el schema
sqlite3 registry.db ".schema"

# Ver proposals pendientes
sqlite3 registry.db "SELECT id, kind, status, title FROM proposals WHERE status = 'pending';"

La BD usa WAL mode — puedes leerla mientras se escribe sin bloqueos.


Tablas: registry.db

functions (27 columnas)

Cada funcion registrada: su firma, purity, dependencias, y metadata.

Columna Tipo Descripcion
id TEXT PK {name}_{lang}_{domain}
name TEXT snake_case
kind TEXT function / pipeline / component
lang TEXT go / python / typescript / sql
domain TEXT core / finance / datascience / cybersecurity / ...
version TEXT semver
purity TEXT pure / impure
signature TEXT firma completa
description TEXT que hace y cuando usarla
tags JSON[] etiquetas
uses_functions JSON[] IDs de funciones que invoca
uses_types JSON[] IDs de tipos que recibe
returns JSON[] IDs de tipos que devuelve (no tipos nativos)
returns_optional INT 0/1
error_type TEXT ID del tipo de error (obligatorio si impure)
imports JSON[] dependencias externas
example TEXT codigo de ejemplo extraido del .md
tested INT 0/1
tests JSON[] nombres de tests
test_file_path TEXT ruta al archivo de test
file_path TEXT ruta relativa al .go/.py/.tsx
props JSON[] solo components: PropDef[]
emits JSON[] solo components: eventos emitidos
has_state INT? solo components: nullable
framework TEXT solo components: react/vue/...
variant JSON[] solo components: variantes
created_at, updated_at TEXT RFC3339

types (13 columnas)

Tipos algebraicos: product (struct) y sum (interface/union).

Columna Tipo Descripcion
id TEXT PK {name}_{lang}_{domain}
name TEXT snake_case
lang TEXT lenguaje
domain TEXT dominio
version TEXT semver
algebraic TEXT product / sum
definition TEXT codigo fuente del tipo
description TEXT descripcion
tags JSON[] etiquetas
uses_types JSON[] IDs de tipos que compone (sin auto-ref)
file_path TEXT ruta relativa
created_at, updated_at TEXT RFC3339

proposals (11 columnas)

Mejoras propuestas al registry. Las crea el agente (reactive loop) o el humano.

Columna Tipo Descripcion
id TEXT PK proposal_{timestamp} o manual
kind TEXT new_function / new_type / improve_function / improve_type / new_pipeline
target_id TEXT ID de la funcion/tipo a mejorar (obligatorio para improve_*)
title TEXT titulo corto
description TEXT detalle
evidence JSON{} datos que justifican la propuesta (assertion failures, metrics, etc)
status TEXT pending / approved / rejected / implemented
created_by TEXT quien creo (agente, humano, reactive_loop)
reviewed_by TEXT quien reviso
created_at, updated_at TEXT RFC3339

FTS5 (busqueda full-text)

  • functions_fts — indexa: id, name, description, tags, signature, domain
  • types_fts — indexa: id, name, description, tags, domain
  • proposals_fts — indexa: id, title, description, evidence

Sincronizados automaticamente via triggers (INSERT/UPDATE/DELETE).


Tablas: operations.db

entities (12 columnas)

Instancia concreta de un tipo del registry dentro de un proyecto.

Columna Tipo Descripcion
id TEXT PK identificador unico en el proyecto
name TEXT nombre descriptivo
type_ref TEXT ID del tipo en registry (ej: ohlcv_go_finance)
status TEXT active / stale / corrupted / archived
description TEXT que representa esta entity
domain TEXT dominio
tags JSON[] etiquetas
source TEXT origen de los datos (obligatorio)
metadata JSON{} campos del tipo instanciados con valores reales
notes TEXT notas libres
created_at, updated_at TEXT RFC3339

relations (17 columnas)

Conexion/transformacion entre entities. Puede ser causal (via funcion) o semantica.

Columna Tipo Descripcion
id TEXT PK identificador
name TEXT nombre descriptivo
from_entity TEXT entity origen
to_entity TEXT entity destino (obligatorio)
via TEXT ID de funcion del registry que transforma (si vacio = semantica)
description TEXT que hace esta relacion
purity TEXT pure / impure / ``
direction TEXT unidirectional / bidirectional / inverse
weight REAL 0.0-1.0, importancia
status TEXT designed / implemented / tested / running / deprecated
started_at, ended_at TEXT ciclo de vida
order INT para secuencias
tags JSON[] etiquetas
notes TEXT notas
created_at, updated_at TEXT RFC3339

relation_inputs (5 columnas)

Multi-input para relaciones N→1 (joins, merges, agregaciones).

Columna Tipo Descripcion
id TEXT PK
relation_id TEXT FK referencia a relations.id (CASCADE delete)
entity_id TEXT FK referencia a entities.id
role TEXT rol semantico del input (ej: "left", "right", "config")
order INT orden de procesamiento

types_snapshot (7 columnas)

Copia inmutable de un tipo del registry en el momento de uso. Hace operations.db autonoma.

Columna Tipo Descripcion
id TEXT PK mismo ID que en registry.types
version TEXT version capturada
lang TEXT lenguaje
algebraic TEXT product/sum
definition TEXT codigo fuente capturado
description TEXT descripcion capturada
snapped_at TEXT cuando se hizo el snapshot

executions (12 columnas)

Cada ejecucion de un pipeline. Memoria de comportamiento del sistema.

Columna Tipo Descripcion
id TEXT PK exec_{timestamp} o manual
pipeline_id TEXT ID de funcion del registry (ej: tick_to_ohlcv_go_finance)
relation_id TEXT relacion asociada (opcional)
status TEXT success / failure / partial
started_at TEXT inicio (obligatorio)
ended_at TEXT fin (nullable si en progreso)
duration_ms INT auto-calculado si started_at y ended_at presentes
records_in INT registros de entrada (nullable)
records_out INT registros de salida (nullable)
error TEXT mensaje de error si fallo
metrics JSON{} metricas custom (ej: {"mean_close": 42000})
created_at TEXT RFC3339

assertions (9 columnas)

Regla de calidad formal sobre una entity. Evaluable automaticamente contra metadata.

Columna Tipo Descripcion
id TEXT PK
entity_id TEXT FK referencia a entities.id
name TEXT nombre descriptivo
kind TEXT tipo libre: range, null, statistical, consistency, freshness, o custom
rule TEXT expresion SQL evaluable (ver motor de evaluacion abajo)
severity TEXT critical / warning / info
description TEXT que verifica
active INT 0/1 — solo las activas se evaluan
created_at TEXT RFC3339

assertion_results (7 columnas)

Historial de evaluaciones de assertions.

Columna Tipo Descripcion
id TEXT PK
assertion_id TEXT FK referencia a assertions.id
execution_id TEXT referencia a executions.id (vacio si eval manual)
status TEXT pass / fail / skip
value JSON{} datos capturados en el momento de evaluacion
message TEXT detalle del resultado
evaluated_at TEXT RFC3339

FTS5 operations

  • entities_fts — indexa: id, name, description, tags, domain
  • assertions_fts — indexa: id, name, description, rule

Motor de evaluacion de assertions

Las rules se escriben como expresiones SQL. Campos sin prefijo se reescriben automaticamente a json_extract(metadata, '$.campo'):

close > 0                          → json_extract(metadata, '$.close') > 0
low <= close AND close <= high     → json_extract(metadata, '$.low') <= ...
open IS NOT NULL                   → json_extract(metadata, '$.open') IS NOT NULL

Si la rule ya usa json_extract, se deja como esta. Palabras SQL (AND, OR, NOT, IS, NULL, BETWEEN, etc) y funciones SQLite (datetime, abs, max, min, etc) no se reescriben.

Kinds documentados (puedes añadir nuevos sin tocar schema):

Kind Descripcion Ejemplo de rule
range Valor dentro de rango close BETWEEN 0 AND 1000000
null Campo no nulo open IS NOT NULL
consistency Relacion entre campos low <= close AND close <= high
freshness Datos recientes json_extract(metadata, '$.ts') > datetime('now', '-1 hour')
statistical Desviacion estadistica (evaluar externamente, registrar manual)

Dos modos:

  • Auto: fn ops assertion eval --entity-id X ejecuta rules SQL contra entities.metadata
  • Manual: fn ops assertion result add registra resultados de assertions que el sistema no puede evaluar

Bucle reactivo (con --react):

  • fn ops assertion eval --entity-id X --react evalua Y reacciona:
    • Critical fail → entity.status = corrupted + auto-crea proposal en registry.db
    • Warning fail → entity.status = stale (solo si era active)
    • Info fail → sin cambio

Sistema de migraciones

Ambas BDs usan un sistema de migraciones con embed.FS:

registry/migrations/
  001_init.sql              # functions + types + FTS
  002_proposals.sql         # proposals + FTS

fn_operations/migrations/
  001_init.sql              # entities + relations + relation_inputs + types_snapshot + FTS
  002_executions_assertions.sql  # executions + assertions + assertion_results + FTS
  • Tabla schema_migrations en cada BD rastrea versiones aplicadas
  • CREATE TABLE IF NOT EXISTS + transacciones por migracion = idempotente
  • Al hacer Open() se aplican automaticamente las migraciones pendientes
  • Para añadir una nueva migracion: crear NNN_nombre.sql en la carpeta correspondiente

Estructura del repositorio

fn-registry/
  functions/              # Codigo y docs de funciones
    core/                 # Utilidades genericas (filter, map, pipeline, retry...)
    finance/              # Indicadores, riesgo, IO de mercado
    datascience/          # Estadistica, DSP, IO de datos
    cybersecurity/        # Crypto, analisis de red, IO de seguridad
    pipelines/            # Composiciones de funciones, siempre impuras
    components/           # Componentes React (.tsx)
  types/                  # Tipos algebraicos (product y sum)
    core/                 # Result, Option, Pair, Error
    finance/              # OHLCV, Tick, BollingerResult, DrawdownResult
    datascience/          # OutlierResult
    cybersecurity/        # CIDRBlock, ThreatResult, PortResult
  registry/               # Paquete Go: modelos, SQLite, parser, indexer, validacion, migraciones
    models.go             # Function, Type, Proposal structs + enums (Kind, Purity, Algebraic, ProposalKind, ProposalStatus)
    db.go                 # Open/Close/Drop + WAL + migraciones
    store.go              # CRUD: Insert/Get/Update/Delete/List/Search para functions, types, proposals
    validate.go           # ValidateFunction, ValidateType, ValidateProposal
    parser.go             # ParseFunctionMD, ParseTypeMD (YAML frontmatter)
    indexer.go            # Index() — two-pass: parse → validate refs → insert
    migrate.go            # Motor de migraciones (embed.FS)
    migrations/           # Archivos .sql numerados
  fn_operations/          # Paquete Go: operations database (libreria, NO apps)
    models.go             # Entity, Relation, RelationInput, TypeSnapshot, Execution, Assertion, AssertionResult + enums
    db.go                 # Open/Close/Drop/Conn
    store.go              # CRUD para todas las tablas
    validate.go           # ValidateEntity, ValidateRelation, ValidateExecution, ValidateAssertion, DetectCycle
    operations.go         # Alto nivel: InsertEntityWithSnapshot, InsertRelationSafe, React, ExecuteAndReact
    eval.go               # Motor de evaluacion: rewriteRule, EvalAssertion, EvalEntityAssertions
    migrate.go            # Motor de migraciones
    migrations/           # Archivos .sql numerados
  apps/                   # Aplicaciones ejecutables (TUIs, CLIs) — modulos Go independientes
    docker_tui/           # TUI fullscreen para gestionar Docker
    pipeline_launcher/    # TUI para lanzar pipelines y registrar ejecuciones
  cmd/fn/                 # CLI
    main.go               # Subcomandos: index, search, list, show, add, ops, proposal
    ops.go                # fn ops: entity, relation, graph, snapshot, execution, assertion
    proposal.go           # fn proposal: add, list, show, update
  docs/                   # Specs de diseño (fuente de verdad del schema)
  docs/templates/         # Plantillas de frontmatter
  registry.db             # Indice SQLite FTS5+WAL (regenerable con fn index, excepto proposals)

Build

CGO_ENABLED=1 go build -tags fts5 ./...
CGO_ENABLED=1 go test -tags fts5 ./...
CGO_ENABLED=1 go build -tags fts5 -o fn ./cmd/fn/

CLI completo (cmd/fn)

Registry

fn index                                   # Regenera registry.db desde los .md
fn search "texto"                          # Busqueda FTS en functions + types
fn search -k function -p pure -d core "slice"
fn list                                    # Lista todo
fn list -d finance -k function             # Lista por dominio y kind
fn show filter_slice_go_core               # Muestra entrada completa
fn add -k function                         # Muestra template para copiar

Proposals

fn proposal add --kind new_function --title "..." --created-by agent [--target-id <id>] [--evidence '{}'] [--description "..."]
fn proposal list [-k kind] [-s status]
fn proposal show <id>
fn proposal update <id> --status approved [--reviewed-by lucas]

Operations

fn ops init [path]                         # Crea operations.db en el directorio
fn ops help                                # Ayuda

# Entities
fn ops entity add --id <id> --name <n> --type-ref <tipo> --source <src> [--metadata '{}'] [--domain d] [--tags t1,t2]
fn ops entity list [--domain d] [--status s]
fn ops entity show <id>
fn ops entity delete <id>

# Relations
fn ops relation add --id <id> --name <n> --from <eid> --to <eid> [--via <fn_id>] [--direction uni] [--status designed]
fn ops relation list [--from <eid>]
fn ops relation show <id>
fn ops relation delete <id>

# Graph
fn ops graph                               # ASCII graph de entities y relations

# Snapshots
fn ops snapshot list                       # Lista type snapshots
fn ops snapshot check                      # Compara snapshots vs registry actual
fn ops snapshot update <type_id>|--all     # Re-snapshot desde registry

# Executions
fn ops execution add --pipeline-id <id> --status success [--started-at <ts>] [--ended-at <ts>] [--records-in N] [--records-out N] [--metrics '{}'] [--error "msg"]
fn ops execution list [--pipeline-id <id>] [--relation-id <id>] [-s status]
fn ops execution show <id>

# Assertions
fn ops assertion add --entity-id <id> --name "close positivo" --kind range --rule "close > 0" --severity critical [--description "..."]
fn ops assertion list [--entity-id <id>] [--active] [--inactive]
fn ops assertion show <id>                 # Incluye ultimos 5 resultados
fn ops assertion delete <id>
fn ops assertion eval --entity-id <id> [--execution-id <id>] [--react]    # Evalua assertions activas

# Assertion results (registro manual)
fn ops assertion result add --assertion-id <id> --status pass|fail|skip [--execution-id <id>] [--value '{}'] [--message "..."]
fn ops assertion result list [--assertion-id <id>] [--execution-id <id>]

FN_REGISTRY_ROOT env var permite que fn ops acceda a registry.db desde cualquier directorio.


Reglas para añadir funciones nuevas

Antes de crear

  1. Consulta la BD para verificar que no existe algo similar:
    sqlite3 registry.db "SELECT id, description FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'tu busqueda');"
    
  2. Identifica el dominio correcto: core, finance, datascience, cybersecurity, o crea uno nuevo si no encaja
  3. Decide la purity: pure si no tiene side effects, impure si tiene IO/estado/goroutines/tiempo

Archivos a crear

Cada funcion requiere EXACTAMENTE dos archivos:

  1. Implementacion functions/{domain}/{name}.go — codigo real, compilable
  2. Documentacion functions/{domain}/{name}.md — frontmatter YAML con metadata

Formato del .md (frontmatter YAML)

---
name: nombre_snake_case
kind: function                    # function | pipeline | component
lang: go                          # go | python | typescript | sql
domain: core                      # core | finance | datascience | cybersecurity | ...
version: "1.0.0"
purity: pure                      # pure | impure
signature: "func NombreCompleto(...) ..."
description: "Descripcion en español de que hace y cuando usarla."
tags: [tag1, tag2, tag3]
uses_functions: []                 # IDs de funciones del registry que invoca
uses_types: []                     # IDs de tipos del registry que recibe
returns: []                        # IDs de tipos del registry que devuelve
returns_optional: false
error_type: ""                     # ID de tipo de error, obligatorio si impure
imports: []                        # dependencias externas fuera del registry
tested: false
tests: []
test_file_path: ""
file_path: "functions/{domain}/{name}.go"
---

## Ejemplo

` `` go
resultado := MiFuncion(input)
` ``

## Notas

Explicacion adicional si es necesario.

Reglas de integridad (el indexer las valida)

Regla Condicion
Pipeline siempre impuro kind: pipelinepurity: impure + uses_functions no vacio
Pura sin side effects purity: purereturns_optional: false + error_type: ""
Impura declara errores purity: impureerror_type obligatorio (usar error_go_core)
Tests coherentes tested: truetest_file_path y tests obligatorios
Referencias validas uses_functions, uses_types, returns, error_type deben apuntar a IDs existentes
Component tiene framework kind: componentframework obligatorio, returns vacio (usar emits)
Rutas relativas file_path siempre relativa a la raiz, nunca absoluta
IDs unicos Formato {name}_{lang}_{domain}, colisiones rechazadas

Campo returns vs tipo nativo

El campo returns en el .md es para IDs de tipos del registry (ej: ohlcv_go_finance), NO para tipos nativos de Go (float64, string, bool). Si la funcion devuelve tipos nativos, deja returns: [].

Despues de crear

# Regenerar el indice
./fn index
# o
CGO_ENABLED=1 go build -tags fts5 -o fn ./cmd/fn/ && ./fn index

# Verificar que se indexo sin errores
./fn show {name}_{lang}_{domain}

Reglas para añadir tipos nuevos

Cada tipo requiere dos archivos: types/{domain}/{name}.go y types/{domain}/{name}.md.

---
name: nombre_snake_case
lang: go
domain: core
version: "1.0.0"
algebraic: product                 # product (struct) | sum (interface/union)
definition: |
  type MiTipo struct { ... }
description: "Descripcion en español."
tags: [tag1, tag2]
uses_types: []                     # IDs de otros tipos que compone (sin auto-referencias)
file_path: "types/{domain}/{name}.go"
---

Convenciones

  • IDs: {name}_{lang}_{domain} (ej: filter_slice_go_core)
  • Nombres: snake_case para funciones, PascalCase para tipos en Go
  • Paquete Go: el nombre del directorio (core, finance, datascience, cybersecurity)
  • Tipos en firmas: usar tipos nativos (float64, []float64, string) para evitar imports circulares entre paquetes de funciones. Documentar los tipos del registry en uses_types/returns del .md
  • Purity: puras en el centro, impuras en los bordes. Una pura NUNCA depende de una impura
  • Stubs impuros: si la implementacion real requiere dependencias externas no disponibles, crear stub con return ..., fmt.Errorf("not implemented") y documentar completamente el .md
  • Assertions: kind es texto libre — puedes inventar nuevos kinds sin tocar schema
  • Proposals: las crea el bucle reactivo automaticamente (created_by: reactive_loop) o el humano/agente manualmente

Fuentes de verdad

Que Donde
Codigo archivos .go / .py / .tsx
Documentacion archivos .md junto al codigo
Diseño del schema carpeta docs/
Indice de busqueda registry.db (regenerable con fn index)
Proposals registry.db tabla proposals (NO regenerable, son datos vivos)
Entities, relations operations.db por proyecto (datos vivos)
Executions, assertions operations.db por proyecto (datos vivos)

Importante: fn index regenera functions y types desde los .md pero NO toca proposals. Las proposals, entities, relations, executions, assertions y assertion_results son datos vivos que persisten.


Codigo Go: patrones clave

JSON en columnas TEXT

Arrays y objetos se guardan como JSON serializado en columnas TEXT:

marshalStrings([]string) string    // ["a","b"] → string
unmarshalStrings(string) []string  // string → ["a","b"]
marshalJSON(map[string]any) string // {k:v} → string
unmarshalJSON(string) map[string]any

Validacion

Acumula errores, retorna nil o *ValidationError:

func ValidateX(x *X) *ValidationError {
    var errs []string
    if x.Name == "" { errs = append(errs, "name required") }
    if len(errs) > 0 { return &ValidationError{ID: x.ID, Errors: errs} }
    return nil
}

Migraciones

//go:embed migrations/*.sql
var migrationsFS embed.FS

func migrate(conn *sql.DB) error { ... } // aplica pendientes en transacciones

Cycle detection

Solo relaciones causales (via != "") se verifican. BFS desde to_entity buscando from_entity.