5.2 KiB
5.2 KiB
ADR-016: Schema-Driven Accessor Generation Pattern
Status: Proposed
Date: 2026-01-13
Author: Architecture Team
Supersedes: Manual accessor maintenance in lib_provisioning/config/accessor.nu
Context
The lib_provisioning/config/accessor.nu file contains 1567 lines across 187 accessor functions. Analysis reveals that 95% of these functions follow
an identical mechanical pattern:
export def get-{field-name} [--config: record] {
config-get "{path.to.field}" {default_value} --config $config
}
This represents significant technical debt:
- Manual Maintenance Burden: Adding a new config field requires manually writing a new accessor function
- Schema Drift Risk: No automated validation that accessor matches the actual Nickel schema
- Code Duplication: Nearly identical functions across 187 definitions
- Testing Complexity: Each accessor requires manual testing
Problem Statement
Current Architecture:
- Nickel schemas define configuration structure (source of truth)
- Accessor functions manually mirror the schema structure
- No automated synchronization between schema and accessors
- High risk of accessor-schema mismatch
Key Metrics:
- 1567 lines of accessor code
- 187 repetitive functions
- ~95% code similarity
Decision
Implement Schema-Driven Accessor Generation: automatically generate accessor functions from Nickel schema definitions.
Architecture
Nickel Schema (contracts.ncl)
↓
[Parse & Extract Schema Structure]
↓
[Generate Nushell Functions]
↓
accessor_generated.nu (800 lines)
↓
[Validation & Integration]
↓
CI/CD enforces: schema hash == generated code
Generation Process
- Schema Parsing: Extract field paths, types, and defaults from Nickel contracts
- Code Generation: Create accessor functions with Nushell 0.109 compliance
- Validation: Verify generated code against schema
- CI Integration: Detect schema changes, validate generated code matches
Compliance Requirements
Nushell 0.109 Guidelines:
- No
try-catchblocks (usedo-completepattern) - No
reduce --init(usereduce --fold) - No mutable variables (use immutable bindings)
- No type annotations on boolean flags
- Use
eachnotmap,is-not-emptynotlength
Nickel Compliance:
- Schema-first design (schema is source of truth)
- Type contracts enforce structure
| docbefore| defaultordering
Consequences
Positive
- Elimination of Manual Maintenance: New config fields automatically get accessors
- Zero Schema Drift: Automatic validation ensures accessors match schema
- Reduced Code Size: 1567 lines → ~400 lines (manual core) + ~800 lines (generated)
- Type Safety: Generated code guarantees type correctness
- Consistency: All 187 functions use identical pattern
Negative
- Tool Complexity: Generator must parse Nickel and emit valid Nushell
- CI/CD Changes: Build must validate schema hash
- Initial Migration: One-time effort to verify generated code matches manual versions
Implementation Strategy
-
Create Generator (
tools/codegen/accessor_generator.nu)- Parse Nickel schema files
- Extract paths, types, defaults
- Generate valid Nushell code
- Emit with proper formatting
-
Generate Accessors (
lib_provisioning/config/accessor_generated.nu)- Run generator on
provisioning/schemas/config/settings/contracts.ncl - Output 187 accessor functions
- Verify compatibility with existing code
- Run generator on
-
Validation
- Integration tests comparing manual vs generated output
- Signature validator ensuring generated functions match patterns
- CI check for schema hash validity
-
Gradual Adoption
- Keep manual accessors temporarily
- Feature flag to switch between manual and generated
- Gradual migration of dependent code
Testing Strategy
-
Unit Tests
- Each generated accessor returns correct type
- Default values applied correctly
- Path resolution handles nested fields
-
Integration Tests
- Generated accessors produce identical output to manual versions
- Config loading pipeline works with generated accessors
- Fallback behavior preserved
-
Regression Tests
- All existing config access patterns work
- Performance within 5% of manual version
- No breaking changes to public API
Related ADRs
- ADR-010: Configuration Format Strategy (TOML/YAML/Nickel)
- ADR-011: Nickel Migration (schema-first architecture)
Open Questions
- Should accessors be regenerated on every build or only on schema changes?
- How do we handle conditional fields (if X then Y)?
- What's the fallback strategy if generator fails?
Timeline
- Phase 1: Generator implementation (foundation)
- Phase 2: Generate and validate accessor functions
- Phase 3: Integration tests and feature flags
- Phase 4: Full migration and manual code removal
References
- Nickel Language: https://nickel-lang.org/
- Nushell 0.109 Guidelines:
.claude/guidelines/nushell.md - Current Accessor Implementation:
provisioning/core/nulib/lib_provisioning/config/accessor.nu - Schema Source:
provisioning/schemas/config/settings/contracts.ncl