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;