guardrail-core 1.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 (189) hide show
  1. package/dist/__tests__/autopilot.test.d.ts +7 -0
  2. package/dist/__tests__/autopilot.test.d.ts.map +1 -0
  3. package/dist/__tests__/autopilot.test.js +156 -0
  4. package/dist/__tests__/tier-config.test.d.ts +9 -0
  5. package/dist/__tests__/tier-config.test.d.ts.map +1 -0
  6. package/dist/__tests__/tier-config.test.js +230 -0
  7. package/dist/__tests__/utils/hash-inline.test.d.ts +2 -0
  8. package/dist/__tests__/utils/hash-inline.test.d.ts.map +1 -0
  9. package/dist/__tests__/utils/hash-inline.test.js +62 -0
  10. package/dist/__tests__/utils/hash.test.d.ts +3 -0
  11. package/dist/__tests__/utils/hash.test.d.ts.map +1 -0
  12. package/dist/__tests__/utils/hash.test.js +95 -0
  13. package/dist/__tests__/utils/simple.test.d.ts +1 -0
  14. package/dist/__tests__/utils/simple.test.d.ts.map +1 -0
  15. package/dist/__tests__/utils/simple.test.js +10 -0
  16. package/dist/__tests__/utils/utils-simple.test.d.ts +1 -0
  17. package/dist/__tests__/utils/utils-simple.test.d.ts.map +1 -0
  18. package/dist/__tests__/utils/utils-simple.test.js +6 -0
  19. package/dist/__tests__/utils/utils.test.d.ts +15 -0
  20. package/dist/__tests__/utils/utils.test.d.ts.map +1 -0
  21. package/dist/__tests__/utils/utils.test.js +172 -0
  22. package/dist/autopilot/autopilot-runner.d.ts +33 -0
  23. package/dist/autopilot/autopilot-runner.d.ts.map +1 -0
  24. package/dist/autopilot/autopilot-runner.js +479 -0
  25. package/dist/autopilot/index.d.ts +6 -0
  26. package/dist/autopilot/index.d.ts.map +1 -0
  27. package/dist/autopilot/index.js +25 -0
  28. package/dist/autopilot/types.d.ts +102 -0
  29. package/dist/autopilot/types.d.ts.map +1 -0
  30. package/dist/autopilot/types.js +18 -0
  31. package/dist/cache/index.d.ts +7 -0
  32. package/dist/cache/index.d.ts.map +1 -0
  33. package/dist/cache/index.js +22 -0
  34. package/dist/cache/redis-cache.d.ts +145 -0
  35. package/dist/cache/redis-cache.d.ts.map +1 -0
  36. package/dist/cache/redis-cache.js +459 -0
  37. package/dist/ci/github-actions.d.ts +77 -0
  38. package/dist/ci/github-actions.d.ts.map +1 -0
  39. package/dist/ci/github-actions.js +277 -0
  40. package/dist/ci/index.d.ts +12 -0
  41. package/dist/ci/index.d.ts.map +1 -0
  42. package/dist/ci/index.js +27 -0
  43. package/dist/ci/pre-commit.d.ts +65 -0
  44. package/dist/ci/pre-commit.d.ts.map +1 -0
  45. package/dist/ci/pre-commit.js +286 -0
  46. package/dist/entitlements.d.ts +149 -0
  47. package/dist/entitlements.d.ts.map +1 -0
  48. package/dist/entitlements.js +464 -0
  49. package/dist/env.d.ts +113 -0
  50. package/dist/env.d.ts.map +1 -0
  51. package/dist/env.js +204 -0
  52. package/dist/fix-packs/__tests__/generate-fix-packs.test.d.ts +7 -0
  53. package/dist/fix-packs/__tests__/generate-fix-packs.test.d.ts.map +1 -0
  54. package/dist/fix-packs/__tests__/generate-fix-packs.test.js +250 -0
  55. package/dist/fix-packs/generate-fix-packs.d.ts +15 -0
  56. package/dist/fix-packs/generate-fix-packs.d.ts.map +1 -0
  57. package/dist/fix-packs/generate-fix-packs.js +505 -0
  58. package/dist/fix-packs/index.d.ts +8 -0
  59. package/dist/fix-packs/index.d.ts.map +1 -0
  60. package/dist/fix-packs/index.js +23 -0
  61. package/dist/fix-packs/types.d.ts +113 -0
  62. package/dist/fix-packs/types.d.ts.map +1 -0
  63. package/dist/fix-packs/types.js +71 -0
  64. package/dist/index.d.ts +13 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +28 -0
  67. package/dist/metrics/prometheus.d.ts +99 -0
  68. package/dist/metrics/prometheus.d.ts.map +1 -0
  69. package/dist/metrics/prometheus.js +306 -0
  70. package/dist/quota-ledger.d.ts +119 -0
  71. package/dist/quota-ledger.d.ts.map +1 -0
  72. package/dist/quota-ledger.js +462 -0
  73. package/dist/rbac/__tests__/permissions.test.d.ts +8 -0
  74. package/dist/rbac/__tests__/permissions.test.d.ts.map +1 -0
  75. package/dist/rbac/__tests__/permissions.test.js +350 -0
  76. package/dist/rbac/index.d.ts +9 -0
  77. package/dist/rbac/index.d.ts.map +1 -0
  78. package/dist/rbac/index.js +32 -0
  79. package/dist/rbac/permissions.d.ts +71 -0
  80. package/dist/rbac/permissions.d.ts.map +1 -0
  81. package/dist/rbac/permissions.js +247 -0
  82. package/dist/rbac/types.d.ts +69 -0
  83. package/dist/rbac/types.d.ts.map +1 -0
  84. package/dist/rbac/types.js +213 -0
  85. package/dist/tier-config.d.ts +203 -0
  86. package/dist/tier-config.d.ts.map +1 -0
  87. package/dist/tier-config.js +675 -0
  88. package/dist/types.d.ts +365 -0
  89. package/dist/types.d.ts.map +1 -0
  90. package/dist/types.js +5 -0
  91. package/dist/utils.d.ts +36 -0
  92. package/dist/utils.d.ts.map +1 -0
  93. package/dist/utils.js +127 -0
  94. package/dist/verified-autofix/__tests__/format-validator.test.d.ts +11 -0
  95. package/dist/verified-autofix/__tests__/format-validator.test.d.ts.map +1 -0
  96. package/dist/verified-autofix/__tests__/format-validator.test.js +285 -0
  97. package/dist/verified-autofix/__tests__/pipeline.test.d.ts +11 -0
  98. package/dist/verified-autofix/__tests__/pipeline.test.d.ts.map +1 -0
  99. package/dist/verified-autofix/__tests__/pipeline.test.js +389 -0
  100. package/dist/verified-autofix/__tests__/repo-fingerprint.test.d.ts +11 -0
  101. package/dist/verified-autofix/__tests__/repo-fingerprint.test.d.ts.map +1 -0
  102. package/dist/verified-autofix/__tests__/repo-fingerprint.test.js +236 -0
  103. package/dist/verified-autofix/__tests__/workspace.test.d.ts +11 -0
  104. package/dist/verified-autofix/__tests__/workspace.test.d.ts.map +1 -0
  105. package/dist/verified-autofix/__tests__/workspace.test.js +314 -0
  106. package/dist/verified-autofix/format-validator.d.ts +101 -0
  107. package/dist/verified-autofix/format-validator.d.ts.map +1 -0
  108. package/dist/verified-autofix/format-validator.js +446 -0
  109. package/dist/verified-autofix/index.d.ts +14 -0
  110. package/dist/verified-autofix/index.d.ts.map +1 -0
  111. package/dist/verified-autofix/index.js +39 -0
  112. package/dist/verified-autofix/pipeline.d.ts +68 -0
  113. package/dist/verified-autofix/pipeline.d.ts.map +1 -0
  114. package/dist/verified-autofix/pipeline.js +330 -0
  115. package/dist/verified-autofix/repo-fingerprint.d.ts +56 -0
  116. package/dist/verified-autofix/repo-fingerprint.d.ts.map +1 -0
  117. package/dist/verified-autofix/repo-fingerprint.js +396 -0
  118. package/dist/verified-autofix/workspace.d.ts +83 -0
  119. package/dist/verified-autofix/workspace.d.ts.map +1 -0
  120. package/dist/verified-autofix/workspace.js +454 -0
  121. package/dist/verified-autofix.d.ts +182 -0
  122. package/dist/verified-autofix.d.ts.map +1 -0
  123. package/dist/verified-autofix.js +1021 -0
  124. package/dist/visualization/dependency-graph.d.ts +79 -0
  125. package/dist/visualization/dependency-graph.d.ts.map +1 -0
  126. package/dist/visualization/dependency-graph.js +399 -0
  127. package/dist/visualization/index.d.ts +5 -0
  128. package/dist/visualization/index.d.ts.map +1 -0
  129. package/dist/visualization/index.js +20 -0
  130. package/package.json +29 -0
  131. package/src/__tests__/autopilot.test.ts +196 -0
  132. package/src/__tests__/tier-config.test.ts +289 -0
  133. package/src/__tests__/utils/hash-inline.test.ts +76 -0
  134. package/src/__tests__/utils/hash.test.ts +119 -0
  135. package/src/__tests__/utils/simple.test.ts +10 -0
  136. package/src/__tests__/utils/utils-simple.test.ts +5 -0
  137. package/src/__tests__/utils/utils.test.ts +203 -0
  138. package/src/autopilot/autopilot-runner.ts +503 -0
  139. package/src/autopilot/index.ts +6 -0
  140. package/src/autopilot/types.ts +119 -0
  141. package/src/cache/index.ts +7 -0
  142. package/src/cache/redis-cache.d.ts +155 -0
  143. package/src/cache/redis-cache.d.ts.map +1 -0
  144. package/src/cache/redis-cache.ts +517 -0
  145. package/src/ci/github-actions.ts +335 -0
  146. package/src/ci/index.ts +12 -0
  147. package/src/ci/pre-commit.ts +338 -0
  148. package/src/db/usage-schema.prisma +114 -0
  149. package/src/entitlements.ts +570 -0
  150. package/src/env.d.ts +68 -0
  151. package/src/env.d.ts.map +1 -0
  152. package/src/env.ts +247 -0
  153. package/src/fix-packs/__tests__/generate-fix-packs.test.ts +317 -0
  154. package/src/fix-packs/generate-fix-packs.ts +577 -0
  155. package/src/fix-packs/index.ts +8 -0
  156. package/src/fix-packs/types.ts +206 -0
  157. package/src/index.d.ts +7 -0
  158. package/src/index.d.ts.map +1 -0
  159. package/src/index.ts +12 -0
  160. package/src/metrics/prometheus.d.ts +104 -0
  161. package/src/metrics/prometheus.d.ts.map +1 -0
  162. package/src/metrics/prometheus.ts +446 -0
  163. package/src/quota-ledger.ts +548 -0
  164. package/src/rbac/__tests__/permissions.test.ts +446 -0
  165. package/src/rbac/index.ts +46 -0
  166. package/src/rbac/permissions.ts +301 -0
  167. package/src/rbac/types.ts +298 -0
  168. package/src/tier-config.json +157 -0
  169. package/src/tier-config.ts +815 -0
  170. package/src/types.d.ts +365 -0
  171. package/src/types.d.ts.map +1 -0
  172. package/src/types.ts +441 -0
  173. package/src/utils.d.ts +36 -0
  174. package/src/utils.d.ts.map +1 -0
  175. package/src/utils.ts +140 -0
  176. package/src/verified-autofix/__tests__/format-validator.test.ts +335 -0
  177. package/src/verified-autofix/__tests__/pipeline.test.ts +419 -0
  178. package/src/verified-autofix/__tests__/repo-fingerprint.test.ts +241 -0
  179. package/src/verified-autofix/__tests__/workspace.test.ts +373 -0
  180. package/src/verified-autofix/format-validator.ts +517 -0
  181. package/src/verified-autofix/index.ts +63 -0
  182. package/src/verified-autofix/pipeline.ts +403 -0
  183. package/src/verified-autofix/repo-fingerprint.ts +459 -0
  184. package/src/verified-autofix/workspace.ts +531 -0
  185. package/src/verified-autofix.ts +1187 -0
  186. package/src/visualization/dependency-graph.d.ts +85 -0
  187. package/src/visualization/dependency-graph.d.ts.map +1 -0
  188. package/src/visualization/dependency-graph.ts +495 -0
  189. package/src/visualization/index.ts +5 -0
@@ -0,0 +1,815 @@
1
+ /**
2
+ * Canonical Tier Configuration
3
+ *
4
+ * SINGLE SOURCE OF TRUTH for all tier definitions across:
5
+ * - Backend entitlements
6
+ * - Rate limiting
7
+ * - Stripe billing mapping
8
+ * - Landing page pricing
9
+ * - CLI entitlements
10
+ *
11
+ * DO NOT define tier configurations anywhere else in the codebase.
12
+ */
13
+
14
+ // ============================================================================
15
+ // TIER ENUM
16
+ // ============================================================================
17
+
18
+ export const TIERS = ['free', 'starter', 'pro', 'compliance', 'enterprise', 'unlimited'] as const;
19
+ export type Tier = typeof TIERS[number];
20
+
21
+ /** Tiers that can be purchased (excludes free and unlimited) */
22
+ export const PURCHASABLE_TIERS = ['starter', 'pro', 'compliance', 'enterprise'] as const;
23
+ export type PurchasableTier = typeof PURCHASABLE_TIERS[number];
24
+
25
+ /** Tier order for comparison (lower index = lower tier) */
26
+ export const TIER_ORDER: Tier[] = ['free', 'starter', 'pro', 'compliance', 'enterprise', 'unlimited'];
27
+
28
+ // ============================================================================
29
+ // FEATURE FLAGS
30
+ // ============================================================================
31
+
32
+ export const FEATURES = [
33
+ 'scan',
34
+ 'scan:full',
35
+ 'scan:security',
36
+ 'scan:compliance',
37
+ 'gate',
38
+ 'fix',
39
+ 'fix:auto',
40
+ 'ship',
41
+ 'reality',
42
+ 'reality:flows',
43
+ 'ai-agent',
44
+ 'ai-agent:goals',
45
+ 'autopilot',
46
+ 'context',
47
+ 'badge',
48
+ 'mcp',
49
+ 'compliance:soc2',
50
+ 'compliance:hipaa',
51
+ 'compliance:gdpr',
52
+ 'compliance:pci',
53
+ 'compliance:nist',
54
+ 'compliance:iso27001',
55
+ 'reports:html',
56
+ 'reports:pdf',
57
+ 'reports:sarif',
58
+ 'team:members',
59
+ 'team:admin',
60
+ 'api:access',
61
+ 'webhooks',
62
+ 'deploy-hooks',
63
+ ] as const;
64
+
65
+ export type Feature = typeof FEATURES[number];
66
+
67
+ // ============================================================================
68
+ // TIER CONFIGURATION INTERFACE
69
+ // ============================================================================
70
+
71
+ export interface TierLimits {
72
+ /** Scans per month (-1 = unlimited) */
73
+ scansPerMonth: number;
74
+ /** Reality Mode runs per month */
75
+ realityRunsPerMonth: number;
76
+ /** AI Agent runs per month */
77
+ aiAgentRunsPerMonth: number;
78
+ /** Number of projects */
79
+ projects: number;
80
+ /** Base team members included in tier */
81
+ teamMembers: number;
82
+ /** Compliance frameworks available */
83
+ complianceFrameworks: number;
84
+ }
85
+
86
+ // ============================================================================
87
+ // SEAT PRICING CONFIGURATION
88
+ // ============================================================================
89
+
90
+ export interface SeatPricing {
91
+ /** Price per additional seat per month */
92
+ monthlyPricePerSeat: number;
93
+ /** Price per additional seat per year */
94
+ annualPricePerSeat: number;
95
+ /** Maximum additional seats allowed (-1 = unlimited) */
96
+ maxAdditionalSeats: number;
97
+ /** Whether this tier supports additional seats */
98
+ supportsAdditionalSeats: boolean;
99
+ }
100
+
101
+ export const SEAT_PRICING: Record<Tier, SeatPricing> = {
102
+ free: {
103
+ monthlyPricePerSeat: 0,
104
+ annualPricePerSeat: 0,
105
+ maxAdditionalSeats: 0,
106
+ supportsAdditionalSeats: false,
107
+ },
108
+ starter: {
109
+ monthlyPricePerSeat: 0,
110
+ annualPricePerSeat: 0,
111
+ maxAdditionalSeats: 0,
112
+ supportsAdditionalSeats: false,
113
+ },
114
+ pro: {
115
+ monthlyPricePerSeat: 25,
116
+ annualPricePerSeat: 240, // 20% off: 25 * 12 * 0.8 = 240
117
+ maxAdditionalSeats: 45, // Base 5 + max 45 = 50 total
118
+ supportsAdditionalSeats: true,
119
+ },
120
+ compliance: {
121
+ monthlyPricePerSeat: 35,
122
+ annualPricePerSeat: 336, // 20% off: 35 * 12 * 0.8 = 336
123
+ maxAdditionalSeats: 90, // Base 10 + max 90 = 100 total
124
+ supportsAdditionalSeats: true,
125
+ },
126
+ enterprise: {
127
+ monthlyPricePerSeat: 45,
128
+ annualPricePerSeat: 432, // 20% off: 45 * 12 * 0.8 = 432
129
+ maxAdditionalSeats: -1, // Unlimited
130
+ supportsAdditionalSeats: true,
131
+ },
132
+ unlimited: {
133
+ monthlyPricePerSeat: 0,
134
+ annualPricePerSeat: 0,
135
+ maxAdditionalSeats: -1,
136
+ supportsAdditionalSeats: true,
137
+ },
138
+ };
139
+
140
+ export interface RateLimitConfig {
141
+ /** Requests per minute */
142
+ requestsPerMinute: number;
143
+ /** Burst limit (max requests in short window) */
144
+ burstLimit: number;
145
+ /** Rate limit window in milliseconds */
146
+ windowMs: number;
147
+ }
148
+
149
+ export interface TierConfig {
150
+ /** Tier identifier */
151
+ id: Tier;
152
+ /** Display name */
153
+ name: string;
154
+ /** Monthly price in USD */
155
+ price: number;
156
+ /** Annual price in USD (typically ~2 months free) */
157
+ annualPrice: number;
158
+ /** Short description for pricing page */
159
+ description: string;
160
+ /** Features included in this tier */
161
+ features: Feature[];
162
+ /** Usage limits */
163
+ limits: TierLimits;
164
+ /** API rate limiting configuration */
165
+ rateLimit: RateLimitConfig;
166
+ /** Upsell configuration */
167
+ upsell: {
168
+ message: string;
169
+ nextTier: Tier;
170
+ };
171
+ /** Stripe price IDs (set via environment variables) */
172
+ stripe?: {
173
+ monthlyPriceId?: string;
174
+ annualPriceId?: string;
175
+ /** Stripe price ID for additional seats (metered or per-unit) */
176
+ seatPriceId?: string;
177
+ seatAnnualPriceId?: string;
178
+ };
179
+ }
180
+
181
+ // ============================================================================
182
+ // CANONICAL TIER DEFINITIONS
183
+ // ============================================================================
184
+
185
+ export const TIER_CONFIG: Record<Tier, TierConfig> = {
186
+ free: {
187
+ id: 'free',
188
+ name: 'Free',
189
+ price: 0,
190
+ annualPrice: 0,
191
+ description: 'Get started',
192
+ features: [
193
+ 'scan',
194
+ 'gate',
195
+ 'ship',
196
+ 'context',
197
+ 'badge',
198
+ ],
199
+ limits: {
200
+ scansPerMonth: 10,
201
+ realityRunsPerMonth: 0,
202
+ aiAgentRunsPerMonth: 0,
203
+ projects: 1,
204
+ teamMembers: 1,
205
+ complianceFrameworks: 0,
206
+ },
207
+ rateLimit: {
208
+ requestsPerMinute: 100,
209
+ burstLimit: 150,
210
+ windowMs: 60 * 1000,
211
+ },
212
+ upsell: {
213
+ message: 'Upgrade to Starter for Reality Mode browser testing and 100 scans/month',
214
+ nextTier: 'starter',
215
+ },
216
+ },
217
+
218
+ starter: {
219
+ id: 'starter',
220
+ name: 'Starter',
221
+ price: 29,
222
+ annualPrice: 278, // 20% off: 29 * 12 * 0.8 = 278.40 → 278
223
+ description: 'For solo devs',
224
+ features: [
225
+ 'scan',
226
+ 'scan:full',
227
+ 'gate',
228
+ 'fix',
229
+ 'ship',
230
+ 'reality',
231
+ 'context',
232
+ 'badge',
233
+ 'reports:html',
234
+ ],
235
+ limits: {
236
+ scansPerMonth: 100,
237
+ realityRunsPerMonth: 20,
238
+ aiAgentRunsPerMonth: 0,
239
+ projects: 3,
240
+ teamMembers: 1,
241
+ complianceFrameworks: 0,
242
+ },
243
+ rateLimit: {
244
+ requestsPerMinute: 300,
245
+ burstLimit: 450,
246
+ windowMs: 60 * 1000,
247
+ },
248
+ upsell: {
249
+ message: 'Upgrade to Pro for AI Agent testing, auto-fix, and Autopilot protection',
250
+ nextTier: 'pro',
251
+ },
252
+ },
253
+
254
+ pro: {
255
+ id: 'pro',
256
+ name: 'Pro',
257
+ price: 99,
258
+ annualPrice: 950, // 20% off: 99 * 12 * 0.8 = 950.40 → 950
259
+ description: 'Full automation',
260
+ features: [
261
+ 'scan',
262
+ 'scan:full',
263
+ 'scan:security',
264
+ 'gate',
265
+ 'fix',
266
+ 'fix:auto',
267
+ 'ship',
268
+ 'reality',
269
+ 'reality:flows',
270
+ 'ai-agent',
271
+ 'ai-agent:goals',
272
+ 'autopilot',
273
+ 'context',
274
+ 'badge',
275
+ 'mcp',
276
+ 'reports:html',
277
+ 'reports:sarif',
278
+ 'api:access',
279
+ 'webhooks',
280
+ ],
281
+ limits: {
282
+ scansPerMonth: 500,
283
+ realityRunsPerMonth: 100,
284
+ aiAgentRunsPerMonth: 50,
285
+ projects: 10,
286
+ teamMembers: 5,
287
+ complianceFrameworks: 0,
288
+ },
289
+ rateLimit: {
290
+ requestsPerMinute: 1000,
291
+ burstLimit: 1500,
292
+ windowMs: 60 * 1000,
293
+ },
294
+ upsell: {
295
+ message: 'Upgrade to Compliance tier for SOC2, HIPAA, GDPR frameworks',
296
+ nextTier: 'compliance',
297
+ },
298
+ },
299
+
300
+ compliance: {
301
+ id: 'compliance',
302
+ name: 'Compliance',
303
+ price: 199,
304
+ annualPrice: 1910, // 20% off: 199 * 12 * 0.8 = 1910.40 → 1910
305
+ description: 'Enterprise ready',
306
+ features: [
307
+ 'scan',
308
+ 'scan:full',
309
+ 'scan:security',
310
+ 'scan:compliance',
311
+ 'gate',
312
+ 'fix',
313
+ 'fix:auto',
314
+ 'ship',
315
+ 'reality',
316
+ 'reality:flows',
317
+ 'ai-agent',
318
+ 'ai-agent:goals',
319
+ 'autopilot',
320
+ 'context',
321
+ 'badge',
322
+ 'mcp',
323
+ 'compliance:soc2',
324
+ 'compliance:hipaa',
325
+ 'compliance:gdpr',
326
+ 'compliance:pci',
327
+ 'compliance:nist',
328
+ 'compliance:iso27001',
329
+ 'reports:html',
330
+ 'reports:pdf',
331
+ 'reports:sarif',
332
+ 'api:access',
333
+ 'webhooks',
334
+ 'deploy-hooks',
335
+ ],
336
+ limits: {
337
+ scansPerMonth: 1000,
338
+ realityRunsPerMonth: 200,
339
+ aiAgentRunsPerMonth: 100,
340
+ projects: 25,
341
+ teamMembers: 10,
342
+ complianceFrameworks: 6,
343
+ },
344
+ rateLimit: {
345
+ requestsPerMinute: 2000,
346
+ burstLimit: 3000,
347
+ windowMs: 60 * 1000,
348
+ },
349
+ upsell: {
350
+ message: 'Contact sales for Enterprise with unlimited usage and dedicated support',
351
+ nextTier: 'enterprise',
352
+ },
353
+ },
354
+
355
+ enterprise: {
356
+ id: 'enterprise',
357
+ name: 'Enterprise',
358
+ price: 499,
359
+ annualPrice: 4790, // 20% off: 499 * 12 * 0.8 = 4790.40 → 4790
360
+ description: 'Custom solutions',
361
+ features: [
362
+ 'scan',
363
+ 'scan:full',
364
+ 'scan:security',
365
+ 'scan:compliance',
366
+ 'gate',
367
+ 'fix',
368
+ 'fix:auto',
369
+ 'ship',
370
+ 'reality',
371
+ 'reality:flows',
372
+ 'ai-agent',
373
+ 'ai-agent:goals',
374
+ 'autopilot',
375
+ 'context',
376
+ 'badge',
377
+ 'mcp',
378
+ 'compliance:soc2',
379
+ 'compliance:hipaa',
380
+ 'compliance:gdpr',
381
+ 'compliance:pci',
382
+ 'compliance:nist',
383
+ 'compliance:iso27001',
384
+ 'reports:html',
385
+ 'reports:pdf',
386
+ 'reports:sarif',
387
+ 'api:access',
388
+ 'webhooks',
389
+ 'deploy-hooks',
390
+ 'team:members',
391
+ 'team:admin',
392
+ ],
393
+ limits: {
394
+ scansPerMonth: 5000,
395
+ realityRunsPerMonth: 1000,
396
+ aiAgentRunsPerMonth: 500,
397
+ projects: 100,
398
+ teamMembers: 50,
399
+ complianceFrameworks: 6,
400
+ },
401
+ rateLimit: {
402
+ requestsPerMinute: 10000,
403
+ burstLimit: 15000,
404
+ windowMs: 60 * 1000,
405
+ },
406
+ upsell: {
407
+ message: 'You have our top tier! Contact support for custom requirements.',
408
+ nextTier: 'unlimited',
409
+ },
410
+ },
411
+
412
+ unlimited: {
413
+ id: 'unlimited',
414
+ name: 'Unlimited',
415
+ price: 0,
416
+ annualPrice: 0,
417
+ description: 'Internal/Special',
418
+ features: FEATURES as unknown as Feature[],
419
+ limits: {
420
+ scansPerMonth: -1, // Unlimited
421
+ realityRunsPerMonth: -1,
422
+ aiAgentRunsPerMonth: -1,
423
+ projects: -1,
424
+ teamMembers: -1,
425
+ complianceFrameworks: 6,
426
+ },
427
+ rateLimit: {
428
+ requestsPerMinute: 100000,
429
+ burstLimit: 150000,
430
+ windowMs: 60 * 1000,
431
+ },
432
+ upsell: {
433
+ message: 'You have unlimited access!',
434
+ nextTier: 'unlimited',
435
+ },
436
+ },
437
+ };
438
+
439
+ // ============================================================================
440
+ // HELPER FUNCTIONS
441
+ // ============================================================================
442
+
443
+ /**
444
+ * Check if a tier string is valid
445
+ */
446
+ export function isValidTier(tier: string): tier is Tier {
447
+ return TIERS.includes(tier as Tier);
448
+ }
449
+
450
+ /**
451
+ * Get tier config by tier name
452
+ */
453
+ export function getTierConfig(tier: Tier): TierConfig {
454
+ return TIER_CONFIG[tier];
455
+ }
456
+
457
+ /**
458
+ * Get all tier configs as array (useful for iteration)
459
+ */
460
+ export function getAllTierConfigs(): TierConfig[] {
461
+ return TIER_ORDER.map(tier => TIER_CONFIG[tier]);
462
+ }
463
+
464
+ /**
465
+ * Get purchasable tier configs (for pricing page)
466
+ */
467
+ export function getPurchasableTierConfigs(): TierConfig[] {
468
+ return PURCHASABLE_TIERS.map(tier => TIER_CONFIG[tier]);
469
+ }
470
+
471
+ /**
472
+ * Compare two tiers (-1 if a < b, 0 if equal, 1 if a > b)
473
+ */
474
+ export function compareTiers(a: Tier, b: Tier): number {
475
+ const indexA = TIER_ORDER.indexOf(a);
476
+ const indexB = TIER_ORDER.indexOf(b);
477
+ return indexA - indexB;
478
+ }
479
+
480
+ /**
481
+ * Check if tier A is higher than tier B
482
+ */
483
+ export function isTierHigher(a: Tier, b: Tier): boolean {
484
+ return compareTiers(a, b) > 0;
485
+ }
486
+
487
+ /**
488
+ * Find the minimum tier that has a specific feature
489
+ */
490
+ export function getMinimumTierForFeature(feature: Feature): Tier | null {
491
+ for (const tier of TIER_ORDER) {
492
+ if (TIER_CONFIG[tier].features.includes(feature)) {
493
+ return tier;
494
+ }
495
+ }
496
+ return null;
497
+ }
498
+
499
+ /**
500
+ * Check if a tier has a specific feature
501
+ */
502
+ export function tierHasFeature(tier: Tier, feature: Feature): boolean {
503
+ const config = TIER_CONFIG[tier];
504
+ // Unlimited tier has all features
505
+ if (tier === 'unlimited') return true;
506
+ return config.features.includes(feature);
507
+ }
508
+
509
+ /**
510
+ * Get limit value, handling -1 as Infinity
511
+ */
512
+ export function getEffectiveLimit(limit: number): number {
513
+ return limit === -1 ? Infinity : limit;
514
+ }
515
+
516
+ /**
517
+ * Format limit for display
518
+ */
519
+ export function formatLimit(limit: number): string {
520
+ return limit === -1 ? 'Unlimited' : limit.toLocaleString();
521
+ }
522
+
523
+ /**
524
+ * Get Stripe price ID for a tier (from environment)
525
+ */
526
+ export function getStripePriceId(tier: PurchasableTier, interval: 'month' | 'year'): string | undefined {
527
+ const envKey = interval === 'year'
528
+ ? `STRIPE_PRICE_${tier.toUpperCase()}_ANNUAL`
529
+ : `STRIPE_PRICE_${tier.toUpperCase()}_MONTHLY`;
530
+
531
+ return process.env[envKey] || process.env[`STRIPE_PRICE_ID_${tier.toUpperCase()}`];
532
+ }
533
+
534
+ /**
535
+ * Map Stripe price ID back to tier
536
+ */
537
+ export function getTierFromStripePriceId(priceId: string): Tier {
538
+ for (const tier of PURCHASABLE_TIERS) {
539
+ const monthlyId = getStripePriceId(tier, 'month');
540
+ const annualId = getStripePriceId(tier, 'year');
541
+ if (priceId === monthlyId || priceId === annualId) {
542
+ return tier;
543
+ }
544
+ }
545
+ return 'free';
546
+ }
547
+
548
+ // ============================================================================
549
+ // PRICING PAGE HELPERS
550
+ // ============================================================================
551
+
552
+ /**
553
+ * Get pricing tiers formatted for landing page display
554
+ */
555
+ export function getPricingPageTiers(): Array<{
556
+ id: Tier;
557
+ name: string;
558
+ price: number;
559
+ annual: number;
560
+ description: string;
561
+ popular: boolean;
562
+ features: string[];
563
+ }> {
564
+ return [
565
+ {
566
+ id: 'free',
567
+ name: TIER_CONFIG.free.name,
568
+ price: TIER_CONFIG.free.price,
569
+ annual: TIER_CONFIG.free.annualPrice,
570
+ description: TIER_CONFIG.free.description,
571
+ popular: false,
572
+ features: [
573
+ 'Static code analysis',
574
+ 'AI code validation',
575
+ 'Ship badge generator',
576
+ `${formatLimit(TIER_CONFIG.free.limits.scansPerMonth)} scans/month`,
577
+ ],
578
+ },
579
+ {
580
+ id: 'starter',
581
+ name: TIER_CONFIG.starter.name,
582
+ price: TIER_CONFIG.starter.price,
583
+ annual: TIER_CONFIG.starter.annualPrice,
584
+ description: TIER_CONFIG.starter.description,
585
+ popular: false,
586
+ features: [
587
+ 'Everything in Free, plus:',
588
+ 'Reality Mode browser testing',
589
+ 'CI/CD deploy blocking',
590
+ 'Mock detection',
591
+ `${formatLimit(TIER_CONFIG.starter.limits.scansPerMonth)} scans, ${formatLimit(TIER_CONFIG.starter.limits.realityRunsPerMonth)} Reality runs`,
592
+ ],
593
+ },
594
+ {
595
+ id: 'pro',
596
+ name: TIER_CONFIG.pro.name,
597
+ price: TIER_CONFIG.pro.price,
598
+ annual: TIER_CONFIG.pro.annualPrice,
599
+ description: TIER_CONFIG.pro.description,
600
+ popular: true,
601
+ features: [
602
+ 'Everything in Starter, plus:',
603
+ 'AI Agent autonomous testing',
604
+ 'Auto-fix with generated prompts',
605
+ 'Autopilot continuous protection',
606
+ 'MCP plugin for your IDE',
607
+ `${formatLimit(TIER_CONFIG.pro.limits.realityRunsPerMonth)} Reality, ${formatLimit(TIER_CONFIG.pro.limits.aiAgentRunsPerMonth)} AI Agent runs`,
608
+ ],
609
+ },
610
+ {
611
+ id: 'compliance',
612
+ name: TIER_CONFIG.compliance.name,
613
+ price: TIER_CONFIG.compliance.price,
614
+ annual: TIER_CONFIG.compliance.annualPrice,
615
+ description: TIER_CONFIG.compliance.description,
616
+ popular: false,
617
+ features: [
618
+ 'Everything in Pro, plus:',
619
+ 'SOC2, HIPAA, GDPR, PCI-DSS',
620
+ 'NIST and ISO 27001 frameworks',
621
+ 'Audit-ready PDF reports',
622
+ `${formatLimit(TIER_CONFIG.compliance.limits.realityRunsPerMonth)} Reality, ${formatLimit(TIER_CONFIG.compliance.limits.aiAgentRunsPerMonth)} AI Agent runs`,
623
+ ],
624
+ },
625
+ ];
626
+ }
627
+
628
+ // ============================================================================
629
+ // RATE LIMIT HELPERS
630
+ // ============================================================================
631
+
632
+ /**
633
+ * Get rate limit config for a tier (for rate-limiter middleware)
634
+ */
635
+ export function getRateLimitForTier(tier: Tier): RateLimitConfig {
636
+ return TIER_CONFIG[tier].rateLimit;
637
+ }
638
+
639
+ /**
640
+ * Get user tiers formatted for rate limiter
641
+ */
642
+ export function getRateLimiterTiers(): Record<Tier, {
643
+ name: string;
644
+ baseLimit: number;
645
+ burstLimit: number;
646
+ windowMs: number;
647
+ }> {
648
+ const result: Record<string, any> = {};
649
+ for (const tier of TIERS) {
650
+ const config = TIER_CONFIG[tier];
651
+ result[tier] = {
652
+ name: config.name,
653
+ baseLimit: config.rateLimit.requestsPerMinute,
654
+ burstLimit: config.rateLimit.burstLimit,
655
+ windowMs: config.rateLimit.windowMs,
656
+ };
657
+ }
658
+ return result as Record<Tier, any>;
659
+ }
660
+
661
+ // ============================================================================
662
+ // SEAT MANAGEMENT HELPERS
663
+ // ============================================================================
664
+
665
+ /**
666
+ * Get seat pricing for a tier
667
+ */
668
+ export function getSeatPricing(tier: Tier): SeatPricing {
669
+ return SEAT_PRICING[tier];
670
+ }
671
+
672
+ /**
673
+ * Calculate effective team seats (base + purchased extras)
674
+ */
675
+ export function calculateEffectiveSeats(tier: Tier, purchasedExtraSeats: number): number {
676
+ const baseSeats = TIER_CONFIG[tier].limits.teamMembers;
677
+ const seatConfig = SEAT_PRICING[tier];
678
+
679
+ // If tier doesn't support additional seats, return base only
680
+ if (!seatConfig.supportsAdditionalSeats) {
681
+ return baseSeats === -1 ? Infinity : baseSeats;
682
+ }
683
+
684
+ // Handle unlimited base seats
685
+ if (baseSeats === -1) {
686
+ return Infinity;
687
+ }
688
+
689
+ // Cap purchased seats at max allowed (if not unlimited)
690
+ let effectiveExtras = purchasedExtraSeats;
691
+ if (seatConfig.maxAdditionalSeats !== -1) {
692
+ effectiveExtras = Math.min(purchasedExtraSeats, seatConfig.maxAdditionalSeats);
693
+ }
694
+
695
+ return baseSeats + effectiveExtras;
696
+ }
697
+
698
+ /**
699
+ * Check if a member can be added given current seats and effective limit
700
+ */
701
+ export function canAddMember(
702
+ tier: Tier,
703
+ currentMemberCount: number,
704
+ purchasedExtraSeats: number
705
+ ): { allowed: boolean; reason?: string; effectiveSeats: number } {
706
+ const effectiveSeats = calculateEffectiveSeats(tier, purchasedExtraSeats);
707
+
708
+ if (effectiveSeats === Infinity) {
709
+ return { allowed: true, effectiveSeats };
710
+ }
711
+
712
+ if (currentMemberCount >= effectiveSeats) {
713
+ const seatConfig = SEAT_PRICING[tier];
714
+ const canPurchaseMore = seatConfig.supportsAdditionalSeats &&
715
+ (seatConfig.maxAdditionalSeats === -1 || purchasedExtraSeats < seatConfig.maxAdditionalSeats);
716
+
717
+ return {
718
+ allowed: false,
719
+ reason: canPurchaseMore
720
+ ? `Seat limit reached (${currentMemberCount}/${effectiveSeats}). Purchase additional seats at $${seatConfig.monthlyPricePerSeat}/seat/month.`
721
+ : `Seat limit reached (${currentMemberCount}/${effectiveSeats}). Upgrade to a higher tier for more seats.`,
722
+ effectiveSeats,
723
+ };
724
+ }
725
+
726
+ return { allowed: true, effectiveSeats };
727
+ }
728
+
729
+ /**
730
+ * Calculate cost for additional seats
731
+ */
732
+ export function calculateSeatCost(
733
+ tier: Tier,
734
+ additionalSeats: number,
735
+ billingInterval: 'month' | 'year'
736
+ ): { total: number; perSeat: number; supported: boolean } {
737
+ const seatConfig = SEAT_PRICING[tier];
738
+
739
+ if (!seatConfig.supportsAdditionalSeats) {
740
+ return { total: 0, perSeat: 0, supported: false };
741
+ }
742
+
743
+ const perSeat = billingInterval === 'year'
744
+ ? seatConfig.annualPricePerSeat
745
+ : seatConfig.monthlyPricePerSeat;
746
+
747
+ return {
748
+ total: perSeat * additionalSeats,
749
+ perSeat,
750
+ supported: true,
751
+ };
752
+ }
753
+
754
+ /**
755
+ * Get Stripe seat price ID for a tier
756
+ */
757
+ export function getStripeSeatPriceId(tier: Tier, interval: 'month' | 'year'): string | undefined {
758
+ const envKey = interval === 'year'
759
+ ? `STRIPE_SEAT_PRICE_${tier.toUpperCase()}_ANNUAL`
760
+ : `STRIPE_SEAT_PRICE_${tier.toUpperCase()}_MONTHLY`;
761
+
762
+ return process.env[envKey];
763
+ }
764
+
765
+ /**
766
+ * Format seat info for display
767
+ */
768
+ export function formatSeatInfo(tier: Tier): string {
769
+ const config = TIER_CONFIG[tier];
770
+ const seatConfig = SEAT_PRICING[tier];
771
+ const baseSeats = config.limits.teamMembers;
772
+
773
+ if (baseSeats === -1) {
774
+ return 'Unlimited team members';
775
+ }
776
+
777
+ if (!seatConfig.supportsAdditionalSeats) {
778
+ return `${baseSeats} team member${baseSeats !== 1 ? 's' : ''}`;
779
+ }
780
+
781
+ return `${baseSeats} seats included, +$${seatConfig.monthlyPricePerSeat}/seat/mo`;
782
+ }
783
+
784
+ /**
785
+ * Validate seat reduction (graceful handling)
786
+ * Returns info about whether reduction is safe or requires admin action
787
+ */
788
+ export function validateSeatReduction(
789
+ currentMemberCount: number,
790
+ _currentEffectiveSeats: number, // Kept for API compatibility, may be used for logging
791
+ newEffectiveSeats: number
792
+ ): {
793
+ safe: boolean;
794
+ requiresAction: boolean;
795
+ excessMembers: number;
796
+ message: string;
797
+ } {
798
+ if (newEffectiveSeats >= currentMemberCount) {
799
+ return {
800
+ safe: true,
801
+ requiresAction: false,
802
+ excessMembers: 0,
803
+ message: 'Seat reduction is safe.',
804
+ };
805
+ }
806
+
807
+ const excessMembers = currentMemberCount - newEffectiveSeats;
808
+
809
+ return {
810
+ safe: false,
811
+ requiresAction: true,
812
+ excessMembers,
813
+ message: `Cannot reduce seats: ${excessMembers} member(s) would exceed the new limit. Remove members before reducing seats.`,
814
+ };
815
+ }