Skip to main content

holographic_memory/core/
schema.rs

1// Copyright 2024-2026 WritersLogic Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4use anyhow::{bail, Result};
5use serde::{Deserialize, Serialize};
6use std::path::Path;
7
8use super::config::HmsConfig;
9
10#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
11pub struct EmbeddingSpace {
12    pub model: String,
13    pub revision: String,
14    pub dimensions: usize,
15    pub normalization: String,
16    pub metric: String,
17}
18
19#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
20struct StoreSchema {
21    version: u32,
22    dimensions: usize,
23    text_encoder: String,
24    vector_encoder: String,
25    metric: String,
26    encrypted: bool,
27    meaning_enabled: bool,
28    embedding_space: Option<EmbeddingSpace>,
29}
30
31/// Checks the store's `store.json` against `config` (writing it for a new store) and returns
32/// its schema version, which fixes the journal format the store is written in.
33pub(crate) fn validate_store(path: &Path, dimensions: usize, config: &HmsConfig) -> Result<u32> {
34    let mut expected = StoreSchema {
35        version: super::format::STORE_SCHEMA_VERSION,
36        dimensions,
37        text_encoder: config.text_encoder.name().into(),
38        vector_encoder: "signed-projection-v2".into(),
39        metric: "jaccard".into(),
40        encrypted: config.security.encryption_enabled,
41        meaning_enabled: config.meaning.enabled,
42        embedding_space: config.embedding_space.clone(),
43    };
44    let schema_path = path.join("store.json");
45    if schema_path.exists() {
46        let bytes = std::fs::read(schema_path)?;
47        if let Some(version) = serde_json::from_slice::<serde_json::Value>(&bytes)
48            .ok()
49            .and_then(|v| v["version"].as_u64())
50            .filter(|&v| v > u64::from(super::format::STORE_SCHEMA_VERSION))
51        {
52            bail!(
53                "store schema version {version} is newer than the supported version {}; open it with a release that supports it",
54                super::format::STORE_SCHEMA_VERSION
55            );
56        }
57        let actual: StoreSchema = serde_json::from_slice(&bytes)?;
58        // Every schema from the oldest supported one opens; only the version differs between
59        // them (it selects the journal format).
60        if (super::format::STORE_SCHEMA_OLDEST..=super::format::STORE_SCHEMA_VERSION)
61            .contains(&actual.version)
62        {
63            expected.version = actual.version;
64        }
65        if actual != expected {
66            bail!("incompatible store schema: dimensions, encoder, embedding model, encryption, or meaning configuration changed; reopen with the original configuration or re-encode source documents into a new store");
67        }
68    } else {
69        if path.join("vectors_data.bin").exists() {
70            bail!("legacy store has no embedding schema; preserve it and use hms-admin reencode with original source documents to create a new store");
71        }
72        super::durable_file::atomic_write(&schema_path, &serde_json::to_vec_pretty(&expected)?)?;
73    }
74    Ok(expected.version)
75}