Skip to main content

coven_storage/
cloud_object_storage.rs

1//! The storage service trait: exact-slot providers expose protocol-object and
2//! blob operations over the addressing and protection model in
3//! [`coven_protocol::objects`].
4
5use async_trait::async_trait;
6
7use std::path::Path;
8
9use coven_protocol::objects::{
10    BlobSpoolProtection, BlobSpoolWrite, BlobWriteAuthority, ExactObjectRef, ObjectSlot,
11    PreparedExactObject, ProtocolObjectContext, ResolvedProviderBinding, StorageError,
12};
13
14pub const BLOB_TOMBSTONE_PREFIX: &str = "blob_tombstones/";
15
16pub fn blob_tombstone_object_id(
17    stored: &coven_protocol::blob::locator::StoredBlobRef,
18) -> coven_protocol::store_commit::ObjectHash {
19    coven_protocol::remote_object::remote_object_id(stored.object())
20}
21
22pub fn blob_tombstone_key(
23    stored: &coven_protocol::blob::locator::StoredBlobRef,
24    suffix: &str,
25) -> String {
26    format!(
27        "{BLOB_TOMBSTONE_PREFIX}{}{suffix}",
28        blob_tombstone_object_id(stored)
29    )
30}
31
32#[derive(Clone, Debug)]
33pub enum ListedBlobTombstone {
34    Opened {
35        object_id: coven_protocol::store_commit::ObjectHash,
36        plaintext: Vec<u8>,
37    },
38    InvalidKey {
39        provider_key: String,
40    },
41    InvalidBody {
42        provider_key: String,
43        source: std::sync::Arc<coven_keys::encryption::EncryptionError>,
44    },
45}
46
47#[async_trait]
48pub trait CloudSyncObjectStorage: Send + Sync {
49    /// Return the cloud home's fixed blob path representation.
50    fn blob_path_scheme(&self) -> crate::BlobPathScheme;
51
52    /// Verify that the retained provider session is reachable and usable.
53    async fn probe_provider(&self) -> Result<(), StorageError>;
54
55    /// The running total of provider operations issued through this storage's
56    /// home, so a run's stage timings can report each stage's count beside its
57    /// wall time. `None` when nothing is counting — see
58    /// [`CloudHome::provider_requests`](crate::cloud::CloudHome::provider_requests).
59    fn provider_requests(
60        &self,
61    ) -> Option<std::sync::Arc<dyn coven_foundation::stage_timing::ProviderRequests>>;
62
63    /// Apply and read back one provider membership-access state.
64    async fn set_member_access(
65        &self,
66        state: crate::cloud::CloudAccessState,
67    ) -> Result<crate::cloud::CloudAccessOutcome, StorageError>;
68
69    async fn read_blob_tombstone(
70        &self,
71        stored: &coven_protocol::blob::locator::StoredBlobRef,
72    ) -> Result<Option<Vec<u8>>, StorageError>;
73
74    async fn write_blob_tombstone(
75        &self,
76        stored: &coven_protocol::blob::locator::StoredBlobRef,
77        plaintext: Vec<u8>,
78    ) -> Result<(), StorageError>;
79
80    async fn list_blob_tombstones(&self) -> Result<Vec<ListedBlobTombstone>, StorageError>;
81
82    async fn blob_tombstone_exists(
83        &self,
84        stored: &coven_protocol::blob::locator::StoredBlobRef,
85    ) -> Result<bool, StorageError>;
86
87    async fn delete_blob_tombstone(
88        &self,
89        stored: &coven_protocol::blob::locator::StoredBlobRef,
90    ) -> Result<(), StorageError>;
91
92    #[cfg(any(test, feature = "test-utils"))]
93    async fn read_provider_bytes_for_test(&self, key: &str) -> Result<Vec<u8>, StorageError>;
94
95    #[cfg(any(test, feature = "test-utils"))]
96    async fn write_provider_bytes_for_test(
97        &self,
98        key: &str,
99        bytes: Vec<u8>,
100    ) -> Result<(), StorageError>;
101
102    #[cfg(any(test, feature = "test-utils"))]
103    async fn list_provider_keys_for_test(&self, prefix: &str) -> Result<Vec<String>, StorageError>;
104
105    #[cfg(any(test, feature = "test-utils"))]
106    async fn provider_key_exists_for_test(&self, key: &str) -> Result<bool, StorageError>;
107
108    async fn reserve_cross_principal_response_slot(
109        &self,
110        probe_id: coven_protocol::provider::ProviderProbeId,
111    ) -> Result<ObjectSlot, coven_protocol::provider::ProviderProbeError>;
112
113    async fn prepare_cross_principal_challenge(
114        &self,
115        publication_journal: &dyn coven_protocol::provider::DeviceJoinChallengePublicationJournal,
116        probe_id: coven_protocol::provider::ProviderProbeId,
117        store: &coven_protocol::StoreProviderBinding,
118        context: &coven_protocol::provider::CrossPrincipalChallengeContext,
119        administrator_signer: &dyn coven_keys::keys::DeviceSigningAuthority,
120    ) -> Result<
121        coven_protocol::provider::CrossPrincipalProbeChallenge,
122        coven_protocol::provider::ProviderProbeError,
123    >;
124
125    async fn settle_cross_principal_challenge(
126        &self,
127        publication_journal: &dyn coven_protocol::provider::DeviceJoinChallengePublicationJournal,
128        authorization: &coven_protocol::provider::DeviceJoinChallengePublicationAuthorization,
129        challenge: &coven_protocol::provider::CrossPrincipalProbeChallenge,
130        context: &coven_protocol::provider::CrossPrincipalChallengeContext,
131        store: &coven_protocol::StoreProviderBinding,
132    ) -> Result<
133        coven_protocol::provider::CrossPrincipalProbeChallenge,
134        coven_protocol::provider::ProviderProbeError,
135    >;
136
137    async fn create_cross_principal_response(
138        &self,
139        challenge: &coven_protocol::provider::CrossPrincipalProbeChallenge,
140        context: &coven_protocol::provider::CrossPrincipalResponseContext,
141        store: &coven_protocol::StoreProviderBinding,
142        administrator_signing_pubkey: &str,
143        peer_signer: &coven_keys::keys::UserKeypair,
144    ) -> Result<
145        coven_protocol::provider::CrossPrincipalProbeResponse,
146        coven_protocol::provider::ProviderProbeError,
147    >;
148
149    async fn complete_cross_principal_probe(
150        &self,
151        journal: &dyn coven_protocol::provider::ProviderProbeJournal,
152        challenge: &coven_protocol::provider::CrossPrincipalProbeChallenge,
153        response: &coven_protocol::provider::CrossPrincipalProbeResponse,
154        context: &coven_protocol::provider::CrossPrincipalResponseContext,
155        store: &coven_protocol::StoreProviderBinding,
156        administrator_signer: &dyn coven_keys::keys::DeviceSigningAuthority,
157        peer_signing_pubkey: &str,
158    ) -> Result<
159        coven_protocol::provider::CrossPrincipalProbeReceipt,
160        coven_protocol::provider::ProviderProbeError,
161    >;
162
163    async fn probe_exact_slots(
164        &self,
165        journal: &dyn coven_protocol::provider::ProviderProbeJournal,
166        probe_id: coven_protocol::provider::ProviderProbeId,
167        binding: &ResolvedProviderBinding,
168    ) -> Result<
169        coven_protocol::provider::ExactSlotProbeReceipt,
170        coven_protocol::provider::ProviderProbeError,
171    >;
172
173    /// Observe the exact object identity currently occupying `slot` without
174    /// opening its protocol bytes.
175    async fn observe_exact_slot(
176        &self,
177        slot: &ObjectSlot,
178    ) -> Result<Option<ExactObjectRef>, StorageError>;
179
180    /// Delete whatever occupies `slot` and prove the exact slot is absent.
181    async fn delete_exact_slot_and_verify_absent(
182        &self,
183        slot: &ObjectSlot,
184    ) -> Result<(), StorageError>;
185
186    /// The value identity of the key used for new Store blobs. Browsable homes
187    /// have no key fingerprint; the key-bearing service remains inside storage.
188    fn store_blob_key_fingerprint(
189        &self,
190    ) -> Result<Option<coven_keys::encryption::KeyFingerprint>, StorageError>;
191
192    /// Create the signed root's proof that this storage owns the Store key.
193    fn create_store_key_confirmation(
194        &self,
195        creation_id: coven_protocol::store_commit::StoreCreationId,
196    ) -> Result<coven_protocol::store_commit::StoreKeyConfirmation, StorageError>;
197
198    /// Verify the signed root's Store-key proof with this storage's retained key.
199    fn verify_store_key_confirmation(
200        &self,
201        creation_id: coven_protocol::store_commit::StoreCreationId,
202        confirmation: &coven_protocol::store_commit::StoreKeyConfirmation,
203    ) -> Result<(), StorageError>;
204
205    /// Resolve the provider corpus and authenticated principal used by this
206    /// adapter. Registrations bind the principal before allocating descendants.
207    async fn provider_binding(&self) -> Result<ResolvedProviderBinding, StorageError>;
208
209    /// Reserve the exact provider slot for a protocol object.
210    async fn allocate_protocol_slot(
211        &self,
212        context: &ProtocolObjectContext,
213        semantic_prefix: &str,
214        extension: &str,
215    ) -> Result<ObjectSlot, StorageError>;
216
217    /// Seal canonical protocol bytes once and bind their exact stored size/hash.
218    fn prepare_protocol_object(
219        &self,
220        context: &ProtocolObjectContext,
221        slot: ObjectSlot,
222        semantic_prefix: &str,
223        data: Vec<u8>,
224    ) -> Result<PreparedExactObject, StorageError>;
225
226    /// Open a locally retained prepared object without fetching it from the
227    /// provider. This verifies spool bytes at publication boundaries while the
228    /// provider adapter separately proves the stored representation.
229    async fn open_prepared_protocol_object(
230        &self,
231        context: &ProtocolObjectContext,
232        prepared: &PreparedExactObject,
233        semantic_prefix: &str,
234    ) -> Result<Vec<u8>, StorageError>;
235
236    /// Verify that a locally retained prepared object opens to the canonical
237    /// bytes its durable journal records.
238    async fn verify_prepared_protocol_object(
239        &self,
240        context: &ProtocolObjectContext,
241        prepared: &PreparedExactObject,
242        semantic_prefix: &str,
243        expected: &[u8],
244    ) -> Result<(), StorageError> {
245        if self
246            .open_prepared_protocol_object(context, prepared, semantic_prefix)
247            .await?
248            == expected
249        {
250            return Ok(());
251        }
252        Err(StorageError::PreparedObjectMismatch(
253            prepared.reference().slot().logical_key().to_string(),
254        ))
255    }
256
257    /// Verify the retained semantic bytes before creating their exact stored
258    /// representation at the provider.
259    async fn create_verified_protocol_object(
260        &self,
261        context: &ProtocolObjectContext,
262        prepared: &PreparedExactObject,
263        semantic_prefix: &str,
264        expected: &[u8],
265    ) -> Result<(), StorageError> {
266        self.verify_prepared_protocol_object(context, prepared, semantic_prefix, expected)
267            .await?;
268        self.create_protocol_object(prepared).await
269    }
270
271    /// Create the prepared bytes at their reserved slot, settling ambiguous
272    /// responses through the provider's configured exact-upload verification.
273    /// A successful return guarantees that every client can immediately read
274    /// the object through its exact slot or reference. Prefix listings may lag;
275    /// an exact read may not report the created object as absent.
276    async fn create_protocol_object(
277        &self,
278        prepared: &PreparedExactObject,
279    ) -> Result<(), StorageError>;
280
281    /// Create one bounded mutable protocol record at its permanent slot and
282    /// return the provider revision observed with the exact stored bytes.
283    async fn create_versioned_protocol_record(
284        &self,
285        context: &ProtocolObjectContext,
286        prepared: &PreparedExactObject,
287        semantic_prefix: &str,
288        expected: &[u8],
289    ) -> Result<crate::cloud::CloudObjectVersion, StorageError>;
290
291    /// Read and open one exact Store protocol object using the signed
292    /// semantic prefix as encryption AAD.
293    async fn read_protocol_object(
294        &self,
295        context: &ProtocolObjectContext,
296        object: &ExactObjectRef,
297        semantic_prefix: &str,
298    ) -> Result<Vec<u8>, StorageError>;
299
300    /// Read and open one exact Store protocol object while reporting cumulative
301    /// provider bytes as each response buffer arrives.
302    async fn read_protocol_object_with_progress(
303        &self,
304        context: &ProtocolObjectContext,
305        object: &ExactObjectRef,
306        semantic_prefix: &str,
307        progress: crate::cloud::DownloadProgress,
308    ) -> Result<Vec<u8>, StorageError>;
309
310    /// Read and open the mutable Store record at `slot`, retaining the opaque
311    /// provider revision required for its next conditional replacement.
312    async fn read_versioned_protocol_record(
313        &self,
314        context: &ProtocolObjectContext,
315        slot: &ObjectSlot,
316        semantic_prefix: &str,
317    ) -> Result<(Vec<u8>, crate::cloud::CloudObjectVersion), StorageError>;
318
319    /// Seal and replace the mutable Store record only if its provider revision
320    /// still equals `expected`.
321    async fn replace_protocol_record_if_version(
322        &self,
323        context: &ProtocolObjectContext,
324        slot: &ObjectSlot,
325        semantic_prefix: &str,
326        expected: &crate::cloud::CloudObjectVersion,
327        data: Vec<u8>,
328    ) -> Result<crate::cloud::ConditionalWriteOutcome, StorageError>;
329
330    /// Name every slot under `listing_prefix` that holds a `context` object.
331    ///
332    /// A listing is not evidence. It decides which bytes are worth fetching and
333    /// nothing else: each slot it yields is read and verified exactly as one
334    /// named by a signed reference would be, so a listing that omits, invents,
335    /// or reorders entries changes how many round trips a reader makes and
336    /// never what it believes. Slots whose logical key is not one this domain
337    /// writes are dropped here rather than fetched.
338    ///
339    /// This exists because a chain of slots that each name the next costs one
340    /// round trip per link to walk, while the slots themselves are named by
341    /// coordinate and so share a prefix a provider can enumerate at once.
342    async fn list_protocol_slots(
343        &self,
344        context: &ProtocolObjectContext,
345        listing_prefix: &str,
346    ) -> Result<Vec<ObjectSlot>, StorageError>;
347
348    /// Read one predecessor-reserved successor slot and return both its opened
349    /// bytes and the completed exact reference derived from the stored bytes.
350    async fn read_protocol_slot(
351        &self,
352        context: &ProtocolObjectContext,
353        slot: &ObjectSlot,
354        semantic_prefix: &str,
355    ) -> Result<(Vec<u8>, ExactObjectRef), StorageError>;
356
357    /// Read one predecessor-reserved successor slot while retaining its exact
358    /// stored representation for a durable retry journal.
359    async fn read_prepared_protocol_slot(
360        &self,
361        context: &ProtocolObjectContext,
362        slot: &ObjectSlot,
363        semantic_prefix: &str,
364    ) -> Result<(Vec<u8>, PreparedExactObject), StorageError>;
365
366    /// Delete one exact Store protocol object and verify absence.
367    async fn delete_protocol_object(&self, object: &ExactObjectRef) -> Result<(), StorageError>;
368
369    /// Reserve the exact provider slot for a stored blob body.
370    async fn allocate_blob_slot(
371        &self,
372        locator: &coven_protocol::blob::locator::BlobLocator,
373        authority: &BlobWriteAuthority<'_>,
374    ) -> Result<ObjectSlot, StorageError>;
375
376    /// Verify one plaintext source against its locator and write the exact stored
377    /// representation through the caller-owned spool stage. The stage's owner
378    /// determines the file and directory durability barriers.
379    async fn seal_blob_to_spool(
380        &self,
381        locator: &coven_protocol::blob::locator::BlobLocator,
382        authority: &BlobWriteAuthority<'_>,
383        protection: BlobSpoolProtection,
384        plaintext_file: &Path,
385        spool: coven_foundation::local_file::AtomicStagedFile,
386        progress: crate::cloud::PreparationProgress,
387    ) -> Result<BlobSpoolWrite, StorageError>;
388
389    /// Seal a Store-audience blob without handing the Store key to the caller.
390    async fn seal_store_blob_to_spool(
391        &self,
392        locator: &coven_protocol::blob::locator::BlobLocator,
393        authority: &BlobWriteAuthority<'_>,
394        plaintext_file: &Path,
395        spool: coven_foundation::local_file::AtomicStagedFile,
396        progress: crate::cloud::PreparationProgress,
397    ) -> Result<BlobSpoolWrite, StorageError>;
398
399    /// Derive an exact reference from an immutable stored blob file.
400    async fn prepare_blob_object(
401        &self,
402        locator: &coven_protocol::blob::locator::BlobLocator,
403        authority: &BlobWriteAuthority<'_>,
404        slot: ObjectSlot,
405        stored_file: &Path,
406    ) -> Result<coven_protocol::blob::locator::StoredBlobRef, StorageError>;
407
408    /// Create the exact stored blob body from its immutable local file.
409    async fn create_blob_object_from_file(
410        &self,
411        blob: &coven_protocol::blob::locator::StoredBlobRef,
412        authority: &BlobWriteAuthority<'_>,
413        stored_file: &Path,
414        control: &crate::cloud::UploadControl,
415    ) -> Result<(), StorageError>;
416
417    /// Read one exact stored blob body and verify its signed size/hash reference.
418    async fn verify_blob_object(
419        &self,
420        blob: &coven_protocol::blob::locator::StoredBlobRef,
421    ) -> Result<(), StorageError>;
422
423    /// Download and exact-verify the stored object into the caller-owned stage,
424    /// open it under the audience-owned protection, and return the unpublished
425    /// plaintext only after its locator size and hash have also been verified.
426    async fn stage_verified_blob_plaintext(
427        &self,
428        blob: &coven_protocol::blob::locator::StoredBlobRef,
429        protection: BlobSpoolProtection,
430        stage: coven_foundation::local_file::AtomicStagedFile,
431        progress: crate::cloud::DownloadProgress,
432    ) -> Result<coven_foundation::local_file::AtomicStagedFile, StorageError>;
433
434    /// Open and verify a Store-audience blob without exposing the Store key.
435    async fn stage_verified_store_blob_plaintext(
436        &self,
437        blob: &coven_protocol::blob::locator::StoredBlobRef,
438        stage: coven_foundation::local_file::AtomicStagedFile,
439        progress: crate::cloud::DownloadProgress,
440    ) -> Result<coven_foundation::local_file::AtomicStagedFile, StorageError>;
441
442    /// Open a reader that serves plaintext ranges of a stored blob by fetching
443    /// only the sealed chunks covering each range. The ranged counterpart of
444    /// [`Self::stage_verified_blob_plaintext`], which materializes the whole
445    /// blob; a host seeking around a large blob opens this instead so a range
446    /// costs its own bytes rather than the object's.
447    async fn open_blob_range_reader(
448        &self,
449        blob: &coven_protocol::blob::locator::StoredBlobRef,
450        protection: BlobSpoolProtection,
451    ) -> Result<crate::BlobRangeReader, StorageError>;
452
453    /// Open Store-audience ranges without exposing the Store key.
454    async fn open_store_blob_range_reader(
455        &self,
456        blob: &coven_protocol::blob::locator::StoredBlobRef,
457    ) -> Result<crate::BlobRangeReader, StorageError>;
458
459    /// Delete one exact stored blob body.
460    async fn delete_blob_object(
461        &self,
462        blob: &coven_protocol::blob::locator::StoredBlobRef,
463    ) -> Result<(), StorageError>;
464}