@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,159 @@
1
+ /**
2
+ * Types for Automated Transient Error Pattern Discovery
3
+ * @module workers/lib/pattern-discovery/types
4
+ */
5
+
6
+ /** Pattern types supported by the DSL (ordered by safety) */
7
+ export type PatternType = 'contains' | 'startsWith' | 'statusCode' | 'regex';
8
+
9
+ /** Scope for pattern application */
10
+ export type PatternScope = 'global' | `service:${string}` | `upstream:${string}`;
11
+
12
+ /** Pattern suggestion status - expanded for self-tuning */
13
+ export type SuggestionStatus = 'pending' | 'shadow' | 'approved' | 'stale' | 'rejected' | 'disabled';
14
+
15
+ /** Pattern source tracking */
16
+ export type PatternSource = 'ai-discovered' | 'static-import' | 'manual';
17
+
18
+ /** Audit log action types - expanded for self-tuning */
19
+ export type AuditAction =
20
+ | 'created'
21
+ | 'approved'
22
+ | 'rejected'
23
+ | 'enabled'
24
+ | 'disabled'
25
+ | 'auto-disabled'
26
+ | 'backtest-passed'
27
+ | 'backtest-failed'
28
+ | 'shadow-started'
29
+ | 'shadow-completed'
30
+ | 'expired'
31
+ // Self-tuning actions
32
+ | 'auto-promoted'
33
+ | 'auto-demoted'
34
+ | 'ready-for-review'
35
+ | 'reactivated'
36
+ | 'imported';
37
+
38
+ /** Pattern rule using the constrained DSL */
39
+ export interface PatternRule {
40
+ type: PatternType;
41
+ value: string; // Token(s) for contains, prefix for startsWith, code for statusCode, regex string for regex
42
+ category: string;
43
+ scope: PatternScope;
44
+ }
45
+
46
+ /** Pattern suggestion from AI analysis */
47
+ export interface PatternSuggestion {
48
+ id: string;
49
+ patternType: PatternType;
50
+ patternValue: string;
51
+ category: string;
52
+ scope: PatternScope;
53
+ confidenceScore: number;
54
+ sampleMessages: string[];
55
+ aiReasoning: string;
56
+ clusterId: string | null;
57
+ status: SuggestionStatus;
58
+ reviewedBy: string | null;
59
+ reviewedAt: number | null;
60
+ rejectionReason: string | null;
61
+ backtestMatchCount: number | null;
62
+ backtestTotalErrors: number | null;
63
+ backtestMatchRate: number | null;
64
+ // Shadow mode tracking
65
+ shadowModeStart: number | null;
66
+ shadowModeEnd: number | null;
67
+ shadowModeMatches: number;
68
+ shadowMatchDays: string[] | null; // Unique days with matches
69
+ // Lifecycle
70
+ enabledAt: number | null;
71
+ disabledAt: number | null;
72
+ lastMatchedAt: number | null;
73
+ matchCount: number;
74
+ // Self-tuning fields
75
+ isProtected: boolean;
76
+ source: PatternSource;
77
+ originalRegex: string | null;
78
+ // Timestamps
79
+ createdAt: number;
80
+ updatedAt: number;
81
+ }
82
+
83
+ /** Shadow evaluation result for auto-promotion decision */
84
+ export interface ShadowEvaluationResult {
85
+ patternId: string;
86
+ shadowMatchCount: number;
87
+ shadowDays: number;
88
+ matchSpreadDays: number; // How many unique days had matches
89
+ currentMatchRate: number;
90
+ recommendation: 'promote' | 'demote' | 'continue';
91
+ reasoning: string;
92
+ }
93
+
94
+ /** Configuration for shadow evaluation thresholds */
95
+ export interface ShadowEvaluationConfig {
96
+ minMatchesForPromotion: number; // Default: 5
97
+ minSpreadDaysForPromotion: number; // Default: 3
98
+ maxMatchRateForPromotion: number; // Default: 0.8 (80%)
99
+ shadowPeriodDays: number; // Default: 7
100
+ staleDaysThreshold: number; // Default: 30
101
+ }
102
+
103
+ /** Error cluster for grouping similar unclassified errors */
104
+ export interface ErrorCluster {
105
+ id: string;
106
+ clusterHash: string;
107
+ representativeMessage: string;
108
+ occurrenceCount: number;
109
+ uniqueFingerprints: number;
110
+ firstSeenAt: number;
111
+ lastSeenAt: number;
112
+ scripts: string[];
113
+ status: 'pending' | 'processing' | 'suggested' | 'ignored';
114
+ suggestionId: string | null;
115
+ }
116
+
117
+ /** AI suggestion response from DeepSeek */
118
+ export interface AISuggestionResponse {
119
+ patterns: Array<{
120
+ patternType: PatternType;
121
+ patternValue: string;
122
+ category: string;
123
+ confidence: number;
124
+ reasoning: string;
125
+ positiveExamples: string[];
126
+ negativeExamples: string[];
127
+ }>;
128
+ summary: string;
129
+ }
130
+
131
+ /** Backtest result for a pattern */
132
+ export interface BacktestResult {
133
+ patternId: string;
134
+ matchCount: number;
135
+ totalErrors: number;
136
+ matchRate: number;
137
+ matchedFingerprints: string[];
138
+ overMatching: boolean; // true if > 80% match rate
139
+ runAt: number;
140
+ }
141
+
142
+ /** Unclassified error from D1 for clustering */
143
+ export interface UnclassifiedError {
144
+ fingerprint: string;
145
+ scriptName: string;
146
+ normalizedMessage: string;
147
+ occurrenceCount: number;
148
+ lastSeenAt: number;
149
+ }
150
+
151
+ /** Discovery run result */
152
+ export interface DiscoveryResult {
153
+ runId: string;
154
+ runAt: number;
155
+ clustersFound: number;
156
+ clustersProcessed: number;
157
+ suggestionsCreated: number;
158
+ errors: string[];
159
+ }
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Pattern Validation and Safety Checks
3
+ *
4
+ * Validates AI-generated patterns for safety (ReDoS prevention)
5
+ * and correctness (backtest against historical data).
6
+ *
7
+ * @module workers/lib/pattern-discovery/validation
8
+ */
9
+
10
+ import type { D1Database } from '@cloudflare/workers-types';
11
+ import type { PatternType, PatternRule, BacktestResult } from './types';
12
+ import type { Logger } from '@littlebearapps/platform-consumer-sdk';
13
+
14
+ /** Maximum regex execution time in ms */
15
+ const MAX_REGEX_EXEC_TIME_MS = 10;
16
+
17
+ /** Match rate threshold for over-matching detection */
18
+ const OVER_MATCHING_THRESHOLD = 0.8;
19
+
20
+ /** Dangerous regex patterns to reject */
21
+ const DANGEROUS_PATTERNS = [
22
+ /\(\.\*\)\+/, // (.*)+
23
+ /\(\.\+\)\+/, // (.+)+
24
+ /\([^)]*\|[^)]*\)\+/, // (a|b)+ nested alternation with quantifier
25
+ /\(\?<[!=]/, // Lookbehind (not supported in all engines)
26
+ /\\1/, // Backreferences
27
+ ];
28
+
29
+ /** Maximum allowed regex length */
30
+ const MAX_REGEX_LENGTH = 200;
31
+
32
+ /** Maximum allowed alternations in regex */
33
+ const MAX_ALTERNATIONS = 10;
34
+
35
+ /**
36
+ * Validate a pattern for safety
37
+ * Returns error message if invalid, null if valid
38
+ */
39
+ export function validatePatternSafety(
40
+ patternType: PatternType,
41
+ patternValue: string
42
+ ): string | null {
43
+ // Non-regex patterns are always safe
44
+ if (patternType !== 'regex') {
45
+ // Basic validation for other types
46
+ if (!patternValue || patternValue.length === 0) {
47
+ return 'Pattern value cannot be empty';
48
+ }
49
+ if (patternType === 'statusCode' && !/^\d{3}$/.test(patternValue)) {
50
+ return 'Status code must be 3 digits';
51
+ }
52
+ return null;
53
+ }
54
+
55
+ // Regex validation
56
+ if (patternValue.length > MAX_REGEX_LENGTH) {
57
+ return `Regex too long (max ${MAX_REGEX_LENGTH} chars)`;
58
+ }
59
+
60
+ // Check for dangerous patterns
61
+ for (const dangerous of DANGEROUS_PATTERNS) {
62
+ if (dangerous.test(patternValue)) {
63
+ return `Regex contains dangerous pattern: ${dangerous.source}`;
64
+ }
65
+ }
66
+
67
+ // Check alternation count
68
+ const alternations = (patternValue.match(/\|/g) || []).length;
69
+ if (alternations > MAX_ALTERNATIONS) {
70
+ return `Too many alternations (max ${MAX_ALTERNATIONS})`;
71
+ }
72
+
73
+ // Try to compile the regex
74
+ try {
75
+ new RegExp(patternValue, 'i');
76
+ } catch (error) {
77
+ return `Invalid regex: ${error instanceof Error ? error.message : 'Unknown error'}`;
78
+ }
79
+
80
+ // Performance test with adversarial input
81
+ const testResult = testRegexPerformance(patternValue);
82
+ if (testResult !== null) {
83
+ return testResult;
84
+ }
85
+
86
+ return null;
87
+ }
88
+
89
+ /**
90
+ * Test regex performance with adversarial input
91
+ * Returns error message if too slow, null if OK
92
+ */
93
+ function testRegexPerformance(pattern: string): string | null {
94
+ const regex = new RegExp(pattern, 'i');
95
+
96
+ // Test with various adversarial inputs
97
+ const adversarialInputs = [
98
+ 'a'.repeat(100),
99
+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!',
100
+ ' '.repeat(100),
101
+ 'x'.repeat(50) + 'y'.repeat(50),
102
+ '0'.repeat(100),
103
+ ];
104
+
105
+ for (const input of adversarialInputs) {
106
+ const start = performance.now();
107
+ try {
108
+ regex.test(input);
109
+ } catch {
110
+ return 'Regex execution failed';
111
+ }
112
+ const elapsed = performance.now() - start;
113
+
114
+ if (elapsed > MAX_REGEX_EXEC_TIME_MS) {
115
+ return `Regex too slow (${elapsed.toFixed(1)}ms > ${MAX_REGEX_EXEC_TIME_MS}ms)`;
116
+ }
117
+ }
118
+
119
+ return null;
120
+ }
121
+
122
+ /**
123
+ * Compile a pattern rule to a test function
124
+ */
125
+ export function compilePattern(rule: PatternRule): (message: string) => boolean {
126
+ switch (rule.type) {
127
+ case 'contains': {
128
+ const tokens = rule.value.toLowerCase().split(/\s+/);
129
+ return (message: string) => {
130
+ const lower = message.toLowerCase();
131
+ return tokens.every((token) => lower.includes(token));
132
+ };
133
+ }
134
+
135
+ case 'startsWith': {
136
+ const prefix = rule.value.toLowerCase();
137
+ return (message: string) => message.toLowerCase().startsWith(prefix);
138
+ }
139
+
140
+ case 'statusCode': {
141
+ const code = rule.value;
142
+ const codePattern = new RegExp(`\\b${code}\\b`);
143
+ return (message: string) => codePattern.test(message);
144
+ }
145
+
146
+ case 'regex': {
147
+ const regex = new RegExp(rule.value, 'i');
148
+ return (message: string) => regex.test(message);
149
+ }
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Run backtest against historical error data
155
+ */
156
+ export async function backtestPattern(
157
+ patternId: string,
158
+ rule: PatternRule,
159
+ db: D1Database,
160
+ log: Logger,
161
+ daysBack: number = 7
162
+ ): Promise<BacktestResult> {
163
+ const cutoff = Math.floor(Date.now() / 1000) - daysBack * 24 * 60 * 60;
164
+
165
+ // Get recent errors
166
+ const result = await db
167
+ .prepare(
168
+ `
169
+ SELECT fingerprint, normalized_message as message
170
+ FROM error_occurrences
171
+ WHERE last_seen_at >= ?
172
+ LIMIT 10000
173
+ `
174
+ )
175
+ .bind(cutoff)
176
+ .all<{ fingerprint: string; message: string | null }>();
177
+
178
+ const errors = result.results;
179
+ const testFn = compilePattern(rule);
180
+
181
+ const matchedFingerprints: string[] = [];
182
+ let matchCount = 0;
183
+
184
+ for (const error of errors) {
185
+ if (error.message && testFn(error.message)) {
186
+ matchCount++;
187
+ if (matchedFingerprints.length < 100) {
188
+ matchedFingerprints.push(error.fingerprint);
189
+ }
190
+ }
191
+ }
192
+
193
+ const totalErrors = errors.length;
194
+ const matchRate = totalErrors > 0 ? matchCount / totalErrors : 0;
195
+ const overMatching = matchRate > OVER_MATCHING_THRESHOLD;
196
+
197
+ log.info('Backtest complete', {
198
+ patternId,
199
+ matchCount,
200
+ totalErrors,
201
+ matchRate: (matchRate * 100).toFixed(1) + '%',
202
+ overMatching,
203
+ });
204
+
205
+ return {
206
+ patternId,
207
+ matchCount,
208
+ totalErrors,
209
+ matchRate,
210
+ matchedFingerprints,
211
+ overMatching,
212
+ runAt: Math.floor(Date.now() / 1000),
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Store backtest result in D1
218
+ */
219
+ export async function storeBacktestResult(
220
+ db: D1Database,
221
+ result: BacktestResult,
222
+ log: Logger
223
+ ): Promise<void> {
224
+ try {
225
+ await db
226
+ .prepare(
227
+ `
228
+ UPDATE transient_pattern_suggestions
229
+ SET
230
+ backtest_match_count = ?,
231
+ backtest_total_errors = ?,
232
+ backtest_match_rate = ?,
233
+ backtest_run_at = ?,
234
+ updated_at = unixepoch()
235
+ WHERE id = ?
236
+ `
237
+ )
238
+ .bind(
239
+ result.matchCount,
240
+ result.totalErrors,
241
+ result.matchRate,
242
+ result.runAt,
243
+ result.patternId
244
+ )
245
+ .run();
246
+
247
+ // Log audit event
248
+ const action = result.overMatching ? 'backtest-failed' : 'backtest-passed';
249
+ await db
250
+ .prepare(
251
+ `
252
+ INSERT INTO pattern_audit_log (id, pattern_id, action, actor, reason, metadata)
253
+ VALUES (?, ?, ?, 'system:backtest', ?, ?)
254
+ `
255
+ )
256
+ .bind(
257
+ `audit-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
258
+ result.patternId,
259
+ action,
260
+ result.overMatching
261
+ ? `Over-matching: ${(result.matchRate * 100).toFixed(1)}% match rate`
262
+ : `Passed: ${(result.matchRate * 100).toFixed(1)}% match rate`,
263
+ JSON.stringify({
264
+ matchCount: result.matchCount,
265
+ totalErrors: result.totalErrors,
266
+ matchRate: result.matchRate,
267
+ })
268
+ )
269
+ .run();
270
+
271
+ log.info('Stored backtest result', {
272
+ patternId: result.patternId,
273
+ action,
274
+ });
275
+ } catch (error) {
276
+ log.error('Failed to store backtest result', error, { patternId: result.patternId });
277
+ }
278
+ }