Evospin — Master Architecture Reference¶
Read this alongside 2–3 of the flow docs in
docs/flows/and you will have a complete working mental model of the system: who talks to whom, what state lives where, which traces answer which question, and where the known potholes are. Generated: 2026-04-16 · Sources: all 15 Phase-2 flow docs, 3 security findings,docs/observability.md,CLAUDE.md, the three source repos (ebit-api/,ebit-fe/,ebit-admin-fe/).
0. Workspace reality¶
The workspace root (<REPO_ROOT>) is three sibling git repos (ebit-api/, ebit-fe/, ebit-admin-fe/) plus tests-e2e/ and a shared top-level docker-compose.yml. The compose file is the glue — each repo still has its own Dockerfile, package.json, and CI config and ships independently.
Canonical stack inventory (versions, package managers, role-per-repo):
engineering/stack.md§2. Don't re-state versions here.
Do not mix package managers across repos — pnpm install in ebit-api/ or npm install in the FE repos corrupts lockfiles.
1. Context (C4 L1) — external systems + actors¶
flowchart LR
subgraph actors["Actors"]
direction TB
player(("Player<br/><i>Browser on dropbet</i>"))
admin(("Admin / Ops<br/><i>Internal operator</i>"))
bot(("House bot fleet<br/><i>BullMQ-driven simulated activity</i>"))
end
subgraph ebit["ebit platform"]
direction TB
dropbet["dropbet<br/><i>ebit-fe + ebit-api<br/>sign-in · wallet · bets · leaderboard</i>"]
admin_ui["Admin dashboard<br/><i>ebit-admin-fe + ebit-api<br/>user mgmt · bet review · config</i>"]
end
subgraph externals["External systems"]
direction TB
subgraph game_fair["Game fairness"]
eos["EOS blockchain<br/><i>Wax/Jungle · speed-roulette<br/>waitForBlock</i>"]
end
subgraph auth_msg["Auth & messaging"]
recaptcha["Google reCAPTCHA<br/><i>sign-up bot gate</i>"]
smtp["SMTP provider<br/><i>sign-up / reset / welcome</i>"]
end
subgraph obs["Observability (non-local)"]
sentry["Sentry<br/><i>JS + Nest error events</i>"]
otlp_ext["External OTLP / APM<br/><i>collector fan-out</i>"]
end
subgraph dead["Stubbed / orphan"]
evo["EVO-Games wallet RPC<br/><i>only orphan ebit-bj reaches it</i>"]:::orphan
ft["FastTrack bonus tracker<br/><i>RabbitMQ sink, disabled=true</i>"]:::stub
end
end
%% Actor → ebit
player -- "HTTPS + WSS" --> dropbet
admin -- "HTTPS (2FA-gated)" --> admin_ui
bot -- "BullMQ producer" --> dropbet
admin_ui -- "same ebit-api" --> dropbet
%% ebit → externals
dropbet -- "waitForBlock" --> eos
dropbet -- "verify token" --> recaptcha
dropbet -- "send mail" --> smtp
dropbet -- "error events" --> sentry
dropbet -- "OTLP (non-local)" --> otlp_ext
classDef stub fill:#eee,stroke:#999,color:#666,stroke-dasharray:5 5;
classDef orphan fill:#fff3cd,stroke:#a07000,color:#000;
Trust boundaries¶
- Player — untrusted; all
/auth/*,/bets/*,/accounting/*,/promo/*paths guarded byJwtGuard+ per-route rate limiting (sliding-window Lua on cache Redis). - Admin — JWT +
PermissionGuard('<key>'); SuperAdmin routes additionally gated byRolesGuard(SuperAdmin)+OtpGuard(per-request TOTP). - Bots — run inside
apps/apias BullMQ producers; skip HTTP layer. - EOS — hard external dependency for speed-roulette (§5 and
dropbet-speed-roulette.md§6 #2). Outage stalls the round queue. - EVO-Games — reachable only from the orphan
ebit-bjapp on 4002; no dropbet path touches it (seedropbet-blackjack.md§6 #4).
2. Containers (C4 L2) — runtime topology¶
flowchart LR
subgraph actors["Actors"]
direction TB
player(("Player"))
admin(("Admin"))
end
subgraph fe["Browser-facing"]
direction TB
fe1["ebit-fe :3000<br/><i>Next.js 14 dropbet<br/>i18n · @vercel/otel RUM</i>"]
fe2["ebit-admin-fe<br/><i>:5173 host / :3003 compose<br/>Vite + React 19<br/>AF-1 integration bugs</i>"]
end
subgraph nest["ebit-api NestJS monorepo (5 apps)"]
direction TB
api["ebit-api :4000<br/><i>REST + Swagger</i>"]
rt["ebit-rt :4001<br/><i>socket.io /events</i>"]
bo["ebit-bo :4003<br/><i>ops backoffice</i>"]
sr["ebit-speed-roulette :4004<br/><i>EOS-anchored, BullMQ concurrency=1</i>"]
bj["ebit-bj :4002<br/><i>orphan blackjack</i>"]:::orphan
end
subgraph data["Stateful"]
direction TB
pg[("Postgres 5555→5432<br/>public · blackjack · speed_roulette")]
redisCache[("Redis :6379<br/>cache · BullMQ · throttler<br/>ONLINE_USERS · pub/sub")]
redisBot[("Redis :6380<br/>bot state")]
rmq["RabbitMQ :5672<br/><i>vhost=ft · fast-track stub</i>"]:::stub
end
%% Actor → FE
player -- "HTTPS + WSS" --> fe1
admin -- "HTTPS" --> fe2
%% FE → Nest
fe1 -- "fetch + traceparent" --> api
fe1 -- "socket.io" --> rt
fe2 -- "fetch (AF-1)" --> api
%% Nest → Data
api --> pg
api --> redisCache
rt --> redisCache
sr --> redisCache
sr --> pg
bj --> pg
bo --> pg
api --> redisBot
api -. "fast-track stub" .-> rmq
%% Inter-app RPC (AF-2 trace gap)
api == "@GatewayMethod<br/>AF-2 NO traceparent" ==> rt
api == "ExternalControllerClient<br/>AF-2 NO traceparent" ==> sr
classDef stub fill:#eee,stroke:#999,color:#666,stroke-dasharray:5 5;
classDef orphan fill:#fff3cd,stroke:#a07000,color:#000;
linkStyle 14,15 stroke:#cc0000,stroke-width:2.5px;
Red thick edges = inter-app RPC over Redis pub/sub with no
traceparentpropagation (AF-2 in./weaknesses-register.md). Dashed edges = stubbed (RabbitMQ disabled=true).Observability sidecar (OTel collector + Jaeger + Prometheus + Loki + Grafana) is deliberately not shown here — it's a sidecar bus on every Nest container, out-of-band relative to the request path. See
observability.md+architecture/tracing-flow.mdfor the full telemetry topology.Detailed runtime topology + port-by-port edge inventory:
./architecture/service-map.md.
Port contract¶
| Service | Port | Exposed by compose | Notes |
|---|---|---|---|
| ebit-fe | 3000 | yes | dev server (pnpm dev) |
| ebit-admin-fe | 3001 | host-networking workaround (see memory project_compose_decisions) |
four known integration bugs block cross-service traces |
| ebit-api | 4000 | yes | /swagger enabled |
| ebit-rt | 4001 | yes | main.ts boots with http: null — only socket.io |
| ebit-bj | 4002 | yes | orphan; no dropbet path |
| ebit-bo, ebit-speed-roulette | internal | — | no HTTP surface exercised by E2E |
| Postgres | 5555→5432 | yes | three schemas in one DB |
| Redis cache | 6379 | yes | password cache |
| Redis bot | 6380 | yes | password bot |
| RabbitMQ | 5672 + UI 15672 | yes | rabbitmq/rabbitmq, vhost ft — receives zero traffic until fast-track stub removed |
| otel-collector | 4317 / 4318 | yes | observability/otel-collector.yml |
| Jaeger | 16686 | yes | |
| Prometheus | 9090 | yes | |
| Loki | 3100 | yes | |
| Grafana | 3003 | yes | admin/grafana |
3. Components (C4 L3) — inside ebit-api¶
The NestJS monorepo declares 5 applications in nest-cli.json sharing 11 libs. Path aliases: @api/*, @rt/*, @bj/*, @bo/*, @speed-roulette/* (apps); @app/shared, @app/auth, @app/games, @app/accounting, @app/integrations, @app/gateway, @app/ws-throttler, @app/modules, @app/is-localhost, @app/_prisma, @app/guards (libs).
Module inventory of apps/api/src/, grouped by domain. No edges drawn between these — they're a categorisation, not a flow. (For request-flow edges see ./flows/; for inter-app RPC see §2 above + AF-2 in ./weaknesses-register.md.)
Application modules (apps/api/src/)¶
| Domain | Modules |
|---|---|
| Auth + session | auth/ (AuthController · AuthService · cookies.ts · JwtGuard · RolesGuard · OtpGuard · session/session.queue-producer.ts BullMQ) · user/ (UserController · AdminUserController · AdminNotesController · UserService · UserRepository · ProfileNotifierService · OnlineTrackerService) · kyc/ · api-key/ |
| Bet pipeline (shared by all house games) | bet/ (BetController · BetCrudService · BetRepository · BetQueueProducer BullMQ bet_settled_queue) · casino/ (/casino/games/house/* controllers: dice · limbo · mines · plinko · blackjack) · provably-fair/ (ProvablyFairService · popUserSeed · seed rotation) · accounting/ (UserBalanceRepository · ledger transaction log · ExchangeRatesService.toUsd) |
| Promo + challenge + loyalty | challenge/ (AdminChallengeController · award-only, no user-claim route) · promo/ (PromoController — missing @Post on claim, SF in #38 · PromoEffectService · FastTrackBonusService stub) · leaderboard/ (LeaderboardService · dead-code LeaderboardQueueProducer SF-020 · leaderboard_user_position_view) · rakeback/ (calc on bet_settled_queue handler) · vip-program/ · tips/ + admin-tips/ |
| External integrations | payment/ (deposit / withdraw adapters) · sportbook/ (no dropbet history endpoint) · external-notification-sender/ (SMTP fan-out) · telegram/ (bot endpoints) · captcha/ (RecaptchaService, local='pass' bypass FM-1) · fast-track/ (RabbitMQ producer — .rmq.module.ts:8 disabled=true) |
| Admin + platform | dashboard-v2/ + dashboard/ (admin analytics) · site-config/ (feature flags) · faq/ (CMS) · coming-soon/ (waitlist) · country/ (geo-IP + ban list) · chat/ · bots/ (house-bot fleet, BullMQ) · rt-notification/ (publishers → ebit-rt) · gateway/ (EventsGateway + @MessagePattern consumers, intra-Nest RPC) |
Shared libraries (libs/)¶
| Lib | Role |
|---|---|
@app/shared |
Nest bootstrap · OTel pre-main · EvoLogger · NestLoggerModule (pino) · base.main.ts |
@app/auth |
Shared JWT guard + decorators |
@app/games |
Cross-game DTOs + RNG helpers |
@app/accounting |
Currency registry · balance DTOs |
@app/integrations |
EOS client · EVO-Games client · external APIs |
@app/gateway |
EventsGateway base + event name registry (Private.*, Server.*, Client.*) · @ExternalControllerClient + @GatewayMethod |
@app/ws-throttler |
Socket-level rate limit |
@app/modules |
AdminLoggerInterceptor (writes admin_action_log) · idempotency lock · pagination DTOs |
@app/is-localhost |
env-gated helpers |
@app/_prisma |
Split schema (api + blackjack + speed_roulette) · PrismaService · seed entrypoint |
@app/guards |
Shared HTTP guards |
Noteworthy module boundaries¶
@app/gatewayis the spinal cord.@ExternalControllerClient+@GatewayMethod+@MessagePatternimplement inter-app RPC on Nest's Redis pub/sub transport. This is also the single biggest blind spot in the trace graph — W3Ctraceparentdoesn't cross the hop (memoryproject_otel_microservice_transport_gap§6 #1).- BullMQ, not RabbitMQ. Every production async path (
session.queue-producer,bet_settled_queue,bots/system/bull/,challenges,leaderboard,promo tasks,user-stats-migration,skindeck-deposits, both speed-roulette queues) runs on Redis-backed BullMQ. RabbitMQ is only wired toapps/api/src/fast-track/rabbitmq/fast-track.rmq.module.tsand that module returnsdisabled = true. Debugging a stalled job →redis-cli -a cache KEYS bull:*, not the RabbitMQ UI. AdminLoggerInterceptor(libs/modules/src/admin-logger/) wraps every non-GET/admin/*and writes anadmin_action_logrow from atap().safeLogswallows insert errors — a transient DB fault produces a silent audit gap.@app/shared/basic/pre/pre-otel.main.tsis imported first in every app'smain.ts; it boots@opentelemetry/sdk-node+PrismaInstrumentation+PinoInstrumentationbefore Nest.bjandbobypass thecreateNestApphelper — they must still import@app/shared/basic/pre-importsas the first line.
4. Runtime scenarios (links to flow docs)¶
Every scenario below has a 7-heading flow doc with Jaeger traceIDs and a pinned Playwright spec under tests-e2e/tests/. Read the flow doc for sequence diagram, span timing, data model, and failure modes.
| # | Flow | Flow doc | E2E spec | Entry |
|---|---|---|---|---|
| 1 | Dropbet sign-up (captcha → user INSERT → floating email) | dropbet-sign-up.md |
tests/dropbet-sign-up.spec.ts |
POST /auth/sign-up |
| 2 | Dropbet sign-in + 2FA | dropbet-sign-in.md |
tests/dropbet-sign-in.spec.ts |
POST /auth/sign-in → /auth/verify-2fa |
| 3 | Password reset (forgeable token + no one-shot persistence) | dropbet-password-reset.md |
tests/dropbet-password-reset.spec.ts |
POST /auth/password-reset-request |
| 4 | Wallet / balance view (negative-balance to-vault bug) |
dropbet-wallet.md |
tests/dropbet-wallet.spec.ts |
GET /accounting/balances |
| 5 | Bet placement shared pipeline (dice as vector) | dropbet-bet-place.md |
tests/dropbet-bet-place.spec.ts |
POST /casino/games/house/dice/bets |
| 6 | Dice vs Limbo house-game mechanics | dropbet-house-game.md |
tests/dropbet-house-game.spec.ts |
POST /casino/games/house/{dice,limbo}/bets |
| 7 | Blackjack session (abandoned-hand fund lockup) | dropbet-blackjack.md |
tests/dropbet-blackjack.spec.ts |
POST /casino/games/house/blackjack/init |
| 8 | Speed-roulette session (EOS-anchored, cross-service trace gap) | dropbet-speed-roulette.md |
tests/dropbet-speed-roulette.spec.ts |
POST /speed-roulette/bet |
| 9 | Bet history + details (detail endpoints leak seed to anon) | dropbet-bet-history.md |
tests/dropbet-bet-history.spec.ts |
GET /bets/my/house-game |
| 10 | Leaderboard (RACE_ENABLED gate, dead producer, stale cache) | dropbet-leaderboard.md |
tests/dropbet-leaderboard.spec.ts |
GET /leaderboards/active |
| 11 | Challenges + promos (404 promo claim — missing @Post) |
dropbet-challenges.md |
tests/dropbet-challenges.spec.ts |
GET /challenges/active + POST /promo/public/:code |
| 12 | RT websocket connect + events (online count padded) | rt-websocket.md |
tests/dropbet-rt-websocket.spec.ts |
WSS /events |
| 13 | Admin sign-in (cookie-name mismatch blocks admin-fe) | admin-sign-in.md |
tests/admin-sign-in.spec.ts |
POST /auth/sign-in + TOTP |
| 14 | Admin user management (ban/unban/note + audit log) | admin-user-mgmt.md |
tests/admin-user-mgmt.spec.ts |
POST /admin/user/all |
| 15 | Admin bet view + adjustment (no adjust endpoint; SuperAdmin MFA bypass) | admin-bets.md |
tests/admin-bets.spec.ts |
POST /admin/bets |
Flows 1–12 drive ebit-fe + ebit-api (+ ebit-rt for 7/8/12). Flows 13–15 drive ebit-api directly because admin-fe is blocked by the bug cluster in §7 — driving the UI would produce a broken traceparent chain. See ebit-admin-fe/src/middleware.ts:59-60 for the cookie-name culprit.
Read-order suggestions for new hires¶
- Backend engineer → this doc →
dropbet-sign-in.md→dropbet-bet-place.md→rt-websocket.md. That tour hits auth, Prisma+BullMQ, and the gateway pub/sub blind spot. - Frontend engineer → this doc →
dropbet-sign-in.md→dropbet-wallet.md→rt-websocket.md. Covers cookie contract, socket_token handshake, and the balance fan-out. - Ops / SRE → this doc § 6 + § 7 →
admin-user-mgmt.md§ 6 →observability.md. Covers the audit path, silent-log failure mode, and trace-to-log pivot.
5. Data model — split Prisma schema¶
libs/_prisma/src/schema/ is declared with previewFeatures = ["prismaSchemaFolder", "multiSchema", "views", "tracing"]. Three files, one Postgres DB, three Postgres schemas:
| File | Postgres schema | Models | Primary domain |
|---|---|---|---|
api.prisma |
public |
65 | All of the player + admin surface |
blackjack.prisma |
blackjack |
8 | Blackjack-specific state (rounds, hands, cards) |
speed_roulette.prisma |
speed_roulette |
3 | Speed-roulette game + bet + eos block |
Schema commands are wrapped with env-cmd -f .env (or .env.test) — always use the npm scripts (db:migrate:dev, db:seed, db:reset, prisma:*:test). Calling npx prisma directly misses the env file.
Core entities¶
| Entity | Written by | Read by | Trace anchor |
|---|---|---|---|
user (api.prisma) |
sign-up / ban / role changes | every guard + admin surface | POST /admin/user/all trace |
user_session |
BullMQ session.queue-producer (skipped on first sign-up) |
admin lookup, same-device check | §7 weakness WK-2 |
user_balance |
UserBalanceRepository · bet tx · deposit/withdraw · to-vault (SF-013) |
/accounting/balances · bet pipeline |
dropbet-wallet.md |
transaction (ledger) |
every balance mutation | rt Private.TransactionFindMany (no HTTP SF-015) |
dropbet-wallet.md |
bet |
BetCrudService.placeBet inside a Prisma transaction |
/bets/my/*, admin /bets, history cache |
dropbet-bet-place.md |
game_identity + game_provider + game_in_category |
migrations + admin | every bet (fat-hydration problem — dropbet-blackjack.md §6 #3) |
— |
user_seed |
popUserSeed inside the bet tx |
fairness controller | dropbet-house-game.md |
leaderboard · leaderboard_user · leaderboard_user_position_view |
bet_settled_queue direct write |
GET /leaderboards/active |
dropbet-leaderboard.md |
challenge · user_challenge |
admin award/rollback only | browse + participation | dropbet-challenges.md |
promo · user_promo_code |
floating 404 on claim (missing @Post) |
GET /promo/public/:code |
dropbet-challenges.md |
admin_action_log |
AdminLoggerInterceptor.tap() (non-GET /admin/*) |
GET /admin/user/admin-audit (filter by admin actor, not target) |
admin-user-mgmt.md |
user_note |
AdminNotesController |
admin user detail | admin-user-mgmt.md |
BlackjackGame · BlackjackHand · BlackjackBet (blackjack schema) |
/casino/games/house/blackjack/* on ebit-api (NOT ebit-bj) |
same | dropbet-blackjack.md |
SpeedRouletteGame · SpeedRouletteBet · eos block columns |
ebit-speed-roulette state machine (BullMQ concurrency=1) | rt pushes + repository reads | dropbet-speed-roulette.md |
Redis keyspace¶
Canonical inventory of every Redis key prefix used by the platform: data-model/redis-keyspace.md.
6. Observability catalog¶
Canonical observability wiring + dashboards + correlation recipe: observability.md.
7. Known weaknesses¶
Canonical scoreboard for every known weakness (architectural blind spots AF-*, security findings SF-*, flow-doc failure modes FM-*, workspace-level gaps WK-*): weaknesses-register.md.
8. Outstanding questions this doc does not answer¶
- Full backoffice surface (
ebit-bo). Only touched tangentially via the deadPOST /betsproxy (SF-026). - Payment deposit/withdraw adapter internals — the
payment/module has its own Prisma + external APIs and deserves a dedicated flow doc. - KYC lifecycle (upload → review → tiered limits) —
kyc/module. - VIP program tier progression —
vip-program/. - Bot-fleet behavior model (
bots/) — simulated activity skews every dashboard that doesn't filter byis_bot. - SuperAdmin mutations (
PUT /admin/user/:id/{balance,roles,permissions}) — OTP-gated and not exercised inadmin-user-mgmt.md.
If you need any of these, start from the module directory listed above; the pattern established in the 15 Phase-2 flow docs is the template.