harden(spec-drift): stop the recurring #467/#470 spam + close drift-test false-negatives#471
Conversation
…, content titles The recurring #467 (nightly) / #470 (weekly) issues are two detectors on one real, deterministic upstream drift — but the reporting mechanics amplified the pain. Fix the mechanics without weakening the read-only security model. Nightly (spec-drift.yml): - Comment only when the failing-test-ID set changes (a fingerprint), not every failing night — a deterministic upstream produced identical spam indefinitely. - `spec-drift-ack` / `snoozed` label silences comments while a reconcile is in flight, without closing the tracker. - New report-success job auto-closes the tracker on a green scheduled run (the workflow previously only ever opened/commented). Weekly (spec-sync.yml): - Title leads with a content signal ([content-changed] + path delta + short fingerprint) instead of a bare version — Kalshi changes content in place without bumping info.version, so "3.23.0 → 3.23.0" read as a false alarm. - Supersede stale weekly snapshots so exactly one weekly drift issue stays open instead of one per upstream micro-revision. Scoped to spec-sync-bot so the nightly tracker is never touched. Refs #467, #470 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Direction 1 — less brittle (stop benign churn redding CI against a fast-moving, loosely-versioned upstream): - Additive drift split: a spec-REQUIRED field the SDK lacks still hard-fails (act now); a spec-OPTIONAL field becomes a non-blocking AdditiveOptionalDriftWarning. It deliberately subclasses Warning (not UserWarning) so the nightly `-W error::UserWarning` gate cannot promote benign optional drift to a red build. +3 regression tests lock the contract. - test_schema_coverage batches all unresolved schemas into one failure instead of failing on the first (one-at-a-time reruns on multi-removal drift). Direction 2 — close false-negatives (catch real drift the suite missed): - test_every_spec_endpoint_is_mapped asserts every spec OPERATION (core+perps+perps_scm) is in a METHOD_ENDPOINT_MAP, minus a reasoned _UNIMPLEMENTED_ENDPOINTS allowlist. Catches ADDED endpoints/operations the schema/path-count diff misses — verified against live 3.24.0, which had it caught the two new /margin/* endpoints AND a new GET on an existing /communications/rfqs/.../quotes path that the weekly path diff reported as "Added endpoints: (none)". - kwarg_rename exclusions are now spec-anchored (test_exclusion_map_is_current): the spec-side param must still exist for the endpoint, so a rename can't go stale and silently mask an upstream param removal/rename. Docstring updated to the actual post-#171/#172 behavior and to document why response-side field REMOVAL is intentionally NOT gated: the vendored "Kalshi Trade API Manual Endpoints" spec is a curated subset, so SDK response models are a superset by design; gating removals would red CI on every intentional SDK field. Request-BODY drift already gates removals (_check_model_drift), which is where a rename's dropped old name is caught. Refs #467, #470 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review: harden(spec-drift) — PR #471Solid, well-scoped PR. The mechanics fix (fingerprint-gated nightly comments, ack/snooze, auto-close, content-signal titles, weekly supersede) and the drift-test improvements (required-vs-optional additive split, batched schema-coverage failures, endpoint-completeness check, spec-anchored Potential bug: fingerprint blind spot for
|
Summary
The weekly (#470) and nightly (#467) spec-drift issues recur because Kalshi's OpenAPI is a fast-moving, loosely-versioned upstream (~1 change every ~5 days; content changes land without an
info.versionbump) and our reporting/test mechanics amplified the churn. I confirmed the current drift is real, not a CI artifact (live spec fetched 3× → byte-identical SHA256;/exchange/announcementsgenuinely removed; upstream now at 3.24.0). This PR fixes the mechanics so real drift is reported once, benign drift stops redding CI, and previously-invisible drift gets caught. It does not reconcile the SDK to 3.24.0 — that's a separate follow-up (the announcements removal is being handled as a soft-deprecate pending a confirming fetch).Changes
CI reporting (
fe9ec5a)spec-drift-ack/snoozedlabel silences noise during an in-flight reconcile; a newreport-successjob auto-closes the tracker on a green scheduled run.[content-changed] (paths 91→90, fp 9834160a)— instead of a bare3.23.0 → 3.23.0that read as a false alarm; older weekly snapshots are superseded so exactly one stays open (scoped tospec-sync-bot; the nightly tracker is never touched).Drift tests (
4f68ce5)AdditiveOptionalDriftWarning(subclassesWarning, notUserWarning, so-W error::UserWarningcan't promote benign drift). +3 regression tests.test_schema_coveragenow batches all misses into one failure.test_every_spec_endpoint_is_mappedasserts every spec operation (core+perps+perps_scm) is mapped — catching ADDED endpoints/operations the schema/path-count diff misses.kwarg_renameexclusions are now spec-anchored so a stale rename can't mask an upstream param removal.Test plan
uv run pytest tests/test_contracts.py -q— 660 passed on the pinned 3.23.0 baselineuv run ruff check .— cleanuv run mypy kalshi/— clean (157 files)tests/test_contracts.py+ 2 workflow YAMLs)/margin/*endpoints and a newGETon an existing/communications/rfqs/.../quotespath (which the weekly path diff reported as "Added endpoints: (none)")Notes for reviewers
Non-obvious design decisions, called out deliberately:
AdditiveOptionalDriftWarningmust not subclassUserWarning— a regression test locks this, because that property is what keeps the nightly gate from re-brittling.Orderbook.yes/no, plus real-but-undocumented fields). A hard-fail removal gate would red CI on every intentional SDK field, re-introducing the exact churn this PR removes. Request-body drift already gates removals (_check_model_drift), which is where a rename's dropped old name is caught. The one residual gap — a response-only optional rename — surfaces as anAdditiveOptionalDriftWarningand is reconciled on the next sync._sdk_type_kind/_spec_property_kindhelpers) — deferred because it needs careful type-equivalence handling to avoid a churny baseline break; (3) a maintainer-gatedworkflow_dispatchreconcile-PR workflow.tests/and.github/change, so no CHANGELOG entry.Issue links
Refs #467, #470