Generated Provisioning Output Example

This directory demonstrates the complete output structure generated by the provisioning generator across all 7 layers.

Directory Structure

output-example/
├── config.ncl                  # Master configuration file
├── constraints.toml            # Layer 1: Validation bounds (single source of truth)
├── schemas/                    # Layer 2: Type contracts
│   ├── http_server.ncl
│   ├── database.ncl
│   ├── authentication.ncl
│   └── ...
├── validators/                 # Layer 3: Validation logic
│   ├── http_server.ncl
│   ├── database.ncl
│   ├── common.ncl
│   └── ...
├── defaults/                   # Layer 4: Default values
│   ├── http_server.ncl
│   ├── database.ncl
│   └── ...
├── fragments/                  # Layer 5: TypeDialog forms
│   ├── http_server-section.toml
│   ├── database-section.toml
│   ├── authentication-section.toml
│   └── ...
├── scripts/                    # Layer 6: Orchestration
│   ├── config.sh
│   ├── config.nu
│   ├── json-to-nickel.sh
│   ├── nickel-to-json.sh
│   ├── validate-nickel.sh
│   └── README.md (orchestration guide)
└── README.md                   # This file

7-Layer Validation Architecture

The provisioning system implements a complete validation stack:

Layer 1: Constraints (Single Source of Truth)

File: constraints.toml

Defines bounds and rules for all fields:

[http_server.bind_address]
min_length = 3
max_length = 100

[http_server.timeout_seconds]
min = 1
max = 600

[database.port]
min = 1
max = 65535
unique = false

[authentication.jwt_secret]
min_length = 32
max_length = 256

Purpose:

  • Single source of truth for validation rules
  • Referenced by validators, forms, and documentation
  • Enables constraint interpolation across layers
  • Facilitates consistency across multiple validation backends

Layer 2: Schemas (Type Contracts)

Directory: schemas/

Nickel type definitions for each domain feature:

Example: schemas/http_server.ncl

{
  HttpServer = {
    bind_address | String,
    timeout_seconds | Number,
    max_connections | Number | optional,
  },
}

Purpose:

  • Type-safe configuration definitions
  • Interfaces between application and configuration
  • Validated by Nickel before use
  • Self-documenting code

Layer 3: Validators (Validation Logic)

Directory: validators/

Functions that enforce constraints:

Example: validators/http_server.ncl

let validate_bind_address = fun value =>
  (std.is_string value) &&
  (std.string.length value >= 3) &&
  (std.string.length value <= 100);

let validate_timeout_seconds = fun value =>
  (std.is_number value) &&
  (value >= 1) &&
  (value <= 600);

{
  validate_bind_address,
  validate_timeout_seconds,
}

Purpose:

  • Enforce constraints defined in Layer 1
  • Provide detailed validation error messages
  • Can include complex logic (e.g., regex matching)
  • Reusable across multiple configs

Layer 4: Defaults (Default Values)

Directory: defaults/

Sensible defaults for each feature:

Example: defaults/http_server.ncl

{
  http_server = {
    bind_address = "0.0.0.0:8080",
    timeout_seconds = 30,
    max_connections = 1000,
  },
}

Purpose:

  • Provide reasonable configuration starting points
  • Reduce user decisions
  • Document typical values
  • Enable incremental configuration

Layer 5: Fragments (Form Definitions)

Directory: fragments/

TypeDialog form fragments for user input:

Example: fragments/http_server-section.toml

[section.http_server]
description = "HTTP Server Configuration"

[[section.http_server.fields]]
name = "bind_address"
type = "text"
prompt = "Server bind address"
placeholder = "0.0.0.0:8080"
default = "0.0.0.0:8080"
help = "Format: IP:PORT (e.g., 0.0.0.0:8080)"
required = true

[[section.http_server.fields]]
name = "timeout_seconds"
type = "number"
prompt = "Request timeout"
default = 30
min = 1
max = 600
help = "Maximum seconds to wait for request completion"

Purpose:

  • Provide user-friendly configuration UI
  • Enforce constraints client-side
  • Guide users with prompts and help text
  • Support multiple backends (CLI, TUI, Web)

Layer 6: Scripts (Orchestration)

Directory: scripts/

Automated configuration management:

  • config.sh / config.nu: Master orchestrator
  • json-to-nickel.sh / json-to-nickel.nu: Convert JSON → Nickel
  • nickel-to-json.sh / nickel-to-json.nu: Convert Nickel → JSON
  • validate-nickel.sh / validate-nickel.nu: Validate Nickel syntax

Purpose:

  • Automate configuration pipeline
  • Convert between formats
  • Validate configuration integrity
  • Enable CI/CD integration

Layer 7: Master Config

File: config.ncl

Integrates all layers:

let constraints = import "constraints.toml"
let http_server_schema = import "schemas/http_server.ncl"
let http_server_validator = import "validators/http_server.ncl"
let http_server_defaults = import "defaults/http_server.ncl"

{
  http_server = http_server_defaults.http_server
           | http_server_schema.HttpServer
           | http_server_validator,
}

Using the Generated Structure

1. Display Configuration Form

# Interactive CLI form
typedialog fragments/http_server-section.toml --backend cli

# Web-based form
typedialog-web fragments/http_server-section.toml --port 3000

2. Validate Existing Config

# Validate Nickel syntax
./scripts/validate-nickel.sh < config.ncl

# Convert and validate JSON
./scripts/json-to-nickel.sh < input.json | ./scripts/validate-nickel.sh

3. Generate Config from User Input

# Collect form input
typedialog fragments/*.toml --backend cli --output config.json

# Convert to Nickel
./scripts/json-to-nickel.sh < config.json > config.ncl

# Validate
./scripts/validate-nickel.sh < config.ncl

4. Use in Application

// Load and parse configuration
let config_str = std::fs::read_to_string("config.ncl")?;
let config: ServiceConfig = nickel::evaluate(&config_str)?;

// Use typed configuration
println!("Server: {}:{}", config.http_server.bind_address);

5. Export to Other Formats

# Export to JSON
./scripts/nickel-to-json.sh < config.ncl > config.json

# Export to YAML
./scripts/nickel-to-json.sh < config.ncl | jq -r 'to_entries | map("\(.key): \(.value)") | .[]'

Constraint Interpolation

Constraints can be referenced in validators and forms:

In constraints.toml:

[http_server.timeout_seconds]
min = 1
max = 600

In validators:

let max_timeout = {{ constraints.http_server.timeout_seconds.max }};
validate_timeout = fun value => value <= max_timeout

In fragments:

max = {{ constraints.http_server.timeout_seconds.max }}

This ensures single source of truth for validation bounds.

Field Types Supported

Primitive Types

  • Text - Free-form string input
  • Number - Numeric value (integer or float)
  • Password - Sensitive text (encrypted)
  • Confirm - Boolean yes/no toggle

Selection Types

  • Select - Single choice from predefined options
  • MultiSelect - Multiple selections

Complex Types

  • Editor - Multi-line code or text editing
  • Date - Calendar date picker
  • RepeatingGroup - Array of structured items

Sensitive Data Handling

Fields marked as sensitive are automatically encrypted:

[[section.authentication.fields]]
name = "jwt_secret"
type = "password"
sensitive = true
encryption_backend = "age"  # or: sops, secretumvault, aws-kms, gcp-kms

Supported Encryption Backends:

  • age - Modern encryption (recommended)
  • sops - Mozilla SOPS
  • secretumvault - SecretumVault integration
  • aws-kms - AWS KMS
  • gcp-kms - GCP Cloud KMS
  • azure-kms - Azure Key Vault

Encrypted fields are:

  • Masked in TypeDialog forms
  • Decrypted only when needed
  • Never logged or stored in plain text

Configuration Workflow

Typical workflow with generated structure:

1. User runs interactive form
   ↓
   typedialog fragments/*.toml
   ↓
   User input: {http_server: {bind_address: "0.0.0.0:3000"}}

2. Convert to Nickel
   ↓
   ./scripts/json-to-nickel.sh < input.json > config.ncl

3. Validate against schema
   ↓
   ./scripts/validate-nickel.sh < config.ncl
   ✓ Valid configuration

4. Use in application
   ↓
   app.load_config("config.ncl")
   ↓
   Server starts on 0.0.0.0:3000

Extending Generated Structure

Add a New Feature

  1. Add schema (schemas/my_feature.ncl):

    {
      MyFeature = {
        field1 | String,
        field2 | Number | optional,
      },
    }
    
  2. Add validator (validators/my_feature.ncl):

    {
      validate_field1 = fun value => true,
      validate_field2 = fun value => true,
    }
    
  3. Add defaults (defaults/my_feature.ncl):

    {
      my_feature = {
        field1 = "default",
        field2 = 42,
      },
    }
    
  4. Add fragment (fragments/my_feature-section.toml):

    [section.my_feature]
    [[section.my_feature.fields]]
    name = "field1"
    type = "text"
    
  5. Add constraints (update constraints.toml):

    [my_feature.field1]
    min_length = 1
    max_length = 100
    
  6. Update master config (config.ncl):

    let my_feature_schema = import "schemas/my_feature.ncl"
    # ... include in final config
    

Testing Configuration

# Test entire pipeline
./scripts/config.sh validate

# Test specific feature
typedialog fragments/http_server-section.toml --backend cli --test

# Test with sample data
echo '{"http_server":{"bind_address":"127.0.0.1:8080"}}' | \
  ./scripts/json-to-nickel.sh | \
  ./scripts/validate-nickel.sh

Documentation and References

  • Nickel Docs: Type contracts and validation
  • TypeDialog Docs: Form field types and features
  • Scripts Guide: Orchestration and conversions (scripts/README.md)

Summary

The 7-layer architecture provides:

Separation of Concerns: Each layer has a specific role Single Source of Truth: Constraints defined once, used everywhere Type Safety: Nickel contracts prevent invalid configs User-Friendly Forms: TypeDialog provides intuitive UI Automated Validation: Constraints enforced at multiple layers Format Flexibility: Convert between JSON, TOML, Nickel Reusability: Generic components shared across projects Extensibility: Easy to add features and customize

The generated structure is ready for:

  • Production deployment
  • CI/CD integration
  • Distributed configuration management
  • Multi-environment setups