Skip to main content

somatize_tree/
data.rs

1//! What data sits under each version, and what belongs to none of them.
2//!
3//! Iterating five versions of one question in an afternoon leaves five sets of
4//! intermediates in the store, and a month later nobody can say which was
5//! whose. Nothing is written down to answer that: a probe already says what
6//! every node's answer will be called, so attribution is two questions to the
7//! store and no index anybody has to keep up to date.
8//!
9//! Two ways to attribute, kept both because they say different things. *By
10//! key*: a key that matches means this is exactly the value that version would
11//! ask for — exact, and fragile in one place, since a key is computed against
12//! the probing interpreter's environment, so probing a three-month-old commit
13//! today gives keys that match nothing stored back then. *By fingerprint*:
14//! whoever ran wrote which node and which code version produced each value, so
15//! it answers about old data, which is what nobody can attribute from memory.
16//!
17//! A value whose fingerprint belongs to no version nameable here comes out
18//! anyway, saying so. Keeping it quiet would let the mute hashes back in
19//! through the back door.
20
21use crate::snapshot::Snapshot;
22use somatize_core::Key;
23use somatize_store::{Bound, Store};
24use std::collections::{BTreeMap, HashMap};
25
26/// How a value was found to belong to a version.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
28#[serde(rename_all = "lowercase")]
29pub enum How {
30    /// Named as that version will name that node: the value it would ask for,
31    /// not one like it.
32    Named,
33    /// Produced by that version's code, per what whoever ran wrote beside it.
34    /// Survives the environment of back then no longer existing.
35    Written,
36}
37
38/// A value in the store, and which version it turned out to be from.
39#[derive(Debug, Clone, serde::Serialize)]
40pub struct Belongs {
41    /// The name it is bound under in the store.
42    pub name: String,
43    /// Which node produced it, if that was said.
44    pub node: Option<String>,
45    /// Which version of the code, if that was said.
46    pub fingerprint: Option<String>,
47    /// With what input, by the name its content has.
48    pub input: Option<String>,
49    /// Against what environment, by its short name.
50    pub environment: Option<String>,
51    /// When it was bound, in seconds since the epoch.
52    pub when: u64,
53    /// Which commits it is from, and how that is known. Empty is an answer:
54    /// from none that can be named here.
55    pub of: BTreeMap<String, How>,
56}
57
58impl Belongs {
59    /// Whether it turned out to be from none of the versions asked about.
60    ///
61    /// **Not the same as being spare**: it may be from a branch nobody looked
62    /// at, a commit that is gone, or an environment that cannot be reproduced.
63    pub fn is_nobodys(&self) -> bool {
64        self.of.is_empty()
65    }
66}
67
68/// What is in the store, attributed to the versions passed in.
69///
70/// One walk of the store and not one blob read: what is needed is in the
71/// record, which is this store's cost rule from the first day.
72pub fn under(
73    store: &dyn Store,
74    known: &HashMap<&str, Snapshot>,
75) -> Result<Vec<Belongs>, Box<dyn std::error::Error>> {
76    // Both indices inverted once, rather than walking the versions per value:
77    // with forty commits and a few thousand values that is the same work done
78    // thousands of times.
79    let mut by_name: HashMap<&str, Vec<&str>> = HashMap::new();
80    let mut by_code: HashMap<(&str, &str), Vec<&str>> = HashMap::new();
81    let names: Vec<(&str, BTreeMap<String, String>)> = known
82        .iter()
83        .map(|(commit, taken)| (*commit, taken.names()))
84        .collect();
85    let codes: Vec<(&str, BTreeMap<String, String>)> = known
86        .iter()
87        .map(|(commit, taken)| (*commit, taken.fingerprints()))
88        .collect();
89    // A key names the **recipe**; a name is where that recipe's value is bound,
90    // and they are not the same string. The store translates, so it is asked
91    // rather than having its `format!` copied here.
92    let bound_as: Vec<(&str, Vec<String>)> = names
93        .iter()
94        .map(|(commit, said)| {
95            (
96                *commit,
97                said.values()
98                    .map(|key| somatize_store::name_of(&Key::new(key.clone())))
99                    .collect(),
100            )
101        })
102        .collect();
103    for (commit, said) in &bound_as {
104        for name in said {
105            by_name.entry(name.as_str()).or_default().push(commit);
106        }
107    }
108    for (commit, said) in &codes {
109        for (node, written) in said {
110            by_code
111                .entry((node.as_str(), written.as_str()))
112                .or_default()
113                .push(commit);
114        }
115    }
116
117    let mut said: Vec<Belongs> = store
118        .bound()?
119        .into_iter()
120        .filter(|bound| !bookkeeping(bound))
121        .map(|bound| {
122            let meta = |what: &str| {
123                bound
124                    .meta
125                    .iter()
126                    .find(|(said, _)| said == what)
127                    .map(|(_, told)| told.clone())
128            };
129            let (node, fingerprint) = (meta(somatize_core::NODE), meta(somatize_core::FINGERPRINT));
130            let mut of: BTreeMap<String, How> = BTreeMap::new();
131            // Fingerprint first and key after, so `Named` wins where both
132            // hold: it is the stronger of the two.
133            if let (Some(node), Some(written)) = (&node, &fingerprint) {
134                for commit in by_code
135                    .get(&(node.as_str(), written.as_str()))
136                    .into_iter()
137                    .flatten()
138                {
139                    of.insert((*commit).to_string(), How::Written);
140                }
141            }
142            for commit in by_name.get(bound.name.as_str()).into_iter().flatten() {
143                of.insert((*commit).to_string(), How::Named);
144            }
145            Belongs {
146                name: bound.name.clone(),
147                node,
148                fingerprint,
149                input: meta(somatize_core::INPUT),
150                environment: meta(ENVIRONMENT),
151                when: bound.when,
152                of,
153            }
154        })
155        .collect();
156    said.sort_by(|a, b| (b.when, &a.name).cmp(&(a.when, &b.name)));
157    Ok(said)
158}
159
160/// What the environment a value was produced against is called in its `meta`.
161///
162/// The word is `somatize._environment`'s and not the engine's, so it is not
163/// among the core's constants. Written here once and not at every reader.
164pub const ENVIRONMENT: &str = "env";
165
166/// What is not a run's data but the bookkeeping of whoever looks.
167///
168/// Three writers share this store and only one leaves intermediates: `exp/…`
169/// is this tool's own notebook and carries the commit in its name, `snapshot:…`
170/// is its probe cache, and `env/…` is a reading of an environment that soma
171/// writes so the short name values carry can be understood. Everything else is
172/// data, including what nobody turns out to own — a filter that kept only the
173/// recognised would be a listing that can never show the case that matters.
174fn bookkeeping(bound: &Bound) -> bool {
175    ["exp/", "snapshot:", "env/"]
176        .iter()
177        .any(|prefix| bound.name.starts_with(prefix))
178}