Skip to main content

loong_contracts/
work_types.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[non_exhaustive]
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum WorkUnitKind {
8    Feature,
9    Issue,
10    Review,
11    Ops,
12}
13
14impl WorkUnitKind {
15    pub const fn as_str(self) -> &'static str {
16        match self {
17            Self::Feature => "feature",
18            Self::Issue => "issue",
19            Self::Review => "review",
20            Self::Ops => "ops",
21        }
22    }
23
24    pub fn parse(raw: &str) -> Option<Self> {
25        let normalized = raw.trim().to_ascii_lowercase();
26        match normalized.as_str() {
27            "feature" => Some(Self::Feature),
28            "issue" => Some(Self::Issue),
29            "review" => Some(Self::Review),
30            "ops" => Some(Self::Ops),
31            _ => None,
32        }
33    }
34}
35
36#[non_exhaustive]
37#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
38#[serde(rename_all = "snake_case")]
39pub enum WorkSourceKind {
40    Manual,
41    Discord,
42    Github,
43}
44
45impl WorkSourceKind {
46    pub const fn as_str(self) -> &'static str {
47        match self {
48            Self::Manual => "manual",
49            Self::Discord => "discord",
50            Self::Github => "github",
51        }
52    }
53
54    pub fn parse(raw: &str) -> Option<Self> {
55        let normalized = raw.trim().to_ascii_lowercase();
56        match normalized.as_str() {
57            "manual" => Some(Self::Manual),
58            "discord" => Some(Self::Discord),
59            "github" => Some(Self::Github),
60            _ => None,
61        }
62    }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
66pub struct WorkUnitSourceRef {
67    pub source_kind: WorkSourceKind,
68    pub project_id: Option<String>,
69    pub channel_id: Option<String>,
70    pub thread_id: Option<String>,
71    pub message_id: Option<String>,
72    pub external_ref: Option<String>,
73    pub source_url: Option<String>,
74}
75
76impl Default for WorkUnitSourceRef {
77    fn default() -> Self {
78        Self {
79            source_kind: WorkSourceKind::Manual,
80            project_id: None,
81            channel_id: None,
82            thread_id: None,
83            message_id: None,
84            external_ref: None,
85            source_url: None,
86        }
87    }
88}
89
90#[non_exhaustive]
91#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
92#[serde(rename_all = "snake_case")]
93pub enum WorkUnitPriority {
94    Low,
95    Normal,
96    High,
97    Critical,
98}
99
100impl WorkUnitPriority {
101    pub const fn as_str(self) -> &'static str {
102        match self {
103            Self::Low => "low",
104            Self::Normal => "normal",
105            Self::High => "high",
106            Self::Critical => "critical",
107        }
108    }
109
110    pub fn parse(raw: &str) -> Option<Self> {
111        let normalized = raw.trim().to_ascii_lowercase();
112        match normalized.as_str() {
113            "low" => Some(Self::Low),
114            "normal" => Some(Self::Normal),
115            "high" => Some(Self::High),
116            "critical" => Some(Self::Critical),
117            _ => None,
118        }
119    }
120}
121
122#[non_exhaustive]
123#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
124#[serde(rename_all = "snake_case")]
125pub enum WorkUnitStatus {
126    Captured,
127    Triaged,
128    Ready,
129    Leased,
130    Running,
131    WaitingExternal,
132    WaitingReview,
133    RetryPending,
134    Completed,
135    FailedTerminal,
136    Cancelled,
137    Archived,
138}
139
140impl WorkUnitStatus {
141    pub const fn as_str(self) -> &'static str {
142        match self {
143            Self::Captured => "captured",
144            Self::Triaged => "triaged",
145            Self::Ready => "ready",
146            Self::Leased => "leased",
147            Self::Running => "running",
148            Self::WaitingExternal => "waiting_external",
149            Self::WaitingReview => "waiting_review",
150            Self::RetryPending => "retry_pending",
151            Self::Completed => "completed",
152            Self::FailedTerminal => "failed_terminal",
153            Self::Cancelled => "cancelled",
154            Self::Archived => "archived",
155        }
156    }
157
158    pub fn parse(raw: &str) -> Option<Self> {
159        let normalized = raw.trim().to_ascii_lowercase();
160        match normalized.as_str() {
161            "captured" => Some(Self::Captured),
162            "triaged" => Some(Self::Triaged),
163            "ready" => Some(Self::Ready),
164            "leased" => Some(Self::Leased),
165            "running" => Some(Self::Running),
166            "waiting_external" => Some(Self::WaitingExternal),
167            "waiting_review" => Some(Self::WaitingReview),
168            "retry_pending" => Some(Self::RetryPending),
169            "completed" => Some(Self::Completed),
170            "failed_terminal" => Some(Self::FailedTerminal),
171            "cancelled" => Some(Self::Cancelled),
172            "archived" => Some(Self::Archived),
173            _ => None,
174        }
175    }
176
177    pub const fn is_terminal(self) -> bool {
178        matches!(
179            self,
180            Self::Completed | Self::FailedTerminal | Self::Cancelled | Self::Archived
181        )
182    }
183
184    pub const fn is_ready_for_lease(self) -> bool {
185        matches!(self, Self::Ready | Self::RetryPending)
186    }
187
188    pub const fn is_blocked(self) -> bool {
189        matches!(self, Self::WaitingExternal | Self::WaitingReview)
190    }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
194pub struct WorkUnitRetryPolicy {
195    pub max_attempts: u32,
196    pub initial_backoff_ms: u64,
197    pub max_backoff_ms: u64,
198}
199
200impl Default for WorkUnitRetryPolicy {
201    fn default() -> Self {
202        Self {
203            max_attempts: 3,
204            initial_backoff_ms: 1_000,
205            max_backoff_ms: 60_000,
206        }
207    }
208}
209
210#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
211pub struct WorkUnitRecord {
212    pub work_unit_id: String,
213    pub kind: WorkUnitKind,
214    pub title: String,
215    pub description: String,
216    pub source_ref: WorkUnitSourceRef,
217    pub status: WorkUnitStatus,
218    pub priority: WorkUnitPriority,
219    pub assigned_to: Option<String>,
220    pub retry_policy: WorkUnitRetryPolicy,
221    pub attempt_count: u32,
222    pub next_run_at_ms: i64,
223    pub last_error: Option<String>,
224    pub blocking_reason: Option<String>,
225    pub parent_work_unit_id: Option<String>,
226    pub blocks_work_unit_ids: Vec<String>,
227    pub blocked_by_work_unit_ids: Vec<String>,
228    pub result_payload_json: Option<Value>,
229    pub created_at_ms: i64,
230    pub updated_at_ms: i64,
231    pub archived_at_ms: Option<i64>,
232}
233
234#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
235pub struct WorkUnitLeaseRecord {
236    pub work_unit_id: String,
237    pub owner: String,
238    pub lease_version: u64,
239    pub acquired_at_ms: i64,
240    pub heartbeat_at_ms: i64,
241    pub expires_at_ms: i64,
242}
243
244#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
245pub struct WorkUnitSnapshot {
246    pub work_unit: WorkUnitRecord,
247    pub lease: Option<WorkUnitLeaseRecord>,
248}
249
250#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
251pub struct WorkUnitEventRecord {
252    pub sequence_id: i64,
253    pub work_unit_id: String,
254    pub event_kind: String,
255    pub actor: Option<String>,
256    pub payload_json: Value,
257    pub recorded_at_ms: i64,
258}
259
260#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
261pub struct WorkRuntimeHealthSnapshot {
262    pub total_count: usize,
263    pub ready_count: usize,
264    pub leased_count: usize,
265    pub running_count: usize,
266    pub blocked_count: usize,
267    pub retry_pending_count: usize,
268    pub terminal_count: usize,
269    pub archived_count: usize,
270    pub expired_lease_count: usize,
271}