Skip to main content

laser_wire/
checkpoint.rs

1use crate::agent::wire_id;
2use crate::authz::{
3    Action, Feature, SUPERVISOR_ASSERTION_KEY_ID_BYTES, SupervisorActorAssertion,
4    SupervisorAssertionAction,
5};
6use crate::destination::{
7    BackendBinding, DestinationDesiredState, DestinationId, MaterializationDestination,
8    ProjectionRef, QueryRoute, QueryRouteId,
9};
10use crate::error::InvalidError;
11use crate::schema::{Digest32, LogicalSchemaRef, SchemaFingerprint, TypedValue, UuidValue};
12use crate::source::SourceIncarnation;
13use crate::validate::Validate;
14use serde::{Deserialize, Serialize};
15use std::collections::BTreeSet;
16
17pub const MAX_CHECKPOINT_PARTITIONS: usize = 65_536;
18pub const MAX_ATTEMPT_OBJECTS: usize = 100_000;
19pub const MAX_CREDENTIAL_GENERATIONS: usize = 32;
20pub const MAX_MANIFEST_IDENTITY_BYTES: usize = 4_096;
21pub const MAX_CHECKPOINT_ERROR_BYTES: usize = 4_096;
22pub const MAX_REPAIR_DETAIL_BYTES: usize = 4_096;
23pub const MAX_CHECKPOINT_LEASE_DURATION_MICROS: u64 = 300_000_000;
24
25wire_id!(
26    /// Stable identity of one Plane process incarnation that may own a destination.
27    CheckpointOwnerId
28);
29
30wire_id!(
31    /// Immutable identity of one prepared materialization attempt.
32    PreparedAttemptId
33);
34
35wire_id!(
36    /// Stable request identity used to deduplicate a checkpoint mutation retry.
37    CheckpointRequestId
38);
39
40#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
41pub struct PartitionCheckpoint {
42    pub incarnation: SourceIncarnation,
43    pub started_at_offset: u64,
44    pub next_offset: u64,
45    pub lifecycle: PartitionLifecycleState,
46}
47
48impl Validate for PartitionCheckpoint {
49    fn validate(&self) -> Result<(), InvalidError> {
50        self.incarnation.validate()?;
51        if self.started_at_offset > self.next_offset {
52            return Err(InvalidError::new(
53                "partition start offset must not exceed its next offset",
54            ));
55        }
56        Ok(())
57    }
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(rename_all = "snake_case")]
62#[non_exhaustive]
63pub enum PartitionLifecycleState {
64    Active,
65    Removed,
66    Recreated,
67}
68
69#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
70pub struct SourceOffsetRange {
71    pub incarnation: SourceIncarnation,
72    pub start: u64,
73    pub end_exclusive: u64,
74}
75
76impl Validate for SourceOffsetRange {
77    fn validate(&self) -> Result<(), InvalidError> {
78        self.incarnation.validate()?;
79        if self.start >= self.end_exclusive {
80            return Err(InvalidError::new(format!(
81                "source range [{}, {}) is empty or inverted",
82                self.start, self.end_exclusive
83            )));
84        }
85        Ok(())
86    }
87}
88
89#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
90pub struct CheckpointOwnerLease {
91    pub owner: CheckpointOwnerId,
92    pub epoch: u64,
93    pub sequence: u64,
94    pub deadline_micros: u64,
95}
96
97impl Validate for CheckpointOwnerLease {
98    fn validate(&self) -> Result<(), InvalidError> {
99        if self.owner.as_u128() == 0 {
100            return Err(InvalidError::new("checkpoint owner id must be nonzero"));
101        }
102        if self.epoch == 0 || self.sequence == 0 || self.deadline_micros == 0 {
103            return Err(InvalidError::new(
104                "checkpoint lease epoch, sequence, and deadline must be nonzero",
105            ));
106        }
107        Ok(())
108    }
109}
110
111#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
112pub struct CredentialGeneration {
113    pub role: String,
114    pub generation: u64,
115}
116
117impl Validate for CredentialGeneration {
118    fn validate(&self) -> Result<(), InvalidError> {
119        if self.role.is_empty() || self.role.len() > 64 {
120            return Err(InvalidError::new(
121                "credential role must contain 1..=64 bytes",
122            ));
123        }
124        if self
125            .role
126            .bytes()
127            .any(|byte| !matches!(byte, b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_'))
128        {
129            return Err(InvalidError::new(
130                "credential role contains a disallowed byte",
131            ));
132        }
133        if self.generation == 0 {
134            return Err(InvalidError::new("credential generation must be nonzero"));
135        }
136        Ok(())
137    }
138}
139
140#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
141pub struct AttemptObject {
142    pub identity: String,
143    pub size_bytes: u64,
144    pub row_count: u64,
145    pub sha256: Digest32,
146    pub columns: Vec<AttemptColumnMetrics>,
147}
148
149#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
150pub struct AttemptColumnMetrics {
151    pub field_id: u32,
152    pub value_count: u64,
153    pub null_count: u64,
154    pub nan_count: u64,
155    #[serde(default, skip_serializing_if = "Option::is_none")]
156    pub lower_bound: Option<TypedValue>,
157    #[serde(default, skip_serializing_if = "Option::is_none")]
158    pub upper_bound: Option<TypedValue>,
159}
160
161impl Validate for AttemptObject {
162    fn validate(&self) -> Result<(), InvalidError> {
163        validate_object_identity(&self.identity)?;
164        if self.size_bytes == 0 {
165            return Err(InvalidError::new("attempt object size must be nonzero"));
166        }
167        if self.row_count == 0 {
168            return Err(InvalidError::new(
169                "attempt object row count must be nonzero",
170            ));
171        }
172        self.sha256.validate()?;
173        if self.columns.len() > crate::schema::MAX_LOGICAL_SCHEMA_FIELDS {
174            return Err(InvalidError::new(
175                "attempt object column metric count exceeds the schema field cap",
176            ));
177        }
178        let mut fields = BTreeSet::new();
179        for column in &self.columns {
180            column.validate()?;
181            if !fields.insert(column.field_id) {
182                return Err(InvalidError::new(format!(
183                    "attempt object repeats column metric {}",
184                    column.field_id
185                )));
186            }
187        }
188        Ok(())
189    }
190}
191
192impl Validate for AttemptColumnMetrics {
193    fn validate(&self) -> Result<(), InvalidError> {
194        if self.field_id == 0
195            || self.null_count > self.value_count
196            || self.nan_count > self.value_count.saturating_sub(self.null_count)
197        {
198            return Err(InvalidError::new("attempt column metrics are invalid"));
199        }
200        if let Some(value) = &self.lower_bound {
201            value.validate()?;
202        }
203        if let Some(value) = &self.upper_bound {
204            value.validate()?;
205        }
206        if self.lower_bound.is_some() != self.upper_bound.is_some() {
207            return Err(InvalidError::new(
208                "attempt column bounds must be both present or both absent",
209            ));
210        }
211        Ok(())
212    }
213}
214
215#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
216pub struct PreparedTableRequirements {
217    pub table_uuid: UuidValue,
218    pub base_metadata_identity: String,
219    #[serde(default, skip_serializing_if = "Option::is_none")]
220    pub base_snapshot_id: Option<i64>,
221    pub schema_id: i32,
222    pub partition_spec_id: i32,
223    pub commit_requirements: Vec<IcebergCommitRequirement>,
224}
225
226#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
227#[serde(tag = "kind", rename_all = "snake_case")]
228#[non_exhaustive]
229pub enum IcebergCommitRequirement {
230    AssertTableUuid { table_uuid: UuidValue },
231    AssertMetadataIdentity { identity: String },
232    AssertCurrentSnapshot { snapshot_id: Option<i64> },
233    AssertCurrentSchema { schema_id: i32 },
234    AssertDefaultPartitionSpec { partition_spec_id: i32 },
235}
236
237impl Validate for PreparedTableRequirements {
238    fn validate(&self) -> Result<(), InvalidError> {
239        validate_uuid(&self.table_uuid)?;
240        validate_object_identity(&self.base_metadata_identity)?;
241        if self
242            .base_snapshot_id
243            .is_some_and(|snapshot_id| snapshot_id <= 0)
244            || self.schema_id < 0
245            || self.partition_spec_id < 0
246        {
247            return Err(InvalidError::new(
248                "prepared table snapshot, schema, or partition spec is invalid",
249            ));
250        }
251        let expected = [
252            IcebergCommitRequirement::AssertTableUuid {
253                table_uuid: self.table_uuid.clone(),
254            },
255            IcebergCommitRequirement::AssertMetadataIdentity {
256                identity: self.base_metadata_identity.clone(),
257            },
258            IcebergCommitRequirement::AssertCurrentSnapshot {
259                snapshot_id: self.base_snapshot_id,
260            },
261            IcebergCommitRequirement::AssertCurrentSchema {
262                schema_id: self.schema_id,
263            },
264            IcebergCommitRequirement::AssertDefaultPartitionSpec {
265                partition_spec_id: self.partition_spec_id,
266            },
267        ];
268        if self.commit_requirements.as_slice() != expected.as_slice() {
269            return Err(InvalidError::new(
270                "Iceberg commit requirements must contain the exact frozen table preconditions in canonical order",
271            ));
272        }
273        Ok(())
274    }
275}
276
277#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
278pub struct PreparedAttempt {
279    pub id: PreparedAttemptId,
280    pub destination_id: DestinationId,
281    pub destination_generation: u64,
282    pub backend: BackendBinding,
283    pub owner: CheckpointOwnerId,
284    pub epoch: u64,
285    pub created_at_checkpoint_revision: u64,
286    pub table: PreparedTableRequirements,
287    pub schema_fingerprint: SchemaFingerprint,
288    pub projection: ProjectionRef,
289    pub ranges: Vec<SourceOffsetRange>,
290    pub resulting_boundary: Vec<PartitionCheckpoint>,
291    pub resulting_boundary_digest: Digest32,
292    pub manifest_identity: String,
293    pub manifest_digest: Digest32,
294    pub objects: Vec<AttemptObject>,
295    pub credential_generations: Vec<CredentialGeneration>,
296}
297
298impl Validate for PreparedAttempt {
299    fn validate(&self) -> Result<(), InvalidError> {
300        validate_attempt_identity(self.id, self.destination_id, self.destination_generation)?;
301        self.backend.validate()?;
302        if self.owner.as_u128() == 0 || self.epoch == 0 {
303            return Err(InvalidError::new(
304                "prepared attempt owner and epoch must be nonzero",
305            ));
306        }
307        if self.created_at_checkpoint_revision == 0 {
308            return Err(InvalidError::new(
309                "prepared attempt creation revision must be nonzero",
310            ));
311        }
312        self.table.validate()?;
313        self.schema_fingerprint.validate()?;
314        self.projection.validate()?;
315        self.resulting_boundary_digest.validate()?;
316        self.manifest_digest.validate()?;
317        validate_ranges(&self.ranges)?;
318        validate_resulting_boundary(&self.resulting_boundary, &self.ranges)?;
319        validate_object_identity(&self.manifest_identity)?;
320        if self.objects.is_empty() || self.objects.len() > MAX_ATTEMPT_OBJECTS {
321            return Err(InvalidError::new(format!(
322                "prepared attempt must contain 1..={MAX_ATTEMPT_OBJECTS} objects"
323            )));
324        }
325        let mut object_identities = BTreeSet::new();
326        for object in &self.objects {
327            object.validate()?;
328            if !object_identities.insert(&object.identity) {
329                return Err(InvalidError::new(format!(
330                    "prepared attempt repeats object `{}`",
331                    object.identity
332                )));
333            }
334        }
335        validate_credential_generations(&self.credential_generations)
336    }
337}
338
339#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
340pub struct PreparedAttemptSummary {
341    pub id: PreparedAttemptId,
342    pub owner: CheckpointOwnerId,
343    pub epoch: u64,
344    pub table: PreparedTableRequirements,
345    pub schema_fingerprint: SchemaFingerprint,
346    pub projection: ProjectionRef,
347    pub manifest_identity: String,
348    pub manifest_digest: Digest32,
349    pub resulting_boundary_digest: Digest32,
350    pub ranges: Vec<SourceOffsetRange>,
351    pub object_count: u32,
352    pub credential_generations: Vec<CredentialGeneration>,
353}
354
355impl Validate for PreparedAttemptSummary {
356    fn validate(&self) -> Result<(), InvalidError> {
357        if self.id.as_u128() == 0 || self.owner.as_u128() == 0 || self.epoch == 0 {
358            return Err(InvalidError::new(
359                "prepared attempt summary identity must be nonzero",
360            ));
361        }
362        self.table.validate()?;
363        self.schema_fingerprint.validate()?;
364        self.projection.validate()?;
365        validate_object_identity(&self.manifest_identity)?;
366        self.manifest_digest.validate()?;
367        self.resulting_boundary_digest.validate()?;
368        validate_ranges(&self.ranges)?;
369        if self.object_count == 0 || self.object_count as usize > MAX_ATTEMPT_OBJECTS {
370            return Err(InvalidError::new(
371                "prepared attempt summary object count is invalid",
372            ));
373        }
374        validate_credential_generations(&self.credential_generations)
375    }
376}
377
378#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
379pub struct CompletedAttempt {
380    pub id: PreparedAttemptId,
381    pub table_uuid: UuidValue,
382    pub snapshot_id: i64,
383    pub manifest_digest: Digest32,
384    pub resulting_boundary_digest: Digest32,
385    pub ranges: Vec<SourceOffsetRange>,
386    pub completion_revision: u64,
387}
388
389impl Validate for CompletedAttempt {
390    fn validate(&self) -> Result<(), InvalidError> {
391        if self.id.as_u128() == 0 {
392            return Err(InvalidError::new("completed attempt id must be nonzero"));
393        }
394        if self.table_uuid.as_bytes().len() != UuidValue::BYTES {
395            return Err(InvalidError::new("completed attempt table UUID is invalid"));
396        }
397        if self.snapshot_id <= 0 {
398            return Err(InvalidError::new(
399                "completed attempt snapshot id must be positive",
400            ));
401        }
402        self.manifest_digest.validate()?;
403        self.resulting_boundary_digest.validate()?;
404        if self.completion_revision == 0 {
405            return Err(InvalidError::new(
406                "completed attempt revision must be nonzero",
407            ));
408        }
409        validate_ranges(&self.ranges)
410    }
411}
412
413#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
414pub struct RetentionGap {
415    pub incarnation: SourceIncarnation,
416    pub required_next_offset: u64,
417    pub retained_start: u64,
418}
419
420impl Validate for RetentionGap {
421    fn validate(&self) -> Result<(), InvalidError> {
422        self.incarnation.validate()?;
423        if self.required_next_offset >= self.retained_start {
424            return Err(InvalidError::new(
425                "retention gap requires next offset below retained start",
426            ));
427        }
428        Ok(())
429    }
430}
431
432#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
433#[serde(rename_all = "snake_case")]
434#[non_exhaustive]
435pub enum DestinationBlockCode {
436    Decode,
437    Schema,
438    Projection,
439    Value,
440    Size,
441    RetentionGap,
442    PreparedAttempt,
443    BackendGeneration,
444    BackendUnavailable,
445    TableIdentity,
446    CatalogOutcomeUnknown,
447    SourceIncarnation,
448    Authorization,
449}
450
451#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
452pub struct DestinationBlock {
453    pub code: DestinationBlockCode,
454    pub message: String,
455    #[serde(default, skip_serializing_if = "Option::is_none")]
456    pub incarnation: Option<SourceIncarnation>,
457    #[serde(default, skip_serializing_if = "Option::is_none")]
458    pub offset: Option<u64>,
459    #[serde(default, skip_serializing_if = "Option::is_none")]
460    pub row_ordinal: Option<u32>,
461}
462
463impl Validate for DestinationBlock {
464    fn validate(&self) -> Result<(), InvalidError> {
465        if self.message.is_empty() || self.message.len() > MAX_CHECKPOINT_ERROR_BYTES {
466            return Err(InvalidError::new(format!(
467                "checkpoint error message must contain 1..={MAX_CHECKPOINT_ERROR_BYTES} bytes"
468            )));
469        }
470        if let Some(incarnation) = &self.incarnation {
471            incarnation.validate()?;
472        }
473        Ok(())
474    }
475}
476
477#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
478#[serde(rename_all = "snake_case")]
479#[non_exhaustive]
480pub enum CheckpointReadConsistency {
481    Linearizable,
482    #[default]
483    PotentiallyStale,
484}
485
486#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
487#[serde(rename_all = "snake_case")]
488#[non_exhaustive]
489pub enum DestinationEffectiveState {
490    Disabled,
491    WaitingForBackend,
492    Ready,
493    Running,
494    Blocked,
495}
496
497#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
498pub struct DestinationCheckpointStatus {
499    pub destination_id: DestinationId,
500    pub destination_generation: u64,
501    pub backend: BackendBinding,
502    pub schema: LogicalSchemaRef,
503    pub projection: ProjectionRef,
504    pub global_state_revision: u64,
505    pub definition_revision: u64,
506    pub checkpoint_revision: u64,
507    pub desired_state: DestinationDesiredState,
508    pub effective_state: DestinationEffectiveState,
509    #[serde(default, skip_serializing_if = "Option::is_none")]
510    pub table_uuid: Option<UuidValue>,
511    #[serde(default, skip_serializing_if = "Option::is_none")]
512    pub owner: Option<CheckpointOwnerLease>,
513    pub partitions: Vec<PartitionCheckpoint>,
514    #[serde(default, skip_serializing_if = "Option::is_none")]
515    pub prepared_attempt: Option<PreparedAttemptSummary>,
516    #[serde(default, skip_serializing_if = "Option::is_none")]
517    pub last_completion: Option<CompletedAttempt>,
518    #[serde(default, skip_serializing_if = "Option::is_none")]
519    pub retention_gap: Option<RetentionGap>,
520    #[serde(default, skip_serializing_if = "Option::is_none")]
521    pub block: Option<DestinationBlock>,
522    #[serde(default, skip_serializing_if = "Option::is_none")]
523    pub last_repair: Option<RepairRecord>,
524    pub consistency: CheckpointReadConsistency,
525}
526
527impl Validate for DestinationCheckpointStatus {
528    fn validate(&self) -> Result<(), InvalidError> {
529        if self.destination_id.as_u128() == 0 || self.destination_generation == 0 {
530            return Err(InvalidError::new(
531                "checkpoint status destination identity must be nonzero",
532            ));
533        }
534        self.backend.validate()?;
535        self.schema.validate()?;
536        self.projection.validate()?;
537        if self.global_state_revision == 0 || self.definition_revision == 0 {
538            return Err(InvalidError::new(
539                "checkpoint status global and definition revisions must be nonzero",
540            ));
541        }
542        if self.partitions.len() > MAX_CHECKPOINT_PARTITIONS {
543            return Err(InvalidError::new(format!(
544                "checkpoint status exceeds {MAX_CHECKPOINT_PARTITIONS} partitions"
545            )));
546        }
547        let mut partitions = BTreeSet::new();
548        let mut namespace = None;
549        for partition in &self.partitions {
550            partition.validate()?;
551            let current = (
552                partition.incarnation.cluster,
553                partition.incarnation.stream_id,
554                partition.incarnation.topic_id,
555            );
556            if namespace.is_some_and(|expected| expected != current) {
557                return Err(InvalidError::new(
558                    "checkpoint partitions must share one cluster, stream, and topic",
559                ));
560            }
561            namespace = Some(current);
562            if !partitions.insert(partition.incarnation.partition_id) {
563                return Err(InvalidError::new(format!(
564                    "checkpoint status repeats partition {}",
565                    partition.incarnation.partition_id
566                )));
567            }
568        }
569        if let Some(owner) = &self.owner {
570            owner.validate()?;
571        }
572        if let Some(table_uuid) = &self.table_uuid {
573            validate_uuid(table_uuid)?;
574        }
575        if let Some(prepared) = &self.prepared_attempt {
576            prepared.validate()?;
577        }
578        if let Some(completion) = &self.last_completion {
579            completion.validate()?;
580        }
581        if let Some(gap) = &self.retention_gap {
582            gap.validate()?;
583        }
584        if let Some(block) = &self.block {
585            block.validate()?;
586        }
587        if let Some(repair) = &self.last_repair {
588            repair.validate()?;
589        }
590        Ok(())
591    }
592}
593
594#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
595pub struct CheckpointRequestEnvelope {
596    pub v: u32,
597    pub request_id: CheckpointRequestId,
598    pub expected_global_state_revision: u64,
599    pub mutation: PublicCheckpointMutation,
600    #[serde(default, skip_serializing_if = "Option::is_none")]
601    pub supervisor_assertion: Option<SupervisorActorAssertion>,
602}
603
604impl CheckpointRequestEnvelope {
605    pub const fn new(
606        request_id: CheckpointRequestId,
607        expected_global_state_revision: u64,
608        mutation: PublicCheckpointMutation,
609    ) -> Self {
610        Self {
611            v: crate::codes::CHECKPOINT_OP_VERSION,
612            request_id,
613            expected_global_state_revision,
614            mutation,
615            supervisor_assertion: None,
616        }
617    }
618
619    #[must_use]
620    pub fn with_supervisor_assertion(mut self, assertion: SupervisorActorAssertion) -> Self {
621        self.supervisor_assertion = Some(assertion);
622        self
623    }
624}
625
626impl Validate for CheckpointRequestEnvelope {
627    fn validate(&self) -> Result<(), InvalidError> {
628        if self.v != crate::codes::CHECKPOINT_OP_VERSION {
629            return Err(InvalidError::new(format!(
630                "checkpoint version must be {}, got {}",
631                crate::codes::CHECKPOINT_OP_VERSION,
632                self.v
633            )));
634        }
635        if self.request_id.as_u128() == 0 {
636            return Err(InvalidError::new("checkpoint request id must be nonzero"));
637        }
638        self.mutation.validate()?;
639        validate_supervisor_assertion(self)
640    }
641}
642
643#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
644#[serde(tag = "kind", rename_all = "snake_case")]
645#[non_exhaustive]
646pub enum PublicCheckpointMutation {
647    RegisterDestination {
648        destination: MaterializationDestination,
649    },
650    RegisterQueryRoute {
651        route: QueryRoute,
652    },
653    RemoveQueryRoute {
654        route_id: QueryRouteId,
655        route_generation: u64,
656        expected_definition_revision: u64,
657    },
658    BindTable {
659        destination_id: DestinationId,
660        destination_generation: u64,
661        expected_definition_revision: u64,
662        table_uuid: UuidValue,
663    },
664    SetDesiredState {
665        destination_id: DestinationId,
666        destination_generation: u64,
667        expected_definition_revision: u64,
668        desired_state: DestinationDesiredState,
669    },
670    AddPartition {
671        destination_id: DestinationId,
672        destination_generation: u64,
673        expected_checkpoint_revision: u64,
674        partition_id: u32,
675    },
676    ObservePartitionLifecycle {
677        destination_id: DestinationId,
678        destination_generation: u64,
679        expected_checkpoint_revision: u64,
680        partition_id: u32,
681    },
682    AcquireLease {
683        destination_id: DestinationId,
684        destination_generation: u64,
685        owner: CheckpointOwnerId,
686        expected_lease_sequence: u64,
687        lease_duration_micros: u64,
688    },
689    RenewLease {
690        destination_id: DestinationId,
691        destination_generation: u64,
692        owner: CheckpointOwnerId,
693        epoch: u64,
694        expected_lease_sequence: u64,
695        lease_duration_micros: u64,
696    },
697    TakeoverLease {
698        destination_id: DestinationId,
699        destination_generation: u64,
700        owner: CheckpointOwnerId,
701        expected_lease_sequence: u64,
702        lease_duration_micros: u64,
703    },
704    Prepare {
705        expected_checkpoint_revision: u64,
706        attempt: PreparedAttempt,
707    },
708    Complete {
709        destination_id: DestinationId,
710        destination_generation: u64,
711        owner: CheckpointOwnerId,
712        epoch: u64,
713        expected_checkpoint_revision: u64,
714        completion: CompletedAttempt,
715    },
716    RecordBlock {
717        destination_id: DestinationId,
718        destination_generation: u64,
719        expected_checkpoint_revision: u64,
720        block: DestinationBlock,
721    },
722    ClearBlock {
723        destination_id: DestinationId,
724        destination_generation: u64,
725        expected_checkpoint_revision: u64,
726        expected_code: DestinationBlockCode,
727    },
728    RecordRetentionGap {
729        destination_id: DestinationId,
730        destination_generation: u64,
731        expected_checkpoint_revision: u64,
732        gap: RetentionGap,
733    },
734    AcceptRetentionGap {
735        destination_id: DestinationId,
736        destination_generation: u64,
737        expected_checkpoint_revision: u64,
738        next_offset: u64,
739    },
740    SupersedeGeneration {
741        expected_definition_revision: u64,
742        replacement: MaterializationDestination,
743    },
744    RecordRepair {
745        destination_id: DestinationId,
746        destination_generation: u64,
747        expected_checkpoint_revision: u64,
748        repair: RepairRecord,
749    },
750}
751
752impl Validate for PublicCheckpointMutation {
753    fn validate(&self) -> Result<(), InvalidError> {
754        match self {
755            Self::RegisterDestination { destination } => destination.validate(),
756            Self::RegisterQueryRoute { route } => route.validate(),
757            Self::RemoveQueryRoute {
758                route_id,
759                route_generation,
760                expected_definition_revision,
761            } => {
762                if route_id.as_u128() == 0
763                    || *route_generation == 0
764                    || *expected_definition_revision == 0
765                {
766                    return Err(InvalidError::new(
767                        "query route identity, generation, and revision must be nonzero",
768                    ));
769                }
770                Ok(())
771            }
772            Self::BindTable {
773                destination_id,
774                destination_generation,
775                expected_definition_revision,
776                table_uuid,
777            } => {
778                validate_destination_revision(
779                    *destination_id,
780                    *destination_generation,
781                    *expected_definition_revision,
782                    "definition",
783                )?;
784                validate_uuid(table_uuid)
785            }
786            Self::SetDesiredState {
787                destination_id,
788                destination_generation,
789                expected_definition_revision,
790                ..
791            } => validate_destination_revision(
792                *destination_id,
793                *destination_generation,
794                *expected_definition_revision,
795                "definition",
796            ),
797            Self::AddPartition {
798                destination_id,
799                destination_generation,
800                expected_checkpoint_revision,
801                ..
802            }
803            | Self::ObservePartitionLifecycle {
804                destination_id,
805                destination_generation,
806                expected_checkpoint_revision,
807                ..
808            }
809            | Self::ClearBlock {
810                destination_id,
811                destination_generation,
812                expected_checkpoint_revision,
813                ..
814            }
815            | Self::AcceptRetentionGap {
816                destination_id,
817                destination_generation,
818                expected_checkpoint_revision,
819                ..
820            } => validate_destination_revision(
821                *destination_id,
822                *destination_generation,
823                *expected_checkpoint_revision,
824                "checkpoint",
825            ),
826            Self::AcquireLease {
827                destination_id,
828                destination_generation,
829                owner,
830                lease_duration_micros,
831                ..
832            }
833            | Self::TakeoverLease {
834                destination_id,
835                destination_generation,
836                owner,
837                lease_duration_micros,
838                ..
839            } => validate_lease_request(
840                *destination_id,
841                *destination_generation,
842                *owner,
843                *lease_duration_micros,
844            ),
845            Self::RenewLease {
846                destination_id,
847                destination_generation,
848                owner,
849                epoch,
850                lease_duration_micros,
851                ..
852            } => {
853                validate_lease_request(
854                    *destination_id,
855                    *destination_generation,
856                    *owner,
857                    *lease_duration_micros,
858                )?;
859                if *epoch == 0 {
860                    return Err(InvalidError::new("lease epoch must be nonzero"));
861                }
862                Ok(())
863            }
864            Self::Prepare {
865                expected_checkpoint_revision,
866                attempt,
867            } => {
868                if *expected_checkpoint_revision == 0 {
869                    return Err(InvalidError::new(
870                        "expected checkpoint revision must be nonzero",
871                    ));
872                }
873                attempt.validate()
874            }
875            Self::Complete {
876                destination_id,
877                destination_generation,
878                owner,
879                epoch,
880                expected_checkpoint_revision,
881                completion,
882            } => {
883                validate_destination_revision(
884                    *destination_id,
885                    *destination_generation,
886                    *expected_checkpoint_revision,
887                    "checkpoint",
888                )?;
889                if owner.as_u128() == 0 || *epoch == 0 {
890                    return Err(InvalidError::new(
891                        "completion owner and epoch must be nonzero",
892                    ));
893                }
894                completion.validate()
895            }
896            Self::RecordBlock {
897                destination_id,
898                destination_generation,
899                expected_checkpoint_revision,
900                block,
901            } => {
902                validate_destination_revision(
903                    *destination_id,
904                    *destination_generation,
905                    *expected_checkpoint_revision,
906                    "checkpoint",
907                )?;
908                block.validate()
909            }
910            Self::RecordRetentionGap {
911                destination_id,
912                destination_generation,
913                expected_checkpoint_revision,
914                gap,
915            } => {
916                validate_destination_revision(
917                    *destination_id,
918                    *destination_generation,
919                    *expected_checkpoint_revision,
920                    "checkpoint",
921                )?;
922                gap.validate()
923            }
924            Self::SupersedeGeneration {
925                expected_definition_revision,
926                replacement,
927            } => {
928                if *expected_definition_revision == 0 {
929                    return Err(InvalidError::new(
930                        "expected definition revision must be nonzero",
931                    ));
932                }
933                replacement.validate()
934            }
935            Self::RecordRepair {
936                destination_id,
937                destination_generation,
938                expected_checkpoint_revision,
939                repair,
940            } => {
941                validate_destination_revision(
942                    *destination_id,
943                    *destination_generation,
944                    *expected_checkpoint_revision,
945                    "checkpoint",
946                )?;
947                repair.validate()
948            }
949        }
950    }
951}
952
953impl PublicCheckpointMutation {
954    pub const fn required_capability(&self) -> (Feature, Action) {
955        match self {
956            Self::RegisterDestination { .. }
957            | Self::RegisterQueryRoute { .. }
958            | Self::RemoveQueryRoute { .. }
959            | Self::BindTable { .. }
960            | Self::SetDesiredState { .. }
961            | Self::AddPartition { .. }
962            | Self::ObservePartitionLifecycle { .. } => (Feature::Destination, Action::Write),
963            Self::AcceptRetentionGap { .. }
964            | Self::SupersedeGeneration { .. }
965            | Self::RecordRepair { .. } => (Feature::Checkpoint, Action::Admin),
966            Self::AcquireLease { .. }
967            | Self::RenewLease { .. }
968            | Self::TakeoverLease { .. }
969            | Self::Prepare { .. }
970            | Self::Complete { .. }
971            | Self::RecordBlock { .. }
972            | Self::ClearBlock { .. }
973            | Self::RecordRetentionGap { .. } => (Feature::Checkpoint, Action::Write),
974        }
975    }
976}
977
978#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
979#[serde(rename_all = "snake_case")]
980#[non_exhaustive]
981pub enum PartitionLifecycleChange {
982    Removed,
983    Recreated,
984}
985
986#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
987pub struct RepairRecord {
988    pub action: RepairAction,
989    pub detail: String,
990}
991
992impl Validate for RepairRecord {
993    fn validate(&self) -> Result<(), InvalidError> {
994        if self.detail.is_empty() || self.detail.len() > MAX_REPAIR_DETAIL_BYTES {
995            return Err(InvalidError::new(format!(
996                "repair detail must contain 1..={MAX_REPAIR_DETAIL_BYTES} bytes"
997            )));
998        }
999        Ok(())
1000    }
1001}
1002
1003#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1004#[serde(rename_all = "snake_case")]
1005#[non_exhaustive]
1006pub enum RepairAction {
1007    ReconciledPreparedAttempt,
1008    AcceptedRetentionGap,
1009    ClearedRetryableBlock,
1010    SupersededGeneration,
1011}
1012
1013#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1014pub struct CheckpointMutationStamp {
1015    pub committed_at_micros: u64,
1016    pub iggy_actor_id: u32,
1017    #[serde(default, skip_serializing_if = "Option::is_none")]
1018    pub supervisor_actor: Option<VerifiedSupervisorActor>,
1019}
1020
1021#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1022pub struct VerifiedSupervisorActor {
1023    pub request_id: CheckpointRequestId,
1024    pub deployment_id: u32,
1025    pub cloud_user_id: u32,
1026    pub action: SupervisorAssertionAction,
1027    pub destination_id: DestinationId,
1028    pub destination_generation: u64,
1029    pub expected_revision: u64,
1030    #[serde(with = "crate::encoding::bin_bytes")]
1031    pub key_id: Vec<u8>,
1032    pub issued_at_micros: u64,
1033}
1034
1035impl Validate for CheckpointMutationStamp {
1036    fn validate(&self) -> Result<(), InvalidError> {
1037        if self.committed_at_micros == 0 {
1038            return Err(InvalidError::new(
1039                "checkpoint committed timestamp must be nonzero",
1040            ));
1041        }
1042        if let Some(actor) = &self.supervisor_actor
1043            && (actor.request_id.as_u128() == 0
1044                || actor.deployment_id == 0
1045                || actor.cloud_user_id == 0
1046                || actor.destination_id.as_u128() == 0
1047                || actor.destination_generation == 0
1048                || actor.expected_revision == 0
1049                || actor.issued_at_micros == 0
1050                || actor.key_id.len() != SUPERVISOR_ASSERTION_KEY_ID_BYTES)
1051        {
1052            return Err(InvalidError::new(
1053                "verified supervisor actor evidence is invalid",
1054            ));
1055        }
1056        Ok(())
1057    }
1058}
1059
1060#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1061pub struct ReplicatedCheckpointMutation {
1062    pub request_id: CheckpointRequestId,
1063    pub expected_global_state_revision: u64,
1064    pub stamp: CheckpointMutationStamp,
1065    pub mutation: ReplicatedCheckpointMutationBody,
1066}
1067
1068#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1069#[serde(tag = "kind", rename_all = "snake_case")]
1070#[non_exhaustive]
1071pub enum ReplicatedCheckpointMutationBody {
1072    RegisterDestination {
1073        destination: MaterializationDestination,
1074    },
1075    RegisterQueryRoute {
1076        route: QueryRoute,
1077    },
1078    RemoveQueryRoute {
1079        route_id: QueryRouteId,
1080        route_generation: u64,
1081        expected_definition_revision: u64,
1082    },
1083    BindTable {
1084        destination_id: DestinationId,
1085        destination_generation: u64,
1086        expected_definition_revision: u64,
1087        table_uuid: UuidValue,
1088    },
1089    Activate {
1090        destination_id: DestinationId,
1091        destination_generation: u64,
1092        expected_definition_revision: u64,
1093        source_cut: crate::source::SourceCut,
1094    },
1095    Disable {
1096        destination_id: DestinationId,
1097        destination_generation: u64,
1098        expected_definition_revision: u64,
1099    },
1100    AddPartition {
1101        destination_id: DestinationId,
1102        destination_generation: u64,
1103        expected_checkpoint_revision: u64,
1104        partition: crate::source::SourcePartitionCut,
1105    },
1106    ObservePartitionLifecycle {
1107        destination_id: DestinationId,
1108        destination_generation: u64,
1109        expected_checkpoint_revision: u64,
1110        incarnation: SourceIncarnation,
1111        change: PartitionLifecycleChange,
1112    },
1113    AcquireLease {
1114        destination_id: DestinationId,
1115        destination_generation: u64,
1116        owner: CheckpointOwnerId,
1117        expected_lease_sequence: u64,
1118        deadline_micros: u64,
1119    },
1120    RenewLease {
1121        destination_id: DestinationId,
1122        destination_generation: u64,
1123        owner: CheckpointOwnerId,
1124        epoch: u64,
1125        expected_lease_sequence: u64,
1126        deadline_micros: u64,
1127    },
1128    TakeoverLease {
1129        destination_id: DestinationId,
1130        destination_generation: u64,
1131        owner: CheckpointOwnerId,
1132        expected_lease_sequence: u64,
1133        deadline_micros: u64,
1134    },
1135    Prepare {
1136        expected_checkpoint_revision: u64,
1137        attempt: PreparedAttempt,
1138    },
1139    Complete {
1140        destination_id: DestinationId,
1141        destination_generation: u64,
1142        owner: CheckpointOwnerId,
1143        epoch: u64,
1144        expected_checkpoint_revision: u64,
1145        completion: CompletedAttempt,
1146    },
1147    RecordBlock {
1148        destination_id: DestinationId,
1149        destination_generation: u64,
1150        expected_checkpoint_revision: u64,
1151        block: DestinationBlock,
1152    },
1153    ClearBlock {
1154        destination_id: DestinationId,
1155        destination_generation: u64,
1156        expected_checkpoint_revision: u64,
1157        expected_code: DestinationBlockCode,
1158    },
1159    RecordRetentionGap {
1160        destination_id: DestinationId,
1161        destination_generation: u64,
1162        expected_checkpoint_revision: u64,
1163        gap: RetentionGap,
1164    },
1165    AcceptRetentionGap {
1166        destination_id: DestinationId,
1167        destination_generation: u64,
1168        expected_checkpoint_revision: u64,
1169        incarnation: SourceIncarnation,
1170        next_offset: u64,
1171        repair: RepairRecord,
1172    },
1173    SupersedeGeneration {
1174        expected_definition_revision: u64,
1175        replacement: MaterializationDestination,
1176        repair: RepairRecord,
1177    },
1178    RecordRepair {
1179        destination_id: DestinationId,
1180        destination_generation: u64,
1181        expected_checkpoint_revision: u64,
1182        repair: RepairRecord,
1183    },
1184}
1185
1186impl Validate for ReplicatedCheckpointMutation {
1187    fn validate(&self) -> Result<(), InvalidError> {
1188        if self.request_id.as_u128() == 0 {
1189            return Err(InvalidError::new(
1190                "replicated checkpoint request id must be nonzero",
1191            ));
1192        }
1193        self.stamp.validate()?;
1194        self.mutation.validate()
1195    }
1196}
1197
1198impl Validate for ReplicatedCheckpointMutationBody {
1199    fn validate(&self) -> Result<(), InvalidError> {
1200        match self {
1201            Self::RegisterDestination { destination } => destination.validate(),
1202            Self::RegisterQueryRoute { route } => route.validate(),
1203            Self::RemoveQueryRoute {
1204                route_id,
1205                route_generation,
1206                expected_definition_revision,
1207            } => {
1208                if route_id.as_u128() == 0
1209                    || *route_generation == 0
1210                    || *expected_definition_revision == 0
1211                {
1212                    return Err(InvalidError::new(
1213                        "replicated query route identity and revision must be nonzero",
1214                    ));
1215                }
1216                Ok(())
1217            }
1218            Self::BindTable {
1219                destination_id,
1220                destination_generation,
1221                expected_definition_revision,
1222                table_uuid,
1223            } => {
1224                validate_destination_revision(
1225                    *destination_id,
1226                    *destination_generation,
1227                    *expected_definition_revision,
1228                    "definition",
1229                )?;
1230                validate_uuid(table_uuid)
1231            }
1232            Self::Activate {
1233                destination_id,
1234                destination_generation,
1235                expected_definition_revision,
1236                source_cut,
1237            } => {
1238                validate_destination_revision(
1239                    *destination_id,
1240                    *destination_generation,
1241                    *expected_definition_revision,
1242                    "definition",
1243                )?;
1244                source_cut.validate()
1245            }
1246            Self::Disable {
1247                destination_id,
1248                destination_generation,
1249                expected_definition_revision,
1250            } => validate_destination_revision(
1251                *destination_id,
1252                *destination_generation,
1253                *expected_definition_revision,
1254                "definition",
1255            ),
1256            Self::AddPartition {
1257                destination_id,
1258                destination_generation,
1259                expected_checkpoint_revision,
1260                partition,
1261            } => {
1262                validate_destination_revision(
1263                    *destination_id,
1264                    *destination_generation,
1265                    *expected_checkpoint_revision,
1266                    "checkpoint",
1267                )?;
1268                partition.validate()
1269            }
1270            Self::ObservePartitionLifecycle {
1271                destination_id,
1272                destination_generation,
1273                expected_checkpoint_revision,
1274                incarnation,
1275                ..
1276            } => {
1277                validate_destination_revision(
1278                    *destination_id,
1279                    *destination_generation,
1280                    *expected_checkpoint_revision,
1281                    "checkpoint",
1282                )?;
1283                incarnation.validate()
1284            }
1285            Self::AcquireLease {
1286                destination_id,
1287                destination_generation,
1288                owner,
1289                deadline_micros,
1290                ..
1291            }
1292            | Self::TakeoverLease {
1293                destination_id,
1294                destination_generation,
1295                owner,
1296                deadline_micros,
1297                ..
1298            } => validate_lease_request(
1299                *destination_id,
1300                *destination_generation,
1301                *owner,
1302                *deadline_micros,
1303            ),
1304            Self::RenewLease {
1305                destination_id,
1306                destination_generation,
1307                owner,
1308                epoch,
1309                deadline_micros,
1310                ..
1311            } => {
1312                validate_lease_request(
1313                    *destination_id,
1314                    *destination_generation,
1315                    *owner,
1316                    *deadline_micros,
1317                )?;
1318                if *epoch == 0 {
1319                    return Err(InvalidError::new("replicated lease epoch must be nonzero"));
1320                }
1321                Ok(())
1322            }
1323            Self::Prepare {
1324                expected_checkpoint_revision,
1325                attempt,
1326            } => {
1327                if *expected_checkpoint_revision == 0 {
1328                    return Err(InvalidError::new(
1329                        "expected checkpoint revision must be nonzero",
1330                    ));
1331                }
1332                attempt.validate()
1333            }
1334            Self::Complete {
1335                destination_id,
1336                destination_generation,
1337                owner,
1338                epoch,
1339                expected_checkpoint_revision,
1340                completion,
1341            } => {
1342                validate_destination_revision(
1343                    *destination_id,
1344                    *destination_generation,
1345                    *expected_checkpoint_revision,
1346                    "checkpoint",
1347                )?;
1348                if owner.as_u128() == 0 || *epoch == 0 {
1349                    return Err(InvalidError::new(
1350                        "replicated completion owner and epoch must be nonzero",
1351                    ));
1352                }
1353                completion.validate()
1354            }
1355            Self::RecordBlock {
1356                destination_id,
1357                destination_generation,
1358                expected_checkpoint_revision,
1359                block,
1360            } => {
1361                validate_destination_revision(
1362                    *destination_id,
1363                    *destination_generation,
1364                    *expected_checkpoint_revision,
1365                    "checkpoint",
1366                )?;
1367                block.validate()
1368            }
1369            Self::ClearBlock {
1370                destination_id,
1371                destination_generation,
1372                expected_checkpoint_revision,
1373                ..
1374            } => validate_destination_revision(
1375                *destination_id,
1376                *destination_generation,
1377                *expected_checkpoint_revision,
1378                "checkpoint",
1379            ),
1380            Self::RecordRetentionGap {
1381                destination_id,
1382                destination_generation,
1383                expected_checkpoint_revision,
1384                gap,
1385            } => {
1386                validate_destination_revision(
1387                    *destination_id,
1388                    *destination_generation,
1389                    *expected_checkpoint_revision,
1390                    "checkpoint",
1391                )?;
1392                gap.validate()
1393            }
1394            Self::AcceptRetentionGap {
1395                destination_id,
1396                destination_generation,
1397                expected_checkpoint_revision,
1398                incarnation,
1399                repair,
1400                ..
1401            } => {
1402                validate_destination_revision(
1403                    *destination_id,
1404                    *destination_generation,
1405                    *expected_checkpoint_revision,
1406                    "checkpoint",
1407                )?;
1408                incarnation.validate()?;
1409                repair.validate()
1410            }
1411            Self::SupersedeGeneration {
1412                expected_definition_revision,
1413                replacement,
1414                repair,
1415            } => {
1416                if *expected_definition_revision == 0 {
1417                    return Err(InvalidError::new(
1418                        "expected definition revision must be nonzero",
1419                    ));
1420                }
1421                replacement.validate()?;
1422                repair.validate()
1423            }
1424            Self::RecordRepair {
1425                destination_id,
1426                destination_generation,
1427                expected_checkpoint_revision,
1428                repair,
1429            } => {
1430                validate_destination_revision(
1431                    *destination_id,
1432                    *destination_generation,
1433                    *expected_checkpoint_revision,
1434                    "checkpoint",
1435                )?;
1436                repair.validate()
1437            }
1438        }
1439    }
1440}
1441
1442#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1443#[serde(tag = "kind", rename_all = "snake_case")]
1444#[non_exhaustive]
1445pub enum CheckpointMutationResult {
1446    Destination {
1447        request_id: CheckpointRequestId,
1448        destination_id: DestinationId,
1449        destination_generation: u64,
1450        global_state_revision: u64,
1451        definition_revision: u64,
1452        checkpoint_revision: u64,
1453        #[serde(default, skip_serializing_if = "Option::is_none")]
1454        lease: Option<CheckpointOwnerLease>,
1455    },
1456    QueryRoute {
1457        request_id: CheckpointRequestId,
1458        route_id: QueryRouteId,
1459        route_generation: u64,
1460        global_state_revision: u64,
1461        definition_revision: u64,
1462    },
1463}
1464
1465#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
1466#[non_exhaustive]
1467pub enum CheckpointError {
1468    #[error("checkpoint request is invalid: {0}")]
1469    Invalid(String),
1470    #[error("checkpoint destination was not found")]
1471    NotFound,
1472    #[error("checkpoint mutation conflicts with revision {observed_revision}")]
1473    Conflict { observed_revision: u64 },
1474    #[error("checkpoint owner lease was lost")]
1475    LeaseLost,
1476    #[error("checkpoint mutation is unauthorized")]
1477    Unauthorized,
1478    #[error("checkpoint service is unavailable: {0}")]
1479    Unavailable(String),
1480    #[error("checkpoint version mismatch: expected {expected}, got {got}")]
1481    Version { expected: u32, got: u32 },
1482}
1483
1484#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1485#[non_exhaustive]
1486pub enum CheckpointReply {
1487    Ok(CheckpointMutationResult),
1488    Err(CheckpointError),
1489}
1490
1491#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1492pub struct DestinationGetRequest {
1493    pub v: u32,
1494    pub destination_id: DestinationId,
1495    pub consistency: CheckpointReadConsistency,
1496}
1497
1498impl DestinationGetRequest {
1499    pub const fn new(
1500        destination_id: DestinationId,
1501        consistency: CheckpointReadConsistency,
1502    ) -> Self {
1503        Self {
1504            v: crate::codes::CHECKPOINT_OP_VERSION,
1505            destination_id,
1506            consistency,
1507        }
1508    }
1509}
1510
1511#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1512pub struct DestinationListFilter {
1513    #[serde(default, skip_serializing_if = "Option::is_none")]
1514    pub source_stream: Option<String>,
1515    #[serde(default, skip_serializing_if = "Option::is_none")]
1516    pub source_topic: Option<String>,
1517    #[serde(default, skip_serializing_if = "Option::is_none")]
1518    pub name_contains: Option<String>,
1519}
1520
1521#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1522pub struct DestinationListRequest {
1523    pub v: u32,
1524    #[serde(default)]
1525    pub filter: DestinationListFilter,
1526    #[serde(default, skip_serializing_if = "Option::is_none")]
1527    pub after: Option<DestinationId>,
1528    pub limit: u32,
1529    pub consistency: CheckpointReadConsistency,
1530}
1531
1532#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1533pub struct QueryRouteListRequest {
1534    pub v: u32,
1535    #[serde(default, skip_serializing_if = "Option::is_none")]
1536    pub name_contains: Option<String>,
1537    #[serde(default, skip_serializing_if = "Option::is_none")]
1538    pub after: Option<QueryRouteId>,
1539    pub limit: u32,
1540    pub consistency: CheckpointReadConsistency,
1541}
1542
1543#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1544pub struct DestinationCheckpointView {
1545    pub destination: MaterializationDestination,
1546    pub status: DestinationCheckpointStatus,
1547}
1548
1549#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1550pub struct DestinationCheckpointPage {
1551    pub destinations: Vec<DestinationCheckpointView>,
1552    #[serde(default, skip_serializing_if = "Option::is_none")]
1553    pub next_after: Option<DestinationId>,
1554    pub global_state_revision: u64,
1555    pub consistency: CheckpointReadConsistency,
1556}
1557
1558#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1559pub struct QueryRoutePage {
1560    pub routes: Vec<QueryRoute>,
1561    #[serde(default, skip_serializing_if = "Option::is_none")]
1562    pub next_after: Option<QueryRouteId>,
1563    pub global_state_revision: u64,
1564    pub consistency: CheckpointReadConsistency,
1565}
1566
1567#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1568#[non_exhaustive]
1569pub enum CheckpointReadReply {
1570    Destination(Option<Box<DestinationCheckpointView>>),
1571    Destinations(DestinationCheckpointPage),
1572    QueryRoutes(QueryRoutePage),
1573    Err(CheckpointError),
1574}
1575
1576impl Validate for DestinationGetRequest {
1577    fn validate(&self) -> Result<(), InvalidError> {
1578        validate_checkpoint_read(self.v)?;
1579        if self.destination_id.as_u128() == 0 {
1580            return Err(InvalidError::new("destination id must be nonzero"));
1581        }
1582        Ok(())
1583    }
1584}
1585
1586impl Validate for DestinationListRequest {
1587    fn validate(&self) -> Result<(), InvalidError> {
1588        validate_checkpoint_read(self.v)?;
1589        validate_read_limit(self.limit)?;
1590        validate_optional_filter("source stream", self.filter.source_stream.as_deref())?;
1591        validate_optional_filter("source topic", self.filter.source_topic.as_deref())?;
1592        validate_optional_filter("destination name", self.filter.name_contains.as_deref())
1593    }
1594}
1595
1596impl Validate for QueryRouteListRequest {
1597    fn validate(&self) -> Result<(), InvalidError> {
1598        validate_checkpoint_read(self.v)?;
1599        validate_read_limit(self.limit)?;
1600        validate_optional_filter("query route name", self.name_contains.as_deref())
1601    }
1602}
1603
1604impl Validate for CheckpointMutationResult {
1605    fn validate(&self) -> Result<(), InvalidError> {
1606        match self {
1607            Self::Destination {
1608                request_id,
1609                destination_id,
1610                destination_generation,
1611                global_state_revision,
1612                definition_revision,
1613                lease,
1614                ..
1615            } => {
1616                if request_id.as_u128() == 0
1617                    || destination_id.as_u128() == 0
1618                    || *destination_generation == 0
1619                    || *global_state_revision == 0
1620                    || *definition_revision == 0
1621                {
1622                    return Err(InvalidError::new(
1623                        "checkpoint destination result identity, generation, global revision, and definition revision must be nonzero",
1624                    ));
1625                }
1626                if let Some(lease) = lease {
1627                    lease.validate()?;
1628                }
1629            }
1630            Self::QueryRoute {
1631                request_id,
1632                route_id,
1633                route_generation,
1634                global_state_revision,
1635                definition_revision,
1636            } => {
1637                if request_id.as_u128() == 0
1638                    || route_id.as_u128() == 0
1639                    || *route_generation == 0
1640                    || *global_state_revision == 0
1641                    || *definition_revision == 0
1642                {
1643                    return Err(InvalidError::new(
1644                        "checkpoint query route result identity, generation, global revision, and definition revision must be nonzero",
1645                    ));
1646                }
1647            }
1648        }
1649        Ok(())
1650    }
1651}
1652
1653impl Validate for DestinationCheckpointView {
1654    fn validate(&self) -> Result<(), InvalidError> {
1655        self.destination.validate()?;
1656        self.status.validate()?;
1657        if self.destination.id != self.status.destination_id
1658            || self.destination.generation != self.status.destination_generation
1659            || self.destination.definition_revision != self.status.definition_revision
1660            || self.destination.backend != self.status.backend
1661            || self.destination.schema != self.status.schema
1662            || self.destination.projection != self.status.projection
1663        {
1664            return Err(InvalidError::new(
1665                "destination declaration and checkpoint status do not describe the same generation",
1666            ));
1667        }
1668        Ok(())
1669    }
1670}
1671
1672impl Validate for DestinationCheckpointPage {
1673    fn validate(&self) -> Result<(), InvalidError> {
1674        if self.global_state_revision == 0 || self.destinations.len() > crate::limits::MAX_PAGE_SIZE
1675        {
1676            return Err(InvalidError::new("destination page metadata is invalid"));
1677        }
1678        let mut ids = BTreeSet::new();
1679        for destination in &self.destinations {
1680            destination.validate()?;
1681            if destination.status.global_state_revision > self.global_state_revision
1682                || !ids.insert(destination.destination.id)
1683            {
1684                return Err(InvalidError::new(
1685                    "destination page contains duplicate or future state",
1686                ));
1687            }
1688        }
1689        Ok(())
1690    }
1691}
1692
1693impl Validate for QueryRoutePage {
1694    fn validate(&self) -> Result<(), InvalidError> {
1695        if self.global_state_revision == 0 || self.routes.len() > crate::limits::MAX_PAGE_SIZE {
1696            return Err(InvalidError::new("query route page metadata is invalid"));
1697        }
1698        let mut ids = BTreeSet::new();
1699        for route in &self.routes {
1700            route.validate()?;
1701            if !ids.insert(route.id) {
1702                return Err(InvalidError::new("query route page repeats a route id"));
1703            }
1704        }
1705        Ok(())
1706    }
1707}
1708
1709impl Validate for CheckpointReadReply {
1710    fn validate(&self) -> Result<(), InvalidError> {
1711        match self {
1712            Self::Destination(Some(destination)) => destination.validate(),
1713            Self::Destination(None) | Self::Err(_) => Ok(()),
1714            Self::Destinations(page) => page.validate(),
1715            Self::QueryRoutes(page) => page.validate(),
1716        }
1717    }
1718}
1719
1720fn validate_checkpoint_read(version: u32) -> Result<(), InvalidError> {
1721    if version != crate::codes::CHECKPOINT_OP_VERSION {
1722        return Err(InvalidError::new(format!(
1723            "checkpoint read version must be {}, got {version}",
1724            crate::codes::CHECKPOINT_OP_VERSION
1725        )));
1726    }
1727    Ok(())
1728}
1729
1730fn validate_read_limit(limit: u32) -> Result<(), InvalidError> {
1731    if limit == 0 || limit > crate::limits::MAX_PAGE_SIZE as u32 {
1732        return Err(InvalidError::new(format!(
1733            "checkpoint read limit must be in 1..={}",
1734            crate::limits::MAX_PAGE_SIZE
1735        )));
1736    }
1737    Ok(())
1738}
1739
1740fn validate_optional_filter(label: &str, value: Option<&str>) -> Result<(), InvalidError> {
1741    if let Some(value) = value
1742        && (value.is_empty() || value.len() > 255 || value.chars().any(char::is_control))
1743    {
1744        return Err(InvalidError::new(format!(
1745            "{label} filter must contain 1..=255 bytes without control characters"
1746        )));
1747    }
1748    Ok(())
1749}
1750
1751fn validate_supervisor_assertion(envelope: &CheckpointRequestEnvelope) -> Result<(), InvalidError> {
1752    let required = match &envelope.mutation {
1753        PublicCheckpointMutation::AcceptRetentionGap {
1754            destination_id,
1755            destination_generation,
1756            expected_checkpoint_revision,
1757            ..
1758        } => Some((
1759            SupervisorAssertionAction::AcceptRetentionGap,
1760            *destination_id,
1761            *destination_generation,
1762            *expected_checkpoint_revision,
1763        )),
1764        PublicCheckpointMutation::SupersedeGeneration {
1765            expected_definition_revision,
1766            replacement,
1767        } => Some((
1768            SupervisorAssertionAction::SupersedeGeneration,
1769            replacement.id,
1770            replacement.generation,
1771            *expected_definition_revision,
1772        )),
1773        PublicCheckpointMutation::RecordRepair {
1774            destination_id,
1775            destination_generation,
1776            expected_checkpoint_revision,
1777            ..
1778        } => Some((
1779            SupervisorAssertionAction::RecordRepair,
1780            *destination_id,
1781            *destination_generation,
1782            *expected_checkpoint_revision,
1783        )),
1784        _ => None,
1785    };
1786    match (required, &envelope.supervisor_assertion) {
1787        (None, None) => Ok(()),
1788        (None, Some(_)) => Err(InvalidError::new(
1789            "supervisor assertion is not accepted for this checkpoint mutation",
1790        )),
1791        (Some(_), None) => Err(InvalidError::new(
1792            "high-risk checkpoint mutation requires a supervisor assertion",
1793        )),
1794        (
1795            Some((action, destination_id, destination_generation, expected_revision)),
1796            Some(assertion),
1797        ) => {
1798            assertion.validate()?;
1799            if assertion.claims.request_id != envelope.request_id
1800                || assertion.claims.action != action
1801                || assertion.claims.destination_id != destination_id
1802                || assertion.claims.destination_generation != destination_generation
1803                || assertion.claims.expected_revision != Some(expected_revision)
1804            {
1805                return Err(InvalidError::new(
1806                    "supervisor assertion is not bound to this request, action, and destination",
1807                ));
1808            }
1809            Ok(())
1810        }
1811    }
1812}
1813
1814fn validate_destination_revision(
1815    destination_id: DestinationId,
1816    destination_generation: u64,
1817    revision: u64,
1818    revision_kind: &str,
1819) -> Result<(), InvalidError> {
1820    if destination_id.as_u128() == 0 || destination_generation == 0 {
1821        return Err(InvalidError::new(
1822            "checkpoint destination identity and generation must be nonzero",
1823        ));
1824    }
1825    if revision == 0 {
1826        return Err(InvalidError::new(format!(
1827            "expected {revision_kind} revision must be nonzero"
1828        )));
1829    }
1830    Ok(())
1831}
1832
1833fn validate_uuid(value: &UuidValue) -> Result<(), InvalidError> {
1834    if value.as_bytes().len() != UuidValue::BYTES {
1835        return Err(InvalidError::new(format!(
1836            "table UUID must be {} bytes",
1837            UuidValue::BYTES
1838        )));
1839    }
1840    Ok(())
1841}
1842
1843fn validate_lease_request(
1844    destination_id: DestinationId,
1845    destination_generation: u64,
1846    owner: CheckpointOwnerId,
1847    lease_duration_micros: u64,
1848) -> Result<(), InvalidError> {
1849    if destination_id.as_u128() == 0
1850        || destination_generation == 0
1851        || owner.as_u128() == 0
1852        || lease_duration_micros == 0
1853        || lease_duration_micros > MAX_CHECKPOINT_LEASE_DURATION_MICROS
1854    {
1855        return Err(InvalidError::new(
1856            "lease destination, owner, and generation must be nonzero, and duration must be within the configured cap",
1857        ));
1858    }
1859    Ok(())
1860}
1861
1862fn validate_attempt_identity(
1863    attempt_id: PreparedAttemptId,
1864    destination_id: DestinationId,
1865    destination_generation: u64,
1866) -> Result<(), InvalidError> {
1867    if attempt_id.as_u128() == 0 {
1868        return Err(InvalidError::new("prepared attempt id must be nonzero"));
1869    }
1870    if destination_id.as_u128() == 0 || destination_generation == 0 {
1871        return Err(InvalidError::new(
1872            "prepared attempt destination identity must be nonzero",
1873        ));
1874    }
1875    Ok(())
1876}
1877
1878fn validate_ranges(ranges: &[SourceOffsetRange]) -> Result<(), InvalidError> {
1879    if ranges.is_empty() || ranges.len() > MAX_CHECKPOINT_PARTITIONS {
1880        return Err(InvalidError::new(format!(
1881            "source ranges must contain 1..={MAX_CHECKPOINT_PARTITIONS} partitions"
1882        )));
1883    }
1884    let mut partitions = BTreeSet::new();
1885    let mut previous_partition = None;
1886    let mut namespace = None;
1887    for range in ranges {
1888        range.validate()?;
1889        let current = (
1890            range.incarnation.cluster,
1891            range.incarnation.stream_id,
1892            range.incarnation.topic_id,
1893        );
1894        if namespace.is_some_and(|expected| expected != current) {
1895            return Err(InvalidError::new(
1896                "source ranges must share one cluster, stream, and topic",
1897            ));
1898        }
1899        namespace = Some(current);
1900        if !partitions.insert(range.incarnation.partition_id) {
1901            return Err(InvalidError::new(format!(
1902                "source ranges repeat partition {}",
1903                range.incarnation.partition_id
1904            )));
1905        }
1906        if previous_partition.is_some_and(|previous| previous >= range.incarnation.partition_id) {
1907            return Err(InvalidError::new(
1908                "source ranges must be ordered by ascending partition id",
1909            ));
1910        }
1911        previous_partition = Some(range.incarnation.partition_id);
1912    }
1913    Ok(())
1914}
1915
1916fn validate_resulting_boundary(
1917    boundary: &[PartitionCheckpoint],
1918    ranges: &[SourceOffsetRange],
1919) -> Result<(), InvalidError> {
1920    if boundary.len() != ranges.len() {
1921        return Err(InvalidError::new(
1922            "prepared attempt boundary must cover every source range exactly once",
1923        ));
1924    }
1925    for (range, checkpoint) in ranges.iter().zip(boundary) {
1926        checkpoint.validate()?;
1927        if checkpoint.incarnation != range.incarnation
1928            || checkpoint.next_offset != range.end_exclusive
1929            || checkpoint.lifecycle != PartitionLifecycleState::Active
1930        {
1931            return Err(InvalidError::new(
1932                "prepared attempt boundary does not match its source ranges",
1933            ));
1934        }
1935    }
1936    Ok(())
1937}
1938
1939fn validate_credential_generations(
1940    generations: &[CredentialGeneration],
1941) -> Result<(), InvalidError> {
1942    if generations.is_empty() || generations.len() > MAX_CREDENTIAL_GENERATIONS {
1943        return Err(InvalidError::new(format!(
1944            "credential generations must contain 1..={MAX_CREDENTIAL_GENERATIONS} roles"
1945        )));
1946    }
1947    let mut roles = BTreeSet::new();
1948    for generation in generations {
1949        generation.validate()?;
1950        if !roles.insert(&generation.role) {
1951            return Err(InvalidError::new(format!(
1952                "credential role `{}` appears more than once",
1953                generation.role
1954            )));
1955        }
1956    }
1957    Ok(())
1958}
1959
1960fn validate_object_identity(identity: &str) -> Result<(), InvalidError> {
1961    if identity.is_empty() || identity.len() > MAX_MANIFEST_IDENTITY_BYTES {
1962        return Err(InvalidError::new(format!(
1963            "object identity must contain 1..={MAX_MANIFEST_IDENTITY_BYTES} bytes"
1964        )));
1965    }
1966    if identity.contains('?')
1967        || identity.contains('#')
1968        || identity.contains('@')
1969        || identity.contains("//")
1970    {
1971        return Err(InvalidError::new(
1972            "object identity must be canonical, provider-relative, and secret-free",
1973        ));
1974    }
1975    Ok(())
1976}
1977
1978#[cfg(test)]
1979mod tests {
1980    use super::*;
1981    #[cfg(feature = "cbor")]
1982    use crate::destination::{
1983        BackendResourceId, DestinationErrorPolicy, FileFormat, NewPartitionPolicy, PhysicalTable,
1984        RecreatedPartitionPolicy, StartPolicy, TableFormat,
1985    };
1986    #[cfg(feature = "cbor")]
1987    use crate::framing::{decode_named, encode_named};
1988    #[cfg(feature = "cbor")]
1989    use crate::schema::{LogicalSchemaId, SchemaFingerprint};
1990    use crate::source::PhysicalClusterIncarnation;
1991    #[cfg(feature = "cbor")]
1992    use crate::source::SourceScope;
1993
1994    fn incarnation(partition_id: u32) -> SourceIncarnation {
1995        SourceIncarnation {
1996            cluster: PhysicalClusterIncarnation::from_u128(1),
1997            stream_id: 2,
1998            topic_id: 3,
1999            partition_id,
2000            partition_created_revision: 4,
2001        }
2002    }
2003
2004    #[cfg(feature = "cbor")]
2005    fn destination() -> MaterializationDestination {
2006        MaterializationDestination {
2007            id: DestinationId::from_u128(10),
2008            generation: 1,
2009            definition_revision: 1,
2010            name: "readings-lakehouse".to_owned(),
2011            source: SourceScope::new("fleet", "readings"),
2012            recreated_partition_policy: RecreatedPartitionPolicy::Reject,
2013            projection: ProjectionRef {
2014                id: crate::control::ProjectionId::new("reading.v1"),
2015                version: 1,
2016            },
2017            schema: LogicalSchemaRef {
2018                id: LogicalSchemaId::from_u128(11),
2019                version: 1,
2020                fingerprint: SchemaFingerprint::new([12; 32]),
2021            },
2022            backend: BackendBinding {
2023                resource_id: BackendResourceId::from_u128(13),
2024                generation: 2,
2025            },
2026            table: PhysicalTable {
2027                namespace: vec!["fleet".to_owned()],
2028                table: "readings".to_owned(),
2029                expected_table_uuid: None,
2030            },
2031            file_format: FileFormat::Parquet,
2032            table_format: TableFormat::IcebergV2,
2033            start_policy: StartPolicy::CapturedLatest,
2034            new_partition_policy: NewPartitionPolicy::CapturedLatest,
2035            error_policy: DestinationErrorPolicy::Block,
2036            desired_state: DestinationDesiredState::Disabled,
2037        }
2038    }
2039
2040    fn table_requirements() -> PreparedTableRequirements {
2041        let table_uuid = UuidValue::new([14; 16]);
2042        let base_metadata_identity = "warehouse/fleet/readings/metadata/00001.json".to_owned();
2043        PreparedTableRequirements {
2044            table_uuid: table_uuid.clone(),
2045            base_metadata_identity: base_metadata_identity.clone(),
2046            base_snapshot_id: Some(15),
2047            schema_id: 1,
2048            partition_spec_id: 0,
2049            commit_requirements: vec![
2050                IcebergCommitRequirement::AssertTableUuid { table_uuid },
2051                IcebergCommitRequirement::AssertMetadataIdentity {
2052                    identity: base_metadata_identity,
2053                },
2054                IcebergCommitRequirement::AssertCurrentSnapshot {
2055                    snapshot_id: Some(15),
2056                },
2057                IcebergCommitRequirement::AssertCurrentSchema { schema_id: 1 },
2058                IcebergCommitRequirement::AssertDefaultPartitionSpec {
2059                    partition_spec_id: 0,
2060                },
2061            ],
2062        }
2063    }
2064
2065    #[cfg(feature = "cbor")]
2066    #[test]
2067    fn given_a_public_request_when_decoded_as_a_replicated_mutation_then_should_fail() {
2068        let request = CheckpointRequestEnvelope::new(
2069            CheckpointRequestId::from_u128(20),
2070            0,
2071            PublicCheckpointMutation::RegisterDestination {
2072                destination: destination(),
2073            },
2074        );
2075        request.validate().expect("public request is valid");
2076        let bytes = encode_named(&request).expect("public request encodes");
2077        assert!(decode_named::<ReplicatedCheckpointMutation>(&bytes).is_err());
2078    }
2079
2080    #[cfg(feature = "cbor")]
2081    #[test]
2082    fn given_a_replicated_mutation_when_decoded_as_a_public_request_then_should_fail() {
2083        let mutation = ReplicatedCheckpointMutation {
2084            request_id: CheckpointRequestId::from_u128(21),
2085            expected_global_state_revision: 0,
2086            stamp: CheckpointMutationStamp {
2087                committed_at_micros: 22,
2088                iggy_actor_id: 23,
2089                supervisor_actor: None,
2090            },
2091            mutation: ReplicatedCheckpointMutationBody::RegisterDestination {
2092                destination: destination(),
2093            },
2094        };
2095        mutation.validate().expect("replicated mutation is valid");
2096        let bytes = encode_named(&mutation).expect("replicated mutation encodes");
2097        assert!(decode_named::<CheckpointRequestEnvelope>(&bytes).is_err());
2098    }
2099
2100    #[test]
2101    fn given_prepared_table_requirements_when_validated_then_should_require_every_frozen_iceberg_precondition_in_order()
2102     {
2103        let valid = table_requirements();
2104        valid.validate().expect("requirements are complete");
2105
2106        let mut missing = valid.clone();
2107        missing.commit_requirements.pop();
2108        assert!(missing.validate().is_err());
2109
2110        let mut reordered = valid;
2111        reordered.commit_requirements.swap(0, 1);
2112        assert!(reordered.validate().is_err());
2113    }
2114
2115    #[test]
2116    fn given_a_lease_duration_when_validated_then_should_be_bounded_before_managed_log_submission()
2117    {
2118        let mutation = PublicCheckpointMutation::AcquireLease {
2119            destination_id: DestinationId::from_u128(10),
2120            destination_generation: 1,
2121            owner: CheckpointOwnerId::from_u128(30),
2122            expected_lease_sequence: 0,
2123            lease_duration_micros: MAX_CHECKPOINT_LEASE_DURATION_MICROS + 1,
2124        };
2125        assert!(mutation.validate().is_err());
2126    }
2127
2128    #[test]
2129    fn given_a_root_authored_stamp_when_validated_then_should_preserve_actor_zero() {
2130        let stamp = CheckpointMutationStamp {
2131            committed_at_micros: 1,
2132            iggy_actor_id: 0,
2133            supervisor_actor: None,
2134        };
2135
2136        stamp
2137            .validate()
2138            .expect("root actor zero is a valid Iggy identity");
2139    }
2140
2141    #[test]
2142    fn given_an_unserved_checkpoint_request_version_when_validated_then_should_reject_it() {
2143        let mut request = CheckpointRequestEnvelope::new(
2144            CheckpointRequestId::from_u128(1),
2145            0,
2146            PublicCheckpointMutation::RegisterDestination {
2147                destination: destination(),
2148            },
2149        );
2150        request.v = crate::codes::CHECKPOINT_OP_VERSION + 1;
2151
2152        assert!(request.validate().is_err());
2153    }
2154
2155    #[test]
2156    fn given_destination_and_query_route_results_when_validated_then_should_require_variant_specific_identity()
2157     {
2158        CheckpointMutationResult::Destination {
2159            request_id: CheckpointRequestId::from_u128(1),
2160            destination_id: DestinationId::from_u128(2),
2161            destination_generation: 3,
2162            global_state_revision: 4,
2163            definition_revision: 5,
2164            checkpoint_revision: 0,
2165            lease: None,
2166        }
2167        .validate()
2168        .expect("destination result is valid");
2169        CheckpointMutationResult::QueryRoute {
2170            request_id: CheckpointRequestId::from_u128(6),
2171            route_id: QueryRouteId::from_u128(7),
2172            route_generation: 8,
2173            global_state_revision: 9,
2174            definition_revision: 10,
2175        }
2176        .validate()
2177        .expect("query route result is valid");
2178    }
2179
2180    #[test]
2181    fn given_source_ranges_when_out_of_canonical_partition_order_then_should_reject() {
2182        let ranges = vec![
2183            SourceOffsetRange {
2184                incarnation: incarnation(2),
2185                start: 0,
2186                end_exclusive: 1,
2187            },
2188            SourceOffsetRange {
2189                incarnation: incarnation(1),
2190                start: 0,
2191                end_exclusive: 1,
2192            },
2193        ];
2194        assert!(validate_ranges(&ranges).is_err());
2195    }
2196}