guardrail-compliance 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.
Files changed (149) hide show
  1. package/dist/audit/emitter.d.ts +97 -0
  2. package/dist/audit/emitter.d.ts.map +1 -0
  3. package/dist/audit/emitter.js +197 -0
  4. package/dist/audit/events.d.ts +304 -0
  5. package/dist/audit/events.d.ts.map +1 -0
  6. package/dist/audit/events.js +267 -0
  7. package/dist/audit/index.d.ts +11 -0
  8. package/dist/audit/index.d.ts.map +1 -0
  9. package/dist/audit/index.js +51 -0
  10. package/dist/audit/storage.d.ts +93 -0
  11. package/dist/audit/storage.d.ts.map +1 -0
  12. package/dist/audit/storage.js +337 -0
  13. package/dist/automation/__tests__/compliance-scheduler.test.d.ts +2 -0
  14. package/dist/automation/__tests__/compliance-scheduler.test.d.ts.map +1 -0
  15. package/dist/automation/__tests__/compliance-scheduler.test.js +140 -0
  16. package/dist/automation/audit-logger.d.ts +129 -0
  17. package/dist/automation/audit-logger.d.ts.map +1 -0
  18. package/dist/automation/audit-logger.js +473 -0
  19. package/dist/automation/compliance-scheduler-fixed.d.ts +1 -0
  20. package/dist/automation/compliance-scheduler-fixed.d.ts.map +1 -0
  21. package/dist/automation/compliance-scheduler-fixed.js +1 -0
  22. package/dist/automation/compliance-scheduler.d.ts +83 -0
  23. package/dist/automation/compliance-scheduler.d.ts.map +1 -0
  24. package/dist/automation/compliance-scheduler.js +414 -0
  25. package/dist/automation/dashboard.d.ts +194 -0
  26. package/dist/automation/dashboard.d.ts.map +1 -0
  27. package/dist/automation/dashboard.js +768 -0
  28. package/dist/automation/email-service.d.ts +69 -0
  29. package/dist/automation/email-service.d.ts.map +1 -0
  30. package/dist/automation/email-service.js +218 -0
  31. package/dist/automation/evidence-collector.d.ts +140 -0
  32. package/dist/automation/evidence-collector.d.ts.map +1 -0
  33. package/dist/automation/evidence-collector.js +682 -0
  34. package/dist/automation/index.d.ts +8 -0
  35. package/dist/automation/index.d.ts.map +1 -0
  36. package/dist/automation/index.js +24 -0
  37. package/dist/automation/pdf-exporter.d.ts +90 -0
  38. package/dist/automation/pdf-exporter.d.ts.map +1 -0
  39. package/dist/automation/pdf-exporter.js +381 -0
  40. package/dist/automation/reporting-engine.d.ts +116 -0
  41. package/dist/automation/reporting-engine.d.ts.map +1 -0
  42. package/dist/automation/reporting-engine.js +329 -0
  43. package/dist/container/index.d.ts +4 -0
  44. package/dist/container/index.d.ts.map +1 -0
  45. package/dist/container/index.js +19 -0
  46. package/dist/container/kubernetes.d.ts +94 -0
  47. package/dist/container/kubernetes.d.ts.map +1 -0
  48. package/dist/container/kubernetes.js +268 -0
  49. package/dist/container/rules.d.ts +27 -0
  50. package/dist/container/rules.d.ts.map +1 -0
  51. package/dist/container/rules.js +216 -0
  52. package/dist/container/scanner.d.ts +50 -0
  53. package/dist/container/scanner.d.ts.map +1 -0
  54. package/dist/container/scanner.js +143 -0
  55. package/dist/frameworks/engine.d.ts +108 -0
  56. package/dist/frameworks/engine.d.ts.map +1 -0
  57. package/dist/frameworks/engine.js +206 -0
  58. package/dist/frameworks/gdpr.d.ts +6 -0
  59. package/dist/frameworks/gdpr.d.ts.map +1 -0
  60. package/dist/frameworks/gdpr.js +198 -0
  61. package/dist/frameworks/hipaa.d.ts +6 -0
  62. package/dist/frameworks/hipaa.d.ts.map +1 -0
  63. package/dist/frameworks/hipaa.js +183 -0
  64. package/dist/frameworks/index.d.ts +8 -0
  65. package/dist/frameworks/index.d.ts.map +1 -0
  66. package/dist/frameworks/index.js +30 -0
  67. package/dist/frameworks/iso27001.d.ts +63 -0
  68. package/dist/frameworks/iso27001.d.ts.map +1 -0
  69. package/dist/frameworks/iso27001.js +331 -0
  70. package/dist/frameworks/nist.d.ts +62 -0
  71. package/dist/frameworks/nist.d.ts.map +1 -0
  72. package/dist/frameworks/nist.js +424 -0
  73. package/dist/frameworks/pci.d.ts +6 -0
  74. package/dist/frameworks/pci.d.ts.map +1 -0
  75. package/dist/frameworks/pci.js +201 -0
  76. package/dist/frameworks/soc2.d.ts +7 -0
  77. package/dist/frameworks/soc2.d.ts.map +1 -0
  78. package/dist/frameworks/soc2.js +248 -0
  79. package/dist/iac/drift-detector.d.ts +64 -0
  80. package/dist/iac/drift-detector.d.ts.map +1 -0
  81. package/dist/iac/drift-detector.js +134 -0
  82. package/dist/iac/index.d.ts +4 -0
  83. package/dist/iac/index.d.ts.map +1 -0
  84. package/dist/iac/index.js +19 -0
  85. package/dist/iac/rules.d.ts +17 -0
  86. package/dist/iac/rules.d.ts.map +1 -0
  87. package/dist/iac/rules.js +385 -0
  88. package/dist/iac/scanner.d.ts +104 -0
  89. package/dist/iac/scanner.d.ts.map +1 -0
  90. package/dist/iac/scanner.js +343 -0
  91. package/dist/index.d.ts +7 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +28 -0
  94. package/dist/pii/data-flow.d.ts +58 -0
  95. package/dist/pii/data-flow.d.ts.map +1 -0
  96. package/dist/pii/data-flow.js +154 -0
  97. package/dist/pii/detector.d.ts +60 -0
  98. package/dist/pii/detector.d.ts.map +1 -0
  99. package/dist/pii/detector.js +267 -0
  100. package/dist/pii/index.d.ts +4 -0
  101. package/dist/pii/index.d.ts.map +1 -0
  102. package/dist/pii/index.js +19 -0
  103. package/dist/pii/patterns.d.ts +36 -0
  104. package/dist/pii/patterns.d.ts.map +1 -0
  105. package/dist/pii/patterns.js +108 -0
  106. package/dist/policy/index.d.ts +5 -0
  107. package/dist/policy/index.d.ts.map +1 -0
  108. package/dist/policy/index.js +20 -0
  109. package/dist/policy/opa-engine.d.ts +121 -0
  110. package/dist/policy/opa-engine.d.ts.map +1 -0
  111. package/dist/policy/opa-engine.js +423 -0
  112. package/package.json +31 -0
  113. package/src/audit/emitter.ts +383 -0
  114. package/src/audit/events.ts +351 -0
  115. package/src/audit/index.ts +35 -0
  116. package/src/audit/storage.ts +394 -0
  117. package/src/automation/__tests__/compliance-scheduler.test.ts +183 -0
  118. package/src/automation/audit-logger.ts +629 -0
  119. package/src/automation/compliance-scheduler-fixed.ts +0 -0
  120. package/src/automation/compliance-scheduler.ts +516 -0
  121. package/src/automation/dashboard.ts +947 -0
  122. package/src/automation/email-service.ts +230 -0
  123. package/src/automation/evidence-collector.ts +866 -0
  124. package/src/automation/index.ts +8 -0
  125. package/src/automation/pdf-exporter.ts +434 -0
  126. package/src/automation/reporting-engine.ts +462 -0
  127. package/src/container/index.ts +3 -0
  128. package/src/container/kubernetes.ts +379 -0
  129. package/src/container/rules.ts +244 -0
  130. package/src/container/scanner.ts +202 -0
  131. package/src/frameworks/engine.ts +298 -0
  132. package/src/frameworks/gdpr.ts +204 -0
  133. package/src/frameworks/hipaa.ts +209 -0
  134. package/src/frameworks/index.ts +23 -0
  135. package/src/frameworks/iso27001.ts +398 -0
  136. package/src/frameworks/nist.ts +518 -0
  137. package/src/frameworks/pci.ts +226 -0
  138. package/src/frameworks/soc2.ts +281 -0
  139. package/src/iac/drift-detector.ts +197 -0
  140. package/src/iac/index.ts +3 -0
  141. package/src/iac/rules.ts +420 -0
  142. package/src/iac/scanner.ts +445 -0
  143. package/src/index.ts +17 -0
  144. package/src/pii/data-flow.ts +216 -0
  145. package/src/pii/detector.ts +327 -0
  146. package/src/pii/index.ts +3 -0
  147. package/src/pii/patterns.ts +128 -0
  148. package/src/policy/index.ts +5 -0
  149. package/src/policy/opa-engine.ts +504 -0
@@ -0,0 +1,516 @@
1
+ import { prisma } from "@guardrail/database";
2
+ import { complianceAutomationEngine } from "../frameworks/engine";
3
+ import { evidenceCollector } from "./evidence-collector";
4
+ import { reportingEngine } from "./reporting-engine";
5
+ import { emailService } from "./email-service";
6
+ // import { auditLogger } from './audit-logger'; // Currently unused
7
+ // import { CronJob } from 'cron'; // Commented out until dependency is installed
8
+
9
+ interface ComplianceSchedule {
10
+ id: string;
11
+ projectId: string;
12
+ frameworkId: string;
13
+ frequency: string; // Cron expression
14
+ enabled: boolean;
15
+ lastRun?: Date;
16
+ nextRun?: Date;
17
+ notifications?: {
18
+ email?: string[];
19
+ slack?: string;
20
+ webhook?: string;
21
+ };
22
+ }
23
+
24
+ interface ComplianceExecutionResult {
25
+ scheduleId: string;
26
+ executionId: string;
27
+ startTime: Date;
28
+ endTime: Date;
29
+ status: "running" | "completed" | "failed";
30
+ result?: {
31
+ assessment?: any;
32
+ evidence?: any;
33
+ report?: any;
34
+ };
35
+ error?: string;
36
+ }
37
+
38
+ /**
39
+ * Compliance Scheduler
40
+ *
41
+ * Manages scheduled compliance checks and notifications
42
+ */
43
+ export class ComplianceScheduler {
44
+ private jobs = new Map<string, any>();
45
+ private executions = new Map<string, ComplianceExecutionResult>();
46
+
47
+ /**
48
+ * Initialize scheduler and load existing schedules
49
+ */
50
+ async initialize(): Promise<void> {
51
+ const schedules = await prisma.complianceSchedule.findMany({
52
+ where: { enabled: true },
53
+ });
54
+
55
+ for (const schedule of schedules) {
56
+ await this.scheduleJob(schedule);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Create or update a schedule
62
+ */
63
+ async upsertSchedule(
64
+ schedule: Omit<ComplianceSchedule, "id" | "lastRun" | "nextRun">,
65
+ ): Promise<string> {
66
+ if (!this.isValidCron(schedule.frequency)) {
67
+ throw new Error("Invalid cron expression");
68
+ }
69
+
70
+ let dbSchedule: any;
71
+
72
+ try {
73
+ dbSchedule = await prisma.complianceSchedule.upsert({
74
+ where: {
75
+ id: `${schedule.projectId}_${schedule.frameworkId}`,
76
+ },
77
+ update: {
78
+ schedule: schedule.frequency as any,
79
+ enabled: schedule.enabled,
80
+ },
81
+ create: {
82
+ projectId: schedule.projectId,
83
+ frameworkId: schedule.frameworkId,
84
+ schedule: schedule.frequency as any,
85
+ enabled: schedule.enabled,
86
+ nextRun: new Date(),
87
+ } as any,
88
+ });
89
+ } catch (error) {
90
+ console.warn("Could not upsert schedule in database:", error);
91
+ // Create a mock schedule object for in-memory operation
92
+ dbSchedule = {
93
+ id: `sched_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
94
+ ...schedule,
95
+ lastRun: null,
96
+ nextRun: new Date(),
97
+ };
98
+ }
99
+
100
+ if (schedule.enabled) {
101
+ await this.scheduleJob(dbSchedule);
102
+ }
103
+
104
+ return dbSchedule.id;
105
+ }
106
+
107
+ /**
108
+ * Remove a schedule
109
+ */
110
+ async removeSchedule(projectId: string, frameworkId: string): Promise<void> {
111
+ const jobKey = `${projectId}:${frameworkId}`;
112
+
113
+ if (this.jobs.has(jobKey)) {
114
+ this.jobs.get(jobKey)!.stop();
115
+ this.jobs.delete(jobKey);
116
+ }
117
+
118
+ try {
119
+ await prisma.complianceSchedule.deleteMany({
120
+ where: {
121
+ projectId,
122
+ frameworkId,
123
+ },
124
+ });
125
+ } catch (error) {
126
+ console.warn("Could not delete schedule from database:", error);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Run a compliance check
132
+ */
133
+ async runCheck(
134
+ projectId: string,
135
+ frameworkId: string,
136
+ options?: {
137
+ collectEvidence?: boolean;
138
+ generateReport?: boolean;
139
+ notifyOnCompletion?: boolean;
140
+ },
141
+ ): Promise<ComplianceExecutionResult> {
142
+ const executionId = `exec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
143
+
144
+ const result: ComplianceExecutionResult = {
145
+ scheduleId: `${projectId}:${frameworkId}`,
146
+ executionId,
147
+ startTime: new Date(),
148
+ endTime: new Date(),
149
+ status: "running",
150
+ };
151
+
152
+ this.executions.set(executionId, result);
153
+
154
+ try {
155
+ // Get project details
156
+ const project = await prisma.project.findUnique({
157
+ where: { id: projectId },
158
+ });
159
+
160
+ if (!project) {
161
+ throw new Error(`Project ${projectId} not found`);
162
+ }
163
+
164
+ // Run compliance assessment
165
+ const assessment = await complianceAutomationEngine.assess(
166
+ project.path || "",
167
+ frameworkId,
168
+ projectId,
169
+ );
170
+
171
+ // Collect evidence if requested
172
+ let evidence: any = null;
173
+ if (options?.collectEvidence !== false) {
174
+ evidence = await evidenceCollector.collectForAssessment(
175
+ projectId,
176
+ frameworkId,
177
+ assessment,
178
+ );
179
+ }
180
+
181
+ // Generate report if requested
182
+ let report: any = null;
183
+ if (options?.generateReport) {
184
+ report = await reportingEngine.generateReport({
185
+ projectId,
186
+ frameworkId,
187
+ type: "compliance",
188
+ format: "json",
189
+ includeEvidence: !!evidence,
190
+ includeRecommendations: true,
191
+ includeCharts: false,
192
+ });
193
+ }
194
+
195
+ // Update result
196
+ result.result = {
197
+ assessment,
198
+ evidence,
199
+ report,
200
+ };
201
+ result.status = "completed";
202
+ result.endTime = new Date();
203
+
204
+ // Send notifications if requested
205
+ if (options?.notifyOnCompletion) {
206
+ setTimeout(() => {
207
+ this.sendNotifications(projectId, frameworkId, result);
208
+ }, 1000);
209
+ }
210
+ } catch (error) {
211
+ result.status = "failed";
212
+ result.error = error instanceof Error ? error.message : "Unknown error";
213
+ result.endTime = new Date();
214
+ }
215
+
216
+ return result;
217
+ }
218
+
219
+ /**
220
+ * Get execution status
221
+ */
222
+ getExecutionStatus(
223
+ executionId: string,
224
+ ): ComplianceExecutionResult | undefined {
225
+ return this.executions.get(executionId);
226
+ }
227
+
228
+ /**
229
+ * Get all schedules
230
+ */
231
+ async getSchedules(projectId?: string): Promise<ComplianceSchedule[]> {
232
+ const schedules = await prisma.complianceSchedule.findMany({
233
+ where: projectId ? { projectId } : undefined,
234
+ orderBy: { nextRun: "asc" },
235
+ });
236
+
237
+ return schedules.map((s: any) => ({
238
+ id: s.id,
239
+ projectId: s.projectId,
240
+ frameworkId: s.frameworkId,
241
+ frequency: (s as any).schedule,
242
+ enabled: s.enabled,
243
+ lastRun: s.lastRun || undefined,
244
+ nextRun: s.nextRun || undefined,
245
+ notifications: (s as any).notifications || undefined,
246
+ }));
247
+ }
248
+
249
+ /**
250
+ * Schedule a job
251
+ */
252
+ private async scheduleJob(schedule: any): Promise<void> {
253
+ const jobKey = `${schedule.projectId}:${schedule.frameworkId}`;
254
+
255
+ // Remove existing job if any
256
+ if (this.jobs.has(jobKey)) {
257
+ this.jobs.get(jobKey)!.stop();
258
+ }
259
+
260
+ // Create new cron job
261
+ // const job = new CronJob(schedule.schedule, async () => {
262
+ // await this.executeScheduledCheck(schedule);
263
+ // }, null, true, 'UTC');
264
+
265
+ // this.jobs.set(jobKey, job);
266
+
267
+ // Update next run time
268
+ try {
269
+ await prisma.complianceSchedule.update({
270
+ where: { id: schedule.id },
271
+ data: { nextRun: new Date() },
272
+ });
273
+ } catch (error) {
274
+ console.warn("Could not update next run time in database:", error);
275
+ }
276
+ }
277
+
278
+ // Execute a scheduled check - currently unused
279
+ /*
280
+ private async executeScheduledCheck(schedule: any): Promise<void> {
281
+ try {
282
+ const result = await this.runCheck(
283
+ schedule.projectId,
284
+ schedule.frameworkId,
285
+ {
286
+ collectEvidence: true,
287
+ generateReport: true,
288
+ notifyOnCompletion: true
289
+ }
290
+ );
291
+
292
+ // Update last run time
293
+ try {
294
+ await prisma.complianceSchedule.update({
295
+ where: { id: schedule.id },
296
+ data: { lastRun: new Date() }
297
+ });
298
+ } catch (error) {
299
+ console.warn('Could not update last run time in database:', error);
300
+ }
301
+
302
+ // Check for compliance failures and send alerts
303
+ if (result.result?.assessment?.summary?.score < 70) {
304
+ await this.sendAlert(schedule, result);
305
+ }
306
+
307
+ } catch (error) {
308
+ console.error(`Scheduled check failed for ${schedule.projectId}:${schedule.frameworkId}:`, error);
309
+ await this.sendErrorAlert(schedule, error);
310
+ }
311
+ }
312
+ */
313
+
314
+ /**
315
+ * Send notifications for completed checks
316
+ */
317
+ private async sendNotifications(
318
+ projectId: string,
319
+ frameworkId: string,
320
+ result: ComplianceExecutionResult,
321
+ ): Promise<void> {
322
+ try {
323
+ const schedule = await prisma.complianceSchedule.findFirst({
324
+ where: {
325
+ projectId,
326
+ frameworkId,
327
+ },
328
+ });
329
+
330
+ if (!(schedule as any)?.notifications) return;
331
+
332
+ const notifications = (schedule as any).notifications;
333
+
334
+ // Send email notifications
335
+ if (notifications.email?.length) {
336
+ const score = result.result?.assessment?.summary?.score;
337
+ const status = result.status === "completed" ? "completed" : "failed";
338
+
339
+ const emailResult = await emailService.sendComplianceNotification(
340
+ notifications.email,
341
+ projectId,
342
+ frameworkId,
343
+ {
344
+ status,
345
+ score,
346
+ summary: result.error || undefined,
347
+ },
348
+ );
349
+
350
+ if (!emailResult.success) {
351
+ console.error(
352
+ `Failed to send compliance email to ${notifications.email.join(", ")}: ${emailResult.error}`,
353
+ );
354
+ } else {
355
+ console.log(
356
+ `Sent compliance check email to ${notifications.email.join(", ")} (messageId: ${emailResult.messageId})`,
357
+ );
358
+ }
359
+ }
360
+
361
+ // Send Slack notifications
362
+ if (notifications.slack) {
363
+ try {
364
+ const score = result.result?.assessment?.summary?.score || 0;
365
+ const status =
366
+ score >= 90 ? "passed" : score >= 70 ? "warning" : "failed";
367
+ const statusIcon =
368
+ status === "passed" ? "✅" : status === "warning" ? "⚠️" : "❌";
369
+
370
+ const slackPayload = {
371
+ blocks: [
372
+ {
373
+ type: "header",
374
+ text: {
375
+ type: "plain_text",
376
+ text: `${statusIcon} Compliance Check ${status.toUpperCase()}`,
377
+ emoji: true,
378
+ },
379
+ },
380
+ {
381
+ type: "section",
382
+ fields: [
383
+ {
384
+ type: "mrkdwn",
385
+ text: `*Project:*\n${projectId}`,
386
+ },
387
+ {
388
+ type: "mrkdwn",
389
+ text: `*Framework:*\n${frameworkId}`,
390
+ },
391
+ ],
392
+ },
393
+ {
394
+ type: "section",
395
+ fields: [
396
+ {
397
+ type: "mrkdwn",
398
+ text: `*Score:*\n${score}%`,
399
+ },
400
+ {
401
+ type: "mrkdwn",
402
+ text: `*Status:*\n${status}`,
403
+ },
404
+ ],
405
+ },
406
+ {
407
+ type: "section",
408
+ text: {
409
+ type: "mrkdwn",
410
+ text: `Check completed at ${new Date().toLocaleString()}`,
411
+ },
412
+ },
413
+ ],
414
+ };
415
+
416
+ await fetch(notifications.slack, {
417
+ method: "POST",
418
+ headers: { "Content-Type": "application/json" },
419
+ body: JSON.stringify(slackPayload),
420
+ });
421
+ console.log(`Sending Slack notification to ${notifications.slack}`);
422
+ } catch (slackError) {
423
+ console.error("Failed to send Slack notification:", slackError);
424
+ }
425
+ }
426
+
427
+ // Send webhook notifications
428
+ if (notifications.webhook) {
429
+ console.log(`Sending webhook notification to ${notifications.webhook}`);
430
+ try {
431
+ const response = await fetch(notifications.webhook, {
432
+ method: "POST",
433
+ headers: {
434
+ "Content-Type": "application/json",
435
+ },
436
+ body: JSON.stringify(result),
437
+ });
438
+
439
+ if (!response.ok) {
440
+ console.error(
441
+ `Failed to send webhook notification: ${response.status} ${response.statusText}`,
442
+ );
443
+ }
444
+ } catch (webhookError) {
445
+ console.error("Error sending webhook notification:", webhookError);
446
+ }
447
+ }
448
+ } catch (error) {
449
+ console.error("Failed to send notifications:", error);
450
+ }
451
+ }
452
+
453
+ /*
454
+ private async sendAlert(schedule: any, result: ComplianceExecutionResult): Promise<void> {
455
+ const score = result.result?.assessment?.summary?.score || 0;
456
+ const message = `Compliance check failed for project ${schedule.projectId} (${schedule.frameworkId}). Score: ${score}%`;
457
+
458
+ await auditLogger.logEvent({
459
+ type: 'compliance_failure',
460
+ category: 'compliance',
461
+ projectId: schedule.projectId,
462
+ timestamp: new Date(),
463
+ severity: 'high',
464
+ source: 'scheduler',
465
+ details: {
466
+ action: 'Compliance check failed',
467
+ framework: schedule.frameworkId,
468
+ score,
469
+ message
470
+ }
471
+ });
472
+
473
+ await this.sendNotifications(schedule.projectId, schedule.frameworkId, result);
474
+ }
475
+ */
476
+
477
+ /*
478
+ private async sendErrorAlert(schedule: any, error: any): Promise<void> {
479
+ await auditLogger.logEvent({
480
+ type: 'compliance_error',
481
+ category: 'system',
482
+ projectId: schedule.projectId,
483
+ timestamp: new Date(),
484
+ severity: 'critical',
485
+ source: 'scheduler',
486
+ details: {
487
+ action: 'Compliance check error',
488
+ framework: schedule.frameworkId,
489
+ error: error instanceof Error ? error.message : 'Unknown error'
490
+ }
491
+ });
492
+ }
493
+ */
494
+
495
+ /**
496
+ * Validate cron expression
497
+ */
498
+ private isValidCron(cron: string): boolean {
499
+ // Basic validation - should be more sophisticated
500
+ const parts = cron.split(" ");
501
+ return parts.length === 5;
502
+ }
503
+
504
+ /**
505
+ * Stop all jobs
506
+ */
507
+ stopAll(): void {
508
+ for (const job of this.jobs.values()) {
509
+ job.stop();
510
+ }
511
+ this.jobs.clear();
512
+ }
513
+ }
514
+
515
+ // Export singleton instance
516
+ export const complianceScheduler = new ComplianceScheduler();