refactor: migrate index metadata to on-disk JSON with fallible access

Migrate index state tracking from filesystem sentinel files to an on-disk JSON schema within `index.meta`. The `IndexMeta` struct is now wrapped in an `Arc` with internal locking, exposing only fallible methods for genome and state access. In-memory mutation capabilities have been removed, requiring callers to handle I/O errors explicitly and pass immutable references to downstream components like `PartitionRouter`. Public sentinel constants have been removed from exports.
This commit is contained in:
Eric Coissac
2026-08-21 21:51:42 +02:00
parent 02dbdd11aa
commit 8d6ba6546b
37 changed files with 573 additions and 251 deletions
+2 -2
View File
@@ -3,7 +3,7 @@ use std::sync::Arc;
use obisys::progress_bar;
use obikindex::KmerIndex;
use obikindex::OKIResult;
use obikindex::{OKIError, OKIResult};
use super::cache::PartitionCache;
use super::family_scan::{Selection, scan_layer_families};
@@ -83,7 +83,7 @@ impl SnpAlignmentExt for KmerIndex {
entropy_bias: Option<EntropyBias>,
) -> OKIResult<SnpAlignment> {
let n_parts = self.n_partitions();
let n_genomes = self.meta().genomes.len();
let n_genomes = self.meta().genomes().map_err(OKIError::Io)?.len();
let with_counts = self.meta().config.with_counts;
let k = self.kmer_size();
let cache = Arc::new(PartitionCache::build(self, n_parts, with_counts)?);
+2 -2
View File
@@ -5,7 +5,7 @@ use ndarray::Array2;
use obisys::progress_bar;
use obikindex::KmerIndex;
use obikindex::OKIResult;
use obikindex::{OKIError, OKIResult};
use super::cache::PartitionCache;
use super::distance::RawSnpDistanceOutput;
@@ -65,7 +65,7 @@ impl CardinalityExt for KmerIndex {
ratio_ceiling: f64,
) -> OKIResult<CardinalityTally> {
let n_parts = self.n_partitions();
let n_genomes = self.meta().genomes.len();
let n_genomes = self.meta().genomes().map_err(OKIError::Io)?.len();
let with_counts = self.meta().config.with_counts;
let k = self.kmer_size();
let included = Array2::from_shape_fn((n_genomes, n_genomes), |(i, j)| {
+4 -4
View File
@@ -5,7 +5,7 @@ use ndarray::Array2;
use obisys::progress_bar;
use obikindex::KmerIndex;
use obikindex::OKIResult;
use obikindex::{OKIError, OKIResult};
use super::cache::PartitionCache;
use super::family_scan::{Selection, scan_layer_families};
@@ -66,7 +66,7 @@ where
C: Fn(Acc, Acc) -> Acc,
{
let n_parts = index.n_partitions();
let n_genomes = index.meta().genomes.len();
let n_genomes = index.meta().genomes().map_err(OKIError::Io)?.len();
let with_counts = index.meta().config.with_counts;
let k = index.kmer_size();
let cache = Arc::new(PartitionCache::build(index, n_parts, with_counts)?);
@@ -149,7 +149,7 @@ pub trait DistanceExt {
impl DistanceExt for KmerIndex {
fn raw_snp_distance(&self) -> OKIResult<RawSnpDistanceOutput> {
let n_genomes = self.meta().genomes.len();
let n_genomes = self.meta().genomes().map_err(OKIError::Io)?.len();
let (snp, shared) = scan_family_pairs(
self,
"raw_snp_distance",
@@ -182,7 +182,7 @@ impl DistanceExt for KmerIndex {
raw: &RawSnpDistanceOutput,
ratio_ceiling: f64,
) -> OKIResult<BasePairTally> {
let n_genomes = self.meta().genomes.len();
let n_genomes = self.meta().genomes().map_err(OKIError::Io)?.len();
let included = Array2::from_shape_fn((n_genomes, n_genomes), |(i, j)| {
if i == j {
return false;
+2 -2
View File
@@ -135,7 +135,7 @@ impl ShannonEntropyExt for KmerIndex {
entropy_bias: Option<EntropyBias>,
) -> OKIResult<()> {
let n_parts = self.n_partitions();
let n_genomes = self.meta().genomes.len();
let n_genomes = self.meta().genomes().map_err(OKIError::Io)?.len();
let with_counts = self.meta().config.with_counts;
let k = self.kmer_size();
let cache = Arc::new(PartitionCache::build(self, n_parts, with_counts)?);
@@ -227,7 +227,7 @@ pub(super) fn ensure_entropy_annexes(index: &KmerIndex, layer_dirs: &[PathBuf])
}
let n_parts = index.n_partitions();
let n_genomes = index.meta().genomes.len();
let n_genomes = index.meta().genomes().map_err(OKIError::Io)?.len();
let with_counts = index.meta().config.with_counts;
let k = index.kmer_size();
let cache = Arc::new(PartitionCache::build(index, n_parts, with_counts)?);
+2 -2
View File
@@ -32,7 +32,7 @@ use ndarray::Array2;
use obisys::progress_bar;
use obikindex::KmerIndex;
use obikindex::OKIResult;
use obikindex::{OKIError, OKIResult};
use super::alignment::{SnpAlignment, iupac_code};
use super::cache::PartitionCache;
@@ -79,7 +79,7 @@ impl SankoffBundleExt for KmerIndex {
exclude_mask: &[bool],
) -> OKIResult<SankoffBundle> {
let n_parts = self.n_partitions();
let n_genomes = self.meta().genomes.len();
let n_genomes = self.meta().genomes().map_err(OKIError::Io)?.len();
let with_counts = self.meta().config.with_counts;
let k = self.kmer_size();
let cache = Arc::new(PartitionCache::build(self, n_parts, with_counts)?);
+2 -2
View File
@@ -5,7 +5,7 @@ use rayon::prelude::*;
use obisys::progress_bar;
use obikindex::KmerIndex;
use obikindex::OKIResult;
use obikindex::{OKIError, OKIResult};
use super::ANNEX_FILE_NAME;
use super::SiblingAnnex;
@@ -105,7 +105,7 @@ impl SiblingStatsExt for KmerIndex {
fn sibling_annex_stats(&self) -> OKIResult<SiblingAnnexStats> {
let n_parts = self.n_partitions();
let n_genomes = self.meta().genomes.len();
let n_genomes = self.meta().genomes().map_err(OKIError::Io)?.len();
let with_counts = self.meta().config.with_counts;
let k = self.kmer_size();
// Same whole-run cache as `build_sibling_annex` — see its docs for
+4 -4
View File
@@ -275,7 +275,7 @@ fn family_scan_consumers_agree_on_one_sibling_each() {
// Merge doesn't promise to preserve source order, so resolve each
// genome's index by label rather than assuming g1 -> 0, g2 -> 1.
let idx_of = |label: &str| merged.meta().genomes.iter().position(|g| g.label == label).unwrap();
let idx_of = |label: &str| merged.meta().genomes().unwrap().iter().position(|g| g.label == label).unwrap();
let (i1, i2) = (idx_of("g1"), idx_of("g2"));
// snp_pseudo_alignment: one variable family, one column — g1's row
@@ -389,7 +389,7 @@ fn subsample_and_shannon_on_one_variable_family() {
let merged = merge_two(dir.path(), &g1, &g2);
merged.build_sibling_annex().expect("build_sibling_annex");
let idx_of = |label: &str| merged.meta().genomes.iter().position(|g| g.label == label).unwrap();
let idx_of = |label: &str| merged.meta().genomes().unwrap().iter().position(|g| g.label == label).unwrap();
let (i1, i2) = (idx_of("g1"), idx_of("g2"));
// This fixture has exactly one non-monomorphic family (see
@@ -442,7 +442,7 @@ fn sankoff_bundle_matches_old_separate_calls() {
let merged = merge_two(dir.path(), &g1, &g2);
merged.build_sibling_annex().expect("build_sibling_annex");
let n_genomes = merged.meta().genomes.len();
let n_genomes = merged.meta().genomes().unwrap().len();
let exclude_mask = vec![false; n_genomes];
let ratio_ceiling = 0.5;
@@ -487,7 +487,7 @@ fn base_pair_tally_accumulates_base_a_diagnostic() {
).expect("merge");
merged.build_sibling_annex().expect("build_sibling_annex");
let n_genomes = merged.meta().genomes.len();
let n_genomes = merged.meta().genomes().unwrap().len();
let exclude_mask = vec![false; n_genomes];
let bundle = merged.sankoff_bundle(None, None, 0.5, &exclude_mask).expect("sankoff_bundle");