Skip to main content

orbok_workers/scheduler/
job.rs

1//! Job model for the resource-aware scheduler (RFC-036 §11, §15).
2//!
3//! Types here are the in-memory representation used by the scheduler.
4//! Persistence uses `orbok_core::{JobType, JobStatus}` and the
5//! `index_jobs` catalog table (RFC-002 §7.9); the scheduler maps
6//! between the two representations.
7
8use orbok_core::{FileId, JobId, JobType, SourceId};
9use serde::{Deserialize, Serialize};
10
11// ── Priority ──────────────────────────────────────────────────────────────
12
13/// Work priority levels (RFC-036 §8.1).
14///
15/// Higher variants are dispatched first. `Ord` is derived so that
16/// `UserBlocking > UserVisible > NormalBackground > LowBackground >
17/// Maintenance`.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum WorkPriority {
21    Maintenance = 0,
22    LowBackground = 1,
23    NormalBackground = 2,
24    UserVisible = 3,
25    UserBlocking = 4,
26}
27
28impl Default for WorkPriority {
29    fn default() -> Self {
30        WorkPriority::NormalBackground
31    }
32}
33
34impl WorkPriority {
35    /// Catalog integer stored in `index_jobs.priority`.
36    ///
37    /// Note: the baseline schema used `DEFAULT 0` for priority before
38    /// RFC-036. Existing rows with priority=0 map to `Maintenance`.
39    /// New jobs use `NormalBackground (2)` or higher by default.
40    pub fn as_i64(self) -> i64 {
41        self as i64
42    }
43
44    pub fn from_i64(v: i64) -> Self {
45        match v {
46            4 => Self::UserBlocking,
47            3 => Self::UserVisible,
48            2 => Self::NormalBackground,
49            1 => Self::LowBackground,
50            _ => Self::Maintenance,
51        }
52    }
53}
54
55// ── Job state ─────────────────────────────────────────────────────────────
56
57/// Scheduler job state (RFC-036 §11; extends `orbok_core::JobStatus`).
58///
59/// This is the in-memory view. The catalog stores a subset via
60/// `JobStatus` in `orbok-core`; `Paused` and `WaitingForDependency`
61/// are new with RFC-036 and are added to `JobStatus` in `status.rs`.
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
63#[serde(rename_all = "snake_case")]
64pub enum JobState {
65    Pending,
66    Running,
67    Paused,
68    Completed,
69    Failed,
70    Cancelled,
71    WaitingForDependency,
72}
73
74// ── Job kind ──────────────────────────────────────────────────────────────
75
76/// Job kind labels used by the scheduler (RFC-036 §11).
77///
78/// Maps 1-to-1 with `orbok_core::JobType`; kept separate so the
79/// scheduler can add kinds (e.g. `Repair`) without changing the
80/// catalog schema until needed.
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82#[serde(rename_all = "snake_case")]
83pub enum JobKind {
84    ScanSource,
85    ExtractFile,
86    ChunkFile,
87    UpdateKeywordIndex,
88    GenerateEmbedding,
89    Cleanup,
90    Repair,
91}
92
93impl JobKind {
94    /// Map to the catalog `JobType` for persistence.
95    pub fn as_job_type(self) -> JobType {
96        match self {
97            JobKind::ScanSource => JobType::Scan,
98            JobKind::ExtractFile => JobType::Extract,
99            JobKind::ChunkFile => JobType::Chunk,
100            JobKind::UpdateKeywordIndex => JobType::KeywordIndex,
101            JobKind::GenerateEmbedding => JobType::Embedding,
102            JobKind::Cleanup => JobType::DeleteStale,
103            JobKind::Repair => JobType::Rebuild,
104        }
105    }
106
107    /// Natural priority for this kind of work (RFC-036 §8).
108    pub fn default_priority(self) -> WorkPriority {
109        match self {
110            JobKind::ScanSource => WorkPriority::NormalBackground,
111            JobKind::ExtractFile => WorkPriority::NormalBackground,
112            JobKind::ChunkFile => WorkPriority::NormalBackground,
113            JobKind::UpdateKeywordIndex => WorkPriority::NormalBackground,
114            JobKind::GenerateEmbedding => WorkPriority::LowBackground,
115            JobKind::Cleanup => WorkPriority::Maintenance,
116            JobKind::Repair => WorkPriority::Maintenance,
117        }
118    }
119}
120
121// ── IndexJob ──────────────────────────────────────────────────────────────
122
123/// An in-memory scheduler job (RFC-036 §11).
124#[derive(Debug, Clone)]
125pub struct IndexJob {
126    pub id: JobId,
127    pub file_id: Option<FileId>,
128    pub source_id: SourceId,
129    pub kind: JobKind,
130    pub priority: WorkPriority,
131    pub state: JobState,
132    pub attempt_count: u32,
133    pub last_error_kind: Option<String>,
134}
135
136impl IndexJob {
137    pub fn new(source_id: SourceId, kind: JobKind) -> Self {
138        Self {
139            id: JobId::generate(),
140            file_id: None,
141            source_id,
142            priority: kind.default_priority(),
143            kind,
144            state: JobState::Pending,
145            attempt_count: 0,
146            last_error_kind: None,
147        }
148    }
149
150    pub fn with_file(mut self, file_id: FileId) -> Self {
151        self.file_id = Some(file_id);
152        self
153    }
154
155    pub fn with_priority(mut self, priority: WorkPriority) -> Self {
156        self.priority = priority;
157        self
158    }
159}
160
161// ── Scheduler events ──────────────────────────────────────────────────────
162
163/// Events emitted by the scheduler (RFC-036 §15).
164///
165/// The app layer listens to these to update the Indexing view.
166/// Use plain-language copy in the UI layer; never expose these
167/// enum names directly to users.
168#[derive(Debug, Clone)]
169pub enum SchedulerEvent {
170    JobQueued(JobId),
171    JobStarted(JobId),
172    JobPaused(JobId),
173    JobResumed(JobId),
174    JobCompleted(JobId),
175    JobFailed {
176        id: JobId,
177        error_kind: String,
178    },
179    JobCancelled(JobId),
180    QueueBackpressureApplied(QueueKind),
181    QueueBackpressureReleased(QueueKind),
182    UserActivityDetected,
183    ResourceModeChanged(ResourceMode),
184    PartialReadinessChanged {
185        ready_count: u64,
186        pending_count: u64,
187    },
188}
189
190/// Which queue triggered backpressure (RFC-036 §10.3).
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub enum QueueKind {
193    Scan,
194    Extract,
195    Chunk,
196    Keyword,
197    Embedding,
198    Maintenance,
199}
200
201// ── Resource mode ─────────────────────────────────────────────────────────
202
203/// Current resource/activity mode of the scheduler (RFC-036 §15).
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
205pub enum ResourceMode {
206    /// Normal background operation.
207    #[default]
208    Normal,
209    /// User is actively searching or typing — reduce background work.
210    UserActive,
211    /// Running in low-impact mode (battery/thermal policy).
212    LowImpact,
213    /// All background work is paused by user request.
214    Paused,
215}