laminar_core/checkpoint/checkpoint_store/
artifact_identity.rs1use crate::checkpoint::{canonical_json_sha256, StateChunkId};
4use crate::checkpoint_decision::CheckpointArtifactInventory;
5use crate::state::LOCAL_NODE_ID;
6
7use super::CheckpointStoreError;
8
9const CHECKPOINT_ARTIFACT_IDENTITY_VERSION: u32 = 2;
10const CHECKPOINT_ARTIFACT_IDENTITY_VERSION_V1: u32 = 1;
11
12#[derive(serde::Serialize)]
13struct CheckpointArtifactIdentityPayload<'a> {
14 version: u32,
15 inventory: &'a CheckpointArtifactInventory,
16 chunk: StateChunkId,
17}
18
19pub fn checkpoint_artifact_identity_sha256(
24 inventory: &CheckpointArtifactInventory,
25 chunk: StateChunkId,
26) -> Result<String, CheckpointStoreError> {
27 inventory.validate().map_err(|error| {
28 CheckpointStoreError::Invalid(format!("checkpoint artifact inventory: {error}"))
29 })?;
30 if chunk.participant_id == 0 || chunk.checkpoint_id != inventory.attempt.checkpoint_id {
31 return Err(CheckpointStoreError::Invalid(
32 "checkpoint artifact chunk does not match its active inventory".into(),
33 ));
34 }
35 match inventory.assignment_fence.as_ref() {
36 Some(fence) if !fence.contains(chunk.participant_id) => {
37 return Err(CheckpointStoreError::Invalid(format!(
38 "checkpoint artifact participant {} is outside its assignment fence",
39 chunk.participant_id
40 )));
41 }
42 None if chunk.participant_id != LOCAL_NODE_ID.0 => {
43 return Err(CheckpointStoreError::Invalid(format!(
44 "local checkpoint artifact participant must be {}",
45 LOCAL_NODE_ID.0
46 )));
47 }
48 Some(_) | None => {}
49 }
50 canonical_json_sha256(&CheckpointArtifactIdentityPayload {
51 version: if inventory.sink_artifact_intent_protocol {
52 CHECKPOINT_ARTIFACT_IDENTITY_VERSION
53 } else {
54 CHECKPOINT_ARTIFACT_IDENTITY_VERSION_V1
55 },
56 inventory,
57 chunk,
58 })
59 .map_err(CheckpointStoreError::Serde)
60}