Skip to main content

systemprompt_models/services/
scheduler.rs

1//! Scheduler job configuration and the built-in job set.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use serde::{Deserialize, Serialize};
7use systemprompt_identifiers::UserId;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(deny_unknown_fields)]
11pub struct JobConfig {
12    #[serde(default)]
13    pub extension: Option<String>,
14    pub name: String,
15    #[serde(default)]
16    pub owner: Option<UserId>,
17    #[serde(default = "default_true")]
18    pub enabled: bool,
19    #[serde(default)]
20    pub schedule: Option<String>,
21    /// Opt-in for destructive job actions (e.g. automated IP bans). When
22    /// `false` — the default — such jobs run in observe-and-log mode.
23    #[serde(default)]
24    pub enforce: bool,
25}
26
27const fn default_true() -> bool {
28    true
29}
30
31impl JobConfig {
32    /// A job with no explicit `owner` runs as the profile `system_admin`,
33    /// resolved per-environment at scheduler start. Set one with
34    /// [`Self::with_owner`] only for a job that must run as a specific user.
35    #[must_use]
36    pub fn new(name: impl Into<String>) -> Self {
37        Self {
38            extension: None,
39            name: name.into(),
40            owner: None,
41            enabled: true,
42            schedule: None,
43            enforce: false,
44        }
45    }
46
47    #[must_use]
48    pub const fn with_enforce(mut self) -> Self {
49        self.enforce = true;
50        self
51    }
52
53    #[must_use]
54    pub fn with_owner(mut self, owner: UserId) -> Self {
55        self.owner = Some(owner);
56        self
57    }
58
59    #[must_use]
60    pub fn with_extension(mut self, extension: impl Into<String>) -> Self {
61        self.extension = Some(extension.into());
62        self
63    }
64
65    #[must_use]
66    pub fn with_schedule(mut self, schedule: impl Into<String>) -> Self {
67        self.schedule = Some(schedule.into());
68        self
69    }
70
71    #[must_use]
72    pub const fn disabled(mut self) -> Self {
73        self.enabled = false;
74        self
75    }
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct SchedulerConfig {
80    #[serde(default = "default_true")]
81    pub enabled: bool,
82    #[serde(default)]
83    pub jobs: Vec<JobConfig>,
84    #[serde(default = "default_bootstrap_jobs")]
85    pub bootstrap_jobs: Vec<String>,
86    #[serde(default = "default_true")]
87    pub distributed_lock: bool,
88}
89
90fn default_bootstrap_jobs() -> Vec<String> {
91    vec![
92        "database_cleanup".to_owned(),
93        "cleanup_inactive_sessions".to_owned(),
94    ]
95}
96
97impl SchedulerConfig {
98    /// The built-in core job set. The four cleanup jobs
99    /// (`cleanup_anonymous_users`, `cleanup_empty_contexts`,
100    /// `cleanup_inactive_sessions`, `database_cleanup`) have no human
101    /// originator, so they carry no explicit `owner` and run as the profile
102    /// `system_admin` resolved per-environment at scheduler start.
103    #[must_use]
104    pub fn with_system_admin() -> Self {
105        Self {
106            enabled: true,
107            jobs: vec![
108                JobConfig::new("cleanup_anonymous_users")
109                    .with_extension("core")
110                    .with_schedule("0 0 3 * * *"),
111                JobConfig::new("cleanup_empty_contexts")
112                    .with_extension("core")
113                    .with_schedule("0 0 * * * *"),
114                JobConfig::new("cleanup_inactive_sessions")
115                    .with_extension("core")
116                    .with_schedule("0 0 * * * *"),
117                JobConfig::new("database_cleanup")
118                    .with_extension("core")
119                    .with_schedule("0 0 4 * * *"),
120            ],
121            bootstrap_jobs: default_bootstrap_jobs(),
122            distributed_lock: true,
123        }
124    }
125}