Found while fixing #8628 (db-job-adapter.timeout.test.ts). That card's mechanism is not confined to the one file it named: the sibling suite has the same shape in four cases, and the PM scoped the sweep out of #8628 deliberately, so this is the follow-up card.
The mechanism (identical to #8628)
CronJobAdapter.schedule() constructs a real croner job. Nothing suppresses that registration's own schedule, so on an every-minute expression croner fires it independently of the explicit trigger(). Any case that then asserts an exact call/execution count reds whenever the run straddles :00.
This was reproduced on demand while fixing #8628 — with only Date faked (real timers, croner's real scheduling path) and the registration positioned 2/5/10/15 ms before a minute boundary, the #8628 case produced 2 rows instead of 1, reproducing the CI signature exactly.
Cases that will red on a boundary
All in packages/services/service-job/src/cron-job-adapter.test.ts, all registering a real CronJobAdapter on '* * * * *':
| line |
case |
exact-count assertion |
| 36 |
records executions |
expect(execs).toHaveLength(1) (L41) |
| 74 |
retries a failing handler per retryPolicy and succeeds |
expect(calls).toBe(3) (L87), expect(execs).toHaveLength(1) (L89) |
| 93 |
exhausts retries and records the last failure |
expect(calls).toBe(3) (L103) |
| 109 |
does not retry when no retryPolicy is given (legacy behavior) |
expect(calls).toBe(1) (L117) |
Same mechanism, far rarer window
These use a daily expression, so the straddle window is one instant per day rather than one per minute — same defect, roughly 1440x less likely to be observed:
cron-job-adapter.test.ts:11 — '0 0 * * *', expect(calls).toBe(1) (L18)
cron-job-adapter.test.ts:208 — DAILY, expect(calls).toBe(1) (L225)
db-job-adapter.test.ts:195 — DAILY ('0 8 * * *'), expect(fired).toEqual(['new-kernel']) (L215)
Checked and NOT hazardous
db-job-adapter.degraded-outcome.test.ts:237 — registers a real cron on '* * * * *' but asserts only [0].status, and a self-fire runs the same handler to the same verdict. Latent, not currently failing; it would become a defect the moment an exact-count assertion is added.
interval-job-adapter.test.ts — its '* * * * *' usages are inert: IntervalJobAdapter stores cron schedules but never executes them (interval-job-adapter.ts:81-89).
db-job-adapter.timeout.test.ts first describe block — DbJobAdapter with no injected cron only warns and never registers a real croner (db-job-adapter.ts:106-109).
Suggested direction (not prescriptive)
The route taken in #8628 applies unchanged and needs no production seam: replace the fixture expression with one croner parses but can never fire — '0 0 30 2 *' (February 30th) has nextRun() === null. Note '0 0 29 2 *' is NOT a substitute: croner resolves Feb 29 to the next leap year (measured: 2028-02-29T00:00:00.000Z).
⛔ Do not loosen the exact-count assertions to "at least one" — they are the only thing that can catch a genuine double-scheduling regression, which is what these suites exist to catch.
Worth carrying over from #8628: that fix pinned the property with assertions (nextRun() must be null for the fixture, and for the croner job the case actually registers) so a later tidy-up back to an every-minute spelling reds immediately and locally instead of intermittently in CI.
Why it matters
Same as #8628: a wall-clock-positional flake reds Test Core for PRs with no dependency path to service-job, and a red shared merge queue taxes every lane at once.
Found while fixing #8628 (
db-job-adapter.timeout.test.ts). That card's mechanism is not confined to the one file it named: the sibling suite has the same shape in four cases, and the PM scoped the sweep out of #8628 deliberately, so this is the follow-up card.The mechanism (identical to #8628)
CronJobAdapter.schedule()constructs a real croner job. Nothing suppresses that registration's own schedule, so on an every-minute expression croner fires it independently of the explicittrigger(). Any case that then asserts an exact call/execution count reds whenever the run straddles:00.This was reproduced on demand while fixing #8628 — with only
Datefaked (real timers, croner's real scheduling path) and the registration positioned 2/5/10/15 ms before a minute boundary, the #8628 case produced 2 rows instead of 1, reproducing the CI signature exactly.Cases that will red on a boundary
All in
packages/services/service-job/src/cron-job-adapter.test.ts, all registering a realCronJobAdapteron'* * * * *':records executionsexpect(execs).toHaveLength(1)(L41)retries a failing handler per retryPolicy and succeedsexpect(calls).toBe(3)(L87),expect(execs).toHaveLength(1)(L89)exhausts retries and records the last failureexpect(calls).toBe(3)(L103)does not retry when no retryPolicy is given (legacy behavior)expect(calls).toBe(1)(L117)Same mechanism, far rarer window
These use a daily expression, so the straddle window is one instant per day rather than one per minute — same defect, roughly 1440x less likely to be observed:
cron-job-adapter.test.ts:11—'0 0 * * *',expect(calls).toBe(1)(L18)cron-job-adapter.test.ts:208—DAILY,expect(calls).toBe(1)(L225)db-job-adapter.test.ts:195—DAILY('0 8 * * *'),expect(fired).toEqual(['new-kernel'])(L215)Checked and NOT hazardous
db-job-adapter.degraded-outcome.test.ts:237— registers a real cron on'* * * * *'but asserts only[0].status, and a self-fire runs the same handler to the same verdict. Latent, not currently failing; it would become a defect the moment an exact-count assertion is added.interval-job-adapter.test.ts— its'* * * * *'usages are inert:IntervalJobAdapterstores cron schedules but never executes them (interval-job-adapter.ts:81-89).db-job-adapter.timeout.test.tsfirst describe block —DbJobAdapterwith no injected cron only warns and never registers a real croner (db-job-adapter.ts:106-109).Suggested direction (not prescriptive)
The route taken in #8628 applies unchanged and needs no production seam: replace the fixture expression with one croner parses but can never fire —
'0 0 30 2 *'(February 30th) hasnextRun() === null. Note'0 0 29 2 *'is NOT a substitute: croner resolves Feb 29 to the next leap year (measured:2028-02-29T00:00:00.000Z).⛔ Do not loosen the exact-count assertions to "at least one" — they are the only thing that can catch a genuine double-scheduling regression, which is what these suites exist to catch.
Worth carrying over from #8628: that fix pinned the property with assertions (
nextRun()must be null for the fixture, and for the croner job the case actually registers) so a later tidy-up back to an every-minute spelling reds immediately and locally instead of intermittently in CI.Why it matters
Same as #8628: a wall-clock-positional flake reds
Test Corefor PRs with no dependency path toservice-job, and a red shared merge queue taxes every lane at once.