@zintrust/workers 0.1.27

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 (178) hide show
  1. package/README.md +861 -0
  2. package/dist/AnomalyDetection.d.ts +102 -0
  3. package/dist/AnomalyDetection.js +321 -0
  4. package/dist/AutoScaler.d.ts +127 -0
  5. package/dist/AutoScaler.js +425 -0
  6. package/dist/BroadcastWorker.d.ts +21 -0
  7. package/dist/BroadcastWorker.js +24 -0
  8. package/dist/CanaryController.d.ts +103 -0
  9. package/dist/CanaryController.js +380 -0
  10. package/dist/ChaosEngineering.d.ts +79 -0
  11. package/dist/ChaosEngineering.js +216 -0
  12. package/dist/CircuitBreaker.d.ts +106 -0
  13. package/dist/CircuitBreaker.js +374 -0
  14. package/dist/ClusterLock.d.ts +90 -0
  15. package/dist/ClusterLock.js +385 -0
  16. package/dist/ComplianceManager.d.ts +177 -0
  17. package/dist/ComplianceManager.js +556 -0
  18. package/dist/DatacenterOrchestrator.d.ts +133 -0
  19. package/dist/DatacenterOrchestrator.js +404 -0
  20. package/dist/DeadLetterQueue.d.ts +122 -0
  21. package/dist/DeadLetterQueue.js +539 -0
  22. package/dist/HealthMonitor.d.ts +42 -0
  23. package/dist/HealthMonitor.js +301 -0
  24. package/dist/MultiQueueWorker.d.ts +89 -0
  25. package/dist/MultiQueueWorker.js +277 -0
  26. package/dist/NotificationWorker.d.ts +21 -0
  27. package/dist/NotificationWorker.js +23 -0
  28. package/dist/Observability.d.ts +153 -0
  29. package/dist/Observability.js +530 -0
  30. package/dist/PluginManager.d.ts +123 -0
  31. package/dist/PluginManager.js +392 -0
  32. package/dist/PriorityQueue.d.ts +117 -0
  33. package/dist/PriorityQueue.js +244 -0
  34. package/dist/ResourceMonitor.d.ts +164 -0
  35. package/dist/ResourceMonitor.js +605 -0
  36. package/dist/SLAMonitor.d.ts +110 -0
  37. package/dist/SLAMonitor.js +274 -0
  38. package/dist/WorkerFactory.d.ts +193 -0
  39. package/dist/WorkerFactory.js +1507 -0
  40. package/dist/WorkerInit.d.ts +85 -0
  41. package/dist/WorkerInit.js +223 -0
  42. package/dist/WorkerMetrics.d.ts +114 -0
  43. package/dist/WorkerMetrics.js +509 -0
  44. package/dist/WorkerRegistry.d.ts +145 -0
  45. package/dist/WorkerRegistry.js +319 -0
  46. package/dist/WorkerShutdown.d.ts +61 -0
  47. package/dist/WorkerShutdown.js +159 -0
  48. package/dist/WorkerVersioning.d.ts +107 -0
  49. package/dist/WorkerVersioning.js +300 -0
  50. package/dist/build-manifest.json +462 -0
  51. package/dist/config/workerConfig.d.ts +3 -0
  52. package/dist/config/workerConfig.js +19 -0
  53. package/dist/createQueueWorker.d.ts +23 -0
  54. package/dist/createQueueWorker.js +113 -0
  55. package/dist/dashboard/index.d.ts +1 -0
  56. package/dist/dashboard/index.js +1 -0
  57. package/dist/dashboard/types.d.ts +117 -0
  58. package/dist/dashboard/types.js +1 -0
  59. package/dist/dashboard/workers-api.d.ts +4 -0
  60. package/dist/dashboard/workers-api.js +638 -0
  61. package/dist/dashboard/workers-dashboard-ui.d.ts +3 -0
  62. package/dist/dashboard/workers-dashboard-ui.js +1026 -0
  63. package/dist/dashboard/workers-dashboard.d.ts +4 -0
  64. package/dist/dashboard/workers-dashboard.js +904 -0
  65. package/dist/helper/index.d.ts +5 -0
  66. package/dist/helper/index.js +10 -0
  67. package/dist/http/WorkerApiController.d.ts +38 -0
  68. package/dist/http/WorkerApiController.js +312 -0
  69. package/dist/http/WorkerController.d.ts +374 -0
  70. package/dist/http/WorkerController.js +1351 -0
  71. package/dist/http/middleware/CustomValidation.d.ts +92 -0
  72. package/dist/http/middleware/CustomValidation.js +270 -0
  73. package/dist/http/middleware/DatacenterValidator.d.ts +3 -0
  74. package/dist/http/middleware/DatacenterValidator.js +94 -0
  75. package/dist/http/middleware/EditWorkerValidation.d.ts +7 -0
  76. package/dist/http/middleware/EditWorkerValidation.js +55 -0
  77. package/dist/http/middleware/FeaturesValidator.d.ts +3 -0
  78. package/dist/http/middleware/FeaturesValidator.js +60 -0
  79. package/dist/http/middleware/InfrastructureValidator.d.ts +31 -0
  80. package/dist/http/middleware/InfrastructureValidator.js +226 -0
  81. package/dist/http/middleware/OptionsValidator.d.ts +3 -0
  82. package/dist/http/middleware/OptionsValidator.js +112 -0
  83. package/dist/http/middleware/PayloadSanitizer.d.ts +7 -0
  84. package/dist/http/middleware/PayloadSanitizer.js +42 -0
  85. package/dist/http/middleware/ProcessorPathSanitizer.d.ts +3 -0
  86. package/dist/http/middleware/ProcessorPathSanitizer.js +74 -0
  87. package/dist/http/middleware/QueueNameSanitizer.d.ts +3 -0
  88. package/dist/http/middleware/QueueNameSanitizer.js +45 -0
  89. package/dist/http/middleware/ValidateDriver.d.ts +7 -0
  90. package/dist/http/middleware/ValidateDriver.js +20 -0
  91. package/dist/http/middleware/VersionSanitizer.d.ts +3 -0
  92. package/dist/http/middleware/VersionSanitizer.js +25 -0
  93. package/dist/http/middleware/WorkerNameSanitizer.d.ts +3 -0
  94. package/dist/http/middleware/WorkerNameSanitizer.js +46 -0
  95. package/dist/http/middleware/WorkerValidationChain.d.ts +27 -0
  96. package/dist/http/middleware/WorkerValidationChain.js +185 -0
  97. package/dist/index.d.ts +46 -0
  98. package/dist/index.js +48 -0
  99. package/dist/routes/workers.d.ts +12 -0
  100. package/dist/routes/workers.js +81 -0
  101. package/dist/storage/WorkerStore.d.ts +45 -0
  102. package/dist/storage/WorkerStore.js +195 -0
  103. package/dist/type.d.ts +76 -0
  104. package/dist/type.js +1 -0
  105. package/dist/ui/router/ui.d.ts +3 -0
  106. package/dist/ui/router/ui.js +83 -0
  107. package/dist/ui/types/worker-ui.d.ts +229 -0
  108. package/dist/ui/types/worker-ui.js +5 -0
  109. package/package.json +53 -0
  110. package/src/AnomalyDetection.ts +434 -0
  111. package/src/AutoScaler.ts +654 -0
  112. package/src/BroadcastWorker.ts +34 -0
  113. package/src/CanaryController.ts +531 -0
  114. package/src/ChaosEngineering.ts +301 -0
  115. package/src/CircuitBreaker.ts +495 -0
  116. package/src/ClusterLock.ts +499 -0
  117. package/src/ComplianceManager.ts +815 -0
  118. package/src/DatacenterOrchestrator.ts +561 -0
  119. package/src/DeadLetterQueue.ts +733 -0
  120. package/src/HealthMonitor.ts +390 -0
  121. package/src/MultiQueueWorker.ts +431 -0
  122. package/src/NotificationWorker.ts +33 -0
  123. package/src/Observability.ts +696 -0
  124. package/src/PluginManager.ts +551 -0
  125. package/src/PriorityQueue.ts +351 -0
  126. package/src/ResourceMonitor.ts +769 -0
  127. package/src/SLAMonitor.ts +408 -0
  128. package/src/WorkerFactory.ts +2108 -0
  129. package/src/WorkerInit.ts +313 -0
  130. package/src/WorkerMetrics.ts +709 -0
  131. package/src/WorkerRegistry.ts +443 -0
  132. package/src/WorkerShutdown.ts +210 -0
  133. package/src/WorkerVersioning.ts +422 -0
  134. package/src/config/workerConfig.ts +25 -0
  135. package/src/createQueueWorker.ts +174 -0
  136. package/src/dashboard/index.ts +6 -0
  137. package/src/dashboard/types.ts +141 -0
  138. package/src/dashboard/workers-api.ts +785 -0
  139. package/src/dashboard/zintrust.svg +30 -0
  140. package/src/helper/index.ts +11 -0
  141. package/src/http/WorkerApiController.ts +369 -0
  142. package/src/http/WorkerController.ts +1512 -0
  143. package/src/http/middleware/CustomValidation.ts +360 -0
  144. package/src/http/middleware/DatacenterValidator.ts +124 -0
  145. package/src/http/middleware/EditWorkerValidation.ts +74 -0
  146. package/src/http/middleware/FeaturesValidator.ts +82 -0
  147. package/src/http/middleware/InfrastructureValidator.ts +295 -0
  148. package/src/http/middleware/OptionsValidator.ts +144 -0
  149. package/src/http/middleware/PayloadSanitizer.ts +52 -0
  150. package/src/http/middleware/ProcessorPathSanitizer.ts +86 -0
  151. package/src/http/middleware/QueueNameSanitizer.ts +55 -0
  152. package/src/http/middleware/ValidateDriver.ts +29 -0
  153. package/src/http/middleware/VersionSanitizer.ts +30 -0
  154. package/src/http/middleware/WorkerNameSanitizer.ts +56 -0
  155. package/src/http/middleware/WorkerValidationChain.ts +230 -0
  156. package/src/index.ts +98 -0
  157. package/src/routes/workers.ts +154 -0
  158. package/src/storage/WorkerStore.ts +240 -0
  159. package/src/type.ts +89 -0
  160. package/src/types/queue-monitor.d.ts +38 -0
  161. package/src/types/queue-redis.d.ts +38 -0
  162. package/src/ui/README.md +13 -0
  163. package/src/ui/components/JsonEditor.js +670 -0
  164. package/src/ui/components/JsonViewer.js +387 -0
  165. package/src/ui/components/WorkerCard.js +178 -0
  166. package/src/ui/components/WorkerExpandPanel.js +257 -0
  167. package/src/ui/components/fetcher.js +42 -0
  168. package/src/ui/components/sla-scorecard.js +32 -0
  169. package/src/ui/components/styles.css +30 -0
  170. package/src/ui/components/table-expander.js +34 -0
  171. package/src/ui/integration/worker-ui-integration.js +565 -0
  172. package/src/ui/router/ui.ts +99 -0
  173. package/src/ui/services/workerApi.js +240 -0
  174. package/src/ui/types/worker-ui.ts +283 -0
  175. package/src/ui/utils/jsonValidator.js +444 -0
  176. package/src/ui/workers/index.html +202 -0
  177. package/src/ui/workers/main.js +1781 -0
  178. package/src/ui/workers/styles.css +1350 -0
@@ -0,0 +1,531 @@
1
+ /**
2
+ * Canary Deployment Controller
3
+ * Gradual rollout with traffic percentage control and automatic rollback
4
+ * Sealed namespace for immutability
5
+ */
6
+
7
+ import { ErrorFactory, Logger } from '@zintrust/core';
8
+ import { CircuitBreaker } from './CircuitBreaker';
9
+
10
+ export type CanaryStage =
11
+ | 'initial'
12
+ | 'ramping'
13
+ | 'monitoring'
14
+ | 'completed'
15
+ | 'rolling-back'
16
+ | 'failed';
17
+
18
+ export type CanaryDeploymentConfig = {
19
+ workerName: string;
20
+ currentVersion: string;
21
+ canaryVersion: string;
22
+ initialTrafficPercent: number; // Start with this percentage
23
+ targetTrafficPercent: number; // End with this percentage
24
+ incrementPercent: number; // Increase traffic by this amount each step
25
+ incrementInterval: number; // Wait this many seconds between increments
26
+ monitoringDuration: number; // Monitor for this many seconds at each step
27
+ errorThreshold: number; // Rollback if error rate exceeds this (0-1)
28
+ latencyThreshold: number; // Rollback if p95 latency exceeds this (ms)
29
+ minSuccessRate: number; // Rollback if success rate below this (0-1)
30
+ autoRollback: boolean; // Automatically rollback on failure
31
+ };
32
+
33
+ export type CanaryDeployment = {
34
+ config: CanaryDeploymentConfig;
35
+ currentTrafficPercent: number;
36
+ stage: CanaryStage;
37
+ startedAt: Date;
38
+ completedAt?: Date;
39
+ metrics: {
40
+ currentVersion: {
41
+ processed: number;
42
+ errors: number;
43
+ avgLatency: number;
44
+ };
45
+ canaryVersion: {
46
+ processed: number;
47
+ errors: number;
48
+ avgLatency: number;
49
+ };
50
+ };
51
+ history: Array<{
52
+ timestamp: Date;
53
+ trafficPercent: number;
54
+ stage: CanaryStage;
55
+ metrics: CanaryDeployment['metrics'];
56
+ decision: string;
57
+ }>;
58
+ };
59
+
60
+ // Internal state
61
+ const canaryDeployments = new Map<string, CanaryDeployment>();
62
+ const canaryTimers = new Map<string, NodeJS.Timeout>();
63
+ const MAX_HISTORY = 1000;
64
+
65
+ /**
66
+ * Helper: Calculate error rate
67
+ */
68
+ const calculateErrorRate = (processed: number, errors: number): number => {
69
+ if (processed === 0) return 0;
70
+ return errors / processed;
71
+ };
72
+
73
+ /**
74
+ * Helper: Calculate success rate
75
+ */
76
+ const calculateSuccessRate = (processed: number, errors: number): number => {
77
+ if (processed === 0) return 1;
78
+ return (processed - errors) / processed;
79
+ };
80
+
81
+ /**
82
+ * Helper: Should rollback based on metrics
83
+ */
84
+ const shouldRollback = (deployment: CanaryDeployment): { should: boolean; reason?: string } => {
85
+ const { config, metrics } = deployment;
86
+ const { canaryVersion } = metrics;
87
+
88
+ // Check error threshold
89
+ const errorRate = calculateErrorRate(canaryVersion.processed, canaryVersion.errors);
90
+ if (errorRate > config.errorThreshold) {
91
+ return {
92
+ should: true,
93
+ reason: `Error rate ${(errorRate * 100).toFixed(2)}% exceeds threshold ${(config.errorThreshold * 100).toFixed(2)}%`,
94
+ };
95
+ }
96
+
97
+ // Check success rate
98
+ const successRate = calculateSuccessRate(canaryVersion.processed, canaryVersion.errors);
99
+ if (successRate < config.minSuccessRate) {
100
+ return {
101
+ should: true,
102
+ reason: `Success rate ${(successRate * 100).toFixed(2)}% below minimum ${(config.minSuccessRate * 100).toFixed(2)}%`,
103
+ };
104
+ }
105
+
106
+ // Check latency threshold
107
+ if (canaryVersion.avgLatency > config.latencyThreshold) {
108
+ return {
109
+ should: true,
110
+ reason: `P95 latency ${canaryVersion.avgLatency}ms exceeds threshold ${config.latencyThreshold}ms`,
111
+ };
112
+ }
113
+
114
+ return { should: false };
115
+ };
116
+
117
+ /**
118
+ * Helper: Increment traffic
119
+ */
120
+ const incrementTraffic = (workerName: string): void => {
121
+ const deployment = canaryDeployments.get(workerName);
122
+
123
+ if (!deployment) {
124
+ Logger.error('Canary deployment not found', { workerName });
125
+ return;
126
+ }
127
+
128
+ const { config } = deployment;
129
+
130
+ // Check if we should rollback
131
+ const rollbackCheck = shouldRollback(deployment);
132
+ if (rollbackCheck.should && config.autoRollback) {
133
+ Logger.warn('Auto-rollback triggered', {
134
+ workerName,
135
+ reason: rollbackCheck.reason,
136
+ });
137
+
138
+ CanaryController.rollback(workerName, rollbackCheck.reason ?? '');
139
+ return;
140
+ }
141
+
142
+ // Increment traffic
143
+ const newTrafficPercent = Math.min(
144
+ deployment.currentTrafficPercent + config.incrementPercent,
145
+ config.targetTrafficPercent
146
+ );
147
+
148
+ deployment.currentTrafficPercent = newTrafficPercent;
149
+
150
+ // Record history
151
+ appendHistory(deployment, {
152
+ timestamp: new Date(),
153
+ trafficPercent: newTrafficPercent,
154
+ stage: deployment.stage,
155
+ metrics: { ...deployment.metrics },
156
+ decision: `Traffic increased to ${newTrafficPercent}%`,
157
+ });
158
+ Logger.info('Canary traffic incremented', {
159
+ workerName,
160
+ trafficPercent: newTrafficPercent,
161
+ targetPercent: config.targetTrafficPercent,
162
+ });
163
+
164
+ // Check if we've reached the target
165
+ if (newTrafficPercent >= config.targetTrafficPercent) {
166
+ deployment.stage = 'monitoring';
167
+
168
+ // Wait for final monitoring period
169
+ const existingCompleteTimer = canaryTimers.get(`${workerName}:complete`);
170
+ if (existingCompleteTimer) {
171
+ clearTimeout(existingCompleteTimer);
172
+ canaryTimers.delete(`${workerName}:complete`);
173
+ }
174
+
175
+ // eslint-disable-next-line no-restricted-syntax
176
+ const timer = setTimeout(() => {
177
+ CanaryController.complete(workerName);
178
+ }, config.monitoringDuration * 1000);
179
+
180
+ canaryTimers.set(`${workerName}:complete`, timer);
181
+ } else {
182
+ // Schedule next increment
183
+ const existingTimer = canaryTimers.get(workerName);
184
+ if (existingTimer) {
185
+ clearTimeout(existingTimer);
186
+ canaryTimers.delete(workerName);
187
+ }
188
+
189
+ // eslint-disable-next-line no-restricted-syntax
190
+ const timer = setTimeout(() => {
191
+ incrementTraffic(workerName);
192
+ }, config.incrementInterval * 1000);
193
+
194
+ canaryTimers.set(workerName, timer);
195
+ }
196
+ };
197
+
198
+ const appendHistory = (
199
+ deployment: CanaryDeployment,
200
+ entry: CanaryDeployment['history'][number]
201
+ ): void => {
202
+ deployment.history.push(entry);
203
+ if (deployment.history.length > MAX_HISTORY) {
204
+ deployment.history.shift();
205
+ }
206
+ };
207
+
208
+ /**
209
+ * Canary Deployment Controller - Sealed namespace
210
+ */
211
+ export const CanaryController = Object.freeze({
212
+ /**
213
+ * Start canary deployment
214
+ */
215
+ start(config: CanaryDeploymentConfig): void {
216
+ const { workerName } = config;
217
+
218
+ if (canaryDeployments.has(workerName)) {
219
+ throw ErrorFactory.createGeneralError(
220
+ `Canary deployment already in progress for "${workerName}"`
221
+ );
222
+ }
223
+
224
+ // Validate config
225
+ if (config.initialTrafficPercent < 0 || config.initialTrafficPercent > 100) {
226
+ throw ErrorFactory.createValidationError('Initial traffic percent must be between 0 and 100');
227
+ }
228
+
229
+ if (
230
+ config.targetTrafficPercent < config.initialTrafficPercent ||
231
+ config.targetTrafficPercent > 100
232
+ ) {
233
+ throw ErrorFactory.createValidationError(
234
+ 'Target traffic percent must be >= initial and <= 100'
235
+ );
236
+ }
237
+
238
+ // Create deployment
239
+ const deployment: CanaryDeployment = {
240
+ config,
241
+ currentTrafficPercent: config.initialTrafficPercent,
242
+ stage: 'initial',
243
+ startedAt: new Date(),
244
+ metrics: {
245
+ currentVersion: { processed: 0, errors: 0, avgLatency: 0 },
246
+ canaryVersion: { processed: 0, errors: 0, avgLatency: 0 },
247
+ },
248
+ history: [],
249
+ };
250
+
251
+ canaryDeployments.set(workerName, deployment);
252
+
253
+ Logger.info('Canary deployment started', {
254
+ workerName,
255
+ currentVersion: config.currentVersion,
256
+ canaryVersion: config.canaryVersion,
257
+ initialTraffic: config.initialTrafficPercent,
258
+ });
259
+
260
+ // Start ramping up
261
+ deployment.stage = 'ramping';
262
+
263
+ // Schedule first increment
264
+ const existingTimer = canaryTimers.get(workerName);
265
+ if (existingTimer) {
266
+ clearTimeout(existingTimer);
267
+ canaryTimers.delete(workerName);
268
+ }
269
+
270
+ // eslint-disable-next-line no-restricted-syntax
271
+ const timer = setTimeout(() => {
272
+ incrementTraffic(workerName);
273
+ }, config.monitoringDuration * 1000);
274
+
275
+ canaryTimers.set(workerName, timer);
276
+ },
277
+
278
+ /**
279
+ * Pause canary deployment
280
+ */
281
+ pause(workerName: string): void {
282
+ const deployment = canaryDeployments.get(workerName);
283
+
284
+ if (!deployment) {
285
+ throw ErrorFactory.createNotFoundError(`Canary deployment not found for "${workerName}"`);
286
+ }
287
+
288
+ // Clear timer
289
+ const timer = canaryTimers.get(workerName);
290
+ if (timer) {
291
+ clearTimeout(timer);
292
+ canaryTimers.delete(workerName);
293
+ }
294
+
295
+ Logger.info('Canary deployment paused', { workerName });
296
+ },
297
+
298
+ /**
299
+ * Resume canary deployment
300
+ */
301
+ resume(workerName: string): void {
302
+ const deployment = canaryDeployments.get(workerName);
303
+
304
+ if (!deployment) {
305
+ throw ErrorFactory.createNotFoundError(`Canary deployment not found for "${workerName}"`);
306
+ }
307
+
308
+ // Resume incrementing
309
+ const existingTimer = canaryTimers.get(workerName);
310
+ if (existingTimer) {
311
+ clearTimeout(existingTimer);
312
+ canaryTimers.delete(workerName);
313
+ }
314
+
315
+ // eslint-disable-next-line no-restricted-syntax
316
+ const timer = setTimeout(() => {
317
+ incrementTraffic(workerName);
318
+ }, deployment.config.incrementInterval * 1000);
319
+
320
+ canaryTimers.set(workerName, timer);
321
+
322
+ Logger.info('Canary deployment resumed', { workerName });
323
+ },
324
+
325
+ /**
326
+ * Complete canary deployment
327
+ */
328
+ complete(workerName: string): void {
329
+ const deployment = canaryDeployments.get(workerName);
330
+
331
+ if (!deployment) {
332
+ throw ErrorFactory.createNotFoundError(`Canary deployment not found for "${workerName}"`);
333
+ }
334
+
335
+ deployment.stage = 'completed';
336
+ deployment.completedAt = new Date();
337
+
338
+ // Clear timers
339
+ const timer = canaryTimers.get(workerName);
340
+ if (timer) {
341
+ clearTimeout(timer);
342
+ canaryTimers.delete(workerName);
343
+ }
344
+
345
+ const completeTimer = canaryTimers.get(`${workerName}:complete`);
346
+ if (completeTimer) {
347
+ clearTimeout(completeTimer);
348
+ canaryTimers.delete(`${workerName}:complete`);
349
+ }
350
+
351
+ // Record completion in history
352
+ appendHistory(deployment, {
353
+ timestamp: new Date(),
354
+ trafficPercent: deployment.currentTrafficPercent,
355
+ stage: 'completed',
356
+ metrics: { ...deployment.metrics },
357
+ decision: 'Deployment completed successfully',
358
+ });
359
+
360
+ Logger.info('Canary deployment completed', {
361
+ workerName,
362
+ duration: deployment.completedAt.getTime() - deployment.startedAt.getTime(),
363
+ });
364
+ },
365
+
366
+ /**
367
+ * Rollback canary deployment
368
+ */
369
+ rollback(workerName: string, reason: string): void {
370
+ const deployment = canaryDeployments.get(workerName);
371
+
372
+ if (!deployment) {
373
+ throw ErrorFactory.createNotFoundError(`Canary deployment not found for "${workerName}"`);
374
+ }
375
+
376
+ deployment.stage = 'rolling-back';
377
+
378
+ // Clear timers
379
+ const timer = canaryTimers.get(workerName);
380
+ if (timer) {
381
+ clearTimeout(timer);
382
+ canaryTimers.delete(workerName);
383
+ }
384
+
385
+ // Roll back traffic to 0%
386
+ deployment.currentTrafficPercent = 0;
387
+
388
+ // Record rollback in history
389
+ appendHistory(deployment, {
390
+ timestamp: new Date(),
391
+ trafficPercent: 0,
392
+ stage: 'rolling-back',
393
+ metrics: { ...deployment.metrics },
394
+ decision: `Rollback initiated: ${reason}`,
395
+ });
396
+
397
+ deployment.stage = 'failed';
398
+ deployment.completedAt = new Date();
399
+
400
+ Logger.error('Canary deployment rolled back', { workerName, reason });
401
+
402
+ // Optional: Open circuit breaker for canary version
403
+ CircuitBreaker.forceOpen(workerName, deployment.config.canaryVersion, reason);
404
+ },
405
+
406
+ /**
407
+ * Get canary deployment status
408
+ */
409
+ getStatus(workerName: string): CanaryDeployment | null {
410
+ const deployment = canaryDeployments.get(workerName);
411
+ return deployment ? { ...deployment } : null;
412
+ },
413
+
414
+ /**
415
+ * Update metrics for canary deployment
416
+ */
417
+ updateMetrics(
418
+ workerName: string,
419
+ version: string,
420
+ processed: number,
421
+ errors: number,
422
+ avgLatency: number
423
+ ): void {
424
+ const deployment = canaryDeployments.get(workerName);
425
+
426
+ if (!deployment) {
427
+ return;
428
+ }
429
+
430
+ if (version === deployment.config.currentVersion) {
431
+ deployment.metrics.currentVersion = { processed, errors, avgLatency };
432
+ } else if (version === deployment.config.canaryVersion) {
433
+ deployment.metrics.canaryVersion = { processed, errors, avgLatency };
434
+ }
435
+ },
436
+
437
+ /**
438
+ * Route job to version based on traffic percentage
439
+ */
440
+ routeJob(workerName: string): string | null {
441
+ const deployment = canaryDeployments.get(workerName);
442
+
443
+ if (!deployment || deployment.stage === 'completed' || deployment.stage === 'failed') {
444
+ return null; // No active canary
445
+ }
446
+
447
+ // Random routing based on traffic percentage
448
+ const random = Math.random() * 100; // NOSONAR
449
+
450
+ if (random < deployment.currentTrafficPercent) {
451
+ return deployment.config.canaryVersion;
452
+ }
453
+
454
+ return deployment.config.currentVersion;
455
+ },
456
+
457
+ /**
458
+ * List all canary deployments
459
+ */
460
+ listDeployments(): string[] {
461
+ return Array.from(canaryDeployments.keys());
462
+ },
463
+
464
+ /**
465
+ * Get deployment history
466
+ */
467
+ getHistory(workerName: string): CanaryDeployment['history'] | null {
468
+ const deployment = canaryDeployments.get(workerName);
469
+ return deployment ? [...deployment.history] : null;
470
+ },
471
+
472
+ /**
473
+ * Remove completed/failed deployment
474
+ */
475
+ remove(workerName: string): void {
476
+ const deployment = canaryDeployments.get(workerName);
477
+
478
+ if (!deployment) {
479
+ throw ErrorFactory.createNotFoundError(`Canary deployment not found for "${workerName}"`);
480
+ }
481
+
482
+ if (deployment.stage !== 'completed' && deployment.stage !== 'failed') {
483
+ throw ErrorFactory.createValidationError(
484
+ 'Cannot remove active deployment. Pause or complete it first.'
485
+ );
486
+ }
487
+
488
+ canaryDeployments.delete(workerName);
489
+
490
+ Logger.info('Canary deployment removed', { workerName });
491
+ },
492
+
493
+ /**
494
+ * Purge deployment data (force cleanup)
495
+ */
496
+ purge(workerName: string): void {
497
+ const timer = canaryTimers.get(workerName);
498
+ if (timer) {
499
+ clearTimeout(timer);
500
+ canaryTimers.delete(workerName);
501
+ }
502
+
503
+ const completionTimer = canaryTimers.get(`${workerName}:complete`);
504
+ if (completionTimer) {
505
+ clearTimeout(completionTimer);
506
+ canaryTimers.delete(`${workerName}:complete`);
507
+ }
508
+
509
+ canaryDeployments.delete(workerName);
510
+ Logger.info('Canary deployment purged', { workerName });
511
+ },
512
+
513
+ /**
514
+ * Shutdown all canary deployments
515
+ */
516
+ shutdown(): void {
517
+ Logger.info('CanaryController shutting down...');
518
+
519
+ // Clear all timers
520
+ for (const timer of canaryTimers.values()) {
521
+ clearTimeout(timer);
522
+ }
523
+ canaryTimers.clear();
524
+
525
+ canaryDeployments.clear();
526
+
527
+ Logger.info('CanaryController shutdown complete');
528
+ },
529
+ });
530
+
531
+ // Graceful shutdown handled by WorkerShutdown