Skip to content

PR5: promote additive drift to hard-fail; register ErrorPayload; WS envelope extra=allow #163

Description

@TexasCoding

Context

With #158 in place and #159#162 completed, additive-drift warnings are at zero. Flip warnings.warn(...)pytest.fail(...) so future spec syncs that introduce unmodelled fields fail CI loudly instead of slipping through five rounds of review (the v2.1.0 Balance.balance_dollars failure mode).

Also closes two ROADMAP carry-overs that are trivial alongside the flip:

  1. Register kalshi.ws.models.base.ErrorPayload in WS_CONTRACT_MAP — currently flagged by test_ws_contract_map_completeness.
  2. Apply model_config = {"extra": "allow"} to the WS envelope models and small helpers. PR polish(models): uniform extra="allow" on all response models (#114) #143 made WS payloads extra="allow" but left the envelopes and helpers at the Pydantic default (extra="ignore"). This is the matching ROADMAP item under "Next milestone".

Depends on #159, #160, #161, #162 (all backfills must land first; otherwise this PR turns 53 warnings into 53 failures).

Scope

Files

  • tests/test_contracts.py — flip three warning sites to failures, rename for clarity
  • kalshi/_contract_map.py — add WS_CONTRACT_MAP entry for ErrorPayload
  • kalshi/ws/models/base.py — set extra="allow" on ErrorMessage, ErrorPayload, SubscribedMessage, SubscriptionInfo, UnsubscribedMessage
  • kalshi/ws/models/fill.pyFillMessage
  • kalshi/ws/models/market_lifecycle.pyMarketLifecycleMessage
  • kalshi/ws/models/market_positions.pyMarketPositionsMessage
  • kalshi/ws/models/multivariate.pyMultivariateLifecycleMessage, MultivariateMessage, SelectedMarket
  • kalshi/ws/models/order_group.pyOrderGroupMessage
  • kalshi/ws/models/orderbook_delta.pyOrderbookDeltaMessage, OrderbookSnapshotMessage
  • kalshi/ws/models/ticker.pyTickerMessage
  • kalshi/ws/models/trade.pyTradeMessage
  • kalshi/ws/models/user_orders.pyUserOrdersMessage
  • tests/test_model_extra_policy.py — extend the existing test to cover envelopes and helpers
  • CHANGELOG.md — Unreleased section

Changes

1. tests/test_contracts.py

  • In TestSpecDrift::test_additive_drift (line ~636): change warnings.warn(...)pytest.fail(...). Drop the import warnings if no other call site remains.
  • In TestWsSpecDrift::test_ws_additive_drift (line ~734): same flip.
  • In TestSpecDrift::test_contract_map_completeness (line ~674) and TestWsSpecDrift::test_ws_contract_map_completeness (line ~885): same flip — these currently warn; promote to fail. (ErrorPayload registration in _contract_map.py makes the WS completeness check pass.)
  • Leave test_required_drift and test_ws_required_drift as warnings. The required-but-optional axis is a separate policy decision (~204 entries; see "Out of scope" below).
  • Update module docstring (lines 1–8) — drop "Additive drift ... WARNING" and "Unmapped SDK models ... WARNING" lines; replace with the new FAIL semantics.

2. kalshi/_contract_map.py

Add to WS_CONTRACT_MAP:

ContractEntry(
    sdk_model="kalshi.ws.models.base.ErrorPayload",
    spec_schema="errorPayload",  # verify exact AsyncAPI schema name at edit time
    notes="WS error-frame payload; surfaced via KalshiSubscriptionError",
),

Confirm the spec component name by reading specs/asyncapi.yaml — it's not ErrorPayload; AsyncAPI uses lowerCamelCase per the existing WS_CONTRACT_MAP entries.

3. kalshi/ws/models/*.py

For every class listed under "Files" above that currently has no model_config, add:

model_config = {"extra": "allow"}

Place it immediately after the field declarations, matching the existing pattern on *Payload siblings in the same module. Do not change populate_by_name, validators, or anything else.

4. tests/test_model_extra_policy.py

Extend the existing test to iterate over every class in kalshi.ws.models.* (envelopes + payloads + helpers) and assert model_config.get("extra") == "allow". This pins the policy so a future model addition that forgets the config fails the test.

5. CHANGELOG.md

Add under ## Unreleased (or whatever the active section is named at the time):

### Changed
- Response-side additive spec drift now hard-fails CI (was a warning). Closes
  the regression path that allowed `Balance.balance_dollars` to ship missing in
  v2.1.0. Intentional deviations require an entry in `EXCLUSIONS`
  (`tests/_contract_support.py`) with a typed `kind` and `reason`.
- WS envelope and helper models now use `extra="allow"`, matching the WS
  payload policy from #143. Closes ROADMAP item.

### Added
- `ErrorPayload` registered in `WS_CONTRACT_MAP` so WS contract coverage is
  complete.

Acceptance criteria

  • uv run pytest tests/test_contracts.py -v passes with zero UserWarning for any additive drift or contract-map-completeness check.
  • Temporarily delete a backfilled field locally (e.g. Market.exchange_index) and re-run tests/test_contracts.py::TestSpecDrift::test_additive_drift[Market] — it must FAIL, not warn. (Smoke test; do not commit.)
  • Temporarily delete the ErrorPayload WS_CONTRACT_MAP entry and re-run test_ws_contract_map_completeness — must FAIL. (Smoke test; do not commit.)
  • uv run pytest tests/test_model_extra_policy.py -v passes for both REST and WS models.
  • uv run pytest tests/ -q --ignore=tests/integration passes with the existing 1945-ish baseline.
  • uv run mypy kalshi/ strict-clean.
  • uv run ruff check . clean.
  • Required-drift tests still emit warnings (intentional — see "Out of scope").

Out of scope

Required-but-Optional drift (test_required_drift, test_ws_required_drift) stays as warning-only. The SDK is intentionally permissive on required-fields because demo API has been known to omit fields the spec marks required. Promoting this axis is a separate policy decision and would require either:

  • A ~204-entry allowlist with kind="response_required_sdk_permissive", or
  • A tightening pass that drops None defaults on fields we trust the server to send.

File a follow-up issue if/when the team wants to take that on. Not part of this stack.

Effort

~50 lines test + WS code. Mostly mechanical once #159#162 are in.

Part of the response-side spec drift hardening stack.


Tracking: #157.

Metadata

Metadata

Assignees

No one assigned

    Labels

    spec-driftUpstream OpenAPI/AsyncAPI spec changed since last synctestingTest coveragewsWebSocket-related

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions