Implemented optimistic locking with version field and SELECT FOR UPDATE
transactions to prevent data corruption from concurrent job status updates.
Changes:
- Added version field to RunnerJob schema for optimistic locking
- Created migration 20260202_add_runner_job_version_for_concurrency
- Implemented ConcurrentUpdateException for conflict detection
- Updated RunnerJobsService methods with optimistic locking:
* updateStatus() - with version checking and retry logic
* updateProgress() - with version checking and retry logic
* cancel() - with version checking and retry logic
- Updated CoordinatorIntegrationService with SELECT FOR UPDATE:
* updateJobStatus() - transaction with row locking
* completeJob() - transaction with row locking
* failJob() - transaction with row locking
* updateJobProgress() - optimistic locking
- Added retry mechanism (3 attempts) with exponential backoff
- Added comprehensive concurrency tests (10 tests, all passing)
- Updated existing test mocks to support updateMany
Test Results:
- All 10 concurrency tests passing ✓
- Tests cover concurrent status updates, progress updates, completions,
cancellations, retry logic, and exponential backoff
This fix prevents race conditions that could cause:
- Lost job results (double completion)
- Lost progress updates
- Invalid status transitions
- Data corruption under concurrent access
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add composite index [jobId, timestamp] to improve query performance
for the most common job_events access patterns.
Changes:
- Add @@index([jobId, timestamp]) to JobEvent model in schema.prisma
- Create migration 20260202122655_add_job_events_composite_index
- Add performance tests to validate index effectiveness
- Document index design rationale in scratchpad
- Fix lint errors in api-key.guard, herald.service, runner-jobs.service
Rationale:
The composite index [jobId, timestamp] optimizes the dominant query
pattern used across all services:
- JobEventsService.getEventsByJobId (WHERE jobId, ORDER BY timestamp)
- RunnerJobsService.streamEvents (WHERE jobId + timestamp range)
- RunnerJobsService.findOne (implicit jobId filter + timestamp order)
This index provides:
- Fast filtering by jobId (highly selective)
- Efficient timestamp-based ordering
- Optimal support for timestamp range queries
- Backward compatibility with jobId-only queries
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements runner-jobs module for job lifecycle management and queue submission.
Changes:
- Created RunnerJobsModule with service, controller, and DTOs
- Implemented job creation with BullMQ queue submission
- Implemented job listing with filters (status, type, agentTaskId)
- Implemented job detail retrieval with steps and events
- Implemented cancel operation for pending/queued jobs
- Implemented retry operation for failed jobs
- Added comprehensive unit tests (24 tests, 100% coverage)
- Integrated with BullMQ for async job processing
- Integrated with Prisma for database operations
- Followed existing CRUD patterns from tasks/events modules
API Endpoints:
- POST /runner-jobs - Create and queue a new job
- GET /runner-jobs - List jobs (with filters)
- GET /runner-jobs/:id - Get job details
- POST /runner-jobs/:id/cancel - Cancel a running job
- POST /runner-jobs/:id/retry - Retry a failed job
Quality Gates:
- Typecheck: ✅ PASSED
- Lint: ✅ PASSED
- Build: ✅ PASSED
- Tests: ✅ PASSED (24/24 tests)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>