ADR-009: Consolidate Exception Hierarchy
Status: Implemented Date: 2026-05-21 Updated: 2026-07-14 Deciders: Project team
Context
django-micboard had two independent API exception roots:
| File | Content |
|---|---|
micboard/exceptions.py |
Structured domain hierarchy rooted at MicboardError. |
services/common/base/exceptions.py |
Separate transport APIError and APIRateLimitError. |
The duplicate roots made catch boundaries ambiguous. Circuit-open failures also escaped manufacturer-specific catches even though ordinary transport failures did not.
Decision
micboard/exceptions.pyis the single authoritative hierarchy.APIError,APIRateLimitError,APIAuthenticationError, andAPITimeoutErrorinherit fromMicboardErrorand expose structured codes and details.- Manufacturer-specific exceptions stay in their integration modules and inherit from the root API types.
BaseHTTPClientraises its configured manufacturer exception for circuit-open failures with codeAPI_CIRCUIT_OPEN.- An optional bounded
httpx.Responseremains available on API exceptions for transport logic; response bodies are never read into public details implicitly. services/common/base/exceptions.pyis deleted and all call sites import the canonical root directly. No re-export or compatibility module remains.- Operational service failures that need a public identity also live in
micboard/exceptions.py. Service modules do not define their own exception roots. - Django authorization/validation exceptions, Pydantic validator
ValueError, abstract-methodNotImplementedError, and programmer precondition errors remain native at their intended framework boundaries rather than being hidden inside generic service errors. - Public service seams preserve canonical
MicboardErrormetadata but translate unexpected failures to fixed, secret-safe structured errors before they can reach additional callers.
Final Hierarchy
Exception MicboardError APIError APIRateLimitError APIAuthenticationError APITimeoutError ShureAPIError SennheiserAPIError ManufacturerNotSupportedError HardwareNotFoundError HardwareValidationError OrganizationDeviceQuotaExceededError LocationNotFoundError LocationAlreadyExistsError SettingNotFoundError AdminAuditSetupError SubscriptionLeaseLostError DiscoveryError ServiceErrorConsequences
- Positive: One catch root and one structured payload contract cover domain and integration failures.
- Positive: Rate-limit metadata and response objects remain available without exposing vendor response text.
- Positive: Service-specific setup, settings, and lease failures are catchable through the same root without losing stable machine-readable codes.
- Negative: Transport exception strings now use the structured
MicboardErrorformat.
Compliance
- No new exception modules outside
micboard/exceptions.pyor plugin-local exception files. - No exception classes are defined inside
micboard/services/. - Call sites import root exceptions directly; aliases and re-exports are forbidden.
