provisioning/schemas/platform/configuration-workflow.md

923 lines
25 KiB
Markdown

# Configuration Workflow: TypeDialog → Nickel → TOML → Rust
Complete documentation of the configuration pipeline that transforms interactive user input into production Rust service configurations.
## Overview
The provisioning platform uses a **four-stage configuration workflow** that leverages TypeDialog for interactive configuration,
Nickel for type-safe composition, and TOML for service consumption:
```nickel
┌─────────────────────────────────────────────────────────────────┐
│ Stage 1: User Interaction (TypeDialog) │
│ - Can use Nickel configuration as default values │
│ if use provisioning/platform/config/ it will be updated │
│ - Interactive form (web/tui/cli) │
│ - Real-time constraint validation │
│ - Generates Nickel configuration │
└────────────────┬────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Stage 2: Composition (Nickel) │
│ - Base defaults imported │
│ - Mode overlay applied │
│ - Validators enforce business rules │
│ - Produces Nickel config file │
└────────────────┬────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Stage 3: Export (Nickel → TOML) │
│ - Nickel config evaluated │
│ - Exported to TOML format │
│ - Saved to provisioning/platform/config/ │
└────────────────┬────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Stage 4: Runtime (Rust Services) │
│ - Services load TOML configuration │
│ - Environment variables override specific values │
│ - Start services with final configuration │
└─────────────────────────────────────────────────────────────────┘
```
---
## Stage 1: User Interaction (TypeDialog)
### Purpose
Collect configuration from users through an interactive, constraint-aware interface.
### Workflow
```bash
# Launch interactive configuration wizard
nu scripts/configure.nu orchestrator solo --backend web
```
### What Happens
1. **Form Loads**
- TypeDialog reads `forms/orchestrator-form.toml`
- Form displays configuration sections
- Constraints from `constraints.toml` enforce min/max values
- Environment variables populate initial defaults
2. **User Interaction**
- User fills in form fields (workspace name, server port, etc.)
- Real-time validation on each field
- Constraint interpolation shows valid ranges:
- `${constraint.orchestrator.workers.min}``1`
- `${constraint.orchestrator.workers.max}``32`
3. **Configuration Submission**
- User submits form
- TypeDialog validates all fields against schemas
- Generates Nickel configuration output
4. **Output Generation**
- Nickel config saved to `values/{service}.{mode}.ncl`
- Example: `values/orchestrator.solo.ncl`
- File becomes source of truth for user customizations
### Form Structure Example
```bash
# forms/orchestrator-form.toml
name = "orchestrator_configuration"
description = "Configure orchestrator service"
[[items]]
name = "workspace_group"
type = "group"
includes = ["fragments/workspace-section.toml"]
[[items]]
name = "server_group"
type = "group"
includes = ["fragments/server-section.toml"]
[[items]]
name = "queue_group"
type = "group"
includes = ["fragments/orchestrator/queue-section.toml"]
```
### Fragment with Constraint Interpolation
```bash
# forms/fragments/orchestrator/queue-section.toml
[[elements]]
name = "max_concurrent_tasks"
type = "number"
prompt = "Maximum Concurrent Tasks"
default = 5
min = "${constraint.orchestrator.queue.concurrent_tasks.min}"
max = "${constraint.orchestrator.queue.concurrent_tasks.max}"
required = true
help = "Range: ${constraint.orchestrator.queue.concurrent_tasks.min}-${constraint.orchestrator.queue.concurrent_tasks.max}"
nickel_path = ["orchestrator", "queue", "max_concurrent_tasks"]
```
### Generated Nickel Output (from TypeDialog)
TypeDialog's `nickel-roundtrip` pattern generates:
```nickel
# values/orchestrator.solo.ncl
# Auto-generated by TypeDialog
{
orchestrator = {
workspace = {
name = "dev-workspace",
path = "/home/developer/provisioning/data/orchestrator",
enabled = true,
},
server = {
host = "127.0.0.1",
port = 9090,
workers = 2,
},
queue = {
max_concurrent_tasks = 3,
retry_attempts = 2,
retry_delay = 1000,
},
},
}
```
---
## Stage 2: Composition (Nickel)
### Purpose
Compose the user input with defaults, validators, and schemas to create a complete, validated configuration.
### Workflow
```bash
# The nickel typecheck command validates the composition
nickel typecheck values/orchestrator.solo.ncl
```
### Composition Layers
The final configuration is built by merging layers in priority order:
#### Layer 1: Schema Import
```bash
# Ensures type safety and required fields
let schemas = import "../schemas/orchestrator.ncl" in
```
#### Layer 2: Base Defaults
```bash
# Default values for all orchestrator configurations
let defaults = import "../defaults/orchestrator-defaults.ncl" in
```
#### Layer 3: Mode Overlay
```bash
# Solo-specific overrides and adjustments
let solo_defaults = import "../defaults/deployment/solo-defaults.ncl" in
```
#### Layer 4: Validators Import
```bash
# Business rule validation (ranges, uniqueness, dependencies)
let validators = import "../validators/orchestrator-validator.ncl" in
```
#### Layer 5: User Values
```bash
# User input from TypeDialog (values/orchestrator.solo.ncl)
# Loaded and merged with defaults
```
### Composition Example
```bash
# configs/orchestrator.solo.ncl (generated composition)
let schemas = import "../schemas/orchestrator.ncl" in
let defaults = import "../defaults/orchestrator-defaults.ncl" in
let solo_defaults = import "../defaults/deployment/solo-defaults.ncl" in
let validators = import "../validators/orchestrator-validator.ncl" in
# Composition: Base defaults + mode overlay + user input
{
orchestrator = defaults.orchestrator & {
# User input from TypeDialog values/orchestrator.solo.ncl
workspace = {
name = "dev-workspace",
path = "/home/developer/provisioning/data/orchestrator",
},
# Solo mode overrides
server = {
workers = validators.ValidWorkers 2,
max_connections = 128,
},
queue = {
max_concurrent_tasks = validators.ValidConcurrentTasks 3,
},
# Fallback to defaults for unspecified fields
},
} | schemas.OrchestratorConfig # Validate against schema
```
### Validation During Composition
Each field is validated through multiple validation layers:
```bash
# validators/orchestrator-validator.ncl
let constraints = import "../constraints/constraints.toml" in
{
# Validate workers within allowed range
ValidWorkers = fun workers =>
if workers < constraints.orchestrator.workers.min then
error "Workers below minimum"
else if workers > constraints.orchestrator.workers.max then
error "Workers above maximum"
else
workers,
# Validate concurrent tasks
ValidConcurrentTasks = fun tasks =>
if tasks < constraints.orchestrator.queue.concurrent_tasks.min then
error "Tasks below minimum"
else if tasks > constraints.orchestrator.queue.concurrent_tasks.max then
error "Tasks above maximum"
else
tasks,
}
```
### Constraints: Single Source of Truth
```bash
# constraints/constraints.toml
[orchestrator.workers]
min = 1
max = 32
[orchestrator.queue.concurrent_tasks]
min = 1
max = 100
[common.server.port]
min = 1024
max = 65535
```
These values are referenced in:
- Form constraints (constraint interpolation)
- Validators (ValidWorkers, ValidConcurrentTasks)
- Default values (appropriate for each mode)
---
## Stage 3: Export (Nickel → TOML)
### Purpose
Convert validated Nickel configuration to TOML format for consumption by Rust services.
### Workflow
```bash
# Export Nickel to TOML
nu scripts/generate-configs.nu orchestrator solo
```
### Command Chain
```bash
# What happens internally:
# 1. Typecheck the Nickel config (catch errors early)
nickel typecheck provisioning/.typedialog/provisioning/platform/configs/orchestrator.solo.ncl
# 2. Export to TOML format
nickel export --format toml provisioning/.typedialog/provisioning/platform/configs/orchestrator.solo.ncl
# 3. Save to output location
# → provisioning/platform/config/orchestrator.solo.toml
```
### Input: Nickel Configuration
```nickel
# From: configs/orchestrator.solo.ncl
{
orchestrator = {
workspace = {
name = "dev-workspace",
path = "/home/developer/provisioning/data/orchestrator",
enabled = true,
multi_workspace = false,
},
server = {
host = "127.0.0.1",
port = 9090,
workers = 2,
keep_alive = 75,
max_connections = 128,
},
storage = {
backend = "filesystem",
path = "/home/developer/provisioning/data/orchestrator",
},
queue = {
max_concurrent_tasks = 3,
retry_attempts = 2,
retry_delay = 1000,
task_timeout = 1800000,
},
monitoring = {
enabled = true,
metrics = {
enabled = false,
},
health_check = {
enabled = true,
interval = 60,
},
},
logging = {
level = "debug",
format = "text",
outputs = [
{
destination = "stdout",
level = "debug",
},
],
},
},
}
```
### Output: TOML Configuration
```toml
# To: provisioning/platform/config/orchestrator.solo.toml
[orchestrator.workspace]
name = "dev-workspace"
path = "/home/developer/provisioning/data/orchestrator"
enabled = true
multi_workspace = false
[orchestrator.server]
host = "127.0.0.1"
port = 9090
workers = 2
keep_alive = 75
max_connections = 128
[orchestrator.storage]
backend = "filesystem"
path = "/home/developer/provisioning/data/orchestrator"
[orchestrator.queue]
max_concurrent_tasks = 3
retry_attempts = 2
retry_delay = 1000
task_timeout = 1800000
[orchestrator.monitoring]
enabled = true
[orchestrator.monitoring.metrics]
enabled = false
[orchestrator.monitoring.health_check]
enabled = true
interval = 60
[orchestrator.logging]
level = "debug"
format = "text"
[[orchestrator.logging.outputs]]
destination = "stdout"
level = "debug"
```
### Output Location
```bash
provisioning/platform/config/
├── orchestrator.solo.toml # Exported from configs/orchestrator.solo.ncl
├── orchestrator.multiuser.toml # Exported from configs/orchestrator.multiuser.ncl
├── orchestrator.cicd.toml # Exported from configs/orchestrator.cicd.ncl
├── orchestrator.enterprise.toml # Exported from configs/orchestrator.enterprise.ncl
├── control-center.solo.toml # Similar structure for each service
├── control-center.multiuser.toml
├── mcp-server.solo.toml
└── mcp-server.enterprise.toml
```
### Validation During Export
The `generate-configs.nu` script:
1. **Typechecks** - Ensures Nickel is syntactically valid
2. **Evaluates** - Computes final values
3. **Exports** - Converts to TOML format
4. **Saves** - Writes to `provisioning/platform/config/`
---
## Stage 4: Runtime (Rust Services)
### Purpose
Load TOML configuration and start Rust services with validated settings.
### Configuration Loading Hierarchy
Rust services load configuration in this priority order:
#### 1. Runtime Arguments (Highest Priority)
```bash
ORCHESTRATOR_CONFIG=/path/to/config.toml cargo run --bin orchestrator
```
#### 2. Environment Variables
```bash
# Environment variable overrides specific TOML values
export ORCHESTRATOR_SERVER_PORT=9999
export ORCHESTRATOR_LOG_LEVEL=debug
ORCHESTRATOR_CONFIG=orchestrator.solo.toml cargo run --bin orchestrator
```
Environment variable format: `ORCHESTRATOR_{SECTION}_{KEY}=value`
Example mappings:
- `ORCHESTRATOR_SERVER_PORT=9999``orchestrator.server.port = 9999`
- `ORCHESTRATOR_LOG_LEVEL=debug``orchestrator.logging.level = "debug"`
- `ORCHESTRATOR_QUEUE_MAX_CONCURRENT_TASKS=10``orchestrator.queue.max_concurrent_tasks = 10`
#### 3. TOML Configuration File
```toml
# Load from TOML (medium priority)
ORCHESTRATOR_CONFIG=orchestrator.solo.toml cargo run --bin orchestrator
```
#### 4. Compiled Defaults (Lowest Priority)
```bash
// In Rust code - fallback for unspecified values
let config = Config::from_file(config_path)
.unwrap_or_else(|_| Config::default());
```
### Example: Solo Mode Startup
```bash
# Step 1: User generates config through TypeDialog
nu scripts/configure.nu orchestrator solo --backend web
# Step 2: Export to TOML
nu scripts/generate-configs.nu orchestrator solo
# Step 3: Set environment variables for environment-specific overrides
export ORCHESTRATOR_SERVER_PORT=9090
export ORCHESTRATOR_LOG_LEVEL=debug
# Step 4: Start the Rust service
ORCHESTRATOR_CONFIG=provisioning/platform/config/orchestrator.solo.toml cargo run --bin orchestrator
```
### Rust Service Configuration Loading
```rust
// In orchestrator/src/config.rs
use config::{Config, ConfigError, Environment, File};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct OrchestratorConfig {
pub orchestrator: OrchestratorService,
}
#[derive(Debug, Deserialize)]
pub struct OrchestratorService {
pub workspace: Workspace,
pub server: Server,
pub storage: Storage,
pub queue: Queue,
}
impl OrchestratorConfig {
pub fn load(config_path: Option<&str>) -> Result<Self, ConfigError> {
let mut builder = Config::builder();
// 1. Load TOML file if provided
if let Some(path) = config_path {
builder = builder.add_source(File::from(Path::new(path)));
} else {
// Fallback to defaults
builder = builder.add_source(File::with_name("config/orchestrator.defaults.toml"));
}
// 2. Apply environment variable overrides
builder = builder.add_source(
Environment::with_prefix("ORCHESTRATOR")
.separator("_")
);
let config = builder.build()?;
config.try_deserialize()
}
}
```
### Configuration Validation in Rust
```rust
impl OrchestratorConfig {
pub fn validate(&self) -> Result<(), ConfigError> {
// Validate server configuration
if self.orchestrator.server.port < 1024 || self.orchestrator.server.port > 65535 {
return Err(ConfigError::Message(
"Server port must be between 1024 and 65535".to_string()
));
}
// Validate queue configuration
if self.orchestrator.queue.max_concurrent_tasks == 0 {
return Err(ConfigError::Message(
"max_concurrent_tasks must be > 0".to_string()
));
}
// Validate storage configuration
match self.orchestrator.storage.backend.as_str() {
"filesystem" | "surrealdb" | "rocksdb" => {
// Valid backend
},
backend => {
return Err(ConfigError::Message(
format!("Unknown storage backend: {}", backend)
));
}
}
Ok(())
}
}
```
### Runtime Startup Sequence
```bash
#[tokio::main]
async fn main() -> Result<()> {
// Load configuration
let config = OrchestratorConfig::load(
std::env::var("ORCHESTRATOR_CONFIG").ok().as_deref()
)?;
// Validate configuration
config.validate()?;
// Initialize logging
init_logging(&config.orchestrator.logging)?;
// Start HTTP server
let server = Server::new(
config.orchestrator.server.host.clone(),
config.orchestrator.server.port,
);
// Initialize storage backend
let storage = Storage::new(&config.orchestrator.storage)?;
// Start the service
server.start(storage).await?;
Ok(())
}
```
---
## Complete Example: Solo Mode End-to-End
### Step 1: Interactive Configuration
```toml
$ nu scripts/configure.nu orchestrator solo --backend web
# TypeDialog launches web interface
# User fills in form:
# - Workspace name: "dev-workspace"
# - Server host: "127.0.0.1"
# - Server port: 9090
# - Storage backend: "filesystem"
# - Storage path: "/home/developer/provisioning/data/orchestrator"
# - Max concurrent tasks: 3
# - Log level: "debug"
# Saves to: values/orchestrator.solo.ncl
```
### Step 2: Generated Nickel Configuration
```nickel
# values/orchestrator.solo.ncl
{
orchestrator = {
workspace = {
name = "dev-workspace",
path = "/home/developer/provisioning/data/orchestrator",
enabled = true,
multi_workspace = false,
},
server = {
host = "127.0.0.1",
port = 9090,
workers = 2,
keep_alive = 75,
max_connections = 128,
},
storage = {
backend = "filesystem",
path = "/home/developer/provisioning/data/orchestrator",
},
queue = {
max_concurrent_tasks = 3,
retry_attempts = 2,
retry_delay = 1000,
task_timeout = 1800000,
},
logging = {
level = "debug",
format = "text",
outputs = [{
destination = "stdout",
level = "debug",
}],
},
},
}
```
### Step 3: Composition and Validation
```bash
$ nickel typecheck provisioning/.typedialog/provisioning/platform/configs/orchestrator.solo.ncl
# Validation passes:
# - Workspace name: valid string ✓
# - Port 9090: within range 1024-65535 ✓
# - Max concurrent tasks 3: within range 1-100 ✓
# - Log level: recognized level ✓
```
### Step 4: Export to TOML
```toml
$ nu scripts/generate-configs.nu orchestrator solo
# Generates: provisioning/platform/config/orchestrator.solo.toml
```
### Step 5: TOML File Created
```toml
# provisioning/platform/config/orchestrator.solo.toml
[orchestrator.workspace]
name = "dev-workspace"
path = "/home/developer/provisioning/data/orchestrator"
enabled = true
multi_workspace = false
[orchestrator.server]
host = "127.0.0.1"
port = 9090
workers = 2
keep_alive = 75
max_connections = 128
[orchestrator.storage]
backend = "filesystem"
path = "/home/developer/provisioning/data/orchestrator"
[orchestrator.queue]
max_concurrent_tasks = 3
retry_attempts = 2
retry_delay = 1000
task_timeout = 1800000
[orchestrator.logging]
level = "debug"
format = "text"
[[orchestrator.logging.outputs]]
destination = "stdout"
level = "debug"
```
### Step 6: Runtime Startup
```bash
$ export ORCHESTRATOR_LOG_LEVEL=debug
$ ORCHESTRATOR_CONFIG=provisioning/platform/config/orchestrator.solo.toml cargo run --bin orchestrator
# Service loads orchestrator.solo.toml
# Environment variable overrides ORCHESTRATOR_LOG_LEVEL to "debug"
# Service starts and begins accepting requests on 127.0.0.1:9090
```
---
## Configuration Modification Workflow
### Scenario: User Wants to Change Port
#### Option A: Modify TypeDialog Form and Regenerate
```bash
# 1. Re-run interactive configuration
nu scripts/configure.nu orchestrator solo --backend web
# 2. User changes port to 9999 in form
# 3. TypeDialog generates new values/orchestrator.solo.ncl
# 4. Export updated config
nu scripts/generate-configs.nu orchestrator solo
# 5. New TOML created with port: 9999
# 6. Restart service
ORCHESTRATOR_CONFIG=provisioning/platform/config/orchestrator.solo.toml cargo run --bin orchestrator
```
#### Option B: Direct TOML Edit
```toml
# 1. Edit TOML directly
vi provisioning/platform/config/orchestrator.solo.toml
# Change: port = 9999
# 2. Restart service (no Nickel re-export needed)
ORCHESTRATOR_CONFIG=provisioning/platform/config/orchestrator.solo.toml cargo run --bin orchestrator
```
#### Option C: Environment Variable Override
```bash
# 1. No file changes needed
# 2. Just override environment variable
export ORCHESTRATOR_SERVER_PORT=9999
# 3. Restart service
ORCHESTRATOR_CONFIG=provisioning/platform/config/orchestrator.solo.toml cargo run --bin orchestrator
```
---
## Architecture Relationships
### Component Interactions
```bash
TypeDialog Forms Nickel Schemas
(forms/*.toml) ←shares→ (schemas/*.ncl)
│ │
│ user input │ type definitions
│ │
▼ ▼
values/*.ncl ←─ constraint validation ─→ constraints.toml
(single source of truth)
│ │
│ │
├──→ imported into composition ────────────┤
(configs/*.ncl)
│ │
│ base defaults ───→ defaults/*.ncl │
│ mode overlay ─────→ deployment/*.ncl │
│ validators ──────→ validators/*.ncl │
│ │
└──→ typecheck + export ──────────────→─────┘
nickel export --format toml
provisioning/platform/config/
*.toml files
│ loaded by Rust services
│ at runtime
Running Service
(orchestrator, control-center, mcp-server)
```
---
## Best Practices
### 1. Always Validate Before Deploying
```bash
# Typecheck Nickel before export
nickel typecheck provisioning/.typedialog/provisioning/platform/configs/orchestrator.solo.ncl
# Validate TOML before loading in Rust
cargo run --bin orchestrator -- --validate-config orchestrator.solo.toml
```
### 2. Use Version Control for TOML Configs
```toml
# Commit generated TOML files
git add provisioning/platform/config/orchestrator.solo.toml
git commit -m "Update orchestrator solo configuration"
# But NOT the values/*.ncl files
echo "values/*.ncl" >> provisioning/.typedialog/provisioning/platform/.gitignore
```
### 3. Document Configuration Changes
```toml
# In TypeDialog form, add comments
[[items]]
name = "max_concurrent_tasks"
type = "number"
prompt = "Max concurrent tasks (3 for dev, 50+ for production)"
help = "Increased from 3 to 10 for higher throughput testing"
```
### 4. Environment Variables for Sensitive Data
Never hardcode secrets in TOML:
```toml
# Instead of:
# [orchestrator.security]
# jwt_secret = "hardcoded-secret"
# Use environment variable:
export ORCHESTRATOR_SECURITY_JWT_SECRET="actual-secret"
# TOML can reference it:
# [orchestrator.security]
# jwt_secret = "${JWT_SECRET}"
```
### 5. Test Configuration Changes in Staging First
```toml
# Generate staging config
nu scripts/configure.nu orchestrator multiuser --backend web
# Export to staging TOML
nu scripts/generate-configs.nu orchestrator multiuser
# Test in staging environment
ORCHESTRATOR_CONFIG=orchestrator.multiuser.toml cargo run --bin orchestrator
# Monitor logs and verify behavior
# Then deploy to production
```
---
## Summary
The four-stage workflow provides:
1. **User-Friendly Interface**: TypeDialog forms with real-time validation
2. **Type Safety**: Nickel schemas and validators catch configuration errors early
3. **Flexibility**: TOML format can be edited manually or generated programmatically
4. **Runtime Configurability**: Environment variables allow deployment-time overrides
5. **Single Source of Truth**: Constraints, schemas, and validators all reference shared definitions
This layered approach ensures that:
- Invalid configurations are caught before deployment
- Users can modify configuration safely
- Different deployment modes have appropriate defaults
- Configuration changes can be version-controlled
- Services can be reconfigured without code changes