Skip to main content

trellis_core/
oracle.rs

1use crate::{Graph, GraphError, GraphResult, NodeId, OutputKey, ResourceKey};
2use std::collections::BTreeMap;
3
4/// Result of comparing incremental graph state against full recompute.
5#[derive(Clone, Debug, Eq, PartialEq)]
6pub struct FullRecomputeCheck {
7    /// Derived nodes checked in deterministic topological order.
8    pub checked_derived: Vec<NodeId>,
9    /// Collection nodes checked in deterministic topological order.
10    pub checked_collections: Vec<NodeId>,
11    /// Desired resource keys whose owner sets were checked.
12    pub checked_resources: Vec<ResourceKey>,
13    /// Materialized outputs whose current values were checked.
14    pub checked_outputs: Vec<OutputKey>,
15}
16
17impl<C, O: Clone> Graph<C, O> {
18    /// Recomputes supported graph state from canonical inputs and compares it.
19    pub fn full_recompute(&self) -> GraphResult<FullRecomputeCheck>
20    where
21        O: PartialEq,
22    {
23        self.full_recompute_check()
24    }
25
26    /// Asserts that incremental state equals a supported full recompute.
27    pub fn assert_incremental_equals_full(&self) -> GraphResult<FullRecomputeCheck>
28    where
29        O: PartialEq,
30    {
31        self.full_recompute_check()
32    }
33
34    /// Compares committed incremental state against full recompute.
35    pub fn full_recompute_check(&self) -> GraphResult<FullRecomputeCheck>
36    where
37        O: PartialEq,
38    {
39        let mut full = self.clone();
40        full.derived_values.clear();
41        full.collection_values.clear();
42        full.previous_collection_values.clear();
43        full.collection_diffs.clear();
44        full.resource_owners.clear();
45        full.output_values.clear();
46        let order = full.derived_topological_order()?;
47
48        for node in &order {
49            let dependencies = full
50                .nodes
51                .get(node)
52                .expect("derived node metadata exists")
53                .dependencies()
54                .clone();
55            let value = full.compute_derived(*node, dependencies.as_slice())?;
56            full.derived_values.insert(*node, value);
57        }
58
59        for node in &order {
60            let incremental = self
61                .derived_values
62                .get(node)
63                .ok_or(GraphError::FullRecomputeMismatch(*node))?;
64            let recomputed = full
65                .derived_values
66                .get(node)
67                .ok_or(GraphError::FullRecomputeMismatch(*node))?;
68            if !incremental.equals(recomputed.as_ref()) {
69                return Err(GraphError::FullRecomputeMismatch(*node));
70            }
71        }
72
73        let collection_order = full.collection_topological_order()?;
74        let all_nodes: Vec<NodeId> = full.nodes.keys().copied().collect();
75        full.recompute_dirty_collections(&all_nodes)?;
76        self.compare_full_recomputed_collections(&full, &collection_order)?;
77        let checked_resources = self.compare_full_recomputed_resources(&mut full)?;
78        let checked_outputs = self.compare_full_recomputed_outputs(&mut full, &all_nodes)?;
79
80        Ok(FullRecomputeCheck {
81            checked_derived: order,
82            checked_collections: collection_order,
83            checked_resources,
84            checked_outputs,
85        })
86    }
87
88    fn compare_full_recomputed_resources(
89        &self,
90        full: &mut Graph<C, O>,
91    ) -> GraphResult<Vec<ResourceKey>> {
92        let planner_collections: Vec<NodeId> = full
93            .resource_planners
94            .iter()
95            .map(|planner| planner.collection)
96            .collect();
97        full.baseline_collection_diffs(&planner_collections);
98        full.produce_resource_plan(&[])?;
99        if self.resource_owners != full.resource_owners {
100            let node = planner_collections
101                .into_iter()
102                .next()
103                .unwrap_or_else(|| NodeId::from_index(1));
104            return Err(GraphError::FullRecomputeMismatch(node));
105        }
106        Ok(self.resource_owners.keys().cloned().collect())
107    }
108
109    fn compare_full_recomputed_outputs(
110        &self,
111        full: &mut Graph<C, O>,
112        all_nodes: &[NodeId],
113    ) -> GraphResult<Vec<OutputKey>>
114    where
115        O: PartialEq,
116    {
117        full.produce_output_frames(
118            all_nodes,
119            &[],
120            &BTreeMap::new(),
121            self.next_transaction_id,
122            self.revision,
123        )?;
124        if self.output_values != full.output_values {
125            let node = self
126                .outputs
127                .values()
128                .flat_map(|meta| meta.dependencies().as_slice().iter().copied())
129                .next()
130                .unwrap_or_else(|| NodeId::from_index(1));
131            return Err(GraphError::FullRecomputeMismatch(node));
132        }
133        Ok(self.output_values.keys().copied().collect())
134    }
135}