@way_marks/server 0.9.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,453 @@
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: map total weight (0-15) to score (0-10)
74
+ // Using divisor of 10.4 for balanced risk scaling
75
+ // Maps: low (3)→2.9, medium (4.3)→4.1, high (5.1)→4.9, critical (10.4)→10
76
+ const totalWeight = factors.reduce((sum, f) => sum + f.weight, 0);
77
+ const score = Math.min(10, (totalWeight / 10.4) * 10);
78
+ // Determine risk level
79
+ const level = getRiskLevel(score);
80
+ // Generate recommendations
81
+ const recommendations = generateRecommendations(score, level, factors, actions);
82
+ return {
83
+ score,
84
+ level,
85
+ factors,
86
+ recommendations,
87
+ timestamp,
88
+ };
89
+ }
90
+ /**
91
+ * Calculate operation type risk
92
+ * High-risk tools: write_file, delete_file, bash, api_call with mutations
93
+ * Medium-risk: read operations that require consistency
94
+ * Low-risk: information gathering, metadata operations
95
+ */
96
+ function calculateOperationTypeRisk(actions) {
97
+ const toolRisks = {
98
+ // HIGH RISK (2.0-2.5 points) - data modification/deletion
99
+ delete_file: 2.5,
100
+ bash: 2.0,
101
+ rmdir: 2.0,
102
+ // MEDIUM RISK (1.5-1.8 points) - write operations
103
+ write_file: 1.8,
104
+ mkdir: 1.5,
105
+ api_call: 1.5, // Depends on mutation type
106
+ // LOW RISK (0 points) - read-only
107
+ read_file: 0,
108
+ find_files: 0,
109
+ grep: 0,
110
+ };
111
+ const sub_factors = [];
112
+ let totalRisk = 0;
113
+ let highRiskCount = 0;
114
+ let deleteCount = 0;
115
+ let writeCount = 0;
116
+ let bashCount = 0;
117
+ for (const action of actions) {
118
+ const risk = toolRisks[action.tool_name] ?? 0.5; // Default medium for unknown tools
119
+ totalRisk += risk;
120
+ if (risk >= 2.0)
121
+ highRiskCount++;
122
+ if (action.tool_name === 'delete_file')
123
+ deleteCount++;
124
+ if (action.tool_name === 'write_file')
125
+ writeCount++;
126
+ if (action.tool_name === 'bash')
127
+ bashCount++;
128
+ sub_factors.push({
129
+ category: action.tool_name,
130
+ weight: risk,
131
+ reason: `Tool: ${action.tool_name}`,
132
+ });
133
+ }
134
+ const avgToolRisk = actions.length > 0 ? totalRisk / actions.length : 0;
135
+ const weight = Math.min(3, avgToolRisk);
136
+ let reason = `Average tool risk: ${avgToolRisk.toFixed(1)}/3.0`;
137
+ if (deleteCount > 0)
138
+ reason += ` (${deleteCount} delete operations)`;
139
+ if (writeCount > 0)
140
+ reason += ` (${writeCount} write operations)`;
141
+ if (bashCount > 0)
142
+ reason += ` (${bashCount} bash commands)`;
143
+ if (deleteCount === 0 && writeCount === 0 && bashCount === 0) {
144
+ reason += ' - read-only operations';
145
+ }
146
+ if (highRiskCount > actions.length / 2)
147
+ reason += ' - majority high-risk operations';
148
+ return { weight, reason, sub_factors };
149
+ }
150
+ /**
151
+ * Calculate scale risk based on number of actions
152
+ * 1 file = 0 risk
153
+ * 2-5 files = 0.5 risk
154
+ * 6-20 files = 1.5 risk
155
+ * 20+ files = 3 risk
156
+ */
157
+ function calculateScaleRisk(actions) {
158
+ const count = actions.length;
159
+ let weight = 0;
160
+ if (count <= 1) {
161
+ weight = 0;
162
+ }
163
+ else if (count <= 5) {
164
+ weight = 0.5;
165
+ }
166
+ else if (count <= 20) {
167
+ weight = 1.5;
168
+ }
169
+ else {
170
+ weight = 3.0;
171
+ }
172
+ return {
173
+ weight,
174
+ reason: `${count} action(s) - ${count <= 1
175
+ ? 'single operation'
176
+ : count <= 5
177
+ ? 'small batch'
178
+ : count <= 20
179
+ ? 'large batch'
180
+ : 'very large batch'}`,
181
+ };
182
+ }
183
+ /**
184
+ * Calculate error pattern risk
185
+ * No errors = 0
186
+ * Transient errors (timeout, network) = 0.5
187
+ * Permission errors = 1.5
188
+ * Data errors (validation, format) = 2.0
189
+ * System errors (crash, critical) = 3.0
190
+ */
191
+ function calculateErrorPatternRisk(actions) {
192
+ const sub_factors = [];
193
+ const errorActions = actions.filter(a => a.status === 'error');
194
+ if (errorActions.length === 0) {
195
+ return {
196
+ weight: 0,
197
+ reason: 'No errors - all operations successful',
198
+ sub_factors: [],
199
+ };
200
+ }
201
+ let totalErrorRisk = 0;
202
+ let transientCount = 0;
203
+ let permissionCount = 0;
204
+ let dataCount = 0;
205
+ let systemCount = 0;
206
+ for (const action of errorActions) {
207
+ const errorMsg = (action.error_message || '').toLowerCase();
208
+ let errorRisk = 0.5; // Default: transient
209
+ if (errorMsg.includes('permission') ||
210
+ errorMsg.includes('denied') ||
211
+ errorMsg.includes('unauthorized')) {
212
+ errorRisk = 1.5;
213
+ permissionCount++;
214
+ }
215
+ else if (errorMsg.includes('validation') ||
216
+ errorMsg.includes('format') ||
217
+ errorMsg.includes('invalid')) {
218
+ errorRisk = 2.0;
219
+ dataCount++;
220
+ }
221
+ else if (errorMsg.includes('crash') ||
222
+ errorMsg.includes('segfault') ||
223
+ errorMsg.includes('segmentation') ||
224
+ errorMsg.includes('fatal') ||
225
+ errorMsg.includes('panic')) {
226
+ errorRisk = 3.0;
227
+ systemCount++;
228
+ }
229
+ else if (errorMsg.includes('timeout') || errorMsg.includes('network')) {
230
+ errorRisk = 0.5;
231
+ transientCount++;
232
+ }
233
+ totalErrorRisk += errorRisk;
234
+ sub_factors.push({
235
+ category: `error_${action.action_id}`,
236
+ weight: errorRisk,
237
+ reason: `${action.tool_name}: ${action.error_message?.substring(0, 50) || 'unknown error'}`,
238
+ });
239
+ }
240
+ const avgErrorRisk = totalErrorRisk / errorActions.length;
241
+ const weight = Math.min(3, avgErrorRisk);
242
+ let reason = `${errorActions.length} error(s) - average risk: ${avgErrorRisk.toFixed(1)}/3.0`;
243
+ if (systemCount > 0)
244
+ reason += ` (${systemCount} system errors)`;
245
+ if (dataCount > 0)
246
+ reason += ` (${dataCount} data errors)`;
247
+ if (permissionCount > 0)
248
+ reason += ` (${permissionCount} permission errors)`;
249
+ if (transientCount > 0)
250
+ reason += ` (${transientCount} transient errors)`;
251
+ return { weight, reason, sub_factors };
252
+ }
253
+ /**
254
+ * Calculate time risk based on how old actions are
255
+ * <5 minutes = 0
256
+ * 5-60 minutes = 0.5
257
+ * 1-6 hours = 1.5
258
+ * 6-24 hours = 2.0
259
+ * 24+ hours = 3.0
260
+ */
261
+ function calculateTimeRisk(session) {
262
+ const createdAt = new Date(session.created_at);
263
+ const now = new Date();
264
+ const ageMs = now.getTime() - createdAt.getTime();
265
+ const ageMinutes = ageMs / (1000 * 60);
266
+ const ageHours = ageMinutes / 60;
267
+ const ageDays = ageHours / 24;
268
+ let weight = 0;
269
+ let ageStr = '';
270
+ if (ageMinutes < 5) {
271
+ weight = 0;
272
+ ageStr = `${Math.round(ageMinutes)}m ago - very fresh`;
273
+ }
274
+ else if (ageMinutes < 60) {
275
+ weight = 0.5;
276
+ ageStr = `${Math.round(ageMinutes)}m ago - recent`;
277
+ }
278
+ else if (ageHours < 6) {
279
+ weight = 1.5;
280
+ ageStr = `${Math.round(ageHours)}h ago - older`;
281
+ }
282
+ else if (ageHours < 24) {
283
+ weight = 2.0;
284
+ ageStr = `${Math.round(ageHours)}h ago - very old`;
285
+ }
286
+ else {
287
+ weight = 3.0;
288
+ ageStr = `${Math.round(ageDays)}d ago - ancient`;
289
+ }
290
+ return {
291
+ weight,
292
+ reason: ageStr,
293
+ };
294
+ }
295
+ /**
296
+ * Calculate system state risk
297
+ * Low load (cpu <50%, memory <50%) = 0
298
+ * Moderate (cpu 50-75%, memory 50-75%) = 1.0
299
+ * High (cpu 75-90%, memory 75-90%) = 2.0
300
+ * Critical (cpu >90%, memory >90%) = 3.0
301
+ */
302
+ function calculateSystemStateRisk(systemState) {
303
+ // Default: assume moderate load if no system state provided
304
+ if (!systemState) {
305
+ return {
306
+ weight: 1.0,
307
+ reason: 'System state unknown - assuming moderate load',
308
+ };
309
+ }
310
+ const avgLoad = (systemState.cpu_usage + systemState.memory_usage) / 2;
311
+ let weight = 0;
312
+ let description = '';
313
+ if (avgLoad < 50) {
314
+ weight = 0;
315
+ description = `Idle system (CPU: ${systemState.cpu_usage}%, Memory: ${systemState.memory_usage}%)`;
316
+ }
317
+ else if (avgLoad < 75) {
318
+ weight = 1.0;
319
+ description = `Moderate load (CPU: ${systemState.cpu_usage}%, Memory: ${systemState.memory_usage}%)`;
320
+ }
321
+ else if (avgLoad < 90) {
322
+ weight = 2.0;
323
+ description = `High load (CPU: ${systemState.cpu_usage}%, Memory: ${systemState.memory_usage}%)`;
324
+ }
325
+ else {
326
+ weight = 3.0;
327
+ description = `Critical load (CPU: ${systemState.cpu_usage}%, Memory: ${systemState.memory_usage}%)`;
328
+ }
329
+ if (systemState.active_users > 50) {
330
+ description += ` - ${systemState.active_users} active users`;
331
+ }
332
+ // Boost weight for very high request rates
333
+ if (systemState.request_rate > 1500) {
334
+ weight = Math.min(3, weight + 1.2);
335
+ description += ` - very high request rate (${systemState.request_rate} req/s)`;
336
+ }
337
+ else if (systemState.request_rate > 1000) {
338
+ weight = Math.min(3, weight + 0.5);
339
+ description += ` - high request rate (${systemState.request_rate} req/s)`;
340
+ }
341
+ return {
342
+ weight,
343
+ reason: description,
344
+ };
345
+ }
346
+ /**
347
+ * Determine risk level from score
348
+ */
349
+ function getRiskLevel(score) {
350
+ if (score < 1)
351
+ return 'none';
352
+ if (score < 3)
353
+ return 'low';
354
+ if (score < 4.9)
355
+ return 'medium';
356
+ if (score < 8)
357
+ return 'high';
358
+ return 'critical';
359
+ }
360
+ /**
361
+ * Generate recommendations based on risk assessment
362
+ */
363
+ function generateRecommendations(score, level, factors, actions) {
364
+ const recommendations = [];
365
+ // Score-based recommendations
366
+ if (score < 2) {
367
+ recommendations.push('Low risk: Safe to proceed with rollback');
368
+ }
369
+ else if (score < 4) {
370
+ recommendations.push('Moderate risk: Recommend approval from team lead');
371
+ }
372
+ else if (score < 7) {
373
+ recommendations.push('High risk: Requires escalation to manager');
374
+ recommendations.push('Consider partial rollback of safe operations only');
375
+ }
376
+ else {
377
+ recommendations.push('Critical risk: Requires executive approval');
378
+ recommendations.push('Recommend staged rollback with verification');
379
+ recommendations.push('Consider manual remediation instead of full rollback');
380
+ }
381
+ // Factor-specific recommendations
382
+ const operationFactor = factors.find(f => f.category === 'operation_type');
383
+ if (operationFactor && operationFactor.weight > 1.5) {
384
+ recommendations.push('Identify safe read-only operations for selective rollback');
385
+ }
386
+ const scaleFactor = factors.find(f => f.category === 'scale');
387
+ if (scaleFactor && scaleFactor.weight > 1.5) {
388
+ recommendations.push('Consider staged rollback due to large action count');
389
+ }
390
+ const errorFactor = factors.find(f => f.category === 'error_pattern');
391
+ if (errorFactor && errorFactor.weight >= 1.5) {
392
+ recommendations.push('Multiple errors detected: review each error before rollback');
393
+ }
394
+ const timeFactor = factors.find(f => f.category === 'time');
395
+ if (timeFactor && timeFactor.weight > 1.5) {
396
+ recommendations.push('Operations are old: ensure no dependencies have changed');
397
+ }
398
+ const systemFactor = factors.find(f => f.category === 'system_state');
399
+ if (systemFactor && systemFactor.weight > 1.5) {
400
+ recommendations.push('System under high load: consider scheduling rollback during off-peak');
401
+ }
402
+ // Check for specific patterns
403
+ const deleteCount = actions.filter(a => a.tool_name === 'delete_file').length;
404
+ if (deleteCount > 0) {
405
+ recommendations.push(`${deleteCount} delete operation(s): verify backups before proceeding`);
406
+ }
407
+ const writeCount = actions.filter(a => a.tool_name === 'write_file').length;
408
+ if (writeCount > 5) {
409
+ recommendations.push('Multiple write operations: consider data consistency checks');
410
+ }
411
+ return recommendations;
412
+ }
413
+ /**
414
+ * Check if risk assessment should auto-block rollback
415
+ * Auto-block threshold: configurable, default 7.0
416
+ */
417
+ function shouldAutoBlock(score, threshold = 7.0) {
418
+ return score >= threshold;
419
+ }
420
+ /**
421
+ * Get human-readable risk summary
422
+ */
423
+ function getRiskSummary(assessment) {
424
+ const score = assessment.score.toFixed(1);
425
+ const level = assessment.level.toUpperCase();
426
+ let summary = `Risk Level: ${level} (${score}/10)\n`;
427
+ summary += '\nRisk Factors:\n';
428
+ for (const factor of assessment.factors) {
429
+ summary += ` • ${factor.category}: ${factor.weight.toFixed(1)}/3.0 - ${factor.reason}\n`;
430
+ }
431
+ if (assessment.blocked_reason) {
432
+ summary += `\n⚠️ AUTO-BLOCKED: ${assessment.blocked_reason}\n`;
433
+ }
434
+ if (assessment.recommendations.length > 0) {
435
+ summary += '\nRecommendations:\n';
436
+ for (const rec of assessment.recommendations) {
437
+ summary += ` → ${rec}\n`;
438
+ }
439
+ }
440
+ return summary;
441
+ }
442
+ exports.default = {
443
+ assessRisk,
444
+ calculateOperationTypeRisk,
445
+ calculateScaleRisk,
446
+ calculateErrorPatternRisk,
447
+ calculateTimeRisk,
448
+ calculateSystemStateRisk,
449
+ getRiskLevel,
450
+ generateRecommendations,
451
+ shouldAutoBlock,
452
+ getRiskSummary,
453
+ };
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ /**
3
+ * Auto-Block Rules Engine — Phase 4D
4
+ *
5
+ * Automatically blocks risky rollbacks based on:
6
+ * - Risk assessment score
7
+ * - Policy violations
8
+ * - Configured block rules
9
+ *
10
+ * Prevents rollback of:
11
+ * - High-risk operations (score > threshold)
12
+ * - Policy-violating operations
13
+ * - Manual block rules
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.evaluateAutoBlock = evaluateAutoBlock;
17
+ exports.createBlock = createBlock;
18
+ exports.unblockSession = unblockSession;
19
+ exports.getDefaultBlockRules = getDefaultBlockRules;
20
+ /**
21
+ * Evaluate if rollback should be auto-blocked
22
+ */
23
+ function evaluateAutoBlock(session, actions, riskAssessment, policyViolations, blockRules, riskThreshold = 7.0, userRole = 'user') {
24
+ const blockingRules = [];
25
+ const blockedAt = new Date().toISOString();
26
+ const blockId = generateBlockId();
27
+ // Check risk score threshold
28
+ const isHighRisk = riskAssessment.score >= riskThreshold;
29
+ // Check matching block rules
30
+ const matchingRules = blockRules.filter(rule => rule.enabled && evaluateBlockRuleCondition(rule.condition, actions, session));
31
+ blockingRules.push(...matchingRules);
32
+ // Check policy violations with block action
33
+ const blockingViolations = policyViolations.filter(v => v.action === 'block');
34
+ // Determine if blocked
35
+ const hasBlockingViolations = blockingViolations.length > 0;
36
+ const blocked = isHighRisk || blockingRules.length > 0 || hasBlockingViolations;
37
+ // Generate reason
38
+ let reason = '';
39
+ const reasons = [];
40
+ if (isHighRisk) {
41
+ reasons.push(`Risk score ${riskAssessment.score.toFixed(1)}/10 exceeds threshold ${riskThreshold}`);
42
+ }
43
+ if (blockingRules.length > 0) {
44
+ reasons.push(`${blockingRules.length} block rule(s) matched`);
45
+ }
46
+ if (hasBlockingViolations) {
47
+ reasons.push(`${blockingViolations.length} policy violation(s) require blocking`);
48
+ }
49
+ reason = reasons.join('; ');
50
+ // Determine override capability
51
+ const canOverride = userRole === 'admin' || userRole === 'super_admin';
52
+ const overrideRole = userRole === 'admin' || userRole === 'super_admin' ? 'admin' : 'admin';
53
+ const result = {
54
+ blocked,
55
+ reason: blocked ? reason : undefined,
56
+ blocking_rules: blockingRules,
57
+ policy_violations: blockingViolations,
58
+ risk_score: riskAssessment.score,
59
+ can_override: canOverride,
60
+ override_required_role: overrideRole,
61
+ };
62
+ if (blocked) {
63
+ result.block_id = blockId;
64
+ result.blocked_at = blockedAt;
65
+ }
66
+ return result;
67
+ }
68
+ /**
69
+ * Evaluate block rule condition
70
+ */
71
+ function evaluateBlockRuleCondition(condition, actions, session) {
72
+ // Use same logic as policy conditions
73
+ // Check if any action matches
74
+ for (const action of actions) {
75
+ if (matchCondition(condition, action, session)) {
76
+ return true;
77
+ }
78
+ }
79
+ return false;
80
+ }
81
+ /**
82
+ * Simple condition matcher
83
+ */
84
+ function matchCondition(condition, action, session) {
85
+ switch (condition.type) {
86
+ case 'operation_type':
87
+ case 'tool_name':
88
+ return matchString(action.tool_name, condition.value, condition.operator);
89
+ case 'action_count':
90
+ return matchNumeric(session.action_count, condition.value, condition.operator);
91
+ case 'file_pattern':
92
+ const target = action.target || '';
93
+ return matchString(target, condition.value, condition.operator);
94
+ case 'error_present':
95
+ const hasError = action.status === 'error';
96
+ return hasError === (condition.value === 'true');
97
+ default:
98
+ return false;
99
+ }
100
+ }
101
+ /**
102
+ * String comparison
103
+ */
104
+ function matchString(actual, expected, operator) {
105
+ if (operator === 'equals' || operator === 'in') {
106
+ if (Array.isArray(expected)) {
107
+ return expected.includes(actual);
108
+ }
109
+ return actual === String(expected);
110
+ }
111
+ if (operator === 'contains') {
112
+ if (Array.isArray(expected)) {
113
+ return expected.some(e => actual.includes(String(e)));
114
+ }
115
+ return actual.includes(String(expected));
116
+ }
117
+ if (operator === 'matches_regex') {
118
+ try {
119
+ const regex = new RegExp(String(expected));
120
+ return regex.test(actual);
121
+ }
122
+ catch {
123
+ return false;
124
+ }
125
+ }
126
+ return false;
127
+ }
128
+ /**
129
+ * Numeric comparison
130
+ */
131
+ function matchNumeric(actual, expected, operator) {
132
+ const expectedNum = typeof expected === 'number' ? expected : parseInt(String(expected), 10);
133
+ if (isNaN(expectedNum))
134
+ return false;
135
+ switch (operator) {
136
+ case 'equals':
137
+ return actual === expectedNum;
138
+ case 'greater_than':
139
+ return actual > expectedNum;
140
+ case 'less_than':
141
+ return actual < expectedNum;
142
+ default:
143
+ return false;
144
+ }
145
+ }
146
+ /**
147
+ * Create a block for a session
148
+ */
149
+ function createBlock(sessionId, riskScore, reason, ruleId, policyViolationCount = 0) {
150
+ return {
151
+ block_id: generateBlockId(),
152
+ session_id: sessionId,
153
+ rule_id: ruleId,
154
+ policy_violation_count: policyViolationCount,
155
+ risk_score: riskScore,
156
+ reason,
157
+ blocked_at: new Date().toISOString(),
158
+ };
159
+ }
160
+ /**
161
+ * Unblock a session (admin override)
162
+ */
163
+ function unblockSession(block, unblockedBy, reason, overrideToken) {
164
+ return {
165
+ ...block,
166
+ unblocked_at: new Date().toISOString(),
167
+ unblocked_by: unblockedBy,
168
+ unblock_reason: reason,
169
+ override_token: overrideToken,
170
+ };
171
+ }
172
+ /**
173
+ * Generate unique block ID
174
+ */
175
+ function generateBlockId() {
176
+ return `block-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
177
+ }
178
+ /**
179
+ * Get default block rules
180
+ */
181
+ function getDefaultBlockRules() {
182
+ return [
183
+ {
184
+ rule_id: 'block-production-delete-hours',
185
+ name: 'Production Delete During Off-Hours',
186
+ description: 'Block delete operations in production environment during off-hours',
187
+ condition: {
188
+ type: 'operation_type',
189
+ operator: 'equals',
190
+ value: 'delete_file',
191
+ },
192
+ action: 'block',
193
+ severity: 'error',
194
+ message: 'Cannot delete files during off-hours in production',
195
+ enabled: true,
196
+ created_at: new Date().toISOString(),
197
+ created_by: 'system',
198
+ },
199
+ {
200
+ rule_id: 'block-large-batch-delete',
201
+ name: 'Large Batch Delete Protection',
202
+ description: 'Block deletion of more than 20 files in single session',
203
+ condition: {
204
+ type: 'action_count',
205
+ operator: 'greater_than',
206
+ value: 20,
207
+ },
208
+ action: 'require_approval',
209
+ severity: 'warning',
210
+ message: 'Large batch deletion requires approval',
211
+ enabled: true,
212
+ created_at: new Date().toISOString(),
213
+ created_by: 'system',
214
+ },
215
+ ];
216
+ }
217
+ exports.default = {
218
+ evaluateAutoBlock,
219
+ createBlock,
220
+ unblockSession,
221
+ getDefaultBlockRules,
222
+ };