Repository navigation
Conversation
okapitula
marked this pull request as ready for review
October 21, 2026 10:45
okapitula
requested review from
jakubfilinger-b,
klaudia-blazyczek-blurify,
marek-chmielowski-blurify,
mp-blurify and
zaxovaiko
as code owners
October 21, 2026 10:45
zaxovaiko
removed request for
jakubfilinger-b,
klaudia-blazyczek-blurify,
mp-blurify and
zaxovaiko
October 21, 2026 10:45
okapitula
force-pushed
the
feat/sportsbook-module
branch
from
October 21, 2026 11:47
0546af7 to
e0ad5cf
Compare
klaudia-blazyczek-blurify
requested changes
Oct 21, 2026
okapitula
force-pushed
the
feat/sportsbook-module
branch
from
October 21, 2026 13:38
e0ad5cf to
195d8ea
Compare
okapitula
force-pushed
the
feat/sportsbook-module
branch
2 times, most recently
from
October 25, 2026 12:18
eda3d61 to
34224fc
Compare
Adds sportsbook/betting: the provider settles bets, core holds the money. - Ledger port: reserve, confirm, cancel, settle and reopen. Each runs in one transaction, idempotent on the provider transaction id plus a request fingerprint, under one lock order (config -> player -> bet -> wallet/debt rows -> audit_log). Wallet refs are tx:<provider tx id> for a callback move, offset:<paying provider>:<provider tx id>:<shortfall id> for the debt offset a payout triggers, and collect:<idempotency key>:<shortfall id> for an admin collect. Provider tx ids under core-align: are refused; align writes its own there. - Confirm terms: legs may change only their odds; with decimal odds the potential payout must move in the direction of the odds product. - Sessions with a country blocklist; RG cooling-off or self-exclusion revokes live sessions through a retried job, audited in-transaction, that retries until the provider acknowledges each revoke; the reconcile cron retries any revoke still unacknowledged, least recently tried first, and revokes the live sessions of any player play eligibility refuses. An acknowledgement counts only for a revoke sent after core recorded the provider's open. The provider's session lookup refuses a restricted player. - Reopen correction debit; any shortfall becomes sportsbook debt with a wallet withdrawal hold, collectable or write-off by an admin under a required idempotency key. Each shortfall is kept with its bet. A later win's offset and an admin collect both take them back oldest first, under the owing bet's provider and round and as the reopen's leg type, so RG loss nets it and a reopened void's stake still counts as wagered. A reopen asking more than the wallet paid its round is refused as invalid_amount and filed. - Reconciliation findings, admin align-with-provider, and a reconcile cron for stale reserves and overdue accepted bets. Align applies the provider's answer only to the bet state it asked about. - RG loss-limit re-evaluation on placed, settled, cancelled and reopened, and on every debt change. KYC is treated as for casino: no play-time check. - Audit: each bet leg writes its audit row in its own transaction, with the player as actor. Every bet, debt, balance and restriction event goes to the outbox in its transaction; the RG eval and revoke consumers run once per event. - Test provider plugin and three E2E suites (ledger, reconcile, routes) booted through bootE2e. Contracts outside the module: LanguageTagSchema is shared by the session locale and the casino category translation keys; it now also accepts a one-character subtag that another subtag follows (en-US-u-hc-h12). Deploy workers before producers: older workers reject the new sportsbook.rg-revoke queue and RG eval trigger values.
…roduct Migration 0018 gave rakeback and the rank challenge product lists that default to casino, live-casino and pvp. That default also landed on ladders configured before the lists existed, so an operator who already paid rakeback on sportsbook silently stopped doing so. Migration 0019 sets both lists to empty (every product) on rows that still hold 0018's default. A list an operator has changed is kept, so 0019 is safe to run after one was saved; a list saved as exactly the default set is indistinguishable and is reset too. Ladders created afterwards keep the narrow default. A rank challenge on an install with no ladder settings row is out of scope here; its fallback lives with the product lists.
The player's client navigates to the launch url the provider returns, and core passed it through unchecked, so a javascript: or plain http url reached the player. Core now parses the provider's session result against SportsbookProviderSessionSchema: a launch url that is not https takes the same path as a refused issue, revoking the session at both ends and answering 503. The session response schema is https-only too.
okapitula
force-pushed
the
feat/sportsbook-module
branch
from
October 26, 2026 09:11
34224fc to
e4e8a5e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #334. Review that PR first; this one retargets to
devonce it merges.Summary
Adds
sportsbook/betting, a provider-settled sportsbook: a third-party provider runs odds, bet acceptance and settlement in its own widget, and core holds the money behind one command port,SPORTSBOOK_LEDGER. It also backfills the gamification product lists so ladders configured before they existed keep paying rakeback on every product.Why
Operators want to offer sports betting through a turnkey provider without core learning any vendor's wire protocol. Every provider callback moves money, has to be idempotent against provider retries, and can be reversed after the winnings were withdrawn, so the ledger step, its lock order and its debt handling belong in core. The signature check and the codec belong in the operator's overlay. ADR-0046 records the decision.
What ships:
reserve,confirm,cancel,settleandreopen. Each runs in one transaction, is idempotent on the provider transaction id plus a request fingerprint, and follows one lock order: config, then player, then bet, then wallet and debt rows, then audit. A replay with the same content returns the first answer and moves nothing. A reused id with different content is refused and filed as a finding.emitInTransaction). Its consumers (RG evaluation, session revoke) run once per event, so a redelivery adds no second job.bootE2e.fix(gamification): migration 0019 sets the rakeback and rank challenge product lists to empty (every product) onpromo_rank_configrows still holding 0018's default, so a ladder that already paid on sportsbook keeps doing so. A list an operator changed is kept. New ladders keep the narrow default.Outside the module,
LanguageTagSchema(shared by the session locale and the casino category translation keys) now accepts a one-character subtag followed by another subtag, such asen-US-u-hc-h12.Diagram
Alternatives considered
GAMING_COMMANDS: the overlay would then own the lock order and the transaction boundary.Risks
blockedCountriesis re-checked at reserve.sportsbook.rg-revokequeue and the new RG eval trigger values.audit_logrow in its own transaction, so it holds the global audit chain lock until commit. The lock is taken last, but callbacks serialize with every other audited write while they hold it.