2026-01-14 02:59:52 +00:00
..

Validators\n\nValidation logic for configuration values using constraints and business rules.\n\n## Purpose\n\nValidators provide:\n- Constraint checking - Numeric ranges, required fields\n- Business logic validation - Service-specific constraints\n- Error messages - Clear feedback on invalid values\n- Composition with configs - Validators applied during config generation\n\n## File Organization\n\n\nvalidators/\n├── README.md # This file\n├── common-validator.ncl # Ports, positive numbers, strings\n├── network-validator.ncl # IP addresses, bind addresses\n├── path-validator.ncl # File paths, directories\n├── resource-validator.ncl # CPU, memory, disk\n├── string-validator.ncl # Workspace names, identifiers\n├── orchestrator-validator.ncl # Queue, workflow validation\n├── control-center-validator.ncl # RBAC, policy validation\n├── mcp-server-validator.ncl # MCP tools, capabilities\n└── deployment-validator.ncl # Resource allocation\n\n\n## Validation Patterns\n\n### 1. Basic Range Validation\n\n\n# validators/common-validator.ncl\nlet constraints = import "../constraints/constraints.toml" in\n\n{\n ValidPort = fun port =>\n if port < constraints.common.server.port.min then\n std.contract.blame_with_message "Port < 1024" port\n else if port > constraints.common.server.port.max then\n std.contract.blame_with_message "Port > 65535" port\n else\n port,\n}\n\n\n### 2. Range Validator (Reusable)\n\n\n# Reusable validator for any numeric range\nValidRange = fun min max value =>\n if value < min then\n std.contract.blame_with_message "Value < %{std.to_string min}" value\n else if value > max then\n std.contract.blame_with_message "Value > %{std.to_string max}" value\n else\n value,\n\n\n### 3. Enum Validation\n\n\n{\n ValidStorageBackend = fun backend =>\n if backend != 'filesystem &&\n backend != 'rocksdb &&\n backend != 'surrealdb &&\n backend != 'postgres then\n std.contract.blame_with_message "Invalid backend" backend\n else\n backend,\n}\n\n\n### 4. String Validation\n\n\n{\n ValidNonEmptyString = fun s =>\n if s == "" then\n std.contract.blame_with_message "Cannot be empty" s\n else\n s,\n\n ValidWorkspaceName = fun name =>\n if std.string.matches "^[a-z0-9_-]+$" name then\n name\n else\n std.contract.blame_with_message "Invalid workspace name" name,\n}\n\n\n## Common Validators\n\n### common-validator.ncl\n\n\nlet constraints = import "../constraints/constraints.toml" in\n\n{\n # Port validation\n ValidPort = fun port =>\n if port < constraints.common.server.port.min then error "Port too low"\n else if port > constraints.common.server.port.max then error "Port too high"\n else port,\n\n # Positive integer\n ValidPositiveNumber = fun n =>\n if n <= 0 then error "Must be positive"\n else n,\n\n # Non-empty string\n ValidNonEmptyString = fun s =>\n if s == "" then error "Cannot be empty"\n else s,\n\n # Generic range validator\n ValidRange = fun min max value =>\n if value < min then error "Value below minimum"\n else if value > max then error "Value above maximum"\n else value,\n}\n\n\n### resource-validator.ncl\n\n\nlet constraints = import "../constraints/constraints.toml" in\nlet common = import "./common-validator.ncl" in\n\n{\n # Validate CPU cores for deployment mode\n ValidCPUCores = fun mode cores =>\n let limits = constraints.deployment.{mode} in\n common.ValidRange limits.cpu.min limits.cpu.max cores,\n\n # Validate memory allocation\n ValidMemory = fun mode memory_mb =>\n let limits = constraints.deployment.{mode} in\n common.ValidRange limits.memory_mb.min limits.memory_mb.max memory_mb,\n}\n\n\n## Service-Specific Validators\n\n### orchestrator-validator.ncl\n\n\nlet constraints = import "../constraints/constraints.toml" in\nlet common = import "./common-validator.ncl" in\n\n{\n # Validate worker count\n ValidWorkers = fun workers =>\n common.ValidRange\n constraints.orchestrator.workers.min\n constraints.orchestrator.workers.max\n workers,\n\n # Validate queue concurrency\n ValidConcurrentTasks = fun tasks =>\n common.ValidRange\n constraints.orchestrator.queue.concurrent_tasks.min\n constraints.orchestrator.queue.concurrent_tasks.max\n tasks,\n\n # Validate batch parallelism\n ValidParallelLimit = fun limit =>\n common.ValidRange\n constraints.orchestrator.batch.parallel_limit.min\n constraints.orchestrator.batch.parallel_limit.max\n limit,\n\n # Validate task timeout (ms)\n ValidTaskTimeout = fun timeout =>\n if timeout < 1000 then error "Timeout < 1 second"\n else if timeout > 86400000 then error "Timeout > 24 hours"\n else timeout,\n}\n\n\n### control-center-validator.ncl\n\n\n{\n # JWT token expiration\n ValidTokenExpiration = fun seconds =>\n if seconds < 300 then error "Token expiration < 5 min"\n else if seconds > 604800 then error "Token expiration > 7 days"\n else seconds,\n\n # Rate limit threshold\n ValidRateLimit = fun requests_per_minute =>\n if requests_per_minute < 10 then error "Rate limit too low"\n else if requests_per_minute > 10000 then error "Rate limit too high"\n else requests_per_minute,\n}\n\n\n### mcp-server-validator.ncl\n\n\n{\n # Max concurrent tool executions\n ValidConcurrentTools = fun count =>\n if count < 1 then error "Must allow >= 1 concurrent"\n else if count > 20 then error "Max 20 concurrent tools"\n else count,\n\n # Max resource size\n ValidMaxResourceSize = fun bytes =>\n if bytes < 1048576 then error "Min 1 MB"\n else if bytes > 1073741824 then error "Max 1 GB"\n else bytes,\n}\n\n\n## Composition with Configs\n\nValidators are applied in config files:\n\n\n# configs/orchestrator.solo.ncl\nlet validators = import "../validators/orchestrator-validator.ncl" in\n\n{\n orchestrator = {\n server.workers = validators.ValidWorkers 2, # Validated\n queue.max_concurrent_tasks = validators.ValidConcurrentTasks 3, # Validated\n },\n}\n\n\nValidation happens at:\n1. Config composition - When config is evaluated\n2. Nickel typecheck - When config is typechecked\n3. Form submission - When TypeDialog form is submitted (constraints)\n4. TOML export - When Nickel is exported to TOML\n\n## Error Handling\n\n### Validation Errors\n\n\n# If validation fails during config evaluation:\n# Error: Port too high\n\n\n### Meaningful Messages\n\nAlways provide context in error messages:\n\n\n# Bad\nstd.contract.blame "Invalid" value\n\n# Good\nstd.contract.blame_with_message "Port must be 1024-65535, got %{std.to_string value}" port\n\n\n## Best Practices\n\n1. Reuse common validators - Build from common-validator.ncl\n2. Name clearly - Prefix with "Valid" (ValidPort, ValidWorkers, etc.)\n3. Error messages - Include valid range or enum in message\n4. Test edge cases - Verify min/max boundary values\n5. Document assumptions - Why a constraint exists\n\n## Testing Validators\n\n\n# Test a single validator\nnickel eval -c 'import "validators/orchestrator-validator.ncl" as v in v.ValidWorkers 2'\n\n# Test config with validators\nnickel typecheck provisioning/.typedialog/provisioning/platform/configs/orchestrator.solo.ncl\n\n# Evaluate config (runs validators)\nnickel eval provisioning/.typedialog/provisioning/platform/configs/orchestrator.solo.ncl\n\n# Export to TOML (validates during export)\nnickel export --format toml provisioning/.typedialog/provisioning/platform/configs/orchestrator.solo.ncl\n\n\n## Adding a New Validator\n\n1. Create validator function in appropriate file:\n\n nickel\n ValidMyValue = fun value =>\n if value < minimum then error "Too low"\n else if value > maximum then error "Too high"\n else value,\n \n\n2. Add constraint to constraints.toml if needed:\n\n toml\n [service.feature.my_value]\n min = 1\n max = 100\n \n\n3. Use in config:\n\n nickel\n my_value = validators.ValidMyValue 50,\n \n\n4. Add form constraint (if interactive):\n\n toml\n [[elements]]\n name = "my_value"\n min = "${constraint.service.feature.my_value.min}"\n max = "${constraint.service.feature.my_value.max}"\n \n\n5. Test:\n\n bash\n nickel typecheck configs/service.mode.ncl\n \n\n---\n\nVersion: 1.0.0\nLast Updated: 2025-01-05