![SYNTAXIS Logo](assets/syntax_logo_a.svg) # ✅ ARQUITECTURA CORRECTA - Declaración KCL Modular en Syntaxis **Fecha**: 2025-11-20 **Estado**: Validado y correcto **Patrón**: Declaración local, consumo adaptativo --- ## 🎯 La Solución Correcta en Una Página ``` syntaxis/configs/*.k (MODULAR KCL) ↓ ÚNICA fuente de verdad ├─ deployment.k (principal) ├─ services.k (módulo) ├─ presets.k (módulo) ├─ schemas.k (tipos + validación) └─ defaults.k (valores + constraints) NO hay generación almacenada: ✅ kcl export on-demand (stdout → temporal) ✅ Tera templates para adaptación ✅ Consumo adaptativo (provisioning/provctl/installer) Validación: ✅ Nativa en KCL (compile-time) ✅ Schemas con constraints ✅ Defaults inteligentes ✅ Herencia de presets ``` --- ## 📐 Estructura de Archivos KCL Modular ### Ubicación ``` syntaxis/ ├── Cargo.toml ├── src/ ├── configs/ │ ├── deployment.k ← MAIN: Define todo │ ├── services.k ← MODULO: Definiciones de servicios │ ├── presets.k ← MODULO: Presets (local/dev/staging/prod) │ ├── schemas.k ← MODULO: Types y validación │ └── defaults.k ← MODULO: Valores por defecto + constraints ├── templates/ │ ├── provisioning-config.j2 ← Para provisioning │ ├── provctl-config.j2 ← Para provctl │ └── installer-settings.j2 ← Para installer interactivo └── scripts/ └── export-config.nu ← NuShell: export KCL → YAML/JSON ``` --- ## 🔧 Archivo 1: schemas.k (Tipos y Validación) ```kcl # syntaxis/configs/schemas.k # Definiciones de tipos y esquemas con validación nativa import regex # Service Schema schema Service: """Define un servicio de syntaxis""" name: str display_name: str description: str type: "cli" | "tui" | "server" | "web" | "database" | "messaging" # Validación: port requerido si type es service port?: int check: port is not None if type in ["server", "web", "database", "messaging"], "Service con puerto requiere port" check: 1 <= port <= 65535 if port is not None, "Port debe estar entre 1-65535" enabled: bool = False requires: [str] = [] optional: [str] = [] # Configuración operativa restart_policy: "always" | "on-failure" | "never" = "on-failure" restart_delay_seconds: int = 5 # Binario o ejecutable binary?: { name: str installed_to: str } # Health check health_check?: { type: "http" | "tcp" | "script" endpoint?: str method?: str = "GET" expected_status?: int = 200 interval_seconds: int = 10 timeout_seconds: int = 5 } # Metadata operativa platform_support: [str] = ["linux", "macos", "windows"] min_disk_space_mb: int = 50 min_memory_mb: int = 64 background_service: bool = False # Configuración config_location?: str database_required: bool = False database_types: [str] = ["sqlite", "surrealdb"] # Database Schema schema Database: """Configuración de base de datos""" type: "sqlite" | "postgres" | "mysql" | "surrealdb" # Validación por tipo path?: str check: path is not None if type == "sqlite", "SQLite requiere path" host?: str = "localhost" check: host is not None if type != "sqlite", "BD remota requiere host" port?: int user?: str = "syntaxis" password?: str # Cache Schema schema Cache: """Configuración de caché""" enabled: bool = False type: "redis" | "memcached" host: str = "localhost" port: int = 6379 # Preset Schema schema Preset: """Define un preset de deployment""" name: str description: str services_enabled: [str] # Overrides específicos del preset database_type?: "sqlite" | "postgres" | "mysql" | "surrealdb" cache_enabled?: bool # Manager para este preset manager: "manual" | "provctl" | "provisioning" = "manual" # Full Deployment Schema schema Deployment: """Configuración completa de deployment""" project: str = "syntaxis" version: str services: {str: Service} presets: {str: Preset} default_database: Database default_cache: Cache # Validación: presets deben referenciar servicios válidos check: [ s in services.keys() for p in presets.values() for s in p.services_enabled ] == p.services_enabled, "Preset referencia servicio inexistente" ``` --- ## 🔧 Archivo 2: defaults.k (Valores y Constraints) ```kcl # syntaxis/configs/defaults.k # Valores por defecto y constraints reutilizables import .schemas # Servicios por defecto default_services: {str: schemas.Service} = { cli = schemas.Service { name = "syntaxis-cli" type = "binary" enabled = True } api = schemas.Service { name = "syntaxis-api" type = "service" port = 3000 enabled = False requires = ["database"] health_check = { type = "http" endpoint = "http://127.0.0.1:3000/health" interval_seconds = 10 timeout_seconds = 5 } } tui = schemas.Service { name = "syntaxis-tui" type = "binary" enabled = False } dashboard = schemas.Service { name = "syntaxis-dashboard" type = "service" port = 8080 enabled = False requires = ["api"] health_check = { type = "http" endpoint = "http://127.0.0.1:8080" } } database = schemas.Service { name = "surrealdb" type = "database" port = 8000 enabled = False } } # Bases de datos por defecto default_databases: {str: schemas.Database} = { sqlite = schemas.Database { type = "sqlite" path = "/var/lib/syntaxis/db.sqlite" } postgres = schemas.Database { type = "postgres" host = "localhost" port = 5432 user = "syntaxis" } surrealdb = schemas.Database { type = "surrealdb" host = "localhost" port = 8000 } } # Caché por defecto default_cache: schemas.Cache = schemas.Cache { enabled = False type = "redis" host = "localhost" port = 6379 } # Presets por defecto (sin overrides específicos aún) base_presets: {str: schemas.Preset} = { local = schemas.Preset { name = "local" description = "Single machine, CLI only (manual execution)" services_enabled = ["cli"] manager = "manual" } dev = schemas.Preset { name = "dev" description = "Full development stack with provctl orchestration" services_enabled = ["cli", "api", "tui", "dashboard", "database"] manager = "provctl" database_type = "sqlite" cache_enabled = False } staging = schemas.Preset { name = "staging" description = "Staging environment on multiple machines" services_enabled = ["cli", "api", "dashboard", "database"] manager = "provisioning" database_type = "postgres" cache_enabled = True } production = schemas.Preset { name = "production" description = "Production environment with high availability" services_enabled = ["api", "dashboard", "database"] manager = "provisioning" database_type = "postgres" cache_enabled = True } } ``` --- ## 🔧 Archivo 3: services.k (Definiciones de Servicios) ```kcl # syntaxis/configs/services.k # Definiciones de servicios específicas (puede refinarse) import .schemas import .defaults # Servicios con defaults aplicados services: {str: schemas.Service} = default_services | { # Puede personalizar servicios aquí si es necesario # Por ahora usa los defaults } ``` --- ## 🔧 Archivo 4: presets.k (Presets Parametrizables) ```kcl # syntaxis/configs/presets.k # Definiciones de presets con posibilidad de override import .schemas import .defaults import .services # Presets con defaults y capacidad de personalización presets: {str: schemas.Preset} = { local = defaults.base_presets.local dev = defaults.base_presets.dev { # Puede refinarse aquí si necesita overrides específicos # Por defecto usa los defaults } staging = defaults.base_presets.staging { # Overrides específicos si es necesario } production = defaults.base_presets.production { # Overrides específicos si es necesario } } # Función auxiliar para obtener preset con parámetros [params] def get_preset(name: str, overrides: {str: str}) -> schemas.Preset: """Obtiene un preset y aplica overrides dinamámicos""" preset = presets[name] # Aplicar overrides si existen if "database_type" in overrides: preset.database_type = overrides["database_type"] if "cache_enabled" in overrides: preset.cache_enabled = overrides["cache_enabled"] == "true" preset ``` --- ## 🔧 Archivo 5: deployment.k (Declaración Principal) ```kcl # syntaxis/configs/deployment.k # Declaración principal que integra todo import .schemas import .services import .presets import .defaults # Deployment principal deployment: schemas.Deployment = schemas.Deployment { project = "syntaxis" version = "1.0.0" services = services presets = presets default_database = defaults.default_databases.sqlite default_cache = defaults.default_cache } # Export principales para consumidores export { deployment services presets default_databases default_cache } ``` --- ## 📜 Templates Tera para Adaptación ### Template 1: provisioning-config.j2 ```jinja2 {# syntaxis/templates/provisioning-config.j2 #} {# Genera configuración para provisioning desde KCL #} # Configuración Syntaxis para provisioning # Generado desde syntaxis/configs/deployment.k project: name = "{{ deployment.project }}" version = "{{ deployment.version }}" services: {% for name, service in deployment.services %} {% if service.enabled %} "{{ name }}": type = "{{ service.type }}" {% if service.port %} port = {{ service.port }} {% endif %} restart_policy = "{{ service.restart_policy }}" {% if service.health_check %} health_check = { type = "{{ service.health_check.type }}" {% if service.health_check.endpoint %} endpoint = "{{ service.health_check.endpoint }}" {% endif %} interval = {{ service.health_check.interval_seconds }} } {% endif %} {% endif %} {% endfor %} database: type = "{{ deployment.default_database.type }}" {% if deployment.default_database.host %} host = "{{ deployment.default_database.host }}" {% endif %} ``` ### Template 2: provctl-config.j2 ```jinja2 {# syntaxis/templates/provctl-config.j2 #} {# Genera TOML para provctl #} # Configuración Syntaxis para provctl # Generado desde syntaxis/configs/deployment.k {% for name, service in deployment.services %} {% if service.enabled %} [[services]] name = "{{ service.name }}" type = "{{ service.type }}" {% if service.port %} port = {{ service.port }} {% endif %} restart_policy = "{{ service.restart_policy }}" {% if service.health_check %} [services.health_check] type = "{{ service.health_check.type }}" {% if service.health_check.endpoint %} endpoint = "{{ service.health_check.endpoint }}" {% endif %} interval_seconds = {{ service.health_check.interval_seconds }} timeout_seconds = {{ service.health_check.timeout_seconds }} {% endif %} {% endif %} {% endfor %} ``` ### Template 3: installer-settings.j2 ```jinja2 {# syntaxis/templates/installer-settings.j2 #} {# Genera configuración específica para installer interactivo #} { "preset": "{{ preset }}", "description": "{{ preset_description }}", "services": [ {% for service in services_enabled %} "{{ service }}"{{ "," if not loop.last else "" }} {% endfor %} ], "database": { "type": "{{ database_type }}" {% if database_type == "postgres" %} ,"host": "{{ database_host | default("localhost") }}", "port": {{ database_port | default(5432) }}, "user": "{{ database_user | default("syntaxis") }}" {% elif database_type == "sqlite" %} ,"path": "{{ database_path | default("/var/lib/syntaxis/db.sqlite") }}" {% endif %} }, "cache_enabled": {{ cache_enabled | lower }} } ``` --- ## 🚀 Scripts NuShell para Exportación On-Demand ### Script: export-config.nu ```nushell # syntaxis/scripts/export-config.nu # Exporta KCL a YAML/JSON on-demand (sin guardar) def export-deployment [format: string = "yaml"] -> string { """ Exporta deployment.k a YAML o JSON Ejemplos: export-deployment # YAML por defecto export-deployment --format json # Exporta como JSON """ # 1. Llamar kcl compile para obtener resultado let kcl_output = ( kcl run syntaxis/configs/deployment.k --format json | from json ) # 2. Convertir según formato requerido match $format { "json" => { $kcl_output | to json } "yaml" => { $kcl_output | to json | from json | to yaml } _ => { error "Formato no soportado: $format (usa 'json' o 'yaml')" } } } def get-preset [preset: string] -> string { """ Obtiene un preset específico en formato YAML Ejemplo: get-preset dev """ let deployment = (export-deployment yaml | from yaml) if ($preset not-in $deployment.presets) { error "Preset '$preset' no existe" } $deployment.presets | get $preset | to yaml } def get-services [preset: string] -> list { """ Lista servicios habilitados en un preset Ejemplo: get-services dev """ let deployment = (export-deployment yaml | from yaml) $deployment.presets | get $preset | get services_enabled } # Comandos disponibles export def "config export" [format: string = "yaml"] { export-deployment $format } export def "config preset" [preset: string] { get-preset $preset } export def "config services" [preset: string] { get-services $preset } ``` --- ## 🔄 Flujo: Cómo Se Consume ### Escenario 1: provisioning (si hay workspace) ```bash # provisioning/kcl/syntaxis-integration.k import /path/to/syntaxis/configs/deployment as sx # Leer declaración KCL de syntaxis syntaxis_config = sx.deployment # Validar contra esquemas provisioning # (provisioning puede validar porque deployment.k es KCL) # Generar configuración provisioning-específica usando template provisioning_settings = render_template( "syntaxis/templates/provisioning-config.j2", { deployment = syntaxis_config } ) # Resultado: config adaptada a provisioning ``` ### Escenario 2: provctl (local con NuShell) ```bash # Developer ejecuta: syntaxis-installer --preset dev # El installer: # 1. Lee syntaxis/configs/deployment.k # 2. Ofrece: "¿Quieres development automático con provctl?" # 3. Si SÍ: # - Ejecuta: nu scripts/export-config.nu yaml # - Obtiene: YAML desde KCL (temporal, stdout) # - Pasa a Tera: installer-settings.j2 # - Pregunta: "¿Qué BD? [sqlite/postgres]" # - Genera: YAML específico para provctl (temporal, no guardado) # - Ejecuta: provctl config apply # 4. Servicios se inician automáticamente ``` ### Escenario 3: syntaxis-installer (interactivo) ```bash $ syntaxis-installer --preset dev ¿Cómo deseas instalar Syntaxis? 1. local - CLI solamente (manual) 2. dev - Full stack con provctl (recomendado) 3. staging - Multi-máquina con provisioning 4. production - HA con provisioning > Selecciona [1-4]: 2 Preset 'dev' seleccionado. ¿Qué base de datos prefieres? 1. sqlite - Archivo local (rápido, desarrollo) 2. postgres - BD remota (escalable) 3. surrealdb - Multi-backend (moderno) > Selecciona [1-3]: 1 ¿Habilitar caché (Redis)? [S/n]: n Verificando dependencias... ✅ provctl disponible ✅ Git disponible Generando configuración... $ nu scripts/export-config.nu yaml > /tmp/syntaxis-dev.yaml $ tera --template syntaxis/templates/installer-settings.j2 \ --input /tmp/syntaxis-dev.yaml \ --values preset=dev,database_type=sqlite \ > /tmp/syntaxis-final.yaml Aplicando configuración... $ provctl config apply /tmp/syntaxis-final.yaml Iniciando servicios... ✅ syntaxis-cli iniciado ✅ syntaxis-api iniciado (http://127.0.0.1:3000) ✅ syntaxis-dashboard iniciado (http://127.0.0.1:8080) ¡Listo! Syntaxis está corriendo en modo dev. ``` --- ## ✅ Validación en KCL (Nativa, Compile-Time) ### Ejemplos de Validación ```kcl # En schemas.k schema Service: name: str type: "binary" | "service" | "database" port?: int # ✅ VALIDACIÓN 1: port requerido si service check: port is not None if type == "service", "Service debe tener port" # ✅ VALIDACIÓN 2: rango válido check: 1 <= port <= 65535 if port is not None, "Port debe estar entre 1 y 65535" schema Database: type: "sqlite" | "postgres" | "mysql" path?: str # ✅ VALIDACIÓN 3: path requerido para sqlite check: path is not None if type == "sqlite", "SQLite requiere path" # ✅ VALIDACIÓN 4: path válido check: regex.match(path, "^/[a-zA-Z0-9/_.-]+$") if path is not None, "Path debe ser ruta absoluta válida" schema Deployment: services: {str: Service} presets: {str: Preset} # ✅ VALIDACIÓN 5: presets referencian servicios válidos check: all( service_name in services.keys() for preset in presets.values() for service_name in preset.services_enabled ), "Preset referencia servicio inexistente" ``` **Resultado**: Validación at compile-time, no en runtime TOML. --- ## 🎯 Ventajas de Esta Arquitectura | Aspecto | Ventaja | |--------|---------| | **Fuente de verdad** | KCL modular, único, versionado con código | | **Validación** | Nativa en KCL, compile-time, no runtime | | **Defaults** | Herencia + override en KCL, no duplicados | | **Modularidad** | Imports, composición, reutilización | | **Generación** | On-demand, ephemeral, no ruido en repo | | **Adaptación** | Parametrizada via Tera templates | | **Consumo** | provisioning/provctl/installer adaptan sin cambios | | **Mantenimiento** | Un solo archivo principal, evoluciona con código | --- ## 🚀 Implementación: Próximos Pasos ### Fase 1: Crear módulos KCL 1. Crear `syntaxis/configs/schemas.k` 2. Crear `syntaxis/configs/defaults.k` 3. Crear `syntaxis/configs/services.k` 4. Crear `syntaxis/configs/presets.k` 5. Crear `syntaxis/configs/deployment.k` ### Fase 2: Crear templates Tera 1. Crear `syntaxis/templates/provisioning-config.j2` 2. Crear `syntaxis/templates/provctl-config.j2` 3. Crear `syntaxis/templates/installer-settings.j2` ### Fase 3: Crear scripts NuShell 1. Crear `syntaxis/scripts/export-config.nu` ### Fase 4: Integración 1. Validar KCL compila sin errores 2. Probar exports: `kcl run syntaxis/configs/deployment.k` 3. Extender syntaxis-installer para usar presets 4. Testing E2E --- ## 📌 Conclusión La arquitectura correcta es: - ✅ **KCL modular en syntaxis/configs/** - Única verdad, validación nativa - ❌ **No generar/guardar YAML/TOML** - On-demand, ephemeral - ✅ **Tera templates para adaptación** - Parámetros → valores - ✅ **Consumo adaptativo** - Cada herramienta se adapta Resultado: Elegante, mantenible, reutilizable, escalable.