@littlebearapps/create-platform 1.0.0 → 1.1.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 (69) hide show
  1. package/README.md +98 -0
  2. package/dist/index.d.ts +6 -1
  3. package/dist/index.js +36 -6
  4. package/dist/prompts.d.ts +14 -2
  5. package/dist/prompts.js +29 -7
  6. package/dist/templates.js +78 -0
  7. package/package.json +3 -2
  8. package/templates/full/workers/lib/pattern-discovery/ai-prompt.ts +644 -0
  9. package/templates/full/workers/lib/pattern-discovery/clustering.ts +278 -0
  10. package/templates/full/workers/lib/pattern-discovery/shadow-evaluation.ts +603 -0
  11. package/templates/full/workers/lib/pattern-discovery/storage.ts +806 -0
  12. package/templates/full/workers/lib/pattern-discovery/types.ts +159 -0
  13. package/templates/full/workers/lib/pattern-discovery/validation.ts +278 -0
  14. package/templates/full/workers/pattern-discovery.ts +661 -0
  15. package/templates/full/workers/platform-alert-router.ts +1809 -0
  16. package/templates/full/workers/platform-notifications.ts +424 -0
  17. package/templates/full/workers/platform-search.ts +480 -0
  18. package/templates/full/workers/platform-settings.ts +436 -0
  19. package/templates/shared/workers/lib/analytics-engine.ts +357 -0
  20. package/templates/shared/workers/lib/billing.ts +293 -0
  21. package/templates/shared/workers/lib/circuit-breaker-middleware.ts +25 -0
  22. package/templates/shared/workers/lib/control.ts +292 -0
  23. package/templates/shared/workers/lib/economics.ts +368 -0
  24. package/templates/shared/workers/lib/metrics.ts +103 -0
  25. package/templates/shared/workers/lib/platform-settings.ts +407 -0
  26. package/templates/shared/workers/lib/shared/allowances.ts +333 -0
  27. package/templates/shared/workers/lib/shared/cloudflare.ts +1362 -0
  28. package/templates/shared/workers/lib/shared/types.ts +58 -0
  29. package/templates/shared/workers/lib/telemetry-sampling.ts +360 -0
  30. package/templates/shared/workers/lib/usage/collectors/example.ts +96 -0
  31. package/templates/shared/workers/lib/usage/collectors/index.ts +128 -0
  32. package/templates/shared/workers/lib/usage/handlers/audit.ts +306 -0
  33. package/templates/shared/workers/lib/usage/handlers/backfill.ts +845 -0
  34. package/templates/shared/workers/lib/usage/handlers/behavioral.ts +429 -0
  35. package/templates/shared/workers/lib/usage/handlers/data-queries.ts +507 -0
  36. package/templates/shared/workers/lib/usage/handlers/dlq-admin.ts +364 -0
  37. package/templates/shared/workers/lib/usage/handlers/health-trends.ts +222 -0
  38. package/templates/shared/workers/lib/usage/handlers/index.ts +35 -0
  39. package/templates/shared/workers/lib/usage/handlers/usage-admin.ts +421 -0
  40. package/templates/shared/workers/lib/usage/handlers/usage-features.ts +1262 -0
  41. package/templates/shared/workers/lib/usage/handlers/usage-metrics.ts +2420 -0
  42. package/templates/shared/workers/lib/usage/handlers/usage-settings.ts +610 -0
  43. package/templates/shared/workers/lib/usage/queue/budget-enforcement.ts +1032 -0
  44. package/templates/shared/workers/lib/usage/queue/cost-budget-enforcement.ts +128 -0
  45. package/templates/shared/workers/lib/usage/queue/cost-calculator.ts +77 -0
  46. package/templates/shared/workers/lib/usage/queue/dlq-handler.ts +161 -0
  47. package/templates/shared/workers/lib/usage/queue/index.ts +19 -0
  48. package/templates/shared/workers/lib/usage/queue/telemetry-processor.ts +790 -0
  49. package/templates/shared/workers/lib/usage/scheduled/anomaly-detection.ts +732 -0
  50. package/templates/shared/workers/lib/usage/scheduled/data-collection.ts +956 -0
  51. package/templates/shared/workers/lib/usage/scheduled/error-digest.ts +343 -0
  52. package/templates/shared/workers/lib/usage/scheduled/index.ts +18 -0
  53. package/templates/shared/workers/lib/usage/scheduled/rollups.ts +1561 -0
  54. package/templates/shared/workers/lib/usage/shared/constants.ts +362 -0
  55. package/templates/shared/workers/lib/usage/shared/index.ts +14 -0
  56. package/templates/shared/workers/lib/usage/shared/types.ts +1066 -0
  57. package/templates/shared/workers/lib/usage/shared/utils.ts +795 -0
  58. package/templates/shared/workers/platform-usage.ts +1915 -0
  59. package/templates/standard/workers/error-collector.ts +2670 -0
  60. package/templates/standard/workers/lib/error-collector/capture.ts +213 -0
  61. package/templates/standard/workers/lib/error-collector/digest.ts +448 -0
  62. package/templates/standard/workers/lib/error-collector/email-health-alerts.ts +262 -0
  63. package/templates/standard/workers/lib/error-collector/fingerprint.ts +258 -0
  64. package/templates/standard/workers/lib/error-collector/gap-alerts.ts +293 -0
  65. package/templates/standard/workers/lib/error-collector/github.ts +329 -0
  66. package/templates/standard/workers/lib/error-collector/types.ts +262 -0
  67. package/templates/standard/workers/lib/sentinel/gap-detection.ts +734 -0
  68. package/templates/standard/workers/lib/shared/slack-alerts.ts +585 -0
  69. package/templates/standard/workers/platform-sentinel.ts +1744 -0
@@ -0,0 +1,343 @@
1
+ /**
2
+ * Error Digest Module
3
+ *
4
+ * Handles error alerting, tracking, and digest generation.
5
+ * Extracted from platform-usage.ts as part of scheduled task modularisation.
6
+ *
7
+ * Functions:
8
+ * - checkAndAlertErrors: Check telemetry for P0 conditions
9
+ * - storeErrorEvent: Store error events to D1 for aggregation
10
+ * - getErrorRateStats: Get error rate statistics over sliding window
11
+ * - sendErrorAlert: Send alerts via alert-router or Slack fallback
12
+ * - sendHourlyErrorDigest: Generate and send P1 hourly digest
13
+ * - sendDailyErrorSummary: Generate and send P2 daily summary
14
+ * - cleanupOldErrorEvents: Remove old error events (7-day retention)
15
+ */
16
+
17
+ import type { Env, ErrorAlertPayload, TelemetryMessage } from '../shared';
18
+ import { ERROR_RATE_THRESHOLDS } from '../shared';
19
+ import { createLoggerFromEnv } from '@littlebearapps/platform-sdk';
20
+
21
+ // =============================================================================
22
+ // ERROR CHECKING AND ALERTING
23
+ // =============================================================================
24
+
25
+ /**
26
+ * Check if telemetry message contains errors that warrant alerting.
27
+ * Detects P0 conditions: circuit breaker trips, high error rates.
28
+ */
29
+ export async function checkAndAlertErrors(telemetry: TelemetryMessage, env: Env): Promise<void> {
30
+ // Skip if no errors reported
31
+ if (!telemetry.error_count || telemetry.error_count === 0) {
32
+ return;
33
+ }
34
+
35
+ // P0 Condition 1: Circuit breaker error
36
+ if (telemetry.error_category === 'CIRCUIT_BREAKER') {
37
+ await sendErrorAlert(env, {
38
+ type: 'p0_immediate',
39
+ feature_key: telemetry.feature_key,
40
+ project: telemetry.project,
41
+ category: telemetry.category,
42
+ feature: telemetry.feature,
43
+ correlation_id: telemetry.correlation_id,
44
+ error_category: telemetry.error_category,
45
+ error_code: telemetry.error_codes?.[0],
46
+ window_minutes: ERROR_RATE_THRESHOLDS.windowMinutes,
47
+ });
48
+ return;
49
+ }
50
+
51
+ // Store error event in D1 for aggregation
52
+ await storeErrorEvent(telemetry, env);
53
+
54
+ // Check error rate over window for P0/P1 conditions
55
+ const errorStats = await getErrorRateStats(telemetry.feature_key, env);
56
+
57
+ if (errorStats.totalRequests >= ERROR_RATE_THRESHOLDS.minRequests) {
58
+ const errorRate = (errorStats.errorCount / errorStats.totalRequests) * 100;
59
+
60
+ if (errorRate >= ERROR_RATE_THRESHOLDS.p0) {
61
+ // P0: High error rate (>50%)
62
+ await sendErrorAlert(env, {
63
+ type: 'p0_immediate',
64
+ feature_key: telemetry.feature_key,
65
+ project: telemetry.project,
66
+ category: telemetry.category,
67
+ feature: telemetry.feature,
68
+ correlation_id: telemetry.correlation_id,
69
+ error_category: telemetry.error_category,
70
+ error_code: telemetry.error_codes?.[0],
71
+ error_rate: errorRate,
72
+ window_minutes: ERROR_RATE_THRESHOLDS.windowMinutes,
73
+ });
74
+ }
75
+ }
76
+ }
77
+
78
+ // =============================================================================
79
+ // ERROR EVENT STORAGE
80
+ // =============================================================================
81
+
82
+ /**
83
+ * Store error event in D1 for aggregation and historical analysis.
84
+ */
85
+ export async function storeErrorEvent(telemetry: TelemetryMessage, env: Env): Promise<void> {
86
+ const log = createLoggerFromEnv(env, 'platform-usage', 'platform:usage:error-tracking');
87
+ try {
88
+ await env.PLATFORM_DB.prepare(
89
+ `INSERT INTO feature_error_events (
90
+ id, feature_key, error_category, error_code, error_message,
91
+ correlation_id, worker, priority, created_at
92
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
93
+ )
94
+ .bind(
95
+ crypto.randomUUID(),
96
+ telemetry.feature_key,
97
+ telemetry.error_category || 'INTERNAL',
98
+ telemetry.error_codes?.[0] || null,
99
+ null, // No message in telemetry (truncated for space)
100
+ telemetry.correlation_id || null,
101
+ null, // Worker name not in telemetry
102
+ 'P2', // Default priority, upgraded by alert detection
103
+ Math.floor(Date.now() / 1000)
104
+ )
105
+ .run();
106
+ } catch (error) {
107
+ log.error('Failed to store error event', error);
108
+ }
109
+ }
110
+
111
+ // =============================================================================
112
+ // ERROR RATE STATISTICS
113
+ // =============================================================================
114
+
115
+ /**
116
+ * Get error rate statistics for a feature over the sliding window.
117
+ */
118
+ export async function getErrorRateStats(
119
+ featureKey: string,
120
+ env: Env
121
+ ): Promise<{ errorCount: number; totalRequests: number }> {
122
+ try {
123
+ const windowStart = Math.floor(Date.now() / 1000) - ERROR_RATE_THRESHOLDS.windowMinutes * 60;
124
+
125
+ const result = await env.PLATFORM_DB.prepare(
126
+ `SELECT
127
+ COUNT(*) as error_count,
128
+ (SELECT COUNT(*) FROM feature_error_events
129
+ WHERE feature_key = ?1 AND created_at >= ?2) as total_events
130
+ FROM feature_error_events
131
+ WHERE feature_key = ?1 AND created_at >= ?2`
132
+ )
133
+ .bind(featureKey, windowStart)
134
+ .first<{ error_count: number; total_events: number }>();
135
+
136
+ return {
137
+ errorCount: result?.error_count ?? 0,
138
+ totalRequests: result?.total_events ?? 0,
139
+ };
140
+ } catch (error) {
141
+ const log = createLoggerFromEnv(env, 'platform-usage', 'platform:usage:error-tracking');
142
+ log.error('Failed to get error rate', error);
143
+ return { errorCount: 0, totalRequests: 0 };
144
+ }
145
+ }
146
+
147
+ // =============================================================================
148
+ // ALERT SENDING
149
+ // =============================================================================
150
+
151
+ /**
152
+ * Send error alert to alert-router.
153
+ * Uses service binding if available, falls back to direct Slack.
154
+ */
155
+ export async function sendErrorAlert(env: Env, payload: ErrorAlertPayload): Promise<void> {
156
+ const log = createLoggerFromEnv(env, 'platform-usage', 'platform:usage:error-alerting');
157
+ try {
158
+ if (env.ALERT_ROUTER) {
159
+ // Use service binding to call alert-router
160
+ const response = await env.ALERT_ROUTER.fetch('https://alert-router/errors', {
161
+ method: 'POST',
162
+ headers: { 'Content-Type': 'application/json' },
163
+ body: JSON.stringify(payload),
164
+ });
165
+
166
+ if (!response.ok) {
167
+ log.error(`alert-router returned ${response.status}`);
168
+ } else {
169
+ log.info('Alert sent', { type: payload.type, featureKey: payload.feature_key });
170
+ }
171
+ } else if (env.SLACK_WEBHOOK_URL) {
172
+ // Fallback: send directly to Slack (basic format)
173
+ const emoji = payload.type === 'p0_immediate' ? '🚨' : '⚠️';
174
+ await fetch(env.SLACK_WEBHOOK_URL, {
175
+ method: 'POST',
176
+ headers: { 'Content-Type': 'application/json' },
177
+ body: JSON.stringify({
178
+ text: `${emoji} [${payload.type.toUpperCase()}] Error in ${payload.feature_key}: ${payload.error_category}`,
179
+ }),
180
+ });
181
+ }
182
+ } catch (error) {
183
+ log.error('Failed to send alert', error);
184
+ }
185
+ }
186
+
187
+ // =============================================================================
188
+ // DIGEST GENERATION
189
+ // =============================================================================
190
+
191
+ /**
192
+ * Generate and send hourly P1 error digest.
193
+ * Only sends if error threshold met (>20% error rate or >100 errors).
194
+ */
195
+ export async function sendHourlyErrorDigest(env: Env): Promise<void> {
196
+ try {
197
+ const hourAgo = Math.floor(Date.now() / 1000) - 3600;
198
+
199
+ // Get top errors from the last hour
200
+ const errors = await env.PLATFORM_DB.prepare(
201
+ `SELECT
202
+ feature_key,
203
+ error_category,
204
+ COUNT(*) as error_count
205
+ FROM feature_error_events
206
+ WHERE created_at >= ?
207
+ GROUP BY feature_key, error_category
208
+ ORDER BY error_count DESC
209
+ LIMIT 10`
210
+ )
211
+ .bind(hourAgo)
212
+ .all<{ feature_key: string; error_category: string; error_count: number }>();
213
+
214
+ if (!errors.results || errors.results.length === 0) {
215
+ return; // No errors in the last hour
216
+ }
217
+
218
+ const totalErrors = errors.results.reduce((sum, e) => sum + e.error_count, 0);
219
+ const distinctTypes = new Set(errors.results.map((e) => e.error_category)).size;
220
+
221
+ // Only send P1 digest if threshold met
222
+ if (totalErrors < 100) {
223
+ return;
224
+ }
225
+
226
+ const now = new Date();
227
+ const hourAgoDate = new Date(now.getTime() - 3600000);
228
+
229
+ const payload: ErrorAlertPayload = {
230
+ type: 'p1_digest',
231
+ feature_key: 'platform:aggregate:hourly',
232
+ project: 'platform',
233
+ category: 'aggregate',
234
+ feature: 'hourly',
235
+ total_errors: totalErrors,
236
+ distinct_types: distinctTypes,
237
+ top_errors: errors.results.map((e) => ({
238
+ feature_key: e.feature_key,
239
+ error_category: e.error_category,
240
+ count: e.error_count,
241
+ })),
242
+ period_start: hourAgoDate.toISOString(),
243
+ period_end: now.toISOString(),
244
+ };
245
+
246
+ await sendErrorAlert(env, payload);
247
+ const log = createLoggerFromEnv(env, 'platform-usage', 'platform:usage:error-digest');
248
+ log.info('P1 hourly digest sent', { totalErrors });
249
+ } catch (error) {
250
+ const log = createLoggerFromEnv(env, 'platform-usage', 'platform:usage:error-digest');
251
+ log.error('Failed to generate hourly digest', error);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Generate and send daily P2 error summary.
257
+ * Runs at midnight UTC (09:00 AEDT).
258
+ */
259
+ export async function sendDailyErrorSummary(env: Env): Promise<void> {
260
+ try {
261
+ const dayAgo = Math.floor(Date.now() / 1000) - 86400;
262
+
263
+ // Get error summary for the last 24 hours
264
+ const errors = await env.PLATFORM_DB.prepare(
265
+ `SELECT
266
+ feature_key,
267
+ error_category,
268
+ COUNT(*) as error_count
269
+ FROM feature_error_events
270
+ WHERE created_at >= ?
271
+ GROUP BY feature_key, error_category
272
+ ORDER BY error_count DESC
273
+ LIMIT 20`
274
+ )
275
+ .bind(dayAgo)
276
+ .all<{ feature_key: string; error_category: string; error_count: number }>();
277
+
278
+ if (!errors.results || errors.results.length === 0) {
279
+ const log = createLoggerFromEnv(env, 'platform-usage', 'platform:usage:error-digest');
280
+ log.info('No errors in the last 24 hours, skipping daily summary');
281
+ return;
282
+ }
283
+
284
+ const totalErrors = errors.results.reduce((sum, e) => sum + e.error_count, 0);
285
+ const distinctFeatures = new Set(errors.results.map((e) => e.feature_key)).size;
286
+
287
+ const now = new Date();
288
+ const dayAgoDate = new Date(now.getTime() - 86400000);
289
+
290
+ const payload: ErrorAlertPayload = {
291
+ type: 'p2_summary',
292
+ feature_key: 'platform:aggregate:daily',
293
+ project: 'platform',
294
+ category: 'aggregate',
295
+ feature: 'daily',
296
+ total_errors: totalErrors,
297
+ distinct_types: distinctFeatures,
298
+ top_errors: errors.results.map((e) => ({
299
+ feature_key: e.feature_key,
300
+ error_category: e.error_category,
301
+ count: e.error_count,
302
+ })),
303
+ period_start: dayAgoDate.toISOString(),
304
+ period_end: now.toISOString(),
305
+ };
306
+
307
+ await sendErrorAlert(env, payload);
308
+ const log = createLoggerFromEnv(env, 'platform-usage', 'platform:usage:error-digest');
309
+ log.info('P2 daily summary sent', { totalErrors, distinctFeatures });
310
+ } catch (error) {
311
+ const log = createLoggerFromEnv(env, 'platform-usage', 'platform:usage:error-digest');
312
+ log.error('Failed to generate daily summary', error);
313
+ }
314
+ }
315
+
316
+ // =============================================================================
317
+ // CLEANUP
318
+ // =============================================================================
319
+
320
+ /**
321
+ * Clean up old error events (7-day retention).
322
+ */
323
+ export async function cleanupOldErrorEvents(env: Env): Promise<number> {
324
+ const log = createLoggerFromEnv(env, 'platform-usage', 'platform:usage:error-cleanup');
325
+ try {
326
+ const weekAgo = Math.floor(Date.now() / 1000) - 7 * 86400;
327
+
328
+ const result = await env.PLATFORM_DB.prepare(
329
+ `DELETE FROM feature_error_events WHERE created_at < ?`
330
+ )
331
+ .bind(weekAgo)
332
+ .run();
333
+
334
+ const deleted = result.meta?.changes ?? 0;
335
+ if (deleted > 0) {
336
+ log.info('Deleted old error events', { deleted });
337
+ }
338
+ return deleted;
339
+ } catch (error) {
340
+ log.error('Failed to cleanup old error events', error);
341
+ return 0;
342
+ }
343
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Scheduled Module Exports
3
+ *
4
+ * Barrel export for all scheduled task modules.
5
+ * These handle cron-triggered data collection, rollups, and monitoring.
6
+ */
7
+
8
+ // Data collection functions (hourly snapshots, third-party APIs)
9
+ export * from './data-collection';
10
+
11
+ // Rollup functions (daily, monthly aggregation)
12
+ export * from './rollups';
13
+
14
+ // Anomaly detection and alerting
15
+ export * from './anomaly-detection';
16
+
17
+ // Error digest and alerting
18
+ export * from './error-digest';