@clovnet/plugin-sdk 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +114 -0
- package/dist/chunk-XRSKWL3A.js +1872 -0
- package/dist/chunk-XRSKWL3A.js.map +1 -0
- package/dist/index.d.ts +3658 -0
- package/dist/index.js +561 -0
- package/dist/index.js.map +1 -0
- package/dist/testing.d.ts +275 -0
- package/dist/testing.js +1010 -0
- package/dist/testing.js.map +1 -0
- package/package.json +75 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3658 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Canonical domain event names, grouped by aggregate.
|
|
5
|
+
*
|
|
6
|
+
* Names are the *contract*. They are dot-delimited `aggregate.action` strings;
|
|
7
|
+
* the NATS subject is derived by prefixing `cwe.` (see `subjects.ts`). Never
|
|
8
|
+
* inline an event-name string anywhere — always reference these constants so a
|
|
9
|
+
* rename is a single, type-checked edit and consumers can't drift.
|
|
10
|
+
*/
|
|
11
|
+
declare const WalletEvents: {
|
|
12
|
+
readonly CREDITED: "wallet.credited";
|
|
13
|
+
readonly DEBITED: "wallet.debited";
|
|
14
|
+
readonly TRANSFERRED: "wallet.transferred";
|
|
15
|
+
readonly DEPOSIT_COMPLETED: "wallet.deposit_completed";
|
|
16
|
+
readonly WITHDRAWAL_REQUESTED: "wallet.withdrawal_requested";
|
|
17
|
+
readonly WITHDRAWAL_APPROVED: "wallet.withdrawal_approved";
|
|
18
|
+
readonly WITHDRAWAL_REJECTED: "wallet.withdrawal_rejected";
|
|
19
|
+
readonly TRANSACTION_REVERSED: "wallet.transaction_reversed";
|
|
20
|
+
readonly WALLET_FROZEN: "wallet.frozen";
|
|
21
|
+
readonly WALLET_UNFROZEN: "wallet.unfrozen";
|
|
22
|
+
};
|
|
23
|
+
declare const PlayerEvents: {
|
|
24
|
+
readonly CREATED: "player.created";
|
|
25
|
+
readonly LOGGED_IN: "player.logged_in";
|
|
26
|
+
readonly LOGGED_OUT: "player.logged_out";
|
|
27
|
+
readonly SESSION_REFRESHED: "player.session_refreshed";
|
|
28
|
+
readonly SOCIAL_LINKED: "player.social_linked";
|
|
29
|
+
readonly SOCIAL_UNLINKED: "player.social_unlinked";
|
|
30
|
+
readonly PASSWORD_CHANGED: "player.password_changed";
|
|
31
|
+
readonly LOGIN_FAILED: "player.login_failed";
|
|
32
|
+
readonly ACCOUNT_LOCKED: "player.account_locked";
|
|
33
|
+
readonly UPDATED: "player.updated";
|
|
34
|
+
/** KYC level-up: the player reached a higher verified level. */
|
|
35
|
+
readonly VERIFIED: "player.verified";
|
|
36
|
+
readonly CLOSED: "player.closed";
|
|
37
|
+
/** Staff suspended the account (backoffice action; payload = ids + reason only). */
|
|
38
|
+
readonly SUSPENDED: "player.suspended";
|
|
39
|
+
/** Staff lifted a suspension, returning the account to active. */
|
|
40
|
+
readonly REACTIVATED: "player.reactivated";
|
|
41
|
+
readonly EMAIL_VERIFIED: "player.email_verified";
|
|
42
|
+
readonly PHONE_VERIFIED: "player.phone_verified";
|
|
43
|
+
readonly SESSION_REVOKED: "player.session_revoked";
|
|
44
|
+
readonly PREFERENCES_UPDATED: "player.preferences_updated";
|
|
45
|
+
readonly LIMIT_CHANGED: "player.limit_changed";
|
|
46
|
+
readonly COOL_OFF_STARTED: "player.cool_off_started";
|
|
47
|
+
readonly SELF_EXCLUDED: "player.self_excluded";
|
|
48
|
+
/**
|
|
49
|
+
* Reality-check tick (§8.2): produced by the worker sweep for active game
|
|
50
|
+
* sessions, delivered to the frontend via the realtime `player` channel.
|
|
51
|
+
* Transport fact only — carries session aggregates, never money truth.
|
|
52
|
+
*/
|
|
53
|
+
readonly REALITY_CHECK: "player.reality_check";
|
|
54
|
+
readonly ONLINE: "player.online";
|
|
55
|
+
readonly OFFLINE: "player.offline";
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Dynamic-KYC lifecycle events (PLAYER_ACCOUNT_BUILD_PROMPT.md §5). Payloads
|
|
59
|
+
* carry ids, document-type KEYS and coded statuses/reasons only — never
|
|
60
|
+
* document content, storage refs, file names or signed URLs.
|
|
61
|
+
*/
|
|
62
|
+
declare const KycEvents: {
|
|
63
|
+
readonly CONFIG_UPDATED: "kyc.config_updated";
|
|
64
|
+
readonly REQUEST_CREATED: "kyc.request_created";
|
|
65
|
+
readonly DOCUMENT_UPLOADED: "kyc.document_uploaded";
|
|
66
|
+
readonly DOCUMENT_APPROVED: "kyc.document_approved";
|
|
67
|
+
readonly DOCUMENT_REJECTED: "kyc.document_rejected";
|
|
68
|
+
readonly REQUEST_SUBMITTED: "kyc.request_submitted";
|
|
69
|
+
readonly REQUEST_APPROVED: "kyc.request_approved";
|
|
70
|
+
readonly REQUEST_REJECTED: "kyc.request_rejected";
|
|
71
|
+
readonly REQUEST_NEEDS_MORE: "kyc.request_needs_more";
|
|
72
|
+
readonly REQUEST_ESCALATED: "kyc.request_escalated";
|
|
73
|
+
};
|
|
74
|
+
declare const BonusEvents: {
|
|
75
|
+
readonly GRANTED: "bonus.granted";
|
|
76
|
+
readonly REVOKED: "bonus.revoked";
|
|
77
|
+
readonly OFFERED: "bonus.offered";
|
|
78
|
+
readonly CLAIMED: "bonus.claimed";
|
|
79
|
+
readonly ACTIVATED: "bonus.activated";
|
|
80
|
+
readonly WAGERING_PROGRESSED: "bonus.wagering_progressed";
|
|
81
|
+
readonly WAGERING_COMPLETED: "bonus.wagering_completed";
|
|
82
|
+
readonly CONVERTED: "bonus.converted";
|
|
83
|
+
readonly EXPIRED: "bonus.expired";
|
|
84
|
+
readonly FORFEITED: "bonus.forfeited";
|
|
85
|
+
readonly VOIDED: "bonus.voided";
|
|
86
|
+
readonly GRANT_QUEUED: "bonus.grant_queued";
|
|
87
|
+
readonly GRANT_REJECTED: "bonus.grant_rejected";
|
|
88
|
+
readonly CONSTRAINT_BREACHED: "bonus.constraint_breached";
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Tournament lifecycle events. Emitted by promo plugins through the SDK's
|
|
92
|
+
* typed `emitDomain` surface (BONUS_PLATFORM.md G7) so CRM/analytics can
|
|
93
|
+
* consume them uniformly; never free-form `plugin.<key>.*` strings.
|
|
94
|
+
*/
|
|
95
|
+
declare const TournamentEvents: {
|
|
96
|
+
readonly STARTED: "tournament.started";
|
|
97
|
+
readonly ENDED: "tournament.ended";
|
|
98
|
+
readonly PRIZE_AWARDED: "tournament.prize_awarded";
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Gamification events (missions, achievements, levels) — plugin-emitted via
|
|
102
|
+
* the typed SDK surface, same rationale as `TournamentEvents`.
|
|
103
|
+
*/
|
|
104
|
+
declare const GamificationEvents: {
|
|
105
|
+
readonly ACHIEVEMENT_UNLOCKED: "gamification.achievement_unlocked";
|
|
106
|
+
readonly MISSION_COMPLETED: "gamification.mission_completed";
|
|
107
|
+
readonly LEVEL_UP: "gamification.level_up";
|
|
108
|
+
};
|
|
109
|
+
/** Loyalty-points events — plugin-emitted via the typed SDK surface. */
|
|
110
|
+
declare const LoyaltyEvents: {
|
|
111
|
+
readonly POINTS_EARNED: "loyalty.points_earned";
|
|
112
|
+
readonly POINTS_REDEEMED: "loyalty.points_redeemed";
|
|
113
|
+
};
|
|
114
|
+
declare const BetEvents: {
|
|
115
|
+
readonly PLACED: "bet.placed";
|
|
116
|
+
readonly SETTLED: "bet.settled";
|
|
117
|
+
};
|
|
118
|
+
declare const AffiliateEvents: {
|
|
119
|
+
readonly CLICK_RECORDED: "affiliate.click_recorded";
|
|
120
|
+
readonly REGISTRATION_ATTRIBUTED: "affiliate.registration_attributed";
|
|
121
|
+
/** The one-time, permanent player→affiliate assignment (one per player, ever). */
|
|
122
|
+
readonly ASSIGNED: "affiliate.assigned";
|
|
123
|
+
readonly COMMISSION_CREATED: "affiliate.commission_created";
|
|
124
|
+
readonly COMMISSION_SETTLED: "affiliate.commission_settled";
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Player classification & audience events (System family, like Plugin/Provider).
|
|
128
|
+
* Emitted by the `@cwe/classification` engine and the audience/conversion
|
|
129
|
+
* exporters — tags, segment membership, and the ad-platform feedback loop
|
|
130
|
+
* (docs/AFFILIATE_ANALYTICS.md Parts G–H). Projection-side facts: they never
|
|
131
|
+
* carry money truth, only classification state changes.
|
|
132
|
+
*/
|
|
133
|
+
declare const ClassificationEvents: {
|
|
134
|
+
readonly PLAYER_TAG_ASSIGNED: "classification.tag_assigned";
|
|
135
|
+
readonly PLAYER_TAG_REMOVED: "classification.tag_removed";
|
|
136
|
+
readonly SEGMENT_MEMBERSHIP_CHANGED: "classification.segment_membership_changed";
|
|
137
|
+
readonly AUDIENCE_EXPORTED: "classification.audience_exported";
|
|
138
|
+
readonly CONVERSION_FEEDBACK_SENT: "classification.conversion_feedback_sent";
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Catalog (game & provider catalog) events. Global/control-plane events
|
|
142
|
+
* (source/provider/category/game definition changes) do NOT carry a tenant scope
|
|
143
|
+
* in their payload; tenant-scoped events (provider toggle, game overlay) do.
|
|
144
|
+
*/
|
|
145
|
+
declare const CatalogEvents: {
|
|
146
|
+
readonly SOURCE_REGISTERED: "catalog.source_registered";
|
|
147
|
+
readonly SOURCE_IMPORTED: "catalog.source_imported";
|
|
148
|
+
readonly PROVIDER_UPSERTED: "catalog.provider_upserted";
|
|
149
|
+
readonly CATEGORY_UPSERTED: "catalog.category_upserted";
|
|
150
|
+
readonly GAME_CREATED: "catalog.game_created";
|
|
151
|
+
readonly GAME_UPDATED: "catalog.game_updated";
|
|
152
|
+
readonly GAME_RETIRED: "catalog.game_retired";
|
|
153
|
+
readonly TENANT_PROVIDER_TOGGLED: "catalog.tenant_provider_toggled";
|
|
154
|
+
readonly TENANT_GAME_OVERLAID: "catalog.tenant_game_overlaid";
|
|
155
|
+
readonly GAMES_BULK_UPDATED: "catalog.games_bulk_updated";
|
|
156
|
+
readonly CACHE_INVALIDATED: "catalog.cache_invalidated";
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Cashier (money-in / money-out) events — the payment lifecycle on top of the
|
|
160
|
+
* wallet's financial events. The wallet still emits `wallet.*` for the actual
|
|
161
|
+
* balance moves; these carry the PSP/payment facts the funnel + NGR waterfall
|
|
162
|
+
* need (payment method, itemized fees, chargebacks, FX snapshot).
|
|
163
|
+
*/
|
|
164
|
+
declare const CashierEvents: {
|
|
165
|
+
readonly DEPOSIT_INITIATED: "cashier.deposit_initiated";
|
|
166
|
+
readonly DEPOSIT_COMPLETED: "cashier.deposit_completed";
|
|
167
|
+
readonly DEPOSIT_FAILED: "cashier.deposit_failed";
|
|
168
|
+
readonly WITHDRAWAL_REQUESTED: "cashier.withdrawal_requested";
|
|
169
|
+
readonly WITHDRAWAL_APPROVED: "cashier.withdrawal_approved";
|
|
170
|
+
readonly WITHDRAWAL_REJECTED: "cashier.withdrawal_rejected";
|
|
171
|
+
readonly WITHDRAWAL_PAID: "cashier.withdrawal_paid";
|
|
172
|
+
readonly WITHDRAWAL_FAILED: "cashier.withdrawal_failed";
|
|
173
|
+
/** Player/BO cancelled a not-yet-processing withdrawal; locked funds released. */
|
|
174
|
+
readonly WITHDRAWAL_CANCELLED: "cashier.withdrawal_cancelled";
|
|
175
|
+
readonly PAYMENT_FEE_RECORDED: "cashier.payment_fee_recorded";
|
|
176
|
+
readonly CHARGEBACK_RECORDED: "cashier.chargeback_recorded";
|
|
177
|
+
readonly PAYMENT_INSTRUMENT_ADDED: "cashier.payment_instrument_added";
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Plugin-platform lifecycle events (System family). Emitted by the plugin host
|
|
181
|
+
* commands (install/enable/configure/publish/…), never by plugin code itself —
|
|
182
|
+
* plugin-emitted events are namespaced `plugin.<key>.*` and flow through the
|
|
183
|
+
* outbox as raw subjects, not through this typed catalog.
|
|
184
|
+
*/
|
|
185
|
+
declare const PluginEvents: {
|
|
186
|
+
readonly INSTALLED: "plugin.installed";
|
|
187
|
+
readonly ENABLED: "plugin.enabled";
|
|
188
|
+
readonly DISABLED: "plugin.disabled";
|
|
189
|
+
readonly UNINSTALLED: "plugin.uninstalled";
|
|
190
|
+
readonly CONFIGURED: "plugin.configured";
|
|
191
|
+
readonly PUBLISHED: "plugin.published";
|
|
192
|
+
readonly VERSION_YANKED: "plugin.version_yanked";
|
|
193
|
+
readonly UPGRADED: "plugin.upgraded";
|
|
194
|
+
readonly TASK_STARTED: "plugin.task_started";
|
|
195
|
+
readonly TASK_COMPLETED: "plugin.task_completed";
|
|
196
|
+
readonly TASK_FAILED: "plugin.task_failed";
|
|
197
|
+
readonly JOB_FAILED: "plugin.job_failed";
|
|
198
|
+
readonly DATA_PURGED: "plugin.data_purged";
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* Provider lifecycle events (System family). `provider.enabled` fires when a
|
|
202
|
+
* provider-kind plugin is enabled for a tenant and its adapter is registered.
|
|
203
|
+
*/
|
|
204
|
+
declare const ProviderEvents: {
|
|
205
|
+
readonly ENABLED: "provider.enabled";
|
|
206
|
+
readonly DISABLED: "provider.disabled";
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* Backoffice-platform events (System family). Emitted by the `packages/backoffice`
|
|
210
|
+
* services (view/dashboard save, export completion, PII reveal, translations).
|
|
211
|
+
* Projection/operational facts only — never money truth. PII_REVEALED carries the
|
|
212
|
+
* subject id + revealed field KEYS only, never the revealed values.
|
|
213
|
+
*/
|
|
214
|
+
declare const BackofficeEvents: {
|
|
215
|
+
readonly VIEW_SAVED: "backoffice.view_saved";
|
|
216
|
+
readonly DASHBOARD_SAVED: "backoffice.dashboard_saved";
|
|
217
|
+
readonly EXPORT_COMPLETED: "backoffice.export_completed";
|
|
218
|
+
readonly PII_REVEALED: "backoffice.pii_revealed";
|
|
219
|
+
readonly TRANSLATIONS_UPDATED: "backoffice.translations_updated";
|
|
220
|
+
/**
|
|
221
|
+
* TASK-005: staff reassigned a player's CRM owner. Carries staff user ids
|
|
222
|
+
* (before/after, either may be null for unassign) + reason. No player PII.
|
|
223
|
+
*/
|
|
224
|
+
readonly OWNER_ASSIGNED: "backoffice.owner_assigned";
|
|
225
|
+
/**
|
|
226
|
+
* TASK-007: staff logged a typed CRM activity on any entity record.
|
|
227
|
+
* Payload carries ids + kind + ownerActorId + dueAt/completedAt only —
|
|
228
|
+
* NO body text (PII exfiltration risk), NO authorActorId (retrievable
|
|
229
|
+
* from the audit row).
|
|
230
|
+
*/
|
|
231
|
+
readonly ACTIVITY_CREATED: "backoffice.activity_created";
|
|
232
|
+
/** TASK-007: staff marked an activity complete. Payload = ids + timestamps. */
|
|
233
|
+
readonly ACTIVITY_COMPLETED: "backoffice.activity_completed";
|
|
234
|
+
};
|
|
235
|
+
/**
|
|
236
|
+
* Platform/system events (System family). Emitted by worker jobs and settings
|
|
237
|
+
* commands — cache-busting + config-change signals, never money truth.
|
|
238
|
+
* `FX_RATES_UPDATED` lets conversion caches invalidate after a rate sync;
|
|
239
|
+
* `TENANT_SETTINGS_UPDATED` fires on any tenant-settings write (e.g. a reporting
|
|
240
|
+
* currency change) so read caches refresh (CURRENCY_SYNC_BUILD.md §3/§4.3).
|
|
241
|
+
*/
|
|
242
|
+
declare const SystemEvents: {
|
|
243
|
+
readonly FX_RATES_UPDATED: "system.fx_rates_updated";
|
|
244
|
+
readonly TENANT_SETTINGS_UPDATED: "system.tenant_settings_updated";
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* Union of every known event name.
|
|
248
|
+
*
|
|
249
|
+
* NOTE: families share some constant KEYS (e.g. both Wallet and Cashier define
|
|
250
|
+
* `DEPOSIT_COMPLETED`, with distinct *values* `wallet.*` vs `cashier.*`). A flat
|
|
251
|
+
* spread would let a later family's value overwrite an earlier one's at that key,
|
|
252
|
+
* silently dropping it from the type. So the runtime object is kept for
|
|
253
|
+
* convenience, but the `DomainEventName` union is derived from each family's
|
|
254
|
+
* VALUE type — guaranteeing every event value is in the union regardless of key
|
|
255
|
+
* collisions.
|
|
256
|
+
*/
|
|
257
|
+
declare const DomainEventNames: {
|
|
258
|
+
readonly FX_RATES_UPDATED: "system.fx_rates_updated";
|
|
259
|
+
readonly TENANT_SETTINGS_UPDATED: "system.tenant_settings_updated";
|
|
260
|
+
readonly VIEW_SAVED: "backoffice.view_saved";
|
|
261
|
+
readonly DASHBOARD_SAVED: "backoffice.dashboard_saved";
|
|
262
|
+
readonly EXPORT_COMPLETED: "backoffice.export_completed";
|
|
263
|
+
readonly PII_REVEALED: "backoffice.pii_revealed";
|
|
264
|
+
readonly TRANSLATIONS_UPDATED: "backoffice.translations_updated";
|
|
265
|
+
/**
|
|
266
|
+
* TASK-005: staff reassigned a player's CRM owner. Carries staff user ids
|
|
267
|
+
* (before/after, either may be null for unassign) + reason. No player PII.
|
|
268
|
+
*/
|
|
269
|
+
readonly OWNER_ASSIGNED: "backoffice.owner_assigned";
|
|
270
|
+
/**
|
|
271
|
+
* TASK-007: staff logged a typed CRM activity on any entity record.
|
|
272
|
+
* Payload carries ids + kind + ownerActorId + dueAt/completedAt only —
|
|
273
|
+
* NO body text (PII exfiltration risk), NO authorActorId (retrievable
|
|
274
|
+
* from the audit row).
|
|
275
|
+
*/
|
|
276
|
+
readonly ACTIVITY_CREATED: "backoffice.activity_created";
|
|
277
|
+
/** TASK-007: staff marked an activity complete. Payload = ids + timestamps. */
|
|
278
|
+
readonly ACTIVITY_COMPLETED: "backoffice.activity_completed";
|
|
279
|
+
readonly POINTS_EARNED: "loyalty.points_earned";
|
|
280
|
+
readonly POINTS_REDEEMED: "loyalty.points_redeemed";
|
|
281
|
+
readonly ACHIEVEMENT_UNLOCKED: "gamification.achievement_unlocked";
|
|
282
|
+
readonly MISSION_COMPLETED: "gamification.mission_completed";
|
|
283
|
+
readonly LEVEL_UP: "gamification.level_up";
|
|
284
|
+
readonly STARTED: "tournament.started";
|
|
285
|
+
readonly ENDED: "tournament.ended";
|
|
286
|
+
readonly PRIZE_AWARDED: "tournament.prize_awarded";
|
|
287
|
+
readonly ENABLED: "provider.enabled";
|
|
288
|
+
readonly DISABLED: "provider.disabled";
|
|
289
|
+
readonly INSTALLED: "plugin.installed";
|
|
290
|
+
readonly UNINSTALLED: "plugin.uninstalled";
|
|
291
|
+
readonly CONFIGURED: "plugin.configured";
|
|
292
|
+
readonly PUBLISHED: "plugin.published";
|
|
293
|
+
readonly VERSION_YANKED: "plugin.version_yanked";
|
|
294
|
+
readonly UPGRADED: "plugin.upgraded";
|
|
295
|
+
readonly TASK_STARTED: "plugin.task_started";
|
|
296
|
+
readonly TASK_COMPLETED: "plugin.task_completed";
|
|
297
|
+
readonly TASK_FAILED: "plugin.task_failed";
|
|
298
|
+
readonly JOB_FAILED: "plugin.job_failed";
|
|
299
|
+
readonly DATA_PURGED: "plugin.data_purged";
|
|
300
|
+
readonly DEPOSIT_INITIATED: "cashier.deposit_initiated";
|
|
301
|
+
readonly DEPOSIT_COMPLETED: "cashier.deposit_completed";
|
|
302
|
+
readonly DEPOSIT_FAILED: "cashier.deposit_failed";
|
|
303
|
+
readonly WITHDRAWAL_REQUESTED: "cashier.withdrawal_requested";
|
|
304
|
+
readonly WITHDRAWAL_APPROVED: "cashier.withdrawal_approved";
|
|
305
|
+
readonly WITHDRAWAL_REJECTED: "cashier.withdrawal_rejected";
|
|
306
|
+
readonly WITHDRAWAL_PAID: "cashier.withdrawal_paid";
|
|
307
|
+
readonly WITHDRAWAL_FAILED: "cashier.withdrawal_failed";
|
|
308
|
+
/** Player/BO cancelled a not-yet-processing withdrawal; locked funds released. */
|
|
309
|
+
readonly WITHDRAWAL_CANCELLED: "cashier.withdrawal_cancelled";
|
|
310
|
+
readonly PAYMENT_FEE_RECORDED: "cashier.payment_fee_recorded";
|
|
311
|
+
readonly CHARGEBACK_RECORDED: "cashier.chargeback_recorded";
|
|
312
|
+
readonly PAYMENT_INSTRUMENT_ADDED: "cashier.payment_instrument_added";
|
|
313
|
+
readonly SOURCE_REGISTERED: "catalog.source_registered";
|
|
314
|
+
readonly SOURCE_IMPORTED: "catalog.source_imported";
|
|
315
|
+
readonly PROVIDER_UPSERTED: "catalog.provider_upserted";
|
|
316
|
+
readonly CATEGORY_UPSERTED: "catalog.category_upserted";
|
|
317
|
+
readonly GAME_CREATED: "catalog.game_created";
|
|
318
|
+
readonly GAME_UPDATED: "catalog.game_updated";
|
|
319
|
+
readonly GAME_RETIRED: "catalog.game_retired";
|
|
320
|
+
readonly TENANT_PROVIDER_TOGGLED: "catalog.tenant_provider_toggled";
|
|
321
|
+
readonly TENANT_GAME_OVERLAID: "catalog.tenant_game_overlaid";
|
|
322
|
+
readonly GAMES_BULK_UPDATED: "catalog.games_bulk_updated";
|
|
323
|
+
readonly CACHE_INVALIDATED: "catalog.cache_invalidated";
|
|
324
|
+
readonly PLAYER_TAG_ASSIGNED: "classification.tag_assigned";
|
|
325
|
+
readonly PLAYER_TAG_REMOVED: "classification.tag_removed";
|
|
326
|
+
readonly SEGMENT_MEMBERSHIP_CHANGED: "classification.segment_membership_changed";
|
|
327
|
+
readonly AUDIENCE_EXPORTED: "classification.audience_exported";
|
|
328
|
+
readonly CONVERSION_FEEDBACK_SENT: "classification.conversion_feedback_sent";
|
|
329
|
+
readonly CLICK_RECORDED: "affiliate.click_recorded";
|
|
330
|
+
readonly REGISTRATION_ATTRIBUTED: "affiliate.registration_attributed";
|
|
331
|
+
/** The one-time, permanent player→affiliate assignment (one per player, ever). */
|
|
332
|
+
readonly ASSIGNED: "affiliate.assigned";
|
|
333
|
+
readonly COMMISSION_CREATED: "affiliate.commission_created";
|
|
334
|
+
readonly COMMISSION_SETTLED: "affiliate.commission_settled";
|
|
335
|
+
readonly PLACED: "bet.placed";
|
|
336
|
+
readonly SETTLED: "bet.settled";
|
|
337
|
+
readonly GRANTED: "bonus.granted";
|
|
338
|
+
readonly REVOKED: "bonus.revoked";
|
|
339
|
+
readonly OFFERED: "bonus.offered";
|
|
340
|
+
readonly CLAIMED: "bonus.claimed";
|
|
341
|
+
readonly ACTIVATED: "bonus.activated";
|
|
342
|
+
readonly WAGERING_PROGRESSED: "bonus.wagering_progressed";
|
|
343
|
+
readonly WAGERING_COMPLETED: "bonus.wagering_completed";
|
|
344
|
+
readonly CONVERTED: "bonus.converted";
|
|
345
|
+
readonly EXPIRED: "bonus.expired";
|
|
346
|
+
readonly FORFEITED: "bonus.forfeited";
|
|
347
|
+
readonly VOIDED: "bonus.voided";
|
|
348
|
+
readonly GRANT_QUEUED: "bonus.grant_queued";
|
|
349
|
+
readonly GRANT_REJECTED: "bonus.grant_rejected";
|
|
350
|
+
readonly CONSTRAINT_BREACHED: "bonus.constraint_breached";
|
|
351
|
+
readonly CONFIG_UPDATED: "kyc.config_updated";
|
|
352
|
+
readonly REQUEST_CREATED: "kyc.request_created";
|
|
353
|
+
readonly DOCUMENT_UPLOADED: "kyc.document_uploaded";
|
|
354
|
+
readonly DOCUMENT_APPROVED: "kyc.document_approved";
|
|
355
|
+
readonly DOCUMENT_REJECTED: "kyc.document_rejected";
|
|
356
|
+
readonly REQUEST_SUBMITTED: "kyc.request_submitted";
|
|
357
|
+
readonly REQUEST_APPROVED: "kyc.request_approved";
|
|
358
|
+
readonly REQUEST_REJECTED: "kyc.request_rejected";
|
|
359
|
+
readonly REQUEST_NEEDS_MORE: "kyc.request_needs_more";
|
|
360
|
+
readonly REQUEST_ESCALATED: "kyc.request_escalated";
|
|
361
|
+
readonly CREATED: "player.created";
|
|
362
|
+
readonly LOGGED_IN: "player.logged_in";
|
|
363
|
+
readonly LOGGED_OUT: "player.logged_out";
|
|
364
|
+
readonly SESSION_REFRESHED: "player.session_refreshed";
|
|
365
|
+
readonly SOCIAL_LINKED: "player.social_linked";
|
|
366
|
+
readonly SOCIAL_UNLINKED: "player.social_unlinked";
|
|
367
|
+
readonly PASSWORD_CHANGED: "player.password_changed";
|
|
368
|
+
readonly LOGIN_FAILED: "player.login_failed";
|
|
369
|
+
readonly ACCOUNT_LOCKED: "player.account_locked";
|
|
370
|
+
readonly UPDATED: "player.updated";
|
|
371
|
+
/** KYC level-up: the player reached a higher verified level. */
|
|
372
|
+
readonly VERIFIED: "player.verified";
|
|
373
|
+
readonly CLOSED: "player.closed";
|
|
374
|
+
/** Staff suspended the account (backoffice action; payload = ids + reason only). */
|
|
375
|
+
readonly SUSPENDED: "player.suspended";
|
|
376
|
+
/** Staff lifted a suspension, returning the account to active. */
|
|
377
|
+
readonly REACTIVATED: "player.reactivated";
|
|
378
|
+
readonly EMAIL_VERIFIED: "player.email_verified";
|
|
379
|
+
readonly PHONE_VERIFIED: "player.phone_verified";
|
|
380
|
+
readonly SESSION_REVOKED: "player.session_revoked";
|
|
381
|
+
readonly PREFERENCES_UPDATED: "player.preferences_updated";
|
|
382
|
+
readonly LIMIT_CHANGED: "player.limit_changed";
|
|
383
|
+
readonly COOL_OFF_STARTED: "player.cool_off_started";
|
|
384
|
+
readonly SELF_EXCLUDED: "player.self_excluded";
|
|
385
|
+
/**
|
|
386
|
+
* Reality-check tick (§8.2): produced by the worker sweep for active game
|
|
387
|
+
* sessions, delivered to the frontend via the realtime `player` channel.
|
|
388
|
+
* Transport fact only — carries session aggregates, never money truth.
|
|
389
|
+
*/
|
|
390
|
+
readonly REALITY_CHECK: "player.reality_check";
|
|
391
|
+
readonly ONLINE: "player.online";
|
|
392
|
+
readonly OFFLINE: "player.offline";
|
|
393
|
+
readonly CREDITED: "wallet.credited";
|
|
394
|
+
readonly DEBITED: "wallet.debited";
|
|
395
|
+
readonly TRANSFERRED: "wallet.transferred";
|
|
396
|
+
readonly TRANSACTION_REVERSED: "wallet.transaction_reversed";
|
|
397
|
+
readonly WALLET_FROZEN: "wallet.frozen";
|
|
398
|
+
readonly WALLET_UNFROZEN: "wallet.unfrozen";
|
|
399
|
+
};
|
|
400
|
+
type EventValues<T> = T[keyof T];
|
|
401
|
+
type DomainEventName = EventValues<typeof WalletEvents> | EventValues<typeof PlayerEvents> | EventValues<typeof KycEvents> | EventValues<typeof BonusEvents> | EventValues<typeof BetEvents> | EventValues<typeof AffiliateEvents> | EventValues<typeof ClassificationEvents> | EventValues<typeof CatalogEvents> | EventValues<typeof CashierEvents> | EventValues<typeof PluginEvents> | EventValues<typeof ProviderEvents> | EventValues<typeof TournamentEvents> | EventValues<typeof GamificationEvents> | EventValues<typeof LoyaltyEvents> | EventValues<typeof BackofficeEvents> | EventValues<typeof SystemEvents>;
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Strongly-typed payload interfaces — one per event name.
|
|
405
|
+
*
|
|
406
|
+
* These are the stable wire contracts consumers depend on. Payloads are
|
|
407
|
+
* additive-only within a version: to make a breaking change, bump the event
|
|
408
|
+
* version in `versions.ts` and add a new payload interface (e.g.
|
|
409
|
+
* `WalletCreditedPayloadV2`) rather than mutating this one.
|
|
410
|
+
*/
|
|
411
|
+
|
|
412
|
+
interface WalletCreditedPayload {
|
|
413
|
+
walletId: string;
|
|
414
|
+
playerId: string;
|
|
415
|
+
transactionId: string;
|
|
416
|
+
amount: string;
|
|
417
|
+
currency: string;
|
|
418
|
+
source: string;
|
|
419
|
+
balanceBefore: string;
|
|
420
|
+
balanceAfter: string;
|
|
421
|
+
/** Bucket this credit applied to. Additive — populated for realtime wallet.balance. */
|
|
422
|
+
walletType?: WalletBucket;
|
|
423
|
+
/** Post-event balances of every bucket (cash/bonus/locked). Additive. */
|
|
424
|
+
balances?: WalletBucketBalances;
|
|
425
|
+
}
|
|
426
|
+
interface WalletDebitedPayload {
|
|
427
|
+
walletId: string;
|
|
428
|
+
playerId: string;
|
|
429
|
+
transactionId: string;
|
|
430
|
+
amount: string;
|
|
431
|
+
currency: string;
|
|
432
|
+
source: string;
|
|
433
|
+
balanceBefore: string;
|
|
434
|
+
balanceAfter: string;
|
|
435
|
+
/** Bucket this debit applied to. Additive — populated for realtime wallet.balance. */
|
|
436
|
+
walletType?: WalletBucket;
|
|
437
|
+
/** Post-event balances of every bucket (cash/bonus/locked). Additive. */
|
|
438
|
+
balances?: WalletBucketBalances;
|
|
439
|
+
}
|
|
440
|
+
/** The bucket (cash/bonus/locked/…) a movement applied to. */
|
|
441
|
+
type WalletBucket = "cash" | "bonus" | "locked" | (string & {});
|
|
442
|
+
/**
|
|
443
|
+
* Post-event balances of every wallet bucket, as fixed-precision strings. Carried
|
|
444
|
+
* additively on balance-affecting wallet events so the realtime `wallet.balance`
|
|
445
|
+
* projection can ship an authoritative per-bucket snapshot (the SDK contract)
|
|
446
|
+
* without a second read. Buckets beyond these are added here as the engine grows.
|
|
447
|
+
*/
|
|
448
|
+
interface WalletBucketBalances {
|
|
449
|
+
cash: string;
|
|
450
|
+
bonus: string;
|
|
451
|
+
locked: string;
|
|
452
|
+
}
|
|
453
|
+
/** An internal transfer between two buckets of the same wallet. */
|
|
454
|
+
interface WalletTransferredPayload {
|
|
455
|
+
walletId: string;
|
|
456
|
+
playerId: string;
|
|
457
|
+
transactionId: string;
|
|
458
|
+
fromWalletType: WalletBucket;
|
|
459
|
+
toWalletType: WalletBucket;
|
|
460
|
+
amount: string;
|
|
461
|
+
currency: string;
|
|
462
|
+
source: string;
|
|
463
|
+
fromBalanceBefore: string;
|
|
464
|
+
fromBalanceAfter: string;
|
|
465
|
+
toBalanceBefore: string;
|
|
466
|
+
toBalanceAfter: string;
|
|
467
|
+
/** Post-event balances of every bucket (cash/bonus/locked). Additive. */
|
|
468
|
+
balances?: WalletBucketBalances;
|
|
469
|
+
}
|
|
470
|
+
interface WalletDepositCompletedPayload {
|
|
471
|
+
walletId: string;
|
|
472
|
+
playerId: string;
|
|
473
|
+
transactionId: string;
|
|
474
|
+
externalTransactionId: string | null;
|
|
475
|
+
amount: string;
|
|
476
|
+
currency: string;
|
|
477
|
+
source: string;
|
|
478
|
+
balanceBefore: string;
|
|
479
|
+
balanceAfter: string;
|
|
480
|
+
}
|
|
481
|
+
interface WalletWithdrawalRequestedPayload {
|
|
482
|
+
walletId: string;
|
|
483
|
+
playerId: string;
|
|
484
|
+
transactionId: string;
|
|
485
|
+
amount: string;
|
|
486
|
+
currency: string;
|
|
487
|
+
source: string;
|
|
488
|
+
/** Cash balance after the funds were moved into the locked bucket. */
|
|
489
|
+
balanceAfter: string;
|
|
490
|
+
lockedBalanceAfter: string;
|
|
491
|
+
}
|
|
492
|
+
interface WalletWithdrawalApprovedPayload {
|
|
493
|
+
walletId: string;
|
|
494
|
+
playerId: string;
|
|
495
|
+
transactionId: string;
|
|
496
|
+
amount: string;
|
|
497
|
+
currency: string;
|
|
498
|
+
lockedBalanceAfter: string;
|
|
499
|
+
}
|
|
500
|
+
interface WalletWithdrawalRejectedPayload {
|
|
501
|
+
walletId: string;
|
|
502
|
+
playerId: string;
|
|
503
|
+
transactionId: string;
|
|
504
|
+
amount: string;
|
|
505
|
+
currency: string;
|
|
506
|
+
reason: string;
|
|
507
|
+
/** Cash balance after the locked funds were returned. */
|
|
508
|
+
balanceAfter: string;
|
|
509
|
+
lockedBalanceAfter: string;
|
|
510
|
+
}
|
|
511
|
+
interface WalletTransactionReversedPayload {
|
|
512
|
+
walletId: string;
|
|
513
|
+
playerId: string;
|
|
514
|
+
/** The newly-created reversing transaction. */
|
|
515
|
+
transactionId: string;
|
|
516
|
+
/** The original transaction that was reversed. */
|
|
517
|
+
originalTransactionId: string;
|
|
518
|
+
originalType: string;
|
|
519
|
+
amount: string;
|
|
520
|
+
currency: string;
|
|
521
|
+
reason: string;
|
|
522
|
+
/** Internal catalog GameId carried from the original transaction (v2). */
|
|
523
|
+
gameId?: string | null;
|
|
524
|
+
}
|
|
525
|
+
interface WalletFrozenPayload {
|
|
526
|
+
walletId: string;
|
|
527
|
+
playerId: string;
|
|
528
|
+
currency: string;
|
|
529
|
+
reason: string;
|
|
530
|
+
}
|
|
531
|
+
interface WalletUnfrozenPayload {
|
|
532
|
+
walletId: string;
|
|
533
|
+
playerId: string;
|
|
534
|
+
currency: string;
|
|
535
|
+
reason: string;
|
|
536
|
+
}
|
|
537
|
+
interface PlayerCreatedPayload {
|
|
538
|
+
playerId: string;
|
|
539
|
+
brandId: string;
|
|
540
|
+
externalId: string;
|
|
541
|
+
/** How the player came to exist: credential signup or a social provider. */
|
|
542
|
+
signupMethod?: "password" | "social";
|
|
543
|
+
provider?: string;
|
|
544
|
+
/**
|
|
545
|
+
* Attribution hints captured at signup (additive). The click chain lives in
|
|
546
|
+
* a cookie/param set by the tracking endpoint; the affiliate consumer
|
|
547
|
+
* resolves them into the one-time player_attribution row. Capture-or-lose:
|
|
548
|
+
* these cannot be reconstructed later.
|
|
549
|
+
*/
|
|
550
|
+
signupClickId?: string;
|
|
551
|
+
affiliateCode?: string;
|
|
552
|
+
deviceFingerprint?: string;
|
|
553
|
+
}
|
|
554
|
+
/** A successful authentication. `method` distinguishes password vs social login. */
|
|
555
|
+
interface PlayerLoggedInPayload {
|
|
556
|
+
playerId: string;
|
|
557
|
+
sessionId: string;
|
|
558
|
+
method: "password" | "social" | "refresh";
|
|
559
|
+
provider?: string;
|
|
560
|
+
ip?: string;
|
|
561
|
+
/** Resolved geo at login — carried for fraud/risk/compliance consumers. */
|
|
562
|
+
country?: string;
|
|
563
|
+
region?: string;
|
|
564
|
+
city?: string;
|
|
565
|
+
}
|
|
566
|
+
interface PlayerLoggedOutPayload {
|
|
567
|
+
playerId: string;
|
|
568
|
+
sessionId: string;
|
|
569
|
+
}
|
|
570
|
+
interface PlayerSessionRefreshedPayload {
|
|
571
|
+
playerId: string;
|
|
572
|
+
/** The revoked session. */
|
|
573
|
+
previousSessionId: string;
|
|
574
|
+
/** The freshly minted session that replaced it. */
|
|
575
|
+
sessionId: string;
|
|
576
|
+
}
|
|
577
|
+
interface PlayerSocialLinkedPayload {
|
|
578
|
+
playerId: string;
|
|
579
|
+
provider: string;
|
|
580
|
+
providerUserId: string;
|
|
581
|
+
socialAccountId: string;
|
|
582
|
+
}
|
|
583
|
+
interface PlayerSocialUnlinkedPayload {
|
|
584
|
+
playerId: string;
|
|
585
|
+
provider: string;
|
|
586
|
+
socialAccountId: string;
|
|
587
|
+
}
|
|
588
|
+
interface PlayerPasswordChangedPayload {
|
|
589
|
+
playerId: string;
|
|
590
|
+
}
|
|
591
|
+
interface PlayerLoginFailedPayload {
|
|
592
|
+
/** Player id when the account was found; null for unknown email. */
|
|
593
|
+
playerId: string | null;
|
|
594
|
+
email?: string;
|
|
595
|
+
reason: "invalid_credentials" | "account_locked" | "account_inactive";
|
|
596
|
+
ip?: string;
|
|
597
|
+
}
|
|
598
|
+
interface PlayerAccountLockedPayload {
|
|
599
|
+
playerId: string;
|
|
600
|
+
lockedUntil: string;
|
|
601
|
+
failedLoginAttempts: number;
|
|
602
|
+
}
|
|
603
|
+
/** Profile fields changed. `changedFields` lists NAMES only — never values. */
|
|
604
|
+
interface PlayerUpdatedPayload {
|
|
605
|
+
playerId: string;
|
|
606
|
+
changedFields: string[];
|
|
607
|
+
}
|
|
608
|
+
/** KYC level-up: the player's verified level increased (§5.5). */
|
|
609
|
+
interface PlayerVerifiedPayload {
|
|
610
|
+
playerId: string;
|
|
611
|
+
level: number;
|
|
612
|
+
previousLevel: number;
|
|
613
|
+
/** The KYC request whose approval caused the level-up (null = level 0). */
|
|
614
|
+
kycRequestId: string | null;
|
|
615
|
+
}
|
|
616
|
+
/** Player-initiated account closure (§9). Financial history is retained. */
|
|
617
|
+
interface PlayerClosedPayload {
|
|
618
|
+
playerId: string;
|
|
619
|
+
/** Optional player-supplied reason category; free text stays in audit only. */
|
|
620
|
+
reason: string | null;
|
|
621
|
+
}
|
|
622
|
+
/** Staff suspended the account (backoffice). Reason category only — no PII. */
|
|
623
|
+
interface PlayerSuspendedPayload {
|
|
624
|
+
playerId: string;
|
|
625
|
+
reason: string | null;
|
|
626
|
+
}
|
|
627
|
+
/** Staff lifted a suspension. Reason category only — no PII. */
|
|
628
|
+
interface PlayerReactivatedPayload {
|
|
629
|
+
playerId: string;
|
|
630
|
+
reason: string | null;
|
|
631
|
+
}
|
|
632
|
+
interface PlayerEmailVerifiedPayload {
|
|
633
|
+
playerId: string;
|
|
634
|
+
/** Which variant confirmed it: emailed link token or 6-digit OTP. */
|
|
635
|
+
method: "token" | "otp";
|
|
636
|
+
}
|
|
637
|
+
interface PlayerPhoneVerifiedPayload {
|
|
638
|
+
playerId: string;
|
|
639
|
+
}
|
|
640
|
+
/** One or more sessions revoked (list + revoke, password change/reset, close). */
|
|
641
|
+
interface PlayerSessionRevokedPayload {
|
|
642
|
+
playerId: string;
|
|
643
|
+
sessionIds: string[];
|
|
644
|
+
reason: "player" | "password_changed" | "password_reset" | "account_closed" | "admin" | "token_reuse";
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Preferences and/or marketing consents changed. Consent changes are also
|
|
648
|
+
* appended to the `player_consents` GDPR trail; the event carries key+granted
|
|
649
|
+
* (not PII) so CRM consumers can react without reading the table.
|
|
650
|
+
*/
|
|
651
|
+
interface PlayerPreferencesUpdatedPayload {
|
|
652
|
+
playerId: string;
|
|
653
|
+
changedKeys: string[];
|
|
654
|
+
consentChanges: Array<{
|
|
655
|
+
consentKey: string;
|
|
656
|
+
granted: boolean;
|
|
657
|
+
}>;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* A responsible-gaming limit changed (§8.1). Decreases apply immediately;
|
|
661
|
+
* increases/removals are stored pending and apply at `appliesAt` (regulatory
|
|
662
|
+
* ratchet). `value` is a minor-unit amount string, or minutes for
|
|
663
|
+
* `session_time`; null = limit removal.
|
|
664
|
+
*/
|
|
665
|
+
interface PlayerLimitChangedPayload {
|
|
666
|
+
playerId: string;
|
|
667
|
+
kind: "deposit" | "loss" | "wager" | "session_time";
|
|
668
|
+
period: "day" | "week" | "month";
|
|
669
|
+
value: string | null;
|
|
670
|
+
currency?: string;
|
|
671
|
+
/** ISO-8601 moment the new value takes effect. */
|
|
672
|
+
appliesAt: string;
|
|
673
|
+
/** True when the change is a ratcheted (delayed) increase/removal. */
|
|
674
|
+
pending: boolean;
|
|
675
|
+
}
|
|
676
|
+
interface PlayerCoolOffStartedPayload {
|
|
677
|
+
playerId: string;
|
|
678
|
+
period: "24h" | "48h" | "7d" | "30d";
|
|
679
|
+
endsAt: string;
|
|
680
|
+
}
|
|
681
|
+
interface PlayerSelfExcludedPayload {
|
|
682
|
+
playerId: string;
|
|
683
|
+
period: "6m" | "1y" | "5y" | "permanent";
|
|
684
|
+
/** ISO-8601 expiry; null = permanent. */
|
|
685
|
+
endsAt: string | null;
|
|
686
|
+
}
|
|
687
|
+
/** Reality-check tick for an active game session (§8.2). */
|
|
688
|
+
interface PlayerRealityCheckPayload {
|
|
689
|
+
playerId: string;
|
|
690
|
+
gameSessionId: string;
|
|
691
|
+
sessionMinutes: number;
|
|
692
|
+
betTotalMinor: string;
|
|
693
|
+
winTotalMinor: string;
|
|
694
|
+
currency: string | null;
|
|
695
|
+
}
|
|
696
|
+
/** The player transitioned offline→online (first activity after idle/login). */
|
|
697
|
+
interface PlayerOnlinePayload {
|
|
698
|
+
playerId: string;
|
|
699
|
+
}
|
|
700
|
+
/** The player transitioned online→offline (presence TTL expiry, logout, or revocation). */
|
|
701
|
+
interface PlayerOfflinePayload {
|
|
702
|
+
playerId: string;
|
|
703
|
+
/** ISO-8601 timestamp of the last activity we saw; null if unknown. */
|
|
704
|
+
lastActivityAt: string | null;
|
|
705
|
+
reason: "ttl_expired" | "logged_out" | "sessions_revoked";
|
|
706
|
+
}
|
|
707
|
+
/** Operator edited the KYC configuration (document types / requirement sets). */
|
|
708
|
+
interface KycConfigUpdatedPayload {
|
|
709
|
+
entity: "document_type" | "requirement_set";
|
|
710
|
+
key: string;
|
|
711
|
+
active: boolean;
|
|
712
|
+
}
|
|
713
|
+
interface KycRequestCreatedPayload {
|
|
714
|
+
kycRequestId: string;
|
|
715
|
+
playerId: string;
|
|
716
|
+
trigger: "manual" | "signup" | "deposit_total_threshold" | "withdrawal_amount_threshold" | "withdrawal_total_threshold" | "risk_flag";
|
|
717
|
+
requirementSetId: string | null;
|
|
718
|
+
targetLevel: number | null;
|
|
719
|
+
/** Snapshot of required document-type keys at creation time. */
|
|
720
|
+
requiredDocumentTypes: string[];
|
|
721
|
+
}
|
|
722
|
+
/** A document landed — this is what auto-verify KYC adapters key on (§2.4). */
|
|
723
|
+
interface KycDocumentUploadedPayload {
|
|
724
|
+
documentId: string;
|
|
725
|
+
kycRequestId: string | null;
|
|
726
|
+
playerId: string;
|
|
727
|
+
documentTypeKey: string;
|
|
728
|
+
mimeType: string;
|
|
729
|
+
sizeBytes: number;
|
|
730
|
+
}
|
|
731
|
+
interface KycDocumentApprovedPayload {
|
|
732
|
+
documentId: string;
|
|
733
|
+
kycRequestId: string | null;
|
|
734
|
+
playerId: string;
|
|
735
|
+
documentTypeKey: string;
|
|
736
|
+
}
|
|
737
|
+
interface KycDocumentRejectedPayload {
|
|
738
|
+
documentId: string;
|
|
739
|
+
kycRequestId: string | null;
|
|
740
|
+
playerId: string;
|
|
741
|
+
documentTypeKey: string;
|
|
742
|
+
/** Coded rejection reason (e.g. `blurry`, `expired`, `mismatch`). */
|
|
743
|
+
reason: string;
|
|
744
|
+
}
|
|
745
|
+
interface KycRequestSubmittedPayload {
|
|
746
|
+
kycRequestId: string;
|
|
747
|
+
playerId: string;
|
|
748
|
+
documentCount: number;
|
|
749
|
+
}
|
|
750
|
+
interface KycRequestApprovedPayload {
|
|
751
|
+
kycRequestId: string;
|
|
752
|
+
playerId: string;
|
|
753
|
+
/** Level the approval grants (requirement set's targetLevel). */
|
|
754
|
+
level: number;
|
|
755
|
+
}
|
|
756
|
+
interface KycRequestRejectedPayload {
|
|
757
|
+
kycRequestId: string;
|
|
758
|
+
playerId: string;
|
|
759
|
+
reasonCode: string;
|
|
760
|
+
}
|
|
761
|
+
/** BO requested more documents — request flips back to draft with new items. */
|
|
762
|
+
interface KycRequestNeedsMorePayload {
|
|
763
|
+
kycRequestId: string;
|
|
764
|
+
playerId: string;
|
|
765
|
+
requestedDocumentTypes: string[];
|
|
766
|
+
reasonCode: string | null;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* TASK-013 — a BO reviewer escalated a submitted request into the senior
|
|
770
|
+
* `in_review` lane. Carries ids + the mandatory coded/free reason only.
|
|
771
|
+
*/
|
|
772
|
+
interface KycRequestEscalatedPayload {
|
|
773
|
+
kycRequestId: string;
|
|
774
|
+
playerId: string;
|
|
775
|
+
reason: string;
|
|
776
|
+
}
|
|
777
|
+
interface BonusGrantedPayload {
|
|
778
|
+
bonusId: string;
|
|
779
|
+
playerId: string;
|
|
780
|
+
campaign: string;
|
|
781
|
+
amount: string;
|
|
782
|
+
currency: string;
|
|
783
|
+
}
|
|
784
|
+
interface BonusRevokedPayload {
|
|
785
|
+
bonusId: string;
|
|
786
|
+
playerId: string;
|
|
787
|
+
reason: string;
|
|
788
|
+
}
|
|
789
|
+
interface BonusOfferedPayload {
|
|
790
|
+
instanceId: string;
|
|
791
|
+
templateId: string;
|
|
792
|
+
templateVersion: number;
|
|
793
|
+
playerId: string;
|
|
794
|
+
amount: string;
|
|
795
|
+
currency: string;
|
|
796
|
+
/** ISO-8601 claim deadline; null when the offer has no claim clock. */
|
|
797
|
+
claimBy: string | null;
|
|
798
|
+
/** Domain event id (or other natural key) that triggered the grant. */
|
|
799
|
+
sourceEventId: string | null;
|
|
800
|
+
}
|
|
801
|
+
interface BonusClaimedPayload {
|
|
802
|
+
instanceId: string;
|
|
803
|
+
templateId: string;
|
|
804
|
+
playerId: string;
|
|
805
|
+
}
|
|
806
|
+
interface BonusActivatedPayload {
|
|
807
|
+
instanceId: string;
|
|
808
|
+
templateId: string;
|
|
809
|
+
playerId: string;
|
|
810
|
+
amount: string;
|
|
811
|
+
currency: string;
|
|
812
|
+
wageringRequired: string;
|
|
813
|
+
/** ISO-8601 wagering deadline; null when the plan has no complete-by clock. */
|
|
814
|
+
completeBy: string | null;
|
|
815
|
+
}
|
|
816
|
+
interface BonusWageringProgressedPayload {
|
|
817
|
+
instanceId: string;
|
|
818
|
+
playerId: string;
|
|
819
|
+
progress: string;
|
|
820
|
+
required: string;
|
|
821
|
+
/** The 10%-step milestone just crossed (10, 20, … 100). */
|
|
822
|
+
milestonePct: number;
|
|
823
|
+
}
|
|
824
|
+
interface BonusWageringCompletedPayload {
|
|
825
|
+
instanceId: string;
|
|
826
|
+
playerId: string;
|
|
827
|
+
wagered: string;
|
|
828
|
+
required: string;
|
|
829
|
+
}
|
|
830
|
+
interface BonusConvertedPayload {
|
|
831
|
+
instanceId: string;
|
|
832
|
+
playerId: string;
|
|
833
|
+
/** Amount moved bonus → cash after caps. */
|
|
834
|
+
convertedAmount: string;
|
|
835
|
+
/** Excess removed by max-win/conversion caps ("0.0000" when uncapped). */
|
|
836
|
+
cappedAmount: string;
|
|
837
|
+
currency: string;
|
|
838
|
+
/** false = phantom bonus: principal removed, only winnings converted. */
|
|
839
|
+
cashable: boolean;
|
|
840
|
+
}
|
|
841
|
+
interface BonusExpiredPayload {
|
|
842
|
+
instanceId: string;
|
|
843
|
+
playerId: string;
|
|
844
|
+
/** Which clock ran out: the claim-by window or the wagering complete-by window. */
|
|
845
|
+
phase: "claim" | "wagering";
|
|
846
|
+
forfeitedAmount: string;
|
|
847
|
+
currency: string;
|
|
848
|
+
}
|
|
849
|
+
interface BonusForfeitedPayload {
|
|
850
|
+
instanceId: string;
|
|
851
|
+
playerId: string;
|
|
852
|
+
reason: "player_cancelled" | "withdrawal" | "breach" | "admin";
|
|
853
|
+
forfeitedAmount: string;
|
|
854
|
+
currency: string;
|
|
855
|
+
}
|
|
856
|
+
interface BonusVoidedPayload {
|
|
857
|
+
instanceId: string;
|
|
858
|
+
playerId: string;
|
|
859
|
+
reason: string;
|
|
860
|
+
voidedAmount: string;
|
|
861
|
+
currency: string;
|
|
862
|
+
}
|
|
863
|
+
interface BonusGrantQueuedPayload {
|
|
864
|
+
approvalId: string;
|
|
865
|
+
templateId: string;
|
|
866
|
+
playerId: string;
|
|
867
|
+
amount: string;
|
|
868
|
+
currency: string;
|
|
869
|
+
reason: string;
|
|
870
|
+
}
|
|
871
|
+
interface BonusGrantRejectedPayload {
|
|
872
|
+
templateId: string;
|
|
873
|
+
playerId: string;
|
|
874
|
+
/** Guard/policy that rejected the grant, for audit + analytics. */
|
|
875
|
+
reason: string;
|
|
876
|
+
sourceEventId: string | null;
|
|
877
|
+
}
|
|
878
|
+
interface BonusConstraintBreachedPayload {
|
|
879
|
+
instanceId: string;
|
|
880
|
+
playerId: string;
|
|
881
|
+
kind: "max_bet" | "restricted_game";
|
|
882
|
+
action: "rejected" | "warned" | "voided";
|
|
883
|
+
roundId: string | null;
|
|
884
|
+
gameId: string | null;
|
|
885
|
+
}
|
|
886
|
+
interface TournamentStartedPayload {
|
|
887
|
+
tournamentId: string;
|
|
888
|
+
name: string;
|
|
889
|
+
/** Plugin that runs the tournament (emitDomain attribution). */
|
|
890
|
+
pluginKey: string | null;
|
|
891
|
+
startsAt: string;
|
|
892
|
+
endsAt: string;
|
|
893
|
+
}
|
|
894
|
+
interface TournamentEndedPayload {
|
|
895
|
+
tournamentId: string;
|
|
896
|
+
name: string;
|
|
897
|
+
pluginKey: string | null;
|
|
898
|
+
endedAt: string;
|
|
899
|
+
participants: number;
|
|
900
|
+
}
|
|
901
|
+
interface TournamentPrizeAwardedPayload {
|
|
902
|
+
tournamentId: string;
|
|
903
|
+
playerId: string;
|
|
904
|
+
rank: number;
|
|
905
|
+
/** RewardArtifact kind granted for the rank (cash, bonusFunds, freeSpins, …). */
|
|
906
|
+
artifactKind: string;
|
|
907
|
+
amount: string | null;
|
|
908
|
+
currency: string | null;
|
|
909
|
+
/** Bonus instance created by the prize grant, when the artifact was fund-like. */
|
|
910
|
+
bonusInstanceId: string | null;
|
|
911
|
+
}
|
|
912
|
+
interface AchievementUnlockedPayload {
|
|
913
|
+
playerId: string;
|
|
914
|
+
achievementKey: string;
|
|
915
|
+
pluginKey: string | null;
|
|
916
|
+
}
|
|
917
|
+
interface MissionCompletedPayload {
|
|
918
|
+
playerId: string;
|
|
919
|
+
missionKey: string;
|
|
920
|
+
pluginKey: string | null;
|
|
921
|
+
}
|
|
922
|
+
interface LevelUpPayload {
|
|
923
|
+
playerId: string;
|
|
924
|
+
level: number;
|
|
925
|
+
previousLevel: number;
|
|
926
|
+
pluginKey: string | null;
|
|
927
|
+
}
|
|
928
|
+
interface LoyaltyPointsEarnedPayload {
|
|
929
|
+
playerId: string;
|
|
930
|
+
points: number;
|
|
931
|
+
balance: number;
|
|
932
|
+
/** Earn rule / source key (e.g. "bet_turnover", "mission:daily-spin"). */
|
|
933
|
+
source: string;
|
|
934
|
+
pluginKey: string | null;
|
|
935
|
+
}
|
|
936
|
+
interface LoyaltyPointsRedeemedPayload {
|
|
937
|
+
playerId: string;
|
|
938
|
+
points: number;
|
|
939
|
+
balance: number;
|
|
940
|
+
/** What the points were redeemed for (shop item / reward key). */
|
|
941
|
+
redemptionKey: string;
|
|
942
|
+
pluginKey: string | null;
|
|
943
|
+
}
|
|
944
|
+
interface BetPlacedPayload {
|
|
945
|
+
playerId: string;
|
|
946
|
+
roundId: string;
|
|
947
|
+
amount: string;
|
|
948
|
+
currency: string;
|
|
949
|
+
provider: string;
|
|
950
|
+
/** Internal catalog GameId (v2; resolved by the provider/command layer). */
|
|
951
|
+
gameId: string | null;
|
|
952
|
+
}
|
|
953
|
+
interface BetSettledPayload {
|
|
954
|
+
playerId: string;
|
|
955
|
+
roundId: string;
|
|
956
|
+
payout: string;
|
|
957
|
+
currency: string;
|
|
958
|
+
provider: string;
|
|
959
|
+
/** Internal catalog GameId (v2). */
|
|
960
|
+
gameId: string | null;
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* A tracked click on an affiliate link — the irreversible capture. Click IDs
|
|
964
|
+
* (`gclid`/`fbclid`/…) can only be keyed here; a click ID not stored at click
|
|
965
|
+
* time is unrecoverable (the whole PPC feedback loop depends on this event).
|
|
966
|
+
*/
|
|
967
|
+
interface AffiliateClickRecordedPayload {
|
|
968
|
+
clickId: string;
|
|
969
|
+
affiliateId: string;
|
|
970
|
+
campaignId?: string;
|
|
971
|
+
creativeId?: string;
|
|
972
|
+
subId1?: string;
|
|
973
|
+
subId2?: string;
|
|
974
|
+
subId3?: string;
|
|
975
|
+
subId4?: string;
|
|
976
|
+
subId5?: string;
|
|
977
|
+
gclid?: string;
|
|
978
|
+
fbclid?: string;
|
|
979
|
+
ttclid?: string;
|
|
980
|
+
msclkid?: string;
|
|
981
|
+
utmSource?: string;
|
|
982
|
+
utmMedium?: string;
|
|
983
|
+
utmCampaign?: string;
|
|
984
|
+
utmTerm?: string;
|
|
985
|
+
utmContent?: string;
|
|
986
|
+
landingPage?: string;
|
|
987
|
+
referrer?: string;
|
|
988
|
+
/** PII-adjacent fraud/attribution signals — analytics must not re-expose these. */
|
|
989
|
+
ip?: string;
|
|
990
|
+
userAgent?: string;
|
|
991
|
+
deviceFingerprint?: string;
|
|
992
|
+
}
|
|
993
|
+
/** A registration matched to a recorded click (funnel step: click → reg). */
|
|
994
|
+
interface AffiliateRegistrationAttributedPayload {
|
|
995
|
+
playerId: string;
|
|
996
|
+
affiliateId: string;
|
|
997
|
+
clickId: string | null;
|
|
998
|
+
/** Attribution model that made the match. */
|
|
999
|
+
attributionModel: "first_touch" | "last_touch" | "multi_touch";
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* The one-time player→affiliate assignment. Exactly one per player, ever
|
|
1003
|
+
* (`UNIQUE(tenantId, playerId)`); re-delivery never reassigns.
|
|
1004
|
+
*/
|
|
1005
|
+
interface AffiliateAssignedPayload {
|
|
1006
|
+
playerId: string;
|
|
1007
|
+
affiliateId: string;
|
|
1008
|
+
dealId: string | null;
|
|
1009
|
+
clickId: string | null;
|
|
1010
|
+
attributionModel: "first_touch" | "last_touch" | "multi_touch";
|
|
1011
|
+
/** Which lifecycle moment resolved the attribution. */
|
|
1012
|
+
trigger: "registration" | "first_deposit";
|
|
1013
|
+
/** Denormalized acquisition dimensions for analytics facts. */
|
|
1014
|
+
channel?: string;
|
|
1015
|
+
source?: string;
|
|
1016
|
+
campaignId?: string;
|
|
1017
|
+
gclid?: string;
|
|
1018
|
+
fbclid?: string;
|
|
1019
|
+
ttclid?: string;
|
|
1020
|
+
msclkid?: string;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* One append-only `affiliate_commissions` ledger row (v2 — ledger semantics).
|
|
1024
|
+
* `playerId` is set for per-player accruals (CPA) and null for period-cohort
|
|
1025
|
+
* accruals (RevShare); corrections are compensating rows, never mutations.
|
|
1026
|
+
*/
|
|
1027
|
+
interface AffiliateCommissionCreatedPayload {
|
|
1028
|
+
entryId: string;
|
|
1029
|
+
affiliateId: string;
|
|
1030
|
+
dealId: string | null;
|
|
1031
|
+
/** Ledger leg kind. */
|
|
1032
|
+
kind: "cpa" | "revshare" | "adjustment" | "settlement";
|
|
1033
|
+
playerId: string | null;
|
|
1034
|
+
/** Commission run this accrual belongs to (null for out-of-run adjustments). */
|
|
1035
|
+
runId: string | null;
|
|
1036
|
+
/** Signed amount in `currency` (negative = carryover deficit / clawback). */
|
|
1037
|
+
amount: string;
|
|
1038
|
+
currency: string;
|
|
1039
|
+
baseCurrency?: string;
|
|
1040
|
+
fxRate?: string;
|
|
1041
|
+
/** Running affiliate commission balance, wallet-ledger style. */
|
|
1042
|
+
balanceBefore: string;
|
|
1043
|
+
balanceAfter: string;
|
|
1044
|
+
}
|
|
1045
|
+
/** A closed commission run settled under maker-checker. */
|
|
1046
|
+
interface AffiliateCommissionSettledPayload {
|
|
1047
|
+
runId: string;
|
|
1048
|
+
affiliateId: string;
|
|
1049
|
+
/** Period the statement covers (ISO dates, inclusive start / exclusive end). */
|
|
1050
|
+
periodStart: string;
|
|
1051
|
+
periodEnd: string;
|
|
1052
|
+
/** Net payable after carryover + admin fee ("0.0000" when carryover ate it). */
|
|
1053
|
+
amount: string;
|
|
1054
|
+
currency: string;
|
|
1055
|
+
baseCurrency?: string;
|
|
1056
|
+
fxRate?: string;
|
|
1057
|
+
/** Carryover balance remaining after this settlement (negative = still owed). */
|
|
1058
|
+
carryoverAfter: string;
|
|
1059
|
+
makerActorId: string;
|
|
1060
|
+
checkerActorId: string;
|
|
1061
|
+
}
|
|
1062
|
+
interface PlayerTagAssignedPayload {
|
|
1063
|
+
playerId: string;
|
|
1064
|
+
/** Namespaced `category:value` tag key, e.g. `value:whale`. */
|
|
1065
|
+
tagKey: string;
|
|
1066
|
+
source: "auto" | "manual" | "import" | "campaign";
|
|
1067
|
+
/** Ruleset/model version that produced the assignment (auditable). */
|
|
1068
|
+
ruleVersion: string | null;
|
|
1069
|
+
reason: string | null;
|
|
1070
|
+
/** ISO-8601 expiry; null = does not auto-expire. */
|
|
1071
|
+
expiresAt: string | null;
|
|
1072
|
+
}
|
|
1073
|
+
interface PlayerTagRemovedPayload {
|
|
1074
|
+
playerId: string;
|
|
1075
|
+
tagKey: string;
|
|
1076
|
+
reason: "expired" | "rule_no_longer_matches" | "manual" | "superseded";
|
|
1077
|
+
ruleVersion: string | null;
|
|
1078
|
+
}
|
|
1079
|
+
interface SegmentMembershipChangedPayload {
|
|
1080
|
+
segmentId: string;
|
|
1081
|
+
playerId: string;
|
|
1082
|
+
change: "entered" | "exited";
|
|
1083
|
+
/** Segment definition version the recompute evaluated. */
|
|
1084
|
+
segmentVersion: number;
|
|
1085
|
+
}
|
|
1086
|
+
interface AudienceExportedPayload {
|
|
1087
|
+
exportId: string;
|
|
1088
|
+
segmentId: string;
|
|
1089
|
+
destination: "google" | "meta" | "tiktok" | "dsp" | "csv";
|
|
1090
|
+
purpose: "seed" | "suppression";
|
|
1091
|
+
/** How match keys were hashed (e.g. `sha256_email_phone`). Never the keys. */
|
|
1092
|
+
matchKeyType: string;
|
|
1093
|
+
count: number;
|
|
1094
|
+
/** Players excluded by the RG/consent guard at the exporter boundary. */
|
|
1095
|
+
excludedCount: number;
|
|
1096
|
+
}
|
|
1097
|
+
interface ConversionFeedbackSentPayload {
|
|
1098
|
+
playerId: string;
|
|
1099
|
+
destination: "google" | "meta" | "tiktok";
|
|
1100
|
+
/** Which stored click ID keyed the conversion. */
|
|
1101
|
+
clickIdType: "gclid" | "fbclid" | "ttclid" | "msclkid";
|
|
1102
|
+
/** Milestone that triggered the postback. */
|
|
1103
|
+
conversionEvent: "ftd" | "value_milestone";
|
|
1104
|
+
conversionValue: string;
|
|
1105
|
+
currency: string;
|
|
1106
|
+
valueSource: "deposit" | "pltv";
|
|
1107
|
+
}
|
|
1108
|
+
interface CatalogSourceRegisteredPayload {
|
|
1109
|
+
sourceId: string;
|
|
1110
|
+
key: string;
|
|
1111
|
+
adapter: string;
|
|
1112
|
+
/** NULL/undefined = platform/global source; set = tenant-owned. */
|
|
1113
|
+
ownerTenantId?: string | null;
|
|
1114
|
+
}
|
|
1115
|
+
interface CatalogSourceImportedPayload {
|
|
1116
|
+
sourceId: string;
|
|
1117
|
+
/** Per-entity diff counts produced by the idempotent import. */
|
|
1118
|
+
providers: {
|
|
1119
|
+
inserted: number;
|
|
1120
|
+
updated: number;
|
|
1121
|
+
retired: number;
|
|
1122
|
+
};
|
|
1123
|
+
games: {
|
|
1124
|
+
inserted: number;
|
|
1125
|
+
updated: number;
|
|
1126
|
+
retired: number;
|
|
1127
|
+
};
|
|
1128
|
+
categories: {
|
|
1129
|
+
inserted: number;
|
|
1130
|
+
updated: number;
|
|
1131
|
+
};
|
|
1132
|
+
restrictionGroups: {
|
|
1133
|
+
inserted: number;
|
|
1134
|
+
updated: number;
|
|
1135
|
+
};
|
|
1136
|
+
currencyGroups: {
|
|
1137
|
+
inserted: number;
|
|
1138
|
+
updated: number;
|
|
1139
|
+
};
|
|
1140
|
+
durationMs: number;
|
|
1141
|
+
}
|
|
1142
|
+
interface CatalogProviderUpsertedPayload {
|
|
1143
|
+
sourceId: string;
|
|
1144
|
+
providerId: string;
|
|
1145
|
+
externalId: string;
|
|
1146
|
+
parentProviderId: string | null;
|
|
1147
|
+
operation: "inserted" | "updated";
|
|
1148
|
+
}
|
|
1149
|
+
interface CatalogCategoryUpsertedPayload {
|
|
1150
|
+
sourceId: string;
|
|
1151
|
+
categoryId: string;
|
|
1152
|
+
slug: string;
|
|
1153
|
+
parentCategoryId: string | null;
|
|
1154
|
+
operation: "inserted" | "updated";
|
|
1155
|
+
}
|
|
1156
|
+
interface CatalogGameCreatedPayload {
|
|
1157
|
+
sourceId: string;
|
|
1158
|
+
gameId: string;
|
|
1159
|
+
externalGameId: string;
|
|
1160
|
+
providerId: string;
|
|
1161
|
+
launchCode: string | null;
|
|
1162
|
+
/** Set when this is an operator-owned custom game. */
|
|
1163
|
+
ownerTenantId?: string | null;
|
|
1164
|
+
}
|
|
1165
|
+
interface CatalogGameUpdatedPayload {
|
|
1166
|
+
sourceId: string;
|
|
1167
|
+
gameId: string;
|
|
1168
|
+
externalGameId: string;
|
|
1169
|
+
changedFields: string[];
|
|
1170
|
+
}
|
|
1171
|
+
interface CatalogGameRetiredPayload {
|
|
1172
|
+
sourceId: string;
|
|
1173
|
+
gameId: string;
|
|
1174
|
+
externalGameId: string;
|
|
1175
|
+
}
|
|
1176
|
+
interface CatalogTenantProviderToggledPayload {
|
|
1177
|
+
tenantId: string;
|
|
1178
|
+
providerId: string;
|
|
1179
|
+
status: "enabled" | "disabled";
|
|
1180
|
+
}
|
|
1181
|
+
interface CatalogTenantGameOverlaidPayload {
|
|
1182
|
+
tenantId: string;
|
|
1183
|
+
gameId: string;
|
|
1184
|
+
status: "enabled" | "disabled" | "archived" | "hidden";
|
|
1185
|
+
featured?: boolean;
|
|
1186
|
+
}
|
|
1187
|
+
interface CatalogGamesBulkUpdatedPayload {
|
|
1188
|
+
tenantId: string;
|
|
1189
|
+
count: number;
|
|
1190
|
+
status?: "enabled" | "disabled" | "archived" | "hidden";
|
|
1191
|
+
}
|
|
1192
|
+
interface CatalogCacheInvalidatedPayload {
|
|
1193
|
+
/** Scope of the invalidation. Global bumps affect all tenants. */
|
|
1194
|
+
scope: "global" | "tenant";
|
|
1195
|
+
/** Present when scope === "tenant". */
|
|
1196
|
+
tenantId?: string;
|
|
1197
|
+
/** The new effective version counter value. */
|
|
1198
|
+
version: number;
|
|
1199
|
+
}
|
|
1200
|
+
interface CashierDepositInitiatedPayload {
|
|
1201
|
+
depositId: string;
|
|
1202
|
+
playerId: string;
|
|
1203
|
+
amount: string;
|
|
1204
|
+
currency: string;
|
|
1205
|
+
providerKey: string;
|
|
1206
|
+
methodKey?: string;
|
|
1207
|
+
isFirstDeposit: boolean;
|
|
1208
|
+
}
|
|
1209
|
+
interface CashierDepositCompletedPayload {
|
|
1210
|
+
depositId: string;
|
|
1211
|
+
playerId: string;
|
|
1212
|
+
/** SOFT id of the wallet transaction that credited cash. */
|
|
1213
|
+
walletTransactionId: string;
|
|
1214
|
+
amount: string;
|
|
1215
|
+
currency: string;
|
|
1216
|
+
baseCurrency?: string;
|
|
1217
|
+
/** FX rate snapshot at event time (string for precision). */
|
|
1218
|
+
fxRate?: string;
|
|
1219
|
+
providerKey: string;
|
|
1220
|
+
methodKey?: string;
|
|
1221
|
+
/** Total estimated fee at capture (itemized rows come via PAYMENT_FEE_RECORDED). */
|
|
1222
|
+
feeAmount?: string;
|
|
1223
|
+
isFirstDeposit: boolean;
|
|
1224
|
+
affiliateId?: string;
|
|
1225
|
+
}
|
|
1226
|
+
interface CashierDepositFailedPayload {
|
|
1227
|
+
depositId: string;
|
|
1228
|
+
playerId: string;
|
|
1229
|
+
amount: string;
|
|
1230
|
+
currency: string;
|
|
1231
|
+
providerKey: string;
|
|
1232
|
+
reason: string;
|
|
1233
|
+
}
|
|
1234
|
+
interface CashierWithdrawalRequestedPayload {
|
|
1235
|
+
withdrawalId: string;
|
|
1236
|
+
playerId: string;
|
|
1237
|
+
amount: string;
|
|
1238
|
+
currency: string;
|
|
1239
|
+
/** SOFT id of the wallet transaction that reserved cash→locked. */
|
|
1240
|
+
reserveWalletTxId: string;
|
|
1241
|
+
status: string;
|
|
1242
|
+
}
|
|
1243
|
+
interface CashierWithdrawalApprovedPayload {
|
|
1244
|
+
withdrawalId: string;
|
|
1245
|
+
playerId: string;
|
|
1246
|
+
amount: string;
|
|
1247
|
+
currency: string;
|
|
1248
|
+
providerKey: string;
|
|
1249
|
+
approvalActorType?: string;
|
|
1250
|
+
approvalActorId?: string;
|
|
1251
|
+
}
|
|
1252
|
+
interface CashierWithdrawalRejectedPayload {
|
|
1253
|
+
withdrawalId: string;
|
|
1254
|
+
playerId: string;
|
|
1255
|
+
amount: string;
|
|
1256
|
+
currency: string;
|
|
1257
|
+
reason: string;
|
|
1258
|
+
/** SOFT id of the wallet transaction that released locked→cash. */
|
|
1259
|
+
releaseWalletTxId: string;
|
|
1260
|
+
}
|
|
1261
|
+
interface CashierWithdrawalPaidPayload {
|
|
1262
|
+
withdrawalId: string;
|
|
1263
|
+
playerId: string;
|
|
1264
|
+
amount: string;
|
|
1265
|
+
currency: string;
|
|
1266
|
+
providerKey: string;
|
|
1267
|
+
/** SOFT id of the wallet transaction that debited the locked funds. */
|
|
1268
|
+
settleWalletTxId: string;
|
|
1269
|
+
}
|
|
1270
|
+
/** Cancel while `requested`/`pending_review`: locked funds released to cash. */
|
|
1271
|
+
interface CashierWithdrawalCancelledPayload {
|
|
1272
|
+
withdrawalId: string;
|
|
1273
|
+
playerId: string;
|
|
1274
|
+
amount: string;
|
|
1275
|
+
currency: string;
|
|
1276
|
+
cancelledBy: "player" | "staff" | "plugin";
|
|
1277
|
+
/** SOFT id of the wallet transaction that released locked→cash. */
|
|
1278
|
+
releaseWalletTxId: string;
|
|
1279
|
+
}
|
|
1280
|
+
interface CashierWithdrawalFailedPayload {
|
|
1281
|
+
withdrawalId: string;
|
|
1282
|
+
playerId: string;
|
|
1283
|
+
amount: string;
|
|
1284
|
+
currency: string;
|
|
1285
|
+
reason: string;
|
|
1286
|
+
/** SOFT id of the wallet transaction that released locked→cash (if released). */
|
|
1287
|
+
releaseWalletTxId?: string;
|
|
1288
|
+
}
|
|
1289
|
+
/** NGR deduction fact — `ded_payment_fees`. Itemized, never collapsed. */
|
|
1290
|
+
interface CashierPaymentFeeRecordedPayload {
|
|
1291
|
+
feeId: string;
|
|
1292
|
+
depositId?: string;
|
|
1293
|
+
withdrawalId?: string;
|
|
1294
|
+
playerId?: string;
|
|
1295
|
+
feeType: string;
|
|
1296
|
+
estimatedOrActual: "estimated" | "actual";
|
|
1297
|
+
amount: string;
|
|
1298
|
+
currency: string;
|
|
1299
|
+
baseCurrency?: string;
|
|
1300
|
+
fxRate?: string;
|
|
1301
|
+
source: string;
|
|
1302
|
+
sourceTxId?: string;
|
|
1303
|
+
sourceReportId?: string;
|
|
1304
|
+
affiliateId?: string;
|
|
1305
|
+
}
|
|
1306
|
+
/** NGR deduction fact — `ded_chargebacks`. */
|
|
1307
|
+
interface CashierChargebackRecordedPayload {
|
|
1308
|
+
chargebackId: string;
|
|
1309
|
+
depositId?: string;
|
|
1310
|
+
playerId?: string;
|
|
1311
|
+
disputeId: string;
|
|
1312
|
+
stage: string;
|
|
1313
|
+
amount: string;
|
|
1314
|
+
feeAmount?: string;
|
|
1315
|
+
currency: string;
|
|
1316
|
+
baseCurrency?: string;
|
|
1317
|
+
fxRate?: string;
|
|
1318
|
+
/** SOFT id of the wallet clawback transaction (set on `lost`). */
|
|
1319
|
+
clawbackWalletTxId?: string;
|
|
1320
|
+
affiliateId?: string;
|
|
1321
|
+
}
|
|
1322
|
+
interface CashierPaymentInstrumentAddedPayload {
|
|
1323
|
+
instrumentId: string;
|
|
1324
|
+
playerId: string;
|
|
1325
|
+
providerKey: string;
|
|
1326
|
+
kind: string;
|
|
1327
|
+
brand?: string;
|
|
1328
|
+
fingerprint?: string;
|
|
1329
|
+
}
|
|
1330
|
+
interface PluginInstalledPayload {
|
|
1331
|
+
pluginId: string;
|
|
1332
|
+
pluginKey: string;
|
|
1333
|
+
version: string;
|
|
1334
|
+
channel: string;
|
|
1335
|
+
}
|
|
1336
|
+
interface PluginEnabledPayload {
|
|
1337
|
+
pluginId: string;
|
|
1338
|
+
pluginKey: string;
|
|
1339
|
+
version: string;
|
|
1340
|
+
/** Set for provider-kind plugins (the provider adapter that was registered). */
|
|
1341
|
+
providerKey?: string;
|
|
1342
|
+
}
|
|
1343
|
+
interface PluginDisabledPayload {
|
|
1344
|
+
pluginId: string;
|
|
1345
|
+
pluginKey: string;
|
|
1346
|
+
reason?: string;
|
|
1347
|
+
providerKey?: string;
|
|
1348
|
+
}
|
|
1349
|
+
interface PluginUninstalledPayload {
|
|
1350
|
+
pluginId: string;
|
|
1351
|
+
pluginKey: string;
|
|
1352
|
+
}
|
|
1353
|
+
interface PluginConfiguredPayload {
|
|
1354
|
+
pluginId: string;
|
|
1355
|
+
pluginKey: string;
|
|
1356
|
+
/** Plugin version the saved values validate against. */
|
|
1357
|
+
schemaVersion: string;
|
|
1358
|
+
/** Non-secret setting keys that changed. Values are never carried on events. */
|
|
1359
|
+
changedKeys: string[];
|
|
1360
|
+
/** Secret keys that were (re)written. Only key names — never values. */
|
|
1361
|
+
secretKeysChanged: string[];
|
|
1362
|
+
/** Set when this event records a pin/unpin rather than a settings write. */
|
|
1363
|
+
pinnedVersion?: string | null;
|
|
1364
|
+
}
|
|
1365
|
+
interface PluginPublishedPayload {
|
|
1366
|
+
pluginId: string;
|
|
1367
|
+
pluginKey: string;
|
|
1368
|
+
version: string;
|
|
1369
|
+
channel: string;
|
|
1370
|
+
checksum: string;
|
|
1371
|
+
}
|
|
1372
|
+
interface PluginVersionYankedPayload {
|
|
1373
|
+
pluginId: string;
|
|
1374
|
+
pluginKey: string;
|
|
1375
|
+
version: string;
|
|
1376
|
+
reason?: string;
|
|
1377
|
+
}
|
|
1378
|
+
interface PluginUpgradedPayload {
|
|
1379
|
+
pluginId: string;
|
|
1380
|
+
pluginKey: string;
|
|
1381
|
+
fromVersion: string;
|
|
1382
|
+
toVersion: string;
|
|
1383
|
+
/** Migration step ids that ran as part of the upgrade (may be empty). */
|
|
1384
|
+
migrationsRun: string[];
|
|
1385
|
+
}
|
|
1386
|
+
interface PluginTaskStartedPayload {
|
|
1387
|
+
pluginId: string;
|
|
1388
|
+
pluginKey: string;
|
|
1389
|
+
taskId: string;
|
|
1390
|
+
/** Task type declared by the plugin, e.g. `seed-games`. */
|
|
1391
|
+
type: string;
|
|
1392
|
+
}
|
|
1393
|
+
interface PluginTaskCompletedPayload {
|
|
1394
|
+
pluginId: string;
|
|
1395
|
+
pluginKey: string;
|
|
1396
|
+
taskId: string;
|
|
1397
|
+
type: string;
|
|
1398
|
+
durationMs: number;
|
|
1399
|
+
}
|
|
1400
|
+
interface PluginTaskFailedPayload {
|
|
1401
|
+
pluginId: string;
|
|
1402
|
+
pluginKey: string;
|
|
1403
|
+
taskId: string;
|
|
1404
|
+
type: string;
|
|
1405
|
+
/** Redacted failure summary — never a stack trace or secret. */
|
|
1406
|
+
error: string;
|
|
1407
|
+
attempts: number;
|
|
1408
|
+
}
|
|
1409
|
+
interface PluginJobFailedPayload {
|
|
1410
|
+
pluginId: string;
|
|
1411
|
+
pluginKey: string;
|
|
1412
|
+
job: string;
|
|
1413
|
+
/** Emitted only when the failure-streak policy trips (3 consecutive). */
|
|
1414
|
+
consecutiveFailures: number;
|
|
1415
|
+
error: string;
|
|
1416
|
+
}
|
|
1417
|
+
interface PluginDataPurgedPayload {
|
|
1418
|
+
pluginId: string;
|
|
1419
|
+
pluginKey: string;
|
|
1420
|
+
dataset: string;
|
|
1421
|
+
/** Archived rows hard-deleted by the purge command. */
|
|
1422
|
+
purgedCount: number;
|
|
1423
|
+
}
|
|
1424
|
+
interface ProviderEnabledPayload {
|
|
1425
|
+
providerKey: string;
|
|
1426
|
+
/** Present when the provider was contributed by a plugin. */
|
|
1427
|
+
pluginKey?: string;
|
|
1428
|
+
pluginVersion?: string;
|
|
1429
|
+
}
|
|
1430
|
+
interface ProviderDisabledPayload {
|
|
1431
|
+
providerKey: string;
|
|
1432
|
+
pluginKey?: string;
|
|
1433
|
+
reason?: string;
|
|
1434
|
+
}
|
|
1435
|
+
interface BackofficeViewSavedPayload {
|
|
1436
|
+
viewId: string;
|
|
1437
|
+
resource: string;
|
|
1438
|
+
scope: "personal" | "shared";
|
|
1439
|
+
actorStaffId: string;
|
|
1440
|
+
}
|
|
1441
|
+
interface BackofficeDashboardSavedPayload {
|
|
1442
|
+
dashboardId: string;
|
|
1443
|
+
scope: "personal" | "shared";
|
|
1444
|
+
actorStaffId: string;
|
|
1445
|
+
}
|
|
1446
|
+
interface BackofficeExportCompletedPayload {
|
|
1447
|
+
exportId: string;
|
|
1448
|
+
resource: string;
|
|
1449
|
+
rowCount: number;
|
|
1450
|
+
format: string;
|
|
1451
|
+
actorStaffId: string;
|
|
1452
|
+
}
|
|
1453
|
+
/** PII_REVEALED: field KEYS only, never the revealed values. */
|
|
1454
|
+
interface BackofficePiiRevealedPayload {
|
|
1455
|
+
subjectType: string;
|
|
1456
|
+
subjectId: string;
|
|
1457
|
+
fields: string[];
|
|
1458
|
+
actorStaffId: string;
|
|
1459
|
+
reason?: string;
|
|
1460
|
+
}
|
|
1461
|
+
interface BackofficeTranslationsUpdatedPayload {
|
|
1462
|
+
locale: string;
|
|
1463
|
+
namespace: string;
|
|
1464
|
+
keyCount: number;
|
|
1465
|
+
actorStaffId: string;
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* TASK-005: staff reassigned a player's CRM owner. Carries staff user ids
|
|
1469
|
+
* (either may be null for unassign) + reason. No player PII, no email/phone,
|
|
1470
|
+
* no owner names — consumers resolve names via the staff_users label endpoint.
|
|
1471
|
+
*/
|
|
1472
|
+
interface BackofficeOwnerAssignedPayload {
|
|
1473
|
+
playerId: string;
|
|
1474
|
+
fromOwnerActorId: string | null;
|
|
1475
|
+
toOwnerActorId: string | null;
|
|
1476
|
+
reason: string | null;
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* TASK-007: staff logged a typed CRM activity on any entity record. Carries
|
|
1480
|
+
* ids + kind + owner + dueAt/completedAt only — NO body text (PII exfiltration
|
|
1481
|
+
* risk), NO authorActorId (retrievable from the audit row).
|
|
1482
|
+
*/
|
|
1483
|
+
interface BackofficeActivityCreatedPayload {
|
|
1484
|
+
activityId: string;
|
|
1485
|
+
entityKey: string;
|
|
1486
|
+
entityId: string;
|
|
1487
|
+
kind: string;
|
|
1488
|
+
ownerActorId: string | null;
|
|
1489
|
+
/** ISO-8601 or null (unscheduled). */
|
|
1490
|
+
dueAt: string | null;
|
|
1491
|
+
/** ISO-8601 or null (always null on create — set by the complete path). */
|
|
1492
|
+
completedAt: string | null;
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* TASK-007: staff marked an activity complete. `completedAt` is always non-null.
|
|
1496
|
+
* Same PII-exclusion rules as `BackofficeActivityCreatedPayload`.
|
|
1497
|
+
*/
|
|
1498
|
+
interface BackofficeActivityCompletedPayload {
|
|
1499
|
+
activityId: string;
|
|
1500
|
+
entityKey: string;
|
|
1501
|
+
entityId: string;
|
|
1502
|
+
kind: string;
|
|
1503
|
+
ownerActorId: string | null;
|
|
1504
|
+
/** ISO-8601, always non-null. */
|
|
1505
|
+
completedAt: string;
|
|
1506
|
+
}
|
|
1507
|
+
/**
|
|
1508
|
+
* The master map: event name → payload type. This is what makes the whole
|
|
1509
|
+
* package type-safe end to end — builders, the outbox, and consumers all key
|
|
1510
|
+
* off this single source of truth.
|
|
1511
|
+
*/
|
|
1512
|
+
/** A rate sync run published new/updated rates — cache-bust signal (§4.3). */
|
|
1513
|
+
interface SystemFxRatesUpdatedPayload {
|
|
1514
|
+
source: string;
|
|
1515
|
+
kind: "fiat" | "crypto";
|
|
1516
|
+
/** The as-of day the synced rates apply to (`YYYY-MM-DD`). */
|
|
1517
|
+
asOfDate: string;
|
|
1518
|
+
ratesUpserted: number;
|
|
1519
|
+
}
|
|
1520
|
+
/** A tenant setting changed (e.g. reporting currency) — refresh read caches. */
|
|
1521
|
+
interface SystemTenantSettingsUpdatedPayload {
|
|
1522
|
+
key: string;
|
|
1523
|
+
updatedBy?: string;
|
|
1524
|
+
}
|
|
1525
|
+
interface DomainEventPayloads {
|
|
1526
|
+
[WalletEvents.CREDITED]: WalletCreditedPayload;
|
|
1527
|
+
[WalletEvents.DEBITED]: WalletDebitedPayload;
|
|
1528
|
+
[WalletEvents.TRANSFERRED]: WalletTransferredPayload;
|
|
1529
|
+
[WalletEvents.DEPOSIT_COMPLETED]: WalletDepositCompletedPayload;
|
|
1530
|
+
[WalletEvents.WITHDRAWAL_REQUESTED]: WalletWithdrawalRequestedPayload;
|
|
1531
|
+
[WalletEvents.WITHDRAWAL_APPROVED]: WalletWithdrawalApprovedPayload;
|
|
1532
|
+
[WalletEvents.WITHDRAWAL_REJECTED]: WalletWithdrawalRejectedPayload;
|
|
1533
|
+
[WalletEvents.TRANSACTION_REVERSED]: WalletTransactionReversedPayload;
|
|
1534
|
+
[WalletEvents.WALLET_FROZEN]: WalletFrozenPayload;
|
|
1535
|
+
[WalletEvents.WALLET_UNFROZEN]: WalletUnfrozenPayload;
|
|
1536
|
+
[PlayerEvents.CREATED]: PlayerCreatedPayload;
|
|
1537
|
+
[PlayerEvents.LOGGED_IN]: PlayerLoggedInPayload;
|
|
1538
|
+
[PlayerEvents.LOGGED_OUT]: PlayerLoggedOutPayload;
|
|
1539
|
+
[PlayerEvents.SESSION_REFRESHED]: PlayerSessionRefreshedPayload;
|
|
1540
|
+
[PlayerEvents.SOCIAL_LINKED]: PlayerSocialLinkedPayload;
|
|
1541
|
+
[PlayerEvents.SOCIAL_UNLINKED]: PlayerSocialUnlinkedPayload;
|
|
1542
|
+
[PlayerEvents.PASSWORD_CHANGED]: PlayerPasswordChangedPayload;
|
|
1543
|
+
[PlayerEvents.LOGIN_FAILED]: PlayerLoginFailedPayload;
|
|
1544
|
+
[PlayerEvents.ACCOUNT_LOCKED]: PlayerAccountLockedPayload;
|
|
1545
|
+
[PlayerEvents.UPDATED]: PlayerUpdatedPayload;
|
|
1546
|
+
[PlayerEvents.VERIFIED]: PlayerVerifiedPayload;
|
|
1547
|
+
[PlayerEvents.CLOSED]: PlayerClosedPayload;
|
|
1548
|
+
[PlayerEvents.SUSPENDED]: PlayerSuspendedPayload;
|
|
1549
|
+
[PlayerEvents.REACTIVATED]: PlayerReactivatedPayload;
|
|
1550
|
+
[PlayerEvents.EMAIL_VERIFIED]: PlayerEmailVerifiedPayload;
|
|
1551
|
+
[PlayerEvents.PHONE_VERIFIED]: PlayerPhoneVerifiedPayload;
|
|
1552
|
+
[PlayerEvents.SESSION_REVOKED]: PlayerSessionRevokedPayload;
|
|
1553
|
+
[PlayerEvents.PREFERENCES_UPDATED]: PlayerPreferencesUpdatedPayload;
|
|
1554
|
+
[PlayerEvents.LIMIT_CHANGED]: PlayerLimitChangedPayload;
|
|
1555
|
+
[PlayerEvents.COOL_OFF_STARTED]: PlayerCoolOffStartedPayload;
|
|
1556
|
+
[PlayerEvents.SELF_EXCLUDED]: PlayerSelfExcludedPayload;
|
|
1557
|
+
[PlayerEvents.REALITY_CHECK]: PlayerRealityCheckPayload;
|
|
1558
|
+
[PlayerEvents.ONLINE]: PlayerOnlinePayload;
|
|
1559
|
+
[PlayerEvents.OFFLINE]: PlayerOfflinePayload;
|
|
1560
|
+
[KycEvents.CONFIG_UPDATED]: KycConfigUpdatedPayload;
|
|
1561
|
+
[KycEvents.REQUEST_CREATED]: KycRequestCreatedPayload;
|
|
1562
|
+
[KycEvents.DOCUMENT_UPLOADED]: KycDocumentUploadedPayload;
|
|
1563
|
+
[KycEvents.DOCUMENT_APPROVED]: KycDocumentApprovedPayload;
|
|
1564
|
+
[KycEvents.DOCUMENT_REJECTED]: KycDocumentRejectedPayload;
|
|
1565
|
+
[KycEvents.REQUEST_SUBMITTED]: KycRequestSubmittedPayload;
|
|
1566
|
+
[KycEvents.REQUEST_APPROVED]: KycRequestApprovedPayload;
|
|
1567
|
+
[KycEvents.REQUEST_REJECTED]: KycRequestRejectedPayload;
|
|
1568
|
+
[KycEvents.REQUEST_NEEDS_MORE]: KycRequestNeedsMorePayload;
|
|
1569
|
+
[KycEvents.REQUEST_ESCALATED]: KycRequestEscalatedPayload;
|
|
1570
|
+
[BonusEvents.GRANTED]: BonusGrantedPayload;
|
|
1571
|
+
[BonusEvents.REVOKED]: BonusRevokedPayload;
|
|
1572
|
+
[BonusEvents.OFFERED]: BonusOfferedPayload;
|
|
1573
|
+
[BonusEvents.CLAIMED]: BonusClaimedPayload;
|
|
1574
|
+
[BonusEvents.ACTIVATED]: BonusActivatedPayload;
|
|
1575
|
+
[BonusEvents.WAGERING_PROGRESSED]: BonusWageringProgressedPayload;
|
|
1576
|
+
[BonusEvents.WAGERING_COMPLETED]: BonusWageringCompletedPayload;
|
|
1577
|
+
[BonusEvents.CONVERTED]: BonusConvertedPayload;
|
|
1578
|
+
[BonusEvents.EXPIRED]: BonusExpiredPayload;
|
|
1579
|
+
[BonusEvents.FORFEITED]: BonusForfeitedPayload;
|
|
1580
|
+
[BonusEvents.VOIDED]: BonusVoidedPayload;
|
|
1581
|
+
[BonusEvents.GRANT_QUEUED]: BonusGrantQueuedPayload;
|
|
1582
|
+
[BonusEvents.GRANT_REJECTED]: BonusGrantRejectedPayload;
|
|
1583
|
+
[BonusEvents.CONSTRAINT_BREACHED]: BonusConstraintBreachedPayload;
|
|
1584
|
+
[BetEvents.PLACED]: BetPlacedPayload;
|
|
1585
|
+
[BetEvents.SETTLED]: BetSettledPayload;
|
|
1586
|
+
[AffiliateEvents.CLICK_RECORDED]: AffiliateClickRecordedPayload;
|
|
1587
|
+
[AffiliateEvents.REGISTRATION_ATTRIBUTED]: AffiliateRegistrationAttributedPayload;
|
|
1588
|
+
[AffiliateEvents.ASSIGNED]: AffiliateAssignedPayload;
|
|
1589
|
+
[AffiliateEvents.COMMISSION_CREATED]: AffiliateCommissionCreatedPayload;
|
|
1590
|
+
[AffiliateEvents.COMMISSION_SETTLED]: AffiliateCommissionSettledPayload;
|
|
1591
|
+
[ClassificationEvents.PLAYER_TAG_ASSIGNED]: PlayerTagAssignedPayload;
|
|
1592
|
+
[ClassificationEvents.PLAYER_TAG_REMOVED]: PlayerTagRemovedPayload;
|
|
1593
|
+
[ClassificationEvents.SEGMENT_MEMBERSHIP_CHANGED]: SegmentMembershipChangedPayload;
|
|
1594
|
+
[ClassificationEvents.AUDIENCE_EXPORTED]: AudienceExportedPayload;
|
|
1595
|
+
[ClassificationEvents.CONVERSION_FEEDBACK_SENT]: ConversionFeedbackSentPayload;
|
|
1596
|
+
[CatalogEvents.SOURCE_REGISTERED]: CatalogSourceRegisteredPayload;
|
|
1597
|
+
[CatalogEvents.SOURCE_IMPORTED]: CatalogSourceImportedPayload;
|
|
1598
|
+
[CatalogEvents.PROVIDER_UPSERTED]: CatalogProviderUpsertedPayload;
|
|
1599
|
+
[CatalogEvents.CATEGORY_UPSERTED]: CatalogCategoryUpsertedPayload;
|
|
1600
|
+
[CatalogEvents.GAME_CREATED]: CatalogGameCreatedPayload;
|
|
1601
|
+
[CatalogEvents.GAME_UPDATED]: CatalogGameUpdatedPayload;
|
|
1602
|
+
[CatalogEvents.GAME_RETIRED]: CatalogGameRetiredPayload;
|
|
1603
|
+
[CatalogEvents.TENANT_PROVIDER_TOGGLED]: CatalogTenantProviderToggledPayload;
|
|
1604
|
+
[CatalogEvents.TENANT_GAME_OVERLAID]: CatalogTenantGameOverlaidPayload;
|
|
1605
|
+
[CatalogEvents.GAMES_BULK_UPDATED]: CatalogGamesBulkUpdatedPayload;
|
|
1606
|
+
[CatalogEvents.CACHE_INVALIDATED]: CatalogCacheInvalidatedPayload;
|
|
1607
|
+
[CashierEvents.DEPOSIT_INITIATED]: CashierDepositInitiatedPayload;
|
|
1608
|
+
[CashierEvents.DEPOSIT_COMPLETED]: CashierDepositCompletedPayload;
|
|
1609
|
+
[CashierEvents.DEPOSIT_FAILED]: CashierDepositFailedPayload;
|
|
1610
|
+
[CashierEvents.WITHDRAWAL_REQUESTED]: CashierWithdrawalRequestedPayload;
|
|
1611
|
+
[CashierEvents.WITHDRAWAL_APPROVED]: CashierWithdrawalApprovedPayload;
|
|
1612
|
+
[CashierEvents.WITHDRAWAL_REJECTED]: CashierWithdrawalRejectedPayload;
|
|
1613
|
+
[CashierEvents.WITHDRAWAL_PAID]: CashierWithdrawalPaidPayload;
|
|
1614
|
+
[CashierEvents.WITHDRAWAL_FAILED]: CashierWithdrawalFailedPayload;
|
|
1615
|
+
[CashierEvents.WITHDRAWAL_CANCELLED]: CashierWithdrawalCancelledPayload;
|
|
1616
|
+
[CashierEvents.PAYMENT_FEE_RECORDED]: CashierPaymentFeeRecordedPayload;
|
|
1617
|
+
[CashierEvents.CHARGEBACK_RECORDED]: CashierChargebackRecordedPayload;
|
|
1618
|
+
[CashierEvents.PAYMENT_INSTRUMENT_ADDED]: CashierPaymentInstrumentAddedPayload;
|
|
1619
|
+
[PluginEvents.INSTALLED]: PluginInstalledPayload;
|
|
1620
|
+
[PluginEvents.ENABLED]: PluginEnabledPayload;
|
|
1621
|
+
[PluginEvents.DISABLED]: PluginDisabledPayload;
|
|
1622
|
+
[PluginEvents.UNINSTALLED]: PluginUninstalledPayload;
|
|
1623
|
+
[PluginEvents.CONFIGURED]: PluginConfiguredPayload;
|
|
1624
|
+
[PluginEvents.PUBLISHED]: PluginPublishedPayload;
|
|
1625
|
+
[PluginEvents.VERSION_YANKED]: PluginVersionYankedPayload;
|
|
1626
|
+
[PluginEvents.UPGRADED]: PluginUpgradedPayload;
|
|
1627
|
+
[PluginEvents.TASK_STARTED]: PluginTaskStartedPayload;
|
|
1628
|
+
[PluginEvents.TASK_COMPLETED]: PluginTaskCompletedPayload;
|
|
1629
|
+
[PluginEvents.TASK_FAILED]: PluginTaskFailedPayload;
|
|
1630
|
+
[PluginEvents.JOB_FAILED]: PluginJobFailedPayload;
|
|
1631
|
+
[PluginEvents.DATA_PURGED]: PluginDataPurgedPayload;
|
|
1632
|
+
[ProviderEvents.ENABLED]: ProviderEnabledPayload;
|
|
1633
|
+
[ProviderEvents.DISABLED]: ProviderDisabledPayload;
|
|
1634
|
+
[TournamentEvents.STARTED]: TournamentStartedPayload;
|
|
1635
|
+
[TournamentEvents.ENDED]: TournamentEndedPayload;
|
|
1636
|
+
[TournamentEvents.PRIZE_AWARDED]: TournamentPrizeAwardedPayload;
|
|
1637
|
+
[GamificationEvents.ACHIEVEMENT_UNLOCKED]: AchievementUnlockedPayload;
|
|
1638
|
+
[GamificationEvents.MISSION_COMPLETED]: MissionCompletedPayload;
|
|
1639
|
+
[GamificationEvents.LEVEL_UP]: LevelUpPayload;
|
|
1640
|
+
[LoyaltyEvents.POINTS_EARNED]: LoyaltyPointsEarnedPayload;
|
|
1641
|
+
[LoyaltyEvents.POINTS_REDEEMED]: LoyaltyPointsRedeemedPayload;
|
|
1642
|
+
[BackofficeEvents.VIEW_SAVED]: BackofficeViewSavedPayload;
|
|
1643
|
+
[BackofficeEvents.DASHBOARD_SAVED]: BackofficeDashboardSavedPayload;
|
|
1644
|
+
[BackofficeEvents.EXPORT_COMPLETED]: BackofficeExportCompletedPayload;
|
|
1645
|
+
[BackofficeEvents.PII_REVEALED]: BackofficePiiRevealedPayload;
|
|
1646
|
+
[BackofficeEvents.TRANSLATIONS_UPDATED]: BackofficeTranslationsUpdatedPayload;
|
|
1647
|
+
[BackofficeEvents.OWNER_ASSIGNED]: BackofficeOwnerAssignedPayload;
|
|
1648
|
+
[BackofficeEvents.ACTIVITY_CREATED]: BackofficeActivityCreatedPayload;
|
|
1649
|
+
[BackofficeEvents.ACTIVITY_COMPLETED]: BackofficeActivityCompletedPayload;
|
|
1650
|
+
[SystemEvents.FX_RATES_UPDATED]: SystemFxRatesUpdatedPayload;
|
|
1651
|
+
[SystemEvents.TENANT_SETTINGS_UPDATED]: SystemTenantSettingsUpdatedPayload;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
/**
|
|
1655
|
+
* Cross-cutting domain types shared by every module.
|
|
1656
|
+
*/
|
|
1657
|
+
/** Tenant context attached to every request. */
|
|
1658
|
+
interface TenantContext$1 {
|
|
1659
|
+
tenantId: string;
|
|
1660
|
+
brandId: string;
|
|
1661
|
+
region: string;
|
|
1662
|
+
}
|
|
1663
|
+
/** Which realm a request's caller belongs to. */
|
|
1664
|
+
type PrincipalType = "player" | "staff" | "system" | "provider" | "plugin" | "anonymous";
|
|
1665
|
+
/**
|
|
1666
|
+
* The authenticated caller of a request, resolved fail-closed by the
|
|
1667
|
+
* auth-context plugin and attached to `request.user`. A player token can never
|
|
1668
|
+
* yield a `staff` principal and vice-versa (separate realms, separate cookies).
|
|
1669
|
+
*
|
|
1670
|
+
* `permissions` is the resolved staff permission set (empty for non-staff);
|
|
1671
|
+
* `roles` is informational. `anonymous` means no/invalid credentials — it can
|
|
1672
|
+
* only satisfy `public` routes.
|
|
1673
|
+
*/
|
|
1674
|
+
interface Principal {
|
|
1675
|
+
type: PrincipalType;
|
|
1676
|
+
id: string | null;
|
|
1677
|
+
tenantId: string | null;
|
|
1678
|
+
roles: string[];
|
|
1679
|
+
permissions: string[];
|
|
1680
|
+
sessionId?: string | null;
|
|
1681
|
+
/** Raw token the principal authenticated with, if any. */
|
|
1682
|
+
token?: string | null;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/**
|
|
1686
|
+
* Fixed-window rate limiter seam. Backed by Redis in the apps so every API
|
|
1687
|
+
* instance shares the window; the in-memory variant is for tests/dev. Used by
|
|
1688
|
+
* token-issuing auth endpoints (password reset, OTPs) and upload endpoints.
|
|
1689
|
+
* Rate-limit state is ephemeral by definition — Redis-appropriate.
|
|
1690
|
+
*/
|
|
1691
|
+
interface RateLimiter {
|
|
1692
|
+
/** True when the caller identified by `key` is within `max` per `windowSeconds`. */
|
|
1693
|
+
allow(key: string, windowSeconds: number, max: number): Promise<boolean>;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
/**
|
|
1697
|
+
* Messaging delivery seam (PLAYER_ACCOUNT_BUILD_PROMPT.md §2.4): email + SMS
|
|
1698
|
+
* for verification tokens, OTPs, password resets and KYC notifications.
|
|
1699
|
+
*
|
|
1700
|
+
* Core modules depend only on this contract. The default binding is the
|
|
1701
|
+
* FakeMessagingAdapter below (dev/test — captures sends so the verify suites
|
|
1702
|
+
* can assert on delivered tokens); real providers ship as messaging-kind
|
|
1703
|
+
* plugins whose adapters the plugin host bridges into this seam.
|
|
1704
|
+
*
|
|
1705
|
+
* Templates are KEYS (tenant-configurable content lives in tenant settings);
|
|
1706
|
+
* variables are template-validated at the send boundary. Raw secrets (tokens,
|
|
1707
|
+
* OTPs) ride in `variables` — adapters must never log them.
|
|
1708
|
+
*/
|
|
1709
|
+
interface MessagingSendInput {
|
|
1710
|
+
channel: "email" | "sms";
|
|
1711
|
+
to: string;
|
|
1712
|
+
/** Template key, e.g. `auth.password_reset`, `auth.phone_otp`. */
|
|
1713
|
+
template: string;
|
|
1714
|
+
variables: Record<string, string>;
|
|
1715
|
+
}
|
|
1716
|
+
interface MessagingSendResult {
|
|
1717
|
+
/** Provider-side message id, when the provider returns one. */
|
|
1718
|
+
externalRef?: string;
|
|
1719
|
+
}
|
|
1720
|
+
interface MessagingAdapter$1 {
|
|
1721
|
+
/** e.g. `messaging:fake`, `messaging:twilio-like`. */
|
|
1722
|
+
readonly providerKey: string;
|
|
1723
|
+
send(tenant: TenantContext$1, message: MessagingSendInput): Promise<MessagingSendResult>;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
/**
|
|
1727
|
+
* Player presence seam (docs/PROPOSED_DECISIONS.md §10).
|
|
1728
|
+
*
|
|
1729
|
+
* The implementation lives in `@cwe/players` (`createPresenceService`) —
|
|
1730
|
+
* Redis-backed, TTL-driven, transition events through the outbox. This
|
|
1731
|
+
* interface exists so `@cwe/auth` (login/logout/revocation paths) and the
|
|
1732
|
+
* API auth-context hook can call presence without a cross-domain import;
|
|
1733
|
+
* the composition roots in `apps/*` wire the concrete service in.
|
|
1734
|
+
*
|
|
1735
|
+
* Both methods are best-effort from the caller's point of view: presence is
|
|
1736
|
+
* ephemeral state and must never fail or slow an auth flow. Callers swallow
|
|
1737
|
+
* errors (`.catch(() => undefined)`); the implementation degrades gracefully
|
|
1738
|
+
* when Redis blips.
|
|
1739
|
+
*/
|
|
1740
|
+
interface PlayerPresence {
|
|
1741
|
+
/**
|
|
1742
|
+
* Record authenticated player activity "now": refreshes the Redis presence
|
|
1743
|
+
* entry, throttles a durable `players.lastActivityAt` write, and emits
|
|
1744
|
+
* `player.online` through the outbox on an offline→online transition.
|
|
1745
|
+
*/
|
|
1746
|
+
refresh(tenant: TenantContext$1, playerId: string): Promise<void>;
|
|
1747
|
+
/**
|
|
1748
|
+
* Immediately mark the player offline (logout / full session revocation):
|
|
1749
|
+
* clears the Redis presence entry and emits `player.offline` through the
|
|
1750
|
+
* outbox if the player was online. No-op when already offline.
|
|
1751
|
+
*/
|
|
1752
|
+
clear(tenant: TenantContext$1, playerId: string, reason?: "logged_out" | "sessions_revoked"): Promise<void>;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
declare module "fastify" {
|
|
1756
|
+
interface FastifyRequest {
|
|
1757
|
+
tenant: TenantContext$1;
|
|
1758
|
+
user: Principal;
|
|
1759
|
+
}
|
|
1760
|
+
interface FastifyInstance {
|
|
1761
|
+
/**
|
|
1762
|
+
* Shared fixed-window rate limiter (Redis-backed in the apps). Optional:
|
|
1763
|
+
* modules fall back to an in-memory limiter when the app doesn't wire one.
|
|
1764
|
+
*/
|
|
1765
|
+
rateLimiter?: RateLimiter;
|
|
1766
|
+
/**
|
|
1767
|
+
* Messaging delivery seam (email/SMS). Optional: modules fall back to the
|
|
1768
|
+
* FakeMessagingAdapter (dev/test) when the app doesn't wire one.
|
|
1769
|
+
*/
|
|
1770
|
+
messaging?: MessagingAdapter$1;
|
|
1771
|
+
/**
|
|
1772
|
+
* Player presence seam (docs/PROPOSED_DECISIONS.md §10), wired by the API
|
|
1773
|
+
* composition root from `@cwe/players`. Optional: without it, presence
|
|
1774
|
+
* refresh/clear are silently skipped (players simply never read online).
|
|
1775
|
+
*/
|
|
1776
|
+
presence?: PlayerPresence;
|
|
1777
|
+
/**
|
|
1778
|
+
* Flows framework seam (PLAYER_ACCOUNT_BUILD_PROMPT.md §2), wired by the
|
|
1779
|
+
* composition root from the plugin host. Optional: without it, flows run
|
|
1780
|
+
* core-only (no plugin interception, no delegation).
|
|
1781
|
+
*/
|
|
1782
|
+
flows?: {
|
|
1783
|
+
/** Run every intercept hook for `stage`; throws FlowRejectedError on reject. */
|
|
1784
|
+
runIntercept<I, O>(tenant: TenantContext$1, stage: string, input: I): Promise<O[]>;
|
|
1785
|
+
/** Owning plugin key when `flows.<flow>.mode = plugin:<key>`, else null. */
|
|
1786
|
+
isDelegated(tenant: TenantContext$1, flow: string): Promise<string | null>;
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
/**
|
|
1792
|
+
* The canonical envelope every domain event is wrapped in. This is exactly what
|
|
1793
|
+
* gets stored in `event_outbox.payload` and published to NATS, so it IS the
|
|
1794
|
+
* external contract.
|
|
1795
|
+
*
|
|
1796
|
+
* @typeParam N - the event name (keys the payload type).
|
|
1797
|
+
*/
|
|
1798
|
+
interface DomainEvent<N extends DomainEventName = DomainEventName> {
|
|
1799
|
+
/** Unique id for this occurrence — used for idempotent consumers. */
|
|
1800
|
+
eventId: string;
|
|
1801
|
+
/** Event name, e.g. `wallet.credited`. */
|
|
1802
|
+
name: N;
|
|
1803
|
+
/** Payload schema version. */
|
|
1804
|
+
version: number;
|
|
1805
|
+
/** ISO-8601 timestamp of when the event occurred. */
|
|
1806
|
+
occurredAt: string;
|
|
1807
|
+
/** Tenant the event belongs to. */
|
|
1808
|
+
tenant: TenantContext$1;
|
|
1809
|
+
/** Strongly-typed, name-specific payload. */
|
|
1810
|
+
payload: DomainEventPayloads[N];
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
/**
|
|
1814
|
+
* Scoped data reads (`ctx.data`) — the contract half of the host's
|
|
1815
|
+
* ReadModelRegistry. Plugins never query tables: they call named, versioned
|
|
1816
|
+
* read models, and may only call a model whose required scope is declared in
|
|
1817
|
+
* `manifest.permissions.dataScopes`. Results are DTOs, never entities —
|
|
1818
|
+
* `passwordHash`/`refreshTokenHash`/secrets are structurally absent from
|
|
1819
|
+
* these shapes.
|
|
1820
|
+
*/
|
|
1821
|
+
/** Read-model scopes. Granted per manifest; enforced fail-closed per call. */
|
|
1822
|
+
type DataScope = "players:read" | "players:pii" | "wallet:read" | "ledger:read" | "bets:read" | "bonus:read" | "affiliate:read" | "tenant:read" | "kyc:read" | "documents:read" | "limits:read" | "catalog:read";
|
|
1823
|
+
declare const DataScopes: readonly DataScope[];
|
|
1824
|
+
/** Cursor-paginated page shape shared by list read models. Max page 200. */
|
|
1825
|
+
interface ReadModelPage<T> {
|
|
1826
|
+
items: T[];
|
|
1827
|
+
nextCursor?: string;
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1830
|
+
* Player DTO. PII fields (`email`, `username`, `phone`) are populated only
|
|
1831
|
+
* when the plugin holds `players:pii`; with bare `players:read` they are
|
|
1832
|
+
* absent from the result.
|
|
1833
|
+
*/
|
|
1834
|
+
interface PlayerSummaryDTO {
|
|
1835
|
+
id: string;
|
|
1836
|
+
brandId: string;
|
|
1837
|
+
status: string;
|
|
1838
|
+
createdAt: string;
|
|
1839
|
+
email?: string | null;
|
|
1840
|
+
username?: string | null;
|
|
1841
|
+
phone?: string | null;
|
|
1842
|
+
}
|
|
1843
|
+
/** Wallet projection snapshot — buckets, never a mutation surface. */
|
|
1844
|
+
interface WalletBalanceDTO {
|
|
1845
|
+
walletId: string;
|
|
1846
|
+
playerId: string;
|
|
1847
|
+
currency: string;
|
|
1848
|
+
cash: string;
|
|
1849
|
+
bonus: string;
|
|
1850
|
+
locked: string;
|
|
1851
|
+
status: string;
|
|
1852
|
+
}
|
|
1853
|
+
/** One immutable `wallet_ledger` leg. */
|
|
1854
|
+
interface LedgerEntryDTO {
|
|
1855
|
+
id: string;
|
|
1856
|
+
playerId: string;
|
|
1857
|
+
walletId: string;
|
|
1858
|
+
transactionId: string;
|
|
1859
|
+
walletType: string;
|
|
1860
|
+
direction: "credit" | "debit";
|
|
1861
|
+
amount: string;
|
|
1862
|
+
currency: string;
|
|
1863
|
+
balanceBefore: string;
|
|
1864
|
+
balanceAfter: string;
|
|
1865
|
+
createdAt: string;
|
|
1866
|
+
}
|
|
1867
|
+
/** A bet-flow wallet transaction (type bet | settle | rollback). */
|
|
1868
|
+
interface BetRecordDTO {
|
|
1869
|
+
transactionId: string;
|
|
1870
|
+
playerId: string;
|
|
1871
|
+
walletId: string;
|
|
1872
|
+
type: string;
|
|
1873
|
+
amount: string;
|
|
1874
|
+
currency: string;
|
|
1875
|
+
source: string;
|
|
1876
|
+
externalTransactionId: string | null;
|
|
1877
|
+
gameId: string | null;
|
|
1878
|
+
status: string;
|
|
1879
|
+
createdAt: string;
|
|
1880
|
+
}
|
|
1881
|
+
interface TenantInfoDTO {
|
|
1882
|
+
tenantId: string;
|
|
1883
|
+
brandId: string;
|
|
1884
|
+
region: string;
|
|
1885
|
+
/** Currencies with at least one wallet in this tenant. */
|
|
1886
|
+
currencies: string[];
|
|
1887
|
+
}
|
|
1888
|
+
/** player_kyc_state projection — level/status only, never document content. */
|
|
1889
|
+
interface KycStateDTO {
|
|
1890
|
+
playerId: string;
|
|
1891
|
+
level: number;
|
|
1892
|
+
status: string;
|
|
1893
|
+
approvedAt: string | null;
|
|
1894
|
+
}
|
|
1895
|
+
interface KycRequestDTO {
|
|
1896
|
+
id: string;
|
|
1897
|
+
playerId: string;
|
|
1898
|
+
trigger: string;
|
|
1899
|
+
status: string;
|
|
1900
|
+
targetLevel: number | null;
|
|
1901
|
+
requiredDocumentTypes: string[];
|
|
1902
|
+
createdAt: string;
|
|
1903
|
+
}
|
|
1904
|
+
/** Document METADATA — storageRef/bytes/signed URLs are structurally absent. */
|
|
1905
|
+
interface KycDocumentDTO {
|
|
1906
|
+
id: string;
|
|
1907
|
+
playerId: string;
|
|
1908
|
+
kycRequestId: string | null;
|
|
1909
|
+
documentTypeKey: string;
|
|
1910
|
+
status: string;
|
|
1911
|
+
mimeType: string;
|
|
1912
|
+
sizeBytes: number;
|
|
1913
|
+
uploadedAt: string;
|
|
1914
|
+
}
|
|
1915
|
+
/** A player-history bet row (the `bets` projection, not wallet transactions). */
|
|
1916
|
+
interface PlayerBetDTO {
|
|
1917
|
+
betId: string;
|
|
1918
|
+
playerId: string;
|
|
1919
|
+
roundId: string;
|
|
1920
|
+
providerKey: string;
|
|
1921
|
+
gameId: string | null;
|
|
1922
|
+
betAmount: string;
|
|
1923
|
+
winAmount: string | null;
|
|
1924
|
+
currency: string;
|
|
1925
|
+
status: string;
|
|
1926
|
+
placedAt: string;
|
|
1927
|
+
}
|
|
1928
|
+
interface GameSessionDTO {
|
|
1929
|
+
id: string;
|
|
1930
|
+
playerId: string;
|
|
1931
|
+
providerKey: string;
|
|
1932
|
+
gameId: string | null;
|
|
1933
|
+
status: string;
|
|
1934
|
+
betCount: number;
|
|
1935
|
+
startedAt: string;
|
|
1936
|
+
lastActivityAt: string | null;
|
|
1937
|
+
}
|
|
1938
|
+
/** A responsible-gaming limit (read-only — no RG command is plugin-reachable). */
|
|
1939
|
+
interface PlayerLimitDTO {
|
|
1940
|
+
playerId: string;
|
|
1941
|
+
kind: string;
|
|
1942
|
+
period: string;
|
|
1943
|
+
value: string;
|
|
1944
|
+
currency: string | null;
|
|
1945
|
+
pendingValue: string | null;
|
|
1946
|
+
pendingActiveAt: string | null;
|
|
1947
|
+
}
|
|
1948
|
+
/** A visible game, resolved for the tenant (overlay name/featured applied). */
|
|
1949
|
+
interface CatalogGameDTO {
|
|
1950
|
+
id: string;
|
|
1951
|
+
slug: string | null;
|
|
1952
|
+
/** Locale-picked title (overlay override wins). */
|
|
1953
|
+
title: string | null;
|
|
1954
|
+
/** Full i18n name map. */
|
|
1955
|
+
name: unknown;
|
|
1956
|
+
providerId: string;
|
|
1957
|
+
launchCode: string | null;
|
|
1958
|
+
imageUrl: string | null;
|
|
1959
|
+
rtp: string | null;
|
|
1960
|
+
minBet: string | null;
|
|
1961
|
+
maxBet: string | null;
|
|
1962
|
+
featured: boolean;
|
|
1963
|
+
features: Record<string, unknown>;
|
|
1964
|
+
}
|
|
1965
|
+
/** A resolved category tree node (tenant visibility/sort/label applied). */
|
|
1966
|
+
interface CatalogCategoryNodeDTO {
|
|
1967
|
+
id: string;
|
|
1968
|
+
slug: string;
|
|
1969
|
+
title: string | null;
|
|
1970
|
+
kind: string;
|
|
1971
|
+
sort: number;
|
|
1972
|
+
children: CatalogCategoryNodeDTO[];
|
|
1973
|
+
}
|
|
1974
|
+
/** A provider visible to the tenant (enabled ∪ tenant-owned). */
|
|
1975
|
+
interface CatalogProviderDTO {
|
|
1976
|
+
id: string;
|
|
1977
|
+
externalId: string;
|
|
1978
|
+
name: string;
|
|
1979
|
+
alias: string | null;
|
|
1980
|
+
logoUrl: string | null;
|
|
1981
|
+
logoPath: string | null;
|
|
1982
|
+
weight: number;
|
|
1983
|
+
parentProviderId: string | null;
|
|
1984
|
+
}
|
|
1985
|
+
/**
|
|
1986
|
+
* Optional eligibility context accepted by the catalog read models — passed
|
|
1987
|
+
* through to the platform resolver so plugin routes automatically respect
|
|
1988
|
+
* restriction-group geo blocking and currency-group filtering. Restriction
|
|
1989
|
+
* groups only ever NARROW availability: a plugin's own groups apply on top
|
|
1990
|
+
* of tenant/platform compliance, never instead of it.
|
|
1991
|
+
*/
|
|
1992
|
+
interface CatalogEligibilityParams {
|
|
1993
|
+
/** Raw country (alpha-2/alpha-3); normalized by the resolver. */
|
|
1994
|
+
country?: string;
|
|
1995
|
+
/** ISO-4217 currency — when set, currency filtering is strict. */
|
|
1996
|
+
currency?: string;
|
|
1997
|
+
locale?: string;
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* The read-model catalog: name → { params, result }. The host implements each
|
|
2001
|
+
* entry with Zod-validated params, tenant-filtered repositories and explicit
|
|
2002
|
+
* indexes; this map is what makes `ctx.data.query` type-safe for plugin code.
|
|
2003
|
+
*/
|
|
2004
|
+
interface ReadModelDefinitions {
|
|
2005
|
+
"players.list": {
|
|
2006
|
+
params: {
|
|
2007
|
+
status?: string;
|
|
2008
|
+
cursor?: string;
|
|
2009
|
+
limit?: number;
|
|
2010
|
+
};
|
|
2011
|
+
result: ReadModelPage<PlayerSummaryDTO>;
|
|
2012
|
+
};
|
|
2013
|
+
"players.byId": {
|
|
2014
|
+
params: {
|
|
2015
|
+
playerId: string;
|
|
2016
|
+
};
|
|
2017
|
+
result: {
|
|
2018
|
+
player: PlayerSummaryDTO | null;
|
|
2019
|
+
};
|
|
2020
|
+
};
|
|
2021
|
+
"wallet.balances": {
|
|
2022
|
+
params: {
|
|
2023
|
+
playerId: string;
|
|
2024
|
+
};
|
|
2025
|
+
result: {
|
|
2026
|
+
wallets: WalletBalanceDTO[];
|
|
2027
|
+
};
|
|
2028
|
+
};
|
|
2029
|
+
"ledger.entries": {
|
|
2030
|
+
params: {
|
|
2031
|
+
playerId?: string;
|
|
2032
|
+
walletId?: string;
|
|
2033
|
+
cursor?: string;
|
|
2034
|
+
limit?: number;
|
|
2035
|
+
};
|
|
2036
|
+
result: ReadModelPage<LedgerEntryDTO>;
|
|
2037
|
+
};
|
|
2038
|
+
"bets.list": {
|
|
2039
|
+
params: {
|
|
2040
|
+
playerId?: string;
|
|
2041
|
+
gameId?: string;
|
|
2042
|
+
source?: string;
|
|
2043
|
+
cursor?: string;
|
|
2044
|
+
limit?: number;
|
|
2045
|
+
};
|
|
2046
|
+
result: ReadModelPage<BetRecordDTO>;
|
|
2047
|
+
};
|
|
2048
|
+
"bets.byRound": {
|
|
2049
|
+
params: {
|
|
2050
|
+
source: string;
|
|
2051
|
+
externalTransactionId: string;
|
|
2052
|
+
};
|
|
2053
|
+
result: {
|
|
2054
|
+
bets: BetRecordDTO[];
|
|
2055
|
+
};
|
|
2056
|
+
};
|
|
2057
|
+
"tenant.info": {
|
|
2058
|
+
params: Record<string, never>;
|
|
2059
|
+
result: TenantInfoDTO;
|
|
2060
|
+
};
|
|
2061
|
+
"kyc.state": {
|
|
2062
|
+
params: {
|
|
2063
|
+
playerId: string;
|
|
2064
|
+
};
|
|
2065
|
+
result: {
|
|
2066
|
+
state: KycStateDTO | null;
|
|
2067
|
+
};
|
|
2068
|
+
};
|
|
2069
|
+
"kyc.requests": {
|
|
2070
|
+
params: {
|
|
2071
|
+
playerId?: string;
|
|
2072
|
+
status?: string;
|
|
2073
|
+
cursor?: string;
|
|
2074
|
+
limit?: number;
|
|
2075
|
+
};
|
|
2076
|
+
result: ReadModelPage<KycRequestDTO>;
|
|
2077
|
+
};
|
|
2078
|
+
"kyc.documents": {
|
|
2079
|
+
params: {
|
|
2080
|
+
playerId?: string;
|
|
2081
|
+
kycRequestId?: string;
|
|
2082
|
+
cursor?: string;
|
|
2083
|
+
limit?: number;
|
|
2084
|
+
};
|
|
2085
|
+
result: ReadModelPage<KycDocumentDTO>;
|
|
2086
|
+
};
|
|
2087
|
+
"bets.byPlayer": {
|
|
2088
|
+
params: {
|
|
2089
|
+
playerId: string;
|
|
2090
|
+
status?: string;
|
|
2091
|
+
cursor?: string;
|
|
2092
|
+
limit?: number;
|
|
2093
|
+
};
|
|
2094
|
+
result: ReadModelPage<PlayerBetDTO>;
|
|
2095
|
+
};
|
|
2096
|
+
"gameSessions.list": {
|
|
2097
|
+
params: {
|
|
2098
|
+
playerId?: string;
|
|
2099
|
+
cursor?: string;
|
|
2100
|
+
limit?: number;
|
|
2101
|
+
};
|
|
2102
|
+
result: ReadModelPage<GameSessionDTO>;
|
|
2103
|
+
};
|
|
2104
|
+
"limits.byPlayer": {
|
|
2105
|
+
params: {
|
|
2106
|
+
playerId: string;
|
|
2107
|
+
};
|
|
2108
|
+
result: {
|
|
2109
|
+
limits: PlayerLimitDTO[];
|
|
2110
|
+
};
|
|
2111
|
+
};
|
|
2112
|
+
"catalog.games": {
|
|
2113
|
+
params: CatalogEligibilityParams & {
|
|
2114
|
+
search?: string;
|
|
2115
|
+
categorySlug?: string;
|
|
2116
|
+
providerId?: string;
|
|
2117
|
+
cursor?: string;
|
|
2118
|
+
limit?: number;
|
|
2119
|
+
};
|
|
2120
|
+
result: ReadModelPage<CatalogGameDTO>;
|
|
2121
|
+
};
|
|
2122
|
+
"catalog.categories": {
|
|
2123
|
+
params: CatalogEligibilityParams & {
|
|
2124
|
+
cursor?: string;
|
|
2125
|
+
limit?: number;
|
|
2126
|
+
};
|
|
2127
|
+
result: {
|
|
2128
|
+
categories: CatalogCategoryNodeDTO[];
|
|
2129
|
+
nextCursor?: string;
|
|
2130
|
+
};
|
|
2131
|
+
};
|
|
2132
|
+
"catalog.providers": {
|
|
2133
|
+
params: {
|
|
2134
|
+
cursor?: string;
|
|
2135
|
+
limit?: number;
|
|
2136
|
+
};
|
|
2137
|
+
result: ReadModelPage<CatalogProviderDTO>;
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
type ReadModelName = keyof ReadModelDefinitions;
|
|
2141
|
+
type ReadModelParams<Q extends ReadModelName> = ReadModelDefinitions[Q]["params"];
|
|
2142
|
+
type ReadModelResult<Q extends ReadModelName> = ReadModelDefinitions[Q]["result"];
|
|
2143
|
+
/**
|
|
2144
|
+
* Which scope each read model requires. Single source of truth for the host
|
|
2145
|
+
* enforcer AND the dev-harness doctor (declared-vs-used permission checks).
|
|
2146
|
+
* `players.*` additionally populates PII fields only under `players:pii`.
|
|
2147
|
+
*/
|
|
2148
|
+
declare const ReadModelRequiredScopes: Record<ReadModelName, DataScope>;
|
|
2149
|
+
/** Hard cap on any read-model page size (host clamps, never errors). */
|
|
2150
|
+
declare const READ_MODEL_MAX_PAGE = 200;
|
|
2151
|
+
|
|
2152
|
+
/**
|
|
2153
|
+
* Explicit allowlists — the whole permission model. The host's capability
|
|
2154
|
+
* layer enforces these at runtime: a command not in `commands` throws
|
|
2155
|
+
* `ForbiddenError` and writes a `plugin.permission_denied` audit row; an event
|
|
2156
|
+
* not in `events.subscribe` is never delivered; an emitted event must match
|
|
2157
|
+
* `events.emit` (namespaced `plugin.<key>.*`); a read model whose scope is
|
|
2158
|
+
* not in `dataScopes` is refused; a foreign dataset not in `datasets.read`
|
|
2159
|
+
* is invisible.
|
|
2160
|
+
*/
|
|
2161
|
+
interface PluginPermissions {
|
|
2162
|
+
/** Command names the plugin may execute through `ctx.commands.execute`. */
|
|
2163
|
+
commands: string[];
|
|
2164
|
+
events: {
|
|
2165
|
+
/** Typed domain events the plugin may subscribe to. */
|
|
2166
|
+
subscribe: DomainEventName[];
|
|
2167
|
+
/** Event names the plugin may emit — must be namespaced `plugin.<key>.*`. */
|
|
2168
|
+
emit: string[];
|
|
2169
|
+
};
|
|
2170
|
+
/**
|
|
2171
|
+
* Read-model scopes for `ctx.data` (v2 vocabulary, see `data.ts`). Legacy
|
|
2172
|
+
* v1 `PluginScope` values are still accepted for backward compatibility but
|
|
2173
|
+
* grant no read models.
|
|
2174
|
+
*/
|
|
2175
|
+
dataScopes?: Array<DataScope | PluginScope>;
|
|
2176
|
+
/** Foreign dataset reads: `"ownerPluginKey.datasetName"` entries. */
|
|
2177
|
+
datasets?: {
|
|
2178
|
+
read?: string[];
|
|
2179
|
+
};
|
|
2180
|
+
/**
|
|
2181
|
+
* Flow stages the plugin may hook (`"signup.validate"`, …). A `flows` entry
|
|
2182
|
+
* whose stage is not granted here fails publish; runtime enforcement fails
|
|
2183
|
+
* closed with a `plugin.permission_denied` audit like every capability.
|
|
2184
|
+
*/
|
|
2185
|
+
flows?: string[];
|
|
2186
|
+
/** Provider plugins: which provider keys this plugin may own. */
|
|
2187
|
+
providerKeys?: string[];
|
|
2188
|
+
/**
|
|
2189
|
+
* Short event types (`<key>.<type>`, i.e. the emitted name minus its
|
|
2190
|
+
* `plugin.` prefix) allowed to fan out to player sockets on the
|
|
2191
|
+
* `ext.<key>` realtime channel. Must be a subset of `events.emit`
|
|
2192
|
+
* (doctor-enforced); anything not listed is dropped by the gateway.
|
|
2193
|
+
*/
|
|
2194
|
+
frontendEvents?: string[];
|
|
2195
|
+
/**
|
|
2196
|
+
* @deprecated v1 field — use `manifest.network.allowedHosts`. The host
|
|
2197
|
+
* honours the union of both while plugins migrate.
|
|
2198
|
+
*/
|
|
2199
|
+
networkAllow?: string[];
|
|
2200
|
+
}
|
|
2201
|
+
/** Coarse capability scopes (vocabulary for `dataScopes`). */
|
|
2202
|
+
declare const PluginScopes: {
|
|
2203
|
+
readonly WalletRead: "wallet:read";
|
|
2204
|
+
readonly WalletWrite: "wallet:write";
|
|
2205
|
+
readonly PlayerRead: "player:read";
|
|
2206
|
+
readonly PlayerWrite: "player:write";
|
|
2207
|
+
readonly CatalogRead: "catalog:read";
|
|
2208
|
+
readonly BonusWrite: "bonus:write";
|
|
2209
|
+
readonly EventsSubscribe: "events:subscribe";
|
|
2210
|
+
readonly EventsPublish: "events:publish";
|
|
2211
|
+
readonly CommandsExecute: "commands:execute";
|
|
2212
|
+
};
|
|
2213
|
+
type PluginScope = (typeof PluginScopes)[keyof typeof PluginScopes];
|
|
2214
|
+
/** Prefix every plugin-emitted event name must carry: `plugin.<key>.` */
|
|
2215
|
+
declare function pluginEventPrefix(pluginKey: string): string;
|
|
2216
|
+
|
|
2217
|
+
/**
|
|
2218
|
+
* Typed per-tenant settings. Each field pairs UI metadata (label, type,
|
|
2219
|
+
* secret flag) with the Zod validator actually enforced at the boundary.
|
|
2220
|
+
* Secret fields are stored encrypted in `plugin_secrets` and are write-only
|
|
2221
|
+
* over the API; non-secret values live in `plugin_settings.values`.
|
|
2222
|
+
*/
|
|
2223
|
+
type PluginSettingsFieldType = "string" | "number" | "boolean" | "enum" | "json";
|
|
2224
|
+
interface PluginSettingsField {
|
|
2225
|
+
type: PluginSettingsFieldType;
|
|
2226
|
+
label: string;
|
|
2227
|
+
description?: string;
|
|
2228
|
+
required: boolean;
|
|
2229
|
+
/** Secret fields go to `plugin_secrets`, never returned, never logged. */
|
|
2230
|
+
secret?: boolean;
|
|
2231
|
+
default?: unknown;
|
|
2232
|
+
enumValues?: string[];
|
|
2233
|
+
/** The actual validator used at the boundary. */
|
|
2234
|
+
zod: z.ZodTypeAny;
|
|
2235
|
+
}
|
|
2236
|
+
interface PluginSettingsSchema {
|
|
2237
|
+
fields: Record<string, PluginSettingsField>;
|
|
2238
|
+
/** Optional migration when a new version changes the schema shape. */
|
|
2239
|
+
migrate?: (previous: Record<string, unknown>) => Record<string, unknown>;
|
|
2240
|
+
}
|
|
2241
|
+
type FieldOptions = {
|
|
2242
|
+
label: string;
|
|
2243
|
+
description?: string;
|
|
2244
|
+
required?: boolean;
|
|
2245
|
+
secret?: boolean;
|
|
2246
|
+
default?: unknown;
|
|
2247
|
+
/** Override the derived validator (e.g. `z.string().url()`). */
|
|
2248
|
+
zod?: z.ZodTypeAny;
|
|
2249
|
+
};
|
|
2250
|
+
/** Zod-backed field helpers — the ergonomic way to author a settings schema. */
|
|
2251
|
+
declare const settingsField: {
|
|
2252
|
+
string: (opts: FieldOptions) => PluginSettingsField;
|
|
2253
|
+
number: (opts: FieldOptions) => PluginSettingsField;
|
|
2254
|
+
boolean: (opts: FieldOptions) => PluginSettingsField;
|
|
2255
|
+
enum: (opts: FieldOptions & {
|
|
2256
|
+
enumValues: [string, ...string[]];
|
|
2257
|
+
}) => PluginSettingsField;
|
|
2258
|
+
json: (opts: FieldOptions) => PluginSettingsField;
|
|
2259
|
+
};
|
|
2260
|
+
/** Wire-safe render descriptor for one field — everything except the validator. */
|
|
2261
|
+
interface PluginSettingsFieldDescriptor {
|
|
2262
|
+
type: PluginSettingsFieldType;
|
|
2263
|
+
label: string;
|
|
2264
|
+
description?: string;
|
|
2265
|
+
required: boolean;
|
|
2266
|
+
secret: boolean;
|
|
2267
|
+
/** Omitted for secret fields — defaults could leak intended values. */
|
|
2268
|
+
default?: unknown;
|
|
2269
|
+
enumValues?: string[];
|
|
2270
|
+
}
|
|
2271
|
+
type PluginSettingsSchemaDescriptor = Record<string, PluginSettingsFieldDescriptor>;
|
|
2272
|
+
/**
|
|
2273
|
+
* Serialize a settings schema for the backoffice form renderer and for the
|
|
2274
|
+
* `plugin_versions.settingsSchema` snapshot: strips `zod`, drops secret
|
|
2275
|
+
* defaults. This is what `GET …/settings/schema` returns.
|
|
2276
|
+
*/
|
|
2277
|
+
declare function serializeSettingsSchema(schema: PluginSettingsSchema): PluginSettingsSchemaDescriptor;
|
|
2278
|
+
/**
|
|
2279
|
+
* Compose the Zod object validating a settings payload. `secret: "only"`
|
|
2280
|
+
* validates just the secret fields (for the write-only secrets payload),
|
|
2281
|
+
* `"exclude"` just the non-secret values, `"include"` everything.
|
|
2282
|
+
*/
|
|
2283
|
+
declare function settingsZodObject(schema: PluginSettingsSchema, secret?: "include" | "exclude" | "only"): z.ZodObject<Record<string, z.ZodTypeAny>>;
|
|
2284
|
+
/** Default non-secret values derived from the schema (used to seed on install). */
|
|
2285
|
+
declare function defaultSettingsValues(schema: PluginSettingsSchema): Record<string, unknown>;
|
|
2286
|
+
|
|
2287
|
+
/**
|
|
2288
|
+
* Every provider call carries the tenant context so adapters can route to the
|
|
2289
|
+
* correct upstream credentials / endpoints per tenant.
|
|
2290
|
+
*/
|
|
2291
|
+
interface ProviderContext {
|
|
2292
|
+
tenant: TenantContext$1;
|
|
2293
|
+
}
|
|
2294
|
+
interface CreateSessionInput {
|
|
2295
|
+
playerId: string;
|
|
2296
|
+
gameId: string;
|
|
2297
|
+
currency: string;
|
|
2298
|
+
}
|
|
2299
|
+
interface CreateSessionResult {
|
|
2300
|
+
sessionId: string;
|
|
2301
|
+
launchUrl: string;
|
|
2302
|
+
}
|
|
2303
|
+
interface BalanceResult {
|
|
2304
|
+
currency: string;
|
|
2305
|
+
balance: string;
|
|
2306
|
+
}
|
|
2307
|
+
interface PlaceBetInput {
|
|
2308
|
+
sessionId: string;
|
|
2309
|
+
roundId: string;
|
|
2310
|
+
amount: string;
|
|
2311
|
+
currency: string;
|
|
2312
|
+
}
|
|
2313
|
+
interface SettleBetInput {
|
|
2314
|
+
sessionId: string;
|
|
2315
|
+
roundId: string;
|
|
2316
|
+
payout: string;
|
|
2317
|
+
currency: string;
|
|
2318
|
+
}
|
|
2319
|
+
interface RollbackInput {
|
|
2320
|
+
sessionId: string;
|
|
2321
|
+
roundId: string;
|
|
2322
|
+
}
|
|
2323
|
+
interface CloseRoundInput {
|
|
2324
|
+
sessionId: string;
|
|
2325
|
+
roundId: string;
|
|
2326
|
+
}
|
|
2327
|
+
interface FreeSpinInput {
|
|
2328
|
+
sessionId: string;
|
|
2329
|
+
roundId: string;
|
|
2330
|
+
/** Number of free spins awarded/consumed. */
|
|
2331
|
+
spins: number;
|
|
2332
|
+
campaign?: string;
|
|
2333
|
+
}
|
|
2334
|
+
interface BonusWinInput {
|
|
2335
|
+
sessionId: string;
|
|
2336
|
+
roundId: string;
|
|
2337
|
+
amount: string;
|
|
2338
|
+
currency: string;
|
|
2339
|
+
campaign?: string;
|
|
2340
|
+
}
|
|
2341
|
+
interface BetResult {
|
|
2342
|
+
roundId: string;
|
|
2343
|
+
balance: string;
|
|
2344
|
+
status: "ok" | "rejected";
|
|
2345
|
+
}
|
|
2346
|
+
interface RoundResult {
|
|
2347
|
+
roundId: string;
|
|
2348
|
+
status: "open" | "closed";
|
|
2349
|
+
}
|
|
2350
|
+
/**
|
|
2351
|
+
* The contract every game/payment provider integration must implement.
|
|
2352
|
+
* Business code depends only on this interface — never on a concrete provider.
|
|
2353
|
+
*
|
|
2354
|
+
* Extension points are grouped: session/balance, bet lifecycle
|
|
2355
|
+
* (place→settle→rollback→close), and bonus mechanics (free spins, bonus wins).
|
|
2356
|
+
* Keeping all of these in the contract now avoids a breaking redesign when real
|
|
2357
|
+
* providers are integrated.
|
|
2358
|
+
*/
|
|
2359
|
+
interface ProviderAdapter {
|
|
2360
|
+
readonly name: string;
|
|
2361
|
+
createSession(ctx: ProviderContext, input: CreateSessionInput): Promise<CreateSessionResult>;
|
|
2362
|
+
getBalance(ctx: ProviderContext, playerId: string, currency: string): Promise<BalanceResult>;
|
|
2363
|
+
placeBet(ctx: ProviderContext, input: PlaceBetInput): Promise<BetResult>;
|
|
2364
|
+
settleBet(ctx: ProviderContext, input: SettleBetInput): Promise<BetResult>;
|
|
2365
|
+
rollback(ctx: ProviderContext, input: RollbackInput): Promise<BetResult>;
|
|
2366
|
+
closeRound(ctx: ProviderContext, input: CloseRoundInput): Promise<RoundResult>;
|
|
2367
|
+
freeSpin(ctx: ProviderContext, input: FreeSpinInput): Promise<BetResult>;
|
|
2368
|
+
bonusWin(ctx: ProviderContext, input: BonusWinInput): Promise<BetResult>;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
/**
|
|
2372
|
+
* Provider-adapter contracts a plugin can contribute besides game providers
|
|
2373
|
+
* (PLAYER_ACCOUNT_BUILD_PROMPT.md §2.4): payments, KYC auto-verification and
|
|
2374
|
+
* messaging delivery. Registered in `setup(ctx)` via the matching
|
|
2375
|
+
* `ctx.register*Adapter` capability (gated by `permissions.providerKeys`,
|
|
2376
|
+
* exactly like game adapters); the host bridges them into the cashier /
|
|
2377
|
+
* KYC / messaging seams on tenant enable.
|
|
2378
|
+
*
|
|
2379
|
+
* Money rule: adapters NEVER move money themselves. Webhooks arrive on the
|
|
2380
|
+
* plugin's `callback` route surface; the handler verifies the signature and
|
|
2381
|
+
* dispatches the allowlisted cashier commands (`cashier.deposit.confirm`,
|
|
2382
|
+
* `cashier.withdrawal.confirm_payout`, `cashier.withdrawal.cancel`, …) via
|
|
2383
|
+
* `ctx.commands`, idempotent on (providerKey, sourceTxId).
|
|
2384
|
+
*/
|
|
2385
|
+
interface PluginPaymentMethodDescriptor {
|
|
2386
|
+
methodKey: string;
|
|
2387
|
+
providerKey: string;
|
|
2388
|
+
title: string;
|
|
2389
|
+
kind: "card" | "bank" | "ewallet" | "crypto" | "voucher";
|
|
2390
|
+
direction: "deposit" | "withdrawal" | "both";
|
|
2391
|
+
/** 4-dp integer minor units as strings. */
|
|
2392
|
+
limits: {
|
|
2393
|
+
minMinor: string;
|
|
2394
|
+
maxMinor: string;
|
|
2395
|
+
currency: string;
|
|
2396
|
+
};
|
|
2397
|
+
requiresKycLevel?: number;
|
|
2398
|
+
fields?: Array<{
|
|
2399
|
+
key: string;
|
|
2400
|
+
label: string;
|
|
2401
|
+
type: "text" | "number" | "select";
|
|
2402
|
+
required?: boolean;
|
|
2403
|
+
options?: string[];
|
|
2404
|
+
}>;
|
|
2405
|
+
sort?: number;
|
|
2406
|
+
}
|
|
2407
|
+
interface PspDepositInput {
|
|
2408
|
+
depositId: string;
|
|
2409
|
+
playerId: string;
|
|
2410
|
+
amount: string;
|
|
2411
|
+
currency: string;
|
|
2412
|
+
methodKey?: string;
|
|
2413
|
+
returnUrl?: string;
|
|
2414
|
+
}
|
|
2415
|
+
interface PspDepositResult {
|
|
2416
|
+
/** PSP-side intent/charge id — the (providerKey, sourceTxId) natural key. */
|
|
2417
|
+
sourceTxId: string;
|
|
2418
|
+
nextAction?: {
|
|
2419
|
+
type: "redirect" | "qr" | "iframe" | "none";
|
|
2420
|
+
url?: string;
|
|
2421
|
+
data?: unknown;
|
|
2422
|
+
};
|
|
2423
|
+
}
|
|
2424
|
+
interface PspWithdrawalInput {
|
|
2425
|
+
withdrawalId: string;
|
|
2426
|
+
playerId: string;
|
|
2427
|
+
amount: string;
|
|
2428
|
+
currency: string;
|
|
2429
|
+
methodKey?: string;
|
|
2430
|
+
instrumentToken?: string;
|
|
2431
|
+
}
|
|
2432
|
+
interface PspWithdrawalResult {
|
|
2433
|
+
sourceTxId: string;
|
|
2434
|
+
}
|
|
2435
|
+
interface PaymentProviderAdapter {
|
|
2436
|
+
/** e.g. `psp:coinpay`. Must be listed in `permissions.providerKeys`. */
|
|
2437
|
+
readonly providerKey: string;
|
|
2438
|
+
/** Feeds `cashier.listMethods` (§6.3). */
|
|
2439
|
+
methods(ctx: PluginContext): Promise<PluginPaymentMethodDescriptor[]>;
|
|
2440
|
+
createDeposit(ctx: PluginContext, input: PspDepositInput): Promise<PspDepositResult>;
|
|
2441
|
+
createWithdrawal(ctx: PluginContext, input: PspWithdrawalInput): Promise<PspWithdrawalResult>;
|
|
2442
|
+
}
|
|
2443
|
+
/** Document reference passed to a KYC provider — METADATA only, never bytes. */
|
|
2444
|
+
interface KycDocumentRef {
|
|
2445
|
+
documentId: string;
|
|
2446
|
+
playerId: string;
|
|
2447
|
+
documentTypeKey: string;
|
|
2448
|
+
mimeType: string;
|
|
2449
|
+
sizeBytes: number;
|
|
2450
|
+
}
|
|
2451
|
+
interface KycProviderAdapter {
|
|
2452
|
+
/** e.g. `kyc:sumsub-like`. Must be listed in `permissions.providerKeys`. */
|
|
2453
|
+
readonly providerKey: string;
|
|
2454
|
+
/**
|
|
2455
|
+
* Called by the host (observe hook `kyc.document.submitted`) when
|
|
2456
|
+
* auto-verify is enabled. The provider webhook lands on the plugin's
|
|
2457
|
+
* callback surface and dispatches `kyc.document.approve` /
|
|
2458
|
+
* `kyc.document.reject` via `ctx.commands`.
|
|
2459
|
+
*/
|
|
2460
|
+
submitDocument(ctx: PluginContext, doc: KycDocumentRef): Promise<{
|
|
2461
|
+
externalRef: string;
|
|
2462
|
+
}>;
|
|
2463
|
+
}
|
|
2464
|
+
interface PluginMessage {
|
|
2465
|
+
channel: "email" | "sms";
|
|
2466
|
+
to: string;
|
|
2467
|
+
/** Template KEY — content is tenant configuration. */
|
|
2468
|
+
template: string;
|
|
2469
|
+
variables: Record<string, string>;
|
|
2470
|
+
}
|
|
2471
|
+
interface MessagingAdapter {
|
|
2472
|
+
/** e.g. `messaging:twilio-like`. Must be listed in `permissions.providerKeys`. */
|
|
2473
|
+
readonly providerKey: string;
|
|
2474
|
+
send(ctx: PluginContext, message: PluginMessage): Promise<{
|
|
2475
|
+
externalRef?: string;
|
|
2476
|
+
}>;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
/**
|
|
2480
|
+
* Plugin datasets — plugin-owned custom data (the Metafields/Metaobjects
|
|
2481
|
+
* analogue). Declared in the manifest, provisioned at install (snapshot only;
|
|
2482
|
+
* rows materialize on first write), accessed exclusively through
|
|
2483
|
+
* `ctx.datasets` — a host-owned capability that injects `tenantId`, validates
|
|
2484
|
+
* every write against the declared Zod schema and enforces quotas. There is
|
|
2485
|
+
* no dynamic DDL, ever: all records share one table with up to three
|
|
2486
|
+
* extracted index columns.
|
|
2487
|
+
*/
|
|
2488
|
+
interface PluginDatasetDecl {
|
|
2489
|
+
/** Record shape, validated on every write. */
|
|
2490
|
+
schema: z.ZodObject<z.ZodRawShape>;
|
|
2491
|
+
/** Business key inside the record (upsert identity). */
|
|
2492
|
+
keyField: string;
|
|
2493
|
+
/** Up to 3 top-level fields queryable via `.query({ where })`. */
|
|
2494
|
+
indexes?: string[];
|
|
2495
|
+
/** Readable by other plugins that declare `permissions.datasets.read`. */
|
|
2496
|
+
shared?: boolean;
|
|
2497
|
+
/** Per-tenant record cap. The host cap wins when smaller. */
|
|
2498
|
+
maxRecords?: number;
|
|
2499
|
+
}
|
|
2500
|
+
interface PluginDatasetQuery {
|
|
2501
|
+
/** Indexed fields only — a non-indexed field here is a ValidationError. */
|
|
2502
|
+
where?: Partial<Record<string, string | number | boolean>>;
|
|
2503
|
+
cursor?: string;
|
|
2504
|
+
/** Max 200 (host clamps). */
|
|
2505
|
+
limit?: number;
|
|
2506
|
+
}
|
|
2507
|
+
interface PluginDatasetCollection<T = Record<string, unknown>> {
|
|
2508
|
+
get(key: string): Promise<T | null>;
|
|
2509
|
+
/** Upsert by business key. */
|
|
2510
|
+
put(key: string, value: T): Promise<void>;
|
|
2511
|
+
/** Batched upsert, max 500 per call. All-or-nothing on quota breach. */
|
|
2512
|
+
putMany(records: Array<{
|
|
2513
|
+
key: string;
|
|
2514
|
+
value: T;
|
|
2515
|
+
}>): Promise<void>;
|
|
2516
|
+
delete(key: string): Promise<void>;
|
|
2517
|
+
query(q: PluginDatasetQuery): Promise<{
|
|
2518
|
+
records: Array<{
|
|
2519
|
+
key: string;
|
|
2520
|
+
value: T;
|
|
2521
|
+
}>;
|
|
2522
|
+
nextCursor?: string;
|
|
2523
|
+
}>;
|
|
2524
|
+
count(): Promise<number>;
|
|
2525
|
+
/** Audited; own datasets only. */
|
|
2526
|
+
truncate(): Promise<void>;
|
|
2527
|
+
}
|
|
2528
|
+
/** Host-enforced dataset limits (Phase 1, shared-table storage model). */
|
|
2529
|
+
declare const PLUGIN_DATASET_LIMITS: {
|
|
2530
|
+
readonly maxIndexes: 3;
|
|
2531
|
+
readonly maxBatch: 500;
|
|
2532
|
+
readonly maxQueryLimit: 200;
|
|
2533
|
+
/** Serialized record cap in bytes. */
|
|
2534
|
+
readonly maxRecordBytes: 65536;
|
|
2535
|
+
/** Default per-tenant record quota when the declaration omits one. */
|
|
2536
|
+
readonly defaultMaxRecords: 100000;
|
|
2537
|
+
};
|
|
2538
|
+
|
|
2539
|
+
/**
|
|
2540
|
+
* Outbound HTTP (`ctx.http`) — allowlisted, HTTPS-only, SSRF-guarded egress.
|
|
2541
|
+
* The host enforces `manifest.network.allowedHosts` (exact host or one-level
|
|
2542
|
+
* wildcard `*.example.com`), blocks private/link-local/metadata IP ranges
|
|
2543
|
+
* after DNS resolution, and rejects redirects that leave the allowlist.
|
|
2544
|
+
*
|
|
2545
|
+
* Secrets never transit plugin code: `secretHeaders` names a tenant secret
|
|
2546
|
+
* per header and the HOST resolves it at send time — logs and persisted
|
|
2547
|
+
* exchanges show `<redacted:name>`.
|
|
2548
|
+
*/
|
|
2549
|
+
interface PluginFetchInit {
|
|
2550
|
+
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD";
|
|
2551
|
+
headers?: Record<string, string>;
|
|
2552
|
+
/** Objects are JSON-serialized with `content-type: application/json`. */
|
|
2553
|
+
body?: string | Record<string, unknown> | Array<unknown>;
|
|
2554
|
+
/** Default 10s. */
|
|
2555
|
+
timeoutMs?: number;
|
|
2556
|
+
/** header name → SECRET NAME, resolved host-side, never exposed. */
|
|
2557
|
+
secretHeaders?: Record<string, string>;
|
|
2558
|
+
/**
|
|
2559
|
+
* Persist the redacted request/response to `plugin_http_exchanges` (same
|
|
2560
|
+
* mechanism as provider raw I/O persistence) for support/debugging.
|
|
2561
|
+
*/
|
|
2562
|
+
persistExchange?: boolean;
|
|
2563
|
+
}
|
|
2564
|
+
/** Materialized response — body already read, capped at 30 MB by the host. */
|
|
2565
|
+
interface PluginFetchResponse {
|
|
2566
|
+
status: number;
|
|
2567
|
+
ok: boolean;
|
|
2568
|
+
headers: Readonly<Record<string, string>>;
|
|
2569
|
+
bodyText: string;
|
|
2570
|
+
/** JSON.parse of bodyText, typed for convenience. Throws on non-JSON. */
|
|
2571
|
+
json<T = unknown>(): T;
|
|
2572
|
+
}
|
|
2573
|
+
declare const PLUGIN_HTTP_LIMITS: {
|
|
2574
|
+
readonly defaultTimeoutMs: 10000;
|
|
2575
|
+
readonly maxResponseBytes: 31457280;
|
|
2576
|
+
/** Retries on idempotent methods (GET/HEAD) only. */
|
|
2577
|
+
readonly maxRetries: 2;
|
|
2578
|
+
readonly maxRedirects: 3;
|
|
2579
|
+
};
|
|
2580
|
+
|
|
2581
|
+
/**
|
|
2582
|
+
* CatalogSourceAdapter — the seam that makes the catalog multi-source from day
|
|
2583
|
+
* one. Each aggregator integration (SoftGamings first; operator-owned sources
|
|
2584
|
+
* later) implements this interface to turn its native export into ONE normalized,
|
|
2585
|
+
* source-agnostic shape the importer understands. The importer never knows which
|
|
2586
|
+
* aggregator it's importing — only this contract.
|
|
2587
|
+
*
|
|
2588
|
+
* Normalization rules the adapter MUST apply (so the importer can stay dumb):
|
|
2589
|
+
* - Never reuse the aggregator's ids as our PKs — they become `externalId`.
|
|
2590
|
+
* - Country codes → lowercase ISO-3166 alpha-3 (normalizeCountry).
|
|
2591
|
+
* - Currency codes → uppercase ISO-4217 (normalizeCurrency).
|
|
2592
|
+
* - Emit a provider entry for EVERY referenced provider externalId (including
|
|
2593
|
+
* parents and game MerchantIDs missing from the source's merchant list) so the
|
|
2594
|
+
* NOT-NULL games.providerId FK always resolves. Missing ones are `stub: true`.
|
|
2595
|
+
*/
|
|
2596
|
+
/** A provider (merchant / sub-provider) in normalized form. */
|
|
2597
|
+
interface NormalizedProvider {
|
|
2598
|
+
externalId: string;
|
|
2599
|
+
name: string;
|
|
2600
|
+
alias?: string | null;
|
|
2601
|
+
/** Source parent merchant id (resolved to parentProviderId by the importer). */
|
|
2602
|
+
parentExternalId?: string | null;
|
|
2603
|
+
logoPath?: string | null;
|
|
2604
|
+
logoUrl?: string | null;
|
|
2605
|
+
weight?: number;
|
|
2606
|
+
/** True when this provider was only referenced (not in the source list). */
|
|
2607
|
+
stub?: boolean;
|
|
2608
|
+
}
|
|
2609
|
+
/** A category in normalized form (flat source nodes; the curated tree is built by the importer). */
|
|
2610
|
+
interface NormalizedCategory {
|
|
2611
|
+
externalId: string;
|
|
2612
|
+
slug: string;
|
|
2613
|
+
/** i18n locale map. */
|
|
2614
|
+
name?: Record<string, string> | null;
|
|
2615
|
+
tags?: string[] | null;
|
|
2616
|
+
sort?: number;
|
|
2617
|
+
subSort?: number;
|
|
2618
|
+
}
|
|
2619
|
+
interface NormalizedRestrictionGroup {
|
|
2620
|
+
externalId: string;
|
|
2621
|
+
name?: string | null;
|
|
2622
|
+
providerExternalId?: string | null;
|
|
2623
|
+
/** Lowercase ISO-3 alpha codes BLOCKED by this group. */
|
|
2624
|
+
blockedCountries: string[];
|
|
2625
|
+
isDefault?: boolean;
|
|
2626
|
+
parentExternalId?: string | null;
|
|
2627
|
+
subdivisions?: string[] | null;
|
|
2628
|
+
}
|
|
2629
|
+
interface NormalizedCurrencyGroup {
|
|
2630
|
+
externalId: string;
|
|
2631
|
+
name?: string | null;
|
|
2632
|
+
providerExternalId?: string | null;
|
|
2633
|
+
/** Uppercase ISO-4217 codes supported by this group. */
|
|
2634
|
+
supportedCurrencies: string[];
|
|
2635
|
+
defaultCurrency?: string | null;
|
|
2636
|
+
isDefault?: boolean;
|
|
2637
|
+
}
|
|
2638
|
+
/** Per-platform launch codes (PageCode variants). */
|
|
2639
|
+
interface NormalizedLaunchCodes {
|
|
2640
|
+
desktop?: string | null;
|
|
2641
|
+
mobile?: string | null;
|
|
2642
|
+
android?: string | null;
|
|
2643
|
+
windows?: string | null;
|
|
2644
|
+
}
|
|
2645
|
+
/** A game in normalized form. The importer mints our internal UUID + slug. */
|
|
2646
|
+
interface NormalizedGame {
|
|
2647
|
+
externalGameId: string;
|
|
2648
|
+
/** Primary launch code (desktop PageCode). */
|
|
2649
|
+
launchCode?: string | null;
|
|
2650
|
+
launchCodes?: NormalizedLaunchCodes | null;
|
|
2651
|
+
providerExternalId: string;
|
|
2652
|
+
subProviderExternalId?: string | null;
|
|
2653
|
+
name?: Record<string, string> | null;
|
|
2654
|
+
description?: Record<string, string> | null;
|
|
2655
|
+
launchUrl?: string | null;
|
|
2656
|
+
mobileLaunchUrl?: string | null;
|
|
2657
|
+
imagePath?: string | null;
|
|
2658
|
+
imageUrl?: string | null;
|
|
2659
|
+
/** Source category externalIds this game belongs to (unknown ones are skipped). */
|
|
2660
|
+
categoryExternalIds: string[];
|
|
2661
|
+
/** Per-(source)category sort, keyed by category externalId. */
|
|
2662
|
+
sortPerCategory?: Record<string, number> | null;
|
|
2663
|
+
restrictionGroupExternalId?: string | null;
|
|
2664
|
+
currencyGroupExternalId?: string | null;
|
|
2665
|
+
rtp?: string | null;
|
|
2666
|
+
minBet?: string | null;
|
|
2667
|
+
maxBet?: string | null;
|
|
2668
|
+
maxMultiplier?: string | null;
|
|
2669
|
+
aspectRatio?: string | null;
|
|
2670
|
+
hasDemo: boolean;
|
|
2671
|
+
isVirtual: boolean;
|
|
2672
|
+
bonusBuy: boolean;
|
|
2673
|
+
megaways: boolean;
|
|
2674
|
+
/** Full feature flag set (typed columns above are promoted from here). */
|
|
2675
|
+
features: Record<string, boolean>;
|
|
2676
|
+
/** Raw/rare source fields. */
|
|
2677
|
+
metadata?: Record<string, unknown> | null;
|
|
2678
|
+
}
|
|
2679
|
+
interface NormalizedCatalog {
|
|
2680
|
+
providers: NormalizedProvider[];
|
|
2681
|
+
categories: NormalizedCategory[];
|
|
2682
|
+
restrictionGroups: NormalizedRestrictionGroup[];
|
|
2683
|
+
currencyGroups: NormalizedCurrencyGroup[];
|
|
2684
|
+
games: NormalizedGame[];
|
|
2685
|
+
}
|
|
2686
|
+
/** One game↔category link, both sides referenced by their source externalIds. */
|
|
2687
|
+
interface NormalizedGameCategoryLink {
|
|
2688
|
+
gameExternalId: string;
|
|
2689
|
+
categoryExternalId: string;
|
|
2690
|
+
}
|
|
2691
|
+
/**
|
|
2692
|
+
* A PARTIAL catalog delta — the incremental counterpart of `NormalizedCatalog`
|
|
2693
|
+
* (every family optional; NO retire semantics — retire is snapshot-import-only).
|
|
2694
|
+
* Consumed by `upsertNormalizedDelta` and submitted by the plugin SDK's
|
|
2695
|
+
* `ctx.catalog.upsertBatch`. Reference-resolution rules match the importer:
|
|
2696
|
+
* `*ExternalId` references resolve within the SAME source only; an unknown
|
|
2697
|
+
* provider reference auto-creates a stub provider row; an unknown
|
|
2698
|
+
* restriction/currency-group/category reference is a validation error
|
|
2699
|
+
* (compliance data is never silently invented).
|
|
2700
|
+
*/
|
|
2701
|
+
interface NormalizedCatalogDelta {
|
|
2702
|
+
providers?: NormalizedProvider[];
|
|
2703
|
+
categories?: NormalizedCategory[];
|
|
2704
|
+
restrictionGroups?: NormalizedRestrictionGroup[];
|
|
2705
|
+
currencyGroups?: NormalizedCurrencyGroup[];
|
|
2706
|
+
games?: NormalizedGame[];
|
|
2707
|
+
gameCategories?: NormalizedGameCategoryLink[];
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
/** Reserved task type the HOST executes: full ingest via the registered
|
|
2711
|
+
* adapter → validate → snapshot import (insert/update/retire). Plugins may
|
|
2712
|
+
* use it as `install.seedTask` and via `ctx.catalog.requestImport()`; they
|
|
2713
|
+
* never implement a handler for it. */
|
|
2714
|
+
declare const CATALOG_IMPORT_TASK_TYPE = "catalog:import";
|
|
2715
|
+
/** Plugin kinds allowed to declare `manifest.catalog` (doctor-enforced). */
|
|
2716
|
+
declare const CATALOG_ALLOWED_KINDS: readonly ["provider", "content", "integration"];
|
|
2717
|
+
declare const PLUGIN_CATALOG_LIMITS: {
|
|
2718
|
+
/** Max rows per family per `upsertBatch` call. */
|
|
2719
|
+
readonly maxRowsPerFamily: 500;
|
|
2720
|
+
};
|
|
2721
|
+
/** Per-family counts returned by the incremental upsert path. */
|
|
2722
|
+
interface ImportSummaryLite {
|
|
2723
|
+
providers: {
|
|
2724
|
+
inserted: number;
|
|
2725
|
+
updated: number;
|
|
2726
|
+
};
|
|
2727
|
+
categories: {
|
|
2728
|
+
inserted: number;
|
|
2729
|
+
updated: number;
|
|
2730
|
+
};
|
|
2731
|
+
restrictionGroups: {
|
|
2732
|
+
inserted: number;
|
|
2733
|
+
updated: number;
|
|
2734
|
+
};
|
|
2735
|
+
currencyGroups: {
|
|
2736
|
+
inserted: number;
|
|
2737
|
+
updated: number;
|
|
2738
|
+
};
|
|
2739
|
+
games: {
|
|
2740
|
+
inserted: number;
|
|
2741
|
+
updated: number;
|
|
2742
|
+
unchanged: number;
|
|
2743
|
+
};
|
|
2744
|
+
/** Games whose category memberships changed via `gameCategories` links. */
|
|
2745
|
+
gameCategoryLinks: number;
|
|
2746
|
+
}
|
|
2747
|
+
/** Full snapshot-import summary (host `catalog:import` task result shape). */
|
|
2748
|
+
interface CatalogImportSummary {
|
|
2749
|
+
providers: {
|
|
2750
|
+
inserted: number;
|
|
2751
|
+
updated: number;
|
|
2752
|
+
};
|
|
2753
|
+
categories: {
|
|
2754
|
+
inserted: number;
|
|
2755
|
+
updated: number;
|
|
2756
|
+
};
|
|
2757
|
+
restrictionGroups: {
|
|
2758
|
+
inserted: number;
|
|
2759
|
+
updated: number;
|
|
2760
|
+
};
|
|
2761
|
+
currencyGroups: {
|
|
2762
|
+
inserted: number;
|
|
2763
|
+
updated: number;
|
|
2764
|
+
};
|
|
2765
|
+
games: {
|
|
2766
|
+
inserted: number;
|
|
2767
|
+
updated: number;
|
|
2768
|
+
retired: number;
|
|
2769
|
+
unchanged: number;
|
|
2770
|
+
};
|
|
2771
|
+
skippedUnknownCategoryLinks: number;
|
|
2772
|
+
durationMs: number;
|
|
2773
|
+
}
|
|
2774
|
+
/** One of the plugin's OWN games (management view — includes retired rows). */
|
|
2775
|
+
interface OwnGameSummary {
|
|
2776
|
+
/** Internal catalog GameId — what wallet/analytics reference. */
|
|
2777
|
+
gameId: string;
|
|
2778
|
+
externalGameId: string;
|
|
2779
|
+
slug: string | null;
|
|
2780
|
+
name: Record<string, string> | null;
|
|
2781
|
+
launchCode: string | null;
|
|
2782
|
+
providerId: string;
|
|
2783
|
+
status: "active" | "retired";
|
|
2784
|
+
}
|
|
2785
|
+
/**
|
|
2786
|
+
* `ctx.catalog` — present only when the manifest declares a `catalog` block
|
|
2787
|
+
* AND the plugin kind is in `CATALOG_ALLOWED_KINDS`. All `*ExternalId`
|
|
2788
|
+
* references resolve within the plugin's own source only. A game referencing
|
|
2789
|
+
* an unknown provider auto-creates a stub provider row (the importer's own
|
|
2790
|
+
* rule — `games.providerId` is NOT NULL and must always resolve); an unknown
|
|
2791
|
+
* restriction/currency-group/category reference is a ValidationError naming
|
|
2792
|
+
* the missing externalId — compliance data is never silently invented.
|
|
2793
|
+
* Restriction groups only ever NARROW availability: they apply on top of —
|
|
2794
|
+
* never instead of — tenant/platform compliance rules.
|
|
2795
|
+
*/
|
|
2796
|
+
interface PluginCatalogCapability {
|
|
2797
|
+
/**
|
|
2798
|
+
* RECOMMENDED incremental entry point: a partial NormalizedCatalog delta.
|
|
2799
|
+
* The bridge runs the SAME family ordering + second-pass FK resolution as
|
|
2800
|
+
* the importer (providers → restriction/currency groups → categories →
|
|
2801
|
+
* games → category links). No retire step (that is import-only).
|
|
2802
|
+
* ≤500 rows per family per call.
|
|
2803
|
+
*/
|
|
2804
|
+
upsertBatch(delta: NormalizedCatalogDelta): Promise<ImportSummaryLite>;
|
|
2805
|
+
upsertProviders(providers: NormalizedProvider[]): Promise<{
|
|
2806
|
+
upserted: number;
|
|
2807
|
+
}>;
|
|
2808
|
+
upsertRestrictionGroups(groups: NormalizedRestrictionGroup[]): Promise<{
|
|
2809
|
+
upserted: number;
|
|
2810
|
+
}>;
|
|
2811
|
+
upsertCurrencyGroups(groups: NormalizedCurrencyGroup[]): Promise<{
|
|
2812
|
+
upserted: number;
|
|
2813
|
+
}>;
|
|
2814
|
+
upsertCategories(categories: NormalizedCategory[]): Promise<{
|
|
2815
|
+
upserted: number;
|
|
2816
|
+
}>;
|
|
2817
|
+
upsertGames(games: NormalizedGame[]): Promise<{
|
|
2818
|
+
upserted: number;
|
|
2819
|
+
}>;
|
|
2820
|
+
attachGameCategories(links: Array<{
|
|
2821
|
+
gameExternalId: string;
|
|
2822
|
+
categoryExternalId: string;
|
|
2823
|
+
}>): Promise<void>;
|
|
2824
|
+
/** Enable/disable one of the plugin's OWN games (status flip — never an
|
|
2825
|
+
* operator overlay, never a delete). */
|
|
2826
|
+
setGameStatus(externalGameId: string, enabled: boolean): Promise<void>;
|
|
2827
|
+
/** Cursor-paginated listing of the plugin's own rows (incl. retired). */
|
|
2828
|
+
listOwnGames(q?: {
|
|
2829
|
+
cursor?: string;
|
|
2830
|
+
limit?: number;
|
|
2831
|
+
}): Promise<{
|
|
2832
|
+
games: OwnGameSummary[];
|
|
2833
|
+
nextCursor?: string;
|
|
2834
|
+
}>;
|
|
2835
|
+
/** Enqueue a full ingest via the registered adapter (the host-run
|
|
2836
|
+
* `catalog:import` task: ingest → validate → import with retire). */
|
|
2837
|
+
requestImport(): Promise<{
|
|
2838
|
+
taskId: string;
|
|
2839
|
+
}>;
|
|
2840
|
+
}
|
|
2841
|
+
/**
|
|
2842
|
+
* The plugin's catalog source adapter — registered in `setup(ctx)` via
|
|
2843
|
+
* `ctx.registerCatalogSource`. `ingest` receives the PluginContext so it can
|
|
2844
|
+
* use `ctx.http` / `ctx.settings` / `ctx.secrets`; the host adapts it to the
|
|
2845
|
+
* catalog's `CatalogSourceAdapter` shape and runs it inside the
|
|
2846
|
+
* `catalog:import` task.
|
|
2847
|
+
*/
|
|
2848
|
+
interface PluginCatalogSourceAdapter {
|
|
2849
|
+
ingest(ctx: PluginContext): Promise<NormalizedCatalog>;
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
/** A command dispatch request from a plugin — name must be in `permissions.commands`. */
|
|
2853
|
+
interface PluginCommand {
|
|
2854
|
+
name: string;
|
|
2855
|
+
input: unknown;
|
|
2856
|
+
}
|
|
2857
|
+
/**
|
|
2858
|
+
* An event a plugin emits. Name must be declared in `permissions.events.emit`
|
|
2859
|
+
* and namespaced `plugin.<key>.*`. Routed through the outbox — never NATS.
|
|
2860
|
+
*/
|
|
2861
|
+
interface PluginEmittedEvent {
|
|
2862
|
+
name: string;
|
|
2863
|
+
payload: Record<string, unknown>;
|
|
2864
|
+
version?: number;
|
|
2865
|
+
/**
|
|
2866
|
+
* Player the event addresses. REQUIRED for the event to reach player
|
|
2867
|
+
* sockets on the `ext.<key>` realtime channel (tenant broadcast is out of
|
|
2868
|
+
* scope — the gateway drops player-less events). Carried on the outbox
|
|
2869
|
+
* envelope, never inside `payload`.
|
|
2870
|
+
*/
|
|
2871
|
+
playerId?: string;
|
|
2872
|
+
}
|
|
2873
|
+
/** Logger scoped to tenant + plugin; the host's implementation redacts secrets. */
|
|
2874
|
+
interface ScopedLogger {
|
|
2875
|
+
debug(message: string, extra?: Record<string, unknown>): void;
|
|
2876
|
+
info(message: string, extra?: Record<string, unknown>): void;
|
|
2877
|
+
warn(message: string, extra?: Record<string, unknown>): void;
|
|
2878
|
+
error(message: string, extra?: Record<string, unknown>): void;
|
|
2879
|
+
}
|
|
2880
|
+
/**
|
|
2881
|
+
* The ENTIRE surface a plugin may touch, built per tenant by the host's
|
|
2882
|
+
* capability layer. There is intentionally no `db`, no NATS handle, no wallet
|
|
2883
|
+
* service here — state changes go through commands, events through the outbox.
|
|
2884
|
+
*/
|
|
2885
|
+
interface PluginContext {
|
|
2886
|
+
readonly tenantId: string;
|
|
2887
|
+
readonly region: string;
|
|
2888
|
+
readonly pluginKey: string;
|
|
2889
|
+
readonly version: string;
|
|
2890
|
+
/** The ONLY way a plugin changes state. Rejects commands not in the manifest allowlist. */
|
|
2891
|
+
commands: {
|
|
2892
|
+
execute<TResult = unknown>(command: PluginCommand, opts?: {
|
|
2893
|
+
idempotencyKey?: string;
|
|
2894
|
+
}): Promise<TResult>;
|
|
2895
|
+
};
|
|
2896
|
+
/** Typed, validated, READ-ONLY non-secret settings for this plugin + tenant. */
|
|
2897
|
+
settings: {
|
|
2898
|
+
get<T = Record<string, unknown>>(): T;
|
|
2899
|
+
};
|
|
2900
|
+
/** Secret values resolved on demand; never logged, never echoed to the API layer. */
|
|
2901
|
+
secrets: {
|
|
2902
|
+
get(key: string): Promise<string | undefined>;
|
|
2903
|
+
};
|
|
2904
|
+
events: {
|
|
2905
|
+
/** Subscribe to a domain event declared in `permissions.events.subscribe`. */
|
|
2906
|
+
on<E extends DomainEventName>(event: E, handler: (e: DomainEvent<E>) => Promise<void>): void;
|
|
2907
|
+
/** Emit a `plugin.<key>.*` event declared in `permissions.events.emit`. */
|
|
2908
|
+
emit(event: PluginEmittedEvent): Promise<void>;
|
|
2909
|
+
};
|
|
2910
|
+
/**
|
|
2911
|
+
* Scoped READ access to core data via registered read models. The model's
|
|
2912
|
+
* required scope must be declared in `permissions.dataScopes`; results are
|
|
2913
|
+
* DTOs (PII only under `players:pii`), cursor-paginated, max page 200.
|
|
2914
|
+
*/
|
|
2915
|
+
data: {
|
|
2916
|
+
query<Q extends ReadModelName>(model: Q, params: ReadModelParams<Q>): Promise<ReadModelResult<Q>>;
|
|
2917
|
+
};
|
|
2918
|
+
/**
|
|
2919
|
+
* Plugin-owned datasets declared in the manifest. Own datasets: full CRUD.
|
|
2920
|
+
* Foreign datasets (`"owner.dataset"`): read-only, only when the owner marks
|
|
2921
|
+
* them `shared` AND this plugin declares `permissions.datasets.read`.
|
|
2922
|
+
*/
|
|
2923
|
+
datasets: {
|
|
2924
|
+
collection<T = Record<string, unknown>>(name: string): PluginDatasetCollection<T>;
|
|
2925
|
+
};
|
|
2926
|
+
/**
|
|
2927
|
+
* Outbound HTTP restricted to `manifest.network.allowedHosts`. HTTPS only,
|
|
2928
|
+
* SSRF-guarded, secrets injected host-side via `init.secretHeaders`.
|
|
2929
|
+
*/
|
|
2930
|
+
http: {
|
|
2931
|
+
fetch(url: string, init?: PluginFetchInit): Promise<PluginFetchResponse>;
|
|
2932
|
+
/** HMAC over `payload` with the named tenant secret, computed host-side. */
|
|
2933
|
+
hmacSha256(secretName: string, payload: string): Promise<string>;
|
|
2934
|
+
};
|
|
2935
|
+
/** Enqueue a background task on this plugin (seeding, backfill). */
|
|
2936
|
+
tasks: {
|
|
2937
|
+
start(type: string, input?: Record<string, unknown>): Promise<{
|
|
2938
|
+
taskId: string;
|
|
2939
|
+
}>;
|
|
2940
|
+
};
|
|
2941
|
+
logger: ScopedLogger;
|
|
2942
|
+
/**
|
|
2943
|
+
* Game-catalog content surface (§7b) — present ONLY when the manifest
|
|
2944
|
+
* declares a `catalog` block and the plugin kind allows it. Every write is
|
|
2945
|
+
* scoped to the plugin's own tenant-owned catalog source; the platform
|
|
2946
|
+
* keeps search/categories/visibility/eligibility/overlays/stats/caching.
|
|
2947
|
+
*/
|
|
2948
|
+
catalog?: PluginCatalogCapability;
|
|
2949
|
+
/**
|
|
2950
|
+
* Register the catalog source adapter for this tenant (in `setup(ctx)`,
|
|
2951
|
+
* like provider adapters). The host runs it inside the reserved
|
|
2952
|
+
* `catalog:import` task: ingest → validate → snapshot import (with retire).
|
|
2953
|
+
* Requires `manifest.catalog`.
|
|
2954
|
+
*/
|
|
2955
|
+
registerCatalogSource?(adapter: PluginCatalogSourceAdapter): void;
|
|
2956
|
+
/** Provider plugins only: register the adapter for this tenant's provider key. */
|
|
2957
|
+
registerProviderAdapter?(adapter: ProviderAdapter): void;
|
|
2958
|
+
registerPaymentAdapter?(adapter: PaymentProviderAdapter): void;
|
|
2959
|
+
registerKycAdapter?(adapter: KycProviderAdapter): void;
|
|
2960
|
+
registerMessagingAdapter?(adapter: MessagingAdapter): void;
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
/**
|
|
2964
|
+
* Lifecycle hooks a plugin MAY implement. The host invokes them through the
|
|
2965
|
+
* hook runner (per-hook try/catch + timeout): a throwing or hanging hook marks
|
|
2966
|
+
* the tenant's plugin `errored` with `lastError` set — it never rolls back the
|
|
2967
|
+
* core transaction that triggered it.
|
|
2968
|
+
*/
|
|
2969
|
+
declare const PluginHookNames: readonly ["onInstall", "onEnable", "onConfigure", "onDisable", "onUninstall", "onMigrateSettings", "onUpgrade"];
|
|
2970
|
+
type PluginHookName = (typeof PluginHookNames)[number];
|
|
2971
|
+
interface PluginRuntimeHooks {
|
|
2972
|
+
onInstall?(ctx: PluginContext): Promise<void> | void;
|
|
2973
|
+
onEnable?(ctx: PluginContext): Promise<void> | void;
|
|
2974
|
+
/** Runs after settings were validated and saved for a tenant. */
|
|
2975
|
+
onConfigure?(ctx: PluginContext): Promise<void> | void;
|
|
2976
|
+
onDisable?(ctx: PluginContext): Promise<void> | void;
|
|
2977
|
+
onUninstall?(ctx: PluginContext): Promise<void> | void;
|
|
2978
|
+
/**
|
|
2979
|
+
* Runs when a tenant's saved settings were validated against an older
|
|
2980
|
+
* schema version than the one now enabled. Returns the migrated values.
|
|
2981
|
+
*/
|
|
2982
|
+
onMigrateSettings?(ctx: PluginContext, previous: Record<string, unknown>): Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
2983
|
+
/**
|
|
2984
|
+
* Runs after `UpgradePluginCommand` completed all migrations and flipped
|
|
2985
|
+
* `installedVersion` — the plugin is already running `toVersion`.
|
|
2986
|
+
*/
|
|
2987
|
+
onUpgrade?(ctx: PluginContext, info: {
|
|
2988
|
+
fromVersion: string;
|
|
2989
|
+
toVersion: string;
|
|
2990
|
+
}): Promise<void> | void;
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
/**
|
|
2994
|
+
* Host contract types, VENDORED from `@cwe/shared` (distribution prompt §2).
|
|
2995
|
+
*
|
|
2996
|
+
* The published SDK must be fully self-contained — its d.ts may not reference
|
|
2997
|
+
* private workspace packages. These are small, stable, structural interfaces:
|
|
2998
|
+
* TypeScript's structural typing keeps them assignment-compatible with the
|
|
2999
|
+
* host's own `@cwe/shared` definitions, and the SDK↔runtime lockstep gate
|
|
3000
|
+
* (major.minor == RUNTIME_API_VERSION) is the contract that keeps them in
|
|
3001
|
+
* sync. If a field changes in `@cwe/shared`, change it here in the same PR.
|
|
3002
|
+
*/
|
|
3003
|
+
/** Tenant context attached to every request. */
|
|
3004
|
+
interface TenantContext {
|
|
3005
|
+
tenantId: string;
|
|
3006
|
+
brandId: string;
|
|
3007
|
+
region: string;
|
|
3008
|
+
}
|
|
3009
|
+
/** The kind of principal that triggered an action — recorded in the audit trail. */
|
|
3010
|
+
type ActorType = "user" | "player" | "staff" | "system" | "provider" | "plugin" | "anonymous";
|
|
3011
|
+
/**
|
|
3012
|
+
* Who is performing an action. Flows into commands and the audit log so every
|
|
3013
|
+
* state change is attributable.
|
|
3014
|
+
*/
|
|
3015
|
+
interface Actor {
|
|
3016
|
+
type: ActorType;
|
|
3017
|
+
id: string | null;
|
|
3018
|
+
}
|
|
3019
|
+
/** Standard money representation. Amounts are fixed-precision strings. */
|
|
3020
|
+
interface Money {
|
|
3021
|
+
amount: string;
|
|
3022
|
+
currency: string;
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
/**
|
|
3026
|
+
* Plugin HTTP routes — how a plugin adds endpoints to the casino API. Routes
|
|
3027
|
+
* are DECLARED in the manifest and IMPLEMENTED as named handlers in
|
|
3028
|
+
* `definePlugin({ handlers.routes })`; the host's PluginRouter mounts them:
|
|
3029
|
+
*
|
|
3030
|
+
* public /api/ext/:pluginKey/* no session (tenant context as always)
|
|
3031
|
+
* player /api/ext/:pluginKey/* player session required
|
|
3032
|
+
* admin /admin/ext/:pluginKey/* staff RBAC `plugin:<key>:admin`
|
|
3033
|
+
* callback /callbacks/:pluginKey/* mandatory signature verification
|
|
3034
|
+
*
|
|
3035
|
+
* Routes exist only for tenants where the plugin is enabled — otherwise 404
|
|
3036
|
+
* (never 403; installation state must not leak). Handlers never see Fastify:
|
|
3037
|
+
* they get a sanitized PluginRequest and return a PluginResponse.
|
|
3038
|
+
*/
|
|
3039
|
+
type PluginRouteSurface = "public" | "player" | "admin" | "callback";
|
|
3040
|
+
type PluginRouteMethod = "GET" | "POST" | "PUT" | "DELETE";
|
|
3041
|
+
interface PluginRouteDecl {
|
|
3042
|
+
method: PluginRouteMethod;
|
|
3043
|
+
/** Relative path, e.g. `/lobby` or `/games/:gameKey`. */
|
|
3044
|
+
path: string;
|
|
3045
|
+
surface: PluginRouteSurface;
|
|
3046
|
+
/** Name of the implementation in `definePlugin({ handlers: { routes } })`. */
|
|
3047
|
+
handler: string;
|
|
3048
|
+
/** Zod at the boundary — validated by the host before the handler runs. */
|
|
3049
|
+
input?: {
|
|
3050
|
+
params?: z.ZodTypeAny;
|
|
3051
|
+
query?: z.ZodTypeAny;
|
|
3052
|
+
body?: z.ZodTypeAny;
|
|
3053
|
+
};
|
|
3054
|
+
/**
|
|
3055
|
+
* Optional Zod schema of the success response body. Not enforced at
|
|
3056
|
+
* runtime — used at publish/codegen time to derive the action catalog's
|
|
3057
|
+
* `output` JSON Schema and the generated client's result types.
|
|
3058
|
+
*/
|
|
3059
|
+
output?: z.ZodTypeAny;
|
|
3060
|
+
/** Per tenant+IP, Redis-backed. Host defaults apply when omitted. */
|
|
3061
|
+
rateLimit?: {
|
|
3062
|
+
windowSec: number;
|
|
3063
|
+
max: number;
|
|
3064
|
+
};
|
|
3065
|
+
/** POST routes that require an `Idempotency-Key` header. */
|
|
3066
|
+
idempotent?: boolean;
|
|
3067
|
+
}
|
|
3068
|
+
/** Options for callback-surface signature verification. */
|
|
3069
|
+
interface SignatureVerifyOptions {
|
|
3070
|
+
/** Header carrying the signature. Default `x-signature`. */
|
|
3071
|
+
header?: string;
|
|
3072
|
+
/** HMAC algorithm. Default `sha256`. */
|
|
3073
|
+
algorithm?: "sha256" | "sha512";
|
|
3074
|
+
/** Signature encoding in the header. Default `hex`. */
|
|
3075
|
+
encoding?: "hex" | "base64";
|
|
3076
|
+
}
|
|
3077
|
+
/**
|
|
3078
|
+
* What a route handler receives — already Zod-validated per the declaration,
|
|
3079
|
+
* with a sanitized header subset (no cookies, no authorization header).
|
|
3080
|
+
*/
|
|
3081
|
+
interface PluginRequest {
|
|
3082
|
+
params: unknown;
|
|
3083
|
+
query: unknown;
|
|
3084
|
+
body: unknown;
|
|
3085
|
+
/** Player surface only. */
|
|
3086
|
+
player?: {
|
|
3087
|
+
id: string;
|
|
3088
|
+
};
|
|
3089
|
+
/** Admin surface only. */
|
|
3090
|
+
actor?: Actor;
|
|
3091
|
+
headers: Readonly<Record<string, string>>;
|
|
3092
|
+
/** Set when the route declared `idempotent: true`. */
|
|
3093
|
+
idempotencyKey?: string;
|
|
3094
|
+
/**
|
|
3095
|
+
* Callback surface: verify the request signature against the named tenant
|
|
3096
|
+
* secret BEFORE trusting the body. Throws on mismatch. The dev-harness
|
|
3097
|
+
* doctor rejects callback handlers that never call this.
|
|
3098
|
+
*/
|
|
3099
|
+
verifySignature(secretName: string, opts?: SignatureVerifyOptions): Promise<void>;
|
|
3100
|
+
}
|
|
3101
|
+
interface PluginResponse {
|
|
3102
|
+
/** Default 200. */
|
|
3103
|
+
status?: number;
|
|
3104
|
+
body?: unknown;
|
|
3105
|
+
headers?: Record<string, string>;
|
|
3106
|
+
}
|
|
3107
|
+
type PluginRouteHandler = (req: PluginRequest, ctx: PluginContext) => Promise<PluginResponse>;
|
|
3108
|
+
/** Host defaults enforced around every handler invocation. */
|
|
3109
|
+
declare const PLUGIN_ROUTE_LIMITS: {
|
|
3110
|
+
/** Handler timeout (ms) — public/player/admin surfaces. */
|
|
3111
|
+
readonly timeoutMs: 5000;
|
|
3112
|
+
/** Handler timeout (ms) — callback surface (providers can be slow). */
|
|
3113
|
+
readonly callbackTimeoutMs: 10000;
|
|
3114
|
+
/** Serialized response body cap in bytes. */
|
|
3115
|
+
readonly maxResponseBytes: 1048576;
|
|
3116
|
+
/** Default public-surface rate limit when the manifest omits one. */
|
|
3117
|
+
readonly defaultRateLimit: {
|
|
3118
|
+
readonly windowSec: 60;
|
|
3119
|
+
readonly max: 60;
|
|
3120
|
+
};
|
|
3121
|
+
};
|
|
3122
|
+
|
|
3123
|
+
/**
|
|
3124
|
+
* Scheduled plugin jobs. Declared in the manifest, implemented as named
|
|
3125
|
+
* handlers in `definePlugin({ handlers.jobs })`, executed by the JobScheduler
|
|
3126
|
+
* in `apps/worker` per `(tenant, enabled plugin, job)` under a Redis lock so
|
|
3127
|
+
* exactly one worker runs a due job. Missed schedules coalesce to ONE
|
|
3128
|
+
* catch-up run; handlers must be idempotent regardless.
|
|
3129
|
+
*/
|
|
3130
|
+
interface PluginJobDecl {
|
|
3131
|
+
/** 5-field cron expression, UTC. Minimum effective interval: 1 minute. */
|
|
3132
|
+
schedule: string;
|
|
3133
|
+
/** Name of the implementation in `definePlugin({ handlers: { jobs } })`. */
|
|
3134
|
+
handler: string;
|
|
3135
|
+
/** Default 300, max 900. */
|
|
3136
|
+
timeoutSec?: number;
|
|
3137
|
+
}
|
|
3138
|
+
type PluginJobHandler = (ctx: PluginContext) => Promise<void>;
|
|
3139
|
+
declare const PLUGIN_JOB_LIMITS: {
|
|
3140
|
+
readonly defaultTimeoutSec: 300;
|
|
3141
|
+
readonly maxTimeoutSec: 900;
|
|
3142
|
+
/** Consecutive failures before `PLUGIN_JOB_FAILED` is emitted. */
|
|
3143
|
+
readonly failureStreakThreshold: 3;
|
|
3144
|
+
};
|
|
3145
|
+
|
|
3146
|
+
/**
|
|
3147
|
+
* Upgrade migrations — ordered dataset/settings transforms that run inside
|
|
3148
|
+
* `UpgradePluginCommand` when a tenant moves THROUGH `toVersion`. Each step is
|
|
3149
|
+
* recorded in `plugin_migrations` (running → completed | failed) and must be
|
|
3150
|
+
* idempotent: a failed upgrade leaves the OLD version running and a retry
|
|
3151
|
+
* resumes from the failed step's checkpoint.
|
|
3152
|
+
*
|
|
3153
|
+
* Migrations are forward-only. Rollback = pin to the old version, which never
|
|
3154
|
+
* re-runs old migrations — so dataset schemas must stay read-compatible one
|
|
3155
|
+
* version back.
|
|
3156
|
+
*/
|
|
3157
|
+
interface PluginMigrationDecl {
|
|
3158
|
+
/** Runs when upgrading through this version (semver in (from, to]). */
|
|
3159
|
+
toVersion: string;
|
|
3160
|
+
/** Name of the implementation in `definePlugin({ handlers: { migrations } })`. */
|
|
3161
|
+
handler: string;
|
|
3162
|
+
description: string;
|
|
3163
|
+
}
|
|
3164
|
+
/**
|
|
3165
|
+
* Batch-transform helper handed to migration handlers. Iterates an own
|
|
3166
|
+
* dataset in batches of 500, applies the transform, upserts the result and
|
|
3167
|
+
* checkpoints the cursor into the `plugin_migrations` row after every batch —
|
|
3168
|
+
* a re-run after a mid-batch failure resumes where it stopped.
|
|
3169
|
+
*/
|
|
3170
|
+
interface PluginMigrationHelper {
|
|
3171
|
+
transformDataset<T = Record<string, unknown>>(dataset: string, transform: (record: {
|
|
3172
|
+
key: string;
|
|
3173
|
+
value: T;
|
|
3174
|
+
}) => Promise<{
|
|
3175
|
+
key: string;
|
|
3176
|
+
value: T;
|
|
3177
|
+
} | null> | {
|
|
3178
|
+
key: string;
|
|
3179
|
+
value: T;
|
|
3180
|
+
} | null): Promise<{
|
|
3181
|
+
processed: number;
|
|
3182
|
+
}>;
|
|
3183
|
+
}
|
|
3184
|
+
type PluginMigrationHandler = (ctx: PluginContext, helper: PluginMigrationHelper) => Promise<void>;
|
|
3185
|
+
declare const PLUGIN_MIGRATION_LIMITS: {
|
|
3186
|
+
readonly batchSize: 500;
|
|
3187
|
+
/** Per-step timeout (ms). */
|
|
3188
|
+
readonly stepTimeoutMs: 900000;
|
|
3189
|
+
};
|
|
3190
|
+
|
|
3191
|
+
/**
|
|
3192
|
+
* Backoffice surface DESCRIPTORS — data only, no UI is built now. Plugins
|
|
3193
|
+
* declare navigation entries and generic page blocks; the platform stores the
|
|
3194
|
+
* descriptors with the manifest snapshot and serves them so a future
|
|
3195
|
+
* backoffice can render plugin UI generically, pulling data from the plugin's
|
|
3196
|
+
* own `admin`-surface routes (the doctor validates every `dataRoute` /
|
|
3197
|
+
* `submitRoute` references a declared admin route).
|
|
3198
|
+
*/
|
|
3199
|
+
type PluginSurfaceBlock = {
|
|
3200
|
+
type: "table";
|
|
3201
|
+
dataRoute: string;
|
|
3202
|
+
columns: Array<{
|
|
3203
|
+
key: string;
|
|
3204
|
+
label: string;
|
|
3205
|
+
}>;
|
|
3206
|
+
} | {
|
|
3207
|
+
type: "stats";
|
|
3208
|
+
dataRoute: string;
|
|
3209
|
+
} | {
|
|
3210
|
+
type: "form";
|
|
3211
|
+
submitRoute: string;
|
|
3212
|
+
fields: PluginSettingsFieldDescriptor[];
|
|
3213
|
+
} | {
|
|
3214
|
+
type: "json";
|
|
3215
|
+
dataRoute: string;
|
|
3216
|
+
};
|
|
3217
|
+
interface PluginSurfacePage {
|
|
3218
|
+
key: string;
|
|
3219
|
+
title: string;
|
|
3220
|
+
blocks: PluginSurfaceBlock[];
|
|
3221
|
+
}
|
|
3222
|
+
interface PluginSurfacesDecl {
|
|
3223
|
+
backoffice?: {
|
|
3224
|
+
nav?: Array<{
|
|
3225
|
+
label: string;
|
|
3226
|
+
pageKey: string;
|
|
3227
|
+
}>;
|
|
3228
|
+
pages?: PluginSurfacePage[];
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
/**
|
|
3233
|
+
* The Flows framework (PLAYER_ACCOUNT_BUILD_PROMPT.md §2) — the extensibility
|
|
3234
|
+
* backbone that lets a plugin participate in the core signup / KYC / deposit /
|
|
3235
|
+
* withdrawal flows WITHOUT a workflow engine. A Flow is a named sequence of
|
|
3236
|
+
* stages executed inside core services; each stage exposes plugin
|
|
3237
|
+
* participation in one of two modes:
|
|
3238
|
+
*
|
|
3239
|
+
* - `observe` — fire-and-isolate, AFTER commit, driven off the domain event
|
|
3240
|
+
* in the consumers runtime. Cannot veto or modify; failures never affect the
|
|
3241
|
+
* flow. The default, and all compliance-sensitive stages allow only this.
|
|
3242
|
+
* - `intercept` — runs INSIDE the flow, before commit, and may return a typed
|
|
3243
|
+
* patch (`modify` / `reject`). Wrapped in the same timeout/circuit machinery
|
|
3244
|
+
* as plugin routes; the fail policy is per stage (closed stages reject the
|
|
3245
|
+
* action when the hook errors, open stages proceed without it). An intercept
|
|
3246
|
+
* hook can never touch money, change tenant/player identity, or bypass a
|
|
3247
|
+
* core validator — the host re-validates every patch.
|
|
3248
|
+
*/
|
|
3249
|
+
type FlowName = "signup" | "kyc" | "deposit" | "withdrawal";
|
|
3250
|
+
type FlowStageMode = "observe" | "intercept";
|
|
3251
|
+
type FlowFailPolicy = "closed" | "open";
|
|
3252
|
+
interface FlowHookDecl {
|
|
3253
|
+
flow: FlowName;
|
|
3254
|
+
/** Stage name from {@link FLOW_STAGE_CATALOG} — validated at publish time. */
|
|
3255
|
+
stage: FlowStageName;
|
|
3256
|
+
mode: FlowStageMode;
|
|
3257
|
+
/** Named handler in `definePlugin({ handlers: { flows } })`. */
|
|
3258
|
+
handler: string;
|
|
3259
|
+
/** Intercept budget. Default 2000, max 5000. */
|
|
3260
|
+
timeoutMs?: number;
|
|
3261
|
+
}
|
|
3262
|
+
/**
|
|
3263
|
+
* Handler signature: return a patch (intercept) or nothing (observe). The
|
|
3264
|
+
* `any` defaults keep specifically-typed handlers assignable to the
|
|
3265
|
+
* `handlers.flows` map (inputs are host-validated per stage regardless).
|
|
3266
|
+
*/
|
|
3267
|
+
type FlowHookHandler<I = any, O = any> = (ctx: PluginContext, input: Readonly<I>) => Promise<O | void> | O | void;
|
|
3268
|
+
/**
|
|
3269
|
+
* The initial stage catalog (§2.2) — extended via decision record ONLY. Each
|
|
3270
|
+
* entry pins which modes a stage admits and its intercept fail policy. An
|
|
3271
|
+
* undeclared stage fails publish; a stage used beyond its allowed mode too.
|
|
3272
|
+
*/
|
|
3273
|
+
declare const FLOW_STAGE_CATALOG: {
|
|
3274
|
+
readonly "signup.validate": {
|
|
3275
|
+
readonly flow: "signup";
|
|
3276
|
+
readonly modes: readonly ["intercept"];
|
|
3277
|
+
readonly failPolicy: "closed";
|
|
3278
|
+
};
|
|
3279
|
+
readonly "signup.completed": {
|
|
3280
|
+
readonly flow: "signup";
|
|
3281
|
+
readonly modes: readonly ["observe"];
|
|
3282
|
+
readonly failPolicy: "open";
|
|
3283
|
+
};
|
|
3284
|
+
readonly "kyc.requirements.resolve": {
|
|
3285
|
+
readonly flow: "kyc";
|
|
3286
|
+
readonly modes: readonly ["intercept"];
|
|
3287
|
+
readonly failPolicy: "closed";
|
|
3288
|
+
};
|
|
3289
|
+
readonly "kyc.document.submitted": {
|
|
3290
|
+
readonly flow: "kyc";
|
|
3291
|
+
readonly modes: readonly ["observe"];
|
|
3292
|
+
readonly failPolicy: "open";
|
|
3293
|
+
};
|
|
3294
|
+
readonly "kyc.decision": {
|
|
3295
|
+
readonly flow: "kyc";
|
|
3296
|
+
readonly modes: readonly ["observe"];
|
|
3297
|
+
readonly failPolicy: "open";
|
|
3298
|
+
};
|
|
3299
|
+
readonly "deposit.validate": {
|
|
3300
|
+
readonly flow: "deposit";
|
|
3301
|
+
readonly modes: readonly ["intercept"];
|
|
3302
|
+
readonly failPolicy: "closed";
|
|
3303
|
+
};
|
|
3304
|
+
readonly "deposit.completed": {
|
|
3305
|
+
readonly flow: "deposit";
|
|
3306
|
+
readonly modes: readonly ["observe"];
|
|
3307
|
+
readonly failPolicy: "open";
|
|
3308
|
+
};
|
|
3309
|
+
readonly "withdrawal.validate": {
|
|
3310
|
+
readonly flow: "withdrawal";
|
|
3311
|
+
readonly modes: readonly ["intercept"];
|
|
3312
|
+
readonly failPolicy: "closed";
|
|
3313
|
+
};
|
|
3314
|
+
readonly "withdrawal.review": {
|
|
3315
|
+
readonly flow: "withdrawal";
|
|
3316
|
+
readonly modes: readonly ["intercept"];
|
|
3317
|
+
readonly failPolicy: "open";
|
|
3318
|
+
};
|
|
3319
|
+
readonly "withdrawal.settled": {
|
|
3320
|
+
readonly flow: "withdrawal";
|
|
3321
|
+
readonly modes: readonly ["observe"];
|
|
3322
|
+
readonly failPolicy: "open";
|
|
3323
|
+
};
|
|
3324
|
+
};
|
|
3325
|
+
type FlowStageName = keyof typeof FLOW_STAGE_CATALOG;
|
|
3326
|
+
declare const FLOW_STAGE_NAMES: FlowStageName[];
|
|
3327
|
+
declare const FLOW_HOOK_DEFAULT_TIMEOUT_MS = 2000;
|
|
3328
|
+
declare const FLOW_HOOK_MAX_TIMEOUT_MS = 5000;
|
|
3329
|
+
/** Generic intercept verdict: modify a whitelisted subset, or reject. */
|
|
3330
|
+
interface FlowInterceptPatch<M = Record<string, unknown>> {
|
|
3331
|
+
modify?: Partial<M>;
|
|
3332
|
+
reject?: {
|
|
3333
|
+
code: string;
|
|
3334
|
+
message: string;
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
interface SignupValidateInput {
|
|
3338
|
+
email?: string;
|
|
3339
|
+
username?: string;
|
|
3340
|
+
profile?: Record<string, unknown>;
|
|
3341
|
+
metadata?: Record<string, unknown>;
|
|
3342
|
+
}
|
|
3343
|
+
type SignupValidateOutput = FlowInterceptPatch<{
|
|
3344
|
+
profile: Record<string, unknown>;
|
|
3345
|
+
metadata: Record<string, unknown>;
|
|
3346
|
+
}>;
|
|
3347
|
+
interface KycRequirementsResolveInput {
|
|
3348
|
+
playerId: string;
|
|
3349
|
+
trigger: string;
|
|
3350
|
+
baseRequirements: Array<{
|
|
3351
|
+
documentTypeKey: string;
|
|
3352
|
+
required: boolean;
|
|
3353
|
+
}>;
|
|
3354
|
+
}
|
|
3355
|
+
/** Add/remove requirement items — only known document-type keys are honored. */
|
|
3356
|
+
interface KycRequirementsResolveOutput {
|
|
3357
|
+
add?: string[];
|
|
3358
|
+
satisfied?: string[];
|
|
3359
|
+
}
|
|
3360
|
+
interface DepositValidateInput {
|
|
3361
|
+
playerId: string;
|
|
3362
|
+
amount: string;
|
|
3363
|
+
currency: string;
|
|
3364
|
+
providerKey?: string;
|
|
3365
|
+
methodKey?: string;
|
|
3366
|
+
metadata?: Record<string, unknown>;
|
|
3367
|
+
}
|
|
3368
|
+
type DepositValidateOutput = FlowInterceptPatch<{
|
|
3369
|
+
metadata: Record<string, unknown>;
|
|
3370
|
+
}>;
|
|
3371
|
+
interface WithdrawalValidateInput {
|
|
3372
|
+
playerId: string;
|
|
3373
|
+
amount: string;
|
|
3374
|
+
currency: string;
|
|
3375
|
+
methodKey?: string;
|
|
3376
|
+
metadata?: Record<string, unknown>;
|
|
3377
|
+
}
|
|
3378
|
+
type WithdrawalValidateOutput = FlowInterceptPatch<{
|
|
3379
|
+
metadata: Record<string, unknown>;
|
|
3380
|
+
}>;
|
|
3381
|
+
interface WithdrawalReviewInput {
|
|
3382
|
+
withdrawalId: string;
|
|
3383
|
+
playerId: string;
|
|
3384
|
+
amount: string;
|
|
3385
|
+
currency: string;
|
|
3386
|
+
riskContext?: {
|
|
3387
|
+
score?: number;
|
|
3388
|
+
flags?: string[];
|
|
3389
|
+
};
|
|
3390
|
+
}
|
|
3391
|
+
/** ADVISORY — the final verdict is always core/back-office (§2.2). */
|
|
3392
|
+
interface WithdrawalReviewOutput {
|
|
3393
|
+
reviewVerdict?: "approve" | "hold" | "reject";
|
|
3394
|
+
reason?: string;
|
|
3395
|
+
}
|
|
3396
|
+
/**
|
|
3397
|
+
* Tenant flow ownership (§2.3): `flows.<name>.mode` tenant setting. When a
|
|
3398
|
+
* flow is plugin-owned, the core's default player routes for it return
|
|
3399
|
+
* 409 FLOW_DELEGATED with `details.pluginKey`.
|
|
3400
|
+
*/
|
|
3401
|
+
type FlowMode = "core" | `plugin:${string}`;
|
|
3402
|
+
declare const flowModeSettingKey: (flow: FlowName) => string;
|
|
3403
|
+
|
|
3404
|
+
/**
|
|
3405
|
+
* Plugin actions (manifest v3) — SDK-exposed aliases of a plugin's declared
|
|
3406
|
+
* `public`/`player` routes, served to frontends through the host's extension
|
|
3407
|
+
* catalog (`GET /api/ext/_catalog`) and called generically via
|
|
3408
|
+
* `sdk.ext("<key>").call("<action>", input)`.
|
|
3409
|
+
*
|
|
3410
|
+
* Spec: docs/prompts/PLUGIN_ACTIONS_RUNTIME_BUILD_PROMPT.md §2. The doctor
|
|
3411
|
+
* enforces every structural rule below at build/publish time (fail closed):
|
|
3412
|
+
*
|
|
3413
|
+
* - `route` references a declared route with surface `public` or `player` —
|
|
3414
|
+
* never `admin` or `callback`.
|
|
3415
|
+
* - `kind: "query"` ⇒ GET; `kind: "mutation"` ⇒ POST/PUT/DELETE.
|
|
3416
|
+
* - `idempotent` matches the referenced route declaration's flag.
|
|
3417
|
+
* - keys unique per plugin, camelCase.
|
|
3418
|
+
* - `emits` ⊆ manifest-declared emitted events (short form — see below);
|
|
3419
|
+
* `permissions.frontendEvents` ⊆ emitted events.
|
|
3420
|
+
* - `frontend.widgets[].dataAction` is a declared `query` action and
|
|
3421
|
+
* `actions[]` are declared mutations.
|
|
3422
|
+
*
|
|
3423
|
+
* Event-type convention: `emits` and `permissions.frontendEvents` use the
|
|
3424
|
+
* SHORT event type — the emitted name without its `plugin.` prefix, e.g.
|
|
3425
|
+
* `"cashback.claimed"` for an emitted `"plugin.cashback.claimed"`. The same
|
|
3426
|
+
* short type is what reaches player sockets as `ExtPluginEvent.data.type`.
|
|
3427
|
+
*/
|
|
3428
|
+
/** camelCase action keys — a plugin's stable public API. */
|
|
3429
|
+
declare const ACTION_KEY_RE: RegExp;
|
|
3430
|
+
interface PluginActionDecl {
|
|
3431
|
+
/** camelCase, unique per plugin, stable public API. */
|
|
3432
|
+
key: string;
|
|
3433
|
+
/** `"<METHOD> <path>"` referencing a declared route, e.g. `"POST /claim"`. */
|
|
3434
|
+
route: string;
|
|
3435
|
+
title: string;
|
|
3436
|
+
description?: string;
|
|
3437
|
+
kind: "query" | "mutation";
|
|
3438
|
+
/** Mutations requiring `Idempotency-Key` (must match the route decl). */
|
|
3439
|
+
idempotent?: boolean;
|
|
3440
|
+
/** Short plugin event types this action may cause, e.g. `"cashback.claimed"`. */
|
|
3441
|
+
emits?: string[];
|
|
3442
|
+
deprecated?: string;
|
|
3443
|
+
}
|
|
3444
|
+
interface PluginFrontendWidgetDecl {
|
|
3445
|
+
key: string;
|
|
3446
|
+
title: string;
|
|
3447
|
+
slot: "lobby" | "account" | "cashier" | "game-sidebar";
|
|
3448
|
+
/** Must reference a declared `kind: "query"` action. */
|
|
3449
|
+
dataAction: string;
|
|
3450
|
+
/** Mutation action keys the widget may invoke. */
|
|
3451
|
+
actions?: string[];
|
|
3452
|
+
}
|
|
3453
|
+
interface PluginFrontendDecl {
|
|
3454
|
+
widgets?: PluginFrontendWidgetDecl[];
|
|
3455
|
+
}
|
|
3456
|
+
/**
|
|
3457
|
+
* Parse an action's `route` reference into method + path. Returns null when
|
|
3458
|
+
* the string is not of the form `"<METHOD> <path>"`.
|
|
3459
|
+
*/
|
|
3460
|
+
declare function parseActionRoute(route: string): {
|
|
3461
|
+
method: string;
|
|
3462
|
+
path: string;
|
|
3463
|
+
} | null;
|
|
3464
|
+
/** Short event type (`<key>.<type>`) → full emitted name (`plugin.<key>.<type>`). */
|
|
3465
|
+
declare function fullPluginEventName(shortType: string): string;
|
|
3466
|
+
/** Full emitted name → short event type, or null if not `plugin.`-prefixed. */
|
|
3467
|
+
declare function shortPluginEventType(fullName: string): string | null;
|
|
3468
|
+
|
|
3469
|
+
/** What a plugin *is* to the platform — drives wiring (provider bridge, consumers, backoffice). */
|
|
3470
|
+
type PluginKind = "provider" | "backoffice" | "consumer" | "integration" | "content";
|
|
3471
|
+
/** Capabilities a provider-kind plugin's adapter implements. */
|
|
3472
|
+
type ProviderCapability = "launch" | "balance" | "bet" | "settle" | "rollback" | "closeRound" | "freeSpin" | "bonusWin";
|
|
3473
|
+
/**
|
|
3474
|
+
* The declarative descriptor every plugin ships. The host reads this to
|
|
3475
|
+
* install, grant permissions, render/validate settings and wire hooks. The
|
|
3476
|
+
* manifest is snapshotted into `plugin_versions` at publish time — treat every
|
|
3477
|
+
* field as a stable wire contract.
|
|
3478
|
+
*/
|
|
3479
|
+
interface PluginManifest {
|
|
3480
|
+
/** Unique slug, e.g. `sloterv`. Doubles as the registry key. */
|
|
3481
|
+
key: string;
|
|
3482
|
+
name: string;
|
|
3483
|
+
author: string;
|
|
3484
|
+
/** SemVer version of this plugin build. */
|
|
3485
|
+
version: string;
|
|
3486
|
+
kind: PluginKind;
|
|
3487
|
+
/** SemVer range against the host's RUNTIME_API_VERSION, e.g. `>=0.1.0 <0.2.0`. */
|
|
3488
|
+
runtimeCompat: string;
|
|
3489
|
+
description?: string;
|
|
3490
|
+
/** Explicit allowlists. Anything not declared here is denied and audited. */
|
|
3491
|
+
permissions: PluginPermissions;
|
|
3492
|
+
/** Typed per-tenant settings; the backoffice renders a form from this. */
|
|
3493
|
+
settings: PluginSettingsSchema;
|
|
3494
|
+
/** Provider plugins only: the provider key this plugin owns + its capabilities. */
|
|
3495
|
+
provider?: {
|
|
3496
|
+
providerKey: string;
|
|
3497
|
+
capabilities: ProviderCapability[];
|
|
3498
|
+
};
|
|
3499
|
+
/**
|
|
3500
|
+
* Game-catalog content surface (§7b). Presence of this block IS the grant:
|
|
3501
|
+
* the host registers a tenant-owned catalog source for (tenant, plugin) on
|
|
3502
|
+
* enable and exposes `ctx.catalog`. Allowed kinds: provider | content |
|
|
3503
|
+
* integration (doctor-enforced). `sourceName` labels the source in the
|
|
3504
|
+
* backoffice (defaults to the plugin name).
|
|
3505
|
+
*/
|
|
3506
|
+
catalog?: {
|
|
3507
|
+
sourceName?: string;
|
|
3508
|
+
};
|
|
3509
|
+
/** Lifecycle hooks this plugin implements (informational; host still probes). */
|
|
3510
|
+
hooks?: Partial<Record<PluginHookName, true>>;
|
|
3511
|
+
/** Regions the plugin is allowed to run in (omitted = all). */
|
|
3512
|
+
regions?: string[];
|
|
3513
|
+
/** HTTP routes this plugin adds to the casino API (host PluginRouter). */
|
|
3514
|
+
routes?: PluginRouteDecl[];
|
|
3515
|
+
/** Plugin-owned datasets, provisioned at install, accessed via `ctx.datasets`. */
|
|
3516
|
+
datasets?: Record<string, PluginDatasetDecl>;
|
|
3517
|
+
/** Ordered dataset/settings migrations run by `UpgradePluginCommand`. */
|
|
3518
|
+
migrations?: PluginMigrationDecl[];
|
|
3519
|
+
/** Scheduled jobs run per tenant by the worker's JobScheduler. */
|
|
3520
|
+
jobs?: Record<string, PluginJobDecl>;
|
|
3521
|
+
/**
|
|
3522
|
+
* Flow-stage participation (PLAYER_ACCOUNT_BUILD_PROMPT.md §2). Each entry
|
|
3523
|
+
* must name a catalog stage, an allowed mode, and a handler in
|
|
3524
|
+
* `handlers.flows`; the stage must also be granted in `permissions.flows`.
|
|
3525
|
+
*/
|
|
3526
|
+
flows?: FlowHookDecl[];
|
|
3527
|
+
/**
|
|
3528
|
+
* Outbound HTTP allowlist for `ctx.http` — exact hosts or one-level
|
|
3529
|
+
* wildcards (`*.sloterv.com`). HTTPS only. Nothing declared = no egress.
|
|
3530
|
+
*/
|
|
3531
|
+
network?: {
|
|
3532
|
+
allowedHosts: string[];
|
|
3533
|
+
};
|
|
3534
|
+
/** Backoffice surface descriptors (data only — no UI is built now). */
|
|
3535
|
+
surfaces?: PluginSurfacesDecl;
|
|
3536
|
+
/** Task type to enqueue after install commits (async dataset seeding). */
|
|
3537
|
+
install?: {
|
|
3538
|
+
seedTask?: string;
|
|
3539
|
+
};
|
|
3540
|
+
/**
|
|
3541
|
+
* SDK-exposed action aliases of declared `public`/`player` routes, served
|
|
3542
|
+
* through the tenant extension catalog with JSON Schemas derived from the
|
|
3543
|
+
* routes' Zod declarations at publish time.
|
|
3544
|
+
*/
|
|
3545
|
+
actions?: PluginActionDecl[];
|
|
3546
|
+
/** Frontend widget descriptors binding catalog actions to casino UI slots. */
|
|
3547
|
+
frontend?: PluginFrontendDecl;
|
|
3548
|
+
}
|
|
3549
|
+
/** Release channels a version can be published to. */
|
|
3550
|
+
declare const PluginChannels: readonly ["dev", "beta", "stable"];
|
|
3551
|
+
type PluginChannel = (typeof PluginChannels)[number];
|
|
3552
|
+
/** Per-tenant enablement state (host-managed, `tenant_plugins.state`). */
|
|
3553
|
+
declare const TenantPluginStates: readonly ["installed", "enabled", "disabled", "errored"];
|
|
3554
|
+
type TenantPluginState = (typeof TenantPluginStates)[number];
|
|
3555
|
+
/** Catalog lifecycle of a published version (`plugin_versions.status`). */
|
|
3556
|
+
declare const PluginVersionStatuses: readonly ["draft", "published", "yanked"];
|
|
3557
|
+
type PluginVersionStatus = (typeof PluginVersionStatuses)[number];
|
|
3558
|
+
|
|
3559
|
+
/**
|
|
3560
|
+
* Async plugin tasks — long-running background work (install-time dataset
|
|
3561
|
+
* seeding, backfills) that must never run inside a lifecycle transaction.
|
|
3562
|
+
* Enqueued via `StartPluginTaskCommand` (or `ctx.tasks.start`), claimed and
|
|
3563
|
+
* executed by the TaskRunner in `apps/worker` with the full PluginContext.
|
|
3564
|
+
*
|
|
3565
|
+
* Handlers MUST be idempotent/resumable: a retry re-invokes the handler with
|
|
3566
|
+
* the last saved checkpoint, and `putMany` upserts make re-runs safe.
|
|
3567
|
+
*/
|
|
3568
|
+
interface PluginTaskProgress {
|
|
3569
|
+
/** Persist progress (0–100) + message so the backoffice can poll it. */
|
|
3570
|
+
report(percent: number, message?: string): Promise<void>;
|
|
3571
|
+
/** Checkpoint saved by a previous (failed/interrupted) attempt, if any. */
|
|
3572
|
+
readonly checkpoint: Record<string, unknown> | null;
|
|
3573
|
+
/** Persist a resumability cursor; survives worker crashes and retries. */
|
|
3574
|
+
saveCheckpoint(checkpoint: Record<string, unknown>): Promise<void>;
|
|
3575
|
+
}
|
|
3576
|
+
type PluginTaskHandler = (ctx: PluginContext, input: Record<string, unknown>, progress: PluginTaskProgress) => Promise<void>;
|
|
3577
|
+
declare const PLUGIN_TASK_LIMITS: {
|
|
3578
|
+
/** Task execution timeout (ms). */
|
|
3579
|
+
readonly timeoutMs: 900000;
|
|
3580
|
+
};
|
|
3581
|
+
|
|
3582
|
+
/**
|
|
3583
|
+
* Named implementations that manifest declarations reference by string:
|
|
3584
|
+
* `routes[].handler`, `jobs.<name>.handler`, `install.seedTask` /
|
|
3585
|
+
* `ctx.tasks.start` types, and `migrations[].handler`. The host validates at
|
|
3586
|
+
* load time that every referenced handler exists (`plugin doctor` reports any
|
|
3587
|
+
* mismatch before publish).
|
|
3588
|
+
*/
|
|
3589
|
+
interface PluginHandlers {
|
|
3590
|
+
routes?: Record<string, PluginRouteHandler>;
|
|
3591
|
+
jobs?: Record<string, PluginJobHandler>;
|
|
3592
|
+
tasks?: Record<string, PluginTaskHandler>;
|
|
3593
|
+
migrations?: Record<string, PluginMigrationHandler>;
|
|
3594
|
+
/** Flow-stage handlers referenced by `manifest.flows[].handler` (§2). */
|
|
3595
|
+
flows?: Record<string, FlowHookHandler>;
|
|
3596
|
+
}
|
|
3597
|
+
/**
|
|
3598
|
+
* The full authored shape of a plugin — what a plugin package's entry module
|
|
3599
|
+
* exports and what the host loads. `setup` runs when the host activates the
|
|
3600
|
+
* plugin for a tenant: register event handlers via `ctx.events.on` and (for
|
|
3601
|
+
* provider plugins) the adapter via `ctx.registerProviderAdapter`.
|
|
3602
|
+
*/
|
|
3603
|
+
interface PluginDefinition {
|
|
3604
|
+
manifest: PluginManifest;
|
|
3605
|
+
hooks?: PluginRuntimeHooks;
|
|
3606
|
+
handlers?: PluginHandlers;
|
|
3607
|
+
setup?(ctx: PluginContext): Promise<void> | void;
|
|
3608
|
+
}
|
|
3609
|
+
/** Identity helper that pins the authored object to the contract type. */
|
|
3610
|
+
declare function definePlugin(definition: PluginDefinition): PluginDefinition;
|
|
3611
|
+
|
|
3612
|
+
/** Persisted per-action schema document (draft-07 JSON Schemas, JSON-safe). */
|
|
3613
|
+
interface PluginActionSchema {
|
|
3614
|
+
/**
|
|
3615
|
+
* Input schema. For GET (query) actions this is the route's `query` schema;
|
|
3616
|
+
* for mutations it is the route's `body` schema. Absent when the route
|
|
3617
|
+
* declares none.
|
|
3618
|
+
*/
|
|
3619
|
+
input?: unknown;
|
|
3620
|
+
/** Output schema, when the route declares `output`. */
|
|
3621
|
+
output?: unknown;
|
|
3622
|
+
}
|
|
3623
|
+
/** Map persisted on `plugin_versions.actionSchemas`, keyed by action key. */
|
|
3624
|
+
type PluginActionSchemas = Record<string, PluginActionSchema>;
|
|
3625
|
+
/**
|
|
3626
|
+
* Derive the persisted action-schema map for a plugin definition. Assumes the
|
|
3627
|
+
* definition already passed the doctor (unknown route references are skipped,
|
|
3628
|
+
* not thrown — validation owns the error surface).
|
|
3629
|
+
*/
|
|
3630
|
+
declare function deriveActionSchemas(definition: PluginDefinition): PluginActionSchemas;
|
|
3631
|
+
|
|
3632
|
+
interface ActionValidationIssues {
|
|
3633
|
+
errors: string[];
|
|
3634
|
+
warnings: string[];
|
|
3635
|
+
}
|
|
3636
|
+
declare function validateActionDecls(definition: PluginDefinition): ActionValidationIssues;
|
|
3637
|
+
|
|
3638
|
+
/** Relative path → file contents. Paths use `/` separators. */
|
|
3639
|
+
type GeneratedClientFiles = Record<string, string>;
|
|
3640
|
+
interface GenerateClientOptions {
|
|
3641
|
+
/** Peer range on the Casino SDK. Default `">=0.3.0"`. */
|
|
3642
|
+
sdkPeerRange?: string;
|
|
3643
|
+
}
|
|
3644
|
+
declare function pascalCase(value: string): string;
|
|
3645
|
+
/**
|
|
3646
|
+
* Print a TypeScript type for a derived JSON Schema. Handles exactly the
|
|
3647
|
+
* shapes zod-to-json-schema emits for plugin route schemas; anything outside
|
|
3648
|
+
* the subset degrades to `unknown` (never throws — codegen must not be able
|
|
3649
|
+
* to fail on an exotic schema the doctor already accepted).
|
|
3650
|
+
*/
|
|
3651
|
+
declare function jsonSchemaToTsType(schema: unknown): string;
|
|
3652
|
+
/**
|
|
3653
|
+
* Generate the full `@cwe-plugins/<key>-client` package for a definition.
|
|
3654
|
+
* Pure and deterministic — no filesystem access, no clock, no randomness.
|
|
3655
|
+
*/
|
|
3656
|
+
declare function generateClientPackage(definition: PluginDefinition, opts?: GenerateClientOptions): GeneratedClientFiles;
|
|
3657
|
+
|
|
3658
|
+
export { ACTION_KEY_RE, type ActionValidationIssues, type Actor, type ActorType, AffiliateEvents, type BalanceResult, BetEvents, type BetRecordDTO, type BetResult, BonusEvents, CATALOG_ALLOWED_KINDS, CATALOG_IMPORT_TASK_TYPE, CashierEvents, type CatalogCategoryNodeDTO, type CatalogEligibilityParams, CatalogEvents, type CatalogGameDTO, type CatalogImportSummary, type CatalogProviderDTO, type CloseRoundInput, type CreateSessionInput, type CreateSessionResult, type DataScope, DataScopes, type DepositValidateInput, type DepositValidateOutput, type DomainEvent, type DomainEventName, DomainEventNames, type DomainEventPayloads, FLOW_HOOK_DEFAULT_TIMEOUT_MS, FLOW_HOOK_MAX_TIMEOUT_MS, FLOW_STAGE_CATALOG, FLOW_STAGE_NAMES, type FlowFailPolicy, type FlowHookDecl, type FlowHookHandler, type FlowInterceptPatch, type FlowMode, type FlowName, type FlowStageMode, type FlowStageName, type GameSessionDTO, type GenerateClientOptions, type GeneratedClientFiles, type ImportSummaryLite, type KycDocumentDTO, type KycDocumentRef, type KycProviderAdapter, type KycRequestDTO, type KycRequirementsResolveInput, type KycRequirementsResolveOutput, type KycStateDTO, type LedgerEntryDTO, type MessagingAdapter, type Money, type NormalizedCatalog, type NormalizedCatalogDelta, type NormalizedCategory, type NormalizedCurrencyGroup, type NormalizedGame, type NormalizedGameCategoryLink, type NormalizedLaunchCodes, type NormalizedProvider, type NormalizedRestrictionGroup, type OwnGameSummary, PLUGIN_CATALOG_LIMITS, PLUGIN_DATASET_LIMITS, PLUGIN_HTTP_LIMITS, PLUGIN_JOB_LIMITS, PLUGIN_MIGRATION_LIMITS, PLUGIN_ROUTE_LIMITS, PLUGIN_TASK_LIMITS, type PaymentProviderAdapter, type PlaceBetInput, type PlayerBetDTO, PlayerEvents, type PlayerLimitDTO, type PlayerSummaryDTO, type PluginActionDecl, type PluginActionSchema, type PluginActionSchemas, type PluginCatalogCapability, type PluginCatalogSourceAdapter, type PluginChannel, PluginChannels, type PluginCommand, type PluginContext, type PluginDatasetCollection, type PluginDatasetDecl, type PluginDatasetQuery, type PluginDefinition, type PluginEmittedEvent, PluginEvents, type PluginFetchInit, type PluginFetchResponse, type PluginFrontendDecl, type PluginFrontendWidgetDecl, type PluginHandlers, type PluginHookName, PluginHookNames, type PluginJobDecl, type PluginJobHandler, type PluginKind, type PluginManifest, type PluginMessage, type PluginMigrationDecl, type PluginMigrationHandler, type PluginMigrationHelper, type PluginPaymentMethodDescriptor, type PluginPermissions, type PluginRequest, type PluginResponse, type PluginRouteDecl, type PluginRouteHandler, type PluginRouteMethod, type PluginRouteSurface, type PluginRuntimeHooks, type PluginScope, PluginScopes, type PluginSettingsField, type PluginSettingsFieldDescriptor, type PluginSettingsFieldType, type PluginSettingsSchema, type PluginSettingsSchemaDescriptor, type PluginSurfaceBlock, type PluginSurfacePage, type PluginSurfacesDecl, type PluginTaskHandler, type PluginTaskProgress, type PluginVersionStatus, PluginVersionStatuses, type ProviderAdapter, type ProviderCapability, type ProviderContext, ProviderEvents, type PspDepositInput, type PspDepositResult, type PspWithdrawalInput, type PspWithdrawalResult, READ_MODEL_MAX_PAGE, type ReadModelDefinitions, type ReadModelName, type ReadModelPage, type ReadModelParams, ReadModelRequiredScopes, type ReadModelResult, type RollbackInput, type RoundResult, type ScopedLogger, type SettleBetInput, type SignatureVerifyOptions, type SignupValidateInput, type SignupValidateOutput, type TenantContext, type TenantInfoDTO, type TenantPluginState, TenantPluginStates, type WalletBalanceDTO, WalletEvents, type WithdrawalReviewInput, type WithdrawalReviewOutput, type WithdrawalValidateInput, type WithdrawalValidateOutput, defaultSettingsValues, definePlugin, deriveActionSchemas, flowModeSettingKey, fullPluginEventName, generateClientPackage, jsonSchemaToTsType, parseActionRoute, pascalCase, pluginEventPrefix, serializeSettingsSchema, settingsField, settingsZodObject, shortPluginEventType, validateActionDecls };
|