@littlebearapps/platform-admin-sdk 1.4.2 → 1.5.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 (111) hide show
  1. package/dist/templates.d.ts +1 -1
  2. package/dist/templates.js +121 -2
  3. package/package.json +1 -1
  4. package/templates/full/config/audit-targets.yaml +72 -0
  5. package/templates/full/dashboard/src/components/notifications/NotificationBell.tsx +30 -0
  6. package/templates/full/dashboard/src/components/notifications/NotificationList.tsx +116 -0
  7. package/templates/full/dashboard/src/components/notifications/index.ts +2 -0
  8. package/templates/full/dashboard/src/components/patterns/PatternStats.tsx +60 -0
  9. package/templates/full/dashboard/src/components/patterns/SuggestionsQueue.tsx +115 -0
  10. package/templates/full/dashboard/src/components/patterns/index.ts +2 -0
  11. package/templates/full/dashboard/src/components/search/SearchModal.tsx +108 -0
  12. package/templates/full/dashboard/src/pages/api/notifications/index.ts +47 -0
  13. package/templates/full/dashboard/src/pages/api/notifications/unread-count.ts +31 -0
  14. package/templates/full/dashboard/src/pages/api/patterns/approve.ts +55 -0
  15. package/templates/full/dashboard/src/pages/api/patterns/index.ts +36 -0
  16. package/templates/full/dashboard/src/pages/api/patterns/reject.ts +54 -0
  17. package/templates/full/dashboard/src/pages/api/search/index.ts +74 -0
  18. package/templates/full/dashboard/src/pages/notifications.astro +11 -0
  19. package/templates/full/migrations/008_auditor.sql +99 -0
  20. package/templates/full/migrations/010_pricing_versions.sql +110 -0
  21. package/templates/full/migrations/011_multi_account.sql +51 -0
  22. package/templates/full/scripts/ops/set-kv-pricing.ts +182 -0
  23. package/templates/full/workers/lib/ai-judge-schema.ts +181 -0
  24. package/templates/full/workers/lib/auditor/comprehensive-report.ts +407 -0
  25. package/templates/full/workers/lib/auditor/feature-coverage.ts +348 -0
  26. package/templates/full/workers/lib/auditor/index.ts +9 -0
  27. package/templates/full/workers/lib/auditor/types.ts +167 -0
  28. package/templates/full/workers/platform-auditor.ts +1071 -0
  29. package/templates/full/wrangler.auditor.jsonc.hbs +75 -0
  30. package/templates/shared/.github/workflows/platform-check.yml.hbs +28 -0
  31. package/templates/shared/config/observability.yaml.hbs +276 -0
  32. package/templates/shared/contracts/schemas/envelope.v1.schema.json +64 -0
  33. package/templates/shared/contracts/schemas/error_report.v1.schema.json +65 -0
  34. package/templates/shared/contracts/types/telemetry-envelope.types.ts +139 -0
  35. package/templates/shared/dashboard/astro.config.mjs +21 -0
  36. package/templates/shared/dashboard/package.json.hbs +29 -0
  37. package/templates/shared/dashboard/src/components/Header.astro +29 -0
  38. package/templates/shared/dashboard/src/components/Nav.astro.hbs +57 -0
  39. package/templates/shared/dashboard/src/components/overview/ActivityFeed.tsx +134 -0
  40. package/templates/shared/dashboard/src/components/overview/CostQuadrant.tsx +131 -0
  41. package/templates/shared/dashboard/src/components/overview/ErrorsQuadrant.tsx +113 -0
  42. package/templates/shared/dashboard/src/components/overview/HealthQuadrant.tsx +87 -0
  43. package/templates/shared/dashboard/src/components/overview/MissionControl.tsx +139 -0
  44. package/templates/shared/dashboard/src/components/resources/AllowanceStatus.tsx +44 -0
  45. package/templates/shared/dashboard/src/components/resources/CostCentreOverview.tsx +42 -0
  46. package/templates/shared/dashboard/src/components/resources/ResourceTabs.tsx +69 -0
  47. package/templates/shared/dashboard/src/components/resources/index.ts +3 -0
  48. package/templates/shared/dashboard/src/components/settings/SettingsCard.tsx +21 -0
  49. package/templates/shared/dashboard/src/components/settings/index.ts +1 -0
  50. package/templates/shared/dashboard/src/components/ui/AlertBanner.tsx +39 -0
  51. package/templates/shared/dashboard/src/components/ui/Sparkline.tsx +127 -0
  52. package/templates/shared/dashboard/src/components/ui/StatusDot.tsx +21 -0
  53. package/templates/shared/dashboard/src/components/ui/index.ts +3 -0
  54. package/templates/shared/dashboard/src/env.d.ts.hbs +34 -0
  55. package/templates/shared/dashboard/src/layouts/DashboardLayout.astro +37 -0
  56. package/templates/shared/dashboard/src/lib/fetch.ts +29 -0
  57. package/templates/shared/dashboard/src/lib/types.ts +72 -0
  58. package/templates/shared/dashboard/src/middleware/auth.ts +100 -0
  59. package/templates/shared/dashboard/src/middleware/index.ts +1 -0
  60. package/templates/shared/dashboard/src/pages/api/overview/summary.ts +311 -0
  61. package/templates/shared/dashboard/src/pages/api/usage/circuit-breakers.ts +44 -0
  62. package/templates/shared/dashboard/src/pages/api/usage/status.ts +42 -0
  63. package/templates/shared/dashboard/src/pages/dashboard.astro +11 -0
  64. package/templates/shared/dashboard/src/pages/index.astro +3 -0
  65. package/templates/shared/dashboard/src/pages/resources.astro +11 -0
  66. package/templates/shared/dashboard/src/pages/settings/index.astro +28 -0
  67. package/templates/shared/dashboard/src/styles/global.css +29 -0
  68. package/templates/shared/dashboard/tailwind.config.mjs +9 -0
  69. package/templates/shared/dashboard/tsconfig.json +9 -0
  70. package/templates/shared/dashboard/wrangler.json.hbs +47 -0
  71. package/templates/shared/package.json.hbs +12 -1
  72. package/templates/shared/scripts/ops/backfill-cloudflare-hourly.ts +473 -0
  73. package/templates/shared/scripts/ops/discover-graphql-datasets.ts +482 -0
  74. package/templates/shared/scripts/ops/reset-budget-state.ts +279 -0
  75. package/templates/shared/scripts/ops/validate-pipeline.ts +237 -0
  76. package/templates/shared/scripts/ops/verify-account-completeness.ts +236 -0
  77. package/templates/shared/scripts/validate-schemas.js +61 -0
  78. package/templates/shared/workers/lib/usage/collectors/anthropic.ts +114 -0
  79. package/templates/shared/workers/lib/usage/collectors/apify.ts +96 -0
  80. package/templates/shared/workers/lib/usage/collectors/custom-http.ts +151 -0
  81. package/templates/shared/workers/lib/usage/collectors/deepseek.ts +92 -0
  82. package/templates/shared/workers/lib/usage/collectors/gemini.ts +263 -0
  83. package/templates/shared/workers/lib/usage/collectors/github.ts +362 -0
  84. package/templates/shared/workers/lib/usage/collectors/index.ts +31 -15
  85. package/templates/shared/workers/lib/usage/collectors/minimax.ts +106 -0
  86. package/templates/shared/workers/lib/usage/collectors/openai.ts +171 -0
  87. package/templates/shared/workers/lib/usage/collectors/resend.ts +79 -0
  88. package/templates/shared/workers/lib/usage/collectors/stripe.ts +192 -0
  89. package/templates/shared/workers/lib/usage/shared/types.ts +46 -0
  90. package/templates/shared/workers/platform-usage.ts +98 -8
  91. package/templates/standard/dashboard/src/components/errors/ErrorStats.tsx +53 -0
  92. package/templates/standard/dashboard/src/components/errors/ErrorsTable.tsx +133 -0
  93. package/templates/standard/dashboard/src/components/errors/index.ts +2 -0
  94. package/templates/standard/dashboard/src/components/health/DlqStatusCard.tsx +52 -0
  95. package/templates/standard/dashboard/src/components/health/HealthTabs.tsx +86 -0
  96. package/templates/standard/dashboard/src/components/health/index.ts +2 -0
  97. package/templates/standard/dashboard/src/lib/errors.ts +28 -0
  98. package/templates/standard/dashboard/src/pages/api/errors/index.ts +58 -0
  99. package/templates/standard/dashboard/src/pages/api/errors/stats.ts +55 -0
  100. package/templates/standard/dashboard/src/pages/api/health/dlq.ts +43 -0
  101. package/templates/standard/dashboard/src/pages/errors.astro +13 -0
  102. package/templates/standard/dashboard/src/pages/health.astro +11 -0
  103. package/templates/standard/migrations/009_topology_mapper.sql +65 -0
  104. package/templates/standard/workers/lib/error-collector/email-health-alerts.ts +37 -3
  105. package/templates/standard/workers/lib/error-collector/gap-alerts.ts +32 -1
  106. package/templates/standard/workers/lib/mapper/attribution-check.ts +339 -0
  107. package/templates/standard/workers/lib/mapper/index.ts +7 -0
  108. package/templates/standard/workers/platform-mapper.ts +482 -0
  109. package/templates/standard/workers/platform-sdk-test-client.ts +125 -0
  110. package/templates/standard/wrangler.mapper.jsonc.hbs +85 -0
  111. package/templates/standard/wrangler.sdk-test-client.jsonc.hbs +62 -0
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Attribution Check Module for Platform Mapper
3
+ *
4
+ * Checks whether discovered Cloudflare resources are properly attributed to projects.
5
+ * Runs every 15 minutes as part of platform-mapper's scheduled handler.
6
+ *
7
+ * @module workers/lib/mapper/attribution-check
8
+ */
9
+
10
+ import type { Logger } from '@littlebearapps/platform-consumer-sdk';
11
+
12
+ /**
13
+ * Environment bindings required for attribution checking.
14
+ * Uses generic types to work with both raw Env and TrackedEnv.
15
+ */
16
+ export interface AttributionEnv {
17
+ PLATFORM_DB: {
18
+ prepare: (query: string) => {
19
+ bind: (...args: unknown[]) => {
20
+ run: () => Promise<unknown>;
21
+ first: <T>() => Promise<T | null>;
22
+ };
23
+ first: <T>() => Promise<T | null>;
24
+ };
25
+ };
26
+ PLATFORM_CACHE: {
27
+ get: (key: string) => Promise<string | null>;
28
+ put: (key: string, value: string, options?: { expirationTtl?: number }) => Promise<void>;
29
+ };
30
+ SERVICE_REGISTRY: {
31
+ get: (key: string) => Promise<string | null>;
32
+ };
33
+ SLACK_WEBHOOK_URL?: string;
34
+ }
35
+
36
+ /**
37
+ * Discovered resource
38
+ */
39
+ export interface DiscoveredResource {
40
+ resourceType: string; // worker, d1, kv, r2, queue, do
41
+ resourceId: string;
42
+ resourceName: string;
43
+ metadata?: Record<string, unknown>;
44
+ }
45
+
46
+ /**
47
+ * Attribution result for a single resource
48
+ */
49
+ export interface ResourceAttribution {
50
+ resource: DiscoveredResource;
51
+ project: string | null;
52
+ matchedPattern: string | null;
53
+ confidence: 'high' | 'medium' | 'low' | 'none';
54
+ }
55
+
56
+ /**
57
+ * Full attribution report
58
+ */
59
+ export interface AttributionReport {
60
+ discoveryTime: string;
61
+ attributed: ResourceAttribution[];
62
+ unattributed: ResourceAttribution[];
63
+ totalResources: number;
64
+ attributedCount: number;
65
+ unattributedCount: number;
66
+ }
67
+
68
+ /**
69
+ * Project patterns for attribution.
70
+ * Maps resource name patterns to projects.
71
+ *
72
+ * TODO: Add your project patterns here. Each key is a project name,
73
+ * and the value is an array of RegExp patterns that match resource names
74
+ * belonging to that project.
75
+ *
76
+ * Example:
77
+ * 'my-app': [/^my-app-/i, /my-app-db/i, /my-app-kv/i],
78
+ * 'api': [/^api-/i, /api-worker/i],
79
+ */
80
+ const PROJECT_PATTERNS: Record<string, RegExp[]> = {
81
+ // Add your project patterns here:
82
+ // 'project-name': [/^project-name-/i, /project-name-db/i],
83
+ };
84
+
85
+ /**
86
+ * Resources to exclude from attribution (system/shared resources).
87
+ *
88
+ * TODO: Adjust these patterns to match resources you want to skip.
89
+ */
90
+ const EXCLUDED_PATTERNS: RegExp[] = [
91
+ /^_/i, // Internal resources
92
+ /test/i, // Test resources
93
+ /dev-/i, // Dev resources
94
+ /staging/i, // Staging resources
95
+ ];
96
+
97
+ /**
98
+ * Check attribution of discovered resources
99
+ */
100
+ export function checkAttribution(resources: DiscoveredResource[]): AttributionReport {
101
+ const report: AttributionReport = {
102
+ discoveryTime: new Date().toISOString(),
103
+ attributed: [],
104
+ unattributed: [],
105
+ totalResources: 0,
106
+ attributedCount: 0,
107
+ unattributedCount: 0,
108
+ };
109
+
110
+ for (const resource of resources) {
111
+ // Skip excluded resources
112
+ if (EXCLUDED_PATTERNS.some((pattern) => pattern.test(resource.resourceName))) {
113
+ continue;
114
+ }
115
+
116
+ report.totalResources++;
117
+ const attribution = attributeResource(resource);
118
+
119
+ if (attribution.project) {
120
+ report.attributed.push(attribution);
121
+ report.attributedCount++;
122
+ } else {
123
+ report.unattributed.push(attribution);
124
+ report.unattributedCount++;
125
+ }
126
+ }
127
+
128
+ return report;
129
+ }
130
+
131
+ /**
132
+ * Attribute a single resource to a project
133
+ */
134
+ function attributeResource(resource: DiscoveredResource): ResourceAttribution {
135
+ for (const [project, patterns] of Object.entries(PROJECT_PATTERNS)) {
136
+ for (const pattern of patterns) {
137
+ if (pattern.test(resource.resourceName)) {
138
+ return {
139
+ resource,
140
+ project,
141
+ matchedPattern: pattern.source,
142
+ confidence: 'high',
143
+ };
144
+ }
145
+ }
146
+ }
147
+
148
+ // Try to infer from resource name structure
149
+ const suggestedProject = suggestProject(resource.resourceName);
150
+ if (suggestedProject) {
151
+ return {
152
+ resource,
153
+ project: suggestedProject,
154
+ matchedPattern: 'name_inference',
155
+ confidence: 'low',
156
+ };
157
+ }
158
+
159
+ return {
160
+ resource,
161
+ project: null,
162
+ matchedPattern: null,
163
+ confidence: 'none',
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Suggest a project from resource name using heuristics.
169
+ *
170
+ * TODO: Update this function to match your project naming conventions.
171
+ * The heuristics below look for common substrings in resource names.
172
+ */
173
+ function suggestProject(resourceName: string): string | null {
174
+ const lowerName = resourceName.toLowerCase();
175
+
176
+ // Check against PROJECT_PATTERNS keys as substring match
177
+ for (const project of Object.keys(PROJECT_PATTERNS)) {
178
+ if (lowerName.includes(project.toLowerCase())) {
179
+ return project;
180
+ }
181
+ }
182
+
183
+ return null;
184
+ }
185
+
186
+ /**
187
+ * Store attribution report in D1
188
+ */
189
+ export async function storeAttributionReport(
190
+ env: AttributionEnv,
191
+ report: AttributionReport,
192
+ log: Logger
193
+ ): Promise<void> {
194
+ try {
195
+ const id = crypto.randomUUID();
196
+
197
+ await env.PLATFORM_DB.prepare(
198
+ `
199
+ INSERT INTO attribution_reports (id, discovery_time, total_resources, attributed_count, unattributed_count, report_json)
200
+ VALUES (?, ?, ?, ?, ?, ?)
201
+ `
202
+ )
203
+ .bind(
204
+ id,
205
+ report.discoveryTime,
206
+ report.totalResources,
207
+ report.attributedCount,
208
+ report.unattributedCount,
209
+ JSON.stringify(report)
210
+ )
211
+ .run();
212
+
213
+ log.debug('Stored attribution report', {
214
+ id,
215
+ total: report.totalResources,
216
+ unattributed: report.unattributedCount,
217
+ });
218
+ } catch (error) {
219
+ log.error('Failed to store attribution report', error);
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Alert on unattributed resources via Slack
225
+ */
226
+ export async function alertUnattributedResources(
227
+ env: AttributionEnv,
228
+ report: AttributionReport,
229
+ log: Logger
230
+ ): Promise<void> {
231
+ // Only alert if there are unattributed resources
232
+ if (report.unattributedCount === 0) {
233
+ return;
234
+ }
235
+
236
+ // Check rate limit (1 alert per 4 hours)
237
+ const alertKey = 'attribution:alert';
238
+ const alreadySent = await env.PLATFORM_CACHE.get(alertKey);
239
+
240
+ if (alreadySent) {
241
+ log.debug('Attribution alert rate limited');
242
+ return;
243
+ }
244
+
245
+ if (!env.SLACK_WEBHOOK_URL) {
246
+ log.debug('No SLACK_WEBHOOK_URL configured, skipping attribution alert');
247
+ return;
248
+ }
249
+
250
+ // Build summary of unattributed resources
251
+ const summary = report.unattributed
252
+ .slice(0, 10)
253
+ .map((a) => `${a.resource.resourceType}: ${a.resource.resourceName}`)
254
+ .join('\n');
255
+
256
+ const message = {
257
+ text: `[WARNING] ${report.unattributedCount} Cloudflare resources not attributed to projects`,
258
+ blocks: [
259
+ {
260
+ type: 'header',
261
+ text: {
262
+ type: 'plain_text',
263
+ text: 'Unattributed Resources Detected',
264
+ },
265
+ },
266
+ {
267
+ type: 'section',
268
+ fields: [
269
+ { type: 'mrkdwn', text: `*Total Resources:*\n${report.totalResources}` },
270
+ { type: 'mrkdwn', text: `*Attributed:*\n${report.attributedCount}` },
271
+ { type: 'mrkdwn', text: `*Unattributed:*\n${report.unattributedCount}` },
272
+ { type: 'mrkdwn', text: `*Discovery Time:*\n${report.discoveryTime}` },
273
+ ],
274
+ },
275
+ {
276
+ type: 'section',
277
+ text: {
278
+ type: 'mrkdwn',
279
+ text: `*Unattributed Resources (first 10):*\n\`\`\`${summary}\`\`\``,
280
+ },
281
+ },
282
+ {
283
+ type: 'section',
284
+ text: {
285
+ type: 'mrkdwn',
286
+ text: `*Action Required:*\nUpdate PROJECT_PATTERNS in \`workers/lib/mapper/attribution-check.ts\` to include patterns for these resources.`,
287
+ },
288
+ },
289
+ ],
290
+ };
291
+
292
+ try {
293
+ const response = await fetch(env.SLACK_WEBHOOK_URL, {
294
+ method: 'POST',
295
+ headers: { 'Content-Type': 'application/json' },
296
+ body: JSON.stringify(message),
297
+ });
298
+
299
+ if (response.ok) {
300
+ // Set rate limit (4 hours)
301
+ await env.PLATFORM_CACHE.put(alertKey, new Date().toISOString(), {
302
+ expirationTtl: 14400,
303
+ });
304
+ log.info('Sent attribution alert', { unattributed: report.unattributedCount });
305
+ } else {
306
+ const text = await response.text();
307
+ log.error('Failed to send attribution alert', undefined, { status: response.status, error: text });
308
+ }
309
+ } catch (error) {
310
+ log.error('Error sending attribution alert', error);
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Get latest attribution report from D1
316
+ */
317
+ export async function getLatestAttributionReport(
318
+ env: AttributionEnv,
319
+ log: Logger
320
+ ): Promise<AttributionReport | null> {
321
+ try {
322
+ const result = await env.PLATFORM_DB.prepare(
323
+ `
324
+ SELECT report_json
325
+ FROM attribution_reports
326
+ ORDER BY discovery_time DESC
327
+ LIMIT 1
328
+ `
329
+ ).first<{ report_json: string }>();
330
+
331
+ if (result?.report_json) {
332
+ return JSON.parse(result.report_json) as AttributionReport;
333
+ }
334
+ return null;
335
+ } catch (error) {
336
+ log.error('Failed to get latest attribution report', error);
337
+ return null;
338
+ }
339
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Mapper Module Exports
3
+ *
4
+ * Barrel export for all mapper modules.
5
+ */
6
+
7
+ export * from './attribution-check';