#!/usr/bin/env nu # VAPORA Configuration Validation Utility # Comprehensive validation of Nickel and rendered configurations # Version: 1.0.0 def main [ --config: string --mode: string ] { if ($config == null) and ($mode == null) { print "VAPORA Configuration Validator" print "" print "Usage:" print " Validate single config: nu validate-config.nu --config " print " Validate mode config: nu validate-config.nu --mode " print " Validate all modes: nu validate-config.nu --mode all" return } if ($config != null) { validate-config-file $config } else if ($mode == "all") { ["solo", "multiuser", "enterprise"] | each { |m| validate-mode $m } } else { validate-mode $mode } } def validate-mode [mode: string] { print $"🔍 Validating ($mode) mode configuration" print "" # Step 1: Export from Nickel print "Step 1: Generating from Nickel..." let config_file = ([$env.PWD, "..", "schemas", "platform", "configs", $"vapora-($mode).ncl"] | path join) if not ($config_file | path exists) { error make {msg: $"Config not found: ($config_file)"} } let result = do { nickel export $config_file } | complete if $result.exit_code != 0 { error make {msg: $"Nickel export failed: ($result.stderr)"} } let json_output = ($result.stdout | from json) print " ✓ Nickel export successful" # Step 2: Validate structure print "Step 2: Validating structure..." validate-structure $json_output print " ✓ Structure valid" # Step 3: Validate field ranges print "Step 3: Validating field ranges..." validate-ranges $json_output $mode print " ✓ Field ranges valid" # Step 4: Validate provider configuration print "Step 4: Validating provider configuration..." validate-providers $json_output $mode print " ✓ Provider configuration valid" # Step 5: Validate security settings print "Step 5: Validating security settings..." validate-security $json_output $mode print " ✓ Security settings valid" # Step 6: Consistency checks print "Step 6: Checking consistency..." validate-consistency $json_output $mode print " ✓ Consistency checks passed" print "" print $"✅ ($mode) configuration valid" print "" } def validate-config-file [config_path: string] { print $"🔍 Validating: ($config_path)" print "" if not ($config_path | path exists) { error make {msg: $"Config file not found: ($config_path)"} } # Determine file type if ($config_path | str ends-with ".json") { validate-json-file $config_path } else if ($config_path | str ends-with ".ncl") { validate-nickel-file $config_path } else if ($config_path | str ends-with ".toml") { validate-toml-file $config_path } else if ($config_path | str ends-with ".yaml") or ($config_path | str ends-with ".yml") { validate-yaml-file $config_path } else { error make {msg: "Unknown file type"} } } def validate-structure [config: record] { let required_fields = [ "deployment_mode" "workspace_name" "backend" "agents" "llm_router" "frontend" "database" "nats" "providers" "monitoring" "security" "storage" ] let missing = $required_fields | where { |field| ($config | get $field -o) == null } if ($missing | length) > 0 { error make {msg: $"Missing required fields: ($missing | str join ', ')"} } # Validate nested structures let backend_required = ["host", "port", "workers", "auth", "database"] let backend_missing = $backend_required | where { |field| ($config.backend | get $field -i) == null } if ($backend_missing | length) > 0 { error make {msg: $"Backend missing: ($backend_missing | str join ', ')"} } } def validate-ranges [config: record, mode: string] { let port_min = 1024 let port_max = 65535 # Validate ports if ($config.backend.port < $port_min) or ($config.backend.port > $port_max) { error make {msg: $"Invalid backend port: ($config.backend.port)"} } if ($config.agents.port < $port_min) or ($config.agents.port > $port_max) { error make {msg: $"Invalid agents port: ($config.agents.port)"} } if ($config.llm_router.port < $port_min) or ($config.llm_router.port > $port_max) { error make {msg: $"Invalid llm_router port: ($config.llm_router.port)"} } # Validate workers based on mode let max_workers = match $mode { "solo" => 4 "multiuser" => 16 "enterprise" => 32 _ => 4 } if ($config.backend.workers < 1) or ($config.backend.workers > $max_workers) { error make {msg: $"Invalid worker count: ($config.backend.workers)"} } # Validate pool sizes if ($config.backend.database.pool_size < 1) or ($config.backend.database.pool_size > 500) { error make {msg: $"Invalid pool size: ($config.backend.database.pool_size)"} } # Validate timeouts if ($config.backend.request_timeout < 1000) or ($config.backend.request_timeout > 600000) { error make {msg: $"Invalid request timeout: ($config.backend.request_timeout)"} } } def validate-providers [config: record, mode: string] { let provider_count = [ $config.providers.claude_enabled $config.providers.openai_enabled $config.providers.gemini_enabled $config.providers.ollama_enabled ] | where { |p| $p } | length if $provider_count == 0 { error make {msg: "At least one LLM provider must be enabled"} } # Validate Ollama URL if enabled if $config.providers.ollama_enabled { if ($config.providers.ollama_url | is-empty) { error make {msg: "Ollama enabled but URL not set"} } if not ($config.providers.ollama_url | str starts-with "http") { error make {msg: "Invalid Ollama URL format"} } } } def validate-security [config: record, mode: string] { # JWT secret warning (but allow empty for local dev) if ($config.backend.auth.jwt_secret | is-empty) and ($mode != "solo") { print " ⚠️ Warning: JWT secret is empty (non-solo mode)" } # TLS validation if $config.security.tls_enabled { if ($config.security.tls_cert_path | is-empty) { error make {msg: "TLS enabled but cert path not set"} } if ($config.security.tls_key_path | is-empty) { error make {msg: "TLS enabled but key path not set"} } } # MFA validation if $config.backend.auth.mfa_enabled and ($config.backend.auth.method == "jwt") { print " ⚠️ Warning: MFA with JWT only (consider OAuth2)" } } def validate-consistency [config: record, mode: string] { # Deployment mode consistency if $config.deployment_mode != $mode { error make {msg: $"Deployment mode mismatch: expected ($mode), got ($config.deployment_mode)"} } # Database URL should match mode expectations if $mode == "solo" { if not ($config.database.url | str contains "localhost") and not ($config.database.url | str contains "127.0.0.1") { print " ⚠️ Warning: Solo mode with remote database" } } else if $mode == "multiuser" { if not ($config.agents.nats.enabled) { print " ⚠️ Warning: Multiuser mode without NATS" } } # Enterprise mode should have high availability enabled if $mode == "enterprise" { if not ($config.agents.nats.enabled) { error make {msg: "Enterprise mode requires NATS enabled"} } if not ($config.monitoring.prometheus_enabled) { print " ⚠️ Warning: Enterprise mode without Prometheus" } } # API URL should be set for non-localhost deployments if ($config.backend.host != "127.0.0.1") { if ($config.frontend.api_url == null) or ($config.frontend.api_url | is-empty) { print " ⚠️ Warning: No API URL set for non-localhost backend" } } } def validate-json-file [path: string] { print "Validating JSON file..." let result = do { open $path } | complete if $result.exit_code != 0 { error make {msg: "Failed to parse JSON"} } let config = ($result.stdout | from json) print " ✓ Valid JSON" validate-structure $config print " ✓ Structure valid" print "" print "✅ JSON file valid" } def validate-nickel-file [path: string] { print "Validating Nickel file..." # Typecheck let typecheck_result = do { nickel typecheck $path } | complete if $typecheck_result.exit_code != 0 { error make {msg: $"Typecheck failed: ($typecheck_result.stderr)"} } print " ✓ Typecheck passed" # Export let export_result = do { nickel export $path } | complete if $export_result.exit_code != 0 { error make {msg: $"Export failed: ($export_result.stderr)"} } print " ✓ Export successful" print "" print "✅ Nickel file valid" } def validate-yaml-file [path: string] { print "Validating YAML file..." let result = do { yq eval '.' $path } | complete if $result.exit_code != 0 { error make {msg: "Invalid YAML syntax"} } print " ✓ Valid YAML" print "" print "✅ YAML file valid" } def validate-toml-file [path: string] { print "Validating TOML file..." # Basic check: should parse and have [sections] let content = (open $path) if not ($content | str contains "[") { error make {msg: "Invalid TOML: no sections found"} } print " ✓ Valid TOML structure" print "" print "✅ TOML file valid" } # Run main function main