@littlebearapps/platform-admin-sdk 1.5.0 → 2.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 (86) hide show
  1. package/dist/templates.d.ts +1 -1
  2. package/dist/templates.js +112 -1
  3. package/package.json +1 -1
  4. package/templates/full/dashboard/src/components/patterns/ActivePatterns.tsx +62 -0
  5. package/templates/full/dashboard/src/components/patterns/PatternTabs.tsx +116 -0
  6. package/templates/full/dashboard/src/components/patterns/SystemPatterns.tsx +52 -0
  7. package/templates/full/dashboard/src/components/patterns/index.ts +3 -0
  8. package/templates/full/dashboard/src/components/reports/GapDetectionReport.tsx +69 -0
  9. package/templates/full/dashboard/src/components/reports/SdkAuditReport.tsx +72 -0
  10. package/templates/full/dashboard/src/components/reports/index.ts +2 -0
  11. package/templates/full/dashboard/src/pages/api/notifications/[id]/read.ts +37 -0
  12. package/templates/full/dashboard/src/pages/api/notifications/read-all.ts +28 -0
  13. package/templates/full/dashboard/src/pages/api/patterns/cache-refresh.ts +38 -0
  14. package/templates/full/dashboard/src/pages/api/patterns/discover.ts +36 -0
  15. package/templates/full/dashboard/src/pages/api/patterns/ready-for-review.ts +39 -0
  16. package/templates/full/dashboard/src/pages/api/patterns/stats.ts +39 -0
  17. package/templates/full/dashboard/src/pages/api/patterns/suggestions.ts +43 -0
  18. package/templates/full/dashboard/src/pages/api/reports/audit.ts +45 -0
  19. package/templates/full/dashboard/src/pages/api/reports/usage.ts +52 -0
  20. package/templates/full/dashboard/src/pages/api/search/reindex.ts +28 -0
  21. package/templates/full/dashboard/src/pages/api/search/stats.ts +27 -0
  22. package/templates/full/dashboard/src/pages/api/settings/index.ts +37 -0
  23. package/templates/full/dashboard/src/pages/api/settings/update.ts +41 -0
  24. package/templates/full/dashboard/src/pages/api/topology/index.ts +56 -0
  25. package/templates/full/scripts/ops/universal-backfill.ts +147 -0
  26. package/templates/shared/.github/workflows/contract-check.yml.hbs +42 -0
  27. package/templates/shared/.github/workflows/dashboard-deploy.yml.hbs +39 -0
  28. package/templates/shared/.github/workflows/security.yml +33 -0
  29. package/templates/shared/dashboard/src/components/Nav.astro.hbs +2 -0
  30. package/templates/shared/dashboard/src/components/infrastructure/AlertHistory.tsx +57 -0
  31. package/templates/shared/dashboard/src/components/infrastructure/InfrastructureStats.tsx +73 -0
  32. package/templates/shared/dashboard/src/components/infrastructure/ServiceRegistry.tsx +55 -0
  33. package/templates/shared/dashboard/src/components/infrastructure/UptimeStatus.tsx +56 -0
  34. package/templates/shared/dashboard/src/components/infrastructure/index.ts +4 -0
  35. package/templates/shared/dashboard/src/components/ui/Breadcrumbs.tsx +27 -0
  36. package/templates/shared/dashboard/src/components/ui/EmptyState.tsx +26 -0
  37. package/templates/shared/dashboard/src/components/ui/ErrorBoundary.tsx +42 -0
  38. package/templates/shared/dashboard/src/components/ui/LoadingSkeleton.tsx +18 -0
  39. package/templates/shared/dashboard/src/components/ui/PageShell.tsx +26 -0
  40. package/templates/shared/dashboard/src/components/ui/Toast.tsx +44 -0
  41. package/templates/shared/dashboard/src/components/ui/index.ts +6 -0
  42. package/templates/shared/dashboard/src/components/usage/AnomaliesWidget.tsx +68 -0
  43. package/templates/shared/dashboard/src/components/usage/HourlyUsageChart.tsx +55 -0
  44. package/templates/shared/dashboard/src/components/usage/PlanAllowanceDashboard.tsx +67 -0
  45. package/templates/shared/dashboard/src/components/usage/ProjectCostBreakdown.tsx +55 -0
  46. package/templates/shared/dashboard/src/components/usage/index.ts +4 -0
  47. package/templates/shared/dashboard/src/lib/cloudflare/costs.ts +21 -0
  48. package/templates/shared/dashboard/src/pages/api/costs/overview.ts +65 -0
  49. package/templates/shared/dashboard/src/pages/api/costs/providers.ts +47 -0
  50. package/templates/shared/dashboard/src/pages/api/infrastructure/services.ts +55 -0
  51. package/templates/shared/dashboard/src/pages/api/infrastructure/stats.ts +99 -0
  52. package/templates/shared/dashboard/src/pages/api/usage/allowances.ts +56 -0
  53. package/templates/shared/dashboard/src/pages/api/usage/anomalies.ts +45 -0
  54. package/templates/shared/dashboard/src/pages/api/usage/billing.ts +53 -0
  55. package/templates/shared/dashboard/src/pages/api/usage/granular.ts +50 -0
  56. package/templates/shared/dashboard/src/pages/api/usage/hourly.ts +45 -0
  57. package/templates/shared/dashboard/src/pages/api/usage/projects.ts +51 -0
  58. package/templates/shared/dashboard/src/pages/api/user/identity.ts +11 -0
  59. package/templates/shared/dashboard/src/pages/settings/notifications.astro +34 -0
  60. package/templates/shared/dashboard/src/pages/settings/thresholds.astro +39 -0
  61. package/templates/shared/dashboard/src/pages/settings/usage.astro +28 -0
  62. package/templates/shared/docs/architecture.md +89 -0
  63. package/templates/shared/docs/post-deploy-runbook.md +126 -0
  64. package/templates/shared/docs/troubleshooting.md +91 -0
  65. package/templates/shared/package.json.hbs +5 -0
  66. package/templates/shared/scripts/ops/backfill-cloudflare-daily.ts +145 -0
  67. package/templates/shared/scripts/ops/backfill-monthly-rollups.ts +125 -0
  68. package/templates/shared/scripts/ops/validate-controls.js +141 -0
  69. package/templates/shared/tests/contract/validate-schemas.test.ts +130 -0
  70. package/templates/shared/tests/fixtures/telemetry-envelope-invalid.json +9 -0
  71. package/templates/shared/tests/fixtures/telemetry-envelope-valid.json +27 -0
  72. package/templates/shared/tests/helpers/mock-d1.ts +61 -0
  73. package/templates/shared/tests/helpers/mock-kv.ts +37 -0
  74. package/templates/shared/tests/unit/workers/batch-persistence.test.ts +133 -0
  75. package/templates/shared/tests/unit/workers/budget-enforcement.test.ts +214 -0
  76. package/templates/shared/vitest.config.ts +18 -0
  77. package/templates/standard/dashboard/src/components/health/CircuitBreakerEvents.tsx +69 -0
  78. package/templates/standard/dashboard/src/components/health/CircuitBreakerPanel.tsx +97 -0
  79. package/templates/standard/dashboard/src/components/health/index.ts +2 -0
  80. package/templates/standard/dashboard/src/pages/api/errors/[fingerprint]/mute.ts +49 -0
  81. package/templates/standard/dashboard/src/pages/api/errors/[fingerprint]/resolve.ts +36 -0
  82. package/templates/standard/dashboard/src/pages/api/errors/[fingerprint].ts +55 -0
  83. package/templates/standard/dashboard/src/pages/api/health/audit-history.ts +37 -0
  84. package/templates/standard/dashboard/src/pages/circuit-breakers.astro +13 -0
  85. package/templates/standard/tests/unit/error-collector/capture.test.ts +106 -0
  86. package/templates/standard/tests/unit/error-collector/fingerprint.test.ts +155 -0
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Unit Tests for Error Capture Logic
3
+ *
4
+ * Covers:
5
+ * - shouldCapture() filtering logic
6
+ * - Outcome handling for all 7 non-OK outcomes
7
+ * - Deduplication via KV fingerprint keys
8
+ */
9
+
10
+ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
11
+ import { createMockKV } from '../../../tests/helpers/mock-kv';
12
+ import { MockD1Database } from '../../../tests/helpers/mock-d1';
13
+
14
+ vi.mock('@littlebearapps/platform-consumer-sdk', async (importOriginal) => {
15
+ const actual = await importOriginal();
16
+ return {
17
+ ...actual,
18
+ createLoggerFromEnv: () => ({
19
+ info: vi.fn(),
20
+ warn: vi.fn(),
21
+ error: vi.fn(),
22
+ debug: vi.fn(),
23
+ }),
24
+ };
25
+ });
26
+
27
+ import { shouldCapture } from '../../../workers/lib/error-collector/capture';
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Helpers
31
+ // ---------------------------------------------------------------------------
32
+
33
+ function createTailEvent(overrides: Record<string, unknown> = {}) {
34
+ return {
35
+ scriptName: 'test-worker',
36
+ outcome: 'exception',
37
+ exceptions: [{ name: 'Error', message: 'Test error' }],
38
+ logs: [],
39
+ eventTimestamp: Date.now(),
40
+ ...overrides,
41
+ };
42
+ }
43
+
44
+ // ===========================================================================
45
+ // shouldCapture
46
+ // ===========================================================================
47
+
48
+ describe('shouldCapture', () => {
49
+ it('captures exception outcomes', () => {
50
+ const event = createTailEvent({ outcome: 'exception' });
51
+ expect(shouldCapture(event as never)).toBe(true);
52
+ });
53
+
54
+ it('captures exceededCpu outcomes', () => {
55
+ const event = createTailEvent({ outcome: 'exceededCpu' });
56
+ expect(shouldCapture(event as never)).toBe(true);
57
+ });
58
+
59
+ it('captures exceededMemory outcomes', () => {
60
+ const event = createTailEvent({ outcome: 'exceededMemory' });
61
+ expect(shouldCapture(event as never)).toBe(true);
62
+ });
63
+
64
+ it('captures canceled outcomes', () => {
65
+ const event = createTailEvent({ outcome: 'canceled' });
66
+ expect(shouldCapture(event as never)).toBe(true);
67
+ });
68
+
69
+ it('captures responseStreamDisconnected outcomes', () => {
70
+ const event = createTailEvent({ outcome: 'responseStreamDisconnected' });
71
+ expect(shouldCapture(event as never)).toBe(true);
72
+ });
73
+
74
+ it('captures scriptNotFound outcomes', () => {
75
+ const event = createTailEvent({ outcome: 'scriptNotFound' });
76
+ expect(shouldCapture(event as never)).toBe(true);
77
+ });
78
+
79
+ it('does not capture ok outcomes without error logs', () => {
80
+ const event = createTailEvent({ outcome: 'ok', exceptions: [], logs: [] });
81
+ expect(shouldCapture(event as never)).toBe(false);
82
+ });
83
+
84
+ it('captures ok outcomes with error-level logs (soft errors)', () => {
85
+ const event = createTailEvent({
86
+ outcome: 'ok',
87
+ exceptions: [],
88
+ logs: [{ level: 'error', message: ['Something failed'], timestamp: Date.now() }],
89
+ });
90
+ expect(shouldCapture(event as never)).toBe(true);
91
+ });
92
+
93
+ it('captures ok outcomes with warning-level logs', () => {
94
+ const event = createTailEvent({
95
+ outcome: 'ok',
96
+ exceptions: [],
97
+ logs: [{ level: 'warn', message: ['Deprecation warning'], timestamp: Date.now() }],
98
+ });
99
+ expect(shouldCapture(event as never)).toBe(true);
100
+ });
101
+
102
+ it('ignores events without scriptName', () => {
103
+ const event = createTailEvent({ scriptName: undefined, outcome: 'exception' });
104
+ expect(shouldCapture(event as never)).toBe(false);
105
+ });
106
+ });
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Unit Tests for Error Fingerprinting
3
+ *
4
+ * Covers:
5
+ * - Error classification by outcome type
6
+ * - Transient error detection (quota, rate-limit, timeout)
7
+ * - Message normalisation (variable stripping)
8
+ * - Fingerprint stability (same error = same fingerprint)
9
+ */
10
+
11
+ import { describe, expect, it, vi } from 'vitest';
12
+
13
+ vi.mock('@littlebearapps/platform-consumer-sdk', async (importOriginal) => {
14
+ const actual = await importOriginal();
15
+ return {
16
+ ...actual,
17
+ createLoggerFromEnv: () => ({
18
+ info: vi.fn(),
19
+ warn: vi.fn(),
20
+ error: vi.fn(),
21
+ debug: vi.fn(),
22
+ }),
23
+ };
24
+ });
25
+
26
+ import {
27
+ classifyError,
28
+ generateFingerprint,
29
+ normalizeMessage,
30
+ isTransientError,
31
+ } from '../../../workers/lib/error-collector/fingerprint';
32
+
33
+ // ===========================================================================
34
+ // Classification
35
+ // ===========================================================================
36
+
37
+ describe('classifyError', () => {
38
+ it('classifies exception as P0-P2 based on severity', () => {
39
+ const result = classifyError({
40
+ outcome: 'exception',
41
+ scriptName: 'test-worker',
42
+ message: 'Uncaught ReferenceError: foo is not defined',
43
+ logs: [],
44
+ } as never);
45
+
46
+ expect(result).toBeDefined();
47
+ expect(['P0', 'P1', 'P2']).toContain(result.priority);
48
+ });
49
+
50
+ it('classifies exceededCpu as P0', () => {
51
+ const result = classifyError({
52
+ outcome: 'exceededCpu',
53
+ scriptName: 'test-worker',
54
+ message: 'Worker exceeded CPU time limit',
55
+ logs: [],
56
+ } as never);
57
+
58
+ expect(result.priority).toBe('P0');
59
+ });
60
+
61
+ it('classifies canceled as P2', () => {
62
+ const result = classifyError({
63
+ outcome: 'canceled',
64
+ scriptName: 'test-worker',
65
+ message: 'Request was canceled',
66
+ logs: [],
67
+ } as never);
68
+
69
+ expect(result.priority).toBe('P2');
70
+ });
71
+
72
+ it('classifies soft_error from logs as P2-P3', () => {
73
+ const result = classifyError({
74
+ outcome: 'ok',
75
+ scriptName: 'test-worker',
76
+ message: 'Soft error captured via console.error',
77
+ logs: [{ level: 'error', message: ['Something failed'] }],
78
+ } as never);
79
+
80
+ expect(['P2', 'P3']).toContain(result.priority);
81
+ });
82
+ });
83
+
84
+ // ===========================================================================
85
+ // Transient Detection
86
+ // ===========================================================================
87
+
88
+ describe('isTransientError', () => {
89
+ it('detects quota-exhausted as transient', () => {
90
+ expect(isTransientError('D1_ERROR: too many requests (quota exhausted)')).toBe(true);
91
+ });
92
+
93
+ it('detects rate-limited as transient', () => {
94
+ expect(isTransientError('Rate limited: try again in 30 seconds')).toBe(true);
95
+ });
96
+
97
+ it('detects timeout as transient', () => {
98
+ expect(isTransientError('Network request timed out after 30000ms')).toBe(true);
99
+ });
100
+
101
+ it('does not flag regular errors as transient', () => {
102
+ expect(isTransientError('TypeError: Cannot read property of null')).toBe(false);
103
+ });
104
+ });
105
+
106
+ // ===========================================================================
107
+ // Message Normalisation
108
+ // ===========================================================================
109
+
110
+ describe('normalizeMessage', () => {
111
+ it('strips UUIDs from messages', () => {
112
+ const result = normalizeMessage('Error for user a1b2c3d4-e5f6-7890-abcd-ef1234567890');
113
+ expect(result).not.toContain('a1b2c3d4');
114
+ expect(result).toContain('<UUID>');
115
+ });
116
+
117
+ it('strips numeric IDs from messages', () => {
118
+ const result = normalizeMessage('Failed to process request 1234567890');
119
+ expect(result).not.toContain('1234567890');
120
+ });
121
+
122
+ it('strips timestamps from messages', () => {
123
+ const result = normalizeMessage('Error at 2026-03-05T12:00:00Z in handler');
124
+ expect(result).not.toContain('2026-03-05');
125
+ });
126
+
127
+ it('preserves error type prefixes', () => {
128
+ const result = normalizeMessage('TypeError: Cannot read property of null');
129
+ expect(result).toContain('TypeError');
130
+ });
131
+ });
132
+
133
+ // ===========================================================================
134
+ // Fingerprint Stability
135
+ // ===========================================================================
136
+
137
+ describe('generateFingerprint', () => {
138
+ it('produces consistent fingerprint for same error', () => {
139
+ const fp1 = generateFingerprint('test-worker', 'TypeError: Cannot read property of null');
140
+ const fp2 = generateFingerprint('test-worker', 'TypeError: Cannot read property of null');
141
+ expect(fp1).toBe(fp2);
142
+ });
143
+
144
+ it('produces different fingerprints for different errors', () => {
145
+ const fp1 = generateFingerprint('test-worker', 'TypeError: Cannot read property of null');
146
+ const fp2 = generateFingerprint('test-worker', 'RangeError: Maximum call stack exceeded');
147
+ expect(fp1).not.toBe(fp2);
148
+ });
149
+
150
+ it('produces different fingerprints for different workers', () => {
151
+ const fp1 = generateFingerprint('worker-a', 'Error: timeout');
152
+ const fp2 = generateFingerprint('worker-b', 'Error: timeout');
153
+ expect(fp1).not.toBe(fp2);
154
+ });
155
+ });