provisioning/docs/src/architecture/adr/adr-016-schema-driven-accessor-generation.md

159 lines
5.2 KiB
Markdown
Raw Normal View History

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:
2026-01-14 04:53:58 +00:00
```javascript
2026-01-14 04:53:21 +00:00
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
2026-01-14 04:53:58 +00:00
```bash
2026-01-14 04:53:21 +00:00
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`
2026-01-14 04:53:58 +00:00
- Schema Source: `provisioning/schemas/config/settings/contracts.ncl`