@feltdb/core 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/create/package-versions.js +1 -1
  2. package/dist/create/server-source/crates/feltdb/src/application.rs +19 -0
  3. package/dist/create/server-source/crates/feltdb/src/authority.rs +349 -0
  4. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority-client.rs +32 -0
  5. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority.rs +19 -0
  6. package/dist/create/server-source/crates/feltdb/src/lib.rs +444 -189
  7. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +244 -51
  8. package/dist/create/server-source/crates/feltdb/tests/authority_process.rs +297 -0
  9. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +41 -11
  10. package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +8 -3
  11. package/dist/create/server-source/crates/feltdb-server/src/main.rs +1164 -225
  12. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +134 -0
  13. package/dist/http-db.d.ts +3 -0
  14. package/dist/http-db.d.ts.map +1 -1
  15. package/dist/http-db.js +2 -1
  16. package/dist/state-contract.d.ts +4 -1
  17. package/dist/state-contract.d.ts.map +1 -1
  18. package/dist/state-contract.js +1 -1
  19. package/dist/studio-app/assets/{feltdb_wasm-bIqcRzAr.js → feltdb_wasm-DB8cX151.js} +1 -1
  20. package/dist/studio-app/assets/feltdb_wasm_bg-ClhDHp0S.wasm +0 -0
  21. package/dist/studio-app/assets/{index-XGYdlElN.js → index-B0k4UAlI.js} +1 -1
  22. package/dist/studio-app/index.html +1 -1
  23. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  24. package/package.json +1 -1
  25. package/dist/studio-app/assets/feltdb_wasm_bg-BE79okwX.wasm +0 -0
@@ -1,69 +1,65 @@
1
1
  #[cfg(test)]
2
2
  mod acceptance_tests;
3
- #[cfg(test)]
4
- mod admission_contract_tests;
5
- #[cfg(test)]
6
- mod managed_cas_tests;
7
3
  mod acquisition;
8
4
  pub mod admission;
5
+ #[cfg(test)]
6
+ mod admission_contract_tests;
7
+ pub mod adversarial_transport;
8
+ pub mod analytics;
9
9
  pub mod application;
10
10
  pub mod application_runtime;
11
+ #[cfg(not(target_arch = "wasm32"))]
12
+ pub mod authority;
13
+ pub mod authority_failover;
11
14
  pub mod authorization;
12
- pub mod policy_evaluation;
13
15
  #[cfg(test)]
14
16
  mod authorization_security_tests;
15
17
  pub mod capabilities;
16
18
  mod capability;
19
+ mod cardinality_diagnostics;
20
+ pub mod cardinality_endpoint;
21
+ pub mod causal_backlog_bound;
22
+ pub mod causal_dependency_barrier;
23
+ #[cfg(test)]
24
+ mod causal_dependency_barrier_phase_7_1;
25
+ pub mod concurrency_fuzzing;
26
+ pub mod consistency_contract;
17
27
  mod content_distribution;
18
28
  pub mod convergence;
29
+ pub mod crash_atomic_boundary;
30
+ pub mod crash_injection;
31
+ #[cfg(test)]
32
+ mod crash_recovery_tests;
19
33
  mod cron;
34
+ pub mod dedup_bound_investigation;
35
+ pub mod distributed_indexing;
20
36
  #[cfg(test)]
21
37
  mod distributed_tests;
22
- mod execution;
23
- pub mod analytics;
24
- pub mod distributed_indexing;
25
- pub mod indexing;
26
- pub mod sharding;
27
- pub mod transaction_preconditions;
28
- pub mod transactions;
29
- pub mod state_hash;
30
- pub mod state_model;
31
- pub mod state_facade;
32
- pub mod crash_injection;
33
- pub mod concurrency_fuzzing;
34
- pub mod replay_fuzzing;
35
- pub mod permutation_scheduler;
36
- pub mod multi_node_convergence;
37
38
  pub mod distributed_transactions;
38
- pub mod durable_operation_log;
39
+ pub mod durability_guarantees;
39
40
  pub mod durable_dedup_set;
40
- pub mod replication_protocol;
41
+ pub mod durable_operation_identity;
42
+ pub mod durable_operation_log;
43
+ pub mod durable_sync;
44
+ mod execution;
41
45
  pub mod in_process_transport;
42
- #[cfg(not(target_arch = "wasm32"))]
43
- pub mod tcp_transport;
44
- pub mod operation_algebra;
45
- pub mod crash_atomic_boundary;
46
+ pub mod indexing;
47
+ #[cfg(test)]
48
+ mod managed_cas_tests;
49
+ mod materialization;
50
+ pub mod metrics;
51
+ pub mod multi_node_convergence;
46
52
  pub mod multi_operation_transaction;
47
- pub mod p1_atomicity_acceptance;
53
+ pub mod observability;
54
+ mod operation;
55
+ pub mod operation_algebra;
56
+ pub mod operation_log;
48
57
  pub mod p1_application_atomicity;
49
- pub mod causal_backlog_bound;
50
- pub mod dedup_bound_investigation;
51
- pub mod durable_operation_identity;
52
- pub mod causal_dependency_barrier;
58
+ pub mod p1_atomicity_acceptance;
53
59
  pub mod partition_reconciliation;
54
- pub mod consistency_contract;
60
+ mod peer_registry;
61
+ pub mod permutation_scheduler;
55
62
  pub mod persistence_reality;
56
- pub mod adversarial_transport;
57
- pub mod replica_membership;
58
- pub mod replica_acknowledgements;
59
- pub mod replication_manager;
60
- pub mod authority_failover;
61
- pub mod metrics;
62
- pub mod query_performance;
63
- pub mod durable_sync;
64
- pub mod production_api;
65
- pub mod observability;
66
- pub mod durability_guarantees;
67
63
  #[cfg(test)]
68
64
  mod phase1b_acceptance;
69
65
  #[cfg(test)]
@@ -71,9 +67,19 @@ mod phase1c1_acceptance;
71
67
  #[cfg(test)]
72
68
  mod phase1c2_acceptance;
73
69
  #[cfg(test)]
70
+ mod phase1c3_acceptance;
71
+ #[cfg(test)]
74
72
  mod phase1c_atomicity_proof;
75
73
  #[cfg(test)]
76
- mod phase1c3_acceptance;
74
+ mod phase5_integration;
75
+ #[cfg(test)]
76
+ mod phase5_scenarios;
77
+ #[cfg(test)]
78
+ mod phase6_adversarial_scenarios;
79
+ #[cfg(test)]
80
+ mod phase6_convergence_validator;
81
+ #[cfg(test)]
82
+ mod phase6_persistence;
77
83
  #[cfg(test)]
78
84
  #[cfg(test)]
79
85
  mod phase_1c_real_tcp;
@@ -89,38 +95,34 @@ mod phase_3_durability;
89
95
  mod phase_4_baseline;
90
96
  #[cfg(test)]
91
97
  mod phase_5_soak;
92
- #[cfg(test)]
93
- mod phase5_integration;
94
- #[cfg(test)]
95
- mod phase5_scenarios;
96
- #[cfg(test)]
97
- mod phase6_persistence;
98
- #[cfg(test)]
99
- mod phase6_adversarial_scenarios;
100
- #[cfg(test)]
101
- mod phase6_convergence_validator;
102
- #[cfg(test)]
103
- mod transaction_invariants;
104
- #[cfg(test)]
105
- mod crash_recovery_tests;
106
- #[cfg(test)]
107
- mod causal_dependency_barrier_phase_7_1;
108
- mod cardinality_diagnostics;
109
- pub mod cardinality_endpoint;
110
- mod materialization;
111
- mod operation;
112
- pub mod operation_log;
113
- pub mod state_transition_store;
114
- pub mod submission;
115
- pub mod transaction_api;
116
- mod peer_registry;
98
+ pub mod policy_evaluation;
99
+ pub mod production_api;
117
100
  mod provenance;
101
+ pub mod query_performance;
118
102
  mod references;
103
+ pub mod replay_fuzzing;
104
+ pub mod replica_acknowledgements;
105
+ pub mod replica_membership;
106
+ pub mod replication_manager;
107
+ pub mod replication_protocol;
119
108
  mod routing;
109
+ pub mod sharding;
120
110
  pub mod state_contract;
111
+ pub mod state_facade;
112
+ pub mod state_hash;
113
+ pub mod state_model;
114
+ pub mod state_transition_store;
121
115
  mod storage;
116
+ pub mod submission;
122
117
  mod sync;
123
118
  pub mod sync_contract;
119
+ #[cfg(not(target_arch = "wasm32"))]
120
+ pub mod tcp_transport;
121
+ pub mod transaction_api;
122
+ #[cfg(test)]
123
+ mod transaction_invariants;
124
+ pub mod transaction_preconditions;
125
+ pub mod transactions;
124
126
  mod trigger;
125
127
  pub mod worker_mesh;
126
128
  mod workflow;
@@ -134,6 +136,14 @@ pub use acquisition::{
134
136
  AcquisitionDeduplicator, AcquisitionPolicy, AcquisitionResult, ContentRequest, ContentResponse,
135
137
  ContentTransferError,
136
138
  };
139
+ pub use admission::{ProductionRetryPolicy, RejectionReason, SubmissionOutcome};
140
+ pub use adversarial_transport::{
141
+ AdversarialTransport, ConnectionId, ConnectivityMatrix, FaultSeed, InMemoryTransport,
142
+ LinkState, MessageId, NetworkEvent, NetworkSchedule, NodeId, ReplicationTransport,
143
+ };
144
+ pub use analytics::{
145
+ AutoTuner, IndexAnalytics, IndexMetrics, PerformanceReport, QueryTrace, TuningRecommendation,
146
+ };
137
147
  pub use capability::{
138
148
  CapabilityAccessModel, CapabilityCheckpoint, CapabilityContext, CapabilityExecutionContext,
139
149
  CapabilityExecutionResult, CapabilityId, CapabilityLifecycleState, CapabilityMetadata,
@@ -141,6 +151,15 @@ pub use capability::{
141
151
  CapabilityRegistry, CapabilityRequirements, CapabilityResolver, CapabilityRole,
142
152
  DistributedCapabilityAdvertisement, ExecutionRef, FlowCapability, StateRequirement,
143
153
  };
154
+ pub use causal_dependency_barrier::{
155
+ ApplyResult, CausalDependencyBarrier, OperationState, PendingOperation, ReceiveResult,
156
+ StateCounters,
157
+ };
158
+ pub use consistency_contract::{
159
+ CommutativeVerificationResult, ConsensusEnforcementResult, ConsensusRequirementChecker,
160
+ ConsistencyContractEnforcer, DeterministicMergeVerificationResult,
161
+ NoDowngradeVerificationResult, OperationApplication,
162
+ };
144
163
  pub use content_distribution::{
145
164
  CapabilityLocationRegistry, ContentAcquisitionManager, ContentAddressableStore,
146
165
  DistributedReferenceResolver, ProjectionDistribution,
@@ -149,74 +168,41 @@ pub use convergence::{
149
168
  CausalHistory, ConflictResolution, ConflictResolutionStrategy, MergeResult, StateVersion,
150
169
  VectorClock,
151
170
  };
152
- pub use analytics::{AutoTuner, IndexAnalytics, IndexMetrics, PerformanceReport, QueryTrace, TuningRecommendation};
171
+ pub use crash_atomic_boundary::{
172
+ CrashAtomicExecutor, CrashBoundaryPoint, CrashRecoveryState, InvariantCheckResult,
173
+ RecoveryDecision,
174
+ };
153
175
  pub use cron::{CronSchedule, CronScheduler};
154
176
  pub use distributed_indexing::{
155
177
  ConsistencyLevel, ConsistencyProtocol, DistributedIndexManager, DistributedSyncStatus,
156
178
  IndexOperation, IndexReplicationState, IndexSyncMessage, IndexVectorClock, RemoteIndexMetadata,
157
179
  RemoteIndexState, ReplicationStatus, VersionConflict,
158
180
  };
181
+ pub use distributed_transactions::{
182
+ DistributedTransactionExecutor, EnvelopeId, ReplicaState, ReplicationMessage,
183
+ TransactionEnvelope,
184
+ };
159
185
  pub use execution::{Execution, ExecutionQueue, ExecutionStatus, RetryPolicy};
160
186
  pub use indexing::{IndexConfig, IndexManager, IndexStats, IndexType};
161
- pub use submission::{SubmissionManager, SubmissionMetrics};
162
- pub use admission::{SubmissionOutcome, RejectionReason, ProductionRetryPolicy};
163
- pub use sharding::{
164
- HotspotAlert, RebalanceOperation, ShardDistributionSummary, ShardId, ShardKey, ShardManager,
165
- ShardMetrics, ShardRange, ShardingStrategy,
166
- };
167
- pub use transactions::{
168
- CommitId, CommitValidator, ConsistencyContract, OperationCommand,
169
- OperationId, StateTransition, StateDependency, TransactionExecutor,
170
- TransitionResult,
171
- };
172
- pub use state_hash::{CanonicalState, StateHash};
173
- pub use state_model::{
174
- StateId, StateRevision, StateTopology, Relationship, SemanticDiff, SemanticChange,
175
- ChangeKind, PathComponent, ConflictClassification, ConflictClass, PathConflict,
176
- ReconciliationPlan, StateReconciliationResult, StateStore, STATE_MODEL_VERSION,
177
- };
178
- pub use state_facade::FeltDBStateSystem;
179
- pub use permutation_scheduler::{OperationSchedule, PermutationScheduler, ScheduleStrategy};
187
+ pub use materialization::{MaterializationHandler, ReactiveCollectionIntegration};
180
188
  pub use multi_node_convergence::{
181
189
  ConvergenceAggregation, ConvergenceResult, MultiNodeConvergenceSimulator, NodeExecutionResult,
182
190
  };
183
- pub use distributed_transactions::{
184
- DistributedTransactionExecutor, EnvelopeId, ReplicaState, ReplicationMessage, TransactionEnvelope,
185
- };
191
+ pub use operation::{MutationOrder, Operation, OperationType, DEFAULT_MUTATION_ORDER};
186
192
  pub use operation_algebra::{
187
193
  ConflictType, ConvergenceSemantic, OperationAlgebra, OperationAlgebraRegistry,
188
194
  };
189
- pub use crash_atomic_boundary::{
190
- CrashAtomicExecutor, CrashBoundaryPoint, CrashRecoveryState, InvariantCheckResult,
191
- RecoveryDecision,
192
- };
193
- pub use causal_dependency_barrier::{
194
- ApplyResult, CausalDependencyBarrier, OperationState, PendingOperation, ReceiveResult,
195
- StateCounters,
196
- };
195
+ pub use operation_log::{MemoryOperationLog, OperationLogEntry};
197
196
  pub use partition_reconciliation::{
198
197
  DeterminismCheckResult, PartitionDeterminismChecker, PartitionTrace, PartitionedReplica,
199
198
  ReconciliationResult, ReplicaPartition,
200
199
  };
201
- pub use consistency_contract::{
202
- CommutativeVerificationResult, ConsensusEnforcementResult, ConsensusRequirementChecker,
203
- ConsistencyContractEnforcer, DeterministicMergeVerificationResult, NoDowngradeVerificationResult,
204
- OperationApplication,
205
- };
200
+ pub use peer_registry::PeerRegistry;
201
+ pub use permutation_scheduler::{OperationSchedule, PermutationScheduler, ScheduleStrategy};
206
202
  pub use persistence_reality::{
207
203
  CorruptionDetectionResult, DurablePersistenceLayer, FsyncDurabilityResult,
208
- PersistenceCrashPoint, PersistenceContractVerifier, RecoveryResult, WriteResult, WALEntry,
209
- };
210
- pub use adversarial_transport::{
211
- AdversarialTransport, ConnectionId, ConnectivityMatrix, FaultSeed, InMemoryTransport,
212
- LinkState, MessageId, NetworkEvent, NetworkSchedule, NodeId, ReplicationTransport,
204
+ PersistenceContractVerifier, PersistenceCrashPoint, RecoveryResult, WALEntry, WriteResult,
213
205
  };
214
- pub use materialization::{MaterializationHandler, ReactiveCollectionIntegration};
215
- pub use operation::{MutationOrder, Operation, OperationType, DEFAULT_MUTATION_ORDER};
216
- pub use operation_log::{MemoryOperationLog, OperationLogEntry};
217
- pub use state_transition_store::{MemoryStateTransitionStore, StateTransitionRecord};
218
- pub use transaction_api::{TransactionManager, TxError, TxResult};
219
- pub use peer_registry::PeerRegistry;
220
206
  pub use provenance::{
221
207
  AcquisitionMetadata, DerivedState, DerivedStateProvenance, DerivedValue, OperationSource,
222
208
  };
@@ -228,8 +214,26 @@ pub use routing::{
228
214
  CapabilityRouter, ExecutionClaim, ExecutionClaimer, ExecutionResultPublisher, PeerStateVersion,
229
215
  RouteDecision,
230
216
  };
217
+ pub use sharding::{
218
+ HotspotAlert, RebalanceOperation, ShardDistributionSummary, ShardId, ShardKey, ShardManager,
219
+ ShardMetrics, ShardRange, ShardingStrategy,
220
+ };
221
+ pub use state_facade::FeltDBStateSystem;
222
+ pub use state_hash::{CanonicalState, StateHash};
223
+ pub use state_model::{
224
+ ChangeKind, ConflictClass, ConflictClassification, PathComponent, PathConflict,
225
+ ReconciliationPlan, Relationship, SemanticChange, SemanticDiff, StateId,
226
+ StateReconciliationResult, StateRevision, StateStore, StateTopology, STATE_MODEL_VERSION,
227
+ };
228
+ pub use state_transition_store::{MemoryStateTransitionStore, StateTransitionRecord};
231
229
  pub use storage::{CheckpointData, FileStorage, MemoryStorage, Storage};
230
+ pub use submission::{SubmissionManager, SubmissionMetrics};
232
231
  pub use sync::{ChangeLog, Conflict, ConflictDetector, PeerState, SyncMessage, SyncState};
232
+ pub use transaction_api::{TransactionManager, TxError, TxResult};
233
+ pub use transactions::{
234
+ CommitId, CommitValidator, ConsistencyContract, OperationCommand, OperationId, StateDependency,
235
+ StateTransition, TransactionExecutor, TransitionResult,
236
+ };
233
237
  pub use trigger::{Trigger, TriggerFilter, TriggerRegistry};
234
238
  pub use workflow::{
235
239
  BlockedReason, WorkflowGraph, WorkflowInstance, WorkflowNode, WorkflowOperation, WorkflowRef,
@@ -240,6 +244,7 @@ pub use workflow_integration::{
240
244
  };
241
245
  pub use workflow_runtime::{WorkflowOperationEntry, WorkflowRuntime};
242
246
 
247
+ use crate::state_contract::AuthorizationContext;
243
248
  use serde::de::DeserializeOwned;
244
249
  use serde::{Deserialize, Serialize};
245
250
  use serde_json::Value;
@@ -247,16 +252,15 @@ use sha2::{Digest, Sha256};
247
252
  use std::any::type_name;
248
253
  use std::collections::hash_map::DefaultHasher;
249
254
  use std::collections::{BTreeMap, HashMap, HashSet};
250
- use std::ops::Bound::{Excluded, Unbounded};
251
255
  use std::fmt::{Display, Formatter};
252
256
  use std::fs::{self, OpenOptions};
253
257
  use std::hash::{Hash, Hasher};
254
258
  use std::io::{BufRead, BufReader, Write};
259
+ use std::ops::Bound::{Excluded, Unbounded};
255
260
  use std::path::{Path, PathBuf};
256
261
  use std::sync::{Arc, Mutex};
257
262
  use std::time::{SystemTime, UNIX_EPOCH};
258
263
  use tokio::sync::broadcast;
259
- use crate::state_contract::AuthorizationContext;
260
264
 
261
265
  pub type Result<T> = std::result::Result<T, FlowError>;
262
266
 
@@ -272,6 +276,11 @@ pub enum FlowError {
272
276
  /// outcome of a race and a caller is meant to branch on it, while a
273
277
  /// capability error is not.
274
278
  PreconditionFailed(Box<PreconditionFailure>),
279
+ /// The transaction was based on a revision which is no longer current.
280
+ RevisionConflict {
281
+ expected: u64,
282
+ actual: u64,
283
+ },
275
284
  }
276
285
 
277
286
  impl Display for FlowError {
@@ -282,6 +291,10 @@ impl Display for FlowError {
282
291
  FlowError::CorruptLogLine(line) => write!(f, "corrupt log line: {line}"),
283
292
  FlowError::CapabilityError(msg) => write!(f, "capability error: {msg}"),
284
293
  FlowError::PreconditionFailed(failure) => write!(f, "PRECONDITION_FAILED: {failure}"),
294
+ FlowError::RevisionConflict { expected, actual } => write!(
295
+ f,
296
+ "REVISION_CONFLICT: expected authority revision {expected}, current revision is {actual}"
297
+ ),
285
298
  }
286
299
  }
287
300
  }
@@ -322,6 +335,9 @@ struct Inner {
322
335
  adaptive_indexes: HashSet<String>,
323
336
  instance_id: String,
324
337
  sequence: u64,
338
+ /// Monotonic, authority-assigned commit order. Unlike `sequence`, one
339
+ /// transaction advances this exactly once regardless of its mutation count.
340
+ authority_revision: u64,
325
341
  sync_state: SyncState,
326
342
  change_log: ChangeLog,
327
343
  execution_queue: ExecutionQueue,
@@ -334,6 +350,7 @@ struct Inner {
334
350
  bootstrapped: bool,
335
351
  applied_transactions: HashSet<String>,
336
352
  transaction_payload_hashes: HashMap<String, String>,
353
+ transaction_revisions: HashMap<String, (u64, u64)>,
337
354
  collection_cardinality: HashMap<String, u64>,
338
355
  }
339
356
 
@@ -409,9 +426,24 @@ pub enum PreconditionFailure {
409
426
  Present { collection: String, key: String },
410
427
  /// A predicate was given for a record that does not exist.
411
428
  Missing { collection: String, key: String },
412
- Version { collection: String, key: String, expected: u64, actual: u64 },
413
- Epoch { collection: String, key: String, expected: u64, actual: u64 },
414
- Lease { collection: String, key: String, expected: String, actual: Option<String> },
429
+ Version {
430
+ collection: String,
431
+ key: String,
432
+ expected: u64,
433
+ actual: u64,
434
+ },
435
+ Epoch {
436
+ collection: String,
437
+ key: String,
438
+ expected: u64,
439
+ actual: u64,
440
+ },
441
+ Lease {
442
+ collection: String,
443
+ key: String,
444
+ expected: String,
445
+ actual: Option<String>,
446
+ },
415
447
  }
416
448
 
417
449
  impl PreconditionFailure {
@@ -439,28 +471,58 @@ impl PreconditionFailure {
439
471
  impl Display for PreconditionFailure {
440
472
  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
441
473
  match self {
442
- Self::Present { collection, key } =>
443
- write!(f, "{collection}/{key} was required to be absent but exists"),
444
- Self::Missing { collection, key } =>
445
- write!(f, "{collection}/{key} does not exist"),
446
- Self::Version { collection, key, expected, actual } =>
447
- write!(f, "{collection}/{key} is at version {actual}, expected {expected}"),
448
- Self::Epoch { collection, key, expected, actual } =>
449
- write!(f, "{collection}/{key} is at epoch {actual}, expected {expected}"),
450
- Self::Lease { collection, key, expected, actual } =>
451
- write!(f, "{collection}/{key} holds lease {actual:?}, expected {expected}"),
474
+ Self::Present { collection, key } => {
475
+ write!(f, "{collection}/{key} was required to be absent but exists")
476
+ }
477
+ Self::Missing { collection, key } => write!(f, "{collection}/{key} does not exist"),
478
+ Self::Version {
479
+ collection,
480
+ key,
481
+ expected,
482
+ actual,
483
+ } => write!(
484
+ f,
485
+ "{collection}/{key} is at version {actual}, expected {expected}"
486
+ ),
487
+ Self::Epoch {
488
+ collection,
489
+ key,
490
+ expected,
491
+ actual,
492
+ } => write!(
493
+ f,
494
+ "{collection}/{key} is at epoch {actual}, expected {expected}"
495
+ ),
496
+ Self::Lease {
497
+ collection,
498
+ key,
499
+ expected,
500
+ actual,
501
+ } => write!(
502
+ f,
503
+ "{collection}/{key} holds lease {actual:?}, expected {expected}"
504
+ ),
452
505
  }
453
506
  }
454
507
  }
455
508
  #[derive(Debug, Clone, Serialize, Deserialize)]
456
509
  pub struct AtomicCommit {
457
510
  pub transaction_id: String,
511
+ pub base_revision: u64,
512
+ pub commit_revision: u64,
513
+ pub status: TransactionStatus,
458
514
  pub state_before: u64,
459
515
  pub state_after: u64,
460
516
  pub rows: Vec<StoredRow>,
461
517
  pub duplicate: bool,
462
518
  }
463
519
 
520
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
521
+ #[serde(rename_all = "snake_case")]
522
+ pub enum TransactionStatus {
523
+ Committed,
524
+ }
525
+
464
526
  #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
465
527
  pub enum JsonCasResult {
466
528
  Updated {
@@ -494,6 +556,10 @@ struct TransactionLogRecord {
494
556
  payload_hash: Option<String>,
495
557
  state_before: u64,
496
558
  state_after: u64,
559
+ #[serde(default)]
560
+ base_revision: u64,
561
+ #[serde(default)]
562
+ commit_revision: u64,
497
563
  rows: Vec<StoredRow>,
498
564
  #[serde(default)]
499
565
  audit: Option<Value>,
@@ -754,6 +820,7 @@ impl FeltDb {
754
820
  operation: Some(operation.clone()),
755
821
  };
756
822
  append_event(&inner.path, &row)?;
823
+ inner.authority_revision += 1;
757
824
  inner.change_log.add_operation(operation);
758
825
  inner
759
826
  .rows
@@ -828,9 +895,10 @@ impl FeltDb {
828
895
  if !context.writable_collections.is_empty()
829
896
  && !context.writable_collections.contains(&capability)
830
897
  {
831
- return Err(FlowError::CapabilityError(
832
- format!("AUTHORIZATION_DENIED:not_writable_collection:{}", capability),
833
- ));
898
+ return Err(FlowError::CapabilityError(format!(
899
+ "AUTHORIZATION_DENIED:not_writable_collection:{}",
900
+ capability
901
+ )));
834
902
  }
835
903
 
836
904
  // Authorized: apply mutation
@@ -858,9 +926,10 @@ impl FeltDb {
858
926
  if !context.writable_collections.is_empty()
859
927
  && !context.writable_collections.contains(&capability)
860
928
  {
861
- return Err(FlowError::CapabilityError(
862
- format!("AUTHORIZATION_DENIED:not_writable_collection:{}", capability),
863
- ));
929
+ return Err(FlowError::CapabilityError(format!(
930
+ "AUTHORIZATION_DENIED:not_writable_collection:{}",
931
+ capability
932
+ )));
864
933
  }
865
934
 
866
935
  // Authorized: apply mutation
@@ -888,9 +957,10 @@ impl FeltDb {
888
957
  if !context.writable_collections.is_empty()
889
958
  && !context.writable_collections.contains(&capability)
890
959
  {
891
- return Err(FlowError::CapabilityError(
892
- format!("AUTHORIZATION_DENIED:not_writable_collection:{}", capability),
893
- ));
960
+ return Err(FlowError::CapabilityError(format!(
961
+ "AUTHORIZATION_DENIED:not_writable_collection:{}",
962
+ capability
963
+ )));
894
964
  }
895
965
 
896
966
  // Authorized: apply mutation
@@ -970,11 +1040,34 @@ impl FeltDb {
970
1040
  Ok(inner.sequence)
971
1041
  }
972
1042
 
1043
+ /// Revision of the snapshot held by this embedded authority instance.
1044
+ /// In multi-process topology callers must use `AuthorityClient::read` to
1045
+ /// cross the authority boundary and obtain a current authoritative value.
1046
+ pub fn current_revision(&self) -> Result<u64> {
1047
+ Ok(self.inner.lock().expect("lock poisoned").authority_revision)
1048
+ }
1049
+
1050
+ /// Capture revision and rows under one lock. Authority reads must never
1051
+ /// pair metadata from one commit with state from another.
1052
+ pub(crate) fn authority_state(&self) -> (u64, Vec<StoredRow>) {
1053
+ let inner = self.inner.lock().expect("lock poisoned");
1054
+ let rows = inner
1055
+ .rows
1056
+ .values()
1057
+ .flat_map(|bucket| bucket.values().cloned())
1058
+ .collect();
1059
+ (inner.authority_revision, rows)
1060
+ }
1061
+
973
1062
  /// Get the maintained cardinality (record count) for a collection capability.
974
1063
  /// Returns the count of non-deleted records in the collection.
975
1064
  pub fn collection_cardinality(&self, capability: &str) -> Result<u64> {
976
1065
  let inner = self.inner.lock().expect("lock poisoned");
977
- Ok(inner.collection_cardinality.get(capability).copied().unwrap_or(0))
1066
+ Ok(inner
1067
+ .collection_cardinality
1068
+ .get(capability)
1069
+ .copied()
1070
+ .unwrap_or(0))
978
1071
  }
979
1072
 
980
1073
  /// Diagnostic: List all collection capabilities and their cardinalities.
@@ -1026,7 +1119,13 @@ impl FeltDb {
1026
1119
  }
1027
1120
 
1028
1121
  pub fn applied_transaction_payload_hash(&self, transaction_id: &str) -> Result<Option<String>> {
1029
- Ok(self.inner.lock().expect("lock poisoned").transaction_payload_hashes.get(transaction_id).cloned())
1122
+ Ok(self
1123
+ .inner
1124
+ .lock()
1125
+ .expect("lock poisoned")
1126
+ .transaction_payload_hashes
1127
+ .get(transaction_id)
1128
+ .cloned())
1030
1129
  }
1031
1130
 
1032
1131
  /// Atomically validates and durably applies an ordered mutation batch.
@@ -1040,7 +1139,14 @@ impl FeltDb {
1040
1139
  mutations: &[AtomicMutation],
1041
1140
  audit: Option<Value>,
1042
1141
  ) -> Result<AtomicCommit> {
1043
- self.apply_atomic_transaction_content_addressed(transaction_id, None, expected_parent, preconditions, mutations, audit)
1142
+ self.apply_atomic_transaction_content_addressed(
1143
+ transaction_id,
1144
+ None,
1145
+ expected_parent,
1146
+ preconditions,
1147
+ mutations,
1148
+ audit,
1149
+ )
1044
1150
  }
1045
1151
 
1046
1152
  /// Applies a transaction whose identifier is durably bound to a canonical
@@ -1092,12 +1198,27 @@ impl FeltDb {
1092
1198
  let mut inner = self.inner.lock().expect("lock poisoned");
1093
1199
  if inner.applied_transactions.contains(transaction_id) {
1094
1200
  if let Some(expected_hash) = payload_hash {
1095
- if inner.transaction_payload_hashes.get(transaction_id).map(String::as_str) != Some(expected_hash) {
1096
- return Err(FlowError::CapabilityError(format!("TRANSACTION_ID_PAYLOAD_MISMATCH:{transaction_id}")));
1201
+ if inner
1202
+ .transaction_payload_hashes
1203
+ .get(transaction_id)
1204
+ .map(String::as_str)
1205
+ != Some(expected_hash)
1206
+ {
1207
+ return Err(FlowError::CapabilityError(format!(
1208
+ "TRANSACTION_ID_PAYLOAD_MISMATCH:{transaction_id}"
1209
+ )));
1097
1210
  }
1098
1211
  }
1212
+ let (base_revision, commit_revision) = inner
1213
+ .transaction_revisions
1214
+ .get(transaction_id)
1215
+ .copied()
1216
+ .unwrap_or((inner.authority_revision, inner.authority_revision));
1099
1217
  return Ok(AtomicCommit {
1100
1218
  transaction_id: transaction_id.into(),
1219
+ base_revision,
1220
+ commit_revision,
1221
+ status: TransactionStatus::Committed,
1101
1222
  state_before: inner.sequence,
1102
1223
  state_after: inner.sequence,
1103
1224
  rows: vec![],
@@ -1146,7 +1267,12 @@ impl FeltDb {
1146
1267
  .unwrap_or(1);
1147
1268
  if actual != expected {
1148
1269
  return Err(FlowError::PreconditionFailed(Box::new(
1149
- PreconditionFailure::Version { collection, key, expected, actual },
1270
+ PreconditionFailure::Version {
1271
+ collection,
1272
+ key,
1273
+ expected,
1274
+ actual,
1275
+ },
1150
1276
  )));
1151
1277
  }
1152
1278
  }
@@ -1158,14 +1284,22 @@ impl FeltDb {
1158
1284
  .unwrap_or(0);
1159
1285
  if actual != expected {
1160
1286
  return Err(FlowError::PreconditionFailed(Box::new(
1161
- PreconditionFailure::Epoch { collection, key, expected, actual },
1287
+ PreconditionFailure::Epoch {
1288
+ collection,
1289
+ key,
1290
+ expected,
1291
+ actual,
1292
+ },
1162
1293
  )));
1163
1294
  }
1164
1295
  }
1165
1296
  if let Some(expected) = &condition.expected_lease_id {
1166
1297
  let lease = current.value.get("lease").filter(|lease| !lease.is_null());
1167
1298
  let held = lease.and_then(|lease| {
1168
- lease.get("leaseId").and_then(Value::as_str).map(str::to_string)
1299
+ lease
1300
+ .get("leaseId")
1301
+ .and_then(Value::as_str)
1302
+ .map(str::to_string)
1169
1303
  });
1170
1304
  // An expired lease is not held, so naming it is a conflict:
1171
1305
  // the caller believes it owns something it no longer does.
@@ -1209,9 +1343,12 @@ impl FeltDb {
1209
1343
  let fenced: HashMap<(&str, &str), u64> = record_preconditions
1210
1344
  .iter()
1211
1345
  .filter_map(|condition| {
1212
- condition
1213
- .expected_version
1214
- .map(|version| ((condition.capability.as_str(), condition.key.as_str()), version))
1346
+ condition.expected_version.map(|version| {
1347
+ (
1348
+ (condition.capability.as_str(), condition.key.as_str()),
1349
+ version,
1350
+ )
1351
+ })
1215
1352
  })
1216
1353
  .collect();
1217
1354
  let mutations: &[AtomicMutation] = if fenced.is_empty() && creates.is_empty() {
@@ -1248,12 +1385,13 @@ impl FeltDb {
1248
1385
  &advanced
1249
1386
  };
1250
1387
 
1388
+ let base_revision = inner.authority_revision;
1251
1389
  if let Some(expected) = expected_parent {
1252
- if expected != inner.sequence {
1253
- return Err(FlowError::CapabilityError(format!(
1254
- "PRECONDITION_FAILED:transaction_parent:{expected}:{}",
1255
- inner.sequence
1256
- )));
1390
+ if expected != base_revision {
1391
+ return Err(FlowError::RevisionConflict {
1392
+ expected,
1393
+ actual: base_revision,
1394
+ });
1257
1395
  }
1258
1396
  }
1259
1397
  for condition in preconditions {
@@ -1337,10 +1475,13 @@ impl FeltDb {
1337
1475
  payload_hash: payload_hash.map(str::to_owned),
1338
1476
  state_before,
1339
1477
  state_after: next_sequence,
1478
+ base_revision,
1479
+ commit_revision: base_revision + 1,
1340
1480
  rows: rows.clone(),
1341
1481
  audit,
1342
1482
  };
1343
1483
  append_transaction(&inner.path, &record)?;
1484
+ inner.authority_revision = base_revision + 1;
1344
1485
  inner.sequence = next_sequence;
1345
1486
  for row in &rows {
1346
1487
  if let Some(operation) = row.operation.clone() {
@@ -1351,7 +1492,10 @@ impl FeltDb {
1351
1492
  bucket.remove(&row.key);
1352
1493
  }
1353
1494
  // Decrement cardinality on delete
1354
- let count = inner.collection_cardinality.entry(row.capability.clone()).or_default();
1495
+ let count = inner
1496
+ .collection_cardinality
1497
+ .entry(row.capability.clone())
1498
+ .or_default();
1355
1499
  *count = count.saturating_sub(1);
1356
1500
  } else {
1357
1501
  // Track whether this is an insert (new row) or update (existing row)
@@ -1368,14 +1512,25 @@ impl FeltDb {
1368
1512
 
1369
1513
  // Increment cardinality only on insert, not update
1370
1514
  if is_insert {
1371
- let count = inner.collection_cardinality.entry(row.capability.clone()).or_default();
1515
+ let count = inner
1516
+ .collection_cardinality
1517
+ .entry(row.capability.clone())
1518
+ .or_default();
1372
1519
  *count = count.saturating_add(1);
1373
1520
  }
1374
1521
  }
1375
1522
  }
1376
1523
  inner.sync_state.merge_vector_clock(&vector_clock);
1377
1524
  inner.applied_transactions.insert(transaction_id.into());
1378
- if let Some(hash) = payload_hash { inner.transaction_payload_hashes.insert(transaction_id.into(), hash.into()); }
1525
+ let commit_revision = inner.authority_revision;
1526
+ inner
1527
+ .transaction_revisions
1528
+ .insert(transaction_id.into(), (base_revision, commit_revision));
1529
+ if let Some(hash) = payload_hash {
1530
+ inner
1531
+ .transaction_payload_hashes
1532
+ .insert(transaction_id.into(), hash.into());
1533
+ }
1379
1534
  let events = rows
1380
1535
  .iter()
1381
1536
  .map(|row| ChangeEvent {
@@ -1388,6 +1543,9 @@ impl FeltDb {
1388
1543
  (
1389
1544
  AtomicCommit {
1390
1545
  transaction_id: transaction_id.into(),
1546
+ base_revision,
1547
+ commit_revision: inner.authority_revision,
1548
+ status: TransactionStatus::Committed,
1391
1549
  state_before,
1392
1550
  state_after: inner.sequence,
1393
1551
  rows,
@@ -1428,7 +1586,13 @@ impl FeltDb {
1428
1586
  }
1429
1587
 
1430
1588
  // All authorized: proceed with atomic transaction (no partial application)
1431
- self.apply_atomic_transaction(transaction_id, expected_parent, preconditions, mutations, audit)
1589
+ self.apply_atomic_transaction(
1590
+ transaction_id,
1591
+ expected_parent,
1592
+ preconditions,
1593
+ mutations,
1594
+ audit,
1595
+ )
1432
1596
  }
1433
1597
 
1434
1598
  fn insert_internal<T: Serialize>(
@@ -1480,6 +1644,7 @@ impl FeltDb {
1480
1644
  inner.change_log.add_operation(op.clone());
1481
1645
 
1482
1646
  append_event(&inner.path, &row)?;
1647
+ inner.authority_revision += 1;
1483
1648
  inner
1484
1649
  .rows
1485
1650
  .entry(capability.clone())
@@ -1552,6 +1717,7 @@ impl FeltDb {
1552
1717
  inner.change_log.add_operation(op.clone());
1553
1718
 
1554
1719
  append_event(&inner.path, &row)?;
1720
+ inner.authority_revision += 1;
1555
1721
  inner
1556
1722
  .rows
1557
1723
  .entry(capability.clone())
@@ -1620,6 +1786,7 @@ impl FeltDb {
1620
1786
  operation: Some(op),
1621
1787
  };
1622
1788
  append_event(&inner.path, &tombstone)?;
1789
+ inner.authority_revision += 1;
1623
1790
 
1624
1791
  // Remove from in-memory store
1625
1792
  if let Some(bucket) = inner.rows.get_mut(&capability) {
@@ -1723,20 +1890,39 @@ impl FeltDb {
1723
1890
  }
1724
1891
 
1725
1892
  /// Traverse one collection in immutable record-key order without materializing it.
1726
- pub fn list_collection_page(&self, capability: &str, after: Option<&str>, limit: usize) -> Result<Vec<StoredRow>> {
1893
+ pub fn list_collection_page(
1894
+ &self,
1895
+ capability: &str,
1896
+ after: Option<&str>,
1897
+ limit: usize,
1898
+ ) -> Result<Vec<StoredRow>> {
1727
1899
  let inner = self.inner.lock().expect("lock poisoned");
1728
- let Some(rows) = inner.rows.get(capability) else { return Ok(vec![]) };
1900
+ let Some(rows) = inner.rows.get(capability) else {
1901
+ return Ok(vec![]);
1902
+ };
1729
1903
  let values: Box<dyn Iterator<Item = &StoredRow>> = match after {
1730
- Some(key) => Box::new(rows.range::<str, _>((Excluded(key), Unbounded)).map(|(_, row)| row)),
1904
+ Some(key) => Box::new(
1905
+ rows.range::<str, _>((Excluded(key), Unbounded))
1906
+ .map(|(_, row)| row),
1907
+ ),
1731
1908
  None => Box::new(rows.values()),
1732
1909
  };
1733
- Ok(values.filter(|row| !row.deleted).take(limit).cloned().collect())
1910
+ Ok(values
1911
+ .filter(|row| !row.deleted)
1912
+ .take(limit)
1913
+ .cloned()
1914
+ .collect())
1734
1915
  }
1735
1916
 
1736
1917
  /// Fetch one live collection record without materializing collection state.
1737
1918
  pub fn get_collection_record(&self, capability: &str, key: &str) -> Result<Option<StoredRow>> {
1738
1919
  let inner = self.inner.lock().expect("lock poisoned");
1739
- Ok(inner.rows.get(capability).and_then(|rows| rows.get(key)).filter(|row| !row.deleted).cloned())
1920
+ Ok(inner
1921
+ .rows
1922
+ .get(capability)
1923
+ .and_then(|rows| rows.get(key))
1924
+ .filter(|row| !row.deleted)
1925
+ .cloned())
1740
1926
  }
1741
1927
 
1742
1928
  /// Subscribe to canonical mutation events.
@@ -1820,6 +2006,22 @@ impl FeltDb {
1820
2006
  .collect())
1821
2007
  }
1822
2008
 
2009
+ /// Materialized rows belonging to one application state namespace.
2010
+ ///
2011
+ /// State-contract capabilities are encoded as `<state namespace>:<collection>`.
2012
+ /// Selecting the capability buckets before cloning prevents one application's
2013
+ /// read snapshot from materializing every other application's durable rows.
2014
+ pub fn state_rows_for_namespace(&self, state_namespace: &str) -> Result<Vec<StoredRow>> {
2015
+ let inner = self.inner.lock().expect("lock poisoned");
2016
+ let prefix = format!("{state_namespace}:");
2017
+ Ok(inner
2018
+ .rows
2019
+ .iter()
2020
+ .filter(|(capability, _)| capability.starts_with(&prefix))
2021
+ .flat_map(|(_, bucket)| bucket.values().cloned())
2022
+ .collect())
2023
+ }
2024
+
1823
2025
  pub fn can_install_snapshot(&self) -> Result<bool> {
1824
2026
  let inner = self.inner.lock().expect("lock poisoned");
1825
2027
  Ok(!inner.bootstrapped && inner.rows.is_empty() && inner.change_log.pending.is_empty())
@@ -2095,6 +2297,7 @@ impl FeltDb {
2095
2297
  {
2096
2298
  history_row.operation = Some(op.clone());
2097
2299
  append_event(&inner.path, &history_row)?;
2300
+ inner.authority_revision += 1;
2098
2301
  }
2099
2302
  inner.change_log.add_operation(op);
2100
2303
  inner.sync_state.increment_received(1);
@@ -2120,6 +2323,7 @@ impl FeltDb {
2120
2323
  match op_to_apply.op_type {
2121
2324
  OperationType::Insert | OperationType::Update => {
2122
2325
  append_event(&inner.path, &row)?;
2326
+ inner.authority_revision += 1;
2123
2327
  inner
2124
2328
  .rows
2125
2329
  .entry(op_to_apply.capability.clone())
@@ -2128,6 +2332,7 @@ impl FeltDb {
2128
2332
  }
2129
2333
  OperationType::Delete => {
2130
2334
  append_event(&inner.path, &row)?;
2335
+ inner.authority_revision += 1;
2131
2336
  if let Some(bucket) = inner.rows.get_mut(&op_to_apply.capability) {
2132
2337
  bucket.remove(&op_to_apply.key);
2133
2338
  }
@@ -2530,8 +2735,29 @@ fn replay_log(inner: &mut Inner) -> Result<()> {
2530
2735
  }
2531
2736
  if value.get("record_type").and_then(Value::as_str) == Some("feltdb.transaction.v1") {
2532
2737
  let transaction: TransactionLogRecord = serde_json::from_value(value)?;
2738
+ // Older records did not carry a commit revision. Their durable
2739
+ // order in the log supplies an unambiguous upgrade path.
2740
+ inner.authority_revision =
2741
+ inner
2742
+ .authority_revision
2743
+ .max(if transaction.commit_revision == 0 {
2744
+ inner.authority_revision + 1
2745
+ } else {
2746
+ transaction.commit_revision
2747
+ });
2748
+ let base_revision = if transaction.commit_revision == 0 {
2749
+ inner.authority_revision.saturating_sub(1)
2750
+ } else {
2751
+ transaction.base_revision
2752
+ };
2753
+ inner.transaction_revisions.insert(
2754
+ transaction.transaction_id.clone(),
2755
+ (base_revision, inner.authority_revision),
2756
+ );
2533
2757
  if let Some(hash) = transaction.payload_hash.clone() {
2534
- inner.transaction_payload_hashes.insert(transaction.transaction_id.clone(), hash);
2758
+ inner
2759
+ .transaction_payload_hashes
2760
+ .insert(transaction.transaction_id.clone(), hash);
2535
2761
  }
2536
2762
  if inner
2537
2763
  .applied_transactions
@@ -2578,6 +2804,7 @@ fn replay_log(inner: &mut Inner) -> Result<()> {
2578
2804
  // store. Preserve a monotonic sequence when upgrading them.
2579
2805
  inner.sequence += 1;
2580
2806
  }
2807
+ inner.authority_revision += 1;
2581
2808
  if let Some(id) = row
2582
2809
  .key
2583
2810
  .rsplit_once(':')
@@ -2605,7 +2832,9 @@ fn replay_log(inner: &mut Inner) -> Result<()> {
2605
2832
  // Compute collection cardinality from final row state (count non-deleted rows per capability)
2606
2833
  for (capability, bucket) in &inner.rows {
2607
2834
  let count = bucket.values().filter(|row| !row.deleted).count() as u64;
2608
- inner.collection_cardinality.insert(capability.clone(), count);
2835
+ inner
2836
+ .collection_cardinality
2837
+ .insert(capability.clone(), count);
2609
2838
  }
2610
2839
 
2611
2840
  Ok(())
@@ -3331,7 +3560,8 @@ mod tests {
3331
3560
  },
3332
3561
  ];
3333
3562
 
3334
- db.apply_atomic_transaction("txn-1", None, &[], &mutations, None).unwrap();
3563
+ db.apply_atomic_transaction("txn-1", None, &[], &mutations, None)
3564
+ .unwrap();
3335
3565
 
3336
3566
  let cardinality = db.collection_cardinality(capability).unwrap();
3337
3567
  assert_eq!(cardinality, 2, "Cardinality should be 2 after 2 inserts");
@@ -3352,9 +3582,14 @@ mod tests {
3352
3582
  value: Some(serde_json::json!({"id": i, "name": format!("Record {}", i)})),
3353
3583
  }];
3354
3584
 
3355
- db.apply_atomic_transaction(&format!("txn-{}", i), None, &[], &mutations, None).unwrap();
3585
+ db.apply_atomic_transaction(&format!("txn-{}", i), None, &[], &mutations, None)
3586
+ .unwrap();
3356
3587
  let cardinality = db.collection_cardinality(capability).unwrap();
3357
- assert_eq!(cardinality, i as u64, "Cardinality should be {} after {} inserts", i, i);
3588
+ assert_eq!(
3589
+ cardinality, i as u64,
3590
+ "Cardinality should be {} after {} inserts",
3591
+ i, i
3592
+ );
3358
3593
  }
3359
3594
 
3360
3595
  let _ = fs::remove_file(path);
@@ -3384,19 +3619,23 @@ mod tests {
3384
3619
  },
3385
3620
  ];
3386
3621
 
3387
- db.apply_atomic_transaction("txn-insert", None, &[], &insert_mutations, None).unwrap();
3622
+ db.apply_atomic_transaction("txn-insert", None, &[], &insert_mutations, None)
3623
+ .unwrap();
3388
3624
  assert_eq!(db.collection_cardinality(capability).unwrap(), 3);
3389
3625
 
3390
- let delete_mutations = vec![
3391
- AtomicMutation {
3392
- capability: capability.to_string(),
3393
- key: "rec-1".to_string(),
3394
- value: None,
3395
- },
3396
- ];
3626
+ let delete_mutations = vec![AtomicMutation {
3627
+ capability: capability.to_string(),
3628
+ key: "rec-1".to_string(),
3629
+ value: None,
3630
+ }];
3397
3631
 
3398
- db.apply_atomic_transaction("txn-delete", None, &[], &delete_mutations, None).unwrap();
3399
- assert_eq!(db.collection_cardinality(capability).unwrap(), 2, "Cardinality should be 2 after deleting 1 record");
3632
+ db.apply_atomic_transaction("txn-delete", None, &[], &delete_mutations, None)
3633
+ .unwrap();
3634
+ assert_eq!(
3635
+ db.collection_cardinality(capability).unwrap(),
3636
+ 2,
3637
+ "Cardinality should be 2 after deleting 1 record"
3638
+ );
3400
3639
 
3401
3640
  let _ = fs::remove_file(path);
3402
3641
  }
@@ -3413,7 +3652,8 @@ mod tests {
3413
3652
  value: Some(serde_json::json!({"id": 1, "status": "active"})),
3414
3653
  }];
3415
3654
 
3416
- db.apply_atomic_transaction("txn-insert", None, &[], &insert_mutations, None).unwrap();
3655
+ db.apply_atomic_transaction("txn-insert", None, &[], &insert_mutations, None)
3656
+ .unwrap();
3417
3657
  assert_eq!(db.collection_cardinality(capability).unwrap(), 1);
3418
3658
 
3419
3659
  let update_mutations = vec![AtomicMutation {
@@ -3422,8 +3662,13 @@ mod tests {
3422
3662
  value: Some(serde_json::json!({"id": 1, "status": "inactive"})),
3423
3663
  }];
3424
3664
 
3425
- db.apply_atomic_transaction("txn-update", None, &[], &update_mutations, None).unwrap();
3426
- assert_eq!(db.collection_cardinality(capability).unwrap(), 1, "Cardinality should remain 1 after update");
3665
+ db.apply_atomic_transaction("txn-update", None, &[], &update_mutations, None)
3666
+ .unwrap();
3667
+ assert_eq!(
3668
+ db.collection_cardinality(capability).unwrap(),
3669
+ 1,
3670
+ "Cardinality should remain 1 after update"
3671
+ );
3427
3672
 
3428
3673
  let _ = fs::remove_file(path);
3429
3674
  }
@@ -3449,14 +3694,18 @@ mod tests {
3449
3694
  },
3450
3695
  ];
3451
3696
 
3452
- db.apply_atomic_transaction("txn-1", None, &[], &mutations, None).unwrap();
3697
+ db.apply_atomic_transaction("txn-1", None, &[], &mutations, None)
3698
+ .unwrap();
3453
3699
  assert_eq!(db.collection_cardinality(capability).unwrap(), 2);
3454
3700
  }
3455
3701
 
3456
3702
  let reopened = open(&path).unwrap();
3457
3703
  let capability = "test_collection";
3458
3704
  let cardinality = reopened.collection_cardinality(capability).unwrap();
3459
- assert_eq!(cardinality, 2, "Cardinality should survive restart and be recomputed from rows");
3705
+ assert_eq!(
3706
+ cardinality, 2,
3707
+ "Cardinality should survive restart and be recomputed from rows"
3708
+ );
3460
3709
 
3461
3710
  let _ = fs::remove_file(path);
3462
3711
  }
@@ -3488,9 +3737,13 @@ mod tests {
3488
3737
  });
3489
3738
  }
3490
3739
 
3491
- db.apply_atomic_transaction("txn-large", None, &[], &mutations, None).unwrap();
3740
+ db.apply_atomic_transaction("txn-large", None, &[], &mutations, None)
3741
+ .unwrap();
3492
3742
  let cardinality = db.collection_cardinality(capability).unwrap();
3493
- assert_eq!(cardinality, 100, "Large collection with 100 records should have cardinality 100");
3743
+ assert_eq!(
3744
+ cardinality, 100,
3745
+ "Large collection with 100 records should have cardinality 100"
3746
+ );
3494
3747
 
3495
3748
  let _ = fs::remove_file(path);
3496
3749
  }
@@ -3516,7 +3769,8 @@ mod tests {
3516
3769
  },
3517
3770
  ];
3518
3771
 
3519
- db.apply_atomic_transaction("txn-col1", None, &[], &mutations1, None).unwrap();
3772
+ db.apply_atomic_transaction("txn-col1", None, &[], &mutations1, None)
3773
+ .unwrap();
3520
3774
 
3521
3775
  let mutations2 = vec![
3522
3776
  AtomicMutation {
@@ -3536,7 +3790,8 @@ mod tests {
3536
3790
  },
3537
3791
  ];
3538
3792
 
3539
- db.apply_atomic_transaction("txn-col2", None, &[], &mutations2, None).unwrap();
3793
+ db.apply_atomic_transaction("txn-col2", None, &[], &mutations2, None)
3794
+ .unwrap();
3540
3795
 
3541
3796
  assert_eq!(db.collection_cardinality(col1).unwrap(), 2);
3542
3797
  assert_eq!(db.collection_cardinality(col2).unwrap(), 3);