fix(schedule): close fixed-rate review gaps

This commit is contained in:
pku-xht
2026-08-06 21:39:01 +08:00
committed by Tianyi Cui
parent 93d239b5d7
commit 49a6e4ddb9
13 changed files with 133 additions and 17 deletions
@@ -316,6 +316,18 @@ describe('fixed-rate records and durable progression', () => {
.toThrow(ScheduleInputError)
expect(() => createEveryScheduleRecord(ScheduleId('schedule-every'), 'x', 300, Number.NaN))
.toThrow(ScheduleInputError)
try {
createEveryScheduleRecord(
ScheduleId('schedule-every'),
'x',
300,
Date.parse('0000-12-31T23:50:00.000Z'),
)
throw new Error('expected every lower-bound failure')
} catch (error: unknown) {
expect(error).toBeInstanceOf(ScheduleInputError)
expect((error as ScheduleInputError).code).toBe('time_out_of_range')
}
})
it('selects the latest due occurrence and first strictly future anchor point', () => {
@@ -412,6 +424,37 @@ describe('fixed-rate records and durable progression', () => {
])).toThrow(/at least 300 seconds apart/)
})
it('terminates every record when the shared gate has no four-digit-year admission', () => {
const folded = foldScheduleEvents([
scheduleEvent(everyCreateData(
'schedule-final',
'final batch',
'9999-12-31T23:55:00.000Z',
), 0),
scheduleEvent(everyCreateData(
'schedule-staggered',
'staggered target',
'9999-12-31T23:58:00.000Z',
), 1),
scheduleEvent(createData(
'schedule-once',
'one shot survives',
'9999-12-31T23:59:00.000Z',
), 2),
scheduleEvent({
version: 1,
operation: 'dispatch',
id: 'schedule-final',
acceptedAt: '9999-12-31T23:57:30.000Z',
}, 3),
])
expect(folded).toEqual({
active: [expect.objectContaining({ id: 'schedule-once', kind: 'after' })],
seenIds: ['schedule-final', 'schedule-staggered', 'schedule-once'],
lastRecurringAcceptedAt: '9999-12-31T23:57:30.000Z',
})
})
it('derives each recurring receipt and renders one escaped batch payload', () => {
const events = [
scheduleEvent(everyCreateData(), 0),
@@ -348,6 +348,37 @@ describe('Schedule timer and admission runtime', () => {
await owner.dispose()
})
it('waits for the recurring gate instead of staggered recurring targets', async () => {
const test = await harness()
appendEvery(test, 'schedule-overdue', 300, Date.parse('2026-08-05T11:53:00.000Z'), 'overdue')
const owner = ownerFor(test)
owner.start()
await settle()
expect(test.followed).toHaveLength(1)
appendEvery(test, 'schedule-staggered', 300, Date.parse('2026-08-05T11:59:00.000Z'), 'staggered')
owner.requestDrive()
await settle()
await vi.advanceTimersByTimeAsync(180_000)
await settle()
const flushesAtFirstDue = test.controls.flushCount
expect(test.followed).toHaveLength(1)
await vi.advanceTimersByTimeAsync(60_000)
await settle()
expect(test.controls.flushCount).toBe(flushesAtFirstDue)
await vi.advanceTimersByTimeAsync(60_000)
await settle()
expect(test.followed).toHaveLength(2)
const batch = test.followed[1]?.content[0]
if (batch?.type !== 'text') throw new Error('expected recurring batch text')
expect(batch.text).toContain('"schedule_id":"schedule-overdue"')
expect(batch.text).toContain('"schedule_id":"schedule-staggered"')
await owner.dispose()
})
it('rechecks the wall clock after claiming maintenance before queuing', async () => {
const test = await harness()
appendAfter(test, 'schedule-1', 1, Date.now() - 1_000)