1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
use serde::{Deserialize, Serialize};
use std::fmt;
/// EventKind wraps a private u16. Products cannot construct arbitrary system kinds.
/// Use [`EventKind::try_custom`] for runtime input and [`EventKind::custom`]
/// for const/internal call sites that should panic on invalid shape.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct EventKind(u16); // PRIVATE inner field — not pub
/// Validation error returned by [`EventKind::try_custom`].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum EventKindError {
/// The category does not fit in the supported 4-bit namespace.
CategoryOutOfRange {
/// Rejected category value.
category: u8,
},
/// Category `0x0` is reserved for system kinds.
ReservedSystemCategory,
/// Category `0xD` is reserved for effect kinds.
ReservedEffectCategory,
/// The type id exceeds the supported 12-bit namespace.
TypeIdOutOfRange {
/// Rejected type identifier.
type_id: u16,
},
}
impl EventKind {
/// Fallible custom kind constructor for public input.
///
/// Use this when the category/type pair originates from user input,
/// configuration, or any other runtime boundary that must not panic.
///
/// # Errors
/// Returns [`EventKindError`] when the caller supplies a reserved category
/// or a value that does not fit the supported 4-bit/12-bit custom namespace.
pub fn try_custom(category: u8, type_id: u16) -> Result<Self, EventKindError> {
if category >= 16 {
return Err(EventKindError::CategoryOutOfRange { category });
}
if category == 0 {
return Err(EventKindError::ReservedSystemCategory);
}
if category == 0xD {
return Err(EventKindError::ReservedEffectCategory);
}
if type_id >= 0x1000 {
return Err(EventKindError::TypeIdOutOfRange { type_id });
}
Ok(Self(((category as u16) << 12) | type_id))
}
/// category:type encoding. Upper 4 bits = category, lower 12 = type.
/// Products use categories 0x1-0xC, 0xE-0xF. System reserves 0x0 and 0xD.
///
/// This constructor is intentionally strict: invalid inputs panic instead
/// of silently truncating into a different namespace. That keeps the
/// `const fn` surface honest and prevents accidental cross-category
/// collisions from being smuggled through as "best effort" encoding.
/// Runtime callers should prefer [`EventKind::try_custom`] so invalid user
/// input becomes a typed error instead of a process abort.
///
/// # Example
/// ```
/// use batpak::prelude::*;
/// let kind = EventKind::custom(0xF, 1);
/// assert!(!kind.is_system());
/// assert!(!kind.is_effect());
/// ```
///
/// # Panics
/// Panics when `category` or `type_id` fall outside the supported custom
/// namespace. This is intentional for const/static/internal use only.
pub const fn custom(category: u8, type_id: u16) -> Self {
// Validate: only lower 4 bits of category survive the shift.
// category >= 16 would silently overflow into wrong namespace.
assert!(category < 16, "EventKind category must be 0-15 (4 bits)");
// Categories 0x0 (system) and 0xD (effect) are reserved for library constants.
// Products must use 0x1-0xC or 0xE-0xF.
assert!(
category != 0,
"EventKind category 0x0 is reserved for system kinds (SYSTEM_INIT, etc.)"
);
assert!(
category != 0xD,
"EventKind category 0xD is reserved for effect kinds (EFFECT_ERROR, etc.)"
);
assert!(
type_id < 0x1000,
"EventKind type_id must fit in 12 bits without truncation"
);
Self(((category as u16) << 12) | type_id)
}
/// Returns the upper 4-bit category of this kind.
pub const fn category(self) -> u8 {
(self.0 >> 12) as u8
}
/// Returns the lower 12-bit type identifier within the category.
pub const fn type_id(self) -> u16 {
self.0 & 0x0FFF
}
/// Returns `true` if this kind is in the reserved system category (0x0).
pub const fn is_system(self) -> bool {
self.category() == 0x0
}
/// Returns `true` if this kind is in the reserved effect category (0xD).
pub const fn is_effect(self) -> bool {
self.category() == 0xD
}
/// Library constants. Products NEVER define these — they use custom().
/// User-defined data event. Uses product category 0x1, not system category 0x0.
pub const DATA: Self = Self(0x1000);
/// System initialisation event.
pub const SYSTEM_INIT: Self = Self(0x0001);
/// System shutdown event.
pub const SYSTEM_SHUTDOWN: Self = Self(0x0002);
/// System heartbeat event.
pub const SYSTEM_HEARTBEAT: Self = Self(0x0003);
/// System configuration-change event.
pub const SYSTEM_CONFIG_CHANGE: Self = Self(0x0004);
/// System checkpoint event.
pub const SYSTEM_CHECKPOINT: Self = Self(0x0005);
/// Batch envelope marker. Internal only—never visible to queries.
pub const SYSTEM_BATCH_BEGIN: Self = Self(0x0006);
/// Batch commit marker. Internal only—never visible to queries.
/// Paired with SYSTEM_BATCH_BEGIN for two-phase commit semantics.
pub const SYSTEM_BATCH_COMMIT: Self = Self(0x0007);
/// Store lifecycle receipt emitted after a successful mutable open.
pub const SYSTEM_OPEN_COMPLETED: Self = Self(0x0008);
/// Store lifecycle receipt emitted during an explicit graceful close.
pub const SYSTEM_CLOSE_COMPLETED: Self = Self(0x0009);
/// Persisted gate-denial audit receipt.
pub const SYSTEM_DENIAL: Self = Self(0x000F);
/// Effect: an error was observed during processing.
pub const EFFECT_ERROR: Self = Self(0xD001);
/// Effect: a retry is being attempted.
pub const EFFECT_RETRY: Self = Self(0xD002);
/// Effect: acknowledgement of a prior event.
pub const EFFECT_ACK: Self = Self(0xD004);
/// Effect: backpressure signal to slow producers.
pub const EFFECT_BACKPRESSURE: Self = Self(0xD005);
/// Effect: cancellation of a prior request.
pub const EFFECT_CANCEL: Self = Self(0xD006);
/// Effect: a conflict was detected.
pub const EFFECT_CONFLICT: Self = Self(0xD007);
/// Used by compact() for tombstone markers. Public so consumers can detect them.
pub const TOMBSTONE: Self = Self(0x0FFE);
}
impl fmt::Display for EventKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x{:04X}", self.0)
}
}
impl fmt::Display for EventKindError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CategoryOutOfRange { category } => {
write!(f, "EventKind category {category:#X} must fit in 4 bits")
}
Self::ReservedSystemCategory => {
write!(f, "EventKind category 0x0 is reserved for system kinds")
}
Self::ReservedEffectCategory => {
write!(f, "EventKind category 0xD is reserved for effect kinds")
}
Self::TypeIdOutOfRange { type_id } => {
write!(f, "EventKind type_id {type_id:#X} must fit in 12 bits")
}
}
}
}
impl std::error::Error for EventKindError {}