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)
19 KiB
19 KiB
✅ 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
- Crear
syntaxis/configs/schemas.k - Crear
syntaxis/configs/defaults.k - Crear
syntaxis/configs/services.k - Crear
syntaxis/configs/presets.k - Crear
syntaxis/configs/deployment.k
Fase 2: Crear templates Tera
- Crear
syntaxis/templates/provisioning-config.j2 - Crear
syntaxis/templates/provctl-config.j2 - Crear
syntaxis/templates/installer-settings.j2
Fase 3: Crear scripts NuShell
- Crear
syntaxis/scripts/export-config.nu
Fase 4: Integración
- Validar KCL compila sin errores
- Probar exports:
kcl run syntaxis/configs/deployment.k - Extender syntaxis-installer para usar presets
- 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.