use std::path::{Path, PathBuf};
use triblespace::core::repo::StoreRead;
use crate::schemas::embeddings::Embedding768;
use anybytes::View;
use anyhow::{anyhow, Context, Result};
use mary::model_collection::ModelSnapshot;
use mary::selection::{ModelSelector, TokenizerSelector};
use triblespace::core::collection::CollectionHandle;
use triblespace::macros::{entity, find, pattern};
use triblespace::prelude::inlineencodings::Handle;
use triblespace::prelude::*;
pub const NOMIC_TEXT_MODEL: &str = "nomic-ai/nomic-embed-text-v1.5";
pub const NOMIC_VISION_MODEL: &str = "nomic-ai/nomic-embed-vision-v1.5";
fn working_pile() -> Result<PathBuf> {
std::env::var_os("PILE")
.map(PathBuf::from)
.ok_or_else(|| anyhow!("PILE is not set; the nomic models are read from the working pile"))
}
const NOMIC_QUANTIZATIONS: [&str; 2] = ["nvfp4-calibrated", mary::persist::QUANTIZATION_NATIVE];
fn select_weights<R: BlobStoreGet>(
snapshot: &ModelSnapshot<R>,
source: &str,
pile: &Path,
) -> Result<std::collections::HashMap<String, (Vec<f32>, Vec<usize>)>> {
for quantization in NOMIC_QUANTIZATIONS {
let roots = mary::selection::matching_model_roots_acquiring(
snapshot.facts(), snapshot.store(),
ModelSelector::Source { source, quantization },
)?;
if roots.is_empty() {
continue;
}
return mary::selection::load_keymap_for_roots(
snapshot.facts(), snapshot.store(), &roots,
).with_context(|| format!(
"select {quantization} {source} weights from {}", pile.display(),
));
}
Err(anyhow!(
"{} carries no {source} root labelled {} (pack it in with nomic_pack --append)",
pile.display(), NOMIC_QUANTIZATIONS.join(" or "),
))
}
pub fn load_text_embedder() -> Result<mary::embed::NomicTextEmbedder<mary::nn::backend::B>> {
let pile = working_pile()?;
crate::model_storage::with_snapshot(&pile, NOMIC_TEXT_MODEL, |snapshot| text_embedder_from(snapshot, &pile))
}
pub fn load_text_embedder_in<R: StoreRead>(
store: &R,
) -> Result<mary::embed::NomicTextEmbedder<mary::nn::backend::B>> {
let snapshot = mary::model_collection::snapshot_model_collection_acquiring_in(store)
.context("freeze the working pile's model collection for nomic-embed-text")?;
text_embedder_from(&snapshot, Path::new("the working pile"))
}
fn text_embedder_from<R: BlobStoreGet>(
snapshot: &ModelSnapshot<R>,
pile: &Path,
) -> Result<mary::embed::NomicTextEmbedder<mary::nn::backend::B>> {
let keymap = select_weights(snapshot, NOMIC_TEXT_MODEL, pile)?;
let tokenizer_root = mary::selection::select_tokenizer_root_acquiring(
snapshot.facts(),
snapshot.store(),
TokenizerSelector::Name(NOMIC_TEXT_MODEL),
)?;
let tokenizer = mary::selection::load_tokenizer_from_graph(
snapshot.facts(),
snapshot.store(),
TokenizerSelector::Root(tokenizer_root),
)
.with_context(|| format!("select native Nomic text tokenizer from {}", pile.display()))?;
mary::embed::nomic_text_from_parts(keymap, tokenizer, mary::embed::default_device())
.with_context(|| {
format!(
"build Nomic text embedder from native collection {}",
pile.display()
)
})
}
pub fn load_vision_embedder() -> Result<mary::embed::NomicVisionEmbedder<mary::nn::backend::B>> {
let pile = working_pile()?;
crate::model_storage::with_snapshot(&pile, NOMIC_VISION_MODEL, |snapshot| vision_embedder_from(snapshot, &pile))
}
pub fn load_vision_embedder_in<R: StoreRead>(
store: &R,
) -> Result<mary::embed::NomicVisionEmbedder<mary::nn::backend::B>> {
let snapshot = mary::model_collection::snapshot_model_collection_acquiring_in(store)
.context("freeze the working pile's model collection for nomic-embed-vision")?;
vision_embedder_from(&snapshot, Path::new("the working pile"))
}
fn vision_embedder_from<R: BlobStoreGet>(
snapshot: &ModelSnapshot<R>,
pile: &Path,
) -> Result<mary::embed::NomicVisionEmbedder<mary::nn::backend::B>> {
let keymap = select_weights(snapshot, NOMIC_VISION_MODEL, pile)?;
mary::embed::load_nomic_vision_from_keymap(keymap, mary::embed::default_device()).with_context(
|| {
format!(
"build Nomic vision embedder from native collection {}",
pile.display()
)
},
)
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IndexModels {
pub collection: CollectionHandle,
pub text_root: triblespace::core::id::Id,
pub vision_root: triblespace::core::id::Id,
pub tokenizer_root: triblespace::core::id::Id,
}
pub fn index_models_in<R: StoreRead>(store: &R) -> Result<IndexModels> {
let snapshot = mary::model_collection::snapshot_model_collection_acquiring_in(store)
.context("freeze the working pile's model collection for the semantic index")?;
index_models_from(&snapshot)
}
fn index_models_from<R: BlobStoreGet>(snapshot: &ModelSnapshot<R>) -> Result<IndexModels> {
let text_root = preferred_root(snapshot, NOMIC_TEXT_MODEL)?;
let vision_root = preferred_root(snapshot, NOMIC_VISION_MODEL)?;
let tokenizer_root = mary::selection::select_tokenizer_root_acquiring(
snapshot.facts(),
snapshot.store(),
TokenizerSelector::Name(NOMIC_TEXT_MODEL),
)
.context("select the semantic index's text tokenizer root")?;
Ok(IndexModels {
collection: snapshot.support().collection().handle(),
text_root,
vision_root,
tokenizer_root,
})
}
fn preferred_root<R: BlobStoreGet>(snapshot: &ModelSnapshot<R>, source: &str) -> Result<triblespace::core::id::Id> {
for quantization in NOMIC_QUANTIZATIONS {
let roots = mary::selection::matching_model_roots_acquiring(
snapshot.facts(),
snapshot.store(),
ModelSelector::Source {
source,
quantization,
},
)?;
match roots.as_slice() {
[root] => return Ok(*root),
[] => continue,
many => {
return Err(anyhow!(
"the working pile carries {} {quantization} roots of {source}; one is needed",
many.len()
))
}
}
}
Err(anyhow!(
"the working pile carries no {source} root labelled {} (pack it in with nomic_pack --append)",
NOMIC_QUANTIZATIONS.join(" or ")
))
}
#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::SigningKey;
use mary::format::{attrs, F32Array, U64Array};
use tempfile::NamedTempFile;
use triblespace::core::repo::pile::Pile;
use triblespace::prelude::blobencodings::UTF8String;
use triblespace::prelude::*;
const WORDPIECE: &str = r###"{
"added_tokens": [],
"normalizer": {"type": "BertNormalizer", "clean_text": true,
"handle_chinese_chars": true, "strip_accents": null,
"lowercase": true},
"pre_tokenizer": {"type": "BertPreTokenizer"},
"decoder": {"type": "WordPiece", "prefix": "##", "cleanup": true},
"model": {"type": "WordPiece", "unk_token": "[UNK]",
"continuing_subword_prefix": "##",
"max_input_chars_per_word": 100,
"vocab": {"[UNK]": 0, "hello": 1}}
}"###;
#[test]
fn preferred_quantization_never_falls_back_after_a_label_read_failure() {
use triblespace::core::blob::{Blob, IntoBlob};
use triblespace::core::collection::{AdmissionPolicy, CollectionPolicy, CollectionStoreExt};
use triblespace::core::repo::memoryrepo::MemoryRepo;
let mut repo = MemoryRepo::default();
let key = SigningKey::from_bytes(&[37; 32]);
let collection = repo.collection(
mary::model_collection::mary_model_graph_name(),
CollectionPolicy::new(
AdmissionPolicy::direct(key.verifying_key()),
AdmissionPolicy::direct(key.verifying_key()),
),
).unwrap();
let native = weight_fragment(NOMIC_TEXT_MODEL, "native.weight", 1.0);
repo.commit(collection, &key, native).unwrap();
let absent: Blob<UTF8String> = "an unread packed-model label".to_owned().to_blob();
repo.commit(collection, &key, entity! {
attrs::source: absent.get_handle(),
attrs::quantization: NOMIC_QUANTIZATIONS[0],
attrs::member: fucid(),
}).unwrap();
let frozen = repo.snapshot().unwrap();
let snapshot = mary::model_collection::snapshot_model_collection_acquiring_in(&frozen).unwrap();
let error = preferred_root(&snapshot, NOMIC_TEXT_MODEL).unwrap_err();
assert!(error.to_string().contains("read source label"));
assert!(select_weights(&snapshot, NOMIC_TEXT_MODEL, Path::new("fixture")).is_err());
repo.put::<UTF8String, _>(absent).unwrap();
let available = repo.snapshot().unwrap();
let snapshot = mary::model_collection::snapshot_model_collection_acquiring_in(&available).unwrap();
assert!(preferred_root(&snapshot, NOMIC_TEXT_MODEL).is_ok());
}
fn weight_fragment(source: &str, tensor_name: &str, value: f32) -> Fragment {
let mut fragment = Fragment::empty();
let data = fragment.put::<F32Array, _>(vec![value]);
let shape = fragment.put::<U64Array, _>(vec![1_u64]);
let leaf = entity! { _ @ attrs::data: data, attrs::shape: shape };
let leaf_id = leaf.root().expect("tensor leaf root");
fragment += leaf;
let tensor_name = fragment.put::<UTF8String, _>(tensor_name.to_owned());
let member = entity! { _ @
attrs::safetensor_path: tensor_name,
attrs::weight: &leaf_id,
};
let member_id = member.root().expect("model member root");
fragment += member;
let model_name = fragment.put::<UTF8String, _>(format!("{source}.safetensors"));
let source = fragment.put::<UTF8String, _>(source.to_owned());
fragment += entity! { _ @
attrs::model_name: model_name,
attrs::source: source,
attrs::quantization: mary::persist::QUANTIZATION_NATIVE,
attrs::member: &member_id,
};
fragment
}
fn tokenizer_fragment() -> Fragment {
let mut fragment = Fragment::empty();
let tokenizer = mary::tokenizer::save_tokenizer_json(
WORDPIECE.as_bytes(),
NOMIC_TEXT_MODEL,
fragment.blobs_mut(),
)
.expect("build synthetic tokenizer graph");
fragment += tokenizer;
fragment
}
fn publish(path: &Path, fragments: impl IntoIterator<Item = Fragment>) {
let mut fragments = fragments.into_iter();
let Some(first) = fragments.next() else {
return;
};
let root = SigningKey::from_bytes(&[0x30; 32]);
let mut pile = Pile::open(path).expect("open synthetic model pile");
mary::model_collection::publish_model_fragment(&mut pile, &root, first)
.expect("publish fixture root fragment");
for (index, fragment) in fragments.enumerate() {
let signer = SigningKey::from_bytes(&[0x31 + index as u8; 32]);
let collection =
mary::model_collection::model_graph_collection_or_create(&mut pile, &root)
.expect("open synthetic model policy collection");
triblespace::core::collection::grant_collection_write(
&mut pile,
collection.handle(),
&root,
signer.verifying_key(),
)
.expect("grant fixture writer");
mary::model_collection::publish_model_fragment(&mut pile, &signer, fragment)
.expect("publish native model fragment");
}
pile.close().expect("close synthetic model pile");
}
#[test]
fn one_native_snapshot_selects_each_nomic_runtime_graph() {
let text_file = NamedTempFile::new().expect("create text pile");
publish(
text_file.path(),
[
weight_fragment(NOMIC_TEXT_MODEL, "text.weight", 1.25),
tokenizer_fragment(),
],
);
crate::model_storage::with_snapshot(text_file.path(), NOMIC_TEXT_MODEL, |text| {
assert_eq!(text.support().len(), 2);
publish(
text_file.path(),
[weight_fragment(NOMIC_TEXT_MODEL, "text.weight", 9.0)],
);
let text_keymap = mary::selection::load_keymap_from_graph(
text.facts(),
text.store(),
ModelSelector::Source {
source: NOMIC_TEXT_MODEL,
quantization: mary::persist::QUANTIZATION_NATIVE,
},
)
.expect("select text weights from frozen snapshot");
assert_eq!(text_keymap["text.weight"], (vec![1.25], vec![1]));
let tokenizer = mary::selection::load_tokenizer_from_graph(
text.facts(),
text.store(),
TokenizerSelector::Name(NOMIC_TEXT_MODEL),
)
.expect("select tokenizer from the same frozen snapshot");
assert_eq!(tokenizer.token_to_id("hello"), Some(1));
Ok(())
})
.expect("load and use one text collection snapshot with its owner alive");
crate::model_storage::with_snapshot(text_file.path(), NOMIC_TEXT_MODEL, |widened| {
let collision = mary::selection::load_keymap_from_graph(
widened.facts(),
widened.store(),
ModelSelector::Source {
source: NOMIC_TEXT_MODEL,
quantization: mary::persist::QUANTIZATION_NATIVE,
},
)
.expect_err("later shard with a duplicate tensor must fail closed");
assert!(
collision.to_string().contains("appears in both root"),
"unexpected collision diagnostic: {collision}"
);
Ok(())
})
.expect("load later widened text snapshot");
let vision_file = NamedTempFile::new().expect("create vision pile");
publish(
vision_file.path(),
[weight_fragment(NOMIC_VISION_MODEL, "vision.weight", 2.5)],
);
crate::model_storage::with_snapshot(vision_file.path(), NOMIC_VISION_MODEL, |vision| {
assert_eq!(vision.support().len(), 1);
let vision_keymap = mary::selection::load_keymap_from_graph(
vision.facts(),
vision.store(),
ModelSelector::Source {
source: NOMIC_VISION_MODEL,
quantization: mary::persist::QUANTIZATION_NATIVE,
},
)
.expect("select vision weights from frozen snapshot");
assert_eq!(vision_keymap["vision.weight"], (vec![2.5], vec![1]));
Ok(())
})
.expect("load and use one vision collection snapshot with its owner alive");
}
#[test]
fn ordinary_runtime_source_has_no_legacy_storage_or_json_path() {
let source = include_str!("nomic.rs");
let runtime = source
.split("#[cfg(test)]")
.next()
.expect("runtime source precedes tests");
for forbidden in [
concat!("repo::", "Repository"),
concat!("Repository", "::"),
concat!("Workspace", "<"),
concat!("tokenizer", "_json"),
concat!("load_keymap_from_", "pile"),
concat!("load_tokenizer_from_", "pile"),
concat!("materialize_", "tokenizer"),
] {
assert!(
!runtime.contains(forbidden),
"ordinary Nomic runtime regained forbidden legacy seam {forbidden}"
);
}
let memory = include_str!("bin/memory.rs");
assert!(!memory.contains(concat!("import-", "tokenizer")));
assert!(!memory.contains(concat!("ingest-", "tokenizer")));
}
}
pub mod golden {
pub use crate::schemas::embeddings::golden::{image_embedding, text_embedding};
pub const TEXT: &str = "Golden text for the Files semantic index, recorded 2026-09-14: every device that publishes rows embeds this sentence first, and the vector it makes is compared to the one recorded on the model root.";
pub fn image_png() -> Vec<u8> {
let image = image::RgbImage::from_fn(224, 224, |x, y| {
image::Rgb([
((x * 37 + y * 11) % 256) as u8,
((x ^ y) % 256) as u8,
((x * y / 197) % 256) as u8,
])
});
let mut png = std::io::Cursor::new(Vec::new());
image
.write_to(&mut png, image::ImageFormat::Png)
.expect("encode the golden image as PNG in memory");
png.into_inner()
}
pub const FLOOR: f32 = 0.999;
}
pub struct GoldenRow {
pub model: &'static str,
pub root: triblespace::core::id::Id,
pub computed: Vec<f32>,
pub recorded: Vec<Vec<f32>>,
}
impl GoldenRow {
pub fn cosine(&self) -> Option<f32> {
self.recorded
.iter()
.map(|recorded| cosine(&self.computed, recorded))
.min_by(f32::total_cmp)
}
}
pub struct GoldenReport {
pub model_collection: CollectionHandle,
pub rows: Vec<GoldenRow>,
}
impl GoldenReport {
fn unrecorded_observations(&self) -> (Fragment, Vec<&'static str>) {
let mut fragment = Fragment::empty();
let mut recorded = Vec::new();
for row in &self.rows {
if !row.recorded.is_empty() {
continue;
}
let handle = fragment.put::<Embedding768, _>(row.computed.clone());
fragment += match row.model {
"text" => entity! {
mary::format::attrs::model_root: row.root,
golden::text_embedding: handle,
},
_ => entity! {
mary::format::attrs::model_root: row.root,
golden::image_embedding: handle,
},
};
recorded.push(row.model);
}
(fragment, recorded)
}
pub fn admit(&self) -> Result<()> {
for row in &self.rows {
match row.cosine() {
Some(cos) if cos < golden::FLOOR => anyhow::bail!(
"this device embeds the golden {} input to cosine {cos:.5} of a vector recorded for root {:X} (floor {}); it does not publish rows into an index computed elsewhere",
row.model,
row.root,
golden::FLOOR
),
Some(_) => {}
None => eprintln!(
"warning: no golden vector recorded for {} root {:X}; rows publish unverified (`files golden --publish` on the canonical device records one)",
row.model, row.root
),
}
}
Ok(())
}
}
pub fn cosine(a: &[f32], b: &[f32]) -> f32 {
let dot: f32 = a.iter().zip(b).map(|(x, y)| x * y).sum();
let na = a.iter().map(|x| x * x).sum::<f32>().sqrt();
let nb = b.iter().map(|x| x * x).sum::<f32>().sqrt();
if na == 0.0 || nb == 0.0 {
0.0
} else {
dot / (na * nb)
}
}
pub fn golden_report<R: StoreRead>(store: &R) -> Result<GoldenReport> {
use mary::embed::LocalEmbedder as _;
let snapshot = mary::model_collection::snapshot_model_collection_acquiring_in(store)
.context("freeze the working pile's model collection for the golden vectors")?;
let roots = index_models_from(&snapshot)?;
let facts = snapshot.facts();
let text = mary::embed::nomic_text_from_parts(
mary::selection::load_keymap_from_graph(
facts,
store,
ModelSelector::Root(roots.text_root),
)?,
mary::selection::load_tokenizer_from_graph(
facts,
store,
TokenizerSelector::Root(roots.tokenizer_root),
)?,
mary::embed::default_device(),
)
.context("load the semantic index's selected text model for golden comparison")?;
let vision = mary::embed::load_nomic_vision_from_keymap(
mary::selection::load_keymap_from_graph(
facts,
store,
ModelSelector::Root(roots.vision_root),
)?,
mary::embed::default_device(),
)
.context("load the semantic index's selected vision model for golden comparison")?;
let computed_text = crate::memory_cover::l2_normalize(
text.embed_document(golden::TEXT)
.context("embed the golden text")?,
);
let computed_image = crate::memory_cover::l2_normalize(
vision
.embed_image(&golden::image_png())
.context("embed the golden image")?,
);
let text_root = roots.text_root;
let vision_root = roots.vision_root;
let recorded_text: std::collections::BTreeSet<Inline<Handle<Embedding768>>> = find!(
h: Inline<Handle<Embedding768>>,
pattern!(facts, [{ _?observation @
mary::format::attrs::model_root: text_root,
golden::text_embedding: ?h,
}])
)
.chain(find!(
h: Inline<Handle<Embedding768>>,
pattern!(facts, [{ text_root @ golden::text_embedding: ?h }])
))
.collect();
let recorded_image: std::collections::BTreeSet<Inline<Handle<Embedding768>>> = find!(
h: Inline<Handle<Embedding768>>,
pattern!(facts, [{ _?observation @
mary::format::attrs::model_root: vision_root,
golden::image_embedding: ?h,
}])
)
.chain(find!(
h: Inline<Handle<Embedding768>>,
pattern!(facts, [{ vision_root @ golden::image_embedding: ?h }])
))
.collect();
let read = |handles: std::collections::BTreeSet<Inline<Handle<Embedding768>>>| -> Result<Vec<Vec<f32>>> {
handles
.into_iter()
.map(|h| {
let view: View<[f32]> = store
.get(h)
.map_err(|error| anyhow!("read a recorded golden vector: {error:?}"))?;
Ok(view.as_ref().to_vec())
})
.collect()
};
Ok(GoldenReport {
model_collection: roots.collection,
rows: vec![
GoldenRow {
model: "text",
root: text_root,
computed: computed_text,
recorded: read(recorded_text)?,
},
GoldenRow {
model: "image",
root: vision_root,
computed: computed_image,
recorded: read(recorded_image)?,
},
],
})
}
pub fn golden_publish(
store: &mut crate::storage::FacultyStore,
signer: &ed25519_dalek::SigningKey,
runtime: &std::sync::Arc<tokio::runtime::Runtime>,
) -> Result<(GoldenReport, Vec<&'static str>)> {
use triblespace::core::repo::SnapshotSource;
let snapshot = crate::storage::AcquiringReader::new(
store.snapshot().context("freeze the pile for the golden vectors")?,
runtime.clone(),
);
let report = golden_report(&snapshot)?;
let collection =
mary::model_collection::ModelCollection::open(&snapshot, report.model_collection)
.context("open the golden report's model collection")?;
drop(snapshot);
let (fragment, recorded) = report.unrecorded_observations();
if !recorded.is_empty() {
crate::collection_names::require_command_write_admission_acquiring(
store,
collection,
signer,
"golden-report model",
"files golden",
runtime,
)?;
store
.commit(collection, signer, fragment)
.context("record golden observations referring to the model roots")?;
}
Ok((report, recorded))
}
#[cfg(test)]
mod golden_tests {
use super::*;
use triblespace::macros::id_hex;
#[test]
fn golden_image_for_files_index_is_deterministic_and_decodes() {
let first = golden::image_png();
let second = golden::image_png();
assert_eq!(first, second);
let decoded = image::load_from_memory(&first).unwrap();
assert_eq!((decoded.width(), decoded.height()), (224, 224));
}
#[test]
fn golden_files_admission_refuses_below_the_floor() {
let root = id_hex!("18AD4630637E03D4A8214A7464D06AAC");
let unit = |i: usize| {
let mut v = vec![0.0f32; 768];
v[i] = 1.0;
v
};
let row = |recorded: Vec<Vec<f32>>| GoldenRow {
model: "text",
root,
computed: unit(0),
recorded,
};
assert!(GoldenReport {
model_collection: Inline::new([7; 32]),
rows: vec![row(vec![unit(0)])]
}
.admit()
.is_ok());
assert!(GoldenReport {
model_collection: Inline::new([7; 32]),
rows: vec![row(vec![])]
}
.admit()
.is_ok());
let off = GoldenReport {
model_collection: Inline::new([7; 32]),
rows: vec![row(vec![unit(0), unit(1)])],
};
let error = off.admit().unwrap_err().to_string();
assert!(error.contains("cosine 0.00000"), "{error}");
assert!((cosine(&unit(0), &unit(0)) - 1.0).abs() < 1e-6);
}
#[test]
fn golden_observations_reference_models_without_owning_their_facts() {
let text = triblespace::core::id::fucid();
let vision = triblespace::core::id::fucid();
let report = GoldenReport {
model_collection: Inline::new([7; 32]),
rows: vec![
GoldenRow {
model: "text",
root: *text,
computed: vec![1.0; 768],
recorded: Vec::new(),
},
GoldenRow {
model: "image",
root: *vision,
computed: vec![2.0; 768],
recorded: Vec::new(),
},
],
};
let (observations, recorded) = report.unrecorded_observations();
assert_eq!(recorded, ["text", "image"]);
assert!(observations
.facts()
.iter()
.all(|fact| fact.e() != &*text && fact.e() != &*vision));
let text_subjects: Vec<Id> = find!(
observation: Id,
pattern!(observations.facts(), [{ ?observation @
mary::format::attrs::model_root: *text,
golden::text_embedding: _?vector,
}])
)
.collect();
let vision_subjects: Vec<Id> = find!(
observation: Id,
pattern!(observations.facts(), [{ ?observation @
mary::format::attrs::model_root: *vision,
golden::image_embedding: _?vector,
}])
)
.collect();
assert_eq!(text_subjects.len(), 1);
assert_eq!(vision_subjects.len(), 1);
assert_ne!(text_subjects[0], vision_subjects[0]);
assert_eq!(observations, report.unrecorded_observations().0);
let historical = GoldenReport {
model_collection: report.model_collection,
rows: report
.rows
.into_iter()
.map(|mut row| {
row.recorded.push(row.computed.clone());
row
})
.collect(),
};
let (unchanged, recorded) = historical.unrecorded_observations();
assert!(unchanged.facts().is_empty());
assert!(recorded.is_empty());
}
}