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}