syntaxis/docs/provision/architecture-correct-final.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

19 KiB

SYNTAXIS Logo

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)

# 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)

# 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)

# 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)

# 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)

# 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

{# 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

{# 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

{# 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

# 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)

# 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)

# 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 <yaml>
# 4. Servicios se inician automáticamente

Escenario 3: syntaxis-installer (interactivo)

$ 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

# 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.