Skip to content

Latest commit

 

History

History
142 lines (124 loc) · 7.18 KB

File metadata and controls

142 lines (124 loc) · 7.18 KB

Compliance & Sandbox Boundaries

This document captures the non-negotiable constraints the MVP must respect today and the boundaries that protect us when we eventually go live. It is intentionally short — every rule here is something that, if broken, would cost us in either trust, money, or both.

Status: living document. Update with every PR that materially changes what tokens, payments, or user data do in the system.

Scope

  • "Today" means the sandbox-first MVP described in AGENTS.md.
  • "Live" means any future state where real money or real user funds enter or exit the system.

Rules in force today (sandbox phase)

  1. Tokens are internal test units. They are not crypto, not redeemable for cash, not exchangeable for goods or services. They have no monetary value outside this MVP.
  2. No real-money withdrawals exist — no flow that converts internal tokens back into fiat or any other store of value.
  3. Deposits use the M8 mock provider by default (NUVEI_MODE=mock). No external PSP credentials are required for MVP. Real provider HTTP is deferred (ADR-0011). Mock flow + grant_tokens are supported funding paths.
  4. Tokens only move via ledger entries. No code path may mutate wallet.balance directly — there is no balance column on Wallet, by design. Balance is always SUM(LedgerEntry.amount) for the wallet, computed at query time. The only sanctioned writer to the ledger is wallets.services.record_entry. Reviewers should reject any PR that calls LedgerEntry.objects.create() directly. Two DB-level CheckConstraints back this up (amount != 0 and reference_type != "") so even a misbehaving service cannot produce a meaningless or untraceable entry.
  5. Payment-to-ledger traceability is mandatory. Every ledger entry caused by a payment links back to the originating PaymentTransaction. Every ledger entry caused by a bet must link back to the originating Bet via (reference_type, reference_id). As of M6 this is enforced for stake debits: every bet_stake LedgerEntry carries reference_type="bet", reference_id=Bet.id, and a derived idempotency_key=f"bet-stake-{Bet.id}". betting.services.place_bet is the only sanctioned creator of Bet rows and writes the bet and the ledger debit in one DB transaction. As of M7, winning payouts (bet_payout) and void refunds (bet_void) use the same reference pair with derived keys f"bet-payout-{Bet.id}" / f"bet-void-{Bet.id}". The per-match SettlementEvent row (unique idempotency_key=f"settle-{match.id}") is the durable anchor that makes the entire settlement run safe to retry — see betting.services.settle_match. As of M8, deposit credits (deposit) follow the same shape: reference_type="payment_transaction", reference_id=PaymentTransaction.id, derived idempotency_key=f"deposit-{PaymentTransaction.id}". The per-event (PaymentTransaction, provider_event_id) unique index on PaymentEvent is the durable webhook-replay anchor — see payments.services.record_provider_event and ADR-0007.
  6. Server is the source of truth for balances, odds snapshots, match results, and settlement outcomes. The client is never trusted for any of those values. As of M6, odds are frozen onto Bet.odds_snapshot at placement; M7 settlement credits payouts from Bet.potential_payout (computed at placement from the snapshot), never live Match.odds_*.
  7. Idempotency on every retriable write — bet placement (M6), settlement runs (M7), deposits (M8), payment-provider callbacks (M8), GRID sync HTTP (M5b — ExternalSyncLog audit; settlement idempotent per match via SettlementEvent). Duplicate input must not produce duplicate effect. POST /api/bets/ enforces this: the Idempotency-Key header is required (400 if missing); replays return the original bet with 200 OK and write no second ledger entry. settle_match is idempotent per match: a second call returns the existing SettlementEvent and writes no additional ledger rows. POST /api/payments/deposits/ requires the same Idempotency-Key header (400 if missing); replays return the original PaymentTransaction with 200 OK and never call the provider a second time. Provider webhook delivery (or, in M8 mock, the simulate-callback endpoint) is idempotent on (PaymentTransaction, provider_event_id); the wallet deposit credit is itself idempotent on its derived ledger key. Three layers of belt-and-braces — even a wildly broken caller cannot double-credit.

User data we collect today

Minimal by design:

  • Email (login identifier). Users may also be created via POST /api/auth/register/ (self-serve) — there is no email verification, age gate, or KYC in MVP; duplicate emails return 409 (code: EmailAlreadyRegistered). See ADR-0008.
  • Password (Django-hashed, never logged, never returned in any API response — see accounts.serializers.UserSerializer).
  • Session records produced by Django sessions (django_session).

Out of scope for the MVP: real names, addresses, payment instruments, phone numbers, government IDs, geolocation, behavioral analytics.

Things we explicitly do NOT do yet

  • No real-money flows. Live-mode payment credentials must not be configured in any deployed environment.
  • No KYC / AML automation. Real launches will require this; the MVP doesn't pretend to do it.
  • No responsible-gambling tooling (deposit limits, self-exclusion, cool-down periods). To be designed before any non-sandbox release.
  • No bonuses, promotions, referral credits, or marketing token giveaways. Each of these would require its own ledger entry type and audit story; we keep the surface clean.
  • No raw provider payloads in API responses. Webhook bodies and provider-side metadata stay backend-internal.

When we approach "going live"

Before any environment is flipped to a non-sandbox payment mode, this document must be expanded with:

  • Jurisdictional review — where can the product legally operate.
  • KYC/AML strategy — provider, retention, escalation.
  • Responsible-gambling controls — limits, self-exclusion, audit log.
  • Data-retention policy — for ledger, payment, and session records.
  • Incident response — for payment disputes, refund handling, and webhook spoofing attempts.

These are deliberately not in MVP scope and should not be added piecemeal.

Related

  • AGENTS.md — non-negotiable engineering rules.
  • docs/architecture.md — domain boundaries and idempotency strategy.
  • docs/api-contracts.md — endpoint shapes.
  • docs/decisions/0001-use-monorepo.md
  • docs/decisions/0002-python-code-style.md
  • docs/decisions/0003-database-conventions.md
  • docs/decisions/0004-m7-bet-settlement.md
  • docs/decisions/0007-m8-nuvei-deposits.md
  • docs/decisions/0008-self-serve-registration.md
  • docs/decisions/0009-m5b-grid-ingestion.md — GRID CS2 sync.
  • docs/decisions/0010-multi-game-catalog-target.md — planned multi-game shape.
  • docs/decisions/0011-psp-deferred-mvp-completion.md — mock-only MVP; PSP deferred.