2026-01-14 04:53:21 +00:00
|
|
|
# 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:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
export def get-{field-name} [--config: record] {
|
|
|
|
|
config-get "{path.to.field}" {default_value} --config $config
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This represents significant technical debt:
|
|
|
|
|
|
|
|
|
|
1. **Manual Maintenance Burden**: Adding a new config field requires manually writing a new accessor function
|
|
|
|
|
2. **Schema Drift Risk**: No automated validation that accessor matches the actual Nickel schema
|
|
|
|
|
3. **Code Duplication**: Nearly identical functions across 187 definitions
|
|
|
|
|
4. **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
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
1. **Schema Parsing**: Extract field paths, types, and defaults from Nickel contracts
|
|
|
|
|
2. **Code Generation**: Create accessor functions with Nushell 0.109 compliance
|
|
|
|
|
3. **Validation**: Verify generated code against schema
|
|
|
|
|
4. **CI Integration**: Detect schema changes, validate generated code matches
|
|
|
|
|
|
|
|
|
|
### Compliance Requirements
|
|
|
|
|
|
|
|
|
|
**Nushell 0.109 Guidelines**:
|
|
|
|
|
- No `try-catch` blocks (use `do-complete` pattern)
|
|
|
|
|
- No `reduce --init` (use `reduce --fold`)
|
|
|
|
|
- No mutable variables (use immutable bindings)
|
|
|
|
|
- No type annotations on boolean flags
|
|
|
|
|
- Use `each` not `map`, `is-not-empty` not `length`
|
|
|
|
|
|
|
|
|
|
**Nickel Compliance**:
|
|
|
|
|
- Schema-first design (schema is source of truth)
|
|
|
|
|
- Type contracts enforce structure
|
|
|
|
|
- `| doc` before `| default` ordering
|
|
|
|
|
|
|
|
|
|
## 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
|
|
|
|
|
|
|
|
|
|
1. **Create Generator** (`tools/codegen/accessor_generator.nu`)
|
|
|
|
|
- Parse Nickel schema files
|
|
|
|
|
- Extract paths, types, defaults
|
|
|
|
|
- Generate valid Nushell code
|
|
|
|
|
- Emit with proper formatting
|
|
|
|
|
|
|
|
|
|
2. **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
|
|
|
|
|
|
|
|
|
|
3. **Validation**
|
|
|
|
|
- Integration tests comparing manual vs generated output
|
|
|
|
|
- Signature validator ensuring generated functions match patterns
|
|
|
|
|
- CI check for schema hash validity
|
|
|
|
|
|
|
|
|
|
4. **Gradual Adoption**
|
|
|
|
|
- Keep manual accessors temporarily
|
|
|
|
|
- Feature flag to switch between manual and generated
|
|
|
|
|
- Gradual migration of dependent code
|
|
|
|
|
|
|
|
|
|
## Testing Strategy
|
|
|
|
|
|
|
|
|
|
1. **Unit Tests**
|
|
|
|
|
- Each generated accessor returns correct type
|
|
|
|
|
- Default values applied correctly
|
|
|
|
|
- Path resolution handles nested fields
|
|
|
|
|
|
|
|
|
|
2. **Integration Tests**
|
|
|
|
|
- Generated accessors produce identical output to manual versions
|
|
|
|
|
- Config loading pipeline works with generated accessors
|
|
|
|
|
- Fallback behavior preserved
|
|
|
|
|
|
|
|
|
|
3. **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
|
|
|
|
|
|
|
|
|
|
1. Should accessors be regenerated on every build or only on schema changes?
|
|
|
|
|
2. How do we handle conditional fields (if X then Y)?
|
|
|
|
|
3. 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/](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`
|