@way_marks/server 0.8.0 → 1.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/approval/manager.test.js +315 -0
- package/dist/db/database.js +544 -1
- package/dist/escalation/manager.js +205 -0
- package/dist/escalation/manager.test.js +519 -0
- package/dist/notifications/service.js +457 -0
- package/dist/policy/engine.js +420 -0
- package/dist/remediation/recommender.js +309 -0
- package/dist/risk/analyzer.js +442 -0
- package/dist/risk/analyzer.test.js +482 -0
- package/dist/rollback/blocker.js +222 -0
- package/dist/rollback/manager.js +245 -0
- package/dist/rollback/manager.test.js +552 -0
- package/package.json +1 -1
- package/src/ui/index.html +862 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Notification Service — Slack & Email integration for approval requests
|
|
4
|
+
*
|
|
5
|
+
* Sends notifications when:
|
|
6
|
+
* - Approval request is created (notify required approvers)
|
|
7
|
+
* - Approval decision is submitted (notify original requester and team)
|
|
8
|
+
*
|
|
9
|
+
* Supports:
|
|
10
|
+
* - Slack webhook notifications with interactive approval buttons
|
|
11
|
+
* - Email notifications with decision details
|
|
12
|
+
* - Custom templates and retry logic
|
|
13
|
+
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.notifyApprovalRequestSlack = notifyApprovalRequestSlack;
|
|
49
|
+
exports.notifyApprovalDecisionSlack = notifyApprovalDecisionSlack;
|
|
50
|
+
exports.notifyApprovalRequestEmail = notifyApprovalRequestEmail;
|
|
51
|
+
exports.notifyApprovalDecisionEmail = notifyApprovalDecisionEmail;
|
|
52
|
+
exports.broadcastApprovalRequest = broadcastApprovalRequest;
|
|
53
|
+
exports.broadcastApprovalDecision = broadcastApprovalDecision;
|
|
54
|
+
exports.notifyEscalationSlack = notifyEscalationSlack;
|
|
55
|
+
exports.notifyEscalationDecisionSlack = notifyEscalationDecisionSlack;
|
|
56
|
+
exports.notifyEscalationEmail = notifyEscalationEmail;
|
|
57
|
+
exports.notifyEscalationDecisionEmail = notifyEscalationDecisionEmail;
|
|
58
|
+
exports.broadcastEscalation = broadcastEscalation;
|
|
59
|
+
exports.broadcastEscalationDecision = broadcastEscalationDecision;
|
|
60
|
+
const fs = __importStar(require("fs"));
|
|
61
|
+
const path = __importStar(require("path"));
|
|
62
|
+
const PROJECT_ROOT = process.env.WAYMARK_PROJECT_ROOT || process.cwd();
|
|
63
|
+
const CONFIG_PATH = path.join(PROJECT_ROOT, '.waymark', 'config.json');
|
|
64
|
+
/**
|
|
65
|
+
* Load notification configuration from .waymark/config.json
|
|
66
|
+
*/
|
|
67
|
+
function loadNotificationConfig() {
|
|
68
|
+
try {
|
|
69
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
73
|
+
return config.notifications || {};
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Send Slack notification for approval request
|
|
81
|
+
*/
|
|
82
|
+
async function notifyApprovalRequestSlack(approver_ids, session_id, request_id, requester_name, action_count) {
|
|
83
|
+
const config = loadNotificationConfig();
|
|
84
|
+
const slackConfig = config.slack;
|
|
85
|
+
if (!slackConfig?.enabled || !slackConfig.webhook_url) {
|
|
86
|
+
console.log('[Notifications] Slack disabled or unconfigured');
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const approversList = approver_ids.join(', ');
|
|
91
|
+
const message = {
|
|
92
|
+
text: `🔔 New approval request from ${requester_name}`,
|
|
93
|
+
blocks: [
|
|
94
|
+
{
|
|
95
|
+
type: 'section',
|
|
96
|
+
text: {
|
|
97
|
+
type: 'mrkdwn',
|
|
98
|
+
text: `*Approval Required* 🔔\n\nUser *${requester_name}* requests approval to rollback session with *${action_count}* actions.\n\n*Session:* \`${session_id}\`\n*Approvers:* ${approversList}`,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
type: 'actions',
|
|
103
|
+
elements: [
|
|
104
|
+
{
|
|
105
|
+
type: 'button',
|
|
106
|
+
text: {
|
|
107
|
+
type: 'plain_text',
|
|
108
|
+
text: '✅ Approve',
|
|
109
|
+
},
|
|
110
|
+
value: request_id,
|
|
111
|
+
action_id: 'waymark_approval_approve',
|
|
112
|
+
style: 'primary',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
type: 'button',
|
|
116
|
+
text: {
|
|
117
|
+
type: 'plain_text',
|
|
118
|
+
text: '❌ Reject',
|
|
119
|
+
},
|
|
120
|
+
value: request_id,
|
|
121
|
+
action_id: 'waymark_approval_reject',
|
|
122
|
+
style: 'danger',
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
};
|
|
128
|
+
const response = await fetch(slackConfig.webhook_url, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: { 'Content-Type': 'application/json' },
|
|
131
|
+
body: JSON.stringify(message),
|
|
132
|
+
});
|
|
133
|
+
return response.ok;
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
console.error('[Notifications] Slack error:', err);
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Send Slack notification for approval decision
|
|
142
|
+
*/
|
|
143
|
+
async function notifyApprovalDecisionSlack(approver_name, decision, session_id, reason) {
|
|
144
|
+
const config = loadNotificationConfig();
|
|
145
|
+
const slackConfig = config.slack;
|
|
146
|
+
if (!slackConfig?.enabled || !slackConfig.webhook_url) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const emoji = decision === 'approve' ? '✅' : '❌';
|
|
151
|
+
const action = decision === 'approve' ? 'Approved' : 'Rejected';
|
|
152
|
+
const reasonText = reason ? `\n*Reason:* ${reason}` : '';
|
|
153
|
+
const message = {
|
|
154
|
+
text: `${emoji} Approval ${action} by ${approver_name}`,
|
|
155
|
+
blocks: [
|
|
156
|
+
{
|
|
157
|
+
type: 'section',
|
|
158
|
+
text: {
|
|
159
|
+
type: 'mrkdwn',
|
|
160
|
+
text: `${emoji} *Approval ${action}*\n\nApprover: *${approver_name}*\nSession: \`${session_id}\`${reasonText}`,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
const response = await fetch(slackConfig.webhook_url, {
|
|
166
|
+
method: 'POST',
|
|
167
|
+
headers: { 'Content-Type': 'application/json' },
|
|
168
|
+
body: JSON.stringify(message),
|
|
169
|
+
});
|
|
170
|
+
return response.ok;
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
console.error('[Notifications] Slack decision error:', err);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Send email notification for approval request
|
|
179
|
+
*/
|
|
180
|
+
async function notifyApprovalRequestEmail(recipient_email, recipient_name, session_id, request_id, requester_name, action_count) {
|
|
181
|
+
const config = loadNotificationConfig();
|
|
182
|
+
const emailConfig = config.email;
|
|
183
|
+
if (!emailConfig?.enabled) {
|
|
184
|
+
console.log('[Notifications] Email disabled or unconfigured');
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
// For now, log the email that would be sent
|
|
189
|
+
// In production, integrate with SMTP or email service
|
|
190
|
+
console.log(`[Notifications] Email approval request to ${recipient_email}:
|
|
191
|
+
Recipient: ${recipient_name}
|
|
192
|
+
From: ${requester_name}
|
|
193
|
+
Session: ${session_id}
|
|
194
|
+
Actions: ${action_count}
|
|
195
|
+
Request ID: ${request_id}`);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
console.error('[Notifications] Email error:', err);
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Send email notification for approval decision
|
|
205
|
+
*/
|
|
206
|
+
async function notifyApprovalDecisionEmail(recipient_email, approver_name, decision, session_id, reason) {
|
|
207
|
+
const config = loadNotificationConfig();
|
|
208
|
+
const emailConfig = config.email;
|
|
209
|
+
if (!emailConfig?.enabled) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const action = decision === 'approve' ? 'Approved' : 'Rejected';
|
|
214
|
+
const reasonText = reason ? `\n\nReason: ${reason}` : '';
|
|
215
|
+
console.log(`[Notifications] Email approval decision to ${recipient_email}:
|
|
216
|
+
Approver: ${approver_name}
|
|
217
|
+
Decision: ${action}
|
|
218
|
+
Session: ${session_id}${reasonText}`);
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
console.error('[Notifications] Email decision error:', err);
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Broadcast approval request to all required approvers
|
|
228
|
+
*/
|
|
229
|
+
async function broadcastApprovalRequest(approvers, session_id, request_id, requester_name, action_count) {
|
|
230
|
+
let slack_sent = 0;
|
|
231
|
+
let email_sent = 0;
|
|
232
|
+
// Send Slack notification (once to all approvers)
|
|
233
|
+
const slackSuccess = await notifyApprovalRequestSlack(approvers.map(a => a.member_id), session_id, request_id, requester_name, action_count);
|
|
234
|
+
if (slackSuccess)
|
|
235
|
+
slack_sent++;
|
|
236
|
+
// Send email to each approver
|
|
237
|
+
for (const approver of approvers) {
|
|
238
|
+
const emailSuccess = await notifyApprovalRequestEmail(approver.email, approver.name, session_id, request_id, requester_name, action_count);
|
|
239
|
+
if (emailSuccess)
|
|
240
|
+
email_sent++;
|
|
241
|
+
}
|
|
242
|
+
return { slack_sent, email_sent };
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Broadcast approval decision to all stakeholders
|
|
246
|
+
*/
|
|
247
|
+
async function broadcastApprovalDecision(approver, decision, session_id, requester_email, reason) {
|
|
248
|
+
let slack_sent = 0;
|
|
249
|
+
let email_sent = 0;
|
|
250
|
+
// Send Slack notification
|
|
251
|
+
const slackSuccess = await notifyApprovalDecisionSlack(approver.name, decision, session_id, reason);
|
|
252
|
+
if (slackSuccess)
|
|
253
|
+
slack_sent++;
|
|
254
|
+
// Send email to requester
|
|
255
|
+
const emailSuccess = await notifyApprovalDecisionEmail(requester_email, approver.name, decision, session_id, reason);
|
|
256
|
+
if (emailSuccess)
|
|
257
|
+
email_sent++;
|
|
258
|
+
return { slack_sent, email_sent };
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Phase 3: Escalation Notifications
|
|
262
|
+
*/
|
|
263
|
+
/**
|
|
264
|
+
* Send Slack escalation notification
|
|
265
|
+
*/
|
|
266
|
+
async function notifyEscalationSlack(escalation_targets, session_id, request_id, requester_name, escalation_deadline) {
|
|
267
|
+
const config = loadNotificationConfig();
|
|
268
|
+
const slackConfig = config.slack;
|
|
269
|
+
if (!slackConfig?.enabled || !slackConfig.webhook_url) {
|
|
270
|
+
console.log('[Notifications] Slack disabled or unconfigured');
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
const deadlineTime = new Date(escalation_deadline).toLocaleString();
|
|
275
|
+
const targetsList = escalation_targets.join(', ');
|
|
276
|
+
const message = {
|
|
277
|
+
text: `⏰ Escalation needed for ${requester_name}'s rollback request`,
|
|
278
|
+
blocks: [
|
|
279
|
+
{
|
|
280
|
+
type: 'section',
|
|
281
|
+
text: {
|
|
282
|
+
type: 'mrkdwn',
|
|
283
|
+
text: `*Approval Escalation* ⏰\n\nApproval for *${requester_name}*'s rollback is stalled.\n\n*Session:* \`${session_id}\`\n*Escalation Targets:* ${targetsList}\n*Decision Deadline:* ${deadlineTime}`,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
type: 'actions',
|
|
288
|
+
elements: [
|
|
289
|
+
{
|
|
290
|
+
type: 'button',
|
|
291
|
+
text: {
|
|
292
|
+
type: 'plain_text',
|
|
293
|
+
text: '✅ Proceed with Rollback',
|
|
294
|
+
},
|
|
295
|
+
value: request_id,
|
|
296
|
+
action_id: 'waymark_escalation_proceed',
|
|
297
|
+
style: 'primary',
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
type: 'button',
|
|
301
|
+
text: {
|
|
302
|
+
type: 'plain_text',
|
|
303
|
+
text: '❌ Block Rollback',
|
|
304
|
+
},
|
|
305
|
+
value: request_id,
|
|
306
|
+
action_id: 'waymark_escalation_block',
|
|
307
|
+
style: 'danger',
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
};
|
|
313
|
+
const response = await fetch(slackConfig.webhook_url, {
|
|
314
|
+
method: 'POST',
|
|
315
|
+
headers: { 'Content-Type': 'application/json' },
|
|
316
|
+
body: JSON.stringify(message),
|
|
317
|
+
});
|
|
318
|
+
return response.ok;
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
console.error('[Notifications] Slack escalation error:', err);
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Send Slack escalation decision notification
|
|
327
|
+
*/
|
|
328
|
+
async function notifyEscalationDecisionSlack(target_name, decision, session_id, reason) {
|
|
329
|
+
const config = loadNotificationConfig();
|
|
330
|
+
const slackConfig = config.slack;
|
|
331
|
+
if (!slackConfig?.enabled || !slackConfig.webhook_url) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const emoji = decision === 'proceed' ? '✅' : '❌';
|
|
336
|
+
const action = decision === 'proceed' ? 'Allowed' : 'Blocked';
|
|
337
|
+
const reasonText = reason ? `\n*Reason:* ${reason}` : '';
|
|
338
|
+
const message = {
|
|
339
|
+
text: `${emoji} Escalation ${action} by ${target_name}`,
|
|
340
|
+
blocks: [
|
|
341
|
+
{
|
|
342
|
+
type: 'section',
|
|
343
|
+
text: {
|
|
344
|
+
type: 'mrkdwn',
|
|
345
|
+
text: `${emoji} *Escalation ${action}*\n\nTarget: *${target_name}*\nSession: \`${session_id}\`${reasonText}`,
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
};
|
|
350
|
+
const response = await fetch(slackConfig.webhook_url, {
|
|
351
|
+
method: 'POST',
|
|
352
|
+
headers: { 'Content-Type': 'application/json' },
|
|
353
|
+
body: JSON.stringify(message),
|
|
354
|
+
});
|
|
355
|
+
return response.ok;
|
|
356
|
+
}
|
|
357
|
+
catch (err) {
|
|
358
|
+
console.error('[Notifications] Slack escalation decision error:', err);
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Send email escalation notification
|
|
364
|
+
*/
|
|
365
|
+
async function notifyEscalationEmail(recipient_email, recipient_name, session_id, request_id, requester_name, escalation_deadline) {
|
|
366
|
+
const config = loadNotificationConfig();
|
|
367
|
+
const emailConfig = config.email;
|
|
368
|
+
if (!emailConfig?.enabled) {
|
|
369
|
+
console.log('[Notifications] Email disabled or unconfigured');
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
try {
|
|
373
|
+
const deadlineTime = new Date(escalation_deadline).toLocaleString();
|
|
374
|
+
console.log(`[Notifications] Email escalation to ${recipient_email}:
|
|
375
|
+
Recipient: ${recipient_name}
|
|
376
|
+
Requester: ${requester_name}
|
|
377
|
+
Session: ${session_id}
|
|
378
|
+
Request ID: ${request_id}
|
|
379
|
+
Deadline: ${deadlineTime}`);
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
catch (err) {
|
|
383
|
+
console.error('[Notifications] Email escalation error:', err);
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Send email escalation decision notification
|
|
389
|
+
*/
|
|
390
|
+
async function notifyEscalationDecisionEmail(recipient_email, target_name, decision, session_id, reason) {
|
|
391
|
+
const config = loadNotificationConfig();
|
|
392
|
+
const emailConfig = config.email;
|
|
393
|
+
if (!emailConfig?.enabled) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
const action = decision === 'proceed' ? 'Allowed' : 'Blocked';
|
|
398
|
+
const reasonText = reason ? `\n\nReason: ${reason}` : '';
|
|
399
|
+
console.log(`[Notifications] Email escalation decision to ${recipient_email}:
|
|
400
|
+
Target: ${target_name}
|
|
401
|
+
Decision: ${action}
|
|
402
|
+
Session: ${session_id}${reasonText}`);
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
console.error('[Notifications] Email escalation decision error:', err);
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Broadcast escalation request to all targets
|
|
412
|
+
*/
|
|
413
|
+
async function broadcastEscalation(targets, session_id, request_id, requester_name, escalation_deadline) {
|
|
414
|
+
let slack_sent = 0;
|
|
415
|
+
let email_sent = 0;
|
|
416
|
+
// Send Slack notification (once to all targets)
|
|
417
|
+
const slackSuccess = await notifyEscalationSlack(targets.map(t => t.member_id), session_id, request_id, requester_name, escalation_deadline);
|
|
418
|
+
if (slackSuccess)
|
|
419
|
+
slack_sent++;
|
|
420
|
+
// Send email to each target
|
|
421
|
+
for (const target of targets) {
|
|
422
|
+
const emailSuccess = await notifyEscalationEmail(target.email, target.name, session_id, request_id, requester_name, escalation_deadline);
|
|
423
|
+
if (emailSuccess)
|
|
424
|
+
email_sent++;
|
|
425
|
+
}
|
|
426
|
+
return { slack_sent, email_sent };
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Broadcast escalation decision to all stakeholders
|
|
430
|
+
*/
|
|
431
|
+
async function broadcastEscalationDecision(target, decision, session_id, requester_email, reason) {
|
|
432
|
+
let slack_sent = 0;
|
|
433
|
+
let email_sent = 0;
|
|
434
|
+
// Send Slack notification
|
|
435
|
+
const slackSuccess = await notifyEscalationDecisionSlack(target.name, decision, session_id, reason);
|
|
436
|
+
if (slackSuccess)
|
|
437
|
+
slack_sent++;
|
|
438
|
+
// Send email to requester
|
|
439
|
+
const emailSuccess = await notifyEscalationDecisionEmail(requester_email, target.name, decision, session_id, reason);
|
|
440
|
+
if (emailSuccess)
|
|
441
|
+
email_sent++;
|
|
442
|
+
return { slack_sent, email_sent };
|
|
443
|
+
}
|
|
444
|
+
exports.default = {
|
|
445
|
+
notifyApprovalRequestSlack,
|
|
446
|
+
notifyApprovalDecisionSlack,
|
|
447
|
+
notifyApprovalRequestEmail,
|
|
448
|
+
notifyApprovalDecisionEmail,
|
|
449
|
+
broadcastApprovalRequest,
|
|
450
|
+
broadcastApprovalDecision,
|
|
451
|
+
notifyEscalationSlack,
|
|
452
|
+
notifyEscalationDecisionSlack,
|
|
453
|
+
notifyEscalationEmail,
|
|
454
|
+
notifyEscalationDecisionEmail,
|
|
455
|
+
broadcastEscalation,
|
|
456
|
+
broadcastEscalationDecision,
|
|
457
|
+
};
|