---
phase: 04-ws-resilience-enhancements
plan: 01
subsystem: streaming
tags:
  [websocket, ping-pong, keepalive, rate-limiting, ban-handling, binance, tokio]

# Dependency graph
requires: []
provides:
  - WebSocketError with RateLimited, BanDetected, PongTimeout variants
  - ReconnectionPolicy with keepalive config (ping_interval, pong_timeout, subscribe rate limits)
  - StreamSignal enum (Connect, FirstData, Disconnect) for observability
  - Client-side ping with real 10s pong deadline in both per-symbol and combined WS
  - Ban duration parser and sleep-until-expiry handling in run_with_reconnect
  - WsControlRateLimiter for future dynamic subscribe/unsubscribe pacing
affects: [sidecar, live-engine, heartbeat]

# Tech tracking
tech-stack:
  added: []
  patterns:
    [client-side-ping-pong-deadline, ban-sleep-resume, stream-signal-tracing]

key-files:
  created: []
  modified:
    - crates/opendeviationbar-providers/src/binance/websocket.rs
    - crates/opendeviationbar-providers/src/binance/combined_ws.rs
    - crates/opendeviationbar-providers/src/binance/mod.rs
    - crates/opendeviationbar-providers/tests/rest_ws_junction_integration.rs

key-decisions:
  - "PongTimeout is recoverable (triggers reconnect), RateLimited and BanDetected are terminal (prevent retry storm)"
  - "BanDetected handled specially in run_with_reconnect: sleep until expiry then resume, despite terminal classification"
  - "Pong deadline checked at loop top before select (not as select branch) for simplicity and correctness"
  - "WsControlRateLimiter kept as dead_code for future use -- no dynamic subscribe/unsubscribe in current flow"

patterns-established:
  - "Ping-pong deadline: send ping, track sent_at, check elapsed > timeout before each select iteration"
  - "Stream signals via structured tracing: signal field enables journalctl parsing by heartbeat"
  - "Ban handling: parse timestamp from close frame reason, sleep_until with shutdown cancellation"

requirements-completed: []

# Metrics
duration: 7min
completed: 2026-03-22
---

# Phase 04 Plan 01: WS Resilience Summary

**Client-side ping with real 10s pong deadline, Binance 429/ban error classification, and CONNECT/FIRST_DATA/DISCONNECT stream signal emission on both per-symbol and combined WebSocket paths**

## Performance

- **Duration:** 7 min
- **Started:** 2026-03-22T20:03:39Z
- **Completed:** 2026-03-22T20:11:37Z
- **Tasks:** 2
- **Files modified:** 4

## Accomplishments

- Added 3 new WebSocketError variants: RateLimited (terminal), BanDetected (terminal), PongTimeout (recoverable)
- Client-side ping every 5s with real 10s pong deadline tracking in both legacy per-symbol and combined WS paths
- Ban detection from Binance close frames with automatic sleep-until-expiry and resume
- StreamSignal enum exported for downstream consumer observability
- WsControlRateLimiter (3 effective msg/sec after 2 reserved for pings) ready for future dynamic subscriptions
- All 1087 workspace tests pass with zero warnings

## Task Commits

Each task was committed atomically:

1. **Task 1: Add WS error variants, keepalive config, stream signal enum, and client-side ping to websocket.rs** - `8d51096` (feat)
2. **Task 2: Implement ping sender with pong deadline, ban handling, stream signals, and rate limiter in combined_ws.rs** - `11cd837` (feat)

## Files Created/Modified

- `crates/opendeviationbar-providers/src/binance/websocket.rs` - New error variants, keepalive config fields, StreamSignal enum, client-side ping with pong deadline in connect_and_stream
- `crates/opendeviationbar-providers/src/binance/combined_ws.rs` - Ping/pong deadline, ban/429 detection, stream signals, WsControlRateLimiter, parse_ban_duration helper
- `crates/opendeviationbar-providers/src/binance/mod.rs` - Export StreamSignal
- `crates/opendeviationbar-providers/tests/rest_ws_junction_integration.rs` - Updated connect_and_stream call with new ping/pong parameters

## Decisions Made

- PongTimeout is recoverable (not terminal) because it indicates a transient network issue that reconnection can fix, unlike RateLimited/BanDetected which indicate we are violating Binance rules
- BanDetected is classified as terminal in is_terminal() to prevent lower-level code from retrying, but run_with_reconnect handles it specially by sleeping until ban expiry then resuming
- Pong deadline is checked at the top of the loop (before tokio::select!) rather than as a select branch -- simpler, avoids pin issues, and checks on every iteration
- WsControlRateLimiter is annotated with #[allow(dead_code)] since dynamic subscribe/unsubscribe is not yet implemented -- it's available for future use

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 3 - Blocking] Updated integration test caller for new connect_and_stream signature**

- **Found during:** Task 1 (websocket.rs changes)
- **Issue:** rest_ws_junction_integration.rs called connect_and_stream with old 4-arg signature
- **Fix:** Added ping_interval and pong_timeout Duration arguments to match new 6-arg signature
- **Files modified:** crates/opendeviationbar-providers/tests/rest_ws_junction_integration.rs
- **Verification:** All 110 provider tests pass
- **Committed in:** 8d51096 (Task 1 commit)

**2. [Rule 1 - Bug] Suppressed dead_code warnings on WsControlRateLimiter**

- **Found during:** Task 2 (combined_ws.rs changes)
- **Issue:** WsControlRateLimiter is pub(crate) but only used in tests, causing compiler warnings
- **Fix:** Added #[allow(dead_code)] on struct and impl blocks
- **Files modified:** crates/opendeviationbar-providers/src/binance/combined_ws.rs
- **Verification:** cargo build --workspace produces zero warnings
- **Committed in:** 11cd837 (Task 2 commit)

---

**Total deviations:** 2 auto-fixed (1 blocking, 1 bug)
**Impact on plan:** Both auto-fixes necessary for compilation. No scope creep.

## Issues Encountered

None

## Known Stubs

None - all implementations are complete and functional.

## User Setup Required

None - no external service configuration required.

## Next Phase Readiness

- WS layer hardened with keepalive, ban handling, and stream signals
- Ready for Plan 02 (if applicable) or downstream integration
- StreamSignal enum is available for heartbeat journal parsing

---

_Phase: 04-ws-resilience-enhancements_
_Completed: 2026-03-22_
