@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,207 @@
1
+ /// Durable Operation Log
2
+ ///
3
+ /// Provides persistent storage for operation envelopes with fsync durability.
4
+ /// The log is the source of truth for recovery; replica state and dedup sets are derived from it.
5
+ ///
6
+ /// Invariant: Every successfully persisted operation can be recovered after process restart.
7
+
8
+ use crate::distributed_transactions::TransactionEnvelope;
9
+ use std::fs::{File, OpenOptions};
10
+ use std::io::{BufRead, BufReader, Write};
11
+ use std::path::{Path, PathBuf};
12
+
13
+ /// Durable operation log with fsync semantics
14
+ pub struct OperationLog {
15
+ path: PathBuf,
16
+ file: Option<File>,
17
+ }
18
+
19
+ impl OperationLog {
20
+ /// Create or open a durable log at the given path
21
+ pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, std::io::Error> {
22
+ let path = path.as_ref().to_path_buf();
23
+
24
+ // Create parent directory if needed
25
+ if let Some(parent) = path.parent() {
26
+ std::fs::create_dir_all(parent)?;
27
+ }
28
+
29
+ // Open log file in append mode
30
+ let file = OpenOptions::new()
31
+ .create(true)
32
+ .append(true)
33
+ .open(&path)?;
34
+
35
+ Ok(Self {
36
+ path,
37
+ file: Some(file),
38
+ })
39
+ }
40
+
41
+ /// Append and durably persist an operation envelope
42
+ /// Returns error if write or fsync fails
43
+ pub fn append(&mut self, envelope: &TransactionEnvelope) -> Result<(), std::io::Error> {
44
+ if let Some(ref mut file) = self.file {
45
+ // Serialize envelope to JSON line
46
+ let json = serde_json::to_string(envelope)
47
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
48
+
49
+ // Write JSON line (newline-delimited)
50
+ writeln!(file, "{}", json)?;
51
+
52
+ // Force durable persistence to disk
53
+ file.sync_all()?;
54
+
55
+ Ok(())
56
+ } else {
57
+ Err(std::io::Error::new(
58
+ std::io::ErrorKind::NotConnected,
59
+ "Operation log file not open",
60
+ ))
61
+ }
62
+ }
63
+
64
+ /// Load all persisted envelopes from log
65
+ /// Returns envelopes in order they were persisted
66
+ pub fn load_all(&self) -> Result<Vec<TransactionEnvelope>, std::io::Error> {
67
+ if !self.path.exists() {
68
+ return Ok(Vec::new());
69
+ }
70
+
71
+ let file = File::open(&self.path)?;
72
+ let reader = BufReader::new(file);
73
+ let mut envelopes = Vec::new();
74
+
75
+ for line in reader.lines() {
76
+ let line = line?;
77
+ if line.trim().is_empty() {
78
+ continue;
79
+ }
80
+
81
+ match serde_json::from_str::<TransactionEnvelope>(&line) {
82
+ Ok(envelope) => envelopes.push(envelope),
83
+ Err(e) => {
84
+ return Err(std::io::Error::new(
85
+ std::io::ErrorKind::InvalidData,
86
+ format!("Failed to deserialize envelope: {}", e),
87
+ ))
88
+ }
89
+ }
90
+ }
91
+
92
+ Ok(envelopes)
93
+ }
94
+
95
+ /// Get the number of persisted operations
96
+ pub fn count(&self) -> Result<usize, std::io::Error> {
97
+ let envelopes = self.load_all()?;
98
+ Ok(envelopes.len())
99
+ }
100
+
101
+ /// Clear the log (used for testing)
102
+ pub fn clear(&mut self) -> Result<(), std::io::Error> {
103
+ if let Some(file) = self.file.take() {
104
+ drop(file);
105
+ }
106
+
107
+ std::fs::remove_file(&self.path)?;
108
+
109
+ let file = OpenOptions::new()
110
+ .create(true)
111
+ .append(true)
112
+ .open(&self.path)?;
113
+
114
+ self.file = Some(file);
115
+ Ok(())
116
+ }
117
+ }
118
+
119
+ #[cfg(test)]
120
+ mod tests {
121
+ use super::*;
122
+ use crate::transactions::{ConsistencyLevel, ConsistencyContract};
123
+ use tempfile::TempDir;
124
+
125
+ fn create_test_envelope(sequence: u64) -> TransactionEnvelope {
126
+ TransactionEnvelope::new(
127
+ "node1".to_string(),
128
+ sequence,
129
+ format!("tx{}", sequence),
130
+ Vec::new(), // Empty operations for test
131
+ ConsistencyContract {
132
+ level: ConsistencyLevel::Causal,
133
+ timeout_ms: Some(1000),
134
+ required_acks: None,
135
+ },
136
+ )
137
+ }
138
+
139
+ #[test]
140
+ fn test_operation_log_append() -> Result<(), Box<dyn std::error::Error>> {
141
+ let temp_dir = TempDir::new()?;
142
+ let log_path = temp_dir.path().join("test.log");
143
+
144
+ let mut log = OperationLog::new(&log_path)?;
145
+ let envelope = create_test_envelope(1);
146
+
147
+ log.append(&envelope)?;
148
+
149
+ assert!(log_path.exists());
150
+ Ok(())
151
+ }
152
+
153
+ #[test]
154
+ fn test_operation_log_load() -> Result<(), Box<dyn std::error::Error>> {
155
+ let temp_dir = TempDir::new()?;
156
+ let log_path = temp_dir.path().join("test.log");
157
+
158
+ let mut log = OperationLog::new(&log_path)?;
159
+ let envelope1 = create_test_envelope(1);
160
+ let envelope2 = create_test_envelope(2);
161
+
162
+ log.append(&envelope1)?;
163
+ log.append(&envelope2)?;
164
+
165
+ let loaded = log.load_all()?;
166
+ assert_eq!(loaded.len(), 2);
167
+ assert_eq!(loaded[0].envelope_id.sequence, 1);
168
+ assert_eq!(loaded[1].envelope_id.sequence, 2);
169
+
170
+ Ok(())
171
+ }
172
+
173
+ #[test]
174
+ fn test_operation_log_persistence() -> Result<(), Box<dyn std::error::Error>> {
175
+ let temp_dir = TempDir::new()?;
176
+ let log_path = temp_dir.path().join("test.log");
177
+
178
+ // Write to log
179
+ {
180
+ let mut log = OperationLog::new(&log_path)?;
181
+ let envelope = create_test_envelope(1);
182
+ log.append(&envelope)?;
183
+ }
184
+
185
+ // Reload from disk
186
+ {
187
+ let log = OperationLog::new(&log_path)?;
188
+ let loaded = log.load_all()?;
189
+ assert_eq!(loaded.len(), 1);
190
+ assert_eq!(loaded[0].envelope_id.sequence, 1);
191
+ }
192
+
193
+ Ok(())
194
+ }
195
+
196
+ #[test]
197
+ fn test_operation_log_empty() -> Result<(), Box<dyn std::error::Error>> {
198
+ let temp_dir = TempDir::new()?;
199
+ let log_path = temp_dir.path().join("nonexistent.log");
200
+
201
+ let log = OperationLog::new(&log_path)?;
202
+ let loaded = log.load_all()?;
203
+ assert_eq!(loaded.len(), 0);
204
+
205
+ Ok(())
206
+ }
207
+ }
@@ -0,0 +1,316 @@
1
+ /// Durable Synchronization Primitives
2
+ ///
3
+ /// Core synchronization guarantees:
4
+ /// - Change/delta ingestion with sequence tracking
5
+ /// - Idempotent application of operations
6
+ /// - Ordering guarantees via sequence numbers
7
+ /// - Checkpoints and high-water marks for recovery
8
+ /// - Replay and recovery mechanisms
9
+ /// - Backpressure handling for flow control
10
+
11
+ use serde::{Deserialize, Serialize};
12
+ use std::collections::HashMap;
13
+
14
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
15
+ pub struct ChangeId {
16
+ pub node_id: String,
17
+ pub sequence: u64,
18
+ }
19
+
20
+ impl ChangeId {
21
+ pub fn new(node_id: String, sequence: u64) -> Self {
22
+ ChangeId { node_id, sequence }
23
+ }
24
+ }
25
+
26
+ #[derive(Debug, Clone, Serialize, Deserialize)]
27
+ pub enum ChangeType {
28
+ Insert,
29
+ Update,
30
+ Delete,
31
+ Upsert,
32
+ }
33
+
34
+ #[derive(Debug, Clone, Serialize, Deserialize)]
35
+ pub struct DurableChange {
36
+ pub change_id: ChangeId,
37
+ pub change_type: ChangeType,
38
+ pub resource_id: String,
39
+ pub data: String,
40
+ pub timestamp: u64,
41
+ pub idempotency_key: String,
42
+ }
43
+
44
+ impl DurableChange {
45
+ pub fn new(
46
+ node_id: String,
47
+ sequence: u64,
48
+ change_type: ChangeType,
49
+ resource_id: String,
50
+ data: String,
51
+ idempotency_key: String,
52
+ ) -> Self {
53
+ DurableChange {
54
+ change_id: ChangeId::new(node_id, sequence),
55
+ change_type,
56
+ resource_id,
57
+ data,
58
+ timestamp: std::time::SystemTime::now()
59
+ .duration_since(std::time::UNIX_EPOCH)
60
+ .unwrap_or_default()
61
+ .as_secs(),
62
+ idempotency_key,
63
+ }
64
+ }
65
+ }
66
+
67
+ #[derive(Debug, Clone, Serialize, Deserialize)]
68
+ pub struct Checkpoint {
69
+ pub checkpoint_id: String,
70
+ pub node_id: String,
71
+ pub high_water_mark: u64,
72
+ pub applied_changes: Vec<String>,
73
+ pub timestamp: u64,
74
+ }
75
+
76
+ #[derive(Debug, Clone, Serialize, Deserialize)]
77
+ pub struct SyncState {
78
+ pub node_id: String,
79
+ pub last_applied_sequence: u64,
80
+ pub pending_changes: Vec<DurableChange>,
81
+ pub checkpoints: Vec<Checkpoint>,
82
+ }
83
+
84
+ #[derive(Debug, Clone, Serialize, Deserialize)]
85
+ pub enum BackpressureAction {
86
+ Accept,
87
+ Throttle { delay_ms: u64 },
88
+ Reject,
89
+ }
90
+
91
+ pub struct DurableSyncManager {
92
+ node_id: String,
93
+ sync_state: SyncState,
94
+ applied_ids: std::collections::HashSet<String>,
95
+ backpressure_threshold: usize,
96
+ max_pending_changes: usize,
97
+ }
98
+
99
+ impl DurableSyncManager {
100
+ pub fn new(node_id: String) -> Self {
101
+ DurableSyncManager {
102
+ node_id: node_id.clone(),
103
+ sync_state: SyncState {
104
+ node_id,
105
+ last_applied_sequence: 0,
106
+ pending_changes: Vec::new(),
107
+ checkpoints: Vec::new(),
108
+ },
109
+ applied_ids: std::collections::HashSet::new(),
110
+ backpressure_threshold: 10000,
111
+ max_pending_changes: 50000,
112
+ }
113
+ }
114
+
115
+ pub fn ingest_change(&mut self, change: DurableChange) -> Result<(), String> {
116
+ if self.sync_state.pending_changes.len() >= self.max_pending_changes {
117
+ return Err("Max pending changes exceeded".to_string());
118
+ }
119
+
120
+ self.sync_state.pending_changes.push(change);
121
+ self.sync_state.pending_changes.sort_by_key(|c| c.change_id.sequence);
122
+
123
+ Ok(())
124
+ }
125
+
126
+ pub fn apply_changes_idempotent(&mut self) -> Result<Vec<DurableChange>, String> {
127
+ let mut applied = Vec::new();
128
+
129
+ while let Some(change) = self.sync_state.pending_changes.first() {
130
+ if change.change_id.sequence != self.sync_state.last_applied_sequence + 1 {
131
+ break;
132
+ }
133
+
134
+ if self.applied_ids.contains(&change.idempotency_key) {
135
+ self.sync_state.pending_changes.remove(0);
136
+ continue;
137
+ }
138
+
139
+ applied.push(change.clone());
140
+ self.applied_ids.insert(change.idempotency_key.clone());
141
+ self.sync_state.last_applied_sequence = change.change_id.sequence;
142
+ self.sync_state.pending_changes.remove(0);
143
+ }
144
+
145
+ Ok(applied)
146
+ }
147
+
148
+ pub fn create_checkpoint(&mut self) -> Result<Checkpoint, String> {
149
+ let checkpoint = Checkpoint {
150
+ checkpoint_id: format!("cp-{}-{}", self.node_id, self.sync_state.last_applied_sequence),
151
+ node_id: self.node_id.clone(),
152
+ high_water_mark: self.sync_state.last_applied_sequence,
153
+ applied_changes: self
154
+ .applied_ids
155
+ .iter()
156
+ .cloned()
157
+ .collect(),
158
+ timestamp: std::time::SystemTime::now()
159
+ .duration_since(std::time::UNIX_EPOCH)
160
+ .unwrap_or_default()
161
+ .as_secs(),
162
+ };
163
+
164
+ self.sync_state.checkpoints.push(checkpoint.clone());
165
+ Ok(checkpoint)
166
+ }
167
+
168
+ pub fn get_high_water_mark(&self) -> u64 {
169
+ self.sync_state.last_applied_sequence
170
+ }
171
+
172
+ pub fn get_pending_count(&self) -> usize {
173
+ self.sync_state.pending_changes.len()
174
+ }
175
+
176
+ pub fn evaluate_backpressure(&self) -> BackpressureAction {
177
+ let pending = self.sync_state.pending_changes.len();
178
+
179
+ if pending >= self.max_pending_changes {
180
+ BackpressureAction::Reject
181
+ } else if pending >= self.backpressure_threshold {
182
+ BackpressureAction::Throttle {
183
+ delay_ms: ((pending - self.backpressure_threshold) / 100) as u64,
184
+ }
185
+ } else {
186
+ BackpressureAction::Accept
187
+ }
188
+ }
189
+
190
+ pub fn replay_from_checkpoint(&mut self, checkpoint: &Checkpoint) -> Result<usize, String> {
191
+ let mut replayed = 0;
192
+ let applied_set: std::collections::HashSet<_> = checkpoint.applied_changes.iter().cloned().collect();
193
+
194
+ for change in self.sync_state.pending_changes.drain(..) {
195
+ if !applied_set.contains(&change.idempotency_key)
196
+ && change.change_id.sequence <= checkpoint.high_water_mark
197
+ {
198
+ replayed += 1;
199
+ }
200
+ }
201
+
202
+ self.sync_state.last_applied_sequence = checkpoint.high_water_mark;
203
+ Ok(replayed)
204
+ }
205
+
206
+ pub fn get_checkpoint_count(&self) -> usize {
207
+ self.sync_state.checkpoints.len()
208
+ }
209
+
210
+ pub fn get_last_checkpoint(&self) -> Option<Checkpoint> {
211
+ self.sync_state.checkpoints.last().cloned()
212
+ }
213
+ }
214
+
215
+ #[cfg(test)]
216
+ mod tests {
217
+ use super::*;
218
+
219
+ #[test]
220
+ fn test_idempotent_application() {
221
+ let mut manager = DurableSyncManager::new("node-1".to_string());
222
+
223
+ let change = DurableChange::new(
224
+ "node-1".to_string(),
225
+ 1,
226
+ ChangeType::Insert,
227
+ "resource-1".to_string(),
228
+ "data".to_string(),
229
+ "idem-1".to_string(),
230
+ );
231
+
232
+ manager.ingest_change(change.clone()).unwrap();
233
+ let applied = manager.apply_changes_idempotent().unwrap();
234
+
235
+ assert_eq!(applied.len(), 1);
236
+ assert_eq!(manager.get_high_water_mark(), 1);
237
+
238
+ manager.ingest_change(change).unwrap();
239
+ let applied = manager.apply_changes_idempotent().unwrap();
240
+ assert_eq!(applied.len(), 0);
241
+ }
242
+
243
+ #[test]
244
+ fn test_checkpoint_recovery() {
245
+ let mut manager = DurableSyncManager::new("node-1".to_string());
246
+
247
+ let change1 = DurableChange::new(
248
+ "node-1".to_string(),
249
+ 1,
250
+ ChangeType::Insert,
251
+ "res-1".to_string(),
252
+ "data1".to_string(),
253
+ "idem-1".to_string(),
254
+ );
255
+
256
+ manager.ingest_change(change1).unwrap();
257
+ manager.apply_changes_idempotent().unwrap();
258
+ let cp = manager.create_checkpoint().unwrap();
259
+
260
+ assert_eq!(cp.high_water_mark, 1);
261
+ assert_eq!(manager.get_checkpoint_count(), 1);
262
+ }
263
+
264
+ #[test]
265
+ fn test_backpressure() {
266
+ let mut manager = DurableSyncManager::new("node-1".to_string());
267
+ manager.backpressure_threshold = 100;
268
+
269
+ for i in 0..150 {
270
+ let change = DurableChange::new(
271
+ "node-1".to_string(),
272
+ (i + 1) as u64,
273
+ ChangeType::Insert,
274
+ format!("res-{}", i),
275
+ "data".to_string(),
276
+ format!("idem-{}", i),
277
+ );
278
+ manager.ingest_change(change).ok();
279
+ }
280
+
281
+ match manager.evaluate_backpressure() {
282
+ BackpressureAction::Throttle { delay_ms } => assert!(delay_ms >= 0),
283
+ _ => panic!("Expected throttle"),
284
+ }
285
+ }
286
+
287
+ #[test]
288
+ fn test_ordering_guarantee() {
289
+ let mut manager = DurableSyncManager::new("node-1".to_string());
290
+
291
+ let change2 = DurableChange::new(
292
+ "node-1".to_string(),
293
+ 2,
294
+ ChangeType::Insert,
295
+ "res-2".to_string(),
296
+ "data2".to_string(),
297
+ "idem-2".to_string(),
298
+ );
299
+
300
+ let change1 = DurableChange::new(
301
+ "node-1".to_string(),
302
+ 1,
303
+ ChangeType::Insert,
304
+ "res-1".to_string(),
305
+ "data1".to_string(),
306
+ "idem-1".to_string(),
307
+ );
308
+
309
+ manager.ingest_change(change2).unwrap();
310
+ manager.ingest_change(change1).unwrap();
311
+
312
+ let applied = manager.apply_changes_idempotent().unwrap();
313
+ assert_eq!(applied[0].change_id.sequence, 1);
314
+ assert_eq!(applied[1].change_id.sequence, 2);
315
+ }
316
+ }