Skip to content

Nightly spec-drift: strict contract tests failing (since 2026-09-09) #515

Description

@github-actions

Nightly strict contract tests failed

The scheduled Spec Drift Detection run found the SDK contract suite no longer matches upstream OpenAPI/AsyncAPI.

To resolve: locally run uv run python scripts/sync_spec.py + uv run python scripts/generate.py, reconcile models/maps, and open a PR with Closes #<this issue>. This tracker auto-closes on the next green scheduled run.

To silence while a reconcile is in flight, add the spec-drift-ack label — nightly runs then stop commenting until the failing set changes.

Failing test output (last 200 lines)

tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[WithdrawSettlementBalanceResponse] PASSED [ 97%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[GetSettlementBalanceWithdrawalResponse] PASSED [ 97%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[SettlementBalanceHistoryEntry] PASSED [ 97%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[MarginSubtraderGroup] PASSED [ 97%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[GetMarginSubtraderGroupsResponse] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[CreateMarginSubtraderGroupRequest] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[CreateMarginSubtraderGroupResponse] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[UpdateMarginSubtraderGroupRequest] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[GetSettlementPricesResponse] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[EstimatePortfolioMaintenanceMarginPosition] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[EstimatePortfolioMaintenanceMarginRequest] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[EstimatePortfolioMaintenanceMarginResponse] PASSED [ 98%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[MarginFcmApiKey] PASSED [ 99%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[ListMarginFcmApiKeysResponse] PASSED [ 99%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[CreateMarginFcmApiKeyRequest] PASSED [ 99%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[CreateMarginFcmApiKeyResponse] PASSED [ 99%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[GenerateMarginFcmApiKeyRequest] PASSED [ 99%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[GenerateMarginFcmApiKeyResponse] PASSED [ 99%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[MemberFundingPayment] PASSED [ 99%]
tests/test_contracts.py::TestPerpsScmSpecDrift::test_response_drift[GetMemberFundingPaymentsResponse] PASSED [100%]

=================================== FAILURES ===================================
________________ test_every_spec_endpoint_is_mapped[perps_scm] _________________

spec_loader = <function _load_perps_scm_spec at 0x7fc468f69940>
endpoint_map = [MethodEndpointEntry(sdk_method='kalshi.perps.klear.resources.margin.MarginResource.margin_reports', http_method='GET'...p_method='GET', path_template='/margin/obligations/{obligation_id}/settlement_details', request_body_schema=None), ...]
label = 'perps_scm'

    @pytest.mark.parametrize(
        ("spec_loader", "endpoint_map", "label"),
        [
            (_load_spec, METHOD_ENDPOINT_MAP, "core"),
            (_load_perps_spec, PERPS_METHOD_ENDPOINT_MAP, "perps"),
            (_load_perps_scm_spec, PERPS_SCM_METHOD_ENDPOINT_MAP, "perps_scm"),
        ],
        ids=["core", "perps", "perps_scm"],
    )
    def test_every_spec_endpoint_is_mapped(
        spec_loader: Any,
        endpoint_map: list[MethodEndpointEntry],
        label: str,
    ) -> None:
        """Fail if a spec operation is neither mapped nor explicitly unimplemented.
    
        Closes the false-negative gap where a newly-ADDED upstream endpoint shipped
        untested because nothing asserted spec paths ⊆ ``METHOD_ENDPOINT_MAP``. The
        reverse direction (a map entry whose path the spec dropped) is caught by the
        param-drift tests, which fail loud when the path is absent from the spec.
        """
        spec_ops = _spec_operations(spec_loader())
        mapped = {(e.http_method.upper(), e.path_template) for e in endpoint_map}
        unmapped = spec_ops - mapped - set(_UNIMPLEMENTED_ENDPOINTS)
        if unmapped:
>           pytest.fail(
                f"{label}: spec operations not in the endpoint map (add a "
                f"MethodEndpointEntry, or record in _UNIMPLEMENTED_ENDPOINTS with a reason):\n"
                + "\n".join(f"  - {m} {p}" for m, p in sorted(unmapped))
            )
E           Failed: perps_scm: spec operations not in the endpoint map (add a MethodEndpointEntry, or record in _UNIMPLEMENTED_ENDPOINTS with a reason):
E             - GET /margin/estimate_maintenance_margin/metadata
E             - GET /margin/funding_estimate_by_asset_class

tests/test_contracts.py:1299: Failed
_______ TestRequestBodyDrift.test_body_properties_match_spec[create_rfq] _______

self = <tests.test_contracts.TestRequestBodyDrift object at 0x7fc468e61f70>
entry = MethodEndpointEntry(sdk_method='kalshi.resources.communications.CommunicationsResource.create_rfq', http_method='POST', path_template='/communications/rfqs', request_body_schema='#/components/schemas/CreateRFQRequest')

    def test_body_properties_match_spec(
        self,
        entry: MethodEndpointEntry,
    ) -> None:
        assert entry.request_body_schema is not None
        model_fqn = BODY_MODEL_MAP.get(entry.request_body_schema)
        assert model_fqn is not None, (
            f"No request model registered in BODY_MODEL_MAP for "
            f"{entry.request_body_schema!r}. Add the mapping."
        )
        model_cls = _get_model_class_from_fqn(model_fqn)
    
        schema = _resolve_request_body_schema(
            self.spec,
            entry.path_template,
            entry.http_method,
        )
        assert schema is not None, (
            f"Spec has no body schema for {entry.http_method} {entry.path_template}"
        )
    
        errors: list[str] = []
        _check_model_drift(
            model_cls=model_cls,
            model_fqn=model_fqn,
            spec_schema=schema,
            errors=errors,
        )
    
        # Issue #52: also drift-check nested BaseModel fields (one level deep).
        for field_name, nested_cls, nested_schema in _iter_nested_body_models(
            model_cls,
            schema,
            self.spec,
        ):
            nested_fqn = f"{nested_cls.__module__}.{nested_cls.__qualname__}"
            _check_model_drift(
                model_cls=nested_cls,
                model_fqn=nested_fqn,
                spec_schema=nested_schema,
                errors=errors,
                context=f"{model_fqn}.{field_name}",
            )
    
        if errors:
>           pytest.fail(f"{model_fqn} <-> {entry.request_body_schema}\n" + "\n".join(errors))
E           Failed: kalshi.models.communications.CreateRFQRequest <-> #/components/schemas/CreateRFQRequest
E           [ADD drift] spec has properties SDK model missing: ['target_cost_excludes_fees']

tests/test_contracts.py:1724: Failed
________ TestRequestBodyDrift.test_body_properties_match_spec[create2] _________

self = <tests.test_contracts.TestRequestBodyDrift object at 0x7fc468e621b0>
entry = MethodEndpointEntry(sdk_method='kalshi.resources.communications.RFQsResource.create', http_method='POST', path_template='/communications/rfqs', request_body_schema='#/components/schemas/CreateRFQRequest')

    def test_body_properties_match_spec(
        self,
        entry: MethodEndpointEntry,
    ) -> None:
        assert entry.request_body_schema is not None
        model_fqn = BODY_MODEL_MAP.get(entry.request_body_schema)
        assert model_fqn is not None, (
            f"No request model registered in BODY_MODEL_MAP for "
            f"{entry.request_body_schema!r}. Add the mapping."
        )
        model_cls = _get_model_class_from_fqn(model_fqn)
    
        schema = _resolve_request_body_schema(
            self.spec,
            entry.path_template,
            entry.http_method,
        )
        assert schema is not None, (
            f"Spec has no body schema for {entry.http_method} {entry.path_template}"
        )
    
        errors: list[str] = []
        _check_model_drift(
            model_cls=model_cls,
            model_fqn=model_fqn,
            spec_schema=schema,
            errors=errors,
        )
    
        # Issue #52: also drift-check nested BaseModel fields (one level deep).
        for field_name, nested_cls, nested_schema in _iter_nested_body_models(
            model_cls,
            schema,
            self.spec,
        ):
            nested_fqn = f"{nested_cls.__module__}.{nested_cls.__qualname__}"
            _check_model_drift(
                model_cls=nested_cls,
                model_fqn=nested_fqn,
                spec_schema=nested_schema,
                errors=errors,
                context=f"{model_fqn}.{field_name}",
            )
    
        if errors:
>           pytest.fail(f"{model_fqn} <-> {entry.request_body_schema}\n" + "\n".join(errors))
E           Failed: kalshi.models.communications.CreateRFQRequest <-> #/components/schemas/CreateRFQRequest
E           [ADD drift] spec has properties SDK model missing: ['target_cost_excludes_fees']

tests/test_contracts.py:1724: Failed
=============================== warnings summary ===============================
tests/test_contracts.py::TestSpecDrift::test_additive_drift[RFQ]
  /home/runner/work/kalshi-python-sdk/kalshi-python-sdk/tests/test_contracts.py:837: AdditiveOptionalDriftWarning: Additive-optional drift in kalshi.models.communications.RFQ: Spec field 'target_cost_excludes_fees' has no SDK mapping
    _warn_additive_optional(entry.sdk_model, additive_optional)

tests/test_contracts.py::TestSpecDrift::test_additive_drift[Quote]
  /home/runner/work/kalshi-python-sdk/kalshi-python-sdk/tests/test_contracts.py:837: AdditiveOptionalDriftWarning: Additive-optional drift in kalshi.models.communications.Quote: Spec field 'target_cost_excludes_fees' has no SDK mapping
    _warn_additive_optional(entry.sdk_model, additive_optional)

tests/test_contracts.py::TestSpecDrift::test_additive_drift[CreateRFQRequest]
  /home/runner/work/kalshi-python-sdk/kalshi-python-sdk/tests/test_contracts.py:837: AdditiveOptionalDriftWarning: Additive-optional drift in kalshi.models.communications.CreateRFQRequest: Spec field 'target_cost_excludes_fees' has no SDK mapping
    _warn_additive_optional(entry.sdk_model, additive_optional)

tests/test_contracts.py::TestAdditiveDriftClassification::test_optional_drift_survives_the_nightly_strict_gate
  /home/runner/work/kalshi-python-sdk/kalshi-python-sdk/tests/test_contracts.py:988: AdditiveOptionalDriftWarning: Additive-optional drift in tests.synthetic.Fake: Spec field 'opt_new' has no SDK mapping
    _warn_additive_optional("tests.synthetic.Fake", msgs)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_contracts.py::test_every_spec_endpoint_is_mapped[perps_scm] - Failed: perps_scm: spec operations not in the endpoint map (add a MethodEndpointEntry, or record in _UNIMPLEMENTED_ENDPOINTS with a reason):
  - GET /margin/estimate_maintenance_margin/metadata
  - GET /margin/funding_estimate_by_asset_class
FAILED tests/test_contracts.py::TestRequestBodyDrift::test_body_properties_match_spec[create_rfq] - Failed: kalshi.models.communications.CreateRFQRequest <-> #/components/schemas/CreateRFQRequest
[ADD drift] spec has properties SDK model missing: ['target_cost_excludes_fees']
FAILED tests/test_contracts.py::TestRequestBodyDrift::test_body_properties_match_spec[create2] - Failed: kalshi.models.communications.CreateRFQRequest <-> #/components/schemas/CreateRFQRequest
[ADD drift] spec has properties SDK model missing: ['target_cost_excludes_fees']
============ 3 failed, 778 passed, 4 warnings in 300.03s (0:05:00) =============

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    spec-driftUpstream OpenAPI/AsyncAPI spec changed since last sync

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions