triblespace-core 0.47.1

The triblespace core implementation.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
//! The collections derived from a source or attached to it, found from
//! their descriptors, and kept up as a group.
//!
//! An attached collection names its parent instead of a source; every write's
//! [`ensure_downstream`] attaches the parent's current frontier into it, and
//! [`maintain_downstream`] carries the parent first and attaches what the
//! carry left. The prose below is about derived collections.
//!
//! Every derived collection's descriptor names its immediate source, and every
//! store can list the collections its records name. So "what derives from
//! `X`" needs no list kept per faculty: one pass over the store's collections,
//! one small blob read each, and the chain comes out in dependency order --
//! source, its images, their images. A derived collection joins that list
//! with its first record: a store lists the collections its records name,
//! and a descriptor alone is a blob like any other. So whoever registers a
//! derived collection realizes it once, then, when the source has something
//! to image; from then on every write's [`ensure_downstream`] finds it.
//!
//! Upkeep on a write is "derive what you wrote". [`ensure_downstream`] gives
//! every derived collection a leaf for each source foundation the signer
//! owns and the collection has none for yet, and publishes no merge; a
//! write that calls it after its commit leaves the commit readable through
//! every view, at the cost of one locator per own foundation and that
//! commit's own images. [`maintain_downstream`] also derives every
//! foundation left without a usable leaf, whoever owns it, and carries each
//! derived collection's own lattice of leaf images into the signer's merges,
//! as the daemon does; nothing reads a source's merges. Both visit a
//! collection after its source, so a leaf image of the source is here
//! before the collection above it maps it. Neither decides what is cheap:
//! the first call on a cold store may take as long as the backlog is, and
//! says so by taking it. A representation this binary cannot map, or a
//! descriptor naming a mapping other than the representation's canonical
//! one, is named in the report rather than guessed at; a signer the target's
//! policy does not admit is named too, after its collection is carried.

use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::future::Future;

use ed25519_dalek::SigningKey;

use crate::blob::encodings::entity_id_set::EntityIdSetBlob;
use crate::blob::encodings::succinctarchive::{
    Rank9AcceleratedSuccinctArchiveBlob, SuccinctArchiveBlob,
};
use crate::id::Id;
use crate::inline::encodings::hash::Handle;
use crate::inline::InlineEncoding;
use crate::metadata::MetaDescribe;
use crate::repo::async_store::AsyncBlobStoreAcquire;
use crate::repo::{Store, StoreRead};
use crate::trible::TribleSet;

use super::api::{load_collection_descriptor, CollectionStoreExt};
use super::encoding::{CollectionAttachment, CollectionDerivation};
use super::exact_derived::CollectionRealizationError;
use super::latest::LatestBlob;
use super::lww_register::LwwRegisterBlob;
use super::Collection;
use super::{descriptor, CollectionHandle};

/// One collection derived, directly or through other derived collections,
/// from the source asked about.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Derived {
    /// The derived collection.
    pub handle: CollectionHandle,
    /// Its immediate source, which is the source asked about or another
    /// entry earlier in the same list.
    pub source: CollectionHandle,
    /// The blob representation its descriptor names.
    pub representation: Id,
    /// The mapping algorithm its descriptor names, if any.
    pub algorithm: Option<Id>,
}

/// Every collection derived from `source`, transitively, each one after the
/// collection it derives from.
///
/// Read from the store's collection listing and each candidate's descriptor;
/// no record is enumerated. A collection whose descriptor is not resident or
/// does not decode is left out: nothing here could maintain it, and its
/// own source is unknown.
pub fn derived_from<S>(
    snapshot: &S,
    source: CollectionHandle,
) -> Result<Vec<Derived>, S::RecordsError>
where
    S: StoreRead,
{
    let mut by_source: BTreeMap<CollectionHandle, Vec<Derived>> = BTreeMap::new();
    for collection in snapshot.collections()? {
        if collection == source {
            continue;
        }
        let Ok(facts) = snapshot.get::<TribleSet, _>(collection) else {
            continue;
        };
        let (Ok(Some(parent)), Ok(representation), Ok(algorithm)) = (
            descriptor::source(&facts),
            descriptor::representation(&facts),
            descriptor::mapping_algorithm(&facts),
        ) else {
            continue;
        };
        by_source.entry(parent).or_default().push(Derived {
            handle: collection,
            source: parent,
            representation,
            algorithm,
        });
    }
    let mut ordered = Vec::new();
    let mut seen = BTreeSet::from([source]);
    let mut queue = VecDeque::from([source]);
    while let Some(parent) = queue.pop_front() {
        let Some(children) = by_source.get(&parent) else {
            continue;
        };
        for child in children {
            if seen.insert(child.handle) {
                ordered.push(*child);
                queue.push_back(child.handle);
            }
        }
    }
    Ok(ordered)
}

/// One collection attached to the parent asked about.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Attached {
    /// The attached collection.
    pub handle: CollectionHandle,
    /// The collection whose nodes it indexes.
    pub parent: CollectionHandle,
    /// The blob representation its descriptor names.
    pub representation: Id,
    /// The mapping algorithm its descriptor names, if any.
    pub algorithm: Option<Id>,
    /// The sibling attached collections its mapping reads, each earlier in
    /// the same list when it is attached to the same parent.
    pub siblings: Vec<CollectionHandle>,
}

/// Every collection attached to `parent` alone, each after the siblings its
/// mapping reads.
///
/// Read from the store's collection listing and each candidate's
/// descriptor, like [`derived_from`]; an attached collection is listed once
/// a record names it. A descriptor naming a second parent is not listed:
/// an attached read walks one parent's lattice, so nothing here serves it.
/// A sibling cycle, which no mapping should name, is broken by listing what
/// remains in handle order.
pub fn attached_to<S>(
    snapshot: &S,
    parent: CollectionHandle,
) -> Result<Vec<Attached>, S::RecordsError>
where
    S: StoreRead,
{
    let mut pending: BTreeMap<CollectionHandle, Attached> = BTreeMap::new();
    for collection in snapshot.collections()? {
        let Ok(facts) = snapshot.get::<TribleSet, _>(collection) else {
            continue;
        };
        let (Ok(named), Ok(representation), Ok(algorithm), Ok(siblings)) = (
            descriptor::parents(&facts),
            descriptor::representation(&facts),
            descriptor::mapping_algorithm(&facts),
            descriptor::reads_attached(&facts),
        ) else {
            continue;
        };
        if named.as_slice() != [parent] {
            continue;
        }
        pending.insert(
            collection,
            Attached {
                handle: collection,
                parent,
                representation,
                algorithm,
                siblings,
            },
        );
    }
    let mut ordered = Vec::new();
    while !pending.is_empty() {
        let ready: Vec<CollectionHandle> = pending
            .values()
            .filter(|attached| {
                attached
                    .siblings
                    .iter()
                    .all(|sibling| !pending.contains_key(sibling))
            })
            .map(|attached| attached.handle)
            .collect();
        let next = if ready.is_empty() {
            vec![*pending.keys().next().expect("pending is not empty")]
        } else {
            ready
        };
        for handle in next {
            ordered.push(
                pending
                    .remove(&handle)
                    .expect("a pending attached collection"),
            );
        }
    }
    Ok(ordered)
}

/// How far to take a derived or attached collection.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Upkeep {
    /// Derive the signer's own missing leaves, or attach the parent's
    /// current frontier; publish no merge.
    Ensure,
    /// Derive every foundation left without a usable leaf and carry the
    /// derived collection's own lattice; for an attached collection, carry
    /// its parent first and attach the frontier after.
    Maintain,
}

/// What one realizer did with one derived or attached collection.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Realized {
    /// The target was taken as far as asked.
    Done,
    /// The signer may not write the target, so no leaf was derived. Under
    /// [`Upkeep::Maintain`] the target's own lattice was still carried: a
    /// merge needs no WRITE.
    Unadmitted,
    /// This realizer knows neither the target's representation nor the
    /// mapping its descriptor names, so nothing was published.
    Unknown,
}

/// Takes one derived or attached collection as far as asked, for the
/// representations it knows. [`CoreRealizer`] knows this crate's; a binary
/// that carries more encodings wraps it and answers for those first.
pub trait RealizeDerived<S>
where
    S: Store + AsyncBlobStoreAcquire + Send,
{
    /// Realize `derived` under `upkeep`, signing what is published with
    /// `signer`, or say why nothing was.
    fn realize(
        &mut self,
        store: &mut S,
        derived: &Derived,
        signer: &SigningKey,
        upkeep: Upkeep,
    ) -> impl Future<Output = Result<Realized, CollectionRealizationError>> + Send;

    /// Realize one attached collection under `upkeep`, signing its MAPs with
    /// `signer`, which must be the store's host, or say that this realizer
    /// does not know it.
    fn realize_attached(
        &mut self,
        store: &mut S,
        attached: &Attached,
        signer: &SigningKey,
        upkeep: Upkeep,
    ) -> impl Future<Output = Result<Realized, CollectionRealizationError>> + Send;
}

/// Take one collection of a known encoding as far as asked through its
/// canonical mapping, or say why it was not: the descriptor names another
/// mapping than `T`'s canonical one, or the signer may not write it.
///
/// A descriptor registered through an explicit mapping value carries that
/// mapping's algorithm, and `T`'s canonical mapping refuses to bind to it.
/// That refusal is the answer, not an error: whoever registered the mapping
/// realizes it, and a realizer that knows the mapping answers for it first.
///
/// Only a new leaf needs WRITE. A signer the target does not admit derives
/// nothing and answers [`Realized::Unadmitted`]; under
/// [`Upkeep::Maintain`] it still carries the target's own lattice, because
/// a merge needs only the store's host key.
pub async fn realize_as<S, T>(
    store: &mut S,
    derived: &Derived,
    signer: &SigningKey,
    upkeep: Upkeep,
) -> Result<Realized, CollectionRealizationError>
where
    S: Store + AsyncBlobStoreAcquire + Send,
    T: CollectionDerivation + MetaDescribe,
    Handle<T>: InlineEncoding,
{
    let snapshot = store.snapshot().map_err(|error| {
        CollectionRealizationError::storage("observe derived collection", error)
    })?;
    let collection: Collection<T> = Collection::open(&snapshot, derived.handle)
        .map_err(|error| CollectionRealizationError::storage("open derived descriptor", error))?;
    let target = load_collection_descriptor(&snapshot, derived.handle)
        .map_err(|error| CollectionRealizationError::storage("read derived descriptor", error))?;
    let source = load_collection_descriptor(&snapshot, derived.source)
        .map_err(|error| CollectionRealizationError::storage("read source descriptor", error))?;
    if T::bind(&source.fragment, &target.fragment).is_err() {
        return Ok(Realized::Unknown);
    }
    let admitted = collection
        .writer_is_admitted(&snapshot, signer.verifying_key())
        .map_err(|error| CollectionRealizationError::storage("admit derived writer", error))?;
    drop(snapshot);
    match (upkeep, admitted) {
        (Upkeep::Ensure, false) => return Ok(Realized::Unadmitted),
        (Upkeep::Ensure, true) => drop(store.ensure(collection, signer).await?),
        // A key the target does not admit derives nothing and carries.
        (Upkeep::Maintain, _) => drop(store.maintain(collection, signer).await?),
    }
    Ok(if admitted {
        Realized::Done
    } else {
        Realized::Unadmitted
    })
}

/// Take one attached collection of a known encoding as far as asked through
/// its canonical mapping, or answer [`Realized::Unknown`] when its
/// descriptor names another mapping. There is no WRITE check: an attached
/// collection has no policy, and its MAPs are believed on the host's key.
pub async fn realize_attached_as<S, T>(
    store: &mut S,
    attached: &Attached,
    signer: &SigningKey,
    upkeep: Upkeep,
) -> Result<Realized, CollectionRealizationError>
where
    S: Store + AsyncBlobStoreAcquire + Send,
    T: CollectionAttachment + MetaDescribe,
    Handle<T>: InlineEncoding,
{
    let snapshot = store.snapshot().map_err(|error| {
        CollectionRealizationError::storage("observe attached collection", error)
    })?;
    let collection: Collection<T> = Collection::open(&snapshot, attached.handle)
        .map_err(|error| CollectionRealizationError::storage("open attached descriptor", error))?;
    let descriptor = load_collection_descriptor(&snapshot, attached.handle)
        .map_err(|error| CollectionRealizationError::storage("read attached descriptor", error))?;
    let parent = load_collection_descriptor(&snapshot, attached.parent)
        .map_err(|error| CollectionRealizationError::storage("read parent descriptor", error))?;
    drop(snapshot);
    if T::bind(&parent.fragment, &descriptor.fragment).is_err() {
        return Ok(Realized::Unknown);
    }
    match upkeep {
        Upkeep::Ensure => drop(store.ensure_attached(collection, signer).await?),
        Upkeep::Maintain => drop(store.maintain_attached(collection, signer).await?),
    }
    Ok(Realized::Done)
}

/// The realizer for this crate's own encodings. It knows no derived
/// collection: every index this crate defines -- Succinct and Rank9
/// archives, entity-id sets, latest indexes and last-writer-wins registers --
/// is attached to its parent instead.
#[derive(Clone, Copy, Debug, Default)]
pub struct CoreRealizer;

impl<S> RealizeDerived<S> for CoreRealizer
where
    S: Store + AsyncBlobStoreAcquire + Send,
{
    async fn realize(
        &mut self,
        _store: &mut S,
        _derived: &Derived,
        _signer: &SigningKey,
        _upkeep: Upkeep,
    ) -> Result<Realized, CollectionRealizationError> {
        Ok(Realized::Unknown)
    }

    async fn realize_attached(
        &mut self,
        store: &mut S,
        attached: &Attached,
        signer: &SigningKey,
        upkeep: Upkeep,
    ) -> Result<Realized, CollectionRealizationError> {
        let representation = attached.representation;
        if representation == <SuccinctArchiveBlob as MetaDescribe>::id() {
            realize_attached_as::<S, SuccinctArchiveBlob>(store, attached, signer, upkeep).await
        } else if representation == <Rank9AcceleratedSuccinctArchiveBlob as MetaDescribe>::id() {
            realize_attached_as::<S, Rank9AcceleratedSuccinctArchiveBlob>(
                store, attached, signer, upkeep,
            )
            .await
        } else if representation == <EntityIdSetBlob as MetaDescribe>::id() {
            realize_attached_as::<S, EntityIdSetBlob>(store, attached, signer, upkeep).await
        } else if representation == <LatestBlob as MetaDescribe>::id() {
            realize_attached_as::<S, LatestBlob>(store, attached, signer, upkeep).await
        } else if representation == <LwwRegisterBlob as MetaDescribe>::id() {
            realize_attached_as::<S, LwwRegisterBlob>(store, attached, signer, upkeep).await
        } else {
            Ok(Realized::Unknown)
        }
    }
}

/// What a pass over a source's derived and attached collections did.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct UpkeepReport {
    /// Taken as far as asked, in the order they were visited: derived
    /// collections first, then attached ones.
    pub realized: Vec<CollectionHandle>,
    /// Derived collections given no leaf because the signer may not write
    /// them; under maintenance each was still carried.
    pub unadmitted: Vec<CollectionHandle>,
    /// Derived collections left alone because no realizer knows their
    /// representation or the mapping their descriptor names; each carries
    /// both.
    pub unknown: Vec<Derived>,
    /// Attached collections left alone for the same reason.
    pub unknown_attached: Vec<Attached>,
    /// Derived collections whose upkeep failed, each with why; the pass went
    /// on to the rest. What one leaves underived or uncarried is its lag,
    /// and what it did publish stands.
    pub failed: Vec<(Derived, String)>,
    /// Attached collections whose upkeep failed, each with why; the pass
    /// went on to the rest. What one leaves unattached is its readers'
    /// residual, read from its bytes or named unread.
    pub failed_attached: Vec<(Attached, String)>,
}

/// Visit every collection derived from `source`, each after its own source,
/// then every collection attached to `source`, each after the siblings it
/// reads, and take each as far as `upkeep` asks with `realizer`.
///
/// One derived or attached collection failing is that collection's lag, not
/// the pass's: it is named in [`UpkeepReport::failed`] or
/// [`UpkeepReport::failed_attached`], and the rest are still taken up, a
/// collection derived from a failed one too, from what the failed one does
/// hold. Only a signer that is not the store's host stops the pass, because
/// no collection believes its MERGEs or MAPs.
pub async fn upkeep_downstream<S, R>(
    store: &mut S,
    source: CollectionHandle,
    signer: &SigningKey,
    upkeep: Upkeep,
    realizer: &mut R,
) -> Result<UpkeepReport, CollectionRealizationError>
where
    S: Store + AsyncBlobStoreAcquire + Send,
    R: RealizeDerived<S>,
{
    let snapshot = store.snapshot().map_err(|error| {
        CollectionRealizationError::storage("observe derived collections", error)
    })?;
    let derived = derived_from(&snapshot, source)
        .map_err(|error| CollectionRealizationError::storage("list derived collections", error))?;
    let attached = attached_to(&snapshot, source)
        .map_err(|error| CollectionRealizationError::storage("list attached collections", error))?;
    drop(snapshot);
    let mut report = UpkeepReport::default();
    for target in derived {
        match realizer.realize(store, &target, signer, upkeep).await {
            Ok(Realized::Done) => report.realized.push(target.handle),
            Ok(Realized::Unadmitted) => report.unadmitted.push(target.handle),
            Ok(Realized::Unknown) => report.unknown.push(target),
            Err(error @ CollectionRealizationError::HostMismatch { .. }) => return Err(error),
            Err(error) => report.failed.push((target, error.to_string())),
        }
    }
    for target in attached {
        match realizer
            .realize_attached(store, &target, signer, upkeep)
            .await
        {
            Ok(Realized::Done) => report.realized.push(target.handle),
            Ok(Realized::Unadmitted) => report.unadmitted.push(target.handle),
            Ok(Realized::Unknown) => report.unknown_attached.push(target),
            Err(error @ CollectionRealizationError::HostMismatch { .. }) => return Err(error),
            Err(error) => report.failed_attached.push((target, error.to_string())),
        }
    }
    Ok(report)
}

/// Derive the signer's own missing leaves into every collection derived from
/// `source`, each after its own source, and attach `source`'s current
/// frontier into every collection attached to it, so that what the signer
/// wrote is readable through each of them; publish no merge. A write calls
/// this after its commit.
pub async fn ensure_downstream<S, R>(
    store: &mut S,
    source: CollectionHandle,
    signer: &SigningKey,
    realizer: &mut R,
) -> Result<UpkeepReport, CollectionRealizationError>
where
    S: Store + AsyncBlobStoreAcquire + Send,
    R: RealizeDerived<S>,
{
    upkeep_downstream(store, source, signer, Upkeep::Ensure, realizer).await
}

/// Derive every foundation left without a usable leaf into every collection
/// derived from `source` and carry each one's own lattice, each after its
/// own source; then carry `source` and attach its new frontier into every
/// collection attached to it. What the daemon does for its configured
/// sources.
pub async fn maintain_downstream<S, R>(
    store: &mut S,
    source: CollectionHandle,
    signer: &SigningKey,
    realizer: &mut R,
) -> Result<UpkeepReport, CollectionRealizationError>
where
    S: Store + AsyncBlobStoreAcquire + Send,
    R: RealizeDerived<S>,
{
    upkeep_downstream(store, source, signer, Upkeep::Maintain, realizer).await
}