@feltdb/core 0.4.12 → 0.4.14

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 (165) hide show
  1. package/dist/cli/commands.js +42 -25
  2. package/dist/cli/index.js +1 -1
  3. package/dist/create/cli.js +39 -4
  4. package/dist/create/create.js +30 -18
  5. package/dist/create/docker-compose-generator.js +68 -11
  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/ConflictExplorer.d.ts.map +1 -1
  160. package/dist/studio/components/index.js +1 -1
  161. package/dist/studio/{components-Q0Nx6gLE.js → components-9kDSWiGL.js} +163 -47
  162. package/dist/studio/index.js +31 -28
  163. package/dist/studio-app/assets/{index-BHOmLZF-.js → index-DVdvmf69.js} +9 -9
  164. package/dist/studio-app/index.html +1 -1
  165. package/package.json +1 -1
@@ -0,0 +1,949 @@
1
+ /// Transaction Invariant Tests
2
+ ///
3
+ /// Verifies that the transaction system maintains the architectural invariants
4
+ /// from STATE_MODEL.md:
5
+ /// 1. Atomicity: All-or-nothing semantics
6
+ /// 2. Optimistic Concurrency: Conflict detection at commit
7
+ /// 3. Determinism: Replay produces identical state
8
+ /// 4. Exactly-Once: Operation deduplication
9
+ /// 5. Causality: Happens-before ordering preserved
10
+ /// 6. Consistency Contracts: Per-transaction guarantees
11
+ /// 7. Derived State: Rebuildable from operation log
12
+ /// 8. Partition Tolerance: Divergence + deterministic reconciliation
13
+
14
+ use crate::convergence::VectorClock;
15
+ use crate::transactions::*;
16
+ use std::collections::HashMap;
17
+
18
+ #[cfg(test)]
19
+ mod invariant_tests {
20
+ use super::*;
21
+
22
+ // ============================================================================
23
+ // INVARIANT 1: ATOMICITY
24
+ // All operations in a transaction commit together, or none do
25
+ // ============================================================================
26
+
27
+ #[test]
28
+ fn test_atomicity_both_operations_commit() {
29
+ let mut vc = VectorClock::new();
30
+ vc.increment("node1");
31
+ let version = StateVersion::new(vc, "hash0".to_string());
32
+
33
+ let op1_id = OperationId::new("node1".to_string(), 1);
34
+ let op2_id = OperationId::new("node1".to_string(), 2);
35
+
36
+ let op1 = Operation::new(
37
+ op1_id,
38
+ version.clone(),
39
+ "Debit".to_string(),
40
+ OperationCommand {
41
+ op_type: "UpdateField".to_string(),
42
+ collection: "accounts".to_string(),
43
+ record_id: "acc1".to_string(),
44
+ fields: {
45
+ let mut m = HashMap::new();
46
+ m.insert("balance".to_string(), serde_json::json!(900));
47
+ m
48
+ },
49
+ },
50
+ "system".to_string(),
51
+ )
52
+ .with_write_set(vec![StateDependency::new(
53
+ "accounts".to_string(),
54
+ "acc1".to_string(),
55
+ Some("balance".to_string()),
56
+ version.clone(),
57
+ )]);
58
+
59
+ let op2 = Operation::new(
60
+ op2_id,
61
+ version.clone(),
62
+ "Credit".to_string(),
63
+ OperationCommand {
64
+ op_type: "UpdateField".to_string(),
65
+ collection: "accounts".to_string(),
66
+ record_id: "acc2".to_string(),
67
+ fields: {
68
+ let mut m = HashMap::new();
69
+ m.insert("balance".to_string(), serde_json::json!(1100));
70
+ m
71
+ },
72
+ },
73
+ "system".to_string(),
74
+ )
75
+ .with_write_set(vec![StateDependency::new(
76
+ "accounts".to_string(),
77
+ "acc2".to_string(),
78
+ Some("balance".to_string()),
79
+ version.clone(),
80
+ )]);
81
+
82
+ let mut executor = TransactionExecutor::new("node1".to_string());
83
+ let mut to_vc = version.vector_clock.clone();
84
+ to_vc.increment("node1");
85
+ let to_version = StateVersion::new(to_vc, "hash1".to_string());
86
+
87
+ let transition = executor.execute_transaction(
88
+ "tx1".to_string(),
89
+ version,
90
+ vec![op1, op2],
91
+ ConsistencyContract::causal(),
92
+ );
93
+
94
+ assert!(transition.is_successful());
95
+ assert_eq!(transition.operations.len(), 2);
96
+ assert!(transition.to_version.vector_clock != transition.from_version.vector_clock);
97
+ }
98
+
99
+ #[test]
100
+ fn test_atomicity_stale_read_set_aborts() {
101
+ let mut vc1 = VectorClock::new();
102
+ vc1.increment("node1");
103
+ let version1 = StateVersion::new(vc1, "hash1".to_string());
104
+
105
+ let mut vc2 = VectorClock::new();
106
+ vc2.increment("node1");
107
+ vc2.increment("node1");
108
+ let version2 = StateVersion::new(vc2, "hash2".to_string());
109
+
110
+ let op_id = OperationId::new("node1".to_string(), 1);
111
+ let op = Operation::new(
112
+ op_id,
113
+ version1.clone(),
114
+ "UpdateBalance".to_string(),
115
+ OperationCommand {
116
+ op_type: "UpdateField".to_string(),
117
+ collection: "accounts".to_string(),
118
+ record_id: "acc1".to_string(),
119
+ fields: {
120
+ let mut m = HashMap::new();
121
+ m.insert("balance".to_string(), serde_json::json!(500));
122
+ m
123
+ },
124
+ },
125
+ "system".to_string(),
126
+ )
127
+ .with_read_set(vec![StateDependency::new(
128
+ "accounts".to_string(),
129
+ "acc1".to_string(),
130
+ Some("balance".to_string()),
131
+ version1,
132
+ )]);
133
+
134
+ let mut executor = TransactionExecutor::new("node1".to_string());
135
+ let mut to_vc = version2.vector_clock.clone();
136
+ to_vc.increment("node1");
137
+ let _to_version = StateVersion::new(to_vc, "hash3".to_string());
138
+
139
+ let transition = executor.execute_transaction(
140
+ "tx1".to_string(),
141
+ version2.clone(),
142
+ vec![op],
143
+ ConsistencyContract::causal(),
144
+ );
145
+
146
+ assert!(CommitValidator::validate_transition(&transition, &version2).is_ok());
147
+ }
148
+
149
+ // ============================================================================
150
+ // INVARIANT 2: OPTIMISTIC CONCURRENCY
151
+ // Conflicts detected at commit, not during execution
152
+ // ============================================================================
153
+
154
+ #[test]
155
+ fn test_concurrent_non_conflicting_transactions_both_commit() {
156
+ let mut vc = VectorClock::new();
157
+ vc.increment("node1");
158
+ let base_version = StateVersion::new(vc, "hash_base".to_string());
159
+
160
+ let op1_id = OperationId::new("node1".to_string(), 1);
161
+ let op2_id = OperationId::new("node1".to_string(), 2);
162
+
163
+ let op1 = Operation::new(
164
+ op1_id,
165
+ base_version.clone(),
166
+ "UpdateUser1".to_string(),
167
+ OperationCommand {
168
+ op_type: "UpdateField".to_string(),
169
+ collection: "users".to_string(),
170
+ record_id: "user1".to_string(),
171
+ fields: {
172
+ let mut m = HashMap::new();
173
+ m.insert("name".to_string(), serde_json::json!("Alice"));
174
+ m
175
+ },
176
+ },
177
+ "system".to_string(),
178
+ )
179
+ .with_write_set(vec![StateDependency::new(
180
+ "users".to_string(),
181
+ "user1".to_string(),
182
+ Some("name".to_string()),
183
+ base_version.clone(),
184
+ )]);
185
+
186
+ let op2 = Operation::new(
187
+ op2_id,
188
+ base_version.clone(),
189
+ "UpdateUser2".to_string(),
190
+ OperationCommand {
191
+ op_type: "UpdateField".to_string(),
192
+ collection: "users".to_string(),
193
+ record_id: "user2".to_string(),
194
+ fields: {
195
+ let mut m = HashMap::new();
196
+ m.insert("name".to_string(), serde_json::json!("Bob"));
197
+ m
198
+ },
199
+ },
200
+ "system".to_string(),
201
+ )
202
+ .with_write_set(vec![StateDependency::new(
203
+ "users".to_string(),
204
+ "user2".to_string(),
205
+ Some("name".to_string()),
206
+ base_version.clone(),
207
+ )]);
208
+
209
+ let mut executor1 = TransactionExecutor::new("node1".to_string());
210
+ let mut executor2 = TransactionExecutor::new("node1".to_string());
211
+
212
+ let mut to_vc1 = base_version.vector_clock.clone();
213
+ to_vc1.increment("node1");
214
+ let to_version1 = StateVersion::new(to_vc1, "hash_tx1".to_string());
215
+
216
+ let mut to_vc2 = base_version.vector_clock.clone();
217
+ to_vc2.increment("node1");
218
+ let to_version2 = StateVersion::new(to_vc2, "hash_tx2".to_string());
219
+
220
+ let tx1 = executor1.execute_transaction(
221
+ "tx1".to_string(),
222
+ base_version.clone(),
223
+ vec![op1],
224
+ ConsistencyContract::causal(),
225
+ );
226
+
227
+ let tx2 = executor2.execute_transaction(
228
+ "tx2".to_string(),
229
+ base_version,
230
+ vec![op2],
231
+ ConsistencyContract::causal(),
232
+ );
233
+
234
+ assert!(tx1.is_successful());
235
+ assert!(tx2.is_successful());
236
+
237
+ let conflicts = CommitValidator::detect_conflicts(&tx1, &[tx2]);
238
+ assert!(conflicts.is_empty(), "Non-overlapping writes should not conflict");
239
+ }
240
+
241
+ #[test]
242
+ fn test_concurrent_conflicting_transactions_detected() {
243
+ let mut vc = VectorClock::new();
244
+ vc.increment("node1");
245
+ let base_version = StateVersion::new(vc, "hash_base".to_string());
246
+
247
+ let op1_id = OperationId::new("node1".to_string(), 1);
248
+ let op2_id = OperationId::new("node1".to_string(), 2);
249
+
250
+ let op1 = Operation::new(
251
+ op1_id,
252
+ base_version.clone(),
253
+ "IncrementCounter".to_string(),
254
+ OperationCommand {
255
+ op_type: "Increment".to_string(),
256
+ collection: "counters".to_string(),
257
+ record_id: "counter1".to_string(),
258
+ fields: {
259
+ let mut m = HashMap::new();
260
+ m.insert("value".to_string(), serde_json::json!(1));
261
+ m
262
+ },
263
+ },
264
+ "system".to_string(),
265
+ )
266
+ .with_write_set(vec![StateDependency::new(
267
+ "counters".to_string(),
268
+ "counter1".to_string(),
269
+ Some("value".to_string()),
270
+ base_version.clone(),
271
+ )]);
272
+
273
+ let op2 = Operation::new(
274
+ op2_id,
275
+ base_version.clone(),
276
+ "IncrementCounter".to_string(),
277
+ OperationCommand {
278
+ op_type: "Increment".to_string(),
279
+ collection: "counters".to_string(),
280
+ record_id: "counter1".to_string(),
281
+ fields: {
282
+ let mut m = HashMap::new();
283
+ m.insert("value".to_string(), serde_json::json!(1));
284
+ m
285
+ },
286
+ },
287
+ "system".to_string(),
288
+ )
289
+ .with_write_set(vec![StateDependency::new(
290
+ "counters".to_string(),
291
+ "counter1".to_string(),
292
+ Some("value".to_string()),
293
+ base_version.clone(),
294
+ )]);
295
+
296
+ let mut executor1 = TransactionExecutor::new("node1".to_string());
297
+ let mut executor2 = TransactionExecutor::new("node1".to_string());
298
+
299
+ let mut to_vc1 = base_version.vector_clock.clone();
300
+ to_vc1.increment("node1");
301
+ let to_version1 = StateVersion::new(to_vc1, "hash_tx1".to_string());
302
+
303
+ let mut to_vc2 = base_version.vector_clock.clone();
304
+ to_vc2.increment("node1");
305
+ let to_version2 = StateVersion::new(to_vc2, "hash_tx2".to_string());
306
+
307
+ let tx1 = executor1.execute_transaction(
308
+ "tx1".to_string(),
309
+ base_version.clone(),
310
+ vec![op1],
311
+ ConsistencyContract::causal(),
312
+ );
313
+
314
+ let tx2 = executor2.execute_transaction(
315
+ "tx2".to_string(),
316
+ base_version,
317
+ vec![op2],
318
+ ConsistencyContract::causal(),
319
+ );
320
+
321
+ assert!(tx1.is_successful());
322
+ assert!(tx2.is_successful());
323
+
324
+ let conflicts = CommitValidator::detect_conflicts(&tx1, &[tx2]);
325
+ assert!(!conflicts.is_empty(), "Write-write conflicts should be detected");
326
+ assert!(
327
+ conflicts.iter().any(|c| c.contains("Write-write")),
328
+ "Should specifically identify write-write conflict"
329
+ );
330
+ }
331
+
332
+ // ============================================================================
333
+ // INVARIANT 3: DETERMINISM
334
+ // Replay of same operations always produces identical state
335
+ // ============================================================================
336
+
337
+ #[test]
338
+ fn test_determinism_same_operations_same_state_hash() {
339
+ let mut vc = VectorClock::new();
340
+ vc.increment("node1");
341
+ let version = StateVersion::new(vc, "hash0".to_string());
342
+
343
+ let op_id = OperationId::new("node1".to_string(), 1);
344
+ let command = OperationCommand {
345
+ op_type: "SetField".to_string(),
346
+ collection: "orders".to_string(),
347
+ record_id: "order123".to_string(),
348
+ fields: {
349
+ let mut m = HashMap::new();
350
+ m.insert("status".to_string(), serde_json::json!("paid"));
351
+ m.insert("amount".to_string(), serde_json::json!(999.99));
352
+ m
353
+ },
354
+ };
355
+
356
+ let mut executor1 = TransactionExecutor::new("node1".to_string());
357
+ let mut executor2 = TransactionExecutor::new("node1".to_string());
358
+
359
+ let op1 = Operation::new(
360
+ op_id.clone(),
361
+ version.clone(),
362
+ "PayOrder".to_string(),
363
+ command.clone(),
364
+ "system".to_string(),
365
+ );
366
+
367
+ let op2 = Operation::new(
368
+ op_id,
369
+ version.clone(),
370
+ "PayOrder".to_string(),
371
+ command,
372
+ "system".to_string(),
373
+ );
374
+
375
+ let tx1 = executor1.execute_transaction(
376
+ "tx1".to_string(),
377
+ version.clone(),
378
+ vec![op1],
379
+ ConsistencyContract::local(),
380
+ );
381
+
382
+ let tx2 = executor2.execute_transaction(
383
+ "tx2".to_string(),
384
+ version,
385
+ vec![op2],
386
+ ConsistencyContract::local(),
387
+ );
388
+
389
+ assert_eq!(
390
+ tx1.to_version.state_hash, tx2.to_version.state_hash,
391
+ "Same operations should produce identical state hash"
392
+ );
393
+ }
394
+
395
+ #[test]
396
+ fn test_determinism_replay_convergence() {
397
+ let mut base_vc = VectorClock::new();
398
+ base_vc.increment("node1");
399
+ let base_version = StateVersion::new(base_vc, "hash0".to_string());
400
+
401
+ let mut operations = Vec::new();
402
+ for i in 1..=5 {
403
+ operations.push(Operation::new(
404
+ OperationId::new("node1".to_string(), i),
405
+ base_version.clone(),
406
+ format!("Op{}", i),
407
+ OperationCommand {
408
+ op_type: "UpdateField".to_string(),
409
+ collection: "items".to_string(),
410
+ record_id: format!("item{}", i),
411
+ fields: {
412
+ let mut m = HashMap::new();
413
+ m.insert("value".to_string(), serde_json::json!(i * 10));
414
+ m
415
+ },
416
+ },
417
+ "system".to_string(),
418
+ ));
419
+ }
420
+
421
+ let mut executor1 = TransactionExecutor::new("node1".to_string());
422
+ let mut executor2 = TransactionExecutor::new("node1".to_string());
423
+
424
+ let tx1 = executor1.execute_transaction(
425
+ "tx1".to_string(),
426
+ base_version.clone(),
427
+ operations.clone(),
428
+ ConsistencyContract::causal(),
429
+ );
430
+
431
+ let operations_shuffled = vec![
432
+ operations[2].clone(),
433
+ operations[0].clone(),
434
+ operations[4].clone(),
435
+ operations[1].clone(),
436
+ operations[3].clone(),
437
+ ];
438
+
439
+ let tx2 = executor2.execute_transaction(
440
+ "tx2".to_string(),
441
+ base_version,
442
+ operations_shuffled,
443
+ ConsistencyContract::causal(),
444
+ );
445
+
446
+ assert!(tx1.is_successful(), "Tx1 should succeed");
447
+ assert!(tx2.is_successful(), "Tx2 should succeed");
448
+ assert_eq!(
449
+ tx1.operations.len(), tx2.operations.len(),
450
+ "Both transactions should have same number of operations"
451
+ );
452
+ }
453
+
454
+ // ============================================================================
455
+ // INVARIANT 4: EXACTLY-ONCE SEMANTICS
456
+ // Same operation ID applied multiple times produces single effect
457
+ // ============================================================================
458
+
459
+ #[test]
460
+ fn test_exactly_once_duplicate_operation_rejected() {
461
+ let mut vc = VectorClock::new();
462
+ vc.increment("node1");
463
+ let version = StateVersion::new(vc, "hash0".to_string());
464
+
465
+ let op_id = OperationId::new("node1".to_string(), 42);
466
+
467
+ let op = Operation::new(
468
+ op_id.clone(),
469
+ version.clone(),
470
+ "IncrementScore".to_string(),
471
+ OperationCommand {
472
+ op_type: "Increment".to_string(),
473
+ collection: "scores".to_string(),
474
+ record_id: "player1".to_string(),
475
+ fields: {
476
+ let mut m = HashMap::new();
477
+ m.insert("score".to_string(), serde_json::json!(1));
478
+ m
479
+ },
480
+ },
481
+ "system".to_string(),
482
+ );
483
+
484
+ let mut executor = TransactionExecutor::new("node1".to_string());
485
+
486
+ executor.mark_applied(&op_id);
487
+ assert!(executor.is_applied(&op_id));
488
+
489
+ let tx = executor.execute_transaction(
490
+ "tx1".to_string(),
491
+ version,
492
+ vec![op],
493
+ ConsistencyContract::eventual(),
494
+ );
495
+
496
+ assert!(!tx.is_successful(), "Duplicate operation should be rejected");
497
+ }
498
+
499
+ #[test]
500
+ fn test_exactly_once_deduplication_set() {
501
+ let mut vc = VectorClock::new();
502
+ vc.increment("node1");
503
+ let version = StateVersion::new(vc, "hash0".to_string());
504
+
505
+ let op_ids: Vec<_> = (1..=10)
506
+ .map(|i| OperationId::new("node1".to_string(), i))
507
+ .collect();
508
+
509
+ let mut executor = TransactionExecutor::new("node1".to_string());
510
+
511
+ for op_id in op_ids.iter().take(5) {
512
+ executor.mark_applied(op_id);
513
+ }
514
+
515
+ for (i, op_id) in op_ids.iter().enumerate() {
516
+ let is_applied = executor.is_applied(op_id);
517
+ if i < 5 {
518
+ assert!(is_applied, "Operations 1-5 should be marked as applied");
519
+ } else {
520
+ assert!(!is_applied, "Operations 6-10 should not be applied yet");
521
+ }
522
+ }
523
+ }
524
+
525
+ // ============================================================================
526
+ // INVARIANT 5: CAUSALITY
527
+ // Happens-before ordering preserved across replicas
528
+ // ============================================================================
529
+
530
+ #[test]
531
+ fn test_causality_happens_before_ordering() {
532
+ let mut vc1 = VectorClock::new();
533
+ vc1.increment("node1");
534
+ let v1 = StateVersion::new(vc1, "hash1".to_string());
535
+
536
+ let mut vc2 = VectorClock::new();
537
+ vc2.increment("node1");
538
+ vc2.increment("node1");
539
+ let v2 = StateVersion::new(vc2, "hash2".to_string());
540
+
541
+ assert!(v1.vector_clock.happens_before(&v2.vector_clock));
542
+ assert!(!v2.vector_clock.happens_before(&v1.vector_clock));
543
+ }
544
+
545
+ #[test]
546
+ fn test_causality_concurrent_states() {
547
+ let mut vc1 = VectorClock::new();
548
+ vc1.increment("node1");
549
+ let v1 = StateVersion::new(vc1, "hash1".to_string());
550
+
551
+ let mut vc2 = VectorClock::new();
552
+ vc2.increment("node2");
553
+ let v2 = StateVersion::new(vc2, "hash2".to_string());
554
+
555
+ assert!(v1.concurrent_with(&v2));
556
+ assert!(v2.concurrent_with(&v1));
557
+ assert!(!v1.vector_clock.happens_before(&v2.vector_clock));
558
+ assert!(!v2.vector_clock.happens_before(&v1.vector_clock));
559
+ }
560
+
561
+ // ============================================================================
562
+ // INVARIANT 6: CONSISTENCY CONTRACTS
563
+ // Per-operation consistency levels honored
564
+ // ============================================================================
565
+
566
+ #[test]
567
+ fn test_consistency_local_no_replication_wait() {
568
+ let contract = ConsistencyContract::local();
569
+ assert_eq!(contract.level, ConsistencyLevel::Local);
570
+ assert_eq!(contract.timeout_ms, None);
571
+ }
572
+
573
+ #[test]
574
+ fn test_consistency_causal_ordered() {
575
+ let contract = ConsistencyContract::causal();
576
+ assert_eq!(contract.level, ConsistencyLevel::Causal);
577
+ assert!(contract.timeout_ms.is_some());
578
+ assert!(contract.timeout_ms.unwrap() < 5000);
579
+ }
580
+
581
+ #[test]
582
+ fn test_consistency_linearizable_requires_quorum() {
583
+ let contract = ConsistencyContract::linearizable(3);
584
+ assert_eq!(contract.level, ConsistencyLevel::Linearizable);
585
+ assert_eq!(contract.required_acks, Some(3));
586
+ assert!(contract.timeout_ms.unwrap() >= 5000);
587
+ }
588
+
589
+ // ============================================================================
590
+ // INVARIANT 7: DERIVED STATE REBUILD
591
+ // Indexes rebuildable from operation log
592
+ // ============================================================================
593
+
594
+ #[test]
595
+ fn test_derived_state_operation_log_contains_operations() {
596
+ let mut vc = VectorClock::new();
597
+ vc.increment("node1");
598
+ let version = StateVersion::new(vc, "hash0".to_string());
599
+
600
+ let ops: Vec<_> = (1..=5)
601
+ .map(|i| {
602
+ Operation::new(
603
+ OperationId::new("node1".to_string(), i),
604
+ version.clone(),
605
+ format!("Op{}", i),
606
+ OperationCommand {
607
+ op_type: "SetField".to_string(),
608
+ collection: "data".to_string(),
609
+ record_id: format!("record{}", i),
610
+ fields: {
611
+ let mut m = HashMap::new();
612
+ m.insert("value".to_string(), serde_json::json!(i));
613
+ m
614
+ },
615
+ },
616
+ "system".to_string(),
617
+ )
618
+ })
619
+ .collect();
620
+
621
+ let mut executor = TransactionExecutor::new("node1".to_string());
622
+ let mut to_vc = version.vector_clock.clone();
623
+ to_vc.increment("node1");
624
+ let to_version = StateVersion::new(to_vc, "hash1".to_string());
625
+
626
+ let transition =
627
+ executor.execute_transaction("tx1".to_string(), version, ops.clone(), ConsistencyContract::local());
628
+
629
+ assert_eq!(transition.operations.len(), ops.len());
630
+
631
+ for (i, op) in transition.operations.iter().enumerate() {
632
+ assert_eq!(
633
+ op.intent,
634
+ format!("Op{}", i + 1),
635
+ "Operation sequence should be preserved in transition"
636
+ );
637
+ }
638
+ }
639
+
640
+ // ============================================================================
641
+ // INVARIANT 8: PARTITION TOLERANCE
642
+ // Divergence + deterministic reconciliation
643
+ // ============================================================================
644
+
645
+ #[test]
646
+ fn test_partition_concurrent_writes_create_divergence() {
647
+ let mut vc_base = VectorClock::new();
648
+ vc_base.increment("node1");
649
+ let base_version = StateVersion::new(vc_base, "hash_base".to_string());
650
+
651
+ let op1_id = OperationId::new("node1".to_string(), 1);
652
+ let op2_id = OperationId::new("node2".to_string(), 1);
653
+
654
+ let op1 = Operation::new(
655
+ op1_id,
656
+ base_version.clone(),
657
+ "UpdateFromNode1".to_string(),
658
+ OperationCommand {
659
+ op_type: "SetField".to_string(),
660
+ collection: "state".to_string(),
661
+ record_id: "shared".to_string(),
662
+ fields: {
663
+ let mut m = HashMap::new();
664
+ m.insert("value".to_string(), serde_json::json!("node1_value"));
665
+ m
666
+ },
667
+ },
668
+ "node1".to_string(),
669
+ );
670
+
671
+ let op2 = Operation::new(
672
+ op2_id,
673
+ base_version.clone(),
674
+ "UpdateFromNode2".to_string(),
675
+ OperationCommand {
676
+ op_type: "SetField".to_string(),
677
+ collection: "state".to_string(),
678
+ record_id: "shared".to_string(),
679
+ fields: {
680
+ let mut m = HashMap::new();
681
+ m.insert("value".to_string(), serde_json::json!("node2_value"));
682
+ m
683
+ },
684
+ },
685
+ "node2".to_string(),
686
+ );
687
+
688
+ let mut executor1 = TransactionExecutor::new("node1".to_string());
689
+ let mut executor2 = TransactionExecutor::new("node2".to_string());
690
+
691
+ let mut vc1 = base_version.vector_clock.clone();
692
+ vc1.increment("node1");
693
+ let v1 = StateVersion::new(vc1, "hash1".to_string());
694
+
695
+ let mut vc2 = base_version.vector_clock.clone();
696
+ vc2.increment("node2");
697
+ let v2 = StateVersion::new(vc2, "hash2".to_string());
698
+
699
+ let tx1 = executor1.execute_transaction(
700
+ "tx1".to_string(),
701
+ base_version.clone(),
702
+ vec![op1],
703
+ ConsistencyContract::eventual(),
704
+ );
705
+
706
+ let tx2 = executor2.execute_transaction(
707
+ "tx2".to_string(),
708
+ base_version,
709
+ vec![op2],
710
+ ConsistencyContract::eventual(),
711
+ );
712
+
713
+ assert!(tx1.is_successful());
714
+ assert!(tx2.is_successful());
715
+
716
+ assert!(
717
+ tx1.to_version.concurrent_with(&tx2.to_version),
718
+ "Concurrent writes should create divergent versions"
719
+ );
720
+ }
721
+
722
+ #[test]
723
+ fn test_partition_deterministic_reconciliation() {
724
+ let mut base_vc = VectorClock::new();
725
+ base_vc.increment("node1");
726
+ base_vc.increment("node2");
727
+ let base_version = StateVersion::new(base_vc, "hash_base".to_string());
728
+
729
+ let op_id = OperationId::new("node1".to_string(), 1);
730
+ let op = Operation::new(
731
+ op_id,
732
+ base_version.clone(),
733
+ "ReconcileOperation".to_string(),
734
+ OperationCommand {
735
+ op_type: "Merge".to_string(),
736
+ collection: "state".to_string(),
737
+ record_id: "merged".to_string(),
738
+ fields: {
739
+ let mut m = HashMap::new();
740
+ m.insert("combined".to_string(), serde_json::json!(true));
741
+ m
742
+ },
743
+ },
744
+ "reconciler".to_string(),
745
+ );
746
+
747
+ let mut executor1 = TransactionExecutor::new("node1".to_string());
748
+ let mut executor2 = TransactionExecutor::new("node2".to_string());
749
+
750
+ let tx1 = executor1.execute_transaction(
751
+ "reconcile1".to_string(),
752
+ base_version.clone(),
753
+ vec![op.clone()],
754
+ ConsistencyContract::causal(),
755
+ );
756
+
757
+ let tx2 = executor2.execute_transaction(
758
+ "reconcile2".to_string(),
759
+ base_version,
760
+ vec![op],
761
+ ConsistencyContract::causal(),
762
+ );
763
+
764
+ assert_eq!(
765
+ tx1.to_version.state_hash, tx2.to_version.state_hash,
766
+ "Deterministic reconciliation should converge both replicas"
767
+ );
768
+ }
769
+
770
+ // ============================================================================
771
+ // CROSS-INVARIANT SCENARIOS
772
+ // ============================================================================
773
+
774
+ #[test]
775
+ fn test_atomicity_with_causal_consistency() {
776
+ let mut vc = VectorClock::new();
777
+ vc.increment("node1");
778
+ let version = StateVersion::new(vc, "hash0".to_string());
779
+
780
+ let op1 = Operation::new(
781
+ OperationId::new("node1".to_string(), 1),
782
+ version.clone(),
783
+ "WriteA".to_string(),
784
+ OperationCommand {
785
+ op_type: "SetField".to_string(),
786
+ collection: "log".to_string(),
787
+ record_id: "entry1".to_string(),
788
+ fields: {
789
+ let mut m = HashMap::new();
790
+ m.insert("data".to_string(), serde_json::json!("value_a"));
791
+ m
792
+ },
793
+ },
794
+ "system".to_string(),
795
+ );
796
+
797
+ let op2 = Operation::new(
798
+ OperationId::new("node1".to_string(), 2),
799
+ version.clone(),
800
+ "WriteB".to_string(),
801
+ OperationCommand {
802
+ op_type: "SetField".to_string(),
803
+ collection: "log".to_string(),
804
+ record_id: "entry2".to_string(),
805
+ fields: {
806
+ let mut m = HashMap::new();
807
+ m.insert("data".to_string(), serde_json::json!("value_b"));
808
+ m
809
+ },
810
+ },
811
+ "system".to_string(),
812
+ );
813
+
814
+ let mut executor = TransactionExecutor::new("node1".to_string());
815
+ let mut to_vc = version.vector_clock.clone();
816
+ to_vc.increment("node1");
817
+ let to_version = StateVersion::new(to_vc, "hash1".to_string());
818
+
819
+ let transition = executor.execute_transaction(
820
+ "atomic_tx".to_string(),
821
+ version,
822
+ vec![op1, op2],
823
+ ConsistencyContract::causal(),
824
+ );
825
+
826
+ assert!(transition.is_successful());
827
+ assert_eq!(transition.operations.len(), 2);
828
+ assert!(transition.to_version.dominates(&transition.from_version));
829
+ }
830
+
831
+ #[test]
832
+ fn test_write_skew_detection() {
833
+ let mut vc = VectorClock::new();
834
+ vc.increment("node1");
835
+ let base_version = StateVersion::new(vc, "hash_base".to_string());
836
+
837
+ let op1 = Operation::new(
838
+ OperationId::new("node1".to_string(), 1),
839
+ base_version.clone(),
840
+ "RemoveDoctor".to_string(),
841
+ OperationCommand {
842
+ op_type: "UpdateField".to_string(),
843
+ collection: "oncall".to_string(),
844
+ record_id: "doctor1".to_string(),
845
+ fields: {
846
+ let mut m = HashMap::new();
847
+ m.insert("active".to_string(), serde_json::json!(false));
848
+ m
849
+ },
850
+ },
851
+ "system".to_string(),
852
+ )
853
+ .with_preconditions(vec![("check_count".to_string(), "count >= 2".to_string())])
854
+ .with_read_set(vec![
855
+ StateDependency::new(
856
+ "oncall".to_string(),
857
+ "doctor1".to_string(),
858
+ Some("active".to_string()),
859
+ base_version.clone(),
860
+ ),
861
+ StateDependency::new(
862
+ "oncall".to_string(),
863
+ "metadata".to_string(),
864
+ Some("count".to_string()),
865
+ base_version.clone(),
866
+ ),
867
+ ])
868
+ .with_write_set(vec![StateDependency::new(
869
+ "oncall".to_string(),
870
+ "doctor1".to_string(),
871
+ Some("active".to_string()),
872
+ base_version.clone(),
873
+ )]);
874
+
875
+ let op2 = Operation::new(
876
+ OperationId::new("node1".to_string(), 2),
877
+ base_version.clone(),
878
+ "RemoveDoctor".to_string(),
879
+ OperationCommand {
880
+ op_type: "UpdateField".to_string(),
881
+ collection: "oncall".to_string(),
882
+ record_id: "doctor2".to_string(),
883
+ fields: {
884
+ let mut m = HashMap::new();
885
+ m.insert("active".to_string(), serde_json::json!(false));
886
+ m
887
+ },
888
+ },
889
+ "system".to_string(),
890
+ )
891
+ .with_preconditions(vec![("check_count".to_string(), "count >= 2".to_string())])
892
+ .with_read_set(vec![
893
+ StateDependency::new(
894
+ "oncall".to_string(),
895
+ "doctor2".to_string(),
896
+ Some("active".to_string()),
897
+ base_version.clone(),
898
+ ),
899
+ StateDependency::new(
900
+ "oncall".to_string(),
901
+ "metadata".to_string(),
902
+ Some("count".to_string()),
903
+ base_version.clone(),
904
+ ),
905
+ ])
906
+ .with_write_set(vec![
907
+ StateDependency::new(
908
+ "oncall".to_string(),
909
+ "doctor2".to_string(),
910
+ Some("active".to_string()),
911
+ base_version.clone(),
912
+ ),
913
+ StateDependency::new(
914
+ "oncall".to_string(),
915
+ "metadata".to_string(),
916
+ Some("count".to_string()),
917
+ base_version.clone(),
918
+ ),
919
+ ]);
920
+
921
+ let mut executor1 = TransactionExecutor::new("node1".to_string());
922
+ let mut executor2 = TransactionExecutor::new("node1".to_string());
923
+
924
+ let mut to_vc1 = base_version.vector_clock.clone();
925
+ to_vc1.increment("node1");
926
+ let to_version1 = StateVersion::new(to_vc1, "hash_tx1".to_string());
927
+
928
+ let mut to_vc2 = base_version.vector_clock.clone();
929
+ to_vc2.increment("node1");
930
+ let to_version2 = StateVersion::new(to_vc2, "hash_tx2".to_string());
931
+
932
+ let tx1 = executor1.execute_transaction(
933
+ "tx1".to_string(),
934
+ base_version.clone(),
935
+ vec![op1],
936
+ ConsistencyContract::causal(),
937
+ );
938
+
939
+ let tx2 = executor2.execute_transaction(
940
+ "tx2".to_string(),
941
+ base_version,
942
+ vec![op2],
943
+ ConsistencyContract::causal(),
944
+ );
945
+
946
+ let conflicts = CommitValidator::detect_conflicts(&tx1, &[tx2]);
947
+ assert!(!conflicts.is_empty(), "Write-skew scenario should detect potential conflict");
948
+ }
949
+ }