haematite-cli 0.35.0

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
//! Fork origins are recorded at the branch's immutable roots.

use haematite::branch::ForkWrite;
use serde::{Deserialize, Serialize};

use super::{Captured, Failure, Storage, identifier, reads};
use crate::serve::protocol::Version;

const PROVENANCE_KEY: &[u8] = b"b\0fork";

#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ForkProvenance {
    pub branch: String,
    pub version: Version,
}

/// The provenance row as stored: an ordinary branch's bytes are the same as
/// before lines, and a line's add `"line": true` (STOR-050 R2). Each fork
/// writes its own, so a fork of a line is a line only when it asks to be.
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct Stored {
    branch: String,
    version: Version,
    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
    line: bool,
}

pub(super) fn provenance_write(
    writer: &haematite::db::branches::BranchDatabase,
    branch: &str,
    at: Version,
    line: bool,
) -> Result<ForkWrite, Failure> {
    Ok(ForkWrite {
        shard_id: writer.shard_for(PROVENANCE_KEY),
        key: PROVENANCE_KEY.to_vec(),
        value: Some(
            serde_json::to_vec(&Stored {
                branch: branch.to_owned(),
                version: at,
                line,
            })
            .map_err(Failure::storage)?,
        ),
    })
}

/// A provenance row's origin and whether it made a line.
///
/// # Errors
/// A row that does not decode, or names no branch.
pub(in crate::serve) fn decode(bytes: &[u8]) -> Result<(ForkProvenance, bool), Failure> {
    let stored: Stored = serde_json::from_slice(bytes).map_err(Failure::storage)?;
    identifier(&stored.branch)?;
    Ok((
        ForkProvenance {
            branch: stored.branch,
            version: stored.version,
        },
        stored.line,
    ))
}

/// The fork origin recorded at `at`'s roots and whether it made a line, or
/// `None` for a branch that was not forked.
///
/// # Errors
/// As [`decode`]; storage failures.
pub(super) fn origin(
    reader: &haematite::db::ReadOnlyDatabase,
    at: &Captured,
) -> Result<Option<(ForkProvenance, bool)>, Failure> {
    reads::get_bytes(reader, at, PROVENANCE_KEY)?
        .map(|bytes| decode(&bytes))
        .transpose()
}

pub(super) fn read_provenance(
    reader: &haematite::db::ReadOnlyDatabase,
    at: &Captured,
) -> Result<ForkProvenance, Failure> {
    provenance(reader, at)?.ok_or_else(|| {
        Failure::new(
            "fork_provenance_missing",
            "branch has no recorded fork origin",
        )
    })
}

/// The fork origin recorded at `at`'s roots, or `None` for a branch that was
/// not forked (STOR-003 HR1: a release reads it for every live branch).
pub(super) fn provenance(
    reader: &haematite::db::ReadOnlyDatabase,
    at: &Captured,
) -> Result<Option<ForkProvenance>, Failure> {
    Ok(origin(reader, at)?.map(|(provenance, _line)| provenance))
}

impl Storage {
    /// Read the explicit fork origin at the selected immutable roots.
    ///
    /// # Errors
    /// Refuses missing or invalid provenance and propagates storage failures.
    pub fn fork_provenance(&self, at: &Captured) -> Result<ForkProvenance, Failure> {
        read_provenance(&self.reader, at)
    }
}