@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
+ /// Production submission layer for FeltDB
2
+ ///
3
+ /// Integrates the admission contract with actual operation submission,
4
+ /// ensuring no silent failures and explicit rejection semantics.
5
+
6
+ use crate::admission::{ProductionRetryPolicy, RejectionReason, SubmissionOutcome};
7
+ use crate::transaction_api::{TransactionManager, TxError};
8
+ use crate::transactions::{ConsistencyContract, Operation};
9
+ use sha2::{Digest, Sha256};
10
+ use std::sync::{Arc, Mutex};
11
+ use std::time::{SystemTime, UNIX_EPOCH};
12
+
13
+ /// Metadata about a submission's retry history
14
+ #[derive(Clone, Debug)]
15
+ pub struct SubmissionMetrics {
16
+ pub outcome: SubmissionOutcome,
17
+ pub first_attempt_latency_us: u64,
18
+ pub total_latency_us: u64,
19
+ pub retry_attempts: usize,
20
+ pub eventually_accepted: bool,
21
+ }
22
+
23
+ /// Production submission manager
24
+ /// Handles operation admission with explicit contract guarantees
25
+ pub struct SubmissionManager {
26
+ transaction_manager: Arc<Mutex<TransactionManager>>,
27
+ retry_policy: ProductionRetryPolicy,
28
+ deduplication_cache: Arc<Mutex<std::collections::HashMap<String, u64>>>,
29
+ }
30
+
31
+ impl SubmissionManager {
32
+ /// Create a new submission manager with standard production retry policy
33
+ pub fn new(transaction_manager: Arc<Mutex<TransactionManager>>) -> Self {
34
+ Self {
35
+ transaction_manager,
36
+ retry_policy: ProductionRetryPolicy::standard(),
37
+ deduplication_cache: Arc::new(Mutex::new(std::collections::HashMap::new())),
38
+ }
39
+ }
40
+
41
+ /// Create with custom retry policy
42
+ pub fn with_policy(
43
+ transaction_manager: Arc<Mutex<TransactionManager>>,
44
+ retry_policy: ProductionRetryPolicy,
45
+ ) -> Self {
46
+ Self {
47
+ transaction_manager,
48
+ retry_policy,
49
+ deduplication_cache: Arc::new(Mutex::new(std::collections::HashMap::new())),
50
+ }
51
+ }
52
+
53
+ /// Submit a single operation for processing with detailed metrics
54
+ /// Returns SubmissionMetrics including outcome, latencies, and retry count
55
+ pub fn submit_with_metrics(&self, operation: Operation) -> SubmissionMetrics {
56
+ let content_hash = self.hash_operation(&operation);
57
+ let start_time = std::time::Instant::now();
58
+
59
+ // Check deduplication cache
60
+ {
61
+ let cache = self.deduplication_cache.lock().unwrap();
62
+ if let Some(&original_id) = cache.get(&content_hash) {
63
+ let latency = start_time.elapsed().as_micros() as u64;
64
+ return SubmissionMetrics {
65
+ outcome: SubmissionOutcome::Rejected(RejectionReason::DuplicateDetected {
66
+ original_operation_id: original_id,
67
+ }),
68
+ first_attempt_latency_us: latency,
69
+ total_latency_us: latency,
70
+ retry_attempts: 0,
71
+ eventually_accepted: false,
72
+ };
73
+ }
74
+ }
75
+
76
+ // Attempt submission with bounded retries
77
+ let mut first_attempt_latency_us = 0u64;
78
+ let mut retry_attempts = 0usize;
79
+
80
+ for attempt in 0..self.retry_policy.max_attempts {
81
+ let attempt_start = std::time::Instant::now();
82
+ match self.try_submit_once(&operation) {
83
+ Ok(operation_id) => {
84
+ let attempt_latency = attempt_start.elapsed().as_micros() as u64;
85
+ if attempt == 0 {
86
+ first_attempt_latency_us = attempt_latency;
87
+ }
88
+ // Cache this operation
89
+ {
90
+ let mut cache = self.deduplication_cache.lock().unwrap();
91
+ cache.insert(content_hash, operation_id);
92
+ }
93
+ return SubmissionMetrics {
94
+ outcome: SubmissionOutcome::Accepted { operation_id },
95
+ first_attempt_latency_us,
96
+ total_latency_us: start_time.elapsed().as_micros() as u64,
97
+ retry_attempts,
98
+ eventually_accepted: true,
99
+ };
100
+ }
101
+ Err(rejection) => {
102
+ let attempt_latency = attempt_start.elapsed().as_micros() as u64;
103
+ if attempt == 0 {
104
+ first_attempt_latency_us = attempt_latency;
105
+ }
106
+
107
+ // Determine if we should retry or reject
108
+ match &rejection {
109
+ RejectionReason::AdmissionBackpressure {
110
+ retry_after_us: _,
111
+ suggested_backoff_ms: _,
112
+ } => {
113
+ // Apply backoff if not at final attempt
114
+ if attempt < self.retry_policy.max_attempts - 1 {
115
+ let backoff_us = self.retry_policy.backoff_for_attempt(attempt + 1);
116
+ spin_sleep_us(backoff_us);
117
+ retry_attempts += 1;
118
+ continue;
119
+ } else {
120
+ // Final attempt exhausted
121
+ return SubmissionMetrics {
122
+ outcome: SubmissionOutcome::Rejected(rejection),
123
+ first_attempt_latency_us,
124
+ total_latency_us: start_time.elapsed().as_micros() as u64,
125
+ retry_attempts,
126
+ eventually_accepted: false,
127
+ };
128
+ }
129
+ }
130
+ RejectionReason::TransientLockContention => {
131
+ // Immediate retry likely to succeed; retry without delay on first attempt
132
+ if attempt < self.retry_policy.max_attempts - 1 {
133
+ retry_attempts += 1;
134
+ continue;
135
+ } else {
136
+ return SubmissionMetrics {
137
+ outcome: SubmissionOutcome::Rejected(rejection),
138
+ first_attempt_latency_us,
139
+ total_latency_us: start_time.elapsed().as_micros() as u64,
140
+ retry_attempts,
141
+ eventually_accepted: false,
142
+ };
143
+ }
144
+ }
145
+ RejectionReason::QueueFull | RejectionReason::DuplicateDetected { .. } => {
146
+ // No point retrying these
147
+ return SubmissionMetrics {
148
+ outcome: SubmissionOutcome::Rejected(rejection),
149
+ first_attempt_latency_us,
150
+ total_latency_us: start_time.elapsed().as_micros() as u64,
151
+ retry_attempts,
152
+ eventually_accepted: false,
153
+ };
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+ // Should not reach here, but if we do, reject due to exhausted attempts
161
+ SubmissionMetrics {
162
+ outcome: SubmissionOutcome::Rejected(RejectionReason::AdmissionBackpressure {
163
+ retry_after_us: 1000,
164
+ suggested_backoff_ms: 10,
165
+ }),
166
+ first_attempt_latency_us,
167
+ total_latency_us: start_time.elapsed().as_micros() as u64,
168
+ retry_attempts,
169
+ eventually_accepted: false,
170
+ }
171
+ }
172
+
173
+ /// Submit a single operation for processing
174
+ /// Returns explicit SubmissionOutcome: accepted with operation_id, or rejected with reason
175
+ pub fn submit(&self, operation: Operation) -> SubmissionOutcome {
176
+ let content_hash = self.hash_operation(&operation);
177
+
178
+ // Check deduplication cache
179
+ {
180
+ let cache = self.deduplication_cache.lock().unwrap();
181
+ if let Some(&original_id) = cache.get(&content_hash) {
182
+ return SubmissionOutcome::Rejected(RejectionReason::DuplicateDetected {
183
+ original_operation_id: original_id,
184
+ });
185
+ }
186
+ }
187
+
188
+ // Attempt submission with bounded retries
189
+ for attempt in 0..self.retry_policy.max_attempts {
190
+ match self.try_submit_once(&operation) {
191
+ Ok(operation_id) => {
192
+ // Cache this operation
193
+ {
194
+ let mut cache = self.deduplication_cache.lock().unwrap();
195
+ cache.insert(content_hash, operation_id);
196
+ }
197
+ return SubmissionOutcome::Accepted { operation_id };
198
+ }
199
+ Err(rejection) => {
200
+ // Determine if we should retry or reject
201
+ match &rejection {
202
+ RejectionReason::AdmissionBackpressure {
203
+ retry_after_us: _,
204
+ suggested_backoff_ms: _,
205
+ } => {
206
+ // Apply backoff if not at final attempt
207
+ if attempt < self.retry_policy.max_attempts - 1 {
208
+ let backoff_us = self.retry_policy.backoff_for_attempt(attempt + 1);
209
+ spin_sleep_us(backoff_us);
210
+ continue;
211
+ } else {
212
+ // Final attempt exhausted
213
+ return SubmissionOutcome::Rejected(rejection);
214
+ }
215
+ }
216
+ RejectionReason::TransientLockContention => {
217
+ // Immediate retry likely to succeed; retry without delay on first attempt
218
+ if attempt < self.retry_policy.max_attempts - 1 {
219
+ continue;
220
+ } else {
221
+ return SubmissionOutcome::Rejected(rejection);
222
+ }
223
+ }
224
+ RejectionReason::QueueFull | RejectionReason::DuplicateDetected { .. } => {
225
+ // No point retrying these
226
+ return SubmissionOutcome::Rejected(rejection);
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+
233
+ // Should not reach here, but if we do, reject due to exhausted attempts
234
+ SubmissionOutcome::Rejected(RejectionReason::AdmissionBackpressure {
235
+ retry_after_us: 1000,
236
+ suggested_backoff_ms: 10,
237
+ })
238
+ }
239
+
240
+ /// Try to submit once without retry logic
241
+ /// Used internally; only call once per submission flow
242
+ fn try_submit_once(&self, operation: &Operation) -> Result<u64, RejectionReason> {
243
+ let mut manager = self.transaction_manager.lock().unwrap();
244
+
245
+ // Extract operation ID - this is the unique identifier
246
+ let operation_id = u64::from_str_radix(
247
+ &operation.operation_id.sequence.to_string(),
248
+ 10,
249
+ )
250
+ .unwrap_or(0);
251
+
252
+ // Attempt to execute transaction
253
+ match manager.execute_and_commit(
254
+ format!("txn_{}", operation_id),
255
+ vec![operation.clone()],
256
+ ConsistencyContract::local(),
257
+ ) {
258
+ Ok(_) => Ok(operation_id),
259
+ Err(err) => {
260
+ // Map TxError to RejectionReason
261
+ match err {
262
+ TxError::OperationDeduplication(_) => {
263
+ Err(RejectionReason::DuplicateDetected {
264
+ original_operation_id: operation_id,
265
+ })
266
+ }
267
+ TxError::PreconditionFailed(_) | TxError::ConflictDetected(_) => {
268
+ // These could indicate lock contention
269
+ Err(RejectionReason::TransientLockContention)
270
+ }
271
+ TxError::InvalidState(_) => Err(RejectionReason::AdmissionBackpressure {
272
+ retry_after_us: 100,
273
+ suggested_backoff_ms: 1,
274
+ }),
275
+ TxError::PersistenceFailure(_) => Err(RejectionReason::AdmissionBackpressure {
276
+ retry_after_us: 500,
277
+ suggested_backoff_ms: 5,
278
+ }),
279
+ }
280
+ }
281
+ }
282
+ }
283
+
284
+ /// Compute content hash of an operation for deduplication
285
+ fn hash_operation(&self, operation: &Operation) -> String {
286
+ let mut hasher = Sha256::new();
287
+ // Hash the operation's complete identifying fields
288
+ hasher.update(operation.operation_id.node_id.as_bytes());
289
+ hasher.update(operation.operation_id.sequence.to_le_bytes());
290
+ hasher.update(operation.command.op_type.as_bytes());
291
+ hasher.update(operation.command.collection.as_bytes());
292
+ hasher.update(operation.command.record_id.as_bytes());
293
+ // Include field values in the hash for complete uniqueness
294
+ for (key, val) in &operation.command.fields {
295
+ hasher.update(key.as_bytes());
296
+ hasher.update(val.to_string().as_bytes());
297
+ }
298
+ let result = hasher.finalize();
299
+ format!("{:x}", result)
300
+ }
301
+ }
302
+
303
+ /// Spin-based sleep in microseconds (for tight retry loops)
304
+ fn spin_sleep_us(us: u64) {
305
+ if us == 0 {
306
+ return;
307
+ }
308
+ let start = std::time::Instant::now();
309
+ let duration = std::time::Duration::from_micros(us);
310
+ while start.elapsed() < duration {
311
+ // Spin
312
+ std::hint::spin_loop();
313
+ }
314
+ }
315
+
316
+ #[cfg(test)]
317
+ mod tests {
318
+ use super::*;
319
+ use crate::convergence::VectorClock;
320
+ use crate::transactions::{Operation, OperationCommand, OperationId, StateVersion};
321
+ use std::collections::HashMap;
322
+
323
+ fn create_test_manager() -> Arc<Mutex<TransactionManager>> {
324
+ let mut vc = VectorClock::new();
325
+ vc.increment("node1");
326
+ let version = StateVersion::new(vc, "hash0".to_string());
327
+ Arc::new(Mutex::new(TransactionManager::new(
328
+ "node1".to_string(),
329
+ version,
330
+ )))
331
+ }
332
+
333
+ fn create_test_operation(seq: u64) -> Operation {
334
+ Operation::new(
335
+ OperationId::new("node1".to_string(), seq),
336
+ StateVersion::new(VectorClock::new(), "hash0".to_string()),
337
+ "Update".to_string(),
338
+ OperationCommand {
339
+ op_type: "UpdateField".to_string(),
340
+ collection: "test".to_string(),
341
+ record_id: "rec1".to_string(),
342
+ fields: {
343
+ let mut m = HashMap::new();
344
+ m.insert("field".to_string(), serde_json::json!("value"));
345
+ m
346
+ },
347
+ },
348
+ "user".to_string(),
349
+ )
350
+ }
351
+
352
+ #[test]
353
+ fn submission_contract_accepted() {
354
+ let manager = create_test_manager();
355
+ let submitter = SubmissionManager::new(manager);
356
+
357
+ let op = create_test_operation(1);
358
+ let outcome = submitter.submit(op);
359
+
360
+ assert!(outcome.is_accepted());
361
+ match outcome {
362
+ SubmissionOutcome::Accepted { operation_id } => {
363
+ assert_eq!(operation_id, 1);
364
+ }
365
+ _ => panic!("Expected Accepted outcome"),
366
+ }
367
+ }
368
+
369
+ #[test]
370
+ fn submission_contract_duplicate_detection() {
371
+ let manager = create_test_manager();
372
+ let submitter = SubmissionManager::new(manager);
373
+
374
+ let op = create_test_operation(1);
375
+ let outcome1 = submitter.submit(op.clone());
376
+ assert!(outcome1.is_accepted());
377
+
378
+ // Submit same operation again
379
+ let outcome2 = submitter.submit(op);
380
+ assert!(outcome2.is_rejected());
381
+
382
+ match outcome2 {
383
+ SubmissionOutcome::Rejected(RejectionReason::DuplicateDetected {
384
+ original_operation_id,
385
+ }) => {
386
+ assert_eq!(original_operation_id, 1);
387
+ }
388
+ _ => panic!("Expected DuplicateDetected rejection"),
389
+ }
390
+ }
391
+
392
+ #[test]
393
+ fn submission_contract_never_silently_drops() {
394
+ // This test verifies that submit() always returns an explicit outcome
395
+ let manager = create_test_manager();
396
+ let submitter = SubmissionManager::new(manager);
397
+
398
+ for i in 1..=10 {
399
+ let op = create_test_operation(i);
400
+ let outcome = submitter.submit(op);
401
+ // Every submission gets an explicit outcome
402
+ match outcome {
403
+ SubmissionOutcome::Accepted { .. } => {
404
+ assert!(true, "Operation {} accepted", i);
405
+ }
406
+ SubmissionOutcome::Rejected(_) => {
407
+ assert!(true, "Operation {} explicitly rejected", i);
408
+ }
409
+ }
410
+ }
411
+ }
412
+
413
+ #[test]
414
+ fn production_retry_policy_exhaustion() {
415
+ let manager = create_test_manager();
416
+ let retry_policy = ProductionRetryPolicy::standard();
417
+ let submitter = SubmissionManager::with_policy(manager, retry_policy);
418
+
419
+ let op = create_test_operation(1);
420
+ let outcome = submitter.submit(op);
421
+
422
+ // Should succeed normally
423
+ assert!(outcome.is_accepted());
424
+ }
425
+
426
+ #[test]
427
+ fn deduplication_cache_prevents_duplicates() {
428
+ let manager = create_test_manager();
429
+ let submitter = SubmissionManager::new(manager);
430
+
431
+ // Submit three different operations
432
+ for i in 1..=3 {
433
+ let op = create_test_operation(i);
434
+ let outcome = submitter.submit(op);
435
+ assert!(outcome.is_accepted());
436
+ }
437
+
438
+ // Duplicate should be detected
439
+ let op_dup = create_test_operation(2);
440
+ let outcome = submitter.submit(op_dup);
441
+ assert!(outcome.is_rejected());
442
+ }
443
+
444
+ #[test]
445
+ fn rejection_reason_display() {
446
+ let backpressure = RejectionReason::AdmissionBackpressure {
447
+ retry_after_us: 100,
448
+ suggested_backoff_ms: 1,
449
+ };
450
+ let msg = format!("{}", backpressure);
451
+ assert!(msg.contains("backpressure"));
452
+ assert!(msg.contains("100"));
453
+
454
+ let duplicate = RejectionReason::DuplicateDetected {
455
+ original_operation_id: 42,
456
+ };
457
+ let msg = format!("{}", duplicate);
458
+ assert!(msg.contains("duplicate"));
459
+ assert!(msg.contains("42"));
460
+ }
461
+
462
+ #[test]
463
+ fn acceptance_guarantees_operation_id() {
464
+ let manager = create_test_manager();
465
+ let submitter = SubmissionManager::new(manager);
466
+
467
+ let op = create_test_operation(123);
468
+ let outcome = submitter.submit(op);
469
+
470
+ match outcome {
471
+ SubmissionOutcome::Accepted { operation_id } => {
472
+ assert_eq!(operation_id, 123);
473
+ }
474
+ SubmissionOutcome::Rejected(_) => panic!("Expected acceptance"),
475
+ }
476
+ }
477
+ }