@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,245 @@
1
+ "use strict";
2
+ /**
3
+ * Rollback Manager
4
+ *
5
+ * Handles atomic, all-or-nothing rollback of entire agent sessions.
6
+ * Restores files from snapshots, records rollback operations, and maintains audit trail.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.validateRollbackable = validateRollbackable;
43
+ exports.createRollbackTransaction = createRollbackTransaction;
44
+ exports.executeRollbackTransaction = executeRollbackTransaction;
45
+ exports.rollbackSession = rollbackSession;
46
+ const fs = __importStar(require("fs"));
47
+ const path = __importStar(require("path"));
48
+ const database_1 = require("../db/database");
49
+ /**
50
+ * Parse a snapshot JSON to extract file information
51
+ */
52
+ function parseSnapshot(snapshotJson) {
53
+ if (!snapshotJson)
54
+ return null;
55
+ try {
56
+ return JSON.parse(snapshotJson);
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ /**
63
+ * Validate that all actions in session are rollbackable
64
+ */
65
+ function validateRollbackable(actions) {
66
+ const errors = [];
67
+ for (const action of actions) {
68
+ // Check if action is marked as reversible
69
+ if (action.is_reversible === 0) {
70
+ errors.push({
71
+ action_id: action.action_id,
72
+ reason: 'Action marked as non-reversible',
73
+ });
74
+ }
75
+ // For write_file actions, check if we have before_snapshot
76
+ if (action.tool_name === 'write_file' && !action.before_snapshot) {
77
+ errors.push({
78
+ action_id: action.action_id,
79
+ reason: 'Missing before_snapshot for write_file action',
80
+ });
81
+ }
82
+ // For bash actions, warn if they had side effects (e.g., database mutations)
83
+ if (action.tool_name === 'bash') {
84
+ const inputPayload = JSON.parse(action.input_payload || '{}');
85
+ const command = inputPayload.command || '';
86
+ // List of dangerous patterns that can't be rolled back
87
+ const irreversiblePatterns = [
88
+ /DROP\s+TABLE/i,
89
+ /DELETE\s+FROM/i,
90
+ /TRUNCATE/i,
91
+ /rm\s+-rf/,
92
+ /git\s+push/,
93
+ /npm\s+publish/,
94
+ ];
95
+ for (const pattern of irreversiblePatterns) {
96
+ if (pattern.test(command)) {
97
+ errors.push({
98
+ action_id: action.action_id,
99
+ reason: `Bash command contains irreversible operation: "${command.substring(0, 50)}"`,
100
+ });
101
+ }
102
+ }
103
+ }
104
+ }
105
+ return {
106
+ isValid: errors.length === 0,
107
+ errors,
108
+ warningCount: 0,
109
+ };
110
+ }
111
+ /**
112
+ * Create a rollback transaction (plan, don't execute)
113
+ */
114
+ function createRollbackTransaction(session_id, actions) {
115
+ const fileRestores = [];
116
+ for (const action of actions) {
117
+ // Only process write_file actions for file restoration
118
+ if (action.tool_name === 'write_file') {
119
+ const snapshot = parseSnapshot(action.before_snapshot);
120
+ if (snapshot) {
121
+ fileRestores.push({
122
+ action_id: action.action_id,
123
+ file_path: snapshot.file_path,
124
+ snapshot,
125
+ });
126
+ }
127
+ }
128
+ }
129
+ return {
130
+ session_id,
131
+ actions,
132
+ fileRestores,
133
+ };
134
+ }
135
+ /**
136
+ * Execute rollback transaction (atomic, all-or-nothing)
137
+ */
138
+ function executeRollbackTransaction(transaction) {
139
+ try {
140
+ // Phase 1: Validate all files can be restored before making any changes
141
+ const projectRoot = process.env.WAYMARK_PROJECT_ROOT || process.cwd();
142
+ for (const restore of transaction.fileRestores) {
143
+ const fullPath = path.resolve(projectRoot, restore.file_path);
144
+ // Check write permissions
145
+ if (fs.existsSync(fullPath)) {
146
+ try {
147
+ // Try to stat the file to ensure we can access it
148
+ fs.statSync(fullPath);
149
+ }
150
+ catch (err) {
151
+ throw new Error(`Cannot access file for rollback: ${fullPath}`);
152
+ }
153
+ }
154
+ }
155
+ // Phase 2: Restore all files (all-or-nothing)
156
+ let filesRestored = 0;
157
+ for (const restore of transaction.fileRestores) {
158
+ const fullPath = path.resolve(projectRoot, restore.file_path);
159
+ if (restore.snapshot.existed === false) {
160
+ // File didn't exist before, so delete it
161
+ if (fs.existsSync(fullPath)) {
162
+ fs.unlinkSync(fullPath);
163
+ }
164
+ }
165
+ else {
166
+ // File existed, restore its content
167
+ const dir = path.dirname(fullPath);
168
+ if (!fs.existsSync(dir)) {
169
+ fs.mkdirSync(dir, { recursive: true });
170
+ }
171
+ fs.writeFileSync(fullPath, restore.snapshot.content || '', 'utf-8');
172
+ }
173
+ filesRestored++;
174
+ }
175
+ // Phase 3: Mark actions as rolled back in database
176
+ for (const action of transaction.actions) {
177
+ (0, database_1.markRolledBack)(action.action_id);
178
+ }
179
+ // Phase 4: Mark session as rolled back
180
+ (0, database_1.markSessionRolledBack)(transaction.session_id);
181
+ return {
182
+ success: true,
183
+ filesRestored,
184
+ };
185
+ }
186
+ catch (error) {
187
+ return {
188
+ success: false,
189
+ filesRestored: 0,
190
+ error: error.message,
191
+ };
192
+ }
193
+ }
194
+ /**
195
+ * Rollback entire session (high-level API)
196
+ *
197
+ * Steps:
198
+ * 1. Get all actions in session
199
+ * 2. Validate all are reversible
200
+ * 3. Create rollback transaction
201
+ * 4. Execute rollback (atomic)
202
+ * 5. Record audit trail
203
+ */
204
+ function rollbackSession(session_id) {
205
+ try {
206
+ // Get actions
207
+ const actions = (0, database_1.getSessionActions)(session_id);
208
+ if (actions.length === 0) {
209
+ return {
210
+ success: false,
211
+ message: `Session ${session_id} has no actions to rollback`,
212
+ };
213
+ }
214
+ // Validate
215
+ const validation = validateRollbackable(actions);
216
+ if (!validation.isValid) {
217
+ const errorList = validation.errors.map((e) => `${e.action_id}: ${e.reason}`).join('; ');
218
+ return {
219
+ success: false,
220
+ message: `Cannot rollback session: ${errorList}`,
221
+ };
222
+ }
223
+ // Create transaction
224
+ const transaction = createRollbackTransaction(session_id, actions);
225
+ // Execute transaction
226
+ const result = executeRollbackTransaction(transaction);
227
+ if (!result.success) {
228
+ return {
229
+ success: false,
230
+ message: result.error || 'Unknown error during rollback',
231
+ };
232
+ }
233
+ return {
234
+ success: true,
235
+ message: `Successfully rolled back ${actions.length} action(s), restored ${result.filesRestored} file(s)`,
236
+ filesRestored: result.filesRestored,
237
+ };
238
+ }
239
+ catch (error) {
240
+ return {
241
+ success: false,
242
+ message: `Rollback failed: ${error.message}`,
243
+ };
244
+ }
245
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@way_marks/server",
3
- "version": "0.9.0",
3
+ "version": "2.0.0",
4
4
  "description": "Waymark MCP server and dashboard",
5
5
  "author": "Waymark <hello@waymarks.dev>",
6
6
  "license": "MIT",