Skip to main content

frame_state/
types.rs

1use std::fmt;
2
3use frame_core::component::ComponentId;
4use haematite::{BranchKind, Hash};
5
6/// Fixed-width BLAKE3 identity of an entity's exact bytes.
7#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
8pub struct EntityId([u8; 32]);
9
10impl EntityId {
11    /// Derives an entity identity from its complete byte representation.
12    #[must_use]
13    pub fn of(bytes: &[u8]) -> Self {
14        Self(Hash::of(bytes).into_bytes())
15    }
16
17    /// Constructs an identity from an already validated BLAKE3 digest.
18    #[must_use]
19    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
20        Self(bytes)
21    }
22
23    /// Returns the fixed-width digest used in entity keys.
24    #[must_use]
25    pub const fn as_bytes(&self) -> &[u8; 32] {
26        &self.0
27    }
28}
29
30impl fmt::Display for EntityId {
31    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
32        for byte in self.0 {
33            write!(formatter, "{byte:02x}")?;
34        }
35        Ok(())
36    }
37}
38
39/// A content-addressed link to one entity in one component namespace.
40///
41/// The binary form is versioned and fixed-width: one version byte followed by
42/// the target component's 32 bytes and the entity digest's 32 bytes.
43#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
44pub struct CrossComponentRef {
45    /// Component whose committed namespace contains the entity.
46    pub target: ComponentId,
47    /// Content identity to read from that namespace.
48    pub entity: EntityId,
49}
50
51impl CrossComponentRef {
52    /// Constructs a typed cross-component link.
53    #[must_use]
54    pub const fn new(target: ComponentId, entity: EntityId) -> Self {
55        Self { target, entity }
56    }
57
58    /// Encodes this link using Frame's stable v1 binary representation.
59    #[must_use]
60    pub fn to_bytes(self) -> [u8; crate::codec::CROSS_COMPONENT_REF_WIDTH] {
61        crate::codec::encode_cross_component_ref(self)
62    }
63
64    /// Decodes one complete versioned link record.
65    ///
66    /// # Errors
67    ///
68    /// Returns [`crate::StateError::CorruptRecord`] naming the link field or
69    /// version that failed validation.
70    pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::StateError> {
71        crate::codec::decode_cross_component_ref(bytes)
72    }
73}
74
75/// Durable target state carried by a dangling cross-component reference.
76#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
77pub enum ReferenceTargetState {
78    /// No durable meta record exists for this component identity.
79    NeverInstalled,
80    /// Install meta is durable but the namespace is not yet active.
81    Installing,
82    /// Archive is in progress for this generation.
83    Archiving {
84        /// Generation being archived.
85        generation: u64,
86    },
87    /// This generation is durably archived and recoverable through the facade.
88    Archived {
89        /// Pinned archive generation.
90        generation: u64,
91    },
92}
93
94impl fmt::Display for ReferenceTargetState {
95    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
96        match self {
97            Self::NeverInstalled => formatter.write_str("never installed"),
98            Self::Installing => formatter.write_str("installing"),
99            Self::Archiving { generation } => {
100                write!(formatter, "archiving generation {generation}")
101            }
102            Self::Archived { generation } => write!(formatter, "archived generation {generation}"),
103        }
104    }
105}
106
107/// Executable conflict policy declared by a component's storage schema.
108#[derive(Clone, Debug, Eq, PartialEq)]
109pub enum MergePolicy {
110    /// The Work branch's conflicting value wins.
111    Lww,
112    /// Haematite's vector-clock policy vocabulary.
113    ///
114    /// Haematite 0.5.0 exposes this policy but reports it unimplemented, so
115    /// schema declaration currently refuses this variant.
116    VectorClock,
117    /// A resolver previously registered under this stable name.
118    Custom(String),
119}
120
121impl fmt::Display for MergePolicy {
122    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
123        match self {
124            Self::Lww => formatter.write_str("lww"),
125            Self::VectorClock => formatter.write_str("vector-clock"),
126            Self::Custom(name) => write!(formatter, "custom:{name}"),
127        }
128    }
129}
130
131/// Frame-level storage declaration fixed before a namespace is installed.
132#[derive(Clone, Debug, Eq, PartialEq)]
133pub struct ComponentSchema {
134    /// Conflict policy used for every Work merge for this component.
135    pub merge_policy: MergePolicy,
136}
137
138impl ComponentSchema {
139    /// Declares last-writer-wins conflict resolution.
140    #[must_use]
141    pub const fn lww() -> Self {
142        Self {
143            merge_policy: MergePolicy::Lww,
144        }
145    }
146}
147
148/// One Work branch abandoned during namespace archive.
149#[derive(Clone, Debug, Eq, PartialEq)]
150pub struct AbandonedWork {
151    /// Durable branch name removed by archive.
152    pub branch: String,
153    /// Persisted branch kind; always [`BranchKind::Work`] for valid records.
154    pub kind: BranchKind,
155    /// Last committed root, retained for forensic checkout.
156    pub last_root: Hash,
157}
158
159/// One enumerable archived namespace generation.
160#[derive(Clone, Debug, Eq, PartialEq)]
161pub struct Archive {
162    /// Monotonic generation, equal to the archived incarnation.
163    pub generation: u64,
164    /// Deterministic snapshot name containing component id and generation.
165    pub snapshot_name: String,
166    /// Pinned namespace root.
167    pub root: Hash,
168    /// Work branches explicitly abandoned with this archive.
169    pub abandoned_work: Vec<AbandonedWork>,
170}
171
172/// Durable audit record written into a namespace by every Work merge.
173#[derive(Clone, Debug, Eq, PartialEq)]
174pub struct MergeRecord {
175    /// Monotonic record number within the component incarnation.
176    pub sequence: u64,
177    /// Declared policy used by the engine.
178    pub policy: MergePolicy,
179    /// Durable Work source branch.
180    pub source_branch: String,
181    /// Durable Namespace destination branch.
182    pub destination_branch: String,
183    /// Work root supplied to the three-way merge.
184    pub source_root: Hash,
185    /// Namespace root before applying the merge.
186    pub destination_root_before: Hash,
187    /// Content root produced by the engine before this audit record is added.
188    pub merged_content_root: Hash,
189}
190
191/// A durable inconsistency repaired or transition completed at boot.
192#[derive(Clone, Debug, Eq, PartialEq)]
193pub enum ReconcileAction {
194    /// An install with no branch was rolled forward by creating it.
195    InstallBranchCreated {
196        /// Component whose Namespace branch was created.
197        component: ComponentId,
198    },
199    /// An Archiving record with a live branch was archived, never deleted.
200    OrphanedByCrash {
201        /// Component whose archive was completed.
202        component: ComponentId,
203        /// Archive generation completed.
204        generation: u64,
205    },
206    /// A pin-and-remove completed before the Archived meta update.
207    ArchiveFinalized {
208        /// Component whose durable marker was finalized.
209        component: ComponentId,
210        /// Archive generation finalized.
211        generation: u64,
212    },
213}
214
215/// Complete, observable result of one boot reconciliation pass.
216#[derive(Clone, Debug, Default, Eq, PartialEq)]
217pub struct ReconcileReport {
218    /// Active namespaces compared durable-to-durable and left untouched.
219    pub healthy_active: usize,
220    /// Every transitional action taken; no finding is discarded.
221    pub actions: Vec<ReconcileAction>,
222}
223
224#[derive(Clone, Debug, Eq, PartialEq)]
225pub(crate) enum MetaState {
226    Installing,
227    Active,
228    Archiving {
229        generation: u64,
230        abandoned_work: Vec<AbandonedWork>,
231    },
232    Archived {
233        generation: u64,
234        abandoned_work: Vec<AbandonedWork>,
235    },
236}
237
238#[derive(Clone, Debug, Eq, PartialEq)]
239pub(crate) struct MetaRecord {
240    pub(crate) component: ComponentId,
241    pub(crate) incarnation: u64,
242    pub(crate) schema: ComponentSchema,
243    pub(crate) state: MetaState,
244}