syntaxis/docs/provision/validation-registry-nushell.md
Jesús Pérez 9cef9b8d57 refactor: consolidate configuration directories
Merge _configs/ into config/ for single configuration directory.
Update all path references.

Changes:
- Move _configs/* to config/
- Update .gitignore for new patterns
- No code references to _configs/ found

Impact: -1 root directory (layout_conventions.md compliance)
2025-12-26 18:36:23 +00:00

25 KiB
Raw Permalink Blame History

🔍 Validación y Registro con NuShell + KCL

Fecha: 2025-11-20 Clasificación: Implementación operacional Enfoque: Automatización via NuShell + Leveraging KCL native capabilities


📋 Tabla de Contenidos

  1. Visión General
  2. Validación Automática (KCL nativa)
  3. Scripts NuShell para Validación
  4. Sistema de Registro (Service Discovery)
  5. Integración en CI/CD
  6. Monitoreo y Alertas

🎯 Visión General

KCL proporciona:
├─ Validación de esquemas (tipos)
├─ Detección de ciclos de dependencias
├─ Validación de referencias de servicios
├─ Detección de conflictos de puertos
└─ Validación de recursos (CPU/Memory)
    → TODO esto "on the fly" en compilación

NuShell proporciona:
├─ Orquestación de comandos
├─ Parsing de salidas
├─ CI/CD integration
├─ Generación de reportes
└─ Alertas y notificaciones

Flujo Completo

┌──────────────────────────────────────────────┐
│ Cambio en services.k (developer comitea)     │
└──────────────────────────────────────────────┘
              ↓ (git push)
┌──────────────────────────────────────────────┐
│ GitHub Actions / GitLab CI                    │
└──────────────────────────────────────────────┘
              ↓
    validate-catalog.nu
              ↓
    ┌─────────────────────────────────────────┐
    │ 1. KCL type-check                       │
    │    └─ Detecta errores de esquema        │
    │                                          │
    │ 2. Ciclos de dependencias                │
    │    └─ KCL políticas (policies.k)        │
    │                                          │
    │ 3. Conflictos de puertos                 │
    │    └─ Script NuShell analiza            │
    │                                          │
    │ 4. Referencias válidas                   │
    │    └─ KCL valida existencia             │
    │                                          │
    │ 5. Cambios compatibles                   │
    │    └─ Script compara con versión anterior
    └─────────────────────────────────────────┘
              ↓
        ¿VÁLIDO?
         ↙      ↘
        SÍ      NO
        ↓       ↓
      ✅     ❌ Reject + Notificar
      Deploy

Validación Automática (KCL nativa)

1. Qué Valida KCL Automáticamente

# El compilador de KCL RECHAZA automáticamente:

# ❌ Tipo incorrecto
service = {
    id: 123  # ERROR: Esperaba str, recibió int
}

# ❌ Campo requerido faltante
service = {
    id: "api"
    # ERROR: name es requerido
}

# ❌ Referencia a servicio inexistente
depends_on: ["service-que-no-existe"]  # ERROR en validación

# ❌ Puerto duplicado en mismo host
ports: [
    {port: 3000},
    {port: 3000}  # ERROR: Puerto duplicado
]

# ❌ Ciclo de dependencias
service_a = { depends_on: ["service_b"] }
service_b = { depends_on: ["service_a"] }  # ERROR: Ciclo detectado

2. Política de Validación: No Ciclos

# policies.k - Validación nativa

# Schema base que define estructura y tipos
Service = {
    id: str
    name: str
    depends_on: [str]
    port: int
    # ... más campos
}

# Función para detectar ciclos
validate_no_dependency_cycles = lambda services {
    """
    KCL aplica esta validación automáticamente
    al compilar las definiciones de servicios
    """

    def has_cycle(service_id, visited, rec_stack, graph):
        visited[service_id] = True
        rec_stack[service_id] = True

        for dep in graph.get(service_id, []):
            if dep not in visited:
                if has_cycle(dep, visited, rec_stack, graph):
                    return True
            elif rec_stack.get(dep):
                return True

        rec_stack[service_id] = False
        return False

    # Construir grafo de dependencias
    graph = {s.id: s.depends_on for s in services}

    # Validar cada servicio
    for service_id in graph:
        assert not has_cycle(service_id, {}, {}, graph), \
            "Cyclic dependency detected in service: {}".format(service_id)
}

# Validar que referencias existen
validate_all_dependencies_exist = lambda services {
    service_ids = {s.id for s in services}

    for service in services:
        for dep_id in service.depends_on:
            assert dep_id in service_ids, \
                "Service '{}' depends on non-existent service '{}'".format(
                    service.id, dep_id
                )
}

# Validar puertos sin conflicto
validate_no_port_conflicts = lambda services {
    port_to_service = {}

    for service in services:
        for port in service.ports:
            port_num = port.port
            assert port_num not in port_to_service, \
                "Port {} used by both '{}' and '{}'".format(
                    port_num,
                    port_to_service[port_num],
                    service.id
                )
            port_to_service[port_num] = service.id
}

# Validar restricciones de recursos
validate_resource_constraints = lambda services {
    # CPU/Memory deben cumplir formato y restricciones
    for service in services:
        assert is_valid_cpu_format(service.resources.cpu), \
            "Invalid CPU format: {}".format(service.resources.cpu)
        assert is_valid_memory_format(service.resources.memory), \
            "Invalid memory format: {}".format(service.resources.memory)
}

3. Ejecución: KCL Valida en Compilación

# KCL compila y valida automáticamente
$ kcl compile services.k

# Compila exitosamente SOLO si:
# ✅ Todos los tipos son correctos
# ✅ No hay ciclos de dependencias
# ✅ Todas las referencias existen
# ✅ No hay conflictos de puertos
# ✅ Recursos tienen formato válido

# Si hay error:
$ kcl compile services.k
Error: Service 'api' depends on non-existent service 'db'
  at services.k:42:15

# El error previene deployment automáticamente

🔧 Scripts NuShell para Validación

1. validate-catalog.nu - Script Principal

#!/usr/bin/env nu

# Script de validación completa del catálogo
# Uso: nu validate-catalog.nu [--check-only] [--verbose]

# Configuración
const KCL_PATH = "provisioning/services.k"
const PATTERNS_PATH = "provisioning/patterns.k"
const POLICIES_PATH = "provisioning/policies.k"
const REPORT_FILE = "validation-report.json"

# Colores
const GREEN = "[32m"
const RED = "[91m"
const YELLOW = "[33m"
const BLUE = "[34m"
const RESET = "[0m"

# Parsear argumentos
let check_only = ($env.COMMAND_LINE? | str contains "--check-only")
let verbose = ($env.COMMAND_LINE? | str contains "--verbose")

print $"($BLUE)🔍 Iniciando validación del catálogo($RESET)"
print ""

# 1. VALIDACIÓN KCL: Type-checking
print $"($BLUE)1⃣  Validación de tipos KCL($RESET)"
try {
    let kcl_output = (kcl compile $KCL_PATH 2>&1 | str trim)

    if ($kcl_output =~ "Error:") {
        print $"($RED)✗ Error de compilación KCL($RESET)"
        print $kcl_output
        exit 1
    } else {
        print $"($GREEN)✓ Tipos KCL válidos($RESET)"
    }
} catch {
    print $"($RED)✗ KCL no está instalado o error en compilación($RESET)"
    exit 1
}

print ""

# 2. VALIDACIÓN: Ciclos de Dependencias
print $"($BLUE)2⃣  Validación: Ciclos de dependencias($RESET)"
let dep_check = (nu scripts/check-dependencies.nu $KCL_PATH)
if ($dep_check.has_cycles) {
    print $"($RED)✗ Ciclos detectados en dependencias($RESET)"
    print $dep_check.cycles
    exit 1
} else {
    print $"($GREEN)✓ Sin ciclos de dependencias($RESET)"
    if $verbose {
        print $"  Servicios validados: ($dep_check.total_services)"
        print $"  Dependencias únicas: ($dep_check.total_dependencies)"
    }
}

print ""

# 3. VALIDACIÓN: Conflictos de Puertos
print $"($BLUE)3⃣  Validación: Conflictos de puertos($RESET)"
let port_check = (nu scripts/check-ports.nu $KCL_PATH)
if ($port_check.has_conflicts) {
    print $"($RED)✗ Conflictos de puertos detectados($RESET)"
    print $port_check.conflicts
    exit 1
} else {
    print $"($GREEN)✓ Sin conflictos de puertos($RESET)"
    if $verbose {
        print $"  Puertos mapeados: ($port_check.total_ports)"
        let unique_ports = ($port_check.ports | length)
        print $"  Puertos únicos: $unique_ports"
    }
}

print ""

# 4. VALIDACIÓN: Compatibilidad hacia Atrás
print $"($BLUE)4⃣  Validación: Compatibilidad$(RESET)"
let compat_check = (nu scripts/check-compatibility.nu $KCL_PATH)
if ($compat_check.breaking_changes) {
    print $"($YELLOW)⚠️  Breaking changes detectados($RESET)"
    print $compat_check.changes
    if not $check_only {
        print $"($YELLOW)Continuar? (s/n)($RESET)"
        let response = (input)
        if $response != "s" {
            exit 1
        }
    }
} else {
    print $"($GREEN)✓ Compatible con versión anterior($RESET)"
}

print ""

# 5. VALIDACIÓN: Integridad de Patrones
print $"($BLUE)5⃣  Validación: Integridad de patrones($RESET)"
let pattern_check = (nu scripts/check-patterns.nu $PATTERNS_PATH $KCL_PATH)
if ($pattern_check.invalid_patterns | length) > 0 {
    print $"($RED)✗ Patrones inválidos($RESET)"
    for pattern in $pattern_check.invalid_patterns {
        print $"  - ($pattern.id): ($pattern.error)"
    }
    exit 1
} else {
    print $"($GREEN)✓ Todos los patrones válidos($RESET)"
    if $verbose {
        print $"  Total de patrones: ($pattern_check.total_patterns)"
    }
}

print ""

# 6. GENERACIÓN DE REPORTE
print $"($BLUE)6⃣  Generando reporte($RESET)"
let report = {
    timestamp: (date now),
    status: "passed",
    validations: {
        kcl_types: {status: "passed", message: "Tipos KCL válidos"}
        dependencies: {status: "passed", cycles: 0, total: $dep_check.total_dependencies}
        ports: {status: "passed", conflicts: 0, total: $port_check.total_ports}
        compatibility: {status: ($compat_check.breaking_changes | if . then "warning" else "passed" end)}
        patterns: {status: "passed", total: $pattern_check.total_patterns}
    }
}

$report | to json | save --force $REPORT_FILE
print $"($GREEN)✓ Reporte guardado en ($REPORT_FILE)($RESET)"

print ""
print $"($GREEN)═══════════════════════════════════════($RESET)"
print $"($GREEN)✅ Validación completada exitosamente($RESET)"
print $"($GREEN)═══════════════════════════════════════($RESET)"
print ""

# Salir con código de éxito
exit 0

2. check-dependencies.nu - Análisis de Dependencias

#!/usr/bin/env nu

# Analizar ciclos de dependencias en servicios
# Uso: nu check-dependencies.nu services.k

let kcl_file = $in

# Extraer servicios y dependencias de KCL
# Esto analiza la salida compilada de KCL
let services = (
    kcl compile $kcl_file 2>&1
    | grep "service"
    | each { |line|
        let parts = ($line | split column "=" | transpose)
        {
            id: ($parts.0.column1 | str trim)
            depends_on: ($parts.1.column1 | str trim | split row "," | each { |d| $d | str trim })
        }
    }
)

# Implementar detección de ciclos (algoritmo DFS)
def has_cycle [graph, node, visited, rec_stack] {
    $visited | upsert ($node | tostring) true
    $rec_stack | upsert ($node | tostring) true

    let neighbors = ($graph | get $node)
    for neighbor in $neighbors {
        if not ($visited | has ($neighbor | tostring)) {
            if (has_cycle $graph $neighbor $visited $rec_stack) {
                return true
            }
        } else if ($rec_stack | get ($neighbor | tostring)) {
            return true
        }
    }

    $rec_stack | upsert ($node | tostring) false
    false
}

# Construir grafo y detectar ciclos
let graph = (
    $services
    | reduce .[] as $service (
        {};
        . + {($service.id): $service.depends_on}
    )
)

let cycles = ($services | filter { |s|
    has_cycle $graph $s.id {} {}
})

{
    has_cycles: ($cycles | length) > 0
    cycles: $cycles
    total_services: ($services | length)
    total_dependencies: (
        $services
        | map { |s| $s.depends_on | length }
        | math sum
    )
}

3. check-ports.nu - Análisis de Puertos

#!/usr/bin/env nu

# Detectar conflictos de puertos
# Uso: nu check-ports.nu services.k

let kcl_file = $in

# Extraer puertos de KCL compilado
let ports = (
    kcl compile $kcl_file 2>&1
    | grep -E "port:\s*[0-9]+"
    | parse "{service}: {port}"
    | each {
        let port_num = ($in.port | into int)
        {
            service: $in.service
            port: $port_num
        }
    }
)

# Detectar conflictos
let grouped = (
    $ports
    | group-by { |p| $p.port }
)

let conflicts = (
    $grouped
    | filter { |group| ($group | length) > 1 }
)

{
    has_conflicts: ($conflicts | length) > 0
    conflicts: (
        $conflicts
        | map { |group|
            {
                port: ($group.0.port)
                services: ($group | map { |p| $p.service })
            }
        }
    )
    total_ports: ($ports | length)
    ports: $ports
}

4. check-patterns.nu - Validación de Patrones

#!/usr/bin/env nu

# Validar que patrones referencian servicios existentes
# Uso: nu check-patterns.nu patterns.k services.k

let patterns_file = $in.0
let services_file = $in.1

# Obtener lista de servicios
let valid_services = (
    kcl compile $services_file 2>&1
    | grep "id:"
    | parse '{id: "{service}"}'
    | map { |row| $row.service }
)

# Validar cada patrón
let patterns = (
    kcl compile $patterns_file 2>&1
    | parse '{id: "{pattern_id}", services: [{services}]}'
)

let invalid = (
    $patterns
    | filter { |pattern|
        let pattern_services = (
            $pattern.services
            | split row ","
            | each { |s| $s | str trim }
        )

        let invalid_services = (
            $pattern_services
            | filter { |svc| not ($svc in $valid_services) }
        )

        ($invalid_services | length) > 0
    }
    | map { |pattern|
        {
            id: $pattern.pattern_id
            error: "Contiene referencias a servicios inexistentes"
        }
    }
)

{
    total_patterns: ($patterns | length)
    invalid_patterns: $invalid
    status: (if ($invalid | length) == 0 { "passed" } else { "failed" })
}

5. check-compatibility.nu - Cambios hacia Atrás

#!/usr/bin/env nu

# Detectar breaking changes respecto a versión anterior
# Uso: nu check-compatibility.nu services.k

let kcl_file = $in

# Obtener versión actual desde git
let current = (kcl compile $kcl_file 2>&1)

# Obtener versión anterior
let previous = (git show HEAD:$kcl_file | kcl compile - 2>&1)

# Detectar cambios
let added_services = (
    $current
    | grep "id:"
    | split row ","
    | map { |s| $s | str trim }
)

let removed_services = (
    $previous
    | grep "id:"
    | split row ","
    | map { |s| $s | str trim }
    | filter { |s| not ($s in $added_services) }
)

let breaking = ($removed_services | length) > 0

{
    breaking_changes: $breaking
    changes: {
        removed_services: $removed_services
        summary: if $breaking {
            "WARNING: Servicios removidos (breaking change)"
        } else {
            "No breaking changes"
        }
    }
}

📦 Sistema de Registro (Service Discovery)

1. service-catalog.nu - Registro Principal

#!/usr/bin/env nu

# Gestor de catálogo de servicios
# Uso: nu service-catalog.nu [list|ports|groups|patterns]

const KCL_SERVICES_FILE = "provisioning/services.k"
const KCL_PATTERNS_FILE = "provisioning/patterns.k"

def list [] {
    """
    Listar todos los servicios disponibles
    """
    print "📋 Servicios disponibles:"
    print ""

    let services = (
        kcl compile $KCL_SERVICES_FILE 2>&1
        | parse '{id: "{id}", name: "{name}", image: "{image}"}'
    )

    $services
    | each { |s|
        print $"  • ($s.id) - ($s.name)"
        print $"    imagen: ($s.image)"
    }
}

def ports [] {
    """
    Mostrar mapeo de puertos
    """
    print "🔌 Mapeo de puertos:"
    print ""

    let ports = (
        kcl compile $KCL_SERVICES_FILE 2>&1
        | parse '{service: "{service}", port: {port}}'
    )

    let sorted = ($ports | sort-by port)

    $sorted
    | each { |p|
        print $"  ($p.port) → ($p.service)"
    }
}

def groups [] {
    """
    Listar grupos de servicios
    """
    print "📦 Grupos de servicios:"
    print ""

    let groups = (
        kcl compile $KCL_SERVICES_FILE 2>&1
        | grep "group:"
        | parse '{group: "{group}", services: [{services}]}'
    )

    $groups
    | each { |g|
        print $"  ($g.group):"
        print $"    servicios: ($g.services | str trim)"
    }
}

def patterns [] {
    """
    Listar patrones de despliegue disponibles
    """
    print "🎯 Patrones de despliegue:"
    print ""

    let patterns = (
        kcl compile $KCL_PATTERNS_FILE 2>&1
        | parse '{id: "{id}", name: "{name}", environment: "{environment}"}'
    )

    $patterns
    | each { |p|
        print $"  ($p.id) - ($p.name) [($p.environment)]"
    }
}

def search [query: string] {
    """
    Buscar servicios por nombre o tag
    """
    print $"🔍 Buscando: ($query)"
    print ""

    let services = (
        kcl compile $KCL_SERVICES_FILE 2>&1
        | parse '{id: "{id}", name: "{name}", tags: [{tags}]}'
        | filter { |s|
            ($s.id | str contains $query) or
            ($s.name | str contains $query) or
            ($s.tags | str contains $query)
        }
    )

    if ($services | length) == 0 {
        print "  ❌ No se encontraron servicios"
    } else {
        $services | each { |s|
            print $"  • ($s.id)"
        }
    }
}

def dependencies [service: string] {
    """
    Mostrar dependencias de un servicio
    """
    print $"📍 Dependencias de ($service):"
    print ""

    let deps = (
        kcl compile $KCL_SERVICES_FILE 2>&1
        | parse '{id: "{id}", depends_on: [{depends}]}'
        | filter { |s| $s.id == $service }
    )

    if ($deps | length) == 0 {
        print "  ❌ Servicio no encontrado"
    } else {
        let depends = ($deps.0.depends | split row "," | each { |d| $d | str trim })
        if ($depends | length) == 0 {
            print "  ✓ Sin dependencias"
        } else {
            $depends | each { |d| print $"  ← ($d)" }
        }
    }
}

# Parsear argumentos
let action = ($nu.env.ARGS? | get 0 | default "list")

match $action {
    "list" => { list }
    "ports" => { ports }
    "groups" => { groups }
    "patterns" => { patterns }
    "search" => { search ($nu.env.ARGS?.1 | default "") }
    "dependencies" => { dependencies ($nu.env.ARGS?.1 | default "") }
    _ => {
        print "Uso: nu service-catalog.nu [list|ports|groups|patterns|search|dependencies]"
    }
}

2. Auto-Discovery: Registro Automático

#!/usr/bin/env nu

# Auto-discover servicios y registrarlos en cache
# Uso: nu discover-services.nu

const CACHE_FILE = ".syntaxis/service-registry-cache.json"

print "🔍 Auto-discovering servicios..."

# 1. Compilar KCL
let kcl_services = (
    kcl compile provisioning/services.k 2>&1
)

# 2. Extraer información
let services = (
    $kcl_services
    | grep "service:"
    | parse '{id: "{id}", port: {port}}'
)

# 3. Enrichir con información adicional
let enriched = (
    $services
    | each { |service|
        let details = (
            $kcl_services
            | grep $service.id
            | parse '{status: "active"}'
        )

        {
            id: $service.id
            port: $service.port
            status: "active"
            discovered_at: (date now)
        }
    }
)

# 4. Guardar en cache
mkdir -p (.syntaxis)
$enriched | to json | save --force $CACHE_FILE

print $"✅ ($enriched | length) servicios registrados"
print $"📁 Cache guardado en ($CACHE_FILE)"

🔄 Integración en CI/CD

1. GitHub Actions Workflow

# .github/workflows/validate-catalog.yml

name: Validate Service Catalog

on:
  push:
    paths:
      - 'provisioning/services.k'
      - 'provisioning/patterns.k'
      - 'provisioning/policies.k'
  pull_request:
    paths:
      - 'provisioning/services.k'
      - 'provisioning/patterns.k'

jobs:
  validate:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Install NuShell
        run: cargo install nushell

      - name: Install KCL
        run: |
          wget https://github.com/kcl-lang/kcl/releases/download/v0.8.0/kcl-v0.8.0-linux-x86_64.tar.gz
          tar xzf kcl-v0.8.0-linux-x86_64.tar.gz
          sudo mv kcl-v0.8.0/bin/kcl /usr/local/bin/

      - name: Run validation
        run: nu validate-catalog.nu --verbose

      - name: Upload report
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: validation-report
          path: validation-report.json

      - name: Comment on PR
        if: failure()
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '❌ Validación de catálogo fallida. Ver detalles en los artifacts.'
            })

2. Pre-Commit Hook

# .git/hooks/pre-commit

#!/bin/bash

echo "🔍 Validando catálogo antes de commit..."

if nu validate-catalog.nu --check-only; then
    echo "✅ Catálogo válido"
    exit 0
else
    echo "❌ Validación fallida. Resuelve los errores antes de commitear."
    exit 1
fi

📊 Monitoreo y Alertas

1. monitor-catalog.nu - Monitoreo Continuo

#!/usr/bin/env nu

# Monitorear cambios en el catálogo
# Uso: nu monitor-catalog.nu

const WATCH_FILE = "provisioning/services.k"
const CHECK_INTERVAL = 5  # segundos

print "👁️  Monitoreando catálogo de servicios..."
print ""

let last_hash = (
    open $WATCH_FILE
    | hash sha256
)

loop {
    let current_hash = (
        open $WATCH_FILE
        | hash sha256
    )

    if $current_hash != $last_hash {
        print ""
        print "📝 Cambio detectado en ($WATCH_FILE)"
        print $"⏰ (date now | format date '%H:%M:%S')"

        # Ejecutar validación
        let validation = (nu validate-catalog.nu 2>&1)

        if ($validation | str contains "✅") {
            print "✅ Validación pasada"
        } else {
            print "❌ Validación fallida"
            # Enviar alerta
            # slack-notify "Validación de catálogo fallida"
        }

        # Actualizar hash
        let last_hash = $current_hash
    }

    sleep 5sec
}

2. Alertas por Slack

#!/usr/bin/env nu

# Enviar alerta a Slack
# Uso: nu alert.nu "mensaje"

def slack_notify [message: string] {
    let webhook_url = $env.SLACK_WEBHOOK_URL?

    if ($webhook_url | is-empty) {
        print "⚠️  SLACK_WEBHOOK_URL no configurado"
        return
    }

    let payload = {
        text: "⚠️ Catalog Validation Alert"
        blocks: [
            {
                type: "section"
                text: {
                    type: "mrkdwn"
                    text: $message
                }
            }
        ]
    }

    http post $webhook_url ($payload | to json)
    print "✅ Alerta enviada a Slack"
}

# Uso
let message = "❌ Validación fallida\nVerifica el reporte: https://..."
slack_notify $message

🎯 Resumen: KCL vs NuShell

Responsabilidad KCL NuShell
Validación de tipos Automática en compilación -
Ciclos de dependencias Automática en compilación Verificación adicional
Conflictos de puertos Esquema KCL Análisis detallado
Referencias válidas Type system Cross-check
Registro/Discovery - Auto-discovery + cache
Orquestación - Scripts y workflows
CI/CD Integration - Pipelines GitHub/GitLab
Reportes - JSON + alertas

📋 Checklist de Implementación

  • Crear policies.k con validaciones
  • Crear validate-catalog.nu
  • Crear check-dependencies.nu
  • Crear check-ports.nu
  • Crear check-patterns.nu
  • Crear service-catalog.nu (registro)
  • Crear .git/hooks/pre-commit
  • Crear .github/workflows/validate-catalog.yml
  • Crear monitor-catalog.nu
  • Setup alertas Slack
  • Documentar CLI commands para DevOps
  • Test con cambios (breaking y non-breaking)

Conclusión: KCL proporciona validación automática "on the fly" en compilación. NuShell orquesta la validación, gestiona el registro, e integra con CI/CD. Juntos proporcionan un sistema robusto sin necesidad de recodificar lógica de validación en Rust.