Some checks failed
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
mdBook Build & Deploy / Build mdBook (push) Has been cancelled
Nickel Type Check / Nickel Type Checking (push) Has been cancelled
mdBook Build & Deploy / Documentation Quality Check (push) Has been cancelled
mdBook Build & Deploy / Deploy to GitHub Pages (push) Has been cancelled
mdBook Build & Deploy / Notification (push) Has been cancelled
339 lines
9.8 KiB
Plaintext
Executable File
339 lines
9.8 KiB
Plaintext
Executable File
#!/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 <path>"
|
|
print " Validate mode config: nu validate-config.nu --mode <solo|multiuser|enterprise>"
|
|
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
|