@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,364 @@
1
+ /// Durability & Authority Guarantees
2
+ ///
3
+ /// Provides formal durability and authority contracts:
4
+ /// - Restart recovery with consistency verification
5
+ /// - Crash-point consistency across all phases
6
+ /// - Authoritative-write semantics (no write conflicts)
7
+ /// - Reconciliation after divergence
8
+ /// - Write authority tracking and conflicts
9
+ /// - Recovery contracts
10
+
11
+ use serde::{Deserialize, Serialize};
12
+ use std::collections::HashMap;
13
+
14
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
15
+ pub enum WriteAuthority {
16
+ Primary,
17
+ Secondary,
18
+ Replica,
19
+ }
20
+
21
+ #[derive(Debug, Clone, Serialize, Deserialize)]
22
+ pub struct WriteAuthorityRecord {
23
+ pub resource_id: String,
24
+ pub authority: WriteAuthority,
25
+ pub authority_node: String,
26
+ pub version: u64,
27
+ pub timestamp: u64,
28
+ }
29
+
30
+ #[derive(Debug, Clone, Serialize, Deserialize)]
31
+ pub struct DurabilityContract {
32
+ pub level: String,
33
+ pub guarantees: Vec<String>,
34
+ pub violation_scenarios: Vec<String>,
35
+ }
36
+
37
+ #[derive(Debug, Clone, Serialize, Deserialize)]
38
+ pub struct CrashPoint {
39
+ pub phase: String,
40
+ pub description: String,
41
+ pub recovery_required: bool,
42
+ pub data_loss_possible: bool,
43
+ pub state_consistency: String,
44
+ }
45
+
46
+ #[derive(Debug, Clone, Serialize, Deserialize)]
47
+ pub struct RecoveryCheckpoint {
48
+ pub checkpoint_id: String,
49
+ pub timestamp: u64,
50
+ pub operations_count: u64,
51
+ pub data_hash: String,
52
+ pub state_valid: bool,
53
+ }
54
+
55
+ #[derive(Debug, Clone, Serialize, Deserialize)]
56
+ pub struct ReconciliationStrategy {
57
+ pub strategy_name: String,
58
+ pub detect_divergence: bool,
59
+ pub merge_strategy: String,
60
+ pub conflict_resolution: String,
61
+ pub final_authority: String,
62
+ }
63
+
64
+ #[derive(Debug, Clone, Serialize, Deserialize)]
65
+ pub struct AuthorityConflict {
66
+ pub resource_id: String,
67
+ pub conflicting_writes: Vec<(String, u64)>,
68
+ pub detected_at: u64,
69
+ pub resolution: String,
70
+ }
71
+
72
+ pub struct DurabilityGuarantees {
73
+ node_id: String,
74
+ write_authorities: HashMap<String, WriteAuthorityRecord>,
75
+ crash_points: Vec<CrashPoint>,
76
+ recovery_checkpoints: Vec<RecoveryCheckpoint>,
77
+ authority_conflicts: Vec<AuthorityConflict>,
78
+ checkpoint_counter: u64,
79
+ }
80
+
81
+ impl DurabilityGuarantees {
82
+ pub fn new(node_id: String) -> Self {
83
+ DurabilityGuarantees {
84
+ node_id,
85
+ write_authorities: HashMap::new(),
86
+ crash_points: vec![
87
+ CrashPoint {
88
+ phase: "execution".to_string(),
89
+ description: "During operation execution".to_string(),
90
+ recovery_required: true,
91
+ data_loss_possible: false,
92
+ state_consistency: "partial".to_string(),
93
+ },
94
+ CrashPoint {
95
+ phase: "journal".to_string(),
96
+ description: "During journal append".to_string(),
97
+ recovery_required: true,
98
+ data_loss_possible: false,
99
+ state_consistency: "consistent".to_string(),
100
+ },
101
+ CrashPoint {
102
+ phase: "replication".to_string(),
103
+ description: "During replication".to_string(),
104
+ recovery_required: false,
105
+ data_loss_possible: false,
106
+ state_consistency: "consistent".to_string(),
107
+ },
108
+ CrashPoint {
109
+ phase: "convergence".to_string(),
110
+ description: "During convergence".to_string(),
111
+ recovery_required: true,
112
+ data_loss_possible: false,
113
+ state_consistency: "eventual".to_string(),
114
+ },
115
+ ],
116
+ recovery_checkpoints: Vec::new(),
117
+ authority_conflicts: Vec::new(),
118
+ checkpoint_counter: 0,
119
+ }
120
+ }
121
+
122
+ pub fn establish_write_authority(
123
+ &mut self,
124
+ resource_id: String,
125
+ authority_node: String,
126
+ ) -> Result<(), String> {
127
+ if self.write_authorities.contains_key(&resource_id) {
128
+ return Err(format!("Write authority already established for {}", resource_id));
129
+ }
130
+
131
+ self.write_authorities.insert(
132
+ resource_id.clone(),
133
+ WriteAuthorityRecord {
134
+ resource_id,
135
+ authority: WriteAuthority::Primary,
136
+ authority_node,
137
+ version: 1,
138
+ timestamp: std::time::SystemTime::now()
139
+ .duration_since(std::time::UNIX_EPOCH)
140
+ .unwrap_or_default()
141
+ .as_secs(),
142
+ },
143
+ );
144
+
145
+ Ok(())
146
+ }
147
+
148
+ pub fn get_write_authority(&self, resource_id: &str) -> Option<WriteAuthorityRecord> {
149
+ self.write_authorities.get(resource_id).cloned()
150
+ }
151
+
152
+ pub fn verify_write_authority(
153
+ &self,
154
+ resource_id: &str,
155
+ writing_node: &str,
156
+ ) -> Result<bool, String> {
157
+ match self.write_authorities.get(resource_id) {
158
+ Some(record) => Ok(record.authority_node == writing_node),
159
+ None => Err(format!("No write authority for {}", resource_id)),
160
+ }
161
+ }
162
+
163
+ pub fn create_recovery_checkpoint(&mut self, operations_count: u64, data_hash: String) -> Result<RecoveryCheckpoint, String> {
164
+ self.checkpoint_counter += 1;
165
+
166
+ let checkpoint = RecoveryCheckpoint {
167
+ checkpoint_id: format!("cp-{}-{}", self.node_id, self.checkpoint_counter),
168
+ timestamp: std::time::SystemTime::now()
169
+ .duration_since(std::time::UNIX_EPOCH)
170
+ .unwrap_or_default()
171
+ .as_secs(),
172
+ operations_count,
173
+ data_hash,
174
+ state_valid: true,
175
+ };
176
+
177
+ self.recovery_checkpoints.push(checkpoint.clone());
178
+ Ok(checkpoint)
179
+ }
180
+
181
+ pub fn restart_recovery(
182
+ &mut self,
183
+ last_checkpoint_id: Option<&str>,
184
+ ) -> Result<RecoveryCheckpoint, String> {
185
+ let checkpoint = if let Some(id) = last_checkpoint_id {
186
+ self.recovery_checkpoints
187
+ .iter()
188
+ .find(|cp| cp.checkpoint_id == id)
189
+ .cloned()
190
+ .ok_or_else(|| format!("Checkpoint not found: {}", id))?
191
+ } else {
192
+ self.recovery_checkpoints
193
+ .last()
194
+ .cloned()
195
+ .ok_or_else(|| "No recovery checkpoint available".to_string())?
196
+ };
197
+
198
+ if !checkpoint.state_valid {
199
+ return Err("Last checkpoint is invalid".to_string());
200
+ }
201
+
202
+ Ok(checkpoint)
203
+ }
204
+
205
+ pub fn detect_crash_consistency(
206
+ &self,
207
+ crash_phase: &str,
208
+ ) -> Result<Vec<CrashPoint>, String> {
209
+ Ok(self
210
+ .crash_points
211
+ .iter()
212
+ .filter(|cp| cp.phase == crash_phase || crash_phase == "all")
213
+ .cloned()
214
+ .collect())
215
+ }
216
+
217
+ pub fn record_authority_conflict(
218
+ &mut self,
219
+ resource_id: String,
220
+ write_nodes: Vec<(String, u64)>,
221
+ resolution: String,
222
+ ) {
223
+ self.authority_conflicts.push(AuthorityConflict {
224
+ resource_id,
225
+ conflicting_writes: write_nodes,
226
+ detected_at: std::time::SystemTime::now()
227
+ .duration_since(std::time::UNIX_EPOCH)
228
+ .unwrap_or_default()
229
+ .as_secs(),
230
+ resolution,
231
+ });
232
+ }
233
+
234
+ pub fn get_durability_contract(&self) -> DurabilityContract {
235
+ DurabilityContract {
236
+ level: "strong".to_string(),
237
+ guarantees: vec![
238
+ "No data loss after successful write".to_string(),
239
+ "Atomic operation execution".to_string(),
240
+ "Consistent state after recovery".to_string(),
241
+ "Write authority prevents conflicts".to_string(),
242
+ "All crash points handle gracefully".to_string(),
243
+ ],
244
+ violation_scenarios: vec![
245
+ "Complete cluster loss before replication".to_string(),
246
+ "Byzantine failures (untrusted nodes)".to_string(),
247
+ "Corrupted storage medium".to_string(),
248
+ ],
249
+ }
250
+ }
251
+
252
+ pub fn reconcile_divergence(
253
+ &mut self,
254
+ local_version: u64,
255
+ remote_version: u64,
256
+ strategy: ReconciliationStrategy,
257
+ ) -> Result<u64, String> {
258
+ let final_version = match strategy.conflict_resolution.as_str() {
259
+ "last-write-wins" => local_version.max(remote_version),
260
+ "first-write-wins" => local_version.min(remote_version),
261
+ "authority-decides" => {
262
+ if strategy.final_authority == self.node_id {
263
+ local_version
264
+ } else {
265
+ remote_version
266
+ }
267
+ }
268
+ _ => local_version,
269
+ };
270
+
271
+ Ok(final_version)
272
+ }
273
+
274
+ pub fn verify_crash_recovery(&self, checkpoint: &RecoveryCheckpoint) -> Result<bool, String> {
275
+ if !checkpoint.state_valid {
276
+ return Ok(false);
277
+ }
278
+
279
+ Ok(true)
280
+ }
281
+
282
+ pub fn get_authority_conflicts(&self) -> Vec<AuthorityConflict> {
283
+ self.authority_conflicts.clone()
284
+ }
285
+
286
+ pub fn get_checkpoint_count(&self) -> usize {
287
+ self.recovery_checkpoints.len()
288
+ }
289
+ }
290
+
291
+ impl Default for DurabilityGuarantees {
292
+ fn default() -> Self {
293
+ Self::new("node".to_string())
294
+ }
295
+ }
296
+
297
+ #[cfg(test)]
298
+ mod tests {
299
+ use super::*;
300
+
301
+ #[test]
302
+ fn test_write_authority() {
303
+ let mut dg = DurabilityGuarantees::new("node-1".to_string());
304
+ dg.establish_write_authority("res-1".to_string(), "node-1".to_string())
305
+ .unwrap();
306
+
307
+ assert!(dg.verify_write_authority("res-1", "node-1").unwrap());
308
+ assert!(!dg.verify_write_authority("res-1", "node-2").unwrap());
309
+ }
310
+
311
+ #[test]
312
+ fn test_recovery_checkpoint() {
313
+ let mut dg = DurabilityGuarantees::new("node-1".to_string());
314
+ let cp = dg
315
+ .create_recovery_checkpoint(100, "hash123".to_string())
316
+ .unwrap();
317
+
318
+ assert_eq!(cp.operations_count, 100);
319
+ assert!(cp.state_valid);
320
+ assert_eq!(dg.get_checkpoint_count(), 1);
321
+ }
322
+
323
+ #[test]
324
+ fn test_restart_recovery() {
325
+ let mut dg = DurabilityGuarantees::new("node-1".to_string());
326
+ dg.create_recovery_checkpoint(100, "hash1".to_string()).unwrap();
327
+
328
+ let recovered = dg.restart_recovery(None).unwrap();
329
+ assert_eq!(recovered.operations_count, 100);
330
+ }
331
+
332
+ #[test]
333
+ fn test_crash_consistency() {
334
+ let dg = DurabilityGuarantees::new("node-1".to_string());
335
+ let journal_crashes = dg.detect_crash_consistency("journal").unwrap();
336
+
337
+ assert!(journal_crashes.len() > 0);
338
+ assert_eq!(journal_crashes[0].phase, "journal");
339
+ }
340
+
341
+ #[test]
342
+ fn test_durability_contract() {
343
+ let dg = DurabilityGuarantees::new("node-1".to_string());
344
+ let contract = dg.get_durability_contract();
345
+
346
+ assert_eq!(contract.level, "strong");
347
+ assert!(contract.guarantees.len() > 0);
348
+ }
349
+
350
+ #[test]
351
+ fn test_reconciliation() {
352
+ let mut dg = DurabilityGuarantees::new("node-1".to_string());
353
+ let strategy = ReconciliationStrategy {
354
+ strategy_name: "lww".to_string(),
355
+ detect_divergence: true,
356
+ merge_strategy: "timestamp".to_string(),
357
+ conflict_resolution: "last-write-wins".to_string(),
358
+ final_authority: "node-1".to_string(),
359
+ };
360
+
361
+ let result = dg.reconcile_divergence(100, 200, strategy).unwrap();
362
+ assert_eq!(result, 200);
363
+ }
364
+ }
@@ -0,0 +1,235 @@
1
+ /// Durable Deduplication Set
2
+ ///
3
+ /// Provides persistent storage for the dedup set (seen envelope IDs).
4
+ /// The dedup set prevents duplicate operations from being applied multiple times.
5
+ ///
6
+ /// Invariant: Every envelope ID persisted survives process restart.
7
+
8
+ use crate::distributed_transactions::EnvelopeId;
9
+ use std::collections::HashSet;
10
+ use std::fs::{File, OpenOptions};
11
+ use std::io::{BufRead, BufReader, Write};
12
+ use std::path::{Path, PathBuf};
13
+
14
+ /// Persistent dedup set (seen envelope IDs)
15
+ pub struct DeduplicationSet {
16
+ path: PathBuf,
17
+ file: Option<File>,
18
+ }
19
+
20
+ impl DeduplicationSet {
21
+ /// Create or open a persistent dedup set at the given path
22
+ pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, std::io::Error> {
23
+ let path = path.as_ref().to_path_buf();
24
+
25
+ // Create parent directory if needed
26
+ if let Some(parent) = path.parent() {
27
+ std::fs::create_dir_all(parent)?;
28
+ }
29
+
30
+ // Open dedup file in append mode
31
+ let file = OpenOptions::new()
32
+ .create(true)
33
+ .append(true)
34
+ .open(&path)?;
35
+
36
+ Ok(Self {
37
+ path,
38
+ file: Some(file),
39
+ })
40
+ }
41
+
42
+ /// Record an envelope ID as seen (persist immediately with fsync)
43
+ pub fn mark_seen(&mut self, envelope_id: &EnvelopeId) -> Result<(), std::io::Error> {
44
+ if let Some(ref mut file) = self.file {
45
+ // Serialize envelope ID to JSON line
46
+ let json = serde_json::to_string(envelope_id)
47
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
48
+
49
+ // Write ID 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
+ "Dedup set file not open",
60
+ ))
61
+ }
62
+ }
63
+
64
+ /// Load all seen envelope IDs from persistent storage
65
+ pub fn load_all(&self) -> Result<HashSet<EnvelopeId>, std::io::Error> {
66
+ if !self.path.exists() {
67
+ return Ok(HashSet::new());
68
+ }
69
+
70
+ let file = File::open(&self.path)?;
71
+ let reader = BufReader::new(file);
72
+ let mut seen = HashSet::new();
73
+
74
+ for line in reader.lines() {
75
+ let line = line?;
76
+ if line.trim().is_empty() {
77
+ continue;
78
+ }
79
+
80
+ match serde_json::from_str::<EnvelopeId>(&line) {
81
+ Ok(envelope_id) => {
82
+ seen.insert(envelope_id);
83
+ }
84
+ Err(e) => {
85
+ return Err(std::io::Error::new(
86
+ std::io::ErrorKind::InvalidData,
87
+ format!("Failed to deserialize envelope ID: {}", e),
88
+ ))
89
+ }
90
+ }
91
+ }
92
+
93
+ Ok(seen)
94
+ }
95
+
96
+ /// Get count of persisted envelope IDs
97
+ pub fn count(&self) -> Result<usize, std::io::Error> {
98
+ let seen = self.load_all()?;
99
+ Ok(seen.len())
100
+ }
101
+
102
+ /// Clear the dedup set (used for testing)
103
+ pub fn clear(&mut self) -> Result<(), std::io::Error> {
104
+ if let Some(file) = self.file.take() {
105
+ drop(file);
106
+ }
107
+
108
+ std::fs::remove_file(&self.path)?;
109
+
110
+ let file = OpenOptions::new()
111
+ .create(true)
112
+ .append(true)
113
+ .open(&self.path)?;
114
+
115
+ self.file = Some(file);
116
+ Ok(())
117
+ }
118
+ }
119
+
120
+ #[cfg(test)]
121
+ mod tests {
122
+ use super::*;
123
+ use crate::distributed_transactions::EnvelopeId;
124
+ use tempfile::TempDir;
125
+
126
+ fn create_test_id(node: &str, seq: u64) -> EnvelopeId {
127
+ EnvelopeId::new(node.to_string(), seq)
128
+ }
129
+
130
+ #[test]
131
+ fn test_dedup_set_mark_and_load() -> Result<(), Box<dyn std::error::Error>> {
132
+ let temp_dir = TempDir::new()?;
133
+ let dedup_path = temp_dir.path().join("dedup.set");
134
+
135
+ // Mark some IDs as seen
136
+ {
137
+ let mut dedup = DeduplicationSet::new(&dedup_path)?;
138
+ let id1 = create_test_id("node1", 1);
139
+ let id2 = create_test_id("node1", 2);
140
+
141
+ dedup.mark_seen(&id1)?;
142
+ dedup.mark_seen(&id2)?;
143
+ }
144
+
145
+ // Reload and verify
146
+ {
147
+ let dedup = DeduplicationSet::new(&dedup_path)?;
148
+ let seen = dedup.load_all()?;
149
+
150
+ assert_eq!(seen.len(), 2);
151
+ assert!(seen.contains(&create_test_id("node1", 1)));
152
+ assert!(seen.contains(&create_test_id("node1", 2)));
153
+ }
154
+
155
+ Ok(())
156
+ }
157
+
158
+ #[test]
159
+ fn test_dedup_set_persists_across_restart() -> Result<(), Box<dyn std::error::Error>> {
160
+ let temp_dir = TempDir::new()?;
161
+ let dedup_path = temp_dir.path().join("dedup.set");
162
+
163
+ // Create and populate
164
+ {
165
+ let mut dedup = DeduplicationSet::new(&dedup_path)?;
166
+ for seq in 1..=5 {
167
+ let id = create_test_id("nodeA", seq);
168
+ dedup.mark_seen(&id)?;
169
+ }
170
+ }
171
+
172
+ // Restart and verify all are still there
173
+ {
174
+ let dedup = DeduplicationSet::new(&dedup_path)?;
175
+ let seen = dedup.load_all()?;
176
+
177
+ assert_eq!(seen.len(), 5);
178
+ for seq in 1..=5 {
179
+ assert!(seen.contains(&create_test_id("nodeA", seq)));
180
+ }
181
+ }
182
+
183
+ Ok(())
184
+ }
185
+
186
+ #[test]
187
+ fn test_dedup_set_empty_recovery() -> Result<(), Box<dyn std::error::Error>> {
188
+ let temp_dir = TempDir::new()?;
189
+ let dedup_path = temp_dir.path().join("nonexistent.dedup");
190
+
191
+ let dedup = DeduplicationSet::new(&dedup_path)?;
192
+ let seen = dedup.load_all()?;
193
+
194
+ assert_eq!(seen.len(), 0);
195
+
196
+ Ok(())
197
+ }
198
+
199
+ #[test]
200
+ fn test_dedup_set_fsync_semantics() -> Result<(), Box<dyn std::error::Error>> {
201
+ let temp_dir = TempDir::new()?;
202
+ let dedup_path = temp_dir.path().join("dedup.set");
203
+
204
+ let mut dedup = DeduplicationSet::new(&dedup_path)?;
205
+
206
+ // Mark many IDs to verify fsync durability
207
+ for seq in 1..=50 {
208
+ let id = create_test_id("node", seq);
209
+ dedup.mark_seen(&id)?;
210
+ }
211
+
212
+ // Verify all 50 persisted
213
+ let loaded = dedup.load_all()?;
214
+ assert_eq!(loaded.len(), 50);
215
+
216
+ Ok(())
217
+ }
218
+
219
+ #[test]
220
+ fn test_dedup_set_count() -> Result<(), Box<dyn std::error::Error>> {
221
+ let temp_dir = TempDir::new()?;
222
+ let dedup_path = temp_dir.path().join("dedup.set");
223
+
224
+ let mut dedup = DeduplicationSet::new(&dedup_path)?;
225
+
226
+ for seq in 1..=3 {
227
+ let id = create_test_id("node", seq);
228
+ dedup.mark_seen(&id)?;
229
+ }
230
+
231
+ assert_eq!(dedup.count()?, 3);
232
+
233
+ Ok(())
234
+ }
235
+ }