@vreko/cli 3.0.1

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 (98) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +45 -0
  3. package/dist/CeremonyView-LQS7FTMK.js +134 -0
  4. package/dist/CeremonyView-LQS7FTMK.js.map +1 -0
  5. package/dist/InitApp-7K5DTYSW.js +1479 -0
  6. package/dist/InitApp-7K5DTYSW.js.map +1 -0
  7. package/dist/SkippedTestDetector-PJSKSOZR.js +7 -0
  8. package/dist/SkippedTestDetector-PJSKSOZR.js.map +1 -0
  9. package/dist/TuiApp-FX23XQBK.js +8 -0
  10. package/dist/TuiApp-FX23XQBK.js.map +1 -0
  11. package/dist/analysis-ABEO6RTN.js +8 -0
  12. package/dist/analysis-ABEO6RTN.js.map +1 -0
  13. package/dist/auth-XNBEBNPY.js +7669 -0
  14. package/dist/auth-XNBEBNPY.js.map +1 -0
  15. package/dist/ceremony-M7CXVBVA.js +45 -0
  16. package/dist/ceremony-M7CXVBVA.js.map +1 -0
  17. package/dist/chunk-A3QSZJPD.js +3147 -0
  18. package/dist/chunk-A3QSZJPD.js.map +1 -0
  19. package/dist/chunk-ASGZ5B6C.js +3969 -0
  20. package/dist/chunk-ASGZ5B6C.js.map +1 -0
  21. package/dist/chunk-DMXC2JTC.js +58 -0
  22. package/dist/chunk-DMXC2JTC.js.map +1 -0
  23. package/dist/chunk-EEBSK2IH.js +161 -0
  24. package/dist/chunk-EEBSK2IH.js.map +1 -0
  25. package/dist/chunk-EWOJGXRX.js +22 -0
  26. package/dist/chunk-EWOJGXRX.js.map +1 -0
  27. package/dist/chunk-F7GEJLP7.js +2389 -0
  28. package/dist/chunk-F7GEJLP7.js.map +1 -0
  29. package/dist/chunk-GOYL3F4T.js +605 -0
  30. package/dist/chunk-GOYL3F4T.js.map +1 -0
  31. package/dist/chunk-GRMRYWYS.js +17 -0
  32. package/dist/chunk-GRMRYWYS.js.map +1 -0
  33. package/dist/chunk-GSUGROXB.js +1951 -0
  34. package/dist/chunk-GSUGROXB.js.map +1 -0
  35. package/dist/chunk-H7773ONB.js +50 -0
  36. package/dist/chunk-H7773ONB.js.map +1 -0
  37. package/dist/chunk-HFQHU5LC.js +445 -0
  38. package/dist/chunk-HFQHU5LC.js.map +1 -0
  39. package/dist/chunk-IVHUBLJD.js +318 -0
  40. package/dist/chunk-IVHUBLJD.js.map +1 -0
  41. package/dist/chunk-KJWKY4L4.js +14 -0
  42. package/dist/chunk-KJWKY4L4.js.map +1 -0
  43. package/dist/chunk-MJVY2XUN.js +1793 -0
  44. package/dist/chunk-MJVY2XUN.js.map +1 -0
  45. package/dist/chunk-QWZVCJII.js +1797 -0
  46. package/dist/chunk-QWZVCJII.js.map +1 -0
  47. package/dist/chunk-VTSNRV3V.js +3237 -0
  48. package/dist/chunk-VTSNRV3V.js.map +1 -0
  49. package/dist/chunk-W5B4GTXR.js +1466 -0
  50. package/dist/chunk-W5B4GTXR.js.map +1 -0
  51. package/dist/chunk-WZEZLVOW.js +4995 -0
  52. package/dist/chunk-WZEZLVOW.js.map +1 -0
  53. package/dist/chunk-YPTTIXKC.js +199 -0
  54. package/dist/chunk-YPTTIXKC.js.map +1 -0
  55. package/dist/chunk-Z55UGM6X.js +6360 -0
  56. package/dist/chunk-Z55UGM6X.js.map +1 -0
  57. package/dist/chunk-ZIIRQODJ.js +110 -0
  58. package/dist/chunk-ZIIRQODJ.js.map +1 -0
  59. package/dist/chunk-ZSUQ4FMB.js +77 -0
  60. package/dist/chunk-ZSUQ4FMB.js.map +1 -0
  61. package/dist/client-JMTSZS3V.js +10 -0
  62. package/dist/client-JMTSZS3V.js.map +1 -0
  63. package/dist/deprecated-snap.js +19 -0
  64. package/dist/deprecated-snap.js.map +1 -0
  65. package/dist/dist-2KWBZFLA.js +14 -0
  66. package/dist/dist-2KWBZFLA.js.map +1 -0
  67. package/dist/dist-5ZYKNNU3.js +7 -0
  68. package/dist/dist-5ZYKNNU3.js.map +1 -0
  69. package/dist/dist-CP3RFHPI.js +11 -0
  70. package/dist/dist-CP3RFHPI.js.map +1 -0
  71. package/dist/gecko-53ITAGG6.js +56 -0
  72. package/dist/gecko-53ITAGG6.js.map +1 -0
  73. package/dist/guards-QAFC64NO.js +7 -0
  74. package/dist/guards-QAFC64NO.js.map +1 -0
  75. package/dist/index.js +57785 -0
  76. package/dist/index.js.map +1 -0
  77. package/dist/init-command-246JIVXM.js +7 -0
  78. package/dist/init-command-246JIVXM.js.map +1 -0
  79. package/dist/init-core-KAI7LCXZ.js +12 -0
  80. package/dist/init-core-KAI7LCXZ.js.map +1 -0
  81. package/dist/init-scan-RZNYDTUV.js +1919 -0
  82. package/dist/init-scan-RZNYDTUV.js.map +1 -0
  83. package/dist/local-service-adapter-6KNN6WQL.js +8 -0
  84. package/dist/local-service-adapter-6KNN6WQL.js.map +1 -0
  85. package/dist/secure-credentials-JXWAQLS2.js +306 -0
  86. package/dist/secure-credentials-JXWAQLS2.js.map +1 -0
  87. package/dist/tui-TPJPUS2R.js +111 -0
  88. package/dist/tui-TPJPUS2R.js.map +1 -0
  89. package/dist/vreko-dir-O3RLG7PI.js +8 -0
  90. package/dist/vreko-dir-O3RLG7PI.js.map +1 -0
  91. package/package.json +132 -0
  92. package/scripts/check-banned-words.ts +152 -0
  93. package/scripts/hooks/posttooluse-file-notify.sh +108 -0
  94. package/scripts/hooks/pretooluse-fragile-guard.sh +82 -0
  95. package/scripts/post-install-notice.js +24 -0
  96. package/scripts/postinstall.mjs +84 -0
  97. package/scripts/preuninstall.mjs +34 -0
  98. package/scripts/verify-jsx-transform.mjs +55 -0
@@ -0,0 +1,3969 @@
1
+ #!/usr/bin/env node
2
+ import { isRedisAvailable, getCache, setCache, deleteCache } from './chunk-IVHUBLJD.js';
3
+ import { logger } from './chunk-GOYL3F4T.js';
4
+ import { snapshots, snapshotFiles, agentSuggestions, quarantineEvents, postAcceptOutcomes, policyEvaluations, loops, feedback, telemetryEvents, telemetryIdempotencyKeys, combinedSchema, user, aiChat, organization, member, invitation, purchase, session, account, verification, passkey, db, userAttributions, subscriptions, trials, pioneers, pioneerRedemptions, usageLimits, creditsLedger, mcpObservations, mcpToolInvocations, extensionSyncState, sagas, apiKeys, TIER_STALENESS_THRESHOLD_MS, WORKSPACE_LINK_TTL_MS, patterns, closeDatabaseConnection, checkDatabaseConnection } from './chunk-Z55UGM6X.js';
5
+ import { shouldMergeAttribution, WorkspaceRegistrationSchema, createLogger, LogLevel, getEffectiveTier, isFeatureAvailableAtTier, getTierFeatures, getTierLimit, TIER_UPGRADE_SAGA } from './chunk-WZEZLVOW.js';
6
+ import { __name } from './chunk-EWOJGXRX.js';
7
+ import { eq, desc, and, gte, lte, sum, gt, sql, inArray } from 'drizzle-orm';
8
+ import { z } from 'zod';
9
+ import slugify from '@sindresorhus/slugify';
10
+ import { nanoid } from 'nanoid';
11
+ import * as crypto2 from 'crypto';
12
+ import { randomBytes } from 'crypto';
13
+ import { pgTable, timestamp, boolean, text, varchar, index, jsonb } from 'drizzle-orm/pg-core';
14
+ import { createSelectSchema, createUpdateSchema, createInsertSchema } from 'drizzle-zod';
15
+ import 'fs';
16
+ import 'fs/promises';
17
+ import 'os';
18
+ import 'path';
19
+ import { PostHog } from 'posthog-node';
20
+
21
+ process.env.VREKO_CLI='true';process.env.NODE_NO_WARNINGS='1';
22
+ var SnapshotStoreDb = class {
23
+ static {
24
+ __name(this, "SnapshotStoreDb");
25
+ }
26
+ db;
27
+ constructor(db2) {
28
+ this.db = db2;
29
+ }
30
+ /**
31
+ * Create a new snapshot
32
+ */
33
+ async createSnapshot(snapshot) {
34
+ const id = crypto.randomUUID();
35
+ const now = /* @__PURE__ */ new Date();
36
+ await this.db.insert(snapshots).values({
37
+ id,
38
+ userId: snapshot.userId,
39
+ apiKeyId: snapshot.apiKeyId,
40
+ name: snapshot.name,
41
+ description: snapshot.description,
42
+ trigger: snapshot.triggerType,
43
+ fileCount: snapshot.fileCount,
44
+ totalSizeBytes: snapshot.totalSizeBytes,
45
+ riskScore: snapshot.riskScore,
46
+ createdAt: now,
47
+ expiresAt: snapshot.expiresAt,
48
+ workspaceId: snapshot.workspaceId
49
+ });
50
+ return id;
51
+ }
52
+ /**
53
+ * Add files to a snapshot
54
+ */
55
+ async addFilesToSnapshot(snapshotId, files) {
56
+ const values = files.map((file) => ({
57
+ id: crypto.randomUUID(),
58
+ snapshotId,
59
+ filePath: file.filePath,
60
+ fileHash: file.fileHash,
61
+ fileSizeBytes: file.fileSizeBytes,
62
+ changeType: file.changeType,
63
+ linesChanged: file.linesChanged,
64
+ containsSecrets: file.containsSecrets,
65
+ riskLevel: file.riskLevel,
66
+ cloudBackupUrl: file.cloudBackupUrl,
67
+ createdAt: /* @__PURE__ */ new Date()
68
+ }));
69
+ if (values.length > 0) {
70
+ await this.db.insert(snapshotFiles).values(values);
71
+ }
72
+ }
73
+ /**
74
+ * List snapshots for a user
75
+ */
76
+ async listSnapshots(userId, limit = 50) {
77
+ const result = await this.db.select().from(snapshots).where(eq(snapshots.userId, userId)).orderBy(desc(snapshots.createdAt)).limit(limit);
78
+ return result.map((row) => ({
79
+ id: row.id,
80
+ userId: row.userId,
81
+ apiKeyId: row.apiKeyId,
82
+ workspaceId: row.workspaceId || void 0,
83
+ name: row.name || void 0,
84
+ description: row.description || void 0,
85
+ triggerType: row.trigger,
86
+ fileCount: row.fileCount,
87
+ totalSizeBytes: row.totalSizeBytes,
88
+ riskScore: row.riskScore || void 0,
89
+ createdAt: row.createdAt,
90
+ expiresAt: row.expiresAt || void 0
91
+ }));
92
+ }
93
+ /**
94
+ * Fetch a snapshot by ID
95
+ */
96
+ async fetchSnapshot(id) {
97
+ const result = await this.db.select().from(snapshots).where(eq(snapshots.id, id)).limit(1);
98
+ if (result.length === 0) {
99
+ return null;
100
+ }
101
+ const row = result[0];
102
+ if (!row) {
103
+ return null;
104
+ }
105
+ return {
106
+ id: row.id,
107
+ userId: row.userId,
108
+ apiKeyId: row.apiKeyId,
109
+ workspaceId: row.workspaceId || void 0,
110
+ name: row.name || void 0,
111
+ description: row.description || void 0,
112
+ triggerType: row.trigger,
113
+ fileCount: row.fileCount,
114
+ totalSizeBytes: row.totalSizeBytes,
115
+ riskScore: row.riskScore || void 0,
116
+ createdAt: row.createdAt,
117
+ expiresAt: row.expiresAt || void 0
118
+ };
119
+ }
120
+ /**
121
+ * Fetch files for a snapshot
122
+ */
123
+ async fetchSnapshotFiles(snapshotId) {
124
+ const result = await this.db.select().from(snapshotFiles).where(eq(snapshotFiles.snapshotId, snapshotId));
125
+ return result.map((row) => ({
126
+ id: row.id,
127
+ snapshotId: row.snapshotId,
128
+ filePath: row.filePath,
129
+ fileHash: row.fileHash,
130
+ fileSizeBytes: row.fileSizeBytes,
131
+ changeType: row.changeType || void 0,
132
+ linesChanged: row.linesChanged || void 0,
133
+ containsSecrets: row.containsSecrets || void 0,
134
+ riskLevel: row.riskLevel || void 0,
135
+ cloudBackupUrl: row.cloudBackupUrl || void 0,
136
+ createdAt: row.createdAt
137
+ }));
138
+ }
139
+ };
140
+ function redactString(value) {
141
+ return value.replace(/./g, "*");
142
+ }
143
+ __name(redactString, "redactString");
144
+ function redactObject(obj) {
145
+ if (!obj || typeof obj !== "object") {
146
+ return obj;
147
+ }
148
+ const record = obj;
149
+ const redacted = Array.isArray(obj) ? [] : {};
150
+ const target = redacted;
151
+ for (const key in record) {
152
+ if (typeof record[key] === "string") {
153
+ target[key] = redactString(record[key]);
154
+ } else if (typeof record[key] === "object") {
155
+ target[key] = redactObject(record[key]);
156
+ } else {
157
+ target[key] = record[key];
158
+ }
159
+ }
160
+ return target;
161
+ }
162
+ __name(redactObject, "redactObject");
163
+ var SLOW_MS = 200;
164
+ function asTyped(value) {
165
+ return value;
166
+ }
167
+ __name(asTyped, "asTyped");
168
+ function quarantinePayload(event, error) {
169
+ return {
170
+ id: crypto.randomUUID(),
171
+ userId: event.userId,
172
+ apiKeyId: event.apiKeyId,
173
+ originalEvent: event,
174
+ errorReason: error instanceof Error ? error.message : String(error),
175
+ errorStack: error instanceof Error ? error.stack : void 0,
176
+ attemptedAt: /* @__PURE__ */ new Date(),
177
+ createdAt: /* @__PURE__ */ new Date()
178
+ };
179
+ }
180
+ __name(quarantinePayload, "quarantinePayload");
181
+ function agentSuggestionInsert(event) {
182
+ const redacted = asTyped(applyRedaction(event));
183
+ return {
184
+ userId: redacted.userId,
185
+ apiKeyId: redacted.apiKeyId,
186
+ sessionId: redacted.sessionId,
187
+ requestId: redacted.requestId,
188
+ suggestionId: redacted.suggestionId,
189
+ suggestionText: redacted.suggestionText,
190
+ suggestionType: redacted.suggestionType,
191
+ filePath: redacted.filePath,
192
+ lineStart: redacted.lineStart,
193
+ lineEnd: redacted.lineEnd,
194
+ characterStart: redacted.characterStart,
195
+ characterEnd: redacted.characterEnd,
196
+ accepted: redacted.accepted,
197
+ dismissed: redacted.dismissed,
198
+ timestamp: redacted.timestamp,
199
+ createdAt: /* @__PURE__ */ new Date()
200
+ };
201
+ }
202
+ __name(agentSuggestionInsert, "agentSuggestionInsert");
203
+ function postAcceptOutcomeInsert(event) {
204
+ const redacted = asTyped(applyRedaction(event));
205
+ return {
206
+ userId: redacted.userId,
207
+ apiKeyId: redacted.apiKeyId,
208
+ suggestionId: redacted.suggestionId,
209
+ editsMade: redacted.editsMade,
210
+ timeToEditMs: redacted.timeToEditMs,
211
+ timeToSubmitMs: redacted.timeToSubmitMs,
212
+ userFeedback: redacted.userFeedback,
213
+ timestamp: redacted.timestamp,
214
+ createdAt: /* @__PURE__ */ new Date()
215
+ };
216
+ }
217
+ __name(postAcceptOutcomeInsert, "postAcceptOutcomeInsert");
218
+ function policyEvaluationInsert(event) {
219
+ const redacted = asTyped(applyRedaction(event));
220
+ return {
221
+ userId: redacted.userId,
222
+ apiKeyId: redacted.apiKeyId,
223
+ sessionId: redacted.sessionId,
224
+ requestId: redacted.requestId,
225
+ policyName: redacted.policyName,
226
+ policyVersion: redacted.policyVersion,
227
+ evaluationResult: redacted.evaluationResult,
228
+ violations: redacted.violations,
229
+ remediationSteps: redacted.remediationSteps,
230
+ timestamp: redacted.timestamp,
231
+ createdAt: /* @__PURE__ */ new Date()
232
+ };
233
+ }
234
+ __name(policyEvaluationInsert, "policyEvaluationInsert");
235
+ function loopInsert(event) {
236
+ const redacted = asTyped(applyRedaction(event));
237
+ return {
238
+ userId: redacted.userId,
239
+ apiKeyId: redacted.apiKeyId,
240
+ sessionId: redacted.sessionId,
241
+ requestId: redacted.requestId,
242
+ loopType: redacted.loopType,
243
+ iterationCount: redacted.iterationCount,
244
+ durationMs: redacted.durationMs,
245
+ success: redacted.success,
246
+ errorMessage: redacted.errorMessage,
247
+ timestamp: redacted.timestamp,
248
+ createdAt: /* @__PURE__ */ new Date()
249
+ };
250
+ }
251
+ __name(loopInsert, "loopInsert");
252
+ function feedbackInsert(event) {
253
+ const redacted = asTyped(applyRedaction(event));
254
+ return {
255
+ userId: redacted.userId,
256
+ apiKeyId: redacted.apiKeyId,
257
+ sessionId: redacted.sessionId,
258
+ requestId: redacted.requestId,
259
+ feedbackType: redacted.feedbackType,
260
+ feedbackText: redacted.feedbackText,
261
+ rating: redacted.rating,
262
+ metadata: redacted.metadata,
263
+ timestamp: redacted.timestamp,
264
+ createdAt: /* @__PURE__ */ new Date()
265
+ };
266
+ }
267
+ __name(feedbackInsert, "feedbackInsert");
268
+ function applyRedaction(event) {
269
+ const redacted = {
270
+ ...event
271
+ };
272
+ for (const key of [
273
+ "suggestionText",
274
+ "filePath",
275
+ "userFeedback",
276
+ "errorMessage",
277
+ "feedbackText"
278
+ ]) {
279
+ if (key in redacted && redacted[key]) {
280
+ redacted[key] = redactString(String(redacted[key]));
281
+ }
282
+ }
283
+ if ("violations" in redacted && redacted.violations) {
284
+ redacted.violations = redactObject(redacted.violations);
285
+ }
286
+ if ("remediationSteps" in redacted && redacted.remediationSteps) {
287
+ redacted.remediationSteps = redactObject(redacted.remediationSteps);
288
+ }
289
+ return redacted;
290
+ }
291
+ __name(applyRedaction, "applyRedaction");
292
+ function logSlowQuery(operationName, durationMs) {
293
+ if (durationMs > SLOW_MS) {
294
+ console.warn(`Slow query detected in ${operationName}: ${durationMs}ms`);
295
+ }
296
+ }
297
+ __name(logSlowQuery, "logSlowQuery");
298
+ var TelemetrySinkDb = class {
299
+ static {
300
+ __name(this, "TelemetrySinkDb");
301
+ }
302
+ db;
303
+ constructor(db2) {
304
+ this.db = db2;
305
+ }
306
+ /**
307
+ * Insert agent suggestion event with idempotency check
308
+ */
309
+ async insertAgentSuggestion(event) {
310
+ const startTime = Date.now();
311
+ try {
312
+ const existing = await this.db.select().from(agentSuggestions).where(eq(agentSuggestions.requestId, event.requestId)).limit(1);
313
+ if (existing.length > 0) {
314
+ return;
315
+ }
316
+ await this.db.insert(agentSuggestions).values(agentSuggestionInsert(event));
317
+ const duration = Date.now() - startTime;
318
+ logSlowQuery("insertAgentSuggestion", duration);
319
+ } catch (error) {
320
+ await this.db.insert(quarantineEvents).values(quarantinePayload(event, error));
321
+ }
322
+ }
323
+ /**
324
+ * Insert post-accept outcome event with idempotency check
325
+ */
326
+ async insertPostAcceptOutcome(event) {
327
+ const startTime = Date.now();
328
+ try {
329
+ const existing = await this.db.select().from(postAcceptOutcomes).where(eq(postAcceptOutcomes.suggestionId, event.suggestionId)).limit(1);
330
+ if (existing.length > 0) {
331
+ return;
332
+ }
333
+ await this.db.insert(postAcceptOutcomes).values(postAcceptOutcomeInsert(event));
334
+ const duration = Date.now() - startTime;
335
+ logSlowQuery("insertPostAcceptOutcome", duration);
336
+ } catch (error) {
337
+ await this.db.insert(quarantineEvents).values(quarantinePayload(event, error));
338
+ }
339
+ }
340
+ /**
341
+ * Insert policy evaluation event with idempotency check
342
+ */
343
+ async insertPolicyEvaluation(event) {
344
+ const startTime = Date.now();
345
+ try {
346
+ const existing = await this.db.select().from(policyEvaluations).where(eq(policyEvaluations.requestId, event.requestId)).limit(1);
347
+ if (existing.length > 0) {
348
+ return;
349
+ }
350
+ await this.db.insert(policyEvaluations).values(policyEvaluationInsert(event));
351
+ const duration = Date.now() - startTime;
352
+ logSlowQuery("insertPolicyEvaluation", duration);
353
+ } catch (error) {
354
+ await this.db.insert(quarantineEvents).values(quarantinePayload(event, error));
355
+ }
356
+ }
357
+ /**
358
+ * Insert loop event with idempotency check
359
+ */
360
+ async insertLoop(event) {
361
+ const startTime = Date.now();
362
+ try {
363
+ const existing = await this.db.select().from(loops).where(eq(loops.requestId, event.requestId)).limit(1);
364
+ if (existing.length > 0) {
365
+ return;
366
+ }
367
+ await this.db.insert(loops).values(loopInsert(event));
368
+ const duration = Date.now() - startTime;
369
+ logSlowQuery("insertLoop", duration);
370
+ } catch (error) {
371
+ await this.db.insert(quarantineEvents).values(quarantinePayload(event, error));
372
+ }
373
+ }
374
+ /**
375
+ * Insert feedback event with idempotency check
376
+ */
377
+ async insertFeedback(event) {
378
+ const startTime = Date.now();
379
+ try {
380
+ const existing = await this.db.select().from(feedback).where(eq(feedback.requestId, event.requestId)).limit(1);
381
+ if (existing.length > 0) {
382
+ return;
383
+ }
384
+ await this.db.insert(feedback).values(feedbackInsert(event));
385
+ const duration = Date.now() - startTime;
386
+ logSlowQuery("insertFeedback", duration);
387
+ } catch (error) {
388
+ await this.db.insert(quarantineEvents).values(quarantinePayload(event, error));
389
+ }
390
+ }
391
+ /**
392
+ * Batch insert agent suggestions
393
+ */
394
+ async batchInsertAgentSuggestions(events) {
395
+ const startTime = Date.now();
396
+ if (events.length === 0) {
397
+ return;
398
+ }
399
+ try {
400
+ const values = events.map((event) => agentSuggestionInsert(event));
401
+ await this.db.insert(agentSuggestions).values(values);
402
+ const duration = Date.now() - startTime;
403
+ logSlowQuery(`batchInsertAgentSuggestions(${events.length} items)`, duration);
404
+ } catch (error) {
405
+ for (const event of events) {
406
+ await this.db.insert(quarantineEvents).values({
407
+ id: crypto.randomUUID(),
408
+ userId: event.userId,
409
+ apiKeyId: event.apiKeyId,
410
+ originalEvent: event,
411
+ errorReason: error instanceof Error ? error.message : String(error),
412
+ errorStack: error instanceof Error ? error.stack : void 0,
413
+ attemptedAt: /* @__PURE__ */ new Date(),
414
+ createdAt: /* @__PURE__ */ new Date()
415
+ });
416
+ }
417
+ }
418
+ }
419
+ /**
420
+ * Batch insert policy evaluations
421
+ */
422
+ async batchInsertPolicyEvaluations(events) {
423
+ const startTime = Date.now();
424
+ if (events.length === 0) {
425
+ return;
426
+ }
427
+ try {
428
+ const values = events.map((event) => policyEvaluationInsert(event));
429
+ await this.db.insert(policyEvaluations).values(values);
430
+ const duration = Date.now() - startTime;
431
+ logSlowQuery(`batchInsertPolicyEvaluations(${events.length} items)`, duration);
432
+ } catch (error) {
433
+ for (const event of events) {
434
+ await this.db.insert(quarantineEvents).values({
435
+ id: crypto.randomUUID(),
436
+ userId: event.userId,
437
+ apiKeyId: event.apiKeyId,
438
+ originalEvent: event,
439
+ errorReason: error instanceof Error ? error.message : String(error),
440
+ errorStack: error instanceof Error ? error.stack : void 0,
441
+ attemptedAt: /* @__PURE__ */ new Date(),
442
+ createdAt: /* @__PURE__ */ new Date()
443
+ });
444
+ }
445
+ }
446
+ }
447
+ };
448
+ var TelemetrySinkDbAdapter = class {
449
+ static {
450
+ __name(this, "TelemetrySinkDbAdapter");
451
+ }
452
+ db;
453
+ constructor(db2) {
454
+ this.db = db2;
455
+ }
456
+ /**
457
+ * Store telemetry events with idempotency check
458
+ */
459
+ async storeEvents(events) {
460
+ if (events.length === 0) {
461
+ return;
462
+ }
463
+ const values = events.map((event) => ({
464
+ id: event.id,
465
+ userId: this.extractUserId(event),
466
+ apiKeyId: this.extractApiKeyId(event),
467
+ eventType: event.eventType,
468
+ eventCategory: this.categorizeEvent(event),
469
+ properties: this.redactProperties(event.payload),
470
+ sessionId: event.context?.sessionId,
471
+ platform: event.context?.client,
472
+ timestamp: new Date(event.timestamp),
473
+ createdAt: /* @__PURE__ */ new Date()
474
+ }));
475
+ await this.db.insert(telemetryEvents).values(values);
476
+ }
477
+ /**
478
+ * Retrieve telemetry events with optional filtering
479
+ */
480
+ async getEvents(filter) {
481
+ const results = await this.db.select().from(telemetryEvents);
482
+ let events = results.map((row) => this.toTelemetryEvent(row));
483
+ if (filter?.eventType) {
484
+ events = events.filter((e) => e.eventType === filter.eventType);
485
+ }
486
+ if (filter?.sessionId) {
487
+ events = events.filter((e) => e.context?.sessionId === filter.sessionId);
488
+ }
489
+ if (filter?.startTime !== void 0) {
490
+ events = events.filter((e) => e.timestamp >= filter.startTime);
491
+ }
492
+ if (filter?.endTime !== void 0) {
493
+ events = events.filter((e) => e.timestamp <= filter.endTime);
494
+ }
495
+ return events;
496
+ }
497
+ /**
498
+ * Check if a request ID has been processed (idempotency)
499
+ */
500
+ async hasRequestId(requestId) {
501
+ const result = await this.db.select().from(telemetryIdempotencyKeys).where(eq(telemetryIdempotencyKeys.idempotencyKey, requestId)).limit(1);
502
+ return result.length > 0;
503
+ }
504
+ /**
505
+ * Record a request ID as processed (idempotency)
506
+ * Caches response data for duplicate request handling (2026 best practice)
507
+ */
508
+ async recordRequestId(requestId, responseData = {}) {
509
+ const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1e3);
510
+ await this.db.insert(telemetryIdempotencyKeys).values({
511
+ idempotencyKey: requestId,
512
+ responseData,
513
+ createdAt: /* @__PURE__ */ new Date(),
514
+ expiresAt
515
+ });
516
+ }
517
+ /**
518
+ * Extract user ID from event context or payload
519
+ */
520
+ extractUserId(event) {
521
+ return event.payload.userId || event.payload.user_id || null;
522
+ }
523
+ /**
524
+ * Extract API key ID from event context or payload
525
+ */
526
+ extractApiKeyId(event) {
527
+ return event.payload.apiKeyId || event.payload.api_key_id || null;
528
+ }
529
+ /**
530
+ * Categorize event based on eventType
531
+ * Maps to eventCategory enum in schema
532
+ */
533
+ categorizeEvent(event) {
534
+ const type = event.eventType.toLowerCase();
535
+ if (type.includes("error")) {
536
+ return "error";
537
+ }
538
+ if (type.includes("feature") || type.includes("usage")) {
539
+ return "feature_usage";
540
+ }
541
+ if (type.includes("lifecycle") || type.includes("session")) {
542
+ return "lifecycle";
543
+ }
544
+ if (type.includes("engagement") || type.includes("interaction")) {
545
+ return "engagement";
546
+ }
547
+ return "system";
548
+ }
549
+ /**
550
+ * Redact sensitive properties before storage (GDPR compliance 2026)
551
+ * Prevents PII leakage through telemetry data
552
+ */
553
+ redactProperties(properties) {
554
+ const sensitiveFields = [
555
+ "email",
556
+ "password",
557
+ "token",
558
+ "apiKey",
559
+ "api_key",
560
+ "secret",
561
+ "accessToken",
562
+ "access_token",
563
+ "refreshToken",
564
+ "refresh_token",
565
+ "creditCard",
566
+ "credit_card",
567
+ "ssn",
568
+ "socialSecurity"
569
+ ];
570
+ const redacted = {
571
+ ...properties
572
+ };
573
+ for (const field of sensitiveFields) {
574
+ if (field in redacted) {
575
+ redacted[field] = "[REDACTED]";
576
+ }
577
+ }
578
+ return redacted;
579
+ }
580
+ /**
581
+ * Transform database row to TelemetryEvent contract type
582
+ */
583
+ toTelemetryEvent(row) {
584
+ return {
585
+ id: row.id,
586
+ eventType: row.eventType,
587
+ payload: row.properties || {},
588
+ timestamp: row.timestamp.getTime(),
589
+ context: row.sessionId || row.requestId ? {
590
+ sessionId: row.sessionId || "",
591
+ requestId: row.requestId || `synthetic-${row.id}`,
592
+ workspaceId: row.workspaceId || void 0,
593
+ client: row.platform || "unknown"
594
+ } : void 0
595
+ };
596
+ }
597
+ };
598
+
599
+ // ../../packages/platform/dist/db/database-service.js
600
+ var databaseService = {
601
+ drizzle: db,
602
+ isConnected: /* @__PURE__ */ __name(async () => {
603
+ return await checkDatabaseConnection();
604
+ }, "isConnected"),
605
+ disconnect: /* @__PURE__ */ __name(async () => {
606
+ await closeDatabaseConnection();
607
+ }, "disconnect")
608
+ };
609
+ var healthCheck = /* @__PURE__ */ __name(async () => {
610
+ const connected = await checkDatabaseConnection();
611
+ return {
612
+ connected,
613
+ timestamp: /* @__PURE__ */ new Date()
614
+ };
615
+ }, "healthCheck");
616
+ var { userDetectionCapabilities, capabilityAudit } = combinedSchema;
617
+ var userIdSchema = z.string().min(1, "User ID required").transform((val) => val.trim()).refine((val) => val.length > 0, "User ID cannot be whitespace");
618
+ var countSchema = z.number().int().positive("Count must be a positive integer");
619
+ var accuracyScoreSchema = z.number().min(0, "Accuracy score must be between 0.0 and 1.0").max(1, "Accuracy score must be between 0.0 and 1.0");
620
+ var capabilityCache = /* @__PURE__ */ new Map();
621
+ var CACHE_TTL_MS = 60 * 1e3;
622
+ var MAX_CACHE_SIZE = 1e4;
623
+ var cacheAccessOrder = [];
624
+ var cacheHits = 0;
625
+ var cacheMisses = 0;
626
+ function getCacheMetrics() {
627
+ const total = cacheHits + cacheMisses;
628
+ return {
629
+ hits: cacheHits,
630
+ misses: cacheMisses,
631
+ hitRate: total > 0 ? cacheHits / total : 0
632
+ };
633
+ }
634
+ __name(getCacheMetrics, "getCacheMetrics");
635
+ function resetCacheMetrics() {
636
+ cacheHits = 0;
637
+ cacheMisses = 0;
638
+ }
639
+ __name(resetCacheMetrics, "resetCacheMetrics");
640
+ function invalidateCapabilityCache(userId) {
641
+ capabilityCache.delete(userId);
642
+ const index2 = cacheAccessOrder.indexOf(userId);
643
+ if (index2 >= 0) {
644
+ cacheAccessOrder.splice(index2, 1);
645
+ }
646
+ }
647
+ __name(invalidateCapabilityCache, "invalidateCapabilityCache");
648
+ function clearCapabilityCache() {
649
+ capabilityCache.clear();
650
+ cacheAccessOrder.length = 0;
651
+ cacheHits = 0;
652
+ cacheMisses = 0;
653
+ }
654
+ __name(clearCapabilityCache, "clearCapabilityCache");
655
+ function updateCacheWithLRU(userId, data) {
656
+ const existingIndex = cacheAccessOrder.indexOf(userId);
657
+ if (existingIndex >= 0) {
658
+ cacheAccessOrder.splice(existingIndex, 1);
659
+ }
660
+ cacheAccessOrder.push(userId);
661
+ if (cacheAccessOrder.length > MAX_CACHE_SIZE) {
662
+ const evictKey = cacheAccessOrder.shift();
663
+ if (evictKey) {
664
+ capabilityCache.delete(evictKey);
665
+ }
666
+ }
667
+ capabilityCache.set(userId, data);
668
+ }
669
+ __name(updateCacheWithLRU, "updateCacheWithLRU");
670
+ async function getCapabilities(userId, options = {}) {
671
+ const validatedUserId = userIdSchema.parse(userId);
672
+ if (!options.skipCache) {
673
+ const cached = capabilityCache.get(validatedUserId);
674
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
675
+ cacheHits++;
676
+ return cached.data;
677
+ }
678
+ }
679
+ cacheMisses++;
680
+ if (!db) {
681
+ throw new Error("Database not available");
682
+ }
683
+ let capabilities = await db.query.userDetectionCapabilities.findFirst({
684
+ // biome-ignore lint/suspicious/noExplicitAny: Drizzle query builder callback
685
+ where: /* @__PURE__ */ __name((cap, { eq: eq15 }) => eq15(cap.userId, validatedUserId), "where")
686
+ });
687
+ if (!capabilities) {
688
+ const [created] = await db.insert(userDetectionCapabilities).values({
689
+ userId: validatedUserId,
690
+ tier: "free"
691
+ }).onConflictDoUpdate({
692
+ target: userDetectionCapabilities.userId,
693
+ set: {
694
+ lastUpdated: /* @__PURE__ */ new Date()
695
+ }
696
+ }).returning();
697
+ capabilities = created;
698
+ }
699
+ if (!capabilities) {
700
+ throw new Error("Failed to fetch or create capabilities");
701
+ }
702
+ updateCacheWithLRU(validatedUserId, {
703
+ data: capabilities,
704
+ timestamp: Date.now()
705
+ });
706
+ return capabilities;
707
+ }
708
+ __name(getCapabilities, "getCapabilities");
709
+ async function updateCapabilities(userId, updates, expectedVersion) {
710
+ if (!db) {
711
+ throw new Error("Database not available");
712
+ }
713
+ const validatedUserId = userIdSchema.parse(userId);
714
+ const updateValues = {
715
+ lastUpdated: /* @__PURE__ */ new Date(),
716
+ version: sql`${userDetectionCapabilities.version} + 1`
717
+ };
718
+ if (updates.falsePositivePatterns !== void 0) {
719
+ updateValues.falsePositivePatterns = updates.falsePositivePatterns;
720
+ }
721
+ if (updates.customRiskIndicators !== void 0) {
722
+ updateValues.customRiskIndicators = updates.customRiskIndicators;
723
+ }
724
+ if (updates.thresholdOverrides !== void 0) {
725
+ updateValues.thresholdOverrides = updates.thresholdOverrides;
726
+ }
727
+ if (updates.accuracyScore !== void 0) {
728
+ const validatedScore = accuracyScoreSchema.parse(updates.accuracyScore);
729
+ updateValues.accuracyScore = validatedScore.toFixed(4);
730
+ }
731
+ if (updates.toolAccuracy !== void 0) {
732
+ updateValues.toolAccuracy = updates.toolAccuracy;
733
+ }
734
+ if (updates.totalDetectionsAnalyzed !== void 0) {
735
+ updateValues.totalDetectionsAnalyzed = updates.totalDetectionsAnalyzed;
736
+ }
737
+ if (updates.tier !== void 0) {
738
+ updateValues.tier = updates.tier;
739
+ }
740
+ let result;
741
+ if (expectedVersion != null) {
742
+ result = await db.update(userDetectionCapabilities).set(updateValues).where(sql`${userDetectionCapabilities.userId} = ${validatedUserId} AND ${userDetectionCapabilities.version} = ${expectedVersion}`).returning();
743
+ } else {
744
+ result = await db.update(userDetectionCapabilities).set(updateValues).where(eq(userDetectionCapabilities.userId, validatedUserId)).returning();
745
+ }
746
+ invalidateCapabilityCache(validatedUserId);
747
+ return result.length > 0 ? result[0] ?? null : null;
748
+ }
749
+ __name(updateCapabilities, "updateCapabilities");
750
+ async function appendFalsePositivePatterns(userId, patterns2) {
751
+ if (!db || patterns2.length === 0) {
752
+ return null;
753
+ }
754
+ const current = await getCapabilities(userId, {
755
+ skipCache: true
756
+ });
757
+ const existingKeys = new Set(current.falsePositivePatterns?.map((p) => `${p.patternKey}:${p.aiTool}`) ?? []);
758
+ const newPatterns = patterns2.filter((p) => !existingKeys.has(`${p.patternKey}:${p.aiTool}`));
759
+ const mergedPatterns = [
760
+ ...current.falsePositivePatterns ?? [],
761
+ ...newPatterns
762
+ ];
763
+ const version = current.version ?? void 0;
764
+ return await updateCapabilities(userId, {
765
+ falsePositivePatterns: mergedPatterns
766
+ }, version);
767
+ }
768
+ __name(appendFalsePositivePatterns, "appendFalsePositivePatterns");
769
+ async function incrementDetectionsAnalyzed(userId, count) {
770
+ if (!db) {
771
+ return;
772
+ }
773
+ const validatedCount = countSchema.parse(count);
774
+ await db.update(userDetectionCapabilities).set({
775
+ totalDetectionsAnalyzed: sql`${userDetectionCapabilities.totalDetectionsAnalyzed} + ${validatedCount}`,
776
+ lastUpdated: /* @__PURE__ */ new Date()
777
+ }).where(eq(userDetectionCapabilities.userId, userId));
778
+ invalidateCapabilityCache(userId);
779
+ }
780
+ __name(incrementDetectionsAnalyzed, "incrementDetectionsAnalyzed");
781
+ async function handleTierUpgrade(userId, newTier, options = {}) {
782
+ if (!db) {
783
+ throw new Error("Database not available");
784
+ }
785
+ const current = await getCapabilities(userId, {
786
+ skipCache: true
787
+ });
788
+ const oldTier = current.tier ?? "free";
789
+ const version = current.version ?? void 0;
790
+ const updated = await updateCapabilities(userId, {
791
+ tier: newTier
792
+ }, version);
793
+ if (!updated) {
794
+ return;
795
+ }
796
+ await logCapabilityAudit({
797
+ userId,
798
+ capabilityType: "tier_upgraded",
799
+ change: {
800
+ type: "tier_upgraded",
801
+ tier: {
802
+ oldTier,
803
+ newTier
804
+ }
805
+ },
806
+ reason: options.reason ?? "User subscription upgraded",
807
+ sessionId: options.sessionId,
808
+ workspaceId: options.workspaceId
809
+ });
810
+ }
811
+ __name(handleTierUpgrade, "handleTierUpgrade");
812
+ async function handleTierDowngrade(userId, options = {}) {
813
+ if (!db) {
814
+ throw new Error("Database not available");
815
+ }
816
+ const current = await getCapabilities(userId, {
817
+ skipCache: true
818
+ });
819
+ const oldTier = current.tier ?? "free";
820
+ const clearedCapabilities = [];
821
+ if ((current.customRiskIndicators?.length ?? 0) > 0) {
822
+ clearedCapabilities.push("customRiskIndicators");
823
+ }
824
+ if (Object.keys(current.thresholdOverrides ?? {}).length > 0) {
825
+ clearedCapabilities.push("thresholdOverrides");
826
+ }
827
+ if (Object.keys(current.toolAccuracy ?? {}).length > 0) {
828
+ clearedCapabilities.push("toolAccuracy");
829
+ }
830
+ const version = current.version ?? void 0;
831
+ const updated = await updateCapabilities(userId, {
832
+ tier: "free",
833
+ customRiskIndicators: [],
834
+ thresholdOverrides: {},
835
+ toolAccuracy: {}
836
+ }, version);
837
+ if (!updated) {
838
+ return;
839
+ }
840
+ await logCapabilityAudit({
841
+ userId,
842
+ capabilityType: "tier_downgraded",
843
+ change: {
844
+ type: "tier_downgraded",
845
+ tier: {
846
+ oldTier,
847
+ newTier: "free",
848
+ clearedCapabilities
849
+ }
850
+ },
851
+ reason: options.reason ?? "User subscription downgraded",
852
+ sessionId: options.sessionId,
853
+ workspaceId: options.workspaceId
854
+ });
855
+ }
856
+ __name(handleTierDowngrade, "handleTierDowngrade");
857
+ function computeCapabilityAuditIdempotencyKey(params) {
858
+ if (params.idempotencyKey) {
859
+ return params.idempotencyKey;
860
+ }
861
+ const parts = [
862
+ params.userId,
863
+ params.capabilityType,
864
+ params.sessionId ?? "",
865
+ params.workspaceId ?? "",
866
+ params.clientType ?? "",
867
+ params.reason ?? ""
868
+ ];
869
+ if (params.change) {
870
+ parts.push(JSON.stringify(params.change));
871
+ }
872
+ if (params.performanceBefore) {
873
+ parts.push(JSON.stringify(params.performanceBefore));
874
+ }
875
+ if (params.performanceAfter) {
876
+ parts.push(JSON.stringify(params.performanceAfter));
877
+ }
878
+ return parts.join("|");
879
+ }
880
+ __name(computeCapabilityAuditIdempotencyKey, "computeCapabilityAuditIdempotencyKey");
881
+ async function logCapabilityAudit(params) {
882
+ if (!db) {
883
+ return;
884
+ }
885
+ const idempotencyKey = computeCapabilityAuditIdempotencyKey(params);
886
+ const values = {
887
+ userId: params.userId,
888
+ capabilityType: params.capabilityType,
889
+ change: params.change,
890
+ reason: params.reason,
891
+ performanceBefore: params.performanceBefore,
892
+ performanceAfter: params.performanceAfter,
893
+ sessionId: params.sessionId,
894
+ workspaceId: params.workspaceId,
895
+ clientType: params.clientType,
896
+ idempotencyKey
897
+ };
898
+ await db.insert(capabilityAudit).values(values).onConflictDoNothing({
899
+ target: capabilityAudit.idempotencyKey
900
+ });
901
+ }
902
+ __name(logCapabilityAudit, "logCapabilityAudit");
903
+ async function getCapabilityAuditHistory(userId, limit = 50) {
904
+ if (!db) {
905
+ return [];
906
+ }
907
+ return await db.query.capabilityAudit.findMany({
908
+ // biome-ignore lint/suspicious/noExplicitAny: Drizzle query builder callbacks
909
+ where: /* @__PURE__ */ __name((audit, { eq: eq15 }) => eq15(audit.userId, userId), "where"),
910
+ orderBy: /* @__PURE__ */ __name((audit, { desc: desc4 }) => desc4(audit.createdAt), "orderBy"),
911
+ limit
912
+ });
913
+ }
914
+ __name(getCapabilityAuditHistory, "getCapabilityAuditHistory");
915
+ async function recordFalsePositiveSignal(userId, signal, options = {}) {
916
+ if (!db) {
917
+ return null;
918
+ }
919
+ const current = await getCapabilities(userId, {
920
+ skipCache: true
921
+ });
922
+ const patterns2 = [
923
+ ...current.falsePositivePatterns ?? []
924
+ ];
925
+ const existingIndex = patterns2.findIndex((p) => p.patternKey === signal.patternKey && p.aiTool === signal.aiTool && p.filePattern === signal.filePattern);
926
+ let learned;
927
+ if (existingIndex >= 0) {
928
+ const existingPattern = patterns2[existingIndex];
929
+ if (!existingPattern) {
930
+ throw new Error("Pattern not found at expected index");
931
+ }
932
+ learned = mergeSignalIntoPattern(existingPattern, signal);
933
+ patterns2[existingIndex] = learned;
934
+ } else {
935
+ learned = signalToPattern(signal);
936
+ patterns2.push(learned);
937
+ }
938
+ const version = current.version ?? void 0;
939
+ const updated = await updateCapabilities(userId, {
940
+ falsePositivePatterns: patterns2
941
+ }, version);
942
+ if (!updated) {
943
+ return null;
944
+ }
945
+ await logCapabilityAudit({
946
+ userId,
947
+ capabilityType: "pattern_learned",
948
+ change: {
949
+ type: "pattern_learned",
950
+ patterns: [
951
+ {
952
+ patternKey: learned.patternKey,
953
+ aiTool: learned.aiTool,
954
+ weight: learned.weight,
955
+ decayedWeight: learned.decayedWeight,
956
+ source: signal.type
957
+ }
958
+ ]
959
+ },
960
+ reason: options.reason ?? "False positive feedback recorded",
961
+ performanceBefore: void 0,
962
+ performanceAfter: void 0,
963
+ sessionId: options.sessionId,
964
+ workspaceId: options.workspaceId,
965
+ clientType: options.clientType
966
+ });
967
+ return updated;
968
+ }
969
+ __name(recordFalsePositiveSignal, "recordFalsePositiveSignal");
970
+ async function resetCapabilities(userId, options = {}) {
971
+ if (!db) {
972
+ return null;
973
+ }
974
+ const current = await getCapabilities(userId, {
975
+ skipCache: true
976
+ });
977
+ const version = current.version ?? void 0;
978
+ const updated = await updateCapabilities(userId, {
979
+ falsePositivePatterns: [],
980
+ customRiskIndicators: [],
981
+ thresholdOverrides: {},
982
+ accuracyScore: 0,
983
+ toolAccuracy: {},
984
+ totalDetectionsAnalyzed: 0,
985
+ tier: "free"
986
+ }, version);
987
+ if (!updated) {
988
+ return null;
989
+ }
990
+ await logCapabilityAudit({
991
+ userId,
992
+ capabilityType: "capabilities_reset",
993
+ change: {
994
+ type: "capabilities_reset",
995
+ reason: options.reason ?? "Capabilities reset to baseline"
996
+ },
997
+ reason: options.reason ?? "Capabilities reset to baseline",
998
+ performanceBefore: void 0,
999
+ performanceAfter: void 0,
1000
+ sessionId: options.sessionId,
1001
+ workspaceId: options.workspaceId,
1002
+ clientType: options.clientType
1003
+ });
1004
+ return updated;
1005
+ }
1006
+ __name(resetCapabilities, "resetCapabilities");
1007
+ var IMPLICIT_WEIGHT = 1;
1008
+ var EXPLICIT_WEIGHT = 3;
1009
+ var DECAY_HALF_LIFE_DAYS = 14;
1010
+ function calculateDecayedWeight(signal) {
1011
+ const baseWeight = signal.type === "explicit" ? EXPLICIT_WEIGHT : IMPLICIT_WEIGHT;
1012
+ const timestamp3 = signal.timestamp ?? Date.now();
1013
+ const ageMs = Date.now() - timestamp3;
1014
+ const ageDays = ageMs / (24 * 60 * 60 * 1e3);
1015
+ const decayFactor = 0.5 ** (ageDays / DECAY_HALF_LIFE_DAYS);
1016
+ return baseWeight * decayFactor;
1017
+ }
1018
+ __name(calculateDecayedWeight, "calculateDecayedWeight");
1019
+ function signalToPattern(signal) {
1020
+ const weight = signal.type === "explicit" ? EXPLICIT_WEIGHT : IMPLICIT_WEIGHT;
1021
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1022
+ return {
1023
+ patternKey: signal.patternKey,
1024
+ aiTool: signal.aiTool,
1025
+ filePattern: signal.filePattern,
1026
+ proceedCount: 1,
1027
+ weight,
1028
+ decayedWeight: calculateDecayedWeight(signal),
1029
+ firstSeen: now,
1030
+ lastSeen: now
1031
+ };
1032
+ }
1033
+ __name(signalToPattern, "signalToPattern");
1034
+ function mergeSignalIntoPattern(existing, signal) {
1035
+ const newWeight = signal.type === "explicit" ? EXPLICIT_WEIGHT : IMPLICIT_WEIGHT;
1036
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1037
+ return {
1038
+ ...existing,
1039
+ proceedCount: existing.proceedCount + 1,
1040
+ weight: existing.weight + newWeight,
1041
+ decayedWeight: calculateDecayedWeight({
1042
+ ...signal,
1043
+ timestamp: Date.now()
1044
+ }),
1045
+ lastSeen: now
1046
+ };
1047
+ }
1048
+ __name(mergeSignalIntoPattern, "mergeSignalIntoPattern");
1049
+ var { organization: organization2, member: member2 } = combinedSchema;
1050
+ async function getOrganizations({ limit, offset, query }) {
1051
+ if (!db) {
1052
+ throw new Error("Database not available");
1053
+ }
1054
+ return await db.query.organization.findMany({
1055
+ where: query ? (org, { like }) => like(org.name, `%${query}%`) : void 0,
1056
+ limit,
1057
+ offset,
1058
+ extras: {
1059
+ membersCount: sql`(SELECT COUNT(*) FROM ${member2} WHERE ${member2.organizationId} = ${organization2.id})`.as("membersCount")
1060
+ }
1061
+ });
1062
+ }
1063
+ __name(getOrganizations, "getOrganizations");
1064
+ async function countAllOrganizations() {
1065
+ if (!db) {
1066
+ throw new Error("Database not available");
1067
+ }
1068
+ return await db.$count(organization2);
1069
+ }
1070
+ __name(countAllOrganizations, "countAllOrganizations");
1071
+ async function getOrganizationById(id) {
1072
+ if (!db) {
1073
+ throw new Error("Database not available");
1074
+ }
1075
+ return await db.query.organization.findFirst({
1076
+ where: /* @__PURE__ */ __name((org, { eq: eq15 }) => eq15(org.id, id), "where"),
1077
+ with: {
1078
+ members: true,
1079
+ invitations: true
1080
+ }
1081
+ });
1082
+ }
1083
+ __name(getOrganizationById, "getOrganizationById");
1084
+ async function getOrganizationsWithMembers(userId) {
1085
+ if (!db) {
1086
+ throw new Error("Database not available");
1087
+ }
1088
+ return await db.query.organization.findMany({
1089
+ with: {
1090
+ members: {
1091
+ where: /* @__PURE__ */ __name((member3, { eq: eq15 }) => eq15(member3.userId, userId), "where"),
1092
+ with: {
1093
+ user: true
1094
+ }
1095
+ }
1096
+ }
1097
+ });
1098
+ }
1099
+ __name(getOrganizationsWithMembers, "getOrganizationsWithMembers");
1100
+ async function getInvitationById(id) {
1101
+ if (!db) {
1102
+ throw new Error("Database not available");
1103
+ }
1104
+ return await db.query.invitation.findFirst({
1105
+ where: /* @__PURE__ */ __name((invitation2, { eq: eq15 }) => eq15(invitation2.id, id), "where"),
1106
+ with: {
1107
+ organization: true
1108
+ }
1109
+ });
1110
+ }
1111
+ __name(getInvitationById, "getInvitationById");
1112
+ async function getOrganizationBySlug(slug) {
1113
+ if (!db) {
1114
+ throw new Error("Database not available");
1115
+ }
1116
+ return await db.query.organization.findFirst({
1117
+ where: /* @__PURE__ */ __name((org, { eq: eq15 }) => eq15(org.slug, slug), "where")
1118
+ });
1119
+ }
1120
+ __name(getOrganizationBySlug, "getOrganizationBySlug");
1121
+ async function getOrganizationMembership(organizationId, userId) {
1122
+ if (!db) {
1123
+ throw new Error("Database not available");
1124
+ }
1125
+ return await db.query.member.findFirst({
1126
+ where: /* @__PURE__ */ __name((member3, { and: and5, eq: eq15 }) => and5(eq15(member3.organizationId, organizationId), eq15(member3.userId, userId)), "where"),
1127
+ with: {
1128
+ organization: true
1129
+ }
1130
+ });
1131
+ }
1132
+ __name(getOrganizationMembership, "getOrganizationMembership");
1133
+ async function getOrganizationWithPurchasesAndMembersCount(organizationId) {
1134
+ if (!db) {
1135
+ throw new Error("Database not available");
1136
+ }
1137
+ return await db.query.organization.findFirst({
1138
+ where: /* @__PURE__ */ __name((org, { eq: eq15 }) => eq15(org.id, organizationId), "where"),
1139
+ with: {
1140
+ purchases: true
1141
+ },
1142
+ extras: {
1143
+ membersCount: sql`(SELECT COUNT(*) FROM ${member2} WHERE ${member2.organizationId} = ${organization2.id})`.as("membersCount")
1144
+ }
1145
+ });
1146
+ }
1147
+ __name(getOrganizationWithPurchasesAndMembersCount, "getOrganizationWithPurchasesAndMembersCount");
1148
+ async function getPendingInvitationByEmail(email) {
1149
+ if (!db) {
1150
+ throw new Error("Database not available");
1151
+ }
1152
+ return await db.query.invitation.findFirst({
1153
+ where: /* @__PURE__ */ __name((invitation2, { and: and5, eq: eq15 }) => and5(eq15(invitation2.email, email), eq15(invitation2.status, "pending")), "where")
1154
+ });
1155
+ }
1156
+ __name(getPendingInvitationByEmail, "getPendingInvitationByEmail");
1157
+ async function updateOrganization(updatedOrganization) {
1158
+ if (!db) {
1159
+ throw new Error("Database not available");
1160
+ }
1161
+ return await db.update(organization2).set(updatedOrganization).where(eq(organization2.id, updatedOrganization.id));
1162
+ }
1163
+ __name(updateOrganization, "updateOrganization");
1164
+ async function generateOrganizationSlug(name) {
1165
+ const baseSlug = slugify(name, {
1166
+ lowercase: true
1167
+ });
1168
+ let slug = baseSlug;
1169
+ let hasAvailableSlug = false;
1170
+ for (let i = 0; i < 3; i++) {
1171
+ const existing = await getOrganizationBySlug(slug);
1172
+ if (!existing) {
1173
+ hasAvailableSlug = true;
1174
+ break;
1175
+ }
1176
+ slug = `${baseSlug}-${nanoid(5)}`;
1177
+ }
1178
+ if (!hasAvailableSlug) {
1179
+ throw new Error("Could not generate unique slug");
1180
+ }
1181
+ return slug;
1182
+ }
1183
+ __name(generateOrganizationSlug, "generateOrganizationSlug");
1184
+ var PRIVACY_SALT = process.env.PRIVACY_SALT || "default-salt";
1185
+ function anonymizeUserId(userId) {
1186
+ const hash = crypto2.createHash("sha256").update(userId + PRIVACY_SALT).digest("hex");
1187
+ return `anon_${hash.slice(0, 16)}`;
1188
+ }
1189
+ __name(anonymizeUserId, "anonymizeUserId");
1190
+ function anonymizeEmail(email) {
1191
+ const [local, domain] = email.split("@");
1192
+ if (!domain) {
1193
+ return anonymizeUserId(email);
1194
+ }
1195
+ const domainHash = crypto2.createHash("sha256").update(domain + PRIVACY_SALT).digest("hex").slice(0, 8);
1196
+ const maskedLocal = `${local.charAt(0)}***`;
1197
+ return `${maskedLocal}@${domainHash}`;
1198
+ }
1199
+ __name(anonymizeEmail, "anonymizeEmail");
1200
+ function sanitizeForLogging(obj) {
1201
+ const sensitiveFields = [
1202
+ "password",
1203
+ "email",
1204
+ "token",
1205
+ "apiKey",
1206
+ "key",
1207
+ "secret",
1208
+ "refreshToken",
1209
+ "accessToken",
1210
+ "salt",
1211
+ "hash"
1212
+ ];
1213
+ const result = {
1214
+ ...obj
1215
+ };
1216
+ for (const field of sensitiveFields) {
1217
+ if (field in result) {
1218
+ result[field] = "[REDACTED]";
1219
+ }
1220
+ }
1221
+ return result;
1222
+ }
1223
+ __name(sanitizeForLogging, "sanitizeForLogging");
1224
+ async function logAnonymizedEvent(event, data, userId) {
1225
+ try {
1226
+ const anonymousId = userId ? anonymizeUserId(userId) : void 0;
1227
+ const sanitized = sanitizeForLogging({
1228
+ ...data,
1229
+ userId: void 0,
1230
+ email: void 0,
1231
+ apiKeyId: void 0,
1232
+ token: void 0
1233
+ });
1234
+ logger.info(`Analytics: ${event}`, {
1235
+ event,
1236
+ anonymousId,
1237
+ ...sanitized,
1238
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1239
+ });
1240
+ } catch (error) {
1241
+ logger.error("Failed to log anonymized event", {
1242
+ error: error instanceof Error ? error.message : String(error),
1243
+ event
1244
+ });
1245
+ }
1246
+ }
1247
+ __name(logAnonymizedEvent, "logAnonymizedEvent");
1248
+ async function exportUserData(userId) {
1249
+ try {
1250
+ if (!db) {
1251
+ logger.error("Database not initialized");
1252
+ return null;
1253
+ }
1254
+ const userRecord = await db.select().from(user).where(eq(user.id, userId)).then((rows) => rows[0] || null);
1255
+ if (!userRecord) {
1256
+ logger.warn("User not found for data export", {
1257
+ userId
1258
+ });
1259
+ return null;
1260
+ }
1261
+ const [userSessions, userAccounts, userApiKeys, userSubscriptions] = await Promise.all([
1262
+ db.select().from(session).where(eq(session.userId, userId)),
1263
+ db.select().from(account).where(eq(account.userId, userId)),
1264
+ db.select().from(apiKeys).where(eq(apiKeys.userId, userId)),
1265
+ db.select().from(subscriptions).where(eq(subscriptions.userId, userId))
1266
+ ]);
1267
+ const sanitizedUser = sanitizeForLogging(userRecord);
1268
+ const sanitizedApiKeys = userApiKeys.map((k) => sanitizeForLogging(k));
1269
+ return {
1270
+ user: sanitizedUser,
1271
+ sessions: userSessions,
1272
+ accounts: userAccounts,
1273
+ apiKeys: sanitizedApiKeys,
1274
+ subscriptions: userSubscriptions,
1275
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString()
1276
+ };
1277
+ } catch (error) {
1278
+ logger.error("Failed to export user data", {
1279
+ error: error instanceof Error ? error.message : String(error),
1280
+ userId
1281
+ });
1282
+ throw error;
1283
+ }
1284
+ }
1285
+ __name(exportUserData, "exportUserData");
1286
+ async function deleteUserData(userId) {
1287
+ try {
1288
+ if (!db) {
1289
+ logger.error("Database not initialized");
1290
+ return false;
1291
+ }
1292
+ logger.warn("Deleting all user data (GDPR Right to Erasure)", {
1293
+ userId
1294
+ });
1295
+ await db.delete(user).where(eq(user.id, userId));
1296
+ logger.info("User data deleted successfully", {
1297
+ userId
1298
+ });
1299
+ return true;
1300
+ } catch (error) {
1301
+ logger.error("Failed to delete user data", {
1302
+ error: error instanceof Error ? error.message : String(error),
1303
+ userId
1304
+ });
1305
+ throw error;
1306
+ }
1307
+ }
1308
+ __name(deleteUserData, "deleteUserData");
1309
+ async function deleteUserApiKeys(userId) {
1310
+ try {
1311
+ if (!db) {
1312
+ logger.error("Database not initialized");
1313
+ return 0;
1314
+ }
1315
+ const result = await db.delete(apiKeys).where(eq(apiKeys.userId, userId));
1316
+ logger.info("User API keys deleted", {
1317
+ userId
1318
+ });
1319
+ return result.rowCount || 0;
1320
+ } catch (error) {
1321
+ logger.error("Failed to delete user API keys", {
1322
+ error: error instanceof Error ? error.message : String(error),
1323
+ userId
1324
+ });
1325
+ throw error;
1326
+ }
1327
+ }
1328
+ __name(deleteUserApiKeys, "deleteUserApiKeys");
1329
+ async function anonymizeUserData(userId) {
1330
+ try {
1331
+ if (!db) {
1332
+ logger.error("Database not initialized");
1333
+ return false;
1334
+ }
1335
+ logger.info("Anonymizing user data", {
1336
+ userId
1337
+ });
1338
+ await db.update(user).set({
1339
+ email: `deleted+${anonymizeUserId(userId)}@vreko.local`,
1340
+ name: "Deleted User",
1341
+ image: null,
1342
+ username: null
1343
+ }).where(eq(user.id, userId));
1344
+ await db.delete(session).where(eq(session.userId, userId));
1345
+ await db.delete(apiKeys).where(eq(apiKeys.userId, userId));
1346
+ logger.info("User data anonymized successfully", {
1347
+ userId
1348
+ });
1349
+ return true;
1350
+ } catch (error) {
1351
+ logger.error("Failed to anonymize user data", {
1352
+ error: error instanceof Error ? error.message : String(error),
1353
+ userId
1354
+ });
1355
+ throw error;
1356
+ }
1357
+ }
1358
+ __name(anonymizeUserData, "anonymizeUserData");
1359
+ async function getUserPrivacyPreferences(userId) {
1360
+ try {
1361
+ if (!db) {
1362
+ logger.error("Database not initialized");
1363
+ return null;
1364
+ }
1365
+ const userRecord = await db.select().from(user).where(eq(user.id, userId)).then((rows) => rows[0] || null);
1366
+ if (!userRecord) {
1367
+ return null;
1368
+ }
1369
+ return {
1370
+ analyticsConsent: true,
1371
+ marketingConsent: false,
1372
+ sharingConsent: false
1373
+ };
1374
+ } catch (error) {
1375
+ logger.error("Failed to get privacy preferences", {
1376
+ error: error instanceof Error ? error.message : String(error),
1377
+ userId
1378
+ });
1379
+ return null;
1380
+ }
1381
+ }
1382
+ __name(getUserPrivacyPreferences, "getUserPrivacyPreferences");
1383
+ function shouldRetainData(createdAt, retentionDays = 90) {
1384
+ const retentionMs = retentionDays * 24 * 60 * 60 * 1e3;
1385
+ const ageMs = Date.now() - createdAt.getTime();
1386
+ return ageMs < retentionMs;
1387
+ }
1388
+ __name(shouldRetainData, "shouldRetainData");
1389
+ async function cleanupExpiredData(retentionDays = 90) {
1390
+ try {
1391
+ if (!db) {
1392
+ logger.error("Database not initialized");
1393
+ return;
1394
+ }
1395
+ const cutoffDate = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1e3);
1396
+ await db.delete(session).where(lte(session.expiresAt, cutoffDate));
1397
+ logger.info("Data cleanup completed", {
1398
+ retentionDays,
1399
+ cutoffDate: cutoffDate.toISOString()
1400
+ });
1401
+ } catch (error) {
1402
+ logger.error("Data cleanup failed", {
1403
+ error: error instanceof Error ? error.message : String(error)
1404
+ });
1405
+ }
1406
+ }
1407
+ __name(cleanupExpiredData, "cleanupExpiredData");
1408
+ var { purchase: purchase2 } = combinedSchema;
1409
+ async function getPurchasesByOrganizationId(organizationId) {
1410
+ if (!db) {
1411
+ throw new Error("Database not available");
1412
+ }
1413
+ return db.query.purchase.findMany({
1414
+ where: /* @__PURE__ */ __name((purchase3, { eq: eq15 }) => eq15(purchase3.organizationId, organizationId), "where")
1415
+ });
1416
+ }
1417
+ __name(getPurchasesByOrganizationId, "getPurchasesByOrganizationId");
1418
+ async function getPurchasesByUserId(userId) {
1419
+ if (!db) {
1420
+ throw new Error("Database not available");
1421
+ }
1422
+ return db.query.purchase.findMany({
1423
+ where: /* @__PURE__ */ __name((purchase3, { eq: eq15 }) => eq15(purchase3.userId, userId), "where")
1424
+ });
1425
+ }
1426
+ __name(getPurchasesByUserId, "getPurchasesByUserId");
1427
+ async function getPurchaseById(id) {
1428
+ if (!db) {
1429
+ throw new Error("Database not available");
1430
+ }
1431
+ return db.query.purchase.findFirst({
1432
+ where: /* @__PURE__ */ __name((purchase3, { eq: eq15 }) => eq15(purchase3.id, id), "where")
1433
+ });
1434
+ }
1435
+ __name(getPurchaseById, "getPurchaseById");
1436
+ async function getPurchaseBySubscriptionId(subscriptionId) {
1437
+ if (!db) {
1438
+ throw new Error("Database not available");
1439
+ }
1440
+ return db.query.purchase.findFirst({
1441
+ where: /* @__PURE__ */ __name((purchase3, { eq: eq15 }) => eq15(purchase3.subscriptionId, subscriptionId), "where")
1442
+ });
1443
+ }
1444
+ __name(getPurchaseBySubscriptionId, "getPurchaseBySubscriptionId");
1445
+ async function createPurchase(insertedPurchase) {
1446
+ if (!db) {
1447
+ throw new Error("Database not available");
1448
+ }
1449
+ const result = await db.insert(purchase2).values(insertedPurchase).returning({
1450
+ id: purchase2.id
1451
+ });
1452
+ const firstResult = result[0];
1453
+ if (!firstResult) {
1454
+ throw new Error("Failed to create purchase");
1455
+ }
1456
+ const { id } = firstResult;
1457
+ return getPurchaseById(id);
1458
+ }
1459
+ __name(createPurchase, "createPurchase");
1460
+ async function updatePurchase(updatedPurchase) {
1461
+ if (!db) {
1462
+ throw new Error("Database not available");
1463
+ }
1464
+ const result = await db.update(purchase2).set(updatedPurchase).returning({
1465
+ id: purchase2.id
1466
+ });
1467
+ const firstResult = result[0];
1468
+ if (!firstResult) {
1469
+ throw new Error("Failed to update purchase");
1470
+ }
1471
+ const { id } = firstResult;
1472
+ return getPurchaseById(id);
1473
+ }
1474
+ __name(updatePurchase, "updatePurchase");
1475
+ async function deletePurchaseBySubscriptionId(subscriptionId) {
1476
+ if (!db) {
1477
+ throw new Error("Database not available");
1478
+ }
1479
+ await db.delete(purchase2).where(eq(purchase2.subscriptionId, subscriptionId));
1480
+ }
1481
+ __name(deletePurchaseBySubscriptionId, "deletePurchaseBySubscriptionId");
1482
+ var { user: user2, account: account2 } = combinedSchema;
1483
+ var searchUsersSchema = z.object({
1484
+ query: z.string().min(1).max(100).optional(),
1485
+ limit: z.number().min(1).max(100).default(50),
1486
+ offset: z.number().min(0).default(0)
1487
+ });
1488
+ async function getUsers({ limit, offset, query }) {
1489
+ if (!db) {
1490
+ throw new Error("Database not available");
1491
+ }
1492
+ const validatedParams = searchUsersSchema.parse({
1493
+ query,
1494
+ limit,
1495
+ offset
1496
+ });
1497
+ const whereClause = query ? (user3, { like, sql: sql5 }) => like(user3.name, sql5`${"%"}${validatedParams.query}${"%"}`) : void 0;
1498
+ return await db.query.user.findMany({
1499
+ where: whereClause,
1500
+ limit: validatedParams.limit,
1501
+ offset: validatedParams.offset
1502
+ });
1503
+ }
1504
+ __name(getUsers, "getUsers");
1505
+ async function countAllUsers() {
1506
+ if (!db) {
1507
+ throw new Error("Database not available");
1508
+ }
1509
+ return db.$count(user2);
1510
+ }
1511
+ __name(countAllUsers, "countAllUsers");
1512
+ async function getUserById(id) {
1513
+ if (!db) {
1514
+ throw new Error("Database not available");
1515
+ }
1516
+ return await db.query.user.findFirst({
1517
+ // biome-ignore lint/suspicious/noExplicitAny: Drizzle query builder callback
1518
+ where: /* @__PURE__ */ __name((user3, { eq: eq15 }) => eq15(user3.id, id), "where")
1519
+ });
1520
+ }
1521
+ __name(getUserById, "getUserById");
1522
+ async function getUserByEmail(email) {
1523
+ if (!db) {
1524
+ throw new Error("Database not available");
1525
+ }
1526
+ return await db.query.user.findFirst({
1527
+ // biome-ignore lint/suspicious/noExplicitAny: Drizzle query builder callback
1528
+ where: /* @__PURE__ */ __name((user3, { eq: eq15 }) => eq15(user3.email, email), "where")
1529
+ });
1530
+ }
1531
+ __name(getUserByEmail, "getUserByEmail");
1532
+ async function createUser({ email, name, role, emailVerified, onboardingComplete }) {
1533
+ if (!db) {
1534
+ throw new Error("Database not available");
1535
+ }
1536
+ const result = await db.insert(user2).values({
1537
+ email,
1538
+ name,
1539
+ role,
1540
+ emailVerified,
1541
+ onboardingComplete,
1542
+ createdAt: /* @__PURE__ */ new Date(),
1543
+ updatedAt: /* @__PURE__ */ new Date()
1544
+ }).returning({
1545
+ id: user2.id
1546
+ });
1547
+ const firstResult = result[0];
1548
+ if (!firstResult) {
1549
+ throw new Error("Failed to create user");
1550
+ }
1551
+ const { id } = firstResult;
1552
+ const newUser = await getUserById(id);
1553
+ return newUser;
1554
+ }
1555
+ __name(createUser, "createUser");
1556
+ async function getAccountById(id) {
1557
+ if (!db) {
1558
+ throw new Error("Database not available");
1559
+ }
1560
+ return await db.query.account.findFirst({
1561
+ // biome-ignore lint/suspicious/noExplicitAny: Drizzle query builder callback
1562
+ where: /* @__PURE__ */ __name((account3, { eq: eq15 }) => eq15(account3.id, id), "where")
1563
+ });
1564
+ }
1565
+ __name(getAccountById, "getAccountById");
1566
+ async function createUserAccount({ userId, providerId, accountId, hashedPassword }) {
1567
+ if (!db) {
1568
+ throw new Error("Database not available");
1569
+ }
1570
+ const result = await db.insert(account2).values({
1571
+ userId,
1572
+ accountId,
1573
+ providerId,
1574
+ createdAt: /* @__PURE__ */ new Date(),
1575
+ updatedAt: /* @__PURE__ */ new Date(),
1576
+ password: hashedPassword
1577
+ }).returning({
1578
+ id: account2.id
1579
+ });
1580
+ const firstResult = result[0];
1581
+ if (!firstResult) {
1582
+ throw new Error("Failed to create account");
1583
+ }
1584
+ const { id } = firstResult;
1585
+ const newAccount = await getAccountById(id);
1586
+ return newAccount;
1587
+ }
1588
+ __name(createUserAccount, "createUserAccount");
1589
+ async function updateUser(updatedUser) {
1590
+ if (!db) {
1591
+ throw new Error("Database not available");
1592
+ }
1593
+ return db.update(user2).set(updatedUser).where(eq(user2.id, updatedUser.id));
1594
+ }
1595
+ __name(updateUser, "updateUser");
1596
+ var { workspaceLinks } = combinedSchema;
1597
+ var workspaceIdSchema = z.string().regex(/^([a-f0-9]{12}|ws[g]?_[a-f0-9]{32})$/, "Invalid workspace ID format: must be 12-char hex (unified) or ws_/wsg_ + 32-char hex (legacy)");
1598
+ var linkWorkspaceSchema = z.object({
1599
+ workspaceId: workspaceIdSchema,
1600
+ userId: z.string().min(1),
1601
+ tier: z.enum([
1602
+ "free",
1603
+ "pro",
1604
+ "enterprise"
1605
+ ]).optional().default("free"),
1606
+ displayName: z.string().max(255).optional(),
1607
+ expiresAt: z.date().optional()
1608
+ });
1609
+ var updateTierSchema = z.object({
1610
+ workspaceId: workspaceIdSchema,
1611
+ tier: z.enum([
1612
+ "free",
1613
+ "pro",
1614
+ "enterprise"
1615
+ ])
1616
+ });
1617
+ async function getWorkspaceLinkById(workspaceId) {
1618
+ if (!db) {
1619
+ throw new Error("Database not available");
1620
+ }
1621
+ const validatedId = workspaceIdSchema.parse(workspaceId);
1622
+ return await db.query.workspaceLinks.findFirst({
1623
+ // biome-ignore lint/suspicious/noExplicitAny: Drizzle query builder callback
1624
+ where: /* @__PURE__ */ __name((link, { eq: eq15 }) => eq15(link.workspaceId, validatedId), "where")
1625
+ });
1626
+ }
1627
+ __name(getWorkspaceLinkById, "getWorkspaceLinkById");
1628
+ async function resolveTierByWorkspaceId(workspaceId) {
1629
+ if (!db) {
1630
+ return {
1631
+ found: false,
1632
+ tier: "free"
1633
+ };
1634
+ }
1635
+ try {
1636
+ const validatedId = workspaceIdSchema.parse(workspaceId);
1637
+ const link = await db.query.workspaceLinks.findFirst({
1638
+ // biome-ignore lint/suspicious/noExplicitAny: Drizzle query builder callback
1639
+ where: /* @__PURE__ */ __name((link2, { eq: eq15 }) => eq15(link2.workspaceId, validatedId), "where")
1640
+ });
1641
+ if (!link) {
1642
+ return {
1643
+ found: false,
1644
+ tier: "free"
1645
+ };
1646
+ }
1647
+ if (link.expiresAt && link.expiresAt < /* @__PURE__ */ new Date()) {
1648
+ await db.delete(workspaceLinks).where(eq(workspaceLinks.workspaceId, validatedId));
1649
+ process.stdout.write(`[Workspace Links] Deleted expired link: ${validatedId.slice(0, 10)}...`);
1650
+ return {
1651
+ found: false,
1652
+ tier: "free"
1653
+ };
1654
+ }
1655
+ const staleCutoff = new Date(Date.now() - TIER_STALENESS_THRESHOLD_MS);
1656
+ const tierRefreshedAt = link.tierRefreshedAt ?? link.createdAt;
1657
+ let tierRefreshed = false;
1658
+ let currentTier = link.tier;
1659
+ if (tierRefreshedAt < staleCutoff) {
1660
+ const [subscription] = await db.select({
1661
+ plan: subscriptions.plan
1662
+ }).from(subscriptions).where(eq(subscriptions.userId, link.userId)).limit(1);
1663
+ const freshTier = mapPlanToTier(subscription?.plan);
1664
+ await db.update(workspaceLinks).set({
1665
+ tier: freshTier,
1666
+ tierRefreshedAt: /* @__PURE__ */ new Date(),
1667
+ lastSeenAt: /* @__PURE__ */ new Date()
1668
+ }).where(eq(workspaceLinks.workspaceId, validatedId));
1669
+ currentTier = freshTier;
1670
+ tierRefreshed = true;
1671
+ process.stdout.write(`[Workspace Links] Refreshed stale tier for ${validatedId.slice(0, 10)}...: ${link.tier} -> ${freshTier}`);
1672
+ } else {
1673
+ db.update(workspaceLinks).set({
1674
+ lastSeenAt: /* @__PURE__ */ new Date()
1675
+ }).where(eq(workspaceLinks.workspaceId, validatedId)).catch(() => {
1676
+ });
1677
+ }
1678
+ return {
1679
+ found: true,
1680
+ tier: currentTier,
1681
+ userId: link.userId,
1682
+ displayName: link.displayName ?? void 0,
1683
+ tierRefreshed
1684
+ };
1685
+ } catch (_error) {
1686
+ return {
1687
+ found: false,
1688
+ tier: "free"
1689
+ };
1690
+ }
1691
+ }
1692
+ __name(resolveTierByWorkspaceId, "resolveTierByWorkspaceId");
1693
+ function mapPlanToTier(plan) {
1694
+ switch (plan) {
1695
+ case "pro":
1696
+ case "solo":
1697
+ return "pro";
1698
+ case "team":
1699
+ case "enterprise":
1700
+ return "enterprise";
1701
+ default:
1702
+ return "free";
1703
+ }
1704
+ }
1705
+ __name(mapPlanToTier, "mapPlanToTier");
1706
+ async function linkWorkspace(params) {
1707
+ if (!db) {
1708
+ throw new Error("Database not available");
1709
+ }
1710
+ const validated = linkWorkspaceSchema.parse(params);
1711
+ const expiresAt = validated.expiresAt ?? new Date(Date.now() + WORKSPACE_LINK_TTL_MS);
1712
+ const existing = await getWorkspaceLinkById(validated.workspaceId);
1713
+ if (existing) {
1714
+ await db.update(workspaceLinks).set({
1715
+ userId: validated.userId,
1716
+ tier: validated.tier,
1717
+ displayName: validated.displayName,
1718
+ lastSeenAt: /* @__PURE__ */ new Date(),
1719
+ tierRefreshedAt: /* @__PURE__ */ new Date(),
1720
+ expiresAt
1721
+ }).where(eq(workspaceLinks.workspaceId, validated.workspaceId));
1722
+ return await getWorkspaceLinkById(validated.workspaceId);
1723
+ }
1724
+ await db.insert(workspaceLinks).values({
1725
+ workspaceId: validated.workspaceId,
1726
+ userId: validated.userId,
1727
+ tier: validated.tier,
1728
+ displayName: validated.displayName,
1729
+ tierRefreshedAt: /* @__PURE__ */ new Date(),
1730
+ expiresAt
1731
+ });
1732
+ return await getWorkspaceLinkById(validated.workspaceId);
1733
+ }
1734
+ __name(linkWorkspace, "linkWorkspace");
1735
+ async function updateWorkspaceTier(params) {
1736
+ if (!db) {
1737
+ throw new Error("Database not available");
1738
+ }
1739
+ const validated = updateTierSchema.parse(params);
1740
+ const result = await db.update(workspaceLinks).set({
1741
+ tier: validated.tier,
1742
+ lastSeenAt: /* @__PURE__ */ new Date()
1743
+ }).where(eq(workspaceLinks.workspaceId, validated.workspaceId)).returning({
1744
+ workspaceId: workspaceLinks.workspaceId
1745
+ });
1746
+ return result.length > 0;
1747
+ }
1748
+ __name(updateWorkspaceTier, "updateWorkspaceTier");
1749
+ async function unlinkWorkspace(workspaceId) {
1750
+ if (!db) {
1751
+ throw new Error("Database not available");
1752
+ }
1753
+ const validatedId = workspaceIdSchema.parse(workspaceId);
1754
+ const result = await db.delete(workspaceLinks).where(eq(workspaceLinks.workspaceId, validatedId)).returning({
1755
+ workspaceId: workspaceLinks.workspaceId
1756
+ });
1757
+ return result.length > 0;
1758
+ }
1759
+ __name(unlinkWorkspace, "unlinkWorkspace");
1760
+ async function getWorkspaceLinksByUserId(userId) {
1761
+ if (!db) {
1762
+ throw new Error("Database not available");
1763
+ }
1764
+ return await db.query.workspaceLinks.findMany({
1765
+ // biome-ignore lint/suspicious/noExplicitAny: Drizzle query builder callbacks
1766
+ where: /* @__PURE__ */ __name((link, { eq: eq15 }) => eq15(link.userId, userId), "where"),
1767
+ orderBy: /* @__PURE__ */ __name((link, { desc: desc4 }) => desc4(link.lastSeenAt), "orderBy")
1768
+ });
1769
+ }
1770
+ __name(getWorkspaceLinksByUserId, "getWorkspaceLinksByUserId");
1771
+ async function unlinkAllWorkspacesForUser(userId) {
1772
+ if (!db) {
1773
+ throw new Error("Database not available");
1774
+ }
1775
+ const result = await db.delete(workspaceLinks).where(eq(workspaceLinks.userId, userId)).returning({
1776
+ workspaceId: workspaceLinks.workspaceId
1777
+ });
1778
+ return result.length;
1779
+ }
1780
+ __name(unlinkAllWorkspacesForUser, "unlinkAllWorkspacesForUser");
1781
+ async function searchSimilarPatterns(queryVector, limit = 10, similarityThreshold = 0.75, patternType, isGlobal) {
1782
+ if (queryVector.length !== 256) {
1783
+ throw new Error(`Expected 256-dimensional vector, got ${queryVector.length}`);
1784
+ }
1785
+ const vectorLiteral = `[${queryVector.join(",")}]`;
1786
+ let query = db.select({
1787
+ id: patterns.id,
1788
+ patternSignature: patterns.patternSignature,
1789
+ patternType: patterns.patternType,
1790
+ // Cosine similarity: 1 - distance (higher is more similar)
1791
+ similarity: sql`1 - (${patterns.embedding} <=> ${vectorLiteral}::vector)`,
1792
+ occurrenceCount: patterns.occurrenceCount,
1793
+ successRate: patterns.successRate,
1794
+ isGlobal: patterns.isGlobal
1795
+ }).from(patterns).where(sql`1 - (${patterns.embedding} <=> ${vectorLiteral}::vector) >= ${similarityThreshold}`);
1796
+ if (patternType) {
1797
+ query = query.where(sql`${patterns.patternType} = ${patternType}`);
1798
+ }
1799
+ if (isGlobal !== void 0) {
1800
+ query = query.where(sql`${patterns.isGlobal} = ${isGlobal}`);
1801
+ }
1802
+ const results = await query.orderBy(sql`${patterns.embedding} <=> ${vectorLiteral}::vector`).limit(limit);
1803
+ return results;
1804
+ }
1805
+ __name(searchSimilarPatterns, "searchSimilarPatterns");
1806
+ async function findSimilarPatterns(patternId, limit = 5, excludeSelf = true) {
1807
+ const referencePattern = await db.select({
1808
+ embedding: patterns.embedding
1809
+ }).from(patterns).where(sql`${patterns.id} = ${patternId}`).limit(1);
1810
+ if (!referencePattern.length || !referencePattern[0].embedding) {
1811
+ throw new Error(`Pattern ${patternId} not found or has no embedding`);
1812
+ }
1813
+ const queryVector = referencePattern[0].embedding;
1814
+ const vectorLiteral = `[${queryVector.join(",")}]`;
1815
+ let query = db.select({
1816
+ id: patterns.id,
1817
+ patternSignature: patterns.patternSignature,
1818
+ patternType: patterns.patternType,
1819
+ similarity: sql`1 - (${patterns.embedding} <=> ${vectorLiteral}::vector)`,
1820
+ occurrenceCount: patterns.occurrenceCount,
1821
+ successRate: patterns.successRate,
1822
+ isGlobal: patterns.isGlobal
1823
+ }).from(patterns).where(sql`${patterns.embedding} IS NOT NULL`);
1824
+ if (excludeSelf) {
1825
+ query = query.where(sql`${patterns.id} != ${patternId}`);
1826
+ }
1827
+ const results = await query.orderBy(sql`${patterns.embedding} <=> ${vectorLiteral}::vector`).limit(limit);
1828
+ return results;
1829
+ }
1830
+ __name(findSimilarPatterns, "findSimilarPatterns");
1831
+ async function insertPatternWithEmbedding(patternSignature, embedding, patternType, userId, toolAffinity, fileTypes) {
1832
+ if (embedding.length !== 256) {
1833
+ throw new Error(`Expected 256-dimensional vector, got ${embedding.length}`);
1834
+ }
1835
+ const result = await db.insert(patterns).values({
1836
+ patternSignature,
1837
+ embedding,
1838
+ patternType,
1839
+ userId,
1840
+ toolAffinity: toolAffinity ?? [],
1841
+ fileTypes: fileTypes ?? [],
1842
+ isGlobal: !userId
1843
+ }).returning({
1844
+ id: patterns.id
1845
+ });
1846
+ return result[0].id;
1847
+ }
1848
+ __name(insertPatternWithEmbedding, "insertPatternWithEmbedding");
1849
+ async function updatePatternEmbedding(patternId, embedding) {
1850
+ if (embedding.length !== 256) {
1851
+ throw new Error(`Expected 256-dimensional vector, got ${embedding.length}`);
1852
+ }
1853
+ await db.update(patterns).set({
1854
+ embedding
1855
+ }).where(sql`${patterns.id} = ${patternId}`);
1856
+ }
1857
+ __name(updatePatternEmbedding, "updatePatternEmbedding");
1858
+ async function isPgvectorEnabled() {
1859
+ try {
1860
+ const result = await db.execute(sql`
1861
+ SELECT 1 FROM pg_extension WHERE extname = 'vector'
1862
+ `);
1863
+ return result.rows.length > 0;
1864
+ } catch {
1865
+ return false;
1866
+ }
1867
+ }
1868
+ __name(isPgvectorEnabled, "isPgvectorEnabled");
1869
+ async function getVectorStats() {
1870
+ const [totalResult, embeddingResult, indexResult] = await Promise.all([
1871
+ db.select({
1872
+ count: sql`count(*)`
1873
+ }).from(patterns),
1874
+ db.select({
1875
+ count: sql`count(*)`
1876
+ }).from(patterns).where(sql`${patterns.embedding} IS NOT NULL`),
1877
+ db.execute(sql`
1878
+ SELECT indexname, indexdef
1879
+ FROM pg_indexes
1880
+ WHERE tablename = 'patterns'
1881
+ AND indexdef LIKE '%hnsw%'
1882
+ `)
1883
+ ]);
1884
+ const indexRow = indexResult.rows[0];
1885
+ return {
1886
+ totalPatterns: totalResult[0]?.count ?? 0,
1887
+ patternsWithEmbeddings: embeddingResult[0]?.count ?? 0,
1888
+ indexName: indexRow?.indexname ?? null,
1889
+ indexType: indexRow?.indexdef?.includes("hnsw") ? "hnsw" : null
1890
+ };
1891
+ }
1892
+ __name(getVectorStats, "getVectorStats");
1893
+ var extensionLinkTokens = pgTable("extension_link_tokens", {
1894
+ id: varchar("id", {
1895
+ length: 255
1896
+ }).$defaultFn(() => nanoid()).primaryKey(),
1897
+ tokenHash: text("token_hash").notNull(),
1898
+ userId: varchar("user_id", {
1899
+ length: 255
1900
+ }).notNull().references(() => user.id, {
1901
+ onDelete: "cascade"
1902
+ }),
1903
+ workspaceId: varchar("workspace_id", {
1904
+ length: 255
1905
+ }),
1906
+ client: text("client").notNull(),
1907
+ used: boolean("used").notNull().default(false),
1908
+ expiresAt: timestamp("expires_at", {
1909
+ withTimezone: true
1910
+ }).notNull(),
1911
+ createdAt: timestamp("created_at", {
1912
+ withTimezone: true
1913
+ }).notNull().defaultNow()
1914
+ }, (table) => ({
1915
+ // Partial index for fast active token lookup (used=false, not expired)
1916
+ tokenHashIdx: index("idx_extension_link_tokens_hash").on(table.tokenHash),
1917
+ // Index for cleanup jobs
1918
+ expiryIdx: index("idx_extension_link_tokens_expiry").on(table.expiresAt)
1919
+ }));
1920
+ var extensionSessions = pgTable("extension_sessions", {
1921
+ id: varchar("id", {
1922
+ length: 255
1923
+ }).$defaultFn(() => nanoid()).primaryKey(),
1924
+ userId: varchar("user_id", {
1925
+ length: 255
1926
+ }).notNull().references(() => user.id, {
1927
+ onDelete: "cascade"
1928
+ }),
1929
+ workspaceId: varchar("workspace_id", {
1930
+ length: 255
1931
+ }),
1932
+ client: text("client").notNull(),
1933
+ refreshTokenHash: text("refresh_token_hash").notNull(),
1934
+ createdAt: timestamp("created_at", {
1935
+ withTimezone: true
1936
+ }).notNull().defaultNow(),
1937
+ lastUsedAt: timestamp("last_used_at", {
1938
+ withTimezone: true
1939
+ }),
1940
+ revokedAt: timestamp("revoked_at", {
1941
+ withTimezone: true
1942
+ }),
1943
+ expiresAt: timestamp("expires_at", {
1944
+ withTimezone: true
1945
+ }).notNull(),
1946
+ metadata: jsonb("metadata").$type()
1947
+ }, (table) => ({
1948
+ // Unique index for fast refresh token lookup (only non-revoked)
1949
+ refreshHashIdx: index("idx_extension_sessions_refresh_hash").on(table.refreshTokenHash),
1950
+ // Index for user session queries (Phase 2 UI)
1951
+ userIdx: index("idx_extension_sessions_user").on(table.userId),
1952
+ // Index for active sessions
1953
+ activeIdx: index("idx_extension_sessions_active").on(table.userId, table.revokedAt)
1954
+ }));
1955
+
1956
+ // ../../packages/platform/dist/db/test-utils.js
1957
+ var testInTransaction = /* @__PURE__ */ __name((_testName, testFn) => {
1958
+ return async () => {
1959
+ const module = await import('./client-JMTSZS3V.js');
1960
+ const db2 = module.db;
1961
+ await testFn(db2);
1962
+ };
1963
+ }, "testInTransaction");
1964
+ var createTestUser = /* @__PURE__ */ __name(async (_tx, userData) => {
1965
+ const mockUser = {
1966
+ id: `user_${Date.now()}`,
1967
+ email: userData.email,
1968
+ emailVerified: userData.emailVerified || false,
1969
+ createdAt: /* @__PURE__ */ new Date(),
1970
+ updatedAt: /* @__PURE__ */ new Date()
1971
+ };
1972
+ return mockUser;
1973
+ }, "createTestUser");
1974
+ var truncateAllTables = /* @__PURE__ */ __name(async () => {
1975
+ }, "truncateAllTables");
1976
+ var getTestDb = /* @__PURE__ */ __name(async () => {
1977
+ const module = await import('./client-JMTSZS3V.js');
1978
+ return module.db;
1979
+ }, "getTestDb");
1980
+ var closeTestDb = /* @__PURE__ */ __name(async () => {
1981
+ process.stdout.write("closeTestDb called");
1982
+ }, "closeTestDb");
1983
+
1984
+ // ../../packages/platform/dist/db/zod.js
1985
+ var AiChatSchema = createSelectSchema(aiChat);
1986
+ var UserSchema = createSelectSchema(user);
1987
+ var UserUpdateSchema = createUpdateSchema(user, {
1988
+ id: z.string()
1989
+ });
1990
+ var OrganizationSchema = createSelectSchema(organization);
1991
+ var OrganizationUpdateSchema = createUpdateSchema(organization, {
1992
+ id: z.string()
1993
+ });
1994
+ var MemberSchema = createSelectSchema(member);
1995
+ var InvitationSchema = createSelectSchema(invitation);
1996
+ var PurchaseSchema = createSelectSchema(purchase);
1997
+ var PurchaseInsertSchema = createInsertSchema(purchase);
1998
+ var PurchaseUpdateSchema = createUpdateSchema(purchase, {
1999
+ id: z.string()
2000
+ });
2001
+ var SessionSchema = createSelectSchema(session);
2002
+ var AccountSchema = createSelectSchema(account);
2003
+ var VerificationSchema = createSelectSchema(verification);
2004
+ var PasskeySchema = createSelectSchema(passkey);
2005
+ var AttributionServiceImpl = class {
2006
+ static {
2007
+ __name(this, "AttributionServiceImpl");
2008
+ }
2009
+ // In-memory storage (stub - will be replaced with database)
2010
+ attributions = /* @__PURE__ */ new Map();
2011
+ fingerprintIndex = /* @__PURE__ */ new Map();
2012
+ /**
2013
+ * Transfer attribution from web to platform
2014
+ */
2015
+ async transferAttribution(request) {
2016
+ const { userId, fingerprint, attribution } = request;
2017
+ if (!db) {
2018
+ return this.transferAttributionInMemory(request);
2019
+ }
2020
+ try {
2021
+ const [existingByFingerprint] = await db.select().from(userAttributions).where(eq(userAttributions.fingerprint, fingerprint)).limit(1);
2022
+ if (existingByFingerprint) {
2023
+ const shouldMerge = shouldMergeAttribution({
2024
+ attributionId: existingByFingerprint.id,
2025
+ source: existingByFingerprint.source,
2026
+ createdAt: existingByFingerprint.createdAt.toISOString(),
2027
+ campaignId: existingByFingerprint.campaignId || void 0
2028
+ }, attribution);
2029
+ if (shouldMerge) {
2030
+ await db.update(userAttributions).set({
2031
+ utmParams: {
2032
+ ...existingByFingerprint.utmParams,
2033
+ ...attribution.utmParams
2034
+ },
2035
+ conversionData: {
2036
+ ...existingByFingerprint.conversionData,
2037
+ ...attribution.conversionData
2038
+ }
2039
+ }).where(eq(userAttributions.id, existingByFingerprint.id));
2040
+ process.stdout.write(`[Attribution] Merged attribution for user ${userId} (fingerprint: ${fingerprint})`);
2041
+ return {
2042
+ success: true,
2043
+ attributionId: existingByFingerprint.id,
2044
+ action: "merged",
2045
+ existingAttribution: {
2046
+ attributionId: existingByFingerprint.id,
2047
+ source: existingByFingerprint.source,
2048
+ createdAt: existingByFingerprint.createdAt.toISOString(),
2049
+ campaignId: existingByFingerprint.campaignId || void 0
2050
+ },
2051
+ message: "Attribution updated with new touch point"
2052
+ };
2053
+ }
2054
+ process.stdout.write(`[Attribution] Ignored duplicate attribution for user ${userId}`);
2055
+ return {
2056
+ success: true,
2057
+ attributionId: existingByFingerprint.id,
2058
+ action: "ignored",
2059
+ existingAttribution: {
2060
+ attributionId: existingByFingerprint.id,
2061
+ source: existingByFingerprint.source,
2062
+ createdAt: existingByFingerprint.createdAt.toISOString(),
2063
+ campaignId: existingByFingerprint.campaignId || void 0
2064
+ },
2065
+ message: "Attribution already exists for this fingerprint"
2066
+ };
2067
+ }
2068
+ const [existingByUser] = await db.select().from(userAttributions).where(eq(userAttributions.userId, userId)).limit(1);
2069
+ if (existingByUser) {
2070
+ return {
2071
+ success: true,
2072
+ attributionId: existingByUser.id,
2073
+ action: "ignored",
2074
+ existingAttribution: {
2075
+ attributionId: existingByUser.id,
2076
+ source: existingByUser.source,
2077
+ createdAt: existingByUser.createdAt.toISOString(),
2078
+ campaignId: existingByUser.campaignId || void 0
2079
+ },
2080
+ message: "User already has attribution"
2081
+ };
2082
+ }
2083
+ const [newAttribution] = await db.insert(userAttributions).values({
2084
+ userId,
2085
+ source: attribution.source,
2086
+ campaignId: attribution.campaignId,
2087
+ fingerprint,
2088
+ conversionData: attribution.conversionData,
2089
+ utmParams: attribution.utmParams,
2090
+ referralCode: attribution.referralCode,
2091
+ createdAt: /* @__PURE__ */ new Date()
2092
+ }).returning();
2093
+ if (!newAttribution) {
2094
+ throw new Error("Failed to create attribution record");
2095
+ }
2096
+ process.stdout.write(`[Attribution] Created new attribution for user ${userId} - Source: ${attribution.source}`);
2097
+ return {
2098
+ success: true,
2099
+ attributionId: newAttribution.id,
2100
+ action: "created",
2101
+ message: "Attribution recorded successfully"
2102
+ };
2103
+ } catch (_error) {
2104
+ return this.transferAttributionInMemory(request);
2105
+ }
2106
+ }
2107
+ /**
2108
+ * Get attribution for a user
2109
+ */
2110
+ async getAttribution(userId) {
2111
+ if (!db) {
2112
+ return this.attributions.get(userId) || null;
2113
+ }
2114
+ try {
2115
+ const [attribution] = await db.select().from(userAttributions).where(eq(userAttributions.userId, userId)).limit(1);
2116
+ if (!attribution) {
2117
+ return null;
2118
+ }
2119
+ return {
2120
+ id: attribution.id,
2121
+ userId: attribution.userId,
2122
+ source: attribution.source,
2123
+ campaignId: attribution.campaignId || void 0,
2124
+ fingerprint: attribution.fingerprint,
2125
+ conversionData: attribution.conversionData,
2126
+ utmParams: attribution.utmParams,
2127
+ createdAt: attribution.createdAt,
2128
+ referralCode: attribution.referralCode || void 0,
2129
+ convertedAt: attribution.convertedAt || void 0
2130
+ };
2131
+ } catch (_error) {
2132
+ return this.attributions.get(userId) || null;
2133
+ }
2134
+ }
2135
+ /**
2136
+ * Mark user as converted (purchased subscription)
2137
+ */
2138
+ async markConverted(userId) {
2139
+ if (!db) {
2140
+ const attribution = this.attributions.get(userId);
2141
+ if (!attribution) {
2142
+ return false;
2143
+ }
2144
+ attribution.convertedAt = /* @__PURE__ */ new Date();
2145
+ process.stdout.write(`[Attribution] Marked user ${userId} as converted - Source: ${attribution.source}`);
2146
+ return true;
2147
+ }
2148
+ try {
2149
+ const result = await db.update(userAttributions).set({
2150
+ convertedAt: /* @__PURE__ */ new Date()
2151
+ }).where(eq(userAttributions.userId, userId)).returning();
2152
+ const updated = result[0];
2153
+ if (!updated) {
2154
+ return false;
2155
+ }
2156
+ process.stdout.write(`[Attribution] Marked user ${userId} as converted - Source: ${updated.source}`);
2157
+ return true;
2158
+ } catch (_error) {
2159
+ return false;
2160
+ }
2161
+ }
2162
+ /**
2163
+ * Get conversion metrics by source
2164
+ */
2165
+ async getConversionMetrics(dateRange) {
2166
+ if (!db) {
2167
+ return this.getConversionMetricsInMemory(dateRange);
2168
+ }
2169
+ try {
2170
+ let query = db.select().from(userAttributions);
2171
+ if (dateRange) {
2172
+ query = query.where(and(gte(userAttributions.createdAt, dateRange.from), lte(userAttributions.createdAt, dateRange.to)));
2173
+ }
2174
+ const attributions = await query;
2175
+ const metricsBySource = /* @__PURE__ */ new Map();
2176
+ for (const record of attributions) {
2177
+ const metrics = metricsBySource.get(record.source) || {
2178
+ total: 0,
2179
+ conversions: 0,
2180
+ timesToConvert: []
2181
+ };
2182
+ metrics.total++;
2183
+ if (record.convertedAt) {
2184
+ metrics.conversions++;
2185
+ const daysToConvert = (record.convertedAt.getTime() - record.createdAt.getTime()) / (1e3 * 60 * 60 * 24);
2186
+ metrics.timesToConvert.push(daysToConvert);
2187
+ }
2188
+ metricsBySource.set(record.source, metrics);
2189
+ }
2190
+ const results = [];
2191
+ for (const [source, data] of metricsBySource.entries()) {
2192
+ const avgTimeToConvert = data.timesToConvert.length > 0 ? data.timesToConvert.reduce((a, b) => a + b, 0) / data.timesToConvert.length : void 0;
2193
+ results.push({
2194
+ source,
2195
+ totalUsers: data.total,
2196
+ conversions: data.conversions,
2197
+ conversionRate: data.conversions / data.total,
2198
+ avgTimeToConvert
2199
+ });
2200
+ }
2201
+ return results.sort((a, b) => b.totalUsers - a.totalUsers);
2202
+ } catch (_error) {
2203
+ return this.getConversionMetricsInMemory(dateRange);
2204
+ }
2205
+ }
2206
+ /**
2207
+ * Fallback: Transfer attribution in-memory
2208
+ */
2209
+ transferAttributionInMemory(request) {
2210
+ const { userId, fingerprint, attribution } = request;
2211
+ const existingUserId = this.fingerprintIndex.get(fingerprint);
2212
+ if (existingUserId) {
2213
+ const existing = this.attributions.get(existingUserId);
2214
+ if (existing) {
2215
+ const shouldMerge = shouldMergeAttribution({
2216
+ attributionId: existing.id,
2217
+ source: existing.source,
2218
+ createdAt: existing.createdAt.toISOString(),
2219
+ campaignId: existing.campaignId
2220
+ }, attribution);
2221
+ if (shouldMerge) {
2222
+ existing.utmParams = {
2223
+ ...existing.utmParams,
2224
+ ...attribution.utmParams
2225
+ };
2226
+ existing.conversionData = {
2227
+ ...existing.conversionData,
2228
+ ...attribution.conversionData
2229
+ };
2230
+ return {
2231
+ success: true,
2232
+ attributionId: existing.id,
2233
+ action: "merged",
2234
+ existingAttribution: {
2235
+ attributionId: existing.id,
2236
+ source: existing.source,
2237
+ createdAt: existing.createdAt.toISOString(),
2238
+ campaignId: existing.campaignId
2239
+ },
2240
+ message: "Attribution updated with new touch point"
2241
+ };
2242
+ }
2243
+ return {
2244
+ success: true,
2245
+ attributionId: existing.id,
2246
+ action: "ignored",
2247
+ existingAttribution: {
2248
+ attributionId: existing.id,
2249
+ source: existing.source,
2250
+ createdAt: existing.createdAt.toISOString(),
2251
+ campaignId: existing.campaignId
2252
+ },
2253
+ message: "Attribution already exists for this fingerprint"
2254
+ };
2255
+ }
2256
+ }
2257
+ const attributionId = `attr_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2258
+ const record = {
2259
+ id: attributionId,
2260
+ userId,
2261
+ source: attribution.source,
2262
+ campaignId: attribution.campaignId,
2263
+ fingerprint,
2264
+ conversionData: attribution.conversionData,
2265
+ utmParams: attribution.utmParams,
2266
+ createdAt: /* @__PURE__ */ new Date(),
2267
+ referralCode: attribution.referralCode
2268
+ };
2269
+ this.attributions.set(userId, record);
2270
+ this.fingerprintIndex.set(fingerprint, userId);
2271
+ return {
2272
+ success: true,
2273
+ attributionId,
2274
+ action: "created",
2275
+ message: "Attribution recorded successfully"
2276
+ };
2277
+ }
2278
+ /**
2279
+ * Fallback: Get conversion metrics in-memory
2280
+ */
2281
+ getConversionMetricsInMemory(dateRange) {
2282
+ const metricsBySource = /* @__PURE__ */ new Map();
2283
+ for (const record of this.attributions.values()) {
2284
+ if (dateRange) {
2285
+ if (record.createdAt < dateRange.from || record.createdAt > dateRange.to) {
2286
+ continue;
2287
+ }
2288
+ }
2289
+ const metrics = metricsBySource.get(record.source) || {
2290
+ total: 0,
2291
+ conversions: 0,
2292
+ timesToConvert: []
2293
+ };
2294
+ metrics.total++;
2295
+ if (record.convertedAt) {
2296
+ metrics.conversions++;
2297
+ const daysToConvert = (record.convertedAt.getTime() - record.createdAt.getTime()) / (1e3 * 60 * 60 * 24);
2298
+ metrics.timesToConvert.push(daysToConvert);
2299
+ }
2300
+ metricsBySource.set(record.source, metrics);
2301
+ }
2302
+ const results = [];
2303
+ for (const [source, data] of metricsBySource.entries()) {
2304
+ const avgTimeToConvert = data.timesToConvert.length > 0 ? data.timesToConvert.reduce((a, b) => a + b, 0) / data.timesToConvert.length : void 0;
2305
+ results.push({
2306
+ source,
2307
+ totalUsers: data.total,
2308
+ conversions: data.conversions,
2309
+ conversionRate: data.conversions / data.total,
2310
+ avgTimeToConvert
2311
+ });
2312
+ }
2313
+ return results.sort((a, b) => b.totalUsers - a.totalUsers);
2314
+ }
2315
+ };
2316
+
2317
+ // ../../packages/config/dist/subscription-config.js
2318
+ var PRO_TRIAL_DAYS = 14;
2319
+ var TIER_CREDIT_ALLOWANCES = {
2320
+ free: 25,
2321
+ pro: 200,
2322
+ team: 200,
2323
+ enterprise: 0
2324
+ };
2325
+ var CREDIT_OVERAGE_SOFT_CAP = -100;
2326
+
2327
+ // ../../packages/config/dist/config.js
2328
+ var config = {
2329
+ appName: "Vreko",
2330
+ tagline: "AI-Native DevOps",
2331
+ description: "Protection for your code.",
2332
+ organizations: {
2333
+ enable: true,
2334
+ enableBilling: true,
2335
+ enableUsersToCreateOrganizations: true,
2336
+ requireOrganization: false,
2337
+ hideOrganization: false,
2338
+ forbiddenOrganizationSlugs: [
2339
+ "admin",
2340
+ "root",
2341
+ "api",
2342
+ "app"
2343
+ ]
2344
+ },
2345
+ users: {
2346
+ enableBilling: true,
2347
+ enableOnboarding: true
2348
+ },
2349
+ auth: {
2350
+ enableSignup: true,
2351
+ enableMagicLink: true,
2352
+ enableSocialLogin: true,
2353
+ enablePasskeys: false,
2354
+ enablePasswordLogin: true,
2355
+ enableTwoFactor: false,
2356
+ redirectAfterSignIn: "/app",
2357
+ redirectAfterLogout: "/",
2358
+ sessionCookieMaxAge: 60 * 60 * 24 * 30
2359
+ },
2360
+ mails: {
2361
+ from: "no-reply@vreko.dev"
2362
+ },
2363
+ storage: {
2364
+ bucketNames: {
2365
+ avatars: "vreko-avatars",
2366
+ checkpoints: "vreko-checkpoints",
2367
+ snapshots: "vreko-snapshots"
2368
+ }
2369
+ },
2370
+ ui: {
2371
+ enabledThemes: [
2372
+ "light",
2373
+ "dark"
2374
+ ],
2375
+ defaultTheme: "dark",
2376
+ saas: {
2377
+ enabled: true,
2378
+ useSidebarLayout: true
2379
+ },
2380
+ marketing: {
2381
+ enabled: true
2382
+ }
2383
+ },
2384
+ contactForm: {
2385
+ enabled: true,
2386
+ to: "support@vreko.dev",
2387
+ subject: "Contact from Vreko"
2388
+ },
2389
+ payments: {
2390
+ plans: {
2391
+ free: {
2392
+ name: "Free",
2393
+ description: "Essential protection for individuals.",
2394
+ features: [
2395
+ "Unlimited local checkpoints",
2396
+ "Basic AI detection",
2397
+ "Community support"
2398
+ ],
2399
+ isFree: true,
2400
+ prices: [
2401
+ {
2402
+ productId: "price_free",
2403
+ amount: 0,
2404
+ currency: "USD",
2405
+ type: "recurring",
2406
+ interval: "month"
2407
+ }
2408
+ ]
2409
+ },
2410
+ pro: {
2411
+ name: "Pro",
2412
+ description: "Advanced protection for professional developers.",
2413
+ features: [
2414
+ "Cloud backup & sync",
2415
+ "Advanced AI analysis",
2416
+ "Priority support",
2417
+ "Unlimited history"
2418
+ ],
2419
+ recommended: true,
2420
+ prices: [
2421
+ {
2422
+ productId: "price_pro_monthly",
2423
+ amount: 2e3,
2424
+ currency: "USD",
2425
+ type: "recurring",
2426
+ interval: "month",
2427
+ trialPeriodDays: PRO_TRIAL_DAYS
2428
+ },
2429
+ {
2430
+ productId: "price_pro_yearly",
2431
+ amount: 2e4,
2432
+ currency: "USD",
2433
+ type: "recurring",
2434
+ interval: "year",
2435
+ trialPeriodDays: PRO_TRIAL_DAYS
2436
+ }
2437
+ ]
2438
+ },
2439
+ team: {
2440
+ name: "Team",
2441
+ description: "Collaborative security for teams.",
2442
+ features: [
2443
+ "Everything in Pro",
2444
+ "Team dashboard",
2445
+ "Centralized policy management",
2446
+ "Audit logs"
2447
+ ],
2448
+ prices: [
2449
+ {
2450
+ productId: "price_team_monthly",
2451
+ amount: 4900,
2452
+ currency: "USD",
2453
+ type: "recurring",
2454
+ interval: "month",
2455
+ seatBased: true
2456
+ }
2457
+ ]
2458
+ }
2459
+ }
2460
+ }
2461
+ };
2462
+
2463
+ // ../../packages/config/dist/feature-flags.js
2464
+ process.env.ENABLE_EXTENSION_AUTH === "true";
2465
+ process.env.ENABLE_API_KEYS === "true";
2466
+ process.env.ENABLE_RATE_LIMITING === "true";
2467
+ process.env.ENABLE_INTELLIGENCE_LAYER === "true";
2468
+ process.env.ENABLE_TRUST_CALIBRATION === "true";
2469
+ process.env.ENABLE_PATTERN_LIBRARY === "true";
2470
+ process.env.ENABLE_PREDICTION_ENGINE === "true";
2471
+ process.env.ENABLE_GITHUB_INTEGRATION === "true";
2472
+ process.env.ENABLE_SSO === "true";
2473
+ process.env.ENABLE_CAPTCHA === "true";
2474
+ process.env.ENABLE_MULTI_SESSION === "true";
2475
+ process.env.ENABLE_ENHANCED_2FA === "true";
2476
+ var ProtectionLevelSchema = z.enum([
2477
+ "watch",
2478
+ "warn",
2479
+ "block"
2480
+ ]);
2481
+ var ProtectionRuleSchema = z.object({
2482
+ pattern: z.string().describe("Glob pattern (e.g., '*.env*', 'package.json')"),
2483
+ level: ProtectionLevelSchema,
2484
+ reason: z.string().optional().describe("Why this pattern is protected"),
2485
+ precedence: z.number().int().min(0).max(1e3).default(0)
2486
+ });
2487
+ var EngineConfigSchema = z.object({
2488
+ maxDepth: z.number().int().min(0).max(10).default(2).describe("Max dependency tree depth for analysis"),
2489
+ burstThreshold: z.number().int().min(1).max(100).default(30).describe("Min simultaneous file changes to trigger burst detection"),
2490
+ cooldowns: z.object({
2491
+ block: z.number().int().min(0).default(6e4),
2492
+ warn: z.number().int().min(0).default(3e4),
2493
+ watch: z.number().int().min(0).default(0)
2494
+ }).default({
2495
+ block: 6e4,
2496
+ warn: 3e4,
2497
+ watch: 0
2498
+ }).describe("Cooldown durations (ms) between alerts per level")
2499
+ });
2500
+ var IgnorePatternsSchema = z.array(z.string()).default([]).describe("Glob patterns to exclude from protection (e.g., node_modules, .git)");
2501
+ var PrivacySettingsSchema = z.object({
2502
+ consent: z.boolean().default(false).describe("User has given privacy consent"),
2503
+ clipboard: z.boolean().default(false).describe("Allow clipboard monitoring"),
2504
+ watcher: z.boolean().default(false).describe("Allow file watcher"),
2505
+ gitWrapper: z.boolean().default(false).describe("Allow git wrapper integration"),
2506
+ lastReminded: z.string().optional().describe("ISO timestamp of last consent reminder")
2507
+ }).default({});
2508
+ var NotificationsSettingsSchema = z.object({
2509
+ enabled: z.boolean().default(true),
2510
+ quietHours: z.object({
2511
+ start: z.string().default("22:00"),
2512
+ end: z.string().default("08:00")
2513
+ }).default({
2514
+ start: "22:00",
2515
+ end: "08:00"
2516
+ }),
2517
+ rateLimit: z.number().int().min(1).default(5).describe("Max notifications per minute")
2518
+ }).default({});
2519
+ var SnapshotSettingsSchema = z.object({
2520
+ enabled: z.boolean().default(true),
2521
+ autoCreate: z.boolean().default(true),
2522
+ retentionDays: z.number().int().min(1).default(30)
2523
+ }).default({});
2524
+ var AISettingsSchema = z.object({
2525
+ enabled: z.boolean().default(true),
2526
+ context: z.boolean().default(true).describe("Include code context in AI analysis"),
2527
+ copilot: z.boolean().default(true).describe("Integrate with GitHub Copilot")
2528
+ }).default({});
2529
+ var GuardianPluginsSchema = z.object({
2530
+ secretDetection: z.boolean().default(true),
2531
+ mockReplacement: z.boolean().default(true),
2532
+ phantomDependency: z.boolean().default(true)
2533
+ }).default({});
2534
+ var GuardianThresholdsSchema = z.object({
2535
+ warn: z.number().int().min(0).default(6),
2536
+ block: z.number().int().min(0).default(8)
2537
+ }).default({
2538
+ warn: 6,
2539
+ block: 8
2540
+ });
2541
+ var GuardianSettingsSchema = z.object({
2542
+ enabled: z.boolean().default(true),
2543
+ warnThreshold: z.number().int().min(0).max(100).default(5),
2544
+ blockThreshold: z.number().int().min(0).max(100).default(8),
2545
+ protectionLevel: ProtectionLevelSchema.default("warn"),
2546
+ plugins: GuardianPluginsSchema,
2547
+ thresholds: GuardianThresholdsSchema
2548
+ }).default({});
2549
+ var AutoDecisionSettingsSchema = z.object({
2550
+ riskThreshold: z.number().int().min(0).max(100).default(60).describe("Risk score threshold (0-100) for automatic snapshot creation"),
2551
+ notifyThreshold: z.number().int().min(0).max(100).default(40).describe("Risk score threshold (0-100) for user notifications"),
2552
+ minFilesForBurst: z.number().int().min(1).default(3).describe("Minimum files changed simultaneously to trigger burst detection"),
2553
+ maxSnapshotsPerMinute: z.number().int().min(1).default(4).describe("Maximum snapshots allowed per minute (rate limiting)")
2554
+ }).default({});
2555
+ var MCPSettingsSchema = z.object({
2556
+ performanceBudgets: z.record(z.number().int().min(0)).default({
2557
+ analyze_risk: 200,
2558
+ create_snapshot: 500
2559
+ }).describe("Performance budgets (ms) for MCP operations"),
2560
+ context7: z.object({
2561
+ apiKey: z.string().optional(),
2562
+ apiUrl: z.string().url().default("https://context7.com/api"),
2563
+ cacheTtlSearch: z.number().int().min(0).default(3600),
2564
+ cacheTtlDocs: z.number().int().min(0).default(86400)
2565
+ }).default({}),
2566
+ api: z.object({
2567
+ apiKey: z.string().optional(),
2568
+ baseUrl: z.string().url().default("https://api.vreko.dev")
2569
+ }).default({}),
2570
+ http: z.object({
2571
+ allowedOrigins: z.array(z.string()).default([
2572
+ "*"
2573
+ ]),
2574
+ apiUrl: z.string().url().default("http://api:8080")
2575
+ }).default({})
2576
+ }).default({});
2577
+ var SettingsSchema = z.object({
2578
+ defaultProtectionLevel: ProtectionLevelSchema.default("watch"),
2579
+ requireSnapshotMessage: z.boolean().default(true),
2580
+ maxSnapshots: z.number().int().min(1).default(100),
2581
+ aiDetectionEnabled: z.boolean().default(true),
2582
+ autoRestoreOnDetection: z.boolean().default(false),
2583
+ privacy: PrivacySettingsSchema,
2584
+ notifications: NotificationsSettingsSchema,
2585
+ snapshots: SnapshotSettingsSchema,
2586
+ ai: AISettingsSchema,
2587
+ guardian: GuardianSettingsSchema,
2588
+ autoDecision: AutoDecisionSettingsSchema,
2589
+ webBaseUrl: z.string().url().default("https://console.vreko.dev"),
2590
+ apiBaseUrl: z.string().url().optional(),
2591
+ mcp: MCPSettingsSchema
2592
+ }).default({});
2593
+ var PolicyOverrideSchema = z.object({
2594
+ pattern: z.string(),
2595
+ level: ProtectionLevelSchema,
2596
+ ttl: z.number().optional().describe("Expiration timestamp (ms since epoch)")
2597
+ });
2598
+ var PoliciesSchema = z.object({
2599
+ enforceProtectionLevels: z.boolean().default(false),
2600
+ allowOverrides: z.boolean().default(true),
2601
+ overrides: z.array(PolicyOverrideSchema).default([])
2602
+ }).default({});
2603
+ z.object({
2604
+ version: z.literal(2).default(2),
2605
+ protections: z.array(ProtectionRuleSchema).default([]),
2606
+ ignore: IgnorePatternsSchema,
2607
+ engine: EngineConfigSchema.default({}),
2608
+ settings: SettingsSchema,
2609
+ policies: PoliciesSchema,
2610
+ mcp: MCPSettingsSchema.optional(),
2611
+ // Phase 2: Workspace registry (SB-CTX-001)
2612
+ workspaces: z.array(WorkspaceRegistrationSchema).default([])
2613
+ });
2614
+
2615
+ // ../../packages/config/dist/utils/base-url.js
2616
+ function getBaseUrl() {
2617
+ if (process.env.NEXT_PUBLIC_SITE_URL) {
2618
+ return process.env.NEXT_PUBLIC_SITE_URL;
2619
+ }
2620
+ if (process.env.NEXT_PUBLIC_VERCEL_URL) {
2621
+ return `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`;
2622
+ }
2623
+ return `http://localhost:${process.env.PORT ?? 3e3}`;
2624
+ }
2625
+ __name(getBaseUrl, "getBaseUrl");
2626
+ createLogger({
2627
+ name: "feature-flags",
2628
+ level: LogLevel.INFO
2629
+ });
2630
+ new PostHog(process.env.POSTHOG_API_KEY || "default_key", {
2631
+ host: process.env.POSTHOG_HOST || "https://app.posthog.com"
2632
+ });
2633
+ var EntitlementsServiceImpl = class {
2634
+ static {
2635
+ __name(this, "EntitlementsServiceImpl");
2636
+ }
2637
+ cache = /* @__PURE__ */ new Map();
2638
+ CACHE_TTL_MS = 6e4;
2639
+ /**
2640
+ * Fetch complete entitlement set for a user
2641
+ * Queries database for subscription, trial, and Pioneer status with Redis caching
2642
+ */
2643
+ async getEntitlements(userId) {
2644
+ if (isRedisAvailable()) {
2645
+ const cached = await getCache(`entitlements:${userId}`);
2646
+ if (cached) {
2647
+ return cached;
2648
+ }
2649
+ }
2650
+ const memoryCached = this.cache.get(userId);
2651
+ if (memoryCached && Date.now() - memoryCached.timestamp < this.CACHE_TTL_MS) {
2652
+ return memoryCached.entitlements;
2653
+ }
2654
+ if (!db) {
2655
+ const entitlements = this.createDefaultEntitlements(userId);
2656
+ return entitlements;
2657
+ }
2658
+ try {
2659
+ const [subscription] = await db.select().from(subscriptions).where(eq(subscriptions.userId, userId)).limit(1);
2660
+ const [trial] = await db.select().from(trials).where(and(eq(trials.userId, userId), eq(trials.status, "active"))).limit(1);
2661
+ const isPioneerEnabled = process.env.FEATURE_PIONEER_PROGRAM !== "false";
2662
+ let pioneerData = null;
2663
+ if (isPioneerEnabled) {
2664
+ try {
2665
+ const [pioneerRow] = await db.select({
2666
+ id: pioneers.id,
2667
+ githubStarred: pioneers.githubStarred,
2668
+ joinedAt: pioneers.joinedAt
2669
+ }).from(pioneers).where(eq(pioneers.userId, userId)).limit(1);
2670
+ if (pioneerRow) {
2671
+ const [redemption] = await db.select({
2672
+ id: pioneerRedemptions.id
2673
+ }).from(pioneerRedemptions).where(eq(pioneerRedemptions.userId, userId)).limit(1);
2674
+ pioneerData = {
2675
+ tier: redemption ? "founding_pioneer" : "pioneer",
2676
+ totalPoints: 0
2677
+ };
2678
+ }
2679
+ } catch (pioneerError) {
2680
+ logger.warn("[entitlements] Pioneer query failed, continuing without pioneer data", {
2681
+ userId,
2682
+ error: pioneerError instanceof Error ? pioneerError.message : String(pioneerError)
2683
+ });
2684
+ }
2685
+ }
2686
+ const tier = getEffectiveTier(subscription?.plan || "free");
2687
+ const [usageData] = subscription ? await db.select().from(usageLimits).where(eq(usageLimits.subscriptionId, subscription.id)).orderBy(desc(usageLimits.month)).limit(1) : [
2688
+ null
2689
+ ];
2690
+ const creditBalance = await this.calculateCreditBalance(userId, tier, subscription);
2691
+ const entitlements = this.buildEntitlements(userId, tier, trial || null, pioneerData, subscription || null, usageData || null, creditBalance);
2692
+ if (isRedisAvailable()) {
2693
+ await setCache(`entitlements:${userId}`, entitlements, 60);
2694
+ }
2695
+ this.cache.set(userId, {
2696
+ entitlements,
2697
+ timestamp: Date.now()
2698
+ });
2699
+ return entitlements;
2700
+ } catch (_error) {
2701
+ return this.createDefaultEntitlements(userId);
2702
+ }
2703
+ }
2704
+ /**
2705
+ * Check if user has access to a specific feature
2706
+ */
2707
+ async checkFeatureAccess(userId, feature) {
2708
+ const entitlements = await this.getEntitlements(userId);
2709
+ if (entitlements.features.includes(feature)) {
2710
+ const limit = entitlements.limits.get(feature);
2711
+ if (limit && limit.max !== null && limit.current >= limit.max) {
2712
+ return {
2713
+ granted: false,
2714
+ reason: "usage_limit_reached",
2715
+ limitInfo: limit
2716
+ };
2717
+ }
2718
+ return {
2719
+ granted: true
2720
+ };
2721
+ }
2722
+ if (entitlements.trial?.active && entitlements.trial.features.includes(feature)) {
2723
+ return {
2724
+ granted: true
2725
+ };
2726
+ }
2727
+ if (entitlements.pioneer?.tier === "founding_pioneer") {
2728
+ if (isFeatureAvailableAtTier(feature, "pro")) {
2729
+ return {
2730
+ granted: true
2731
+ };
2732
+ }
2733
+ }
2734
+ const requiredTier = this.getRequiredTierForFeature(feature);
2735
+ return {
2736
+ granted: false,
2737
+ reason: "feature_not_in_plan",
2738
+ requiredTier
2739
+ };
2740
+ }
2741
+ /**
2742
+ * Get usage limits for a specific feature
2743
+ */
2744
+ async getFeatureLimits(userId, feature) {
2745
+ const entitlements = await this.getEntitlements(userId);
2746
+ return entitlements.limits.get(feature) || null;
2747
+ }
2748
+ /**
2749
+ * Check if user meets minimum tier requirement
2750
+ */
2751
+ async checkTierRequirement(userId, requiredTier) {
2752
+ const entitlements = await this.getEntitlements(userId);
2753
+ return this.compareTiers(entitlements.tier, requiredTier) >= 0;
2754
+ }
2755
+ /**
2756
+ * Invalidate cached entitlements for a user (both Redis and memory)
2757
+ */
2758
+ async invalidateCache(userId) {
2759
+ this.cache.delete(userId);
2760
+ if (isRedisAvailable()) {
2761
+ await deleteCache(`entitlements:${userId}`);
2762
+ }
2763
+ }
2764
+ /**
2765
+ * Build entitlements from database query results
2766
+ */
2767
+ buildEntitlements(userId, tier, trial, pioneer, subscription, usageData, creditBalance) {
2768
+ const features = getTierFeatures(tier);
2769
+ const trialInfo = trial ? {
2770
+ active: true,
2771
+ endsAt: trial.endsAt,
2772
+ features: trial.features || []
2773
+ } : null;
2774
+ const pioneerInfo = pioneer ? {
2775
+ tier: pioneer.tier,
2776
+ totalPoints: pioneer.totalPoints,
2777
+ pointsToNext: this.calculatePointsToNext(pioneer.tier, pioneer.totalPoints),
2778
+ nextTier: this.getNextTier(pioneer.tier),
2779
+ discountPercent: this.getTierDiscount(pioneer.tier),
2780
+ benefits: this.getTierBenefits(pioneer.tier)
2781
+ } : null;
2782
+ const limits = /* @__PURE__ */ new Map();
2783
+ for (const feature of features) {
2784
+ const maxLimit = getTierLimit(tier, feature);
2785
+ if (maxLimit !== null) {
2786
+ const current = this.getFeatureUsage(feature, usageData);
2787
+ limits.set(feature, {
2788
+ feature,
2789
+ current,
2790
+ max: maxLimit,
2791
+ period: "monthly"
2792
+ });
2793
+ }
2794
+ }
2795
+ const effectiveDate = subscription?.currentPeriodStart || /* @__PURE__ */ new Date();
2796
+ const expiresAt = subscription?.currentPeriodEnd || null;
2797
+ return {
2798
+ userId,
2799
+ tier,
2800
+ features,
2801
+ limits,
2802
+ trial: trialInfo,
2803
+ pioneer: pioneerInfo,
2804
+ credits: creditBalance,
2805
+ effectiveDate,
2806
+ expiresAt,
2807
+ version: 1,
2808
+ reason: "subscription"
2809
+ };
2810
+ }
2811
+ /**
2812
+ * Create default entitlements for a user (graceful degradation fallback)
2813
+ * Used when database is unavailable - returns free tier with no usage data
2814
+ */
2815
+ createDefaultEntitlements(userId) {
2816
+ const tier = "free";
2817
+ const features = getTierFeatures(tier);
2818
+ const limits = /* @__PURE__ */ new Map();
2819
+ for (const feature of features) {
2820
+ const maxLimit = getTierLimit(tier, feature);
2821
+ if (maxLimit !== null) {
2822
+ limits.set(feature, {
2823
+ feature,
2824
+ current: 0,
2825
+ max: maxLimit,
2826
+ period: "monthly"
2827
+ });
2828
+ }
2829
+ }
2830
+ const defaultCredits = {
2831
+ included: TIER_CREDIT_ALLOWANCES.free,
2832
+ topups: 0,
2833
+ total: TIER_CREDIT_ALLOWANCES.free,
2834
+ overage: 0,
2835
+ softCapReached: false,
2836
+ monthlyAllowance: TIER_CREDIT_ALLOWANCES.free
2837
+ };
2838
+ return {
2839
+ userId,
2840
+ tier,
2841
+ features,
2842
+ limits,
2843
+ trial: null,
2844
+ pioneer: null,
2845
+ credits: defaultCredits,
2846
+ effectiveDate: /* @__PURE__ */ new Date(),
2847
+ expiresAt: null,
2848
+ version: 1,
2849
+ reason: "subscription"
2850
+ };
2851
+ }
2852
+ /**
2853
+ * Calculate credit balance from the credits ledger (pricing_spec_v3.md)
2854
+ * Per spec: Balance = included credits + top-up credits - consumption
2855
+ */
2856
+ async calculateCreditBalance(userId, tier, subscription) {
2857
+ const monthlyAllowance = TIER_CREDIT_ALLOWANCES[tier] || 0;
2858
+ const now = /* @__PURE__ */ new Date();
2859
+ const billingPeriodStart = subscription?.currentPeriodStart || new Date(now.getFullYear(), now.getMonth(), 1);
2860
+ const billingPeriodEnd = subscription?.currentPeriodEnd || new Date(now.getFullYear(), now.getMonth() + 1, 0);
2861
+ try {
2862
+ const [includedResult] = await db.select({
2863
+ total: sum(creditsLedger.credits)
2864
+ }).from(creditsLedger).where(and(eq(creditsLedger.userId, userId), eq(creditsLedger.transactionType, "monthly_allowance"), gt(creditsLedger.createdAt, billingPeriodStart), lte(creditsLedger.createdAt, billingPeriodEnd)));
2865
+ const [topupResult] = await db.select({
2866
+ total: sum(creditsLedger.credits)
2867
+ }).from(creditsLedger).where(and(eq(creditsLedger.userId, userId), eq(creditsLedger.transactionType, "top_up")));
2868
+ const [consumptionResult] = await db.select({
2869
+ total: sum(creditsLedger.credits)
2870
+ }).from(creditsLedger).where(and(eq(creditsLedger.userId, userId), eq(creditsLedger.transactionType, "job_consumption")));
2871
+ const included = Number(includedResult?.total || 0);
2872
+ const topups = Number(topupResult?.total || 0);
2873
+ const consumption = Number(consumptionResult?.total || 0);
2874
+ const total = included + topups + consumption;
2875
+ const overage = total < 0 ? Math.abs(total) : 0;
2876
+ const softCapReached = total <= CREDIT_OVERAGE_SOFT_CAP;
2877
+ return {
2878
+ included: Math.max(0, included + consumption),
2879
+ topups,
2880
+ total,
2881
+ overage,
2882
+ softCapReached,
2883
+ monthlyAllowance
2884
+ };
2885
+ } catch (_error) {
2886
+ return {
2887
+ included: monthlyAllowance,
2888
+ topups: 0,
2889
+ total: monthlyAllowance,
2890
+ overage: 0,
2891
+ softCapReached: false,
2892
+ monthlyAllowance
2893
+ };
2894
+ }
2895
+ }
2896
+ /**
2897
+ * Determine required tier for a feature
2898
+ */
2899
+ getRequiredTierForFeature(feature) {
2900
+ const tiers = [
2901
+ "free",
2902
+ "pro",
2903
+ "team",
2904
+ "enterprise"
2905
+ ];
2906
+ for (const tier of tiers) {
2907
+ if (isFeatureAvailableAtTier(feature, tier)) {
2908
+ return tier;
2909
+ }
2910
+ }
2911
+ return "enterprise";
2912
+ }
2913
+ /**
2914
+ * Calculate points needed to reach next tier
2915
+ */
2916
+ calculatePointsToNext(currentTier, totalPoints) {
2917
+ const PIONEER_TIER_THRESHOLDS = {
2918
+ pioneer: 0,
2919
+ active_pioneer: 1,
2920
+ contributing_pioneer: 2,
2921
+ founding_pioneer: 3
2922
+ };
2923
+ const tierOrder = [
2924
+ "pioneer",
2925
+ "active_pioneer",
2926
+ "contributing_pioneer",
2927
+ "founding_pioneer"
2928
+ ];
2929
+ const currentIndex = tierOrder.indexOf(currentTier);
2930
+ if (currentIndex === -1 || currentIndex === tierOrder.length - 1) {
2931
+ return 0;
2932
+ }
2933
+ const nextTier = tierOrder[currentIndex + 1];
2934
+ if (!nextTier) {
2935
+ return 0;
2936
+ }
2937
+ const threshold = PIONEER_TIER_THRESHOLDS[nextTier];
2938
+ return threshold !== void 0 ? threshold - totalPoints : 0;
2939
+ }
2940
+ /**
2941
+ * Get next Pioneer tier
2942
+ */
2943
+ getNextTier(currentTier) {
2944
+ const tierOrder = [
2945
+ "pioneer",
2946
+ "active_pioneer",
2947
+ "contributing_pioneer",
2948
+ "founding_pioneer"
2949
+ ];
2950
+ const currentIndex = tierOrder.indexOf(currentTier);
2951
+ if (currentIndex === -1 || currentIndex === tierOrder.length - 1) {
2952
+ return null;
2953
+ }
2954
+ return tierOrder[currentIndex + 1] ?? null;
2955
+ }
2956
+ /**
2957
+ * Get discount percentage for Pioneer tier
2958
+ */
2959
+ getTierDiscount(tier) {
2960
+ const discounts = {
2961
+ pioneer: 0,
2962
+ active_pioneer: 50,
2963
+ contributing_pioneer: 75,
2964
+ founding_pioneer: 100
2965
+ };
2966
+ return discounts[tier] || 0;
2967
+ }
2968
+ /**
2969
+ * Get benefits for Pioneer tier
2970
+ */
2971
+ getTierBenefits(tier) {
2972
+ const benefits = {
2973
+ pioneer: [
2974
+ "Pioneer badge",
2975
+ "Community access"
2976
+ ],
2977
+ active_pioneer: [
2978
+ "Pioneer badge",
2979
+ "Community access",
2980
+ "50% discount on Pro plan"
2981
+ ],
2982
+ contributing_pioneer: [
2983
+ "Pioneer badge",
2984
+ "Community access",
2985
+ "75% discount on Pro plan",
2986
+ "Priority support"
2987
+ ],
2988
+ founding_pioneer: [
2989
+ "Pioneer badge",
2990
+ "Community access",
2991
+ "Lifetime Pro access",
2992
+ "Priority support",
2993
+ "Founding member recognition"
2994
+ ]
2995
+ };
2996
+ return benefits[tier] || [];
2997
+ }
2998
+ /**
2999
+ * Compare two tiers (returns -1, 0, or 1)
3000
+ */
3001
+ compareTiers(userTier, requiredTier) {
3002
+ const tierOrder = [
3003
+ "free",
3004
+ "pro",
3005
+ "team",
3006
+ "enterprise"
3007
+ ];
3008
+ const userIndex = tierOrder.indexOf(userTier);
3009
+ const requiredIndex = tierOrder.indexOf(requiredTier);
3010
+ return userIndex - requiredIndex;
3011
+ }
3012
+ /**
3013
+ * Map feature to actual usage from usage_limits table (ENT-001)
3014
+ * Maps feature names to database columns for usage tracking
3015
+ */
3016
+ getFeatureUsage(feature, usageData) {
3017
+ if (!usageData) {
3018
+ return 0;
3019
+ }
3020
+ switch (feature) {
3021
+ case "cloud_backup":
3022
+ return usageData.snapshotsUsed || 0;
3023
+ case "api_access":
3024
+ return usageData.apiCallsUsed || 0;
3025
+ // Features without usage tracking (binary access)
3026
+ case "advanced_analytics":
3027
+ case "unlimited_workspaces":
3028
+ case "cli_full_features":
3029
+ case "team_dashboard":
3030
+ case "multi_workspace":
3031
+ case "sso_authentication":
3032
+ case "audit_logs":
3033
+ case "priority_support":
3034
+ case "custom_retention":
3035
+ return 0;
3036
+ // No usage tracking for these features
3037
+ default:
3038
+ return 0;
3039
+ }
3040
+ }
3041
+ };
3042
+ var entitlementsService = new EntitlementsServiceImpl();
3043
+ function getDb() {
3044
+ if (!db) {
3045
+ throw new Error("Database not initialized. Check DATABASE_URL environment variable.");
3046
+ }
3047
+ return db;
3048
+ }
3049
+ __name(getDb, "getDb");
3050
+ var MCPService = class {
3051
+ static {
3052
+ __name(this, "MCPService");
3053
+ }
3054
+ /**
3055
+ * Record an observation from the MCP bridge
3056
+ * Uses idempotency key to prevent duplicates
3057
+ */
3058
+ async recordObservation(input) {
3059
+ const database = getDb();
3060
+ if (input.idempotencyKey) {
3061
+ const existing = await database.select({
3062
+ id: mcpObservations.id
3063
+ }).from(mcpObservations).where(eq(mcpObservations.idempotencyKey, input.idempotencyKey)).limit(1);
3064
+ if (existing.length > 0) {
3065
+ return {
3066
+ id: existing[0].id,
3067
+ created: false
3068
+ };
3069
+ }
3070
+ }
3071
+ const data = {
3072
+ workspaceId: input.workspaceId,
3073
+ userId: input.userId,
3074
+ type: input.type,
3075
+ severity: input.severity,
3076
+ message: input.message,
3077
+ context: input.context ?? {},
3078
+ filePath: input.filePath,
3079
+ lineNumber: input.lineNumber,
3080
+ source: input.source ?? "extension",
3081
+ toolName: input.toolName,
3082
+ idempotencyKey: input.idempotencyKey,
3083
+ deviceId: input.deviceId,
3084
+ observedAt: input.observedAt ?? /* @__PURE__ */ new Date(),
3085
+ processed: false
3086
+ };
3087
+ const result = await database.insert(mcpObservations).values(data).returning({
3088
+ id: mcpObservations.id
3089
+ });
3090
+ return {
3091
+ id: result[0].id,
3092
+ created: true
3093
+ };
3094
+ }
3095
+ /**
3096
+ * Record a tool invocation
3097
+ * Uses idempotency key to prevent duplicates
3098
+ */
3099
+ async recordInvocation(input) {
3100
+ const database = getDb();
3101
+ if (input.idempotencyKey) {
3102
+ const existing = await database.select({
3103
+ id: mcpToolInvocations.id
3104
+ }).from(mcpToolInvocations).where(eq(mcpToolInvocations.idempotencyKey, input.idempotencyKey)).limit(1);
3105
+ if (existing.length > 0) {
3106
+ return {
3107
+ id: existing[0].id,
3108
+ created: false
3109
+ };
3110
+ }
3111
+ }
3112
+ const data = {
3113
+ workspaceId: input.workspaceId,
3114
+ userId: input.userId,
3115
+ toolName: input.toolName,
3116
+ toolVersion: input.toolVersion,
3117
+ invocationType: input.invocationType,
3118
+ requestPayload: input.requestPayload,
3119
+ responsePayload: input.responsePayload,
3120
+ status: input.status ?? "pending",
3121
+ errorMessage: input.errorMessage,
3122
+ inputTokens: input.inputTokens ?? 0,
3123
+ outputTokens: input.outputTokens ?? 0,
3124
+ idempotencyKey: input.idempotencyKey,
3125
+ source: input.source ?? "extension",
3126
+ sessionId: input.sessionId,
3127
+ durationMs: input.durationMs
3128
+ };
3129
+ const result = await database.insert(mcpToolInvocations).values(data).returning({
3130
+ id: mcpToolInvocations.id
3131
+ });
3132
+ return {
3133
+ id: result[0].id,
3134
+ created: true
3135
+ };
3136
+ }
3137
+ /**
3138
+ * Update sync state for a device
3139
+ * Creates or updates the sync state record
3140
+ */
3141
+ async updateSyncState(input) {
3142
+ const database = getDb();
3143
+ const updateResult = await database.update(extensionSyncState).set({
3144
+ lastSyncAt: input.lastSyncAt ?? /* @__PURE__ */ new Date(),
3145
+ syncVersion: input.syncVersion ?? sql`${extensionSyncState.syncVersion} + 1`,
3146
+ deviceType: input.deviceType,
3147
+ deviceName: input.deviceName,
3148
+ pendingChangesCount: input.pendingChangesCount ?? 0,
3149
+ pendingChanges: input.pendingChanges ?? [],
3150
+ isOnline: input.isOnline ?? true,
3151
+ lastHeartbeatAt: /* @__PURE__ */ new Date(),
3152
+ updatedAt: /* @__PURE__ */ new Date()
3153
+ }).where(and(eq(extensionSyncState.userId, input.userId), eq(extensionSyncState.workspaceId, input.workspaceId), eq(extensionSyncState.deviceId, input.deviceId))).returning({
3154
+ id: extensionSyncState.id
3155
+ });
3156
+ if (updateResult.length > 0) {
3157
+ return {
3158
+ id: updateResult[0].id,
3159
+ created: false
3160
+ };
3161
+ }
3162
+ const data = {
3163
+ userId: input.userId,
3164
+ workspaceId: input.workspaceId,
3165
+ deviceId: input.deviceId,
3166
+ deviceType: input.deviceType,
3167
+ deviceName: input.deviceName,
3168
+ lastSyncAt: input.lastSyncAt ?? /* @__PURE__ */ new Date(),
3169
+ syncVersion: input.syncVersion ?? 1,
3170
+ pendingChangesCount: input.pendingChangesCount ?? 0,
3171
+ pendingChanges: input.pendingChanges ?? [],
3172
+ isOnline: input.isOnline ?? true,
3173
+ lastHeartbeatAt: /* @__PURE__ */ new Date()
3174
+ };
3175
+ const result = await database.insert(extensionSyncState).values(data).returning({
3176
+ id: extensionSyncState.id
3177
+ });
3178
+ return {
3179
+ id: result[0].id,
3180
+ created: true
3181
+ };
3182
+ }
3183
+ /**
3184
+ * Query observations with filters
3185
+ */
3186
+ async queryObservations(input) {
3187
+ const database = getDb();
3188
+ const conditions = [
3189
+ eq(mcpObservations.workspaceId, input.workspaceId)
3190
+ ];
3191
+ if (input.userId) {
3192
+ conditions.push(eq(mcpObservations.userId, input.userId));
3193
+ }
3194
+ if (input.processed !== void 0) {
3195
+ conditions.push(eq(mcpObservations.processed, input.processed));
3196
+ }
3197
+ if (input.after) {
3198
+ conditions.push(gte(mcpObservations.createdAt, input.after));
3199
+ }
3200
+ const results = await database.select().from(mcpObservations).where(and(...conditions)).orderBy(sql`${mcpObservations.createdAt} DESC`).limit(input.limit ?? 100);
3201
+ return results;
3202
+ }
3203
+ /**
3204
+ * Mark observations as processed
3205
+ */
3206
+ async markObservationsProcessed(observationIds) {
3207
+ if (observationIds.length === 0) {
3208
+ return 0;
3209
+ }
3210
+ const database = getDb();
3211
+ const result = await database.update(mcpObservations).set({
3212
+ processed: true,
3213
+ processedAt: /* @__PURE__ */ new Date(),
3214
+ updatedAt: /* @__PURE__ */ new Date()
3215
+ }).where(sql`${mcpObservations.id} IN (${observationIds.join(",")})`);
3216
+ return result.rowCount ?? 0;
3217
+ }
3218
+ };
3219
+ var instance = null;
3220
+ function getMCPService() {
3221
+ if (!instance) {
3222
+ instance = new MCPService();
3223
+ }
3224
+ return instance;
3225
+ }
3226
+ __name(getMCPService, "getMCPService");
3227
+ var SagaOrchestratorImpl = class {
3228
+ static {
3229
+ __name(this, "SagaOrchestratorImpl");
3230
+ }
3231
+ persistence;
3232
+ definitions = /* @__PURE__ */ new Map();
3233
+ runningInstances = /* @__PURE__ */ new Map();
3234
+ constructor(persistence) {
3235
+ this.persistence = persistence;
3236
+ }
3237
+ /**
3238
+ * Register a saga definition
3239
+ */
3240
+ registerSaga(definition) {
3241
+ this.definitions.set(definition.sagaType, definition);
3242
+ process.stdout.write(`[SagaOrchestrator] Registered saga type: ${definition.sagaType}`);
3243
+ }
3244
+ /**
3245
+ * Start a new saga instance
3246
+ */
3247
+ async start(sagaType, initialContext) {
3248
+ const definition = this.definitions.get(sagaType);
3249
+ if (!definition) {
3250
+ throw new Error(`Saga type not registered: ${sagaType}`);
3251
+ }
3252
+ const sagaId = this.generateSagaId();
3253
+ const instance2 = {
3254
+ sagaId,
3255
+ sagaType,
3256
+ status: "pending",
3257
+ context: initialContext,
3258
+ steps: definition.steps.map((step) => ({
3259
+ stepId: step.stepId,
3260
+ stepName: step.stepName,
3261
+ status: "pending",
3262
+ input: {},
3263
+ output: null,
3264
+ error: null,
3265
+ startedAt: null,
3266
+ completedAt: null,
3267
+ compensatedAt: null
3268
+ })),
3269
+ startedAt: /* @__PURE__ */ new Date(),
3270
+ completedAt: null,
3271
+ failedAt: null,
3272
+ error: null,
3273
+ retryCount: 0,
3274
+ maxRetries: definition.maxRetries || 3
3275
+ };
3276
+ await this.persistence.save(instance2);
3277
+ this.runningInstances.set(sagaId, instance2);
3278
+ this.executeAsync(sagaId, definition);
3279
+ return instance2;
3280
+ }
3281
+ /**
3282
+ * Resume a saga from persisted state
3283
+ */
3284
+ async resume(sagaId) {
3285
+ const instance2 = await this.persistence.load(sagaId);
3286
+ if (!instance2) {
3287
+ throw new Error(`Saga not found: ${sagaId}`);
3288
+ }
3289
+ const definition = this.definitions.get(instance2.sagaType);
3290
+ if (!definition) {
3291
+ throw new Error(`Saga type not registered: ${instance2.sagaType}`);
3292
+ }
3293
+ this.runningInstances.set(sagaId, instance2);
3294
+ this.executeAsync(sagaId, definition);
3295
+ return instance2;
3296
+ }
3297
+ /**
3298
+ * Get saga status
3299
+ */
3300
+ async getStatus(sagaId) {
3301
+ const running = this.runningInstances.get(sagaId);
3302
+ if (running) {
3303
+ return running;
3304
+ }
3305
+ return this.persistence.load(sagaId);
3306
+ }
3307
+ /**
3308
+ * List all sagas
3309
+ */
3310
+ async listSagas(filter) {
3311
+ return this.persistence.listAll(filter);
3312
+ }
3313
+ /**
3314
+ * Execute saga steps asynchronously
3315
+ */
3316
+ async executeAsync(sagaId, definition) {
3317
+ const instance2 = this.runningInstances.get(sagaId);
3318
+ if (!instance2) {
3319
+ return;
3320
+ }
3321
+ try {
3322
+ instance2.status = "running";
3323
+ await this.persistence.update(sagaId, {
3324
+ status: "running"
3325
+ });
3326
+ for (let i = 0; i < definition.steps.length; i++) {
3327
+ const stepDef = definition.steps[i];
3328
+ const stepExec = instance2.steps[i];
3329
+ if (!stepDef || !stepExec) {
3330
+ throw new Error(`Step definition or execution not found at index ${i}`);
3331
+ }
3332
+ if (stepExec.status === "completed") {
3333
+ continue;
3334
+ }
3335
+ const success = await this.executeStep(instance2, stepDef, stepExec);
3336
+ if (!success) {
3337
+ await this.compensate(instance2, definition, i);
3338
+ return;
3339
+ }
3340
+ if (definition.persistenceInterval) {
3341
+ await this.persistence.update(sagaId, {
3342
+ steps: instance2.steps
3343
+ });
3344
+ }
3345
+ }
3346
+ instance2.status = "completed";
3347
+ instance2.completedAt = /* @__PURE__ */ new Date();
3348
+ await this.persistence.update(sagaId, {
3349
+ status: "completed",
3350
+ completedAt: instance2.completedAt
3351
+ });
3352
+ process.stdout.write(`[SagaOrchestrator] Saga completed: ${sagaId}`);
3353
+ } catch (error) {
3354
+ instance2.status = "failed";
3355
+ instance2.failedAt = /* @__PURE__ */ new Date();
3356
+ instance2.error = error instanceof Error ? error.message : String(error);
3357
+ await this.persistence.update(sagaId, {
3358
+ status: "failed",
3359
+ failedAt: instance2.failedAt,
3360
+ error: instance2.error
3361
+ });
3362
+ } finally {
3363
+ this.runningInstances.delete(sagaId);
3364
+ }
3365
+ }
3366
+ /**
3367
+ * Execute a single saga step
3368
+ */
3369
+ async executeStep(instance2, stepDef, stepExec) {
3370
+ stepExec.status = "running";
3371
+ stepExec.startedAt = /* @__PURE__ */ new Date();
3372
+ try {
3373
+ const timeoutMs = stepDef.timeout || 3e4;
3374
+ const result = await this.executeWithTimeout(stepDef.execute(stepExec.input, instance2.context), timeoutMs);
3375
+ stepExec.output = result;
3376
+ stepExec.status = "completed";
3377
+ stepExec.completedAt = /* @__PURE__ */ new Date();
3378
+ process.stdout.write(`[SagaOrchestrator] Step completed: ${stepDef.stepId}`);
3379
+ return true;
3380
+ } catch (error) {
3381
+ stepExec.error = error instanceof Error ? error.message : String(error);
3382
+ stepExec.status = "failed";
3383
+ if (stepDef.retryable && instance2.retryCount < instance2.maxRetries) {
3384
+ instance2.retryCount++;
3385
+ process.stdout.write(`[SagaOrchestrator] Retrying step ${stepDef.stepId} (${instance2.retryCount}/${instance2.maxRetries})`);
3386
+ stepExec.status = "pending";
3387
+ stepExec.error = null;
3388
+ const delayMs = Math.min(1e3 * 2 ** instance2.retryCount, 3e4);
3389
+ await this.sleep(delayMs);
3390
+ return this.executeStep(instance2, stepDef, stepExec);
3391
+ }
3392
+ return false;
3393
+ }
3394
+ }
3395
+ /**
3396
+ * Compensate (rollback) completed steps
3397
+ */
3398
+ async compensate(instance2, definition, failedStepIndex) {
3399
+ instance2.status = "compensating";
3400
+ await this.persistence.update(instance2.sagaId, {
3401
+ status: "compensating"
3402
+ });
3403
+ process.stdout.write(`[SagaOrchestrator] Starting compensation for saga: ${instance2.sagaId}`);
3404
+ for (let i = failedStepIndex - 1; i >= 0; i--) {
3405
+ const stepDef = definition.steps[i];
3406
+ const stepExec = instance2.steps[i];
3407
+ if (!stepDef || !stepExec) {
3408
+ continue;
3409
+ }
3410
+ if (stepExec.status !== "completed") {
3411
+ continue;
3412
+ }
3413
+ if (!stepDef.compensate) {
3414
+ process.stdout.write(`[SagaOrchestrator] No compensation for step: ${stepDef.stepId}`);
3415
+ continue;
3416
+ }
3417
+ try {
3418
+ await stepDef.compensate(stepExec.input, stepExec.output, instance2.context);
3419
+ stepExec.status = "compensated";
3420
+ stepExec.compensatedAt = /* @__PURE__ */ new Date();
3421
+ process.stdout.write(`[SagaOrchestrator] Compensated step: ${stepDef.stepId}`);
3422
+ } catch (_error) {
3423
+ }
3424
+ }
3425
+ instance2.status = "compensated";
3426
+ instance2.failedAt = /* @__PURE__ */ new Date();
3427
+ await this.persistence.update(instance2.sagaId, {
3428
+ status: "compensated",
3429
+ failedAt: instance2.failedAt,
3430
+ steps: instance2.steps
3431
+ });
3432
+ process.stdout.write(`[SagaOrchestrator] Compensation completed for saga: ${instance2.sagaId}`);
3433
+ }
3434
+ // Helper methods
3435
+ generateSagaId() {
3436
+ return `saga_${randomBytes(16).toString("hex")}`;
3437
+ }
3438
+ async executeWithTimeout(promise, timeoutMs) {
3439
+ return Promise.race([
3440
+ promise,
3441
+ new Promise((_resolve, reject) => setTimeout(() => reject(new Error("Step execution timeout")), timeoutMs))
3442
+ ]);
3443
+ }
3444
+ sleep(ms) {
3445
+ return new Promise((resolve) => setTimeout(resolve, ms));
3446
+ }
3447
+ };
3448
+ var SagaPersistenceImpl = class {
3449
+ static {
3450
+ __name(this, "SagaPersistenceImpl");
3451
+ }
3452
+ // In-memory fallback storage
3453
+ inMemoryStore = /* @__PURE__ */ new Map();
3454
+ /**
3455
+ * Save a new saga instance to database
3456
+ */
3457
+ async save(saga) {
3458
+ if (!db) {
3459
+ this.inMemoryStore.set(saga.sagaId, saga);
3460
+ process.stdout.write(`[SagaPersistence] Saved saga ${saga.sagaId} to in-memory store (DB unavailable)`);
3461
+ return;
3462
+ }
3463
+ try {
3464
+ await db.insert(sagas).values({
3465
+ sagaId: saga.sagaId,
3466
+ sagaType: saga.sagaType,
3467
+ status: saga.status,
3468
+ context: saga.context,
3469
+ steps: saga.steps.map((step) => ({
3470
+ stepId: step.stepId,
3471
+ stepName: step.stepName,
3472
+ status: step.status,
3473
+ input: step.input,
3474
+ output: step.output,
3475
+ error: step.error,
3476
+ startedAt: step.startedAt?.toISOString() || null,
3477
+ completedAt: step.completedAt?.toISOString() || null,
3478
+ compensatedAt: step.compensatedAt?.toISOString() || null
3479
+ })),
3480
+ error: saga.error,
3481
+ retryCount: String(saga.retryCount),
3482
+ maxRetries: String(saga.maxRetries),
3483
+ startedAt: saga.startedAt,
3484
+ completedAt: saga.completedAt,
3485
+ failedAt: saga.failedAt
3486
+ });
3487
+ process.stdout.write(`[SagaPersistence] Saved saga ${saga.sagaId} to database`);
3488
+ } catch (_error) {
3489
+ this.inMemoryStore.set(saga.sagaId, saga);
3490
+ }
3491
+ }
3492
+ /**
3493
+ * Load a saga instance from database
3494
+ */
3495
+ async load(sagaId) {
3496
+ if (!db) {
3497
+ const saga = this.inMemoryStore.get(sagaId);
3498
+ if (saga) {
3499
+ process.stdout.write(`[SagaPersistence] Loaded saga ${sagaId} from in-memory store (DB unavailable)`);
3500
+ }
3501
+ return saga || null;
3502
+ }
3503
+ try {
3504
+ const [row] = await db.select().from(sagas).where(eq(sagas.sagaId, sagaId)).limit(1);
3505
+ if (!row) {
3506
+ return this.inMemoryStore.get(sagaId) || null;
3507
+ }
3508
+ const instance2 = {
3509
+ sagaId: row.sagaId,
3510
+ sagaType: row.sagaType,
3511
+ status: row.status,
3512
+ context: row.context,
3513
+ steps: row.steps.map((step) => ({
3514
+ stepId: step.stepId,
3515
+ stepName: step.stepName,
3516
+ status: step.status,
3517
+ input: step.input,
3518
+ output: step.output,
3519
+ error: step.error,
3520
+ startedAt: step.startedAt ? new Date(step.startedAt) : null,
3521
+ completedAt: step.completedAt ? new Date(step.completedAt) : null,
3522
+ compensatedAt: step.compensatedAt ? new Date(step.compensatedAt) : null
3523
+ })),
3524
+ startedAt: row.startedAt,
3525
+ completedAt: row.completedAt,
3526
+ failedAt: row.failedAt,
3527
+ error: row.error,
3528
+ retryCount: Number.parseInt(row.retryCount, 10),
3529
+ maxRetries: Number.parseInt(row.maxRetries, 10)
3530
+ };
3531
+ process.stdout.write(`[SagaPersistence] Loaded saga ${sagaId} from database`);
3532
+ return instance2;
3533
+ } catch (_error) {
3534
+ return this.inMemoryStore.get(sagaId) || null;
3535
+ }
3536
+ }
3537
+ /**
3538
+ * Update saga instance fields
3539
+ */
3540
+ async update(sagaId, updates) {
3541
+ if (!db) {
3542
+ const existing = this.inMemoryStore.get(sagaId);
3543
+ if (existing) {
3544
+ this.inMemoryStore.set(sagaId, {
3545
+ ...existing,
3546
+ ...updates
3547
+ });
3548
+ process.stdout.write(`[SagaPersistence] Updated saga ${sagaId} in in-memory store (DB unavailable)`);
3549
+ }
3550
+ return;
3551
+ }
3552
+ try {
3553
+ const updateData = {};
3554
+ if (updates.status) {
3555
+ updateData.status = updates.status;
3556
+ }
3557
+ if (updates.error !== void 0) {
3558
+ updateData.error = updates.error;
3559
+ }
3560
+ if (updates.completedAt !== void 0) {
3561
+ updateData.completedAt = updates.completedAt;
3562
+ }
3563
+ if (updates.failedAt !== void 0) {
3564
+ updateData.failedAt = updates.failedAt;
3565
+ }
3566
+ if (updates.retryCount !== void 0) {
3567
+ updateData.retryCount = String(updates.retryCount);
3568
+ }
3569
+ if (updates.context) {
3570
+ updateData.context = updates.context;
3571
+ }
3572
+ if (updates.steps) {
3573
+ updateData.steps = updates.steps.map((step) => ({
3574
+ stepId: step.stepId,
3575
+ stepName: step.stepName,
3576
+ status: step.status,
3577
+ input: step.input,
3578
+ output: step.output,
3579
+ error: step.error,
3580
+ startedAt: step.startedAt?.toISOString() || null,
3581
+ completedAt: step.completedAt?.toISOString() || null,
3582
+ compensatedAt: step.compensatedAt?.toISOString() || null
3583
+ }));
3584
+ }
3585
+ updateData.updatedAt = /* @__PURE__ */ new Date();
3586
+ await db.update(sagas).set(updateData).where(eq(sagas.sagaId, sagaId));
3587
+ process.stdout.write(`[SagaPersistence] Updated saga ${sagaId} in database`);
3588
+ } catch (_error) {
3589
+ const existing = this.inMemoryStore.get(sagaId);
3590
+ if (existing) {
3591
+ this.inMemoryStore.set(sagaId, {
3592
+ ...existing,
3593
+ ...updates
3594
+ });
3595
+ }
3596
+ }
3597
+ }
3598
+ /**
3599
+ * List all sagas with optional filtering
3600
+ */
3601
+ async listAll(filter) {
3602
+ if (!db) {
3603
+ let results = Array.from(this.inMemoryStore.values());
3604
+ if (filter?.sagaType) {
3605
+ results = results.filter((s) => s.sagaType === filter.sagaType);
3606
+ }
3607
+ if (filter?.status) {
3608
+ results = results.filter((s) => filter.status?.includes(s.status));
3609
+ }
3610
+ if (filter?.startedAfter) {
3611
+ const startedAfter = filter.startedAfter;
3612
+ results = results.filter((s) => s.startedAt >= startedAfter);
3613
+ }
3614
+ if (filter?.startedBefore) {
3615
+ const startedBefore = filter.startedBefore;
3616
+ results = results.filter((s) => s.startedAt <= startedBefore);
3617
+ }
3618
+ if (filter?.limit) {
3619
+ results = results.slice(0, filter.limit);
3620
+ }
3621
+ process.stdout.write(`[SagaPersistence] Listed ${results.length} sagas from in-memory store (DB unavailable)`);
3622
+ return results;
3623
+ }
3624
+ try {
3625
+ const conditions = [];
3626
+ if (filter?.sagaType) {
3627
+ conditions.push(eq(sagas.sagaType, filter.sagaType));
3628
+ }
3629
+ if (filter?.status && filter.status.length > 0) {
3630
+ conditions.push(inArray(sagas.status, filter.status));
3631
+ }
3632
+ if (filter?.startedAfter) {
3633
+ conditions.push(gte(sagas.startedAt, filter.startedAfter));
3634
+ }
3635
+ if (filter?.startedBefore) {
3636
+ conditions.push(lte(sagas.startedAt, filter.startedBefore));
3637
+ }
3638
+ let query = db.select().from(sagas);
3639
+ if (conditions.length > 0) {
3640
+ query = query.where(and(...conditions));
3641
+ }
3642
+ query = query.orderBy(desc(sagas.startedAt));
3643
+ if (filter?.limit) {
3644
+ query = query.limit(filter.limit);
3645
+ }
3646
+ const rows = await query;
3647
+ const instances = rows.map((row) => ({
3648
+ sagaId: row.sagaId,
3649
+ sagaType: row.sagaType,
3650
+ status: row.status,
3651
+ context: row.context,
3652
+ steps: row.steps.map((step) => ({
3653
+ stepId: step.stepId,
3654
+ stepName: step.stepName,
3655
+ status: step.status,
3656
+ input: step.input,
3657
+ output: step.output,
3658
+ error: step.error,
3659
+ startedAt: step.startedAt ? new Date(step.startedAt) : null,
3660
+ completedAt: step.completedAt ? new Date(step.completedAt) : null,
3661
+ compensatedAt: step.compensatedAt ? new Date(step.compensatedAt) : null
3662
+ })),
3663
+ startedAt: row.startedAt,
3664
+ completedAt: row.completedAt,
3665
+ failedAt: row.failedAt,
3666
+ error: row.error,
3667
+ retryCount: Number.parseInt(row.retryCount, 10),
3668
+ maxRetries: Number.parseInt(row.maxRetries, 10)
3669
+ }));
3670
+ process.stdout.write(`[SagaPersistence] Listed ${instances.length} sagas from database`);
3671
+ return instances;
3672
+ } catch (_error) {
3673
+ return Array.from(this.inMemoryStore.values());
3674
+ }
3675
+ }
3676
+ /**
3677
+ * Delete a saga instance from storage
3678
+ */
3679
+ async delete(sagaId) {
3680
+ if (!db) {
3681
+ this.inMemoryStore.delete(sagaId);
3682
+ logger.debug(`[SagaPersistence] Deleted saga ${sagaId} from in-memory store (DB unavailable)`);
3683
+ return;
3684
+ }
3685
+ try {
3686
+ await db.delete(sagas).where(eq(sagas.sagaId, sagaId));
3687
+ this.inMemoryStore.delete(sagaId);
3688
+ logger.debug(`[SagaPersistence] Deleted saga ${sagaId} from database`);
3689
+ } catch (_error) {
3690
+ this.inMemoryStore.delete(sagaId);
3691
+ }
3692
+ }
3693
+ };
3694
+ var sagaPersistence = new SagaPersistenceImpl();
3695
+ async function updateSubscriptionStep(input, context) {
3696
+ const priceIdMap = {
3697
+ pro: process.env.STRIPE_PRO_MONTHLY_PRICE_ID,
3698
+ team: process.env.STRIPE_TEAM_MONTHLY_PRICE_ID,
3699
+ enterprise: process.env.STRIPE_ENTERPRISE_MONTHLY_PRICE_ID
3700
+ };
3701
+ const newPriceId = priceIdMap[input.toTier];
3702
+ if (!newPriceId) {
3703
+ throw new Error(`No price ID configured for tier: ${input.toTier}`);
3704
+ }
3705
+ if (!input.subscriptionId) {
3706
+ throw new Error("Subscription ID is required for tier upgrade");
3707
+ }
3708
+ const stripeClient = context.stripeClient;
3709
+ if (!stripeClient) {
3710
+ throw new Error("Stripe client not available in context");
3711
+ }
3712
+ const currentSubscription = await stripeClient.subscriptions.retrieve(input.subscriptionId);
3713
+ const currentPriceId = currentSubscription.items.data[0]?.price?.id;
3714
+ context.previousPriceId = currentPriceId;
3715
+ await stripeClient.subscriptions.update(input.subscriptionId, {
3716
+ items: [
3717
+ {
3718
+ id: currentSubscription.items.data[0].id,
3719
+ price: newPriceId
3720
+ }
3721
+ ],
3722
+ proration_behavior: "create_prorations"
3723
+ });
3724
+ const effectiveDate = /* @__PURE__ */ new Date();
3725
+ const prorationAmount = null;
3726
+ process.stdout.write(`[TierUpgradeSaga] Updated subscription ${input.subscriptionId} to ${input.toTier} tier (price: ${newPriceId})`);
3727
+ return {
3728
+ subscriptionId: input.subscriptionId,
3729
+ priceId: newPriceId,
3730
+ effectiveDate,
3731
+ prorationAmount
3732
+ };
3733
+ }
3734
+ __name(updateSubscriptionStep, "updateSubscriptionStep");
3735
+ async function compensateUpdateSubscription(_input, output, context) {
3736
+ if (!output?.subscriptionId || !context.previousPriceId) {
3737
+ return;
3738
+ }
3739
+ try {
3740
+ const stripeClient = context.stripeClient;
3741
+ if (!stripeClient) {
3742
+ return;
3743
+ }
3744
+ const currentSubscription = await stripeClient.subscriptions.retrieve(output.subscriptionId);
3745
+ await stripeClient.subscriptions.update(output.subscriptionId, {
3746
+ items: [
3747
+ {
3748
+ id: currentSubscription.items.data[0].id,
3749
+ price: context.previousPriceId
3750
+ }
3751
+ ],
3752
+ proration_behavior: "none"
3753
+ });
3754
+ process.stdout.write(`[TierUpgradeSaga] Compensated: Reverted subscription ${output.subscriptionId} to price ${context.previousPriceId}`);
3755
+ } catch {
3756
+ }
3757
+ }
3758
+ __name(compensateUpdateSubscription, "compensateUpdateSubscription");
3759
+ async function updateUserTierStep(input, context) {
3760
+ if (!db) {
3761
+ throw new Error("Database not available");
3762
+ }
3763
+ const currentEntitlements = await entitlementsService.getEntitlements(input.userId);
3764
+ const previousTier = currentEntitlements.tier;
3765
+ context.previousTier = previousTier;
3766
+ await db.update(user).set({
3767
+ subscriptionTier: input.toTier
3768
+ }).where(eq(user.id, input.userId));
3769
+ await db.update(subscriptions).set({
3770
+ plan: input.toTier
3771
+ }).where(eq(subscriptions.userId, input.userId));
3772
+ process.stdout.write(`[TierUpgradeSaga] Updated user ${input.userId} tier from ${previousTier} to ${input.toTier}`);
3773
+ return {
3774
+ previousTier,
3775
+ updatedAt: /* @__PURE__ */ new Date()
3776
+ };
3777
+ }
3778
+ __name(updateUserTierStep, "updateUserTierStep");
3779
+ async function compensateUpdateUserTier(input, output, context) {
3780
+ if (!output?.previousTier || !db) {
3781
+ return;
3782
+ }
3783
+ try {
3784
+ const previousTier = context.previousTier;
3785
+ await db.update(user).set({
3786
+ subscriptionTier: previousTier
3787
+ }).where(eq(user.id, input.userId));
3788
+ await db.update(subscriptions).set({
3789
+ plan: previousTier
3790
+ }).where(eq(subscriptions.userId, input.userId));
3791
+ process.stdout.write(`[TierUpgradeSaga] Compensated: Reverted user ${input.userId} tier to ${previousTier}`);
3792
+ } catch {
3793
+ }
3794
+ }
3795
+ __name(compensateUpdateUserTier, "compensateUpdateUserTier");
3796
+ async function updateUpgradeEntitlementsStep(input, _context) {
3797
+ const currentEntitlements = await entitlementsService.getEntitlements(input.userId);
3798
+ const previousVersion = 1;
3799
+ await entitlementsService.invalidateCache(input.userId);
3800
+ const newEntitlements = await entitlementsService.getEntitlements(input.userId);
3801
+ const addedFeatures = newEntitlements.features.filter((f) => !currentEntitlements.features.includes(f));
3802
+ process.stdout.write(`[TierUpgradeSaga] Updated entitlements for user ${input.userId}: +${addedFeatures.length} features`);
3803
+ return {
3804
+ entitlements: newEntitlements,
3805
+ previousVersion,
3806
+ addedFeatures
3807
+ };
3808
+ }
3809
+ __name(updateUpgradeEntitlementsStep, "updateUpgradeEntitlementsStep");
3810
+ async function compensateUpdateUpgradeEntitlements(input, _output, _context) {
3811
+ try {
3812
+ await entitlementsService.invalidateCache(input.userId);
3813
+ process.stdout.write(`[TierUpgradeSaga] Compensated: Invalidated entitlements for user ${input.userId}`);
3814
+ } catch {
3815
+ }
3816
+ }
3817
+ __name(compensateUpdateUpgradeEntitlements, "compensateUpdateUpgradeEntitlements");
3818
+ async function sendUpgradeConfirmationStep(input, context) {
3819
+ const emailService = context.emailService;
3820
+ if (!emailService) {
3821
+ const skippedJobId = `skipped_${nanoid()}`;
3822
+ return {
3823
+ emailJobId: skippedJobId,
3824
+ scheduledAt: /* @__PURE__ */ new Date()
3825
+ };
3826
+ }
3827
+ const emailJobData = {
3828
+ id: nanoid(),
3829
+ userId: input.userId,
3830
+ recipientEmail: input.userEmail,
3831
+ template: "tier_upgraded",
3832
+ templateVersion: 1,
3833
+ variant: null,
3834
+ templateData: {
3835
+ fromTier: input.fromTier,
3836
+ toTier: input.toTier,
3837
+ newFeatures: input.newFeatures,
3838
+ effectiveDate: input.effectiveDate.toISOString()
3839
+ },
3840
+ sendAt: /* @__PURE__ */ new Date(),
3841
+ priority: "medium"
3842
+ };
3843
+ const emailJobId = await emailService.queueEmail(emailJobData);
3844
+ const scheduledAt = /* @__PURE__ */ new Date();
3845
+ process.stdout.write(`[TierUpgradeSaga] Scheduled upgrade confirmation email ${emailJobId} for user ${input.userId}`);
3846
+ return {
3847
+ emailJobId,
3848
+ scheduledAt
3849
+ };
3850
+ }
3851
+ __name(sendUpgradeConfirmationStep, "sendUpgradeConfirmationStep");
3852
+ async function compensateSendUpgradeConfirmation(_input, output, context) {
3853
+ if (!output?.emailJobId) {
3854
+ return;
3855
+ }
3856
+ if (output.emailJobId.startsWith("skipped_")) {
3857
+ process.stdout.write("[TierUpgradeSaga] Compensated: Email was skipped, no cancellation needed");
3858
+ return;
3859
+ }
3860
+ const emailService = context.emailService;
3861
+ if (!emailService) {
3862
+ return;
3863
+ }
3864
+ try {
3865
+ await emailService.cancelJob(output.emailJobId);
3866
+ process.stdout.write(`[TierUpgradeSaga] Compensated: Cancelled email job ${output.emailJobId}`);
3867
+ } catch {
3868
+ }
3869
+ }
3870
+ __name(compensateSendUpgradeConfirmation, "compensateSendUpgradeConfirmation");
3871
+ async function emitTierUpgradedStep(input, context) {
3872
+ const eventBus = context.eventBus;
3873
+ const eventId = `event_${nanoid()}`;
3874
+ const timestamp3 = /* @__PURE__ */ new Date();
3875
+ const sagaId = context.sagaId ?? `saga_${nanoid()}`;
3876
+ if (eventBus) {
3877
+ eventBus.emit("tier:upgraded", {
3878
+ userId: input.userId,
3879
+ fromTier: input.fromTier,
3880
+ toTier: input.toTier,
3881
+ subscriptionId: input.subscriptionId,
3882
+ effectiveDate: input.effectiveDate,
3883
+ sagaId
3884
+ });
3885
+ process.stdout.write(`[TierUpgradeSaga] Emitted tier_upgraded event ${eventId}: ${input.fromTier} \u2192 ${input.toTier}`);
3886
+ }
3887
+ return {
3888
+ eventId,
3889
+ timestamp: timestamp3
3890
+ };
3891
+ }
3892
+ __name(emitTierUpgradedStep, "emitTierUpgradedStep");
3893
+ function getTierUpgradeSaga() {
3894
+ return {
3895
+ ...TIER_UPGRADE_SAGA,
3896
+ steps: [
3897
+ {
3898
+ stepId: "update_subscription",
3899
+ stepName: "Update Subscription in Payment Provider",
3900
+ execute: updateSubscriptionStep,
3901
+ compensate: compensateUpdateSubscription,
3902
+ retryable: true,
3903
+ timeout: 3e4
3904
+ },
3905
+ {
3906
+ stepId: "update_user_tier",
3907
+ stepName: "Update User Tier in Database",
3908
+ execute: updateUserTierStep,
3909
+ compensate: compensateUpdateUserTier,
3910
+ retryable: true,
3911
+ timeout: 5e3
3912
+ },
3913
+ {
3914
+ stepId: "update_entitlements",
3915
+ stepName: "Update Entitlements with New Tier Features",
3916
+ execute: updateUpgradeEntitlementsStep,
3917
+ compensate: compensateUpdateUpgradeEntitlements,
3918
+ retryable: true,
3919
+ timeout: 5e3
3920
+ },
3921
+ {
3922
+ stepId: "send_confirmation",
3923
+ stepName: "Send Upgrade Confirmation Email",
3924
+ execute: sendUpgradeConfirmationStep,
3925
+ compensate: compensateSendUpgradeConfirmation,
3926
+ retryable: true,
3927
+ timeout: 1e4
3928
+ },
3929
+ {
3930
+ stepId: "emit_event",
3931
+ stepName: "Emit Tier Upgraded Event",
3932
+ execute: emitTierUpgradedStep,
3933
+ // No compensation needed for events (idempotent)
3934
+ retryable: false,
3935
+ timeout: 3e3
3936
+ }
3937
+ ]
3938
+ };
3939
+ }
3940
+ __name(getTierUpgradeSaga, "getTierUpgradeSaga");
3941
+ function createTierUpgradeSagaWithDeps(deps) {
3942
+ const sagaDef = getTierUpgradeSaga();
3943
+ const originalSteps = sagaDef.steps;
3944
+ return {
3945
+ ...sagaDef,
3946
+ steps: originalSteps.map((step) => {
3947
+ const enhanceContext = /* @__PURE__ */ __name((context) => ({
3948
+ ...context,
3949
+ emailService: deps.emailService,
3950
+ eventBus: deps.eventBus
3951
+ }), "enhanceContext");
3952
+ return {
3953
+ ...step,
3954
+ execute: /* @__PURE__ */ __name(async (input, context) => {
3955
+ return step.execute(input, enhanceContext(context));
3956
+ }, "execute"),
3957
+ // Also wrap compensate to ensure dependencies are available during rollback
3958
+ compensate: step.compensate ? async (input, output, context) => {
3959
+ return step.compensate?.(input, output, enhanceContext(context));
3960
+ } : void 0
3961
+ };
3962
+ })
3963
+ };
3964
+ }
3965
+ __name(createTierUpgradeSagaWithDeps, "createTierUpgradeSagaWithDeps");
3966
+
3967
+ export { AccountSchema, AiChatSchema, AttributionServiceImpl, EntitlementsServiceImpl, InvitationSchema, MCPService, MemberSchema, OrganizationSchema, OrganizationUpdateSchema, PasskeySchema, PurchaseInsertSchema, PurchaseSchema, PurchaseUpdateSchema, SagaOrchestratorImpl, SessionSchema, SnapshotStoreDb, TelemetrySinkDb, TelemetrySinkDbAdapter, UserSchema, UserUpdateSchema, VerificationSchema, anonymizeEmail, anonymizeUserData, anonymizeUserId, appendFalsePositivePatterns, calculateDecayedWeight, cleanupExpiredData, clearCapabilityCache, closeTestDb, config, countAllOrganizations, countAllUsers, createPurchase, createTestUser, createTierUpgradeSagaWithDeps, createUser, createUserAccount, databaseService, deletePurchaseBySubscriptionId, deleteUserApiKeys, deleteUserData, exportUserData, extensionLinkTokens, extensionSessions, findSimilarPatterns, generateOrganizationSlug, getAccountById, getBaseUrl, getCacheMetrics, getCapabilities, getCapabilityAuditHistory, getInvitationById, getMCPService, getOrganizationById, getOrganizationBySlug, getOrganizationMembership, getOrganizationWithPurchasesAndMembersCount, getOrganizations, getOrganizationsWithMembers, getPendingInvitationByEmail, getPurchaseById, getPurchaseBySubscriptionId, getPurchasesByOrganizationId, getPurchasesByUserId, getTestDb, getUserByEmail, getUserById, getUserPrivacyPreferences, getUsers, getVectorStats, getWorkspaceLinkById, getWorkspaceLinksByUserId, handleTierDowngrade, handleTierUpgrade, healthCheck, incrementDetectionsAnalyzed, insertPatternWithEmbedding, invalidateCapabilityCache, isPgvectorEnabled, linkWorkspace, logAnonymizedEvent, logCapabilityAudit, mergeSignalIntoPattern, recordFalsePositiveSignal, resetCacheMetrics, resetCapabilities, resolveTierByWorkspaceId, sagaPersistence, sanitizeForLogging, searchSimilarPatterns, shouldRetainData, signalToPattern, testInTransaction, truncateAllTables, unlinkAllWorkspacesForUser, unlinkWorkspace, updateCapabilities, updateOrganization, updatePatternEmbedding, updatePurchase, updateUser, updateWorkspaceTier };
3968
+ //# sourceMappingURL=chunk-ASGZ5B6C.js.map
3969
+ //# sourceMappingURL=chunk-ASGZ5B6C.js.map