{
"issueNumber": 444,
"branchName": "feat/444-migrate-eventemitter2-to-eventbus",
"generatedAt": "2026-04-13T12:00:00Z",
"stories": [
{
"id": "S1",
"title": "Round 1: Migrate task.updated — replace 4 emits in TaskStateService, 2 @OnEvent listeners in WsGatewayService and MetricsCache, update 3 modules and specs",
"priority": 1,
"dependsOn": [],
"acceptanceCriteria": [
"TaskStateService uses EventBusService.emit('task.updated') with try/catch at all 4 call sites",
"WsGatewayService.onTaskUpdated registered via eventBus.on() in onModuleInit",
"MetricsCache.invalidate registered via eventBus.on() in onModuleInit",
"npm run build && npm run test pass"
],
"passes": false,
"completedAt": null
},
{
"id": "S2",
"title": "Round 2: Migrate task.stuck — replace 1 emit in WatchdogService, 1 @OnEvent in WsGatewayService, update module and specs",
"priority": 2,
"dependsOn": [
"S1"
],
"acceptanceCriteria": [
"WatchdogService uses EventBusService.emit('task.stuck') with try/catch",
"WsGatewayService.onTaskStuck registered via eventBus.on() in onModuleInit",
"npm run build && npm run test pass"
],
"passes": false,
"completedAt": null
},
{
"id": "S3",
"title": "Round 3: Migrate artefacts.synced — replace 2 emits in PhaseRouterService, 1 @OnEvent in WsGatewayService, update module and specs",
"priority": 3,
"dependsOn": [
"S2"
],
"acceptanceCriteria": [
"PhaseRouterService uses EventBusService.emit('artefacts.synced') with try/catch at both call sites",
"WsGatewayService.onArtefactsSynced registered via eventBus.on() in onModuleInit",
"npm run build && npm run test pass"
],
"passes": false,
"completedAt": null
},
{
"id": "S4",
"title": "Round 4: Migrate job.completed and job.failed — replace 2 emits in SqliteJobQueue, 2 @OnEvent in WsGatewayService, update module and specs",
"priority": 4,
"dependsOn": [
"S3"
],
"acceptanceCriteria": [
"SqliteJobQueue uses EventBusService.emit() for job.completed and job.failed with try/catch",
"WsGatewayService listeners registered via eventBus.on() in onModuleInit",
"npm run build && npm run test pass"
],
"passes": false,
"completedAt": null
},
{
"id": "S5",
"title": "Round 5: Migrate job.created — replace self-trigger with direct processNext(), emit to Redis, remove @OnEvent, update specs",
"priority": 5,
"dependsOn": [
"S4"
],
"acceptanceCriteria": [
"SqliteJobQueue calls void this.processNext() directly instead of emitting job.created",
"Redis emit added for future multi-instance support",
"@OnEvent('job.created') decorator removed from processNext()",
"No EventEmitter2 import remains in SqliteJobQueue",
"Job queue processes pending jobs end-to-end",
"npm run build && npm run test pass"
],
"passes": false,
"completedAt": null
},
{
"id": "S6",
"title": "Round 6: Remove @nestjs/event-emitter — delete EventEmitterModule from all modules, uninstall package, verify clean removal",
"priority": 6,
"dependsOn": [
"S5"
],
"acceptanceCriteria": [
"No EventEmitter2 or OnEvent imports in the codebase",
"No EventEmitterModule.forRoot() calls in any module",
"@nestjs/event-emitter removed from package.json",
"npm run build && npm run test pass"
],
"passes": false,
"completedAt": null
}
]
}