Skip to content

Rename the Kalshi sports row's category to events - #152

Merged
juan-malbeclabs merged 5 commits into
mainfrom
jo/kalshi-events-category
Sep 18, 2026
Merged

juan-malbeclabs merged 5 commits into
mainfrom
jo/kalshi-events-category

Conversation

@juan-malbeclabs

@juan-malbeclabs juan-malbeclabs commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

The universe edge-kalshi-sports-mbp carries is event markets, of which sport is one kind, so sports named it too narrowly. The row's category is now events.

The group code is unchanged. That name is the ledger's and the reconciler matches doublezero status on it, so renaming it here would have darkened the row silently — no warning, no failed bind, just a permanently-zero dz_receiver_up. The registry row now carries a NAMING note saying so, next to the value.

⚠️ The hosted feed-registry document has to be republished for this to take effect

The image bakes DZ_FEED_REGISTRY_URL in (Dockerfile:107) and a Url origin wins over the compiled-in copy, so on a default container src/ingest/registry.json is the fallback, not the authority. Until the document served at that URL carries "category": "events", every surface below keeps reporting sports and the rename is invisible to an operator.

That document is behind in the same way today. Fetched 2026-09-18, it serves three rows, all Kalshi:

KALSHI  perps   edge-kalshi-perps-tob
KALSHI  perps   edge-kalshi-perps-mbp
KALSHI  sports  edge-kalshi-sports-mbp

No Phoenix row, no elections rows and neither Hyperliquid row — so a default container ingests that venue and nothing else. Same shape as the stale lashay-* group codes the changelog already called out. Clearing DZ_FEED_REGISTRY_URL on one host makes the built-in document win, which is the single-host workaround; republishing is a separate rollout, outside this repo.

What an operator has to do

Nothing. --channels/DZ_CHANNELS key on the code, not the category, so every existing spec keeps working.

What changes

The producer-side identity every universe-scoped key is built from — the arbitration scope ((venue, category)), the tape-owner rank, and the catalog, book and history keys — plus the two read-only surfaces that report it:

  • category in /v1/status's channels.rows[] and in GET /admin/channels
  • the (category: …) suffix /v1/products appends when a symbol resolves in more than one universe under one Source ID

category is producer-side only and is never serialized onto the WebSocket, so PROTOCOL.md is untouched.

The sites that had to follow the value

Everything that resolves the built-in document by category — a stale literal there does not fail to compile, it stops resolving:

  • ingest/feeds.rs — the row lookup and the (venue, category, kind) -> code arm
  • ingest/registry.rs — sports_channel_ids(), which reads the document rather than the expanded rows
  • ingest/channel_filter.rs, sinks/admin.rs, sinks/api.rs, main.rs — the f.category == … lookups
  • ingest/reconcile.rs — the FeedKey, catalog, book and history keys of the tests built on the real row (sports_row())
  • sinks/api.rs — feed_kind_for's registry rung filters on (venue, category); that test reported "unknown" until the fixture followed

And the operator-facing prose, which a category rename silently contradicts rather than breaks — a sentence that still called it the sports category sent a reader looking for one that no longer resolves:

  • src/ingest/registry.json — the elections rows' notes: "separate from perps and sports" (it pairs two category names, and only one of them still existed), "the rule the sports row uses", "the way the sports feeds are"
  • docs/input-sources.md — the socket-count caveat's "Kalshi's derived 31-channel sports row", now "events row (edge-kalshi-sports-mbp)", naming the group code beside it since that is the value an operator passes to --channels
  • CHANGELOG.md — the /v1/products feed_kind entry's "single-kind sports category" and the elections entry's two mentions, both in the same unreleased section as this rename

Left alone deliberately: sport where it names a channel or a kind of market (the row's own NAMING/CHANNELS notes, the sports.nfl channel labels), test data that only uses "sports" as an arbitrary category label (arbiter, authority, path_race, products, history, and the CLI's sample fixtures), and the helper/test names that name the group rather than the universe (sports_row, sports_channel_ids) — all three copies of sports_row() now say in their doc comments that they are named for the code.

Testing

cargo test --workspace --locked green (1083 tests, 0 failures), cargo clippy --all-targets --locked clean, cargo fmt --check clean.

@juan-malbeclabs juan-malbeclabs left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One operational gap worth fixing before this is called done; the rename itself is clean and carefully scoped.

The rename does not reach a default deployment

Dockerfile:107 bakes ENV DZ_FEED_REGISTRY_URL=https://get.doublezero.xyz/feeds/doublezero-edge-feeds-latest.json, and Origin::from_flags gives a URL document precedence over the compiled-in copy (falling back to it only when the fetch or validation fails). So on a normal container src/ingest/registry.json is the fallback, not the authority — and the hosted document served right now still says:

KALSHI | perps  | edge-kalshi-perps-tob
KALSHI | perps  | edge-kalshi-perps-mbp
KALSHI | sports | edge-kalshi-sports-mbp

Two consequences:

  1. Until that document is republished with "category": "events", the rename is inert wherever the URL resolves. /v1/status, /admin/channels and the (category: …) suffix on /v1/products keep reporting sports, and the arbitration scope, tape-owner rank and catalog/book/history keys keep being built on sports. The changelog promises a change an operator will not see.
  2. Independently of this PR: that hosted document is already well behind the built-in — it carries neither the elections rows nor the Phoenix row. This is the same trap the ### Fixed entry about the stale lashay-* codes called out ("the hosted feed-registry document must carry the same three codes"), and it is evidently still open.

Neither is a code change here. But the changelog entry should say the hosted document must be republished for the rename to take effect, the way the lashay-* entry did — otherwise the next reader concludes the rename shipped.

Verified, no action needed

  • No state migration. category is part of history::Key, the catalog key and authority::MarketKey, so a rename could in principle orphan persisted state. src/history.rs touches no filesystem — the store is in-memory and rebuilt per process, so the rename costs nothing across a restart.
  • Not on the wire. category is producer-side only; PROTOCOL.md correctly untouched.
  • --channels / DZ_CHANNELS unaffected — they key on code, which deliberately stays edge-kalshi-sports-mbp. Leaving the ledger's name alone is the right call, for exactly the reason the changelog gives.
  • src/main.rs:974 — vec!["elections", "events", "perps"] is reordered correctly for the preceding sort_unstable(). That is the one assertion in the diff where a mechanical find-and-replace would have produced a green-looking but wrong edit, and it was handled.

Nits

  • The helpers keep the old name: sports_row(), sports_channel_ids(), sports_ports_are_the_base_plus_the_channel_id, an_excluded_sports_channel_binds_no_socket, and let sports = …. Defensible — they track the group code, which is still …-sports-mbp — and reconcile.rs's copy now says so in its doc comment. But let sports = feeds().find(|f| f.category == "events") reads oddly at each call site, and three copies of sports_row() now exist (reconcile.rs, admin.rs, api.rs) while only one carries the explanation. One line in the other two would settle it.
  • src/main.rs:968-972 still has two consecutive comment lines that both open "The categories themselves…". Pre-existing, but this PR edits the second one — worth folding into one while you are in there.

@juan-malbeclabs
juan-malbeclabs requested review from a team and removed request for a team September 11, 2026 01:29
@juan-malbeclabs
juan-malbeclabs force-pushed the jo/kalshi-events-category branch from f7d90b6 to 4a738a0 Compare September 11, 2026 10:25
@juan-malbeclabs

Copy link
Copy Markdown
Contributor Author

Rebased onto #151's branch so cargo-deny stops failing on the yanked chacha20 0.10.1 this branch inherited from main.

Read the diff as the ten files of the rename only. The base is still main, so GitHub also lists Cargo.lock — that is #151's change, not part of this PR. Once #151 merges its commit becomes an ancestor of main and the diff here collapses back to the rename.

Nothing in the rename was touched: the rebase applied cleanly with no conflicts, and the tree against #151's branch is the same ten files as before. Verified on the rebased head: cargo metadata --locked exits 0 and cargo build --workspace --release --locked succeeds.

⚠️ The branch was force-pushed (f7d90b6 → 4a738a0), so a local checkout needs git fetch && git reset --hard origin/jo/kalshi-events-category.

@juan-malbeclabs

Copy link
Copy Markdown
Contributor Author

Pushed 87e595e addressing the review above.

The hosted document is now called out in the changelog. The entry says the rename does not take effect until the document at DZ_FEED_REGISTRY_URL carries "category": "events" — the image bakes that URL in and a URL origin wins over the compiled-in copy, so on a default container src/ingest/registry.json is the fallback, not the authority. It notes the document is behind in the same way today (no elections rows, no Phoenix row), points at the same shape in the lashay-* entry, and gives clearing the variable as the single-host workaround.

Nits. The two sports_row() helpers whose doc comment did not explain the name now say they are named for the group code, which is the ledger's and unchanged, rather than for the category. And the duplicated comment above the market-by-price universe assertion in main.rs is folded into one.

No behavior change; fmt, clippy and the 863-test lib suite are clean.

The universe that `edge-kalshi-sports-mbp` carries is event markets, of which
sport is one kind, so `sports` named it too narrowly. The group `code` is
unchanged: that name is the ledger's and the reconciler matches `doublezero
status` on it, so renaming it here would have darkened the row silently.

Nothing an operator passes changes -- `--channels`/`DZ_CHANNELS` key on the
`code`, not the category. What changes is the producer-side identity every
universe-scoped key is built from (the arbitration scope, the tape-owner rank,
the catalog/book/history keys) plus the two read-only surfaces that report it:
`category` in `/v1/status`'s `channels.rows[]` and in `/admin/channels`, and the
`(category: ...)` suffix `/v1/products` appends when a symbol resolves in more
than one universe under one Source ID. `category` is never serialized onto the
WebSocket, so PROTOCOL.md is untouched.

Every site that resolves the built-in document by category follows the value:
the `feeds.rs` row lookup and its `(venue, category, kind) -> code` arm,
`registry::sports_channel_ids`, and the channel-filter/admin/api/main/reconcile
lookups with the fixtures keyed to the real row. Test data that only uses
"sports" as an arbitrary category label is left alone, as are the helper and
test names that name the group rather than the universe.
…to land

The image bakes DZ_FEED_REGISTRY_URL in and a URL origin wins over the
compiled-in copy, so on a default container src/ingest/registry.json is the
fallback rather than the authority: until the document served at that URL
carries "category": "events", every surface the entry names keeps reporting
sports and the rename is invisible to an operator. The document is behind in the
same way today — it carries neither the elections rows nor the Phoenix row.

Also name the two `sports_row()` helpers after the group code they actually
track, in the two copies whose doc comment did not say so, and fold the
duplicated comment above the market-by-price universe assertion.
@juan-malbeclabs
juan-malbeclabs force-pushed the jo/kalshi-events-category branch from 87e595e to b67b865 Compare September 11, 2026 14:06
Copilot AI lite review requested due to automatic review settings September 18, 2026 15:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The default container still consumes the hosted registry, and current operator documentation retains the old category name.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Renames the Kalshi sports universe category to events while preserving its ledger group code and channel-filter compatibility.

Changes:

  • Updates the built-in registry and category-scoped tests.
  • Adjusts feed selection, reconciliation, admin, and API fixtures.
  • Documents the new category and deployment caveat.
File summaries
File Description
src/ingest/registry.json Renames the built-in row category.
src/ingest/registry.rs Updates registry test helpers.
src/ingest/feeds.rs Updates feed and port expectations.
src/ingest/channel_filter.rs Updates channel-filter tests.
src/ingest/reconcile.rs Updates category-scoped reconciliation tests.
src/sinks/api.rs Updates API status fixtures.
src/sinks/admin.rs Updates admin fixtures.
src/main.rs Updates feed-selection tests.
CLAUDE.md Documents the renamed category.
CHANGELOG.md Records the rename and deployment caveat.
Review details
  • Files reviewed: 10/10 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/ingest/registry.json
Comment thread src/ingest/registry.json
The rename moved the `category` but left three sentences calling the
universe `sports`, all of them operator-facing: the elections rows'
`notes` ("separate from perps and sports", "the rule the sports row
uses", "the way the sports feeds are") and the socket-count caveat in
`docs/input-sources.md`. A reader following any of them looks for a
category that no longer resolves, so they now say `events` and the
input-sources line names the group `code` beside it, since that is the
value an operator passes to `--channels`. `sport` stays only where it
names a channel or a kind of market.

Also makes the hosted-document caveat say what that document actually
holds rather than which two rows it is missing: fetched today it carries
three rows, all Kalshi, so a default container ingests that venue and
nothing else — neither Hyperliquid row is there either, which the
previous wording implied was fine.

No code change; `cargo test --workspace` (1083 tests) and
`cargo clippy --all-targets` clean.
@juan-malbeclabs

Copy link
Copy Markdown
Contributor Author

Pushed c781482 for the Copilot review. Docs only — no code, no test changes.

The prose now follows the value. Four sentences still called the universe sports: three in the elections rows' notes ("separate from perps and sports", "the rule the sports row uses", "the way the sports feeds are") and the socket-count caveat in docs/input-sources.md, plus the /v1/products feed_kind and elections entries in the same unreleased CHANGELOG.md section. A reader following any of them looked for a category that no longer resolves. sport is kept only where it names a channel or a kind of market, and the group code edge-kalshi-sports-mbp is unchanged everywhere.

The hosted-document caveat is now dated and says what the document holds. That caveat already existed (b67b865), but it named the two rows it was missing. Re-fetched today, DZ_FEED_REGISTRY_URL serves three rows, all Kalshi — no Phoenix row, no elections rows and neither Hyperliquid row — so a default container ingests that venue and nothing else. Republishing it is outside this repo; the entry still gives clearing the variable as the single-host workaround.

cargo test --workspace --locked green (1083 tests, 0 failures), cargo clippy --all-targets --locked clean, cargo fmt --check clean.

@juan-malbeclabs

Copy link
Copy Markdown
Contributor Author

cargo-deny is red here on RUSTSEC-2026-0285 (rustls 0.23.40 accepting TLS 1.3 handshake messages at the wrong encryption level), which is main's lockfile and not this branch's change. #160 pins it forward to 0.23.45, lockfile only, and is green. Once that lands this branch needs main merged in to pick the lock up — cherry-picking the bump here instead would only collide with it.

juan-malbeclabs added a commit that referenced this pull request Sep 18, 2026
rustls 0.23.40 accepted a TLS 1.3 handshake message that followed a key-changing
message in the same record — a plaintext EncryptedExtensions behind the
ServerHello — where RFC 8446 section 5.1 says to terminate with
unexpected_message. The transcript stays authenticated, so it is not a way to
alter or complete a handshake; it lets a peer send in plaintext what should have
been encrypted without the connection being refused.

Lockfile only: 0.23.45, with rustls-webpki 0.103.13 -> 0.103.15 alongside it.
Nothing in this crate chose the version — it arrives through reqwest and
tokio-tungstenite, which is also the whole of the exposure, since rustls is used
for outbound client TLS only: the wss:// public inputs and the JSON-RPC and
registry fetches. This process terminates no TLS itself.

`cargo deny check advisories` is a required status, so this was red on every open
pull request, #152 included, and none of them could have fixed it.
@juan-malbeclabs
juan-malbeclabs merged commit 91758e5 into main Sep 18, 2026
8 checks passed
@juan-malbeclabs
juan-malbeclabs deleted the jo/kalshi-events-category branch September 18, 2026 17:30
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.

3 participants