Skip to main content

hopper_core/account/
segment_role.rs

1//! Segment roles -- typed semantic classification for account segments.
2//!
3//! Each segment in a Hopper segmented account can be assigned a role that
4//! conveys its purpose to tooling, migration planners, and runtime guards.
5//!
6//! ## Roles
7//!
8//! | Role      | Description                              | Writability | Migration |
9//! |-----------|------------------------------------------|-------------|-----------|
10//! | Core      | Primary fixed-layout state               | Read/Write  | Must copy |
11//! | Extension | Optional fields appended in later version| Read/Write  | Append-safe|
12//! | Journal   | Append-only audit trail (`Journal<T>`)   | Append-only | Clearable |
13//! | Index     | Lookup index (SortedVec, SlotMap)         | Read/Write  | Rebuildable|
14//! | Cache     | Derived/computed values, can be rebuilt   | Read/Write  | Droppable |
15//! | Audit     | Immutable audit log, locked after init    | Read-only   | Must copy |
16//! | Shard     | Part of a sharded collection              | Read/Write  | Redistributable|
17//!
18//! ## Wire Encoding
19//!
20//! The role is encoded in the **upper 4 bits** of the segment entry `flags` field
21//! (bits 12-15 of the u16 flags). The lower 12 bits remain available for
22//! per-segment flags (`LOCKED`, `FROZEN`, `DYNAMIC`, etc.).
23//!
24//! ```text
25//! flags [u16 LE]:
26//!   bits 0-2:   LOCKED | FROZEN | DYNAMIC (existing)
27//!   bits 3-11:  reserved for future per-segment flags
28//!   bits 12-15: SegmentRole (0-7)
29//! ```
30
31/// Segment role classification.
32///
33/// Encoded as a 4-bit value (0-15). Currently 8 roles defined.
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35#[repr(u8)]
36pub enum SegmentRole {
37    /// Primary fixed-layout state. Must be preserved across migrations.
38    Core = 0,
39    /// Optional extension fields added in later versions.
40    Extension = 1,
41    /// Append-only audit trail (`Journal<T>`). Can be cleared on migration.
42    Journal = 2,
43    /// Lookup index (`SortedVec`, `SlotMap`). Rebuildable from core data.
44    Index = 3,
45    /// Derived/computed cache. Safe to drop and rebuild.
46    Cache = 4,
47    /// Immutable audit log. Locked after initialization.
48    Audit = 5,
49    /// Part of a sharded collection. May be redistributed on rebalance.
50    Shard = 6,
51    /// Unclassified segment (legacy compatibility).
52    Unclassified = 7,
53}
54
55impl SegmentRole {
56    /// Decode role from segment flags (upper 4 bits).
57    #[inline(always)]
58    pub const fn from_flags(flags: u16) -> Self {
59        match (flags >> 12) & 0xF {
60            0 => Self::Core,
61            1 => Self::Extension,
62            2 => Self::Journal,
63            3 => Self::Index,
64            4 => Self::Cache,
65            5 => Self::Audit,
66            6 => Self::Shard,
67            _ => Self::Unclassified,
68        }
69    }
70
71    /// Encode role into segment flags (preserving lower 12 bits).
72    #[inline(always)]
73    pub const fn into_flags(self, existing_flags: u16) -> u16 {
74        (existing_flags & 0x0FFF) | ((self as u16) << 12)
75    }
76
77    /// Whether this segment must be preserved during migration.
78    #[inline(always)]
79    pub const fn must_preserve(&self) -> bool {
80        matches!(*self, Self::Core | Self::Audit)
81    }
82
83    /// Whether this segment can safely be cleared on migration.
84    #[inline(always)]
85    pub const fn clearable_on_migration(&self) -> bool {
86        matches!(*self, Self::Journal | Self::Cache)
87    }
88
89    /// Whether this segment can be rebuilt from other data.
90    #[inline(always)]
91    pub const fn rebuildable(&self) -> bool {
92        matches!(*self, Self::Index | Self::Cache)
93    }
94
95    /// Whether this segment should be append-only at runtime.
96    #[inline(always)]
97    pub const fn is_append_only(&self) -> bool {
98        matches!(*self, Self::Journal | Self::Audit)
99    }
100
101    /// Whether writes to this segment should be rejected after init.
102    #[inline(always)]
103    pub const fn is_immutable_after_init(&self) -> bool {
104        matches!(*self, Self::Audit)
105    }
106
107    /// Whether this segment's data must be copied during migration.
108    ///
109    /// Core and Audit segments contain irreplaceable state that cannot
110    /// be rebuilt or cleared, their bytes must survive migration intact.
111    #[inline(always)]
112    pub const fn requires_migration_copy(&self) -> bool {
113        matches!(*self, Self::Core | Self::Audit)
114    }
115
116    /// Whether this segment can be safely dropped (zeroed) without data loss.
117    ///
118    /// Cache segments hold derived/computed values that can be rebuilt
119    /// from other on-chain state. Dropping them is always safe.
120    #[inline(always)]
121    pub const fn is_safe_to_drop(&self) -> bool {
122        matches!(*self, Self::Cache)
123    }
124
125    /// Whether mutations to this segment should generate a receipt entry.
126    ///
127    /// Core, Extension, and Shard mutations are always receipt-worthy.
128    /// Journal and Audit appends are also receipt-worthy.
129    /// Cache and Index rebuilds typically are not.
130    #[inline(always)]
131    pub const fn should_emit_receipt(&self) -> bool {
132        matches!(
133            *self,
134            Self::Core | Self::Extension | Self::Journal | Self::Audit | Self::Shard
135        )
136    }
137
138    /// Whether this segment is relevant to operator dashboards and Manager output.
139    ///
140    /// Core, Audit, and Journal segments carry meaningful business state.
141    /// Cache and Index are derived and typically hidden from operators.
142    #[inline(always)]
143    pub const fn is_operator_relevant(&self) -> bool {
144        matches!(
145            *self,
146            Self::Core | Self::Extension | Self::Journal | Self::Audit | Self::Shard
147        )
148    }
149
150    /// Whether this segment potentially holds financial state.
151    ///
152    /// Core and Extension segments may contain balance/treasury fields.
153    /// Other segments (Journal, Cache, Index) typically hold derived data.
154    #[inline(always)]
155    pub const fn may_hold_financial_state(&self) -> bool {
156        matches!(*self, Self::Core | Self::Extension)
157    }
158
159    /// Human-readable role name (for schema export and tooling).
160    #[inline(always)]
161    pub const fn name(&self) -> &'static str {
162        match self {
163            Self::Core => "core",
164            Self::Extension => "extension",
165            Self::Journal => "journal",
166            Self::Index => "index",
167            Self::Cache => "cache",
168            Self::Audit => "audit",
169            Self::Shard => "shard",
170            Self::Unclassified => "unclassified",
171        }
172    }
173}
174
175/// Segment role flags -- convenience constants for `SegmentEntry::new()`.
176pub const SEG_ROLE_CORE: u16 = (SegmentRole::Core as u16) << 12;
177pub const SEG_ROLE_EXTENSION: u16 = (SegmentRole::Extension as u16) << 12;
178pub const SEG_ROLE_JOURNAL: u16 = (SegmentRole::Journal as u16) << 12;
179pub const SEG_ROLE_INDEX: u16 = (SegmentRole::Index as u16) << 12;
180pub const SEG_ROLE_CACHE: u16 = (SegmentRole::Cache as u16) << 12;
181pub const SEG_ROLE_AUDIT: u16 = (SegmentRole::Audit as u16) << 12;
182pub const SEG_ROLE_SHARD: u16 = (SegmentRole::Shard as u16) << 12;