# ADR-019: Configuration Loader Modularization\n\n**Status**: Proposed\n**Date**: 2026-01-13\n**Author**: Architecture Team\n**Supersedes**: Monolithic loader in `lib_provisioning/config/loader.nu`\n\n## Context\n\nThe `lib_provisioning/config/loader.nu` file (2199 lines) is a monolithic implementation mixing multiple unrelated concerns:\n\n```\nCurrent Structure (2199 lines):\n├─ Cache lookup/storage (300 lines)\n├─ Nickel evaluation (400 lines)\n├─ TOML/YAML parsing (250 lines)\n├─ Environment variable loading (200 lines)\n├─ Configuration hierarchy merging (400 lines)\n├─ Validation logic (250 lines)\n├─ Error handling (200 lines)\n└─ Helper utilities (150 lines)\n```\n\n**Problems**:\n\n1. **Single Responsibility Violation**: One file handling 7 different concerns\n2. **Testing Difficulty**: Can't test TOML parsing without cache setup\n3. **Change Amplification**: Modifying one component affects entire file\n4. **Code Reuse**: Hard to reuse individual loaders in other contexts\n5. **Maintenance Burden**: 2199 lines of tightly coupled code\n\n## Problem Statement\n\n**Metrics**:\n- 2199 lines in single file\n- 7 distinct responsibilities mixed together\n- Hard to test individual components\n- Changes in one area risk breaking others\n\n## Decision\n\nImplement **Layered Loader Architecture**: decompose monolithic loader into specialized, testable modules with a thin orchestrator.\n\n### Target Architecture\n\n```\nlib_provisioning/config/\n├── loader.nu                   # ORCHESTRATOR (< 300 lines)\n│   └─ Coordinates loading pipeline\n├── loaders/                    # SPECIALIZED LOADERS\n│   ├── nickel_loader.nu       # Nickel evaluation + cache (150 lines)\n│   ├── toml_loader.nu         # TOML parsing (80 lines)\n│   ├── yaml_loader.nu         # YAML parsing (80 lines)\n│   ├── env_loader.nu          # Environment variables (100 lines)\n│   └── hierarchy.nu           # Configuration merging (200 lines)\n├── cache/                      # EXISTING - already modular\n│   ├── core.nu                # Cache core\n│   ├── nickel.nu              # Nickel-specific caching\n│   └── final.nu               # Final config caching\n└── validation/                 # EXTRACTED\n    └── config_validator.nu    # Validation rules (100 lines)\n```\n\n### Module Responsibilities\n\n**loader.nu (Orchestrator)**:\n- Define loading pipeline\n- Coordinate loaders\n- Handle high-level errors\n- Return final config\n\n**nickel_loader.nu**:\n- Evaluate Nickel files\n- Apply Nickel type contracts\n- Cache Nickel evaluation results\n- Handle schema validation\n\n**toml_loader.nu**:\n- Parse TOML configuration files\n- Extract key-value pairs\n- Validate TOML structure\n- Return parsed records\n\n**yaml_loader.nu**:\n- Parse YAML configuration files\n- Convert to Nushell records\n- Handle YAML nesting\n- Return normalized records\n\n**env_loader.nu**:\n- Load environment variables\n- Filter by prefix (PROVISIONING_*)\n- Override existing values\n- Return environment records\n\n**hierarchy.nu**:\n- Merge multiple config sources\n- Apply precedence rules\n- Handle nested merging\n- Return unified config\n\n**config_validator.nu**:\n- Validate against schema\n- Check required fields\n- Enforce type constraints\n- Return validation results\n\n## Consequences\n\n### Positive\n\n- **Separation of Concerns**: Each module has single responsibility\n- **Testability**: Can unit test each loader independently\n- **Reusability**: Loaders can be used in other contexts\n- **Maintainability**: Changes isolated to specific module\n- **Debugging**: Easier to isolate issues\n- **Performance**: Can optimize individual loaders\n\n### Negative\n\n- **Increased Complexity**: More files to maintain\n- **Integration Overhead**: Must coordinate between modules\n- **Migration Effort**: Refactoring existing monolithic code\n\n## Implementation Strategy\n\n### Phase 1: Extract Specialized Loaders\n\nCreate each loader as independent module:\n\n1. **toml_loader.nu**\n   ```nushell\n   export def load-toml [path: string] {\n       let content = (open $path)\n       $content\n   }\n   ```\n\n2. **yaml_loader.nu**\n   ```nushell\n   export def load-yaml [path: string] {\n       let content = (open --raw $path | from yaml)\n       $content\n   }\n   ```\n\n3. **env_loader.nu**\n   ```nushell\n   export def load-environment [] {\n       $env\n       | to json\n       | from json\n       | select --contains "PROVISIONING_"\n   }\n   ```\n\n4. **hierarchy.nu**\n   ```nushell\n   export def merge-configs [base override] {\n       $base | merge $override\n   }\n   ```\n\n### Phase 2: Refactor Nickel Loader\n\nExtract Nickel evaluation logic:\n\n```\nexport def evaluate-nickel [file: string] {\n    let result = (\n        do {\n            ^nickel export $file\n        } | complete\n    )\n\n    if $result.exit_code != 0 {\n        error $result.stderr\n    } else {\n        $result.stdout | from json\n    }\n}\n```\n\n### Phase 3: Create Orchestrator\n\nImplement thin loader.nu:\n\n```\nexport def load-provisioning-config [] {\n    let env_config = (env-loader load-environment)\n    let toml_config = (toml-loader load-toml "config.toml")\n    let nickel_config = (nickel-loader evaluate-nickel "main.ncl")\n\n    let merged = (\n        {}\n        | hierarchy merge-configs $toml_config\n        | hierarchy merge-configs $nickel_config\n        | hierarchy merge-configs $env_config\n    )\n\n    let validated = (config-validator validate $merged)\n    $validated\n}\n```\n\n### Phase 4: Testing\n\nCreate test for each module:\n\n```\ntests/config/\n├── loaders/\n│   ├── test_nickel_loader.nu\n│   ├── test_toml_loader.nu\n│   ├── test_yaml_loader.nu\n│   ├── test_env_loader.nu\n│   └── test_hierarchy.nu\n└── test_orchestrator.nu\n```\n\n## Performance Considerations\n\n**Baseline**: Current monolithic loader ~500ms\n\n**Layered Architecture**:\n- Individual loaders: ~50-100ms each\n- Orchestration: ~50ms\n- Total expected: ~400-500ms (within 5% tolerance)\n\n**Optimization**:\n- Cache Nickel evaluation (largest cost)\n- Lazy load YAML (if rarely used)\n- Environment variable filtering\n\n## Backward Compatibility\n\n**Public API Unchanged**:\n```\n# Current usage (unchanged)\nlet config = (load-provisioning-config)\n```\n\n**Internal Only**: Refactoring is internal to loader module, no breaking changes to consumers.\n\n## Related ADRs\n\n- **ADR-010**: Configuration Format Strategy\n- **ADR-011**: Nickel Migration\n- **ADR-016**: Schema-Driven Accessor Generation\n\n## Open Questions\n\n1. Should each loader have its own cache layer?\n2. How do we handle circular dependencies between loaders?\n3. Should validation run after each loader or only at end?\n4. What's the rollback strategy if orchestration fails?\n\n## References\n\n- Current Implementation: `provisioning/core/nulib/lib_provisioning/config/loader.nu`\n- Cache System: `provisioning/core/nulib/lib_provisioning/config/cache/`\n- Nushell 0.109 Guidelines: `.claude/guidelines/nushell.md`\n
