Cron connector
The cron connector is Harn’s in-process scheduler for time-triggered work. It
implements the shared Connector trait, evaluates cron expressions in an IANA
time zone, and persists the last-fired boundary for each trigger in the shared
EventLog.
Manifest shape
Cron triggers live under [[triggers]] and keep their schedule-specific
settings inline with the rest of the trigger manifest entry:
[[triggers]]
id = "daily-digest"
kind = "cron"
provider = "cron"
match = { events = ["cron.tick"] }
handler = "worker://digest-queue"
schedule = "0 9 * * *"
timezone = "America/New_York"
catchup_mode = "skip"
Supported fields:
schedule: five-field cron expression parsed bycronertimezone: IANA time zone name such asAmerica/New_Yorkcatchup_mode:skip(default),all, orlatest
Offset literals such as +02:00 and UTC-5 are rejected at manifest-load
time. Use a named zone instead so DST transitions can be evaluated correctly.
DST semantics
The cron connector intentionally favors stable wall-clock semantics over trying to synthesize impossible local times:
- Fall-back overlaps fire a matching wall-clock slot once, even though the local hour appears twice.
- Spring-forward gaps do not invent a firing for a missing local time. A
schedule like
0 2 * * *simply does not fire on the DST transition day when02:00is skipped. - Named zones continue to track the intended local wall time across standard and
daylight time. Midnight in
America/New_Yorkfires at05:00Zin winter and04:00Zin summer.
Durable state
Every successful firing appends the latest scheduled boundary for that trigger
to the EventLog topic connectors.cron.state. On restart, the connector reloads
the latest entry for each trigger_id and uses it to determine whether any
ticks were missed while the orchestrator was down.
The current implementation persists:
trigger_idlast_fired_at
This keeps recovery append-only and backend-agnostic across the memory, file, and SQLite EventLog implementations.
Catch-up modes
Catch-up behavior is evaluated from the persisted last_fired_at boundary to
the connector’s current clock on activation.
skip: drop missed ticks and resume from “now”all: replay every missed scheduled tick in chronological orderlatest: replay only the most recent missed scheduled tick
Catch-up reuses the original scheduled boundary as occurred_at, so downstream
consumers can distinguish between when a job was due and when the process
actually resumed.
Event output
Until the broader trigger dispatcher lands, cron firings are emitted as
serialized TriggerEvent envelopes on the EventLog topic connectors.cron.tick
with provider cron, kind tick, and a CronEventPayload that includes:
cron_idscheduletick_atraw.catchupraw.timezone
This keeps the connector testable today and preserves a normalized event shape for the follow-up dispatcher work.