ADR-002: Direction-specific providers with a shared registry (JOE-1933)¶
Status: Accepted
Date: 2026-08-02
Epic: JOE-1932 / GitHub #59
Related: JOE-1934–JOE-1943 (transport, config, capabilities, verticals)
Context¶
Aurum already has object-safe TranscriptionProvider (STT) and SynthesisProvider (TTS) traits. Adding remote TTS and more STT backends by extending CLI match arms, engine helpers, capability tables, and OpenRouter-shaped config would create an O(N providers × surfaces) mess and let library/CLI behavior drift.
A single universal “speech provider” trait would be equally wrong: STT and TTS do not share inputs, outputs, timestamp truth, voice semantics, or lifecycle.
Decision¶
- Keep direction traits separate. STT stays in
providers/; TTS stays intts/. Shared concerns (identity, construction, secrets scoping, transport, capabilities, metrics, conformance) live in a provider platform layer. - Validated
ProviderIdis the only core-boundary identity (not free strings). - Direction-specific factories (
TranscriptionProviderFactory/SynthesisProviderFactory) register under an immutable-after-buildProviderRegistry. ProviderBuildContextsupplies cache paths, governor/metrics/pools, and provider-scoped secrets only. A factory never receives every vendor key.- Only compiled/reviewed factories are registered by default. No dynamic plugin loading and no config-driven arbitrary HTTP provider.
- Preserve 0.x convenience APIs (
local_whisper(),local_tts(),LocalWhisperProvider::new,OpenRouterProvider::new). Engine high-level routing migrates in JOE-1938; this ADR does not require that cut-over yet.
Consequences¶
Positive¶
- New named providers (OpenAI, ElevenLabs, xAI) register once and pass conformance without editing CLI/engine match arms for each surface.
- Secret isolation is expressible in the type of the build context.
- Capabilities can migrate onto factory/model descriptors (JOE-1936) without flattening STT/TTS semantics.
Negative / deferred¶
- Two factory traits instead of one (intentional).
- Full engine/CLI routing still lives on temporary branches until JOE-1938.
- Provider-shaped config (
[providers.<id>]) is JOE-1935; build context currently accepts explicit scoped fields until that lands.
Invariants¶
localremains the product default; remote requires deliberate selection.local_onlymust reject remote factories before payload preparation.- Remote TTS audio is normalized to mono PCM via
guide/remote-audio.md(JOE-1937) before it entersSynthesisResult. - No silent local→cloud fallback.
Alternatives considered¶
| Alternative | Why rejected |
|---|---|
One SpeechProvider trait | Meaningless optional methods or wrong shared semantics |
| Keep CLI/engine match arms forever | Does not scale; library embedders diverge |
Dynamic .so plugins | Trust boundary; not in first release of JOE-1932 |
| Generic credentialed URL provider | Violates named-origin security posture |
Implementation map¶
| Piece | Location |
|---|---|
| Identity + registry + factories | aurum_core::provider_platform |
| Built-in registration | provider_platform::builtin |
| ADR (this document) | docs/development/adr-002-provider-registry.md |
| Transport policies | JOE-1934 |
| Config migration | JOE-1935 |
| Capabilities ownership | JOE-1936 |
| Engine/CLI routing | JOE-1938 |