test(fix): dictionary↔model drift test for the FIX message set (closes #437)#440
Conversation
#437) The REST SDK hard-fails on model<->OpenAPI drift; the FIX message layer had no analogous guard — field tag/type faithfulness for the 8 flows was checked only by hand-written golden/round-trip tests, so a dictionary revision (renamed tag, changed type) would silently pass. - Check the authoritative Kalshi FIX dictionary into the repo as specs/kalshi-fix-dictionary.xml (mirrors specs/openapi.yaml). - tests/fix/test_fix_drift.py: parametrized over every FixMessage/FixGroup model field, diffs (tag -> FIX type) against the dictionary's global field table, asserts each group's NumInGroup tag is NUMINGROUP, and each message's MsgType is a real dictionary message. ~278 parametrized cases. - EXCLUSIONS (the only allowed drift, each with a reason): the market-data tags (263/268/269/270/271/279/281/326) and msgtypes (V/W/X/Y/e/f), absent from the stale v1.03 dictionary because it predates market data (those models derive from the docs). One type-equivalence rule: the SDK models the RFQ Y/N flags (21015/21016/21022), typed CHAR-with-{Y,N} in the dictionary, as BOOLEAN (identical wire). test_exclusions_not_stale keeps the exclusions honest. ruff + mypy --strict clean; 530 FIX tests pass (incl. the new drift parametrization). Closes #437. Part of #402. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code Review — FIX Dictionary↔Model Drift Test (#437)OverviewThis PR closes the gap surfaced in #429: the REST SDK had hard-failing drift tests against
The design is consistent with the REST contract tests and the exclusion mechanism is well-thought-out. A few things worth addressing before merge. IssuesMinor (worth fixing)1. Missing assertion message in the group exclusion branch
# current
assert count_tag not in DICT_FIELDS
# should be
assert count_tag not in DICT_FIELDS, (
f"tag {count_tag} is now defined in the dictionary; drop it from MD_DOC_DERIVED_TAGS"
)2. Module-level docstring violates project style CLAUDE.md says "Never write multi-paragraph docstrings or multi-line comment blocks — one short line max." The file opens with a 12-line module docstring. The PR description already contains this explanation; the module docstring can be trimmed to a single line (e.g., 3. XML quote style inconsistency in the dictionary
<!-- rest of file uses single quotes -->
<group name="NoPartyIDs" required="N"> <!-- ← double quotes -->
<field name="PartyID" required="N"/>
<field name="PartyRole" required="N"/>
</group>This is cosmetic but will stand out to anyone diffing the file against an upstream dictionary update. Notes (no action required, but worth being aware of)4.
5. Private API imports from The test imports leading-underscore symbols directly from 6.
What's done well
SummaryThe design and coverage are solid — this is a meaningful addition to the contract-test story. Two things worth fixing before merge: the missing assertion message in the group exclusion branch (a real usability issue on future failures) and the module docstring length (project convention). The XML quote inconsistency is cosmetic but easy to clean up. |
Summary
The FIX analogue of the REST contract-drift suite. The REST SDK hard-fails on drift between its models and
specs/openapi.yaml(TestRequestParamDrift/TestRequestBodyDrift); the FIX message layer had no equivalent — field tag/type faithfulness across the 8 flows was guarded only by hand-written golden-wire + round-trip tests, so a future dictionary revision (renamed tag, changed type) would silently pass. (Surfaced by the #429 pre-PR review.)Changes
specs/kalshi-fix-dictionary.xml— the authoritative Kalshi FIX dictionary, checked into the repo (mirrorsspecs/openapi.yaml).tests/fix/test_fix_drift.py— parametrized over everyFixMessage/FixGroupmodel field (~278 cases). For each it asserts:NumInGrouptag is typedNUMINGROUP;MsgTypeis a real dictionary message.EXCLUSIONS (the only allowed drift — each documented)
263/268/269/270/271/279/281/326) and msgtypes (V/W/X/Y/e/f) — absent from the bundled v1.03 dictionary because it predates market data; those models were derived fromdocs.kalshi.com/fix/market-data. (Swap in a refreshed dictionary when one ships.)RestRemainder/ReplaceExisting/PreferBetterQuote, tags21015/21016/21022) are typedCHARwith{Y,N}values in the dictionary; the SDK models them asBOOLEAN(identical Y/N wire), which the test accepts.test_exclusions_not_staleasserts every excluded tag/msgtype is still used by a model and still absent from the dictionary — so the exclusions can't silently rot.Verification
uv run ruff check .— cleanuv run mypy kalshi/(strict) — clean; the test itself is also mypy-cleanuv run pytest tests/fix/— 530 passed (incl. the new drift parametrization)Closes #437. Part of #402.
🤖 Generated with Claude Code