# 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. ```bash # 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 ```bash 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 ```bash 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 ```bash fn proposal add --kind new_function --title "..." --created-by agent [--target-id ] [--evidence '{}'] [--description "..."] fn proposal list [-k kind] [-s status] fn proposal show fn proposal update --status approved [--reviewed-by lucas] ``` ### Operations ```bash fn ops init [path] # Crea operations.db en el directorio fn ops help # Ayuda # Entities fn ops entity add --id --name --type-ref --source [--metadata '{}'] [--domain d] [--tags t1,t2] fn ops entity list [--domain d] [--status s] fn ops entity show fn ops entity delete # Relations fn ops relation add --id --name --from --to [--via ] [--direction uni] [--status designed] fn ops relation list [--from ] fn ops relation show fn ops relation delete # 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 |--all # Re-snapshot desde registry # Executions fn ops execution add --pipeline-id --status success [--started-at ] [--ended-at ] [--records-in N] [--records-out N] [--metrics '{}'] [--error "msg"] fn ops execution list [--pipeline-id ] [--relation-id ] [-s status] fn ops execution show # Assertions fn ops assertion add --entity-id --name "close positivo" --kind range --rule "close > 0" --severity critical [--description "..."] fn ops assertion list [--entity-id ] [--active] [--inactive] fn ops assertion show # Incluye ultimos 5 resultados fn ops assertion delete fn ops assertion eval --entity-id [--execution-id ] [--react] # Evalua assertions activas # Assertion results (registro manual) fn ops assertion result add --assertion-id --status pass|fail|skip [--execution-id ] [--value '{}'] [--message "..."] fn ops assertion result list [--assertion-id ] [--execution-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: ```bash 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) ```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: pipeline` → `purity: impure` + `uses_functions` no vacio | | Pura sin side effects | `purity: pure` → `returns_optional: false` + `error_type: ""` | | Impura declara errores | `purity: impure` → `error_type` obligatorio (usar `error_go_core`) | | Tests coherentes | `tested: true` → `test_file_path` y `tests` obligatorios | | Referencias validas | `uses_functions`, `uses_types`, `returns`, `error_type` deben apuntar a IDs existentes | | Component tiene framework | `kind: component` → `framework` 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 ```bash # 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`. ```yaml --- 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: ```go 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: ```go 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 //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.