scheduleSpec

fun scheduleSpec(handlers: List<ProcessorBinding<*>>, start: Instant = DEFAULT_START, partitions: Int = 1, block: ScheduleSpec.() -> Unit)

Specifies a saga's @OnSchedule handlers against the real job aggregate: enable an instance of a job, move the time on, and check the ticks the handlers got and the commands they derived. handlers are the saga's, from the function the compiler generated for it (<processor>Handlers), which names the bounded context their jobs run in; its log has partitions partitions (for a SHARDED job's instances). Their calls are answered (ScheduleSpec.answerCalls) and their reads served (ScheduleSpec.givenQueries) as a saga specification's are, and a failing handling is retried or parked by its job's @Retry.

scheduleSpec(billingSagaHandlers(), start = Instant.parse("2026-01-31T12:00:00Z")) {
enable("monthly-invoice", tenantId = "acme", rule = JobRule(zone = "Asia/Shanghai"))
advanceTime(1.days) {
expectCommands(IssueInvoices("acme"))
}
ticks.last().assert().isEqualTo(ScheduleTick("acme", "monthly-invoice", slot = 1, due = ...))
advanceTime(90.days) { ticks.last().skipped.assert().isEqualTo(2) }
}

Time stands still at start until advanceTime moves it; every instance of the specification shares it. A slot runs as the runtime runs it: its instance commits wow.Ticked, and the handlers of its job get the tick. Moving past several slots at once sends only the latest, the others counted as skipped, unless the job's rule catches up. whenTick hands a tick over directly, without the job aggregate.