use std::collections::BTreeSet;
use std::ops::{Deref, DerefMut};
use ed25519_dalek::VerifyingKey;
use crate::blob::encodings::UnknownBlob;
use crate::blob::{BlobEncoding, IntoBlob, TryFromBlob};
use crate::capability::{CapabilityProof, CapabilityProofId};
use crate::inline::encodings::hash::Handle;
use crate::inline::{Inline, InlineEncoding};
use crate::patch::{IdentitySchema, PATCH};
use crate::repo::async_store::AsyncBlobStoreAcquire;
use crate::repo::{
BlobChildren, BlobInfo, BlobMetadata, BlobStoreGet, BlobStoreKeep, BlobStoreList,
BlobStoreMeta, BlobStorePut, CapabilityProofRead, CapabilityProofStore, PinSnapshot,
PinSnapshotSource, SnapshotSource, StorageClose, StorageFlush, StoreChanges, StoreDependencies, StoreSnapshot, WantRead,
WantRequest, WantStore,
};
use super::coverage::{Coverage, CoverageIndex, StoreWriters};
use super::held::{
HeldBlobs, HeldIndex, HeldRead, HeldSource, HeldStore, HeldView, HeldWalkConfig, HeldWalker,
};
use super::{
CollectionHandle, CollectionRead, CollectionRecord,
CollectionRecordSelector, CollectionStore, CoverageRead,
};
pub trait RecordDelta {
fn for_each_record_since(&self, since: Option<&Self>, each: &mut dyn FnMut(&CollectionRecord));
}
pub struct Covered<S: SnapshotSource> {
inner: S,
host: Option<VerifyingKey>,
last: Option<(S::Snapshot, std::sync::Arc<std::sync::Mutex<Memo>>)>,
held: std::sync::Arc<HeldIndex<S::Snapshot>>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct Memo {
index: CoverageIndex,
settled: BTreeSet<CollectionHandle>,
}
impl<S: SnapshotSource> Covered<S> {
pub fn new(inner: S) -> Self {
Self::with_host(inner, None)
}
pub fn with_host(inner: S, host: Option<VerifyingKey>) -> Self {
Self {
inner,
host,
last: None,
held: std::sync::Arc::default(),
}
}
pub fn host(&self) -> Option<VerifyingKey> {
self.host
}
pub fn into_inner(self) -> S {
self.inner
}
}
impl<S: SnapshotSource + Default> Covered<S> {
pub fn for_host(host: VerifyingKey) -> Self {
Self::with_host(S::default(), Some(host))
}
}
impl<S: SnapshotSource + Default> Default for Covered<S> {
fn default() -> Self {
Self::new(S::default())
}
}
impl<S> Clone for Covered<S>
where
S: SnapshotSource + Clone,
S::Snapshot: Clone,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
host: self.host,
last: self.last.clone(),
held: std::sync::Arc::new(self.held.detached_clone()),
}
}
}
impl<S: SnapshotSource + std::fmt::Debug> std::fmt::Debug for Covered<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Covered")
.field("inner", &self.inner)
.field("host", &self.host)
.field("last", &self.last.is_some())
.finish()
}
}
impl<S: SnapshotSource> Deref for Covered<S> {
type Target = S;
fn deref(&self) -> &S {
&self.inner
}
}
impl<S: SnapshotSource> DerefMut for Covered<S> {
fn deref_mut(&mut self) -> &mut S {
&mut self.inner
}
}
impl<S> Covered<S>
where
S: SnapshotSource,
S::Snapshot: RecordDelta + BlobStoreList + BlobStoreGet + CapabilityProofRead + Clone,
{
fn carry_forward(&mut self, now: &S::Snapshot) -> std::sync::Arc<std::sync::Mutex<Memo>> {
let Some((fed, last)) = &self.last else {
return std::sync::Arc::new(std::sync::Mutex::new(Memo {
index: CoverageIndex::with_host(self.host.map(|host| Inline::new(host.to_bytes()))),
settled: BTreeSet::new(),
}));
};
if now.changes_since(fed).is_empty() {
return std::sync::Arc::clone(last);
}
let mut memo = last.lock().expect("coverage memo is not poisoned").clone();
if memo.index.parked_on_lineages() > 0 {
for handle in now.blobs_diff(fed).flatten() {
let descriptor: CollectionHandle = Inline::new(handle.handle.raw);
if memo.index.blob_arrived(descriptor) {
memo.index.wake_descriptor(descriptor);
}
}
}
if !memo.settled.is_empty() {
now.for_each_record_since(Some(fed), &mut |record| {
if memo.settled.contains(&record.collection()) {
memo.index.park_record(record);
}
});
}
if now
.changes_since(fed)
.contains(StoreChanges::CAPABILITY_PROOFS)
{
memo.index.wake_proofs();
}
std::sync::Arc::new(std::sync::Mutex::new(memo))
}
}
impl<S> SnapshotSource for Covered<S>
where
S: SnapshotSource,
S::Snapshot: HeldSource,
{
type Snapshot = CoveredSnapshot<S::Snapshot>;
type SnapshotError = S::SnapshotError;
fn snapshot(&mut self) -> Result<Self::Snapshot, Self::SnapshotError> {
let now = self.inner.snapshot()?;
let memo = self.carry_forward(&now);
let held = self.held.observe(&now);
self.last = Some((now.clone(), std::sync::Arc::clone(&memo)));
Ok(CoveredSnapshot {
inner: now,
memo,
held,
})
}
}
impl<S: SnapshotSource> HeldStore for Covered<S> {
fn track_held(&mut self, collections: impl IntoIterator<Item = CollectionHandle>) {
self.held.track(collections);
}
fn note_held(&mut self, collection: CollectionHandle, handle: Inline<Handle<UnknownBlob>>) {
self.held.note(collection, handle.raw);
}
}
impl<S> Covered<S>
where
S: SnapshotSource,
S::Snapshot: HeldSource,
{
pub fn start_held_walker(&self, config: HeldWalkConfig) -> HeldWalker {
self.held.start_walker(config)
}
pub fn walk_held(&mut self, threads: usize) -> Result<(), S::SnapshotError> {
let now = self.inner.snapshot()?;
self.held.observe(&now);
self.held.walk(threads, None, None);
Ok(())
}
}
impl<S: SnapshotSource + BlobStorePut> BlobStorePut for Covered<S> {
type PutError = S::PutError;
fn put<E, T>(&mut self, item: T) -> Result<Inline<Handle<E>>, Self::PutError>
where
E: BlobEncoding + 'static,
T: IntoBlob<E>,
Handle<E>: InlineEncoding,
{
self.inner.put(item)
}
}
impl<S: SnapshotSource + BlobStoreKeep> BlobStoreKeep for Covered<S> {
fn keep<I>(&mut self, handles: I)
where
I: IntoIterator<Item = Inline<Handle<UnknownBlob>>>,
{
self.inner.keep(handles)
}
}
impl<S: SnapshotSource + CollectionStore> CollectionStore for Covered<S> {
type InsertError = S::InsertError;
fn insert(&mut self, record: CollectionRecord) -> Result<(), Self::InsertError> {
self.inner.insert(record)
}
}
impl<S: SnapshotSource + CapabilityProofStore> CapabilityProofStore for Covered<S> {
type InsertError = S::InsertError;
fn insert_proof(&mut self, proof: CapabilityProof) -> Result<(), Self::InsertError> {
self.inner.insert_proof(proof)
}
}
impl<S: SnapshotSource + WantStore> WantStore for Covered<S> {
type WantError = S::WantError;
fn want(&mut self, request: WantRequest) -> Result<(), Self::WantError> {
self.inner.want(request)
}
}
impl<S: SnapshotSource + StorageFlush> StorageFlush for Covered<S> {
type Error = S::Error;
fn flush(&mut self) -> Result<(), Self::Error> {
self.inner.flush()
}
}
impl<S: SnapshotSource + AsyncBlobStoreAcquire> AsyncBlobStoreAcquire for Covered<S> {
type AcquireError = S::AcquireError;
fn acquire(
&mut self,
handle: Inline<Handle<UnknownBlob>>,
) -> impl std::future::Future<Output = Result<Option<anybytes::Bytes>, Self::AcquireError>> + Send
{
self.inner.acquire(handle)
}
fn acquires_remotely(&self) -> bool {
self.inner.acquires_remotely()
}
}
impl<S: SnapshotSource + PinSnapshotSource> PinSnapshotSource for Covered<S> {
type PinSnapshotError = S::PinSnapshotError;
fn snapshot_pin_heads(&mut self) -> Result<PinSnapshot, Self::PinSnapshotError> {
self.inner.snapshot_pin_heads()
}
}
impl<S: SnapshotSource + StorageClose> StorageClose for Covered<S> {
type Error = S::Error;
fn close(self) -> Result<(), Self::Error> {
self.inner.close()
}
}
impl<S: SnapshotSource + StorageClose> Covered<S> {
pub fn close(self) -> Result<(), S::Error> {
self.inner.close()
}
}
#[derive(Clone)]
pub struct CoveredSnapshot<T> {
inner: T,
memo: std::sync::Arc<std::sync::Mutex<Memo>>,
held: std::sync::Arc<HeldView>,
}
impl<T: std::fmt::Debug> std::fmt::Debug for CoveredSnapshot<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CoveredSnapshot")
.field("inner", &self.inner)
.field("memo", &*self.memo.lock().expect("coverage memo is not poisoned"))
.field("held", &self.held)
.finish()
}
}
impl<T: PartialEq> PartialEq for CoveredSnapshot<T> {
fn eq(&self, other: &Self) -> bool {
if self.inner != other.inner || self.held != other.held {
return false;
}
if std::sync::Arc::ptr_eq(&self.memo, &other.memo) {
return true;
}
*self.memo.lock().expect("coverage memo is not poisoned")
== *other.memo.lock().expect("coverage memo is not poisoned")
}
}
impl<T: Eq> Eq for CoveredSnapshot<T> {}
impl<T> CoveredSnapshot<T> {
pub fn inner(&self) -> &T {
&self.inner
}
}
impl<T: CollectionRead + BlobStoreGet + CapabilityProofRead> CoveredSnapshot<T> {
fn settle_lineage(
&self,
memo: &mut Memo,
collections: &BTreeSet<CollectionHandle>,
) -> Result<(), T::RecordsError> {
let unseen: BTreeSet<_> = collections
.iter()
.filter(|collection| !memo.settled.contains(*collection))
.map(|collection| CollectionRecordSelector::Collection(*collection))
.collect();
if !unseen.is_empty() {
for record in self.inner.select_records(&unseen)? {
memo.index.park_record(&record);
}
memo.settled.extend(collections.iter().copied());
}
memo.index
.settle_collections(&StoreWriters::new(&self.inner), collections);
Ok(())
}
pub fn coverage_index(&self) -> CoverageIndex {
let mut memo = self.memo.lock().expect("coverage memo is not poisoned");
let mut all = BTreeSet::new();
if let Ok(records) = self.inner.records() {
for record in records.flatten() {
if !memo.settled.contains(&record.collection()) {
memo.index.park_record(&record);
}
all.insert(record.collection());
}
}
memo.settled.extend(all.iter().copied());
memo.index
.settle(&StoreWriters::new(&self.inner), std::iter::empty(), false);
memo.index.clone()
}
pub fn coverage(&self) -> Coverage {
self.coverage_index().published().clone()
}
}
impl<T> Deref for CoveredSnapshot<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl<T> std::ops::DerefMut for CoveredSnapshot<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.inner
}
}
impl<T: CollectionRead + BlobStoreGet + CapabilityProofRead> CoverageRead for CoveredSnapshot<T> {
fn index(&self, lineage: &BTreeSet<CollectionHandle>) -> Result<CoverageIndex, Self::RecordsError> {
let mut memo = self.memo.lock().expect("coverage memo is not poisoned");
self.settle_lineage(&mut memo, lineage)?;
Ok(memo.index.clone())
}
}
impl<T> HeldRead for CoveredSnapshot<T> {
fn held(&self, collection: CollectionHandle) -> Option<HeldBlobs> {
self.held.held(collection)
}
fn held_generation(&self) -> u64 {
self.held.generation()
}
}
impl<T: RecordDelta> RecordDelta for CoveredSnapshot<T> {
fn for_each_record_since(&self, since: Option<&Self>, each: &mut dyn FnMut(&CollectionRecord)) {
self.inner
.for_each_record_since(since.map(|since| &since.inner), each)
}
}
impl<T: StoreSnapshot> StoreSnapshot for CoveredSnapshot<T> {
fn changes_since(&self, previous: &Self) -> StoreChanges {
self.inner.changes_since(&previous.inner)
}
fn changes_for(&self, previous: &Self, dependencies: &StoreDependencies) -> StoreChanges {
self.inner.changes_for(&previous.inner, dependencies)
}
}
impl<T: BlobStoreGet> BlobStoreGet for CoveredSnapshot<T> {
type GetError<E: std::error::Error + Send + Sync + 'static> = T::GetError<E>;
fn get<V, S>(
&self,
handle: Inline<Handle<S>>,
) -> Result<V, Self::GetError<<V as TryFromBlob<S>>::Error>>
where
S: BlobEncoding + 'static,
V: TryFromBlob<S>,
Handle<S>: InlineEncoding,
{
self.inner.get(handle)
}
}
impl<T: BlobStoreList> BlobStoreList for CoveredSnapshot<T> {
type Iter<'a>
= T::Iter<'a>
where
Self: 'a;
type Err = T::Err;
fn blobs<'a>(&'a self) -> Self::Iter<'a> {
self.inner.blobs()
}
fn contains_blob<S>(&self, handle: Inline<Handle<S>>) -> Result<bool, Self::Err>
where
S: BlobEncoding + 'static,
Handle<S>: InlineEncoding,
{
self.inner.contains_blob(handle)
}
fn blob_info<S>(&self, handle: Inline<Handle<S>>) -> Result<Option<BlobInfo>, Self::Err>
where
S: BlobEncoding + 'static,
Handle<S>: InlineEncoding,
{
self.inner.blob_info(handle)
}
fn blobs_diff<'a>(&'a self, old: &Self) -> Self::Iter<'a> {
self.inner.blobs_diff(&old.inner)
}
}
impl<T: BlobStoreMeta> BlobStoreMeta for CoveredSnapshot<T> {
type MetaError = T::MetaError;
fn metadata<S>(&self, handle: Inline<Handle<S>>) -> Result<Option<BlobMetadata>, Self::MetaError>
where
S: BlobEncoding + 'static,
Handle<S>: InlineEncoding,
{
self.inner.metadata(handle)
}
fn resident(
&self,
handles: &PATCH<32, IdentitySchema, ()>,
) -> Result<PATCH<32, IdentitySchema, ()>, Self::MetaError> {
self.inner.resident(handles)
}
}
impl<T: BlobChildren> BlobChildren for CoveredSnapshot<T> {
fn children(&self, handle: Inline<Handle<UnknownBlob>>) -> Vec<Inline<Handle<UnknownBlob>>> {
self.inner.children(handle)
}
}
impl<T: CollectionRead> CollectionRead for CoveredSnapshot<T> {
type RecordsError = T::RecordsError;
type RecordIter<'a>
= T::RecordIter<'a>
where
Self: 'a;
fn records<'a>(&'a self) -> Result<Self::RecordIter<'a>, Self::RecordsError> {
self.inner.records()
}
fn collections(&self) -> Result<Vec<CollectionHandle>, Self::RecordsError> {
self.inner.collections()
}
fn select_records(
&self,
selectors: &BTreeSet<CollectionRecordSelector>,
) -> Result<Vec<CollectionRecord>, Self::RecordsError> {
self.inner.select_records(selectors)
}
fn select_record_changes(
&self,
previous: &Self,
selectors: &BTreeSet<CollectionRecordSelector>,
) -> Result<(Vec<CollectionRecord>, Vec<CollectionRecord>), Self::RecordsError> {
self.inner.select_record_changes(&previous.inner, selectors)
}
}
impl<T: CapabilityProofRead> CapabilityProofRead for CoveredSnapshot<T> {
type ProofsError = T::ProofsError;
type ProofIter<'a>
= T::ProofIter<'a>
where
Self: 'a;
fn proofs<'a>(&'a self) -> Result<Self::ProofIter<'a>, Self::ProofsError> {
self.inner.proofs()
}
fn proof(&self, id: CapabilityProofId) -> Result<Option<CapabilityProof>, Self::ProofsError> {
self.inner.proof(id)
}
}
impl<T: WantRead> WantRead for CoveredSnapshot<T> {
type WantsError = T::WantsError;
type WantIter<'a>
= T::WantIter<'a>
where
Self: 'a;
fn wants<'a>(&'a self) -> Result<Self::WantIter<'a>, Self::WantsError> {
self.inner.wants()
}
}
#[cfg(test)]
mod tests {
use ed25519_dalek::SigningKey;
use super::*;
use crate::blob::encodings::simplearchive::SimpleArchive;
use crate::blob::encodings::utf8string::UTF8String;
use crate::blob::Blob;
use crate::capability::CapabilityResource;
use crate::prelude::entity;
use crate::collection::{
write_capability, AdmissionPolicy, Collection, CollectionPolicy, CollectionStoreExt,
};
use crate::repo::memoryrepo::{MemoryRepo, MemoryRepoSnapshot};
fn policy(root: &SigningKey) -> CollectionPolicy {
CollectionPolicy::new(
AdmissionPolicy::direct(root.verifying_key()),
AdmissionPolicy::direct(root.verifying_key()),
)
}
fn agree(snapshot: &CoveredSnapshot<MemoryRepoSnapshot>, step: &str) -> CoverageIndex {
let ours = snapshot.coverage_index();
let theirs = snapshot.inner().coverage_index();
assert_eq!(ours, theirs, "after {step}");
ours
}
#[test]
fn a_reader_pays_for_the_lineage_it_asks_for_and_no_other() {
let root = SigningKey::from_bytes(&[73; 32]);
let mut store = Covered::new(MemoryRepo::default());
let first: Collection<SimpleArchive> = store.collection("first", policy(&root)).unwrap();
let second: Collection<SimpleArchive> = store.collection("second", policy(&root)).unwrap();
for (collection, text) in [(first, "one"), (second, "two")] {
let child = store.put::<UTF8String, _>(text.to_owned()).unwrap();
store
.commit(collection, &root, entity! { crate::metadata::name: child })
.unwrap();
}
let snapshot = store.snapshot().unwrap();
let only_first = snapshot.index(&BTreeSet::from([first.handle()])).unwrap();
assert_eq!(only_first.published().len(), 1);
assert_eq!(only_first.parked(), 0, "the other lineage was never even offered");
let both = snapshot.index(&BTreeSet::from([second.handle()])).unwrap();
assert_eq!(both.published().len(), 2);
assert_eq!(both.parked(), 0);
assert_eq!(snapshot.coverage_index(), both, "the eager form has nothing left to decide");
let child = store.put::<UTF8String, _>("three".to_owned()).unwrap();
store
.commit(first, &root, entity! { crate::metadata::name: child })
.unwrap();
let later = store.snapshot().unwrap();
let carried = later.index(&BTreeSet::from([first.handle()])).unwrap();
assert_eq!(carried.published().len(), 3);
}
#[test]
fn unchanged_snapshots_share_late_control_warming() {
let root = SigningKey::from_bytes(&[73; 32]);
let mut store = MemoryRepo::default();
let collection: Collection<SimpleArchive> =
store.collection("late control warming", policy(&root)).unwrap();
store
.commit(collection, &root, entity! { crate::metadata::name: "one" })
.unwrap();
let control = store.snapshot().unwrap();
let residency = store.snapshot().unwrap();
let lineage = BTreeSet::from([collection.handle()]);
assert!(residency.memo.lock().unwrap().settled.is_empty());
let warmed = control.index(&lineage).unwrap();
assert_eq!(warmed.published().len(), 1);
assert!(residency.memo.lock().unwrap().settled.contains(&collection.handle()));
assert_eq!(residency.memo.lock().unwrap().index, warmed);
let next = store.snapshot().unwrap();
assert!(next.memo.lock().unwrap().settled.contains(&collection.handle()));
assert_eq!(next.index(&lineage).unwrap(), warmed);
}
#[test]
fn changed_records_fork_late_control_warming_without_advancing_it() {
let root = SigningKey::from_bytes(&[73; 32]);
let mut store = MemoryRepo::default();
let collection: Collection<SimpleArchive> =
store.collection("changed control records", policy(&root)).unwrap();
store
.commit(collection, &root, entity! { crate::metadata::name: "one" })
.unwrap();
let control = store.snapshot().unwrap();
let residency = store.snapshot().unwrap();
store
.commit(collection, &root, entity! { crate::metadata::name: "two" })
.unwrap();
let changed = store.snapshot().unwrap();
let lineage = BTreeSet::from([collection.handle()]);
assert!(!std::sync::Arc::ptr_eq(&control.memo, &changed.memo));
assert_eq!(residency.index(&lineage).unwrap().published().len(), 1);
assert!(changed.memo.lock().unwrap().settled.is_empty());
assert_eq!(changed.index(&lineage).unwrap().published().len(), 2);
assert_eq!(control.index(&lineage).unwrap().published().len(), 1);
assert_eq!(residency.index(&lineage).unwrap().published().len(), 1);
}
#[test]
fn changed_proofs_fork_the_memo_without_advancing_old_authority() {
let root = SigningKey::from_bytes(&[71; 32]);
let writer = SigningKey::from_bytes(&[72; 32]);
let mut store = MemoryRepo::default();
let collection: Collection<SimpleArchive> =
store.collection("changed control authority", policy(&root)).unwrap();
store
.commit(collection, &writer, entity! { crate::metadata::name: "waiting" })
.unwrap();
let control = store.snapshot().unwrap();
let residency = store.snapshot().unwrap();
store
.insert_proof(CapabilityProof::new(
CapabilityResource::from(collection.handle()),
&root,
write_capability(),
writer.verifying_key(),
))
.unwrap();
let changed = store.snapshot().unwrap();
assert_eq!(changed.changes_since(&control), StoreChanges::CAPABILITY_PROOFS);
assert!(!std::sync::Arc::ptr_eq(&control.memo, &changed.memo));
let lineage = BTreeSet::from([collection.handle()]);
assert_eq!(changed.index(&lineage).unwrap().published().len(), 1);
let old = control.index(&lineage).unwrap();
assert!(old.published().is_empty());
assert_eq!(old.parked_on_signers(), 1);
assert_eq!(residency.index(&lineage).unwrap(), old);
}
#[test]
fn changed_blob_residency_forks_the_memo_without_filling_old_misses() {
let root = SigningKey::from_bytes(&[71; 32]);
let mut elsewhere = MemoryRepo::default();
let collection: Collection<SimpleArchive> =
elsewhere.collection("late control descriptor", policy(&root)).unwrap();
let record = elsewhere
.commit(collection, &root, entity! { crate::metadata::name: "one" })
.unwrap();
let descriptor: Blob<UnknownBlob> = elsewhere.snapshot().unwrap()
.get(collection.handle().transmute()).unwrap();
let mut store = MemoryRepo::default();
let _: Collection<SimpleArchive> = store
.collection("known capability definitions", policy(&root)).unwrap();
store.insert(CollectionRecord::Commit(record)).unwrap();
let control = store.snapshot().unwrap();
let residency = store.snapshot().unwrap();
store.put::<UnknownBlob, _>(descriptor).unwrap();
let changed = store.snapshot().unwrap();
assert_eq!(changed.changes_since(&control), StoreChanges::BLOBS);
assert!(!std::sync::Arc::ptr_eq(&control.memo, &changed.memo));
assert!(!control.contains_blob(collection.handle()).unwrap());
assert!(changed.contains_blob(collection.handle()).unwrap());
let lineage = BTreeSet::from([collection.handle()]);
assert_eq!(changed.index(&lineage).unwrap().published().len(), 1);
let old = control.index(&lineage).unwrap();
assert!(old.published().is_empty());
assert_eq!(old.parked_on_lineages(), 1);
assert_eq!(residency.index(&lineage).unwrap(), old);
}
#[test]
fn unchanged_pile_snapshots_share_late_control_warming() {
let dir = tempfile::tempdir().unwrap();
let root = SigningKey::from_bytes(&[73; 32]);
let path = dir.path().join("memo.pile");
std::fs::File::create(&path).unwrap();
let mut store = crate::repo::pile::Pile::open(&path).unwrap();
let collection: Collection<SimpleArchive> =
store.collection("pile control warming", policy(&root)).unwrap();
store
.commit(collection, &root, entity! { crate::metadata::name: "one" })
.unwrap();
let control = store.snapshot().unwrap();
let residency = store.snapshot().unwrap();
assert_eq!(residency.changes_since(&control), StoreChanges::NONE);
let lineage = BTreeSet::from([collection.handle()]);
let warmed = control.index(&lineage).unwrap();
assert_eq!(warmed.published().len(), 1);
assert!(residency.memo.lock().unwrap().settled.contains(&collection.handle()));
let next = store.snapshot().unwrap();
assert!(next.memo.lock().unwrap().settled.contains(&collection.handle()));
assert_eq!(next.index(&lineage).unwrap(), warmed);
}
#[test]
fn the_index_fed_by_difference_equals_the_one_fed_by_writes() {
let root = SigningKey::from_bytes(&[71; 32]);
let writer = SigningKey::from_bytes(&[72; 32]);
let mut store = Covered::new(MemoryRepo::default());
let empty = agree(&store.snapshot().unwrap(), "nothing");
assert!(empty.is_empty());
let collection: Collection<SimpleArchive> =
store.collection("equivalence", policy(&root)).unwrap();
let child = store.put::<UTF8String, _>("child".to_owned()).unwrap();
store
.commit(collection, &root, entity! { crate::metadata::name: child })
.unwrap();
let admitted = agree(&store.snapshot().unwrap(), "an admitted commit");
assert_eq!(admitted.published().len(), 1);
let other = store.put::<UTF8String, _>("other".to_owned()).unwrap();
store
.commit(collection, &writer, entity! { crate::metadata::name: other })
.unwrap();
let parked = agree(&store.snapshot().unwrap(), "a commit by an unadmitted writer");
assert_eq!(parked.parked_on_signers(), 1);
assert_eq!(parked.published().len(), 1);
store
.insert_proof(CapabilityProof::new(
CapabilityResource::from(collection.handle()),
&root,
write_capability(),
writer.verifying_key(),
))
.unwrap();
let proven = agree(&store.snapshot().unwrap(), "the proof that admits the writer");
assert_eq!(proven.parked_on_signers(), 0);
assert_eq!(proven.published().len(), 2);
let mut elsewhere = MemoryRepo::default();
let later: Collection<SimpleArchive> = elsewhere.collection("later", policy(&root)).unwrap();
let payload = elsewhere.put::<UTF8String, _>("later".to_owned()).unwrap();
let ahead = elsewhere
.commit(later, &root, entity! { crate::metadata::name: payload })
.unwrap();
let descriptor: Blob<UnknownBlob> = elsewhere
.snapshot()
.unwrap()
.get(later.handle().transmute())
.unwrap();
store.insert(CollectionRecord::Commit(ahead)).unwrap();
let waiting = agree(&store.snapshot().unwrap(), "a record ahead of its descriptor");
assert_eq!(waiting.parked_on_lineages(), 1);
assert_eq!(waiting.parked_on_signers(), 0);
assert_eq!(waiting.published().len(), 2);
store.put::<UnknownBlob, _>(descriptor).unwrap();
let landed = agree(&store.snapshot().unwrap(), "the descriptor it was waiting on");
assert_eq!(landed.parked(), 0);
assert_eq!(landed.published().len(), 3);
let quiet = agree(&store.snapshot().unwrap(), "no change at all");
assert_eq!(quiet, landed);
}
}