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