Skip to main content

riffdb_types/
projection.rs

1//! Foundational projection identities, generations, keys, and hash values.
2
3use std::cmp::Ordering;
4use std::error::Error;
5use std::fmt;
6use std::hash::{Hash, Hasher};
7use std::num::NonZeroU64;
8
9use crate::limits::{MAX_CONTRACT_LINEAGE_BYTES, MAX_KEY_BYTES, MAX_PROJECTION_GROUP_COMPONENTS};
10use crate::{
11    CanonicalValue, CommitSequence, ContractLineage, ProjectionId, ProjectionPlanHash,
12    decode_canonical_value, encode_canonical_value,
13};
14
15/// Immutable v1 projection apply-marker key prefix.
16pub const PROJECTION_APPLY_KEY_V1_PREFIX: [u8; 2] = [0x41, 0x01];
17
18/// Immutable v1 projection frontier/control key prefix.
19pub const PROJECTION_FRONTIER_KEY_V1_PREFIX: [u8; 2] = [0x46, 0x01];
20
21/// Immutable v1 projection group-state key prefix.
22pub const PROJECTION_GROUP_KEY_V1_PREFIX: [u8; 2] = [0x47, 0x01];
23
24/// The exact lineage, projection declaration, and semantic plan identity.
25#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
26pub struct ProjectionIdentity {
27    contract_lineage: ContractLineage,
28    projection_id: ProjectionId,
29    plan_hash: ProjectionPlanHash,
30}
31
32impl ProjectionIdentity {
33    /// Creates an identity from already-validated foundational components.
34    #[must_use]
35    pub const fn new(
36        contract_lineage: ContractLineage,
37        projection_id: ProjectionId,
38        plan_hash: ProjectionPlanHash,
39    ) -> Self {
40        Self {
41            contract_lineage,
42            projection_id,
43            plan_hash,
44        }
45    }
46
47    /// Decodes one complete canonical v1 projection-identity payload.
48    pub fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ProjectionKeyError> {
49        let (identity, consumed) = decode_identity_prefix(bytes)?;
50        if consumed != bytes.len() {
51            return Err(ProjectionKeyError::TrailingBytes);
52        }
53        Ok(identity)
54    }
55
56    /// Returns the exact contract lineage.
57    #[must_use]
58    pub const fn contract_lineage(&self) -> &ContractLineage {
59        &self.contract_lineage
60    }
61
62    /// Returns the stable nonzero projection ID.
63    #[must_use]
64    pub const fn projection_id(&self) -> ProjectionId {
65        self.projection_id
66    }
67
68    /// Returns the exact projection-plan hash.
69    #[must_use]
70    pub const fn plan_hash(&self) -> ProjectionPlanHash {
71        self.plan_hash
72    }
73
74    /// Encodes the canonical v1 projection-identity payload.
75    #[must_use]
76    pub fn to_canonical_bytes(&self) -> Vec<u8> {
77        let lineage = self.contract_lineage.as_bytes();
78        let mut bytes = Vec::with_capacity(4 + lineage.len() + 4 + 32);
79        bytes.extend_from_slice(&(lineage.len() as u32).to_be_bytes());
80        bytes.extend_from_slice(lineage);
81        bytes.extend_from_slice(&self.projection_id.to_be_bytes());
82        bytes.extend_from_slice(self.plan_hash.as_bytes());
83        bytes
84    }
85}
86
87/// A nonzero, never-reused build generation for one projection identity.
88#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
89pub struct ProjectionGeneration(NonZeroU64);
90
91impl ProjectionGeneration {
92    /// Creates a generation, rejecting the semantic zero sentinel.
93    #[must_use]
94    pub const fn new(value: u64) -> Option<Self> {
95        match NonZeroU64::new(value) {
96            Some(value) => Some(Self(value)),
97            None => None,
98        }
99    }
100
101    /// Returns the first generation for one exact projection identity.
102    #[must_use]
103    pub const fn first() -> Self {
104        Self(NonZeroU64::MIN)
105    }
106
107    /// Returns the numeric generation.
108    #[must_use]
109    pub const fn get(self) -> u64 {
110        self.0.get()
111    }
112
113    /// Returns the canonical big-endian representation.
114    #[must_use]
115    pub const fn to_be_bytes(self) -> [u8; 8] {
116        self.get().to_be_bytes()
117    }
118
119    /// Returns the next generation, or `None` at the numeric limit.
120    #[must_use]
121    pub const fn checked_next(self) -> Option<Self> {
122        match self.get().checked_add(1) {
123            Some(value) => Self::new(value),
124            None => None,
125        }
126    }
127}
128
129impl TryFrom<u64> for ProjectionGeneration {
130    type Error = ProjectionGenerationError;
131
132    fn try_from(value: u64) -> Result<Self, Self::Error> {
133        Self::new(value).ok_or(ProjectionGenerationError)
134    }
135}
136
137impl From<ProjectionGeneration> for u64 {
138    fn from(value: ProjectionGeneration) -> Self {
139        value.get()
140    }
141}
142
143/// A safe failure to construct a nonzero projection generation.
144#[derive(Clone, Copy, Debug, Eq, PartialEq)]
145pub struct ProjectionGenerationError;
146
147impl fmt::Display for ProjectionGenerationError {
148    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
149        formatter.write_str("projection generation must be nonzero")
150    }
151}
152
153impl Error for ProjectionGenerationError {}
154
155/// A typed hash of one complete checked projection apply request.
156#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
157pub struct ProjectionApplyHash([u8; 32]);
158
159impl ProjectionApplyHash {
160    /// Creates a typed projection-apply hash from its 32 digest bytes.
161    #[must_use]
162    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
163        Self(bytes)
164    }
165
166    /// Borrows the digest bytes.
167    #[must_use]
168    pub const fn as_bytes(&self) -> &[u8; 32] {
169        &self.0
170    }
171
172    /// Returns the digest bytes.
173    #[must_use]
174    pub const fn into_bytes(self) -> [u8; 32] {
175        self.0
176    }
177}
178
179/// A complete canonical projection group-state key.
180#[derive(Clone)]
181pub struct ProjectionGroupKey {
182    bytes: Vec<u8>,
183    identity: ProjectionIdentity,
184    generation: ProjectionGeneration,
185    components: Vec<CanonicalValue>,
186}
187
188impl ProjectionGroupKey {
189    /// Parses a syntactically canonical complete group key.
190    ///
191    /// Exact component types and count still require the matching checked
192    /// projection group schema.
193    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, ProjectionKeyError> {
194        let parts = decode_group_parts(&bytes, true)?;
195        Ok(Self {
196            bytes,
197            identity: parts.identity,
198            generation: parts.generation,
199            components: parts.components,
200        })
201    }
202
203    /// Borrows the canonical key bytes.
204    #[must_use]
205    pub fn as_bytes(&self) -> &[u8] {
206        &self.bytes
207    }
208
209    /// Returns the canonical key bytes.
210    #[must_use]
211    pub fn into_bytes(self) -> Vec<u8> {
212        self.bytes
213    }
214
215    /// Consumes the key without cloning its decoded component allocation.
216    /// The canonical byte buffer is released; callers retain the exact identity
217    /// and generation explicitly rather than extracting values from a foreign key.
218    #[must_use]
219    pub fn into_parts(
220        self,
221    ) -> (
222        ProjectionIdentity,
223        ProjectionGeneration,
224        Vec<CanonicalValue>,
225    ) {
226        (self.identity, self.generation, self.components)
227    }
228
229    /// Returns the projection identity encoded by the key.
230    #[must_use]
231    pub const fn identity(&self) -> &ProjectionIdentity {
232        &self.identity
233    }
234
235    /// Returns the generation encoded by the key.
236    #[must_use]
237    pub const fn generation(&self) -> ProjectionGeneration {
238        self.generation
239    }
240
241    /// Returns the syntactically validated canonical scalar components.
242    #[must_use]
243    pub fn components(&self) -> &[CanonicalValue] {
244        &self.components
245    }
246}
247
248/// A transient canonical prefix for one published projection generation.
249#[derive(Clone)]
250pub struct ProjectionGroupPrefix {
251    bytes: Vec<u8>,
252    identity: ProjectionIdentity,
253    generation: ProjectionGeneration,
254    components: Vec<CanonicalValue>,
255}
256
257impl ProjectionGroupPrefix {
258    /// Parses a syntactically canonical zero-or-more-component prefix.
259    ///
260    /// Exact leading-component validation still requires the matching checked
261    /// projection group schema.
262    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, ProjectionKeyError> {
263        let parts = decode_group_parts(&bytes, false)?;
264        Ok(Self {
265            bytes,
266            identity: parts.identity,
267            generation: parts.generation,
268            components: parts.components,
269        })
270    }
271
272    /// Borrows the canonical prefix bytes.
273    #[must_use]
274    pub fn as_bytes(&self) -> &[u8] {
275        &self.bytes
276    }
277
278    /// Returns the projection identity encoded by the prefix.
279    #[must_use]
280    pub const fn identity(&self) -> &ProjectionIdentity {
281        &self.identity
282    }
283
284    /// Returns the generation encoded by the prefix.
285    #[must_use]
286    pub const fn generation(&self) -> ProjectionGeneration {
287        self.generation
288    }
289
290    /// Returns the syntactically validated complete leading components.
291    #[must_use]
292    pub fn components(&self) -> &[CanonicalValue] {
293        &self.components
294    }
295
296    /// Returns the lexicographic exclusive successor for a prefix range.
297    ///
298    /// A valid prefix always begins with `0x47`, so a successor always exists.
299    #[must_use]
300    pub fn exclusive_successor(&self) -> Vec<u8> {
301        let mut successor = self.bytes.clone();
302        for index in (0..successor.len()).rev() {
303            if successor[index] != u8::MAX {
304                successor[index] += 1;
305                successor.truncate(index + 1);
306                return successor;
307            }
308        }
309        unreachable!("a projection group prefix begins with 0x47")
310    }
311}
312
313/// Builder for one syntactically canonical complete projection group key.
314#[derive(Clone)]
315pub struct ProjectionGroupKeyBuilder(ProjectionComponentsBuilder);
316
317impl ProjectionGroupKeyBuilder {
318    /// Starts a key with its exact identity and nonzero generation.
319    #[must_use]
320    pub fn new(identity: ProjectionIdentity, generation: ProjectionGeneration) -> Self {
321        Self(ProjectionComponentsBuilder::new(identity, generation))
322    }
323
324    /// Appends one complete canonical scalar component.
325    pub fn push_component(
326        &mut self,
327        value: CanonicalValue,
328    ) -> Result<&mut Self, ProjectionKeyError> {
329        self.0.push_component(value)?;
330        Ok(self)
331    }
332
333    /// Borrows the currently encoded key bytes.
334    #[must_use]
335    pub fn as_bytes(&self) -> &[u8] {
336        &self.0.bytes
337    }
338
339    /// Finishes a nonempty syntactically canonical complete key.
340    pub fn finish(self) -> Result<ProjectionGroupKey, ProjectionKeyError> {
341        if self.0.components.is_empty() {
342            return Err(ProjectionKeyError::EmptyGroupKey);
343        }
344        Ok(ProjectionGroupKey {
345            bytes: self.0.bytes,
346            identity: self.0.identity,
347            generation: self.0.generation,
348            components: self.0.components,
349        })
350    }
351}
352
353/// Builder for a syntactically canonical projection group prefix.
354#[derive(Clone)]
355pub struct ProjectionGroupPrefixBuilder(ProjectionComponentsBuilder);
356
357impl ProjectionGroupPrefixBuilder {
358    /// Starts a prefix with its exact identity and nonzero generation.
359    #[must_use]
360    pub fn new(identity: ProjectionIdentity, generation: ProjectionGeneration) -> Self {
361        Self(ProjectionComponentsBuilder::new(identity, generation))
362    }
363
364    /// Appends one complete canonical leading scalar component.
365    pub fn push_component(
366        &mut self,
367        value: CanonicalValue,
368    ) -> Result<&mut Self, ProjectionKeyError> {
369        self.0.push_component(value)?;
370        Ok(self)
371    }
372
373    /// Borrows the currently encoded prefix bytes.
374    #[must_use]
375    pub fn as_bytes(&self) -> &[u8] {
376        &self.0.bytes
377    }
378
379    /// Finishes a zero-or-more-component syntactically canonical prefix.
380    #[must_use]
381    pub fn finish(self) -> ProjectionGroupPrefix {
382        ProjectionGroupPrefix {
383            bytes: self.0.bytes,
384            identity: self.0.identity,
385            generation: self.0.generation,
386            components: self.0.components,
387        }
388    }
389}
390
391#[derive(Clone)]
392struct ProjectionComponentsBuilder {
393    bytes: Vec<u8>,
394    identity: ProjectionIdentity,
395    generation: ProjectionGeneration,
396    components: Vec<CanonicalValue>,
397}
398
399impl ProjectionComponentsBuilder {
400    fn new(identity: ProjectionIdentity, generation: ProjectionGeneration) -> Self {
401        let identity_bytes = identity.to_canonical_bytes();
402        let mut bytes = Vec::with_capacity(2 + identity_bytes.len() + 8);
403        bytes.extend_from_slice(&PROJECTION_GROUP_KEY_V1_PREFIX);
404        bytes.extend_from_slice(&identity_bytes);
405        bytes.extend_from_slice(&generation.to_be_bytes());
406        Self {
407            bytes,
408            identity,
409            generation,
410            components: Vec::new(),
411        }
412    }
413
414    fn push_component(&mut self, value: CanonicalValue) -> Result<(), ProjectionKeyError> {
415        if self.components.len() == MAX_PROJECTION_GROUP_COMPONENTS {
416            return Err(ProjectionKeyError::TooManyComponents);
417        }
418        let encoded_length = projection_scalar_encoded_length(&value)?;
419        let additional = 4usize
420            .checked_add(encoded_length)
421            .ok_or(ProjectionKeyError::TooLong {
422                actual: usize::MAX,
423                maximum: MAX_KEY_BYTES,
424            })?;
425        let actual =
426            self.bytes
427                .len()
428                .checked_add(additional)
429                .ok_or(ProjectionKeyError::TooLong {
430                    actual: usize::MAX,
431                    maximum: MAX_KEY_BYTES,
432                })?;
433        if actual > MAX_KEY_BYTES {
434            return Err(ProjectionKeyError::TooLong {
435                actual,
436                maximum: MAX_KEY_BYTES,
437            });
438        }
439
440        let encoded = encode_canonical_value(&value)
441            .map_err(|_| ProjectionKeyError::InvalidCanonicalComponent)?;
442        if encoded.len() != encoded_length {
443            return Err(ProjectionKeyError::InvalidCanonicalComponent);
444        }
445        let encoded_length = u32::try_from(encoded.len())
446            .map_err(|_| ProjectionKeyError::InvalidCanonicalComponent)?;
447        self.bytes.extend_from_slice(&encoded_length.to_be_bytes());
448        self.bytes.extend_from_slice(&encoded);
449        self.components.push(value);
450        Ok(())
451    }
452}
453
454/// The generation-neutral control/frontier key for one projection identity.
455#[derive(Clone)]
456pub struct ProjectionFrontierKey {
457    bytes: Vec<u8>,
458    identity: ProjectionIdentity,
459}
460
461impl ProjectionFrontierKey {
462    /// Constructs the exact v1 frontier/control key.
463    #[must_use]
464    pub fn new(identity: ProjectionIdentity) -> Self {
465        let identity_bytes = identity.to_canonical_bytes();
466        let mut bytes = Vec::with_capacity(2 + identity_bytes.len());
467        bytes.extend_from_slice(&PROJECTION_FRONTIER_KEY_V1_PREFIX);
468        bytes.extend_from_slice(&identity_bytes);
469        Self { bytes, identity }
470    }
471
472    /// Parses one exact v1 frontier/control key.
473    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, ProjectionKeyError> {
474        validate_key_bound(&bytes)?;
475        validate_prefix(&bytes, PROJECTION_FRONTIER_KEY_V1_PREFIX)?;
476        let (identity, consumed) = decode_identity_prefix(&bytes[2..])?;
477        if 2 + consumed != bytes.len() {
478            return Err(ProjectionKeyError::TrailingBytes);
479        }
480        Ok(Self { bytes, identity })
481    }
482
483    /// Borrows the canonical key bytes.
484    #[must_use]
485    pub fn as_bytes(&self) -> &[u8] {
486        &self.bytes
487    }
488
489    /// Returns the projection identity encoded by the key.
490    #[must_use]
491    pub const fn identity(&self) -> &ProjectionIdentity {
492        &self.identity
493    }
494}
495
496/// One generation- and sequence-specific projection apply-marker key.
497#[derive(Clone)]
498pub struct ProjectionApplyKey {
499    bytes: Vec<u8>,
500    identity: ProjectionIdentity,
501    generation: ProjectionGeneration,
502    commit_sequence: CommitSequence,
503}
504
505impl ProjectionApplyKey {
506    /// Constructs the exact v1 projection apply-marker key.
507    #[must_use]
508    pub fn new(
509        identity: ProjectionIdentity,
510        generation: ProjectionGeneration,
511        commit_sequence: CommitSequence,
512    ) -> Self {
513        let identity_bytes = identity.to_canonical_bytes();
514        let mut bytes = Vec::with_capacity(2 + identity_bytes.len() + 16);
515        bytes.extend_from_slice(&PROJECTION_APPLY_KEY_V1_PREFIX);
516        bytes.extend_from_slice(&identity_bytes);
517        bytes.extend_from_slice(&generation.to_be_bytes());
518        bytes.extend_from_slice(&commit_sequence.to_be_bytes());
519        Self {
520            bytes,
521            identity,
522            generation,
523            commit_sequence,
524        }
525    }
526
527    /// Parses one exact v1 projection apply-marker key.
528    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, ProjectionKeyError> {
529        validate_key_bound(&bytes)?;
530        validate_prefix(&bytes, PROJECTION_APPLY_KEY_V1_PREFIX)?;
531        let (identity, consumed) = decode_identity_prefix(&bytes[2..])?;
532        let mut position = 2 + consumed;
533        if bytes.len().saturating_sub(position) != 16 {
534            return Err(ProjectionKeyError::TruncatedOrTrailing);
535        }
536        let generation = decode_generation(read_array::<8>(&bytes, &mut position)?)?;
537        let commit_sequence = decode_commit_sequence(read_array::<8>(&bytes, &mut position)?)?;
538        Ok(Self {
539            bytes,
540            identity,
541            generation,
542            commit_sequence,
543        })
544    }
545
546    /// Borrows the canonical key bytes.
547    #[must_use]
548    pub fn as_bytes(&self) -> &[u8] {
549        &self.bytes
550    }
551
552    /// Returns the projection identity encoded by the key.
553    #[must_use]
554    pub const fn identity(&self) -> &ProjectionIdentity {
555        &self.identity
556    }
557
558    /// Returns the projection generation encoded by the key.
559    #[must_use]
560    pub const fn generation(&self) -> ProjectionGeneration {
561        self.generation
562    }
563
564    /// Returns the applied commit sequence encoded by the key.
565    #[must_use]
566    pub const fn commit_sequence(&self) -> CommitSequence {
567        self.commit_sequence
568    }
569}
570
571macro_rules! byte_key_traits {
572    ($type:ty) => {
573        impl PartialEq for $type {
574            fn eq(&self, other: &Self) -> bool {
575                self.bytes == other.bytes
576            }
577        }
578
579        impl Eq for $type {}
580
581        impl PartialOrd for $type {
582            fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
583                Some(self.cmp(other))
584            }
585        }
586
587        impl Ord for $type {
588            fn cmp(&self, other: &Self) -> Ordering {
589                self.bytes.cmp(&other.bytes)
590            }
591        }
592
593        impl Hash for $type {
594            fn hash<H: Hasher>(&self, state: &mut H) {
595                self.bytes.hash(state);
596            }
597        }
598    };
599}
600
601byte_key_traits!(ProjectionGroupKey);
602byte_key_traits!(ProjectionGroupPrefix);
603byte_key_traits!(ProjectionFrontierKey);
604byte_key_traits!(ProjectionApplyKey);
605
606macro_rules! redacted_key_debug {
607    ($type:ty, $name:literal) => {
608        impl fmt::Debug for $type {
609            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
610                formatter
611                    .debug_struct($name)
612                    .field("bytes", &"[REDACTED]")
613                    .field("length", &self.bytes.len())
614                    .finish()
615            }
616        }
617    };
618}
619
620redacted_key_debug!(ProjectionGroupKey, "ProjectionGroupKey");
621redacted_key_debug!(ProjectionGroupPrefix, "ProjectionGroupPrefix");
622redacted_key_debug!(ProjectionFrontierKey, "ProjectionFrontierKey");
623redacted_key_debug!(ProjectionApplyKey, "ProjectionApplyKey");
624
625impl TryFrom<Vec<u8>> for ProjectionGroupKey {
626    type Error = ProjectionKeyError;
627
628    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
629        Self::from_bytes(value)
630    }
631}
632
633impl TryFrom<Vec<u8>> for ProjectionGroupPrefix {
634    type Error = ProjectionKeyError;
635
636    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
637        Self::from_bytes(value)
638    }
639}
640
641impl TryFrom<Vec<u8>> for ProjectionFrontierKey {
642    type Error = ProjectionKeyError;
643
644    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
645        Self::from_bytes(value)
646    }
647}
648
649impl TryFrom<Vec<u8>> for ProjectionApplyKey {
650    type Error = ProjectionKeyError;
651
652    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
653        Self::from_bytes(value)
654    }
655}
656
657/// A safe failure to construct or decode a projection identity or key.
658#[derive(Clone, Copy, Debug, Eq, PartialEq)]
659pub enum ProjectionKeyError {
660    /// The complete key exceeds the durable key hard limit.
661    TooLong {
662        /// Attempted encoded byte length.
663        actual: usize,
664        /// Maximum encoded byte length.
665        maximum: usize,
666    },
667    /// The input ends before a required field is complete.
668    Truncated,
669    /// The input contains bytes after its complete fixed shape.
670    TrailingBytes,
671    /// The fixed shape has either missing or trailing bytes.
672    TruncatedOrTrailing,
673    /// The key belongs to another purpose namespace.
674    WrongPurpose,
675    /// The key uses an unsupported key format version.
676    UnsupportedVersion,
677    /// The projection lineage or identity payload is invalid.
678    InvalidIdentity,
679    /// A projection ID uses the forbidden zero sentinel.
680    ZeroProjectionId,
681    /// A projection generation uses the forbidden zero sentinel.
682    ZeroGeneration,
683    /// A commit sequence uses the forbidden zero sentinel.
684    ZeroCommitSequence,
685    /// A complete group key contains no component.
686    EmptyGroupKey,
687    /// A framed component has a forbidden zero byte length.
688    EmptyComponent,
689    /// The component count exceeds the v1 hard limit.
690    TooManyComponents,
691    /// Null, list, or record is not a projection group scalar.
692    NonScalarComponent,
693    /// A component is not one exact canonical scalar encoding.
694    InvalidCanonicalComponent,
695}
696
697impl fmt::Display for ProjectionKeyError {
698    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
699        formatter.write_str(match self {
700            Self::TooLong { .. } => "projection key exceeds the hard byte limit",
701            Self::Truncated => "projection key is truncated",
702            Self::TrailingBytes => "projection key has trailing bytes",
703            Self::TruncatedOrTrailing => "projection key has the wrong fixed length",
704            Self::WrongPurpose => "projection key has the wrong purpose",
705            Self::UnsupportedVersion => "projection key version is unsupported",
706            Self::InvalidIdentity => "projection identity is invalid",
707            Self::ZeroProjectionId => "projection ID must be nonzero",
708            Self::ZeroGeneration => "projection generation must be nonzero",
709            Self::ZeroCommitSequence => "commit sequence must be nonzero",
710            Self::EmptyGroupKey => "complete projection group key has no components",
711            Self::EmptyComponent => "projection group component is empty",
712            Self::TooManyComponents => "projection group has too many components",
713            Self::NonScalarComponent => "projection group component is not a scalar",
714            Self::InvalidCanonicalComponent => {
715                "projection group component is not canonically encoded"
716            }
717        })
718    }
719}
720
721impl Error for ProjectionKeyError {}
722
723struct ProjectionGroupParts {
724    identity: ProjectionIdentity,
725    generation: ProjectionGeneration,
726    components: Vec<CanonicalValue>,
727}
728
729fn decode_group_parts(
730    bytes: &[u8],
731    require_nonempty: bool,
732) -> Result<ProjectionGroupParts, ProjectionKeyError> {
733    validate_key_bound(bytes)?;
734    validate_prefix(bytes, PROJECTION_GROUP_KEY_V1_PREFIX)?;
735    let (identity, consumed) = decode_identity_prefix(&bytes[2..])?;
736    let mut position = 2 + consumed;
737    let generation = decode_generation(read_array::<8>(bytes, &mut position)?)?;
738    let mut components = Vec::new();
739    while position < bytes.len() {
740        if components.len() == MAX_PROJECTION_GROUP_COMPONENTS {
741            return Err(ProjectionKeyError::TooManyComponents);
742        }
743        let length = u32::from_be_bytes(read_array::<4>(bytes, &mut position)?) as usize;
744        if length == 0 {
745            return Err(ProjectionKeyError::EmptyComponent);
746        }
747        let end = position
748            .checked_add(length)
749            .ok_or(ProjectionKeyError::Truncated)?;
750        let encoded = bytes
751            .get(position..end)
752            .ok_or(ProjectionKeyError::Truncated)?;
753        let value = decode_canonical_value(encoded)
754            .map_err(|_| ProjectionKeyError::InvalidCanonicalComponent)?;
755        projection_scalar_encoded_length(&value)?;
756        let canonical = encode_canonical_value(&value)
757            .map_err(|_| ProjectionKeyError::InvalidCanonicalComponent)?;
758        if canonical != encoded {
759            return Err(ProjectionKeyError::InvalidCanonicalComponent);
760        }
761        components.push(value);
762        position = end;
763    }
764    if require_nonempty && components.is_empty() {
765        return Err(ProjectionKeyError::EmptyGroupKey);
766    }
767    Ok(ProjectionGroupParts {
768        identity,
769        generation,
770        components,
771    })
772}
773
774fn decode_identity_prefix(bytes: &[u8]) -> Result<(ProjectionIdentity, usize), ProjectionKeyError> {
775    let mut position = 0;
776    let lineage_length = u32::from_be_bytes(read_array::<4>(bytes, &mut position)?) as usize;
777    if lineage_length == 0 || lineage_length > MAX_CONTRACT_LINEAGE_BYTES {
778        return Err(ProjectionKeyError::InvalidIdentity);
779    }
780    let lineage_end = position
781        .checked_add(lineage_length)
782        .ok_or(ProjectionKeyError::InvalidIdentity)?;
783    let lineage_bytes = bytes
784        .get(position..lineage_end)
785        .ok_or(ProjectionKeyError::Truncated)?;
786    let lineage =
787        std::str::from_utf8(lineage_bytes).map_err(|_| ProjectionKeyError::InvalidIdentity)?;
788    let lineage = ContractLineage::new(lineage).map_err(|_| ProjectionKeyError::InvalidIdentity)?;
789    position = lineage_end;
790
791    let projection_raw = u32::from_be_bytes(read_array::<4>(bytes, &mut position)?);
792    if projection_raw == 0 {
793        return Err(ProjectionKeyError::ZeroProjectionId);
794    }
795    let projection_id =
796        ProjectionId::try_from(projection_raw).map_err(|_| ProjectionKeyError::ZeroProjectionId)?;
797    let plan_hash = ProjectionPlanHash::from_bytes(read_array::<32>(bytes, &mut position)?);
798    Ok((
799        ProjectionIdentity::new(lineage, projection_id, plan_hash),
800        position,
801    ))
802}
803
804fn projection_scalar_encoded_length(value: &CanonicalValue) -> Result<usize, ProjectionKeyError> {
805    match value {
806        CanonicalValue::Null
807        | CanonicalValue::List(_)
808        | CanonicalValue::Record(_)
809        | CanonicalValue::Vector(_) => Err(ProjectionKeyError::NonScalarComponent),
810        CanonicalValue::Bool(_) => Ok(3),
811        CanonicalValue::I64(_) | CanonicalValue::U64(_) => Ok(10),
812        CanonicalValue::Decimal(_) => Ok(20),
813        CanonicalValue::Money(_) => Ok(23),
814        CanonicalValue::String(value) => value
815            .len()
816            .checked_add(6)
817            .ok_or(ProjectionKeyError::InvalidCanonicalComponent),
818        CanonicalValue::Bytes(value) => value
819            .len()
820            .checked_add(6)
821            .ok_or(ProjectionKeyError::InvalidCanonicalComponent),
822        CanonicalValue::Timestamp(_) => Ok(14),
823        CanonicalValue::Date(_) => Ok(6),
824        CanonicalValue::Uuid(_) => Ok(18),
825        CanonicalValue::Enum { .. } => Ok(10),
826    }
827}
828
829fn validate_key_bound(bytes: &[u8]) -> Result<(), ProjectionKeyError> {
830    if bytes.len() > MAX_KEY_BYTES {
831        return Err(ProjectionKeyError::TooLong {
832            actual: bytes.len(),
833            maximum: MAX_KEY_BYTES,
834        });
835    }
836    Ok(())
837}
838
839fn validate_prefix(bytes: &[u8], expected: [u8; 2]) -> Result<(), ProjectionKeyError> {
840    let purpose = *bytes.first().ok_or(ProjectionKeyError::Truncated)?;
841    if purpose != expected[0] {
842        return Err(ProjectionKeyError::WrongPurpose);
843    }
844    let version = *bytes.get(1).ok_or(ProjectionKeyError::Truncated)?;
845    if version != expected[1] {
846        return Err(ProjectionKeyError::UnsupportedVersion);
847    }
848    Ok(())
849}
850
851fn decode_generation(bytes: [u8; 8]) -> Result<ProjectionGeneration, ProjectionKeyError> {
852    ProjectionGeneration::new(u64::from_be_bytes(bytes)).ok_or(ProjectionKeyError::ZeroGeneration)
853}
854
855fn decode_commit_sequence(bytes: [u8; 8]) -> Result<CommitSequence, ProjectionKeyError> {
856    let value = u64::from_be_bytes(bytes);
857    if value == 0 {
858        return Err(ProjectionKeyError::ZeroCommitSequence);
859    }
860    CommitSequence::try_from(value).map_err(|_| ProjectionKeyError::ZeroCommitSequence)
861}
862
863fn read_array<const N: usize>(
864    bytes: &[u8],
865    position: &mut usize,
866) -> Result<[u8; N], ProjectionKeyError> {
867    let end = position
868        .checked_add(N)
869        .ok_or(ProjectionKeyError::Truncated)?;
870    let value = bytes
871        .get(*position..end)
872        .ok_or(ProjectionKeyError::Truncated)?;
873    *position = end;
874    value.try_into().map_err(|_| ProjectionKeyError::Truncated)
875}
876
877#[cfg(test)]
878mod tests {
879    use proptest::prelude::*;
880
881    use super::*;
882
883    fn projection_id(value: u32) -> ProjectionId {
884        ProjectionId::try_from(value).expect("nonzero projection ID")
885    }
886
887    fn commit_sequence(value: u64) -> CommitSequence {
888        CommitSequence::try_from(value).expect("nonzero commit sequence")
889    }
890
891    fn identity() -> ProjectionIdentity {
892        ProjectionIdentity::new(
893            ContractLineage::new("budget").expect("valid lineage"),
894            projection_id(0x0102_0304),
895            ProjectionPlanHash::from_bytes(std::array::from_fn(|index| index as u8)),
896        )
897    }
898
899    fn generation() -> ProjectionGeneration {
900        ProjectionGeneration::new(7).expect("nonzero generation")
901    }
902
903    fn hex(input: &str) -> Vec<u8> {
904        input
905            .as_bytes()
906            .chunks_exact(2)
907            .map(|pair| {
908                let pair = std::str::from_utf8(pair).expect("ASCII hex");
909                u8::from_str_radix(pair, 16).expect("valid hex")
910            })
911            .collect()
912    }
913
914    #[test]
915    fn identity_and_fixed_key_vectors_are_stable() {
916        let identity = identity();
917        let identity_bytes = hex(
918            "0000000662756467657401020304000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
919        );
920        assert_eq!(identity.to_canonical_bytes(), identity_bytes);
921        assert_eq!(
922            ProjectionIdentity::from_canonical_bytes(&identity_bytes),
923            Ok(identity.clone())
924        );
925
926        let frontier = ProjectionFrontierKey::new(identity.clone());
927        let mut expected_frontier = vec![0x46, 0x01];
928        expected_frontier.extend_from_slice(&identity_bytes);
929        assert_eq!(frontier.as_bytes(), expected_frontier);
930        assert_eq!(
931            ProjectionFrontierKey::from_bytes(expected_frontier),
932            Ok(frontier)
933        );
934
935        let apply = ProjectionApplyKey::new(identity, generation(), commit_sequence(9));
936        let mut expected_apply = vec![0x41, 0x01];
937        expected_apply.extend_from_slice(&identity_bytes);
938        expected_apply.extend_from_slice(&7_u64.to_be_bytes());
939        expected_apply.extend_from_slice(&9_u64.to_be_bytes());
940        assert_eq!(apply.as_bytes(), expected_apply);
941        assert_eq!(ProjectionApplyKey::from_bytes(expected_apply), Ok(apply));
942    }
943
944    #[test]
945    fn complete_and_prefix_group_vectors_are_stable() {
946        let mut complete = ProjectionGroupKeyBuilder::new(identity(), generation());
947        complete
948            .push_component(CanonicalValue::Bool(true))
949            .expect("bounded")
950            .push_component(CanonicalValue::U64(5))
951            .expect("bounded");
952        let complete = complete.finish().expect("nonempty complete key");
953
954        let mut expected = vec![0x47, 0x01];
955        expected.extend_from_slice(&identity().to_canonical_bytes());
956        expected.extend_from_slice(&7_u64.to_be_bytes());
957        expected.extend_from_slice(&3_u32.to_be_bytes());
958        expected.extend_from_slice(&[0x01, 0x01, 0x01]);
959        expected.extend_from_slice(&10_u32.to_be_bytes());
960        expected.extend_from_slice(&[0x01, 0x03]);
961        expected.extend_from_slice(&5_u64.to_be_bytes());
962        assert_eq!(complete.as_bytes(), expected);
963        assert_eq!(
964            ProjectionGroupKey::from_bytes(expected),
965            Ok(complete.clone())
966        );
967
968        let empty_prefix = ProjectionGroupPrefixBuilder::new(identity(), generation()).finish();
969        assert!(complete.as_bytes().starts_with(empty_prefix.as_bytes()));
970
971        let mut one_prefix = ProjectionGroupPrefixBuilder::new(identity(), generation());
972        one_prefix
973            .push_component(CanonicalValue::Bool(true))
974            .expect("bounded");
975        let one_prefix = one_prefix.finish();
976        assert!(complete.as_bytes().starts_with(one_prefix.as_bytes()));
977        assert!(complete.as_bytes() < one_prefix.exclusive_successor().as_slice());
978        assert_eq!(
979            ProjectionGroupPrefix::from_bytes(one_prefix.as_bytes().to_vec()),
980            Ok(one_prefix)
981        );
982    }
983
984    #[test]
985    fn every_projection_scalar_has_a_complete_key_and_prefix_golden() {
986        let decimal_spec = crate::DecimalSpec::new(5, 2).expect("valid decimal type");
987        let decimal = crate::Decimal::new(decimal_spec, -1234).expect("bounded decimal");
988        let values = vec![
989            CanonicalValue::Bool(true),
990            CanonicalValue::I64(-1),
991            CanonicalValue::U64(u64::MAX),
992            CanonicalValue::Decimal(decimal),
993            CanonicalValue::Money(crate::Money::new(
994                crate::CurrencyCode::new("USD").expect("valid currency"),
995                decimal,
996            )),
997            CanonicalValue::string("é").expect("bounded string"),
998            CanonicalValue::bytes([0, 0xff]).expect("bounded bytes"),
999            CanonicalValue::Timestamp(
1000                crate::Timestamp::new(-1, 999_999_999).expect("valid timestamp"),
1001            ),
1002            CanonicalValue::Date(crate::Date::from_days_since_unix_epoch(-1)),
1003            CanonicalValue::Uuid([
1004                0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
1005                0x0e, 0x0f,
1006            ]),
1007            CanonicalValue::Enum {
1008                type_id: crate::EnumTypeId::new(42).expect("nonzero enum type ID"),
1009                variant_id: crate::EnumVariantId::new(7).expect("nonzero enum variant ID"),
1010            },
1011        ];
1012        let encoded_components = [
1013            "010101",
1014            "0102ffffffffffffffff",
1015            "0103ffffffffffffffff",
1016            "01040502fffffffffffffffffffffffffffffb2e",
1017            "01055553440502fffffffffffffffffffffffffffffb2e",
1018            "010600000002c3a9",
1019            "01070000000200ff",
1020            "0108ffffffffffffffff3b9ac9ff",
1021            "0109ffffffff",
1022            "010a000102030405060708090a0b0c0d0e0f",
1023            "010b0000002a00000007",
1024        ]
1025        .map(hex);
1026        assert_eq!(
1027            encoded_components.each_ref().map(|value| value.len()),
1028            [3, 10, 10, 20, 23, 8, 8, 14, 6, 18, 10]
1029        );
1030
1031        let mut complete = ProjectionGroupKeyBuilder::new(identity(), generation());
1032        let mut prefix = ProjectionGroupPrefixBuilder::new(identity(), generation());
1033        for value in &values {
1034            complete
1035                .push_component(value.clone())
1036                .expect("scalar key remains bounded");
1037            prefix
1038                .push_component(value.clone())
1039                .expect("scalar prefix remains bounded");
1040        }
1041        let complete = complete.finish().expect("nonempty complete key");
1042        let prefix = prefix.finish();
1043
1044        let mut expected = vec![0x47, 0x01];
1045        expected.extend_from_slice(&identity().to_canonical_bytes());
1046        expected.extend_from_slice(&generation().to_be_bytes());
1047        for component in encoded_components {
1048            expected.extend_from_slice(&(component.len() as u32).to_be_bytes());
1049            expected.extend_from_slice(&component);
1050        }
1051        assert_eq!(complete.as_bytes(), expected);
1052        assert_eq!(prefix.as_bytes(), expected);
1053        assert_eq!(complete.components(), values);
1054        assert_eq!(prefix.components(), values);
1055        assert_eq!(
1056            ProjectionGroupKey::from_bytes(expected.clone()),
1057            Ok(complete)
1058        );
1059        assert_eq!(ProjectionGroupPrefix::from_bytes(expected), Ok(prefix));
1060    }
1061
1062    #[test]
1063    fn generations_are_nonzero_checked_and_never_wrap() {
1064        assert_eq!(ProjectionGeneration::new(0), None);
1065        assert_eq!(ProjectionGeneration::first().get(), 1);
1066        assert_eq!(
1067            ProjectionGeneration::first().checked_next(),
1068            ProjectionGeneration::new(2)
1069        );
1070        assert_eq!(
1071            ProjectionGeneration::new(u64::MAX)
1072                .expect("nonzero")
1073                .checked_next(),
1074            None
1075        );
1076    }
1077
1078    #[test]
1079    fn group_builder_rejects_non_scalars_and_oversized_components_without_mutation() {
1080        let mut builder = ProjectionGroupKeyBuilder::new(identity(), generation());
1081        let before = builder.as_bytes().to_vec();
1082        assert!(matches!(
1083            builder.push_component(CanonicalValue::Null),
1084            Err(ProjectionKeyError::NonScalarComponent)
1085        ));
1086        assert_eq!(builder.as_bytes(), before);
1087
1088        let oversized = CanonicalValue::string("x".repeat(MAX_KEY_BYTES))
1089            .expect("within canonical document limit");
1090        assert!(matches!(
1091            builder.push_component(oversized),
1092            Err(ProjectionKeyError::TooLong { .. })
1093        ));
1094        assert_eq!(builder.as_bytes(), before);
1095        assert_eq!(builder.finish(), Err(ProjectionKeyError::EmptyGroupKey));
1096    }
1097
1098    #[test]
1099    fn complete_group_key_honors_the_exact_four_kibibyte_boundary() {
1100        let base = ProjectionGroupKeyBuilder::new(identity(), generation())
1101            .as_bytes()
1102            .len();
1103        let exact_string_bytes = MAX_KEY_BYTES - base - 4 - 6;
1104
1105        let mut exact = ProjectionGroupKeyBuilder::new(identity(), generation());
1106        exact
1107            .push_component(
1108                CanonicalValue::string("x".repeat(exact_string_bytes)).expect("bounded value"),
1109            )
1110            .expect("exactly four KiB");
1111        let exact = exact.finish().expect("complete key");
1112        assert_eq!(exact.as_bytes().len(), MAX_KEY_BYTES);
1113        assert_eq!(
1114            ProjectionGroupKey::from_bytes(exact.as_bytes().to_vec()),
1115            Ok(exact)
1116        );
1117
1118        let mut too_large = ProjectionGroupKeyBuilder::new(identity(), generation());
1119        assert!(matches!(
1120            too_large.push_component(
1121                CanonicalValue::string("x".repeat(exact_string_bytes + 1)).expect("bounded value"),
1122            ),
1123            Err(ProjectionKeyError::TooLong {
1124                actual: 4_097,
1125                maximum: MAX_KEY_BYTES,
1126            })
1127        ));
1128    }
1129
1130    #[test]
1131    fn decoders_reject_zero_and_malformed_fields() {
1132        let identity_bytes = identity().to_canonical_bytes();
1133        let projection_id_offset = 4 + "budget".len();
1134        let mut zero_id = identity_bytes.clone();
1135        zero_id[projection_id_offset..projection_id_offset + 4].fill(0);
1136        assert_eq!(
1137            ProjectionIdentity::from_canonical_bytes(&zero_id),
1138            Err(ProjectionKeyError::ZeroProjectionId)
1139        );
1140
1141        let mut zero_generation = ProjectionGroupPrefixBuilder::new(identity(), generation())
1142            .finish()
1143            .as_bytes()
1144            .to_vec();
1145        let generation_offset = 2 + identity_bytes.len();
1146        zero_generation[generation_offset..generation_offset + 8].fill(0);
1147        assert_eq!(
1148            ProjectionGroupPrefix::from_bytes(zero_generation),
1149            Err(ProjectionKeyError::ZeroGeneration)
1150        );
1151
1152        let mut zero_sequence =
1153            ProjectionApplyKey::new(identity(), generation(), commit_sequence(9))
1154                .as_bytes()
1155                .to_vec();
1156        let sequence_offset = 2 + identity_bytes.len() + 8;
1157        zero_sequence[sequence_offset..sequence_offset + 8].fill(0);
1158        assert_eq!(
1159            ProjectionApplyKey::from_bytes(zero_sequence),
1160            Err(ProjectionKeyError::ZeroCommitSequence)
1161        );
1162
1163        let mut wrong_purpose = ProjectionFrontierKey::new(identity()).as_bytes().to_vec();
1164        wrong_purpose[0] = 0x45;
1165        assert_eq!(
1166            ProjectionFrontierKey::from_bytes(wrong_purpose),
1167            Err(ProjectionKeyError::WrongPurpose)
1168        );
1169    }
1170
1171    #[test]
1172    fn group_key_formatting_never_exposes_application_values() {
1173        let mut builder = ProjectionGroupKeyBuilder::new(identity(), generation());
1174        builder
1175            .push_component(CanonicalValue::string("TOP_SECRET").expect("bounded"))
1176            .expect("bounded key");
1177        let key = builder.finish().expect("complete key");
1178        let debug = format!("{key:?}");
1179        assert!(debug.contains("[REDACTED]"));
1180        assert!(!debug.contains("TOP_SECRET"));
1181    }
1182
1183    proptest! {
1184        #[test]
1185        fn canonical_group_keys_round_trip_for_bounded_scalar_sequences(
1186            values in proptest::collection::vec(any::<bool>(), 1..64),
1187        ) {
1188            let mut builder = ProjectionGroupKeyBuilder::new(identity(), generation());
1189            for value in values {
1190                builder
1191                    .push_component(CanonicalValue::Bool(value))
1192                    .expect("generated key is bounded");
1193            }
1194            let key = builder.finish().expect("nonempty complete key");
1195            prop_assert_eq!(
1196                ProjectionGroupKey::from_bytes(key.as_bytes().to_vec()),
1197                Ok(key)
1198            );
1199        }
1200
1201        #[test]
1202        fn arbitrary_projection_key_bytes_never_panic(
1203            bytes in proptest::collection::vec(any::<u8>(), 0..5_000),
1204        ) {
1205            let _ = ProjectionGroupKey::from_bytes(bytes.clone());
1206            let _ = ProjectionGroupPrefix::from_bytes(bytes.clone());
1207            let _ = ProjectionFrontierKey::from_bytes(bytes.clone());
1208            let _ = ProjectionApplyKey::from_bytes(bytes);
1209        }
1210    }
1211}
1212
1213// Exhaustive non-durable ownership inventories for retained provider data.
1214crate::inspect_owned_capacity_copy_v1!(ProjectionGeneration);
1215
1216crate::inspect_owned_capacity_struct_v1!(ProjectionIdentity {
1217    owned: [contract_lineage],
1218    inline: [projection_id, plan_hash]
1219});
1220crate::inspect_owned_capacity_struct_v1!(ProjectionGroupKey {
1221    owned: [bytes, identity, components],
1222    inline: [generation]
1223});