# Circuit Breaker

Fuente conceptual: OpenViking `openviking/utils/circuit_breaker.py` (AGPL-3.0)
Reimplementar desde cero. No copiar codigo.

## Clase a implementar

### CircuitBreaker

- **Dominio:** core
- **Lang:** Python
- **Purity:** impure (tiene estado mutable + usa time.monotonic)
- **Signature:**
  ```python
  class CircuitBreaker:
      def __init__(self, failure_threshold: int = 5, reset_timeout: float = 300.0): ...
      def check(self) -> None: ...          # raises CircuitBreakerOpen
      def record_success(self) -> None: ...
      def record_failure(self, error: Exception) -> None: ...
      @property
      def retry_after(self) -> float: ...   # seconds until half-open, capped at 30s
  ```
- **Descripcion:** Patron circuit breaker thread-safe para proteger llamadas a APIs externas. Tres estados: CLOSED (normal), OPEN (bloqueando), HALF_OPEN (permitiendo 1 request de prueba).
- **Algoritmo:**
  - **CLOSED:** Requests pasan normalmente. Si fallan `failure_threshold` veces consecutivas → OPEN. Errores permanentes (401, 403) abren inmediatamente.
  - **OPEN:** Requests bloqueados con `CircuitBreakerOpen`. Despues de `reset_timeout` segundos → HALF_OPEN.
  - **HALF_OPEN:** Permite 1 request de prueba. Si exito → CLOSED. Si falla → OPEN.
- **Thread safety:** Usar `threading.Lock` para proteger estado interno.
- **Clasificacion de errores:** Integrar con `classify_api_error()` (ver spec 09) para distinguir errores permanentes de transitorios.
- **Deps:** `threading`, `time`
- **Tests:**
  - Transicion CLOSED → OPEN despues de N fallos
  - Transicion OPEN → HALF_OPEN despues de timeout
  - Transicion HALF_OPEN → CLOSED en exito
  - Transicion HALF_OPEN → OPEN en fallo
  - Error permanente abre inmediatamente
  - Thread safety (concurrencia)
  - retry_after retorna 0 cuando no esta OPEN
