Skip to main content

aranya_runtime/storage/
mod.rs

1//! Interfaces for graph storage.
2//!
3//! The [`StorageProvider`] and [`Storage`] interfaces enable high-level
4//! actions on the graph. Traversing the graph is made simpler by splitting
5//! its [`Command`]s into [`Segment`]s. Updating the graph is possible using
6//! [`Perspective`]s, which represent a slice of state.
7
8use alloc::{boxed::Box, string::String, vec::Vec};
9use core::{borrow::Borrow, fmt, ops::Deref};
10
11use buggy::{Bug, BugExt as _};
12use rend::u64_le;
13
14use crate::{Address, CmdId, Command, PolicyId, Prior};
15
16pub mod linear;
17
18#[cfg(any(feature = "libc", feature = "testing"))]
19mod spill;
20#[cfg(feature = "libc")]
21pub use spill::LibcSpill;
22#[cfg(feature = "testing")]
23pub use spill::MemSpill;
24
25/// Byte-addressable overflow storage for braid and convergence data.
26///
27/// Implemented by [`LibcSpill`] (file-backed) and [`MemSpill`] (in-memory);
28/// each backend has its own constructor signature (paths, etc.), matching
29/// how [`IoManager`](linear::io::IoManager) backends are constructed.
30/// Callers build a spill and pass it in.
31pub trait Spill {
32    /// Write `data` at the given byte offset.
33    fn write_at(&mut self, offset: usize, data: &[u8]) -> Result<(), StorageError>;
34    /// Read exactly `data.len()` bytes starting at the given byte offset.
35    fn read_at(&mut self, offset: usize, data: &mut [u8]) -> Result<(), StorageError>;
36}
37
38/// Default capacity for the traversal queue.
39///
40/// This should be large enough to hold the maximum expected "active frontier"
41/// during backward traversal, which is bounded by peer count.
42pub const QUEUE_CAPACITY: usize = 512;
43
44/// Type for the queue used in traversal operations.
45///
46/// Locations with the highest `max_cut` are processed first. This bounds the
47/// queue size to the graph width at any given `max_cut` level, rather than
48/// accumulating entries across many levels as a FIFO would.
49///
50/// Entries are partitioned into uncovered (`entries[0..partition]`) and
51/// covered (`entries[partition..len]`). See [`push_covered`](Self::push_covered)
52/// for the rules governing partition transitions.
53#[derive(Debug, Default)]
54pub struct TraversalQueue {
55    entries: heapless::Vec<Location, QUEUE_CAPACITY>,
56    /// Index separating uncovered (below) from covered (at and above).
57    partition: usize,
58}
59
60impl TraversalQueue {
61    /// Create an empty traversal queue.
62    pub const fn new() -> Self {
63        Self {
64            entries: heapless::Vec::new(),
65            partition: 0,
66        }
67    }
68
69    /// Clear the traversal queue.
70    pub fn clear(&mut self) {
71        self.entries.clear();
72        self.partition = 0;
73    }
74
75    /// Returns true if no entries.
76    pub fn is_empty(&self) -> bool {
77        self.entries.is_empty()
78    }
79
80    /// Enqueues a location as uncovered.
81    ///
82    /// If an entry with the same segment exists, its max cut will be updated
83    /// to the max of the two.
84    pub fn push(&mut self, loc: Location) -> Result<(), StorageError> {
85        self.push_covered(loc, false)
86    }
87
88    /// Enqueues a location with the given covered flag.
89    ///
90    /// If an entry with the same segment already exists, max cut is updated
91    /// to the max. When a higher max_cut changes the head, the new push's
92    /// covered status is adopted (old coverage was below the new head).
93    /// At the same max_cut, covered flags are OR'd. Lower max_cut is ignored.
94    pub fn push_covered(&mut self, loc: Location, covered: bool) -> Result<(), StorageError> {
95        if let Some(i) = self.entries.iter().position(|x| x.same_segment(loc)) {
96            let was_covered = i >= self.partition;
97            let new_covered = if loc.max_cut > self.entries[i].max_cut {
98                self.entries[i].max_cut = loc.max_cut;
99                covered
100            } else if loc.max_cut == self.entries[i].max_cut {
101                was_covered || covered
102            } else {
103                return Ok(());
104            };
105            if !was_covered && new_covered {
106                self.partition = self
107                    .partition
108                    .checked_sub(1)
109                    .assume("partition must be >= 1 when uncovered entry exists")?;
110                self.entries.swap(i, self.partition);
111            } else if was_covered && !new_covered {
112                self.entries.swap(i, self.partition);
113                self.partition = self
114                    .partition
115                    .checked_add(1)
116                    .assume("partition must not overflow")?;
117            }
118            return Ok(());
119        }
120        self.entries
121            .push(loc)
122            .map_err(|_| StorageError::TraversalQueueOverflow(QUEUE_CAPACITY))?;
123        if !covered {
124            let last = self
125                .entries
126                .len()
127                .checked_sub(1)
128                .assume("just pushed, len must be >= 1")?;
129            self.entries.swap(self.partition, last);
130            self.partition = self
131                .partition
132                .checked_add(1)
133                .assume("partition must not overflow")?;
134        }
135        Ok(())
136    }
137
138    /// Enqueues a location without deduplication.
139    ///
140    /// Unlike [`Self::push`], each call adds a new entry even if the location
141    /// is already present. Used by the convergence pre-pass where
142    /// duplicate tracking is needed.
143    pub fn push_duplicate(&mut self, loc: Location) -> Result<(), StorageError> {
144        self.entries
145            .push(loc)
146            .map_err(|_| StorageError::TraversalQueueOverflow(QUEUE_CAPACITY))?;
147        // All duplicate entries are uncovered.
148        let last = self
149            .entries
150            .len()
151            .checked_sub(1)
152            .assume("just pushed, len must be >= 1")?;
153        self.entries.swap(self.partition, last);
154        self.partition = self
155            .partition
156            .checked_add(1)
157            .assume("partition must not overflow")?;
158        Ok(())
159    }
160
161    /// Pop the location with the highest max cut, discarding the covered flag.
162    pub fn pop(&mut self) -> Result<Option<Location>, StorageError> {
163        Ok(self.pop_covered()?.map(|(loc, _)| loc))
164    }
165
166    /// Pop the location with the highest max cut, including its covered flag.
167    pub fn pop_covered(&mut self) -> Result<Option<(Location, bool)>, StorageError> {
168        let Some((i, _)) = self.entries.iter().enumerate().max_by_key(|&(_, loc)| *loc) else {
169            return Ok(None);
170        };
171        if i < self.partition {
172            Ok(Some((self.remove_uncovered(i)?, false)))
173        } else {
174            // Removing from covered region: swap_remove is fine.
175            let loc = self.entries.swap_remove(i);
176            Ok(Some((loc, true)))
177        }
178    }
179
180    /// Remove an entry from the uncovered region at index `i`,
181    /// maintaining the partition invariant.
182    fn remove_uncovered(&mut self, i: usize) -> Result<Location, StorageError> {
183        self.partition = self
184            .partition
185            .checked_sub(1)
186            .assume("partition must be >= 1 when uncovered entry exists")?;
187        self.entries.swap(i, self.partition);
188        Ok(self.entries.swap_remove(self.partition))
189    }
190
191    /// Returns the entry with the highest `max_cut` without removing it.
192    pub fn peek(&self) -> Option<&Location> {
193        self.entries.iter().max_by_key(|loc| *loc)
194    }
195
196    /// Pop the entry with the highest `max_cut`, removing all entries
197    /// at that exact location. Returns `(location, count)`.
198    ///
199    /// Used by the convergence pre-pass. Entries are matched by full
200    /// `Location` equality (segment + max_cut), not just max_cut.
201    pub fn pop_duplicates(&mut self) -> Result<Option<(Location, usize)>, StorageError> {
202        let Some(location) = self.entries.iter().max_by_key(|loc| *loc).copied() else {
203            return Ok(None);
204        };
205
206        // Remove all entries matching this location.
207        // Count them as we go. Iterate backward to avoid index shifts.
208        let mut count: usize = 0;
209        let mut j = self.entries.len();
210        while j > 0 {
211            j = j.checked_sub(1).assume("j > 0 checked in loop condition")?;
212            if self.entries[j] == location {
213                count = count
214                    .checked_add(1)
215                    .assume("count bounded by QUEUE_CAPACITY")?;
216                if j < self.partition {
217                    self.partition = self
218                        .partition
219                        .checked_sub(1)
220                        .assume("partition >= 1 when uncovered entry at j < partition")?;
221                    self.entries.swap(j, self.partition);
222                    self.entries.swap_remove(self.partition);
223                } else {
224                    self.entries.swap_remove(j);
225                }
226            }
227        }
228
229        Ok(Some((location, count)))
230    }
231
232    /// Returns true if all entries are covered (uncovered partition is empty).
233    pub fn all_covered(&self) -> bool {
234        self.partition == 0
235    }
236
237    /// Remove all entries with `max_cut > threshold` from the queue.
238    ///
239    /// Uncovered entries are passed to `f`. Covered entries are discarded
240    /// (the peer already has them).
241    pub fn drain_above(
242        &mut self,
243        threshold: MaxCut,
244        mut f: impl FnMut(Location),
245    ) -> Result<(), StorageError> {
246        // Drain from uncovered region.
247        let mut i = 0;
248        while i < self.partition {
249            if self.entries[i].max_cut > threshold {
250                f(self.remove_uncovered(i)?);
251            } else {
252                i = i.checked_add(1).assume("index must not overflow")?;
253            }
254        }
255        // Discard covered entries above the threshold — the peer
256        // already has these, so they don't belong in the result.
257        let mut i = self.partition;
258        while i < self.entries.len() {
259            if self.entries[i].max_cut > threshold {
260                self.entries.swap_remove(i);
261            } else {
262                i = i.checked_add(1).assume("index must not overflow")?;
263            }
264        }
265        Ok(())
266    }
267
268    /// Mark a segment as covered up to `coverage_mc`. If the segment
269    /// exists in the queue:
270    /// - If `coverage_mc >= longest_mc`: the segment is fully covered.
271    /// - If `coverage_mc >= entry.max_cut`: the entry is updated to
272    ///   `coverage_mc + 1` (still uncovered — the peer needs the rest).
273    /// - If `coverage_mc < entry.max_cut`: no-op (already sending from
274    ///   above the coverage point).
275    pub fn cover_up_to(
276        &mut self,
277        segment: SegmentIndex,
278        coverage_mc: MaxCut,
279        longest_mc: MaxCut,
280    ) -> Result<(), StorageError> {
281        let Some(i) = self.entries.iter().position(|x| x.segment == segment) else {
282            return Ok(());
283        };
284        let was_covered = i >= self.partition;
285        if was_covered {
286            return Ok(());
287        }
288        if coverage_mc >= longest_mc {
289            // Fully covered — move to covered partition.
290            self.partition = self
291                .partition
292                .checked_sub(1)
293                .assume("partition must be >= 1 when uncovered entry exists")?;
294            self.entries.swap(i, self.partition);
295        } else if coverage_mc >= self.entries[i].max_cut {
296            // Partially covered — advance start past the covered portion.
297            self.entries[i].max_cut = coverage_mc
298                .checked_add(1)
299                .assume("coverage_mc + 1 must not overflow")?;
300        }
301        // else: coverage is below our start, nothing to do.
302        Ok(())
303    }
304
305    /// Drain all entries. Uncovered entries are passed to `f`.
306    /// Covered entries are discarded. O(n) single pass.
307    pub fn drain_all(&mut self, mut f: impl FnMut(Location)) {
308        for i in 0..self.partition {
309            f(self.entries[i]);
310        }
311        self.entries.clear();
312        self.partition = 0;
313    }
314}
315
316/// A queue buffer for a single graph traversal operation.
317///
318/// Access via [`get()`](Self::get), which clears the buffer automatically.
319pub struct TraversalBuffer {
320    queue: TraversalQueue,
321}
322
323impl TraversalBuffer {
324    pub const fn new() -> Self {
325        Self {
326            queue: TraversalQueue::new(),
327        }
328    }
329
330    /// Returns a cleared queue ready for use.
331    pub fn get(&mut self) -> &mut TraversalQueue {
332        self.queue.clear();
333        &mut self.queue
334    }
335}
336
337impl Default for TraversalBuffer {
338    fn default() -> Self {
339        Self::new()
340    }
341}
342
343/// Reusable buffers for graph traversal operations.
344///
345/// Contains two independent queue buffers so that an outer traversal
346/// (e.g. `find_needed_segments`) can maintain state in one buffer while
347/// calling leaf operations (e.g. `is_ancestor`) that use the other.
348pub struct TraversalBuffers {
349    pub primary: TraversalBuffer,
350    pub secondary: TraversalBuffer,
351}
352
353impl TraversalBuffers {
354    pub const fn new() -> Self {
355        Self {
356            primary: TraversalBuffer::new(),
357            secondary: TraversalBuffer::new(),
358        }
359    }
360}
361
362impl Default for TraversalBuffers {
363    fn default() -> Self {
364        Self::new()
365    }
366}
367
368#[cfg(feature = "low-mem-usage")]
369pub const MAX_COMMAND_LENGTH: usize = 400;
370#[cfg(not(feature = "low-mem-usage"))]
371pub const MAX_COMMAND_LENGTH: usize = 2048;
372
373aranya_crypto::custom_id! {
374    /// The ID of the graph, taken from initialization.
375    pub struct GraphId;
376}
377
378#[derive(
379    Copy,
380    Clone,
381    Debug,
382    Hash,
383    PartialEq,
384    Eq,
385    PartialOrd,
386    Ord,
387    serde::Serialize,
388    serde::Deserialize,
389    rkyv::Archive,
390    rkyv::Serialize,
391    rkyv::Deserialize,
392    rkyv::Portable,
393    rkyv::bytecheck::CheckBytes,
394    zerocopy::IntoBytes,
395    zerocopy::FromBytes,
396    zerocopy::Immutable,
397    zerocopy::KnownLayout,
398)]
399#[rkyv(as = Self)]
400#[bytecheck(crate = rkyv::bytecheck)]
401#[serde(transparent)]
402#[repr(transparent)]
403pub struct SegmentIndex(#[serde(with = "crate::util::u64_le_serde")] u64_le);
404
405impl fmt::Display for SegmentIndex {
406    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407        fmt::Display::fmt(&self.0, f)
408    }
409}
410
411impl SegmentIndex {
412    pub const fn new(val: u64) -> Self {
413        Self(u64_le::from_native(val))
414    }
415
416    pub const fn get(self) -> u64 {
417        self.0.to_native()
418    }
419}
420
421#[derive(
422    Copy,
423    Clone,
424    Debug,
425    Hash,
426    PartialEq,
427    Eq,
428    PartialOrd,
429    Ord,
430    serde::Serialize,
431    serde::Deserialize,
432    rkyv::Archive,
433    rkyv::Serialize,
434    rkyv::Deserialize,
435    rkyv::Portable,
436    rkyv::bytecheck::CheckBytes,
437    zerocopy::IntoBytes,
438    zerocopy::FromBytes,
439    zerocopy::Immutable,
440    zerocopy::KnownLayout,
441)]
442#[rkyv(as = Self)]
443#[bytecheck(crate = rkyv::bytecheck)]
444#[serde(transparent)]
445#[repr(transparent)]
446pub struct MaxCut(#[serde(with = "crate::util::u64_le_serde")] u64_le);
447
448impl fmt::Display for MaxCut {
449    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450        fmt::Display::fmt(&self.0, f)
451    }
452}
453
454impl MaxCut {
455    pub const fn new(val: u64) -> Self {
456        Self(u64_le::from_native(val))
457    }
458
459    pub const fn get(self) -> u64 {
460        self.0.to_native()
461    }
462
463    /// Adds an amount to the max cut, returning `None` on overflow.
464    #[must_use]
465    pub fn checked_add(self, other: u64) -> Option<Self> {
466        self.get().checked_add(other).map(Self::new)
467    }
468
469    /// Gets a max cut one lower than this, returning `None` on overflow.
470    #[must_use]
471    pub fn decremented(self) -> Option<Self> {
472        self.get().checked_sub(1).map(Self::new)
473    }
474
475    /// Gets the distance between two max cuts, returning `None` on overflow.
476    #[must_use]
477    pub fn distance_from(self, other: Self) -> Option<u64> {
478        self.get().checked_sub(other.get())
479    }
480}
481
482#[derive(
483    Copy,
484    Clone,
485    Debug,
486    Hash,
487    PartialEq,
488    Eq,
489    PartialOrd,
490    Ord,
491    serde::Serialize,
492    serde::Deserialize,
493    rkyv::Archive,
494    rkyv::Serialize,
495    rkyv::Deserialize,
496    rkyv::Portable,
497    rkyv::bytecheck::CheckBytes,
498    zerocopy::IntoBytes,
499    zerocopy::FromBytes,
500    zerocopy::Immutable,
501    zerocopy::KnownLayout,
502)]
503#[rkyv(as = Self)]
504#[bytecheck(crate = rkyv::bytecheck)]
505#[repr(C)]
506pub struct Location {
507    pub max_cut: MaxCut,
508    pub segment: SegmentIndex,
509}
510
511impl From<(SegmentIndex, MaxCut)> for Location {
512    fn from((segment, max_cut): (SegmentIndex, MaxCut)) -> Self {
513        Self::new(segment, max_cut)
514    }
515}
516
517impl AsRef<Self> for Location {
518    fn as_ref(&self) -> &Self {
519        self
520    }
521}
522
523impl Location {
524    pub fn new(segment: SegmentIndex, max_cut: MaxCut) -> Self {
525        Self { max_cut, segment }
526    }
527
528    /// Returns true if other location is in the same segment.
529    pub fn same_segment(self, other: Self) -> bool {
530        self.segment == other.segment
531    }
532}
533
534impl fmt::Display for Location {
535    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536        write!(f, "{}:{}", self.segment, self.max_cut)
537    }
538}
539
540#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
541pub struct LocatedAddress {
542    pub id: CmdId,
543    pub segment: SegmentIndex,
544    pub max_cut: MaxCut,
545}
546
547impl LocatedAddress {
548    pub fn address(self) -> Address {
549        Address {
550            id: self.id,
551            max_cut: self.max_cut,
552        }
553    }
554
555    pub fn location(self) -> Location {
556        Location {
557            segment: self.segment,
558            max_cut: self.max_cut,
559        }
560    }
561}
562
563/// An error returned by [`Storage`] or [`StorageProvider`].
564#[derive(Debug, thiserror::Error)]
565#[cfg_attr(test, derive(PartialEq, Eq))]
566#[non_exhaustive]
567pub enum StorageError {
568    #[error("storage already exists")]
569    StorageExists,
570    #[error("no such storage")]
571    NoSuchStorage,
572    #[error("segment index {} is out of bounds", .0.segment)]
573    SegmentOutOfBounds(Location),
574    #[error("max cut {} is out of bounds in segment {}", .0.max_cut, .0.segment)]
575    CommandOutOfBounds(Location),
576    #[error("IO error")]
577    IoError,
578    #[error("policy mismatch")]
579    PolicyMismatch,
580    #[error("cannot write an empty perspective")]
581    EmptyPerspective,
582    #[error("traversal queue overflow (capacity {0})")]
583    TraversalQueueOverflow(usize),
584    #[error("strand heap overflow (capacity {0})")]
585    StrandHeapOverflow(usize),
586    #[error("convergence root index overflow (capacity {0})")]
587    ConvergenceRootOverflow(usize),
588    #[error("command's parents do not match the perspective head")]
589    PerspectiveHeadMismatch,
590    #[error(transparent)]
591    Bug(#[from] Bug),
592}
593
594/// Handle to storage implementations used by the runtime.
595pub trait StorageProvider {
596    type Perspective: Perspective + Revertable;
597    type Segment: Segment;
598    type Storage: Storage<
599            Segment = Self::Segment,
600            Perspective = Self::Perspective,
601            FactIndex = <Self::Segment as Segment>::FactIndex,
602        >;
603
604    /// Create an unrooted perspective, intended for creating a new graph.
605    ///
606    /// # Arguments
607    ///
608    /// * `policy_id` - The policy to associate with the graph.
609    fn new_perspective(&mut self, policy_id: PolicyId) -> Self::Perspective;
610
611    /// Create a new graph.
612    ///
613    /// # Arguments
614    ///
615    /// * `graph` - ID of the graph, taken from the initialization command.
616    /// * `init` - Contains the data necessary to initialize the new graph.
617    fn new_storage(
618        &mut self,
619        init: Self::Perspective,
620    ) -> Result<(GraphId, &mut Self::Storage), StorageError>;
621
622    /// Get an existing graph.
623    ///
624    /// # Arguments
625    ///
626    /// * `graph` - ID of the graph, taken from the initialization command.
627    fn get_storage(&mut self, graph: GraphId) -> Result<&mut Self::Storage, StorageError>;
628
629    /// Remove a graph.
630    ///
631    /// # Arguments
632    ///
633    /// * `graph` - ID of the graph, taken from the initialization command.
634    fn remove_storage(&mut self, graph: GraphId) -> Result<(), StorageError>;
635
636    /// Gets a list of all stored graphs by their graph ID.
637    // TODO(nikki): rewrite this once we can use coroutines/generators?
638    fn list_graph_ids(
639        &mut self,
640    ) -> Result<impl Iterator<Item = Result<GraphId, StorageError>>, StorageError>;
641}
642
643/// Represents the runtime's graph; [`Command`]s in storage have been validated
644/// by an associated policy and committed to state.
645pub trait Storage {
646    type Perspective: Perspective + Revertable;
647    type FactPerspective: FactPerspective;
648    type Segment: Segment<FactIndex = Self::FactIndex>;
649    type FactIndex: FactIndex;
650
651    /// Returns the location of Command with id if it has been stored by
652    /// searching from the head.
653    fn get_location(
654        &self,
655        address: Address,
656        buffer: &mut TraversalBuffer,
657    ) -> Result<Option<Location>, StorageError> {
658        self.get_location_from(self.get_head()?, address, buffer)
659    }
660
661    /// Returns the location of Command with id by searching from the given location.
662    ///
663    /// See `aranya-docs/docs/graph-traversal.md` for the traversal algorithm specification.
664    fn get_location_from(
665        &self,
666        start: Location,
667        address: Address,
668        buffer: &mut TraversalBuffer,
669    ) -> Result<Option<Location>, StorageError> {
670        if start.max_cut < address.max_cut {
671            return Ok(None);
672        }
673
674        let queue = buffer.get();
675        queue.push(start)?;
676
677        while let Some(loc) = queue.pop()? {
678            debug_assert!(
679                loc.max_cut >= address.max_cut,
680                "Invariant: we only enqueue locations with at least the target max cut"
681            );
682
683            // Must load segment
684            let segment = self.get_segment(loc)?;
685
686            // Search commands in this segment.
687            if let Some(found) = segment.get_by_address(address) {
688                return Ok(Some(found));
689            }
690
691            // Try to use skip list to jump directly backward.
692            // Skip list is sorted by max_cut ascending, so the first entry
693            // with max_cut >= target has the lowest valid max_cut, jumping
694            // furthest back in the graph.
695            if let Some(&skip) = segment
696                .skip_list()
697                .iter()
698                .find(|skip| skip.max_cut >= address.max_cut)
699            {
700                queue.push(skip)?;
701            } else {
702                // No valid skip - add prior locations to queue
703                for prior in segment.prior() {
704                    if prior.max_cut >= address.max_cut {
705                        queue.push(prior)?;
706                    }
707                }
708            }
709        }
710        Ok(None)
711    }
712
713    /// Returns the address of the command at the given location.
714    ///
715    /// By default, this fetches the segment, then the command, then the address.
716    fn get_command_address(&self, location: Location) -> Result<Address, StorageError> {
717        let segment = self.get_segment(location)?;
718        let command = segment
719            .get_command(location)
720            .ok_or(StorageError::CommandOutOfBounds(location))?;
721        let address = command.address()?;
722        Ok(address)
723    }
724
725    /// Returns a linear perspective at the given location.
726    fn get_linear_perspective(&self, parent: Location) -> Result<Self::Perspective, StorageError>;
727
728    /// Returns a fact perspective at the given location, intended for evaluating braids.
729    /// The fact perspective will include the facts of the command at the given location.
730    fn get_fact_perspective(&self, first: Location) -> Result<Self::FactPerspective, StorageError>;
731
732    /// Returns a merge perspective based on the given locations with the braid as prior facts.
733    fn new_merge_perspective(
734        &self,
735        left: Location,
736        right: Location,
737        last_common_ancestor: Location,
738        policy_id: PolicyId,
739        braid: Self::FactIndex,
740    ) -> Result<Self::Perspective, StorageError>;
741
742    /// Returns the segment at the given location.
743    fn get_segment(&self, location: Location) -> Result<Self::Segment, StorageError>;
744
745    /// Returns the location of head of the graph.
746    fn get_head(&self) -> Result<Location, StorageError>;
747
748    /// Returns the address of the head of the graph.
749    fn get_head_address(&self) -> Result<Address, StorageError> {
750        self.get_command_address(self.get_head()?)
751    }
752
753    /// Sets the given segment as the head of the graph.
754    ///
755    /// The given segment must be a descendant of the current graph head.
756    /// Implementations may rely on this for correctness, but not for safety.
757    fn commit(&mut self, segment: Self::Segment) -> Result<(), StorageError>;
758
759    /// Writes the given perspective to a segment.
760    fn write(&mut self, perspective: Self::Perspective) -> Result<Self::Segment, StorageError>;
761
762    /// Writes the given fact perspective to a fact index.
763    fn write_facts(
764        &mut self,
765        fact_perspective: Self::FactPerspective,
766    ) -> Result<Self::FactIndex, StorageError>;
767
768    /// Determine whether the given location is an ancestor of the given location.
769    fn is_ancestor(
770        &self,
771        search_location: Location,
772        start_location: Location,
773        buffer: &mut TraversalBuffer,
774    ) -> Result<bool, StorageError> {
775        if search_location.max_cut > start_location.max_cut || search_location == start_location {
776            return Ok(false);
777        }
778
779        let queue = buffer.get();
780        queue.push(start_location)?;
781
782        while let Some(loc) = queue.pop()? {
783            debug_assert!(
784                loc.max_cut >= search_location.max_cut,
785                "Invariant: we only enqueue locations with at least the target max cut"
786            );
787
788            // Must load segment
789            let segment = self.get_segment(loc)?;
790
791            // Search commands in this segment.
792            if segment.get_command(search_location).is_some() {
793                return Ok(true);
794            }
795
796            // Try to use skip list to jump directly backward.
797            // Skip list is sorted by max_cut ascending, so the first entry
798            // with max_cut >= target has the lowest valid max_cut, jumping
799            // furthest back in the graph.
800            if let Some(&skip) = segment
801                .skip_list()
802                .iter()
803                .find(|skip| skip.max_cut >= search_location.max_cut)
804            {
805                queue.push(skip)?;
806            } else {
807                // No valid skip - add prior locations to queue
808                for prior in segment.prior() {
809                    if prior.max_cut >= search_location.max_cut {
810                        queue.push(prior)?;
811                    }
812                }
813            }
814        }
815        Ok(false)
816    }
817}
818
819/// A segment is a nonempty sequence of commands persisted to storage.
820///
821/// A segment can be one of three types. This might be encoded in a future version of the API.
822/// * init   - This segment is the first segment of the graph and begins with an init command.
823/// * linear - This segment has a single prior command and is simply a sequence of linear commands.
824/// * merge  - This segment merges two other segments and thus begins with a merge command. A merge
825///   segment has a braid as it's prior facts.
826///
827/// Each command past the first must have the parent of the previous command in the segment.
828pub trait Segment {
829    type FactIndex: FactIndex;
830    type Command<'a>: Command
831    where
832        Self: 'a;
833
834    /// Returns the segment's index.
835    fn index(&self) -> SegmentIndex;
836
837    /// Returns the ID of the head of the segment.
838    fn head_id(&self) -> CmdId;
839
840    /// Returns the id for the policy used for this segment.
841    fn policy(&self) -> PolicyId;
842
843    /// Returns the prior segments for this segment.
844    fn prior(&self) -> Prior<Location>;
845
846    /// Returns the command at the given location.
847    fn get_command(&self, location: Location) -> Option<Self::Command<'_>>;
848
849    /// Get the fact index associated with this segment.
850    fn facts(&self) -> Result<Self::FactIndex, StorageError>;
851
852    /// The shortest max cut for this segment.
853    ///
854    /// This will always the max cut of the first command in the segment.
855    fn shortest_max_cut(&self) -> MaxCut;
856
857    /// The longest max cut for this segment.
858    ///
859    /// This will always be the max cut of the last command in the segment.
860    fn longest_max_cut(&self) -> Result<MaxCut, StorageError>;
861
862    /// The skip list is a series of locations that can be safely jumped to
863    /// when searching for a location. As long as the max cut of the location
864    /// you're jumping to is greater than or equal to the location you're
865    /// searching for you can jump to it and be guaranteed not to miss
866    /// the location you're searching for.
867    ///
868    /// For merge commands the last location in the skip list is the least
869    /// common ancestor.
870    fn skip_list(&self) -> &[Location];
871
872    /// Returns an iterator of commands starting at the given location.
873    fn get_from(&self, location: Location) -> Vec<Self::Command<'_>> {
874        let segment = location.segment;
875        core::iter::successors(Some(location.max_cut), |max_cut| max_cut.checked_add(1))
876            .map_while(|max_cut| self.get_command(Location { max_cut, segment }))
877            .collect()
878    }
879
880    /// Returns the location of the command with the given address from within this segment.
881    fn get_by_address(&self, address: Address) -> Option<Location> {
882        let loc = Location::new(self.index(), address.max_cut);
883        let cmd = self.get_command(loc)?;
884        if cmd.id() != address.id {
885            return None;
886        }
887        Some(loc)
888    }
889
890    /// Returns the location of the first command.
891    fn first_location(&self) -> Location {
892        Location {
893            max_cut: self.shortest_max_cut(),
894            segment: self.index(),
895        }
896    }
897
898    /// Returns the location of the head of the segment.
899    fn head_location(&self) -> Result<Location, StorageError> {
900        Ok(Location {
901            max_cut: self.longest_max_cut()?,
902            segment: self.index(),
903        })
904    }
905
906    /// Returns the address of the head of the segment.
907    fn head_address(&self) -> Result<Address, StorageError> {
908        Ok(Address {
909            id: self.head_id(),
910            max_cut: self.longest_max_cut()?,
911        })
912    }
913
914    /// Walks a location toward init if it would still point within this segment.
915    #[must_use]
916    fn previous(&self, mut location: Location) -> Option<Location> {
917        debug_assert_eq!(location.segment, self.index());
918        if location.max_cut <= self.shortest_max_cut() {
919            return None;
920        }
921        location.max_cut = location.max_cut.decremented()?;
922        Some(location)
923    }
924}
925
926/// An index of facts in storage.
927pub trait FactIndex: Query {}
928
929/// A perspective is essentially a mutable, in-memory version of a [`Segment`],
930/// with the same three types.
931pub trait Perspective: FactPerspective {
932    /// Returns the id for the policy used for this perspective.
933    fn policy(&self) -> PolicyId;
934
935    /// Adds the given command to the head of the perspective. The command's
936    /// parent must be the head of the perspective.
937    fn add_command(&mut self, command: &impl Command) -> Result<usize, StorageError>;
938
939    /// Returns true if the perspective contains a command with the given ID.
940    fn includes(&self, id: CmdId) -> bool;
941
942    /// Returns the head address in the perspective, if it exists
943    fn head_address(&self) -> Result<Prior<Address>, Bug>;
944}
945
946/// A fact perspective is essentially a mutable, in-memory version of a [`FactIndex`].
947pub trait FactPerspective: QueryMut {}
948
949/// A revertable perspective can make checkpoints and be reverted such that the
950/// state of the perspective matches that when the checkpoint was created.
951pub trait Revertable {
952    /// Create a checkpoint which can be used to revert the perspective.
953    fn checkpoint(&self) -> Checkpoint;
954
955    /// Revert the perspective to the state it was at when the checkpoint was created.
956    fn revert(&mut self, checkpoint: Checkpoint) -> Result<(), StorageError>;
957}
958
959/// A checkpoint used to revert perspectives.
960pub struct Checkpoint {
961    /// An index interpreted by a given `Revertable` implementation to revert to a prior point.
962    pub index: usize,
963}
964
965/// Can be queried to look up facts.
966///
967/// Facts are labeled by a name, which are generally a bounded set of human-readable strings determined in advance.
968///
969/// Within a name, facts are an association of compound keys to values. The facts are keyed by a compound key
970/// `(k_1, k_2, ..., k_n)`, where each `k` is a sequence of bytes. The fact value is also a sequence of bytes.
971pub trait Query {
972    /// Look up a named fact by an exact match of the compound key.
973    fn query(&self, name: &str, keys: &[Bytes]) -> Result<Option<Bytes>, StorageError>;
974
975    /// Iterator for [`Query::query_prefix`].
976    type QueryIterator: Iterator<Item = Result<Fact, StorageError>>;
977
978    /// Look up all named facts that begin with the prefix of keys, in sorted key order.
979    ///
980    /// The `prefix` is a partial compound key `(k_1, k_2, ..., k_n)`, where each `k` is a sequence of bytes.
981    /// This returns all facts under the name with keys such that `prefix` is equal to a prefix of the fact's keys.
982    fn query_prefix(
983        &self,
984        name: &str,
985        prefix: &[Bytes],
986    ) -> Result<Self::QueryIterator, StorageError>;
987}
988
989/// A fact with a key and value.
990#[derive(Debug, PartialEq, Eq)]
991pub struct Fact {
992    /// The sequence of keys.
993    pub key: Keys,
994    /// The bytes of the value.
995    pub value: Bytes,
996}
997
998/// Can mutate facts by inserting and deleting them.
999///
1000/// See [`Query`] for details on the nature of facts.
1001pub trait QueryMut: Query {
1002    /// Insert a fact labeled by a name, with a given compound key and a value.
1003    ///
1004    /// This fact can later be looked up by [`Query`] methods, using the name and keys.
1005    fn insert(&mut self, name: String, keys: Keys, value: Bytes) -> Result<(), StorageError>;
1006
1007    /// Delete any fact associated to the compound key, under the given name.
1008    fn delete(&mut self, name: String, keys: Keys) -> Result<(), StorageError>;
1009}
1010
1011// TODO(jdygert): Expose this?
1012#[cfg(all(test, feature = "graphviz"))]
1013pub(crate) trait FactIndexExtra {
1014    fn name(&self) -> String;
1015    fn prior(&self) -> Result<Option<Self>, StorageError>
1016    where
1017        Self: Sized;
1018}
1019
1020/// A sequence of byte-based keys, used for facts.
1021#[derive(
1022    Clone,
1023    Debug,
1024    Default,
1025    PartialEq,
1026    Eq,
1027    PartialOrd,
1028    Ord,
1029    serde::Serialize,
1030    serde::Deserialize,
1031    rkyv::Archive,
1032    rkyv::Serialize,
1033    rkyv::Deserialize,
1034)]
1035pub struct Keys(Box<[Bytes]>);
1036
1037impl Deref for Keys {
1038    type Target = [Bytes];
1039    fn deref(&self) -> &[Bytes] {
1040        self.0.as_ref()
1041    }
1042}
1043
1044impl AsRef<[Bytes]> for Keys {
1045    fn as_ref(&self) -> &[Bytes] {
1046        self.0.as_ref()
1047    }
1048}
1049
1050impl Borrow<[Bytes]> for Keys {
1051    fn borrow(&self) -> &[Bytes] {
1052        self.0.as_ref()
1053    }
1054}
1055
1056impl From<Vec<Bytes>> for Keys {
1057    fn from(value: Vec<Bytes>) -> Self {
1058        Self(value.into_boxed_slice())
1059    }
1060}
1061
1062impl From<&[&[u8]]> for Keys {
1063    fn from(value: &[&[u8]]) -> Self {
1064        value.iter().copied().collect()
1065    }
1066}
1067
1068impl<B: Into<Bytes>> FromIterator<B> for Keys {
1069    fn from_iter<T: IntoIterator<Item = B>>(iter: T) -> Self {
1070        Self(iter.into_iter().map(Into::into).collect())
1071    }
1072}
1073
1074impl<'a> IntoIterator for &'a Keys {
1075    type Item = &'a Bytes;
1076    type IntoIter = core::slice::Iter<'a, Bytes>;
1077    fn into_iter(self) -> Self::IntoIter {
1078        self.0.iter()
1079    }
1080}
1081
1082impl ArchivedKeys {
1083    pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
1084        self.0.iter().map(AsRef::as_ref)
1085    }
1086}
1087
1088pub type Bytes = Box<[u8]>;
1089
1090mod impls {
1091    use alloc::boxed::Box;
1092
1093    use super::{GraphId, PolicyId, StorageError, StorageProvider};
1094
1095    impl<SP: StorageProvider> StorageProvider for &mut SP {
1096        type Perspective = SP::Perspective;
1097        type Segment = SP::Segment;
1098        type Storage = SP::Storage;
1099
1100        fn new_perspective(&mut self, policy_id: PolicyId) -> Self::Perspective {
1101            SP::new_perspective(self, policy_id)
1102        }
1103
1104        fn new_storage(
1105            &mut self,
1106            init: Self::Perspective,
1107        ) -> Result<(GraphId, &mut Self::Storage), StorageError> {
1108            SP::new_storage(self, init)
1109        }
1110
1111        fn get_storage(&mut self, graph: GraphId) -> Result<&mut Self::Storage, StorageError> {
1112            SP::get_storage(self, graph)
1113        }
1114
1115        fn remove_storage(&mut self, graph: GraphId) -> Result<(), StorageError> {
1116            SP::remove_storage(self, graph)
1117        }
1118
1119        fn list_graph_ids(
1120            &mut self,
1121        ) -> Result<impl Iterator<Item = Result<GraphId, StorageError>>, StorageError> {
1122            SP::list_graph_ids(self)
1123        }
1124    }
1125
1126    impl<SP: StorageProvider> StorageProvider for Box<SP> {
1127        type Perspective = SP::Perspective;
1128        type Segment = SP::Segment;
1129        type Storage = SP::Storage;
1130
1131        fn new_perspective(&mut self, policy_id: PolicyId) -> Self::Perspective {
1132            SP::new_perspective(self, policy_id)
1133        }
1134
1135        fn new_storage(
1136            &mut self,
1137            init: Self::Perspective,
1138        ) -> Result<(GraphId, &mut Self::Storage), StorageError> {
1139            SP::new_storage(self, init)
1140        }
1141
1142        fn get_storage(&mut self, graph: GraphId) -> Result<&mut Self::Storage, StorageError> {
1143            SP::get_storage(self, graph)
1144        }
1145
1146        fn remove_storage(&mut self, graph: GraphId) -> Result<(), StorageError> {
1147            SP::remove_storage(self, graph)
1148        }
1149
1150        fn list_graph_ids(
1151            &mut self,
1152        ) -> Result<impl Iterator<Item = Result<GraphId, StorageError>>, StorageError> {
1153            SP::list_graph_ids(self)
1154        }
1155    }
1156}
1157
1158#[cfg(test)]
1159mod queue_tests {
1160    use super::*;
1161
1162    fn loc(seg: usize, mc: usize) -> Location {
1163        Location::new(SegmentIndex::new(seg as u64), MaxCut::new(mc as u64))
1164    }
1165
1166    #[test]
1167    fn test_queue_overflow_returns_error() {
1168        let mut queue = TraversalQueue::new();
1169        // Fill to capacity
1170        for i in 0..QUEUE_CAPACITY {
1171            queue.push(loc(i, i)).unwrap();
1172        }
1173        // Next push should fail with TraversalQueueOverflow
1174        let result = queue
1175            .push(loc(999, 999))
1176            .expect_err("expected push_queue to fail");
1177        assert_eq!(result, StorageError::TraversalQueueOverflow(QUEUE_CAPACITY));
1178    }
1179
1180    #[test]
1181    fn test_push_defaults_covered_false() {
1182        let mut queue = TraversalQueue::new();
1183        queue.push(loc(0, 5)).unwrap();
1184        let (_, covered) = queue.pop_covered().unwrap().unwrap();
1185        assert!(!covered);
1186    }
1187
1188    #[test]
1189    fn test_push_covered_preserves_flag() {
1190        let mut queue = TraversalQueue::new();
1191        queue.push_covered(loc(0, 5), true).unwrap();
1192        let (_, covered) = queue.pop_covered().unwrap().unwrap();
1193        assert!(covered);
1194    }
1195
1196    #[test]
1197    fn test_push_covered_same_max_cut_ors_flags() {
1198        let mut queue = TraversalQueue::new();
1199        queue.push_covered(loc(0, 5), false).unwrap();
1200        queue.push_covered(loc(0, 5), true).unwrap();
1201        let (_, covered) = queue.pop_covered().unwrap().unwrap();
1202        assert!(covered);
1203    }
1204
1205    #[test]
1206    fn test_push_covered_same_max_cut_cannot_uncover() {
1207        let mut queue = TraversalQueue::new();
1208        queue.push_covered(loc(0, 5), true).unwrap();
1209        // Pushing uncovered at the same max_cut must not clear covered.
1210        queue.push_covered(loc(0, 5), false).unwrap();
1211        let (_, covered) = queue.pop_covered().unwrap().unwrap();
1212        assert!(covered);
1213    }
1214
1215    #[test]
1216    fn test_push_same_segment_updates_max_cut() {
1217        let mut queue = TraversalQueue::new();
1218        queue.push(loc(0, 5)).unwrap();
1219        queue.push(loc(0, 8)).unwrap();
1220        let l = queue.pop().unwrap().unwrap();
1221        assert_eq!(l.max_cut, MaxCut::new(8));
1222        assert!(queue.is_empty());
1223    }
1224
1225    #[test]
1226    fn test_push_covered_higher_max_cut_adopts_new_flag() {
1227        let mut queue = TraversalQueue::new();
1228        queue.push_covered(loc(0, 5), true).unwrap();
1229        // Higher max_cut uncovered: segment head moved beyond covered point.
1230        queue.push_covered(loc(0, 8), false).unwrap();
1231        let (l, covered) = queue.pop_covered().unwrap().unwrap();
1232        assert_eq!(l.max_cut, MaxCut::new(8));
1233        assert!(!covered);
1234    }
1235
1236    #[test]
1237    fn test_push_covered_lower_max_cut_no_change() {
1238        let mut queue = TraversalQueue::new();
1239        queue.push_covered(loc(0, 8), false).unwrap();
1240        // Lower max_cut should not change anything.
1241        queue.push_covered(loc(0, 3), true).unwrap();
1242        let (l, covered) = queue.pop_covered().unwrap().unwrap();
1243        assert_eq!(l.max_cut, MaxCut::new(8));
1244        assert!(!covered);
1245    }
1246
1247    #[test]
1248    fn test_pop_discards_covered_flag() {
1249        let mut queue = TraversalQueue::new();
1250        queue.push_covered(loc(0, 5), true).unwrap();
1251        // pop() should return only the location.
1252        let l = queue.pop().unwrap().unwrap();
1253        assert_eq!(l.max_cut, MaxCut::new(5));
1254        assert!(queue.is_empty());
1255    }
1256
1257    #[test]
1258    fn test_all_covered() {
1259        let mut queue = TraversalQueue::new();
1260        queue.push_covered(loc(0, 1), true).unwrap();
1261        queue.push_covered(loc(1, 2), true).unwrap();
1262        assert!(queue.all_covered());
1263
1264        queue.push_covered(loc(2, 3), false).unwrap();
1265        assert!(!queue.all_covered());
1266    }
1267
1268    #[test]
1269    fn test_drain_above() {
1270        let mut queue = TraversalQueue::new();
1271        queue.push(loc(0, 3)).unwrap();
1272        queue.push(loc(1, 7)).unwrap();
1273        queue.push(loc(2, 5)).unwrap();
1274
1275        let mut result: heapless::Vec<Location, 8> = heapless::Vec::new();
1276        queue
1277            .drain_above(MaxCut::new(4), |loc| {
1278                let _ = result.push(loc);
1279            })
1280            .unwrap();
1281
1282        // Entries with max_cut > 4 should be drained.
1283        assert_eq!(result.len(), 2);
1284        assert!(result.iter().any(|l| l.max_cut == MaxCut::new(7)));
1285        assert!(result.iter().any(|l| l.max_cut == MaxCut::new(5)));
1286
1287        // Only max_cut=3 should remain in the queue.
1288        let remaining = queue.pop().unwrap().unwrap();
1289        assert_eq!(remaining.max_cut, MaxCut::new(3));
1290        assert!(queue.is_empty());
1291    }
1292
1293    #[test]
1294    fn test_drain_above_with_covered_entries() {
1295        let mut queue = TraversalQueue::new();
1296        // Mix of uncovered and covered entries above and below threshold.
1297        queue.push(loc(0, 3)).unwrap(); // uncovered, below
1298        queue.push(loc(1, 7)).unwrap(); // uncovered, above
1299        queue.push_covered(loc(2, 6), true).unwrap(); // covered, above
1300        queue.push_covered(loc(3, 2), true).unwrap(); // covered, below
1301        queue.push(loc(4, 5)).unwrap(); // uncovered, above
1302
1303        let mut drained: heapless::Vec<Location, 8> = heapless::Vec::new();
1304        queue
1305            .drain_above(MaxCut::new(4), |loc| {
1306                let _ = drained.push(loc);
1307            })
1308            .unwrap();
1309
1310        // Only uncovered entries above threshold should be passed to f.
1311        assert_eq!(drained.len(), 2);
1312        assert!(drained.iter().any(|l| l.segment.get() == 1));
1313        assert!(drained.iter().any(|l| l.segment.get() == 4));
1314
1315        // Covered entry above threshold (seg=2) should be discarded.
1316        // Entries below threshold should remain: seg=0 (uncovered), seg=3 (covered).
1317        let mut remaining = Vec::new();
1318        while let Some((l, covered)) = queue.pop_covered().unwrap() {
1319            remaining.push((l.segment, covered));
1320        }
1321        assert_eq!(remaining.len(), 2);
1322        assert!(remaining.contains(&(SegmentIndex::new(0), false)));
1323        assert!(remaining.contains(&(SegmentIndex::new(3), true)));
1324    }
1325
1326    #[test]
1327    fn test_push_duplicate_keeps_separate_entries() {
1328        let mut queue = TraversalQueue::new();
1329        queue.push_duplicate(loc(0, 5)).unwrap();
1330        queue.push_duplicate(loc(0, 5)).unwrap();
1331        let first = queue.pop().unwrap();
1332        assert!(first.is_some());
1333        let second = queue.pop().unwrap();
1334        assert!(second.is_some());
1335        assert!(queue.is_empty());
1336    }
1337
1338    #[test]
1339    fn test_push_duplicate_overflow() {
1340        let mut queue = TraversalQueue::new();
1341        for i in 0..QUEUE_CAPACITY {
1342            queue.push_duplicate(loc(0, i)).unwrap();
1343        }
1344        let result = queue.push_duplicate(loc(0, 999));
1345        assert_eq!(
1346            result.unwrap_err(),
1347            StorageError::TraversalQueueOverflow(QUEUE_CAPACITY)
1348        );
1349    }
1350
1351    #[test]
1352    fn test_pop_duplicates_returns_count() {
1353        let mut queue = TraversalQueue::new();
1354        queue.push_duplicate(loc(0, 5)).unwrap();
1355        queue.push_duplicate(loc(0, 5)).unwrap();
1356        queue.push_duplicate(loc(1, 3)).unwrap();
1357
1358        let (location, count) = queue.pop_duplicates().unwrap().unwrap();
1359        assert_eq!(location, loc(0, 5));
1360        assert_eq!(count, 2);
1361
1362        let (location, count) = queue.pop_duplicates().unwrap().unwrap();
1363        assert_eq!(location, loc(1, 3));
1364        assert_eq!(count, 1);
1365
1366        assert!(queue.pop_duplicates().unwrap().is_none());
1367    }
1368
1369    #[test]
1370    fn test_pop_duplicates_different_segments_same_max_cut() {
1371        let mut queue = TraversalQueue::new();
1372        queue.push_duplicate(loc(0, 5)).unwrap();
1373        queue.push_duplicate(loc(1, 5)).unwrap();
1374
1375        let (location, count) = queue.pop_duplicates().unwrap().unwrap();
1376        assert_eq!(count, 1);
1377        assert_eq!(location.max_cut, MaxCut::new(5));
1378
1379        let (_, count) = queue.pop_duplicates().unwrap().unwrap();
1380        assert_eq!(count, 1);
1381
1382        assert!(queue.pop_duplicates().unwrap().is_none());
1383    }
1384
1385    #[test]
1386    fn test_pop_duplicates_empty() {
1387        let mut queue = TraversalQueue::new();
1388        assert!(queue.pop_duplicates().unwrap().is_none());
1389    }
1390}