@littlebearapps/platform-admin-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +89 -0
- package/dist/prompts.d.ts +27 -0
- package/dist/prompts.js +80 -0
- package/dist/scaffold.d.ts +5 -0
- package/dist/scaffold.js +65 -0
- package/dist/templates.d.ts +16 -0
- package/dist/templates.js +131 -0
- package/package.json +46 -0
- package/templates/full/migrations/006_pattern_discovery.sql +199 -0
- package/templates/full/migrations/007_notifications_search.sql +127 -0
- package/templates/full/workers/lib/pattern-discovery/ai-prompt.ts +644 -0
- package/templates/full/workers/lib/pattern-discovery/clustering.ts +278 -0
- package/templates/full/workers/lib/pattern-discovery/shadow-evaluation.ts +603 -0
- package/templates/full/workers/lib/pattern-discovery/storage.ts +806 -0
- package/templates/full/workers/lib/pattern-discovery/types.ts +159 -0
- package/templates/full/workers/lib/pattern-discovery/validation.ts +278 -0
- package/templates/full/workers/pattern-discovery.ts +661 -0
- package/templates/full/workers/platform-alert-router.ts +1809 -0
- package/templates/full/workers/platform-notifications.ts +424 -0
- package/templates/full/workers/platform-search.ts +480 -0
- package/templates/full/workers/platform-settings.ts +436 -0
- package/templates/full/wrangler.alert-router.jsonc.hbs +34 -0
- package/templates/full/wrangler.notifications.jsonc.hbs +23 -0
- package/templates/full/wrangler.pattern-discovery.jsonc.hbs +33 -0
- package/templates/full/wrangler.search.jsonc.hbs +16 -0
- package/templates/full/wrangler.settings.jsonc.hbs +23 -0
- package/templates/shared/README.md.hbs +69 -0
- package/templates/shared/config/budgets.yaml.hbs +72 -0
- package/templates/shared/config/services.yaml.hbs +45 -0
- package/templates/shared/migrations/001_core_tables.sql +117 -0
- package/templates/shared/migrations/002_usage_warehouse.sql +830 -0
- package/templates/shared/migrations/003_feature_tracking.sql +250 -0
- package/templates/shared/migrations/004_settings_alerts.sql +452 -0
- package/templates/shared/migrations/seed.sql.hbs +4 -0
- package/templates/shared/package.json.hbs +21 -0
- package/templates/shared/scripts/sync-config.ts +242 -0
- package/templates/shared/tsconfig.json +12 -0
- package/templates/shared/workers/lib/analytics-engine.ts +357 -0
- package/templates/shared/workers/lib/billing.ts +293 -0
- package/templates/shared/workers/lib/circuit-breaker-middleware.ts +25 -0
- package/templates/shared/workers/lib/control.ts +292 -0
- package/templates/shared/workers/lib/economics.ts +368 -0
- package/templates/shared/workers/lib/metrics.ts +103 -0
- package/templates/shared/workers/lib/platform-settings.ts +407 -0
- package/templates/shared/workers/lib/shared/allowances.ts +333 -0
- package/templates/shared/workers/lib/shared/cloudflare.ts +1362 -0
- package/templates/shared/workers/lib/shared/types.ts +58 -0
- package/templates/shared/workers/lib/telemetry-sampling.ts +360 -0
- package/templates/shared/workers/lib/usage/collectors/example.ts +96 -0
- package/templates/shared/workers/lib/usage/collectors/index.ts +128 -0
- package/templates/shared/workers/lib/usage/handlers/audit.ts +306 -0
- package/templates/shared/workers/lib/usage/handlers/backfill.ts +845 -0
- package/templates/shared/workers/lib/usage/handlers/behavioral.ts +429 -0
- package/templates/shared/workers/lib/usage/handlers/data-queries.ts +507 -0
- package/templates/shared/workers/lib/usage/handlers/dlq-admin.ts +364 -0
- package/templates/shared/workers/lib/usage/handlers/health-trends.ts +222 -0
- package/templates/shared/workers/lib/usage/handlers/index.ts +35 -0
- package/templates/shared/workers/lib/usage/handlers/usage-admin.ts +421 -0
- package/templates/shared/workers/lib/usage/handlers/usage-features.ts +1262 -0
- package/templates/shared/workers/lib/usage/handlers/usage-metrics.ts +2420 -0
- package/templates/shared/workers/lib/usage/handlers/usage-settings.ts +610 -0
- package/templates/shared/workers/lib/usage/queue/budget-enforcement.ts +1032 -0
- package/templates/shared/workers/lib/usage/queue/cost-budget-enforcement.ts +128 -0
- package/templates/shared/workers/lib/usage/queue/cost-calculator.ts +77 -0
- package/templates/shared/workers/lib/usage/queue/dlq-handler.ts +161 -0
- package/templates/shared/workers/lib/usage/queue/index.ts +19 -0
- package/templates/shared/workers/lib/usage/queue/telemetry-processor.ts +790 -0
- package/templates/shared/workers/lib/usage/scheduled/anomaly-detection.ts +732 -0
- package/templates/shared/workers/lib/usage/scheduled/data-collection.ts +956 -0
- package/templates/shared/workers/lib/usage/scheduled/error-digest.ts +343 -0
- package/templates/shared/workers/lib/usage/scheduled/index.ts +18 -0
- package/templates/shared/workers/lib/usage/scheduled/rollups.ts +1561 -0
- package/templates/shared/workers/lib/usage/shared/constants.ts +362 -0
- package/templates/shared/workers/lib/usage/shared/index.ts +14 -0
- package/templates/shared/workers/lib/usage/shared/types.ts +1066 -0
- package/templates/shared/workers/lib/usage/shared/utils.ts +795 -0
- package/templates/shared/workers/platform-usage.ts +1915 -0
- package/templates/shared/wrangler.usage.jsonc.hbs +58 -0
- package/templates/standard/migrations/005_error_collection.sql +162 -0
- package/templates/standard/workers/error-collector.ts +2670 -0
- package/templates/standard/workers/lib/error-collector/capture.ts +213 -0
- package/templates/standard/workers/lib/error-collector/digest.ts +448 -0
- package/templates/standard/workers/lib/error-collector/email-health-alerts.ts +262 -0
- package/templates/standard/workers/lib/error-collector/fingerprint.ts +258 -0
- package/templates/standard/workers/lib/error-collector/gap-alerts.ts +293 -0
- package/templates/standard/workers/lib/error-collector/github.ts +329 -0
- package/templates/standard/workers/lib/error-collector/types.ts +262 -0
- package/templates/standard/workers/lib/sentinel/gap-detection.ts +734 -0
- package/templates/standard/workers/lib/shared/slack-alerts.ts +585 -0
- package/templates/standard/workers/platform-sentinel.ts +1744 -0
- package/templates/standard/wrangler.error-collector.jsonc.hbs +44 -0
- package/templates/standard/wrangler.sentinel.jsonc.hbs +45 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavioral Analysis Handlers for platform-usage
|
|
3
|
+
*
|
|
4
|
+
* Provides API endpoints for accessing file hotspots and SDK regression data.
|
|
5
|
+
* Part of the Dashboard Enhancements initiative.
|
|
6
|
+
*
|
|
7
|
+
* Endpoints:
|
|
8
|
+
* - GET /usage/audit/behavioral - Combined hotspots + regressions summary
|
|
9
|
+
* - GET /usage/audit/behavioral/hotspots - File hotspots with risk scoring
|
|
10
|
+
* - GET /usage/audit/behavioral/regressions - SDK regressions with acknowledgment status
|
|
11
|
+
* - POST /usage/audit/behavioral/regressions/:id/acknowledge - Mark regression as acknowledged
|
|
12
|
+
*
|
|
13
|
+
* @module workers/lib/usage/handlers/behavioral
|
|
14
|
+
* @created 2026-01-30
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { D1Database } from '@cloudflare/workers-types';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Environment bindings for behavioral handlers
|
|
21
|
+
*/
|
|
22
|
+
export interface BehavioralHandlerEnv {
|
|
23
|
+
PLATFORM_DB: D1Database;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* File hotspot data from D1
|
|
28
|
+
*/
|
|
29
|
+
interface FileHotspot {
|
|
30
|
+
id: number;
|
|
31
|
+
project: string;
|
|
32
|
+
file_path: string;
|
|
33
|
+
change_count: number;
|
|
34
|
+
last_changed: string | null;
|
|
35
|
+
authors: string | null;
|
|
36
|
+
has_sdk_patterns: boolean;
|
|
37
|
+
sdk_patterns_found: string | null;
|
|
38
|
+
hotspot_score: number;
|
|
39
|
+
audit_date: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* SDK regression data from D1
|
|
44
|
+
*/
|
|
45
|
+
interface SdkRegression {
|
|
46
|
+
id: number;
|
|
47
|
+
project: string;
|
|
48
|
+
commit_sha: string;
|
|
49
|
+
commit_message: string | null;
|
|
50
|
+
commit_author: string | null;
|
|
51
|
+
commit_date: string | null;
|
|
52
|
+
file_path: string;
|
|
53
|
+
regression_type: string;
|
|
54
|
+
description: string | null;
|
|
55
|
+
severity: string;
|
|
56
|
+
acknowledged: boolean;
|
|
57
|
+
acknowledged_at: string | null;
|
|
58
|
+
acknowledged_by: string | null;
|
|
59
|
+
audit_date: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Handle GET /usage/audit/behavioral - Get combined behavioral analysis summary
|
|
64
|
+
*/
|
|
65
|
+
export async function handleGetBehavioral(
|
|
66
|
+
request: Request,
|
|
67
|
+
env: BehavioralHandlerEnv
|
|
68
|
+
): Promise<Response> {
|
|
69
|
+
const url = new URL(request.url);
|
|
70
|
+
const project = url.searchParams.get('project');
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
// Get latest audit date for hotspots
|
|
74
|
+
const latestHotspotResult = await env.PLATFORM_DB.prepare(
|
|
75
|
+
`SELECT MAX(audit_date) as latest FROM audit_file_hotspots`
|
|
76
|
+
).first<{ latest: string | null }>();
|
|
77
|
+
|
|
78
|
+
// Get latest audit date for regressions
|
|
79
|
+
const latestRegressionResult = await env.PLATFORM_DB.prepare(
|
|
80
|
+
`SELECT MAX(audit_date) as latest FROM audit_sdk_regressions`
|
|
81
|
+
).first<{ latest: string | null }>();
|
|
82
|
+
|
|
83
|
+
// Build hotspots query
|
|
84
|
+
let hotspotsQuery = `
|
|
85
|
+
SELECT
|
|
86
|
+
id, project, file_path, change_count, last_changed, authors,
|
|
87
|
+
has_sdk_patterns, sdk_patterns_found, hotspot_score, audit_date
|
|
88
|
+
FROM audit_file_hotspots
|
|
89
|
+
WHERE audit_date = ?
|
|
90
|
+
`;
|
|
91
|
+
const hotspotsBindings: (string | number)[] = [latestHotspotResult?.latest ?? ''];
|
|
92
|
+
|
|
93
|
+
if (project) {
|
|
94
|
+
hotspotsQuery += ' AND project = ?';
|
|
95
|
+
hotspotsBindings.push(project);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
hotspotsQuery += ' ORDER BY hotspot_score DESC LIMIT 20';
|
|
99
|
+
|
|
100
|
+
// Build regressions query
|
|
101
|
+
let regressionsQuery = `
|
|
102
|
+
SELECT
|
|
103
|
+
id, project, commit_sha, commit_message, commit_author, commit_date,
|
|
104
|
+
file_path, regression_type, description, severity, acknowledged,
|
|
105
|
+
acknowledged_at, acknowledged_by, audit_date
|
|
106
|
+
FROM audit_sdk_regressions
|
|
107
|
+
WHERE acknowledged = FALSE
|
|
108
|
+
`;
|
|
109
|
+
const regressionsBindings: (string | number)[] = [];
|
|
110
|
+
|
|
111
|
+
if (project) {
|
|
112
|
+
regressionsQuery += ' AND project = ?';
|
|
113
|
+
regressionsBindings.push(project);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
regressionsQuery += ' ORDER BY CASE severity WHEN \'critical\' THEN 1 WHEN \'high\' THEN 2 WHEN \'medium\' THEN 3 ELSE 4 END, commit_date DESC LIMIT 20';
|
|
117
|
+
|
|
118
|
+
// Execute queries in parallel
|
|
119
|
+
const [hotspotsResult, regressionsResult] = await Promise.all([
|
|
120
|
+
latestHotspotResult?.latest
|
|
121
|
+
? env.PLATFORM_DB.prepare(hotspotsQuery).bind(...hotspotsBindings).all<FileHotspot>()
|
|
122
|
+
: Promise.resolve({ results: [] as FileHotspot[] }),
|
|
123
|
+
env.PLATFORM_DB.prepare(regressionsQuery).bind(...regressionsBindings).all<SdkRegression>(),
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
// Build summary
|
|
127
|
+
const hotspots = hotspotsResult.results ?? [];
|
|
128
|
+
const regressions = regressionsResult.results ?? [];
|
|
129
|
+
|
|
130
|
+
const summary = {
|
|
131
|
+
hotspotsCount: hotspots.length,
|
|
132
|
+
highRiskHotspots: hotspots.filter((h) => h.hotspot_score >= 10).length,
|
|
133
|
+
regressionsCount: regressions.length,
|
|
134
|
+
criticalRegressions: regressions.filter((r) => r.severity === 'critical').length,
|
|
135
|
+
highRegressions: regressions.filter((r) => r.severity === 'high').length,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return Response.json({
|
|
139
|
+
success: true,
|
|
140
|
+
data: {
|
|
141
|
+
summary,
|
|
142
|
+
hotspots: hotspots.map(formatHotspot),
|
|
143
|
+
regressions: regressions.map(formatRegression),
|
|
144
|
+
},
|
|
145
|
+
auditDates: {
|
|
146
|
+
hotspots: latestHotspotResult?.latest ?? null,
|
|
147
|
+
regressions: latestRegressionResult?.latest ?? null,
|
|
148
|
+
},
|
|
149
|
+
timestamp: new Date().toISOString(),
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
153
|
+
return Response.json(
|
|
154
|
+
{ success: false, error: 'Failed to get behavioral analysis', message },
|
|
155
|
+
{ status: 500 }
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Handle GET /usage/audit/behavioral/hotspots - Get file hotspots with risk scoring
|
|
162
|
+
*/
|
|
163
|
+
export async function handleGetHotspots(
|
|
164
|
+
request: Request,
|
|
165
|
+
env: BehavioralHandlerEnv
|
|
166
|
+
): Promise<Response> {
|
|
167
|
+
const url = new URL(request.url);
|
|
168
|
+
const project = url.searchParams.get('project');
|
|
169
|
+
const limit = Math.min(parseInt(url.searchParams.get('limit') || '50', 10), 100);
|
|
170
|
+
const minScore = parseInt(url.searchParams.get('min_score') || '0', 10);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
// Get latest audit date
|
|
174
|
+
const latestResult = await env.PLATFORM_DB.prepare(
|
|
175
|
+
`SELECT MAX(audit_date) as latest FROM audit_file_hotspots`
|
|
176
|
+
).first<{ latest: string | null }>();
|
|
177
|
+
|
|
178
|
+
if (!latestResult?.latest) {
|
|
179
|
+
return Response.json({
|
|
180
|
+
success: true,
|
|
181
|
+
data: {
|
|
182
|
+
auditDate: null,
|
|
183
|
+
summary: { total: 0, highRisk: 0, withoutSdk: 0 },
|
|
184
|
+
hotspots: [],
|
|
185
|
+
},
|
|
186
|
+
message: 'No behavioral analysis data available. Run platform-auditor to generate data.',
|
|
187
|
+
timestamp: new Date().toISOString(),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Build query
|
|
192
|
+
let query = `
|
|
193
|
+
SELECT
|
|
194
|
+
id, project, file_path, change_count, last_changed, authors,
|
|
195
|
+
has_sdk_patterns, sdk_patterns_found, hotspot_score, audit_date
|
|
196
|
+
FROM audit_file_hotspots
|
|
197
|
+
WHERE audit_date = ? AND hotspot_score >= ?
|
|
198
|
+
`;
|
|
199
|
+
const bindings: (string | number)[] = [latestResult.latest, minScore];
|
|
200
|
+
|
|
201
|
+
if (project) {
|
|
202
|
+
query += ' AND project = ?';
|
|
203
|
+
bindings.push(project);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
query += ' ORDER BY hotspot_score DESC LIMIT ?';
|
|
207
|
+
bindings.push(limit);
|
|
208
|
+
|
|
209
|
+
const result = await env.PLATFORM_DB.prepare(query).bind(...bindings).all<FileHotspot>();
|
|
210
|
+
|
|
211
|
+
const hotspots = result.results ?? [];
|
|
212
|
+
|
|
213
|
+
// Build summary
|
|
214
|
+
const summary = {
|
|
215
|
+
total: hotspots.length,
|
|
216
|
+
highRisk: hotspots.filter((h) => h.hotspot_score >= 10).length,
|
|
217
|
+
withoutSdk: hotspots.filter((h) => !h.has_sdk_patterns).length,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return Response.json({
|
|
221
|
+
success: true,
|
|
222
|
+
data: {
|
|
223
|
+
auditDate: latestResult.latest,
|
|
224
|
+
summary,
|
|
225
|
+
hotspots: hotspots.map(formatHotspot),
|
|
226
|
+
},
|
|
227
|
+
timestamp: new Date().toISOString(),
|
|
228
|
+
});
|
|
229
|
+
} catch (error) {
|
|
230
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
231
|
+
return Response.json(
|
|
232
|
+
{ success: false, error: 'Failed to get hotspots', message },
|
|
233
|
+
{ status: 500 }
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Handle GET /usage/audit/behavioral/regressions - Get SDK regressions
|
|
240
|
+
*/
|
|
241
|
+
export async function handleGetRegressions(
|
|
242
|
+
request: Request,
|
|
243
|
+
env: BehavioralHandlerEnv
|
|
244
|
+
): Promise<Response> {
|
|
245
|
+
const url = new URL(request.url);
|
|
246
|
+
const project = url.searchParams.get('project');
|
|
247
|
+
const acknowledged = url.searchParams.get('acknowledged');
|
|
248
|
+
const severity = url.searchParams.get('severity');
|
|
249
|
+
const limit = Math.min(parseInt(url.searchParams.get('limit') || '50', 10), 100);
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
// Build query
|
|
253
|
+
let query = `
|
|
254
|
+
SELECT
|
|
255
|
+
id, project, commit_sha, commit_message, commit_author, commit_date,
|
|
256
|
+
file_path, regression_type, description, severity, acknowledged,
|
|
257
|
+
acknowledged_at, acknowledged_by, audit_date
|
|
258
|
+
FROM audit_sdk_regressions
|
|
259
|
+
WHERE 1=1
|
|
260
|
+
`;
|
|
261
|
+
const bindings: (string | number)[] = [];
|
|
262
|
+
|
|
263
|
+
if (project) {
|
|
264
|
+
query += ' AND project = ?';
|
|
265
|
+
bindings.push(project);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (acknowledged !== null) {
|
|
269
|
+
query += ' AND acknowledged = ?';
|
|
270
|
+
bindings.push(acknowledged === 'true' ? 1 : 0);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (severity) {
|
|
274
|
+
query += ' AND severity = ?';
|
|
275
|
+
bindings.push(severity);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
query += ` ORDER BY
|
|
279
|
+
CASE severity WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 ELSE 4 END,
|
|
280
|
+
commit_date DESC
|
|
281
|
+
LIMIT ?`;
|
|
282
|
+
bindings.push(limit);
|
|
283
|
+
|
|
284
|
+
const result = await env.PLATFORM_DB.prepare(query).bind(...bindings).all<SdkRegression>();
|
|
285
|
+
|
|
286
|
+
const regressions = result.results ?? [];
|
|
287
|
+
|
|
288
|
+
// Build summary
|
|
289
|
+
const summary = {
|
|
290
|
+
total: regressions.length,
|
|
291
|
+
unacknowledged: regressions.filter((r) => !r.acknowledged).length,
|
|
292
|
+
bySeverity: {
|
|
293
|
+
critical: regressions.filter((r) => r.severity === 'critical').length,
|
|
294
|
+
high: regressions.filter((r) => r.severity === 'high').length,
|
|
295
|
+
medium: regressions.filter((r) => r.severity === 'medium').length,
|
|
296
|
+
low: regressions.filter((r) => r.severity === 'low').length,
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
return Response.json({
|
|
301
|
+
success: true,
|
|
302
|
+
data: {
|
|
303
|
+
summary,
|
|
304
|
+
regressions: regressions.map(formatRegression),
|
|
305
|
+
},
|
|
306
|
+
timestamp: new Date().toISOString(),
|
|
307
|
+
});
|
|
308
|
+
} catch (error) {
|
|
309
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
310
|
+
return Response.json(
|
|
311
|
+
{ success: false, error: 'Failed to get regressions', message },
|
|
312
|
+
{ status: 500 }
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Handle POST /usage/audit/behavioral/regressions/:id/acknowledge - Mark regression as acknowledged
|
|
319
|
+
*/
|
|
320
|
+
export async function handleAcknowledgeRegression(
|
|
321
|
+
request: Request,
|
|
322
|
+
env: BehavioralHandlerEnv,
|
|
323
|
+
regressionId: string
|
|
324
|
+
): Promise<Response> {
|
|
325
|
+
try {
|
|
326
|
+
// Parse body for acknowledger info (optional)
|
|
327
|
+
let acknowledgedBy = 'system';
|
|
328
|
+
try {
|
|
329
|
+
const body = await request.json() as { acknowledged_by?: string };
|
|
330
|
+
if (body.acknowledged_by) {
|
|
331
|
+
acknowledgedBy = body.acknowledged_by;
|
|
332
|
+
}
|
|
333
|
+
} catch {
|
|
334
|
+
// Body is optional
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const result = await env.PLATFORM_DB.prepare(
|
|
338
|
+
`UPDATE audit_sdk_regressions
|
|
339
|
+
SET acknowledged = TRUE,
|
|
340
|
+
acknowledged_at = datetime('now'),
|
|
341
|
+
acknowledged_by = ?
|
|
342
|
+
WHERE id = ? AND acknowledged = FALSE`
|
|
343
|
+
)
|
|
344
|
+
.bind(acknowledgedBy, parseInt(regressionId, 10))
|
|
345
|
+
.run();
|
|
346
|
+
|
|
347
|
+
if (result.meta.changes === 0) {
|
|
348
|
+
// Check if it exists
|
|
349
|
+
const existing = await env.PLATFORM_DB.prepare(
|
|
350
|
+
`SELECT id, acknowledged FROM audit_sdk_regressions WHERE id = ?`
|
|
351
|
+
)
|
|
352
|
+
.bind(parseInt(regressionId, 10))
|
|
353
|
+
.first<{ id: number; acknowledged: boolean }>();
|
|
354
|
+
|
|
355
|
+
if (!existing) {
|
|
356
|
+
return Response.json(
|
|
357
|
+
{ success: false, error: 'Regression not found' },
|
|
358
|
+
{ status: 404 }
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (existing.acknowledged) {
|
|
363
|
+
return Response.json(
|
|
364
|
+
{ success: false, error: 'Regression already acknowledged' },
|
|
365
|
+
{ status: 409 }
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return Response.json({
|
|
371
|
+
success: true,
|
|
372
|
+
message: 'Regression acknowledged',
|
|
373
|
+
data: {
|
|
374
|
+
id: parseInt(regressionId, 10),
|
|
375
|
+
acknowledgedAt: new Date().toISOString(),
|
|
376
|
+
acknowledgedBy,
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
} catch (error) {
|
|
380
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
381
|
+
return Response.json(
|
|
382
|
+
{ success: false, error: 'Failed to acknowledge regression', message },
|
|
383
|
+
{ status: 500 }
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Format hotspot for API response
|
|
390
|
+
*/
|
|
391
|
+
function formatHotspot(hotspot: FileHotspot) {
|
|
392
|
+
return {
|
|
393
|
+
id: hotspot.id,
|
|
394
|
+
project: hotspot.project,
|
|
395
|
+
filePath: hotspot.file_path,
|
|
396
|
+
changeCount: hotspot.change_count,
|
|
397
|
+
lastChanged: hotspot.last_changed,
|
|
398
|
+
authors: hotspot.authors ? JSON.parse(hotspot.authors) : [],
|
|
399
|
+
hasSdkPatterns: hotspot.has_sdk_patterns,
|
|
400
|
+
sdkPatternsFound: hotspot.sdk_patterns_found ? JSON.parse(hotspot.sdk_patterns_found) : [],
|
|
401
|
+
hotspotScore: hotspot.hotspot_score,
|
|
402
|
+
riskLevel: hotspot.hotspot_score >= 15 ? 'critical' : hotspot.hotspot_score >= 10 ? 'high' : hotspot.hotspot_score >= 5 ? 'medium' : 'low',
|
|
403
|
+
auditDate: hotspot.audit_date,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Format regression for API response
|
|
409
|
+
*/
|
|
410
|
+
function formatRegression(regression: SdkRegression) {
|
|
411
|
+
return {
|
|
412
|
+
id: regression.id,
|
|
413
|
+
project: regression.project,
|
|
414
|
+
commit: {
|
|
415
|
+
sha: regression.commit_sha,
|
|
416
|
+
message: regression.commit_message,
|
|
417
|
+
author: regression.commit_author,
|
|
418
|
+
date: regression.commit_date,
|
|
419
|
+
},
|
|
420
|
+
filePath: regression.file_path,
|
|
421
|
+
regressionType: regression.regression_type,
|
|
422
|
+
description: regression.description,
|
|
423
|
+
severity: regression.severity,
|
|
424
|
+
acknowledged: regression.acknowledged,
|
|
425
|
+
acknowledgedAt: regression.acknowledged_at,
|
|
426
|
+
acknowledgedBy: regression.acknowledged_by,
|
|
427
|
+
auditDate: regression.audit_date,
|
|
428
|
+
};
|
|
429
|
+
}
|