Skip to main content

ralph/contracts/config/
queue.rs

1//! Queue configuration structs and aging thresholds.
2//!
3//! Responsibilities:
4//! - Define queue-related configuration structs and merge behavior.
5//!
6//! Not handled here:
7//! - Queue file IO (see `crate::queue` module).
8
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11use std::path::PathBuf;
12
13/// Aging threshold configuration for `ralph queue aging`.
14///
15/// Controls the day thresholds for categorizing tasks by age.
16/// Ordering invariant: warning_days < stale_days < rotten_days
17#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
18#[serde(default, deny_unknown_fields)]
19pub struct QueueAgingThresholds {
20    /// Warn when task age is strictly greater than this many days.
21    #[schemars(range(min = 0, max = 3650))]
22    pub warning_days: Option<u32>,
23
24    /// Mark as stale when age is strictly greater than this many days.
25    #[schemars(range(min = 0, max = 3650))]
26    pub stale_days: Option<u32>,
27
28    /// Mark as rotten when age is strictly greater than this many days.
29    #[schemars(range(min = 0, max = 3650))]
30    pub rotten_days: Option<u32>,
31}
32
33/// Queue-related configuration.
34#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
35#[serde(default, deny_unknown_fields)]
36pub struct QueueConfig {
37    /// Path to the JSON queue file, relative to repo root.
38    ///
39    /// Paths are intended to be repo-root relative. Parallel mode requires the
40    /// resolved path to be under the repo root (no `..`) so it can be copied
41    /// into workspace clones.
42    pub file: Option<PathBuf>,
43
44    /// Path to the JSON done archive file, relative to repo root.
45    ///
46    /// Paths are intended to be repo-root relative. Parallel mode requires the
47    /// resolved path to be under the repo root (no `..`) so it can be copied
48    /// into workspace clones.
49    pub done_file: Option<PathBuf>,
50
51    /// ID prefix (default: "RQ").
52    pub id_prefix: Option<String>,
53
54    /// Zero pad width for the numeric suffix (default: 4 -> RQ-0001).
55    #[schemars(range(min = 1, max = 255))]
56    pub id_width: Option<u8>,
57
58    /// Warning threshold for queue file size in KB (default: 500).
59    #[schemars(range(min = 100, max = 10000))]
60    pub size_warning_threshold_kb: Option<u32>,
61
62    /// Warning threshold for number of tasks in queue (default: 500).
63    #[schemars(range(min = 50, max = 5000))]
64    pub task_count_warning_threshold: Option<u32>,
65
66    /// Maximum allowed dependency chain depth before warning (default: 10).
67    #[schemars(range(min = 1, max = 100))]
68    pub max_dependency_depth: Option<u8>,
69
70    /// Auto-archive terminal tasks (done/rejected) from queue to done after this many days.
71    ///
72    /// Semantics:
73    /// - None: disabled (default)
74    /// - Some(0): archive immediately when the sweep runs
75    /// - Some(N): archive when completed_at is at least N days old
76    ///
77    /// The sweep runs after selected queue mutation operations (e.g., task edits and run supervision).
78    /// Tasks with missing or invalid completed_at timestamps are not moved when N > 0.
79    #[schemars(range(min = 0, max = 3650))]
80    pub auto_archive_terminal_after_days: Option<u32>,
81
82    /// Thresholds for `ralph queue aging` buckets.
83    ///
84    /// Default: warning>7d, stale>14d, rotten>30d.
85    /// Ordering must satisfy: warning_days < stale_days < rotten_days.
86    pub aging_thresholds: Option<QueueAgingThresholds>,
87}
88
89impl QueueConfig {
90    pub fn merge_from(&mut self, other: Self) {
91        if other.file.is_some() {
92            self.file = other.file;
93        }
94        if other.done_file.is_some() {
95            self.done_file = other.done_file;
96        }
97        if other.id_prefix.is_some() {
98            self.id_prefix = other.id_prefix;
99        }
100        if other.id_width.is_some() {
101            self.id_width = other.id_width;
102        }
103        if other.size_warning_threshold_kb.is_some() {
104            self.size_warning_threshold_kb = other.size_warning_threshold_kb;
105        }
106        if other.task_count_warning_threshold.is_some() {
107            self.task_count_warning_threshold = other.task_count_warning_threshold;
108        }
109        if other.max_dependency_depth.is_some() {
110            self.max_dependency_depth = other.max_dependency_depth;
111        }
112        if other.auto_archive_terminal_after_days.is_some() {
113            self.auto_archive_terminal_after_days = other.auto_archive_terminal_after_days;
114        }
115        if other.aging_thresholds.is_some() {
116            self.aging_thresholds = other.aging_thresholds;
117        }
118    }
119}