use chrono::{DateTime, Duration, Utc};
use octl_core::{Kind, Node, Status};
pub const STALL_GRACE: Duration = Duration::minutes(12);
pub const DRIVER_NODE_ID: &str = "n-0001";
#[must_use]
pub fn is_stalled(
run_status: Status,
kind: Kind,
driver: Option<&Node>,
now: DateTime<Utc>,
) -> bool {
if run_status != Status::Pending {
return false;
}
if kind != Kind::Orchestrate {
return false;
}
let Some(node) = driver else {
return false;
};
if node.status != Status::Pending {
return false;
}
if !node.children.is_empty() {
return false;
}
now.signed_duration_since(node.updated_at) > STALL_GRACE
}
#[cfg(test)]
mod tests {
use super::*;
use octl_core::{NodeId, RunId};
fn node(status: Status, children: usize, updated_at: DateTime<Utc>) -> Node {
Node {
schema_version: 1,
node_id: NodeId::parse_str("n-0001").unwrap(),
run_id: RunId::parse_str("01arz3ndektsv4rrffq69g5fav").unwrap(),
parent_node_id: None,
kind: Kind::Orchestrate,
status,
task: None,
worktree_path: None,
branch: None,
base_sha: None,
tmux_window: None,
tmux_identity: None,
agent_pid: None,
agent_pid_start_time: None,
supervisor_pid: None,
children: (0..children)
.map(|i| octl_core::ChildRef {
run_id: RunId::parse_str("01arz3ndektsv4rrffq69g5fav").unwrap(),
node_id: NodeId::parse_str(&format!("n-{:04}", i + 2)).unwrap(),
})
.collect(),
started_at: None,
updated_at,
last_report: None,
last_processed_report_seq_by_child: serde_json::Map::new(),
retry_attempts: 0,
}
}
fn now() -> DateTime<Utc> {
"2026-08-06T12:00:00Z".parse().unwrap()
}
#[test]
fn undriven_driver_past_grace_is_stalled() {
let created = now() - STALL_GRACE - Duration::seconds(1);
let n = node(Status::Pending, 0, created);
assert!(is_stalled(
Status::Pending,
Kind::Orchestrate,
Some(&n),
now()
));
}
#[test]
fn driver_with_child_is_not_stalled() {
let created = now() - STALL_GRACE - Duration::hours(1);
let n = node(Status::Pending, 1, created);
assert!(!is_stalled(
Status::Pending,
Kind::Orchestrate,
Some(&n),
now()
));
}
#[test]
fn driver_with_recent_activity_is_not_stalled() {
let recent = now() - Duration::minutes(1);
let n = node(Status::Pending, 0, recent);
assert!(!is_stalled(
Status::Pending,
Kind::Orchestrate,
Some(&n),
now()
));
}
#[test]
fn within_grace_window_is_not_stalled() {
let created = now() - STALL_GRACE + Duration::seconds(1);
let n = node(Status::Pending, 0, created);
assert!(!is_stalled(
Status::Pending,
Kind::Orchestrate,
Some(&n),
now()
));
}
#[test]
fn exactly_at_grace_boundary_is_not_stalled() {
let created = now() - STALL_GRACE;
let n = node(Status::Pending, 0, created);
assert!(!is_stalled(
Status::Pending,
Kind::Orchestrate,
Some(&n),
now()
));
}
#[test]
fn non_pending_run_status_is_never_stalled() {
let created = now() - STALL_GRACE - Duration::hours(1);
let n = node(Status::Pending, 0, created);
for run_status in [
Status::Running,
Status::Blocked,
Status::Done,
Status::Failed,
Status::Cancelled,
] {
assert!(
!is_stalled(run_status, Kind::Orchestrate, Some(&n), now()),
"run status {run_status:?} must not stall"
);
}
}
#[test]
fn non_orchestrate_kind_is_never_stalled() {
let created = now() - STALL_GRACE - Duration::hours(1);
let n = node(Status::Pending, 0, created);
for k in [Kind::Spinoff, Kind::FanOut, Kind::Orchestrated, Kind::Code] {
assert!(
!is_stalled(Status::Pending, k, Some(&n), now()),
"kind {k:?} must not stall"
);
}
}
#[test]
fn non_pending_driver_is_not_stalled() {
let created = now() - STALL_GRACE - Duration::hours(1);
for s in [
Status::Running,
Status::Blocked,
Status::Done,
Status::Failed,
Status::Cancelled,
] {
let n = node(s, 0, created);
assert!(
!is_stalled(Status::Pending, Kind::Orchestrate, Some(&n), now()),
"driver status {s:?} must not stall"
);
}
}
#[test]
fn missing_driver_node_is_not_stalled() {
assert!(!is_stalled(Status::Pending, Kind::Orchestrate, None, now()));
}
}