@way_marks/server 0.8.0 → 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/dist/api/server.js +598 -0
- package/dist/approval/manager.js +249 -0
- package/dist/approval/manager.test.js +315 -0
- package/dist/db/database.js +544 -1
- package/dist/escalation/manager.js +205 -0
- package/dist/escalation/manager.test.js +519 -0
- package/dist/notifications/service.js +457 -0
- package/dist/policy/engine.js +420 -0
- package/dist/remediation/recommender.js +309 -0
- package/dist/risk/analyzer.js +442 -0
- package/dist/risk/analyzer.test.js +482 -0
- package/dist/rollback/blocker.js +222 -0
- package/dist/rollback/manager.js +245 -0
- package/dist/rollback/manager.test.js +552 -0
- package/package.json +1 -1
- package/src/ui/index.html +862 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Risk Assessment Engine — Phase 4A
|
|
4
|
+
*
|
|
5
|
+
* Evaluates rollback safety based on:
|
|
6
|
+
* - Operation type risk (what tools were used)
|
|
7
|
+
* - Scale risk (how many actions)
|
|
8
|
+
* - Error pattern risk (what errors occurred)
|
|
9
|
+
* - Time risk (how old are the actions)
|
|
10
|
+
* - System state risk (current system load)
|
|
11
|
+
*
|
|
12
|
+
* Produces 0-10 risk score and safety level:
|
|
13
|
+
* - NONE (0)
|
|
14
|
+
* - LOW (1-2)
|
|
15
|
+
* - MEDIUM (3-4)
|
|
16
|
+
* - HIGH (5-7)
|
|
17
|
+
* - CRITICAL (8+)
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.assessRisk = assessRisk;
|
|
21
|
+
exports.calculateOperationTypeRisk = calculateOperationTypeRisk;
|
|
22
|
+
exports.calculateScaleRisk = calculateScaleRisk;
|
|
23
|
+
exports.calculateErrorPatternRisk = calculateErrorPatternRisk;
|
|
24
|
+
exports.calculateTimeRisk = calculateTimeRisk;
|
|
25
|
+
exports.calculateSystemStateRisk = calculateSystemStateRisk;
|
|
26
|
+
exports.getRiskLevel = getRiskLevel;
|
|
27
|
+
exports.generateRecommendations = generateRecommendations;
|
|
28
|
+
exports.shouldAutoBlock = shouldAutoBlock;
|
|
29
|
+
exports.getRiskSummary = getRiskSummary;
|
|
30
|
+
/**
|
|
31
|
+
* Assess overall risk of a session rollback
|
|
32
|
+
*/
|
|
33
|
+
function assessRisk(session, actions, systemState) {
|
|
34
|
+
const timestamp = new Date().toISOString();
|
|
35
|
+
const factors = [];
|
|
36
|
+
// 1. Operation type risk
|
|
37
|
+
const operationRisk = calculateOperationTypeRisk(actions);
|
|
38
|
+
factors.push({
|
|
39
|
+
category: 'operation_type',
|
|
40
|
+
weight: operationRisk.weight,
|
|
41
|
+
reason: operationRisk.reason,
|
|
42
|
+
sub_factors: operationRisk.sub_factors,
|
|
43
|
+
});
|
|
44
|
+
// 2. Scale risk
|
|
45
|
+
const scaleRisk = calculateScaleRisk(actions);
|
|
46
|
+
factors.push({
|
|
47
|
+
category: 'scale',
|
|
48
|
+
weight: scaleRisk.weight,
|
|
49
|
+
reason: scaleRisk.reason,
|
|
50
|
+
});
|
|
51
|
+
// 3. Error pattern risk
|
|
52
|
+
const errorRisk = calculateErrorPatternRisk(actions);
|
|
53
|
+
factors.push({
|
|
54
|
+
category: 'error_pattern',
|
|
55
|
+
weight: errorRisk.weight,
|
|
56
|
+
reason: errorRisk.reason,
|
|
57
|
+
sub_factors: errorRisk.sub_factors,
|
|
58
|
+
});
|
|
59
|
+
// 4. Time risk
|
|
60
|
+
const timeRisk = calculateTimeRisk(session);
|
|
61
|
+
factors.push({
|
|
62
|
+
category: 'time',
|
|
63
|
+
weight: timeRisk.weight,
|
|
64
|
+
reason: timeRisk.reason,
|
|
65
|
+
});
|
|
66
|
+
// 5. System state risk
|
|
67
|
+
const systemRisk = calculateSystemStateRisk(systemState);
|
|
68
|
+
factors.push({
|
|
69
|
+
category: 'system_state',
|
|
70
|
+
weight: systemRisk.weight,
|
|
71
|
+
reason: systemRisk.reason,
|
|
72
|
+
});
|
|
73
|
+
// Calculate overall score (average of weights)
|
|
74
|
+
const totalWeight = factors.reduce((sum, f) => sum + f.weight, 0);
|
|
75
|
+
const score = Math.min(10, Math.round((totalWeight / 5) * 10) / 10);
|
|
76
|
+
// Determine risk level
|
|
77
|
+
const level = getRiskLevel(score);
|
|
78
|
+
// Generate recommendations
|
|
79
|
+
const recommendations = generateRecommendations(score, level, factors, actions);
|
|
80
|
+
return {
|
|
81
|
+
score,
|
|
82
|
+
level,
|
|
83
|
+
factors,
|
|
84
|
+
recommendations,
|
|
85
|
+
timestamp,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Calculate operation type risk
|
|
90
|
+
* High-risk tools: write_file, delete_file, bash, api_call with mutations
|
|
91
|
+
* Medium-risk: read operations that require consistency
|
|
92
|
+
* Low-risk: information gathering, metadata operations
|
|
93
|
+
*/
|
|
94
|
+
function calculateOperationTypeRisk(actions) {
|
|
95
|
+
const toolRisks = {
|
|
96
|
+
// HIGH RISK (2.5 points) - data modification/deletion
|
|
97
|
+
delete_file: 2.5,
|
|
98
|
+
write_file: 2.0,
|
|
99
|
+
bash: 2.0,
|
|
100
|
+
api_call: 1.5, // Depends on mutation type
|
|
101
|
+
// MEDIUM RISK (1.0-1.5 points) - consistency-dependent
|
|
102
|
+
mkdir: 1.5,
|
|
103
|
+
rmdir: 2.0,
|
|
104
|
+
// LOW RISK (0 points) - read-only
|
|
105
|
+
read_file: 0,
|
|
106
|
+
find_files: 0,
|
|
107
|
+
grep: 0,
|
|
108
|
+
};
|
|
109
|
+
const sub_factors = [];
|
|
110
|
+
let totalRisk = 0;
|
|
111
|
+
let highRiskCount = 0;
|
|
112
|
+
let deleteCount = 0;
|
|
113
|
+
let writeCount = 0;
|
|
114
|
+
let bashCount = 0;
|
|
115
|
+
for (const action of actions) {
|
|
116
|
+
const risk = toolRisks[action.tool_name] || 0.5; // Default medium for unknown
|
|
117
|
+
totalRisk += risk;
|
|
118
|
+
if (risk >= 2.0)
|
|
119
|
+
highRiskCount++;
|
|
120
|
+
if (action.tool_name === 'delete_file')
|
|
121
|
+
deleteCount++;
|
|
122
|
+
if (action.tool_name === 'write_file')
|
|
123
|
+
writeCount++;
|
|
124
|
+
if (action.tool_name === 'bash')
|
|
125
|
+
bashCount++;
|
|
126
|
+
sub_factors.push({
|
|
127
|
+
category: action.tool_name,
|
|
128
|
+
weight: risk,
|
|
129
|
+
reason: `Tool: ${action.tool_name}`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const avgToolRisk = actions.length > 0 ? totalRisk / actions.length : 0;
|
|
133
|
+
const weight = Math.min(3, avgToolRisk);
|
|
134
|
+
let reason = `Average tool risk: ${avgToolRisk.toFixed(1)}/3.0`;
|
|
135
|
+
if (deleteCount > 0)
|
|
136
|
+
reason += ` (${deleteCount} delete operations)`;
|
|
137
|
+
if (writeCount > 0)
|
|
138
|
+
reason += ` (${writeCount} write operations)`;
|
|
139
|
+
if (bashCount > 0)
|
|
140
|
+
reason += ` (${bashCount} bash commands)`;
|
|
141
|
+
if (highRiskCount > actions.length / 2)
|
|
142
|
+
reason += ' - majority high-risk operations';
|
|
143
|
+
return { weight, reason, sub_factors };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Calculate scale risk based on number of actions
|
|
147
|
+
* 1 file = 0 risk
|
|
148
|
+
* 2-5 files = 0.5 risk
|
|
149
|
+
* 6-20 files = 1.5 risk
|
|
150
|
+
* 20+ files = 3 risk
|
|
151
|
+
*/
|
|
152
|
+
function calculateScaleRisk(actions) {
|
|
153
|
+
const count = actions.length;
|
|
154
|
+
let weight = 0;
|
|
155
|
+
if (count <= 1) {
|
|
156
|
+
weight = 0;
|
|
157
|
+
}
|
|
158
|
+
else if (count <= 5) {
|
|
159
|
+
weight = 0.5;
|
|
160
|
+
}
|
|
161
|
+
else if (count <= 20) {
|
|
162
|
+
weight = 1.5;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
weight = 3.0;
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
weight,
|
|
169
|
+
reason: `${count} action(s) - ${count <= 1
|
|
170
|
+
? 'single operation'
|
|
171
|
+
: count <= 5
|
|
172
|
+
? 'small batch'
|
|
173
|
+
: count <= 20
|
|
174
|
+
? 'large batch'
|
|
175
|
+
: 'very large batch'}`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Calculate error pattern risk
|
|
180
|
+
* No errors = 0
|
|
181
|
+
* Transient errors (timeout, network) = 0.5
|
|
182
|
+
* Permission errors = 1.5
|
|
183
|
+
* Data errors (validation, format) = 2.0
|
|
184
|
+
* System errors (crash, critical) = 3.0
|
|
185
|
+
*/
|
|
186
|
+
function calculateErrorPatternRisk(actions) {
|
|
187
|
+
const sub_factors = [];
|
|
188
|
+
const errorActions = actions.filter(a => a.status === 'error');
|
|
189
|
+
if (errorActions.length === 0) {
|
|
190
|
+
return {
|
|
191
|
+
weight: 0,
|
|
192
|
+
reason: 'No errors - all operations successful',
|
|
193
|
+
sub_factors: [],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
let totalErrorRisk = 0;
|
|
197
|
+
let transientCount = 0;
|
|
198
|
+
let permissionCount = 0;
|
|
199
|
+
let dataCount = 0;
|
|
200
|
+
let systemCount = 0;
|
|
201
|
+
for (const action of errorActions) {
|
|
202
|
+
const errorMsg = (action.error_message || '').toLowerCase();
|
|
203
|
+
let errorRisk = 0.5; // Default: transient
|
|
204
|
+
if (errorMsg.includes('permission') ||
|
|
205
|
+
errorMsg.includes('denied') ||
|
|
206
|
+
errorMsg.includes('unauthorized')) {
|
|
207
|
+
errorRisk = 1.5;
|
|
208
|
+
permissionCount++;
|
|
209
|
+
}
|
|
210
|
+
else if (errorMsg.includes('validation') ||
|
|
211
|
+
errorMsg.includes('format') ||
|
|
212
|
+
errorMsg.includes('invalid')) {
|
|
213
|
+
errorRisk = 2.0;
|
|
214
|
+
dataCount++;
|
|
215
|
+
}
|
|
216
|
+
else if (errorMsg.includes('crash') ||
|
|
217
|
+
errorMsg.includes('segfault') ||
|
|
218
|
+
errorMsg.includes('fatal') ||
|
|
219
|
+
errorMsg.includes('panic')) {
|
|
220
|
+
errorRisk = 3.0;
|
|
221
|
+
systemCount++;
|
|
222
|
+
}
|
|
223
|
+
else if (errorMsg.includes('timeout') || errorMsg.includes('network')) {
|
|
224
|
+
errorRisk = 0.5;
|
|
225
|
+
transientCount++;
|
|
226
|
+
}
|
|
227
|
+
totalErrorRisk += errorRisk;
|
|
228
|
+
sub_factors.push({
|
|
229
|
+
category: `error_${action.action_id}`,
|
|
230
|
+
weight: errorRisk,
|
|
231
|
+
reason: `${action.tool_name}: ${action.error_message?.substring(0, 50) || 'unknown error'}`,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
const avgErrorRisk = totalErrorRisk / errorActions.length;
|
|
235
|
+
const weight = Math.min(3, avgErrorRisk);
|
|
236
|
+
let reason = `${errorActions.length} error(s) - average risk: ${avgErrorRisk.toFixed(1)}/3.0`;
|
|
237
|
+
if (systemCount > 0)
|
|
238
|
+
reason += ` (${systemCount} system errors)`;
|
|
239
|
+
if (dataCount > 0)
|
|
240
|
+
reason += ` (${dataCount} data errors)`;
|
|
241
|
+
if (permissionCount > 0)
|
|
242
|
+
reason += ` (${permissionCount} permission errors)`;
|
|
243
|
+
if (transientCount > 0)
|
|
244
|
+
reason += ` (${transientCount} transient errors)`;
|
|
245
|
+
return { weight, reason, sub_factors };
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Calculate time risk based on how old actions are
|
|
249
|
+
* <5 minutes = 0
|
|
250
|
+
* 5-60 minutes = 0.5
|
|
251
|
+
* 1-6 hours = 1.5
|
|
252
|
+
* 6-24 hours = 2.0
|
|
253
|
+
* 24+ hours = 3.0
|
|
254
|
+
*/
|
|
255
|
+
function calculateTimeRisk(session) {
|
|
256
|
+
const createdAt = new Date(session.created_at);
|
|
257
|
+
const now = new Date();
|
|
258
|
+
const ageMs = now.getTime() - createdAt.getTime();
|
|
259
|
+
const ageMinutes = ageMs / (1000 * 60);
|
|
260
|
+
const ageHours = ageMinutes / 60;
|
|
261
|
+
const ageDays = ageHours / 24;
|
|
262
|
+
let weight = 0;
|
|
263
|
+
let ageStr = '';
|
|
264
|
+
if (ageMinutes < 5) {
|
|
265
|
+
weight = 0;
|
|
266
|
+
ageStr = `${Math.round(ageMinutes)}m ago - very fresh`;
|
|
267
|
+
}
|
|
268
|
+
else if (ageMinutes < 60) {
|
|
269
|
+
weight = 0.5;
|
|
270
|
+
ageStr = `${Math.round(ageMinutes)}m ago - recent`;
|
|
271
|
+
}
|
|
272
|
+
else if (ageHours < 6) {
|
|
273
|
+
weight = 1.5;
|
|
274
|
+
ageStr = `${Math.round(ageHours)}h ago - older`;
|
|
275
|
+
}
|
|
276
|
+
else if (ageHours < 24) {
|
|
277
|
+
weight = 2.0;
|
|
278
|
+
ageStr = `${Math.round(ageHours)}h ago - very old`;
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
weight = 3.0;
|
|
282
|
+
ageStr = `${Math.round(ageDays)}d ago - ancient`;
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
weight,
|
|
286
|
+
reason: ageStr,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Calculate system state risk
|
|
291
|
+
* Low load (cpu <50%, memory <50%) = 0
|
|
292
|
+
* Moderate (cpu 50-75%, memory 50-75%) = 1.0
|
|
293
|
+
* High (cpu 75-90%, memory 75-90%) = 2.0
|
|
294
|
+
* Critical (cpu >90%, memory >90%) = 3.0
|
|
295
|
+
*/
|
|
296
|
+
function calculateSystemStateRisk(systemState) {
|
|
297
|
+
// Default: assume moderate load if no system state provided
|
|
298
|
+
if (!systemState) {
|
|
299
|
+
return {
|
|
300
|
+
weight: 1.0,
|
|
301
|
+
reason: 'System state unknown - assuming moderate load',
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
const avgLoad = (systemState.cpu_usage + systemState.memory_usage) / 2;
|
|
305
|
+
let weight = 0;
|
|
306
|
+
let description = '';
|
|
307
|
+
if (avgLoad < 50) {
|
|
308
|
+
weight = 0;
|
|
309
|
+
description = `Idle system (CPU: ${systemState.cpu_usage}%, Memory: ${systemState.memory_usage}%)`;
|
|
310
|
+
}
|
|
311
|
+
else if (avgLoad < 75) {
|
|
312
|
+
weight = 1.0;
|
|
313
|
+
description = `Moderate load (CPU: ${systemState.cpu_usage}%, Memory: ${systemState.memory_usage}%)`;
|
|
314
|
+
}
|
|
315
|
+
else if (avgLoad < 90) {
|
|
316
|
+
weight = 2.0;
|
|
317
|
+
description = `High load (CPU: ${systemState.cpu_usage}%, Memory: ${systemState.memory_usage}%)`;
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
weight = 3.0;
|
|
321
|
+
description = `Critical load (CPU: ${systemState.cpu_usage}%, Memory: ${systemState.memory_usage}%)`;
|
|
322
|
+
}
|
|
323
|
+
if (systemState.active_users > 50) {
|
|
324
|
+
description += ` - ${systemState.active_users} active users`;
|
|
325
|
+
}
|
|
326
|
+
if (systemState.request_rate > 1000) {
|
|
327
|
+
weight = Math.min(3, weight + 0.5);
|
|
328
|
+
description += ` - high request rate (${systemState.request_rate} req/s)`;
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
weight,
|
|
332
|
+
reason: description,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Determine risk level from score
|
|
337
|
+
*/
|
|
338
|
+
function getRiskLevel(score) {
|
|
339
|
+
if (score < 1)
|
|
340
|
+
return 'none';
|
|
341
|
+
if (score < 3)
|
|
342
|
+
return 'low';
|
|
343
|
+
if (score < 5)
|
|
344
|
+
return 'medium';
|
|
345
|
+
if (score < 8)
|
|
346
|
+
return 'high';
|
|
347
|
+
return 'critical';
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Generate recommendations based on risk assessment
|
|
351
|
+
*/
|
|
352
|
+
function generateRecommendations(score, level, factors, actions) {
|
|
353
|
+
const recommendations = [];
|
|
354
|
+
// Score-based recommendations
|
|
355
|
+
if (score < 2) {
|
|
356
|
+
recommendations.push('Low risk: Safe to proceed with rollback');
|
|
357
|
+
}
|
|
358
|
+
else if (score < 4) {
|
|
359
|
+
recommendations.push('Moderate risk: Recommend approval from team lead');
|
|
360
|
+
}
|
|
361
|
+
else if (score < 7) {
|
|
362
|
+
recommendations.push('High risk: Requires escalation to manager');
|
|
363
|
+
recommendations.push('Consider partial rollback of safe operations only');
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
recommendations.push('Critical risk: Requires executive approval');
|
|
367
|
+
recommendations.push('Recommend staged rollback with verification');
|
|
368
|
+
recommendations.push('Consider manual remediation instead of full rollback');
|
|
369
|
+
}
|
|
370
|
+
// Factor-specific recommendations
|
|
371
|
+
const operationFactor = factors.find(f => f.category === 'operation_type');
|
|
372
|
+
if (operationFactor && operationFactor.weight > 1.5) {
|
|
373
|
+
recommendations.push('Identify safe read-only operations for selective rollback');
|
|
374
|
+
}
|
|
375
|
+
const scaleFactor = factors.find(f => f.category === 'scale');
|
|
376
|
+
if (scaleFactor && scaleFactor.weight > 1.5) {
|
|
377
|
+
recommendations.push('Consider staged rollback due to large action count');
|
|
378
|
+
}
|
|
379
|
+
const errorFactor = factors.find(f => f.category === 'error_pattern');
|
|
380
|
+
if (errorFactor && errorFactor.weight > 1.5) {
|
|
381
|
+
recommendations.push('Multiple errors detected: review each error before rollback');
|
|
382
|
+
}
|
|
383
|
+
const timeFactor = factors.find(f => f.category === 'time');
|
|
384
|
+
if (timeFactor && timeFactor.weight > 1.5) {
|
|
385
|
+
recommendations.push('Operations are old: ensure no dependencies have changed');
|
|
386
|
+
}
|
|
387
|
+
const systemFactor = factors.find(f => f.category === 'system_state');
|
|
388
|
+
if (systemFactor && systemFactor.weight > 1.5) {
|
|
389
|
+
recommendations.push('System under high load: consider scheduling rollback during off-peak');
|
|
390
|
+
}
|
|
391
|
+
// Check for specific patterns
|
|
392
|
+
const deleteCount = actions.filter(a => a.tool_name === 'delete_file').length;
|
|
393
|
+
if (deleteCount > 0) {
|
|
394
|
+
recommendations.push(`${deleteCount} delete operation(s): verify backups before proceeding`);
|
|
395
|
+
}
|
|
396
|
+
const writeCount = actions.filter(a => a.tool_name === 'write_file').length;
|
|
397
|
+
if (writeCount > 5) {
|
|
398
|
+
recommendations.push('Multiple write operations: consider data consistency checks');
|
|
399
|
+
}
|
|
400
|
+
return recommendations;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Check if risk assessment should auto-block rollback
|
|
404
|
+
* Auto-block threshold: configurable, default 7.0
|
|
405
|
+
*/
|
|
406
|
+
function shouldAutoBlock(score, threshold = 7.0) {
|
|
407
|
+
return score >= threshold;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Get human-readable risk summary
|
|
411
|
+
*/
|
|
412
|
+
function getRiskSummary(assessment) {
|
|
413
|
+
const score = assessment.score.toFixed(1);
|
|
414
|
+
const level = assessment.level.toUpperCase();
|
|
415
|
+
let summary = `Risk Level: ${level} (${score}/10)\n`;
|
|
416
|
+
summary += '\nRisk Factors:\n';
|
|
417
|
+
for (const factor of assessment.factors) {
|
|
418
|
+
summary += ` • ${factor.category}: ${factor.weight.toFixed(1)}/3.0 - ${factor.reason}\n`;
|
|
419
|
+
}
|
|
420
|
+
if (assessment.blocked_reason) {
|
|
421
|
+
summary += `\n⚠️ AUTO-BLOCKED: ${assessment.blocked_reason}\n`;
|
|
422
|
+
}
|
|
423
|
+
if (assessment.recommendations.length > 0) {
|
|
424
|
+
summary += '\nRecommendations:\n';
|
|
425
|
+
for (const rec of assessment.recommendations) {
|
|
426
|
+
summary += ` → ${rec}\n`;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return summary;
|
|
430
|
+
}
|
|
431
|
+
exports.default = {
|
|
432
|
+
assessRisk,
|
|
433
|
+
calculateOperationTypeRisk,
|
|
434
|
+
calculateScaleRisk,
|
|
435
|
+
calculateErrorPatternRisk,
|
|
436
|
+
calculateTimeRisk,
|
|
437
|
+
calculateSystemStateRisk,
|
|
438
|
+
getRiskLevel,
|
|
439
|
+
generateRecommendations,
|
|
440
|
+
shouldAutoBlock,
|
|
441
|
+
getRiskSummary,
|
|
442
|
+
};
|