1use std::{path::PathBuf, sync::Arc};
5
6use crate::object::{
7 Action, ActionId, Blob, ContentHash, State, StateAttachment, StateAttachmentId, StateId, Tree,
8};
9
10pub mod actor_presence;
11pub mod agent_task;
12pub mod codec;
13pub mod fs;
14pub mod liveness;
15#[cfg(any(test, feature = "memory-backend"))]
16pub mod memory;
17pub mod pack;
18pub mod shallow;
19pub mod source;
20pub mod store_compliance;
21pub mod writer_lease;
22
23pub use actor_presence::{
24 ActorChainNode, ActorPresence, ActorPresenceStatus, ActorPresenceStore, AgentUsageSummary,
25 ContextQueryEntry, generate_actor_session_id,
26};
27pub use agent_task::{
28 AGENT_TASK_SCHEMA_VERSION, AgentTaskRecord, AgentTaskStatus, AgentTaskStore,
29 generate_agent_task_id, validate_task_id,
30};
31pub use fs::{
32 DEFAULT_PACK_INSTALL_INTENT_TTL_SECS, FsStore, PackInstallIntent, PackInstallMetricsSnapshot,
33 PackInstallPhase, PackInstallRecoverReport, install_pack_bytes_journaled,
34 pack_install_metrics_reset, pack_install_metrics_snapshot, recover_pack_install_intents,
35 recover_pack_install_intents_with_ttl,
36};
37pub use heddle_format::compression::{CompressionConfig, CompressionError, compress, decompress};
38pub use liveness::{
39 AGENT_LEASE_DURATION, Liveness, current_boot_id, process_alive, reservation_liveness_at,
40};
41#[cfg(any(test, feature = "memory-backend"))]
42pub use memory::InMemoryStore;
43pub use pack::{PackBuilder, PackObjectId, PackReader, PackStats, StreamingPackBuilder, SyncData};
44pub use shallow::ShallowInfo;
45#[cfg(feature = "async-source")]
46pub use source::AsyncObjectSource;
47pub use source::ObjectSource;
48pub use writer_lease::{
49 WriterLease, WriterLeaseAuthOutcome, WriterLeaseDraft, WriterLeaseGrant,
50 WriterLeaseReserveOutcome, WriterLeaseStatus, WriterLeaseStore, generate_writer_lease_id,
51 generate_writer_lease_token,
52};
53
54pub trait ExternalObjectSource: Send + Sync {
58 fn get_blob(&self, hash: &ContentHash) -> Result<Option<Blob>>;
59 fn get_tree(&self, hash: &ContentHash) -> Result<Option<Tree>>;
60 fn get_state(&self, id: &StateId) -> Result<Option<State>>;
61 fn list_states(&self) -> Result<Vec<StateId>>;
62}
63
64pub use crate::error::{HeddleError as StoreError, HeddleError, Result};
65
66impl From<CompressionError> for HeddleError {
67 fn from(e: CompressionError) -> Self {
68 HeddleError::Compression(e.to_string())
69 }
70}
71
72#[derive(Clone)]
84pub enum AnyStore {
85 Fs(FsStore),
86}
87
88macro_rules! any_store_dispatch {
94 ($self:ident, $method:ident ( $($arg:expr),* )) => {
95 match $self {
96 AnyStore::Fs(inner) => inner.$method($($arg),*),
97 }
98 };
99}
100
101impl ObjectStore for AnyStore {
102 fn get_blob(&self, hash: &ContentHash) -> Result<Option<Blob>> {
103 match self {
104 AnyStore::Fs(inner) => ObjectStore::get_blob(inner, hash),
105 }
106 }
107 fn put_blob(&self, blob: &Blob) -> Result<ContentHash> {
108 any_store_dispatch!(self, put_blob(blob))
109 }
110 fn get_blob_bytes(&self, hash: &ContentHash) -> Result<Option<bytes::Bytes>> {
111 match self {
112 AnyStore::Fs(inner) => ObjectStore::get_blob_bytes(inner, hash),
113 }
114 }
115 fn blob_size(&self, hash: &ContentHash) -> Result<Option<u64>> {
116 any_store_dispatch!(self, blob_size(hash))
117 }
118 fn loose_blob_path(&self, hash: &ContentHash) -> Option<PathBuf> {
119 any_store_dispatch!(self, loose_blob_path(hash))
120 }
121 fn promote_to_loose_uncompressed(&self, hash: &ContentHash) -> Result<bool> {
122 any_store_dispatch!(self, promote_to_loose_uncompressed(hash))
123 }
124 fn clear_recent_caches(&self) {
125 any_store_dispatch!(self, clear_recent_caches())
126 }
127 fn put_blob_with_hash(&self, blob: &Blob, hash: ContentHash) -> Result<ContentHash> {
128 any_store_dispatch!(self, put_blob_with_hash(blob, hash))
129 }
130 fn has_blob(&self, hash: &ContentHash) -> Result<bool> {
131 any_store_dispatch!(self, has_blob(hash))
132 }
133 fn has_blob_locally(&self, hash: &ContentHash) -> Result<bool> {
134 any_store_dispatch!(self, has_blob_locally(hash))
135 }
136 fn get_tree(&self, hash: &ContentHash) -> Result<Option<Tree>> {
137 match self {
138 AnyStore::Fs(inner) => ObjectStore::get_tree(inner, hash),
139 }
140 }
141 fn get_tree_serialized(&self, hash: &ContentHash) -> Result<Option<Vec<u8>>> {
142 match self {
143 AnyStore::Fs(inner) => ObjectStore::get_tree_serialized(inner, hash),
144 }
145 }
146 fn put_tree(&self, tree: &Tree) -> Result<ContentHash> {
147 any_store_dispatch!(self, put_tree(tree))
148 }
149 fn has_tree(&self, hash: &ContentHash) -> Result<bool> {
150 any_store_dispatch!(self, has_tree(hash))
151 }
152 fn has_tree_locally(&self, hash: &ContentHash) -> Result<bool> {
153 any_store_dispatch!(self, has_tree_locally(hash))
154 }
155 fn get_state(&self, id: &StateId) -> Result<Option<State>> {
156 match self {
157 AnyStore::Fs(inner) => ObjectStore::get_state(inner, id),
158 }
159 }
160 fn put_state(&self, state: &State) -> Result<()> {
161 any_store_dispatch!(self, put_state(state))
162 }
163 fn has_state(&self, id: &StateId) -> Result<bool> {
164 any_store_dispatch!(self, has_state(id))
165 }
166 fn list_states(&self) -> Result<Vec<StateId>> {
167 any_store_dispatch!(self, list_states())
168 }
169 fn get_state_attachment(
170 &self,
171 state: &StateId,
172 id: &StateAttachmentId,
173 ) -> Result<Option<StateAttachment>> {
174 any_store_dispatch!(self, get_state_attachment(state, id))
175 }
176 fn put_state_attachment(&self, attachment: &StateAttachment) -> Result<StateAttachmentId> {
177 any_store_dispatch!(self, put_state_attachment(attachment))
178 }
179 fn list_state_attachments(&self, state: &StateId) -> Result<Vec<StateAttachment>> {
180 any_store_dispatch!(self, list_state_attachments(state))
181 }
182 fn get_action(&self, id: &ActionId) -> Result<Option<Action>> {
183 any_store_dispatch!(self, get_action(id))
184 }
185 fn put_action(&self, action: &mut Action) -> Result<ActionId> {
186 any_store_dispatch!(self, put_action(action))
187 }
188 fn list_actions(&self) -> Result<Vec<ActionId>> {
189 any_store_dispatch!(self, list_actions())
190 }
191 fn list_blobs(&self) -> Result<Vec<ContentHash>> {
192 any_store_dispatch!(self, list_blobs())
193 }
194 fn list_trees(&self) -> Result<Vec<ContentHash>> {
195 any_store_dispatch!(self, list_trees())
196 }
197 fn put_blob_bytes_with_hash(&self, data: &[u8], hash: ContentHash) -> Result<ContentHash> {
198 any_store_dispatch!(self, put_blob_bytes_with_hash(data, hash))
199 }
200 fn put_tree_serialized(&self, data: &[u8], hash: ContentHash) -> Result<ContentHash> {
201 match self {
202 AnyStore::Fs(inner) => ObjectStore::put_tree_serialized(inner, data, hash),
203 }
204 }
205 fn put_state_serialized(&self, data: &[u8], id: StateId) -> Result<()> {
206 any_store_dispatch!(self, put_state_serialized(data, id))
207 }
208 fn put_action_serialized(&self, data: &[u8], id: ActionId) -> Result<()> {
209 any_store_dispatch!(self, put_action_serialized(data, id))
210 }
211 fn get_pack_object(
212 &self,
213 id: &pack::PackObjectId,
214 ) -> Result<Option<(pack::ObjectType, Vec<u8>)>> {
215 any_store_dispatch!(self, get_pack_object(id))
216 }
217 fn put_blobs_packed(&self, blobs: Vec<(ContentHash, Vec<u8>)>) -> Result<()> {
218 any_store_dispatch!(self, put_blobs_packed(blobs))
219 }
220 fn install_pack(&self, pack_data: &[u8], index_data: &[u8]) -> Result<Vec<pack::PackObjectId>> {
221 any_store_dispatch!(self, install_pack(pack_data, index_data))
222 }
223 fn install_pack_streaming(
224 &self,
225 pack_path: &std::path::Path,
226 index_path: &std::path::Path,
227 ) -> Result<Vec<pack::PackObjectId>> {
228 any_store_dispatch!(self, install_pack_streaming(pack_path, index_path))
229 }
230 fn pack_objects(&self, aggressive: bool) -> Result<(u64, u64)> {
231 any_store_dispatch!(self, pack_objects(aggressive))
232 }
233 fn prune_loose_objects(&self) -> Result<(u64, u64)> {
234 any_store_dispatch!(self, prune_loose_objects())
235 }
236 fn begin_snapshot_write_batch(&self) -> Result<()> {
237 any_store_dispatch!(self, begin_snapshot_write_batch())
238 }
239 fn flush_snapshot_write_batch(&self) -> Result<()> {
240 any_store_dispatch!(self, flush_snapshot_write_batch())
241 }
242 fn abort_snapshot_write_batch(&self) {
243 any_store_dispatch!(self, abort_snapshot_write_batch())
244 }
245 fn has_redactions_for_blob(&self, blob: &ContentHash) -> Result<bool> {
246 any_store_dispatch!(self, has_redactions_for_blob(blob))
247 }
248 fn get_redactions_bytes_for_blob(&self, blob: &ContentHash) -> Result<Option<Vec<u8>>> {
249 any_store_dispatch!(self, get_redactions_bytes_for_blob(blob))
250 }
251 fn put_redactions_bytes_for_blob(&self, blob: &ContentHash, bytes: &[u8]) -> Result<()> {
252 any_store_dispatch!(self, put_redactions_bytes_for_blob(blob, bytes))
253 }
254 fn list_blobs_with_redactions(&self) -> Result<Vec<ContentHash>> {
255 any_store_dispatch!(self, list_blobs_with_redactions())
256 }
257 fn has_state_visibility_for_state(&self, state: &StateId) -> Result<bool> {
258 any_store_dispatch!(self, has_state_visibility_for_state(state))
259 }
260 fn get_state_visibility_bytes_for_state(&self, state: &StateId) -> Result<Option<Vec<u8>>> {
261 any_store_dispatch!(self, get_state_visibility_bytes_for_state(state))
262 }
263 fn put_state_visibility_bytes_for_state(&self, state: &StateId, bytes: &[u8]) -> Result<()> {
264 any_store_dispatch!(self, put_state_visibility_bytes_for_state(state, bytes))
265 }
266 fn list_states_with_visibility(&self) -> Result<Vec<StateId>> {
267 any_store_dispatch!(self, list_states_with_visibility())
268 }
269}
270
271impl AnyStore {
272 pub fn set_external_source(&mut self, source: Arc<dyn ExternalObjectSource>) {
274 match self {
275 Self::Fs(store) => store.set_external_source(source),
276 }
277 }
278}
279
280pub trait ObjectStore: Send + Sync {
282 fn get_blob(&self, hash: &ContentHash) -> Result<Option<Blob>>;
283 fn put_blob(&self, blob: &Blob) -> Result<ContentHash>;
284
285 fn get_blob_bytes(&self, hash: &ContentHash) -> Result<Option<bytes::Bytes>> {
296 Ok(self
297 .get_blob(hash)?
298 .map(|blob| bytes::Bytes::from(blob.into_content())))
299 }
300
301 fn blob_size(&self, hash: &ContentHash) -> Result<Option<u64>> {
315 Ok(self.get_blob(hash)?.map(|blob| blob.content().len() as u64))
316 }
317
318 fn loose_blob_path(&self, _hash: &ContentHash) -> Option<PathBuf> {
330 None
331 }
332
333 fn promote_to_loose_uncompressed(&self, _hash: &ContentHash) -> Result<bool> {
370 Ok(false)
371 }
372
373 fn clear_recent_caches(&self) {}
382
383 fn put_blob_with_hash(&self, blob: &Blob, hash: ContentHash) -> Result<ContentHash> {
384 if blob.hash() != hash {
385 return Err(HeddleError::InvalidObject("blob hash mismatch".to_string()));
386 }
387 self.put_blob(blob)
388 }
389
390 fn has_blob(&self, hash: &ContentHash) -> Result<bool>;
391 fn has_blob_locally(&self, hash: &ContentHash) -> Result<bool> {
395 self.has_blob(hash)
396 }
397 fn get_tree(&self, hash: &ContentHash) -> Result<Option<Tree>>;
398 fn put_tree(&self, tree: &Tree) -> Result<ContentHash>;
399 fn has_tree(&self, hash: &ContentHash) -> Result<bool>;
400 fn has_tree_locally(&self, hash: &ContentHash) -> Result<bool> {
403 self.has_tree(hash)
404 }
405 fn get_state(&self, id: &StateId) -> Result<Option<State>>;
406 fn put_state(&self, state: &State) -> Result<()>;
407 fn has_state(&self, id: &StateId) -> Result<bool>;
408 fn list_states(&self) -> Result<Vec<StateId>>;
409 fn get_state_attachment(
410 &self,
411 _state: &StateId,
412 _id: &StateAttachmentId,
413 ) -> Result<Option<StateAttachment>> {
414 Ok(None)
415 }
416 fn put_state_attachment(&self, _attachment: &StateAttachment) -> Result<StateAttachmentId> {
417 Err(HeddleError::InvalidObject(
418 "object store does not support state attachments".to_string(),
419 ))
420 }
421 fn list_state_attachments(&self, _state: &StateId) -> Result<Vec<StateAttachment>> {
422 Ok(Vec::new())
423 }
424 fn get_action(&self, id: &ActionId) -> Result<Option<Action>>;
425 fn put_action(&self, action: &mut Action) -> Result<ActionId>;
426 fn list_actions(&self) -> Result<Vec<ActionId>>;
427 fn list_blobs(&self) -> Result<Vec<ContentHash>>;
428 fn list_trees(&self) -> Result<Vec<ContentHash>>;
429
430 fn put_blob_bytes_with_hash(&self, data: &[u8], hash: ContentHash) -> Result<ContentHash> {
431 self.put_blob_with_hash(&Blob::from_slice(data), hash)
432 }
433
434 fn get_tree_serialized(&self, hash: &ContentHash) -> Result<Option<Vec<u8>>> {
442 Ok(self
443 .get_tree(hash)?
444 .map(|tree| rmp_serde::to_vec(&tree))
445 .transpose()?)
446 }
447
448 fn put_tree_serialized(&self, data: &[u8], hash: ContentHash) -> Result<ContentHash> {
449 let tree: Tree = rmp_serde::from_slice(data)?;
450 tree.validate()?;
451 if tree.hash() != hash {
452 return Err(HeddleError::Corruption {
453 expected: hash,
454 found: tree.hash(),
455 });
456 }
457 self.put_tree(&tree)
458 }
459
460 fn put_state_serialized(&self, data: &[u8], id: StateId) -> Result<()> {
461 let state: State = rmp_serde::from_slice(data)?;
462 let found = state.id();
463 if found != id {
464 return Err(HeddleError::InvalidObject(format!(
465 "state id mismatch: expected {id}, computed {found}"
466 )));
467 }
468 self.put_state(&state)
469 }
470
471 fn put_action_serialized(&self, data: &[u8], id: ActionId) -> Result<()> {
472 let mut action: Action = rmp_serde::from_slice(data)?;
473 let found_id = action.compute_id();
474 if found_id != id {
475 return Err(HeddleError::InvalidObject(format!(
476 "action id mismatch: expected {}, found {}",
477 id, found_id
478 )));
479 }
480 let stored_id = self.put_action(&mut action)?;
481 if stored_id != id {
482 return Err(HeddleError::InvalidObject(format!(
483 "action id mismatch after write: expected {}, found {}",
484 id, stored_id
485 )));
486 }
487 Ok(())
488 }
489
490 fn get_pack_object(
491 &self,
492 id: &pack::PackObjectId,
493 ) -> Result<Option<(pack::ObjectType, Vec<u8>)>> {
494 match id {
495 pack::PackObjectId::Hash(hash) => {
496 if let Some(blob) = self.get_blob(hash)? {
497 return Ok(Some((pack::ObjectType::Blob, blob.content().to_vec())));
498 }
499 if let Some(tree) = self.get_tree(hash)? {
500 return Ok(Some((
501 pack::ObjectType::Tree,
502 rmp_serde::to_vec_named(&tree)?,
503 )));
504 }
505 if let Some(action) = self.get_action(&ActionId::from_hash(*hash))? {
506 return Ok(Some((
507 pack::ObjectType::Action,
508 rmp_serde::to_vec_named(&action)?,
509 )));
510 }
511 Ok(None)
512 }
513 pack::PackObjectId::StateId(change_id) => {
514 if let Some(state) = self.get_state(change_id)? {
515 Ok(Some((
516 pack::ObjectType::State,
517 rmp_serde::to_vec_named(&state)?,
518 )))
519 } else {
520 Ok(None)
521 }
522 }
523 }
524 }
525
526 fn put_blobs_packed(&self, blobs: Vec<(ContentHash, Vec<u8>)>) -> Result<()> {
536 for (hash, data) in blobs {
537 if !self.has_blob(&hash)? {
538 self.put_blob_bytes_with_hash(&data, hash)?;
539 }
540 }
541 Ok(())
542 }
543
544 fn install_pack(&self, pack_data: &[u8], index_data: &[u8]) -> Result<Vec<pack::PackObjectId>> {
545 let reader = pack::PackReader::from_slice(pack_data, index_data)?;
546 let ids = reader.list_ids();
547 for id in &ids {
548 let Some((obj_type, data)) = reader.get_object(id)? else {
549 continue;
550 };
551 match (id, obj_type) {
552 (pack::PackObjectId::Hash(hash), pack::ObjectType::Blob) => {
553 self.put_blob_bytes_with_hash(&data, *hash)?;
554 }
555 (pack::PackObjectId::Hash(hash), pack::ObjectType::Tree) => {
556 self.put_tree_serialized(&data, *hash)?;
557 }
558 (pack::PackObjectId::Hash(hash), pack::ObjectType::Action) => {
559 self.put_action_serialized(&data, ActionId::from_hash(*hash))?;
560 }
561 (pack::PackObjectId::StateId(change_id), pack::ObjectType::State) => {
562 self.put_state_serialized(&data, *change_id)?;
563 }
564 _ => {
565 return Err(HeddleError::InvalidObject(format!(
566 "unsupported native pack object: {:?} {:?}",
567 id, obj_type
568 )));
569 }
570 }
571 }
572 Ok(ids)
573 }
574
575 fn install_pack_streaming(
592 &self,
593 pack_path: &std::path::Path,
594 index_path: &std::path::Path,
595 ) -> Result<Vec<pack::PackObjectId>> {
596 let pack_data = std::fs::read(pack_path).map_err(StoreError::from)?;
597 let index_data = std::fs::read(index_path).map_err(StoreError::from)?;
598 let ids = self.install_pack(&pack_data, &index_data)?;
599 let _ = std::fs::remove_file(pack_path);
603 let _ = std::fs::remove_file(index_path);
604 Ok(ids)
605 }
606
607 fn pack_objects(&self, aggressive: bool) -> Result<(u64, u64)> {
608 let _ = aggressive;
609 Ok((0, 0))
610 }
611
612 fn prune_loose_objects(&self) -> Result<(u64, u64)> {
613 Ok((0, 0))
614 }
615
616 fn begin_snapshot_write_batch(&self) -> Result<()> {
617 Ok(())
618 }
619
620 fn flush_snapshot_write_batch(&self) -> Result<()> {
621 Ok(())
622 }
623
624 fn abort_snapshot_write_batch(&self) {}
625
626 fn has_redactions_for_blob(&self, _blob: &ContentHash) -> Result<bool> {
638 Ok(false)
639 }
640
641 fn get_redactions_bytes_for_blob(&self, _blob: &ContentHash) -> Result<Option<Vec<u8>>> {
649 Ok(None)
650 }
651
652 fn put_redactions_bytes_for_blob(&self, _blob: &ContentHash, _bytes: &[u8]) -> Result<()> {
661 Err(HeddleError::InvalidObject(
662 "this object store does not support persisting redactions".to_string(),
663 ))
664 }
665
666 fn list_blobs_with_redactions(&self) -> Result<Vec<ContentHash>> {
673 Ok(Vec::new())
674 }
675
676 fn has_state_visibility_for_state(&self, _state: &StateId) -> Result<bool> {
686 Ok(false)
687 }
688
689 fn get_state_visibility_bytes_for_state(&self, _state: &StateId) -> Result<Option<Vec<u8>>> {
695 Ok(None)
696 }
697
698 fn put_state_visibility_bytes_for_state(&self, _state: &StateId, _bytes: &[u8]) -> Result<()> {
703 Err(HeddleError::InvalidObject(
704 "this object store does not support persisting state visibility".to_string(),
705 ))
706 }
707
708 fn list_states_with_visibility(&self) -> Result<Vec<StateId>> {
712 Ok(Vec::new())
713 }
714}
715
716#[cfg(test)]
717mod any_store_tests {
718 use tempfile::TempDir;
719
720 use super::*;
721 use crate::object::{Attribution, Operation, Principal};
722
723 fn fs_any_store() -> (TempDir, AnyStore) {
724 let temp = TempDir::new().unwrap();
725 let store = FsStore::new(temp.path().join(".heddle"));
726 store.init().unwrap();
727 (temp, AnyStore::Fs(store))
728 }
729
730 #[test]
736 fn fs_variant_dispatches_every_object_store_method() {
737 let (_temp, store) = fs_any_store();
738
739 let blob = Blob::from("any-store dispatch blob");
741 let blob_hash = store.put_blob(&blob).unwrap();
742 assert_eq!(
743 ObjectStore::get_blob(&store, &blob_hash)
744 .unwrap()
745 .unwrap()
746 .content(),
747 blob.content()
748 );
749 assert!(store.has_blob(&blob_hash).unwrap());
750 assert_eq!(
751 ObjectStore::get_blob_bytes(&store, &blob_hash)
752 .unwrap()
753 .unwrap()
754 .as_ref(),
755 blob.content()
756 );
757 assert_eq!(
758 store.blob_size(&blob_hash).unwrap().unwrap(),
759 blob.content().len() as u64
760 );
761 assert!(store.loose_blob_path(&blob_hash).is_some());
762 store.promote_to_loose_uncompressed(&blob_hash).unwrap();
763 assert!(store.list_blobs().unwrap().contains(&blob_hash));
764
765 let bytes_blob = Blob::from("put-with-hash blob");
766 let bytes_hash = bytes_blob.hash();
767 assert_eq!(
768 store.put_blob_with_hash(&bytes_blob, bytes_hash).unwrap(),
769 bytes_hash
770 );
771 let raw_blob = Blob::from("raw bytes blob");
772 let raw_hash = raw_blob.hash();
773 assert_eq!(
774 store
775 .put_blob_bytes_with_hash(raw_blob.content(), raw_hash)
776 .unwrap(),
777 raw_hash
778 );
779
780 let tree = Tree::new();
782 let tree_hash = store.put_tree(&tree).unwrap();
783 assert!(ObjectStore::get_tree(&store, &tree_hash).unwrap().is_some());
784 assert!(store.has_tree(&tree_hash).unwrap());
785 assert!(store.list_trees().unwrap().contains(&tree_hash));
786 let tree2 = Tree::new();
787 let tree2_bytes = rmp_serde::to_vec_named(&tree2).unwrap();
788 assert_eq!(
789 store
790 .put_tree_serialized(&tree2_bytes, tree2.hash())
791 .unwrap(),
792 tree2.hash()
793 );
794
795 let attribution =
797 Attribution::human(Principal::new("AnyStore Test", "anystore@example.com"));
798 let state = State::new(tree_hash, vec![], attribution.clone());
799 let state_id = state.id();
800 store.put_state(&state).unwrap();
801 assert!(ObjectStore::get_state(&store, &state_id).unwrap().is_some());
802 assert!(store.has_state(&state_id).unwrap());
803 assert!(store.list_states().unwrap().contains(&state_id));
804 let state2 = State::new(tree2.hash(), vec![], attribution.clone());
805 let state2_bytes = rmp_serde::to_vec_named(&state2).unwrap();
806 store
807 .put_state_serialized(&state2_bytes, state2.id())
808 .unwrap();
809
810 let mut action = Action::new(
812 None,
813 StateId::from_bytes([3; 32]),
814 Operation::Snapshot,
815 "any-store action",
816 attribution,
817 );
818 let action_id = store.put_action(&mut action).unwrap();
819 assert!(store.get_action(&action_id).unwrap().is_some());
820 assert!(store.list_actions().unwrap().contains(&action_id));
821 let action_bytes = rmp_serde::to_vec_named(&action).unwrap();
822 store
823 .put_action_serialized(&action_bytes, action_id)
824 .unwrap();
825
826 let packed = Blob::from("packed-via-any-store");
828 let packed_hash = packed.hash();
829 store
830 .put_blobs_packed(vec![(packed_hash, packed.into_content())])
831 .unwrap();
832 assert!(
833 store
834 .get_pack_object(&pack::PackObjectId::Hash(packed_hash))
835 .unwrap()
836 .is_some()
837 );
838 store.pack_objects(false).unwrap();
839 store.prune_loose_objects().unwrap();
840 let _ = store.install_pack(&[], &[]);
844 let _ = store.install_pack_streaming(
845 std::path::Path::new("/nonexistent/pack"),
846 std::path::Path::new("/nonexistent/idx"),
847 );
848
849 store.begin_snapshot_write_batch().unwrap();
851 store.flush_snapshot_write_batch().unwrap();
852 store.begin_snapshot_write_batch().unwrap();
853 store.abort_snapshot_write_batch();
854
855 let redaction = b"any-store redaction bytes";
857 store
858 .put_redactions_bytes_for_blob(&blob_hash, redaction)
859 .unwrap();
860 assert!(store.has_redactions_for_blob(&blob_hash).unwrap());
861 assert_eq!(
862 store
863 .get_redactions_bytes_for_blob(&blob_hash)
864 .unwrap()
865 .as_deref(),
866 Some(redaction.as_slice())
867 );
868 assert!(
869 store
870 .list_blobs_with_redactions()
871 .unwrap()
872 .contains(&blob_hash)
873 );
874
875 let state_visibility = b"any-store state visibility bytes";
877 store
878 .put_state_visibility_bytes_for_state(&state_id, state_visibility)
879 .unwrap();
880 assert!(store.has_state_visibility_for_state(&state_id).unwrap());
881 assert_eq!(
882 store
883 .get_state_visibility_bytes_for_state(&state_id)
884 .unwrap()
885 .as_deref(),
886 Some(state_visibility.as_slice())
887 );
888 assert!(
889 store
890 .list_states_with_visibility()
891 .unwrap()
892 .contains(&state_id)
893 );
894
895 store.clear_recent_caches();
897 }
898}