- Simulates two concurrent getSnapshot calls with no valid snapshot
- Documents the known race: both trigger buildSnapshot, second update overwrites first
- Verifies both calls complete without error (accepted-risk behavior)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- convertQuizCandidates: batch findMany all stems before loop, dedup in-memory
- convertFlashcardCandidates: batch findMany all fronts before loop, dedup in-memory
- 50 items now = 1 query instead of 50
- Update tests: mock findMany instead of per-item findFirst
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add static counters: persistResultFailures, notifyFailures
- Replace .catch(() => {}) with logger.error + counter increment
- Add error-path unit tests for both counter increments
- Reset counters in beforeEach for test isolation
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1. heartbeat: locked→running transition on first heartbeat
2. submitResult: validate job is locked/running before accepting
3. submitFailure: validate job is locked/running before accepting
4. resolveCredentialForJob: update lastUsedAt on credential
5. pollJobs: filter by capabilities (snapshotVersion + outputSchemaVersion)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>