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