Skip to content

fix: ExchangeStatus spec drift — intra_exchange_transfers_active + exchange_index_statuses (#460)#461

Merged
TexasCoding merged 1 commit into
mainfrom
fix/exchange-status-drift-460
Jun 27, 2026
Merged

fix: ExchangeStatus spec drift — intra_exchange_transfers_active + exchange_index_statuses (#460)#461
TexasCoding merged 1 commit into
mainfrom
fix/exchange-status-drift-460

Conversation

@TexasCoding

Copy link
Copy Markdown
Owner

Summary

Closes #460.

The nightly Spec Drift Detection run promoted additive drift to an error one day after the 5.0.0 sync. Root cause: Kalshi mutated the ExchangeStatus schema in place — adding two fields without bumping the OpenAPI version (still 3.22.0) — so yesterday's sync genuinely couldn't have caught it. Re-running scripts/sync_spec.py produced a 38-line additive diff confined to the exchange region.

All changes are additive and backward-compatible (no existing field changed).

Changes

  • specs/openapi.yaml — re-synced from upstream (new ExchangeStatus fields + new ExchangeIndexStatus schema).
  • kalshi/models/exchange.py
    • ExchangeStatus: + intra_exchange_transfers_active: bool | None = None, + exchange_index_statuses: NullableList[ExchangeIndexStatus] = [] (optional; absent → [], null → []).
    • New ExchangeIndexStatus model — per-index/shard status (exchange_index, exchange_active, trading_active, intra_exchange_transfers_active), all required per spec.
  • kalshi/_contract_map.py — registered ExchangeIndexStatus (required by test_contract_map_completeness).
  • kalshi/__init__.py, kalshi/models/__init__.py — export ExchangeIndexStatus (import + __all__).
  • tests/test_exchange.py — 3 sync + 1 async regression tests: per-index breakdown, optional-field defaults, null→[] coercion.
  • Version5.0.0 → 5.0.1 (pyproject.toml + __version__) + CHANGELOG entry.

The hand-written ExchangeIndexStatus matches the datamodel-code-generator output exactly (verified via scripts/generate.py).

Verification

  • Previously-failing test_additive_drift[ExchangeStatus] now passes.
  • Full suite: 4105 passed, 311 skipped, 0 failed.
  • mypy kalshi/ clean · ruff check clean.

🤖 Generated with Claude Code

…+ exchange_index_statuses (#460)

Kalshi added two fields to the ExchangeStatus schema in place, without
bumping the OpenAPI version (still 3.22.0), so the nightly strict contract
suite flagged additive drift the day after the 5.0.0 sync.

- Re-sync specs/openapi.yaml (38-line additive diff in the exchange region)
- ExchangeStatus: add optional intra_exchange_transfers_active and
  exchange_index_statuses (NullableList, defaults to [])
- New ExchangeIndexStatus model (per-index/shard status); register in
  _contract_map.py (required by test_contract_map_completeness) and export
  from kalshi + kalshi.models
- Regression tests: per-index breakdown, optional-field defaults, null->[]
- Bump version 5.0.0 -> 5.0.1 + CHANGELOG

Closes #460

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jun 27, 2026

Copy link
Copy Markdown

Code Review — ExchangeStatus spec drift fix (#460)

Overall: Clean, minimal patch. The implementation is well-scoped and follows project conventions. A few observations below, mostly minor.


Correctness

  • exchange_index: intExchangeIndex in the spec resolves to type: integer (line 4321 of the spec), so the Python type is correct. The contract test will catch any future divergence.
  • intra_exchange_transfers_active: bool | None = None — Field is not in the spec's required array, so None as the absent-field default is the right choice.
  • NullableList on a non-nullable field — The spec does not mark exchange_index_statuses as nullable: true, but using NullableList is a defensive measure against servers returning null anyway. The test test_null_index_statuses_coerced_to_empty validates this behavior. Fine as-is, but worth noting this is intentionally more permissive than the spec.
  • model_config = {"extra": "allow"} on ExchangeIndexStatus — Correct for a response model. Only request models use extra = "forbid" per project convention.

Test Coverage

  • 3 sync + 1 async tests cover the important paths: happy-path with breakdown, optional-field defaults, and null-coercion.
  • Async coverage is lighter: only test_per_index_breakdown is ported to async. The null-coercion and defaults-absent cases are not repeated async, which is acceptable since both paths exercise the same Pydantic model deserialization, but worth noting if the async transport path ever diverges.
  • Missing docstrings on test_per_index_breakdown (sync + async) and test_null_index_statuses_coerced_to_empty. test_defaults_when_new_fields_absent has one; the others do not. Minor inconsistency.

Pattern Consistency

The NullableList[ExchangeIndexStatus] = [] default matches the established pattern across api_keys.py, events.py, milestones.py, and several others. No deviation here.


Suggestions (non-blocking)

  1. Consider adding an async null-coercion test to fully mirror the sync suite. Low priority since model deserialization is shared.
  2. Docstrings on the new tests would help future maintainers understand intent, especially the null-coercion one where the why is not obvious from the name alone.

Summary

No correctness issues found. The spec changes, model, contract map entry, exports, tests, version bump, and changelog are all consistent with each other and with project conventions. Ready to merge.

@TexasCoding TexasCoding merged commit 16442c2 into main Jun 27, 2026
6 checks passed
@TexasCoding TexasCoding deleted the fix/exchange-status-drift-460 branch June 27, 2026 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nightly spec-drift: strict contract tests failing (since 2026-06-27)

1 participant