2026-01-14 04:53:21 +00:00
|
|
|
# ADR-017: Plugin Wrapper Abstraction Framework
|
|
|
|
|
|
|
|
|
|
**Status**: Proposed
|
|
|
|
|
**Date**: 2026-01-13
|
|
|
|
|
**Author**: Architecture Team
|
|
|
|
|
**Supersedes**: Manual plugin wrapper implementations in `lib_provisioning/plugins/`
|
|
|
|
|
|
|
|
|
|
## Context
|
|
|
|
|
|
|
|
|
|
The provisioning system integrates with four critical plugins, each with its own wrapper layer:
|
|
|
|
|
|
|
|
|
|
1. **auth.nu** (1066 lines) - Authentication plugin wrapper
|
|
|
|
|
2. **orchestrator.nu** (~500 lines) - Orchestrator plugin wrapper
|
|
|
|
|
3. **secretumvault.nu** (~500 lines) - Secrets vault plugin wrapper
|
|
|
|
|
4. **kms.nu** (~500 lines) - Key management service plugin wrapper
|
|
|
|
|
|
|
|
|
|
Analysis reveals ~90% code duplication across these wrappers:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
# Pattern repeated 4 times with minor variations:
|
|
|
|
|
export def plugin-available? [] {
|
|
|
|
|
# Check if plugin is installed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export def try-plugin-call [method args] {
|
|
|
|
|
# Try to call the plugin
|
|
|
|
|
# On failure, fallback to HTTP
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export def http-fallback-call [endpoint method args] {
|
|
|
|
|
# HTTP endpoint fallback
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Problem Statement
|
|
|
|
|
|
|
|
|
|
**Current Architecture**:
|
|
|
|
|
- Each plugin has manual wrapper implementation
|
|
|
|
|
- ~3000 total lines across 4 files
|
|
|
|
|
- Boilerplate code repeated for each plugin method
|
|
|
|
|
- HTTP fallback logic duplicated
|
|
|
|
|
- Error handling inconsistent
|
|
|
|
|
- Testing each wrapper requires custom setup
|
|
|
|
|
|
|
|
|
|
**Key Metrics**:
|
|
|
|
|
- 3000 lines of plugin wrapper code
|
|
|
|
|
- 90% code similarity
|
|
|
|
|
- 85% reduction opportunity
|
|
|
|
|
|
|
|
|
|
## Decision
|
|
|
|
|
|
|
|
|
|
Implement **Plugin Wrapper Abstraction Framework**: replace manual plugin wrappers with a generic proxy framework + declarative YAML definitions.
|
|
|
|
|
|
|
|
|
|
### Architecture
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
Plugin Definition (YAML)
|
|
|
|
|
├─ plugin: auth
|
|
|
|
|
├─ methods:
|
|
|
|
|
│ ├─ login(username, password)
|
|
|
|
|
│ ├─ logout()
|
|
|
|
|
│ └─ status()
|
|
|
|
|
└─ http_endpoint: http://localhost:8001
|
|
|
|
|
|
|
|
|
|
Generic Plugin Proxy Framework
|
|
|
|
|
├─ availability() - Check if plugin installed
|
|
|
|
|
├─ call() - Try plugin, fallback to HTTP
|
|
|
|
|
├─ http_fallback() - HTTP call with retry
|
|
|
|
|
└─ error_handler() - Consistent error handling
|
|
|
|
|
|
|
|
|
|
Generated Wrappers
|
|
|
|
|
├─ auth_wrapper.nu (150 lines, autogenerated)
|
|
|
|
|
├─ orchestrator_wrapper.nu (150 lines)
|
|
|
|
|
├─ vault_wrapper.nu (150 lines)
|
|
|
|
|
└─ kms_wrapper.nu (150 lines)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Mechanism
|
|
|
|
|
|
|
|
|
|
**Plugin Call Flow**:
|
|
|
|
|
|
|
|
|
|
1. **Check Availability**: Is plugin installed and running?
|
|
|
|
|
2. **Try Plugin Call**: Execute plugin method with timeout
|
|
|
|
|
3. **On Failure**: Fall back to HTTP endpoint
|
|
|
|
|
4. **Error Handling**: Unified error response format
|
|
|
|
|
5. **Retry Logic**: Configurable retry with exponential backoff
|
|
|
|
|
|
|
|
|
|
### Error Handling Pattern
|
|
|
|
|
|
|
|
|
|
**Nushell 0.109 Compliant** (do-complete pattern, no try-catch):
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
def call-plugin-with-fallback [method: string args: record] {
|
|
|
|
|
let plugin_result = (
|
|
|
|
|
do {
|
|
|
|
|
# Try plugin call
|
|
|
|
|
call-plugin $method $args
|
|
|
|
|
} | complete
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if $plugin_result.exit_code != 0 {
|
|
|
|
|
# Fall back to HTTP
|
|
|
|
|
call-http-endpoint $method $args
|
|
|
|
|
} else {
|
|
|
|
|
$plugin_result.stdout | from json
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Consequences
|
|
|
|
|
|
|
|
|
|
### Positive
|
|
|
|
|
|
|
|
|
|
- **85% Code Reduction**: 3000 lines → 200 (proxy) + 600 (generated)
|
|
|
|
|
- **Consistency**: All plugins use identical call pattern
|
|
|
|
|
- **Maintainability**: Single proxy implementation vs 4 wrapper files
|
|
|
|
|
- **Testability**: Mock proxy for testing, no plugin-specific setup needed
|
|
|
|
|
- **Extensibility**: New plugins require only YAML definition
|
|
|
|
|
|
|
|
|
|
### Negative
|
|
|
|
|
|
|
|
|
|
- **Abstraction Overhead**: Proxy layer adds indirection
|
|
|
|
|
- **YAML Schema**: Must maintain schema for plugin definitions
|
|
|
|
|
- **Migration Risk**: Replacing working code requires careful testing
|
|
|
|
|
|
|
|
|
|
## Implementation Strategy
|
|
|
|
|
|
|
|
|
|
1. **Create Generic Proxy** (`lib_provisioning/plugins/proxy.nu`)
|
|
|
|
|
- Plugin availability detection
|
|
|
|
|
- Call execution with error handling
|
|
|
|
|
- HTTP fallback mechanism
|
|
|
|
|
- Retry logic with backoff
|
|
|
|
|
|
|
|
|
|
2. **Define Plugin Schema** (`lib_provisioning/plugins/definitions/plugin.schema.yaml`)
|
|
|
|
|
- Plugin metadata (name, http_endpoint)
|
|
|
|
|
- Method definitions (parameters, return types)
|
|
|
|
|
- Fallback configuration (retry count, timeout)
|
|
|
|
|
|
|
|
|
|
3. **Plugin Definitions** (`lib_provisioning/plugins/definitions/`)
|
|
|
|
|
- `auth.yaml` - Authentication plugin
|
|
|
|
|
- `orchestrator.yaml` - Orchestrator plugin
|
|
|
|
|
- `secretumvault.yaml` - Secrets vault plugin
|
|
|
|
|
- `kms.yaml` - Key management service plugin
|
|
|
|
|
|
|
|
|
|
4. **Code Generator** (`tools/codegen/plugin_wrapper_generator.nu`)
|
|
|
|
|
- Parse plugin YAML definitions
|
|
|
|
|
- Generate wrapper functions
|
|
|
|
|
- Ensure Nushell 0.109 compliance
|
|
|
|
|
|
|
|
|
|
5. **Integration**
|
|
|
|
|
- Feature flag: `$env.PROVISIONING_USE_GENERATED_PLUGINS`
|
|
|
|
|
- Gradual migration from manual to generated wrappers
|
|
|
|
|
- Full compatibility with existing code
|
|
|
|
|
|
|
|
|
|
## Testing Strategy
|
|
|
|
|
|
|
|
|
|
1. **Unit Tests**
|
|
|
|
|
- Plugin availability detection
|
|
|
|
|
- Successful plugin calls
|
|
|
|
|
- HTTP fallback on plugin failure
|
|
|
|
|
- Error handling and retry logic
|
|
|
|
|
|
|
|
|
|
2. **Integration Tests**
|
|
|
|
|
- Real plugin calls with actual plugins
|
|
|
|
|
- Mock HTTP server for fallback testing
|
|
|
|
|
- Timeout handling
|
|
|
|
|
- Retry with backoff
|
|
|
|
|
|
|
|
|
|
3. **Contract Tests**
|
|
|
|
|
- Plugin method signatures match definitions
|
|
|
|
|
- Return values have expected structure
|
|
|
|
|
- Error responses consistent
|
|
|
|
|
|
|
|
|
|
## Plugin Definitions
|
|
|
|
|
|
|
|
|
|
### auth.yaml Example
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
plugin: auth
|
|
|
|
|
http_endpoint: http://localhost:8001
|
|
|
|
|
methods:
|
|
|
|
|
login:
|
|
|
|
|
params:
|
|
|
|
|
username: string
|
|
|
|
|
password: string
|
|
|
|
|
returns: {token: string}
|
|
|
|
|
logout:
|
|
|
|
|
params: {}
|
|
|
|
|
returns: {status: string}
|
|
|
|
|
status:
|
|
|
|
|
params: {}
|
|
|
|
|
returns: {authenticated: bool}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Rollback Strategy
|
|
|
|
|
|
|
|
|
|
**Feature Flag Approach**:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
# Use original manual wrappers
|
|
|
|
|
export PROVISIONING_USE_GENERATED_PLUGINS=false
|
|
|
|
|
|
|
|
|
|
# Use new generated proxy framework
|
|
|
|
|
export PROVISIONING_USE_GENERATED_PLUGINS=true
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Allows parallel operation and gradual migration.
|
|
|
|
|
|
|
|
|
|
## Related ADRs
|
|
|
|
|
|
|
|
|
|
- **ADR-012**: Nushell/Nickel Plugin CLI Wrapper
|
|
|
|
|
- **ADR-013**: TypeDialog Integration (forms for plugin configuration)
|
|
|
|
|
|
|
|
|
|
## Open Questions
|
|
|
|
|
|
|
|
|
|
1. Should plugin definitions be YAML or Nickel?
|
|
|
|
|
2. How do we handle plugin discovery automatically?
|
|
|
|
|
3. What's the expected HTTP endpoint format for all plugins?
|
|
|
|
|
4. Should retry logic be configurable per plugin?
|
|
|
|
|
|
|
|
|
|
## References
|
|
|
|
|
|
|
|
|
|
- Nushell 0.109 Guidelines: `.claude/guidelines/nushell.md`
|
|
|
|
|
- Do-Complete Pattern: Error handling without try-catch
|
|
|
|
|
- Plugin Framework: `provisioning/core/nulib/lib_provisioning/plugins/`
|