@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.
@@ -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 };