Skip to content

Commit f844f1c

Browse files
hyperpolymathclaude
andcommitted
feat(persistent): wire all 8 modality stores to redb backends
The `persistent` feature previously only wired graph (redb) and document (Tantivy) — vector, tensor, semantic, temporal, provenance, and spatial remained in-memory and lost data on restart. Changes: - Propagate redb-backend feature to all 6 remaining modality crates - Make RedbVersionStore generic over T (was hardcoded to serde_json::Value, but InMemoryOctadStore requires Data = OctadSnapshot) - Update ConcreteOctadStore type alias to use all Redb* stores - Construct persistent stores in AppState::new_async with per-modality redb files (vector.redb, tensor.redb, semantic.redb, etc.) - Gate in-memory imports behind #[cfg(not(feature = "persistent"))] Verified: all 8 redb files created on startup, data survives stop/restart with WAL replay recovering 6/6 entities and modality flags intact for semantic, provenance, and temporal stores. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 667c232 commit f844f1c

3 files changed

Lines changed: 118 additions & 27 deletions

File tree

rust-core/verisim-api/Cargo.toml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,17 @@ hex = "0.4"
4747

4848
[features]
4949
default = []
50-
# Enable persistent storage backends (redb for graph, file-backed Tantivy for documents, WAL).
51-
# Requires VERISIM_PERSISTENCE_DIR environment variable at runtime.
52-
persistent = ["verisim-graph/redb-backend"]
50+
# Enable persistent storage backends (redb for all modalities, file-backed Tantivy for
51+
# documents, WAL for crash recovery). Requires VERISIM_PERSISTENCE_DIR at runtime.
52+
persistent = [
53+
"verisim-graph/redb-backend",
54+
"verisim-vector/redb-backend",
55+
"verisim-tensor/redb-backend",
56+
"verisim-semantic/redb-backend",
57+
"verisim-temporal/redb-backend",
58+
"verisim-provenance/redb-backend",
59+
"verisim-spatial/redb-backend",
60+
]
5361

5462
# Build-dependencies removed: protobuf code is pre-generated at src/proto/verisim.rs.
5563
# To regenerate after changing proto/verisim.proto, run:

rust-core/verisim-api/src/lib.rs

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ use verisim_drift::{DriftDetector, DriftMetrics, DriftThresholds, DriftType};
3535
use verisim_graph::SimpleGraphStore;
3636
#[cfg(feature = "persistent")]
3737
use verisim_graph::RedbGraphStore;
38+
#[cfg(feature = "persistent")]
39+
use verisim_vector::RedbVectorStore;
40+
#[cfg(feature = "persistent")]
41+
use verisim_tensor::RedbTensorStore;
42+
#[cfg(feature = "persistent")]
43+
use verisim_semantic::RedbSemanticStore;
44+
#[cfg(feature = "persistent")]
45+
use verisim_temporal::RedbVersionStore;
46+
#[cfg(feature = "persistent")]
47+
use verisim_provenance::RedbProvenanceStore;
48+
#[cfg(feature = "persistent")]
49+
use verisim_spatial::RedbSpatialStore;
3850
use verisim_planner::{
3951
CacheConfig, ExplainOutput, ExplainAnalyzeOutput, LogicalPlan, ParamValue,
4052
PhysicalPlan, PlanCache, Planner, PlannerConfig, PreparedId, PreparedStatement,
@@ -46,15 +58,22 @@ use verisim_octad::{
4658
OctadSpatialInput, OctadStore, OctadTensorInput, OctadVectorInput,
4759
InMemoryOctadStore, ProvenanceStore, SpatialStore,
4860
};
61+
#[cfg(not(feature = "persistent"))]
4962
use verisim_provenance::InMemoryProvenanceStore;
63+
#[cfg(not(feature = "persistent"))]
5064
use verisim_spatial::InMemorySpatialStore;
5165
use verisim_normalizer::{create_default_normalizer, Normalizer, NormalizerStatus};
66+
#[cfg(not(feature = "persistent"))]
5267
use verisim_semantic::InMemorySemanticStore;
5368
use verisim_semantic::zkp_bridge::{self as zkp_api, PrivacyLevel, ZkpProofRequest as ZkpBridgeRequest};
5469
use verisim_semantic::circuit_registry::CircuitRegistry;
70+
#[cfg(not(feature = "persistent"))]
5571
use verisim_temporal::InMemoryVersionStore;
72+
#[cfg(not(feature = "persistent"))]
5673
use verisim_tensor::InMemoryTensorStore;
57-
use verisim_vector::{DistanceMetric, BruteForceVectorStore};
74+
use verisim_vector::DistanceMetric;
75+
#[cfg(not(feature = "persistent"))]
76+
use verisim_vector::BruteForceVectorStore;
5877

5978
/// Type alias for our concrete OctadStore implementation (octad: 8 modality stores).
6079
///
@@ -73,17 +92,18 @@ pub type ConcreteOctadStore = InMemoryOctadStore<
7392
InMemorySpatialStore,
7493
>;
7594

76-
/// Persistent variant: redb graph store, file-backed Tantivy, WAL enabled.
95+
/// Persistent variant: all modalities backed by redb, file-backed Tantivy for
96+
/// documents, WAL enabled for crash recovery.
7797
#[cfg(feature = "persistent")]
7898
pub type ConcreteOctadStore = InMemoryOctadStore<
7999
RedbGraphStore,
80-
BruteForceVectorStore,
100+
RedbVectorStore,
81101
TantivyDocumentStore,
82-
InMemoryTensorStore,
83-
InMemorySemanticStore,
84-
InMemoryVersionStore<OctadSnapshot>,
85-
InMemoryProvenanceStore,
86-
InMemorySpatialStore,
102+
RedbTensorStore,
103+
RedbSemanticStore,
104+
RedbVersionStore<OctadSnapshot>,
105+
RedbProvenanceStore,
106+
RedbSpatialStore,
87107
>;
88108

89109
/// API errors
@@ -525,14 +545,71 @@ impl AppState {
525545
(g, d)
526546
};
527547

548+
// --- Vector store ---
549+
#[cfg(feature = "persistent")]
550+
let vector = Arc::new(
551+
RedbVectorStore::open(
552+
format!("{}/vector.redb", persist_dir),
553+
config.vector_dimension,
554+
DistanceMetric::Cosine,
555+
)
556+
.await
557+
.map_err(|e| ApiError::Internal(e.to_string()))?,
558+
);
559+
#[cfg(not(feature = "persistent"))]
528560
let vector = Arc::new(BruteForceVectorStore::new(
529561
config.vector_dimension,
530562
DistanceMetric::Cosine,
531563
));
564+
565+
// --- Tensor store ---
566+
#[cfg(feature = "persistent")]
567+
let tensor = Arc::new(
568+
RedbTensorStore::open(format!("{}/tensor.redb", persist_dir))
569+
.await
570+
.map_err(|e| ApiError::Internal(e.to_string()))?,
571+
);
572+
#[cfg(not(feature = "persistent"))]
532573
let tensor = Arc::new(InMemoryTensorStore::new());
574+
575+
// --- Semantic store ---
576+
#[cfg(feature = "persistent")]
577+
let semantic = Arc::new(
578+
RedbSemanticStore::open(format!("{}/semantic.redb", persist_dir))
579+
.await
580+
.map_err(|e| ApiError::Internal(e.to_string()))?,
581+
);
582+
#[cfg(not(feature = "persistent"))]
533583
let semantic = Arc::new(InMemorySemanticStore::new());
584+
585+
// --- Temporal (versioning) store ---
586+
#[cfg(feature = "persistent")]
587+
let temporal = Arc::new(
588+
RedbVersionStore::open(format!("{}/temporal.redb", persist_dir))
589+
.await
590+
.map_err(|e| ApiError::Internal(e.to_string()))?,
591+
);
592+
#[cfg(not(feature = "persistent"))]
534593
let temporal = Arc::new(InMemoryVersionStore::new());
594+
595+
// --- Provenance store ---
596+
#[cfg(feature = "persistent")]
597+
let provenance = Arc::new(
598+
RedbProvenanceStore::open(format!("{}/provenance.redb", persist_dir))
599+
.await
600+
.map_err(|e| ApiError::Internal(e.to_string()))?,
601+
);
602+
#[cfg(not(feature = "persistent"))]
535603
let provenance = Arc::new(InMemoryProvenanceStore::new());
604+
605+
// --- Spatial store ---
606+
#[cfg(feature = "persistent")]
607+
let spatial = Arc::new(
608+
RedbSpatialStore::open(format!("{}/spatial.redb", persist_dir))
609+
.await
610+
.map_err(|e| ApiError::Internal(e.to_string()))?,
611+
);
612+
#[cfg(not(feature = "persistent"))]
536613
let spatial = Arc::new(InMemorySpatialStore::new());
537614

538615
let octad_store_inner = InMemoryOctadStore::new(

rust-core/verisim-temporal/src/persistent.rs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,41 @@
88
// cache for fast reads. Writes go to redb first (durable), then update the
99
// cache.
1010
//
11-
// The associated type `Data` is `serde_json::Value`, making this store a
12-
// universal versioned key-value store. Higher layers can convert to/from
13-
// concrete types using serde.
11+
// The store is generic over `T` (the versioned data type). Any type that is
12+
// Serialize + DeserializeOwned + Clone + Send + Sync can be versioned.
1413

1514
use std::collections::{BTreeMap, HashMap};
15+
use std::marker::PhantomData;
1616
use std::path::Path;
1717
use std::sync::{Arc, RwLock};
1818

1919
use async_trait::async_trait;
2020
use chrono::{DateTime, Utc};
21+
use serde::{de::DeserializeOwned, Serialize};
2122
use tracing::info;
2223
use verisim_storage::redb_backend::RedbBackend;
2324
use verisim_storage::typed::TypedStore;
2425

2526
use crate::{TemporalError, TemporalStore, TimeRange, Version};
2627

27-
/// Type alias matching the InMemory store's internal structure.
28-
type VersionHistory = HashMap<String, BTreeMap<u64, Version<serde_json::Value>>>;
29-
3028
/// Persistent version store: redb for durability, in-memory BTreeMap cache for
3129
/// fast reads and range queries.
3230
///
3331
/// Each entity's entire version history is stored as a serialized
34-
/// `BTreeMap<u64, Version<serde_json::Value>>` under the entity_id key.
35-
pub struct RedbVersionStore {
32+
/// `BTreeMap<u64, Version<T>>` under the entity_id key.
33+
///
34+
/// `T` is the versioned data type — typically `OctadSnapshot` when used inside
35+
/// `InMemoryOctadStore`, or `serde_json::Value` for generic use.
36+
pub struct RedbVersionStore<T: Serialize + DeserializeOwned + Clone + Send + Sync + 'static> {
3637
/// Typed store for version histories, keyed by entity_id.
3738
store: TypedStore<RedbBackend>,
3839
/// In-memory cache of all version histories.
39-
versions: Arc<RwLock<VersionHistory>>,
40+
versions: Arc<RwLock<HashMap<String, BTreeMap<u64, Version<T>>>>>,
41+
/// Phantom data for the generic parameter.
42+
_marker: PhantomData<T>,
4043
}
4144

42-
impl RedbVersionStore {
45+
impl<T: Serialize + DeserializeOwned + Clone + Send + Sync + 'static> RedbVersionStore<T> {
4346
/// Open (or create) a persistent version store at the given path.
4447
///
4548
/// On open, all existing version histories are scanned from redb into the
@@ -49,12 +52,12 @@ impl RedbVersionStore {
4952
.map_err(|e| TemporalError::Conflict(format!("redb open: {}", e)))?;
5053
let store = TypedStore::new(backend, "ver");
5154

52-
let entries: Vec<(String, BTreeMap<u64, Version<serde_json::Value>>)> = store
55+
let entries: Vec<(String, BTreeMap<u64, Version<T>>)> = store
5356
.scan_prefix("", 1_000_000)
5457
.await
5558
.map_err(|e| TemporalError::Conflict(format!("scan: {}", e)))?;
5659

57-
let mut cache: VersionHistory = HashMap::new();
60+
let mut cache: HashMap<String, BTreeMap<u64, Version<T>>> = HashMap::new();
5861
for (id, history) in entries {
5962
cache.insert(id, history);
6063
}
@@ -66,6 +69,7 @@ impl RedbVersionStore {
6669
Ok(Self {
6770
store,
6871
versions: Arc::new(RwLock::new(cache)),
72+
_marker: PhantomData,
6973
})
7074
}
7175

@@ -89,8 +93,10 @@ impl RedbVersionStore {
8993
}
9094

9195
#[async_trait]
92-
impl TemporalStore for RedbVersionStore {
93-
type Data = serde_json::Value;
96+
impl<T: Serialize + DeserializeOwned + Clone + Send + Sync + 'static> TemporalStore
97+
for RedbVersionStore<T>
98+
{
99+
type Data = T;
94100

95101
async fn append(
96102
&self,
@@ -214,7 +220,7 @@ mod tests {
214220

215221
// Write data in one session.
216222
{
217-
let store = RedbVersionStore::open(&path).await.unwrap();
223+
let store = RedbVersionStore::<serde_json::Value>::open(&path).await.unwrap();
218224
let v1 = store
219225
.append(
220226
"entity-1",
@@ -240,7 +246,7 @@ mod tests {
240246

241247
// Reopen and verify data survived.
242248
{
243-
let store = RedbVersionStore::open(&path).await.unwrap();
249+
let store = RedbVersionStore::<serde_json::Value>::open(&path).await.unwrap();
244250

245251
let latest = store.latest("entity-1").await.unwrap().unwrap();
246252
assert_eq!(latest.version, 2);

0 commit comments

Comments
 (0)