@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.
- package/dist/api/server.js +598 -0
- package/dist/approval/manager.js +249 -0
- package/dist/db/database.js +754 -185
- package/dist/escalation/manager.js +205 -0
- package/dist/notifications/service.js +457 -0
- package/dist/policies/engine.js +11 -5
- package/dist/policy/engine.js +420 -0
- package/dist/remediation/recommender.js +309 -0
- package/dist/risk/analyzer.js +453 -0
- package/dist/rollback/blocker.js +222 -0
- package/dist/rollback/manager.js +245 -0
- package/package.json +1 -1
- package/src/ui/index.html +862 -0
- package/dist/approvals/handler.test.js +0 -172
- package/dist/policies/engine.test.js +0 -241
|
@@ -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
|
+
}
|