haematite-cli 0.21.8

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
use serde::{Deserialize, Serialize};

use super::{Caller, ContractKind, FINGERPRINT_FORMAT, ResolvedMutation, identifier};
use crate::serve::protocol::Version;
use crate::serve::storage::Failure;

const RECEIPT_FORMAT: u32 = 2;

/// A stored retry identity; its author is mandatory and never reconstructed.
#[derive(Debug, Serialize)]
pub struct Receipt {
    format: u32,
    request_id: String,
    request_origin: Version,
    caller: Caller,
    contract_kind: ContractKind,
    fingerprint_format: u32,
    fingerprint: String,
    branch: String,
    version: Version,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct ReceiptWire {
    format: u32,
    request_id: String,
    request_origin: Version,
    caller: Caller,
    contract_kind: ContractKind,
    fingerprint_format: u32,
    fingerprint: String,
    branch: String,
    version: Version,
}

#[derive(Deserialize)]
struct FormatHeader {
    format: Option<u64>,
}

impl Receipt {
    /// Record a resolved request's installed identity.
    ///
    /// # Errors
    /// Propagates a failure to serialize the request for its fingerprint.
    pub fn for_request(request: &ResolvedMutation, installed: Version) -> Result<Self, Failure> {
        Ok(Self {
            format: RECEIPT_FORMAT,
            request_id: request.request_id().to_owned(),
            request_origin: request.origin(),
            caller: request.caller().clone(),
            contract_kind: request.contract().kind(),
            fingerprint_format: FINGERPRINT_FORMAT,
            fingerprint: request.fingerprint()?,
            branch: request.destination().to_owned(),
            version: installed,
        })
    }

    /// Decode a receipt and validate its stored identity and digest.
    ///
    /// # Errors
    /// Refuses malformed receipts, unsupported receipt or fingerprint formats, invalid identifiers and non-canonical digests.
    pub fn decode(bytes: &[u8]) -> Result<Self, Failure> {
        let header: FormatHeader = serde_json::from_slice(bytes)
            .map_err(|error| Failure::new("stored_receipt_corrupt", error.to_string()))?;
        if header.format != Some(u64::from(RECEIPT_FORMAT)) {
            return Err(Failure::new(
                "stored_receipt_format_unsupported",
                "stored receipt is not format 2",
            ));
        }
        let wire: ReceiptWire = serde_json::from_slice(bytes)
            .map_err(|error| Failure::new("stored_receipt_corrupt", error.to_string()))?;
        identifier(&wire.request_id)?;
        identifier(&wire.branch)?;
        if wire.fingerprint_format != FINGERPRINT_FORMAT {
            return Err(Failure::new(
                "stored_fingerprint_format_unsupported",
                "stored receipt has an unsupported fingerprint format",
            ));
        }
        if wire.fingerprint.len() != 64
            || !wire
                .fingerprint
                .bytes()
                .all(|byte| matches!(byte, b'0'..=b'9' | b'a'..=b'f'))
        {
            return Err(Failure::new(
                "stored_receipt_corrupt",
                "stored receipt fingerprint is not a canonical digest",
            ));
        }
        Ok(Self {
            format: wire.format,
            request_id: wire.request_id,
            request_origin: wire.request_origin,
            caller: wire.caller,
            contract_kind: wire.contract_kind,
            fingerprint_format: wire.fingerprint_format,
            fingerprint: wire.fingerprint,
            branch: wire.branch,
            version: wire.version,
        })
    }

    /// Verify that this receipt belongs to the identical resolved request.
    ///
    /// # Errors
    /// Returns `receipt_corrupt` for a key mismatch, `request_id_reused` for different content, or the request fingerprint's serialization refusal.
    pub fn replay(&self, request: &ResolvedMutation) -> Result<&Self, Failure> {
        if self.request_id != request.request_id() || self.branch != request.destination() {
            return Err(Failure::new(
                "receipt_corrupt",
                "stored receipt identity differs from its requested key",
            ));
        }
        if self.caller != *request.caller()
            || self.request_origin != request.origin()
            || self.contract_kind != request.contract().kind()
            || self.fingerprint != request.fingerprint()?
        {
            return Err(Failure::new(
                "request_id_reused",
                "request identity was used with different content",
            ));
        }
        Ok(self)
    }

    #[must_use]
    pub const fn caller(&self) -> &Caller {
        &self.caller
    }

    #[must_use]
    pub const fn request_origin(&self) -> Version {
        self.request_origin
    }

    #[must_use]
    pub fn branch(&self) -> &str {
        &self.branch
    }

    #[must_use]
    pub const fn version(&self) -> Version {
        self.version
    }
}