@snapback/cli 1.6.0 → 3.0.0

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