Skip to content

fix(gamification)!: every product list names its products, with a backfill - #398

Open
okapitula wants to merge 2 commits into
fix/sportsbook-rakeback-at-settlefrom
fix/eligible-products-one-rule
Open

okapitula wants to merge 2 commits into
fix/sportsbook-rakeback-at-settlefrom
fix/eligible-products-one-rule

Conversation

@okapitula

@okapitula okapitula commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Rank, rakeback, rank-challenge and streak product lists must name their products when saved, the same rule races follow.
  • One shared input schema (known WagerProduct values, at least one, none repeated) validates races, PUT /backoffice/promo/ranks/config and PUT /backoffice/promo/streaks/config.
  • Migration 0022_rank_streak_products_required drops the empty defaults on the rank and streak lists and gives every empty list all four products.
  • An empty list already stored still counts every product. The previous release can still save one during a rolling upgrade (see Risks).
  • The streak seed takes the input type, the example ladder counts WAGER_PRODUCTS, fixtures name their products, and docs/modules/gamification.md states the rule.
  • Tests:
    • New backfill integration test: empty lists become all four, a sportsbook settle still pays rakeback, named lists are kept, and an empty list saved after the upgrade still counts every product.
    • Hostile E2E cases on the rank config save, and a new E2E for the streak config save.

Why

Races already required an explicit list, while rank, rakeback, the rank challenge and the streak still read an empty one as "every product". That made two rules for the same setting, and an empty list silently counted products the operator never chose, sportsbook rakeback included. Now there is one rule for what a save accepts, with a backfill so no live config changes what it counts.

Alternatives considered

  • Add the CHECKs and drop the empty-list branch now, as races' 0020 did. An instance still on the previous release accepts [] and would get a 500 from the CHECK mid-rollout. Dropping the empty-list branch now would make an empty list it saves after the backfill count nothing. So the database constraint and the runtime cleanup wait for a follow-up.
  • Read an empty list as "nothing". This silently stops rank progress and rakeback on every upgraded empty config.

Risks

  • Breaking input change. Rank and streak eligibleProducts accept only WagerProduct values and must be non-empty. The rakeback and rank-challenge lists must be non-empty when sent. A downstream backoffice that sends [] or a free string gets a 400.
  • Legacy free-string lists. A non-empty list holding unknown strings (for example ['slots']) is kept and still counts nothing. The enum input refuses it, so the operator has to fix that list the next time they save the config.
  • Rolling deploy, in two steps. This release changes the writers and backfills, and leaves out the database constraint. An instance on the previous release can still save [] until it drains, and that list keeps counting every product. A follow-up release, shipped once no instance runs the previous one, backfills again, adds CHECK (cardinality(...) > 0) on the four columns, and drops the empty-list branch from rank, rakeback, streak and the rank challenge. Dropping the defaults is safe now: the previous contract already requires eligibleProducts, so its writers always send it.
  • No audit row for the backfill. The backfill rewrites stored config without an audit record, so an operator's last config audit row can show [] while the next one's before shows all four products. 0019 and 0020 did the same.
  • Migration number. 0022 is the next free number on the base. It gets renumbered if feat(gamification): pay rank periodic bonuses on the house edge #372 (which holds 0022_rank_periodic_margin) merges first.

…kfill

An empty rank, rakeback, rank-challenge or streak product list no longer
means every product. Like races, each list must name known products, at
least one, none repeated; the admin config saves refuse anything else.

Migration 0022 drops the empty defaults, gives every empty list all four
products so no live config changes what it counts, and adds a non-empty
CHECK on all four columns.

BREAKING CHANGE: rank and streak eligibleProducts accept only WagerProduct
values and must be non-empty, and the rakeback and rank-challenge lists must
be non-empty when sent. A save with [] or an unknown product returns 400.
@okapitula
okapitula force-pushed the fix/eligible-products-one-rule branch from b28208c to 2431798 Compare November 5, 2026 11:49

@jakubfilinger-b jakubfilinger-b left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The new constraints need a rollout compatible with the previous API version.

An instance on the previous release still accepts an empty list. With
the CHECKs in 0022 its save failed with a 500, and without the empty
branch a list it saved after the backfill counted nothing. 0022 now
only backfills and drops the defaults; the CHECKs and the empty-list
branch go in a follow-up once the previous release has drained.
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.

2 participants