diff --git a/applications/chatops/slack-bot/src/shared/worker-utils.ts b/applications/chatops/slack-bot/src/shared/worker-utils.ts new file mode 100644 index 0000000..f674d01 --- /dev/null +++ b/applications/chatops/slack-bot/src/shared/worker-utils.ts @@ -0,0 +1,224 @@ +// Shared worker utilities for unified command routing + +import { SQSEvent, SQSBatchResponse, SQSRecord } from 'aws-lambda'; +import { logger } from './logger'; +import { WorkerMessage } from './types'; + +/** + * Handler result with performance metrics + */ +export interface HandlerResult { + syncResponseMs?: number; + asyncResponseMs?: number; +} + +/** + * Configuration for a unified worker + */ +export interface WorkerConfig { + componentName: string; // e.g., 'sr-worker', 'sw-worker' + quadrantName: string; // e.g., 'short-read', 'short-write' + commandHandlers: Record< + string, + (message: WorkerMessage, messageId: string) => Promise + >; +} + +/** + * Log worker performance metrics for monitoring and analysis + * + * This function logs structured performance data to CloudWatch for: + * - E2E latency tracking (from API Gateway to worker completion) + * - Queue wait time analysis + * - Sync/async response time breakdown + * - Error tracking and categorization + * + * @param componentName - Worker component identifier (e.g., 'sr-worker', 'lw-worker') + * @param params - Performance metrics to log + */ +function logWorkerMetrics( + componentName: string, + params: { + correlationId?: string; + command?: string; + totalE2eMs?: number; + workerDurationMs: number; + queueWaitMs?: number; + syncResponseMs?: number; + asyncResponseMs?: number; + success: boolean; + errorType?: string; + errorMessage?: string; + } +) { + logger.info('Performance metrics', { + ...params, + component: componentName, + }); +} + +/** + * Parse and validate the worker message from SQS record + */ +function parseWorkerMessage(record: SQSRecord): WorkerMessage { + return JSON.parse(record.body); +} + +/** + * Create a child logger with correlation context + */ +function createMessageLogger( + correlationId: string | undefined, + messageId: string, + config: WorkerConfig, + message: WorkerMessage +) { + return logger.child(correlationId || messageId, { + component: config.componentName, + command: message.command, + userId: message.user_id, + quadrant: config.quadrantName, + }); +} + +/** + * Find and validate the command handler + */ +function getCommandHandler( + message: WorkerMessage, + config: WorkerConfig +): (message: WorkerMessage, messageId: string) => Promise { + const handler = config.commandHandlers[message.command]; + + if (!handler) { + const availableCommands = Object.keys(config.commandHandlers).join(', '); + throw new Error( + `Unknown command: ${message.command}. Available ${config.quadrantName} commands: ${availableCommands || 'none'}` + ); + } + + return handler; +} + +/** + * Calculate performance metrics + */ +function calculatePerformanceMetrics( + startTime: number, + message: WorkerMessage +) { + const totalDuration = Date.now() - startTime; + const e2eDuration = message.api_gateway_start_time + ? Date.now() - message.api_gateway_start_time + : undefined; + const queueWaitMs = e2eDuration ? Math.max(0, e2eDuration - totalDuration) : undefined; + + return { + totalDuration, + e2eDuration, + queueWaitMs, + }; +} + +/** + * Process a single SQS record + */ +async function processRecord( + record: SQSRecord, + config: WorkerConfig +): Promise<{ success: boolean; itemIdentifier: string }> { + const startTime = Date.now(); + let messageLogger = logger; + let correlationId: string | undefined; + + try { + const message = parseWorkerMessage(record); + correlationId = message.correlation_id; + + messageLogger = createMessageLogger(correlationId, record.messageId, config, message); + + messageLogger.info('Routing command to handler', { + command: message.command, + text: message.text, + user: message.user_name, + messageId: record.messageId, + }); + + const handler = getCommandHandler(message, config); + const handlerResult = await handler(message, record.messageId); + + const { totalDuration, e2eDuration, queueWaitMs } = calculatePerformanceMetrics( + startTime, + message + ); + + logWorkerMetrics(config.componentName, { + correlationId, + command: message.command, + totalE2eMs: e2eDuration, + workerDurationMs: totalDuration, + queueWaitMs, + syncResponseMs: handlerResult.syncResponseMs, + asyncResponseMs: handlerResult.asyncResponseMs, + success: true, + }); + + messageLogger.info('Command processed successfully', { + command: message.command, + duration: totalDuration, + e2eDuration, + }); + + return { success: true, itemIdentifier: record.messageId }; + + } catch (error) { + const duration = Date.now() - startTime; + const err = error as Error; + + messageLogger.error('Failed to process command', err, { + messageId: record.messageId, + duration, + }); + + logWorkerMetrics(config.componentName, { + correlationId, + workerDurationMs: duration, + success: false, + errorType: err.name, + errorMessage: err.message, + }); + + return { success: false, itemIdentifier: record.messageId }; + } +} + +/** + * Creates a unified worker handler function configured for a specific component + * This eliminates code duplication across SR, SW, LR, and LW workers + */ +export function createUnifiedWorkerHandler(config: WorkerConfig) { + return async (event: SQSEvent): Promise => { + logger.info(`${config.componentName.toUpperCase()} unified worker invoked`, { + recordCount: event.Records.length, + quadrant: config.quadrantName, + }); + + const batchItemFailures: { itemIdentifier: string }[] = []; + + for (const record of event.Records) { + const result = await processRecord(record, config); + + if (!result.success) { + batchItemFailures.push({ itemIdentifier: result.itemIdentifier }); + } + } + + logger.info(`${config.componentName.toUpperCase()} worker batch complete`, { + total: event.Records.length, + failed: batchItemFailures.length, + succeeded: event.Records.length - batchItemFailures.length, + }); + + return { batchItemFailures }; + }; +} diff --git a/applications/chatops/slack-bot/src/workers/handlers/build.ts b/applications/chatops/slack-bot/src/workers/handlers/build.ts index 6ff1381..77efad4 100644 --- a/applications/chatops/slack-bot/src/workers/handlers/build.ts +++ b/applications/chatops/slack-bot/src/workers/handlers/build.ts @@ -5,6 +5,7 @@ import { logger } from '../../shared/logger'; import { sendSlackResponse } from '../../shared/slack-client'; import { WorkerMessage } from '../../shared/types'; import { getGitHubToken } from '../../shared/secrets'; +import { HandlerResult } from '../../shared/worker-utils'; interface BuildCommand { component: string; // router, sr, lw, deploy, status, all @@ -101,7 +102,7 @@ async function triggerGitHubWorkflow(params: { * Handle build command * Triggers GitHub Actions workflow to build and upload Lambda artifacts */ -export async function handleBuild(message: WorkerMessage, messageId: string): Promise { +export async function handleBuild(message: WorkerMessage, messageId: string): Promise { const startTime = Date.now(); const messageLogger = logger.child(message.correlation_id || messageId, { @@ -126,6 +127,7 @@ export async function handleBuild(message: WorkerMessage, messageId: string): Pr }); // Send immediate acknowledgment + const syncStartTime = Date.now(); await sendSlackResponse(message.response_url, { response_type: 'in_channel', text: `🔨 Building ${component}...`, @@ -148,6 +150,7 @@ export async function handleBuild(message: WorkerMessage, messageId: string): Pr } ] }); + const syncResponseMs = Date.now() - syncStartTime; // Trigger GitHub Actions workflow await triggerGitHubWorkflow({ @@ -163,6 +166,7 @@ export async function handleBuild(message: WorkerMessage, messageId: string): Pr environment }); + return { syncResponseMs }; } catch (error) { const duration = Date.now() - startTime; @@ -190,6 +194,7 @@ export async function handleBuild(message: WorkerMessage, messageId: string): Pr messageLogger.error('Failed to send error notification', notifyError as Error); } + // Re-throw to let worker handle the error, but we've already sent a user-facing message throw error; } } diff --git a/applications/chatops/slack-bot/src/workers/lr/index.ts b/applications/chatops/slack-bot/src/workers/lr/index.ts index 4e0566c..2f33780 100644 --- a/applications/chatops/slack-bot/src/workers/lr/index.ts +++ b/applications/chatops/slack-bot/src/workers/lr/index.ts @@ -1,160 +1,21 @@ // LR (Long-Read) Unified Worker - Routes all long-read commands to handlers -import { SQSEvent, SQSBatchResponse } from 'aws-lambda'; -import { logger } from '../../shared/logger'; -import { WorkerMessage } from '../../shared/types'; +import { createUnifiedWorkerHandler } from '../../shared/worker-utils'; // Import future long-read handlers here // import { handleAnalyze } from '../handlers/analyze'; // import { handleReport } from '../handlers/report'; -/** - * Log worker performance metrics for monitoring and analysis - */ -function logWorkerMetrics(params: { - correlationId?: string; - command?: string; - totalE2eMs?: number; - workerDurationMs: number; - queueWaitMs?: number; - syncResponseMs?: number; - asyncResponseMs?: number; - success: boolean; - errorType?: string; - errorMessage?: string; -}) { - logger.info('Performance metrics', { - ...params, - component: 'lr-worker', // Add component identifier for filtering - }); -} - -/** - * Handler result with performance metrics - */ -interface HandlerResult { - syncResponseMs?: number; - asyncResponseMs?: number; -} - -/** - * Command handler registry - * Maps command names to their handler functions - */ -const COMMAND_HANDLERS: Record< - string, - (message: WorkerMessage, messageId: string) => Promise -> = { - // Add new long-read commands here (no infrastructure changes needed!) - // '/analyze': handleAnalyze, - // '/report': handleReport, -}; - /** * Unified LR worker handler * Routes commands to appropriate handlers based on command type */ -export async function handler(event: SQSEvent): Promise { - logger.info('LR unified worker invoked', { - recordCount: event.Records.length, - quadrant: 'long-read', - }); - - const batchItemFailures: { itemIdentifier: string }[] = []; - - for (const record of event.Records) { - const startTime = Date.now(); - let messageLogger = logger; - let correlationId: string | undefined; - - try { - const message: WorkerMessage = JSON.parse(record.body); - correlationId = message.correlation_id; - - // Create child logger with correlation ID for request tracing - messageLogger = logger.child(correlationId || record.messageId, { - component: 'lr-worker', - command: message.command, - userId: message.user_id, - quadrant: 'long-read', - }); - - messageLogger.info('Routing command to handler', { - command: message.command, - text: message.text, - user: message.user_name, - messageId: record.messageId, - }); - - // Find handler for command - const handler = COMMAND_HANDLERS[message.command]; - - if (!handler) { - const availableCommands = Object.keys(COMMAND_HANDLERS).join(', '); - const errorMsg = `Unknown command: ${message.command}. Available long-read commands: ${availableCommands || 'none'}`; - - messageLogger.error(errorMsg, new Error('Unknown command'), { - command: message.command, - availableCommands, - }); - - throw new Error(errorMsg); - } - - // Execute handler and get performance metrics - const handlerResult = await handler(message, record.messageId); - - const totalDuration = Date.now() - startTime; - const e2eDuration = message.api_gateway_start_time - ? Date.now() - message.api_gateway_start_time - : undefined; - - // Log structured performance metrics for CloudWatch Insights analysis - logWorkerMetrics({ - correlationId, - command: message.command, - totalE2eMs: e2eDuration, - workerDurationMs: totalDuration, - queueWaitMs: e2eDuration ? Math.max(0, e2eDuration - totalDuration) : undefined, - syncResponseMs: handlerResult.syncResponseMs, - asyncResponseMs: handlerResult.asyncResponseMs, - success: true, - }); - - messageLogger.info('Command processed successfully', { - command: message.command, - duration: totalDuration, - e2eDuration, - }); - - } catch (error) { - const duration = Date.now() - startTime; - const err = error as Error; - - messageLogger.error('Failed to process command', err, { - messageId: record.messageId, - duration, - }); - - // Log performance metrics even for failures - logWorkerMetrics({ - correlationId, - workerDurationMs: duration, - success: false, - errorType: err.name, - errorMessage: err.message, - }); - - // Add to failed items for retry - batchItemFailures.push({ itemIdentifier: record.messageId }); - } - } - - logger.info('LR worker batch complete', { - total: event.Records.length, - failed: batchItemFailures.length, - succeeded: event.Records.length - batchItemFailures.length, - }); - - return { batchItemFailures }; -} +export const handler = createUnifiedWorkerHandler({ + componentName: 'lr-worker', + quadrantName: 'long-read', + commandHandlers: { + // Add new long-read commands here (no infrastructure changes needed!) + // '/analyze': handleAnalyze, + // '/report': handleReport, + }, +}); diff --git a/applications/chatops/slack-bot/src/workers/lw/index.ts b/applications/chatops/slack-bot/src/workers/lw/index.ts index b1e24b5..475355b 100644 --- a/applications/chatops/slack-bot/src/workers/lw/index.ts +++ b/applications/chatops/slack-bot/src/workers/lw/index.ts @@ -1,100 +1,21 @@ // LW (Long-Write) Unified Worker - Routes all long-write commands to handlers -import { SQSEvent, SQSBatchResponse } from 'aws-lambda'; -import { logger } from '../../shared/logger'; -import { WorkerMessage } from '../../shared/types'; +import { createUnifiedWorkerHandler } from '../../shared/worker-utils'; import { handleBuild } from '../handlers/build'; // Import future long-write handlers here // import { handleDeploy } from '../handlers/deploy'; -/** - * Command handler registry - * Maps command names to their handler functions - */ -const COMMAND_HANDLERS: Record Promise> = { - '/build': handleBuild, - // Add new long-write commands here (no infrastructure changes needed!) - // '/deploy': handleDeploy, -}; - /** * Unified LW worker handler * Routes commands to appropriate handlers based on command type */ -export async function handler(event: SQSEvent): Promise { - logger.info('LW unified worker invoked', { - recordCount: event.Records.length, - quadrant: 'long-write', - }); - - const batchItemFailures: { itemIdentifier: string }[] = []; - - for (const record of event.Records) { - const startTime = Date.now(); - let messageLogger = logger; - - try { - const message: WorkerMessage = JSON.parse(record.body); - const correlationId = message.correlation_id; - - // Create child logger with correlation ID for request tracing - messageLogger = logger.child(correlationId || record.messageId, { - component: 'lw-worker', - command: message.command, - userId: message.user_id, - quadrant: 'long-write', - }); - - messageLogger.info('Routing command to handler', { - command: message.command, - text: message.text, - user: message.user_name, - messageId: record.messageId, - }); - - // Find handler for command - const handler = COMMAND_HANDLERS[message.command]; - - if (!handler) { - const availableCommands = Object.keys(COMMAND_HANDLERS).join(', '); - const errorMsg = `Unknown command: ${message.command}. Available long-write commands: ${availableCommands}`; - - messageLogger.error(errorMsg, new Error('Unknown command'), { - command: message.command, - availableCommands, - }); - - throw new Error(errorMsg); - } - - // Execute handler - await handler(message, record.messageId); - - const duration = Date.now() - startTime; - messageLogger.info('Command processed successfully', { - command: message.command, - duration, - }); - - } catch (error) { - const duration = Date.now() - startTime; - - messageLogger.error('Failed to process command', error as Error, { - messageId: record.messageId, - duration, - }); - - // Add to failed items for retry - batchItemFailures.push({ itemIdentifier: record.messageId }); - } - } - - logger.info('LW worker batch complete', { - total: event.Records.length, - failed: batchItemFailures.length, - succeeded: event.Records.length - batchItemFailures.length, - }); - - return { batchItemFailures }; -} +export const handler = createUnifiedWorkerHandler({ + componentName: 'lw-worker', + quadrantName: 'long-write', + commandHandlers: { + '/build': handleBuild, + // Add new long-write commands here (no infrastructure changes needed!) + // '/deploy': handleDeploy, + }, +}); diff --git a/applications/chatops/slack-bot/src/workers/sr/index.ts b/applications/chatops/slack-bot/src/workers/sr/index.ts index 63269dc..802e7e7 100644 --- a/applications/chatops/slack-bot/src/workers/sr/index.ts +++ b/applications/chatops/slack-bot/src/workers/sr/index.ts @@ -1,162 +1,23 @@ // SR (Short-Read) Unified Worker - Routes all short-read commands to handlers -import { SQSEvent, SQSBatchResponse } from 'aws-lambda'; -import { logger } from '../../shared/logger'; -import { WorkerMessage } from '../../shared/types'; +import { createUnifiedWorkerHandler } from '../../shared/worker-utils'; import { handleEcho } from '../handlers/echo'; // Import future short-read handlers here // import { handleStatus } from '../handlers/status'; // import { handleHelp } from '../handlers/help'; -/** - * Log worker performance metrics for monitoring and analysis - */ -function logWorkerMetrics(params: { - correlationId?: string; - command?: string; - totalE2eMs?: number; - workerDurationMs: number; - queueWaitMs?: number; - syncResponseMs?: number; - asyncResponseMs?: number; - success: boolean; - errorType?: string; - errorMessage?: string; -}) { - logger.info('Performance metrics', { - ...params, - component: 'sr-worker', // Add component identifier for filtering - }); -} - -/** - * Handler result with performance metrics - */ -interface HandlerResult { - syncResponseMs?: number; - asyncResponseMs?: number; -} - -/** - * Command handler registry - * Maps command names to their handler functions - */ -const COMMAND_HANDLERS: Record< - string, - (message: WorkerMessage, messageId: string) => Promise -> = { - '/echo': handleEcho, - // Add new short-read commands here (no infrastructure changes needed!) - // '/status': handleStatus, - // '/help': handleHelp, -}; - /** * Unified SR worker handler * Routes commands to appropriate handlers based on command type */ -export async function handler(event: SQSEvent): Promise { - logger.info('SR unified worker invoked', { - recordCount: event.Records.length, - quadrant: 'short-read', - }); - - const batchItemFailures: { itemIdentifier: string }[] = []; - - for (const record of event.Records) { - const startTime = Date.now(); - let messageLogger = logger; - let correlationId: string | undefined; - - try { - const message: WorkerMessage = JSON.parse(record.body); - correlationId = message.correlation_id; - - // Create child logger with correlation ID for request tracing - messageLogger = logger.child(correlationId || record.messageId, { - component: 'sr-worker', - command: message.command, - userId: message.user_id, - quadrant: 'short-read', - }); - - messageLogger.info('Routing command to handler', { - command: message.command, - text: message.text, - user: message.user_name, - messageId: record.messageId, - }); - - // Find handler for command - const handler = COMMAND_HANDLERS[message.command]; - - if (!handler) { - const availableCommands = Object.keys(COMMAND_HANDLERS).join(', '); - const errorMsg = `Unknown command: ${message.command}. Available short-read commands: ${availableCommands}`; - - messageLogger.error(errorMsg, new Error('Unknown command'), { - command: message.command, - availableCommands, - }); - - throw new Error(errorMsg); - } - - // Execute handler and get performance metrics - const handlerResult = await handler(message, record.messageId); - - const totalDuration = Date.now() - startTime; - const e2eDuration = message.api_gateway_start_time - ? Date.now() - message.api_gateway_start_time - : undefined; - - // Log structured performance metrics for CloudWatch Insights analysis - logWorkerMetrics({ - correlationId, - command: message.command, - totalE2eMs: e2eDuration, - workerDurationMs: totalDuration, - queueWaitMs: e2eDuration ? Math.max(0, e2eDuration - totalDuration) : undefined, - syncResponseMs: handlerResult.syncResponseMs, - asyncResponseMs: handlerResult.asyncResponseMs, - success: true, - }); - - messageLogger.info('Command processed successfully', { - command: message.command, - duration: totalDuration, - e2eDuration, - }); - - } catch (error) { - const duration = Date.now() - startTime; - const err = error as Error; - - messageLogger.error('Failed to process command', err, { - messageId: record.messageId, - duration, - }); - - // Log performance metrics even for failures - logWorkerMetrics({ - correlationId, - workerDurationMs: duration, - success: false, - errorType: err.name, - errorMessage: err.message, - }); - - // Add to failed items for retry - batchItemFailures.push({ itemIdentifier: record.messageId }); - } - } - - logger.info('SR worker batch complete', { - total: event.Records.length, - failed: batchItemFailures.length, - succeeded: event.Records.length - batchItemFailures.length, - }); - - return { batchItemFailures }; -} +export const handler = createUnifiedWorkerHandler({ + componentName: 'sr-worker', + quadrantName: 'short-read', + commandHandlers: { + '/echo': handleEcho, + // Add new short-read commands here (no infrastructure changes needed!) + // '/status': handleStatus, + // '/help': handleHelp, + }, +}); diff --git a/applications/chatops/slack-bot/src/workers/sw/index.ts b/applications/chatops/slack-bot/src/workers/sw/index.ts index 82830e3..2ebc339 100644 --- a/applications/chatops/slack-bot/src/workers/sw/index.ts +++ b/applications/chatops/slack-bot/src/workers/sw/index.ts @@ -1,160 +1,21 @@ // SW (Short-Write) Unified Worker - Routes all short-write commands to handlers -import { SQSEvent, SQSBatchResponse } from 'aws-lambda'; -import { logger } from '../../shared/logger'; -import { WorkerMessage } from '../../shared/types'; +import { createUnifiedWorkerHandler } from '../../shared/worker-utils'; // Import future short-write handlers here // import { handleScale } from '../handlers/scale'; // import { handleRestart } from '../handlers/restart'; -/** - * Log worker performance metrics for monitoring and analysis - */ -function logWorkerMetrics(params: { - correlationId?: string; - command?: string; - totalE2eMs?: number; - workerDurationMs: number; - queueWaitMs?: number; - syncResponseMs?: number; - asyncResponseMs?: number; - success: boolean; - errorType?: string; - errorMessage?: string; -}) { - logger.info('Performance metrics', { - ...params, - component: 'sw-worker', // Add component identifier for filtering - }); -} - -/** - * Handler result with performance metrics - */ -interface HandlerResult { - syncResponseMs?: number; - asyncResponseMs?: number; -} - -/** - * Command handler registry - * Maps command names to their handler functions - */ -const COMMAND_HANDLERS: Record< - string, - (message: WorkerMessage, messageId: string) => Promise -> = { - // Add new short-write commands here (no infrastructure changes needed!) - // '/scale': handleScale, - // '/restart': handleRestart, -}; - /** * Unified SW worker handler * Routes commands to appropriate handlers based on command type */ -export async function handler(event: SQSEvent): Promise { - logger.info('SW unified worker invoked', { - recordCount: event.Records.length, - quadrant: 'short-write', - }); - - const batchItemFailures: { itemIdentifier: string }[] = []; - - for (const record of event.Records) { - const startTime = Date.now(); - let messageLogger = logger; - let correlationId: string | undefined; - - try { - const message: WorkerMessage = JSON.parse(record.body); - correlationId = message.correlation_id; - - // Create child logger with correlation ID for request tracing - messageLogger = logger.child(correlationId || record.messageId, { - component: 'sw-worker', - command: message.command, - userId: message.user_id, - quadrant: 'short-write', - }); - - messageLogger.info('Routing command to handler', { - command: message.command, - text: message.text, - user: message.user_name, - messageId: record.messageId, - }); - - // Find handler for command - const handler = COMMAND_HANDLERS[message.command]; - - if (!handler) { - const availableCommands = Object.keys(COMMAND_HANDLERS).join(', '); - const errorMsg = `Unknown command: ${message.command}. Available short-write commands: ${availableCommands || 'none'}`; - - messageLogger.error(errorMsg, new Error('Unknown command'), { - command: message.command, - availableCommands, - }); - - throw new Error(errorMsg); - } - - // Execute handler and get performance metrics - const handlerResult = await handler(message, record.messageId); - - const totalDuration = Date.now() - startTime; - const e2eDuration = message.api_gateway_start_time - ? Date.now() - message.api_gateway_start_time - : undefined; - - // Log structured performance metrics for CloudWatch Insights analysis - logWorkerMetrics({ - correlationId, - command: message.command, - totalE2eMs: e2eDuration, - workerDurationMs: totalDuration, - queueWaitMs: e2eDuration ? Math.max(0, e2eDuration - totalDuration) : undefined, - syncResponseMs: handlerResult.syncResponseMs, - asyncResponseMs: handlerResult.asyncResponseMs, - success: true, - }); - - messageLogger.info('Command processed successfully', { - command: message.command, - duration: totalDuration, - e2eDuration, - }); - - } catch (error) { - const duration = Date.now() - startTime; - const err = error as Error; - - messageLogger.error('Failed to process command', err, { - messageId: record.messageId, - duration, - }); - - // Log performance metrics even for failures - logWorkerMetrics({ - correlationId, - workerDurationMs: duration, - success: false, - errorType: err.name, - errorMessage: err.message, - }); - - // Add to failed items for retry - batchItemFailures.push({ itemIdentifier: record.messageId }); - } - } - - logger.info('SW worker batch complete', { - total: event.Records.length, - failed: batchItemFailures.length, - succeeded: event.Records.length - batchItemFailures.length, - }); - - return { batchItemFailures }; -} +export const handler = createUnifiedWorkerHandler({ + componentName: 'sw-worker', + quadrantName: 'short-write', + commandHandlers: { + // Add new short-write commands here (no infrastructure changes needed!) + // '/scale': handleScale, + // '/restart': handleRestart, + }, +});