JobStore
The storage contract. Implementations plus the conformance kit (klokka-tck) ARE the portability story: any store passing the TCK is a valid backend.
Contract highlights, enforced by the TCK:
All time comparisons use the STORE's clock (database time), never the caller's.
claim must be atomic across concurrent callers: a row is claimed by exactly one worker per lease window. In SQL this is a single statement (SKIP LOCKED or CAS), never an in-process check.
claim scans queues in the given list order: declared order is priority, uniformly across all stores.
claim returns only rows whose kind is in the caller's
kinds; rows of any other kind are left untouched, so a worker never claims a job it cannot run.enqueue treats a uniqueKey conflict with a non-terminal existing job as idempotent success and returns the existing job's id.
transition is a compare-and-set: it returns false and changes nothing when the stored state does not equal from or the fence is stale.
Expired leases make jobs claimable again (at-least-once semantics).
Schedule rows follow the same pattern as job rows: dueSchedules is an atomic fenced claim by the store's clock, filtered to the ids the caller holds definitions for, and completeFire is a fenced compare-and-set that applies a fire (runs plus next fire time) in one transaction. Stores never evaluate schedule expressions.
Inheritors
Functions
Claims up to limit due jobs for worker under a lease of lease, scanning queues in list order (declared order is priority) and returning only jobs whose kind is in kinds. Rows of any other kind are left untouched: not claimed, not transitioned, not counted against limit. An empty kinds returns an empty list. The filter is what lets a worker that does not bind a kind (an older version mid-rollout, a fleet that drains a shared queue for a subset of kinds) leave that job for a worker that does.
Applies one claimed fire ATOMICALLY: inserts runs with enqueue semantics (including the uniqueKey rule), advances the schedule's nextFireAt to nextFireAt (null makes the schedule dormant), sets lastFiredAt to the store's clock, and releases the lease, all in one transaction. Compare-and-set on fence: returns null and changes NOTHING, inserts no runs, when the stored fence differs. On success returns the run ids in runs order, existing ids on uniqueKey dedup, exactly like enqueue. An empty runs is a legal completion (a misfire handled with Skip, for example) and returns an empty list.
Claims up to limit due schedules for worker under a lease of lease, soonest next-fire first. A schedule is due when its stored nextFireAt is non-null and not in the future by the STORE's own clock; no SPI method accepts caller-supplied time for comparisons, because the caller's clock is never authoritative. Only rows whose id is in ids are returned; other rows are left untouched, so a node only ever fires schedules it holds a definition for (the schedule analogue of claim's kind filter). An empty ids returns an empty list.
Persists jobs and returns their ids in the same order. A job whose NewJob.runAt is not in the future by the store's clock is stored as JobState.Enqueued (due, visible to workers); a future NewJob.runAt is stored as JobState.Scheduled (waiting for its time). Promoting Scheduled to Enqueued when that time arrives is the store's concern; the runtime never depends on it, because claim decides due-ness by time, not state. A uniqueKey conflict with a non-terminal job returns that job's id (see interface KDoc).
Extends the lease of running jobs. Ignores ids whose lease this worker no longer holds, including the worker's OWN ids whose lease has already expired: an expired lease is not held, even by its former owner. Reviving an expired job goes through claim, never through heartbeat.
Deletes terminal runs past their retention. Runs as a built-in recurring job.
Registers or reconciles code-defined schedules, once per runtime start. Per spec: an unknown ScheduleSpec.id inserts the row with nextFireAt = fireAt; a known id with an EQUAL fingerprint updates nothing (timing state is preserved across restarts); a known id with a DIFFERENT fingerprint overwrites kind, fingerprint and description, sets nextFireAt = fireAt, clears any lease, AND bumps the fence: a claim taken under the old definition must not be completable, because its runs and next fire time were computed from a definition that no longer exists. Last writer wins; concurrent upserts from nodes running different code versions are expected during rolling deploys. Upsert never deletes: a schedule removed from code keeps its row and simply stops being fired, because no node passes its id to dueSchedules.