Skip to main content

ralph/queue/operations/runnability/
model.rs

1//! Data models for queue runnability reporting.
2//!
3//! Responsibilities:
4//! - Define the serialized report, summary, row, and reason shapes.
5//! - Keep JSON field names and report versioning stable.
6//! - Provide shared domain enums for runnability callers and tests.
7//!
8//! Does not handle:
9//! - Task analysis logic.
10//! - Report aggregation or selection.
11//!
12//! Invariants/assumptions:
13//! - Types are serialized in `snake_case` for CLI/JSON consumers.
14//! - `RUNNABILITY_REPORT_VERSION` changes only on intentional schema updates.
15
16use crate::contracts::{BlockingState, TaskStatus};
17use serde::Serialize;
18
19/// Report version for JSON stability.
20pub const RUNNABILITY_REPORT_VERSION: u32 = 1;
21
22/// A structured report of queue runnability.
23#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
24#[serde(rename_all = "snake_case")]
25pub struct QueueRunnabilityReport {
26    pub version: u32,
27    pub now: String,
28    pub selection: QueueRunnabilitySelection,
29    pub summary: QueueRunnabilitySummary,
30    pub tasks: Vec<TaskRunnabilityRow>,
31}
32
33/// Selection context for the report.
34#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
35#[serde(rename_all = "snake_case")]
36pub struct QueueRunnabilitySelection {
37    pub include_draft: bool,
38    pub prefer_doing: bool,
39    pub selected_task_id: Option<String>,
40    pub selected_task_status: Option<TaskStatus>,
41}
42
43/// Summary counts of runnability states.
44#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
45#[serde(rename_all = "snake_case")]
46pub struct QueueRunnabilitySummary {
47    pub total_active: usize,
48    pub candidates_total: usize,
49    pub runnable_candidates: usize,
50    pub blocked_by_dependencies: usize,
51    pub blocked_by_schedule: usize,
52    pub blocked_by_status_or_flags: usize,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub blocking: Option<BlockingState>,
55}
56
57/// Per-task runnability row.
58#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
59#[serde(rename_all = "snake_case")]
60pub struct TaskRunnabilityRow {
61    pub id: String,
62    pub status: TaskStatus,
63    pub runnable: bool,
64    pub reasons: Vec<NotRunnableReason>,
65}
66
67/// Reason a task is not runnable.
68#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
69#[serde(tag = "kind", rename_all = "snake_case")]
70pub enum NotRunnableReason {
71    /// Status prevents running (Done/Rejected).
72    StatusNotRunnable { status: TaskStatus },
73    /// Draft excluded because include_draft is false.
74    DraftExcluded,
75    /// Dependencies are not met.
76    UnmetDependencies { dependencies: Vec<DependencyIssue> },
77    /// Scheduled start is in the future.
78    ScheduledStartInFuture {
79        scheduled_start: String,
80        now: String,
81        seconds_until_runnable: i64,
82    },
83}
84
85/// Specific dependency issue.
86#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
87#[serde(tag = "kind", rename_all = "snake_case")]
88pub enum DependencyIssue {
89    /// Dependency task not found.
90    Missing { id: String },
91    /// Dependency task exists but is not Done/Rejected.
92    NotComplete { id: String, status: TaskStatus },
93}