From Wave 5 WebSocket audit, finding F-P-10. Severity: medium.
Code
`kalshi/ws/orderbook.py:72-94`:
```python
levels = book.yes if side == "yes" else book.no
...
levels.pop(existing_idx)
levels[existing_idx] = OrderbookLevel(...)
levels.append(...)
levels.sort(key=lambda lv: lv.price)
```
`book.yes` and `book.no` are mutable lists on the `Orderbook` Pydantic model. `OrderbookManager.apply_delta` mutates them directly.
Impact
`_OrderbookIterator.anext` returns `book = self._mgr.get(...)` — the same shared instance every iteration. Subsequent deltas mutate the previously-yielded book. Consumers that snapshot an `Orderbook` by reference (e.g. "state at delta N") silently see future mutations leak in. `Orderbook` is not frozen so the mutation is allowed.
Fix options
- (a) Document explicitly that the `Orderbook` yielded by `orderbook()` is a live view, not a snapshot, and consumers must `deepcopy` if they want to keep history.
- (b) Construct a fresh `Orderbook(ticker=..., yes=list(new_yes), no=list(new_no))` per yield. Slight allocation cost, but matches the natural expectation.
(b) is the safer default; (a) is acceptable with prominent doc warnings + maybe an opt-out flag for performance.
From Wave 5 WebSocket audit, finding F-P-10. Severity: medium.
Code
`kalshi/ws/orderbook.py:72-94`:
```python
levels = book.yes if side == "yes" else book.no
...
levels.pop(existing_idx)
levels[existing_idx] = OrderbookLevel(...)
levels.append(...)
levels.sort(key=lambda lv: lv.price)
```
`book.yes` and `book.no` are mutable lists on the `Orderbook` Pydantic model. `OrderbookManager.apply_delta` mutates them directly.
Impact
`_OrderbookIterator.anext` returns `book = self._mgr.get(...)` — the same shared instance every iteration. Subsequent deltas mutate the previously-yielded book. Consumers that snapshot an `Orderbook` by reference (e.g. "state at delta N") silently see future mutations leak in. `Orderbook` is not frozen so the mutation is allowed.
Fix options
(b) is the safer default; (a) is acceptable with prominent doc warnings + maybe an opt-out flag for performance.