rushdb_types/command.rs
1use serde::{Deserialize, Serialize};
2use uuid::Uuid;
3
4use crate::ids::OwnerKind;
5use crate::ids::TenantId;
6use crate::model::{Cardinality, Nullability, TypeState};
7use crate::occurrence::{EmbeddingBinding, EmbeddingSpaceInfo, OccurrenceRow};
8
9/// Current deterministic command format. Only this version is accepted by
10/// the state-machine expander; it is independent of the platform wire protocol.
11/// Reset to 1 for the first release (no earlier format was ever shipped);
12/// bump it whenever a command type changes.
13pub const COMMAND_VERSION: u32 = 1;
14
15/// Deterministic mutation set applied atomically by the replicated state
16/// machine. Contains allocated IDs and encoded values; apply never reads a
17/// clock, RNG, network or parser (plan documents 05/07).
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19pub struct CommitBatch {
20 /// Command format version used to interpret the batch.
21 pub command_version: u32,
22 /// Tenant whose keyspace the batch mutates.
23 pub tenant: TenantId,
24 /// Client-visible transaction identifier.
25 pub transaction_id: String,
26 /// Caller-chosen idempotency key; replays discover the stored outcome.
27 pub commit_key: Uuid,
28 /// Commit index the batch is expected to be applied at.
29 pub start_index: u64,
30 /// Isolation level under which the batch was planned.
31 pub isolation: IsolationMode,
32 /// Conditions that must hold before any mutation is applied.
33 pub preconditions: Vec<Precondition>,
34 /// Ordered mutations applied atomically.
35 pub mutations: Vec<Mutation>,
36}
37
38/// Isolation level used when planning a batch.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40pub enum IsolationMode {
41 /// Observe reads committed before the batch was planned.
42 ReadCommitted,
43 /// Read a consistent snapshot taken at planning time.
44 Snapshot,
45 /// Fully serializable execution.
46 Serializable,
47}
48
49/// A single key condition guarding a batch.
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51pub struct Precondition {
52 /// Key the condition inspects.
53 pub key: Vec<u8>,
54 /// Condition evaluated against the key.
55 pub kind: PreconditionKind,
56}
57
58/// The two supported precondition checks.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
60pub enum PreconditionKind {
61 /// The key must exist.
62 Exists,
63 /// The key must be absent.
64 Absent,
65}
66
67impl Precondition {
68 /// Require `key` to exist.
69 #[must_use]
70 pub fn exists(key: Vec<u8>) -> Self {
71 Precondition {
72 key,
73 kind: PreconditionKind::Exists,
74 }
75 }
76
77 /// Require `key` to be absent.
78 #[must_use]
79 pub fn absent(key: Vec<u8>) -> Self {
80 Precondition {
81 key,
82 kind: PreconditionKind::Absent,
83 }
84 }
85}
86
87/// A single deterministic mutation applied by the state machine.
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89pub enum Mutation {
90 /// Publish a tenant policy, using the applying CI as its generation.
91 SetSecurityPolicy {
92 /// Policy payload to publish.
93 policy: crate::security::DataAccessPolicy,
94 /// Enforcement strength for the policy.
95 mode: crate::security::AuthorizationMode,
96 },
97 /// Create a record header with the given id, ordinal and label.
98 CreateRecordHeader {
99 /// Public id of the new record.
100 record_id: [u8; 16],
101 /// Dense ordinal assigned to the record.
102 ordinal: u64,
103 /// Primary label of the record.
104 label: String,
105 },
106 /// Delete a record header by id, ordinal and label.
107 DeleteRecordHeader {
108 /// Dense ordinal of the record.
109 ordinal: u64,
110 /// Public id of the record.
111 record_id: [u8; 16],
112 /// Primary label of the record.
113 label: String,
114 },
115 /// Move a record between its single primary-label membership sets while
116 /// preserving its stable public id, ordinal, fields, and relationships.
117 RetypeRecordHeader {
118 /// Public id of the record.
119 record_id: [u8; 16],
120 /// Dense ordinal of the record.
121 ordinal: u64,
122 /// Label the record is leaving.
123 from_label: String,
124 /// Label the record is moving to.
125 to_label: String,
126 },
127 /// Create a relationship header between two records.
128 CreateRelationshipHeader {
129 /// Public id of the new relationship.
130 relationship_id: [u8; 16],
131 /// Dense ordinal assigned to the relationship.
132 ordinal: u64,
133 /// Relationship type.
134 rel_type: String,
135 /// Ordinal of the source record.
136 source_ord: u64,
137 /// Ordinal of the target record.
138 target_ord: u64,
139 /// Public id of the source record.
140 source_id: [u8; 16],
141 /// Public id of the target record.
142 target_id: [u8; 16],
143 },
144 /// Delete a relationship header by id, ordinal and type.
145 DeleteRelationshipHeader {
146 /// Public id of the relationship.
147 relationship_id: [u8; 16],
148 /// Dense ordinal of the relationship.
149 ordinal: u64,
150 /// Relationship type.
151 rel_type: String,
152 /// Ordinal of the source record.
153 source_ord: u64,
154 /// Ordinal of the target record.
155 target_ord: u64,
156 },
157 /// Create a property family if it does not already exist.
158 EnsureFamily {
159 /// Dense id of the family.
160 family_id: u64,
161 /// Namespace of the property.
162 namespace: String,
163 /// Whether the family is owned by records or relationships.
164 owner_scope: OwnerKind,
165 /// Normalized name used for lookup.
166 normalized_name: String,
167 /// Human-readable display name.
168 display_name: String,
169 },
170 /// Create a property definition within a family.
171 EnsureDefinition {
172 /// Dense id of the property.
173 property_id: u64,
174 /// Family the property belongs to.
175 family_id: u64,
176 /// Current type resolution state.
177 type_state: TypeState,
178 /// Allowed value cardinality.
179 cardinality: Cardinality,
180 /// Nullability constraint.
181 nullability: Nullability,
182 },
183 /// Atomically remove a property family: every occurrence of the family
184 /// (records and relationships), the family row/signature, and all property
185 /// definitions. Used by the protocol `deleteProperty` operation.
186 DeleteFamily {
187 /// Dense id of the family.
188 family_id: u64,
189 /// Namespace of the property.
190 namespace: String,
191 /// Whether the family is owned by records or relationships.
192 owner_scope: OwnerKind,
193 /// Normalized name used for lookup.
194 normalized_name: String,
195 /// Human-readable display name.
196 display_name: String,
197 },
198 /// Insert or update one property occurrence.
199 PutOccurrence {
200 /// Occurrence row to persist.
201 row: OccurrenceRow,
202 /// Canonical encoded payload, or `None` for payload-free states.
203 canonical_value_bytes: Option<Vec<u8>>,
204 },
205 /// Delete one property occurrence.
206 DeleteOccurrence {
207 /// Occurrence row to remove.
208 row: OccurrenceRow,
209 },
210 /// Create or update an embedding space.
211 EnsureEmbeddingSpace {
212 /// Alias the space is reachable under.
213 alias: String,
214 /// Space definition to ensure.
215 info: EmbeddingSpaceInfo,
216 /// Whether this creates a new space.
217 new: bool,
218 },
219 /// Insert or update an embedding binding.
220 UpsertEmbeddingBinding {
221 /// Binding to persist.
222 binding: EmbeddingBinding,
223 /// Vector payload to store, if any.
224 vector: Option<Vec<f32>>,
225 /// When set, statuses are recomputed against this expected live hash.
226 expect_live_hash: bool,
227 },
228 /// v3: resolve and remove canonical binary vector keys at apply time.
229 /// The alias must still identify this space; counters and the immutable
230 /// space definition remain so a recreated alias receives a fresh space ID.
231 ClearEmbeddingSpace {
232 /// Alias of the space to clear.
233 alias: String,
234 /// Dense id of the space.
235 space_id: u64,
236 },
237 /// Replicated meta bookkeeping (projection watermarks and similar);
238 /// value semantics are opaque to the state machine.
239 SetMetaValue {
240 /// Logical meta key.
241 key: String,
242 /// New integer value.
243 value: u64,
244 },
245 /// Replicated opaque blob (policies, configuration); replayed verbatim
246 /// by the deterministic state machine like any other mutation.
247 SetMetaBlob {
248 /// Logical meta key.
249 key: String,
250 /// New blob value.
251 value: Vec<u8>,
252 },
253 /// Deterministic deletion of a replicated meta blob (index registry rows).
254 DeleteMetaBlob {
255 /// Logical meta key.
256 key: String,
257 },
258 /// Delete one tenant-scoped raw key during an explicit maintenance repair.
259 /// The maintenance caller must validate the key belongs to its tenant.
260 DeleteRawKey {
261 /// Canonical tenant-scoped key to remove.
262 key: Vec<u8>,
263 },
264 /// Put one tenant-scoped raw key during an explicit maintenance fixture
265 /// or rebuild operation.
266 PutRawKey {
267 /// Canonical tenant-scoped key to write.
268 key: Vec<u8>,
269 /// Value bytes to store.
270 value: Vec<u8>,
271 },
272 /// One bounded, resumable purge step: delete up to `max_keys` of the
273 /// smallest remaining purgeable keys (tenant keyspace excluding the
274 /// security subrange, then vector/ANN metadata), without retaining MVCC
275 /// history for erased keys. The driver loops commits until nothing
276 /// remains; expansion is a pure function of (state, batch), so replay
277 /// reproduces the same sequence.
278 PurgeTenantChunk {
279 /// Maximum number of keys this step may delete.
280 max_keys: u64,
281 },
282 /// Register a tenant scope in the directory: assign the next
283 /// ordinal unless the scope already has an active one. Expansion reads
284 /// and bumps the directory sequence, so replay reproduces the ordinal.
285 RegisterTenant {
286 /// SHA-256 digest of the scope value (the scope string is never
287 /// stored).
288 scope: crate::ids::TenantScope,
289 },
290 /// Retire the batch tenant's ordinal after its keyspace is purged: both
291 /// directory rows move to `Purged`, and the ordinal is never reassigned.
292 RetireTenant,
293 /// Reclaim embedding vector slots whose superseding binding version is at
294 /// or below `through_ci` (the GC watermark). A re-binding allocates a fresh
295 /// slot and records the old one in the reclamation index; the old slot's
296 /// bytes stay readable by pinned views until the history floor passes it.
297 /// Expansion deletes a bounded batch of the oldest reclaimable slots plus
298 /// their reclamation rows; the driver loops commits until none remain.
299 ReclaimEmbeddingSlots {
300 /// Highest superseding commit index safe to reclaim (inclusive).
301 through_ci: u64,
302 },
303 /// Declare an index: allocate the next id and store the
304 /// definition as `Building { cursor: 0 }`. From this commit on, the
305 /// deriver maintains entries of covered records.
306 DefineIndex {
307 /// Tenant-unique name.
308 name: String,
309 /// Record label.
310 label: String,
311 /// Property name (normalized at expansion).
312 property: String,
313 /// Index kind.
314 kind: crate::index::IndexKind,
315 },
316 /// Advance a building index's cursor to `through` (the deriver backfills
317 /// the records it newly covers from their current values), or mark it
318 /// `Ready` when `through` is `None`.
319 IndexBackfill {
320 /// Index id.
321 id: u32,
322 /// New cursor, or `None` for `Ready`.
323 through: Option<u64>,
324 },
325 /// Start dropping an index by name: `Dropping { cursor: 0 }`.
326 DropIndex {
327 /// Index name.
328 name: String,
329 },
330 /// Advance a dropping index's cursor to `through` (the deriver removes
331 /// the entries it no longer covers), or remove the definition when
332 /// `through` is `None`.
333 IndexDrop {
334 /// Index id.
335 id: u32,
336 /// New cursor, or `None` to remove the definition.
337 through: Option<u64>,
338 },
339 /// An index applier (text or structural) processed `owners` of index
340 /// `index` as of commit `upto`: remove each owner's pending row
341 /// unless a later commit re-marked it.
342 IndexApplied {
343 /// Index id.
344 index: u32,
345 /// Snapshot the applier read.
346 upto: u64,
347 /// Owners whose current text was applied.
348 owners: Vec<u64>,
349 },
350 /// [`Mutation::SetSecurityPolicy`] with a small-count threshold.
351 /// Appended last so earlier logs keep their variant indexes; written only
352 /// when the threshold is set, so a log without one stays readable by
353 /// binaries that predate it.
354 SetSecurityPolicyThreshold {
355 /// Policy payload to publish.
356 policy: crate::security::DataAccessPolicy,
357 /// Enforcement strength for the policy.
358 mode: crate::security::AuthorizationMode,
359 /// Smallest group a catalog read may report.
360 min_count: u64,
361 },
362}