Skip to main content

artifactize_core/monitor/model/
states.rs

1//! Eval view states: what one tree row shows, derived from the saved definitions and the
2//! current request states with the readiness rules of [`crate::graph`].
3//!
4//! Dependencies target Artifacts. An ordinary eval waits for every Artifact of its own
5//! component's direct dependency components (its gates); a dependency eval waits for its
6//! `depends_on` Artifacts, which may be cycle peers. Cycle peers never wait for each other
7//! otherwise.
8use std::{
9    cell::RefCell,
10    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
11};
12
13use super::Saved;
14use crate::{
15    config::ProfileKind,
16    graph::EvalStatus,
17    store::RequestView,
18    types::{ArtifactName, EvalId, RequestStatus, RunStatus},
19};
20
21/// Where a done eval's GREEN came from.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum Source {
24    Executed,
25    Provisional,
26    Reused,
27    Derived,
28    /// A saved result the Run used for an eval it has no request for.
29    Saved,
30}
31
32/// Why a queued eval has not started although nothing upstream holds it.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum Queue {
35    /// Ready, waiting for scheduler admission without a Run cap.
36    Ready,
37    /// Waiting for the Run's explicitly chosen `--jobs` cap.
38    Jobs,
39    /// Waiting for a backend slot (limits.json); holds the backend name.
40    Slot(crate::config::Backend),
41    /// Waiting for the running execution of the same reuse key.
42    Joined,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum Activity {
47    Running,
48    /// The claimant, when someone has claimed it.
49    Human(Option<crate::types::ReviewerId>),
50    Queued(Queue),
51}
52
53/// The Artifacts an eval waits for, most actionable first, and one level of root cause when
54/// every one of them is itself waiting: `(via, root)` reads "via waits for root".
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct Waits {
57    pub artifacts: Vec<crate::types::ArtifactName>,
58    pub root: Option<(crate::types::ArtifactName, crate::types::ArtifactName)>,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub enum NotRun {
63    /// A finished Run ended while the eval still waited for these Artifacts.
64    Dependency(Waits),
65    Budget,
66    Stale,
67    Unreviewed,
68    /// The eval has no request in this Run.
69    Absent,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub enum EvalView {
74    Done(Source),
75    /// `verdict` is RED; otherwise an operational ERROR.
76    Failed {
77        verdict: bool,
78    },
79    InProgress(Activity),
80    /// Only while the Run is running.
81    WaitingOn(Waits),
82    /// Upstream failed with a RED verdict or is itself blocked.
83    BlockedBy(Vec<crate::types::ArtifactName>),
84    NotRun(NotRun),
85}
86
87/// Roll-up urgency, ordered from failed to done for eval rows and Artifact completions.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
89pub enum Rank {
90    Failed,
91    InProgress,
92    Blocked,
93    Waiting,
94    NotRun,
95    Done,
96}
97
98impl EvalView {
99    pub fn rank(&self) -> Rank {
100        match self {
101            Self::Failed { .. } => Rank::Failed,
102            Self::InProgress(_) => Rank::InProgress,
103            Self::BlockedBy(_) => Rank::Blocked,
104            Self::WaitingOn(_) => Rank::Waiting,
105            Self::NotRun(_) => Rank::NotRun,
106            Self::Done(_) => Rank::Done,
107        }
108    }
109}
110
111/// The most urgent kind of activity in an in-progress Artifact.
112#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
113pub enum Busy {
114    Running,
115    Human,
116    Queued,
117}
118
119/// An Artifact's evaluation as a whole, as shown where another eval waits for it.
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum Completion {
122    /// Every eval is done; also basis Artifacts and Artifacts without evals.
123    Complete,
124    InProgress(Busy),
125    /// Some eval waits, is blocked or did not run.
126    Waiting {
127        blocked: bool,
128    },
129    /// `verdict` when an eval is RED, otherwise only operational ERRORs.
130    Failed {
131        verdict: bool,
132    },
133}
134
135impl Completion {
136    /// Order of X: failed, in progress, waiting, complete; blocked completions still wait.
137    fn rank(self) -> Rank {
138        match self {
139            Self::Failed { .. } => Rank::Failed,
140            Self::InProgress(_) => Rank::InProgress,
141            Self::Waiting { .. } => Rank::Waiting,
142            Self::Complete => Rank::Done,
143        }
144    }
145}
146
147/// One Artifact an eval depends on, with its current completion.
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct Upstream {
150    pub artifact: crate::types::ArtifactName,
151    pub completion: Completion,
152}
153
154/// Per-Run index from saved definitions: display order, evals per Artifact and the
155/// Artifacts each eval depends on. Built in one pass over the definitions and requests.
156pub(super) struct Index<'a> {
157    pub order: Vec<&'a ArtifactName>,
158    position: HashMap<&'a ArtifactName, usize>,
159    requests: HashMap<&'a EvalId, &'a RequestView>,
160    pub evals: BTreeMap<&'a ArtifactName, Vec<&'a EvalId>>,
161    /// Eval → direct dependency Artifacts, in display order.
162    pub upstream: BTreeMap<&'a EvalId, Vec<&'a ArtifactName>>,
163    pub dependency: BTreeSet<&'a EvalId>,
164    /// Artifact → its cycle peers (same component), by name.
165    pub peers: BTreeMap<&'a ArtifactName, Vec<&'a ArtifactName>>,
166    pub cyclic: BTreeSet<&'a ArtifactName>,
167}
168
169type Component = crate::store::definitions::Component;
170
171fn members(component: &Component) -> Vec<&ArtifactName> {
172    component.artifacts.value().into_iter().flatten().collect()
173}
174
175impl<'a> Index<'a> {
176    pub fn new(saved: &Saved<'a>) -> Self {
177        let order = saved.artifact_ids();
178        let position: HashMap<_, _> = order
179            .iter()
180            .enumerate()
181            .map(|(index, id)| (*id, index))
182            .collect();
183        let mut requests = HashMap::new();
184        for view in saved.requests {
185            requests.entry(&view.request.eval_id).or_insert(view);
186        }
187        let definitions: HashMap<_, _> = saved
188            .eval_definitions()
189            .iter()
190            .map(|eval| (&eval.id, eval))
191            .collect();
192        // Saved Eval definitions targeting each Artifact, then requests without one.
193        let mut targets: HashMap<&ArtifactName, Vec<&EvalId>> = HashMap::new();
194        for eval in saved.eval_definitions() {
195            targets.entry(&eval.target).or_default().push(&eval.id);
196        }
197        for view in saved.requests {
198            let request = &view.request;
199            if !definitions.contains_key(&request.eval_id) {
200                let evals = targets.entry(&request.target).or_default();
201                if !evals.contains(&&request.eval_id) {
202                    evals.push(&request.eval_id);
203                }
204            }
205        }
206        let mut dependency = BTreeSet::new();
207        let mut evals = BTreeMap::new();
208        for &artifact in &order {
209            let mut ids: Vec<(bool, &EvalId)> = targets
210                .remove(artifact)
211                .unwrap_or_default()
212                .into_iter()
213                .map(|eval| {
214                    let kind = definitions
215                        .get(eval)
216                        .and_then(|eval| eval.declaration.value())
217                        .and_then(|declaration| declaration.profile.value())
218                        .map(|profile| profile.known.kind())
219                        .or_else(|| requests.get(eval).map(|view| view.request.profile.kind()));
220                    (kind == Some(ProfileKind::Dependency), eval)
221                })
222                .collect();
223            // Ordinary evals by id, then dependency evals.
224            ids.sort();
225            dependency.extend(
226                ids.iter()
227                    .filter(|(derived, _)| *derived)
228                    .map(|(_, id)| *id),
229            );
230            evals.insert(artifact, ids.into_iter().map(|(_, id)| id).collect());
231        }
232        let components = saved.components();
233        let by_id: HashMap<crate::graph::ComponentId, &Component> = components
234            .iter()
235            .enumerate()
236            .map(|(index, component)| {
237                let id = component
238                    .id
239                    .value()
240                    .copied()
241                    .unwrap_or_else(|| crate::graph::ComponentId::from_position(index));
242                (id, component)
243            })
244            .collect();
245        let mut component_of: HashMap<&ArtifactName, &Component> = HashMap::new();
246        let mut peers = BTreeMap::new();
247        let mut cyclic = BTreeSet::new();
248        for component in components {
249            let mut names = members(component);
250            names.sort_unstable();
251            for &artifact in &names {
252                component_of.entry(artifact).or_insert(component);
253                peers.insert(
254                    artifact,
255                    names.iter().copied().filter(|id| *id != artifact).collect(),
256                );
257                if component.cyclic.value() == Some(&true) {
258                    cyclic.insert(artifact);
259                }
260            }
261        }
262        let mut upstream = BTreeMap::new();
263        for (&artifact, ids) in &evals {
264            let component = component_of.get(artifact).copied();
265            let own = component.map(members).unwrap_or_else(|| vec![artifact]);
266            let gates: Option<Vec<&ArtifactName>> = component.map(|component| {
267                match component.dependencies.value() {
268                    Some(dependencies) => dependencies
269                        .iter()
270                        .filter_map(|id| by_id.get(id))
271                        .flat_map(|component| members(component))
272                        .collect(),
273                    // Saved before components named their dependencies: follow relations.
274                    None => saved
275                        .definitions()
276                        .into_iter()
277                        .flat_map(|graph| graph.relations())
278                        .filter(|relation| {
279                            own.contains(&&relation.target) && !own.contains(&&relation.source)
280                        })
281                        .flat_map(|relation| {
282                            component_of
283                                .get(&relation.source)
284                                .map(|component| members(component))
285                                .unwrap_or_else(|| vec![&relation.source])
286                        })
287                        .collect(),
288                }
289            });
290            for &eval in ids {
291                let request = requests.get(eval).map(|view| &view.request);
292                let deps: Vec<&ArtifactName> = if dependency.contains(eval) {
293                    let definition = definitions
294                        .get(eval)
295                        .and_then(|definition| definition.deps.value());
296                    match (definition, request) {
297                        (Some(deps), _) => deps.iter().collect(),
298                        (None, Some(request)) => request.deps.iter().collect(),
299                        (None, None) => Vec::new(),
300                    }
301                } else {
302                    match (&gates, request) {
303                        (Some(gates), _) => gates.clone(),
304                        // Runs saved without definitions: the request's referenced Artifacts.
305                        (None, Some(request)) => request.deps.iter().collect(),
306                        (None, None) => Vec::new(),
307                    }
308                };
309                let mut deps: Vec<&ArtifactName> = deps
310                    .into_iter()
311                    .filter(|id| *id != artifact)
312                    .filter(|id| dependency.contains(eval) || !own.contains(id))
313                    .collect::<BTreeSet<_>>()
314                    .into_iter()
315                    .collect();
316                deps.sort_by_key(|id| position.get(id).copied().unwrap_or(usize::MAX));
317                upstream.insert(eval, deps);
318            }
319        }
320        Self {
321            order,
322            position,
323            requests,
324            evals,
325            upstream,
326            dependency,
327            peers,
328            cyclic,
329        }
330    }
331}
332
333/// Lazily derived views over one Run; dependency evals may read peers in any order.
334pub(super) struct States<'a> {
335    run: &'a crate::store::Run,
336    #[cfg(any(test, feature = "test-support"))]
337    pub derivations: std::cell::Cell<usize>,
338    pub index: Index<'a>,
339    pub running: bool,
340    views: RefCell<HashMap<&'a EvalId, Option<EvalView>>>,
341    effective: RefCell<HashMap<&'a EvalId, Option<EvalStatus>>>,
342    completions: RefCell<HashMap<&'a ArtifactName, Completion>>,
343    waits: RefCell<HashMap<&'a ArtifactName, Vec<ArtifactName>>>,
344}
345
346impl<'a> States<'a> {
347    /// Derive every Artifact once, upstream before downstream, so each lookup afterwards
348    /// is memoized and no derivation recurses along a long dependency chain.
349    pub fn new(saved: &Saved<'a>) -> Self {
350        let run = &saved.run.run;
351        let states = Self {
352            run,
353            #[cfg(any(test, feature = "test-support"))]
354            derivations: std::cell::Cell::new(0),
355            index: Index::new(saved),
356            running: run.status() == RunStatus::Running,
357            views: RefCell::new(HashMap::new()),
358            effective: RefCell::new(HashMap::new()),
359            completions: RefCell::new(HashMap::new()),
360            waits: RefCell::new(HashMap::new()),
361        };
362        for artifact in states.upstream_first() {
363            for &eval in states.evals(artifact) {
364                states.effective(eval);
365            }
366            states.completion(artifact);
367            states.waits_of(artifact);
368        }
369        states
370    }
371
372    /// Every Artifact after the Artifacts its evals depend on (an iterative depth-first
373    /// post-order; a dependency eval naming a cycle peer may come either way).
374    fn upstream_first(&self) -> Vec<&'a ArtifactName> {
375        let mut done = HashSet::new();
376        let mut seen = HashSet::new();
377        let mut ordered = Vec::with_capacity(self.index.order.len());
378        for &start in &self.index.order {
379            if !seen.insert(start) {
380                continue;
381            }
382            let mut stack = vec![(start, self.dependencies(start), 0)];
383            while let Some((artifact, dependencies, next)) = stack.last_mut() {
384                if let Some(&dependency) = dependencies.get(*next) {
385                    *next += 1;
386                    if seen.insert(dependency) {
387                        let dependencies = self.dependencies(dependency);
388                        stack.push((dependency, dependencies, 0));
389                    }
390                } else {
391                    if done.insert(*artifact) {
392                        ordered.push(*artifact);
393                    }
394                    stack.pop();
395                }
396            }
397        }
398        ordered
399    }
400
401    fn dependencies(&self, artifact: &ArtifactName) -> Vec<&'a ArtifactName> {
402        let mut dependencies: Vec<&'a ArtifactName> = Vec::new();
403        for eval in self.evals(artifact) {
404            for &upstream in self.upstream(eval) {
405                if !dependencies.contains(&upstream) {
406                    dependencies.push(upstream);
407                }
408            }
409        }
410        dependencies
411    }
412
413    /// The Artifact id as borrowed from the index, for memo keys.
414    fn key(&self, artifact: &ArtifactName) -> Option<&'a ArtifactName> {
415        self.index
416            .position
417            .get_key_value(artifact)
418            .map(|(&id, _)| id)
419    }
420
421    pub fn request(&self, eval: &EvalId) -> Option<&'a RequestView> {
422        self.index.requests.get(eval).copied()
423    }
424
425    /// Evidence the graph used for an eval without a request in this Run: the Run's
426    /// record of saved results, or for older Runs the validation saved at their end.
427    fn outside(&self, eval: &EvalId) -> Option<RequestStatus> {
428        if let Some(status) = self.run.evidence.get(eval) {
429            return Some(*status);
430        }
431        let saved = self.run.validation.snapshot()?.evals.value()?;
432        let status = *saved
433            .iter()
434            .find(|saved| &saved.id == eval)?
435            .status
436            .value()?;
437        matches!(
438            status,
439            RequestStatus::Green | RequestStatus::Red | RequestStatus::Error | RequestStatus::Stale
440        )
441        .then_some(status)
442    }
443
444    pub fn evals(&self, artifact: &ArtifactName) -> &[&'a EvalId] {
445        self.index.evals.get(artifact).map_or(&[], Vec::as_slice)
446    }
447
448    pub fn upstream(&self, eval: &EvalId) -> &[&'a ArtifactName] {
449        self.index.upstream.get(eval).map_or(&[], Vec::as_slice)
450    }
451
452    fn position(&self, artifact: &ArtifactName) -> usize {
453        self.index
454            .position
455            .get(artifact)
456            .copied()
457            .unwrap_or(usize::MAX)
458    }
459
460    pub fn view(&self, eval: &'a EvalId) -> EvalView {
461        if let Some(view) = self.views.borrow().get(eval) {
462            // A wait cycle among dependency evals (rejected by graph.rs) reads as not run.
463            return view.clone().unwrap_or(EvalView::NotRun(NotRun::Unreviewed));
464        }
465        self.views.borrow_mut().insert(eval, None);
466        #[cfg(any(test, feature = "test-support"))]
467        self.derivations.set(self.derivations.get() + 1);
468        let view = self.derive(eval);
469        self.views.borrow_mut().insert(eval, Some(view.clone()));
470        view
471    }
472
473    /// The eval's status as `graph.rs` evaluates it for gates: a GREEN result whose own
474    /// gates are unmet still waits or is blocked, and never fulfils a downstream gate.
475    pub fn effective(&self, eval: &'a EvalId) -> EvalStatus {
476        if let Some(status) = self.effective.borrow().get(eval) {
477            // Dependency evals cannot wait on each other (graph.rs rejects it).
478            return status.unwrap_or(EvalStatus::Wait);
479        }
480        self.effective.borrow_mut().insert(eval, None);
481        #[cfg(any(test, feature = "test-support"))]
482        self.derivations.set(self.derivations.get() + 1);
483        let status = self.evaluate(eval);
484        self.effective.borrow_mut().insert(eval, Some(status));
485        status
486    }
487
488    /// `Graph::evaluate` over the saved snapshot: gate readiness first, then
489    /// the eval's own evidence.
490    fn evaluate(&self, eval: &'a EvalId) -> EvalStatus {
491        let request = self.request(eval).map(|view| &view.request);
492        let status = request
493            .map(|request| request.status())
494            .or_else(|| self.outside(eval));
495        let derived = self.index.dependency.contains(eval);
496        if derived && status == Some(RequestStatus::Error) {
497            return EvalStatus::Error;
498        }
499        let mut unmet = Vec::new();
500        {
501            for artifact in self.upstream(eval) {
502                for &gate in self.evals(artifact) {
503                    let status = self.effective(gate);
504                    if !status.passes_gate() {
505                        unmet.push(status);
506                    }
507                }
508            }
509        }
510        if unmet.iter().any(|status| {
511            matches!(status, EvalStatus::Red | EvalStatus::Blocked)
512                || (self.run.modes.speculate && *status == EvalStatus::Error)
513        }) {
514            return EvalStatus::Blocked;
515        }
516        if !unmet.is_empty() {
517            return EvalStatus::Wait;
518        }
519        if derived {
520            return EvalStatus::Green;
521        }
522        match status {
523            Some(RequestStatus::Deferred) => EvalStatus::Deferred,
524            Some(RequestStatus::Provisional) => EvalStatus::Provisional,
525            Some(RequestStatus::Green) => EvalStatus::Green,
526            Some(RequestStatus::Red) => EvalStatus::Red,
527            Some(RequestStatus::Error) => EvalStatus::Error,
528            Some(RequestStatus::Stale) => EvalStatus::Stale,
529            _ => EvalStatus::Unreviewed,
530        }
531    }
532
533    /// Every eval of the Artifact is effectively GREEN: it fulfils downstream gates.
534    fn gate_met(&self, artifact: &ArtifactName) -> bool {
535        self.evals(artifact)
536            .iter()
537            .all(|eval| self.effective(eval).passes_gate())
538    }
539
540    fn met(&self, artifact: &ArtifactName) -> bool {
541        self.evals(artifact)
542            .iter()
543            .all(|eval| self.effective(eval) == EvalStatus::Green)
544    }
545
546    fn derive(&self, eval: &'a EvalId) -> EvalView {
547        let Some(view) = self.request(eval) else {
548            // A saved result the Run used without a request of its own.
549            return match self.outside(eval) {
550                Some(RequestStatus::Green) => EvalView::Done(Source::Saved),
551                Some(RequestStatus::Red) => EvalView::Failed { verdict: true },
552                Some(RequestStatus::Error) => EvalView::Failed { verdict: false },
553                Some(RequestStatus::Stale) => EvalView::NotRun(NotRun::Stale),
554                _ => EvalView::NotRun(NotRun::Absent),
555            };
556        };
557        let request = &view.request;
558        let dependency = request.profile.kind() == ProfileKind::Dependency;
559        match request.status() {
560            RequestStatus::Provisional => EvalView::Done(Source::Provisional),
561            RequestStatus::Green => EvalView::Done(if dependency {
562                Source::Derived
563            } else if crate::query::reused(request) {
564                Source::Reused
565            } else {
566                Source::Executed
567            }),
568            RequestStatus::Red => EvalView::Failed { verdict: true },
569            RequestStatus::Error => EvalView::Failed { verdict: false },
570            RequestStatus::Running => EvalView::InProgress(Activity::Running),
571            RequestStatus::WaitingHuman | RequestStatus::Deferred => EvalView::InProgress(
572                Activity::Human(view.claim.as_ref().map(|claim| claim.reviewer.clone())),
573            ),
574            RequestStatus::BudgetExhausted => EvalView::NotRun(NotRun::Budget),
575            RequestStatus::Stale => EvalView::NotRun(NotRun::Stale),
576            RequestStatus::Queued
577            | RequestStatus::WaitDependency
578            | RequestStatus::Unreviewed
579            | RequestStatus::Blocked => {
580                let unmet = self.unmet(self.upstream(eval));
581                let blocking: Vec<_> = unmet
582                    .iter()
583                    .filter(|artifact| self.blocks(artifact))
584                    .cloned()
585                    .collect();
586                if !blocking.is_empty() {
587                    return EvalView::BlockedBy(blocking);
588                }
589                if self.run.modes.speculate && !dependency {
590                    return self.ready(view);
591                }
592                if !unmet.is_empty() {
593                    let waits = Waits {
594                        root: self.root(&unmet),
595                        artifacts: unmet,
596                    };
597                    return if self.running {
598                        EvalView::WaitingOn(waits)
599                    } else {
600                        EvalView::NotRun(NotRun::Dependency(waits))
601                    };
602                }
603                if dependency {
604                    return EvalView::Done(Source::Derived);
605                }
606                self.ready(view)
607            }
608        }
609    }
610
611    /// Nothing upstream holds the eval.
612    fn ready(&self, view: &RequestView) -> EvalView {
613        let request = &view.request;
614        if !self.running || request.status() != RequestStatus::Queued {
615            return EvalView::NotRun(NotRun::Unreviewed);
616        }
617        EvalView::InProgress(Activity::Queued(
618            if let Some(crate::store::QueueReason::Slot { backend }) = request.queue {
619                Queue::Slot(backend)
620            } else if request.execution_id.is_some() {
621                Queue::Joined
622            } else if self.run.jobs.is_some() {
623                Queue::Jobs
624            } else {
625                Queue::Ready
626            },
627        ))
628    }
629
630    /// Dependency Artifacts whose gates are not met, most actionable first.
631    pub fn unmet(&self, artifacts: &[&'a ArtifactName]) -> Vec<crate::types::ArtifactName> {
632        let mut unmet: Vec<_> = artifacts
633            .iter()
634            .filter(|artifact| !self.gate_met(artifact))
635            .map(|artifact| (*artifact, self.completion(artifact)))
636            .collect();
637        unmet.sort_by_key(|(artifact, completion)| (completion.rank(), self.position(artifact)));
638        unmet
639            .into_iter()
640            .map(|(artifact, _)| artifact.clone())
641            .collect()
642    }
643
644    fn views_of(&self, artifact: &ArtifactName) -> Vec<EvalView> {
645        self.evals(artifact)
646            .iter()
647            .map(|eval| self.view(eval))
648            .collect()
649    }
650
651    /// The Artifact's completion from its eval rows; done rows whose own gates are unmet
652    /// do not complete it.
653    pub fn completion(&self, artifact: &ArtifactName) -> Completion {
654        if let Some(completion) = self.completions.borrow().get(artifact) {
655            return *completion;
656        }
657        let views = self.views_of(artifact);
658        let busy = views
659            .iter()
660            .filter_map(|view| match view {
661                EvalView::InProgress(Activity::Running) => Some(Busy::Running),
662                EvalView::InProgress(Activity::Human(_)) => Some(Busy::Human),
663                EvalView::InProgress(Activity::Queued(_)) => Some(Busy::Queued),
664                _ => None,
665            })
666            .min();
667        let completion = if views
668            .iter()
669            .any(|view| matches!(view, EvalView::Failed { .. }))
670        {
671            // ERROR before RED, like the Artifact's own row; blocking follows `blocks`.
672            Completion::Failed {
673                verdict: !views.contains(&EvalView::Failed { verdict: false }),
674            }
675        } else if let Some(busy) = busy {
676            Completion::InProgress(busy)
677        } else if views.iter().any(|view| !matches!(view, EvalView::Done(_))) || !self.met(artifact)
678        {
679            Completion::Waiting {
680                blocked: self.blocks(artifact)
681                    || views
682                        .iter()
683                        .any(|view| matches!(view, EvalView::BlockedBy(_))),
684            }
685        } else {
686            Completion::Complete
687        };
688        if let Some(id) = self.key(artifact) {
689            self.completions.borrow_mut().insert(id, completion);
690        }
691        completion
692    }
693
694    /// The graph's Blocked readiness downstream: an effective RED or blocked eval.
695    fn blocks(&self, artifact: &ArtifactName) -> bool {
696        self.evals(artifact).iter().any(|eval| {
697            matches!(self.effective(eval), EvalStatus::Red | EvalStatus::Blocked)
698                || (self.run.modes.speculate && self.effective(eval) == EvalStatus::Error)
699        })
700    }
701
702    /// Done evals whose own gates are unmet: the Artifacts holding them back.
703    fn held(&self, artifact: &ArtifactName, blocked: bool) -> Vec<crate::types::ArtifactName> {
704        let mut x: Vec<crate::types::ArtifactName> = Vec::new();
705        for &eval in self.evals(artifact) {
706            let wanted = if blocked {
707                EvalStatus::Blocked
708            } else {
709                EvalStatus::Wait
710            };
711            if matches!(self.view(eval), EvalView::Done(_)) && self.effective(eval) == wanted {
712                for id in self.unmet(self.upstream(eval)) {
713                    if (!blocked || self.blocks(&id)) && !x.contains(&id) {
714                        x.push(id);
715                    }
716                }
717            }
718        }
719        x
720    }
721
722    /// The Artifacts this Artifact's evals wait for, as one X list.
723    pub fn waits_of(&self, artifact: &ArtifactName) -> Vec<crate::types::ArtifactName> {
724        if let Some(x) = self.waits.borrow().get(artifact) {
725            return x.clone();
726        }
727        let mut x = self.held(artifact, false);
728        for view in self.views_of(artifact) {
729            if let EvalView::WaitingOn(waits) | EvalView::NotRun(NotRun::Dependency(waits)) = view {
730                for id in waits.artifacts {
731                    if !x.contains(&id) {
732                        x.push(id);
733                    }
734                }
735            }
736        }
737        x.sort_by_key(|id| (self.completion(id).rank(), self.position(id)));
738        if let Some(id) = self.key(artifact) {
739            self.waits.borrow_mut().insert(id, x.clone());
740        }
741        x
742    }
743
744    /// The Artifacts this Artifact's blocked evals are blocked by.
745    pub fn blockers_of(&self, artifact: &ArtifactName) -> Vec<crate::types::ArtifactName> {
746        let mut x = self.held(artifact, true);
747        for view in self.views_of(artifact) {
748            if let EvalView::BlockedBy(blockers) = view {
749                for id in blockers {
750                    if !x.contains(&id) {
751                        x.push(id);
752                    }
753                }
754            }
755        }
756        x
757    }
758
759    /// When every X is itself waiting, exactly one level of cause: what the first X waits for.
760    fn root(
761        &self,
762        x: &[crate::types::ArtifactName],
763    ) -> Option<(crate::types::ArtifactName, crate::types::ArtifactName)> {
764        if x.iter()
765            .any(|id| !matches!(self.completion(id), Completion::Waiting { blocked: false }))
766        {
767            return None;
768        }
769        let via = x.first()?.clone();
770        let cause = self.waits_of(&via).into_iter().next()?;
771        Some((via, cause))
772    }
773
774    /// Every dependency Artifact of the eval, unmet ones first in X order.
775    pub fn upstream_states(&self, eval: &EvalId) -> Vec<Upstream> {
776        let mut upstream: Vec<_> = self
777            .upstream(eval)
778            .iter()
779            .map(|artifact| Upstream {
780                artifact: (*artifact).clone(),
781                completion: self.completion(artifact),
782            })
783            .collect();
784        upstream.sort_by_key(|upstream| {
785            (
786                upstream.completion.rank(),
787                self.position(&upstream.artifact),
788            )
789        });
790        upstream
791    }
792}