Files
OmniRoute/docs/i18n/pl/docs/frameworks/GAMIFICATION.md
Diego Rodrigues de Sa e Souza 8feea123bb feat(docs): mirror every docs/ page in all 65 locales (#14106)
* feat(docs): mirror every docs/ page in all 65 locales

Extends the documentation mirrors from the 22-page core set (#13940) to
every Markdown page under docs/: 152 sources x 65 locales = 9,880 mirrors
(6,208 new), language bars rewritten for the full locale list, state
adopted so the blocking drift gate now covers all 152 pages.

run-translation.mjs: an oversized block made only of table rows or list
items (PROVIDER_REFERENCE.md 244-row table, FREE_TIERS.md 71-item list) is
cut at item boundaries and rejoined without a blank line — the single
16-40 KB request outlived the backend socket for verbose scripts. 48
older mirrors whose tables had lost rows were retranslated with --force.

* docs(i18n): refresh mirrors for the sources the base changed since the branch cut

Section-level retranslation of the 29 docs (and README.md) whose source
or mirrors moved on release/v3.8.51 during the run, then state adoption;
the drift gate is green again on the merged tree.
2026-09-18 13:16:46 -03:00

50 KiB
Raw Blame History

Gamification & Leaderboard System (Polski)

🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇱 he · 🇮🇳 hi · 🇭🇷 hr · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇮🇹 it · 🇯🇵 ja · 🇬🇪 ka · 🇰🇭 km · 🇮🇳 kn · 🇰🇷 ko · 🇱🇹 lt · 🇱🇻 lv · 🇮🇳 ml · 🇮🇳 mr · 🇲🇾 ms · 🇲🇹 mt · 🇲🇲 my · 🇳🇵 ne · 🇳🇱 nl · 🇳🇴 no · 🇮🇳 or · 🇮🇳 pa · 🇵🇭 phi · 🇵🇹 pt · 🇧🇷 pt-BR · 🇷🇴 ro · 🇷🇺 ru · 🇱🇰 si · 🇸🇰 sk · 🇸🇮 sl · 🇷🇸 sr · 🇸🇪 sv · 🇰🇪 sw · 🇮🇳 ta · 🇮🇳 te · 🇹🇭 th · 🇹🇷 tr · 🇺🇦 uk-UA · 🇵🇰 ur · 🇺🇿 uz · 🇻🇳 vi · 🇳🇬 yo · 🇨🇳 zh-CN · 🇹🇼 zh-TW


title: "System grywalizacji i rankingów" version: 3.8.40 lastUpdated: 2026-06-28

System grywalizacji i rankingów

Source of truth: src/lib/gamification/, src/lib/db/gamification.ts, src/app/api/gamification/ Last updated: 2026-06-28 — v3.8.40

OmniRoute zawiera lokalną warstwę grywalizacji, która nagradza użytkowników za aktywność na platformie — wysyłanie zapytań, przełączanie providerów, tworzenie combo, udostępnianie tokenów i wkład w społeczność. Cały stan żyje w SQLite; federacja z serwerami społecznościowymi jest opcjonalna i oparta na push.

System jest zaprojektowany tak, by zapewniać zerowe opóźnienie na ścieżce gorącej — zdarzenia grywalizacji są wysyłane w trybie fire-and-forget z potoku żądań i nigdy nie blokują odpowiedzi LLM.


Przegląd

Cel

Zwiększyć zaangażowanie i retencję użytkowników przez widoczny postęp (XP, poziomy, odznaki), społeczny dowód (rankingi) oraz zachęty ekonomiczne (udostępnianie tokenów, nagrody za zaproszenia).

Zakres

Feature Description
XP & Levels Zdobywaj XP za akcje; awansuj wzdłuż krzywej wielomianowej
Badges 20+ osiągnięć w 5 kategoriach z 4 poziomami rzadkości
Streaks Śledzenie codziennej aktywności z bieżącą/najdłuższą serią
Leaderboards Zakresy: globalny, tygodniowy, miesięczny, udostępnianie tokenów i wkład
Token Sharing Transfer kredytów między użytkownikami przez księgę double-entry
Invite & Redeem Kody polecające z przechowywaniem hashowanym SHA-256
Community Servers Federacja z zewnętrznymi instancjami OmniRoute
Anti-Cheat Punktacja po stronie serwera, rate limiting, detekcja anomalii z-score

Zasady projektowe

  1. Local-first — cały stan w SQLite, bez wymaganych usług zewnętrznych.
  2. Non-blocking — zdarzenia są fire-and-forget; ścieżka odpowiedzi LLM nigdy nie jest opóźniana przez logikę grywalizacji.
  3. Server-authoritative — XP jest liczone wyłącznie po stronie serwera; klienci nie mogą zawyżać wyników.
  4. Privacy-respecting — udział w rankingu jest opcjonalny; użytkownicy mogą ukryć profil.
  5. Federation-ready — serwery społecznościowe mogą pushować wyniki przez podpisane API; synchronizacja jest nadpisująca, nie addytywna.

Architektura

Przepływ wysokiego poziomu

Żądanie klienta
  → /v1/chat/completions
    → handleChatCore()                      [open-sse/handlers/chatCore.ts]
      → ... (istniejący potok) ...
      → odpowiedź nadrzędna wysłana do klienta
      → setImmediate (uruchom i zapomnij):
        → emitGamificationEvent()           [src/lib/gamification/events.ts]
          → awardXp()                       [src/lib/gamification/xp.ts]
          → updateStreak()                  [src/lib/gamification/streaks.ts]
          → evaluateBadges()                [src/lib/gamification/badges.ts]
          → updateLeaderboard()             [src/lib/gamification/leaderboard.ts]
          → checkAnomalies()                [src/lib/gamification/antiCheat.ts]

Emiter zdarzeń stanowi pojedynczy punkt integracji. chatCore.ts wywołuje emitGamificationEvent() po wysłaniu odpowiedzi; moduł zdarzeń przekazuje ją do podsystemów XP, serii aktywności, odznak, tabeli wyników i ochrony przed oszustwami.

Graf zależności modułów

src/lib/gamification/
  events.ts          ← punkt wejścia (wywoływany z chatCore.ts)
    ├── xp.ts        ← obliczanie XP i ustalanie poziomu
    ├── streaks.ts   ← śledzenie codziennej serii aktywności
    ├── badges.ts    ← ocena kryteriów odznak
    ├── leaderboard.ts ← obliczanie pozycji i rozgłaszanie przez SSE
    ├── antiCheat.ts ← ograniczanie częstotliwości i wykrywanie anomalii
    ├── sharing.ts   ← rejestr transferów tokenów
    ├── invites.ts   ← zarządzanie kodami zaproszeń i ich realizacją
    ├── servers.ts   ← federacja serwerów społecznościowych
    └── notifications.ts ← strumień powiadomień SSE

src/lib/db/
  gamification.ts    ← wszystkie operacje CRUD (8 tabel)

src/app/api/gamification/
  leaderboard/       ← GET rankingi, POST ręczne odświeżenie
  leaderboard/stream ← aktualizacje w czasie rzeczywistym przez SSE
  transfer/          ← GET historia, POST wysyłanie tokenów
  invite/            ← GET/POST kody, DELETE unieważnienie
  invite/redeem/     ← POST realizacja kodu
  servers/           ← GET/POST/DELETE serwery społecznościowe
  federation/score/  ← POST przekazanie wyniku do serwera
  federation/leaderboard/ ← GET pobranie tabeli wyników z serwera
  notifications/     ← powiadomienia SSE o odznakach/awansach na wyższy poziom
  anomalies/         ← GET raporty anomalii (administrator)
  rotate/            ← POST rotacja sekretów tokenów zaproszeń

Warstwa danych

Tabele bazy danych

Wszystkie tabele żyją w głównej bazie SQLite OmniRoute, tworzonej przez migrację 060_create_gamification.sql. Journaling WAL jest dziedziczony z singletona getDbInstance() w src/lib/db/core.ts.

┌─────────────────────────┐     ┌──────────────────────────┐
│      leaderboard        │     │      user_levels          │
├─────────────────────────┤     ├──────────────────────────┤
│ id            TEXT PK   │     │ api_key_id    TEXT PK    │
│ api_key_id    TEXT      │     │ xp            INTEGER    │
│ scope         TEXT      │     │ level         INTEGER    │
│ score         INTEGER   │     │ title         TEXT       │
│ period        TEXT      │     │ updated_at    TEXT       │
│ updated_at    TEXT      │     └──────────────────────────┘
└─────────────────────────┘
                │
                │ 1:N
                ▼
┌─────────────────────────┐     ┌──────────────────────────┐
│     user_badges         │     │    badge_definitions      │
├─────────────────────────┤     ├──────────────────────────┤
│ id            TEXT PK   │     │ id            TEXT PK    │
│ api_key_id    TEXT      │     │ name          TEXT       │
│ badge_id      TEXT FK   │     │ category      TEXT       │
│ earned_at     TEXT      │     │ rarity        TEXT       │
│ notified      INTEGER   │     │ criteria_type TEXT       │
└─────────────────────────┘     │ criteria      TEXT(JSON) │
                                │ description   TEXT       │
                                │ icon          TEXT       │
                                │ hidden        INTEGER    │
                                └──────────────────────────┘

┌─────────────────────────┐     ┌──────────────────────────┐
│     xp_audit_log        │     │     token_ledger         │
├─────────────────────────┤     ├──────────────────────────┤
│ id            TEXT PK   │     │ id            TEXT PK    │
│ api_key_id    TEXT      │     │ from_key_id   TEXT       │
│ action        TEXT      │     │ to_key_id     TEXT       │
│ xp_awarded    INTEGER   │     │ amount        INTEGER    │
│ metadata      TEXT(JSON)│     │ idempotency_key TEXT UQ  │
│ created_at    TEXT      │     │ created_at    TEXT       │
└─────────────────────────┘     └──────────────────────────┘

┌─────────────────────────┐     ┌──────────────────────────┐
│    invite_tokens        │     │   community_servers      │
├─────────────────────────┤     ├──────────────────────────┤
│ id            TEXT PK   │     │ id            TEXT PK    │
│ api_key_id    TEXT      │     │ name          TEXT       │
│ code          TEXT UQ   │     │ url           TEXT       │
│ token_hash    TEXT      │     │ token_hash    TEXT       │
│ uses          INTEGER   │     │ status        TEXT       │
│ max_uses      INTEGER   │     │ last_sync     TEXT       │
│ created_at    TEXT      │     │ created_at    TEXT       │
│ expires_at    TEXT      │     └──────────────────────────┘
└─────────────────────────┘

Moduł domenowy: src/lib/db/gamification.ts

Podąża za standardowym wzorcem OmniRoute — importuje getDbInstance() z core.ts, eksportuje typowane funkcje CRUD. Bez surowego SQL w handlerach tras.

Kluczowe funkcje:

Function Description
upsertLeaderboardEntry() Wstawienie lub aktualizacja wyniku dla (api_key_id, scope, period)
getLeaderboard() Stronicowane rankingi dla danego scope/period
getUserLevel() Pobranie lub utworzenie rekordu poziomu użytkownika
updateUserLevel() Atomowe ustawienie XP, poziomu i tytułu
getBadgeDefinitions() Wszystkie definicje odznak (opcjonalnie filtrowane)
getUserBadges() Odznaki zdobyte przez użytkownika
awardBadge() Wstawienie zdobycia odznaki (idempotentne na badge_id)
logXpAction() Dopisanie do xp_audit_log
getXpAuditLog() Stronicowana historia audytu użytkownika
insertLedgerEntry() Transfer double-entry (w transakcji)
getBalance() Suma otrzymanych minus wysłanych dla użytkownika
getTransferHistory() Stronicowany dziennik transferów
createInviteToken() Wstawienie kodu zaproszenia + zahashowanego tokenu
redeemInviteToken() Wyszukanie po kodzie, walidacja, inkrementacja uses
upsertCommunityServer() Rejestracja lub aktualizacja serwera federacji
getCommunityServers() Lista serwerów użytkownika
deleteCommunityServer() Usunięcie rejestracji serwera

System XP / poziomów

Plik: src/lib/gamification/xp.ts

Krzywa poziomów

Liczba XP wymagana do osiągnięcia poziomu n jest określona krzywą wielomianową:

xp_for_level(n) = floor(100 * n^1.5)
Poziom XP do następnego poziomu Łączne XP Tytuł
1 100 100 Początkujący
5 1,118 2,415 Początkujący
10 3,162 10,523 Odkrywca
25 12,500 86,024 Odkrywca
50 35,355 345,529 Ekspert
75 64,952 948,683 Mistrz
100 100,000 2,050,000 Legenda

Tytuły

Zakres poziomów Tytuł
1 9 Początkujący
10 24 Odkrywca
25 49 Ekspert
50 74 Mistrz
75 100 Legenda

Nagrody XP

Działanie XP Opis
request 1 Za każde żądanie API kierowane przez OmniRoute
provider_switch 5 Przełączenie na innego dostawcę
model_switch 3 Przełączenie na inny model
combo_create 10 Utworzenie nowego zestawu
combo_use 2 Użycie zestawu dla żądania
token_share 1 Za każde 1 000 tokenów udostępnionych innemu użytkownikowi
invite_redeem 50 Wykorzystanie kodu zaproszenia
daily_login 5 Codzienna aktywność (raz dziennie)
streak_bonus 2 Za każdy kolejny dzień serii (pomnożone przez długość serii)
badge_unlock 10 Odblokowanie odznaki

Przebieg przyznawania

export async function awardXp(
  apiKeyId: string,
  action: XpAction,
  metadata?: Record<string, unknown>
): Promise<{ xp: number; level: number; title: string; levelUp: boolean }>;
  1. Odczytaj XP_REWARDS[action], aby uzyskać liczbę XP.
  2. Przekaż przez checkRateLimit() (ochrona przed oszustwami: maks. 1000 XP/min na klucz).
  3. Otwórz transakcję:
    • Odczytaj bieżący wiersz user_levels.
    • Dodaj XP; ponownie oblicz poziom za pomocą levelFromXp(totalXp).
    • Jeśli poziom się zmienił, ustaw levelUp = true.
    • Zaktualizuj wiersz user_levels.
    • Wstaw wpis do xp_audit_log.
  4. Zwróć wynik. Kod wywołujący obsługuje powiadomienia.

Funkcja pomocnicza: levelFromXp(totalXp)

Iteruje po poziomach 1..100, sumując xp_for_level(n), aż łączna liczba XP przekroczy totalXp. Zwraca najwyższy poziom, którego próg został osiągnięty. Złożoność wynosi O(100) — jest to akceptowalne, ponieważ maksymalny poziom to 100.


System odznak

File: src/lib/gamification/badges.ts

Kategorie

Category Description Example Badges
usage Kamienie milowe oparte na wolumenie First Request, 1K Requests, 100K
sharing Udostępnianie tokenów i polecenia First Share, Generous (10 shares)
contribution Zaangażowanie społecznościowe Combo Creator, Provider Explorer
streak Konsekwencja w czasie Week Warrior, Monthly Devoted
rare Trudne lub ukryte osiągnięcia Early Adopter, Bug Reporter

Rzadkości

Rarity Color Probability Hint
common Gray Większość użytkowników
uncommon Green Aktywni użytkownicy
rare Blue Zaangażowani użytkownicy
legendary Gold Top 1%

Typy kryteriów

Type Field Description
action_count count Wykonaj akcję N razy (np. 1000 requestów)
streak days Utrzymaj serię przez N kolejnych dni
unique_count field, n Użyj N unikalnych wartości (np. 10 różnych modeli)
rank scope, n Osiągnij rangę N w zakresie rankingu
first Bądź pierwszym, który wykona akcję
hidden (varies) Kryteria niewidoczne do momentu zdobycia

Definicje odznak są przechowywane w badge_definitions jako JSON criteria:

{
  "type": "action_count",
  "action": "request",
  "count": 1000
}

Przepływ ewaluacji

emitGamificationEvent(event)
  → evaluateBadges(apiKeyId, event)
    → getBadgeDefinitions()           # all definitions
    → getUserBadges(apiKeyId)         # already earned (skip)
    → for each unearned badge:
       → matchesCriteria(badge, event, userState)
       → if match: awardBadge(apiKeyId, badgeId)
         → return notification payload

Ewaluacja jest sterowana zdarzeniami — uruchamia się po każdym zdarzeniu grywalizacji, ale sprawdza tylko odznaki, których criteria.type pasuje do akcji zdarzenia. To utrzymuje ewaluację szybką (< 5ms dla większości zdarzeń).

matchesCriteria(badge, event, userState)

Criteria Type Check
action_count getActionCount(apiKeyId, action) >= count
streak getCurrentStreak(apiKeyId) >= days
unique_count getUniqueCount(apiKeyId, field) >= n
rank getRank(apiKeyId, scope) <= n
first Brak wcześniejszego wpisu xp_audit_log dla tego typu akcji
hidden Deleguje do odpowiedniego pod-sprawdzenia

Wbudowane odznaki (20+)

Pełna lista odznak
Badge Category Rarity Criteria
First Steps usage common 1 request
Getting Warmed Up usage common 100 requests
Power User usage uncommon 1,000 requests
Centurion usage rare 10,000 requests
OmniPower usage legendary 100,000 requests
Provider Hopper contribution common Use 5 different providers
Provider Master contribution uncommon Use 20 different providers
Combo Architect contribution uncommon Create 5 combos
Combo Grandmaster contribution rare Create 25 combos
First Share sharing common 1 token transfer
Generous sharing uncommon 10 token transfers
Philanthropist sharing rare Transfer 10,000 tokens total
Referrer sharing common 1 successful referral
Network Builder sharing uncommon 10 successful referrals
Week Warrior streak uncommon 7-day streak
Monthly Devoted streak rare 30-day streak
Unstoppable streak legendary 365-day streak
Early Adopter rare legendary Join during beta period
Compression Pioneer rare uncommon Use compression 100 times
Skill Collector rare rare Use 10 different skills
Model Explorer contribution uncommon Use 15 different models

Tracker serii (streak)

File: src/lib/gamification/streaks.ts

Model danych

Serie są przechowywane w tabeli key_value (współdzielona tabela narzędziowa) pod kluczami w przestrzeni nazw:

Key Value Description
gamification:streak:{keyId} {current},{longest},{lastDate} Dane aktywnej serii

Logika

export async function updateStreak(
  apiKeyId: string
): Promise<{ current: number; longest: number; milestone: boolean }>;
  1. Odczytaj rekord serii z key_value.
  2. Sparsuj {current}, {longest}, {lastDate} (ciąg daty ISO).
  3. Jeśli lastDate === today — bez zmian (już policzone dziś).
  4. Jeśli lastDate === yesterday — inkrementuj current; zaktualizuj longest w razie potrzeby.
  5. Jeśli lastDate < yesterday — zresetuj current = 1 (seria przerwana).
  6. Zapisz zaktualizowany rekord.
  7. Sprawdź kamienie milowe: 7, 14, 30, 60, 90, 180, 365 dni. Jeśli przekroczono, ustaw milestone = true (caller przyznaje XP i sprawdza odznaki).

Przypadki brzegowe

  • Timezone: serie używają dat UTC (new Date().toISOString().slice(0, 10)). To zamierzone — jedna kanoniczna strefa czasowa zapobiega nadużyciom przez skakanie między strefami.
  • New users: brak rekordu serii; pierwsze żądanie tworzy go z current=1, longest=1, lastDate=today.
  • Multiple requests per day: tylko pierwsze żądanie dnia UTC inkrementuje serię.

Ranking (Leaderboard)

File: src/lib/gamification/leaderboard.ts

Zakresy

Scope Period Description
global all Skumulowane XP wszech czasów
weekly week XP zdobyte w bieżącym tygodniu UTC (pnnd)
monthly month XP zdobyte w bieżącym miesiącu UTC
tokens_shared all Suma tokenów przekazanych innym
contributions all Utworzone combo + użyte providery + użyte skille

Obliczanie rangi

Rangi są liczone w momencie odczytu, nie przechowywane. To unika nieaktualnych danych rangi i eliminuje potrzebę okresowych zadań przeliczania rang.

export async function getLeaderboard(
  scope: LeaderboardScope,
  period: string,
  limit: number,
  offset: number
): Promise<{ entries: LeaderboardEntry[]; total: number }>;

Wzorzec zapytania:

SELECT api_key_id, score,
       RANK() OVER (ORDER BY score DESC) as rank
FROM leaderboard
WHERE scope = ? AND period = ?
ORDER BY score DESC
LIMIT ? OFFSET ?

Rotacja okresów

Tygodniowe i miesięczne rankingi rotują automatycznie:

  1. Archive: na granicy okresu skopiuj bieżące wpisy do leaderboard_archive z etykietą okresu.
  2. Reset: usuń wpisy dla wygasłego okresu.
  3. Trigger: sprawdzane przy każdym wywołaniu updateLeaderboard(); pierwsze żądanie nowego okresu uruchamia rotację.

To gwarantuje, że tablice tygodniowe resetują się w każdy poniedziałek o 00:00 UTC, a miesięczne

  1. dnia każdego miesiąca.

Aktualizacje SSE w czasie rzeczywistym

Endpoint: GET /api/gamification/stream

Client → GET /api/gamification/stream
  → SSE connection established
  → Server sends top-10 leaderboard snapshot immediately
  → Every 5 seconds: push updated top-10 if changed
  → Every 15 seconds: heartbeat comment (": heartbeat\n\n")
  → Client disconnects → cleanup (remove listener)

Format zdarzenia:

event: leaderboard
data: {"scope":"global","entries":[...]}

event: leaderboard
data: {"scope":"weekly","entries":[...]}

: heartbeat

Menedżer SSE śledzi podłączonych klientów per scope i wysyła aktualizacje tylko wtedy, gdy dane rankingu faktycznie się zmieniły od ostatniego pusha.


Udostępnianie tokenów

File: src/lib/gamification/sharing.ts

Księga double-entry

Każdy transfer tworzy dwa wiersze w token_ledger:

Row from_key_id to_key_id amount
Debit sender receiver +amount
Credit receiver sender -amount

Czekaj — konwencja jest taka:

Row from_key_id to_key_id amount Meaning
Send sender receiver +amount Odpływ od nadawcy
Receive receiver sender +amount Dopływ do odbiorcy

Saldo jest liczone jako:

SELECT
  COALESCE(SUM(CASE WHEN to_key_id = ? THEN amount ELSE 0 END), 0)
  - COALESCE(SUM(CASE WHEN from_key_id = ? THEN amount ELSE 0 END), 0)
  AS balance
FROM token_ledger
WHERE from_key_id = ? OR to_key_id = ?

Przepływ transferu

export async function transferTokens(
  fromKeyId: string,
  toKeyId: string,
  amount: number,
  idempotencyKey: string
): Promise<{ success: boolean; balance: number }>;
  1. Validate: amount > 0, fromKeyId !== toKeyId.
  2. Idempotency: sprawdź, czy idempotency_key już istnieje w księdze. Jeśli tak, zwróć wynik z cache.
  3. Transaction (pojedyncza transakcja SQLite): a. Policz saldo nadawcy. b. Jeśli balance < amount, przerwij (niewystarczające środki). c. Wstaw wiersz send (from=sender, to=receiver, amount). d. Wstaw wiersz receive (from=receiver, to=sender, amount).
  4. Rate limit: sprawdź limit transferów nadawcy (max 10 transferów/min).
  5. Event: wyemituj zdarzenie grywalizacji token_share dla XP + ewaluacji odznak.
  6. Zwróć { success: true, balance: newBalance }.

Ograniczenia częstotliwości

  • Max 10 transferów na minutę na klucz API.
  • Max 10 000 tokenów na pojedynczy transfer.
  • Max 100 000 tokenów przelanych dziennie na klucz API.

Tokeny zaproszeń i redeem

File: src/lib/gamification/invites.ts

Format kodu

  • Code: 8-znakowy alfanumeryczny (np. A3K9-X7M2), czytelny dla człowieka, wyświetlany użytkownikowi.
  • Token: 32-bajtowy losowy token, przechowywany jako hash SHA-256. Używany do programowego redeem (np. linki URL).

Przechowywanie

Column Value
code A3K9X7M2 (unique, indexed)
token_hash SHA-256(raw_token)

Surowy token jest zwracany użytkownikowi dokładnie raz w momencie utworzenia. OmniRoute nigdy go potem nie przechowuje ani nie wyświetla — pozostaje tylko hash.

Zapobieganie self-referral

Gdy użytkownik redeemuje kod, system sprawdza:

  1. Kod należy do innego api_key_id.
  2. Redeemujący użytkownik nie redeemował wcześniej żadnego kodu od tego samego polecającego (join na invite_tokens + dziennik redeemów).

Jeśli którekolwiek sprawdzenie zawiedzie, redeem jest odrzucany z jasnym komunikatem błędu.

Wygaśnięcie i limity

  • Domyślne max_uses: 10 (konfigurowalne przy tworzeniu).
  • Domyślne expires_at: 30 dni od utworzenia.
  • Wygasłe lub wyczerpane kody zwracają HTTP 410 Gone.

Federacja serwerów społecznościowych

File: src/lib/gamification/servers.ts

Połączenie

Serwer społecznościowy jest rejestrowany przez token zaproszenia wystawiony przez zdalny serwer. Lokalna instancja:

  1. Odbiera token zaproszenia (np. wklejony w dashboard).
  2. Wywołuje POST /api/gamification/federation/leaderboard na zdalnym serwerze, by zwalidować token i pobrać bieżący ranking.
  3. Zapisuje rekord serwera ze status: connected.

Model synchronizacji

Federacja używa overwrite sync, nie addytywnej:

Local Instance                Community Server
      │                              │
      ├── push score ───────────────►│  POST /federation/score
      │   { api_key_id, score }      │  (server validates token hash)
      │                              │
      ├── pull leaderboard ─────────►│  GET /federation/leaderboard
      │◄── top-N entries ────────────┤  (overwrites local cache)
      │                              │
      └── health check ─────────────►│  GET /federation/health
          (every 60s, timeout 5s)    │

Auth

Żądania federacji zawierają:

Authorization: Bearer <raw_token>
X-Federation-Version: 1

Zdalny serwer haszuje token i wyszukuje pasujący wiersz community_servers. To unika przesyłania przechowywanego hasha.

Monitorowanie zdrowia

Każdy rekord serwera śledzi:

Field Description
status connected, degraded, unreachable
last_sync Znacznik czasu ISO ostatniej udanej synchronizacji
failures Kolejne nieudane health checki

Po 5 kolejnych niepowodzeniach status zmienia się na unreachable i synchronizacja jest wstrzymana do udanego ręcznego health checka.


Anti-Cheat

File: src/lib/gamification/antiCheat.ts

Punktacja po stronie serwera

Wszystkie obliczenia XP dzieją się w src/lib/gamification/xp.ts. Klienci nigdy nie przesyłają wyniku — przesyłają akcje, a serwer liczy XP. Kolumna leaderboard.score jest zapisywalna tylko przez kod serwerowy.

Rate limiting

Limit Value Scope
Max XP per minute 1,000 Per API key
Max transfers per min 10 Per API key
Max transfer amount 10,000 Per transfer
Max daily transfers 100,000 Per API key

Limity używają okna przesuwnego w pamięci (ten sam wzorzec co RateLimitManager w open-sse/services/). Przy restarcie procesu fallbackiem są liczniki oparte na SQLite.

Detekcja anomalii z-score

Dla każdego klucza API system utrzymuje rollingowe 7-dniowe okno XP zdobytego na godzinę. Przy każdym przyznaniu XP:

  1. Policz bieżącą godzinową stopę XP użytkownika.
  2. Policz średnią populacji i odchylenie standardowe.
  3. Oblicz z = (user_rate - mean) / stddev.
  4. Jeśli z > 3.0 (3 odchylenia standardowe), oznacz jako anomalię.

Anomalie są logowane do xp_audit_log z action = 'anomaly_detected' i prezentowane na dashboardzie admina.

Ślad audytu

Każde przyznanie XP, transfer, zdobycie odznaki i detekcja anomalii są logowane do xp_audit_log z:

Field Description
api_key_id Kto
action Co się stało (xp_award, transfer, anomaly, …)
xp_awarded Kwota (0 dla zdarzeń nie-XP)
metadata JSON z kontekstem (typ akcji, target, …)
created_at Kiedy (ISO 8601)

Admini mogą odpytać pełny ślad audytu przez GET /api/gamification/anomalies.


Trasy API

Wszystkie trasy podążają za standardowym wzorcem OmniRoute:

Route → CORS preflight → Body validation (Zod) → Auth (extractApiKey)
  → Handler

Endpointy

Method Path Description Auth
GET /api/gamification/leaderboard Pobierz ranking (scope, period, stronicowanie) Optional
POST /api/gamification/leaderboard Wymuś odświeżenie cache rankingu Required
GET /api/gamification/stream Aktualizacje rankingu SSE w czasie rzeczywistym Optional
GET /api/gamification/transfer Historia transferów (stronicowanie) Required
POST /api/gamification/transfer Wyślij tokeny do innego użytkownika Required
GET /api/gamification/invite Lista moich kodów zaproszeń Required
POST /api/gamification/invite Wygeneruj nowy kod zaproszenia Required
DELETE /api/gamification/invite Unieważnij kod zaproszenia Required
POST /api/gamification/invite/redeem Redeemuj kod zaproszenia Required
GET /api/gamification/servers Lista serwerów społecznościowych Required
POST /api/gamification/servers Połącz z serwerem społecznościowym Required
DELETE /api/gamification/servers Rozłącz z serwerem społecznościowym Required
POST /api/gamification/federation/score Push wyniku na zdalny serwer Federation
GET /api/gamification/federation/leaderboard Pull rankingu ze zdalnego serwera Federation
GET /api/gamification/notifications Powiadomienia SSE o odznakach/level-up Required
GET /api/gamification/anomalies Raporty anomalii (admin) Admin
POST /api/gamification/rotate Rotacja sekretów tokenów zaproszeń Required

Przykłady request/response

POST /api/gamification/transfer

// Request
{
  "to": "recipient-api-key-id",
  "amount": 500,
  "idempotencyKey": "uuid-v4"
}

// Response 200
{
  "success": true,
  "transfer": {
    "id": "txn-uuid",
    "from": "sender-api-key-id",
    "to": "recipient-api-key-id",
    "amount": 500,
    "createdAt": "2026-05-19T12:00:00.000Z"
  },
  "balance": 2500
}

// Response 400 (insufficient funds)
{
  "error": "Insufficient balance",
  "balance": 200,
  "requested": 500
}

GET /api/gamification/leaderboard?scope=weekly&limit=10

{
  "scope": "weekly",
  "period": "2026-W20",
  "entries": [
    {
      "rank": 1,
      "apiKeyId": "key-uuid",
      "displayName": "User***1234",
      "score": 15230,
      "level": 42,
      "title": "Expert"
    }
  ],
  "total": 847,
  "updatedAt": "2026-05-19T12:00:00.000Z"
}

Narzędzia MCP (8)

Zarejestrowane w open-sse/mcp-server/ obok istniejących narzędzi. Objęte zakresem uprawnień gamification.

Narzędzie Opis Schemat danych wejściowych
gamification_leaderboard Pobierz tabelę wyników dla zakresu/okresu { scope, period?, limit? }
gamification_rank Pobierz pozycję wywołującego i jego sąsiadów { scope }
gamification_profile Pobierz podsumowanie XP, poziomu, tytułu i serii {}
gamification_badges Wyświetl zdobyte odznaki lub wszystkie definicje { earned?: boolean }
gamification_transfer Wyślij tokeny innemu użytkownikowi { to, amount }
gamification_invite Wygeneruj lub wyświetl kody zaproszeń `{ action: "create" "list" }`
gamification_servers Wyświetl serwery społeczności lub połącz się z nimi { action, token? }
gamification_anomalies Wyświetl raporty anomalii (zakres administratora) { limit?, since? }

Strony dashboardu

/dashboard/leaderboard

  • Wyświetlanie podium (top 3 z awatarami i XP).
  • Selektor zakresu: Global / Weekly / Monthly / Tokens Shared / Contributions.
  • Stronicowana tabela (25 na stronę) z rangą, nazwą, wynikiem, poziomem, tytułem.
  • Aktualizacje SSE w czasie rzeczywistym — zmiany rang animują się.
  • Bieżący użytkownik podświetlony w tabeli ze sticky wierszem „Your Rank”.

/dashboard/profile

  • Pasek postępu XP z bieżącym poziomem i progiem następnego poziomu.
  • Odznaka tytułu wyświetlana w sposób wyróżniony.
  • Galeria odznak — zdobyte z datą, niezdobyte wyszarzone (ukryte odznaki pokazują „???” do momentu zdobycia).
  • Licznik serii z ikoną płomienia; kalendarz serii (ostatnie 30 dni).
  • Wykres historii XP (dzienne XP z ostatnich 30 dni).

/dashboard/tokens

  • Saldo tokenów (wyróżnione, na górze strony).
  • Formularz transferu: odbiorca, kwota, dialog potwierdzenia.
  • Tabela historii transferów z filtrami (sent/received/all).
  • Sekcja zaproszeń: aktywne kody, generowanie nowych, link do udostępnienia.
  • Serwery społecznościowe: lista ze statusem zdrowia, connect/disconnect.

/dashboard/gamification/admin

  • Lista anomalii z severity, użytkownikiem, znacznikiem czasu, z-score.
  • Przeglądarka dziennika audytu z filtrami (typ akcji, użytkownik, zakres dat).
  • Statystyki systemu: łączne przyznane XP, aktywni użytkownicy, stopy zdobywania odznak.
  • Przegląd zdrowia serwerów federacji.

Integracja z potokiem

Punkt integracji

Grywalizacja podpina się do potoku żądań w jednym punkcie w open-sse/handlers/chatCore.ts:

// After response is sent to client:
setImmediate(() => {
  emitGamificationEvent({
    type: "request.completed",
    apiKeyId,
    metadata: {
      provider: selectedProvider,
      model: selectedModel,
      comboId: resolvedCombo?.id,
      compressionUsed: compressionStats?.applied,
      skillUsed: skillExecution?.name,
    },
  }).catch(() => {
    // Fire-and-forget: log but never propagate to client
  });
});

Typy zdarzeń

Event Type When Emitted
request.completed Wysłano udaną odpowiedź LLM
provider.switch Zmieniono providera (liczy się fallback combo)
combo.created Zapisano nową konfigurację combo
combo.used Udana obsługa targetu combo
badge.earned Ewaluacja odznak znalazła dopasowanie
streak.milestone Przekroczono próg serii
transfer.sent Ukończono transfer tokenów
referral.redeemed Udany redeem kodu zaproszenia
compression.used Zastosowano kompresję promptu
skill.executed Ukończono wykonanie skillu
model.first_use Model nieużywany przez ostatnie 7 dni

Gwarancja non-blocking

Wzorzec setImmediate + .catch(() => {}) gwarantuje:

  1. Odpowiedź jest w pełni wysłana, zanim uruchomi się grywalizacja.
  2. Błędy grywalizacji nigdy nie wychodzą do klienta.
  3. Przetwarzanie zdarzeń działa w następnym microtasku, nie inline.

Bezpieczeństwo

Model zagrożeń

Threat Mitigation
Score inflation XP liczone tylko po stronie serwera; klienci wysyłają akcje, nie wyniki
Replay attacks Klucze idempotencji na transferach; dedup dziennika audytu
Transfer fraud Księga double-entry; atomowe transakcje; rate limits
Self-referral Cross-check api_key_id przy redeemie
Leaderboard manipulation Detekcja anomalii z-score; dashboard anomalii admina
Federation token theft Przechowywanie hashowane SHA-256; surowy token pokazywany tylko raz
Brute force invite codes Rate limiting na endpoincie redeem; entropia 8 znaków
XSS in display names Sanityzacja display names; escapowanie wpisów rankingu
Timing attacks on hashes crypto.timingSafeEqual przy porównaniu hashy tokenów

Wymagania auth

  • Public (bez auth): GET /leaderboard, GET /stream (rankingi tylko do odczytu).
  • API key required: wszystkie operacje zapisu, profil, transfery, zaproszenia.
  • Admin only: dashboard anomalii, przeglądarka dziennika audytu.
  • Federation: osobna ścieżka auth z surowym tokenem w nagłówku Authorization, walidowanym względem przechowywanego hasha SHA-256.

Testowanie

Pliki testowe

Wszystkie testy używają natywnego test runnera Node.js (node --import tsx/esm --test).

Test File Covers Tests
tests/unit/gamification/xp.test.ts Obliczanie XP, krzywa poziomów, tytuły 8
tests/unit/gamification/badges.test.ts Dopasowanie kryteriów odznak, przyznawanie 10
tests/unit/gamification/streaks.test.ts Logika serii, kamienie milowe, edge case'y 7
tests/unit/gamification/leaderboard.test.ts Obliczanie rang, stronicowanie, rotacja 8
tests/unit/gamification/sharing.test.ts Transfery, saldo, idempotencja 9
tests/unit/gamification/invites.test.ts Tworzenie, redeem, wygaśnięcie, self-referral 7
tests/unit/gamification/antiCheat.test.ts Rate limity, z-score, logowanie audytu 6
tests/unit/gamification/events.test.ts Emisja zdarzeń, fan-out, obsługa błędów 5

Uruchamianie testów

# All gamification tests
node --import tsx/esm --test tests/unit/gamification/*.test.ts

# Single test file
node --import tsx/esm --test tests/unit/gamification/xp.test.ts

Wymagania pokrycia

Zgodnie z CONTRIBUTING.md — wszystkie nowe moduły muszą mieć:

  • Pokrycie gałęzi >= 80%.
  • Każda publiczna funkcja przetestowana co najmniej raz.
  • Przetestowane ścieżki błędów (niewystarczające saldo, wygasłe kody, rate limity).

Struktura plików

src/
  lib/
    db/
      migrations/
        060_create_gamification.sql    # Wszystkie 8 tabel i indeksy
      gamification.ts                  # Moduł CRUD domeny
    gamification/
      xp.ts                           # Obliczanie XP, krzywa poziomów, tytuły
      badges.ts                       # Definicje odznak, kryteria, ocena
      streaks.ts                      # Śledzenie codziennych serii
      leaderboard.ts                  # Obliczanie pozycji, SSE, rotacja
      antiCheat.ts                    # Ograniczanie częstotliwości, z-score, audyt
      sharing.ts                      # Rejestr transferów tokenów
      invites.ts                      # Kody zaproszeń i realizacji
      servers.ts                      # Federacja serwerów społecznościowych
      events.ts                       # Emiter zdarzeń (punkt integracji)
      notifications.ts                # Strumień powiadomień SSE
  app/
    api/
      gamification/
        leaderboard/route.ts          # GET/POST rankingu
        leaderboard/stream/route.ts   # Aktualizacje w czasie rzeczywistym przez SSE
        transfer/route.ts             # GET/POST transferów
        invite/route.ts               # GET/POST/DELETE kodów zaproszeń
        invite/redeem/route.ts        # POST realizacji kodu
        servers/route.ts              # GET/POST/DELETE serwerów
        federation/score/route.ts     # POST przesyłania wyniku
        federation/leaderboard/route.ts # GET pobierania rankingu
        notifications/route.ts        # Powiadomienia SSE
        anomalies/route.ts            # GET raportów anomalii
        rotate/route.ts               # POST rotacji sekretów
    (dashboard)/
      dashboard/
        leaderboard/page.tsx           # Strona rankingu
        profile/page.tsx               # Strona XP, odznak i serii
        tokens/page.tsx                # Strona salda, transferów i zaproszeń
        gamification/admin/page.tsx    # Monitorowanie anomalii przez administratora
  shared/
    constants/
      gamification.ts                  # XP_REWARDS, TITLES, BADGE_DEFS, LIMITS

tests/
  unit/
    gamification/
      xp.test.ts
      badges.test.ts
      streaks.test.ts
      leaderboard.test.ts
      sharing.test.ts
      invites.test.ts
      antiCheat.test.ts
      events.test.ts

docs/
  frameworks/
    GAMIFICATION.md                    # Ten dokument

Strategia migracji

Faza 1: Backend core (PR 1)

  • Migracja 060_create_gamification.sql (8 tabel).
  • src/lib/db/gamification.ts (moduł domenowy).
  • src/lib/gamification/xp.ts, streaks.ts, events.ts.
  • Punkt integracji w chatCore.ts.
  • Testy jednostkowe XP, serii, zdarzeń.

Faza 2: Odznaki i ranking (PR 2)

  • src/lib/gamification/badges.ts, leaderboard.ts.
  • Definicje odznak w constants.
  • Trasy API rankingu + strumień SSE.
  • Testy jednostkowe odznak i rankingu.

Faza 3: Udostępnianie i zaproszenia (PR 3)

  • src/lib/gamification/sharing.ts, invites.ts, antiCheat.ts.
  • Trasy API transferów i zaproszeń.
  • Testy jednostkowe sharing, invites, anti-cheat.

Faza 4: Federacja i dashboard (PR 4)

  • src/lib/gamification/servers.ts, notifications.ts.
  • Trasy API federacji.
  • Strony dashboardu (leaderboard, profile, tokens, admin).
  • Rejestracja narzędzi MCP.

Przyszłe rozważania

  • Seasonal events: ograniczone czasowo zestawy odznak i sezony rankingów.
  • Team leaderboards: grupowanie użytkowników według organizacji lub combo.
  • XP multipliers: boost XP w okresach promocyjnych.
  • Achievement sharing: generowanie udostępnialnych kart odznak (obrazy OpenGraph).
  • Mobile push: powiadomienia webhook o zdarzeniach odznak/poziomów.
  • Leaderboard API: publiczne API dla integracji zewnętrznych.