Skip to content

Commit d9d8d82

Browse files
hyperpolymathclaude
andcommitted
Phase 1.1 WIP: persistent stores for tensor, semantic, provenance, temporal, spatial
Added redb-backend feature flag and verisim-storage dependency to all 6 remaining modality crates. Persistent store implementations written for all 5 (tensor, semantic, provenance, temporal, spatial). Some trait API mismatches need fixing before all compile — semantic store trait is more complex than the initial implementation assumed. Vector store (committed previously) compiles and passes tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d843720 commit d9d8d82

16 files changed

Lines changed: 672 additions & 0 deletions

File tree

verisimdb/Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

verisimdb/rust-core/verisim-provenance/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,15 @@ serde_json.workspace = true
1414
thiserror.workspace = true
1515
tracing.workspace = true
1616
async-trait.workspace = true
17+
verisim-storage = { path = "../verisim-storage", optional = true }
1718
tokio.workspace = true
1819
chrono.workspace = true
1920
sha2.workspace = true
2021

2122
[dev-dependencies]
23+
tempfile = "3"
2224
proptest.workspace = true
25+
26+
[features]
27+
default = []
28+
redb-backend = ["verisim-storage/redb-backend"]

verisimdb/rust-core/verisim-provenance/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
//! `HashMap<String, Vec<ProvenanceRecord>>`.
1818
1919
#![forbid(unsafe_code)]
20+
#[cfg(feature = "redb-backend")]
21+
pub mod persistent;
22+
#[cfg(feature = "redb-backend")]
23+
pub use persistent::*;
2024
use async_trait::async_trait;
2125
use chrono::{DateTime, Utc};
2226
use serde::{Deserialize, Serialize};
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
//
4+
// Persistent provenance store backed by redb via verisim-storage.
5+
6+
use std::collections::HashMap;
7+
use std::path::Path;
8+
use std::sync::{Arc, RwLock};
9+
10+
use async_trait::async_trait;
11+
use tracing::info;
12+
use verisim_storage::redb_backend::RedbBackend;
13+
use verisim_storage::typed::TypedStore;
14+
15+
use crate::{ProvenanceChain, ProvenanceError, ProvenanceRecord, ProvenanceStore};
16+
17+
/// Persistent provenance store: redb for durability, in-memory cache for queries.
18+
pub struct RedbProvenanceStore {
19+
store: TypedStore<RedbBackend>,
20+
cache: Arc<RwLock<HashMap<String, ProvenanceChain>>>,
21+
}
22+
23+
impl RedbProvenanceStore {
24+
pub async fn open(path: impl AsRef<Path>) -> Result<Self, ProvenanceError> {
25+
let backend = RedbBackend::open(path.as_ref())
26+
.map_err(|e| ProvenanceError::StorageError(format!("redb open: {}", e)))?;
27+
let store = TypedStore::new(backend, "prov");
28+
29+
let entries: Vec<(String, ProvenanceChain)> = store
30+
.scan_prefix("", 1_000_000)
31+
.await
32+
.map_err(|e| ProvenanceError::StorageError(format!("scan: {}", e)))?;
33+
34+
let mut cache = HashMap::new();
35+
for (id, chain) in entries {
36+
cache.insert(id, chain);
37+
}
38+
39+
info!(count = cache.len(), "Loaded provenance store from redb");
40+
Ok(Self { store, cache: Arc::new(RwLock::new(cache)) })
41+
}
42+
43+
async fn persist_chain(&self, entity_id: &str) -> Result<(), ProvenanceError> {
44+
let c = self.cache.read().map_err(|_| ProvenanceError::LockPoisoned)?;
45+
if let Some(chain) = c.get(entity_id) {
46+
self.store.put(entity_id, chain).await
47+
.map_err(|e| ProvenanceError::StorageError(format!("put: {}", e)))?;
48+
}
49+
Ok(())
50+
}
51+
}
52+
53+
#[async_trait]
54+
impl ProvenanceStore for RedbProvenanceStore {
55+
async fn record(&self, record: ProvenanceRecord) -> Result<(), ProvenanceError> {
56+
let entity_id = record.entity_id.clone();
57+
{
58+
let mut c = self.cache.write().map_err(|_| ProvenanceError::LockPoisoned)?;
59+
let chain = c.entry(entity_id.clone()).or_insert_with(|| ProvenanceChain {
60+
entity_id: entity_id.clone(),
61+
records: Vec::new(),
62+
});
63+
chain.records.push(record);
64+
}
65+
self.persist_chain(&entity_id).await
66+
}
67+
68+
async fn get_chain(&self, entity_id: &str) -> Result<Option<ProvenanceChain>, ProvenanceError> {
69+
let c = self.cache.read().map_err(|_| ProvenanceError::LockPoisoned)?;
70+
Ok(c.get(entity_id).cloned())
71+
}
72+
73+
async fn verify_chain(&self, entity_id: &str) -> Result<bool, ProvenanceError> {
74+
let c = self.cache.read().map_err(|_| ProvenanceError::LockPoisoned)?;
75+
match c.get(entity_id) {
76+
Some(chain) => Ok(chain.verify()),
77+
None => Err(ProvenanceError::NotFound(entity_id.to_string())),
78+
}
79+
}
80+
81+
async fn get_latest(&self, entity_id: &str) -> Result<Option<ProvenanceRecord>, ProvenanceError> {
82+
let c = self.cache.read().map_err(|_| ProvenanceError::LockPoisoned)?;
83+
Ok(c.get(entity_id).and_then(|chain| chain.records.last().cloned()))
84+
}
85+
86+
async fn search_by_actor(&self, actor: &str) -> Result<Vec<ProvenanceRecord>, ProvenanceError> {
87+
let c = self.cache.read().map_err(|_| ProvenanceError::LockPoisoned)?;
88+
let mut results = Vec::new();
89+
for chain in c.values() {
90+
for record in &chain.records {
91+
if record.actor == actor {
92+
results.push(record.clone());
93+
}
94+
}
95+
}
96+
Ok(results)
97+
}
98+
99+
async fn delete_chain(&self, entity_id: &str) -> Result<(), ProvenanceError> {
100+
self.store.delete(entity_id).await
101+
.map_err(|e| ProvenanceError::StorageError(format!("delete: {}", e)))?;
102+
let mut c = self.cache.write().map_err(|_| ProvenanceError::LockPoisoned)?;
103+
c.remove(entity_id);
104+
Ok(())
105+
}
106+
}
107+
108+
#[cfg(test)]
109+
mod tests {
110+
use super::*;
111+
112+
#[tokio::test]
113+
async fn test_persistent_provenance_roundtrip() {
114+
let dir = tempfile::tempdir().unwrap();
115+
let path = dir.path().join("prov.redb");
116+
117+
{
118+
let store = RedbProvenanceStore::open(&path).await.unwrap();
119+
let record = ProvenanceRecord::new("e1", "create", "user1");
120+
store.record(record).await.unwrap();
121+
}
122+
123+
{
124+
let store = RedbProvenanceStore::open(&path).await.unwrap();
125+
let chain = store.get_chain("e1").await.unwrap().unwrap();
126+
assert_eq!(chain.records.len(), 1);
127+
assert_eq!(chain.records[0].actor, "user1");
128+
}
129+
}
130+
}

verisimdb/rust-core/verisim-semantic/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@ serde_json.workspace = true
1818
thiserror.workspace = true
1919
tracing.workspace = true
2020
async-trait.workspace = true
21+
verisim-storage = { path = "../verisim-storage", optional = true }
2122
tokio.workspace = true
2223
hex = "0.4"
2324

2425
[features]
2526
default = ["regex"]
27+
redb-backend = ["verisim-storage/redb-backend"]
2628

2729
[dev-dependencies]
30+
tempfile = "3"
2831
proptest.workspace = true

verisimdb/rust-core/verisim-semantic/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
//! Implements Marr's Computational Level: "What does this mean?"
66
77
#![forbid(unsafe_code)]
8+
#[cfg(feature = "redb-backend")]
9+
pub mod persistent;
10+
#[cfg(feature = "redb-backend")]
11+
pub use persistent::*;
812
pub mod zkp;
913
pub mod zkp_bridge;
1014
pub mod proven_bridge;
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
//
4+
// Persistent semantic store backed by redb via verisim-storage.
5+
6+
use std::collections::HashMap;
7+
use std::path::Path;
8+
use std::sync::{Arc, RwLock};
9+
10+
use async_trait::async_trait;
11+
use tracing::info;
12+
use verisim_storage::redb_backend::RedbBackend;
13+
use verisim_storage::typed::TypedStore;
14+
15+
use crate::{SemanticAnnotation, SemanticError, SemanticStore};
16+
17+
/// Persistent semantic store: redb for durability, in-memory cache for queries.
18+
pub struct RedbSemanticStore {
19+
store: TypedStore<RedbBackend>,
20+
cache: Arc<RwLock<HashMap<String, SemanticAnnotation>>>,
21+
}
22+
23+
impl RedbSemanticStore {
24+
pub async fn open(path: impl AsRef<Path>) -> Result<Self, SemanticError> {
25+
let backend = RedbBackend::open(path.as_ref())
26+
.map_err(|e| SemanticError::StorageError(format!("redb open: {}", e)))?;
27+
let store = TypedStore::new(backend, "sem");
28+
29+
let entries: Vec<(String, SemanticAnnotation)> = store
30+
.scan_prefix("", 1_000_000)
31+
.await
32+
.map_err(|e| SemanticError::StorageError(format!("scan: {}", e)))?;
33+
34+
let mut cache = HashMap::new();
35+
for (id, ann) in entries {
36+
cache.insert(id, ann);
37+
}
38+
39+
info!(count = cache.len(), "Loaded semantic store from redb");
40+
Ok(Self { store, cache: Arc::new(RwLock::new(cache)) })
41+
}
42+
}
43+
44+
#[async_trait]
45+
impl SemanticStore for RedbSemanticStore {
46+
async fn annotate(&self, annotation: &SemanticAnnotation) -> Result<(), SemanticError> {
47+
self.store.put(&annotation.entity_id, annotation).await
48+
.map_err(|e| SemanticError::StorageError(format!("put: {}", e)))?;
49+
let mut c = self.cache.write().map_err(|_| SemanticError::LockPoisoned)?;
50+
c.insert(annotation.entity_id.clone(), annotation.clone());
51+
Ok(())
52+
}
53+
54+
async fn get_annotation(&self, entity_id: &str) -> Result<Option<SemanticAnnotation>, SemanticError> {
55+
let c = self.cache.read().map_err(|_| SemanticError::LockPoisoned)?;
56+
Ok(c.get(entity_id).cloned())
57+
}
58+
59+
async fn delete_annotation(&self, entity_id: &str) -> Result<(), SemanticError> {
60+
self.store.delete(entity_id).await
61+
.map_err(|e| SemanticError::StorageError(format!("delete: {}", e)))?;
62+
let mut c = self.cache.write().map_err(|_| SemanticError::LockPoisoned)?;
63+
c.remove(entity_id);
64+
Ok(())
65+
}
66+
67+
async fn query_by_type(&self, type_uri: &str) -> Result<Vec<SemanticAnnotation>, SemanticError> {
68+
let c = self.cache.read().map_err(|_| SemanticError::LockPoisoned)?;
69+
Ok(c.values()
70+
.filter(|a| a.types.iter().any(|t| t.uri == type_uri))
71+
.cloned()
72+
.collect())
73+
}
74+
75+
async fn validate(&self, entity_id: &str) -> Result<Vec<String>, SemanticError> {
76+
let c = self.cache.read().map_err(|_| SemanticError::LockPoisoned)?;
77+
let ann = c.get(entity_id)
78+
.ok_or_else(|| SemanticError::NotFound(entity_id.to_string()))?;
79+
let mut violations = Vec::new();
80+
for constraint in &ann.constraints {
81+
if !constraint.satisfied {
82+
violations.push(format!("Constraint '{}' not satisfied", constraint.name));
83+
}
84+
}
85+
Ok(violations)
86+
}
87+
}
88+
89+
#[cfg(test)]
90+
mod tests {
91+
use super::*;
92+
use crate::SemanticType;
93+
94+
#[tokio::test]
95+
async fn test_persistent_semantic_roundtrip() {
96+
let dir = tempfile::tempdir().unwrap();
97+
let path = dir.path().join("semantic.redb");
98+
99+
{
100+
let store = RedbSemanticStore::open(&path).await.unwrap();
101+
let ann = SemanticAnnotation {
102+
entity_id: "e1".to_string(),
103+
types: vec![SemanticType { uri: "http://example.org/Person".to_string(), label: Some("Person".to_string()), confidence: 0.95 }],
104+
constraints: vec![],
105+
proofs: vec![],
106+
provenance: None,
107+
};
108+
store.annotate(&ann).await.unwrap();
109+
}
110+
111+
{
112+
let store = RedbSemanticStore::open(&path).await.unwrap();
113+
let ann = store.get_annotation("e1").await.unwrap().unwrap();
114+
assert_eq!(ann.types[0].uri, "http://example.org/Person");
115+
}
116+
}
117+
}

verisimdb/rust-core/verisim-spatial/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ serde_json.workspace = true
1414
thiserror.workspace = true
1515
tracing.workspace = true
1616
async-trait.workspace = true
17+
verisim-storage = { path = "../verisim-storage", optional = true }
1718
tokio.workspace = true
1819

1920
[dev-dependencies]
21+
tempfile = "3"
2022
proptest.workspace = true
23+
24+
[features]
25+
default = []
26+
redb-backend = ["verisim-storage/redb-backend"]

verisimdb/rust-core/verisim-spatial/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
//! similar spatial index.
1919
2020
#![forbid(unsafe_code)]
21+
#[cfg(feature = "redb-backend")]
22+
pub mod persistent;
23+
#[cfg(feature = "redb-backend")]
24+
pub use persistent::*;
2125
use async_trait::async_trait;
2226
use serde::{Deserialize, Serialize};
2327
use std::collections::HashMap;

0 commit comments

Comments
 (0)