- Remove KCL ecosystem (~220 files deleted) - Migrate all infrastructure to Nickel schema system - Consolidate documentation: legacy docs → provisioning/docs/src/ - Add CI/CD workflows (.github/) and Rust build config (.cargo/) - Update core system for Nickel schema parsing - Update README.md and CHANGES.md for v5.0.0 release - Fix pre-commit hooks: end-of-file, trailing-whitespace - Breaking changes: KCL workspaces require migration - Migration bridge available in docs/src/development/
143 lines
4.4 KiB
Plaintext
143 lines
4.4 KiB
Plaintext
# Configuration Composition Helpers
|
|
#
|
|
# Provides utilities for merging configurations from multiple layers:
|
|
# 1. Schema (type contracts)
|
|
# 2. Service Defaults (base values)
|
|
# 3. Mode Overlay (mode-specific tuning)
|
|
# 4. User Customization (overrides)
|
|
|
|
{
|
|
# Recursively merge two record configurations
|
|
# Mode config values override defaults (shallow merge at each level)
|
|
#
|
|
# Example:
|
|
# let base = { server = { port = 9000, workers = 4 } }
|
|
# let mode = { server = { workers = 16 } }
|
|
# merge_with_override base mode
|
|
# # Result: { server = { port = 9000, workers = 16 } }
|
|
merge_with_override = fun base override =>
|
|
if std.type.is_record base && std.type.is_record override then
|
|
let base_fields = std.record.fields base in
|
|
let override_fields = std.record.fields override in
|
|
|
|
base_fields
|
|
|> std.array.fold
|
|
(fun acc key =>
|
|
let base_value = base |> std.record.get key in
|
|
|
|
if std.record.has_field override key then
|
|
let override_value = override |> std.record.get key in
|
|
|
|
if std.type.is_record base_value && std.type.is_record override_value then
|
|
acc
|
|
|> std.record.insert key (
|
|
merge_with_override base_value override_value
|
|
)
|
|
else
|
|
# Override value takes precedence
|
|
acc |> std.record.insert key override_value
|
|
else
|
|
# Keep base value
|
|
acc
|
|
)
|
|
(override_fields
|
|
|> std.array.fold
|
|
(fun acc key =>
|
|
if !std.record.has_field base key then
|
|
acc |> std.record.insert key (override |> std.record.get key)
|
|
else
|
|
acc
|
|
)
|
|
base
|
|
)
|
|
else
|
|
# If either is not a record, override takes precedence
|
|
if std.type.is_null override then base else override,
|
|
|
|
# Compose configuration from multiple layers with proper merging
|
|
#
|
|
# Layer 1: defaults (base values)
|
|
# Layer 2: mode_config (mode-specific overrides)
|
|
# Layer 3: user_overrides (user customization)
|
|
#
|
|
# Example:
|
|
# compose_config
|
|
# orchestrator_defaults
|
|
# solo_mode_config
|
|
# user_customization
|
|
compose_config = fun defaults mode_config user_overrides =>
|
|
let base_merged = merge_with_override defaults mode_config in
|
|
merge_with_override base_merged user_overrides,
|
|
|
|
# Transform configuration for runtime (identity for now, extensible)
|
|
# This is a hook for service-specific transformations
|
|
transform_for_runtime = fun config service_name =>
|
|
config,
|
|
|
|
# Flatten nested record into dot-notation for TOML export
|
|
# Example:
|
|
# flatten_config { server = { port = 9000 } }
|
|
# # Result: { "server.port" = 9000 }
|
|
flatten_config = fun config =>
|
|
let rec flatten_with_prefix = fun prefix config =>
|
|
if std.type.is_record config then
|
|
std.record.fields config
|
|
|> std.array.fold
|
|
(fun acc key =>
|
|
let value = config |> std.record.get key in
|
|
let full_key = if std.string.is_empty prefix then
|
|
key
|
|
else
|
|
prefix ++ "." ++ key
|
|
in
|
|
|
|
if std.type.is_record value then
|
|
acc
|
|
|> std.record.merge (flatten_with_prefix full_key value)
|
|
else
|
|
acc |> std.record.insert full_key value
|
|
)
|
|
{}
|
|
else
|
|
config
|
|
in
|
|
flatten_with_prefix "" config,
|
|
|
|
# Deep merge with recursive descent
|
|
# Used for complex nested configs
|
|
deep_merge = fun a b =>
|
|
if std.type.is_record a && std.type.is_record b then
|
|
let a_fields = std.record.fields a in
|
|
|
|
a_fields
|
|
|> std.array.fold
|
|
(fun acc key =>
|
|
let a_val = a |> std.record.get key in
|
|
let has_b_key = std.record.has_field b key in
|
|
|
|
if has_b_key then
|
|
let b_val = b |> std.record.get key in
|
|
|
|
if std.type.is_record a_val && std.type.is_record b_val then
|
|
acc |> std.record.insert key (deep_merge a_val b_val)
|
|
else
|
|
acc |> std.record.insert key b_val
|
|
else
|
|
acc |> std.record.insert key a_val
|
|
)
|
|
{}
|
|
|> (fun merged =>
|
|
std.record.fields b
|
|
|> std.array.fold
|
|
(fun acc key =>
|
|
if !std.record.has_field a key then
|
|
acc |> std.record.insert key (b |> std.record.get key)
|
|
else
|
|
acc
|
|
)
|
|
merged
|
|
)
|
|
else
|
|
b,
|
|
}
|