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}