# 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, }