@weave_protocol/domere 1.0.13 → 1.0.14
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/audit/index.d.ts +2 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +2 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/replay.d.ts +153 -0
- package/dist/audit/replay.d.ts.map +1 -0
- package/dist/audit/replay.js +328 -0
- package/dist/audit/replay.js.map +1 -0
- package/dist/compliance/checkpoint.d.ts +183 -0
- package/dist/compliance/checkpoint.d.ts.map +1 -0
- package/dist/compliance/checkpoint.js +394 -0
- package/dist/compliance/checkpoint.js.map +1 -0
- package/dist/compliance/index.d.ts +2 -0
- package/dist/compliance/index.d.ts.map +1 -0
- package/dist/compliance/index.js +2 -0
- package/dist/compliance/index.js.map +1 -0
- package/dist/handoff/index.d.ts +2 -0
- package/dist/handoff/index.d.ts.map +1 -0
- package/dist/handoff/index.js +2 -0
- package/dist/handoff/index.js.map +1 -0
- package/dist/handoff/verification.d.ts +115 -0
- package/dist/handoff/verification.d.ts.map +1 -0
- package/dist/handoff/verification.js +285 -0
- package/dist/handoff/verification.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/audit/index.ts +1 -0
- package/src/audit/replay.ts +504 -0
- package/src/compliance/checkpoint.ts +647 -0
- package/src/compliance/index.ts +1 -0
- package/src/handoff/index.ts +1 -0
- package/src/handoff/verification.ts +428 -0
- package/src/index.ts +5 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dōmere - Execution Replay & Audit Trail
|
|
3
|
+
*
|
|
4
|
+
* Cryptographically verifiable audit trail for AI agent actions.
|
|
5
|
+
* Enables complete replay and forensic analysis of agent behavior.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as crypto from 'crypto';
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Types
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
export interface ActionRecord {
|
|
15
|
+
id: string;
|
|
16
|
+
thread_id: string;
|
|
17
|
+
sequence: number;
|
|
18
|
+
timestamp: Date;
|
|
19
|
+
|
|
20
|
+
// Actor
|
|
21
|
+
agent_id: string;
|
|
22
|
+
agent_type: 'llm' | 'tool' | 'human' | 'system';
|
|
23
|
+
|
|
24
|
+
// Action details
|
|
25
|
+
action_type: 'inference' | 'tool_call' | 'api_request' | 'file_access' | 'data_read' | 'data_write' | 'delegation' | 'decision';
|
|
26
|
+
action_name: string;
|
|
27
|
+
|
|
28
|
+
// I/O hashes (not raw data for privacy)
|
|
29
|
+
input_hash: string;
|
|
30
|
+
output_hash: string;
|
|
31
|
+
input_size_bytes?: number;
|
|
32
|
+
output_size_bytes?: number;
|
|
33
|
+
|
|
34
|
+
// Optional raw data (encrypted)
|
|
35
|
+
input_encrypted?: string;
|
|
36
|
+
output_encrypted?: string;
|
|
37
|
+
|
|
38
|
+
// Metadata
|
|
39
|
+
latency_ms: number;
|
|
40
|
+
cost_usd?: number;
|
|
41
|
+
tokens_in?: number;
|
|
42
|
+
tokens_out?: number;
|
|
43
|
+
model?: string;
|
|
44
|
+
provider?: string;
|
|
45
|
+
|
|
46
|
+
// Verification
|
|
47
|
+
previous_hash: string;
|
|
48
|
+
action_hash: string;
|
|
49
|
+
signature?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ExecutionTrail {
|
|
53
|
+
thread_id: string;
|
|
54
|
+
created_at: Date;
|
|
55
|
+
updated_at: Date;
|
|
56
|
+
action_count: number;
|
|
57
|
+
total_cost_usd: number;
|
|
58
|
+
total_latency_ms: number;
|
|
59
|
+
agents_involved: string[];
|
|
60
|
+
merkle_root: string;
|
|
61
|
+
actions: ActionRecord[];
|
|
62
|
+
integrity_valid: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ReplayOptions {
|
|
66
|
+
from_sequence?: number;
|
|
67
|
+
to_sequence?: number;
|
|
68
|
+
agent_filter?: string[];
|
|
69
|
+
action_type_filter?: ActionRecord['action_type'][];
|
|
70
|
+
include_encrypted?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface AuditQuery {
|
|
74
|
+
thread_id?: string;
|
|
75
|
+
agent_id?: string;
|
|
76
|
+
action_type?: ActionRecord['action_type'];
|
|
77
|
+
start_time?: Date;
|
|
78
|
+
end_time?: Date;
|
|
79
|
+
min_cost_usd?: number;
|
|
80
|
+
min_latency_ms?: number;
|
|
81
|
+
limit?: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface AuditReport {
|
|
85
|
+
query: AuditQuery;
|
|
86
|
+
generated_at: Date;
|
|
87
|
+
total_actions: number;
|
|
88
|
+
total_cost_usd: number;
|
|
89
|
+
total_latency_ms: number;
|
|
90
|
+
actions_by_type: Record<string, number>;
|
|
91
|
+
actions_by_agent: Record<string, number>;
|
|
92
|
+
cost_by_agent: Record<string, number>;
|
|
93
|
+
anomalies: AuditAnomaly[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface AuditAnomaly {
|
|
97
|
+
type: 'high_latency' | 'high_cost' | 'repeated_failure' | 'unusual_pattern' | 'integrity_violation';
|
|
98
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
99
|
+
description: string;
|
|
100
|
+
action_ids: string[];
|
|
101
|
+
detected_at: Date;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// =============================================================================
|
|
105
|
+
// Execution Replay Manager
|
|
106
|
+
// =============================================================================
|
|
107
|
+
|
|
108
|
+
export class ExecutionReplayManager {
|
|
109
|
+
private trails: Map<string, ActionRecord[]> = new Map();
|
|
110
|
+
private encryptionKey?: Buffer;
|
|
111
|
+
|
|
112
|
+
constructor(encryptionKey?: string) {
|
|
113
|
+
if (encryptionKey) {
|
|
114
|
+
this.encryptionKey = crypto.scryptSync(encryptionKey, 'domere-audit', 32);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Record an action in the audit trail
|
|
120
|
+
*/
|
|
121
|
+
async recordAction(params: {
|
|
122
|
+
thread_id: string;
|
|
123
|
+
agent_id: string;
|
|
124
|
+
agent_type: ActionRecord['agent_type'];
|
|
125
|
+
action_type: ActionRecord['action_type'];
|
|
126
|
+
action_name: string;
|
|
127
|
+
input: any;
|
|
128
|
+
output: any;
|
|
129
|
+
latency_ms: number;
|
|
130
|
+
cost_usd?: number;
|
|
131
|
+
tokens_in?: number;
|
|
132
|
+
tokens_out?: number;
|
|
133
|
+
model?: string;
|
|
134
|
+
provider?: string;
|
|
135
|
+
store_raw?: boolean;
|
|
136
|
+
}): Promise<ActionRecord> {
|
|
137
|
+
const trail = this.trails.get(params.thread_id) || [];
|
|
138
|
+
const sequence = trail.length;
|
|
139
|
+
const previousHash = sequence > 0 ? trail[sequence - 1].action_hash : '0'.repeat(64);
|
|
140
|
+
|
|
141
|
+
// Hash inputs/outputs
|
|
142
|
+
const inputStr = JSON.stringify(params.input);
|
|
143
|
+
const outputStr = JSON.stringify(params.output);
|
|
144
|
+
const inputHash = crypto.createHash('sha256').update(inputStr).digest('hex');
|
|
145
|
+
const outputHash = crypto.createHash('sha256').update(outputStr).digest('hex');
|
|
146
|
+
|
|
147
|
+
// Create action record
|
|
148
|
+
const record: ActionRecord = {
|
|
149
|
+
id: `act_${crypto.randomUUID()}`,
|
|
150
|
+
thread_id: params.thread_id,
|
|
151
|
+
sequence,
|
|
152
|
+
timestamp: new Date(),
|
|
153
|
+
|
|
154
|
+
agent_id: params.agent_id,
|
|
155
|
+
agent_type: params.agent_type,
|
|
156
|
+
|
|
157
|
+
action_type: params.action_type,
|
|
158
|
+
action_name: params.action_name,
|
|
159
|
+
|
|
160
|
+
input_hash: inputHash,
|
|
161
|
+
output_hash: outputHash,
|
|
162
|
+
input_size_bytes: Buffer.byteLength(inputStr),
|
|
163
|
+
output_size_bytes: Buffer.byteLength(outputStr),
|
|
164
|
+
|
|
165
|
+
latency_ms: params.latency_ms,
|
|
166
|
+
cost_usd: params.cost_usd,
|
|
167
|
+
tokens_in: params.tokens_in,
|
|
168
|
+
tokens_out: params.tokens_out,
|
|
169
|
+
model: params.model,
|
|
170
|
+
provider: params.provider,
|
|
171
|
+
|
|
172
|
+
previous_hash: previousHash,
|
|
173
|
+
action_hash: '', // Computed below
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Optionally encrypt and store raw data
|
|
177
|
+
if (params.store_raw && this.encryptionKey) {
|
|
178
|
+
record.input_encrypted = this.encrypt(inputStr);
|
|
179
|
+
record.output_encrypted = this.encrypt(outputStr);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Compute action hash (chain integrity)
|
|
183
|
+
record.action_hash = this.computeActionHash(record);
|
|
184
|
+
|
|
185
|
+
// Store
|
|
186
|
+
trail.push(record);
|
|
187
|
+
this.trails.set(params.thread_id, trail);
|
|
188
|
+
|
|
189
|
+
return record;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get complete execution trail for a thread
|
|
194
|
+
*/
|
|
195
|
+
async getExecutionTrail(threadId: string, options?: ReplayOptions): Promise<ExecutionTrail | null> {
|
|
196
|
+
const actions = this.trails.get(threadId);
|
|
197
|
+
if (!actions || actions.length === 0) return null;
|
|
198
|
+
|
|
199
|
+
let filtered = [...actions];
|
|
200
|
+
|
|
201
|
+
// Apply filters
|
|
202
|
+
if (options?.from_sequence !== undefined) {
|
|
203
|
+
filtered = filtered.filter(a => a.sequence >= options.from_sequence!);
|
|
204
|
+
}
|
|
205
|
+
if (options?.to_sequence !== undefined) {
|
|
206
|
+
filtered = filtered.filter(a => a.sequence <= options.to_sequence!);
|
|
207
|
+
}
|
|
208
|
+
if (options?.agent_filter?.length) {
|
|
209
|
+
filtered = filtered.filter(a => options.agent_filter!.includes(a.agent_id));
|
|
210
|
+
}
|
|
211
|
+
if (options?.action_type_filter?.length) {
|
|
212
|
+
filtered = filtered.filter(a => options.action_type_filter!.includes(a.action_type));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Remove encrypted data if not requested
|
|
216
|
+
if (!options?.include_encrypted) {
|
|
217
|
+
filtered = filtered.map(a => {
|
|
218
|
+
const { input_encrypted, output_encrypted, ...rest } = a;
|
|
219
|
+
return rest as ActionRecord;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Compute stats
|
|
224
|
+
const totalCost = filtered.reduce((sum, a) => sum + (a.cost_usd || 0), 0);
|
|
225
|
+
const totalLatency = filtered.reduce((sum, a) => sum + a.latency_ms, 0);
|
|
226
|
+
const agents = [...new Set(filtered.map(a => a.agent_id))];
|
|
227
|
+
|
|
228
|
+
// Verify integrity
|
|
229
|
+
const integrityValid = this.verifyTrailIntegrity(actions);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
thread_id: threadId,
|
|
233
|
+
created_at: actions[0].timestamp,
|
|
234
|
+
updated_at: actions[actions.length - 1].timestamp,
|
|
235
|
+
action_count: filtered.length,
|
|
236
|
+
total_cost_usd: totalCost,
|
|
237
|
+
total_latency_ms: totalLatency,
|
|
238
|
+
agents_involved: agents,
|
|
239
|
+
merkle_root: this.computeMerkleRoot(actions),
|
|
240
|
+
actions: filtered,
|
|
241
|
+
integrity_valid: integrityValid,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Replay actions for debugging/analysis
|
|
247
|
+
*/
|
|
248
|
+
async replayActions(threadId: string, options?: ReplayOptions): Promise<{
|
|
249
|
+
actions: ActionRecord[];
|
|
250
|
+
timeline: { timestamp: Date; description: string }[];
|
|
251
|
+
summary: {
|
|
252
|
+
total_actions: number;
|
|
253
|
+
duration_ms: number;
|
|
254
|
+
cost_usd: number;
|
|
255
|
+
agents: string[];
|
|
256
|
+
};
|
|
257
|
+
}> {
|
|
258
|
+
const trail = await this.getExecutionTrail(threadId, options);
|
|
259
|
+
if (!trail) {
|
|
260
|
+
throw new Error(`No trail found for thread ${threadId}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const timeline = trail.actions.map(action => ({
|
|
264
|
+
timestamp: action.timestamp,
|
|
265
|
+
description: `[${action.agent_id}] ${action.action_type}: ${action.action_name} (${action.latency_ms}ms)`,
|
|
266
|
+
}));
|
|
267
|
+
|
|
268
|
+
const duration = trail.actions.length > 1
|
|
269
|
+
? trail.actions[trail.actions.length - 1].timestamp.getTime() - trail.actions[0].timestamp.getTime()
|
|
270
|
+
: 0;
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
actions: trail.actions,
|
|
274
|
+
timeline,
|
|
275
|
+
summary: {
|
|
276
|
+
total_actions: trail.action_count,
|
|
277
|
+
duration_ms: duration,
|
|
278
|
+
cost_usd: trail.total_cost_usd,
|
|
279
|
+
agents: trail.agents_involved,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Query actions across threads
|
|
286
|
+
*/
|
|
287
|
+
async queryActions(query: AuditQuery): Promise<ActionRecord[]> {
|
|
288
|
+
let results: ActionRecord[] = [];
|
|
289
|
+
|
|
290
|
+
for (const [threadId, actions] of this.trails) {
|
|
291
|
+
if (query.thread_id && threadId !== query.thread_id) continue;
|
|
292
|
+
|
|
293
|
+
for (const action of actions) {
|
|
294
|
+
if (query.agent_id && action.agent_id !== query.agent_id) continue;
|
|
295
|
+
if (query.action_type && action.action_type !== query.action_type) continue;
|
|
296
|
+
if (query.start_time && action.timestamp < query.start_time) continue;
|
|
297
|
+
if (query.end_time && action.timestamp > query.end_time) continue;
|
|
298
|
+
if (query.min_cost_usd && (action.cost_usd || 0) < query.min_cost_usd) continue;
|
|
299
|
+
if (query.min_latency_ms && action.latency_ms < query.min_latency_ms) continue;
|
|
300
|
+
|
|
301
|
+
results.push(action);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Sort by timestamp
|
|
306
|
+
results.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
307
|
+
|
|
308
|
+
// Apply limit
|
|
309
|
+
if (query.limit) {
|
|
310
|
+
results = results.slice(0, query.limit);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return results;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Generate audit report
|
|
318
|
+
*/
|
|
319
|
+
async generateAuditReport(query: AuditQuery): Promise<AuditReport> {
|
|
320
|
+
const actions = await this.queryActions(query);
|
|
321
|
+
|
|
322
|
+
const actionsByType: Record<string, number> = {};
|
|
323
|
+
const actionsByAgent: Record<string, number> = {};
|
|
324
|
+
const costByAgent: Record<string, number> = {};
|
|
325
|
+
let totalCost = 0;
|
|
326
|
+
let totalLatency = 0;
|
|
327
|
+
|
|
328
|
+
for (const action of actions) {
|
|
329
|
+
actionsByType[action.action_type] = (actionsByType[action.action_type] || 0) + 1;
|
|
330
|
+
actionsByAgent[action.agent_id] = (actionsByAgent[action.agent_id] || 0) + 1;
|
|
331
|
+
costByAgent[action.agent_id] = (costByAgent[action.agent_id] || 0) + (action.cost_usd || 0);
|
|
332
|
+
totalCost += action.cost_usd || 0;
|
|
333
|
+
totalLatency += action.latency_ms;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Detect anomalies
|
|
337
|
+
const anomalies = this.detectAnomalies(actions);
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
query,
|
|
341
|
+
generated_at: new Date(),
|
|
342
|
+
total_actions: actions.length,
|
|
343
|
+
total_cost_usd: totalCost,
|
|
344
|
+
total_latency_ms: totalLatency,
|
|
345
|
+
actions_by_type: actionsByType,
|
|
346
|
+
actions_by_agent: actionsByAgent,
|
|
347
|
+
cost_by_agent: costByAgent,
|
|
348
|
+
anomalies,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Verify trail integrity
|
|
354
|
+
*/
|
|
355
|
+
verifyTrailIntegrity(actions: ActionRecord[]): boolean {
|
|
356
|
+
if (actions.length === 0) return true;
|
|
357
|
+
|
|
358
|
+
for (let i = 0; i < actions.length; i++) {
|
|
359
|
+
const action = actions[i];
|
|
360
|
+
const expectedPrevHash = i === 0 ? '0'.repeat(64) : actions[i - 1].action_hash;
|
|
361
|
+
|
|
362
|
+
if (action.previous_hash !== expectedPrevHash) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const computedHash = this.computeActionHash(action);
|
|
367
|
+
if (action.action_hash !== computedHash) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Export trail for external storage/verification
|
|
377
|
+
*/
|
|
378
|
+
async exportTrail(threadId: string): Promise<string> {
|
|
379
|
+
const trail = await this.getExecutionTrail(threadId, { include_encrypted: true });
|
|
380
|
+
if (!trail) throw new Error(`No trail found for thread ${threadId}`);
|
|
381
|
+
|
|
382
|
+
return JSON.stringify(trail, null, 2);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Import trail from external source
|
|
387
|
+
*/
|
|
388
|
+
async importTrail(data: string): Promise<{ thread_id: string; actions_imported: number; valid: boolean }> {
|
|
389
|
+
const trail: ExecutionTrail = JSON.parse(data);
|
|
390
|
+
|
|
391
|
+
// Verify integrity
|
|
392
|
+
const valid = this.verifyTrailIntegrity(trail.actions);
|
|
393
|
+
|
|
394
|
+
// Store
|
|
395
|
+
this.trails.set(trail.thread_id, trail.actions);
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
thread_id: trail.thread_id,
|
|
399
|
+
actions_imported: trail.actions.length,
|
|
400
|
+
valid,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ===========================================================================
|
|
405
|
+
// Private Methods
|
|
406
|
+
// ===========================================================================
|
|
407
|
+
|
|
408
|
+
private computeActionHash(action: ActionRecord): string {
|
|
409
|
+
const data = [
|
|
410
|
+
action.thread_id,
|
|
411
|
+
action.sequence.toString(),
|
|
412
|
+
action.agent_id,
|
|
413
|
+
action.action_type,
|
|
414
|
+
action.action_name,
|
|
415
|
+
action.input_hash,
|
|
416
|
+
action.output_hash,
|
|
417
|
+
action.timestamp.toISOString(),
|
|
418
|
+
action.previous_hash,
|
|
419
|
+
].join('|');
|
|
420
|
+
|
|
421
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private computeMerkleRoot(actions: ActionRecord[]): string {
|
|
425
|
+
if (actions.length === 0) return '0'.repeat(64);
|
|
426
|
+
|
|
427
|
+
let hashes = actions.map(a => a.action_hash);
|
|
428
|
+
|
|
429
|
+
while (hashes.length > 1) {
|
|
430
|
+
const newHashes: string[] = [];
|
|
431
|
+
for (let i = 0; i < hashes.length; i += 2) {
|
|
432
|
+
const left = hashes[i];
|
|
433
|
+
const right = hashes[i + 1] || left;
|
|
434
|
+
const combined = crypto.createHash('sha256').update(left + right).digest('hex');
|
|
435
|
+
newHashes.push(combined);
|
|
436
|
+
}
|
|
437
|
+
hashes = newHashes;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return hashes[0];
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private encrypt(data: string): string {
|
|
444
|
+
if (!this.encryptionKey) throw new Error('Encryption key not set');
|
|
445
|
+
|
|
446
|
+
const iv = crypto.randomBytes(16);
|
|
447
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', this.encryptionKey, iv);
|
|
448
|
+
|
|
449
|
+
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
450
|
+
encrypted += cipher.final('hex');
|
|
451
|
+
|
|
452
|
+
const authTag = cipher.getAuthTag();
|
|
453
|
+
|
|
454
|
+
return iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private decrypt(encrypted: string): string {
|
|
458
|
+
if (!this.encryptionKey) throw new Error('Encryption key not set');
|
|
459
|
+
|
|
460
|
+
const [ivHex, authTagHex, data] = encrypted.split(':');
|
|
461
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
462
|
+
const authTag = Buffer.from(authTagHex, 'hex');
|
|
463
|
+
|
|
464
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', this.encryptionKey, iv);
|
|
465
|
+
decipher.setAuthTag(authTag);
|
|
466
|
+
|
|
467
|
+
let decrypted = decipher.update(data, 'hex', 'utf8');
|
|
468
|
+
decrypted += decipher.final('utf8');
|
|
469
|
+
|
|
470
|
+
return decrypted;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
private detectAnomalies(actions: ActionRecord[]): AuditAnomaly[] {
|
|
474
|
+
const anomalies: AuditAnomaly[] = [];
|
|
475
|
+
|
|
476
|
+
// High latency detection (>10s)
|
|
477
|
+
const highLatency = actions.filter(a => a.latency_ms > 10000);
|
|
478
|
+
if (highLatency.length > 0) {
|
|
479
|
+
anomalies.push({
|
|
480
|
+
type: 'high_latency',
|
|
481
|
+
severity: 'medium',
|
|
482
|
+
description: `${highLatency.length} actions exceeded 10s latency`,
|
|
483
|
+
action_ids: highLatency.map(a => a.id),
|
|
484
|
+
detected_at: new Date(),
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// High cost detection (>$1 per action)
|
|
489
|
+
const highCost = actions.filter(a => (a.cost_usd || 0) > 1);
|
|
490
|
+
if (highCost.length > 0) {
|
|
491
|
+
anomalies.push({
|
|
492
|
+
type: 'high_cost',
|
|
493
|
+
severity: 'high',
|
|
494
|
+
description: `${highCost.length} actions exceeded $1 cost`,
|
|
495
|
+
action_ids: highCost.map(a => a.id),
|
|
496
|
+
detected_at: new Date(),
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return anomalies;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export default ExecutionReplayManager;
|