@way_marks/server 1.0.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.
@@ -1,552 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- const fs = __importStar(require("fs"));
37
- const path = __importStar(require("path"));
38
- const os = __importStar(require("os"));
39
- const manager_1 = require("./manager");
40
- // ─── Test Helpers ───────────────────────────────────────────────────────────
41
- function makeTempDir() {
42
- return fs.mkdtempSync(path.join(os.tmpdir(), 'waymark-test-'));
43
- }
44
- function cleanupTempDir(dir) {
45
- if (fs.existsSync(dir)) {
46
- fs.rmSync(dir, { recursive: true, force: true });
47
- }
48
- }
49
- function makeTestAction(overrides = {}) {
50
- return {
51
- id: 1,
52
- action_id: 'test-action-1',
53
- session_id: 'test-session-1',
54
- tool_name: 'write_file',
55
- target_path: '/tmp/test.txt',
56
- input_payload: '{}',
57
- before_snapshot: null,
58
- after_snapshot: null,
59
- status: 'success',
60
- error_message: null,
61
- stdout: null,
62
- stderr: null,
63
- rolled_back: 0,
64
- rolled_back_at: null,
65
- created_at: new Date().toISOString(),
66
- decision: 'allow',
67
- policy_reason: null,
68
- matched_rule: null,
69
- approved_at: null,
70
- approved_by: null,
71
- rejected_at: null,
72
- rejected_reason: null,
73
- event_type: 'execution',
74
- observation_context: null,
75
- request_source: 'direct',
76
- source: 'mcp',
77
- is_reversible: 1,
78
- ...overrides,
79
- };
80
- }
81
- // ─── Tests: validateRollbackable ─────────────────────────────────────────────
82
- describe('rollback/manager.ts', () => {
83
- describe('validateRollbackable', () => {
84
- it('should accept all reversible actions', () => {
85
- const actions = [
86
- makeTestAction({ action_id: 'a1', is_reversible: 1 }),
87
- makeTestAction({ action_id: 'a2', is_reversible: 1 }),
88
- makeTestAction({ action_id: 'a3', is_reversible: 1 }),
89
- ];
90
- const result = (0, manager_1.validateRollbackable)(actions);
91
- expect(result.isValid).toBe(true);
92
- expect(result.errors).toEqual([]);
93
- });
94
- it('should reject non-reversible write_file actions', () => {
95
- const actions = [
96
- makeTestAction({
97
- action_id: 'a1',
98
- is_reversible: 1,
99
- before_snapshot: JSON.stringify({ file_path: '/tmp/test.txt', content: 'old', existed: true }),
100
- }),
101
- makeTestAction({
102
- action_id: 'a2',
103
- is_reversible: 0, // Not reversible!
104
- before_snapshot: null,
105
- }),
106
- ];
107
- const result = (0, manager_1.validateRollbackable)(actions);
108
- expect(result.isValid).toBe(false);
109
- expect(result.errors.length).toBe(1);
110
- expect(result.errors[0].action_id).toBe('a2');
111
- expect(result.errors[0].reason).toMatch(/non-reversible/i);
112
- });
113
- it('should reject write_file without before_snapshot', () => {
114
- const actions = [
115
- makeTestAction({
116
- action_id: 'a1',
117
- tool_name: 'write_file',
118
- is_reversible: 1,
119
- before_snapshot: null, // Missing!
120
- }),
121
- ];
122
- const result = (0, manager_1.validateRollbackable)(actions);
123
- expect(result.isValid).toBe(false);
124
- expect(result.errors.length).toBe(1);
125
- expect(result.errors[0].reason).toMatch(/before_snapshot/i);
126
- });
127
- it('should warn about irreversible bash commands (DELETE)', () => {
128
- const actions = [
129
- makeTestAction({
130
- action_id: 'a1',
131
- tool_name: 'bash',
132
- is_reversible: 1,
133
- input_payload: JSON.stringify({ command: 'DELETE FROM users WHERE id = 1' }),
134
- }),
135
- ];
136
- const result = (0, manager_1.validateRollbackable)(actions);
137
- expect(result.isValid).toBe(false);
138
- expect(result.errors[0].reason).toMatch(/DELETE/);
139
- });
140
- it('should warn about irreversible bash commands (rm -rf)', () => {
141
- const actions = [
142
- makeTestAction({
143
- action_id: 'a1',
144
- tool_name: 'bash',
145
- is_reversible: 1,
146
- input_payload: JSON.stringify({ command: 'rm -rf /important/data' }),
147
- }),
148
- ];
149
- const result = (0, manager_1.validateRollbackable)(actions);
150
- expect(result.isValid).toBe(false);
151
- expect(result.errors[0].reason).toMatch(/rm -rf/);
152
- });
153
- it('should warn about DROP TABLE', () => {
154
- const actions = [
155
- makeTestAction({
156
- action_id: 'a1',
157
- tool_name: 'bash',
158
- is_reversible: 1,
159
- input_payload: JSON.stringify({ command: 'DROP TABLE users' }),
160
- }),
161
- ];
162
- const result = (0, manager_1.validateRollbackable)(actions);
163
- expect(result.isValid).toBe(false);
164
- expect(result.errors[0].reason).toMatch(/DROP\s+TABLE/);
165
- });
166
- it('should allow safe bash commands', () => {
167
- const actions = [
168
- makeTestAction({
169
- action_id: 'a1',
170
- tool_name: 'bash',
171
- is_reversible: 1,
172
- input_payload: JSON.stringify({ command: 'echo hello > /tmp/log.txt' }),
173
- }),
174
- ];
175
- const result = (0, manager_1.validateRollbackable)(actions);
176
- expect(result.isValid).toBe(true);
177
- });
178
- });
179
- // ─── Tests: createRollbackTransaction ────────────────────────────────────
180
- describe('createRollbackTransaction', () => {
181
- it('should create transaction with no file restores for read_file', () => {
182
- const actions = [
183
- makeTestAction({
184
- action_id: 'a1',
185
- tool_name: 'read_file',
186
- is_reversible: 1,
187
- }),
188
- ];
189
- const transaction = (0, manager_1.createRollbackTransaction)('session-1', actions);
190
- expect(transaction.session_id).toBe('session-1');
191
- expect(transaction.actions).toEqual(actions);
192
- expect(transaction.fileRestores).toEqual([]);
193
- });
194
- it('should extract file restores from write_file actions', () => {
195
- const snapshot = {
196
- file_path: '/tmp/test.txt',
197
- content: 'original content',
198
- existed: true,
199
- };
200
- const actions = [
201
- makeTestAction({
202
- action_id: 'a1',
203
- tool_name: 'write_file',
204
- before_snapshot: JSON.stringify(snapshot),
205
- }),
206
- ];
207
- const transaction = (0, manager_1.createRollbackTransaction)('session-1', actions);
208
- expect(transaction.fileRestores).toHaveLength(1);
209
- expect(transaction.fileRestores[0].file_path).toBe('/tmp/test.txt');
210
- expect(transaction.fileRestores[0].snapshot).toEqual(snapshot);
211
- });
212
- it('should handle multiple file restores', () => {
213
- const actions = [
214
- makeTestAction({
215
- action_id: 'a1',
216
- tool_name: 'write_file',
217
- target_path: '/tmp/file1.txt',
218
- before_snapshot: JSON.stringify({
219
- file_path: '/tmp/file1.txt',
220
- content: 'content1',
221
- existed: true,
222
- }),
223
- }),
224
- makeTestAction({
225
- action_id: 'a2',
226
- tool_name: 'write_file',
227
- target_path: '/tmp/file2.txt',
228
- before_snapshot: JSON.stringify({
229
- file_path: '/tmp/file2.txt',
230
- content: 'content2',
231
- existed: true,
232
- }),
233
- }),
234
- ];
235
- const transaction = (0, manager_1.createRollbackTransaction)('session-1', actions);
236
- expect(transaction.fileRestores).toHaveLength(2);
237
- expect(transaction.fileRestores.map((r) => r.file_path)).toEqual(['/tmp/file1.txt', '/tmp/file2.txt']);
238
- });
239
- it('should handle file deletion (file did not exist before)', () => {
240
- const snapshot = {
241
- file_path: '/tmp/newfile.txt',
242
- content: null,
243
- existed: false,
244
- };
245
- const actions = [
246
- makeTestAction({
247
- action_id: 'a1',
248
- tool_name: 'write_file',
249
- before_snapshot: JSON.stringify(snapshot),
250
- }),
251
- ];
252
- const transaction = (0, manager_1.createRollbackTransaction)('session-1', actions);
253
- expect(transaction.fileRestores[0].snapshot.existed).toBe(false);
254
- });
255
- });
256
- // ─── Tests: executeRollbackTransaction ───────────────────────────────────
257
- describe('executeRollbackTransaction', () => {
258
- let tempDir;
259
- beforeEach(() => {
260
- tempDir = makeTempDir();
261
- process.env.WAYMARK_PROJECT_ROOT = tempDir;
262
- });
263
- afterEach(() => {
264
- cleanupTempDir(tempDir);
265
- });
266
- it('should restore a file from snapshot', () => {
267
- const filePath = path.join(tempDir, 'test.txt');
268
- const originalContent = 'original content';
269
- // Create original file
270
- fs.writeFileSync(filePath, originalContent, 'utf-8');
271
- // Modify it (simulating agent action)
272
- fs.writeFileSync(filePath, 'modified content', 'utf-8');
273
- // Create rollback transaction
274
- const transaction = {
275
- session_id: 'session-1',
276
- actions: [
277
- makeTestAction({
278
- action_id: 'a1',
279
- tool_name: 'write_file',
280
- }),
281
- ],
282
- fileRestores: [
283
- {
284
- action_id: 'a1',
285
- file_path: 'test.txt',
286
- snapshot: {
287
- file_path: 'test.txt',
288
- content: originalContent,
289
- existed: true,
290
- },
291
- },
292
- ],
293
- };
294
- // Execute rollback
295
- const result = (0, manager_1.executeRollbackTransaction)(transaction);
296
- expect(result.success).toBe(true);
297
- expect(result.filesRestored).toBe(1);
298
- // Verify file was restored
299
- const restoredContent = fs.readFileSync(filePath, 'utf-8');
300
- expect(restoredContent).toBe(originalContent);
301
- });
302
- it('should delete a file that did not exist before', () => {
303
- const filePath = path.join(tempDir, 'newfile.txt');
304
- // Agent creates a new file
305
- fs.writeFileSync(filePath, 'new content', 'utf-8');
306
- expect(fs.existsSync(filePath)).toBe(true);
307
- // Create rollback transaction (file didn't exist before)
308
- const transaction = {
309
- session_id: 'session-1',
310
- actions: [
311
- makeTestAction({
312
- action_id: 'a1',
313
- tool_name: 'write_file',
314
- }),
315
- ],
316
- fileRestores: [
317
- {
318
- action_id: 'a1',
319
- file_path: 'newfile.txt',
320
- snapshot: {
321
- file_path: 'newfile.txt',
322
- content: null,
323
- existed: false,
324
- },
325
- },
326
- ],
327
- };
328
- // Execute rollback
329
- const result = (0, manager_1.executeRollbackTransaction)(transaction);
330
- expect(result.success).toBe(true);
331
- expect(result.filesRestored).toBe(1);
332
- // Verify file was deleted
333
- expect(fs.existsSync(filePath)).toBe(false);
334
- });
335
- it('should create directories if needed', () => {
336
- const filePath = path.join(tempDir, 'subdir', 'nested', 'test.txt');
337
- const content = 'test content';
338
- // Create rollback transaction for nested file
339
- const transaction = {
340
- session_id: 'session-1',
341
- actions: [
342
- makeTestAction({
343
- action_id: 'a1',
344
- tool_name: 'write_file',
345
- }),
346
- ],
347
- fileRestores: [
348
- {
349
- action_id: 'a1',
350
- file_path: 'subdir/nested/test.txt',
351
- snapshot: {
352
- file_path: 'subdir/nested/test.txt',
353
- content,
354
- existed: true,
355
- },
356
- },
357
- ],
358
- };
359
- // Execute rollback
360
- const result = (0, manager_1.executeRollbackTransaction)(transaction);
361
- expect(result.success).toBe(true);
362
- expect(fs.existsSync(filePath)).toBe(true);
363
- expect(fs.readFileSync(filePath, 'utf-8')).toBe(content);
364
- });
365
- it('should handle multiple file restores atomically', () => {
366
- const file1 = path.join(tempDir, 'file1.txt');
367
- const file2 = path.join(tempDir, 'file2.txt');
368
- fs.writeFileSync(file1, 'original1', 'utf-8');
369
- fs.writeFileSync(file2, 'original2', 'utf-8');
370
- const transaction = {
371
- session_id: 'session-1',
372
- actions: [
373
- makeTestAction({ action_id: 'a1' }),
374
- makeTestAction({ action_id: 'a2' }),
375
- ],
376
- fileRestores: [
377
- {
378
- action_id: 'a1',
379
- file_path: 'file1.txt',
380
- snapshot: { file_path: 'file1.txt', content: 'original1', existed: true },
381
- },
382
- {
383
- action_id: 'a2',
384
- file_path: 'file2.txt',
385
- snapshot: { file_path: 'file2.txt', content: 'original2', existed: true },
386
- },
387
- ],
388
- };
389
- const result = (0, manager_1.executeRollbackTransaction)(transaction);
390
- expect(result.success).toBe(true);
391
- expect(result.filesRestored).toBe(2);
392
- expect(fs.readFileSync(file1, 'utf-8')).toBe('original1');
393
- expect(fs.readFileSync(file2, 'utf-8')).toBe('original2');
394
- });
395
- it('should fail if file is not accessible', () => {
396
- const filePath = path.join(tempDir, 'readonly', 'test.txt');
397
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
398
- fs.writeFileSync(filePath, 'test', 'utf-8');
399
- // Make directory read-only (Unix only)
400
- if (process.platform !== 'win32') {
401
- fs.chmodSync(path.dirname(filePath), 0o444);
402
- }
403
- const transaction = {
404
- session_id: 'session-1',
405
- actions: [makeTestAction({ action_id: 'a1' })],
406
- fileRestores: [
407
- {
408
- action_id: 'a1',
409
- file_path: 'readonly/test.txt',
410
- snapshot: { file_path: 'readonly/test.txt', content: 'modified', existed: true },
411
- },
412
- ],
413
- };
414
- const result = (0, manager_1.executeRollbackTransaction)(transaction);
415
- // Restore permissions
416
- if (process.platform !== 'win32') {
417
- fs.chmodSync(path.dirname(filePath), 0o755);
418
- }
419
- // Should fail on non-Windows systems
420
- if (process.platform !== 'win32') {
421
- expect(result.success).toBe(false);
422
- }
423
- });
424
- });
425
- // ─── Tests: rollbackSession (high-level API) ────────────────────────────
426
- describe('rollbackSession', () => {
427
- let tempDir;
428
- beforeEach(() => {
429
- tempDir = makeTempDir();
430
- process.env.WAYMARK_PROJECT_ROOT = tempDir;
431
- });
432
- afterEach(() => {
433
- cleanupTempDir(tempDir);
434
- });
435
- it('should rollback a simple session', () => {
436
- const filePath = path.join(tempDir, 'test.txt');
437
- fs.writeFileSync(filePath, 'original', 'utf-8');
438
- // Mock getSessionActions to return test actions
439
- const testActions = [
440
- makeTestAction({
441
- action_id: 'a1',
442
- session_id: 'session-1',
443
- tool_name: 'write_file',
444
- before_snapshot: JSON.stringify({
445
- file_path: 'test.txt',
446
- content: 'original',
447
- existed: true,
448
- }),
449
- is_reversible: 1,
450
- }),
451
- ];
452
- // Simulate file modification
453
- fs.writeFileSync(filePath, 'modified', 'utf-8');
454
- // Since rollbackSession calls getSessionActions which hits the DB,
455
- // we can't easily mock it in this test framework.
456
- // For full E2E, we'd need a test database or mock.
457
- // For now, we test the logic components individually.
458
- expect(true).toBe(true);
459
- });
460
- it('should return error if no actions in session', () => {
461
- const result = (0, manager_1.rollbackSession)('empty-session');
462
- expect(result.success).toBe(false);
463
- expect(result.message).toMatch(/no actions/i);
464
- });
465
- });
466
- // ─── Integration Tests ───────────────────────────────────────────────────
467
- describe('integration: full rollback workflow', () => {
468
- let tempDir;
469
- beforeEach(() => {
470
- tempDir = makeTempDir();
471
- process.env.WAYMARK_PROJECT_ROOT = tempDir;
472
- });
473
- afterEach(() => {
474
- cleanupTempDir(tempDir);
475
- });
476
- it('should handle a 3-action session rollback', () => {
477
- // Setup: Create original files
478
- const files = ['file1.txt', 'file2.txt', 'file3.txt'];
479
- files.forEach((f) => fs.writeFileSync(path.join(tempDir, f), `original ${f}`, 'utf-8'));
480
- // Create actions
481
- const actions = files.map((f, i) => makeTestAction({
482
- action_id: `a${i}`,
483
- session_id: 'test-session',
484
- tool_name: 'write_file',
485
- target_path: f,
486
- before_snapshot: JSON.stringify({
487
- file_path: f,
488
- content: `original ${f}`,
489
- existed: true,
490
- }),
491
- is_reversible: 1,
492
- }));
493
- // Validate
494
- const validation = (0, manager_1.validateRollbackable)(actions);
495
- expect(validation.isValid).toBe(true);
496
- // Create transaction
497
- const transaction = (0, manager_1.createRollbackTransaction)('test-session', actions);
498
- expect(transaction.fileRestores).toHaveLength(3);
499
- // Simulate modifications
500
- files.forEach((f) => fs.writeFileSync(path.join(tempDir, f), `modified ${f}`, 'utf-8'));
501
- // Execute rollback
502
- const result = (0, manager_1.executeRollbackTransaction)(transaction);
503
- expect(result.success).toBe(true);
504
- expect(result.filesRestored).toBe(3);
505
- // Verify all files restored
506
- files.forEach((f) => {
507
- const content = fs.readFileSync(path.join(tempDir, f), 'utf-8');
508
- expect(content).toBe(`original ${f}`);
509
- });
510
- });
511
- it('should correctly handle mix of file creation and modification', () => {
512
- const modifiedFile = path.join(tempDir, 'modified.txt');
513
- const newFile = path.join(tempDir, 'new.txt');
514
- // Setup
515
- fs.writeFileSync(modifiedFile, 'original', 'utf-8');
516
- // Actions
517
- const actions = [
518
- makeTestAction({
519
- action_id: 'a1',
520
- tool_name: 'write_file',
521
- before_snapshot: JSON.stringify({
522
- file_path: 'modified.txt',
523
- content: 'original',
524
- existed: true,
525
- }),
526
- is_reversible: 1,
527
- }),
528
- makeTestAction({
529
- action_id: 'a2',
530
- tool_name: 'write_file',
531
- before_snapshot: JSON.stringify({
532
- file_path: 'new.txt',
533
- content: null,
534
- existed: false,
535
- }),
536
- is_reversible: 1,
537
- }),
538
- ];
539
- // Simulate agent actions
540
- fs.writeFileSync(modifiedFile, 'modified', 'utf-8');
541
- fs.writeFileSync(newFile, 'created', 'utf-8');
542
- // Rollback
543
- const validation = (0, manager_1.validateRollbackable)(actions);
544
- expect(validation.isValid).toBe(true);
545
- const transaction = (0, manager_1.createRollbackTransaction)('session', actions);
546
- const result = (0, manager_1.executeRollbackTransaction)(transaction);
547
- expect(result.success).toBe(true);
548
- expect(fs.readFileSync(modifiedFile, 'utf-8')).toBe('original');
549
- expect(fs.existsSync(newFile)).toBe(false);
550
- });
551
- });
552
- });