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}