@openora/core 0.4.1-canary.39 → 0.4.1-canary.40

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.
Files changed (142) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/audit/plugin.d.ts.map +1 -1
  3. package/dist/audit/plugin.js +27 -0
  4. package/dist/audit/plugin.js.map +1 -1
  5. package/dist/common/errors/errors.classes.js +3 -3
  6. package/dist/common/errors/errors.classes.js.map +1 -1
  7. package/dist/common/errors/errors.mapper.js +1 -1
  8. package/dist/common/errors/errors.mapper.js.map +1 -1
  9. package/dist/compliance/__tests__/kyc.service.test.js +5 -0
  10. package/dist/compliance/__tests__/kyc.service.test.js.map +1 -1
  11. package/dist/compliance/service/kyc.service.js +1 -1
  12. package/dist/compliance/service/kyc.service.js.map +1 -1
  13. package/dist/contracts/adapters/identity-reader.d.ts +9 -3
  14. package/dist/contracts/adapters/identity-reader.d.ts.map +1 -1
  15. package/dist/contracts/adapters/identity-reader.js.map +1 -1
  16. package/dist/contracts/adapters/index.d.ts +2 -0
  17. package/dist/contracts/adapters/index.d.ts.map +1 -1
  18. package/dist/contracts/adapters/index.js +1 -0
  19. package/dist/contracts/adapters/index.js.map +1 -1
  20. package/dist/contracts/adapters/tag-evaluation-commands.d.ts +10 -0
  21. package/dist/contracts/adapters/tag-evaluation-commands.d.ts.map +1 -0
  22. package/dist/contracts/adapters/tag-evaluation-commands.js +11 -0
  23. package/dist/contracts/adapters/tag-evaluation-commands.js.map +1 -0
  24. package/dist/contracts/adapters/wallet-reader.d.ts +8 -0
  25. package/dist/contracts/adapters/wallet-reader.d.ts.map +1 -1
  26. package/dist/contracts/adapters/wallet-reader.js.map +1 -1
  27. package/dist/contracts/schemas/events.d.ts +57 -0
  28. package/dist/contracts/schemas/events.d.ts.map +1 -1
  29. package/dist/contracts/schemas/events.js +12 -0
  30. package/dist/contracts/schemas/events.js.map +1 -1
  31. package/dist/contracts/schemas/platform-config.d.ts +9 -1
  32. package/dist/contracts/schemas/platform-config.d.ts.map +1 -1
  33. package/dist/contracts/schemas/player.d.ts +10 -2
  34. package/dist/contracts/schemas/player.d.ts.map +1 -1
  35. package/dist/contracts/schemas/tag.d.ts +65 -1
  36. package/dist/contracts/schemas/tag.d.ts.map +1 -1
  37. package/dist/contracts/schemas/tag.js +10 -0
  38. package/dist/contracts/schemas/tag.js.map +1 -1
  39. package/dist/iam/service/iam.service.d.ts +1 -1
  40. package/dist/pam/identity/adapters/identity-reader.service.d.ts +6 -4
  41. package/dist/pam/identity/adapters/identity-reader.service.d.ts.map +1 -1
  42. package/dist/pam/identity/adapters/identity-reader.service.js +19 -1
  43. package/dist/pam/identity/adapters/identity-reader.service.js.map +1 -1
  44. package/dist/pam/identity/service/identity.service.d.ts.map +1 -1
  45. package/dist/pam/identity/service/identity.service.js +4 -0
  46. package/dist/pam/identity/service/identity.service.js.map +1 -1
  47. package/dist/pam/player-management/__tests__/player.router.test.js +2 -2
  48. package/dist/pam/player-management/__tests__/player.router.test.js.map +1 -1
  49. package/dist/pam/player-management/__tests__/player.service.test.js +28 -2
  50. package/dist/pam/player-management/__tests__/player.service.test.js.map +1 -1
  51. package/dist/pam/player-management/contract/index.d.ts +21 -1
  52. package/dist/pam/player-management/contract/index.d.ts.map +1 -1
  53. package/dist/pam/player-management/plugin.js +1 -1
  54. package/dist/pam/player-management/plugin.js.map +1 -1
  55. package/dist/pam/player-management/router/index.d.ts +17 -1
  56. package/dist/pam/player-management/router/index.d.ts.map +1 -1
  57. package/dist/pam/player-management/service/player.service.d.ts +3 -1
  58. package/dist/pam/player-management/service/player.service.d.ts.map +1 -1
  59. package/dist/pam/player-management/service/player.service.js +14 -1
  60. package/dist/pam/player-management/service/player.service.js.map +1 -1
  61. package/dist/pam/tag/__tests__/tag-evaluation.service.test.js +600 -17
  62. package/dist/pam/tag/__tests__/tag-evaluation.service.test.js.map +1 -1
  63. package/dist/pam/tag/__tests__/tag-mappers.test.js +25 -0
  64. package/dist/pam/tag/__tests__/tag-mappers.test.js.map +1 -1
  65. package/dist/pam/tag/__tests__/tag.router.test.js +71 -1
  66. package/dist/pam/tag/__tests__/tag.router.test.js.map +1 -1
  67. package/dist/pam/tag/__tests__/tag.service.test.js +342 -15
  68. package/dist/pam/tag/__tests__/tag.service.test.js.map +1 -1
  69. package/dist/pam/tag/contract/index.d.ts +97 -0
  70. package/dist/pam/tag/contract/index.d.ts.map +1 -1
  71. package/dist/pam/tag/contract/index.js +3 -0
  72. package/dist/pam/tag/contract/index.js.map +1 -1
  73. package/dist/pam/tag/contract/player-tag-assign-metadata.d.ts +37 -0
  74. package/dist/pam/tag/contract/player-tag-assign-metadata.d.ts.map +1 -0
  75. package/dist/pam/tag/contract/player-tag-assign-metadata.js +20 -0
  76. package/dist/pam/tag/contract/player-tag-assign-metadata.js.map +1 -0
  77. package/dist/pam/tag/drizzle/migrations/0002_silent_skaar.sql +4 -0
  78. package/dist/pam/tag/drizzle/migrations/0003_overconfident_guardian.sql +3 -0
  79. package/dist/pam/tag/drizzle/migrations/meta/0002_snapshot.json +335 -0
  80. package/dist/pam/tag/drizzle/migrations/meta/0003_snapshot.json +343 -0
  81. package/dist/pam/tag/drizzle/migrations/meta/_journal.json +14 -0
  82. package/dist/pam/tag/migrate.d.ts.map +1 -1
  83. package/dist/pam/tag/migrate.js +34 -0
  84. package/dist/pam/tag/migrate.js.map +1 -1
  85. package/dist/pam/tag/plugin.d.ts.map +1 -1
  86. package/dist/pam/tag/plugin.js +35 -16
  87. package/dist/pam/tag/plugin.js.map +1 -1
  88. package/dist/pam/tag/router/index.d.ts +81 -0
  89. package/dist/pam/tag/router/index.d.ts.map +1 -1
  90. package/dist/pam/tag/router/index.js +34 -18
  91. package/dist/pam/tag/router/index.js.map +1 -1
  92. package/dist/pam/tag/schema/index.d.ts +42 -3
  93. package/dist/pam/tag/schema/index.d.ts.map +1 -1
  94. package/dist/pam/tag/schema/index.js +13 -2
  95. package/dist/pam/tag/schema/index.js.map +1 -1
  96. package/dist/pam/tag/seed/data/default-tag-rules.d.ts.map +1 -1
  97. package/dist/pam/tag/seed/data/default-tag-rules.js +28 -0
  98. package/dist/pam/tag/seed/data/default-tag-rules.js.map +1 -1
  99. package/dist/pam/tag/seed/data/default-tags.d.ts.map +1 -1
  100. package/dist/pam/tag/seed/data/default-tags.js +4 -0
  101. package/dist/pam/tag/seed/data/default-tags.js.map +1 -1
  102. package/dist/pam/tag/service/tag-evaluation.service.d.ts +113 -12
  103. package/dist/pam/tag/service/tag-evaluation.service.d.ts.map +1 -1
  104. package/dist/pam/tag/service/tag-evaluation.service.js +354 -35
  105. package/dist/pam/tag/service/tag-evaluation.service.js.map +1 -1
  106. package/dist/pam/tag/service/tag-mappers.d.ts +1 -1
  107. package/dist/pam/tag/service/tag-mappers.d.ts.map +1 -1
  108. package/dist/pam/tag/service/tag-mappers.js +1 -0
  109. package/dist/pam/tag/service/tag-mappers.js.map +1 -1
  110. package/dist/pam/tag/service/tag-rule.service.d.ts +3 -3
  111. package/dist/pam/tag/service/tag.service.d.ts +141 -11
  112. package/dist/pam/tag/service/tag.service.d.ts.map +1 -1
  113. package/dist/pam/tag/service/tag.service.js +242 -23
  114. package/dist/pam/tag/service/tag.service.js.map +1 -1
  115. package/dist/server/auth/permissions.d.ts +12 -0
  116. package/dist/server/auth/permissions.d.ts.map +1 -1
  117. package/dist/server/auth/permissions.js +2 -0
  118. package/dist/server/auth/permissions.js.map +1 -1
  119. package/dist/server/db/__tests__/migrate.test.js +79 -2
  120. package/dist/server/db/__tests__/migrate.test.js.map +1 -1
  121. package/dist/server/db/migrate.d.ts +11 -0
  122. package/dist/server/db/migrate.d.ts.map +1 -1
  123. package/dist/server/db/migrate.js +3 -0
  124. package/dist/server/db/migrate.js.map +1 -1
  125. package/dist/server/plugin-host/__tests__/core-plugins.test.js +1 -1
  126. package/dist/server/plugin-host/__tests__/core-plugins.test.js.map +1 -1
  127. package/dist/wallet/__tests__/wallet-auto-withdrawal.test.js +60 -0
  128. package/dist/wallet/__tests__/wallet-auto-withdrawal.test.js.map +1 -1
  129. package/dist/wallet/__tests__/wallet.service.test.js +41 -0
  130. package/dist/wallet/__tests__/wallet.service.test.js.map +1 -1
  131. package/dist/wallet/adapters/wallet-reader.service.d.ts +1 -0
  132. package/dist/wallet/adapters/wallet-reader.service.d.ts.map +1 -1
  133. package/dist/wallet/adapters/wallet-reader.service.js +18 -1
  134. package/dist/wallet/adapters/wallet-reader.service.js.map +1 -1
  135. package/dist/wallet/plugin.d.ts.map +1 -1
  136. package/dist/wallet/plugin.js +7 -4
  137. package/dist/wallet/plugin.js.map +1 -1
  138. package/dist/wallet/service/wallet.service.d.ts +4 -2
  139. package/dist/wallet/service/wallet.service.d.ts.map +1 -1
  140. package/dist/wallet/service/wallet.service.js +11 -1
  141. package/dist/wallet/service/wallet.service.js.map +1 -1
  142. package/package.json +1 -1
@@ -0,0 +1,343 @@
1
+ {
2
+ "id": "b6f2fa7d-7a99-4d8b-be04-afe757c33db6",
3
+ "prevId": "b949de8a-cdab-4056-bf0e-90d1199f1bf1",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "public.player_tag": {
8
+ "name": "player_tag",
9
+ "schema": "",
10
+ "columns": {
11
+ "id": {
12
+ "name": "id",
13
+ "type": "uuid",
14
+ "primaryKey": true,
15
+ "notNull": true,
16
+ "default": "gen_random_uuid()"
17
+ },
18
+ "player_id": {
19
+ "name": "player_id",
20
+ "type": "uuid",
21
+ "primaryKey": false,
22
+ "notNull": true
23
+ },
24
+ "tag_id": {
25
+ "name": "tag_id",
26
+ "type": "uuid",
27
+ "primaryKey": false,
28
+ "notNull": true
29
+ },
30
+ "assign_reason": {
31
+ "name": "assign_reason",
32
+ "type": "text",
33
+ "primaryKey": false,
34
+ "notNull": true
35
+ },
36
+ "assign_actor": {
37
+ "name": "assign_actor",
38
+ "type": "tag_assign_remove_source",
39
+ "typeSchema": "public",
40
+ "primaryKey": false,
41
+ "notNull": true
42
+ },
43
+ "assign_actor_user_id": {
44
+ "name": "assign_actor_user_id",
45
+ "type": "uuid",
46
+ "primaryKey": false,
47
+ "notNull": false
48
+ },
49
+ "assign_metadata": {
50
+ "name": "assign_metadata",
51
+ "type": "jsonb",
52
+ "primaryKey": false,
53
+ "notNull": false
54
+ },
55
+ "removed_at": {
56
+ "name": "removed_at",
57
+ "type": "timestamp with time zone",
58
+ "primaryKey": false,
59
+ "notNull": false
60
+ },
61
+ "removal_reason": {
62
+ "name": "removal_reason",
63
+ "type": "text",
64
+ "primaryKey": false,
65
+ "notNull": false
66
+ },
67
+ "removal_actor": {
68
+ "name": "removal_actor",
69
+ "type": "tag_assign_remove_source",
70
+ "typeSchema": "public",
71
+ "primaryKey": false,
72
+ "notNull": false
73
+ },
74
+ "removal_actor_user_id": {
75
+ "name": "removal_actor_user_id",
76
+ "type": "uuid",
77
+ "primaryKey": false,
78
+ "notNull": false
79
+ },
80
+ "updated_at": {
81
+ "name": "updated_at",
82
+ "type": "timestamp with time zone",
83
+ "primaryKey": false,
84
+ "notNull": true
85
+ },
86
+ "created_at": {
87
+ "name": "created_at",
88
+ "type": "timestamp with time zone",
89
+ "primaryKey": false,
90
+ "notNull": true,
91
+ "default": "now()"
92
+ }
93
+ },
94
+ "indexes": {
95
+ "player_tag_player_tag_idx": {
96
+ "name": "player_tag_player_tag_idx",
97
+ "columns": [
98
+ {
99
+ "expression": "player_id",
100
+ "isExpression": false,
101
+ "asc": true,
102
+ "nulls": "last"
103
+ },
104
+ {
105
+ "expression": "tag_id",
106
+ "isExpression": false,
107
+ "asc": true,
108
+ "nulls": "last"
109
+ }
110
+ ],
111
+ "isUnique": false,
112
+ "concurrently": false,
113
+ "method": "btree",
114
+ "with": {}
115
+ },
116
+ "player_tag_tag_id_idx": {
117
+ "name": "player_tag_tag_id_idx",
118
+ "columns": [
119
+ {
120
+ "expression": "tag_id",
121
+ "isExpression": false,
122
+ "asc": true,
123
+ "nulls": "last"
124
+ }
125
+ ],
126
+ "isUnique": false,
127
+ "where": "\"player_tag\".\"removed_at\" is null",
128
+ "concurrently": false,
129
+ "method": "btree",
130
+ "with": {}
131
+ },
132
+ "player_tag_active_key": {
133
+ "name": "player_tag_active_key",
134
+ "columns": [
135
+ {
136
+ "expression": "tag_id",
137
+ "isExpression": false,
138
+ "asc": true,
139
+ "nulls": "last"
140
+ },
141
+ {
142
+ "expression": "player_id",
143
+ "isExpression": false,
144
+ "asc": true,
145
+ "nulls": "last"
146
+ }
147
+ ],
148
+ "isUnique": true,
149
+ "where": "\"player_tag\".\"removed_at\" is null",
150
+ "concurrently": false,
151
+ "method": "btree",
152
+ "with": {}
153
+ }
154
+ },
155
+ "foreignKeys": {
156
+ "player_tag_tag_id_tag_id_fk": {
157
+ "name": "player_tag_tag_id_tag_id_fk",
158
+ "tableFrom": "player_tag",
159
+ "tableTo": "tag",
160
+ "columnsFrom": ["tag_id"],
161
+ "columnsTo": ["id"],
162
+ "onDelete": "restrict",
163
+ "onUpdate": "no action"
164
+ }
165
+ },
166
+ "compositePrimaryKeys": {},
167
+ "uniqueConstraints": {},
168
+ "policies": {},
169
+ "checkConstraints": {},
170
+ "isRLSEnabled": false
171
+ },
172
+ "public.tag": {
173
+ "name": "tag",
174
+ "schema": "",
175
+ "columns": {
176
+ "id": {
177
+ "name": "id",
178
+ "type": "uuid",
179
+ "primaryKey": true,
180
+ "notNull": true,
181
+ "default": "gen_random_uuid()"
182
+ },
183
+ "key": {
184
+ "name": "key",
185
+ "type": "tag_key",
186
+ "typeSchema": "public",
187
+ "primaryKey": false,
188
+ "notNull": true
189
+ },
190
+ "is_sticky": {
191
+ "name": "is_sticky",
192
+ "type": "boolean",
193
+ "primaryKey": false,
194
+ "notNull": true,
195
+ "default": false
196
+ },
197
+ "updated_at": {
198
+ "name": "updated_at",
199
+ "type": "timestamp with time zone",
200
+ "primaryKey": false,
201
+ "notNull": true
202
+ },
203
+ "created_at": {
204
+ "name": "created_at",
205
+ "type": "timestamp with time zone",
206
+ "primaryKey": false,
207
+ "notNull": true,
208
+ "default": "now()"
209
+ }
210
+ },
211
+ "indexes": {},
212
+ "foreignKeys": {},
213
+ "compositePrimaryKeys": {},
214
+ "uniqueConstraints": {
215
+ "tag_key_unique": {
216
+ "name": "tag_key_unique",
217
+ "nullsNotDistinct": false,
218
+ "columns": ["key"]
219
+ }
220
+ },
221
+ "policies": {},
222
+ "checkConstraints": {},
223
+ "isRLSEnabled": false
224
+ },
225
+ "public.tag_rule": {
226
+ "name": "tag_rule",
227
+ "schema": "",
228
+ "columns": {
229
+ "id": {
230
+ "name": "id",
231
+ "type": "uuid",
232
+ "primaryKey": true,
233
+ "notNull": true,
234
+ "default": "gen_random_uuid()"
235
+ },
236
+ "tag_id": {
237
+ "name": "tag_id",
238
+ "type": "uuid",
239
+ "primaryKey": false,
240
+ "notNull": true
241
+ },
242
+ "is_enabled": {
243
+ "name": "is_enabled",
244
+ "type": "boolean",
245
+ "primaryKey": false,
246
+ "notNull": true,
247
+ "default": false
248
+ },
249
+ "threshold": {
250
+ "name": "threshold",
251
+ "type": "numeric(18, 2)",
252
+ "primaryKey": false,
253
+ "notNull": false
254
+ },
255
+ "threshold_days": {
256
+ "name": "threshold_days",
257
+ "type": "integer",
258
+ "primaryKey": false,
259
+ "notNull": false
260
+ },
261
+ "threshold_count": {
262
+ "name": "threshold_count",
263
+ "type": "integer",
264
+ "primaryKey": false,
265
+ "notNull": false
266
+ },
267
+ "updated_at": {
268
+ "name": "updated_at",
269
+ "type": "timestamp with time zone",
270
+ "primaryKey": false,
271
+ "notNull": true
272
+ },
273
+ "created_at": {
274
+ "name": "created_at",
275
+ "type": "timestamp with time zone",
276
+ "primaryKey": false,
277
+ "notNull": true,
278
+ "default": "now()"
279
+ }
280
+ },
281
+ "indexes": {},
282
+ "foreignKeys": {
283
+ "tag_rule_tag_id_tag_id_fk": {
284
+ "name": "tag_rule_tag_id_tag_id_fk",
285
+ "tableFrom": "tag_rule",
286
+ "tableTo": "tag",
287
+ "columnsFrom": ["tag_id"],
288
+ "columnsTo": ["id"],
289
+ "onDelete": "restrict",
290
+ "onUpdate": "no action"
291
+ }
292
+ },
293
+ "compositePrimaryKeys": {},
294
+ "uniqueConstraints": {
295
+ "tag_rule_tagId_unique": {
296
+ "name": "tag_rule_tagId_unique",
297
+ "nullsNotDistinct": false,
298
+ "columns": ["tag_id"]
299
+ }
300
+ },
301
+ "policies": {},
302
+ "checkConstraints": {},
303
+ "isRLSEnabled": false
304
+ }
305
+ },
306
+ "enums": {
307
+ "public.tag_assign_remove_source": {
308
+ "name": "tag_assign_remove_source",
309
+ "schema": "public",
310
+ "values": ["scheduled", "manual"]
311
+ },
312
+ "public.tag_key": {
313
+ "name": "tag_key",
314
+ "schema": "public",
315
+ "values": [
316
+ "high_roller",
317
+ "vip",
318
+ "bonus_abuser",
319
+ "high_risk",
320
+ "inactive",
321
+ "large_depositor",
322
+ "self_excluded",
323
+ "kyc_pending",
324
+ "kyc_rejected",
325
+ "test_account",
326
+ "dormant_high_roller",
327
+ "withdrawal_review",
328
+ "multi_account",
329
+ "level"
330
+ ]
331
+ }
332
+ },
333
+ "schemas": {},
334
+ "sequences": {},
335
+ "roles": {},
336
+ "policies": {},
337
+ "views": {},
338
+ "_meta": {
339
+ "columns": {},
340
+ "schemas": {},
341
+ "tables": {}
342
+ }
343
+ }
@@ -15,6 +15,20 @@
15
15
  "when": 1783854980666,
16
16
  "tag": "0001_neat_iron_monger",
17
17
  "breakpoints": true
18
+ },
19
+ {
20
+ "idx": 2,
21
+ "version": "7",
22
+ "when": 1784268208305,
23
+ "tag": "0002_silent_skaar",
24
+ "breakpoints": true
25
+ },
26
+ {
27
+ "idx": 3,
28
+ "version": "7",
29
+ "when": 1784892288786,
30
+ "tag": "0003_overconfident_guardian",
31
+ "breakpoints": true
18
32
  }
19
33
  ]
20
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../../src/pam/tag/migrate.ts"],"names":[],"mappings":"AAMA,yFAAyF;AACzF,wBAAgB,OAAO,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ3D"}
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../../src/pam/tag/migrate.ts"],"names":[],"mappings":"AAwCA,yFAAyF;AACzF,wBAAgB,OAAO,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAS3D"}
@@ -3,6 +3,39 @@
3
3
  // tarball ('files') and loads via an import.meta.url-relative path. See ADR-0020/0027.
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { runMigrations } from '@openora/core/server/migrate';
6
+ // Before 0002_silent_skaar.sql existed, "at most one active (tag, player) assignment" was
7
+ // enforced only by a service-level pre-check SELECT, which could not close a genuine concurrent
8
+ // race (or an at-least-once redelivery of the same tagging event): two inserts could both pass
9
+ // the pre-check and land as duplicate active (removed_at IS NULL) rows for the same
10
+ // (tag_id, player_id) pair. 0002_silent_skaar.sql adds `player_tag_active_key`, a partial unique
11
+ // index that closes that race going forward - but on a database that already accumulated
12
+ // duplicates from before this fix, `CREATE UNIQUE INDEX` aborts outright. This statement
13
+ // soft-removes every duplicate active row except the earliest (by created_at) per (tag_id,
14
+ // player_id) pair before the migrator reaches 0002, using this table's normal soft-delete
15
+ // columns (see tag.service.ts's removePlayerTag) so the cleanup itself leaves an audit-visible
16
+ // trace. Guarded by `to_regclass` because `preSql` runs before EVERY pending migration, including
17
+ // 0000 (which creates `player_tag` in the first place) - on a fresh database the table doesn't
18
+ // exist yet, so the guard is a no-op there instead of an error. Idempotent thereafter too: once
19
+ // at most one active row remains per pair, `rn > 1` never matches again.
20
+ const DEDUPE_ACTIVE_PLAYER_TAG_SQL = `
21
+ DO $$
22
+ BEGIN
23
+ IF to_regclass('public.player_tag') IS NOT NULL THEN
24
+ WITH ranked AS (
25
+ SELECT id, row_number() OVER (
26
+ PARTITION BY tag_id, player_id ORDER BY created_at ASC
27
+ ) AS rn
28
+ FROM player_tag
29
+ WHERE removed_at IS NULL
30
+ )
31
+ UPDATE player_tag
32
+ SET removed_at = now(),
33
+ removal_reason = 'duplicate active assignment cleaned up before unique index migration',
34
+ removal_actor = 'scheduled'
35
+ WHERE id IN (SELECT id FROM ranked WHERE rn > 1);
36
+ END IF;
37
+ END $$;
38
+ `;
6
39
  /** Apply the tag module migrations (idempotent: drizzle skips already-recorded ones). */
7
40
  export function migrate(databaseUrl) {
8
41
  return runMigrations({
@@ -10,6 +43,7 @@ export function migrate(databaseUrl) {
10
43
  migrationsTable: '__drizzle_migrations_tag',
11
44
  migrationsSchema: 'drizzle',
12
45
  extensions: [],
46
+ preSql: [DEDUPE_ACTIVE_PLAYER_TAG_SQL],
13
47
  ...(databaseUrl ? { databaseUrl } : {}),
14
48
  });
15
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../../src/pam/tag/migrate.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,gFAAgF;AAChF,uFAAuF;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D,yFAAyF;AACzF,MAAM,UAAU,OAAO,CAAC,WAAoB;IAC1C,OAAO,aAAa,CAAC;QACnB,gBAAgB,EAAE,aAAa,CAAC,IAAI,GAAG,CAAC,sBAAsB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjF,eAAe,EAAE,0BAA0B;QAC3C,gBAAgB,EAAE,SAAS;QAC3B,UAAU,EAAE,EAAE;QACd,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../../src/pam/tag/migrate.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,gFAAgF;AAChF,uFAAuF;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D,0FAA0F;AAC1F,gGAAgG;AAChG,+FAA+F;AAC/F,oFAAoF;AACpF,iGAAiG;AACjG,yFAAyF;AACzF,yFAAyF;AACzF,2FAA2F;AAC3F,0FAA0F;AAC1F,+FAA+F;AAC/F,kGAAkG;AAClG,+FAA+F;AAC/F,gGAAgG;AAChG,yEAAyE;AACzE,MAAM,4BAA4B,GAAG;;;;;;;;;;;;;;;;;;CAkBpC,CAAC;AAEF,yFAAyF;AACzF,MAAM,UAAU,OAAO,CAAC,WAAoB;IAC1C,OAAO,aAAa,CAAC;QACnB,gBAAgB,EAAE,aAAa,CAAC,IAAI,GAAG,CAAC,sBAAsB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjF,eAAe,EAAE,0BAA0B;QAC3C,gBAAgB,EAAE,SAAS;QAC3B,UAAU,EAAE,EAAE;QACd,MAAM,EAAE,CAAC,4BAA4B,CAAC;QACtC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxC,CAAC,CAAC;AACL,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/pam/tag/plugin.ts"],"names":[],"mappings":";AAoBA,wBAsDG"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/pam/tag/plugin.ts"],"names":[],"mappings":";AAqBA,wBA2EG"}
@@ -1,5 +1,5 @@
1
1
  import { definePlugin, EVENT_BUS, DRIZZLE, ADMIN_GUARD, } from '@openora/core/server';
2
- import { PLAYER_TAGS, WALLET_READER, IDENTITY_READER, JOB_QUEUE, queue, } from '@openora/core/contracts';
2
+ import { PLAYER_TAGS, TAG_EVALUATION_COMMANDS, WALLET_READER, IDENTITY_READER, JOB_QUEUE, queue, } from '@openora/core/contracts';
3
3
  import z from 'zod';
4
4
  import { TagService } from './service/tag.service.js';
5
5
  import { TagRuleService } from './service/tag-rule.service.js';
@@ -9,35 +9,54 @@ export default definePlugin({
9
9
  id: 'tag',
10
10
  dependsOn: ['wallet', 'identity'],
11
11
  register(ctx) {
12
- let tagEvaluationService = null;
13
- ctx.events.on('wallet.deposit.completed', (p) => tagEvaluationService?.onDepositCompleted(p));
14
- ctx.events.on('wallet.withdrawal.completed', (p) => tagEvaluationService?.onWithdrawalCompleted(p));
15
- ctx.events.on('identity.user.login', (p) => tagEvaluationService?.onUserLogin(p));
16
- ctx.events.on('compliance.kyc.submitted', (p) => tagEvaluationService?.onKycSubmitted(p));
17
- ctx.events.on('compliance.kyc.updated', (p) => tagEvaluationService?.onKycStatusUpdated(p));
12
+ // One memoized instance backs the PLAYER_TAGS port and the router closure.
13
+ let svc = null;
14
+ const tagService = (c) => (svc ??= new TagService(c.get(DRIZZLE), c.get(EVENT_BUS)));
15
+ // One memoized instance backs the router closure and the TAG_EVALUATION_COMMANDS port.
16
+ let ruleSvc = null;
17
+ const ruleService = (c) => (ruleSvc ??= new TagRuleService(c.get(DRIZZLE), c.get(EVENT_BUS)));
18
+ // One memoized instance backs the daily job, the event subscriptions below, and the
19
+ // TAG_EVALUATION_COMMANDS port - constructed lazily on first access (via either the
20
+ // provide() factory or the router factory, whichever runs first) rather than only
21
+ // inside the router factory, so wallet's synchronous TAG_EVALUATION_COMMANDS call is
22
+ // never blocked on this module's own router having mounted first.
23
+ let evalSvc = null;
24
+ const tagEvaluationService = (c) => (evalSvc ??= new TagEvaluationService(tagService(c), ruleService(c), c.get(WALLET_READER), c.get(IDENTITY_READER)));
25
+ ctx.provide(PLAYER_TAGS, tagService);
26
+ // Synchronous counterpart to the wallet.withdrawal.requested subscription below - see
27
+ // TagEvaluationService.evaluateWithdrawalRequested for why auto-approval can't rely on
28
+ // the async event handler alone. ADR-0017.
29
+ ctx.provide(TAG_EVALUATION_COMMANDS, (c) => ({
30
+ evaluateWithdrawalRequested: (tx, args) => tagEvaluationService(c).evaluateWithdrawalRequested(tx, args),
31
+ }));
32
+ ctx.events.on('wallet.deposit.completed', (p) => evalSvc?.onDepositCompleted(p));
33
+ ctx.events.on('wallet.withdrawal.completed', (p) => evalSvc?.onWithdrawalCompleted(p));
34
+ ctx.events.on('wallet.withdrawal.requested', (p) => evalSvc?.onWithdrawalRequested(p));
35
+ ctx.events.on('identity.user.login', (p) => evalSvc?.onUserLogin(p));
36
+ ctx.events.on('identity.user.phone_login', (p) => evalSvc?.onUserPhoneLogin(p));
37
+ ctx.events.on('compliance.kyc.submitted', (p) => evalSvc?.onKycSubmitted(p));
38
+ ctx.events.on('compliance.kyc.updated', (p) => evalSvc?.onKycStatusUpdated(p));
39
+ ctx.events.on('rg.self_exclusion.activated', (p) => evalSvc?.onSelfExclusionActivated(p));
40
+ ctx.events.on('rg.self_exclusion.lifted', (p) => evalSvc?.onSelfExclusionLifted(p));
41
+ ctx.events.on('player.level.changed', (p) => evalSvc?.onPlayerLevelChanged(p));
18
42
  // Daily inactive-player sweep. schedule() is called in the router factory (container access).
19
43
  ctx.jobs.worker({
20
44
  queue: queue('tag.daily-evaluation'),
21
45
  schema: z.object({}),
22
46
  handler: async () => {
23
- await tagEvaluationService?.runDailyEvaluation();
47
+ await evalSvc?.runDailyEvaluation();
24
48
  },
25
49
  });
26
- // One memoized instance backs both the PLAYER_TAGS port and the router closure.
27
- let svc = null;
28
- const tagService = (c) => (svc ??= new TagService(c.get(DRIZZLE), c.get(EVENT_BUS)));
29
- ctx.provide(PLAYER_TAGS, tagService);
30
50
  ctx.routers.add('tag', (c) => {
31
51
  const tagSvc = tagService(c);
32
- const ruleSvc = new TagRuleService(c.get(DRIZZLE), c.get(EVENT_BUS));
33
- const evalSvc = new TagEvaluationService(tagSvc, ruleSvc, c.get(WALLET_READER), c.get(IDENTITY_READER));
34
- tagEvaluationService = evalSvc;
52
+ const ruleSvcForRouter = ruleService(c);
53
+ tagEvaluationService(c); // ensure evalSvc is constructed for the event handlers/job worker above
35
54
  // Idempotent daily schedule (keyed by scheduleId). In-process driver runs it
36
55
  // manually; a durable overlay (BullMQ) fires at cron time.
37
56
  void c
38
57
  .get(JOB_QUEUE)
39
58
  .schedule(queue('tag.daily-evaluation'), 'tag.daily-evaluation.daily', {}, { cron: '0 2 * * *' });
40
- return createTagRouter(tagSvc, ruleSvc, c.get(ADMIN_GUARD));
59
+ return createTagRouter(tagSvc, ruleSvcForRouter, c.get(ADMIN_GUARD));
41
60
  });
42
61
  },
43
62
  });
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../src/pam/tag/plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../src/pam/tag/plugin.ts"],"names":[],"mappings":"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"}