Skip to main content

kvbm_logical/sequence/
mod.rs

1// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! # Sequence Module
5//!
6//! Block assignment tracking for token sequences.
7//!
8//! This module provides two assignment trackers that share a three-phase
9//! lifecycle: **unassigned** (queued), **staged** (paired with token data),
10//! and **assigned** (committed).
11//!
12//! - [`ExternalBlockAssignments`] tracks at the **identity level** — mapping
13//!   `BlockId` to `SequenceHash`.
14//! - [`LogicalBlockAssignments`] tracks at the **guard level** — managing
15//!   RAII block guards through `MutableBlock` to `CompleteBlock` to
16//!   `ImmutableBlock` transitions.
17//!
18//! Both types are backed by the same ordered-collection machinery and expose
19//! similar query, iteration, and mutation APIs.
20//!
21//! ## `ExternalBlockAssignments`
22//!
23//! Identity-level tracking of block IDs paired with sequence hashes.
24//!
25//! ### Basic flow
26//!
27//! Create a sequence from tokens, register block IDs, and assign them
28//! against the completed token blocks:
29//!
30//! ```rust
31//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
32//! use kvbm_logical::{ExternalBlockAssignments, BlockSequence};
33//!
34//! // Create a sequence with 3 complete blocks (4 tokens each).
35//! let tokens: Vec<u32> = (0..12).collect();
36//! let seq = BlockSequence::new(tokens, 4, None);
37//! assert_eq!(seq.blocks().len(), 3);
38//!
39//! // Create assignments starting at offset 0.
40//! let mut assignments = ExternalBlockAssignments::new(0);
41//!
42//! // Register block IDs (e.g., allocated by the scheduler).
43//! assignments.extend_block_ids(vec![10, 20, 30])?;
44//! assert_eq!(assignments.unassigned_count(), 3);
45//!
46//! // Assign: pairs each pending ID with its token block's hash.
47//! let range = assignments.assign_pending(seq.blocks())?;
48//! assert_eq!(range, 0..3);
49//! assert_eq!(assignments.assigned_count(), 3);
50//! assert_eq!(assignments.unassigned_count(), 0);
51//!
52//! // Query by index.
53//! let (id, hash) = assignments.get_assigned(0).unwrap();
54//! assert_eq!(id, 10);
55//! assert_eq!(hash, seq.all_sequence_hashes()[0]);
56//! # Ok(())
57//! # }
58//! ```
59//!
60//! ### Two-step staging
61//!
62//! Use `stage_pending` and `commit_staged` for explicit control over the
63//! staging phase (useful when validation must happen between staging and
64//! committing):
65//!
66//! ```rust
67//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
68//! use kvbm_logical::{ExternalBlockAssignments, BlockSequence};
69//!
70//! let tokens: Vec<u32> = (0..8).collect();
71//! let seq = BlockSequence::new(tokens, 4, None);
72//!
73//! let mut assignments = ExternalBlockAssignments::new(0);
74//! assignments.extend_block_ids(vec![1, 2])?;
75//!
76//! // Stage: pairs IDs with hashes but does not commit.
77//! let staged_range = assignments.stage_pending(seq.blocks())?;
78//! assert_eq!(staged_range, 0..2);
79//! assert_eq!(assignments.staged_count(), 2);
80//! assert_eq!(assignments.assigned_count(), 0);
81//!
82//! // Commit: moves staged into assigned.
83//! let assigned_range = assignments.commit_staged();
84//! assert_eq!(assigned_range, 0..2);
85//! assert_eq!(assignments.assigned_count(), 2);
86//! assert_eq!(assignments.staged_count(), 0);
87//! # Ok(())
88//! # }
89//! ```
90//!
91//!
92//! ## `LogicalBlockAssignments`
93//!
94//! Guard-level tracking through the full block lifecycle. Blocks flow
95//! through `MutableBlock` (unassigned) to `CompleteBlock` (staged) to
96//! `ImmutableBlock` (assigned/registered).
97//!
98//! ### Full pipeline
99//!
100//! Allocate physical blocks from a `BlockManager`, stage them against token
101//! data, register them, and query the result:
102//!
103//! ```rust
104//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
105//! use kvbm_logical::{
106//!     BlockManager, BlockRegistry, BlockSequence,
107//!     LogicalBlockAssignments,
108//!     manager::FrequencyTrackingCapacity,
109//! };
110//!
111//! // Build a manager with 10 blocks of size 4.
112//! let tracker = FrequencyTrackingCapacity::Small.create_tracker();
113//! let registry = BlockRegistry::builder()
114//!     .frequency_tracker(tracker)
115//!     .build();
116//! let manager = BlockManager::<()>::builder()
117//!     .block_count(10)
118//!     .block_size(4)
119//!     .registry(registry)
120//!     .with_lru_backend()
121//!     .build()?;
122//!
123//! // Create a token sequence with 3 complete blocks.
124//! let tokens: Vec<u32> = (0..12).collect();
125//! let seq = BlockSequence::new(tokens, 4, None);
126//!
127//! // Allocate 3 mutable blocks from the manager.
128//! let blocks = manager.allocate_blocks(3).unwrap();
129//! let ids: Vec<usize> = blocks.iter().map(|b| b.block_id()).collect();
130//!
131//! let mut la = LogicalBlockAssignments::new();
132//!
133//! // Extend: adds mutable blocks to the unassigned queue.
134//! la.extend_blocks(blocks)?;
135//! assert_eq!(la.unassigned_count(), 3);
136//!
137//! // Stage: completes each mutable block with its token data.
138//! la.stage(seq.blocks())?;
139//! assert_eq!(la.staged_count(), 3);
140//! assert_eq!(la.unassigned_count(), 0);
141//!
142//! // Register: finalizes staged blocks through the manager.
143//! la.register(&manager);
144//! assert_eq!(la.assigned_count(), 3);
145//! assert_eq!(la.staged_count(), 0);
146//!
147//! // Query assigned blocks.
148//! for i in 0..3 {
149//!     let (id, immutable) = la.get_assigned(i).unwrap();
150//!     assert_eq!(*id, ids[i]);
151//!     assert_eq!(immutable.sequence_hash(), seq.all_sequence_hashes()[i]);
152//! }
153//! # Ok(())
154//! # }
155//! ```
156
157mod assignments;
158mod store;
159
160pub use assignments::{
161    ExternalBlockAssignments, LogicalBlockAssignmentError, LogicalBlockAssignments, zip_assigned,
162    zip_assigned_pending,
163};
164
165use std::ops::Range;
166
167use dynamo_tokens::{Token, TokenBlock, TokenBlockError, TokenBlockSequence, Tokens};
168
169use crate::{BlockId, KvbmSequenceHashProvider, SequenceHash};
170
171/// Errors that can occur in block sequence operations.
172#[derive(Debug, thiserror::Error)]
173pub enum BlockSequenceError {
174    /// A known block_id appeared after an unknown block_id in `extend_block_ids`.
175    #[error(
176        "ordering violation: known block_id {known_id} at index {known_index} \
177         appeared after new block_id {new_id} at index {first_new_index}"
178    )]
179    OrderingViolation {
180        known_id: BlockId,
181        new_id: BlockId,
182        known_index: usize,
183        first_new_index: usize,
184    },
185
186    /// The position embedded in a sequence hash didn't match the expected position.
187    #[error("position mismatch for block_id {block_id}: expected {expected}, actual {actual}")]
188    PositionMismatch {
189        expected: usize,
190        actual: u64,
191        block_id: BlockId,
192    },
193
194    /// A block_id already exists in one of the collections.
195    #[error("duplicate block_id {block_id} already present")]
196    DuplicateBlockId { block_id: BlockId },
197
198    /// Error from underlying token block operations.
199    #[error("token extension error: {0}")]
200    TokenExtension(#[from] TokenBlockError),
201}
202
203/// Owns a `TokenBlockSequence` and provides sequence access and token extension methods.
204///
205/// This is a thin wrapper around `TokenBlockSequence` that provides a convenient API
206/// for the block assignment workflow. It does NOT embed assignments — those are managed
207/// separately by [`ExternalBlockAssignments`].
208#[derive(Debug)]
209pub struct BlockSequence {
210    sequence: TokenBlockSequence,
211}
212
213impl BlockSequence {
214    /// Creates a new `BlockSequence` from tokens, block size, and optional salt hash.
215    pub fn new(tokens: Vec<Token>, block_size: u32, salt_hash: Option<u64>) -> Self {
216        let tokens = Tokens::from(tokens);
217        Self {
218            sequence: TokenBlockSequence::new(tokens, block_size, salt_hash),
219        }
220    }
221
222    /// Returns the completed token blocks.
223    pub fn blocks(&self) -> &[TokenBlock] {
224        self.sequence.blocks()
225    }
226
227    /// Returns the block size.
228    pub fn block_size(&self) -> usize {
229        self.sequence.block_size()
230    }
231
232    /// Returns the total number of tokens (including partial block).
233    pub fn total_tokens(&self) -> usize {
234        self.sequence.total_tokens()
235    }
236
237    /// Returns a reference to the underlying `TokenBlockSequence`.
238    pub fn sequence(&self) -> &TokenBlockSequence {
239        &self.sequence
240    }
241
242    /// Returns all sequence hashes from completed blocks.
243    pub fn all_sequence_hashes(&self) -> Vec<SequenceHash> {
244        self.sequence
245            .blocks()
246            .iter()
247            .map(|b| b.kvbm_sequence_hash())
248            .collect()
249    }
250
251    /// Extends the sequence with tokens, potentially completing blocks.
252    ///
253    /// Returns the range of newly completed block indices, or `None` if no blocks completed.
254    pub fn extend_tokens(
255        &mut self,
256        tokens: Vec<Token>,
257    ) -> Result<Option<Range<usize>>, BlockSequenceError> {
258        let tokens = Tokens::from(tokens);
259        self.sequence.extend(tokens).map_err(Into::into)
260    }
261
262    /// Appends a single token to the sequence.
263    ///
264    /// Returns the index of the completed block if the token completed a block.
265    pub fn append_token(&mut self, token: Token) -> Result<Option<usize>, BlockSequenceError> {
266        self.sequence.append(token).map_err(Into::into)
267    }
268}