frame-state 0.2.0

Content-addressed state layer — haematite integration, entities, branching, cross-component references
Documentation
use std::fmt;

use frame_core::component::ComponentId;
use haematite::{BranchKind, Hash};

/// Fixed-width BLAKE3 identity of an entity's exact bytes.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct EntityId([u8; 32]);

impl EntityId {
    /// Derives an entity identity from its complete byte representation.
    #[must_use]
    pub fn of(bytes: &[u8]) -> Self {
        Self(Hash::of(bytes).into_bytes())
    }

    /// Constructs an identity from an already validated BLAKE3 digest.
    #[must_use]
    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
        Self(bytes)
    }

    /// Returns the fixed-width digest used in entity keys.
    #[must_use]
    pub const fn as_bytes(&self) -> &[u8; 32] {
        &self.0
    }
}

impl fmt::Display for EntityId {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        for byte in self.0 {
            write!(formatter, "{byte:02x}")?;
        }
        Ok(())
    }
}

/// A content-addressed link to one entity in one component namespace.
///
/// The binary form is versioned and fixed-width: one version byte followed by
/// the target component's 32 bytes and the entity digest's 32 bytes.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CrossComponentRef {
    /// Component whose committed namespace contains the entity.
    pub target: ComponentId,
    /// Content identity to read from that namespace.
    pub entity: EntityId,
}

impl CrossComponentRef {
    /// Constructs a typed cross-component link.
    #[must_use]
    pub const fn new(target: ComponentId, entity: EntityId) -> Self {
        Self { target, entity }
    }

    /// Encodes this link using Frame's stable v1 binary representation.
    #[must_use]
    pub fn to_bytes(self) -> [u8; crate::codec::CROSS_COMPONENT_REF_WIDTH] {
        crate::codec::encode_cross_component_ref(self)
    }

    /// Decodes one complete versioned link record.
    ///
    /// # Errors
    ///
    /// Returns [`crate::StateError::CorruptRecord`] naming the link field or
    /// version that failed validation.
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::StateError> {
        crate::codec::decode_cross_component_ref(bytes)
    }
}

/// Durable target state carried by a dangling cross-component reference.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum ReferenceTargetState {
    /// No durable meta record exists for this component identity.
    NeverInstalled,
    /// Install meta is durable but the namespace is not yet active.
    Installing,
    /// Archive is in progress for this generation.
    Archiving {
        /// Generation being archived.
        generation: u64,
    },
    /// This generation is durably archived and recoverable through the facade.
    Archived {
        /// Pinned archive generation.
        generation: u64,
    },
}

impl fmt::Display for ReferenceTargetState {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::NeverInstalled => formatter.write_str("never installed"),
            Self::Installing => formatter.write_str("installing"),
            Self::Archiving { generation } => {
                write!(formatter, "archiving generation {generation}")
            }
            Self::Archived { generation } => write!(formatter, "archived generation {generation}"),
        }
    }
}

/// Executable conflict policy declared by a component's storage schema.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum MergePolicy {
    /// The Work branch's conflicting value wins.
    Lww,
    /// Haematite's vector-clock policy vocabulary.
    ///
    /// Haematite 0.5.0 exposes this policy but reports it unimplemented, so
    /// schema declaration currently refuses this variant.
    VectorClock,
    /// A resolver previously registered under this stable name.
    Custom(String),
}

impl fmt::Display for MergePolicy {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Lww => formatter.write_str("lww"),
            Self::VectorClock => formatter.write_str("vector-clock"),
            Self::Custom(name) => write!(formatter, "custom:{name}"),
        }
    }
}

/// Frame-level storage declaration fixed before a namespace is installed.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ComponentSchema {
    /// Conflict policy used for every Work merge for this component.
    pub merge_policy: MergePolicy,
}

impl ComponentSchema {
    /// Declares last-writer-wins conflict resolution.
    #[must_use]
    pub const fn lww() -> Self {
        Self {
            merge_policy: MergePolicy::Lww,
        }
    }
}

/// One Work branch abandoned during namespace archive.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AbandonedWork {
    /// Durable branch name removed by archive.
    pub branch: String,
    /// Persisted branch kind; always [`BranchKind::Work`] for valid records.
    pub kind: BranchKind,
    /// Last committed root, retained for forensic checkout.
    pub last_root: Hash,
}

/// One enumerable archived namespace generation.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Archive {
    /// Monotonic generation, equal to the archived incarnation.
    pub generation: u64,
    /// Deterministic snapshot name containing component id and generation.
    pub snapshot_name: String,
    /// Pinned namespace root.
    pub root: Hash,
    /// Work branches explicitly abandoned with this archive.
    pub abandoned_work: Vec<AbandonedWork>,
}

/// Durable audit record written into a namespace by every Work merge.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MergeRecord {
    /// Monotonic record number within the component incarnation.
    pub sequence: u64,
    /// Declared policy used by the engine.
    pub policy: MergePolicy,
    /// Durable Work source branch.
    pub source_branch: String,
    /// Durable Namespace destination branch.
    pub destination_branch: String,
    /// Work root supplied to the three-way merge.
    pub source_root: Hash,
    /// Namespace root before applying the merge.
    pub destination_root_before: Hash,
    /// Content root produced by the engine before this audit record is added.
    pub merged_content_root: Hash,
}

/// A durable inconsistency repaired or transition completed at boot.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ReconcileAction {
    /// An install with no branch was rolled forward by creating it.
    InstallBranchCreated {
        /// Component whose Namespace branch was created.
        component: ComponentId,
    },
    /// An Archiving record with a live branch was archived, never deleted.
    OrphanedByCrash {
        /// Component whose archive was completed.
        component: ComponentId,
        /// Archive generation completed.
        generation: u64,
    },
    /// A pin-and-remove completed before the Archived meta update.
    ArchiveFinalized {
        /// Component whose durable marker was finalized.
        component: ComponentId,
        /// Archive generation finalized.
        generation: u64,
    },
}

/// Complete, observable result of one boot reconciliation pass.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ReconcileReport {
    /// Active namespaces compared durable-to-durable and left untouched.
    pub healthy_active: usize,
    /// Every transitional action taken; no finding is discarded.
    pub actions: Vec<ReconcileAction>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum MetaState {
    Installing,
    Active,
    Archiving {
        generation: u64,
        abandoned_work: Vec<AbandonedWork>,
    },
    Archived {
        generation: u64,
        abandoned_work: Vec<AbandonedWork>,
    },
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct MetaRecord {
    pub(crate) component: ComponentId,
    pub(crate) incarnation: u64,
    pub(crate) schema: ComponentSchema,
    pub(crate) state: MetaState,
}