@littlebearapps/platform-admin-sdk 1.4.2 → 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 (189) hide show
  1. package/dist/templates.d.ts +1 -1
  2. package/dist/templates.js +232 -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/ActivePatterns.tsx +62 -0
  9. package/templates/full/dashboard/src/components/patterns/PatternStats.tsx +60 -0
  10. package/templates/full/dashboard/src/components/patterns/PatternTabs.tsx +116 -0
  11. package/templates/full/dashboard/src/components/patterns/SuggestionsQueue.tsx +115 -0
  12. package/templates/full/dashboard/src/components/patterns/SystemPatterns.tsx +52 -0
  13. package/templates/full/dashboard/src/components/patterns/index.ts +5 -0
  14. package/templates/full/dashboard/src/components/reports/GapDetectionReport.tsx +69 -0
  15. package/templates/full/dashboard/src/components/reports/SdkAuditReport.tsx +72 -0
  16. package/templates/full/dashboard/src/components/reports/index.ts +2 -0
  17. package/templates/full/dashboard/src/components/search/SearchModal.tsx +108 -0
  18. package/templates/full/dashboard/src/pages/api/notifications/[id]/read.ts +37 -0
  19. package/templates/full/dashboard/src/pages/api/notifications/index.ts +47 -0
  20. package/templates/full/dashboard/src/pages/api/notifications/read-all.ts +28 -0
  21. package/templates/full/dashboard/src/pages/api/notifications/unread-count.ts +31 -0
  22. package/templates/full/dashboard/src/pages/api/patterns/approve.ts +55 -0
  23. package/templates/full/dashboard/src/pages/api/patterns/cache-refresh.ts +38 -0
  24. package/templates/full/dashboard/src/pages/api/patterns/discover.ts +36 -0
  25. package/templates/full/dashboard/src/pages/api/patterns/index.ts +36 -0
  26. package/templates/full/dashboard/src/pages/api/patterns/ready-for-review.ts +39 -0
  27. package/templates/full/dashboard/src/pages/api/patterns/reject.ts +54 -0
  28. package/templates/full/dashboard/src/pages/api/patterns/stats.ts +39 -0
  29. package/templates/full/dashboard/src/pages/api/patterns/suggestions.ts +43 -0
  30. package/templates/full/dashboard/src/pages/api/reports/audit.ts +45 -0
  31. package/templates/full/dashboard/src/pages/api/reports/usage.ts +52 -0
  32. package/templates/full/dashboard/src/pages/api/search/index.ts +74 -0
  33. package/templates/full/dashboard/src/pages/api/search/reindex.ts +28 -0
  34. package/templates/full/dashboard/src/pages/api/search/stats.ts +27 -0
  35. package/templates/full/dashboard/src/pages/api/settings/index.ts +37 -0
  36. package/templates/full/dashboard/src/pages/api/settings/update.ts +41 -0
  37. package/templates/full/dashboard/src/pages/api/topology/index.ts +56 -0
  38. package/templates/full/dashboard/src/pages/notifications.astro +11 -0
  39. package/templates/full/migrations/008_auditor.sql +99 -0
  40. package/templates/full/migrations/010_pricing_versions.sql +110 -0
  41. package/templates/full/migrations/011_multi_account.sql +51 -0
  42. package/templates/full/scripts/ops/set-kv-pricing.ts +182 -0
  43. package/templates/full/scripts/ops/universal-backfill.ts +147 -0
  44. package/templates/full/workers/lib/ai-judge-schema.ts +181 -0
  45. package/templates/full/workers/lib/auditor/comprehensive-report.ts +407 -0
  46. package/templates/full/workers/lib/auditor/feature-coverage.ts +348 -0
  47. package/templates/full/workers/lib/auditor/index.ts +9 -0
  48. package/templates/full/workers/lib/auditor/types.ts +167 -0
  49. package/templates/full/workers/platform-auditor.ts +1071 -0
  50. package/templates/full/wrangler.auditor.jsonc.hbs +75 -0
  51. package/templates/shared/.github/workflows/contract-check.yml.hbs +42 -0
  52. package/templates/shared/.github/workflows/dashboard-deploy.yml.hbs +39 -0
  53. package/templates/shared/.github/workflows/platform-check.yml.hbs +28 -0
  54. package/templates/shared/.github/workflows/security.yml +33 -0
  55. package/templates/shared/config/observability.yaml.hbs +276 -0
  56. package/templates/shared/contracts/schemas/envelope.v1.schema.json +64 -0
  57. package/templates/shared/contracts/schemas/error_report.v1.schema.json +65 -0
  58. package/templates/shared/contracts/types/telemetry-envelope.types.ts +139 -0
  59. package/templates/shared/dashboard/astro.config.mjs +21 -0
  60. package/templates/shared/dashboard/package.json.hbs +29 -0
  61. package/templates/shared/dashboard/src/components/Header.astro +29 -0
  62. package/templates/shared/dashboard/src/components/Nav.astro.hbs +59 -0
  63. package/templates/shared/dashboard/src/components/infrastructure/AlertHistory.tsx +57 -0
  64. package/templates/shared/dashboard/src/components/infrastructure/InfrastructureStats.tsx +73 -0
  65. package/templates/shared/dashboard/src/components/infrastructure/ServiceRegistry.tsx +55 -0
  66. package/templates/shared/dashboard/src/components/infrastructure/UptimeStatus.tsx +56 -0
  67. package/templates/shared/dashboard/src/components/infrastructure/index.ts +4 -0
  68. package/templates/shared/dashboard/src/components/overview/ActivityFeed.tsx +134 -0
  69. package/templates/shared/dashboard/src/components/overview/CostQuadrant.tsx +131 -0
  70. package/templates/shared/dashboard/src/components/overview/ErrorsQuadrant.tsx +113 -0
  71. package/templates/shared/dashboard/src/components/overview/HealthQuadrant.tsx +87 -0
  72. package/templates/shared/dashboard/src/components/overview/MissionControl.tsx +139 -0
  73. package/templates/shared/dashboard/src/components/resources/AllowanceStatus.tsx +44 -0
  74. package/templates/shared/dashboard/src/components/resources/CostCentreOverview.tsx +42 -0
  75. package/templates/shared/dashboard/src/components/resources/ResourceTabs.tsx +69 -0
  76. package/templates/shared/dashboard/src/components/resources/index.ts +3 -0
  77. package/templates/shared/dashboard/src/components/settings/SettingsCard.tsx +21 -0
  78. package/templates/shared/dashboard/src/components/settings/index.ts +1 -0
  79. package/templates/shared/dashboard/src/components/ui/AlertBanner.tsx +39 -0
  80. package/templates/shared/dashboard/src/components/ui/Breadcrumbs.tsx +27 -0
  81. package/templates/shared/dashboard/src/components/ui/EmptyState.tsx +26 -0
  82. package/templates/shared/dashboard/src/components/ui/ErrorBoundary.tsx +42 -0
  83. package/templates/shared/dashboard/src/components/ui/LoadingSkeleton.tsx +18 -0
  84. package/templates/shared/dashboard/src/components/ui/PageShell.tsx +26 -0
  85. package/templates/shared/dashboard/src/components/ui/Sparkline.tsx +127 -0
  86. package/templates/shared/dashboard/src/components/ui/StatusDot.tsx +21 -0
  87. package/templates/shared/dashboard/src/components/ui/Toast.tsx +44 -0
  88. package/templates/shared/dashboard/src/components/ui/index.ts +9 -0
  89. package/templates/shared/dashboard/src/components/usage/AnomaliesWidget.tsx +68 -0
  90. package/templates/shared/dashboard/src/components/usage/HourlyUsageChart.tsx +55 -0
  91. package/templates/shared/dashboard/src/components/usage/PlanAllowanceDashboard.tsx +67 -0
  92. package/templates/shared/dashboard/src/components/usage/ProjectCostBreakdown.tsx +55 -0
  93. package/templates/shared/dashboard/src/components/usage/index.ts +4 -0
  94. package/templates/shared/dashboard/src/env.d.ts.hbs +34 -0
  95. package/templates/shared/dashboard/src/layouts/DashboardLayout.astro +37 -0
  96. package/templates/shared/dashboard/src/lib/cloudflare/costs.ts +21 -0
  97. package/templates/shared/dashboard/src/lib/fetch.ts +29 -0
  98. package/templates/shared/dashboard/src/lib/types.ts +72 -0
  99. package/templates/shared/dashboard/src/middleware/auth.ts +100 -0
  100. package/templates/shared/dashboard/src/middleware/index.ts +1 -0
  101. package/templates/shared/dashboard/src/pages/api/costs/overview.ts +65 -0
  102. package/templates/shared/dashboard/src/pages/api/costs/providers.ts +47 -0
  103. package/templates/shared/dashboard/src/pages/api/infrastructure/services.ts +55 -0
  104. package/templates/shared/dashboard/src/pages/api/infrastructure/stats.ts +99 -0
  105. package/templates/shared/dashboard/src/pages/api/overview/summary.ts +311 -0
  106. package/templates/shared/dashboard/src/pages/api/usage/allowances.ts +56 -0
  107. package/templates/shared/dashboard/src/pages/api/usage/anomalies.ts +45 -0
  108. package/templates/shared/dashboard/src/pages/api/usage/billing.ts +53 -0
  109. package/templates/shared/dashboard/src/pages/api/usage/circuit-breakers.ts +44 -0
  110. package/templates/shared/dashboard/src/pages/api/usage/granular.ts +50 -0
  111. package/templates/shared/dashboard/src/pages/api/usage/hourly.ts +45 -0
  112. package/templates/shared/dashboard/src/pages/api/usage/projects.ts +51 -0
  113. package/templates/shared/dashboard/src/pages/api/usage/status.ts +42 -0
  114. package/templates/shared/dashboard/src/pages/api/user/identity.ts +11 -0
  115. package/templates/shared/dashboard/src/pages/dashboard.astro +11 -0
  116. package/templates/shared/dashboard/src/pages/index.astro +3 -0
  117. package/templates/shared/dashboard/src/pages/resources.astro +11 -0
  118. package/templates/shared/dashboard/src/pages/settings/index.astro +28 -0
  119. package/templates/shared/dashboard/src/pages/settings/notifications.astro +34 -0
  120. package/templates/shared/dashboard/src/pages/settings/thresholds.astro +39 -0
  121. package/templates/shared/dashboard/src/pages/settings/usage.astro +28 -0
  122. package/templates/shared/dashboard/src/styles/global.css +29 -0
  123. package/templates/shared/dashboard/tailwind.config.mjs +9 -0
  124. package/templates/shared/dashboard/tsconfig.json +9 -0
  125. package/templates/shared/dashboard/wrangler.json.hbs +47 -0
  126. package/templates/shared/docs/architecture.md +89 -0
  127. package/templates/shared/docs/post-deploy-runbook.md +126 -0
  128. package/templates/shared/docs/troubleshooting.md +91 -0
  129. package/templates/shared/package.json.hbs +17 -1
  130. package/templates/shared/scripts/ops/backfill-cloudflare-daily.ts +145 -0
  131. package/templates/shared/scripts/ops/backfill-cloudflare-hourly.ts +473 -0
  132. package/templates/shared/scripts/ops/backfill-monthly-rollups.ts +125 -0
  133. package/templates/shared/scripts/ops/discover-graphql-datasets.ts +482 -0
  134. package/templates/shared/scripts/ops/reset-budget-state.ts +279 -0
  135. package/templates/shared/scripts/ops/validate-controls.js +141 -0
  136. package/templates/shared/scripts/ops/validate-pipeline.ts +237 -0
  137. package/templates/shared/scripts/ops/verify-account-completeness.ts +236 -0
  138. package/templates/shared/scripts/validate-schemas.js +61 -0
  139. package/templates/shared/tests/contract/validate-schemas.test.ts +130 -0
  140. package/templates/shared/tests/fixtures/telemetry-envelope-invalid.json +9 -0
  141. package/templates/shared/tests/fixtures/telemetry-envelope-valid.json +27 -0
  142. package/templates/shared/tests/helpers/mock-d1.ts +61 -0
  143. package/templates/shared/tests/helpers/mock-kv.ts +37 -0
  144. package/templates/shared/tests/unit/workers/batch-persistence.test.ts +133 -0
  145. package/templates/shared/tests/unit/workers/budget-enforcement.test.ts +214 -0
  146. package/templates/shared/vitest.config.ts +18 -0
  147. package/templates/shared/workers/lib/usage/collectors/anthropic.ts +114 -0
  148. package/templates/shared/workers/lib/usage/collectors/apify.ts +96 -0
  149. package/templates/shared/workers/lib/usage/collectors/custom-http.ts +151 -0
  150. package/templates/shared/workers/lib/usage/collectors/deepseek.ts +92 -0
  151. package/templates/shared/workers/lib/usage/collectors/gemini.ts +263 -0
  152. package/templates/shared/workers/lib/usage/collectors/github.ts +362 -0
  153. package/templates/shared/workers/lib/usage/collectors/index.ts +31 -15
  154. package/templates/shared/workers/lib/usage/collectors/minimax.ts +106 -0
  155. package/templates/shared/workers/lib/usage/collectors/openai.ts +171 -0
  156. package/templates/shared/workers/lib/usage/collectors/resend.ts +79 -0
  157. package/templates/shared/workers/lib/usage/collectors/stripe.ts +192 -0
  158. package/templates/shared/workers/lib/usage/shared/types.ts +46 -0
  159. package/templates/shared/workers/platform-usage.ts +98 -8
  160. package/templates/standard/dashboard/src/components/errors/ErrorStats.tsx +53 -0
  161. package/templates/standard/dashboard/src/components/errors/ErrorsTable.tsx +133 -0
  162. package/templates/standard/dashboard/src/components/errors/index.ts +2 -0
  163. package/templates/standard/dashboard/src/components/health/CircuitBreakerEvents.tsx +69 -0
  164. package/templates/standard/dashboard/src/components/health/CircuitBreakerPanel.tsx +97 -0
  165. package/templates/standard/dashboard/src/components/health/DlqStatusCard.tsx +52 -0
  166. package/templates/standard/dashboard/src/components/health/HealthTabs.tsx +86 -0
  167. package/templates/standard/dashboard/src/components/health/index.ts +4 -0
  168. package/templates/standard/dashboard/src/lib/errors.ts +28 -0
  169. package/templates/standard/dashboard/src/pages/api/errors/[fingerprint]/mute.ts +49 -0
  170. package/templates/standard/dashboard/src/pages/api/errors/[fingerprint]/resolve.ts +36 -0
  171. package/templates/standard/dashboard/src/pages/api/errors/[fingerprint].ts +55 -0
  172. package/templates/standard/dashboard/src/pages/api/errors/index.ts +58 -0
  173. package/templates/standard/dashboard/src/pages/api/errors/stats.ts +55 -0
  174. package/templates/standard/dashboard/src/pages/api/health/audit-history.ts +37 -0
  175. package/templates/standard/dashboard/src/pages/api/health/dlq.ts +43 -0
  176. package/templates/standard/dashboard/src/pages/circuit-breakers.astro +13 -0
  177. package/templates/standard/dashboard/src/pages/errors.astro +13 -0
  178. package/templates/standard/dashboard/src/pages/health.astro +11 -0
  179. package/templates/standard/migrations/009_topology_mapper.sql +65 -0
  180. package/templates/standard/tests/unit/error-collector/capture.test.ts +106 -0
  181. package/templates/standard/tests/unit/error-collector/fingerprint.test.ts +155 -0
  182. package/templates/standard/workers/lib/error-collector/email-health-alerts.ts +37 -3
  183. package/templates/standard/workers/lib/error-collector/gap-alerts.ts +32 -1
  184. package/templates/standard/workers/lib/mapper/attribution-check.ts +339 -0
  185. package/templates/standard/workers/lib/mapper/index.ts +7 -0
  186. package/templates/standard/workers/platform-mapper.ts +482 -0
  187. package/templates/standard/workers/platform-sdk-test-client.ts +125 -0
  188. package/templates/standard/wrangler.mapper.jsonc.hbs +85 -0
  189. package/templates/standard/wrangler.sdk-test-client.jsonc.hbs +62 -0
@@ -0,0 +1,348 @@
1
+ /**
2
+ * Feature Coverage Audit Module
3
+ *
4
+ * Audits which features from budgets.yaml are actively reporting vs dormant.
5
+ * Compares defined features in CONFIG:BUDGETS (KV) against actual heartbeats
6
+ * in system_health_checks (D1).
7
+ *
8
+ * Statuses:
9
+ * active — Heartbeats in last 7 days and events_count > 0
10
+ * dormant — Defined in config but no recent heartbeats
11
+ * undefined — Reporting telemetry but not in budgets.yaml
12
+ *
13
+ * @module workers/lib/auditor/feature-coverage
14
+ */
15
+
16
+ import type { Logger } from '@littlebearapps/platform-consumer-sdk';
17
+
18
+ // =============================================================================
19
+ // Types
20
+ // =============================================================================
21
+
22
+ /** Environment bindings (generic to work with both raw Env and TrackedEnv) */
23
+ export interface FeatureCoverageEnv {
24
+ PLATFORM_DB: {
25
+ prepare: (query: string) => {
26
+ bind: (...args: unknown[]) => {
27
+ run: () => Promise<unknown>;
28
+ all: <T>() => Promise<{ results?: T[] }>;
29
+ first: <T>() => Promise<T | null>;
30
+ };
31
+ all: <T>() => Promise<{ results?: T[] }>;
32
+ first: <T>() => Promise<T | null>;
33
+ };
34
+ };
35
+ PLATFORM_CACHE: {
36
+ get: (key: string) => Promise<string | null>;
37
+ put: (key: string, value: string, options?: { expirationTtl?: number }) => Promise<void>;
38
+ };
39
+ }
40
+
41
+ export type FeatureStatus = 'active' | 'dormant' | 'undefined';
42
+
43
+ export interface FeatureCoverageEntry {
44
+ project: string;
45
+ feature: string;
46
+ featureKey: string;
47
+ status: FeatureStatus;
48
+ lastHeartbeat: string | null;
49
+ eventsLast7d: number;
50
+ definedBudget: number | null;
51
+ budgetUnit: string | null;
52
+ }
53
+
54
+ export interface FeatureCoverageReport {
55
+ auditTime: string;
56
+ entries: FeatureCoverageEntry[];
57
+ summary: {
58
+ totalDefined: number;
59
+ active: number;
60
+ dormant: number;
61
+ undefined: number;
62
+ };
63
+ byProject: Record<string, { active: number; dormant: number; undefined: number }>;
64
+ }
65
+
66
+ interface BudgetConfig {
67
+ features: Record<
68
+ string,
69
+ Record<string, Record<string, { budget: number; unit: string; period: string }>>
70
+ >;
71
+ }
72
+
73
+ // =============================================================================
74
+ // Main Audit Function
75
+ // =============================================================================
76
+
77
+ /**
78
+ * Run feature coverage audit.
79
+ * Compares defined features in CONFIG:BUDGETS against actual heartbeats.
80
+ */
81
+ export async function runFeatureCoverageAudit(
82
+ env: FeatureCoverageEnv,
83
+ log: Logger
84
+ ): Promise<FeatureCoverageReport> {
85
+ const auditTime = new Date().toISOString();
86
+ const entries: FeatureCoverageEntry[] = [];
87
+
88
+ try {
89
+ const definedFeatures = await loadDefinedFeatures(env, log);
90
+ const activeFeatures = await getActiveFeatures(env, log);
91
+ const undefinedFeatures = await getUndefinedFeatures(env, definedFeatures, log);
92
+
93
+ // Build coverage entries for defined features
94
+ for (const [featureKey, feature] of Object.entries(definedFeatures)) {
95
+ const active = activeFeatures.get(featureKey);
96
+ const isActive = !!active && active.eventsLast7d > 0;
97
+
98
+ entries.push({
99
+ project: feature.project,
100
+ feature: feature.feature,
101
+ featureKey,
102
+ status: isActive ? 'active' : 'dormant',
103
+ lastHeartbeat: active?.lastHeartbeat ?? null,
104
+ eventsLast7d: active?.eventsLast7d ?? 0,
105
+ definedBudget: feature.budget,
106
+ budgetUnit: feature.unit,
107
+ });
108
+ }
109
+
110
+ // Add undefined features (reporting but not in config)
111
+ for (const feature of undefinedFeatures) {
112
+ entries.push({
113
+ project: feature.project,
114
+ feature: feature.feature,
115
+ featureKey: feature.featureKey,
116
+ status: 'undefined',
117
+ lastHeartbeat: feature.lastHeartbeat,
118
+ eventsLast7d: feature.eventsLast7d,
119
+ definedBudget: null,
120
+ budgetUnit: null,
121
+ });
122
+ }
123
+
124
+ const summary = {
125
+ totalDefined: Object.keys(definedFeatures).length,
126
+ active: entries.filter((e) => e.status === 'active').length,
127
+ dormant: entries.filter((e) => e.status === 'dormant').length,
128
+ undefined: entries.filter((e) => e.status === 'undefined').length,
129
+ };
130
+
131
+ const byProject: Record<string, { active: number; dormant: number; undefined: number }> = {};
132
+ for (const entry of entries) {
133
+ if (!byProject[entry.project]) {
134
+ byProject[entry.project] = { active: 0, dormant: 0, undefined: 0 };
135
+ }
136
+ byProject[entry.project][entry.status]++;
137
+ }
138
+
139
+ log.info('Feature coverage audit complete', {
140
+ defined: summary.totalDefined,
141
+ active: summary.active,
142
+ dormant: summary.dormant,
143
+ undefined: summary.undefined,
144
+ });
145
+
146
+ return { auditTime, entries, summary, byProject };
147
+ } catch (error) {
148
+ log.error('Feature coverage audit failed', error);
149
+ return {
150
+ auditTime,
151
+ entries: [],
152
+ summary: { totalDefined: 0, active: 0, dormant: 0, undefined: 0 },
153
+ byProject: {},
154
+ };
155
+ }
156
+ }
157
+
158
+ // =============================================================================
159
+ // Internal Helpers
160
+ // =============================================================================
161
+
162
+ /** Load defined features from CONFIG:BUDGETS in KV */
163
+ async function loadDefinedFeatures(
164
+ env: FeatureCoverageEnv,
165
+ log: Logger
166
+ ): Promise<
167
+ Record<
168
+ string,
169
+ { project: string; category: string; feature: string; budget: number; unit: string }
170
+ >
171
+ > {
172
+ const features: Record<
173
+ string,
174
+ { project: string; category: string; feature: string; budget: number; unit: string }
175
+ > = {};
176
+
177
+ try {
178
+ const budgetsJson = await env.PLATFORM_CACHE.get('CONFIG:BUDGETS');
179
+ if (!budgetsJson) {
180
+ log.warn('CONFIG:BUDGETS not found in KV');
181
+ return features;
182
+ }
183
+
184
+ const config = JSON.parse(budgetsJson) as BudgetConfig;
185
+
186
+ for (const [project, categories] of Object.entries(config.features || {})) {
187
+ for (const [category, categoryFeatures] of Object.entries(categories || {})) {
188
+ for (const [featureName, featureConfig] of Object.entries(categoryFeatures || {})) {
189
+ const featureKey = `${project}:${category}:${featureName}`;
190
+ features[featureKey] = {
191
+ project,
192
+ category,
193
+ feature: featureName,
194
+ budget: featureConfig.budget,
195
+ unit: featureConfig.unit,
196
+ };
197
+ }
198
+ }
199
+ }
200
+
201
+ log.debug('Loaded defined features', { count: Object.keys(features).length });
202
+ } catch (error) {
203
+ log.error('Failed to load defined features', error);
204
+ }
205
+
206
+ return features;
207
+ }
208
+
209
+ /** Get active features from system_health_checks (last 7 days) */
210
+ async function getActiveFeatures(
211
+ env: FeatureCoverageEnv,
212
+ log: Logger
213
+ ): Promise<Map<string, { lastHeartbeat: string; eventsLast7d: number }>> {
214
+ const active = new Map<string, { lastHeartbeat: string; eventsLast7d: number }>();
215
+
216
+ try {
217
+ const result = await env.PLATFORM_DB.prepare(
218
+ `
219
+ SELECT
220
+ feature_id AS feature_key,
221
+ MAX(datetime(last_heartbeat, 'unixepoch')) as last_heartbeat,
222
+ COUNT(*) as events_count
223
+ FROM system_health_checks
224
+ WHERE last_heartbeat > unixepoch('now', '-7 days')
225
+ GROUP BY feature_id
226
+ `
227
+ ).all<{ feature_key: string; last_heartbeat: string; events_count: number }>();
228
+
229
+ for (const row of result.results ?? []) {
230
+ active.set(row.feature_key, {
231
+ lastHeartbeat: row.last_heartbeat,
232
+ eventsLast7d: row.events_count,
233
+ });
234
+ }
235
+
236
+ log.debug('Found active features', { count: active.size });
237
+ } catch (error) {
238
+ log.error('Failed to get active features', error);
239
+ }
240
+
241
+ return active;
242
+ }
243
+
244
+ /** Get undefined features (reporting but not in config) */
245
+ async function getUndefinedFeatures(
246
+ env: FeatureCoverageEnv,
247
+ definedFeatures: Record<string, unknown>,
248
+ log: Logger
249
+ ): Promise<
250
+ Array<{
251
+ project: string;
252
+ feature: string;
253
+ featureKey: string;
254
+ lastHeartbeat: string;
255
+ eventsLast7d: number;
256
+ }>
257
+ > {
258
+ const undefinedFeatures: Array<{
259
+ project: string;
260
+ feature: string;
261
+ featureKey: string;
262
+ lastHeartbeat: string;
263
+ eventsLast7d: number;
264
+ }> = [];
265
+
266
+ try {
267
+ const result = await env.PLATFORM_DB.prepare(
268
+ `
269
+ SELECT
270
+ feature_id AS feature_key,
271
+ project_id,
272
+ MAX(datetime(last_heartbeat, 'unixepoch')) as last_heartbeat,
273
+ COUNT(*) as events_count
274
+ FROM system_health_checks
275
+ WHERE last_heartbeat > unixepoch('now', '-7 days')
276
+ GROUP BY feature_id
277
+ `
278
+ ).all<{
279
+ feature_key: string;
280
+ project_id: string;
281
+ last_heartbeat: string;
282
+ events_count: number;
283
+ }>();
284
+
285
+ for (const row of result.results ?? []) {
286
+ if (definedFeatures[row.feature_key]) continue;
287
+
288
+ const parts = row.feature_key.split(':');
289
+ const featureName = parts.length > 2 ? parts.slice(2).join(':') : row.feature_key;
290
+
291
+ undefinedFeatures.push({
292
+ project: row.project_id,
293
+ feature: featureName,
294
+ featureKey: row.feature_key,
295
+ lastHeartbeat: row.last_heartbeat,
296
+ eventsLast7d: row.events_count,
297
+ });
298
+ }
299
+
300
+ log.debug('Found undefined features', { count: undefinedFeatures.length });
301
+ } catch (error) {
302
+ log.error('Failed to get undefined features', error);
303
+ }
304
+
305
+ return undefinedFeatures;
306
+ }
307
+
308
+ // =============================================================================
309
+ // Storage
310
+ // =============================================================================
311
+
312
+ /** Store feature coverage audit results in D1 */
313
+ export async function storeFeatureCoverageAudit(
314
+ env: FeatureCoverageEnv,
315
+ report: FeatureCoverageReport,
316
+ log: Logger
317
+ ): Promise<void> {
318
+ try {
319
+ for (const entry of report.entries) {
320
+ const id = crypto.randomUUID();
321
+
322
+ await env.PLATFORM_DB.prepare(
323
+ `
324
+ INSERT INTO feature_coverage_audit (
325
+ id, audit_time, project, feature, status,
326
+ last_heartbeat, events_last_7d, defined_budget, budget_unit
327
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
328
+ `
329
+ )
330
+ .bind(
331
+ id,
332
+ report.auditTime,
333
+ entry.project,
334
+ entry.featureKey,
335
+ entry.status,
336
+ entry.lastHeartbeat,
337
+ entry.eventsLast7d,
338
+ entry.definedBudget,
339
+ entry.budgetUnit
340
+ )
341
+ .run();
342
+ }
343
+
344
+ log.info('Stored feature coverage audit', { entries: report.entries.length });
345
+ } catch (error) {
346
+ log.error('Failed to store feature coverage audit', error);
347
+ }
348
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Auditor Module Exports
3
+ *
4
+ * Barrel export for all audit modules.
5
+ */
6
+
7
+ export * from './feature-coverage';
8
+ export * from './comprehensive-report';
9
+ export * from './types';
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Auditor Shared Types
3
+ *
4
+ * Type definitions for the platform-auditor worker and its library modules.
5
+ *
6
+ * @module workers/lib/auditor/types
7
+ */
8
+
9
+ import type {
10
+ KVNamespace,
11
+ D1Database,
12
+ Fetcher,
13
+ ExecutionContext,
14
+ } from '@cloudflare/workers-types';
15
+
16
+ // =============================================================================
17
+ // Environment
18
+ // =============================================================================
19
+
20
+ export interface AuditorEnv {
21
+ GITHUB_TOKEN: string;
22
+ PLATFORM_AI_GATEWAY_KEY: string;
23
+ GEMINI_API_KEY: string;
24
+ CLOUDFLARE_ACCOUNT_ID: string;
25
+ PLATFORM_DB: D1Database;
26
+ PLATFORM_CACHE: KVNamespace;
27
+ PLATFORM_TELEMETRY: Queue;
28
+ ALERT_ROUTER: Fetcher;
29
+ GATUS_HEARTBEAT_URL?: string;
30
+ GATUS_TOKEN?: string;
31
+ }
32
+
33
+ // =============================================================================
34
+ // Audit Configuration
35
+ // =============================================================================
36
+
37
+ /** Scan type — full (all dimensions) or focused (weak dimensions only) */
38
+ export type ScanType = 'full' | 'focused';
39
+
40
+ /** Audit dimension for rubric scoring */
41
+ export type AuditDimension = 'sdk' | 'observability' | 'cost' | 'security';
42
+
43
+ /** Audit status determination */
44
+ export type AuditStatus = 'HEALTHY' | 'ZOMBIE' | 'UNTRACKED' | 'BROKEN' | 'NOT_INTEGRATED';
45
+
46
+ /** Deep scan files categorised by audit dimension */
47
+ export interface DimensionFiles {
48
+ sdk: string[];
49
+ observability: string[];
50
+ cost: string[];
51
+ security: string[];
52
+ }
53
+
54
+ /** Audit target from config (audit-targets.yaml) */
55
+ export interface AuditTarget {
56
+ repo: string;
57
+ wranglerPath: string;
58
+ entryPoint: string;
59
+ entryPointFallback: string | null;
60
+ expectedStatus: AuditStatus;
61
+ notes: string;
62
+ deepScanFiles: DimensionFiles;
63
+ }
64
+
65
+ /** Audit targets configuration (loaded from KV or YAML) */
66
+ export interface AuditTargetsConfig {
67
+ targets: Record<string, AuditTarget>;
68
+ sdkPatterns: string[];
69
+ rubricWeights: {
70
+ sdk: number;
71
+ observability: number;
72
+ costProtection: number;
73
+ security: number;
74
+ };
75
+ heartbeatFreshnessHours: number;
76
+ focusedScanThreshold: number;
77
+ aiCacheTtlSeconds: number;
78
+ behavioralAnalysisDays: number;
79
+ hotspotMinCommits: number;
80
+ }
81
+
82
+ // =============================================================================
83
+ // Check Results
84
+ // =============================================================================
85
+
86
+ /** Config check result (wrangler bindings) */
87
+ export interface ConfigCheckResult {
88
+ hasPlatformCache: boolean;
89
+ hasPlatformTelemetry: boolean;
90
+ observabilityEnabled: boolean;
91
+ logsEnabled: boolean;
92
+ tracesEnabled: boolean;
93
+ traceSamplingRate: number | null;
94
+ logSamplingRate: number | null;
95
+ invocationLogsEnabled: boolean;
96
+ sourceMapsEnabled: boolean;
97
+ issues: string[];
98
+ }
99
+
100
+ /** Code smell check result (SDK pattern detection) */
101
+ export interface CodeSmellResult {
102
+ hasSdkFolder: boolean;
103
+ hasWithFeatureBudget: boolean;
104
+ hasTrackedEnv: boolean;
105
+ hasCircuitBreakerError: boolean;
106
+ hasErrorLogging: boolean;
107
+ patternCounts: Record<string, number>;
108
+ }
109
+
110
+ /** Runtime check result (D1 heartbeat freshness) */
111
+ export interface RuntimeCheckResult {
112
+ hasRecentHeartbeat: boolean;
113
+ hoursSinceHeartbeat: number | null;
114
+ lastHeartbeatTime: string | null;
115
+ }
116
+
117
+ /** AI Judge result (from Gemini analysis) */
118
+ export interface AIJudgeResult {
119
+ score: number;
120
+ summary: string;
121
+ issues: string;
122
+ categorisedIssues: string;
123
+ reasoning: string;
124
+ rubricScores: {
125
+ sdk: number;
126
+ observability: number;
127
+ costProtection: number;
128
+ security: number;
129
+ };
130
+ rubricEvidence: string;
131
+ validationRetries: number;
132
+ cachedAt: string | null;
133
+ }
134
+
135
+ /** Behavioral analysis result (git history) */
136
+ export interface BehavioralResult {
137
+ hotspots: Array<{
138
+ filePath: string;
139
+ changeCount: number;
140
+ hasSdkPatterns: boolean;
141
+ hotspotScore: number;
142
+ }>;
143
+ regressions: Array<{
144
+ commitSha: string;
145
+ commitMessage: string;
146
+ removedPattern: string;
147
+ filePath: string;
148
+ }>;
149
+ analysisWindow: number;
150
+ }
151
+
152
+ /** Combined audit result for a single project */
153
+ export interface ProjectAuditResult {
154
+ project: string;
155
+ auditId: string;
156
+ status: AuditStatus;
157
+ statusMessage: string;
158
+ scanType: ScanType;
159
+ focusedDimensions: AuditDimension[] | null;
160
+ config: ConfigCheckResult;
161
+ codeSmells: CodeSmellResult;
162
+ runtime: RuntimeCheckResult;
163
+ aiJudge: AIJudgeResult | null;
164
+ behavioral: BehavioralResult | null;
165
+ compositeScore: number;
166
+ timestamp: string;
167
+ }