@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,128 @@
1
+ /**
2
+ * Cost Budget Enforcement
3
+ *
4
+ * Cost-based circuit breaker enforcement for platform-usage queue processing.
5
+ * Implements rolling window cost accumulation and budget checking.
6
+ *
7
+ * Part of the real-time cost tracking feature for Platform SDK.
8
+ *
9
+ * Key Concepts:
10
+ * - Cost budgets stored in KV: CONFIG:FEATURE:{key}:COST_BUDGET
11
+ * - Accumulated costs stored in KV: STATE:COST:{key}:ACCUMULATED
12
+ * - Status stored in KV: CONFIG:FEATURE:{key}:STATUS (same key as resource CB)
13
+ */
14
+
15
+ import type { Env } from '../shared';
16
+ import { createLoggerFromEnv } from '@littlebearapps/platform-consumer-sdk';
17
+
18
+ /**
19
+ * Cost budget configuration for a feature.
20
+ * Stored in KV at CONFIG:FEATURE:{key}:COST_BUDGET.
21
+ */
22
+ export interface CostBudgetConfig {
23
+ /** Daily cost limit in USD */
24
+ daily_limit_usd: number;
25
+ /** Optional alert threshold percentage (e.g., 0.8 = 80%) */
26
+ alert_threshold_pct?: number;
27
+ }
28
+
29
+ /**
30
+ * Accumulated cost state stored in KV.
31
+ */
32
+ interface AccumulatedCostState {
33
+ /** Total cost accumulated in window */
34
+ cost: number;
35
+ /** Window start timestamp in milliseconds */
36
+ windowStart: number;
37
+ }
38
+
39
+ /**
40
+ * Check and update cost budget status for a feature.
41
+ * Uses a rolling 24-hour window for cost accumulation.
42
+ *
43
+ * If total cost exceeds the configured daily limit, trips the circuit breaker
44
+ * using the same STATUS key as resource-based circuit breakers.
45
+ *
46
+ * @param featureKey - Feature identifier (e.g., 'my-app:scanner:harvest')
47
+ * @param costIncrement - Cost in USD to add to accumulator
48
+ * @param env - Worker environment
49
+ */
50
+ export async function checkAndUpdateCostBudgetStatus(
51
+ featureKey: string,
52
+ costIncrement: number,
53
+ env: Env
54
+ ): Promise<void> {
55
+ const log = createLoggerFromEnv(env, 'platform-usage', 'platform:usage:cost-budget');
56
+
57
+ const budgetKey = `CONFIG:FEATURE:${featureKey}:COST_BUDGET`;
58
+ const statusKey = `CONFIG:FEATURE:${featureKey}:STATUS`;
59
+ const accumulatorKey = `STATE:COST:${featureKey}:ACCUMULATED`;
60
+
61
+ try {
62
+ // Check if cost budget is configured for this feature
63
+ const budgetJson = await env.PLATFORM_CACHE.get(budgetKey);
64
+ if (!budgetJson) {
65
+ // No cost budget configured - skip checking
66
+ return;
67
+ }
68
+
69
+ const budget = JSON.parse(budgetJson) as CostBudgetConfig;
70
+
71
+ // Rolling 24-hour window
72
+ const windowMs = 24 * 60 * 60 * 1000;
73
+ const windowStart = Date.now() - windowMs;
74
+
75
+ // Get current accumulated cost
76
+ let totalCost = costIncrement;
77
+ let existingWindowStart = Date.now();
78
+
79
+ const stored = await env.PLATFORM_CACHE.get(accumulatorKey);
80
+ if (stored) {
81
+ const data = JSON.parse(stored) as AccumulatedCostState;
82
+ // Only add to existing cost if within the same window
83
+ if (data.windowStart > windowStart) {
84
+ // Use fixed precision to prevent floating point accumulation errors
85
+ totalCost = Number((data.cost + costIncrement).toFixed(6));
86
+ existingWindowStart = data.windowStart;
87
+ }
88
+ }
89
+
90
+ // Store updated cost with 25-hour TTL (allows for window overlap)
91
+ // Round to 6 decimal places to prevent floating point run-on
92
+ await env.PLATFORM_CACHE.put(
93
+ accumulatorKey,
94
+ JSON.stringify({ cost: Number(totalCost.toFixed(6)), windowStart: existingWindowStart }),
95
+ { expirationTtl: 90000 } // 25 hours
96
+ );
97
+
98
+ // Check budget violation
99
+ if (totalCost > budget.daily_limit_usd) {
100
+ const reason = `cost_usd=${totalCost.toFixed(4)}>${budget.daily_limit_usd}`;
101
+
102
+ // Trip the circuit breaker in KV
103
+ await env.PLATFORM_CACHE.put(statusKey, 'STOP');
104
+
105
+ // Log to D1 for historical tracking
106
+ try {
107
+ await env.PLATFORM_DB.prepare(
108
+ `INSERT INTO feature_circuit_breaker_events
109
+ (id, feature_key, event_type, reason, violated_resource, current_value, budget_limit, created_at)
110
+ VALUES (?1, ?2, 'trip', ?3, 'cost_usd', ?4, ?5, unixepoch())`
111
+ )
112
+ .bind(crypto.randomUUID(), featureKey, reason, totalCost, budget.daily_limit_usd)
113
+ .run();
114
+ } catch (d1Error) {
115
+ // D1 logging failure should not prevent KV trip
116
+ log.error(`Failed to log cost CB event to D1 for ${featureKey}`, d1Error);
117
+ }
118
+
119
+ log.warn(`Cost CB tripped: ${featureKey}`, {
120
+ totalCost: totalCost.toFixed(4),
121
+ limit: budget.daily_limit_usd,
122
+ });
123
+ }
124
+ } catch (error) {
125
+ // Cost budget check failures should not fail the telemetry write
126
+ log.error(`Cost budget check failed for ${featureKey}`, error);
127
+ }
128
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Cost Calculator
3
+ *
4
+ * Calculates CF resource cost from telemetry metrics.
5
+ * Uses pricing tiers from workers/lib/costs.ts for consistency.
6
+ *
7
+ * Part of the real-time cost tracking feature for Platform SDK.
8
+ */
9
+
10
+ import type { FeatureMetrics } from '@littlebearapps/platform-consumer-sdk';
11
+ import { PRICING_TIERS } from '@littlebearapps/platform-consumer-sdk';
12
+
13
+ /**
14
+ * Calculate CF resource cost from telemetry metrics.
15
+ * Returns cost in USD based on current pricing tiers.
16
+ *
17
+ * @param metrics - Feature metrics from telemetry message
18
+ * @returns Total cost in USD for CF resources
19
+ */
20
+ export function calculateCFCostFromMetrics(metrics: FeatureMetrics): number {
21
+ let cost = 0;
22
+
23
+ // D1: $0.001/billion reads, $1.00/million writes
24
+ if (metrics.d1RowsRead) {
25
+ cost += (metrics.d1RowsRead / 1e9) * PRICING_TIERS.d1.rowsReadPerBillion;
26
+ }
27
+ if (metrics.d1RowsWritten) {
28
+ cost += (metrics.d1RowsWritten / 1e6) * PRICING_TIERS.d1.rowsWrittenPerMillion;
29
+ }
30
+
31
+ // KV: $0.50/million reads, $5.00/million writes, $5.00/million deletes, $5.00/million lists
32
+ if (metrics.kvReads) {
33
+ cost += (metrics.kvReads / 1e6) * PRICING_TIERS.kv.readsPerMillion;
34
+ }
35
+ if (metrics.kvWrites) {
36
+ cost += (metrics.kvWrites / 1e6) * PRICING_TIERS.kv.writesPerMillion;
37
+ }
38
+ if (metrics.kvDeletes) {
39
+ cost += (metrics.kvDeletes / 1e6) * PRICING_TIERS.kv.deletesPerMillion;
40
+ }
41
+ if (metrics.kvLists) {
42
+ cost += (metrics.kvLists / 1e6) * PRICING_TIERS.kv.listsPerMillion;
43
+ }
44
+
45
+ // R2: $4.50/million Class A, $0.36/million Class B
46
+ if (metrics.r2ClassA) {
47
+ cost += (metrics.r2ClassA / 1e6) * PRICING_TIERS.r2.classAPerMillion;
48
+ }
49
+ if (metrics.r2ClassB) {
50
+ cost += (metrics.r2ClassB / 1e6) * PRICING_TIERS.r2.classBPerMillion;
51
+ }
52
+
53
+ // Workers AI: $0.011/1000 neurons
54
+ if (metrics.aiNeurons) {
55
+ cost += (metrics.aiNeurons / 1000) * PRICING_TIERS.workersAI.neuronsPerThousand;
56
+ }
57
+
58
+ // Durable Objects: $0.15/million requests, $12.50/million GB-seconds
59
+ if (metrics.doRequests) {
60
+ cost += (metrics.doRequests / 1e6) * PRICING_TIERS.durableObjects.requestsPerMillion;
61
+ }
62
+ if (metrics.doGbSeconds) {
63
+ cost += (metrics.doGbSeconds / 1e6) * PRICING_TIERS.durableObjects.gbSecondsPerMillion;
64
+ }
65
+
66
+ // Vectorize: $0.01/million queried dimensions
67
+ if (metrics.vectorizeQueries) {
68
+ cost += (metrics.vectorizeQueries / 1e6) * PRICING_TIERS.vectorize.queriedDimensionsPerMillion;
69
+ }
70
+
71
+ // Queues: $0.40/million messages
72
+ if (metrics.queueMessages) {
73
+ cost += (metrics.queueMessages / 1e6) * PRICING_TIERS.queues.messagesPerMillion;
74
+ }
75
+
76
+ return cost;
77
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Dead Letter Queue Handler
3
+ *
4
+ * Consumes messages from the platform-telemetry-dlq queue and persists them
5
+ * to D1 for admin visibility, debugging, and replay.
6
+ *
7
+ * Messages land here after max_retries (5) failures in the main queue consumer.
8
+ */
9
+
10
+ import type { MessageBatch } from '@cloudflare/workers-types';
11
+ import type { Env, TelemetryMessage } from '../shared';
12
+ import { createLoggerFromEnv } from '@littlebearapps/platform-consumer-sdk';
13
+ import { categoriseError } from '@littlebearapps/platform-consumer-sdk';
14
+
15
+ // =============================================================================
16
+ // DLQ CONSTANTS
17
+ // =============================================================================
18
+
19
+ const MAX_ERROR_MESSAGE_LENGTH = 1000;
20
+ const MAX_PAYLOAD_LENGTH = 10000;
21
+
22
+ // =============================================================================
23
+ // DLQ MESSAGE PERSISTENCE
24
+ // =============================================================================
25
+
26
+ /**
27
+ * Persist a DLQ message to D1 for admin visibility.
28
+ */
29
+ async function persistDLQMessage(
30
+ telemetry: TelemetryMessage,
31
+ errorMessage: string | null,
32
+ errorCategory: string,
33
+ errorFingerprint: string,
34
+ retryCount: number,
35
+ env: Env
36
+ ): Promise<void> {
37
+ const payload = JSON.stringify(telemetry);
38
+ const truncatedPayload =
39
+ payload.length > MAX_PAYLOAD_LENGTH ? payload.slice(0, MAX_PAYLOAD_LENGTH) + '...' : payload;
40
+ const truncatedError = errorMessage?.slice(0, MAX_ERROR_MESSAGE_LENGTH) || null;
41
+
42
+ await env.PLATFORM_DB.prepare(
43
+ `INSERT INTO dead_letter_queue (
44
+ id, message_payload, feature_key, project, category, feature,
45
+ error_message, error_category, error_fingerprint, retry_count,
46
+ correlation_id, original_timestamp, status, created_at, updated_at
47
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', unixepoch(), unixepoch())`
48
+ )
49
+ .bind(
50
+ crypto.randomUUID(),
51
+ truncatedPayload,
52
+ telemetry.feature_key,
53
+ telemetry.project,
54
+ telemetry.category,
55
+ telemetry.feature,
56
+ truncatedError,
57
+ errorCategory,
58
+ errorFingerprint,
59
+ retryCount,
60
+ telemetry.correlation_id || null,
61
+ telemetry.timestamp
62
+ )
63
+ .run();
64
+ }
65
+
66
+ // =============================================================================
67
+ // DLQ QUEUE HANDLER
68
+ // =============================================================================
69
+
70
+ /**
71
+ * Handle messages from the Dead Letter Queue.
72
+ *
73
+ * Messages arrive here after exhausting retries in the main queue.
74
+ * We persist them to D1 for visibility and always ack to prevent re-delivery.
75
+ */
76
+ async function handleDLQ(batch: MessageBatch<TelemetryMessage>, env: Env): Promise<void> {
77
+ const log = createLoggerFromEnv(env, 'platform-usage', 'platform:usage:dlq');
78
+ log.warn('Processing DLQ batch', { messages: batch.messages.length });
79
+
80
+ let successCount = 0;
81
+ let errorCount = 0;
82
+
83
+ for (const message of batch.messages) {
84
+ try {
85
+ const telemetry = message.body;
86
+
87
+ // Extract error info from the message metadata if available
88
+ // Cloudflare doesn't expose retry count directly, so we use max_retries setting
89
+ const retryCount = 5; // Matches max_retries in wrangler config
90
+
91
+ // Since we don't have the original error, categorise based on telemetry content
92
+ const errorCategory = telemetry.error_category || 'INTERNAL';
93
+ const errorFingerprint = `dlq:${telemetry.feature_key}:${errorCategory}`;
94
+
95
+ // Persist to D1
96
+ await persistDLQMessage(
97
+ telemetry,
98
+ 'Message exhausted retries in telemetry queue',
99
+ errorCategory,
100
+ errorFingerprint,
101
+ retryCount,
102
+ env
103
+ );
104
+
105
+ log.info('DLQ message persisted', {
106
+ feature_key: telemetry.feature_key,
107
+ project: telemetry.project,
108
+ error_category: errorCategory,
109
+ correlation_id: telemetry.correlation_id,
110
+ });
111
+
112
+ message.ack();
113
+ successCount++;
114
+ } catch (error) {
115
+ // Even if D1 write fails, ack the message to prevent infinite loop
116
+ // Log the error for investigation
117
+ const errorCategory = categoriseError(error);
118
+ log.error('Failed to persist DLQ message, acknowledging anyway', error, {
119
+ feature_key: message.body.feature_key,
120
+ error_category: errorCategory,
121
+ });
122
+
123
+ message.ack();
124
+ errorCount++;
125
+ }
126
+ }
127
+
128
+ log.info('DLQ batch complete', {
129
+ persisted: successCount,
130
+ failed: errorCount,
131
+ total: batch.messages.length,
132
+ });
133
+
134
+ // Send alert if DLQ is receiving messages (indicates systemic issue)
135
+ if (batch.messages.length > 0 && env.ALERT_ROUTER) {
136
+ try {
137
+ await env.ALERT_ROUTER.fetch('https://alert-router/errors', {
138
+ method: 'POST',
139
+ headers: { 'Content-Type': 'application/json' },
140
+ body: JSON.stringify({
141
+ type: 'p1_digest',
142
+ feature_key: 'platform:usage:dlq',
143
+ project: 'platform',
144
+ category: 'usage',
145
+ feature: 'dlq',
146
+ total_errors: batch.messages.length,
147
+ distinct_types: new Set(batch.messages.map((m) => m.body.error_category || 'INTERNAL'))
148
+ .size,
149
+ }),
150
+ });
151
+ } catch (alertError) {
152
+ log.error('Failed to send DLQ alert', alertError);
153
+ }
154
+ }
155
+ }
156
+
157
+ // =============================================================================
158
+ // EXPORTS
159
+ // =============================================================================
160
+
161
+ export { handleDLQ, persistDLQMessage };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Queue Module Exports
3
+ *
4
+ * Barrel export for all queue processing modules.
5
+ * These handle telemetry queue consumption, budget enforcement, and circuit breakers.
6
+ */
7
+
8
+ // Telemetry processing (queue consumer, heartbeat handling)
9
+ export * from './telemetry-processor';
10
+
11
+ // Dead Letter Queue handler
12
+ export * from './dlq-handler';
13
+
14
+ // Budget enforcement (circuit breakers, status tracking)
15
+ export * from './budget-enforcement';
16
+
17
+ // Cost calculation and enforcement
18
+ export * from './cost-calculator';
19
+ export * from './cost-budget-enforcement';