runledger_postgres/jobs/types/schedules.rs
1use chrono::{DateTime, Utc};
2use runledger_core::jobs::{JobType, JobTypeName};
3use serde_json::Value;
4use sqlx::types::Uuid;
5
6/// Maximum accepted schedule jitter, in seconds.
7///
8/// The scheduler treats jitter as a deterministic spread applied to future fire
9/// cursors, and the persistence layer rejects larger values.
10pub const JOB_SCHEDULE_MAX_JITTER_SECONDS: i32 = 86_400;
11
12#[derive(Clone, Debug)]
13pub struct JobScheduleRecord {
14 /// Stable schedule row identifier.
15 pub id: Uuid,
16 /// Unique schedule name.
17 pub name: String,
18 /// Job type enqueued whenever the schedule fires.
19 pub job_type: JobTypeName,
20 /// Optional organization scope copied into jobs created by this schedule.
21 pub organization_id: Option<Uuid>,
22 /// JSON payload template copied into each scheduled job before runtime
23 /// schedule metadata is merged.
24 pub payload_template: Value,
25 /// UTC cron expression used by the runtime scheduler.
26 pub cron_expr: String,
27 /// Whether the runtime scheduler may claim this schedule.
28 ///
29 /// Schedule upserts preserve this value for existing rows; use
30 /// `set_job_schedule_active` to pause or resume a schedule intentionally.
31 pub is_active: bool,
32 /// Maximum deterministic jitter, in seconds, applied when computing the next
33 /// fire cursor. Must not exceed [`JOB_SCHEDULE_MAX_JITTER_SECONDS`].
34 pub max_jitter_seconds: i32,
35 /// Next UTC instant at which this schedule is due for materialization.
36 pub next_fire_at: DateTime<Utc>,
37}
38
39/// Input for creating or updating a cron-backed job schedule.
40///
41/// Schedules are keyed by `name`. Updating an existing schedule refreshes the
42/// stored job type, payload template, cron expression, and jitter, while leaving
43/// scheduler-managed state intact. `organization_id` and `is_active` apply only
44/// when a new schedule row is inserted. `next_fire_at` applies on insert and
45/// when the cron expression changes.
46///
47/// Cron expressions are interpreted in UTC and must be accepted by
48/// `cron::Schedule::from_str`, the same parser used by `runledger-runtime` when
49/// materializing due schedules. The upsert validator rejects blank or padded
50/// schedule names, blank or padded cron expressions, invalid cron expressions,
51/// negative jitter, and jitter above [`JOB_SCHEDULE_MAX_JITTER_SECONDS`].
52///
53/// This input does not encode a compile-time job catalog. The PostgreSQL schema
54/// requires a matching job-definition row for `job_type`, but this API does not
55/// prove that a worker process has registered a runtime handler for that job
56/// type.
57#[derive(Clone, Debug)]
58pub struct JobScheduleUpsert<'a> {
59 /// Stable unique schedule name without surrounding whitespace.
60 pub name: &'a str,
61 /// Job type to enqueue whenever the schedule fires.
62 pub job_type: JobType<'a>,
63 /// Optional organization scope for enqueued jobs on first insert.
64 pub organization_id: Option<Uuid>,
65 /// JSON payload copied into each job created by the scheduler.
66 pub payload_template: &'a Value,
67 /// UTC cron expression without surrounding whitespace, validated on upsert
68 /// and parsed again when the schedule fires.
69 pub cron_expr: &'a str,
70 /// Whether the runtime scheduler should claim this schedule on first insert.
71 pub is_active: bool,
72 /// Initial fire cursor for the scheduler, also used when changing cron syntax.
73 pub next_fire_at: DateTime<Utc>,
74 /// Maximum deterministic jitter applied when materializing a due schedule,
75 /// capped at [`JOB_SCHEDULE_MAX_JITTER_SECONDS`].
76 pub max_jitter_seconds: i32,
77}
78
79/// One catalog-owned schedule sync entry.
80#[derive(Clone, Debug)]
81pub struct JobScheduleCatalogSyncEntry<'a> {
82 /// Schedule definition fields to upsert. Unlike plain schedule upserts,
83 /// catalog sync treats `is_active` as the authoritative desired active state
84 /// for both inserts and conflicts.
85 pub upsert: JobScheduleUpsert<'a>,
86}
87
88/// Result returned by [`crate::jobs::sync_catalog_job_schedules_tx`].
89#[derive(Clone, Debug, Eq, PartialEq)]
90#[non_exhaustive]
91pub struct JobScheduleCatalogSyncReport {
92 /// Schedule names upserted and active state applied during this sync.
93 pub synced_schedule_names: Vec<String>,
94}