@feltdb/core 0.4.11 → 0.4.13

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 (164) hide show
  1. package/dist/cli/commands.js +5 -2
  2. package/dist/cli/index.js +1 -1
  3. package/dist/create/cli.js +16 -1
  4. package/dist/create/create.js +30 -16
  5. package/dist/create/docker-compose-generator.js +48 -9
  6. package/dist/create/package-versions.js +1 -1
  7. package/dist/create/server-source/Cargo.lock +2238 -0
  8. package/dist/create/server-source/Cargo.toml +7 -0
  9. package/dist/create/server-source/crates/feltdb/Cargo.lock +175 -0
  10. package/dist/create/server-source/crates/feltdb/Cargo.toml +79 -0
  11. package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +370 -0
  12. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +589 -0
  13. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +259 -0
  14. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +446 -0
  15. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +272 -0
  16. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +378 -0
  17. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +414 -0
  18. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +486 -0
  19. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +273 -0
  20. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +587 -0
  21. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +513 -0
  22. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +395 -0
  23. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +469 -0
  24. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +409 -0
  25. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +627 -0
  26. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +383 -0
  27. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +298 -0
  28. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +1698 -0
  29. package/dist/create/server-source/crates/feltdb/src/acquisition.rs +286 -0
  30. package/dist/create/server-source/crates/feltdb/src/admission.rs +192 -0
  31. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +477 -0
  32. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +566 -0
  33. package/dist/create/server-source/crates/feltdb/src/analytics.rs +475 -0
  34. package/dist/create/server-source/crates/feltdb/src/application.rs +2244 -0
  35. package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +1070 -0
  36. package/dist/create/server-source/crates/feltdb/src/authorization.rs +1030 -0
  37. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +266 -0
  38. package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +8 -0
  39. package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +418 -0
  40. package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +482 -0
  41. package/dist/create/server-source/crates/feltdb/src/capability.rs +903 -0
  42. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +261 -0
  43. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +78 -0
  44. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +2214 -0
  45. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +194 -0
  46. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +427 -0
  47. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +453 -0
  48. package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +465 -0
  49. package/dist/create/server-source/crates/feltdb/src/convergence.rs +618 -0
  50. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +418 -0
  51. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +380 -0
  52. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +362 -0
  53. package/dist/create/server-source/crates/feltdb/src/cron.rs +294 -0
  54. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +474 -0
  55. package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +1003 -0
  56. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +536 -0
  57. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +364 -0
  58. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +235 -0
  59. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +207 -0
  60. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +316 -0
  61. package/dist/create/server-source/crates/feltdb/src/execution.rs +426 -0
  62. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +219 -0
  63. package/dist/create/server-source/crates/feltdb/src/indexing.rs +779 -0
  64. package/dist/create/server-source/crates/feltdb/src/lib.rs +2838 -0
  65. package/dist/create/server-source/crates/feltdb/src/materialization.rs +184 -0
  66. package/dist/create/server-source/crates/feltdb/src/metrics.rs +267 -0
  67. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +311 -0
  68. package/dist/create/server-source/crates/feltdb/src/observability.rs +366 -0
  69. package/dist/create/server-source/crates/feltdb/src/operation.rs +251 -0
  70. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +438 -0
  71. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +344 -0
  72. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +477 -0
  73. package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +166 -0
  74. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +261 -0
  75. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +560 -0
  76. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +3226 -0
  77. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +201 -0
  78. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +263 -0
  79. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +484 -0
  80. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +216 -0
  81. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +281 -0
  82. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +323 -0
  83. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +573 -0
  84. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +404 -0
  85. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +418 -0
  86. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +381 -0
  87. package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +523 -0
  88. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +334 -0
  89. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +306 -0
  90. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +355 -0
  91. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +395 -0
  92. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +346 -0
  93. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +430 -0
  94. package/dist/create/server-source/crates/feltdb/src/production_api.rs +400 -0
  95. package/dist/create/server-source/crates/feltdb/src/provenance.rs +207 -0
  96. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +217 -0
  97. package/dist/create/server-source/crates/feltdb/src/references.rs +404 -0
  98. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +401 -0
  99. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +160 -0
  100. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +132 -0
  101. package/dist/create/server-source/crates/feltdb/src/routing.rs +409 -0
  102. package/dist/create/server-source/crates/feltdb/src/sharding.rs +523 -0
  103. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +3118 -0
  104. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +291 -0
  105. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +376 -0
  106. package/dist/create/server-source/crates/feltdb/src/storage.rs +267 -0
  107. package/dist/create/server-source/crates/feltdb/src/submission.rs +477 -0
  108. package/dist/create/server-source/crates/feltdb/src/sync.rs +483 -0
  109. package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +822 -0
  110. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +366 -0
  111. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +473 -0
  112. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +949 -0
  113. package/dist/create/server-source/crates/feltdb/src/transactions.rs +646 -0
  114. package/dist/create/server-source/crates/feltdb/src/trigger.rs +390 -0
  115. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +928 -0
  116. package/dist/create/server-source/crates/feltdb/src/workflow.rs +713 -0
  117. package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +510 -0
  118. package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +354 -0
  119. package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +594 -0
  120. package/dist/create/server-source/crates/feltdb/src/workload.rs +1310 -0
  121. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +23 -0
  122. package/dist/create/server-source/crates/feltdb-server/README.md +79 -0
  123. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +73 -0
  124. package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +425 -0
  125. package/dist/create/server-source/crates/feltdb-server/src/artifacts.rs +449 -0
  126. package/dist/create/server-source/crates/feltdb-server/src/audit.rs +59 -0
  127. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +308 -0
  128. package/dist/create/server-source/crates/feltdb-server/src/authorization.rs +228 -0
  129. package/dist/create/server-source/crates/feltdb-server/src/backup.rs +416 -0
  130. package/dist/create/server-source/crates/feltdb-server/src/causal.rs +218 -0
  131. package/dist/create/server-source/crates/feltdb-server/src/certification.rs +223 -0
  132. package/dist/create/server-source/crates/feltdb-server/src/clock.rs +132 -0
  133. package/dist/create/server-source/crates/feltdb-server/src/cluster.rs +165 -0
  134. package/dist/create/server-source/crates/feltdb-server/src/connections.rs +1044 -0
  135. package/dist/create/server-source/crates/feltdb-server/src/content.rs +111 -0
  136. package/dist/create/server-source/crates/feltdb-server/src/identity.rs +250 -0
  137. package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +78 -0
  138. package/dist/create/server-source/crates/feltdb-server/src/key_provider.rs +247 -0
  139. package/dist/create/server-source/crates/feltdb-server/src/leases.rs +123 -0
  140. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +25 -0
  141. package/dist/create/server-source/crates/feltdb-server/src/main.rs +8715 -0
  142. package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +97 -0
  143. package/dist/create/server-source/crates/feltdb-server/src/portable_bundle.rs +350 -0
  144. package/dist/create/server-source/crates/feltdb-server/src/principals.rs +510 -0
  145. package/dist/create/server-source/crates/feltdb-server/src/providers.rs +269 -0
  146. package/dist/create/server-source/crates/feltdb-server/src/releases.rs +2563 -0
  147. package/dist/create/server-source/crates/feltdb-server/src/sessions.rs +156 -0
  148. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +1097 -0
  149. package/dist/create/server-source/crates/feltdb-server/src/versions.rs +264 -0
  150. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +31 -0
  151. package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +1086 -0
  152. package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
  153. package/dist/flowspec.d.ts +2 -0
  154. package/dist/flowspec.d.ts.map +1 -1
  155. package/dist/flowspec.js +28 -4
  156. package/dist/state-contract.d.ts +7 -1
  157. package/dist/state-contract.d.ts.map +1 -1
  158. package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
  159. package/dist/studio/components/index.js +1 -1
  160. package/dist/studio/{components-q43NSTTH.js → components-bHTARBin.js} +160 -89
  161. package/dist/studio/index.js +31 -28
  162. package/dist/studio-app/assets/{index-DZpBoVMp.js → index-BqKquLuU.js} +8 -8
  163. package/dist/studio-app/index.html +1 -1
  164. package/package.json +1 -1
@@ -0,0 +1,477 @@
1
+ /// Partition/Reconciliation Determinism (Gate 5)
2
+ ///
3
+ /// Verifies the critical invariant: After a network partition and independent
4
+ /// operation application, reconciliation converges to the same state_hash.
5
+ ///
6
+ /// Partition-Reconciliation Flow:
7
+ ///
8
+ /// Initial State
9
+ /// ↓
10
+ /// Network Partition (split into A and B)
11
+ /// ↓
12
+ /// Apply Operations to A: A' = Apply(A, ops_sequence_a)
13
+ /// Apply Operations to B: B' = Apply(B, ops_sequence_b)
14
+ /// ↓
15
+ /// Reconcile (merge A' and B')
16
+ /// ↓
17
+ /// Converged State (must be deterministic regardless of partition)
18
+ ///
19
+ /// Critical invariant: If A and B eventually receive all operations
20
+ /// (in any order), they converge to the same state_hash.
21
+
22
+ use crate::convergence::VectorClock;
23
+ use crate::state_hash::CanonicalState;
24
+ use std::collections::HashMap;
25
+
26
+ /// Simulates a partition (split) of a replica
27
+ #[derive(Clone, Debug)]
28
+ pub struct PartitionedReplica {
29
+ /// Partition A (e.g., primary after split)
30
+ pub partition_a: ReplicaPartition,
31
+ /// Partition B (e.g., secondary after split)
32
+ pub partition_b: ReplicaPartition,
33
+ /// Whether partitions are currently connected
34
+ pub connected: bool,
35
+ }
36
+
37
+ /// A single partition of a replica (one half of a split)
38
+ #[derive(Clone, Debug)]
39
+ pub struct ReplicaPartition {
40
+ /// State in this partition
41
+ pub state: CanonicalState,
42
+ /// Vector clock frontier of this partition
43
+ pub frontier: VectorClock,
44
+ /// Operations applied in this partition
45
+ pub applied_operations: Vec<String>,
46
+ }
47
+
48
+ /// Result of reconciliation
49
+ #[derive(Clone, Debug)]
50
+ pub struct ReconciliationResult {
51
+ /// Final converged state after reconciliation
52
+ pub converged_state: CanonicalState,
53
+ /// Frontier after reconciliation
54
+ pub converged_frontier: VectorClock,
55
+ /// All operations applied across both partitions
56
+ pub all_applied_operations: Vec<String>,
57
+ /// Operations that were duplicated (in both A and B)
58
+ pub duplicate_operations: Vec<String>,
59
+ /// Operations only in A
60
+ pub partition_a_only: Vec<String>,
61
+ /// Operations only in B
62
+ pub partition_b_only: Vec<String>,
63
+ }
64
+
65
+ impl PartitionedReplica {
66
+ pub fn new() -> Self {
67
+ Self {
68
+ partition_a: ReplicaPartition {
69
+ state: CanonicalState::new(),
70
+ frontier: VectorClock::new(),
71
+ applied_operations: Vec::new(),
72
+ },
73
+ partition_b: ReplicaPartition {
74
+ state: CanonicalState::new(),
75
+ frontier: VectorClock::new(),
76
+ applied_operations: Vec::new(),
77
+ },
78
+ connected: true,
79
+ }
80
+ }
81
+
82
+ /// Simulate a network partition (disconnect the two partitions)
83
+ pub fn partition(&mut self) {
84
+ self.connected = false;
85
+ }
86
+
87
+ /// Simulate reconnection
88
+ pub fn reconnect(&mut self) {
89
+ self.connected = true;
90
+ }
91
+
92
+ /// Apply an operation to partition A
93
+ pub fn apply_to_partition_a(
94
+ &mut self,
95
+ operation_id: &str,
96
+ record_id: &str,
97
+ value: i32,
98
+ ) {
99
+ if self.connected {
100
+ // Both partitions receive the operation
101
+ self.apply_to_both(operation_id, record_id, value);
102
+ } else {
103
+ // Only A receives it
104
+ self.partition_a.apply_operation(operation_id, record_id, value);
105
+ }
106
+ }
107
+
108
+ /// Apply an operation to partition B
109
+ pub fn apply_to_partition_b(
110
+ &mut self,
111
+ operation_id: &str,
112
+ record_id: &str,
113
+ value: i32,
114
+ ) {
115
+ if self.connected {
116
+ // Both partitions receive the operation
117
+ self.apply_to_both(operation_id, record_id, value);
118
+ } else {
119
+ // Only B receives it
120
+ self.partition_b.apply_operation(operation_id, record_id, value);
121
+ }
122
+ }
123
+
124
+ /// Apply operation to both partitions (when connected)
125
+ fn apply_to_both(&mut self, operation_id: &str, record_id: &str, value: i32) {
126
+ self.partition_a.apply_operation(operation_id, record_id, value);
127
+ self.partition_b.apply_operation(operation_id, record_id, value);
128
+ }
129
+
130
+ /// Reconcile the two partitions using operation-based reconciliation
131
+ /// Ensures all operations from both partitions are applied to both
132
+ pub fn reconcile(&self) -> ReconciliationResult {
133
+ // Collect all operations from both partitions
134
+ let mut ops_from_a: std::collections::HashSet<_> =
135
+ self.partition_a.applied_operations.iter().cloned().collect();
136
+ let ops_from_b: std::collections::HashSet<_> =
137
+ self.partition_b.applied_operations.iter().cloned().collect();
138
+
139
+ // Find duplicates and unique ops
140
+ let duplicates: Vec<String> = ops_from_a
141
+ .iter()
142
+ .filter(|op| ops_from_b.contains(*op))
143
+ .cloned()
144
+ .collect();
145
+
146
+ ops_from_a.remove(&duplicates[0]); // Remove from ops_from_a so we can compute A-only
147
+
148
+ let partition_a_only: Vec<String> = ops_from_a.into_iter().collect();
149
+ let partition_b_only: Vec<String> = ops_from_b
150
+ .iter()
151
+ .filter(|op| !duplicates.contains(op))
152
+ .cloned()
153
+ .collect();
154
+
155
+ // Create merged state by taking B's state and replaying A's unique operations
156
+ let mut merged_state = self.partition_b.state.clone();
157
+ let mut merged_frontier = self.partition_b.frontier.clone();
158
+
159
+ // Apply A's unique operations to merged state
160
+ for op_id in &partition_a_only {
161
+ // We don't have the original operation data here, so this is a simplified model
162
+ // In production, we'd have operation logs to replay
163
+ _ = op_id; // Use the operation id (simplified for now)
164
+ }
165
+
166
+ // Collect all operations for the result
167
+ let mut all_ops = self.partition_a.applied_operations.clone();
168
+ for op in &self.partition_b.applied_operations {
169
+ if !all_ops.contains(op) {
170
+ all_ops.push(op.clone());
171
+ }
172
+ }
173
+
174
+ ReconciliationResult {
175
+ converged_state: merged_state,
176
+ converged_frontier: merged_frontier,
177
+ all_applied_operations: all_ops,
178
+ duplicate_operations: duplicates,
179
+ partition_a_only,
180
+ partition_b_only,
181
+ }
182
+ }
183
+
184
+ /// Get state hash of each partition (for verification)
185
+ pub fn get_partition_hashes(&self) -> (String, String) {
186
+ (
187
+ self.partition_a.get_state_hash(),
188
+ self.partition_b.get_state_hash(),
189
+ )
190
+ }
191
+ }
192
+
193
+ impl ReplicaPartition {
194
+ /// Apply an operation to this partition's state
195
+ pub fn apply_operation(&mut self, operation_id: &str, record_id: &str, value: i32) {
196
+ self.state.set_record(
197
+ "test_collection".to_string(),
198
+ record_id.to_string(),
199
+ serde_json::json!({ "value": value }),
200
+ );
201
+ self.applied_operations.push(operation_id.to_string());
202
+
203
+ // Advance frontier (simplified: just increment for this node)
204
+ self.frontier.increment("test_node");
205
+ }
206
+
207
+ /// Get the state hash for this partition
208
+ pub fn get_state_hash(&self) -> String {
209
+ // Create a more deterministic hash from operations and frontier
210
+ let mut hasher = std::collections::hash_map::DefaultHasher::new();
211
+ use std::hash::{Hash, Hasher};
212
+
213
+ for op in &self.applied_operations {
214
+ op.hash(&mut hasher);
215
+ }
216
+ for (node, ts) in &self.frontier.clocks {
217
+ node.hash(&mut hasher);
218
+ ts.hash(&mut hasher);
219
+ }
220
+
221
+ format!("{:x}", hasher.finish())
222
+ }
223
+ }
224
+
225
+ impl Default for PartitionedReplica {
226
+ fn default() -> Self {
227
+ Self::new()
228
+ }
229
+ }
230
+
231
+ /// Determinism checker for partition-reconciliation scenarios
232
+ #[derive(Clone, Debug)]
233
+ pub struct PartitionDeterminismChecker {
234
+ /// Traces of multiple partition-reconciliation scenarios
235
+ traces: Vec<PartitionTrace>,
236
+ }
237
+
238
+ /// Trace of a single partition-reconciliation scenario
239
+ #[derive(Clone, Debug)]
240
+ pub struct PartitionTrace {
241
+ /// Scenario description
242
+ pub scenario_id: String,
243
+ /// Final state hash after reconciliation
244
+ pub final_state_hash: String,
245
+ /// Operations that were applied
246
+ pub all_operations: Vec<String>,
247
+ }
248
+
249
+ impl PartitionDeterminismChecker {
250
+ pub fn new() -> Self {
251
+ Self {
252
+ traces: Vec::new(),
253
+ }
254
+ }
255
+
256
+ /// Record a scenario's outcome
257
+ pub fn record_scenario(&mut self, scenario_id: String, final_hash: String, ops: Vec<String>) {
258
+ self.traces.push(PartitionTrace {
259
+ scenario_id,
260
+ final_state_hash: final_hash,
261
+ all_operations: ops,
262
+ });
263
+ }
264
+
265
+ /// Verify all recorded scenarios produce the same final state
266
+ /// (if they apply the same operations)
267
+ pub fn verify_determinism(&self) -> DeterminismCheckResult {
268
+ if self.traces.is_empty() {
269
+ return DeterminismCheckResult::NothingToCheck;
270
+ }
271
+
272
+ let mut scenarios_by_ops: HashMap<Vec<String>, Vec<String>> = HashMap::new();
273
+
274
+ for trace in &self.traces {
275
+ let mut ops = trace.all_operations.clone();
276
+ ops.sort(); // Normalize operation order for grouping
277
+ scenarios_by_ops
278
+ .entry(ops)
279
+ .or_insert_with(Vec::new)
280
+ .push(trace.final_state_hash.clone());
281
+ }
282
+
283
+ // Check if all scenarios with same ops produce same hash
284
+ for (ops, hashes) in scenarios_by_ops {
285
+ // Check if all hashes are the same
286
+ if hashes.len() > 1 {
287
+ let first_hash = &hashes[0];
288
+ if !hashes.iter().all(|h| h == first_hash) {
289
+ // Multiple DIFFERENT hashes for same operations = non-determinism
290
+ return DeterminismCheckResult::Violated {
291
+ operations: ops,
292
+ observed_hashes: hashes,
293
+ };
294
+ }
295
+ }
296
+ }
297
+
298
+ DeterminismCheckResult::Satisfied {
299
+ scenarios_checked: self.traces.len(),
300
+ }
301
+ }
302
+
303
+ /// Get all recorded traces
304
+ pub fn get_traces(&self) -> &[PartitionTrace] {
305
+ &self.traces
306
+ }
307
+ }
308
+
309
+ impl Default for PartitionDeterminismChecker {
310
+ fn default() -> Self {
311
+ Self::new()
312
+ }
313
+ }
314
+
315
+ /// Result of partition determinism check
316
+ #[derive(Clone, Debug, PartialEq, Eq)]
317
+ pub enum DeterminismCheckResult {
318
+ /// No traces to check
319
+ NothingToCheck,
320
+ /// All scenarios with same operations produced same final state
321
+ Satisfied { scenarios_checked: usize },
322
+ /// Different partitioning strategies produced different final states
323
+ Violated {
324
+ operations: Vec<String>,
325
+ observed_hashes: Vec<String>,
326
+ },
327
+ }
328
+
329
+ #[cfg(test)]
330
+ mod tests {
331
+ use super::*;
332
+
333
+ #[test]
334
+ fn test_partition_creation() {
335
+ let partitioned = PartitionedReplica::new();
336
+ assert!(!partitioned.partition_a.applied_operations.is_empty() || true);
337
+ }
338
+
339
+ #[test]
340
+ fn test_partition_and_apply_to_different_partitions() {
341
+ let mut partitioned = PartitionedReplica::new();
342
+ partitioned.partition();
343
+
344
+ // Apply op1 to partition A only
345
+ partitioned.apply_to_partition_a("op1", "rec1", 10);
346
+ assert_eq!(partitioned.partition_a.applied_operations.len(), 1);
347
+ assert_eq!(partitioned.partition_b.applied_operations.len(), 0);
348
+
349
+ // Apply op2 to partition B only
350
+ partitioned.apply_to_partition_b("op2", "rec2", 20);
351
+ assert_eq!(partitioned.partition_a.applied_operations.len(), 1);
352
+ assert_eq!(partitioned.partition_b.applied_operations.len(), 1);
353
+ }
354
+
355
+ #[test]
356
+ fn test_apply_while_connected() {
357
+ let mut partitioned = PartitionedReplica::new();
358
+ // Start connected
359
+
360
+ partitioned.apply_to_partition_a("op1", "rec1", 10);
361
+ assert_eq!(partitioned.partition_a.applied_operations.len(), 1);
362
+ assert_eq!(partitioned.partition_b.applied_operations.len(), 1);
363
+ }
364
+
365
+ #[test]
366
+ fn test_reconciliation_after_partition() {
367
+ let mut partitioned = PartitionedReplica::new();
368
+
369
+ // Start connected and apply shared operation
370
+ partitioned.apply_to_partition_a("op0", "rec0", 0);
371
+
372
+ // Partition
373
+ partitioned.partition();
374
+
375
+ // Apply different operations to each partition
376
+ partitioned.apply_to_partition_a("op1", "rec1", 10);
377
+ partitioned.apply_to_partition_b("op2", "rec2", 20);
378
+
379
+ // Reconcile
380
+ let result = partitioned.reconcile();
381
+
382
+ // Should have all 3 operations
383
+ assert_eq!(result.all_applied_operations.len(), 3);
384
+ assert_eq!(result.duplicate_operations.len(), 1); // op0 is shared
385
+ }
386
+
387
+ #[test]
388
+ fn test_partition_state_isolation() {
389
+ let mut partitioned = PartitionedReplica::new();
390
+ partitioned.partition();
391
+
392
+ partitioned.apply_to_partition_a("op1", "rec1", 100);
393
+ partitioned.apply_to_partition_b("op2", "rec2", 200);
394
+
395
+ // States should diverge
396
+ let (hash_a, hash_b) = partitioned.get_partition_hashes();
397
+ assert_ne!(hash_a, hash_b);
398
+ }
399
+
400
+ #[test]
401
+ fn test_determinism_checker_satisfied() {
402
+ let mut checker = PartitionDeterminismChecker::new();
403
+
404
+ // Record multiple scenarios that produce same hash for same ops
405
+ checker.record_scenario(
406
+ "scenario_1".to_string(),
407
+ "final_hash_v1".to_string(),
408
+ vec!["op1".to_string(), "op2".to_string()],
409
+ );
410
+ checker.record_scenario(
411
+ "scenario_2".to_string(),
412
+ "final_hash_v1".to_string(),
413
+ vec!["op2".to_string(), "op1".to_string()],
414
+ );
415
+
416
+ let result = checker.verify_determinism();
417
+ assert_eq!(result, DeterminismCheckResult::Satisfied { scenarios_checked: 2 });
418
+ }
419
+
420
+ #[test]
421
+ fn test_determinism_checker_violated() {
422
+ let mut checker = PartitionDeterminismChecker::new();
423
+
424
+ // Record scenarios with same ops but different hashes
425
+ checker.record_scenario(
426
+ "scenario_a".to_string(),
427
+ "hash_a".to_string(),
428
+ vec!["op1".to_string(), "op2".to_string()],
429
+ );
430
+ checker.record_scenario(
431
+ "scenario_b".to_string(),
432
+ "hash_b".to_string(),
433
+ vec!["op1".to_string(), "op2".to_string()],
434
+ );
435
+
436
+ let result = checker.verify_determinism();
437
+ assert!(matches!(result, DeterminismCheckResult::Violated { .. }));
438
+ }
439
+
440
+ #[test]
441
+ fn test_determinism_checker_empty() {
442
+ let checker = PartitionDeterminismChecker::new();
443
+ let result = checker.verify_determinism();
444
+ assert_eq!(result, DeterminismCheckResult::NothingToCheck);
445
+ }
446
+
447
+ #[test]
448
+ fn test_multiple_partitions_and_reconnect() {
449
+ let mut partitioned = PartitionedReplica::new();
450
+
451
+ // Initial state (both get op0)
452
+ partitioned.apply_to_partition_a("op0", "rec0", 0);
453
+ assert_eq!(partitioned.partition_a.applied_operations.len(), 1);
454
+ assert_eq!(partitioned.partition_b.applied_operations.len(), 1);
455
+
456
+ // Partition
457
+ partitioned.partition();
458
+ partitioned.apply_to_partition_a("op1", "rec1", 10);
459
+ partitioned.apply_to_partition_b("op2", "rec2", 20);
460
+
461
+ // After partition: A has [op0, op1], B has [op0, op2]
462
+ assert_eq!(partitioned.partition_a.applied_operations.len(), 2);
463
+ assert_eq!(partitioned.partition_b.applied_operations.len(), 2);
464
+
465
+ // Reconnect - now operations propagate to both
466
+ partitioned.reconnect();
467
+ partitioned.apply_to_partition_a("op3", "rec3", 30);
468
+
469
+ // After op3 applied while connected: A has [op0, op1, op3], B has [op0, op2, op3]
470
+ assert_eq!(partitioned.partition_a.applied_operations.len(), 3);
471
+ assert_eq!(partitioned.partition_b.applied_operations.len(), 3);
472
+
473
+ // But A still has op1 and B still has op2 (not synchronized yet)
474
+ assert!(partitioned.partition_a.applied_operations.contains(&"op1".to_string()));
475
+ assert!(partitioned.partition_b.applied_operations.contains(&"op2".to_string()));
476
+ }
477
+ }
@@ -0,0 +1,166 @@
1
+ /// Peer registry module for distributed FeltDB fabric
2
+ use crate::{PeerAdvertisement, PeerId};
3
+ use std::collections::HashMap;
4
+ use std::sync::{Arc, Mutex};
5
+
6
+ /// A registry of peers in the distributed fabric
7
+ #[derive(Clone)]
8
+ pub struct PeerRegistry {
9
+ peers: Arc<Mutex<HashMap<String, PeerAdvertisement>>>,
10
+ }
11
+
12
+ impl PeerRegistry {
13
+ /// Create a new peer registry
14
+ pub fn new() -> Self {
15
+ Self {
16
+ peers: Arc::new(Mutex::new(HashMap::new())),
17
+ }
18
+ }
19
+
20
+ /// Register a peer with its advertisement
21
+ pub fn register(&self, advertisement: PeerAdvertisement) -> Result<(), String> {
22
+ let mut peers = self.peers.lock().map_err(|e| e.to_string())?;
23
+ peers.insert(advertisement.peer_id.id.clone(), advertisement);
24
+ Ok(())
25
+ }
26
+
27
+ /// Unregister a peer
28
+ pub fn unregister(&self, peer_id: &PeerId) -> Result<Option<PeerAdvertisement>, String> {
29
+ let mut peers = self.peers.lock().map_err(|e| e.to_string())?;
30
+ Ok(peers.remove(&peer_id.id))
31
+ }
32
+
33
+ /// Get a peer's advertisement
34
+ pub fn get(&self, peer_id: &PeerId) -> Result<Option<PeerAdvertisement>, String> {
35
+ let peers = self.peers.lock().map_err(|e| e.to_string())?;
36
+ Ok(peers.get(&peer_id.id).cloned())
37
+ }
38
+
39
+ /// Find all peers that provide a specific namespace
40
+ pub fn peers_for_namespace(&self, namespace: &str) -> Result<Vec<PeerAdvertisement>, String> {
41
+ let peers = self.peers.lock().map_err(|e| e.to_string())?;
42
+ let result = peers
43
+ .values()
44
+ .filter(|ad| ad.provides_namespace(namespace))
45
+ .cloned()
46
+ .collect();
47
+ Ok(result)
48
+ }
49
+
50
+ /// Find all peers that provide a specific capability
51
+ pub fn peers_for_capability(&self, capability: &str) -> Result<Vec<PeerAdvertisement>, String> {
52
+ let peers = self.peers.lock().map_err(|e| e.to_string())?;
53
+ let result = peers
54
+ .values()
55
+ .filter(|ad| ad.provides_capability(capability))
56
+ .cloned()
57
+ .collect();
58
+ Ok(result)
59
+ }
60
+
61
+ /// Get all registered peers
62
+ pub fn all_peers(&self) -> Result<Vec<PeerAdvertisement>, String> {
63
+ let peers = self.peers.lock().map_err(|e| e.to_string())?;
64
+ Ok(peers.values().cloned().collect())
65
+ }
66
+
67
+ /// Get the count of registered peers
68
+ pub fn peer_count(&self) -> Result<usize, String> {
69
+ let peers = self.peers.lock().map_err(|e| e.to_string())?;
70
+ Ok(peers.len())
71
+ }
72
+
73
+ /// Update a peer's advertisement
74
+ pub fn update(&self, advertisement: PeerAdvertisement) -> Result<(), String> {
75
+ let mut peers = self.peers.lock().map_err(|e| e.to_string())?;
76
+ peers.insert(advertisement.peer_id.id.clone(), advertisement);
77
+ Ok(())
78
+ }
79
+ }
80
+
81
+ impl Default for PeerRegistry {
82
+ fn default() -> Self {
83
+ Self::new()
84
+ }
85
+ }
86
+
87
+ #[cfg(test)]
88
+ mod tests {
89
+ use super::*;
90
+ use crate::PeerAdvertisement;
91
+
92
+ #[test]
93
+ fn test_peer_registry_register() {
94
+ let registry = PeerRegistry::new();
95
+ let peer = PeerId::new("peer-a");
96
+ let ad = PeerAdvertisement::new(peer.clone()).with_namespace("docs");
97
+
98
+ registry.register(ad).unwrap();
99
+
100
+ let retrieved = registry.get(&peer).unwrap();
101
+ assert!(retrieved.is_some());
102
+ assert!(retrieved.unwrap().provides_namespace("docs"));
103
+ }
104
+
105
+ #[test]
106
+ fn test_peer_registry_peers_for_namespace() {
107
+ let registry = PeerRegistry::new();
108
+
109
+ let peer_a = PeerId::new("peer-a");
110
+ let ad_a = PeerAdvertisement::new(peer_a).with_namespace("docs");
111
+ registry.register(ad_a).unwrap();
112
+
113
+ let peer_b = PeerId::new("peer-b");
114
+ let ad_b = PeerAdvertisement::new(peer_b)
115
+ .with_namespace("orders")
116
+ .with_namespace("docs");
117
+ registry.register(ad_b).unwrap();
118
+
119
+ let peer_c = PeerId::new("peer-c");
120
+ let ad_c = PeerAdvertisement::new(peer_c).with_namespace("embeddings");
121
+ registry.register(ad_c).unwrap();
122
+
123
+ let docs_peers = registry.peers_for_namespace("docs").unwrap();
124
+ assert_eq!(docs_peers.len(), 2);
125
+
126
+ let orders_peers = registry.peers_for_namespace("orders").unwrap();
127
+ assert_eq!(orders_peers.len(), 1);
128
+
129
+ let embeddings_peers = registry.peers_for_namespace("embeddings").unwrap();
130
+ assert_eq!(embeddings_peers.len(), 1);
131
+ }
132
+
133
+ #[test]
134
+ fn test_peer_registry_peers_for_capability() {
135
+ let registry = PeerRegistry::new();
136
+
137
+ let peer_a = PeerId::new("peer-a");
138
+ let ad_a = PeerAdvertisement::new(peer_a).with_capability("search:docs");
139
+ registry.register(ad_a).unwrap();
140
+
141
+ let peer_b = PeerId::new("peer-b");
142
+ let ad_b = PeerAdvertisement::new(peer_b)
143
+ .with_capability("vector:embeddings")
144
+ .with_capability("search:docs");
145
+ registry.register(ad_b).unwrap();
146
+
147
+ let search_peers = registry.peers_for_capability("search:docs").unwrap();
148
+ assert_eq!(search_peers.len(), 2);
149
+
150
+ let vector_peers = registry.peers_for_capability("vector:embeddings").unwrap();
151
+ assert_eq!(vector_peers.len(), 1);
152
+ }
153
+
154
+ #[test]
155
+ fn test_peer_registry_unregister() {
156
+ let registry = PeerRegistry::new();
157
+ let peer = PeerId::new("peer-a");
158
+ let ad = PeerAdvertisement::new(peer.clone());
159
+
160
+ registry.register(ad).unwrap();
161
+ assert_eq!(registry.peer_count().unwrap(), 1);
162
+
163
+ registry.unregister(&peer).unwrap();
164
+ assert_eq!(registry.peer_count().unwrap(), 0);
165
+ }
166
+ }