@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,249 @@
1
+ "use strict";
2
+ /**
3
+ * Approval Manager — Core logic for team approval routing
4
+ *
5
+ * Responsibilities:
6
+ * - Determine which approvers are required for a given session rollback
7
+ * - Create approval requests (pending approvals)
8
+ * - Process approval decisions (approve/reject)
9
+ * - Check if all required approvals are satisfied
10
+ * - Track approval history and audit trail
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.determineRequiredApprovers = determineRequiredApprovers;
14
+ exports.createApprovalRequestForSession = createApprovalRequestForSession;
15
+ exports.submitApprovalDecision = submitApprovalDecision;
16
+ exports.getApprovalStatus = getApprovalStatus;
17
+ exports.canProceedWithRollback = canProceedWithRollback;
18
+ exports.getPendingApprovalsForUser = getPendingApprovalsForUser;
19
+ exports.getApprovalHistory = getApprovalHistory;
20
+ const database_1 = require("../db/database");
21
+ /**
22
+ * Determine if a session rollback requires approval based on routes
23
+ */
24
+ function determineRequiredApprovers(session, actions) {
25
+ const routes = (0, database_1.getAllApprovalRoutes)();
26
+ // Collect all unique approvers from matching routes
27
+ const requiredApprovers = new Set();
28
+ const matchedRoutes = [];
29
+ for (const route of routes) {
30
+ if (routeMatches(route, session, actions)) {
31
+ matchedRoutes.push(route);
32
+ const approvers = JSON.parse(route.approver_ids);
33
+ approvers.forEach(a => requiredApprovers.add(a));
34
+ }
35
+ }
36
+ const approverArray = Array.from(requiredApprovers);
37
+ const requiresApproval = approverArray.length > 0;
38
+ return {
39
+ requiresApproval,
40
+ requiredApprovers: approverArray,
41
+ routes: matchedRoutes,
42
+ reason: requiresApproval
43
+ ? `${matchedRoutes.length} approval route(s) matched`
44
+ : 'No approval routes matched',
45
+ };
46
+ }
47
+ /**
48
+ * Check if an approval route matches the given session
49
+ */
50
+ function routeMatches(route, session, actions) {
51
+ const condition_type = route.condition_type || 'all_sessions';
52
+ switch (condition_type) {
53
+ case 'all_sessions':
54
+ return true;
55
+ case 'tool_name':
56
+ // Match if any action uses specified tool
57
+ if (!route.condition_json)
58
+ return false;
59
+ try {
60
+ const condition = JSON.parse(route.condition_json);
61
+ return actions.some(a => a.tool_name === condition.value);
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ case 'action_count':
67
+ // Match if action count exceeds threshold
68
+ if (!route.condition_json)
69
+ return false;
70
+ try {
71
+ const condition = JSON.parse(route.condition_json);
72
+ const threshold = condition.value || 5;
73
+ return actions.length >= threshold;
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ case 'risk_level':
79
+ // Match if any action is flagged as high-risk
80
+ if (!route.condition_json)
81
+ return false;
82
+ try {
83
+ const condition = JSON.parse(route.condition_json);
84
+ // Check if any action has is_reversible = 0 (irreversible = high risk)
85
+ return actions.some(a => a.is_reversible === 0);
86
+ }
87
+ catch {
88
+ return false;
89
+ }
90
+ default:
91
+ return false;
92
+ }
93
+ }
94
+ /**
95
+ * Create an approval request for a session rollback
96
+ * Returns request_id and list of required approvers
97
+ */
98
+ function createApprovalRequestForSession(session_id, session, actions, triggered_by) {
99
+ try {
100
+ const check = determineRequiredApprovers(session, actions);
101
+ if (!check.requiresApproval) {
102
+ // No approval needed
103
+ return {
104
+ success: true,
105
+ request_id: '', // No request created
106
+ requires_approval: false,
107
+ required_approvers: [],
108
+ pending_approvers: [],
109
+ };
110
+ }
111
+ // Create approval request
112
+ const request_id = `approval-${session_id}-${Date.now()}`;
113
+ const route_id = check.routes[0].route_id; // Use first matching route
114
+ (0, database_1.createApprovalRequest)(request_id, session_id, route_id, triggered_by, check.requiredApprovers);
115
+ return {
116
+ success: true,
117
+ request_id,
118
+ requires_approval: true,
119
+ required_approvers: check.requiredApprovers,
120
+ pending_approvers: check.requiredApprovers, // All are pending initially
121
+ };
122
+ }
123
+ catch (error) {
124
+ return {
125
+ success: false,
126
+ request_id: '',
127
+ requires_approval: false,
128
+ required_approvers: [],
129
+ pending_approvers: [],
130
+ error: error.message,
131
+ };
132
+ }
133
+ }
134
+ /**
135
+ * Submit an approval decision (approve or reject)
136
+ */
137
+ function submitApprovalDecision(request_id, approver_id, decision, reason) {
138
+ try {
139
+ const request = (0, database_1.getApprovalRequest)(request_id);
140
+ if (!request) {
141
+ throw new Error(`Approval request ${request_id} not found`);
142
+ }
143
+ // Verify approver is authorized for this request
144
+ const approvers = JSON.parse(request.approver_ids);
145
+ if (!approvers.includes(approver_id)) {
146
+ throw new Error(`${approver_id} is not authorized to approve this request`);
147
+ }
148
+ // Check if already decided by this approver
149
+ const existingDecisions = (0, database_1.getApprovalDecisions)(request_id);
150
+ if (existingDecisions.some(d => d.approver_id === approver_id)) {
151
+ throw new Error(`${approver_id} has already made a decision on this request`);
152
+ }
153
+ // Record decision
154
+ const decision_id = `decision-${request_id}-${approver_id}-${Date.now()}`;
155
+ (0, database_1.submitApprovalDecision)(decision_id, request_id, approver_id, decision, reason);
156
+ // Return updated status
157
+ return getApprovalStatus(request_id);
158
+ }
159
+ catch (error) {
160
+ throw new Error(`Failed to submit decision: ${error.message}`);
161
+ }
162
+ }
163
+ /**
164
+ * Get the current approval status
165
+ */
166
+ function getApprovalStatus(request_id) {
167
+ const request = (0, database_1.getApprovalRequest)(request_id);
168
+ if (!request) {
169
+ throw new Error(`Approval request ${request_id} not found`);
170
+ }
171
+ const decisions = (0, database_1.getApprovalDecisions)(request_id);
172
+ const approvers = JSON.parse(request.approver_ids);
173
+ const pendingApprovers = approvers.filter(a => !decisions.some(d => d.approver_id === a));
174
+ const approvedCount = decisions.filter(d => d.decision === 'approve').length;
175
+ const rejectedCount = decisions.filter(d => d.decision === 'reject').length;
176
+ // Determine if we can proceed
177
+ let can_proceed = false;
178
+ const approverIds = JSON.parse(request.approver_ids);
179
+ let status = 'pending';
180
+ if (rejectedCount > 0) {
181
+ status = 'rejected';
182
+ can_proceed = false;
183
+ }
184
+ else if (approvedCount >= approverIds.length) {
185
+ status = 'approved';
186
+ can_proceed = true;
187
+ }
188
+ else if (approvedCount > 0 && rejectedCount === 0) {
189
+ status = 'mixed';
190
+ can_proceed = false;
191
+ }
192
+ return {
193
+ request_id,
194
+ status,
195
+ approved_count: approvedCount,
196
+ rejected_count: rejectedCount,
197
+ pending_count: pendingApprovers.length,
198
+ required_approvers: approverIds.length,
199
+ decisions: decisions.map(d => ({
200
+ approver_id: d.approver_id,
201
+ decision: d.decision,
202
+ reason: d.reason ?? undefined,
203
+ })),
204
+ can_proceed,
205
+ };
206
+ }
207
+ /**
208
+ * Check if all required approvals are satisfied for a session rollback
209
+ */
210
+ function canProceedWithRollback(request_id) {
211
+ try {
212
+ const status = getApprovalStatus(request_id);
213
+ return status.can_proceed;
214
+ }
215
+ catch {
216
+ return false;
217
+ }
218
+ }
219
+ /**
220
+ * Get all pending approvals for a specific approver
221
+ */
222
+ function getPendingApprovalsForUser(approver_id) {
223
+ const allRequests = (0, database_1.getSessionApprovalRequests)(''); // Get all (filter by approver below)
224
+ // Filter: only pending requests where user is an approver and hasn't decided
225
+ return allRequests.filter(req => {
226
+ if (req.status !== 'pending')
227
+ return false;
228
+ const approvers = JSON.parse(req.approver_ids);
229
+ if (!approvers.includes(approver_id))
230
+ return false;
231
+ const decisions = (0, database_1.getApprovalDecisions)(req.request_id);
232
+ return !decisions.some(d => d.approver_id === approver_id);
233
+ });
234
+ }
235
+ /**
236
+ * Get approval history for a session
237
+ */
238
+ function getApprovalHistory(session_id) {
239
+ return (0, database_1.getSessionApprovalRequests)(session_id);
240
+ }
241
+ exports.default = {
242
+ determineRequiredApprovers,
243
+ createApprovalRequestForSession,
244
+ submitApprovalDecision,
245
+ getApprovalStatus,
246
+ canProceedWithRollback,
247
+ getPendingApprovalsForUser,
248
+ getApprovalHistory,
249
+ };