Restore a collection from checkpoint bytes. `kek` controls the expected framing: - `None` → the file must be plaintext MessagePack (starting with bytes that are NOT `SEGV`). If the file starts with `SEGV` and no key is provided, returns `Err(CheckpointEncryptedNoKey)`. - `Some(key)` → encryption is **required**. If the file starts with `SEGV`, it is decrypted with `key`. If the file is plaintext,
(
bytes: &[u8],
kek: Option<&nodedb_wal::crypto::WalEncryptionKey>,
)
| 246 | /// `Err(CheckpointPlaintextKeyRequired)` — refuse to silently load |
| 247 | /// unencrypted data when the operator has enabled at-rest encryption. |
| 248 | pub fn from_checkpoint( |
| 249 | bytes: &[u8], |
| 250 | kek: Option<&nodedb_wal::crypto::WalEncryptionKey>, |
| 251 | ) -> Result<Option<Self>, VectorError> { |
| 252 | let is_encrypted = bytes.len() >= 4 && bytes[0..4] == SEGV_MAGIC; |
| 253 | |
| 254 | let msgpack: Vec<u8>; |
| 255 | let msgpack_ref: &[u8]; |
| 256 | |
| 257 | if is_encrypted { |
| 258 | if let Some(key) = kek { |
| 259 | msgpack = decrypt_checkpoint(key, bytes)?; |
| 260 | msgpack_ref = &msgpack; |
| 261 | } else { |
| 262 | return Err(VectorError::CheckpointEncryptedNoKey); |
| 263 | } |
| 264 | } else if kek.is_some() { |
| 265 | return Err(VectorError::CheckpointPlaintextKeyRequired); |
| 266 | } else { |
| 267 | msgpack_ref = bytes; |
| 268 | } |
| 269 | |
| 270 | let snap: CollectionSnapshot = match zerompk::from_msgpack(msgpack_ref) { |
| 271 | Ok(s) => s, |
| 272 | Err(_) => return Ok(None), |
| 273 | }; |
| 274 | let metric = match snap.params_metric { |
| 275 | 0 => DistanceMetric::L2, |
| 276 | 1 => DistanceMetric::Cosine, |
| 277 | 2 => DistanceMetric::InnerProduct, |
| 278 | 3 => DistanceMetric::Manhattan, |
| 279 | 4 => DistanceMetric::Chebyshev, |
| 280 | 5 => DistanceMetric::Hamming, |
| 281 | 6 => DistanceMetric::Jaccard, |
| 282 | 7 => DistanceMetric::Pearson, |
| 283 | _ => DistanceMetric::Cosine, |
| 284 | }; |
| 285 | let params = HnswParams { |
| 286 | m: snap.params_m, |
| 287 | m0: snap.params_m0, |
| 288 | ef_construction: snap.params_ef_construction, |
| 289 | metric, |
| 290 | dtype: nodedb_types::vector_dtype::VectorStorageDtype::F32, |
| 291 | }; |
| 292 | |
| 293 | let mut growing = FlatIndex::new(snap.dim, metric); |
| 294 | for (i, v) in snap.growing_vectors.iter().enumerate() { |
| 295 | let deleted = snap.growing_deleted.get(i).copied().unwrap_or(false); |
| 296 | if deleted { |
| 297 | growing.insert_tombstoned(v.clone()); |
| 298 | } else { |
| 299 | growing.insert(v.clone()); |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | let mut sealed = Vec::with_capacity(snap.sealed_segments.len()); |
| 304 | for ss in &snap.sealed_segments { |
| 305 | if let Some(index) = HnswIndex::from_checkpoint(&ss.hnsw_bytes).ok().flatten() { |
nothing calls this directly
no test coverage detected