use std::time::Duration;
use super::repository::{ApprovalRepository, ApprovalRequest, ApprovalStatus};
use systemprompt_identifiers::CallId;
const POLL_INTERVAL: Duration = Duration::from_millis(500);
#[derive(Debug, Clone)]
pub enum ApprovalOutcome {
Approved(Box<ApprovalRequest>),
Denied(Box<ApprovalRequest>),
Expired(Box<ApprovalRequest>),
StillPending(Box<ApprovalRequest>),
Missing,
}
pub async fn wait_for_decision(
repo: &ApprovalRepository,
call_id: &CallId,
hold: Duration,
) -> ApprovalOutcome {
let deadline = tokio::time::Instant::now() + hold;
let mut last_seen: Option<ApprovalRequest> = None;
loop {
match repo.find(call_id).await {
Ok(Some(request)) => match request.status {
ApprovalStatus::Approved => {
return ApprovalOutcome::Approved(Box::new(request));
},
ApprovalStatus::Denied => {
return ApprovalOutcome::Denied(Box::new(request));
},
ApprovalStatus::Expired => {
return ApprovalOutcome::Expired(Box::new(request));
},
ApprovalStatus::Pending => {
if request.expires_at <= chrono::Utc::now() {
return ApprovalOutcome::Expired(Box::new(request));
}
last_seen = Some(request);
},
},
Ok(None) => {
tracing::error!(
call_id = %call_id,
"approval row vanished while a call was waiting on it; \
treating the call as denied"
);
return last_seen.map_or(ApprovalOutcome::Missing, |r| {
ApprovalOutcome::Denied(Box::new(r))
});
},
Err(err) => {
tracing::warn!(
call_id = %call_id,
error = %err,
"could not read the approval row; retrying within the hold budget"
);
},
}
let now = tokio::time::Instant::now();
if now >= deadline {
return last_seen.map_or(ApprovalOutcome::Missing, |r| {
ApprovalOutcome::StillPending(Box::new(r))
});
}
tokio::time::sleep(POLL_INTERVAL.min(deadline - now)).await;
}
}