# 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 # Override values take precedence over base values # When both values are records, merge recursively (deep merge) # # 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.typeof base) == "record" && (std.typeof override) == "record" then let base_fields = std.record.fields base in let override_fields = std.record.fields override in # Pass 1: iterate base fields, merging with override where present base_fields |> std.array.fold_left (fun acc key => let base_value = std.record.get key base in if std.record.has_field key override then let override_value = std.record.get key override in if (std.typeof base_value) == "record" && (std.typeof override_value) == "record" 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 |> std.record.insert key base_value ) {} # Pass 2: add fields exclusive to override |> (fun merged => override_fields |> std.array.fold_left (fun acc key => if !(std.record.has_field key base) then acc |> std.record.insert key (std.record.get key override) else acc ) merged ) else # If either is not a record, override takes precedence if (std.typeof override) == "null" then base else override, # Alias for backwards compatibility deep_merge = fun a b => merge_with_override a b, # 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.typeof config) == "record" then std.record.fields config |> std.array.fold_left (fun acc key => let value = std.record.get key config in let full_key = if std.string.is_empty prefix then key else prefix ++ "." ++ key in if (std.typeof value) == "record" 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, }