@littlebearapps/platform-admin-sdk 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 (94) hide show
  1. package/README.md +112 -0
  2. package/dist/index.d.ts +16 -0
  3. package/dist/index.js +89 -0
  4. package/dist/prompts.d.ts +27 -0
  5. package/dist/prompts.js +80 -0
  6. package/dist/scaffold.d.ts +5 -0
  7. package/dist/scaffold.js +65 -0
  8. package/dist/templates.d.ts +16 -0
  9. package/dist/templates.js +131 -0
  10. package/package.json +46 -0
  11. package/templates/full/migrations/006_pattern_discovery.sql +199 -0
  12. package/templates/full/migrations/007_notifications_search.sql +127 -0
  13. package/templates/full/workers/lib/pattern-discovery/ai-prompt.ts +644 -0
  14. package/templates/full/workers/lib/pattern-discovery/clustering.ts +278 -0
  15. package/templates/full/workers/lib/pattern-discovery/shadow-evaluation.ts +603 -0
  16. package/templates/full/workers/lib/pattern-discovery/storage.ts +806 -0
  17. package/templates/full/workers/lib/pattern-discovery/types.ts +159 -0
  18. package/templates/full/workers/lib/pattern-discovery/validation.ts +278 -0
  19. package/templates/full/workers/pattern-discovery.ts +661 -0
  20. package/templates/full/workers/platform-alert-router.ts +1809 -0
  21. package/templates/full/workers/platform-notifications.ts +424 -0
  22. package/templates/full/workers/platform-search.ts +480 -0
  23. package/templates/full/workers/platform-settings.ts +436 -0
  24. package/templates/full/wrangler.alert-router.jsonc.hbs +34 -0
  25. package/templates/full/wrangler.notifications.jsonc.hbs +23 -0
  26. package/templates/full/wrangler.pattern-discovery.jsonc.hbs +33 -0
  27. package/templates/full/wrangler.search.jsonc.hbs +16 -0
  28. package/templates/full/wrangler.settings.jsonc.hbs +23 -0
  29. package/templates/shared/README.md.hbs +69 -0
  30. package/templates/shared/config/budgets.yaml.hbs +72 -0
  31. package/templates/shared/config/services.yaml.hbs +45 -0
  32. package/templates/shared/migrations/001_core_tables.sql +117 -0
  33. package/templates/shared/migrations/002_usage_warehouse.sql +830 -0
  34. package/templates/shared/migrations/003_feature_tracking.sql +250 -0
  35. package/templates/shared/migrations/004_settings_alerts.sql +452 -0
  36. package/templates/shared/migrations/seed.sql.hbs +4 -0
  37. package/templates/shared/package.json.hbs +21 -0
  38. package/templates/shared/scripts/sync-config.ts +242 -0
  39. package/templates/shared/tsconfig.json +12 -0
  40. package/templates/shared/workers/lib/analytics-engine.ts +357 -0
  41. package/templates/shared/workers/lib/billing.ts +293 -0
  42. package/templates/shared/workers/lib/circuit-breaker-middleware.ts +25 -0
  43. package/templates/shared/workers/lib/control.ts +292 -0
  44. package/templates/shared/workers/lib/economics.ts +368 -0
  45. package/templates/shared/workers/lib/metrics.ts +103 -0
  46. package/templates/shared/workers/lib/platform-settings.ts +407 -0
  47. package/templates/shared/workers/lib/shared/allowances.ts +333 -0
  48. package/templates/shared/workers/lib/shared/cloudflare.ts +1362 -0
  49. package/templates/shared/workers/lib/shared/types.ts +58 -0
  50. package/templates/shared/workers/lib/telemetry-sampling.ts +360 -0
  51. package/templates/shared/workers/lib/usage/collectors/example.ts +96 -0
  52. package/templates/shared/workers/lib/usage/collectors/index.ts +128 -0
  53. package/templates/shared/workers/lib/usage/handlers/audit.ts +306 -0
  54. package/templates/shared/workers/lib/usage/handlers/backfill.ts +845 -0
  55. package/templates/shared/workers/lib/usage/handlers/behavioral.ts +429 -0
  56. package/templates/shared/workers/lib/usage/handlers/data-queries.ts +507 -0
  57. package/templates/shared/workers/lib/usage/handlers/dlq-admin.ts +364 -0
  58. package/templates/shared/workers/lib/usage/handlers/health-trends.ts +222 -0
  59. package/templates/shared/workers/lib/usage/handlers/index.ts +35 -0
  60. package/templates/shared/workers/lib/usage/handlers/usage-admin.ts +421 -0
  61. package/templates/shared/workers/lib/usage/handlers/usage-features.ts +1262 -0
  62. package/templates/shared/workers/lib/usage/handlers/usage-metrics.ts +2420 -0
  63. package/templates/shared/workers/lib/usage/handlers/usage-settings.ts +610 -0
  64. package/templates/shared/workers/lib/usage/queue/budget-enforcement.ts +1032 -0
  65. package/templates/shared/workers/lib/usage/queue/cost-budget-enforcement.ts +128 -0
  66. package/templates/shared/workers/lib/usage/queue/cost-calculator.ts +77 -0
  67. package/templates/shared/workers/lib/usage/queue/dlq-handler.ts +161 -0
  68. package/templates/shared/workers/lib/usage/queue/index.ts +19 -0
  69. package/templates/shared/workers/lib/usage/queue/telemetry-processor.ts +790 -0
  70. package/templates/shared/workers/lib/usage/scheduled/anomaly-detection.ts +732 -0
  71. package/templates/shared/workers/lib/usage/scheduled/data-collection.ts +956 -0
  72. package/templates/shared/workers/lib/usage/scheduled/error-digest.ts +343 -0
  73. package/templates/shared/workers/lib/usage/scheduled/index.ts +18 -0
  74. package/templates/shared/workers/lib/usage/scheduled/rollups.ts +1561 -0
  75. package/templates/shared/workers/lib/usage/shared/constants.ts +362 -0
  76. package/templates/shared/workers/lib/usage/shared/index.ts +14 -0
  77. package/templates/shared/workers/lib/usage/shared/types.ts +1066 -0
  78. package/templates/shared/workers/lib/usage/shared/utils.ts +795 -0
  79. package/templates/shared/workers/platform-usage.ts +1915 -0
  80. package/templates/shared/wrangler.usage.jsonc.hbs +58 -0
  81. package/templates/standard/migrations/005_error_collection.sql +162 -0
  82. package/templates/standard/workers/error-collector.ts +2670 -0
  83. package/templates/standard/workers/lib/error-collector/capture.ts +213 -0
  84. package/templates/standard/workers/lib/error-collector/digest.ts +448 -0
  85. package/templates/standard/workers/lib/error-collector/email-health-alerts.ts +262 -0
  86. package/templates/standard/workers/lib/error-collector/fingerprint.ts +258 -0
  87. package/templates/standard/workers/lib/error-collector/gap-alerts.ts +293 -0
  88. package/templates/standard/workers/lib/error-collector/github.ts +329 -0
  89. package/templates/standard/workers/lib/error-collector/types.ts +262 -0
  90. package/templates/standard/workers/lib/sentinel/gap-detection.ts +734 -0
  91. package/templates/standard/workers/lib/shared/slack-alerts.ts +585 -0
  92. package/templates/standard/workers/platform-sentinel.ts +1744 -0
  93. package/templates/standard/wrangler.error-collector.jsonc.hbs +44 -0
  94. package/templates/standard/wrangler.sentinel.jsonc.hbs +45 -0
@@ -0,0 +1,407 @@
1
+ /**
2
+ * Platform Settings Module
3
+ *
4
+ * Centralised configuration management for platform-wide thresholds,
5
+ * circuit breakers, and resource limits.
6
+ *
7
+ * Caching Strategy:
8
+ * 1. Check KV (PLATFORM_CACHE) first - 1 hour TTL
9
+ * 2. If KV miss, query D1 (usage_settings table)
10
+ * 3. If D1 miss, return default value
11
+ * 4. On D1 hit, write to KV for next request
12
+ *
13
+ * Source of Truth:
14
+ * - Git: platform/config/budgets.yaml
15
+ * - Synced to D1 via scripts/sync-config.ts
16
+ * - Cached in KV for runtime performance
17
+ */
18
+
19
+ import type { KVNamespace, D1Database } from '@cloudflare/workers-types';
20
+
21
+ // =============================================================================
22
+ // TYPES
23
+ // =============================================================================
24
+
25
+ /**
26
+ * All platform settings from D1 usage_settings table.
27
+ * Centralised configuration for thresholds, circuit breakers, and sampling.
28
+ */
29
+ export interface PlatformSettings {
30
+ // Budget thresholds (USD)
31
+ budgetSoftLimit: number;
32
+ budgetWarningThreshold: number;
33
+ budgetCriticalThreshold: number;
34
+
35
+ // Alert/utilization thresholds (percentage)
36
+ alertWarningPct: number;
37
+ alertCriticalPct: number;
38
+ utilizationWarningPct: number;
39
+ utilizationCriticalPct: number;
40
+
41
+ // Sampling thresholds (D1 usage ratio)
42
+ samplingFullThreshold: number;
43
+ samplingHalfThreshold: number;
44
+ samplingQuarterThreshold: number;
45
+
46
+ // Circuit breaker defaults
47
+ cbAutoResetSeconds: number;
48
+ cbCooldownSeconds: number;
49
+ cbMaxConsecutiveTrips: number;
50
+
51
+ // Error rate settings
52
+ errorRateThreshold: number;
53
+ errorRateWindowMinutes: number;
54
+ errorRateMinRequests: number;
55
+
56
+ // Resource limits (daily)
57
+ d1WriteLimit: number;
58
+ doGbSecondsDailyLimit: number;
59
+ }
60
+
61
+ /**
62
+ * Environment bindings required for settings functions.
63
+ */
64
+ export interface SettingsEnv {
65
+ PLATFORM_CACHE: KVNamespace;
66
+ PLATFORM_DB: D1Database;
67
+ }
68
+
69
+ // =============================================================================
70
+ // CONSTANTS
71
+ // =============================================================================
72
+
73
+ /**
74
+ * Default platform settings (fallback if D1/KV query fails).
75
+ * These MUST match the values in budgets.yaml defaults section.
76
+ */
77
+ export const DEFAULT_PLATFORM_SETTINGS: PlatformSettings = {
78
+ // Budget thresholds (USD)
79
+ budgetSoftLimit: 25,
80
+ budgetWarningThreshold: 20,
81
+ budgetCriticalThreshold: 50,
82
+
83
+ // Alert/utilization thresholds (percentage) - from budgets.yaml defaults.thresholds
84
+ alertWarningPct: 70,
85
+ alertCriticalPct: 90,
86
+ utilizationWarningPct: 70,
87
+ utilizationCriticalPct: 90,
88
+
89
+ // Sampling thresholds (D1 usage ratio)
90
+ samplingFullThreshold: 0.6,
91
+ samplingHalfThreshold: 0.8,
92
+ samplingQuarterThreshold: 0.9,
93
+
94
+ // Circuit breaker defaults - from budgets.yaml defaults.circuit_breaker
95
+ cbAutoResetSeconds: 3600,
96
+ cbCooldownSeconds: 300,
97
+ cbMaxConsecutiveTrips: 3,
98
+
99
+ // Error rate settings - from budgets.yaml defaults.error_budget
100
+ errorRateThreshold: 0.1,
101
+ errorRateWindowMinutes: 15,
102
+ errorRateMinRequests: 50,
103
+
104
+ // Resource limits (daily)
105
+ d1WriteLimit: 1_000_000, // 1M writes per 24h (adaptive sampling trigger)
106
+ doGbSecondsDailyLimit: 200_000, // 200K GB-seconds per 24h (~$2.50/day)
107
+ };
108
+
109
+ /**
110
+ * Mapping from D1 setting_key to PlatformSettings property.
111
+ */
112
+ export const SETTING_KEY_MAP: Record<string, keyof PlatformSettings> = {
113
+ budget_soft_limit: 'budgetSoftLimit',
114
+ budget_warning_threshold: 'budgetWarningThreshold',
115
+ budget_critical_threshold: 'budgetCriticalThreshold',
116
+ alert_warning_pct: 'alertWarningPct',
117
+ alert_critical_pct: 'alertCriticalPct',
118
+ utilization_warning_pct: 'utilizationWarningPct',
119
+ utilization_critical_pct: 'utilizationCriticalPct',
120
+ sampling_full_threshold: 'samplingFullThreshold',
121
+ sampling_half_threshold: 'samplingHalfThreshold',
122
+ sampling_quarter_threshold: 'samplingQuarterThreshold',
123
+ cb_auto_reset_seconds: 'cbAutoResetSeconds',
124
+ cb_cooldown_seconds: 'cbCooldownSeconds',
125
+ cb_max_consecutive_trips: 'cbMaxConsecutiveTrips',
126
+ error_rate_threshold: 'errorRateThreshold',
127
+ error_rate_window_minutes: 'errorRateWindowMinutes',
128
+ error_rate_min_requests: 'errorRateMinRequests',
129
+ d1_write_limit: 'd1WriteLimit',
130
+ do_gb_seconds_daily_limit: 'doGbSecondsDailyLimit',
131
+ };
132
+
133
+ /**
134
+ * Reverse mapping from PlatformSettings property to D1 setting_key.
135
+ */
136
+ export const PROPERTY_TO_KEY_MAP: Record<keyof PlatformSettings, string> = Object.fromEntries(
137
+ Object.entries(SETTING_KEY_MAP).map(([k, v]) => [v, k])
138
+ ) as Record<keyof PlatformSettings, string>;
139
+
140
+ /**
141
+ * KV key prefix for settings cache.
142
+ */
143
+ const KV_SETTINGS_PREFIX = 'CONFIG:SETTINGS:';
144
+
145
+ /**
146
+ * KV key for the full settings object cache.
147
+ */
148
+ const KV_ALL_SETTINGS_KEY = 'CONFIG:SETTINGS:ALL';
149
+
150
+ /**
151
+ * KV cache TTL in seconds (1 hour).
152
+ */
153
+ const KV_CACHE_TTL_SECONDS = 3600;
154
+
155
+ // =============================================================================
156
+ // SINGLE SETTING GETTER
157
+ // =============================================================================
158
+
159
+ /**
160
+ * Get a single platform setting with KV-first, D1-fallback caching.
161
+ *
162
+ * Lookup order:
163
+ * 1. KV cache (CONFIG:SETTINGS:{key})
164
+ * 2. D1 usage_settings table
165
+ * 3. Default value
166
+ *
167
+ * On D1 hit, value is cached to KV with 1-hour TTL.
168
+ *
169
+ * @param env - Environment with PLATFORM_CACHE and PLATFORM_DB
170
+ * @param key - Setting key (e.g., 'budget_soft_limit')
171
+ * @param defaultValue - Fallback if not found in KV or D1
172
+ * @returns The setting value, converted to the appropriate type
173
+ */
174
+ export async function getSetting<T extends string | number | boolean>(
175
+ env: SettingsEnv,
176
+ key: string,
177
+ defaultValue: T
178
+ ): Promise<T> {
179
+ const kvKey = `${KV_SETTINGS_PREFIX}${key}`;
180
+
181
+ try {
182
+ // 1. Check KV cache first
183
+ const cached = await env.PLATFORM_CACHE.get(kvKey);
184
+ if (cached !== null) {
185
+ return parseValue(cached, defaultValue);
186
+ }
187
+
188
+ // 2. Query D1
189
+ const result = await env.PLATFORM_DB.prepare(
190
+ `SELECT setting_value FROM usage_settings WHERE project = 'all' AND setting_key = ?`
191
+ )
192
+ .bind(key)
193
+ .first<{ setting_value: string }>();
194
+
195
+ if (result?.setting_value !== undefined) {
196
+ // 3. Cache to KV for next time
197
+ await env.PLATFORM_CACHE.put(kvKey, result.setting_value, {
198
+ expirationTtl: KV_CACHE_TTL_SECONDS,
199
+ });
200
+ return parseValue(result.setting_value, defaultValue);
201
+ }
202
+
203
+ // 4. Return default
204
+ return defaultValue;
205
+ } catch {
206
+ // On error, return default silently (logging handled by caller)
207
+ return defaultValue;
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Parse a string value to the appropriate type based on the default value type.
213
+ */
214
+ function parseValue<T extends string | number | boolean>(value: string, defaultValue: T): T {
215
+ if (typeof defaultValue === 'number') {
216
+ const parsed = parseFloat(value);
217
+ return (isNaN(parsed) ? defaultValue : parsed) as T;
218
+ }
219
+ if (typeof defaultValue === 'boolean') {
220
+ return (value === 'true' || value === '1') as unknown as T;
221
+ }
222
+ return value as T;
223
+ }
224
+
225
+ // =============================================================================
226
+ // FULL SETTINGS GETTER
227
+ // =============================================================================
228
+
229
+ /**
230
+ * Get all platform settings with KV-first, D1-fallback caching.
231
+ *
232
+ * This fetches all settings in a single query rather than N+1 queries.
233
+ * Use this when you need multiple settings in one operation.
234
+ *
235
+ * @param env - Environment with PLATFORM_CACHE and PLATFORM_DB
236
+ * @returns Full PlatformSettings object
237
+ */
238
+ export async function getPlatformSettings(env: SettingsEnv): Promise<PlatformSettings> {
239
+ try {
240
+ // 1. Check KV cache for full settings object
241
+ const cached = await env.PLATFORM_CACHE.get(KV_ALL_SETTINGS_KEY);
242
+ // Empty string means cache was invalidated by sync script - skip to D1
243
+ if (cached !== null && cached !== '') {
244
+ try {
245
+ const parsed = JSON.parse(cached) as Partial<PlatformSettings>;
246
+ // Merge with defaults to handle any missing keys
247
+ return { ...DEFAULT_PLATFORM_SETTINGS, ...parsed };
248
+ } catch {
249
+ // Invalid JSON, fall through to D1
250
+ }
251
+ }
252
+
253
+ // 2. Query all settings from D1
254
+ const result = await env.PLATFORM_DB.prepare(
255
+ `SELECT setting_key, setting_value FROM usage_settings WHERE project = 'all'`
256
+ ).all<{ setting_key: string; setting_value: string }>();
257
+
258
+ const settings = { ...DEFAULT_PLATFORM_SETTINGS };
259
+
260
+ for (const row of result.results ?? []) {
261
+ const prop = SETTING_KEY_MAP[row.setting_key];
262
+ if (prop) {
263
+ const value = parseFloat(row.setting_value);
264
+ if (!isNaN(value)) {
265
+ (settings as Record<string, number>)[prop] = value;
266
+ }
267
+ }
268
+ }
269
+
270
+ // 3. Cache full settings object to KV
271
+ await env.PLATFORM_CACHE.put(KV_ALL_SETTINGS_KEY, JSON.stringify(settings), {
272
+ expirationTtl: KV_CACHE_TTL_SECONDS,
273
+ });
274
+
275
+ return settings;
276
+ } catch {
277
+ // On error, return defaults
278
+ return DEFAULT_PLATFORM_SETTINGS;
279
+ }
280
+ }
281
+
282
+ // =============================================================================
283
+ // PROJECT-SPECIFIC SETTINGS
284
+ // =============================================================================
285
+
286
+ /**
287
+ * Get a project-specific setting, falling back to global ('all') if not found.
288
+ *
289
+ * @param env - Environment with PLATFORM_CACHE and PLATFORM_DB
290
+ * @param project - Project identifier
291
+ * @param key - Setting key (e.g., 'do_gb_seconds_daily_limit')
292
+ * @param defaultValue - Fallback if not found
293
+ * @returns The setting value
294
+ */
295
+ export async function getProjectSetting<T extends string | number | boolean>(
296
+ env: SettingsEnv,
297
+ project: string,
298
+ key: string,
299
+ defaultValue: T
300
+ ): Promise<T> {
301
+ const kvKey = `${KV_SETTINGS_PREFIX}${project}:${key}`;
302
+
303
+ try {
304
+ // 1. Check KV cache for project-specific value
305
+ const cached = await env.PLATFORM_CACHE.get(kvKey);
306
+ if (cached !== null) {
307
+ return parseValue(cached, defaultValue);
308
+ }
309
+
310
+ // 2. Query D1 for project-specific value
311
+ const projectResult = await env.PLATFORM_DB.prepare(
312
+ `SELECT setting_value FROM usage_settings WHERE project = ? AND setting_key = ?`
313
+ )
314
+ .bind(project, key)
315
+ .first<{ setting_value: string }>();
316
+
317
+ if (projectResult?.setting_value !== undefined) {
318
+ await env.PLATFORM_CACHE.put(kvKey, projectResult.setting_value, {
319
+ expirationTtl: KV_CACHE_TTL_SECONDS,
320
+ });
321
+ return parseValue(projectResult.setting_value, defaultValue);
322
+ }
323
+
324
+ // 3. Fallback to global setting
325
+ return getSetting(env, key, defaultValue);
326
+ } catch {
327
+ // On error, try global setting
328
+ return getSetting(env, key, defaultValue);
329
+ }
330
+ }
331
+
332
+ // =============================================================================
333
+ // CACHE INVALIDATION
334
+ // =============================================================================
335
+
336
+ /**
337
+ * Invalidate settings cache for a specific key.
338
+ * Call this after updating a setting in D1.
339
+ */
340
+ export async function invalidateSettingCache(env: SettingsEnv, key: string): Promise<void> {
341
+ await Promise.all([
342
+ env.PLATFORM_CACHE.delete(`${KV_SETTINGS_PREFIX}${key}`),
343
+ env.PLATFORM_CACHE.delete(KV_ALL_SETTINGS_KEY),
344
+ ]);
345
+ }
346
+
347
+ /**
348
+ * Invalidate all settings cache.
349
+ * Call this after bulk updates to usage_settings.
350
+ */
351
+ export async function invalidateAllSettingsCache(env: SettingsEnv): Promise<void> {
352
+ // We can't easily list and delete all CONFIG:SETTINGS:* keys,
353
+ // so we rely on TTL expiration for individual keys.
354
+ // The full settings cache is deleted to force a refresh.
355
+ await env.PLATFORM_CACHE.delete(KV_ALL_SETTINGS_KEY);
356
+ }
357
+
358
+ // =============================================================================
359
+ // UTILITY FUNCTIONS
360
+ // =============================================================================
361
+
362
+ /**
363
+ * Get utilization status based on percentage.
364
+ * Uses thresholds from settings or defaults.
365
+ */
366
+ export function getUtilizationStatus(
367
+ pct: number,
368
+ settings?: { warningPct: number; criticalPct: number }
369
+ ): 'green' | 'yellow' | 'red' {
370
+ const warning = settings?.warningPct ?? DEFAULT_PLATFORM_SETTINGS.utilizationWarningPct;
371
+ const critical = settings?.criticalPct ?? DEFAULT_PLATFORM_SETTINGS.utilizationCriticalPct;
372
+ if (pct < warning) return 'green';
373
+ if (pct < critical) return 'yellow';
374
+ return 'red';
375
+ }
376
+
377
+ /**
378
+ * Expected setting keys that should exist in D1.
379
+ * Used by /usage/settings/verify endpoint.
380
+ */
381
+ export const EXPECTED_SETTINGS_KEYS = [
382
+ // Budget thresholds (USD)
383
+ 'budget_soft_limit',
384
+ 'budget_warning_threshold',
385
+ 'budget_critical_threshold',
386
+ // Alert thresholds (percentage)
387
+ 'alert_warning_pct',
388
+ 'alert_critical_pct',
389
+ // Utilization thresholds (percentage)
390
+ 'utilization_warning_pct',
391
+ 'utilization_critical_pct',
392
+ // Sampling thresholds (D1 usage ratio)
393
+ 'sampling_full_threshold',
394
+ 'sampling_half_threshold',
395
+ 'sampling_quarter_threshold',
396
+ // Circuit breaker defaults
397
+ 'cb_auto_reset_seconds',
398
+ 'cb_cooldown_seconds',
399
+ 'cb_max_consecutive_trips',
400
+ // Error rate settings
401
+ 'error_rate_threshold',
402
+ 'error_rate_window_minutes',
403
+ 'error_rate_min_requests',
404
+ // Resource limits
405
+ 'd1_write_limit',
406
+ 'do_gb_seconds_daily_limit',
407
+ ];