Skip to main content

laminar_core/checkpoint/checkpoint_store/
artifact_identity.rs

1//! Durable binding between checkpoint cleanup authority and object identity.
2
3use 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
19/// SHA-256 binding one active artifact inventory to one exact participant object namespace.
20///
21/// # Errors
22/// Returns an error when the inventory or object identity is not canonical.
23pub 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}