Skip to main content

triblespace_core/collection/
api.rs

1//! Store-centric publication and immutable reads for canonical collections.
2//!
3//! A collection is its descriptor handle. Registering a descriptor through
4//! [`CollectionStoreExt::register_collection`] stores its complete attachment
5//! closure, while later operations take only that handle. The
6//! descriptor's mandatory policy is therefore part of the collection's
7//! identity rather than a caller-supplied policy which could disagree with it.
8//!
9//! Local publication is deliberately unconditional: a store may record any
10//! structurally valid, strictly signed commit. Authority is enforced when a
11//! an admitted cover or logical value is constructed. The descriptor policy
12//! independently governs READ and WRITE admission.
13
14use std::collections::{BTreeMap, BTreeSet};
15use std::convert::Infallible;
16use std::error::Error;
17use std::fmt;
18use std::future::Future;
19use std::num::NonZeroUsize;
20use std::sync::Arc;
21
22use ed25519_dalek::{SigningKey, VerifyingKey};
23
24use crate::blob::encodings::simplearchive::SimpleArchive;
25use crate::blob::Blob;
26use crate::capability::{
27    capability_quorum_authorized_subjects, capability_quorum_decide, CapabilityHandle,
28    CapabilityProof, CapabilityRequest, CapabilityResource, QuorumOutcome,
29};
30use crate::id::Id;
31use crate::inline::encodings::hash::Handle;
32use crate::inline::{Inline, InlineEncoding};
33use crate::patch::{Blake3Merkle, IdentitySchema, PATCH};
34use crate::repo::async_store::AsyncBlobStoreAcquire;
35use crate::repo::{BlobStoreGet, BlobStorePut, CapabilityProofRead};
36use crate::repo::{CapabilityProofStore, SnapshotSource, Store, StoreRead, StoreSnapshot};
37use crate::trible::{Fragment, TribleSet};
38
39use super::exact_derived::CollectionRealizationError;
40use super::simplearchive_union::PreparedCollectionCommit;
41use super::{
42    descriptor, read_capability, write_capability, Collection, CollectionCommit, CollectionData,
43    CollectionEncoding, CollectionHandle, CollectionSnapshot, CollectionStore, CollectionTypeError,
44    RecordDecodeError, TryFromCover, TryFromCoverError,
45};
46use super::{
47    AdmissionPolicy, AttachedSnapshot, CanonicalAttachment, CanonicalDerivation,
48    CollectionAttachment, CollectionDerivation, CollectionPolicy, DeriveMapping, MapMapping,
49};
50
51/// Failure to discover the resident capability evidence used for collection
52/// admission.
53///
54/// Individual candidate proofs are untrusted evidence: an invalid signature,
55/// wrong action, or unavailable definition merely grants nothing. This
56/// error is reserved for failure of the proof-store observation itself, where
57/// silently returning a smaller cover would confuse unavailable evidence with
58/// negative authorization.
59#[derive(Debug)]
60pub enum CollectionEvidenceDiscoveryError<ProofsError> {
61    /// The store could not enumerate its resident proof snapshot.
62    Proofs(ProofsError),
63}
64
65impl<ProofsError> fmt::Display for CollectionEvidenceDiscoveryError<ProofsError>
66where
67    ProofsError: fmt::Display,
68{
69    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
70        match self {
71            Self::Proofs(source) => {
72                write!(
73                    formatter,
74                    "failed to enumerate resident capability proofs: {source}"
75                )
76            }
77        }
78    }
79}
80
81impl<ProofsError> Error for CollectionEvidenceDiscoveryError<ProofsError>
82where
83    ProofsError: Error + 'static,
84{
85    fn source(&self) -> Option<&(dyn Error + 'static)> {
86        match self {
87            Self::Proofs(source) => Some(source),
88        }
89    }
90}
91
92/// Failure while reading a collection descriptor and its local policy by
93/// content identity.
94#[derive(Debug)]
95pub enum CollectionDescriptorError<GetError> {
96    /// The descriptor blob could not be fetched.
97    Get {
98        /// Requested collection identity.
99        collection: CollectionHandle,
100        /// Backend fetch failure.
101        source: GetError,
102    },
103    /// The archive could not be decoded or its singular policy could not be read.
104    Invalid {
105        /// Requested collection identity.
106        collection: CollectionHandle,
107        /// Exact structural failure.
108        source: RecordDecodeError,
109    },
110}
111
112impl<GetError> fmt::Display for CollectionDescriptorError<GetError>
113where
114    GetError: fmt::Display,
115{
116    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
117        match self {
118            Self::Get { collection, source } => write!(
119                formatter,
120                "failed to fetch collection descriptor {}: {source}",
121                hex::encode_upper(collection.raw),
122            ),
123            Self::Invalid { collection, source } => write!(
124                formatter,
125                "collection descriptor {} is invalid: {source}",
126                hex::encode_upper(collection.raw),
127            ),
128        }
129    }
130}
131
132impl<GetError> Error for CollectionDescriptorError<GetError>
133where
134    GetError: Error + 'static,
135{
136    fn source(&self) -> Option<&(dyn Error + 'static)> {
137        match self {
138            Self::Get { source, .. } => Some(source),
139            Self::Invalid { source, .. } => Some(source),
140        }
141    }
142}
143
144/// Failure to open an existing collection as one concrete member encoding.
145///
146/// Opening is a read-only validation boundary. It neither registers the
147/// descriptor nor writes any of its attachment closure into the store.
148#[derive(Debug)]
149pub enum CollectionOpenError<GetError> {
150    /// The descriptor could not be fetched or decoded generically.
151    Descriptor(CollectionDescriptorError<GetError>),
152    /// The descriptor does not denote the requested member encoding.
153    WrongType(CollectionTypeError),
154}
155
156impl<GetError> fmt::Display for CollectionOpenError<GetError>
157where
158    GetError: fmt::Display,
159{
160    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
161        match self {
162            Self::Descriptor(source) => source.fmt(formatter),
163            Self::WrongType(source) => write!(formatter, "wrong collection type: {source}"),
164        }
165    }
166}
167
168impl<GetError> Error for CollectionOpenError<GetError>
169where
170    GetError: Error + 'static,
171{
172    fn source(&self) -> Option<&(dyn Error + 'static)> {
173        match self {
174            Self::Descriptor(source) => Some(source),
175            Self::WrongType(source) => Some(source),
176        }
177    }
178}
179
180/// Failure to persist one root-issued, unbounded collection grant.
181///
182/// The operation validates the descriptor and the exact action roots before
183/// writing one self-contained native proof record.
184#[derive(Debug)]
185pub enum CollectionGrantError<SnapshotError, GetError, InsertError> {
186    /// The store could not freeze the read boundary used to validate policy.
187    Snapshot(SnapshotError),
188    /// The collection descriptor was unavailable or malformed.
189    Descriptor(CollectionDescriptorError<GetError>),
190    /// The collection action is already open and therefore needs no proof.
191    OpenPolicy {
192        /// Exact action whose policy is open.
193        capability: CapabilityHandle,
194        /// Exact collection whose action policy is open.
195        collection: CollectionHandle,
196    },
197    /// The supplied signing key is not one of this collection action's roots.
198    RootNotAuthorized {
199        /// Exact action whose policy rejected the signer.
200        capability: CapabilityHandle,
201        /// Exact collection whose policy rejected the signer.
202        collection: CollectionHandle,
203        /// Public half of the supplied root signing key.
204        root: VerifyingKey,
205    },
206    /// The native proof record could not be inserted.
207    ProofInsert(InsertError),
208}
209
210impl<SnapshotError, GetError, InsertError> fmt::Display
211    for CollectionGrantError<SnapshotError, GetError, InsertError>
212where
213    SnapshotError: fmt::Display,
214    GetError: fmt::Display,
215    InsertError: fmt::Display,
216{
217    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
218        match self {
219            Self::Snapshot(source) => {
220                write!(formatter, "failed to freeze store snapshot: {source}")
221            }
222            Self::Descriptor(source) => source.fmt(formatter),
223            Self::OpenPolicy {
224                capability,
225                collection,
226            } => write!(
227                formatter,
228                "collection {} has an open {} policy; no proof is required",
229                hex::encode_upper(collection.raw),
230                collection_capability_label(*capability),
231            ),
232            Self::RootNotAuthorized {
233                capability,
234                collection,
235                root,
236            } => write!(
237                formatter,
238                "key {} is not a {} root of collection {}",
239                hex::encode_upper(root.to_bytes()),
240                collection_capability_label(*capability),
241                hex::encode_upper(collection.raw),
242            ),
243            Self::ProofInsert(source) => {
244                write!(formatter, "failed to insert capability proof: {source}")
245            }
246        }
247    }
248}
249
250impl<SnapshotError, GetError, InsertError> Error
251    for CollectionGrantError<SnapshotError, GetError, InsertError>
252where
253    SnapshotError: Error + 'static,
254    GetError: Error + 'static,
255    InsertError: Error + 'static,
256{
257    fn source(&self) -> Option<&(dyn Error + 'static)> {
258        match self {
259            Self::Snapshot(source) => Some(source),
260            Self::Descriptor(source) => Some(source),
261            Self::OpenPolicy { .. } | Self::RootNotAuthorized { .. } => None,
262            Self::ProofInsert(source) => Some(source),
263        }
264    }
265}
266
267/// Error returned by [`grant_collection_read`].
268pub type CollectionReadGrantError<SnapshotError, GetError, InsertError> =
269    CollectionGrantError<SnapshotError, GetError, InsertError>;
270
271/// Error returned by [`grant_collection_write`].
272pub type CollectionWriteGrantError<SnapshotError, GetError, InsertError> =
273    CollectionGrantError<SnapshotError, GetError, InsertError>;
274
275fn collection_capability_label(capability: CapabilityHandle) -> String {
276    if capability == read_capability() {
277        "READ".to_owned()
278    } else if capability == write_capability() {
279        "WRITE".to_owned()
280    } else {
281        hex::encode_upper(capability.raw)
282    }
283}
284
285/// Failure to decide whether one subject is currently admitted to a collection.
286///
287/// Descriptor failures mean that the collection's admission policy could not
288/// be established. Evidence failures mean that the resident capability-proof
289/// observation itself was unavailable; invalid or irrelevant individual
290/// proofs merely fail to admit their subjects and are not errors.
291#[derive(Debug)]
292pub enum CollectionAdmissionError<ProofsError, GetError> {
293    /// The collection descriptor was unavailable or malformed.
294    Descriptor(CollectionDescriptorError<GetError>),
295    /// The resident capability-proof observation could not be completed.
296    Evidence(CollectionEvidenceDiscoveryError<ProofsError>),
297    /// A definition read failed, rather than merely being unavailable.
298    Definition(crate::repo::ReadFailure),
299}
300
301/// The Invoke audience derivable for one capability and proof snapshot.
302///
303/// The existing name remains the READ convenience surface; generic consumers
304/// receive the same explicit finite-or-open result.
305#[derive(Clone, Debug, Eq, PartialEq)]
306pub enum CollectionReadAudience {
307    /// Every principal is admitted; no finite principal list is complete.
308    Open,
309    /// Exact finite audience of the restricted policy in public-key order.
310    Restricted(Vec<VerifyingKey>),
311}
312
313impl<ProofsError, GetError> fmt::Display for CollectionAdmissionError<ProofsError, GetError>
314where
315    ProofsError: fmt::Display,
316    GetError: fmt::Display,
317{
318    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
319        match self {
320            Self::Descriptor(source) => source.fmt(formatter),
321            Self::Evidence(source) => source.fmt(formatter),
322            Self::Definition(source) => source.fmt(formatter),
323        }
324    }
325}
326
327impl<ProofsError, GetError> Error for CollectionAdmissionError<ProofsError, GetError>
328where
329    ProofsError: Error + 'static,
330    GetError: Error + 'static,
331{
332    fn source(&self) -> Option<&(dyn Error + 'static)> {
333        match self {
334            Self::Descriptor(source) => Some(source),
335            Self::Evidence(source) => Some(source),
336            Self::Definition(source) => Some(source),
337        }
338    }
339}
340
341/// Failure to register a self-contained collection descriptor.
342#[derive(Debug)]
343pub enum CollectionRegistrationError<PutError> {
344    /// The descriptor names another encoding or invalid encoding context.
345    WrongType(CollectionTypeError),
346    /// One attachment or the canonical descriptor archive could not be stored.
347    DependencyPut(PutError),
348}
349
350impl<PutError> fmt::Display for CollectionRegistrationError<PutError>
351where
352    PutError: fmt::Display,
353{
354    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
355        match self {
356            Self::WrongType(source) => write!(formatter, "wrong collection type: {source}"),
357            Self::DependencyPut(source) => {
358                write!(
359                    formatter,
360                    "failed to store collection descriptor closure: {source}"
361                )
362            }
363        }
364    }
365}
366
367impl<PutError> Error for CollectionRegistrationError<PutError>
368where
369    PutError: Error + 'static,
370{
371    fn source(&self) -> Option<&(dyn Error + 'static)> {
372        match self {
373            Self::WrongType(source) => Some(source),
374            Self::DependencyPut(source) => Some(source),
375        }
376    }
377}
378
379/// One exact point in a collection lattice.
380///
381/// Members are content identities, not signatures. Several commits may attest
382/// the same member with different authors or metadata without changing this
383/// value. A caller may name an exact typed coordinate through
384/// [`Collection::cover`]; admission, resolution, and materialization still
385/// decide independently whether that coordinate is authorized, evidenced, and
386/// resident in a particular immutable store snapshot.
387pub struct Cover<L: CollectionEncoding> {
388    collection: Collection<L>,
389    members: PATCH<32, IdentitySchema, (), Blake3Merkle>,
390}
391
392impl<L: CollectionEncoding> Cover<L> {
393    pub(crate) fn from_members(
394        collection: Collection<L>,
395        members: impl IntoIterator<Item = Inline<Handle<L>>>,
396    ) -> Self {
397        Self {
398            collection,
399            members: PATCH::from_keys(members.into_iter().map(|member| member.raw)),
400        }
401    }
402
403    pub(crate) fn from_data(
404        collection: Collection<L>,
405        members: impl IntoIterator<Item = CollectionData>,
406    ) -> Self {
407        Self {
408            collection,
409            members: PATCH::from_keys(members.into_iter().map(|member| member.raw)),
410        }
411    }
412
413    pub(crate) fn from_patch(
414        collection: Collection<L>,
415        members: PATCH<32, IdentitySchema, (), Blake3Merkle>,
416    ) -> Self {
417        Self {
418            collection,
419            members,
420        }
421    }
422
423    /// Exact descriptor whose lattice contains these members.
424    pub const fn collection(&self) -> Collection<L> {
425        self.collection
426    }
427
428    /// Canonical member identities in ascending byte order.
429    pub fn members(&self) -> impl ExactSizeIterator<Item = Inline<Handle<L>>> + '_ {
430        self.members
431            .iter_ordered()
432            .map(|member| Inline::new(*member))
433    }
434
435    /// Whether this cover contains one exact member identity.
436    pub fn contains(&self, member: Inline<Handle<L>>) -> bool {
437        self.members.get(&member.raw).is_some()
438    }
439
440    pub(crate) fn data_members(&self) -> impl ExactSizeIterator<Item = CollectionData> + '_ {
441        self.members
442            .iter_ordered()
443            .map(|member| Inline::new(*member))
444    }
445
446    /// Number of distinct collection members.
447    pub fn len(&self) -> usize {
448        self.members.len().min(usize::MAX as u64) as usize
449    }
450
451    /// Whether this is the lattice bottom.
452    pub fn is_empty(&self) -> bool {
453        self.members.is_empty()
454    }
455
456    fn ensure_same_collection(&self, other: &Self) -> Result<(), CoverAlgebraError> {
457        if self.collection == other.collection {
458            Ok(())
459        } else {
460            Err(CoverAlgebraError::DifferentCollection {
461                left: self.collection.handle(),
462                right: other.collection.handle(),
463            })
464        }
465    }
466
467    /// Return the set union of two points in the same collection lattice.
468    pub fn union(&self, other: &Self) -> Result<Self, CoverAlgebraError> {
469        self.ensure_same_collection(other)?;
470        let mut members = self.members.clone();
471        members.union(other.members.clone());
472        Ok(Self::from_patch(self.collection, members))
473    }
474
475    /// Return the set intersection of two points in the same collection lattice.
476    pub fn intersection(&self, other: &Self) -> Result<Self, CoverAlgebraError> {
477        self.ensure_same_collection(other)?;
478        Ok(Self::from_patch(
479            self.collection,
480            self.members.intersect(&other.members),
481        ))
482    }
483
484    /// Return the members of `self` absent from `other`.
485    pub fn difference(&self, other: &Self) -> Result<Self, CoverAlgebraError> {
486        self.ensure_same_collection(other)?;
487        Ok(Self::from_patch(
488            self.collection,
489            self.members.difference(&other.members),
490        ))
491    }
492
493    /// Whether every member of `self` is present in `other`.
494    pub fn is_subset(&self, other: &Self) -> Result<bool, CoverAlgebraError> {
495        self.ensure_same_collection(other)?;
496        Ok(self.members <= other.members)
497    }
498
499    /// Return the members added since an earlier observation.
500    ///
501    /// This is PATCH set difference over payload identities. A new signature
502    /// or metadata archive for an existing member is provenance, not a data
503    /// delta. Shrinking observations fail because additions-only maintenance
504    /// would no longer be sound.
505    pub fn additions_since(&self, previous: &Self) -> Result<Self, CoverAdvanceError> {
506        if self.collection != previous.collection {
507            return Err(CoverAdvanceError::DifferentCollection {
508                previous: previous.collection.handle(),
509                current: self.collection.handle(),
510            });
511        }
512        let missing = previous.members.difference(&self.members);
513        if let Some(member) = missing.iter_ordered().next() {
514            return Err(CoverAdvanceError::ResetRequired {
515                missing: Inline::new(*member),
516            });
517        }
518        Ok(Self::from_patch(
519            self.collection,
520            self.members.difference(&previous.members),
521        ))
522    }
523}
524
525impl<L: CollectionEncoding> Clone for Cover<L> {
526    fn clone(&self) -> Self {
527        Self {
528            collection: self.collection,
529            members: self.members.clone(),
530        }
531    }
532}
533
534impl<L: CollectionEncoding> fmt::Debug for Cover<L> {
535    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
536        formatter
537            .debug_struct("Cover")
538            .field("collection", &self.collection)
539            .field("members", &self.data_members().collect::<Vec<_>>())
540            .finish()
541    }
542}
543
544impl<L: CollectionEncoding> PartialEq for Cover<L> {
545    fn eq(&self, other: &Self) -> bool {
546        self.collection == other.collection && self.members == other.members
547    }
548}
549
550impl<L: CollectionEncoding> Eq for Cover<L> {}
551
552/// What a collection stands on: a cover of its OWN foundations.
553///
554/// A root's foundations are its distinct admitted `COMMIT.data` payloads; a
555/// derived collection's are the images its believed leaves (`DERIVE`s) name.
556/// `MERGE` changes the physical cover, never this value, and several
557/// authorized records attesting one payload are one member. Supports of two
558/// collections are never compared: whether a view has caught up with its
559/// source is [`CollectionSnapshot::missing_from`], which asks the view's
560/// leaves for each source foundation's locator.
561///
562/// The parameter is the collection's own encoding; it defaults to a root's.
563pub type Support<E = SimpleArchive> = Cover<E>;
564
565/// Failure to combine covers from distinct collection lattices.
566#[derive(Clone, Debug, Eq, PartialEq)]
567pub enum CoverAlgebraError {
568    /// The operands carry different canonical collection descriptors.
569    DifferentCollection {
570        /// Descriptor carried by the left operand.
571        left: CollectionHandle,
572        /// Descriptor carried by the right operand.
573        right: CollectionHandle,
574    },
575}
576
577impl fmt::Display for CoverAlgebraError {
578    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
579        match self {
580            Self::DifferentCollection { left, right } => write!(
581                formatter,
582                "cover collection {} differs from {}",
583                hex::encode_upper(left.raw),
584                hex::encode_upper(right.raw),
585            ),
586        }
587    }
588}
589
590impl Error for CoverAlgebraError {}
591
592/// Failure to treat two covers as one additions-only continuation.
593#[derive(Clone, Debug, Eq, PartialEq)]
594pub enum CoverAdvanceError {
595    /// The two covers belong to different collection lattices.
596    DifferentCollection {
597        /// Earlier collection descriptor.
598        previous: CollectionHandle,
599        /// Current collection descriptor.
600        current: CollectionHandle,
601    },
602    /// A member of the earlier cover is absent from the current cover.
603    ResetRequired {
604        /// First missing member in canonical content order.
605        missing: CollectionData,
606    },
607}
608
609impl fmt::Display for CoverAdvanceError {
610    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
611        match self {
612            Self::DifferentCollection { previous, current } => write!(
613                formatter,
614                "cover collection {} differs from {}",
615                hex::encode_upper(current.raw),
616                hex::encode_upper(previous.raw),
617            ),
618            Self::ResetRequired { missing } => write!(
619                formatter,
620                "previous cover member {} is absent from the current observation; additions-only processing requires a reset",
621                hex::encode_upper(missing.raw),
622            ),
623        }
624    }
625}
626
627impl Error for CoverAdvanceError {}
628
629/// Failure to publish one collection element into local storage.
630#[derive(Debug)]
631pub enum CollectionCommitError<PutError, InsertError> {
632    /// A described attachment, member, or metadata archive could not be stored.
633    DependencyPut(PutError),
634    /// The signed visibility record could not be inserted after its dependencies.
635    RecordInsert(InsertError),
636}
637
638impl<PutError, InsertError> fmt::Display for CollectionCommitError<PutError, InsertError>
639where
640    PutError: fmt::Display,
641    InsertError: fmt::Display,
642{
643    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
644        match self {
645            Self::DependencyPut(source) => {
646                write!(formatter, "failed to store collection dependency: {source}")
647            }
648            Self::RecordInsert(source) => {
649                write!(formatter, "failed to insert collection commit: {source}")
650            }
651        }
652    }
653}
654
655impl<PutError, InsertError> Error for CollectionCommitError<PutError, InsertError>
656where
657    PutError: Error + 'static,
658    InsertError: Error + 'static,
659{
660    fn source(&self) -> Option<&(dyn Error + 'static)> {
661        match self {
662            Self::DependencyPut(source) => Some(source),
663            Self::RecordInsert(source) => Some(source),
664        }
665    }
666}
667
668/// Failure to read one logical value through a snapshot.
669#[derive(Debug)]
670pub enum CollectionReadError<GetError, ViewError> {
671    /// The snapshot could not attach the collection.
672    Realization(CollectionRealizationError),
673    /// The attached cover could not form the requested logical view.
674    View(TryFromCoverError<GetError, ViewError>),
675}
676
677impl<GetError, ViewError> fmt::Display for CollectionReadError<GetError, ViewError>
678where
679    GetError: fmt::Display,
680    ViewError: fmt::Display,
681{
682    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
683        match self {
684            Self::Realization(source) => source.fmt(formatter),
685            Self::View(source) => source.fmt(formatter),
686        }
687    }
688}
689
690impl<GetError, ViewError> Error for CollectionReadError<GetError, ViewError>
691where
692    GetError: Error + 'static,
693    ViewError: Error + 'static,
694{
695    fn source(&self) -> Option<&(dyn Error + 'static)> {
696        match self {
697            Self::Realization(source) => Some(source),
698            Self::View(source) => Some(source),
699        }
700    }
701}
702
703pub(crate) struct LoadedCollectionDescriptor {
704    pub(crate) fragment: Fragment,
705}
706
707pub(crate) fn load_collection_descriptor<R>(
708    snapshot: &R,
709    collection: CollectionHandle,
710) -> Result<LoadedCollectionDescriptor, CollectionDescriptorError<R::GetError<Infallible>>>
711where
712    R: BlobStoreGet,
713{
714    let descriptor_blob: Blob<SimpleArchive> = snapshot
715        .get(collection)
716        .map_err(|source| CollectionDescriptorError::Get { collection, source })?;
717    let facts =
718        <TribleSet as crate::blob::TryFromBlob<SimpleArchive>>::try_from_blob(descriptor_blob)
719            .map_err(|source| CollectionDescriptorError::Invalid {
720                collection,
721                source: RecordDecodeError::from(source),
722            })?;
723    Ok(LoadedCollectionDescriptor {
724        fragment: Fragment::from(facts),
725    })
726}
727
728pub(crate) enum AdmissionEvidence {
729    Open,
730    Alternatives(Vec<AdmissionEvidence>),
731    Quorum {
732        roots: Vec<VerifyingKey>,
733        invoke_threshold: NonZeroUsize,
734        request: CapabilityRequest,
735        proofs: Arc<[CapabilityProof]>,
736    },
737}
738
739impl AdmissionEvidence {
740    pub(crate) fn authorizes<R: BlobStoreGet>(&self, reader: &R, subject: VerifyingKey) -> bool {
741        matches!(self.decide(reader, subject), QuorumOutcome::Met)
742    }
743
744    /// Decide one subject, and say which arrival could change the decision:
745    /// a proof, or the capability definitions a proof for the subject named
746    /// and this reader could not read.
747    pub(crate) fn decide<R: BlobStoreGet>(
748        &self,
749        reader: &R,
750        subject: VerifyingKey,
751    ) -> QuorumOutcome {
752        match self {
753            Self::Open => QuorumOutcome::Met,
754            Self::Alternatives(alternatives) => {
755                let mut undefined = Vec::new();
756                for evidence in alternatives {
757                    match evidence.decide(reader, subject) {
758                        QuorumOutcome::Met => return QuorumOutcome::Met,
759                        QuorumOutcome::Unmet => {}
760                        QuorumOutcome::Undefined(handles) => undefined.extend(handles),
761                    }
762                }
763                if undefined.is_empty() {
764                    QuorumOutcome::Unmet
765                } else {
766                    QuorumOutcome::Undefined(undefined)
767                }
768            }
769            Self::Quorum {
770                roots,
771                invoke_threshold,
772                request,
773                proofs,
774            } => capability_quorum_decide(
775                reader,
776                proofs.iter(),
777                roots.iter().copied(),
778                subject,
779                *request,
780                *invoke_threshold,
781            ),
782        }
783    }
784
785    fn authorized_subjects<R: BlobStoreGet>(&self, reader: &R) -> CollectionReadAudience {
786        match self {
787            Self::Open => CollectionReadAudience::Open,
788            Self::Alternatives(alternatives) => {
789                let mut subjects = BTreeMap::new();
790                for alternative in alternatives {
791                    match alternative.authorized_subjects(reader) {
792                        CollectionReadAudience::Open => return CollectionReadAudience::Open,
793                        CollectionReadAudience::Restricted(audience) => {
794                            subjects.extend(
795                                audience
796                                    .into_iter()
797                                    .map(|subject| (subject.to_bytes(), subject)),
798                            );
799                        }
800                    }
801                }
802                CollectionReadAudience::Restricted(subjects.into_values().collect())
803            }
804            Self::Quorum {
805                roots,
806                invoke_threshold,
807                request,
808                proofs,
809            } => CollectionReadAudience::Restricted(capability_quorum_authorized_subjects(
810                reader,
811                proofs.iter(),
812                roots.iter().copied(),
813                *request,
814                *invoke_threshold,
815            )),
816        }
817    }
818}
819
820pub(crate) fn admission_evidence_from_proofs(
821    policy: &AdmissionPolicy,
822    action: Id,
823    collection: CollectionHandle,
824    proofs: Arc<[CapabilityProof]>,
825) -> AdmissionEvidence {
826    let AdmissionPolicy::Quorum(quorum) = policy else {
827        return AdmissionEvidence::Open;
828    };
829    let request = CapabilityRequest::new(CapabilityResource::from(collection), action);
830    AdmissionEvidence::Quorum {
831        roots: quorum.roots().to_vec(),
832        invoke_threshold: NonZeroUsize::new(quorum.invoke_threshold() as usize)
833            .expect("validated collection policy has a nonzero invoke threshold"),
834        request,
835        proofs,
836    }
837}
838
839pub(super) fn discover_admission_evidence<S>(
840    snapshot: &S,
841    policies: impl IntoIterator<Item = AdmissionPolicy>,
842    action: Id,
843    collection: CollectionHandle,
844) -> Result<AdmissionEvidence, CollectionEvidenceDiscoveryError<S::ProofsError>>
845where
846    S: CapabilityProofRead,
847{
848    let policies: Vec<_> = policies.into_iter().collect();
849    if policies
850        .iter()
851        .any(|policy| matches!(policy, AdmissionPolicy::Open))
852    {
853        return Ok(AdmissionEvidence::Open);
854    }
855    if policies.is_empty() {
856        return Ok(AdmissionEvidence::Alternatives(Vec::new()));
857    }
858    let proofs = snapshot
859        .proofs()
860        .map_err(CollectionEvidenceDiscoveryError::Proofs)?
861        .collect::<Result<Vec<_>, _>>()
862        .map_err(CollectionEvidenceDiscoveryError::Proofs)?;
863    let proofs: Arc<[CapabilityProof]> = proofs.into();
864    Ok(AdmissionEvidence::Alternatives(
865        policies
866            .iter()
867            .map(|policy| {
868                admission_evidence_from_proofs(policy, action, collection, Arc::clone(&proofs))
869            })
870            .collect(),
871    ))
872}
873
874/// Persist one deterministic root-issued READ/Invoke proof for `recipient`.
875///
876/// This is deliberately representation-neutral: READ authority concerns the
877/// exact descriptor handle, not the collection member encoding. The
878/// descriptor policy is first read through one coherent
879/// store snapshot. `root` must be named by its READ quorum; an open READ policy
880/// needs no grant and is rejected as a redundant operation.
881///
882/// Repeating the same call with the same collection, root, and recipient
883/// reproduces the same self-contained proof bytes and is idempotent on a
884/// conforming store.
885pub fn grant_collection_read<S>(
886    store: &mut S,
887    collection: CollectionHandle,
888    root: &SigningKey,
889    recipient: VerifyingKey,
890) -> Result<
891    CapabilityProof,
892    CollectionReadGrantError<
893        <S as SnapshotSource>::SnapshotError,
894        <<S as SnapshotSource>::Snapshot as BlobStoreGet>::GetError<Infallible>,
895        <S as CapabilityProofStore>::InsertError,
896    >,
897>
898where
899    S: SnapshotSource + CapabilityProofStore,
900    <S as SnapshotSource>::Snapshot: BlobStoreGet,
901{
902    grant_collection_capability(store, collection, read_capability(), root, recipient)
903}
904
905/// Persist one deterministic root-issued WRITE/Invoke proof for `recipient`.
906///
907/// This is the WRITE counterpart of [`grant_collection_read`]. The descriptor
908/// is validated from one coherent snapshot, `root` must be named by its WRITE
909/// quorum. The resulting proof activates matching COMMITs by `recipient` when
910/// a later collection snapshot performs WRITE admission; local publication
911/// itself remains unconditional.
912pub fn grant_collection_write<S>(
913    store: &mut S,
914    collection: CollectionHandle,
915    root: &SigningKey,
916    recipient: VerifyingKey,
917) -> Result<
918    CapabilityProof,
919    CollectionWriteGrantError<
920        <S as SnapshotSource>::SnapshotError,
921        <<S as SnapshotSource>::Snapshot as BlobStoreGet>::GetError<Infallible>,
922        <S as CapabilityProofStore>::InsertError,
923    >,
924>
925where
926    S: SnapshotSource + CapabilityProofStore,
927    <S as SnapshotSource>::Snapshot: BlobStoreGet,
928{
929    grant_collection_capability(store, collection, write_capability(), root, recipient)
930}
931
932/// Persist a root-issued grant described by an already stored definition.
933///
934/// The definition may combine invocation and delegation actions. Authority is
935/// still checked per requested action, against that action's configured roots;
936/// a broad grant cannot borrow an unrelated action's roots. An open action may
937/// accompany a grant for a restricted action; an entirely open grant is
938/// redundant. One root contributes its share, not an entire multi-root quorum.
939pub fn grant_collection_capability<S>(
940    store: &mut S,
941    collection: CollectionHandle,
942    capability: CapabilityHandle,
943    root: &SigningKey,
944    recipient: VerifyingKey,
945) -> Result<
946    CapabilityProof,
947    CollectionGrantError<
948        <S as SnapshotSource>::SnapshotError,
949        <<S as SnapshotSource>::Snapshot as BlobStoreGet>::GetError<Infallible>,
950        <S as CapabilityProofStore>::InsertError,
951    >,
952>
953where
954    S: SnapshotSource + CapabilityProofStore,
955    <S as SnapshotSource>::Snapshot: BlobStoreGet,
956{
957    let snapshot = store.snapshot().map_err(CollectionGrantError::Snapshot)?;
958    let descriptor = load_collection_descriptor(&snapshot, collection)
959        .map_err(CollectionGrantError::Descriptor)?;
960    let definition = load_collection_descriptor(&snapshot, capability)
961        .map_err(CollectionGrantError::Descriptor)?;
962    let actions = crate::prelude::find!(
963        action: Id,
964        crate::prelude::temp!(
965            (grant),
966            crate::prelude::or!(
967                crate::prelude::pattern!(definition.fragment.facts(), [{ ?grant @ crate::capability::capability_action: ?action }]),
968                crate::prelude::pattern!(definition.fragment.facts(), [{ ?grant @ crate::capability::capability_delegate_action: ?action }]),
969            )
970        )
971    );
972    let root_key = root.verifying_key();
973    let mut has_action = false;
974    let mut needs_grant = false;
975    for action in actions {
976        has_action = true;
977        let mut open = false;
978        let mut root_is_authorized = false;
979        for policy in
980            descriptor::admission_policies(&snapshot, descriptor.fragment.facts(), action, None)
981        {
982            open |= matches!(policy, AdmissionPolicy::Open);
983            root_is_authorized |= policy
984                .roots()
985                .is_some_and(|roots| roots.contains(&root_key));
986        }
987        if open {
988            continue;
989        }
990        if !root_is_authorized {
991            return Err(CollectionGrantError::RootNotAuthorized {
992                capability,
993                collection,
994                root: root_key,
995            });
996        }
997        needs_grant = true;
998    }
999    if !has_action {
1000        return Err(CollectionGrantError::RootNotAuthorized {
1001            capability,
1002            collection,
1003            root: root_key,
1004        });
1005    }
1006    if !needs_grant {
1007        return Err(CollectionGrantError::OpenPolicy {
1008            capability,
1009            collection,
1010        });
1011    }
1012    drop(snapshot);
1013
1014    let proof = CapabilityProof::new(
1015        CapabilityResource::from(collection),
1016        root,
1017        capability,
1018        recipient,
1019    );
1020    store
1021        .insert_proof(proof.clone())
1022        .map_err(CollectionGrantError::ProofInsert)?;
1023    Ok(proof)
1024}
1025
1026/// Decide READ admission for an untyped collection handle from explicitly
1027/// supplied portable proofs.
1028///
1029/// Network discovery starts from the descriptor handle carried on the wire,
1030/// before its member encoding is known. This boundary therefore queries the
1031/// descriptor's READ policy without manufacturing a typed
1032/// [`Collection`]. It neither enumerates nor persists ambient proof state.
1033pub fn collection_reader_is_admitted_by<S>(
1034    snapshot: &S,
1035    collection: CollectionHandle,
1036    subject: VerifyingKey,
1037    proofs: &[CapabilityProof],
1038) -> Result<bool, CollectionDescriptorError<S::GetError<Infallible>>>
1039where
1040    S: StoreSnapshot + BlobStoreGet,
1041{
1042    let loaded = load_collection_descriptor(snapshot, collection)?;
1043    let admitted =
1044        descriptor::admission_policies(snapshot, loaded.fragment.facts(), super::ACTION_READ, None)
1045            .any(|policy| {
1046                collection_reader_is_admitted_by_policy(
1047                    snapshot, collection, &policy, subject, proofs,
1048                )
1049            });
1050    Ok(admitted)
1051}
1052
1053/// Decide READ admission against one already validated collection policy.
1054///
1055/// This is the pure seam for a network host which pinned the descriptor policy
1056/// while constructing its immutable per-collection repair overlay. It
1057/// reads reusable capability definitions, with no proof discovery, persistence,
1058/// network acquisition, or clock sampling.
1059pub fn collection_reader_is_admitted_by_policy<R: BlobStoreGet>(
1060    reader: &R,
1061    collection: CollectionHandle,
1062    policy: &AdmissionPolicy,
1063    subject: VerifyingKey,
1064    proofs: &[CapabilityProof],
1065) -> bool {
1066    let evidence = admission_evidence_from_proofs(
1067        policy,
1068        super::ACTION_READ,
1069        collection,
1070        proofs.to_vec().into(),
1071    );
1072    evidence.authorizes(reader, subject)
1073}
1074
1075/// Enumerate the READ audience from an already validated collection policy.
1076///
1077/// Restricted admission is evaluated over independent root paths in the
1078/// supplied self-contained proofs. Configured roots plus every independently
1079/// valid signed prefix participate, so a principal authorized by a proof prefix
1080/// is included even when that prefix is not separately stored. Invalid,
1081/// unrelated proofs are inert. Collection authority does not expire.
1082/// Open admission remains explicit because it cannot be finitely enumerated.
1083pub fn collection_read_audience_by_policy<R: BlobStoreGet>(
1084    reader: &R,
1085    collection: CollectionHandle,
1086    policy: &AdmissionPolicy,
1087    proofs: &[CapabilityProof],
1088) -> CollectionReadAudience {
1089    admission_evidence_from_proofs(
1090        policy,
1091        super::ACTION_READ,
1092        collection,
1093        proofs.to_vec().into(),
1094    )
1095    .authorized_subjects(reader)
1096}
1097
1098/// Discover and enumerate the READ audience from one coherent store snapshot.
1099///
1100/// A failed proof-store observation is reported rather than confused with an
1101/// empty audience. Invalid or unrelated proofs simply grant nothing.
1102pub fn collection_read_audience<S>(
1103    snapshot: &S,
1104    collection: CollectionHandle,
1105) -> Result<CollectionReadAudience, CollectionAdmissionError<S::ProofsError, S::GetError<Infallible>>>
1106where
1107    S: StoreSnapshot + BlobStoreGet + CapabilityProofRead,
1108{
1109    collection_action_audience(snapshot, collection, super::ACTION_READ)
1110}
1111
1112/// Discover the finite invocation audience for one action.
1113///
1114/// This generic collection-resource seam is also used by consumers such as
1115/// key delivery. It never substitutes READ or WRITE for an absent binding.
1116pub fn collection_action_audience<S>(
1117    snapshot: &S,
1118    collection: CollectionHandle,
1119    action: Id,
1120) -> Result<CollectionReadAudience, CollectionAdmissionError<S::ProofsError, S::GetError<Infallible>>>
1121where
1122    S: StoreSnapshot + BlobStoreGet + CapabilityProofRead,
1123{
1124    let loaded = load_collection_descriptor(snapshot, collection)
1125        .map_err(CollectionAdmissionError::Descriptor)?;
1126    let evidence = discover_admission_evidence(
1127        snapshot,
1128        descriptor::admission_policies(snapshot, loaded.fragment.facts(), action, None),
1129        action,
1130        collection,
1131    )
1132    .map_err(CollectionAdmissionError::Evidence)?;
1133    Ok(evidence.authorized_subjects(snapshot))
1134}
1135
1136/// Decide WRITE admission against one already validated collection policy and
1137/// an explicitly supplied portable proof forest.
1138///
1139/// This pure seam lets a network repair client discard inert records before
1140/// they cross the local admission boundary. It performs no store access,
1141/// persistence, or clock sampling.
1142pub fn collection_writer_is_admitted_by_policy<R: BlobStoreGet>(
1143    reader: &R,
1144    collection: CollectionHandle,
1145    policy: &AdmissionPolicy,
1146    subject: VerifyingKey,
1147    proofs: &[CapabilityProof],
1148) -> bool {
1149    let evidence = admission_evidence_from_proofs(
1150        policy,
1151        super::ACTION_WRITE,
1152        collection,
1153        proofs.to_vec().into(),
1154    );
1155    evidence.authorizes(reader, subject)
1156}
1157
1158impl<L: CollectionEncoding> Collection<L> {
1159    /// Name one exact typed coordinate in this collection lattice.
1160    ///
1161    /// This performs no store access and makes no claim that the members are
1162    /// admitted, related by stored equations, or physically resident. It is
1163    /// the construction boundary for durable exact-cover manifests; pass the
1164    /// result to snapshot resolution before reading it as a value.
1165    pub fn cover(self, members: impl IntoIterator<Item = Inline<Handle<L>>>) -> Cover<L> {
1166        Cover::from_members(self, members)
1167    }
1168
1169    /// Open an existing descriptor as a typed collection without mutating the
1170    /// backing store.
1171    ///
1172    /// `snapshot` should be the caller's coherent immutable read boundary. The
1173    /// descriptor is fetched by `handle`, its local policy is read, and the
1174    /// encoding facts needed by `L` are recognized before the cheap typed
1175    /// handle is returned.
1176    pub fn open<S>(
1177        snapshot: &S,
1178        handle: CollectionHandle,
1179    ) -> Result<Self, CollectionOpenError<S::GetError<Infallible>>>
1180    where
1181        S: BlobStoreGet,
1182    {
1183        let descriptor = load_collection_descriptor(snapshot, handle)
1184            .map_err(CollectionOpenError::Descriptor)?;
1185        super::encoding::validate_descriptor_type::<L>(&descriptor.fragment)
1186            .map_err(CollectionOpenError::WrongType)?;
1187        Ok(Self::from_handle(handle))
1188    }
1189
1190    /// Read this collection's immutable admission policy from `snapshot`.
1191    ///
1192    /// The descriptor is fetched and queried for its local READ and WRITE
1193    /// policies. This does not enumerate capability
1194    /// proofs or make an admission decision.
1195    pub fn policy<S>(
1196        self,
1197        snapshot: &S,
1198    ) -> Result<CollectionPolicy, CollectionDescriptorError<S::GetError<Infallible>>>
1199    where
1200        S: BlobStoreGet,
1201    {
1202        let loaded = load_collection_descriptor(snapshot, self.handle())?;
1203        descriptor::policy(loaded.fragment.facts()).map_err(|source| {
1204            CollectionDescriptorError::Invalid {
1205                collection: self.handle(),
1206                source,
1207            }
1208        })
1209    }
1210
1211    /// Decide whether `subject` is admitted as a writer in this snapshot.
1212    ///
1213    /// Open policy admits every subject. A quorum admits the subject only when
1214    /// independently rooted exact-action proofs carry support from enough
1215    /// distinct configured roots.
1216    pub fn writer_is_admitted<S>(
1217        self,
1218        snapshot: &S,
1219        subject: VerifyingKey,
1220    ) -> Result<bool, CollectionAdmissionError<S::ProofsError, S::GetError<Infallible>>>
1221    where
1222        S: StoreSnapshot + BlobStoreGet + CapabilityProofRead,
1223    {
1224        let loaded = load_collection_descriptor(snapshot, self.handle())
1225            .map_err(CollectionAdmissionError::Descriptor)?;
1226        let evidence = discover_admission_evidence(
1227            snapshot,
1228            descriptor::admission_policies(
1229                snapshot,
1230                loaded.fragment.facts(),
1231                super::ACTION_WRITE,
1232                Some(L::id()),
1233            ),
1234            super::ACTION_WRITE,
1235            self.handle(),
1236        )
1237        .map_err(CollectionAdmissionError::Evidence)?;
1238        Ok(evidence.authorizes(snapshot, subject))
1239    }
1240
1241    /// Command admission through an exact-byte reader. Frozen proof evidence
1242    /// is unchanged; unavailable definitions grant nothing, and actual backend
1243    /// failures cannot be swallowed as a denial by the open-world query.
1244    pub fn writer_is_admitted_acquiring<S>(
1245        self,
1246        snapshot: &S,
1247        subject: VerifyingKey,
1248    ) -> Result<bool, CollectionAdmissionError<S::ProofsError, S::GetError<Infallible>>>
1249    where
1250        S: StoreSnapshot + BlobStoreGet + CapabilityProofRead,
1251    {
1252        self.admitted_acquiring(snapshot, subject, super::ACTION_WRITE)
1253    }
1254
1255    /// The READ counterpart of [`Self::writer_is_admitted_acquiring`].
1256    pub fn reader_is_admitted_acquiring<S>(
1257        self,
1258        snapshot: &S,
1259        subject: VerifyingKey,
1260    ) -> Result<bool, CollectionAdmissionError<S::ProofsError, S::GetError<Infallible>>>
1261    where
1262        S: StoreSnapshot + BlobStoreGet + CapabilityProofRead,
1263    {
1264        self.admitted_acquiring(snapshot, subject, super::ACTION_READ)
1265    }
1266
1267    fn admitted_acquiring<S>(
1268        self,
1269        snapshot: &S,
1270        subject: VerifyingKey,
1271        action: Id,
1272    ) -> Result<bool, CollectionAdmissionError<S::ProofsError, S::GetError<Infallible>>>
1273    where
1274        S: StoreSnapshot + BlobStoreGet + CapabilityProofRead,
1275    {
1276        let loaded = load_collection_descriptor(snapshot, self.handle())
1277            .map_err(CollectionAdmissionError::Descriptor)?;
1278        let attempt = crate::repo::read_attempt::ReadAttempt::new(snapshot);
1279        let evidence = discover_admission_evidence(
1280            snapshot,
1281            descriptor::admission_policies(
1282                &attempt,
1283                loaded.fragment.facts(),
1284                action,
1285                Some(L::id()),
1286            ),
1287            action,
1288            self.handle(),
1289        )
1290        .map_err(CollectionAdmissionError::Evidence)?;
1291        let admitted = evidence.authorizes(&attempt, subject);
1292        attempt
1293            .finish()
1294            .map_err(CollectionAdmissionError::Definition)?;
1295        Ok(admitted)
1296    }
1297
1298    /// Decide whether `subject` is admitted as a reader in this snapshot.
1299    ///
1300    /// This is the disclosure boundary corresponding to the descriptor's READ
1301    /// ceiling. Local materialization remains caller-controlled because a
1302    /// local store cannot infer which external principal will receive bytes.
1303    pub fn reader_is_admitted<S>(
1304        self,
1305        snapshot: &S,
1306        subject: VerifyingKey,
1307    ) -> Result<bool, CollectionAdmissionError<S::ProofsError, S::GetError<Infallible>>>
1308    where
1309        S: StoreSnapshot + BlobStoreGet + CapabilityProofRead,
1310    {
1311        let loaded = load_collection_descriptor(snapshot, self.handle())
1312            .map_err(CollectionAdmissionError::Descriptor)?;
1313        let evidence = discover_admission_evidence(
1314            snapshot,
1315            descriptor::admission_policies(
1316                snapshot,
1317                loaded.fragment.facts(),
1318                super::ACTION_READ,
1319                Some(L::id()),
1320            ),
1321            super::ACTION_READ,
1322            self.handle(),
1323        )
1324        .map_err(CollectionAdmissionError::Evidence)?;
1325        Ok(evidence.authorizes(snapshot, subject))
1326    }
1327
1328    /// Decide READ admission from one explicitly supplied portable proof set.
1329    ///
1330    /// This is the network-facing counterpart of [`Self::reader_is_admitted`].
1331    /// It loads only the immutable collection descriptor from `snapshot`; it
1332    /// neither enumerates nor persists ambient proof-store state. An open READ
1333    /// policy therefore succeeds with an empty proof slice, while a quorum is
1334    /// evaluated over the independently rooted supplied paths.
1335    pub fn reader_is_admitted_by<S>(
1336        self,
1337        snapshot: &S,
1338        subject: VerifyingKey,
1339        proofs: &[CapabilityProof],
1340    ) -> Result<bool, CollectionDescriptorError<S::GetError<Infallible>>>
1341    where
1342        S: StoreSnapshot + BlobStoreGet,
1343    {
1344        let loaded = load_collection_descriptor(snapshot, self.handle())?;
1345        let admitted = descriptor::admission_policies(
1346            snapshot,
1347            loaded.fragment.facts(),
1348            super::ACTION_READ,
1349            Some(L::id()),
1350        )
1351        .any(|policy| {
1352            collection_reader_is_admitted_by_policy(
1353                snapshot,
1354                self.handle(),
1355                &policy,
1356                subject,
1357                proofs,
1358            )
1359        });
1360        Ok(admitted)
1361    }
1362
1363    /// What this collection stands for in this snapshot, from the coverage
1364    /// index: its own foundations, resident or not.
1365    ///
1366    /// For a root these are its admitted COMMIT payloads. For a derived
1367    /// collection they are the images its believed leaves name. Nothing is
1368    /// enumerated: the index is settled for this one handle and its
1369    /// frontier's supports are unioned.
1370    pub fn admitted<S>(self, snapshot: &S) -> Result<Cover<L>, S::RecordsError>
1371    where
1372        S: StoreRead,
1373    {
1374        let coverage = snapshot.coverage(&BTreeSet::from([self.handle()]))?;
1375        let (support, _) = coverage.frontier_support(self.handle());
1376        Ok(Cover::from_patch(self, support))
1377    }
1378
1379    /// Read one logical value through this snapshot's realized collection.
1380    ///
1381    /// This is exactly `snapshot.collection(self)?.view()`: it neither fetches
1382    /// missing blobs nor claims support beyond the snapshot's resident cover.
1383    pub fn read<V, S>(
1384        self,
1385        snapshot: &S,
1386    ) -> Result<V, CollectionReadError<S::GetError<Infallible>, V::Error>>
1387    where
1388        S: StoreRead,
1389        V: TryFromCover<L>,
1390    {
1391        snapshot
1392            .collection(self)
1393            .map_err(CollectionReadError::Realization)?
1394            .view()
1395            .map_err(CollectionReadError::View)
1396    }
1397
1398    /// Read known support through exact acquisition without choosing another
1399    /// record/proof frontier after bytes arrive. Unlike a passive read, missing
1400    /// chosen foundation bytes fail the view rather than disappearing from it.
1401    pub fn read_acquiring<V, S>(
1402        self,
1403        snapshot: &S,
1404    ) -> Result<V, CollectionReadError<S::GetError<Infallible>, V::Error>>
1405    where
1406        S: StoreRead,
1407        V: TryFromCover<L>,
1408    {
1409        snapshot
1410            .collection_acquiring(self)
1411            .map_err(CollectionReadError::Realization)?
1412            .view()
1413            .map_err(CollectionReadError::View)
1414    }
1415}
1416
1417/// Immutable collection observations implemented by every complete store snapshot.
1418///
1419/// A snapshot is the temporal boundary. The returned [`CollectionSnapshot`]
1420/// therefore owns this exact store observation, the physical target cover
1421/// selected inside it, and the [`Support`] that cover stands on: a cover of
1422/// the collection's own foundations.
1423pub trait CollectionSnapshotExt: StoreRead + Sized {
1424    /// Observe what one collection contains in this immutable snapshot.
1425    ///
1426    /// Capability decisions use this snapshot's immutable proof and definition
1427    /// state, independently of its clock.
1428    /// Physical target realization is selected entirely from this immutable
1429    /// store snapshot. Accepted target endorsements do not depend on the
1430    /// residency of their historical source records or source payloads.
1431    fn collection<E>(
1432        &self,
1433        target: Collection<E>,
1434    ) -> Result<CollectionSnapshot<Self, E>, CollectionRealizationError>
1435    where
1436        E: CollectionEncoding,
1437        Handle<E>: InlineEncoding,
1438    {
1439        super::observation::attach(self, target)
1440    }
1441
1442    /// Select the resident cover plus known foundations it does not represent.
1443    /// A fetching reader can then read that fixed cover's exact bytes through
1444    /// `view`. No new observation is taken during acquisition; a view which
1445    /// cannot read a chosen member returns an error, not a partial value.
1446    fn collection_acquiring<E>(
1447        &self,
1448        target: Collection<E>,
1449    ) -> Result<CollectionSnapshot<Self, E>, CollectionRealizationError>
1450    where
1451        E: CollectionEncoding,
1452        Handle<E>: InlineEncoding,
1453    {
1454        super::observation::attach_acquiring(self, target)
1455    }
1456
1457    /// Observe what one attached collection holds for its parent in this
1458    /// immutable snapshot: the attached cover.
1459    ///
1460    /// For each node of the parent's frontier, widest first, its usable
1461    /// attachment is taken -- a MAP the host signed, whose bytes and
1462    /// representation dependencies are here -- and otherwise the
1463    /// attachments of the highest nodes beneath it that have one, found
1464    /// through the host's own merges. The parent foundations no taken
1465    /// attachment reaches are the [`AttachedSnapshot::residual`]: read those
1466    /// nodes raw, or report them. Supports and usability come from this one
1467    /// observation.
1468    fn attached<E>(
1469        &self,
1470        attached: Collection<E>,
1471    ) -> Result<AttachedSnapshot<Self, E>, CollectionRealizationError>
1472    where
1473        E: CollectionEncoding,
1474        Handle<E>: InlineEncoding,
1475    {
1476        super::observation::attach_attached(self, attached)
1477    }
1478
1479    /// Select one fixed attached observation, reading its explicit target and
1480    /// parent descriptors even when frozen residency says absent. A fetching
1481    /// reader may acquire those exact bytes. Selection otherwise uses the same
1482    /// frozen records/proofs and resident attachment cover as `attached`;
1483    /// `read_acquiring` subsequently reads its fixed residual.
1484    fn attached_acquiring<E>(
1485        &self,
1486        attached: Collection<E>,
1487    ) -> Result<AttachedSnapshot<Self, E>, CollectionRealizationError>
1488    where
1489        E: CollectionEncoding,
1490        Handle<E>: InlineEncoding,
1491    {
1492        super::observation::attach_attached_acquiring(self, attached)
1493    }
1494}
1495
1496impl<R> CollectionSnapshotExt for R where R: StoreRead {}
1497
1498/// An encoding whose collection can be made resident and maintained by storage.
1499///
1500/// Root `SimpleArchive` collections acquire their signed support and carry
1501/// every node the store holds into the host's own merges. Encodings with a
1502/// canonical [`CollectionDerivation`] derive leaves for the source
1503/// foundations that have no usable leaf and carry their own leaf images the
1504/// same way. This operational capability is separate from
1505/// the pure encoding and mapping laws; no fictitious self-derivation is needed
1506/// for a root collection. The explicit key is the maintainer: it decides what
1507/// is owned, and signs only records published by realization; pure mapping and
1508/// snapshot observation need no signer. A root carry publishes MERGEs, which
1509/// the store folds only for its host, so the store must be opened as this
1510/// key (for example [`crate::repo::pile::Pile::open_as`]).
1511/// Use the four [`CollectionStoreExt`] methods at call sites. Explicit foreign
1512/// mappings remain available through their `*_with` counterparts.
1513pub trait CollectionRealization: CollectionEncoding {
1514    /// A root acquires its admitted commits, whoever wrote them; a derived
1515    /// collection gets a leaf for every source foundation the key owns that
1516    /// has no leaf yet.
1517    fn ensure<'a, S>(
1518        store: &'a mut S,
1519        target: Collection<Self>,
1520        signing_key: &'a SigningKey,
1521    ) -> impl Future<Output = Result<S::Snapshot, CollectionRealizationError>> + Send + 'a
1522    where
1523        S: Store + AsyncBlobStoreAcquire + Send;
1524
1525    /// A root carries every held node to its LSM fixed point in the host's
1526    /// merges; a derived collection gets a leaf for every source foundation
1527    /// without a usable one that this host can derive, whoever owns it, and
1528    /// carries its own leaf images the same way.
1529    fn maintain<'a, S>(
1530        store: &'a mut S,
1531        target: Collection<Self>,
1532        signing_key: &'a SigningKey,
1533    ) -> impl Future<Output = Result<S::Snapshot, CollectionRealizationError>> + Send + 'a
1534    where
1535        S: Store + AsyncBlobStoreAcquire + Send;
1536}
1537
1538impl<T: CollectionDerivation> CollectionRealization for T {
1539    async fn ensure<'a, S>(
1540        store: &'a mut S,
1541        target: Collection<Self>,
1542        signing_key: &'a SigningKey,
1543    ) -> Result<S::Snapshot, CollectionRealizationError>
1544    where
1545        S: Store + AsyncBlobStoreAcquire + Send,
1546    {
1547        store
1548            .ensure_with::<CanonicalDerivation<T>>(target, signing_key)
1549            .await
1550    }
1551
1552    async fn maintain<'a, S>(
1553        store: &'a mut S,
1554        target: Collection<Self>,
1555        signing_key: &'a SigningKey,
1556    ) -> Result<S::Snapshot, CollectionRealizationError>
1557    where
1558        S: Store + AsyncBlobStoreAcquire + Send,
1559    {
1560        store
1561            .maintain_with::<CanonicalDerivation<T>>(target, signing_key)
1562            .await
1563    }
1564}
1565
1566impl CollectionRealization for SimpleArchive {
1567    async fn ensure<'a, S>(
1568        store: &'a mut S,
1569        target: Collection<Self>,
1570        signing_key: &'a SigningKey,
1571    ) -> Result<S::Snapshot, CollectionRealizationError>
1572    where
1573        S: Store + AsyncBlobStoreAcquire + Send,
1574    {
1575        realize_root(store, target, signing_key, false).await
1576    }
1577
1578    async fn maintain<'a, S>(
1579        store: &'a mut S,
1580        target: Collection<Self>,
1581        signing_key: &'a SigningKey,
1582    ) -> Result<S::Snapshot, CollectionRealizationError>
1583    where
1584        S: Store + AsyncBlobStoreAcquire + Send,
1585    {
1586        realize_root(store, target, signing_key, true).await
1587    }
1588}
1589
1590async fn realize_root<S>(
1591    store: &mut S,
1592    target: Collection<SimpleArchive>,
1593    signing_key: &SigningKey,
1594    compact: bool,
1595) -> Result<S::Snapshot, CollectionRealizationError>
1596where
1597    S: Store + AsyncBlobStoreAcquire,
1598{
1599    super::exact_derived::acquire_authority(store, target).await?;
1600    if compact {
1601        // The carry merges every node the store holds, whoever signed the
1602        // foundations beneath it; no absent payload is waited for or fetched.
1603        super::exact_derived::maintain_root_acquiring(store, target, signing_key).await?;
1604    } else {
1605        super::exact_derived::ensure_root(store, target).await?;
1606    }
1607    store.snapshot().map_err(|error| {
1608        CollectionRealizationError::storage("freeze realized root snapshot", error)
1609    })
1610}
1611
1612/// Ergonomic collection operations implemented directly by the backing store.
1613///
1614/// The trait carries no state and is blanket-implemented. Its purpose is only
1615/// method syntax: collections remain plain descriptor handles and stores
1616/// retain their native ownership, flushing, and closing APIs.
1617pub trait CollectionStoreExt: BlobStorePut + CollectionStore + Sized {
1618    /// Register a complete custom descriptor at the raw typed boundary.
1619    ///
1620    /// Normal root and derived construction should use [`collection`](Self::collection)
1621    /// and [`derive`](Self::derive), which make canonical descriptors by
1622    /// construction.
1623    ///
1624    /// Registration recognizes only the encoding facts needed by `L` and
1625    /// stores the complete fragment. Policy and lineage are queried when a
1626    /// consumer needs them; unrecognized policy never grants admission.
1627    fn register_collection<L>(
1628        &mut self,
1629        descriptor: Fragment,
1630    ) -> Result<Collection<L>, CollectionRegistrationError<<Self as BlobStorePut>::PutError>>
1631    where
1632        L: CollectionEncoding,
1633    {
1634        super::encoding::validate_descriptor_type::<L>(&descriptor)
1635            .map_err(CollectionRegistrationError::WrongType)?;
1636        let handle = descriptor::put_closure(self, &descriptor)
1637            .map_err(CollectionRegistrationError::DependencyPut)?;
1638        Ok(Collection::from_handle(handle))
1639    }
1640
1641    /// Create and register one named root fact collection.
1642    fn collection(
1643        &mut self,
1644        name: &str,
1645        policy: CollectionPolicy,
1646    ) -> Result<
1647        Collection<SimpleArchive>,
1648        CollectionRegistrationError<<Self as BlobStorePut>::PutError>,
1649    > {
1650        let descriptor = descriptor::naming::<SimpleArchive>(name, policy);
1651        let handle = descriptor::put_closure(self, &descriptor)
1652            .map_err(CollectionRegistrationError::DependencyPut)?;
1653        Ok(Collection::from_handle(handle))
1654    }
1655
1656    /// Create and register one canonical derived collection.
1657    ///
1658    /// The target encoding owns its canonical source relation; `argument`
1659    /// carries only the runtime values embedded in this concrete mapping.
1660    fn derive<T>(
1661        &mut self,
1662        source: Collection<T::Source>,
1663        argument: T::Argument,
1664        policy: CollectionPolicy,
1665    ) -> Result<Collection<T>, CollectionRegistrationError<<Self as BlobStorePut>::PutError>>
1666    where
1667        T: CollectionDerivation,
1668    {
1669        self.derive_with::<CanonicalDerivation<T>>(
1670            source,
1671            CanonicalDerivation::new(argument),
1672            policy,
1673        )
1674    }
1675
1676    /// Create and register one collection through an explicit mapping value.
1677    ///
1678    /// This lower-level extension point remains available when Rust's orphan
1679    /// rule prevents either encoding crate from owning the canonical
1680    /// [`CollectionDerivation`] implementation.
1681    fn derive_with<M>(
1682        &mut self,
1683        source: Collection<M::Source>,
1684        mapping: M,
1685        policy: CollectionPolicy,
1686    ) -> Result<Collection<M::Target>, CollectionRegistrationError<<Self as BlobStorePut>::PutError>>
1687    where
1688        M: DeriveMapping,
1689    {
1690        let descriptor = descriptor::deriving_with(source.handle(), &mapping, policy);
1691        // The constructor already supplies the target encoding and both
1692        // policies. Only target-specific context may still need checking.
1693        M::Target::validate_descriptor(&descriptor).map_err(|source| {
1694            CollectionRegistrationError::WrongType(CollectionTypeError::InvalidDescriptor(source))
1695        })?;
1696        let handle = descriptor::put_closure(self, &descriptor)
1697            .map_err(CollectionRegistrationError::DependencyPut)?;
1698        Ok(Collection::from_handle(handle))
1699    }
1700
1701    /// Create and register one canonical attached collection: an index of
1702    /// `parent`'s nodes through `T`'s own mapping.
1703    ///
1704    /// The descriptor is the parent's handle and the mapping, with no
1705    /// policy; registering it publishes nothing else.
1706    fn attach<T>(
1707        &mut self,
1708        parent: Collection<SimpleArchive>,
1709        argument: T::Argument,
1710    ) -> Result<Collection<T>, CollectionRegistrationError<<Self as BlobStorePut>::PutError>>
1711    where
1712        T: CollectionAttachment,
1713    {
1714        self.attach_with::<CanonicalAttachment<T>>(parent, CanonicalAttachment::new(argument))
1715    }
1716
1717    /// Create and register one attached collection through an explicit
1718    /// mapping value.
1719    fn attach_with<M>(
1720        &mut self,
1721        parent: Collection<SimpleArchive>,
1722        mapping: M,
1723    ) -> Result<Collection<M::Target>, CollectionRegistrationError<<Self as BlobStorePut>::PutError>>
1724    where
1725        M: MapMapping,
1726    {
1727        let descriptor = descriptor::attaching_with(parent.handle(), &mapping);
1728        M::Target::validate_descriptor(&descriptor).map_err(|source| {
1729            CollectionRegistrationError::WrongType(CollectionTypeError::InvalidDescriptor(source))
1730        })?;
1731        let handle = descriptor::put_closure(self, &descriptor)
1732            .map_err(CollectionRegistrationError::DependencyPut)?;
1733        Ok(Collection::from_handle(handle))
1734    }
1735
1736    /// Give the parent's current frontier nodes their missing attachments:
1737    /// what a write calls after its commit, so the commit is readable
1738    /// through the attached collection. No merge is published.
1739    ///
1740    /// Every MAP is signed by `signing_key`, which must be the store's host
1741    /// ([`CollectionRealizationError::HostMismatch`] otherwise): only the
1742    /// host's MAPs are believed. A mapping reading a sibling attached
1743    /// collection finds that sibling's attachments here, so ensure the
1744    /// sibling first.
1745    fn ensure_attached<'a, T>(
1746        &'a mut self,
1747        attached: Collection<T>,
1748        signing_key: &'a SigningKey,
1749    ) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
1750    where
1751        T: CollectionAttachment,
1752        Self: Store + AsyncBlobStoreAcquire + Send,
1753        Handle<T>: InlineEncoding,
1754    {
1755        self.ensure_attached_with::<CanonicalAttachment<T>>(attached, signing_key)
1756    }
1757
1758    /// [`Self::ensure_attached`] through one explicit mapping.
1759    fn ensure_attached_with<'a, M>(
1760        &'a mut self,
1761        attached: Collection<M::Target>,
1762        signing_key: &'a SigningKey,
1763    ) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
1764    where
1765        M: MapMapping,
1766        Self: Store + AsyncBlobStoreAcquire + Send,
1767        Handle<M::Target>: InlineEncoding,
1768    {
1769        async move {
1770            super::exact_derived::acquire_attached_lineage(self, attached).await?;
1771            super::exact_derived::attach_acquiring_with::<Self, M>(self, attached, signing_key)
1772                .await?;
1773            self.snapshot().map_err(|error| {
1774                CollectionRealizationError::storage("freeze post-attach snapshot", error)
1775            })
1776        }
1777    }
1778
1779    /// Maintain one attached collection: carry its parent first, then give
1780    /// every node of the parent's frontier after that carry its missing
1781    /// attachment, so nothing is built for a node the carry is about to
1782    /// consume. A merged node's attachment is mapped from the merged node's
1783    /// own bytes.
1784    fn maintain_attached<'a, T>(
1785        &'a mut self,
1786        attached: Collection<T>,
1787        signing_key: &'a SigningKey,
1788    ) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
1789    where
1790        T: CollectionAttachment,
1791        Self: Store + AsyncBlobStoreAcquire + Send,
1792        Handle<T>: InlineEncoding,
1793    {
1794        self.maintain_attached_with::<CanonicalAttachment<T>>(attached, signing_key)
1795    }
1796
1797    /// [`Self::maintain_attached`] through one explicit mapping.
1798    fn maintain_attached_with<'a, M>(
1799        &'a mut self,
1800        attached: Collection<M::Target>,
1801        signing_key: &'a SigningKey,
1802    ) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
1803    where
1804        M: MapMapping,
1805        Self: Store + AsyncBlobStoreAcquire + Send,
1806        Handle<M::Target>: InlineEncoding,
1807    {
1808        async move {
1809            super::exact_derived::acquire_attached_lineage(self, attached).await?;
1810            let snapshot = self.snapshot().map_err(|error| {
1811                CollectionRealizationError::storage("observe attached parent", error)
1812            })?;
1813            let (parent, _, _) = super::maintenance::attached_lineage(&snapshot, attached)?;
1814            drop(snapshot);
1815            realize_root(self, parent, signing_key, true).await?;
1816            super::exact_derived::attach_acquiring_with::<Self, M>(self, attached, signing_key)
1817                .await?;
1818            self.snapshot().map_err(|error| {
1819                CollectionRealizationError::storage("freeze post-attach snapshot", error)
1820            })
1821        }
1822    }
1823
1824    /// Ensure one collection for the key: "derive what you wrote".
1825    ///
1826    /// A root acquires its admitted commits, whoever wrote them. A derived
1827    /// collection gets a leaf, `DERIVE(target, L(F), f(F))`, for every
1828    /// foundation `F` of its immediate source the key owns whose locator it
1829    /// has no leaf for yet, whoever signed an existing one; empty images are
1830    /// published too. Other owners' foundations are left to `maintain`,
1831    /// which derives those left without a usable leaf. Each publishing
1832    /// operation runs
1833    /// against one frozen control snapshot; when it names a missing image or
1834    /// descriptor, acquisition ends the operation and the retry starts from a
1835    /// fresh snapshot that sees everything that arrived, records and proofs
1836    /// included. No acquisition emits a durable WANT. Newly published
1837    /// equations are signed by `signing_key`; a target with nothing owed does
1838    /// not require that key to hold WRITE authority. The returned store
1839    /// snapshot observes everything published by this work and by any
1840    /// concurrent writer before that final observation.
1841    fn ensure<'a, T>(
1842        &'a mut self,
1843        target: Collection<T>,
1844        signing_key: &'a SigningKey,
1845    ) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
1846    where
1847        T: CollectionRealization,
1848        Self: Store + AsyncBlobStoreAcquire + Send,
1849        Handle<T>: InlineEncoding,
1850    {
1851        T::ensure(self, target, signing_key)
1852    }
1853
1854    /// Ensure one derived collection for the key through one explicit
1855    /// mapping.
1856    fn ensure_with<'a, M>(
1857        &'a mut self,
1858        target: Collection<M::Target>,
1859        signing_key: &'a SigningKey,
1860    ) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
1861    where
1862        M: DeriveMapping,
1863        Self: Store + AsyncBlobStoreAcquire + Send,
1864        Handle<M::Target>: InlineEncoding,
1865    {
1866        async move {
1867            super::exact_derived::acquire_authority(self, target).await?;
1868            super::exact_derived::ensure_acquiring_with::<Self, M>(self, target, signing_key)
1869                .await?;
1870            self.snapshot().map_err(|error| {
1871                CollectionRealizationError::storage("freeze post-ensure snapshot", error)
1872            })
1873        }
1874    }
1875
1876    /// Maintain one collection for the key.
1877    ///
1878    /// A root carries every frontier node whose bytes are here and read,
1879    /// whoever signed the foundations beneath it: a node inside another held
1880    /// node's support is absorbed, and every tier (`floor(log_8 |support|)`)
1881    /// holding eight held nodes is joined by one n-ary `MERGE`, until none
1882    /// does. A frontier node whose bytes are not here, or do not read, sits
1883    /// out and is not fetched; one that does not read is reported by a read
1884    /// that takes it, not here. A
1885    /// root's merges need no WRITE authority, only a store opened as the key
1886    /// (its host); signing with another key is
1887    /// [`CollectionRealizationError::HostMismatch`].
1888    ///
1889    /// A derived collection gets a leaf for every source foundation this
1890    /// host can derive that has no usable leaf: any believed leaf whose
1891    /// output is here and reads, or can be fetched, suffices, whoever signed
1892    /// it, and a leaf whose output no holder hands over does not count.
1893    /// Outputs that are not here, or do not read, are asked for after the
1894    /// rest of the work, each once, and only through a store that can reach
1895    /// other holders; a store that answers from its own blobs alone leaves
1896    /// such a foundation waiting for its output rather than deriving it
1897    /// again. A fetch that fails outright is reported after the work and is
1898    /// no reason to derive in its place, whatever the blob is to the call. A
1899    /// storage error, the carry's included, ends the call at once and is the
1900    /// error reported, ahead of any fetch; a carry that fails otherwise is
1901    /// reported after the fetches the rest needs. One call lets a
1902    /// bounded number of such fetches fail and starts foundations in an
1903    /// order of its own, so no foundation waits on the same others call
1904    /// after call. The key's own payloads are fetched; another owner's are
1905    /// used only when they are here. Then the collection carries its own
1906    /// lattice exactly as a root does, its leaf images joined by the
1907    /// mapping's join; it never reads its source's merges, and a foundation
1908    /// the mapping refused holds back neither the carry nor the fetches the
1909    /// rest is waiting for: it is reported after them.
1910    /// Deriving needs target WRITE authority, and a mapping pinned to a class
1911    /// of host derives nothing on another host; carrying needs neither, so
1912    /// such a key or host carries and reports nothing more. A derived
1913    /// collection stored in a root's encoding carries only through its
1914    /// mapping (`maintain_with`); this method refuses it. There is no
1915    /// caller-visible budget or tuning knob; every useful result is
1916    /// published independently. The live store may acquire exact
1917    /// dependencies while doing so. The supplied key signs each newly
1918    /// published `MERGE` or `DERIVE`.
1919    fn maintain<'a, T>(
1920        &'a mut self,
1921        target: Collection<T>,
1922        signing_key: &'a SigningKey,
1923    ) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
1924    where
1925        T: CollectionRealization,
1926        Self: Store + AsyncBlobStoreAcquire + Send,
1927        Handle<T>: InlineEncoding,
1928    {
1929        T::maintain(self, target, signing_key)
1930    }
1931
1932    /// Maintain one derived collection for the key through one explicit
1933    /// mapping.
1934    fn maintain_with<'a, M>(
1935        &'a mut self,
1936        target: Collection<M::Target>,
1937        signing_key: &'a SigningKey,
1938    ) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
1939    where
1940        M: DeriveMapping,
1941        Self: Store + AsyncBlobStoreAcquire + Send,
1942        Handle<M::Target>: InlineEncoding,
1943    {
1944        async move {
1945            super::exact_derived::acquire_authority(self, target).await?;
1946            super::exact_derived::maintain_acquiring_with::<Self, M>(self, target, signing_key)
1947                .await?;
1948            self.snapshot().map_err(|error| {
1949                CollectionRealizationError::storage("freeze post-maintenance snapshot", error)
1950            })
1951        }
1952    }
1953
1954    /// Derive the named source foundations again through `T`'s canonical
1955    /// mapping; see [`Self::rederive_with`].
1956    fn rederive<'a, T>(
1957        &'a mut self,
1958        target: Collection<T>,
1959        foundations: &Cover<T::Source>,
1960        signing_key: &'a SigningKey,
1961    ) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
1962    where
1963        T: CollectionDerivation,
1964        Self: Store + AsyncBlobStoreAcquire + Send,
1965        Handle<T>: InlineEncoding,
1966    {
1967        self.rederive_with::<CanonicalDerivation<T>>(target, foundations, signing_key)
1968    }
1969
1970    /// Derive the named source foundations again and publish each result
1971    /// that no believed leaf already names as another leaf: the explicit
1972    /// supplement to a leaf known to be bad.
1973    ///
1974    /// Maintenance never replaces a leaf, and never derives a foundation that
1975    /// already has a usable one, whoever signed it. A leaf whose output is
1976    /// wrong therefore stays; this adds a second leaf beside it, and the
1977    /// derived collection's join holds both. A foundation whose result equals
1978    /// an output its leaves already name adds nothing. `foundations` must be
1979    /// a cover of the target's immediate source naming believed foundations
1980    /// of it; each payload that is not here is fetched, whoever owns it,
1981    /// because re-deriving it was asked for. The key must be one the target
1982    /// admits ([`CollectionRealizationError::UnauthorizedProducer`]), the host
1983    /// one that can compute the mapping, and anything the mapping cannot do
1984    /// with a named foundation is an error.
1985    fn rederive_with<'a, M>(
1986        &'a mut self,
1987        target: Collection<M::Target>,
1988        foundations: &Cover<M::Source>,
1989        signing_key: &'a SigningKey,
1990    ) -> impl Future<Output = Result<Self::Snapshot, CollectionRealizationError>> + Send + 'a
1991    where
1992        M: DeriveMapping,
1993        Self: Store + AsyncBlobStoreAcquire + Send,
1994        Handle<M::Target>: InlineEncoding,
1995    {
1996        let source = foundations.collection().handle();
1997        let members: BTreeSet<CollectionData> = foundations.data_members().collect();
1998        async move {
1999            super::exact_derived::acquire_authority(self, target).await?;
2000            super::exact_derived::rederive_acquiring_with::<Self, M>(
2001                self,
2002                target,
2003                source,
2004                &members,
2005                signing_key,
2006            )
2007            .await?;
2008            self.snapshot().map_err(|error| {
2009                CollectionRealizationError::storage("freeze post-re-derivation snapshot", error)
2010            })
2011        }
2012    }
2013
2014    /// Publish one signed fragment into an already registered collection.
2015    ///
2016    /// This performs no capability or descriptor check. Local storage is a
2017    /// grow-only claim ledger; authority and descriptor validity are applied
2018    /// at untrusted read and synchronization boundaries. Fragment attachments,
2019    /// data, and metadata are stored before the signed record is inserted,
2020    /// without an implicit durability flush.
2021    fn commit(
2022        &mut self,
2023        collection: Collection<SimpleArchive>,
2024        signing_key: &SigningKey,
2025        fragment: Fragment,
2026    ) -> Result<
2027        CollectionCommit,
2028        CollectionCommitError<
2029            <Self as BlobStorePut>::PutError,
2030            <Self as CollectionStore>::InsertError,
2031        >,
2032    > {
2033        PreparedCollectionCommit::from_fragment(fragment)
2034            .stage_for(self, collection, signing_key)?
2035            .finalize()
2036    }
2037}
2038
2039impl<S> CollectionStoreExt for S where S: BlobStorePut + CollectionStore {}
2040
2041#[cfg(test)]
2042mod grant_tests {
2043    use super::*;
2044    use crate::capability::{capability_action, capability_delegate_action};
2045    use crate::collection::{ACTION_READ, ACTION_WRITE};
2046    use crate::prelude::entity;
2047    use crate::repo::memoryrepo::MemoryRepo;
2048
2049    #[test]
2050    fn compound_grants_require_a_root_for_every_invocation_and_delegation_action() {
2051        let read_root = SigningKey::from_bytes(&[71; 32]);
2052        let write_root = SigningKey::from_bytes(&[72; 32]);
2053        let recipient = SigningKey::from_bytes(&[73; 32]).verifying_key();
2054        let mut store = MemoryRepo::default();
2055        let collection = store
2056            .collection(
2057                "split action roots",
2058                CollectionPolicy::new(
2059                    AdmissionPolicy::direct(read_root.verifying_key()),
2060                    AdmissionPolicy::direct(write_root.verifying_key()),
2061                ),
2062            )
2063            .unwrap();
2064        for definition in [
2065            entity! { capability_action*: [ACTION_READ, ACTION_WRITE] },
2066            entity! {
2067                capability_action: ACTION_READ,
2068                capability_delegate_action: ACTION_WRITE,
2069            },
2070            entity! { capability_delegate_action*: [ACTION_READ, ACTION_WRITE] },
2071        ] {
2072            let capability = store
2073                .put::<SimpleArchive, _>(definition.facts().clone())
2074                .unwrap();
2075            for root in [&read_root, &write_root] {
2076                assert!(matches!(
2077                    grant_collection_capability(
2078                        &mut store,
2079                        collection.handle(),
2080                        capability,
2081                        root,
2082                        recipient,
2083                    ),
2084                    Err(CollectionGrantError::RootNotAuthorized { .. })
2085                ));
2086            }
2087        }
2088        assert_eq!(store.snapshot().unwrap().proofs().unwrap().count(), 0);
2089    }
2090
2091    #[test]
2092    fn an_unknown_action_or_empty_definition_cannot_borrow_a_known_action_root() {
2093        let root = SigningKey::from_bytes(&[74; 32]);
2094        let recipient = SigningKey::from_bytes(&[75; 32]).verifying_key();
2095        let mut store = MemoryRepo::default();
2096        let collection = store
2097            .collection(
2098                "unknown grant actions",
2099                CollectionPolicy::new(
2100                    AdmissionPolicy::direct(root.verifying_key()),
2101                    AdmissionPolicy::direct(root.verifying_key()),
2102                ),
2103            )
2104            .unwrap();
2105        let unknown = crate::id::genid().id;
2106        for definition in [
2107            entity! { capability_action*: [ACTION_READ, unknown] },
2108            entity! {
2109                capability_action: ACTION_READ,
2110                capability_delegate_action: unknown,
2111            },
2112            Fragment::empty(),
2113        ] {
2114            let capability = store
2115                .put::<SimpleArchive, _>(definition.facts().clone())
2116                .unwrap();
2117            assert!(matches!(
2118                grant_collection_capability(
2119                    &mut store,
2120                    collection.handle(),
2121                    capability,
2122                    &root,
2123                    recipient,
2124                ),
2125                Err(CollectionGrantError::RootNotAuthorized { .. })
2126            ));
2127        }
2128        assert_eq!(store.snapshot().unwrap().proofs().unwrap().count(), 0);
2129    }
2130
2131    #[test]
2132    fn compound_grants_preserve_independent_quorum_shares() {
2133        let first = SigningKey::from_bytes(&[76; 32]);
2134        let second = SigningKey::from_bytes(&[77; 32]);
2135        let recipient = SigningKey::from_bytes(&[78; 32]).verifying_key();
2136        let mut store = MemoryRepo::default();
2137        let quorum =
2138            AdmissionPolicy::quorum([first.verifying_key(), second.verifying_key()], 2, None)
2139                .unwrap();
2140        let collection = store
2141            .collection(
2142                "compound quorum shares",
2143                CollectionPolicy::new(quorum.clone(), quorum),
2144            )
2145            .unwrap();
2146        let capability = store
2147            .put::<SimpleArchive, _>(
2148                entity! {
2149                    capability_action*: [ACTION_READ, ACTION_WRITE],
2150                    capability_delegate_action: ACTION_READ,
2151                }
2152                .facts()
2153                .clone(),
2154            )
2155            .unwrap();
2156        let first_proof = grant_collection_capability(
2157            &mut store,
2158            collection.handle(),
2159            capability,
2160            &first,
2161            recipient,
2162        )
2163        .unwrap();
2164        let one_share = store.snapshot().unwrap();
2165        assert!(!collection
2166            .reader_is_admitted(&one_share, recipient)
2167            .unwrap());
2168        assert!(!collection
2169            .writer_is_admitted(&one_share, recipient)
2170            .unwrap());
2171        grant_collection_capability(
2172            &mut store,
2173            collection.handle(),
2174            capability,
2175            &second,
2176            recipient,
2177        )
2178        .unwrap();
2179        let repeated = grant_collection_capability(
2180            &mut store,
2181            collection.handle(),
2182            capability,
2183            &first,
2184            recipient,
2185        )
2186        .unwrap();
2187        assert_eq!(repeated, first_proof);
2188        let both_shares = store.snapshot().unwrap();
2189        assert_eq!(both_shares.proofs().unwrap().count(), 2);
2190        assert!(collection
2191            .reader_is_admitted(&both_shares, recipient)
2192            .unwrap());
2193        assert!(collection
2194            .writer_is_admitted(&both_shares, recipient)
2195            .unwrap());
2196    }
2197
2198    #[test]
2199    fn delegate_only_read_grant_can_confer_read_without_invoking_it() {
2200        let root = SigningKey::from_bytes(&[79; 32]);
2201        let delegate = SigningKey::from_bytes(&[80; 32]);
2202        let reader = SigningKey::from_bytes(&[81; 32]).verifying_key();
2203        let mut store = MemoryRepo::default();
2204        let collection = store
2205            .collection(
2206                "delegate without invocation",
2207                CollectionPolicy::new(
2208                    AdmissionPolicy::direct(root.verifying_key()),
2209                    AdmissionPolicy::direct(root.verifying_key()),
2210                ),
2211            )
2212            .unwrap();
2213        let capability = store
2214            .put::<SimpleArchive, _>(
2215                entity! { capability_delegate_action: ACTION_READ }
2216                    .facts()
2217                    .clone(),
2218            )
2219            .unwrap();
2220        let proof = grant_collection_capability(
2221            &mut store,
2222            collection.handle(),
2223            capability,
2224            &root,
2225            delegate.verifying_key(),
2226        )
2227        .unwrap();
2228        let child = proof
2229            .delegate(&delegate, read_capability(), reader)
2230            .unwrap();
2231        store.insert_proof(child).unwrap();
2232        let snapshot = store.snapshot().unwrap();
2233        assert!(!collection
2234            .reader_is_admitted(&snapshot, delegate.verifying_key())
2235            .unwrap());
2236        assert!(!collection
2237            .writer_is_admitted(&snapshot, delegate.verifying_key())
2238            .unwrap());
2239        assert!(collection.reader_is_admitted(&snapshot, reader).unwrap());
2240        assert!(!collection.writer_is_admitted(&snapshot, reader).unwrap());
2241    }
2242
2243    #[test]
2244    fn open_actions_can_accompany_a_restricted_grant_but_cannot_authorize_it() {
2245        let root = SigningKey::from_bytes(&[82; 32]);
2246        let outsider = SigningKey::from_bytes(&[83; 32]);
2247        let recipient = SigningKey::from_bytes(&[84; 32]).verifying_key();
2248        let mut store = MemoryRepo::default();
2249        let collection = store
2250            .collection(
2251                "public reads private writes",
2252                CollectionPolicy::new(
2253                    AdmissionPolicy::Open,
2254                    AdmissionPolicy::direct(root.verifying_key()),
2255                ),
2256            )
2257            .unwrap();
2258        let capability = store
2259            .put::<SimpleArchive, _>(
2260                entity! {
2261                    capability_action*: [ACTION_READ, ACTION_WRITE],
2262                    capability_delegate_action: ACTION_READ,
2263                }
2264                .facts()
2265                .clone(),
2266            )
2267            .unwrap();
2268        assert!(matches!(
2269            grant_collection_capability(
2270                &mut store,
2271                collection.handle(),
2272                capability,
2273                &outsider,
2274                recipient,
2275            ),
2276            Err(CollectionGrantError::RootNotAuthorized { .. })
2277        ));
2278        grant_collection_capability(
2279            &mut store,
2280            collection.handle(),
2281            capability,
2282            &root,
2283            recipient,
2284        )
2285        .unwrap();
2286        let snapshot = store.snapshot().unwrap();
2287        assert!(collection.reader_is_admitted(&snapshot, recipient).unwrap());
2288        assert!(collection.writer_is_admitted(&snapshot, recipient).unwrap());
2289        assert!(matches!(
2290            grant_collection_read(&mut store, collection.handle(), &root, recipient),
2291            Err(CollectionGrantError::OpenPolicy { .. })
2292        ));
2293        let open = store
2294            .collection(
2295                "both actions open",
2296                CollectionPolicy::new(AdmissionPolicy::Open, AdmissionPolicy::Open),
2297            )
2298            .unwrap();
2299        assert!(matches!(
2300            grant_collection_capability(&mut store, open.handle(), capability, &root, recipient),
2301            Err(CollectionGrantError::OpenPolicy { .. })
2302        ));
2303        assert_eq!(store.snapshot().unwrap().proofs().unwrap().count(), 1);
2304    }
2305}