@littlebearapps/platform-admin-sdk 2.1.0 → 2.2.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 (115) hide show
  1. package/README.md +2 -5
  2. package/dist/templates.d.ts +1 -1
  3. package/dist/templates.js +121 -3
  4. package/package.json +1 -1
  5. package/templates/full/dashboard/src/components/notifications/NotificationDropdown.tsx +130 -0
  6. package/templates/full/dashboard/src/components/notifications/NotificationItem.tsx +264 -0
  7. package/templates/full/dashboard/src/components/patterns/PatternInfoButton.tsx +60 -0
  8. package/templates/full/dashboard/src/components/reports/FeatureUsageReport.tsx +339 -0
  9. package/templates/full/dashboard/src/components/search/SearchResultGroup.tsx +46 -0
  10. package/templates/full/dashboard/src/components/search/SearchResultItem.tsx +212 -0
  11. package/templates/full/dashboard/src/pages/api/patterns/[id]/approve.ts +49 -0
  12. package/templates/full/dashboard/src/pages/api/patterns/[id]/reject.ts +50 -0
  13. package/templates/full/dashboard/src/pages/api/reports/digests/stats.ts +38 -0
  14. package/templates/full/dashboard/src/pages/api/reports/digests.ts +39 -0
  15. package/templates/full/dashboard/src/pages/api/search/reindex/[type].ts +56 -0
  16. package/templates/full/dashboard/src/pages/api/test-reports/[id].ts +102 -0
  17. package/templates/full/dashboard/src/pages/feedback.astro +365 -0
  18. package/templates/full/dashboard/src/pages/kiosk.astro +206 -0
  19. package/templates/full/dashboard/src/pages/map.astro +561 -0
  20. package/templates/full/dashboard/src/pages/revenue.astro +72 -0
  21. package/templates/full/dashboard/src/pages/tests.astro +431 -0
  22. package/templates/full/scripts/ops/audit-cost-anomaly.ts +430 -0
  23. package/templates/full/scripts/ops/verify-account-total.ts +256 -0
  24. package/templates/full/tests/integration/feedback-schema.test.ts +361 -0
  25. package/templates/full/tests/integration/r2-archive.test.ts +108 -0
  26. package/templates/shared/.github/workflows/dependabot-automerge.yml +41 -0
  27. package/templates/shared/.github/workflows/validate-controls.yml +27 -0
  28. package/templates/shared/dashboard/src/components/Breadcrumbs.astro +101 -0
  29. package/templates/shared/dashboard/src/components/EmptyState.astro +46 -0
  30. package/templates/shared/dashboard/src/components/ErrorBoundary.astro +79 -0
  31. package/templates/shared/dashboard/src/components/LoadingSkeleton.astro +105 -0
  32. package/templates/shared/dashboard/src/components/PageShell.astro +72 -0
  33. package/templates/shared/dashboard/src/components/SkipLinks.astro +22 -0
  34. package/templates/shared/dashboard/src/components/Toast.astro +170 -0
  35. package/templates/shared/dashboard/src/components/ToastContainer.astro +156 -0
  36. package/templates/shared/dashboard/src/components/costs/ProviderCostsGrid.tsx +401 -0
  37. package/templates/shared/dashboard/src/components/costs/index.ts +4 -0
  38. package/templates/shared/dashboard/src/components/overview/AlertBanner.tsx +94 -0
  39. package/templates/shared/dashboard/src/components/overview/index.ts +9 -0
  40. package/templates/shared/dashboard/src/components/resources/CostChart.tsx +170 -0
  41. package/templates/shared/dashboard/src/components/resources/ProviderCard.tsx +272 -0
  42. package/templates/shared/dashboard/src/components/resources/ProviderDetail.tsx +293 -0
  43. package/templates/shared/dashboard/src/components/settings/SettingsCard.astro +102 -0
  44. package/templates/shared/dashboard/src/components/usage/AllowanceGauge.astro +170 -0
  45. package/templates/shared/dashboard/src/components/usage/AnomalyAlerts.astro +633 -0
  46. package/templates/shared/dashboard/src/components/usage/BillingCycleCountdown.astro +192 -0
  47. package/templates/shared/dashboard/src/components/usage/BurnRateHero.astro +539 -0
  48. package/templates/shared/dashboard/src/components/usage/CircuitBreakerEventLog.astro +542 -0
  49. package/templates/shared/dashboard/src/components/usage/CircuitBreakerPanel.tsx +292 -0
  50. package/templates/shared/dashboard/src/components/usage/CircuitBreakerStatus.astro +669 -0
  51. package/templates/shared/dashboard/src/components/usage/CompactThresholdBanner.astro +531 -0
  52. package/templates/shared/dashboard/src/components/usage/ComparisonModeSelector.astro +651 -0
  53. package/templates/shared/dashboard/src/components/usage/CostBreakdownChart.astro +381 -0
  54. package/templates/shared/dashboard/src/components/usage/CostBreakdownTable.astro +210 -0
  55. package/templates/shared/dashboard/src/components/usage/CostDataTable.astro +0 -0
  56. package/templates/shared/dashboard/src/components/usage/CostDonutChart.astro +311 -0
  57. package/templates/shared/dashboard/src/components/usage/DailyCostChart.astro +632 -0
  58. package/templates/shared/dashboard/src/components/usage/ExportButton.astro +114 -0
  59. package/templates/shared/dashboard/src/components/usage/FeatureBudgetsTable.astro +872 -0
  60. package/templates/shared/dashboard/src/components/usage/FilterBar.astro +190 -0
  61. package/templates/shared/dashboard/src/components/usage/FilterToggles.astro +175 -0
  62. package/templates/shared/dashboard/src/components/usage/GitHubUsageCard.astro +537 -0
  63. package/templates/shared/dashboard/src/components/usage/OverageCostCard.astro +212 -0
  64. package/templates/shared/dashboard/src/components/usage/PlanUtilizationCard.astro +193 -0
  65. package/templates/shared/dashboard/src/components/usage/ProjectCard.astro +640 -0
  66. package/templates/shared/dashboard/src/components/usage/ProjectCardsGrid.astro +272 -0
  67. package/templates/shared/dashboard/src/components/usage/ResourceSearch.astro +279 -0
  68. package/templates/shared/dashboard/src/components/usage/ServiceUtilizationList.astro +604 -0
  69. package/templates/shared/dashboard/src/components/usage/SparklineCard.astro +399 -0
  70. package/templates/shared/dashboard/src/components/usage/StatsHero.astro +600 -0
  71. package/templates/shared/dashboard/src/components/usage/TableFilters.astro +1033 -0
  72. package/templates/shared/dashboard/src/components/usage/ThresholdAlert.astro +271 -0
  73. package/templates/shared/dashboard/src/components/usage/ThresholdSettings.astro +618 -0
  74. package/templates/shared/dashboard/src/components/usage/TopSpenderCard.astro +170 -0
  75. package/templates/shared/dashboard/src/components/usage/UnifiedResourceTable.astro +1737 -0
  76. package/templates/shared/dashboard/src/components/usage/UsageCard.astro +135 -0
  77. package/templates/shared/dashboard/src/components/usage/UsageHealthBanner.astro +387 -0
  78. package/templates/shared/dashboard/src/components/usage/UtilizationBar.astro +159 -0
  79. package/templates/shared/dashboard/src/components/usage/WorkersBreakdownTable.astro +659 -0
  80. package/templates/shared/dashboard/src/components/usage/daily/CostChart.astro +461 -0
  81. package/templates/shared/dashboard/src/components/usage/daily/CostTable.astro +946 -0
  82. package/templates/shared/dashboard/src/components/usage/daily/DailyOverview.astro +1079 -0
  83. package/templates/shared/dashboard/src/components/usage/design-tokens.ts +187 -0
  84. package/templates/shared/dashboard/src/components/usage/filters/InlineDateRange.astro +285 -0
  85. package/templates/shared/dashboard/src/components/usage/filters/PeriodButtons.astro +157 -0
  86. package/templates/shared/dashboard/src/components/usage/filters/ProjectSelect.astro +284 -0
  87. package/templates/shared/dashboard/src/components/usage/scripts/ai-tab-controller.ts +419 -0
  88. package/templates/shared/dashboard/src/components/usage/scripts/constants.ts +60 -0
  89. package/templates/shared/dashboard/src/components/usage/scripts/formatters.ts +62 -0
  90. package/templates/shared/dashboard/src/components/usage/scripts/overview-controller.ts +1633 -0
  91. package/templates/shared/dashboard/src/components/usage/scripts/resource-table-builder.ts +294 -0
  92. package/templates/shared/dashboard/src/components/usage/scripts/tabs-filters-controller.ts +464 -0
  93. package/templates/shared/dashboard/src/components/usage/state/index.ts +55 -0
  94. package/templates/shared/dashboard/src/components/usage/state/usageActions.ts +439 -0
  95. package/templates/shared/dashboard/src/components/usage/state/usageStore.ts +376 -0
  96. package/templates/shared/dashboard/src/components/usage/types.ts +283 -0
  97. package/templates/shared/dashboard/src/components/usage/usage-colors.ts +292 -0
  98. package/templates/shared/dashboard/src/pages/api/usage/ai-models.ts +235 -0
  99. package/templates/shared/dashboard/src/pages/api/usage/billing-context.ts +296 -0
  100. package/templates/shared/scripts/test-telemetry-flow.ts +464 -0
  101. package/templates/shared/tests/e2e/usage-export.test.ts +784 -0
  102. package/templates/shared/tests/e2e/usage-mobile.test.ts +531 -0
  103. package/templates/standard/dashboard/src/components/errors/PriorityBadge.astro +27 -0
  104. package/templates/standard/dashboard/src/components/infrastructure/HealthchecksStatus.tsx +293 -0
  105. package/templates/standard/dashboard/src/components/infrastructure/InfrastructureTabs.tsx +268 -0
  106. package/templates/standard/dashboard/src/pages/analytics.astro +64 -0
  107. package/templates/standard/dashboard/src/pages/api/infrastructure/alerts.ts +85 -0
  108. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks/[id]/flips.ts +110 -0
  109. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks.ts +101 -0
  110. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime/[id]/response-times.ts +121 -0
  111. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime.ts +89 -0
  112. package/templates/standard/dashboard/src/pages/api/test/service-auth.ts +178 -0
  113. package/templates/standard/tests/integration/connectors.test.ts +241 -0
  114. package/templates/standard/tests/integration/github-monitor.test.ts +143 -0
  115. package/templates/standard/tests/integration/ingestion.test.ts +211 -0
@@ -0,0 +1,464 @@
1
+ /**
2
+ * Verification Script: Platform SDK Telemetry Flow
3
+ *
4
+ * This script tests the Platform SDK telemetry flow including:
5
+ * - TelemetryMessage format validation
6
+ * - MetricsAccumulator initialization
7
+ * - Circuit breaker error handling
8
+ * - Feature ID parsing
9
+ * - End-to-end flow simulation
10
+ *
11
+ * Run with: npx tsx scripts/test-telemetry-flow.ts
12
+ *
13
+ * @task SDK pilot integration verification
14
+ */
15
+
16
+ import {
17
+ CircuitBreakerError,
18
+ createMetricsAccumulator,
19
+ CIRCUIT_STATUS,
20
+ KV_KEYS,
21
+ type TelemetryMessage,
22
+ type MetricsAccumulator,
23
+ type FeatureMetrics,
24
+ } from '@littlebearapps/platform-consumer-sdk';
25
+
26
+ // ============================================================================
27
+ // Utility Functions
28
+ // ============================================================================
29
+
30
+ function parseFeatureId(featureId: string): {
31
+ project: string;
32
+ category: string;
33
+ feature: string;
34
+ } {
35
+ const parts = featureId.split(':');
36
+ if (parts.length !== 3) {
37
+ throw new Error(
38
+ `Invalid featureId format: "${featureId}". Expected "project:category:feature"`
39
+ );
40
+ }
41
+ return {
42
+ project: parts[0],
43
+ category: parts[1],
44
+ feature: parts[2],
45
+ };
46
+ }
47
+
48
+ function createTelemetryMessage(featureId: string, metrics: FeatureMetrics): TelemetryMessage {
49
+ const { project, category, feature } = parseFeatureId(featureId);
50
+ return {
51
+ feature_key: featureId,
52
+ project,
53
+ category,
54
+ feature,
55
+ metrics,
56
+ timestamp: Date.now(),
57
+ };
58
+ }
59
+
60
+ // ============================================================================
61
+ // Test Cases: TelemetryMessage Format
62
+ // ============================================================================
63
+
64
+ console.log('='.repeat(70));
65
+ console.log('Platform SDK Telemetry Flow Verification');
66
+ console.log('='.repeat(70));
67
+ console.log();
68
+
69
+ let passed = 0;
70
+ let failed = 0;
71
+
72
+ // Test 1: TelemetryMessage creation
73
+ console.log('Test 1: TelemetryMessage Creation');
74
+ console.log('-'.repeat(50));
75
+
76
+ const testFeatureId = 'scout:ocr:process';
77
+ const testMetrics: FeatureMetrics = {
78
+ d1Reads: 5,
79
+ d1Writes: 2,
80
+ d1RowsRead: 50,
81
+ d1RowsWritten: 10,
82
+ kvReads: 3,
83
+ kvWrites: 1,
84
+ };
85
+
86
+ try {
87
+ const message = createTelemetryMessage(testFeatureId, testMetrics);
88
+
89
+ const checks = [
90
+ { name: 'feature_key matches', pass: message.feature_key === testFeatureId },
91
+ { name: 'project parsed correctly', pass: message.project === 'scout' },
92
+ { name: 'category parsed correctly', pass: message.category === 'ocr' },
93
+ { name: 'feature parsed correctly', pass: message.feature === 'process' },
94
+ { name: 'timestamp is number', pass: typeof message.timestamp === 'number' },
95
+ { name: 'timestamp is recent', pass: Date.now() - message.timestamp < 1000 },
96
+ { name: 'metrics.d1Reads preserved', pass: message.metrics.d1Reads === 5 },
97
+ { name: 'metrics.d1Writes preserved', pass: message.metrics.d1Writes === 2 },
98
+ { name: 'metrics.kvReads preserved', pass: message.metrics.kvReads === 3 },
99
+ ];
100
+
101
+ for (const check of checks) {
102
+ if (check.pass) {
103
+ console.log(` ✓ ${check.name}`);
104
+ passed++;
105
+ } else {
106
+ console.log(` ✗ ${check.name}`);
107
+ failed++;
108
+ }
109
+ }
110
+ } catch (e) {
111
+ console.log(` ✗ TelemetryMessage creation failed: ${e}`);
112
+ failed++;
113
+ }
114
+
115
+ console.log();
116
+
117
+ // ============================================================================
118
+ // Test Cases: MetricsAccumulator
119
+ // ============================================================================
120
+
121
+ console.log('Test 2: MetricsAccumulator Initialization');
122
+ console.log('-'.repeat(50));
123
+
124
+ const accumulator = createMetricsAccumulator();
125
+
126
+ const accumulatorChecks: Array<{ name: string; field: keyof MetricsAccumulator }> = [
127
+ { name: 'd1Writes starts at 0', field: 'd1Writes' },
128
+ { name: 'd1Reads starts at 0', field: 'd1Reads' },
129
+ { name: 'd1RowsRead starts at 0', field: 'd1RowsRead' },
130
+ { name: 'd1RowsWritten starts at 0', field: 'd1RowsWritten' },
131
+ { name: 'kvReads starts at 0', field: 'kvReads' },
132
+ { name: 'kvWrites starts at 0', field: 'kvWrites' },
133
+ { name: 'kvDeletes starts at 0', field: 'kvDeletes' },
134
+ { name: 'kvLists starts at 0', field: 'kvLists' },
135
+ { name: 'aiRequests starts at 0', field: 'aiRequests' },
136
+ { name: 'aiNeurons starts at 0', field: 'aiNeurons' },
137
+ { name: 'vectorizeQueries starts at 0', field: 'vectorizeQueries' },
138
+ { name: 'vectorizeInserts starts at 0', field: 'vectorizeInserts' },
139
+ // vectorizeDeletes removed - Analytics Engine 20 double limit
140
+ ];
141
+
142
+ for (const check of accumulatorChecks) {
143
+ if (accumulator[check.field] === 0) {
144
+ console.log(` ✓ ${check.name}`);
145
+ passed++;
146
+ } else {
147
+ console.log(` ✗ ${check.name} (got ${accumulator[check.field]})`);
148
+ failed++;
149
+ }
150
+ }
151
+
152
+ // Test accumulator mutation
153
+ accumulator.d1Writes += 5;
154
+ accumulator.kvReads += 3;
155
+
156
+ if (accumulator.d1Writes === 5 && accumulator.kvReads === 3) {
157
+ console.log(' ✓ Accumulator is mutable');
158
+ passed++;
159
+ } else {
160
+ console.log(' ✗ Accumulator mutation failed');
161
+ failed++;
162
+ }
163
+
164
+ console.log();
165
+
166
+ // ============================================================================
167
+ // Test Cases: CircuitBreakerError
168
+ // ============================================================================
169
+
170
+ console.log('Test 3: CircuitBreakerError');
171
+ console.log('-'.repeat(50));
172
+
173
+ const cbError = new CircuitBreakerError('scout:ocr:process', 'feature', 'Budget exceeded');
174
+
175
+ const errorChecks = [
176
+ { name: 'is Error instance', pass: cbError instanceof Error },
177
+ { name: 'is CircuitBreakerError instance', pass: cbError instanceof CircuitBreakerError },
178
+ { name: 'name is CircuitBreakerError', pass: cbError.name === 'CircuitBreakerError' },
179
+ { name: 'featureId preserved', pass: cbError.featureId === 'scout:ocr:process' },
180
+ { name: 'level is feature', pass: cbError.level === 'feature' },
181
+ { name: 'reason preserved', pass: cbError.reason === 'Budget exceeded' },
182
+ { name: 'message contains featureId', pass: cbError.message.includes('scout:ocr:process') },
183
+ { name: 'message contains reason', pass: cbError.message.includes('Budget exceeded') },
184
+ ];
185
+
186
+ for (const check of errorChecks) {
187
+ if (check.pass) {
188
+ console.log(` ✓ ${check.name}`);
189
+ passed++;
190
+ } else {
191
+ console.log(` ✗ ${check.name}`);
192
+ failed++;
193
+ }
194
+ }
195
+
196
+ // Test error without reason
197
+ const cbErrorNoReason = new CircuitBreakerError('scout:ocr:process', 'project');
198
+ if (cbErrorNoReason.reason === undefined) {
199
+ console.log(' ✓ Error without reason has undefined reason');
200
+ passed++;
201
+ } else {
202
+ console.log(' ✗ Error without reason should have undefined reason');
203
+ failed++;
204
+ }
205
+
206
+ // Test different levels
207
+ const levelTests: Array<{ level: 'feature' | 'project' | 'global' }> = [
208
+ { level: 'feature' },
209
+ { level: 'project' },
210
+ { level: 'global' },
211
+ ];
212
+
213
+ for (const test of levelTests) {
214
+ const err = new CircuitBreakerError('test:a:b', test.level);
215
+ if (err.level === test.level) {
216
+ console.log(` ✓ Level ${test.level} works correctly`);
217
+ passed++;
218
+ } else {
219
+ console.log(` ✗ Level ${test.level} failed`);
220
+ failed++;
221
+ }
222
+ }
223
+
224
+ console.log();
225
+
226
+ // ============================================================================
227
+ // Test Cases: KV Key Patterns
228
+ // ============================================================================
229
+
230
+ console.log('Test 4: KV Key Patterns');
231
+ console.log('-'.repeat(50));
232
+
233
+ const kvKeyTests = [
234
+ {
235
+ name: 'featureStatus',
236
+ fn: () => KV_KEYS.featureStatus('scout:ocr:process'),
237
+ expected: 'CONFIG:FEATURE:scout:ocr:process:STATUS',
238
+ },
239
+ {
240
+ name: 'projectStatus',
241
+ fn: () => KV_KEYS.projectStatus('scout'),
242
+ expected: 'CONFIG:PROJECT:scout:STATUS',
243
+ },
244
+ {
245
+ name: 'globalStatus',
246
+ fn: () => KV_KEYS.globalStatus(),
247
+ expected: 'CONFIG:GLOBAL:STATUS',
248
+ },
249
+ {
250
+ name: 'featureReason',
251
+ fn: () => KV_KEYS.featureReason('scout:ocr:process'),
252
+ expected: 'CONFIG:FEATURE:scout:ocr:process:REASON',
253
+ },
254
+ {
255
+ name: 'featureDisabledAt',
256
+ fn: () => KV_KEYS.featureDisabledAt('scout:ocr:process'),
257
+ expected: 'CONFIG:FEATURE:scout:ocr:process:DISABLED_AT',
258
+ },
259
+ {
260
+ name: 'featureAutoResetAt',
261
+ fn: () => KV_KEYS.featureAutoResetAt('scout:ocr:process'),
262
+ expected: 'CONFIG:FEATURE:scout:ocr:process:AUTO_RESET_AT',
263
+ },
264
+ {
265
+ name: 'legacy.enabled',
266
+ fn: () => KV_KEYS.legacy.enabled('scout:ocr:process'),
267
+ expected: 'FEATURE:scout:ocr:process:enabled',
268
+ },
269
+ ];
270
+
271
+ for (const test of kvKeyTests) {
272
+ const result = test.fn();
273
+ if (result === test.expected) {
274
+ console.log(` ✓ ${test.name}: ${result}`);
275
+ passed++;
276
+ } else {
277
+ console.log(` ✗ ${test.name}: expected "${test.expected}", got "${result}"`);
278
+ failed++;
279
+ }
280
+ }
281
+
282
+ console.log();
283
+
284
+ // ============================================================================
285
+ // Test Cases: Circuit Status Constants
286
+ // ============================================================================
287
+
288
+ console.log('Test 5: Circuit Status Constants');
289
+ console.log('-'.repeat(50));
290
+
291
+ if (CIRCUIT_STATUS.GO === 'GO') {
292
+ console.log(' ✓ CIRCUIT_STATUS.GO = "GO"');
293
+ passed++;
294
+ } else {
295
+ console.log(' ✗ CIRCUIT_STATUS.GO should be "GO"');
296
+ failed++;
297
+ }
298
+
299
+ if (CIRCUIT_STATUS.STOP === 'STOP') {
300
+ console.log(' ✓ CIRCUIT_STATUS.STOP = "STOP"');
301
+ passed++;
302
+ } else {
303
+ console.log(' ✗ CIRCUIT_STATUS.STOP should be "STOP"');
304
+ failed++;
305
+ }
306
+
307
+ console.log();
308
+
309
+ // ============================================================================
310
+ // Test Cases: Feature ID Parsing
311
+ // ============================================================================
312
+
313
+ console.log('Test 6: Feature ID Parsing');
314
+ console.log('-'.repeat(50));
315
+
316
+ const validFeatureIds = [
317
+ { id: 'scout:ocr:process', project: 'scout', category: 'ocr', feature: 'process' },
318
+ {
319
+ id: 'brand-copilot:scanner:github',
320
+ project: 'brand-copilot',
321
+ category: 'scanner',
322
+ feature: 'github',
323
+ },
324
+ { id: 'platform:usage:scheduled', project: 'platform', category: 'usage', feature: 'scheduled' },
325
+ { id: 'a:b:c', project: 'a', category: 'b', feature: 'c' },
326
+ ];
327
+
328
+ for (const test of validFeatureIds) {
329
+ try {
330
+ const parsed = parseFeatureId(test.id);
331
+ if (
332
+ parsed.project === test.project &&
333
+ parsed.category === test.category &&
334
+ parsed.feature === test.feature
335
+ ) {
336
+ console.log(` ✓ Parsed "${test.id}" correctly`);
337
+ passed++;
338
+ } else {
339
+ console.log(` ✗ Parsed "${test.id}" incorrectly`);
340
+ failed++;
341
+ }
342
+ } catch {
343
+ console.log(` ✗ Failed to parse valid ID "${test.id}"`);
344
+ failed++;
345
+ }
346
+ }
347
+
348
+ // Note: '::' and 'a:b:' technically parse to 3 parts (empty strings are valid parts)
349
+ // The SDK doesn't validate empty parts - only that there are exactly 3 parts
350
+ const invalidFeatureIds = ['scout', 'scout:ocr', 'scout:ocr:process:extra', ''];
351
+
352
+ for (const id of invalidFeatureIds) {
353
+ try {
354
+ parseFeatureId(id);
355
+ console.log(` ✗ Should have rejected invalid ID "${id}"`);
356
+ failed++;
357
+ } catch {
358
+ console.log(` ✓ Correctly rejected invalid ID "${id}"`);
359
+ passed++;
360
+ }
361
+ }
362
+
363
+ console.log();
364
+
365
+ // ============================================================================
366
+ // Test Cases: End-to-End Flow Simulation
367
+ // ============================================================================
368
+
369
+ console.log('Test 7: End-to-End Flow Simulation');
370
+ console.log('-'.repeat(50));
371
+
372
+ // Simulate a complete telemetry flow
373
+ const flowAccumulator = createMetricsAccumulator();
374
+
375
+ // Simulate D1 operations
376
+ flowAccumulator.d1Reads += 1;
377
+ flowAccumulator.d1RowsRead += 10;
378
+ flowAccumulator.d1Writes += 2;
379
+ flowAccumulator.d1RowsWritten += 5;
380
+
381
+ // Simulate KV operations
382
+ flowAccumulator.kvReads += 3;
383
+ flowAccumulator.kvWrites += 1;
384
+
385
+ // Simulate AI operations
386
+ flowAccumulator.aiRequests += 1;
387
+ flowAccumulator.aiNeurons += 500;
388
+
389
+ // Convert to telemetry message
390
+ const flowFeatureId = 'platform:usage:fetch';
391
+ const flowMetrics: FeatureMetrics = {
392
+ d1Reads: flowAccumulator.d1Reads,
393
+ d1Writes: flowAccumulator.d1Writes,
394
+ d1RowsRead: flowAccumulator.d1RowsRead,
395
+ d1RowsWritten: flowAccumulator.d1RowsWritten,
396
+ kvReads: flowAccumulator.kvReads,
397
+ kvWrites: flowAccumulator.kvWrites,
398
+ aiRequests: flowAccumulator.aiRequests,
399
+ aiNeurons: flowAccumulator.aiNeurons,
400
+ };
401
+
402
+ const flowMessage = createTelemetryMessage(flowFeatureId, flowMetrics);
403
+
404
+ // Validate the message structure for queue consumption
405
+ const messageValidation = [
406
+ { name: 'Has feature_key', pass: typeof flowMessage.feature_key === 'string' },
407
+ { name: 'Has project', pass: typeof flowMessage.project === 'string' },
408
+ { name: 'Has category', pass: typeof flowMessage.category === 'string' },
409
+ { name: 'Has feature', pass: typeof flowMessage.feature === 'string' },
410
+ { name: 'Has timestamp', pass: typeof flowMessage.timestamp === 'number' },
411
+ { name: 'Has metrics object', pass: typeof flowMessage.metrics === 'object' },
412
+ { name: 'D1 reads tracked', pass: flowMessage.metrics.d1Reads === 1 },
413
+ { name: 'D1 writes tracked', pass: flowMessage.metrics.d1Writes === 2 },
414
+ { name: 'D1 rows read tracked', pass: flowMessage.metrics.d1RowsRead === 10 },
415
+ { name: 'D1 rows written tracked', pass: flowMessage.metrics.d1RowsWritten === 5 },
416
+ { name: 'KV reads tracked', pass: flowMessage.metrics.kvReads === 3 },
417
+ { name: 'KV writes tracked', pass: flowMessage.metrics.kvWrites === 1 },
418
+ { name: 'AI requests tracked', pass: flowMessage.metrics.aiRequests === 1 },
419
+ { name: 'AI neurons tracked', pass: flowMessage.metrics.aiNeurons === 500 },
420
+ ];
421
+
422
+ for (const check of messageValidation) {
423
+ if (check.pass) {
424
+ console.log(` ✓ ${check.name}`);
425
+ passed++;
426
+ } else {
427
+ console.log(` ✗ ${check.name}`);
428
+ failed++;
429
+ }
430
+ }
431
+
432
+ console.log();
433
+ console.log(' Simulated telemetry message:');
434
+ console.log(
435
+ JSON.stringify(flowMessage, null, 2)
436
+ .split('\n')
437
+ .map((l) => ' ' + l)
438
+ .join('\n')
439
+ );
440
+
441
+ console.log();
442
+
443
+ // ============================================================================
444
+ // Final Summary
445
+ // ============================================================================
446
+
447
+ console.log('='.repeat(70));
448
+ console.log('FINAL SUMMARY');
449
+ console.log('='.repeat(70));
450
+ console.log(`Total tests: ${passed + failed}`);
451
+ console.log(`Passed: ${passed}`);
452
+ console.log(`Failed: ${failed}`);
453
+ console.log();
454
+
455
+ if (failed > 0) {
456
+ console.log('❌ VERIFICATION FAILED');
457
+ process.exit(1);
458
+ } else {
459
+ console.log('✅ ALL VERIFICATIONS PASSED');
460
+ console.log();
461
+ console.log('Platform SDK telemetry flow is working correctly.');
462
+ console.log('Ready for production deployment.');
463
+ process.exit(0);
464
+ }