Skip to main content

artifactize_core/
query.rs

1//! Read-only state queries and result projections.
2
3mod summary;
4pub use summary::{
5    Kinds, Source, SourceKind, profile_name, request_output, request_summary, reused, run_output,
6    run_summary, source,
7};
8
9use std::{collections::BTreeMap, path::Path};
10
11use serde::Serialize;
12
13use crate::{
14    config::{Artifact, Eval, RepoConfig},
15    graph::Graph,
16    project::selection::Selection,
17    scope::Relation,
18};
19
20/// The format version of the printed graph: the shape of its Artifacts, evals, relations
21/// and components. It changes when a field is removed or changes meaning, so that scripts
22/// reading the output can refuse a shape they do not know.
23const GRAPH_FORMAT_VERSION: u32 = 1;
24
25#[derive(Debug, Serialize)]
26#[serde(rename_all = "camelCase")]
27pub struct GraphView<'a> {
28    pub version: u32,
29    #[serde(serialize_with = "crate::platform::path_serde::serialize")]
30    pub repo_path: &'a Path,
31    pub selection: &'a Selection,
32    pub artifacts: BTreeMap<&'a crate::config::ArtifactName, &'a Artifact>,
33    pub evals: Vec<&'a Eval>,
34    pub relations: Vec<GraphRelation<'a>>,
35    pub components: Vec<GraphComponent<'a>>,
36}
37
38#[derive(Debug, Serialize)]
39pub struct GraphRelation<'a> {
40    #[serde(flatten)]
41    pub relation: &'a Relation,
42    pub cyclic: bool,
43}
44
45#[derive(Debug, Serialize)]
46pub struct GraphComponent<'a> {
47    pub id: crate::graph::ComponentId,
48    pub artifacts: Vec<&'a crate::config::ArtifactName>,
49    pub dependencies: Vec<crate::graph::ComponentId>,
50    pub gates: Vec<&'a crate::config::EvalId>,
51    pub cyclic: bool,
52}
53
54/// Full static definitions, restricted to the selected dependency closure.
55pub fn graph<'a>(
56    config: &'a RepoConfig,
57    selection: &'a Selection,
58) -> Result<GraphView<'a>, String> {
59    let graph = Graph::new(config).map_err(|error| error.to_string())?;
60    let selected = selection.resolve(config)?;
61    let required = graph
62        .dependency_closure(&selected.roots)
63        .map_err(|error| error.to_string())?;
64    let artifacts: BTreeMap<_, _> = required
65        .iter()
66        .map(|&id| (id, &config.artifacts[id]))
67        .collect();
68    let evals = config
69        .evals
70        .iter()
71        .filter(|eval| artifacts.contains_key(&eval.target))
72        .collect();
73    let relations: Vec<_> = config
74        .relations
75        .iter()
76        .filter(|relation| {
77            artifacts.contains_key(&relation.source) && artifacts.contains_key(&relation.target)
78        })
79        .map(|relation| GraphRelation {
80            relation,
81            cyclic: graph.artifacts()[relation.source.as_str()].component
82                == graph.artifacts()[relation.target.as_str()].component,
83        })
84        .collect();
85    let components = graph
86        .components()
87        .iter()
88        .enumerate()
89        .filter(|(_, component)| artifacts.contains_key(component.artifacts[0]))
90        .map(|(id, component)| GraphComponent {
91            id: crate::graph::ComponentId::from_position(id),
92            artifacts: component.artifacts.clone(),
93            dependencies: component.dependencies.clone(),
94            gates: component.gates.clone(),
95            cyclic: component.artifacts.len() > 1
96                || relations.iter().any(|edge| {
97                    edge.cyclic && component.artifacts.contains(&&edge.relation.source)
98                }),
99        })
100        .collect();
101    Ok(GraphView {
102        version: GRAPH_FORMAT_VERSION,
103        repo_path: &config.root,
104        selection,
105        artifacts,
106        evals,
107        relations,
108        components,
109    })
110}
111
112/// Conservative change impact: consumers of the selected roots, including cycle peers.
113/// This is structural reachability, not a prediction that every eval's reuse key changed.
114pub fn impact(config: &RepoConfig, selection: &Selection) -> Result<Selection, String> {
115    let resolved = selection.resolve(config)?;
116    let mut affected: std::collections::BTreeSet<_> = resolved.roots.into_iter().cloned().collect();
117    loop {
118        let consumers: Vec<_> = config
119            .relations
120            .iter()
121            .filter(|edge| affected.contains(&edge.source))
122            .map(|edge| edge.target.clone())
123            .collect();
124        let before = affected.len();
125        affected.extend(consumers);
126        if affected.len() == before {
127            break;
128        }
129    }
130    Ok(Selection::Artifacts {
131        artifact_ids: affected.into_iter().collect(),
132    })
133}