Skip to main content

scr_runtime_compression/
compressed_search_path.rs

1//! `CompressedSearchPath` — a search path wrapper that carries compression metadata.
2//!
3//! Wraps a "normal" search path (e.g. a list of namespaces or collection identifiers)
4//! and annotates it with codec identity and provenance so that the semantic-memory
5//! runtime can make governance decisions about whether to use compressed lookup,
6//! exact fallback, or a hybrid path.
7
8use serde::{Deserialize, Serialize};
9
10use super::CodecId;
11
12/// A search path annotated with compression context.
13///
14/// When a query enters the semantic-memory runtime, it may carry compressed
15/// index artifacts (e.g. turbo-quant polar codes or fib-quant radial sketches).
16/// `CompressedSearchPath` wraps the raw search path + the codec metadata needed
17/// to route the query correctly.
18///
19/// ## Type parameters
20/// - `P` — the underlying search path type (e.g. `Vec<NamespaceId>`, `ScopeFilter`, etc.)
21///   must be `Send + Sync` so the wrapper can be used across async boundaries.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23#[serde(bound(serialize = "P: Serialize", deserialize = "P: Deserialize<'de>"))]
24pub struct CompressedSearchPath<P> {
25    /// The raw search path being executed.
26    path: P,
27
28    /// Which codec compressed the index artifacts associated with this path.
29    codec_id: CodecId,
30
31    /// When this compressed path was produced (for audit and staleness checks).
32    #[serde(skip_serializing_if = "Option::is_none")]
33    produced_at: Option<chrono::DateTime<chrono::Utc>>,
34
35    /// Human-readable provenance label, e.g. "turbo-quant:polar:v2" or "fib-quant:radial:1".
36    /// Used in logs and receipts.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    provenance_label: Option<String>,
39
40    /// Whether this path is allowed to use lossy approximate decode.
41    /// `false` means every decode must produce an exact (fallback) result.
42    approximate_allowed: bool,
43}
44
45impl<P> CompressedSearchPath<P>
46where
47    P: Send + Sync,
48{
49    /// Wrap an existing search path with compression context.
50    pub fn new(path: P, codec_id: CodecId) -> Self {
51        Self {
52            path,
53            codec_id,
54            produced_at: None,
55            provenance_label: None,
56            approximate_allowed: true,
57        }
58    }
59
60    /// Set the `produced_at` timestamp.
61    pub fn produced_at(mut self, ts: chrono::DateTime<chrono::Utc>) -> Self {
62        self.produced_at = Some(ts);
63        self
64    }
65
66    /// Set the provenance label.
67    pub fn provenance_label(mut self, label: impl Into<String>) -> Self {
68        self.provenance_label = Some(label.into());
69        self
70    }
71
72    /// Disallow approximate decode — require exact fallback on every decode.
73    pub fn require_exact(mut self) -> Self {
74        self.approximate_allowed = false;
75        self
76    }
77
78    /// Returns the codec identity.
79    pub fn codec_id(&self) -> CodecId {
80        self.codec_id
81    }
82
83    /// Returns the wrapped search path by reference.
84    pub fn path(&self) -> &P {
85        &self.path
86    }
87
88    /// Consumes the wrapper and returns the inner search path.
89    pub fn into_path(self) -> P {
90        self.path
91    }
92
93    /// Returns `true` if approximate decode is allowed on this path.
94    pub fn approximate_allowed(&self) -> bool {
95        self.approximate_allowed
96    }
97
98    /// Returns `true` if this path requires an exact fallback decode.
99    pub fn requires_exact_fallback(&self) -> bool {
100        self.codec_id.requires_exact_fallback() || !self.approximate_allowed
101    }
102
103    /// Map the inner path type using `f`.
104    ///
105    /// This allows transforming the wrapped path type without touching the
106    /// compression metadata.
107    pub fn map_path<Q, F>(self, f: F) -> CompressedSearchPath<Q>
108    where
109        F: FnOnce(P) -> Q,
110    {
111        CompressedSearchPath {
112            path: f(self.path),
113            codec_id: self.codec_id,
114            produced_at: self.produced_at,
115            provenance_label: self.provenance_label,
116            approximate_allowed: self.approximate_allowed,
117        }
118    }
119}
120
121impl<P> std::fmt::Display for CompressedSearchPath<P>
122where
123    P: std::fmt::Debug,
124{
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        write!(
127            f,
128            "CompressedSearchPath(codec={}, approx={}, path={:?})",
129            self.codec_id, self.approximate_allowed, self.path
130        )
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn compressed_search_path_build() {
140        let path = vec!["ns1", "ns2"];
141        let csp = CompressedSearchPath::new(path, CodecId::TurboQuant)
142            .require_exact()
143            .provenance_label("test:v1");
144
145        assert_eq!(csp.codec_id(), CodecId::TurboQuant);
146        assert!(!csp.approximate_allowed());
147        assert!(csp.requires_exact_fallback());
148    }
149
150    #[test]
151    fn codec_id_requires_exact_fallback() {
152        assert!(CodecId::TurboQuant.requires_exact_fallback());
153        assert!(CodecId::FibQuant.requires_exact_fallback());
154        assert!(!CodecId::Uncompressed.requires_exact_fallback());
155    }
156
157    #[test]
158    fn map_path_transforms_inner() {
159        let csp: CompressedSearchPath<Vec<&str>> =
160            CompressedSearchPath::new(vec!["a", "b"], CodecId::FibQuant);
161        let mapped: CompressedSearchPath<Vec<String>> =
162            csp.map_path(|v| v.into_iter().map(String::from).collect());
163        assert_eq!(mapped.into_path(), vec!["a", "b"]);
164    }
165}