@feltdb/core 0.4.12 → 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-Q0Nx6gLE.js → components-bHTARBin.js} +156 -46
  161. package/dist/studio/index.js +31 -28
  162. package/dist/studio-app/assets/{index-BHOmLZF-.js → index-BqKquLuU.js} +8 -8
  163. package/dist/studio-app/index.html +1 -1
  164. package/package.json +1 -1
@@ -0,0 +1,536 @@
1
+ /// Distributed Transactions & Replication
2
+ ///
3
+ /// Coordinates transaction execution across multiple nodes:
4
+ /// - TransactionEnvelope: Atomic unit for multi-replica delivery
5
+ /// - ReplicationMessage: Operation streaming between replicas
6
+ /// - ReplicaState: Per-node state tracking with vector clocks
7
+ /// - DistributedExecutor: Orchestrates consistency semantics
8
+ ///
9
+ /// Invariant: All replicas converge to identical state_hash for same operations
10
+
11
+ use crate::convergence::VectorClock;
12
+ use crate::durable_operation_log::OperationLog;
13
+ use crate::state_hash::StateHash;
14
+ use crate::transactions::*;
15
+ use std::collections::HashMap;
16
+ use std::path::PathBuf;
17
+
18
+ /// Uniquely identifies a transaction envelope
19
+ #[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
20
+ pub struct EnvelopeId {
21
+ pub originating_node: String,
22
+ pub sequence: u64,
23
+ }
24
+
25
+ impl EnvelopeId {
26
+ pub fn new(originating_node: String, sequence: u64) -> Self {
27
+ Self {
28
+ originating_node,
29
+ sequence,
30
+ }
31
+ }
32
+ }
33
+
34
+ /// Atomic unit for multi-replica delivery
35
+ /// Prevents partial delivery of multi-operation transactions
36
+ #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
37
+ pub struct TransactionEnvelope {
38
+ pub envelope_id: EnvelopeId,
39
+ pub transaction_id: String,
40
+ pub operations: Vec<Operation>,
41
+ pub consistency_contract: ConsistencyContract,
42
+ pub originating_node: String,
43
+ pub vector_clock: VectorClock,
44
+ pub timestamp_ms: u64,
45
+ }
46
+
47
+ impl TransactionEnvelope {
48
+ pub fn new(
49
+ originating_node: String,
50
+ sequence: u64,
51
+ transaction_id: String,
52
+ operations: Vec<Operation>,
53
+ consistency_contract: ConsistencyContract,
54
+ ) -> Self {
55
+ let mut vc = VectorClock::new();
56
+ vc.increment(&originating_node);
57
+
58
+ let timestamp_ms = std::time::SystemTime::now()
59
+ .duration_since(std::time::UNIX_EPOCH)
60
+ .unwrap()
61
+ .as_millis() as u64;
62
+
63
+ Self {
64
+ envelope_id: EnvelopeId::new(originating_node.clone(), sequence),
65
+ transaction_id,
66
+ operations,
67
+ consistency_contract,
68
+ originating_node,
69
+ vector_clock: vc,
70
+ timestamp_ms,
71
+ }
72
+ }
73
+
74
+ /// Advance vector clock to account for replication delay
75
+ pub fn advance_clock(&mut self, replica_clock: &VectorClock) {
76
+ self.vector_clock.merge(replica_clock);
77
+ self.vector_clock.increment(&self.originating_node);
78
+ }
79
+
80
+ /// Verify envelope integrity (all operations present)
81
+ pub fn verify_integrity(&self) -> bool {
82
+ !self.operations.is_empty()
83
+ && self.transaction_id.len() > 0
84
+ && self.originating_node.len() > 0
85
+ }
86
+ }
87
+
88
+ /// Message format for distributed operation streaming
89
+ #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
90
+ pub struct ReplicationMessage {
91
+ pub envelope: TransactionEnvelope,
92
+ pub from_node: String,
93
+ pub to_node: String,
94
+ pub sequence_number: u64,
95
+ }
96
+
97
+ impl ReplicationMessage {
98
+ pub fn new(
99
+ envelope: TransactionEnvelope,
100
+ from_node: String,
101
+ to_node: String,
102
+ sequence_number: u64,
103
+ ) -> Self {
104
+ Self {
105
+ envelope,
106
+ from_node,
107
+ to_node,
108
+ sequence_number,
109
+ }
110
+ }
111
+ }
112
+
113
+ /// State of a replica
114
+ #[derive(Clone, Debug)]
115
+ pub struct ReplicaState {
116
+ pub node_id: String,
117
+ pub vector_clock: VectorClock,
118
+ pub state_hash: StateHash,
119
+ pub operations_applied: u64,
120
+ pub last_update_ms: u64,
121
+ }
122
+
123
+ impl ReplicaState {
124
+ pub fn new(node_id: String, initial_hash: StateHash) -> Self {
125
+ Self {
126
+ node_id,
127
+ vector_clock: VectorClock::new(),
128
+ state_hash: initial_hash,
129
+ operations_applied: 0,
130
+ last_update_ms: std::time::SystemTime::now()
131
+ .duration_since(std::time::UNIX_EPOCH)
132
+ .unwrap()
133
+ .as_millis() as u64,
134
+ }
135
+ }
136
+
137
+ /// Update replica state after transaction
138
+ pub fn apply_transaction(
139
+ &mut self,
140
+ envelope: &TransactionEnvelope,
141
+ new_state_hash: StateHash,
142
+ ) {
143
+ self.vector_clock.merge(&envelope.vector_clock);
144
+ self.vector_clock.increment(&self.node_id);
145
+ self.state_hash = new_state_hash;
146
+ self.operations_applied += envelope.operations.len() as u64;
147
+ self.last_update_ms = std::time::SystemTime::now()
148
+ .duration_since(std::time::UNIX_EPOCH)
149
+ .unwrap()
150
+ .as_millis() as u64;
151
+ }
152
+
153
+ /// Causal read: wait for vector clock to advance
154
+ pub fn wait_for_clock_advance(&self, required_clock: &VectorClock) -> bool {
155
+ !self.vector_clock.happens_before(required_clock)
156
+ }
157
+ }
158
+
159
+ /// Distributed transaction executor with durable operation log support
160
+ ///
161
+ /// Durability architecture:
162
+ /// - Operation log is the ONLY durable source of truth
163
+ /// - Dedup set is derived (reconstructed from operation log on startup)
164
+ /// - No separate dedup file (eliminates two-file atomicity problem)
165
+ pub struct DistributedTransactionExecutor {
166
+ pub local_node_id: String,
167
+ pub replicas: HashMap<String, ReplicaState>,
168
+ pub envelopes_seen: std::collections::HashSet<EnvelopeId>,
169
+ pub transaction_executor: TransactionExecutor,
170
+ pub operation_log: Option<OperationLog>,
171
+ }
172
+
173
+ impl DistributedTransactionExecutor {
174
+ /// Create a new distributed executor without durable persistence
175
+ pub fn new(node_id: String, initial_state_hash: StateHash) -> Self {
176
+ let mut replicas = HashMap::new();
177
+ replicas.insert(
178
+ node_id.clone(),
179
+ ReplicaState::new(node_id.clone(), initial_state_hash),
180
+ );
181
+
182
+ Self {
183
+ local_node_id: node_id.clone(),
184
+ replicas,
185
+ envelopes_seen: std::collections::HashSet::new(),
186
+ transaction_executor: TransactionExecutor::new(node_id),
187
+ operation_log: None,
188
+ }
189
+ }
190
+
191
+ /// Create a new distributed executor with durable operation log
192
+ ///
193
+ /// Note: dedup set is NOT persisted separately. It is reconstructed deterministically
194
+ /// from the operation log on startup, eliminating atomicity issues.
195
+ pub fn with_log(
196
+ node_id: String,
197
+ initial_state_hash: StateHash,
198
+ log_path: Option<PathBuf>,
199
+ ) -> Result<Self, std::io::Error> {
200
+ let mut replicas = HashMap::new();
201
+ replicas.insert(
202
+ node_id.clone(),
203
+ ReplicaState::new(node_id.clone(), initial_state_hash),
204
+ );
205
+
206
+ let operation_log = if let Some(path) = &log_path {
207
+ Some(OperationLog::new(path)?)
208
+ } else {
209
+ None
210
+ };
211
+
212
+ Ok(Self {
213
+ local_node_id: node_id.clone(),
214
+ replicas,
215
+ envelopes_seen: std::collections::HashSet::new(),
216
+ transaction_executor: TransactionExecutor::new(node_id),
217
+ operation_log,
218
+ })
219
+ }
220
+
221
+ /// Register a replica (peer node)
222
+ pub fn register_replica(&mut self, node_id: String, initial_state_hash: StateHash) {
223
+ self.replicas.insert(
224
+ node_id.clone(),
225
+ ReplicaState::new(node_id, initial_state_hash),
226
+ );
227
+ }
228
+
229
+ /// Execute transaction locally and prepare for replication
230
+ /// Ordering: execute → persist → update state → record dedup
231
+ pub fn execute_local_transaction(
232
+ &mut self,
233
+ sequence: u64,
234
+ transaction_id: String,
235
+ parent_version: StateVersion,
236
+ operations: Vec<Operation>,
237
+ consistency_contract: ConsistencyContract,
238
+ ) -> Result<TransactionEnvelope, String> {
239
+ // Step 1: Execute transaction through 14C
240
+ let transition = self.transaction_executor.execute_transaction(
241
+ transaction_id.clone(),
242
+ parent_version,
243
+ operations.clone(),
244
+ consistency_contract.clone(),
245
+ );
246
+
247
+ if !transition.is_successful() {
248
+ return Err("Transaction execution failed".to_string());
249
+ }
250
+
251
+ // Step 2: Create envelope for replication
252
+ let envelope = TransactionEnvelope::new(
253
+ self.local_node_id.clone(),
254
+ sequence,
255
+ transaction_id,
256
+ operations,
257
+ consistency_contract,
258
+ );
259
+
260
+ // Step 3: Durably persist operation to log (with fsync)
261
+ // This must succeed before we update in-memory state
262
+ self.persist_envelope(&envelope)?;
263
+
264
+ // Step 4: Update local replica state (in-memory)
265
+ if let Some(local_replica) = self.replicas.get_mut(&self.local_node_id) {
266
+ let state_hash = StateHash::from_hex(transition.to_version.state_hash.clone());
267
+ local_replica.apply_transaction(&envelope, state_hash);
268
+ }
269
+
270
+ // Step 5: Record envelope to prevent duplicates (in-memory, derived from log)
271
+ self.envelopes_seen.insert(envelope.envelope_id.clone());
272
+
273
+ Ok(envelope)
274
+ }
275
+
276
+ /// Receive replicated transaction from peer
277
+ /// Ordering: dedup check → execute → persist → update state
278
+ pub fn receive_replicated_transaction(
279
+ &mut self,
280
+ message: ReplicationMessage,
281
+ parent_version: StateVersion,
282
+ ) -> Result<StateHash, String> {
283
+ // Step 1: Check for duplicate (prevent re-application)
284
+ if self.envelopes_seen.contains(&message.envelope.envelope_id) {
285
+ return Err("Duplicate envelope".to_string());
286
+ }
287
+
288
+ // Step 2: Execute transaction through 14C
289
+ let transition = self.transaction_executor.execute_transaction(
290
+ message.envelope.transaction_id.clone(),
291
+ parent_version,
292
+ message.envelope.operations.clone(),
293
+ message.envelope.consistency_contract.clone(),
294
+ );
295
+
296
+ if !transition.is_successful() {
297
+ return Err("Transaction execution failed".to_string());
298
+ }
299
+
300
+ // Step 3: Durably persist operation to log (with fsync)
301
+ self.persist_envelope(&message.envelope)?;
302
+
303
+ // Step 4: Record envelope to prevent duplicates (in-memory)
304
+ self.envelopes_seen.insert(message.envelope.envelope_id.clone());
305
+
306
+ // Step 5: Update this replica's state (receiver updates itself)
307
+ if let Some(replica) = self.replicas.get_mut(&self.local_node_id) {
308
+ let state_hash = StateHash::from_hex(transition.to_version.state_hash.clone());
309
+ replica.apply_transaction(&message.envelope, state_hash);
310
+ }
311
+
312
+ // Return the state hash
313
+ Ok(StateHash::from_hex(transition.to_version.state_hash))
314
+ }
315
+
316
+ /// Get current state of a replica
317
+ pub fn get_replica_state(&self, node_id: &str) -> Option<ReplicaState> {
318
+ self.replicas.get(node_id).cloned()
319
+ }
320
+
321
+ /// Check if replicas have converged to same state
322
+ pub fn check_convergence(&self) -> bool {
323
+ if self.replicas.is_empty() {
324
+ return true;
325
+ }
326
+
327
+ let first_hash = self
328
+ .replicas
329
+ .values()
330
+ .next()
331
+ .map(|r| r.state_hash.clone());
332
+
333
+ if let Some(first) = first_hash {
334
+ self.replicas.values().all(|r| r.state_hash == first)
335
+ } else {
336
+ false
337
+ }
338
+ }
339
+
340
+ /// Get all replica states
341
+ pub fn get_all_replicas(&self) -> Vec<ReplicaState> {
342
+ self.replicas.values().cloned().collect()
343
+ }
344
+
345
+ /// Causal read: wait for specific version
346
+ pub fn causal_read(&self, required_clock: &VectorClock) -> bool {
347
+ if let Some(local) = self.replicas.get(&self.local_node_id) {
348
+ local.wait_for_clock_advance(required_clock)
349
+ } else {
350
+ false
351
+ }
352
+ }
353
+
354
+ /// Get the local node ID
355
+ pub fn get_local_node_id(&self) -> String {
356
+ self.local_node_id.clone()
357
+ }
358
+
359
+ /// Load persisted operations from disk and rebuild internal state
360
+ ///
361
+ /// Atomicity guarantee:
362
+ /// - Load operation log (single durable source of truth)
363
+ /// - Reconstruct dedup set deterministically from envelopes
364
+ /// - No separate dedup file (no two-file atomicity problem)
365
+ pub fn load_from_disk(&mut self, log_path: PathBuf) -> Result<usize, std::io::Error> {
366
+ let log = OperationLog::new(&log_path)?;
367
+ let envelopes = log.load_all()?;
368
+
369
+ // Reconstruct dedup set from loaded envelopes (deterministic, atomic with log)
370
+ for envelope in &envelopes {
371
+ self.envelopes_seen.insert(envelope.envelope_id.clone());
372
+ }
373
+
374
+ // Note: Replica state is not rebuilt from log in this initial version
375
+ // It will be recovered through normal replication when this node rejoins the cluster
376
+ self.operation_log = Some(log);
377
+
378
+ Ok(envelopes.len())
379
+ }
380
+
381
+ /// Persist an envelope to the durable operation log
382
+ /// Must be called after successful state application but before returning to caller
383
+ ///
384
+ /// Note: dedup set is NOT persisted (it's derived from the operation log).
385
+ fn persist_envelope(&mut self, envelope: &TransactionEnvelope) -> Result<(), String> {
386
+ if let Some(ref mut log) = self.operation_log {
387
+ log.append(envelope)
388
+ .map_err(|e| format!("Failed to persist operation: {}", e))?;
389
+ }
390
+ Ok(())
391
+ }
392
+ }
393
+
394
+ #[cfg(test)]
395
+ mod tests {
396
+ use super::*;
397
+
398
+ #[test]
399
+ fn test_envelope_creation() {
400
+ let envelope = TransactionEnvelope::new(
401
+ "node1".to_string(),
402
+ 1,
403
+ "txn1".to_string(),
404
+ Vec::new(),
405
+ ConsistencyContract::causal(),
406
+ );
407
+
408
+ assert_eq!(envelope.originating_node, "node1");
409
+ assert_eq!(envelope.transaction_id, "txn1");
410
+ assert_eq!(envelope.envelope_id.sequence, 1);
411
+ }
412
+
413
+ #[test]
414
+ fn test_envelope_integrity() {
415
+ let envelope = TransactionEnvelope::new(
416
+ "node1".to_string(),
417
+ 1,
418
+ "txn1".to_string(),
419
+ vec![], // Empty operations
420
+ ConsistencyContract::causal(),
421
+ );
422
+
423
+ assert!(!envelope.verify_integrity());
424
+ }
425
+
426
+ #[test]
427
+ fn test_replication_message_creation() {
428
+ let envelope = TransactionEnvelope::new(
429
+ "node1".to_string(),
430
+ 1,
431
+ "txn1".to_string(),
432
+ Vec::new(),
433
+ ConsistencyContract::causal(),
434
+ );
435
+
436
+ let msg = ReplicationMessage::new(envelope, "node1".to_string(), "node2".to_string(), 1);
437
+
438
+ assert_eq!(msg.from_node, "node1");
439
+ assert_eq!(msg.to_node, "node2");
440
+ }
441
+
442
+ #[test]
443
+ fn test_replica_state_creation() {
444
+ let state = ReplicaState::new("node1".to_string(), StateHash::from_hex("hash0".to_string()));
445
+
446
+ assert_eq!(state.node_id, "node1");
447
+ assert_eq!(state.operations_applied, 0);
448
+ }
449
+
450
+ #[test]
451
+ fn test_distributed_executor_creation() {
452
+ let executor = DistributedTransactionExecutor::new(
453
+ "node1".to_string(),
454
+ StateHash::from_hex("hash0".to_string()),
455
+ );
456
+
457
+ assert!(executor.replicas.contains_key("node1"));
458
+ }
459
+
460
+ #[test]
461
+ fn test_register_replica() {
462
+ let mut executor = DistributedTransactionExecutor::new(
463
+ "node1".to_string(),
464
+ StateHash::from_hex("hash0".to_string()),
465
+ );
466
+
467
+ executor.register_replica("node2".to_string(), StateHash::from_hex("hash0".to_string()));
468
+
469
+ assert!(executor.replicas.contains_key("node2"));
470
+ }
471
+
472
+ #[test]
473
+ fn test_convergence_check_empty() {
474
+ let executor = DistributedTransactionExecutor::new(
475
+ "node1".to_string(),
476
+ StateHash::from_hex("hash0".to_string()),
477
+ );
478
+
479
+ // Single replica should be converged
480
+ assert!(executor.check_convergence());
481
+ }
482
+
483
+ #[test]
484
+ fn test_convergence_check_multiple_replicas() {
485
+ let mut executor = DistributedTransactionExecutor::new(
486
+ "node1".to_string(),
487
+ StateHash::from_hex("hash0".to_string()),
488
+ );
489
+
490
+ executor.register_replica("node2".to_string(), StateHash::from_hex("hash0".to_string()));
491
+ executor.register_replica("node3".to_string(), StateHash::from_hex("hash0".to_string()));
492
+
493
+ assert!(executor.check_convergence());
494
+ }
495
+
496
+ #[test]
497
+ fn test_convergence_check_diverged() {
498
+ let mut executor = DistributedTransactionExecutor::new(
499
+ "node1".to_string(),
500
+ StateHash::from_hex("hash0".to_string()),
501
+ );
502
+
503
+ executor.register_replica("node2".to_string(), StateHash::from_hex("hash1".to_string()));
504
+
505
+ assert!(!executor.check_convergence());
506
+ }
507
+
508
+ #[test]
509
+ fn test_causal_read_initial_state() {
510
+ let executor = DistributedTransactionExecutor::new(
511
+ "node1".to_string(),
512
+ StateHash::from_hex("hash0".to_string()),
513
+ );
514
+
515
+ let vc = VectorClock::new();
516
+ assert!(executor.causal_read(&vc));
517
+ }
518
+
519
+ #[test]
520
+ fn test_envelope_clock_advance() {
521
+ let mut envelope = TransactionEnvelope::new(
522
+ "node1".to_string(),
523
+ 1,
524
+ "txn1".to_string(),
525
+ Vec::new(),
526
+ ConsistencyContract::causal(),
527
+ );
528
+
529
+ let mut other_clock = VectorClock::new();
530
+ other_clock.increment("node2");
531
+
532
+ envelope.advance_clock(&other_clock);
533
+
534
+ assert!(envelope.vector_clock.happens_before(&other_clock) == false);
535
+ }
536
+ }