use crate::core::{RuntimeError, Timestamp};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Condition {
pub kind: &'static str,
pub found: usize,
pub at_least: bool,
pub remedy: &'static str,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Attention {
pub conditions: Vec<Condition>,
pub not_checked: Vec<&'static str>,
}
impl Attention {
#[must_use]
pub fn any(&self) -> bool {
!self.conditions.is_empty()
}
fn note(&mut self, kind: &'static str, found: usize, page: usize, remedy: &'static str) {
if found > 0 {
self.conditions.push(Condition {
kind,
found,
at_least: found >= page,
remedy,
});
}
}
}
impl super::Runtime {
pub async fn attention(&self, at: Timestamp, page: usize) -> Result<Attention, RuntimeError> {
let mut out = Attention::default();
for (outcome, kind, remedy) in [
(
"quarantined",
"run.quarantined",
"establish the undecided effect with `reconcile`, then `quarantine` \
to reopen or abandon",
),
(
"exhausted",
"run.exhausted",
"raise the ceiling and `replay`, or `cancel` the run",
),
(
"withheld",
"run.withheld",
"lift the withdrawal with `halt --lift`, then `replay` — or `cancel` \
the run",
),
] {
let found = self
.store()
.runs_by_outcome(outcome, page)
.await
.map_err(RuntimeError::from_store)?;
out.note(kind, found.len(), page, remedy);
}
let abandoned = self
.store()
.abandoned_runs(page)
.await
.map_err(RuntimeError::from_store)?;
out.note(
"run.abandoned",
abandoned.len(),
page,
"the recovery sweep resumes these; one that keeps reappearing is a \
run nothing can drive",
);
let overdue = self
.store()
.waiting_runs(page)
.await
.map_err(RuntimeError::from_store)?
.into_iter()
.filter(|w| w.reason.until() <= at)
.count();
out.note(
"run.wait_expired",
overdue,
page,
"`replay` the run: it reaches the announced wait and re-arms it",
);
self.backlogs(&mut out, page, at).await?;
Ok(out)
}
async fn backlogs(
&self,
out: &mut Attention,
page: usize,
now: Timestamp,
) -> Result<(), RuntimeError> {
match self.cases() {
Some(cases) => {
let breached = cases
.breached(page)
.await
.map_err(RuntimeError::from_store)?;
out.note(
"obligation.breached",
breached.len(),
page,
"`acknowledge` the breach, saying what was done about it",
);
}
None => out.not_checked.push(
"obligations — this plane holds no case store, so whether any went \
unaccounted for was not established",
),
}
match self.cases() {
Some(cases) => {
let failed = cases
.last_drill()
.await
.map_err(RuntimeError::from_store)?
.is_some_and(|d| !d.sound);
out.note(
"drill.failed",
usize::from(failed),
usize::MAX,
"the last recovery rehearsal found unrecoverable references — \
re-run `drill` and resolve what it names",
);
}
None => out.not_checked.push(
"recovery rehearsal — this plane holds no case store, so there is \
nothing to drill and no verdict to read",
),
}
match self.tasks() {
Some(tasks) => {
let overdue = tasks
.overdue(now, page)
.await
.map_err(RuntimeError::from_store)?;
out.note(
"task.overdue",
overdue.len(),
page,
"`decide` them. Escalation is not an operator act — the sweeper widens \
the audience itself when the window closes",
);
}
None => out.not_checked.push(
"worklist — this plane holds no task store, so whether any approval is \
overdue was not established",
),
}
match self.events() {
Some(events) => {
let dead = events
.dead_letters(page)
.await
.map_err(RuntimeError::from_store)?;
out.note(
"event.dead_lettered",
dead.len(),
page,
"nothing here resolves one: the correlation key belongs to the \
emitter, so this is a diagnosis to carry to them",
);
}
None => out.not_checked.push(
"dead letters — this plane holds no event store, so whether an inbound \
message went unclaimed was not established",
),
}
#[cfg(feature = "push")]
match self.push() {
Some(push) => {
let parked = push.parked(page).await.map_err(RuntimeError::from_store)?;
out.note(
"push.parked",
parked.len(),
page,
"fix the endpoint, then `rearm` the registration",
);
}
None => out.not_checked.push(
"push registrations — this plane holds no push store, so whether a \
worker gave up on one was not established",
),
}
#[cfg(not(feature = "push"))]
out.not_checked
.push("push registrations — this build has no outbound delivery");
Ok(())
}
}