frame-state 0.2.0

Content-addressed state layer — haematite integration, entities, branching, cross-component references
Documentation
use frame_core::component::ComponentId;
use haematite::{BranchKind, Hash, remove_branch};

use crate::codec::{archive_meta_key, decode_meta};
use crate::engine::{Storage, archive_name, commit_named, namespace_name};
use crate::error::{ReconcileInconsistency, StateError};
use crate::types::{AbandonedWork, Archive, MetaState};

const SHARD: usize = 0;

impl Storage {
    pub(crate) fn outstanding_work(
        &mut self,
        component: ComponentId,
    ) -> Result<Vec<AbandonedWork>, StateError> {
        let lineage = namespace_name(component);
        let names: Vec<String> = self
            .refs
            .list()
            .filter(|record| {
                record.kind == BranchKind::Work
                    && record.resolved_namespace_lineage() == Some(lineage.as_str())
            })
            .map(|record| record.name.clone())
            .collect();
        let mut abandoned = Vec::with_capacity(names.len());
        for name in names {
            let root = self.commit_work(&name)?;
            abandoned.push(AbandonedWork {
                branch: name,
                kind: BranchKind::Work,
                last_root: root,
            });
        }
        Ok(abandoned)
    }

    pub(crate) fn pin_remove_archive(
        &mut self,
        component: ComponentId,
        generation: u64,
        abandoned: &[AbandonedWork],
    ) -> Result<(), StateError> {
        let branch = self.namespace(component)?;
        commit_named(&branch, &mut self.nodes, &self.registry, &mut self.refs)?;
        let root = branch.current_root();
        let snapshot = archive_name(component, generation);
        match self.snapshots.get(&snapshot) {
            Some(actual) if actual == root => {}
            Some(actual) => {
                return Err(ReconcileInconsistency::SnapshotRootMismatch {
                    snapshot,
                    expected: root,
                    actual,
                }
                .into());
            }
            None => self.snapshots.name(&snapshot, root)?,
        }
        for work in abandoned {
            let observed = remove_branch(&work.branch, &mut self.refs)?;
            if observed.is_none() && self.work.contains_key(&work.branch) {
                return Err(StateError::WorkNotFound {
                    branch: work.branch.clone(),
                });
            }
            self.work.remove(&work.branch);
        }
        let removed = remove_branch(&namespace_name(component), &mut self.refs)?;
        if removed.is_none() {
            return Err(StateError::NotActive { component });
        }
        self.active.remove(&component);
        Ok(())
    }

    pub(crate) fn archives(&self, component: ComponentId) -> Result<Vec<Archive>, StateError> {
        let prefix = format!("archive/{component}/");
        let mut archives = Vec::new();
        for (name, root, _) in self.snapshots.list_snapshots() {
            let Some(generation_text) = name.strip_prefix(&prefix) else {
                continue;
            };
            let generation =
                generation_text
                    .parse::<u64>()
                    .map_err(|error| StateError::CorruptRecord {
                        detail: format!(
                            "archive snapshot '{name}' has invalid generation: {error}"
                        ),
                    })?;
            let evidence = self
                .meta
                .get(SHARD, &self.nodes, &archive_meta_key(component, generation))?
                .ok_or_else(|| StateError::CorruptRecord {
                    detail: format!(
                        "archive snapshot '{name}' has no immutable meta evidence record"
                    ),
                })?;
            let abandoned_work = match decode_meta(component, &evidence)?.state {
                MetaState::Archived {
                    generation: recorded,
                    abandoned_work,
                } if recorded == generation => abandoned_work,
                _ => {
                    return Err(StateError::CorruptRecord {
                        detail: format!("archive snapshot '{name}' has mismatched meta evidence"),
                    });
                }
            };
            archives.push(Archive {
                generation,
                snapshot_name: name,
                root,
                abandoned_work,
            });
        }
        archives.sort_by_key(|archive| archive.generation);
        Ok(archives)
    }

    pub(crate) fn archived_root(
        &self,
        component: ComponentId,
        generation: u64,
    ) -> Result<Hash, StateError> {
        let name = archive_name(component, generation);
        self.snapshots
            .get(&name)
            .ok_or_else(|| StateError::CorruptRecord {
                detail: format!("archive snapshot '{name}' is absent"),
            })
    }
}