# 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\n**Version**: 1.0.0\n**Last Updated**: 2025-01-05
