@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,429 @@
1
+ /**
2
+ * Behavioral Analysis Handlers for platform-usage
3
+ *
4
+ * Provides API endpoints for accessing file hotspots and SDK regression data.
5
+ * Part of the Dashboard Enhancements initiative.
6
+ *
7
+ * Endpoints:
8
+ * - GET /usage/audit/behavioral - Combined hotspots + regressions summary
9
+ * - GET /usage/audit/behavioral/hotspots - File hotspots with risk scoring
10
+ * - GET /usage/audit/behavioral/regressions - SDK regressions with acknowledgment status
11
+ * - POST /usage/audit/behavioral/regressions/:id/acknowledge - Mark regression as acknowledged
12
+ *
13
+ * @module workers/lib/usage/handlers/behavioral
14
+ * @created 2026-01-30
15
+ */
16
+
17
+ import type { D1Database } from '@cloudflare/workers-types';
18
+
19
+ /**
20
+ * Environment bindings for behavioral handlers
21
+ */
22
+ export interface BehavioralHandlerEnv {
23
+ PLATFORM_DB: D1Database;
24
+ }
25
+
26
+ /**
27
+ * File hotspot data from D1
28
+ */
29
+ interface FileHotspot {
30
+ id: number;
31
+ project: string;
32
+ file_path: string;
33
+ change_count: number;
34
+ last_changed: string | null;
35
+ authors: string | null;
36
+ has_sdk_patterns: boolean;
37
+ sdk_patterns_found: string | null;
38
+ hotspot_score: number;
39
+ audit_date: string;
40
+ }
41
+
42
+ /**
43
+ * SDK regression data from D1
44
+ */
45
+ interface SdkRegression {
46
+ id: number;
47
+ project: string;
48
+ commit_sha: string;
49
+ commit_message: string | null;
50
+ commit_author: string | null;
51
+ commit_date: string | null;
52
+ file_path: string;
53
+ regression_type: string;
54
+ description: string | null;
55
+ severity: string;
56
+ acknowledged: boolean;
57
+ acknowledged_at: string | null;
58
+ acknowledged_by: string | null;
59
+ audit_date: string;
60
+ }
61
+
62
+ /**
63
+ * Handle GET /usage/audit/behavioral - Get combined behavioral analysis summary
64
+ */
65
+ export async function handleGetBehavioral(
66
+ request: Request,
67
+ env: BehavioralHandlerEnv
68
+ ): Promise<Response> {
69
+ const url = new URL(request.url);
70
+ const project = url.searchParams.get('project');
71
+
72
+ try {
73
+ // Get latest audit date for hotspots
74
+ const latestHotspotResult = await env.PLATFORM_DB.prepare(
75
+ `SELECT MAX(audit_date) as latest FROM audit_file_hotspots`
76
+ ).first<{ latest: string | null }>();
77
+
78
+ // Get latest audit date for regressions
79
+ const latestRegressionResult = await env.PLATFORM_DB.prepare(
80
+ `SELECT MAX(audit_date) as latest FROM audit_sdk_regressions`
81
+ ).first<{ latest: string | null }>();
82
+
83
+ // Build hotspots query
84
+ let hotspotsQuery = `
85
+ SELECT
86
+ id, project, file_path, change_count, last_changed, authors,
87
+ has_sdk_patterns, sdk_patterns_found, hotspot_score, audit_date
88
+ FROM audit_file_hotspots
89
+ WHERE audit_date = ?
90
+ `;
91
+ const hotspotsBindings: (string | number)[] = [latestHotspotResult?.latest ?? ''];
92
+
93
+ if (project) {
94
+ hotspotsQuery += ' AND project = ?';
95
+ hotspotsBindings.push(project);
96
+ }
97
+
98
+ hotspotsQuery += ' ORDER BY hotspot_score DESC LIMIT 20';
99
+
100
+ // Build regressions query
101
+ let regressionsQuery = `
102
+ SELECT
103
+ id, project, commit_sha, commit_message, commit_author, commit_date,
104
+ file_path, regression_type, description, severity, acknowledged,
105
+ acknowledged_at, acknowledged_by, audit_date
106
+ FROM audit_sdk_regressions
107
+ WHERE acknowledged = FALSE
108
+ `;
109
+ const regressionsBindings: (string | number)[] = [];
110
+
111
+ if (project) {
112
+ regressionsQuery += ' AND project = ?';
113
+ regressionsBindings.push(project);
114
+ }
115
+
116
+ regressionsQuery += ' ORDER BY CASE severity WHEN \'critical\' THEN 1 WHEN \'high\' THEN 2 WHEN \'medium\' THEN 3 ELSE 4 END, commit_date DESC LIMIT 20';
117
+
118
+ // Execute queries in parallel
119
+ const [hotspotsResult, regressionsResult] = await Promise.all([
120
+ latestHotspotResult?.latest
121
+ ? env.PLATFORM_DB.prepare(hotspotsQuery).bind(...hotspotsBindings).all<FileHotspot>()
122
+ : Promise.resolve({ results: [] as FileHotspot[] }),
123
+ env.PLATFORM_DB.prepare(regressionsQuery).bind(...regressionsBindings).all<SdkRegression>(),
124
+ ]);
125
+
126
+ // Build summary
127
+ const hotspots = hotspotsResult.results ?? [];
128
+ const regressions = regressionsResult.results ?? [];
129
+
130
+ const summary = {
131
+ hotspotsCount: hotspots.length,
132
+ highRiskHotspots: hotspots.filter((h) => h.hotspot_score >= 10).length,
133
+ regressionsCount: regressions.length,
134
+ criticalRegressions: regressions.filter((r) => r.severity === 'critical').length,
135
+ highRegressions: regressions.filter((r) => r.severity === 'high').length,
136
+ };
137
+
138
+ return Response.json({
139
+ success: true,
140
+ data: {
141
+ summary,
142
+ hotspots: hotspots.map(formatHotspot),
143
+ regressions: regressions.map(formatRegression),
144
+ },
145
+ auditDates: {
146
+ hotspots: latestHotspotResult?.latest ?? null,
147
+ regressions: latestRegressionResult?.latest ?? null,
148
+ },
149
+ timestamp: new Date().toISOString(),
150
+ });
151
+ } catch (error) {
152
+ const message = error instanceof Error ? error.message : 'Unknown error';
153
+ return Response.json(
154
+ { success: false, error: 'Failed to get behavioral analysis', message },
155
+ { status: 500 }
156
+ );
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Handle GET /usage/audit/behavioral/hotspots - Get file hotspots with risk scoring
162
+ */
163
+ export async function handleGetHotspots(
164
+ request: Request,
165
+ env: BehavioralHandlerEnv
166
+ ): Promise<Response> {
167
+ const url = new URL(request.url);
168
+ const project = url.searchParams.get('project');
169
+ const limit = Math.min(parseInt(url.searchParams.get('limit') || '50', 10), 100);
170
+ const minScore = parseInt(url.searchParams.get('min_score') || '0', 10);
171
+
172
+ try {
173
+ // Get latest audit date
174
+ const latestResult = await env.PLATFORM_DB.prepare(
175
+ `SELECT MAX(audit_date) as latest FROM audit_file_hotspots`
176
+ ).first<{ latest: string | null }>();
177
+
178
+ if (!latestResult?.latest) {
179
+ return Response.json({
180
+ success: true,
181
+ data: {
182
+ auditDate: null,
183
+ summary: { total: 0, highRisk: 0, withoutSdk: 0 },
184
+ hotspots: [],
185
+ },
186
+ message: 'No behavioral analysis data available. Run platform-auditor to generate data.',
187
+ timestamp: new Date().toISOString(),
188
+ });
189
+ }
190
+
191
+ // Build query
192
+ let query = `
193
+ SELECT
194
+ id, project, file_path, change_count, last_changed, authors,
195
+ has_sdk_patterns, sdk_patterns_found, hotspot_score, audit_date
196
+ FROM audit_file_hotspots
197
+ WHERE audit_date = ? AND hotspot_score >= ?
198
+ `;
199
+ const bindings: (string | number)[] = [latestResult.latest, minScore];
200
+
201
+ if (project) {
202
+ query += ' AND project = ?';
203
+ bindings.push(project);
204
+ }
205
+
206
+ query += ' ORDER BY hotspot_score DESC LIMIT ?';
207
+ bindings.push(limit);
208
+
209
+ const result = await env.PLATFORM_DB.prepare(query).bind(...bindings).all<FileHotspot>();
210
+
211
+ const hotspots = result.results ?? [];
212
+
213
+ // Build summary
214
+ const summary = {
215
+ total: hotspots.length,
216
+ highRisk: hotspots.filter((h) => h.hotspot_score >= 10).length,
217
+ withoutSdk: hotspots.filter((h) => !h.has_sdk_patterns).length,
218
+ };
219
+
220
+ return Response.json({
221
+ success: true,
222
+ data: {
223
+ auditDate: latestResult.latest,
224
+ summary,
225
+ hotspots: hotspots.map(formatHotspot),
226
+ },
227
+ timestamp: new Date().toISOString(),
228
+ });
229
+ } catch (error) {
230
+ const message = error instanceof Error ? error.message : 'Unknown error';
231
+ return Response.json(
232
+ { success: false, error: 'Failed to get hotspots', message },
233
+ { status: 500 }
234
+ );
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Handle GET /usage/audit/behavioral/regressions - Get SDK regressions
240
+ */
241
+ export async function handleGetRegressions(
242
+ request: Request,
243
+ env: BehavioralHandlerEnv
244
+ ): Promise<Response> {
245
+ const url = new URL(request.url);
246
+ const project = url.searchParams.get('project');
247
+ const acknowledged = url.searchParams.get('acknowledged');
248
+ const severity = url.searchParams.get('severity');
249
+ const limit = Math.min(parseInt(url.searchParams.get('limit') || '50', 10), 100);
250
+
251
+ try {
252
+ // Build query
253
+ let query = `
254
+ SELECT
255
+ id, project, commit_sha, commit_message, commit_author, commit_date,
256
+ file_path, regression_type, description, severity, acknowledged,
257
+ acknowledged_at, acknowledged_by, audit_date
258
+ FROM audit_sdk_regressions
259
+ WHERE 1=1
260
+ `;
261
+ const bindings: (string | number)[] = [];
262
+
263
+ if (project) {
264
+ query += ' AND project = ?';
265
+ bindings.push(project);
266
+ }
267
+
268
+ if (acknowledged !== null) {
269
+ query += ' AND acknowledged = ?';
270
+ bindings.push(acknowledged === 'true' ? 1 : 0);
271
+ }
272
+
273
+ if (severity) {
274
+ query += ' AND severity = ?';
275
+ bindings.push(severity);
276
+ }
277
+
278
+ query += ` ORDER BY
279
+ CASE severity WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 ELSE 4 END,
280
+ commit_date DESC
281
+ LIMIT ?`;
282
+ bindings.push(limit);
283
+
284
+ const result = await env.PLATFORM_DB.prepare(query).bind(...bindings).all<SdkRegression>();
285
+
286
+ const regressions = result.results ?? [];
287
+
288
+ // Build summary
289
+ const summary = {
290
+ total: regressions.length,
291
+ unacknowledged: regressions.filter((r) => !r.acknowledged).length,
292
+ bySeverity: {
293
+ critical: regressions.filter((r) => r.severity === 'critical').length,
294
+ high: regressions.filter((r) => r.severity === 'high').length,
295
+ medium: regressions.filter((r) => r.severity === 'medium').length,
296
+ low: regressions.filter((r) => r.severity === 'low').length,
297
+ },
298
+ };
299
+
300
+ return Response.json({
301
+ success: true,
302
+ data: {
303
+ summary,
304
+ regressions: regressions.map(formatRegression),
305
+ },
306
+ timestamp: new Date().toISOString(),
307
+ });
308
+ } catch (error) {
309
+ const message = error instanceof Error ? error.message : 'Unknown error';
310
+ return Response.json(
311
+ { success: false, error: 'Failed to get regressions', message },
312
+ { status: 500 }
313
+ );
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Handle POST /usage/audit/behavioral/regressions/:id/acknowledge - Mark regression as acknowledged
319
+ */
320
+ export async function handleAcknowledgeRegression(
321
+ request: Request,
322
+ env: BehavioralHandlerEnv,
323
+ regressionId: string
324
+ ): Promise<Response> {
325
+ try {
326
+ // Parse body for acknowledger info (optional)
327
+ let acknowledgedBy = 'system';
328
+ try {
329
+ const body = await request.json() as { acknowledged_by?: string };
330
+ if (body.acknowledged_by) {
331
+ acknowledgedBy = body.acknowledged_by;
332
+ }
333
+ } catch {
334
+ // Body is optional
335
+ }
336
+
337
+ const result = await env.PLATFORM_DB.prepare(
338
+ `UPDATE audit_sdk_regressions
339
+ SET acknowledged = TRUE,
340
+ acknowledged_at = datetime('now'),
341
+ acknowledged_by = ?
342
+ WHERE id = ? AND acknowledged = FALSE`
343
+ )
344
+ .bind(acknowledgedBy, parseInt(regressionId, 10))
345
+ .run();
346
+
347
+ if (result.meta.changes === 0) {
348
+ // Check if it exists
349
+ const existing = await env.PLATFORM_DB.prepare(
350
+ `SELECT id, acknowledged FROM audit_sdk_regressions WHERE id = ?`
351
+ )
352
+ .bind(parseInt(regressionId, 10))
353
+ .first<{ id: number; acknowledged: boolean }>();
354
+
355
+ if (!existing) {
356
+ return Response.json(
357
+ { success: false, error: 'Regression not found' },
358
+ { status: 404 }
359
+ );
360
+ }
361
+
362
+ if (existing.acknowledged) {
363
+ return Response.json(
364
+ { success: false, error: 'Regression already acknowledged' },
365
+ { status: 409 }
366
+ );
367
+ }
368
+ }
369
+
370
+ return Response.json({
371
+ success: true,
372
+ message: 'Regression acknowledged',
373
+ data: {
374
+ id: parseInt(regressionId, 10),
375
+ acknowledgedAt: new Date().toISOString(),
376
+ acknowledgedBy,
377
+ },
378
+ });
379
+ } catch (error) {
380
+ const message = error instanceof Error ? error.message : 'Unknown error';
381
+ return Response.json(
382
+ { success: false, error: 'Failed to acknowledge regression', message },
383
+ { status: 500 }
384
+ );
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Format hotspot for API response
390
+ */
391
+ function formatHotspot(hotspot: FileHotspot) {
392
+ return {
393
+ id: hotspot.id,
394
+ project: hotspot.project,
395
+ filePath: hotspot.file_path,
396
+ changeCount: hotspot.change_count,
397
+ lastChanged: hotspot.last_changed,
398
+ authors: hotspot.authors ? JSON.parse(hotspot.authors) : [],
399
+ hasSdkPatterns: hotspot.has_sdk_patterns,
400
+ sdkPatternsFound: hotspot.sdk_patterns_found ? JSON.parse(hotspot.sdk_patterns_found) : [],
401
+ hotspotScore: hotspot.hotspot_score,
402
+ riskLevel: hotspot.hotspot_score >= 15 ? 'critical' : hotspot.hotspot_score >= 10 ? 'high' : hotspot.hotspot_score >= 5 ? 'medium' : 'low',
403
+ auditDate: hotspot.audit_date,
404
+ };
405
+ }
406
+
407
+ /**
408
+ * Format regression for API response
409
+ */
410
+ function formatRegression(regression: SdkRegression) {
411
+ return {
412
+ id: regression.id,
413
+ project: regression.project,
414
+ commit: {
415
+ sha: regression.commit_sha,
416
+ message: regression.commit_message,
417
+ author: regression.commit_author,
418
+ date: regression.commit_date,
419
+ },
420
+ filePath: regression.file_path,
421
+ regressionType: regression.regression_type,
422
+ description: regression.description,
423
+ severity: regression.severity,
424
+ acknowledged: regression.acknowledged,
425
+ acknowledgedAt: regression.acknowledged_at,
426
+ acknowledgedBy: regression.acknowledged_by,
427
+ auditDate: regression.audit_date,
428
+ };
429
+ }