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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
use std::collections::HashMap;
use bincode::{Decode, Encode};
use serde::{Deserialize, Serialize};
use super::address::{RevisionId, SpaceId};
/// History retention mode for compaction in a space.
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, Eq)]
pub enum CompactionPolicy {
/// Keep all revisions (default).
KeepAll,
/// Drop revisions older than the configured horizons during compaction.
RetentionWindow {
version_horizon: RevisionId,
tombstone_horizon: RevisionId,
},
/// Latest revision per address only (destructive).
LatestOnly,
}
impl Default for CompactionPolicy {
fn default() -> Self {
Self::KeepAll
}
}
/// Retention policy for companion `{name}_errors` spaces (resolved records only).
#[derive(Debug, Clone, Default, Serialize, Deserialize, Encode, Decode, PartialEq, Eq)]
pub struct ErrorRetentionPolicy {
/// Maximum tombstone-resolved error records to retain; older resolved pairs are compacted away.
#[serde(default)]
pub max_resolved_keep: Option<usize>,
}
impl ErrorRetentionPolicy {
/// Retain at most `n` most recently resolved error records.
pub fn keep_latest_resolved(n: usize) -> Self {
Self {
max_resolved_keep: Some(n),
}
}
}
/// Endpoint reverse-index coordinate layout (reserved `ENDPOINT_INDEX_SPACE` only).
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, Encode, Decode,
)]
pub enum EndpointIndexLayout {
/// M1 symmetric layout: no polarity coordinate dimension.
#[default]
V1Symmetric,
/// M2 layout: polarity dimension between endpoint coords and edge-id dimensions.
V2PolarityDim,
}
/// Configuration for a registered space.
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
pub struct SpaceConfig {
pub id: SpaceId,
/// Human-readable name.
pub name: String,
/// Number of dimensions all records in this space must have.
pub dims: usize,
/// Hilbert precision (bits per dimension) for this space.
///
/// This is a load-bearing invariant: every block in the space must be keyed
/// at the same precision or `BTreeMap` ordering breaks. It is fixed when the
/// space is registered and must satisfy `dims * bits_per_dim <= 128`.
pub bits_per_dim: u32,
/// Hilbert range sharding within this space (format v4).
///
/// `shard_id = hilbert_key >> (128 - shard_bits)`. Default `4` → 16 shards.
/// `0` disables intra-space sharding (single I/O thread per space).
pub shard_bits: u32,
/// Compaction history retention for this space.
#[serde(default)]
pub compaction_policy: CompactionPolicy,
/// Endpoint index layout version (meaningful only for `ENDPOINT_INDEX_SPACE`).
#[serde(default)]
pub endpoint_index_layout: EndpointIndexLayout,
/// Companion space for operation-level error records (M5).
#[serde(default)]
pub error_space: Option<SpaceId>,
/// When true, do not auto-register a companion error space on registration.
#[serde(default)]
pub skip_error_space: bool,
/// Optional retention for resolved records in the companion error space.
#[serde(default)]
pub error_retention: Option<ErrorRetentionPolicy>,
}
impl SpaceConfig {
/// Create a space configuration with the standard 8-bit Hilbert precision.
pub fn new(id: SpaceId, name: impl Into<String>, dims: usize) -> Self {
Self {
id,
name: name.into(),
dims,
bits_per_dim: 8,
shard_bits: 4,
compaction_policy: CompactionPolicy::default(),
endpoint_index_layout: EndpointIndexLayout::default(),
error_space: None,
skip_error_space: false,
error_retention: None,
}
}
/// Override the Hilbert precision (bits per dimension) for this space.
pub fn with_bits_per_dim(mut self, bits_per_dim: u32) -> Self {
self.bits_per_dim = bits_per_dim;
self
}
/// Override Hilbert range shard count (`2^shard_bits` I/O threads per space).
pub fn with_shard_bits(mut self, shard_bits: u32) -> Self {
self.shard_bits = shard_bits;
self
}
/// Override compaction history retention for this space.
pub fn with_compaction_policy(mut self, policy: CompactionPolicy) -> Self {
self.compaction_policy = policy;
self
}
/// Override endpoint index layout (reserved index space).
pub fn with_endpoint_index_layout(mut self, layout: EndpointIndexLayout) -> Self {
self.endpoint_index_layout = layout;
self
}
/// Link an existing companion error space.
pub fn with_error_space(mut self, error_space: SpaceId) -> Self {
self.error_space = Some(error_space);
self
}
/// Retention policy for resolved records in the companion error space.
pub fn with_error_retention(mut self, policy: ErrorRetentionPolicy) -> Self {
self.error_retention = Some(policy);
self
}
/// Disable auto-registration of a companion error space.
pub fn without_error_space(mut self) -> Self {
self.skip_error_space = true;
self
}
/// Companion error space id, if configured.
pub fn companion_error_space(&self) -> Option<SpaceId> {
self.error_space
}
}
/// Registry of all known spaces in the database.
/// Persisted as part of the database metadata block.
#[derive(Debug, Default, Serialize, Deserialize, Encode, Decode)]
pub struct SpaceRegistry {
spaces: HashMap<SpaceId, SpaceConfig>,
names: HashMap<String, SpaceId>,
}
impl SpaceRegistry {
/// Create an empty space registry.
pub fn new() -> Self {
Self::default()
}
/// Register a new space. Returns an error if the name or ID is already taken.
pub fn register(&mut self, config: SpaceConfig) -> Result<(), SpaceError> {
if self.spaces.contains_key(&config.id) {
return Err(SpaceError::DuplicateId(config.id));
}
if self.names.contains_key(&config.name) {
return Err(SpaceError::DuplicateName(config.name));
}
self.names.insert(config.name.clone(), config.id);
self.spaces.insert(config.id, config);
Ok(())
}
/// Look up a space by ID.
pub fn get(&self, id: SpaceId) -> Option<&SpaceConfig> {
self.spaces.get(&id)
}
/// Look up a space by name.
pub fn get_by_name(&self, name: &str) -> Option<&SpaceConfig> {
self.names.get(name).and_then(|id| self.spaces.get(id))
}
/// Return all registered space IDs.
pub fn space_ids(&self) -> Vec<SpaceId> {
self.spaces.keys().copied().collect()
}
/// Companion error space for a data space, if linked.
pub fn error_space_for(&self, data_space: SpaceId) -> Option<SpaceId> {
self.get(data_space)?.error_space
}
/// Derive a companion error space id from a data space id.
pub fn derive_error_space_id(data_space: SpaceId) -> SpaceId {
SpaceId(data_space.0 ^ 0xE000_0000_0000_0000)
}
/// Remove a space and return its previous configuration, if it existed.
pub fn remove(&mut self, id: SpaceId) -> Option<SpaceConfig> {
if let Some(config) = self.spaces.remove(&id) {
self.names.remove(&config.name);
Some(config)
} else {
None
}
}
/// Replace an existing space configuration (e.g. endpoint index layout upgrade).
pub fn update(&mut self, config: SpaceConfig) -> Result<(), SpaceError> {
let existing = self
.spaces
.get(&config.id)
.ok_or(SpaceError::NotFound(config.id))?;
if existing.name != config.name && self.names.contains_key(&config.name) {
return Err(SpaceError::DuplicateName(config.name));
}
if existing.name != config.name {
self.names.remove(&existing.name);
self.names.insert(config.name.clone(), config.id);
}
self.spaces.insert(config.id, config);
Ok(())
}
}
/// Errors returned by space registry operations.
#[derive(Debug, Clone)]
pub enum SpaceError {
/// The provided `SpaceId` is already registered.
DuplicateId(SpaceId),
/// The provided space name is already registered.
DuplicateName(String),
/// A requested space does not exist.
NotFound(SpaceId),
}