# Phase 4: WS Resilience Enhancements - Context

**Gathered:** 2026-03-22
**Status:** Ready for planning
**Mode:** Auto-generated (infrastructure phase — discuss skipped)

<domain>
## Phase Boundary

Harden the sidecar's Binance WebSocket layer by adopting battle-tested patterns from unicorn-binance-websocket-api. Research was conducted by forking and analyzing <https://github.com/oliver-zehentleitner/unicorn-binance-websocket-api> (fork at ~/fork-tools/unicorn-binance-websocket-api).

### What We Already Have (confirmed via codebase scout)

- Exponential backoff with jitter (backon) + Antaeus stable-connection reset (`combined_ws.rs:225-232`)
- Terminal vs recoverable error classification (`websocket.rs:52-67`)
- TCP keepalive for half-open detection (`websocket.rs:168-175`)
- Data timeout: 90s silence = dead connection (`combined_ws.rs:206-212`)
- Proactive 23h overlapping reconnection before Binance 24h force-disconnect (`combined_ws.rs:234-244`)
- Server-side ping/pong response in read loop (`combined_ws.rs:183-187`)
- REST API adaptive rate limiter with `X-MBX-USED-WEIGHT-1m` feedback (`rate_limiter.rs`)
- CancellationToken graceful shutdown

### What's Missing (from unicorn analysis)

1. **No client-side WS keepalive ping** — we only respond to Binance pings, never send our own. Unicorn uses 5s interval / 10s pong timeout. This matters for proxy/NAT environments with short idle timeouts.
2. **No 429/ban duration extraction** — our error classification doesn't differentiate 429 (fatal rate limit) from 500 (transient). Unicorn extracts ban duration from Binance error response and sleeps until expiry.
3. **No stream state signals** — no CONNECT/FIRST_DATA/DISCONNECT events exposed for consumer API SSE observability.
4. **No WS subscribe/unsubscribe rate limiting** — Binance enforces max 5 control messages/sec (including pings). We have no pacing on subscription operations.
5. **Unbounded trade channels** — `mpsc::Sender<AggTrade>` channels have no backpressure. Slow consumers can cause OOM.

</domain>

<decisions>
## Implementation Decisions

### Claude's Discretion

All implementation choices are at Claude's discretion — pure infrastructure phase. Use ROADMAP success criteria, codebase conventions (Rust-first, backon for backoff, tracing for logging), and the existing combined_ws.rs patterns to guide decisions.

</decisions>

<code_context>

## Existing Code Insights

### Reusable Assets

- `backon::ExponentialBuilder` — already used for reconnection backoff
- `ReconnectionPolicy` struct — extensible for new keepalive parameters
- `WebSocketError` enum — extensible for new error variants (RateLimited, BanDetected)
- `AdaptiveRateLimiter` in `rate_limiter.rs` — pattern for WS rate limiting
- `tokio_tungstenite` — supports custom ping configuration

### Established Patterns

- All WS code in `crates/opendeviationbar-providers/src/binance/`
- Terminal vs recoverable error classification via `is_terminal()`
- `CancellationToken` for lifecycle management
- `tracing::info!/warn!/error!` for structured logging
- `mpsc::Sender<AggTrade>` for per-symbol trade routing

### Integration Points

- `combined_ws.rs` — primary WS connection code (2x9 hybrid)
- `websocket.rs` — per-symbol WS (legacy, still defines shared types)
- `stream_manager.rs` — orchestrates WS connections + LiveBarEngine
- `sidecar.py` — Python sidecar consuming Rust streaming output
- `sidecar_api.py` — consumer SSE API (stream state signals would emit here)
- `heartbeat/checks/stathera.py` — would classify 429 vs 500 errors

</code_context>

<specifics>
## Specific Ideas

### From unicorn-binance-websocket-api Analysis

- Ping interval 5s, pong timeout 10s (unicorn's `ping_interval_default` / `ping_timeout_default`)
- Max 5 messages/sec on WS (Binance limit), with 2 reserved for pings = 3 effective for subscriptions
- 429 handling: permanent crash (not retry) — prevents retry storms during rate limiting
- IP ban extraction: parse ban duration from Binance error response body, sleep until expiry
- Stream signals: CONNECT, FIRST_RECEIVED_DATA, DISCONNECT, STOP, STREAM_UNREPAIRABLE
- Deque with configurable maxlen for per-stream buffers (unicorn uses 500 max)

### Binance WS Documented Limits (from unicorn connection_settings.py)

- 1024 subscriptions/connection (spot)
- 300 connections/5min/IP
- 5 control messages/sec
- 24h max connection lifetime

</specifics>

<deferred>
## Deferred Ideas

None — discussion stayed within phase scope.

</deferred>
