@feltdb/core 0.8.4 → 0.8.5

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 (149) hide show
  1. package/dist/create/package-versions.js +1 -1
  2. package/dist/create/server-source/Cargo.lock +165 -0
  3. package/dist/create/server-source/Cargo.toml +9 -0
  4. package/dist/create/server-source/crates/feltdb/Cargo.toml +3 -0
  5. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +44 -44
  6. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +12 -24
  7. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +7 -13
  8. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +13 -10
  9. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +12 -9
  10. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +78 -25
  11. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +16 -13
  12. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +13 -5
  13. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +41 -22
  14. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +33 -15
  15. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +100 -33
  16. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +56 -20
  17. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +116 -41
  18. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +186 -55
  19. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +64 -26
  20. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +46 -15
  21. package/dist/create/server-source/crates/feltdb/src/admission.rs +8 -15
  22. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +43 -13
  23. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +15 -42
  24. package/dist/create/server-source/crates/feltdb/src/analytics.rs +65 -19
  25. package/dist/create/server-source/crates/feltdb/src/application.rs +113 -30
  26. package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +475 -140
  27. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +17 -15
  28. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -1
  29. package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +59 -15
  30. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +266 -114
  31. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +25 -7
  32. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +10 -15
  33. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +3 -11
  34. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +14 -5
  35. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +21 -25
  36. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +14 -11
  37. package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +103 -22
  38. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +18 -15
  39. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +12 -8
  40. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +1 -5
  41. package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +87 -23
  42. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +3 -7
  43. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +10 -9
  44. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +1 -6
  45. package/dist/create/server-source/crates/feltdb/src/indexing.rs +35 -38
  46. package/dist/create/server-source/crates/feltdb/src/lib.rs +507 -6
  47. package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +4 -1
  48. package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -1
  49. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +1 -2
  50. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +107 -30
  51. package/dist/create/server-source/crates/feltdb/src/observability.rs +19 -6
  52. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +12 -11
  53. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +9 -4
  54. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +65 -18
  55. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +193 -57
  56. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +37 -27
  57. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +38 -10
  58. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +20 -14
  59. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +394 -229
  60. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +8 -6
  61. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +11 -13
  62. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +79 -70
  63. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +3 -3
  64. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +33 -11
  65. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +6 -6
  66. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +14 -56
  67. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +29 -27
  68. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +35 -17
  69. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +8 -2
  70. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +59 -15
  71. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +70 -17
  72. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +23 -6
  73. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +12 -3
  74. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +41 -11
  75. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +56 -25
  76. package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +701 -245
  77. package/dist/create/server-source/crates/feltdb/src/production_api.rs +31 -13
  78. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +6 -8
  79. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +5 -5
  80. package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +48 -18
  81. package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +30 -11
  82. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +6 -3
  83. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +4 -3
  84. package/dist/create/server-source/crates/feltdb/src/sharding.rs +36 -10
  85. package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +516 -0
  86. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +13 -4
  87. package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +222 -0
  88. package/dist/create/server-source/crates/feltdb/src/state_facade.rs +82 -54
  89. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +2 -2
  90. package/dist/create/server-source/crates/feltdb/src/state_model.rs +1514 -537
  91. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +6 -3
  92. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +672 -0
  93. package/dist/create/server-source/crates/feltdb/src/submission.rs +5 -11
  94. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +6 -8
  95. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +24 -35
  96. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +24 -8
  97. package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +248 -59
  98. package/dist/create/server-source/crates/feltdb/src/transactions.rs +17 -20
  99. package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +749 -0
  100. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +1 -0
  101. package/dist/create/server-source/crates/feltdb/src/workload.rs +512 -4
  102. package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +3 -4
  103. package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +299 -0
  104. package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +288 -0
  105. package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +392 -0
  106. package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +436 -220
  107. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +1916 -0
  108. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +1878 -0
  109. package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +1862 -0
  110. package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +2 -1
  111. package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +80 -25
  112. package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +5 -8
  113. package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +52 -44
  114. package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +33 -16
  115. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +22 -7
  116. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +41 -22
  117. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +25 -8
  118. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +9 -6
  119. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +120 -69
  120. package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +21 -10
  121. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +1032 -0
  122. package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +868 -0
  123. package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +221 -0
  124. package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +338 -0
  125. package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +1039 -0
  126. package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +739 -0
  127. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +427 -0
  128. package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +3 -3
  129. package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +25 -22
  130. package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +1799 -0
  131. package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +1316 -0
  132. package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +53 -61
  133. package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +156 -61
  134. package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +299 -0
  135. package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +255 -0
  136. package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +249 -0
  137. package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +994 -0
  138. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +2 -1
  139. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +164 -13
  140. package/dist/create/server-source/crates/feltdb-server/src/main.rs +326 -6
  141. package/dist/studio-app/assets/{feltdb_wasm-CVQWgXO-.js → feltdb_wasm-DaNwCLRX.js} +1 -1
  142. package/dist/studio-app/assets/feltdb_wasm_bg-DnsHNv6g.wasm +0 -0
  143. package/dist/studio-app/assets/{index-DwgNAIIX.js → index-j8IlhNqJ.js} +1 -1
  144. package/dist/studio-app/index.html +1 -1
  145. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  146. package/dist/workload.d.ts +2 -1
  147. package/dist/workload.d.ts.map +1 -1
  148. package/package.json +1 -1
  149. package/dist/studio-app/assets/feltdb_wasm_bg-CNVpvaZV.wasm +0 -0
@@ -84,7 +84,9 @@ mod tests {
84
84
  let record = node.build("tx-reject", operations);
85
85
 
86
86
  match node.commit(&record)? {
87
- ApplyOutcome::Rejected(RejectionReason::PreconditionFailed { operation_index, .. }) => {
87
+ ApplyOutcome::Rejected(RejectionReason::PreconditionFailed {
88
+ operation_index, ..
89
+ }) => {
88
90
  assert_eq!(operation_index, 1);
89
91
  }
90
92
  other => panic!("expected a precondition rejection, got {:?}", other),
@@ -93,18 +95,28 @@ mod tests {
93
95
  // A rejected transaction must leave nothing behind, including the
94
96
  // operations that were legal on their own.
95
97
  assert_eq!(visible(&node, &record), 0, "no operation may be visible");
96
- assert!(node.state().get_record("accounts", "A").is_none(), "A must not be applied");
97
- assert!(node.state().get_record("accounts", "C").is_none(), "C must not be applied");
98
+ assert!(
99
+ node.state().get_record("accounts", "A").is_none(),
100
+ "A must not be applied"
101
+ );
102
+ assert!(
103
+ node.state().get_record("accounts", "C").is_none(),
104
+ "C must not be applied"
105
+ );
98
106
 
99
107
  drop(node);
100
108
  let (restarted, report) = TransactionalNode::open(&path, "node1")?;
101
- assert!(report.committed.is_empty(), "a rejected transaction must never be logged");
109
+ assert!(
110
+ report.committed.is_empty(),
111
+ "a rejected transaction must never be logged"
112
+ );
102
113
  assert!(restarted.state().get_record("accounts", "A").is_none());
103
114
  Ok(())
104
115
  }
105
116
 
106
117
  #[test]
107
- fn a_transaction_writing_one_record_twice_is_refused() -> Result<(), Box<dyn std::error::Error>> {
118
+ fn a_transaction_writing_one_record_twice_is_refused() -> Result<(), Box<dyn std::error::Error>>
119
+ {
108
120
  let dir = TempDir::new()?;
109
121
  let (mut node, _) = TransactionalNode::open(dir.path().join("tx.log"), "node1")?;
110
122
 
@@ -137,8 +149,12 @@ mod tests {
137
149
  CrashPoint::DuringWrite { keep_percent: 90 },
138
150
  CrashPoint::AfterWriteBeforeFsync,
139
151
  CrashPoint::AfterFsyncBeforeApply,
140
- CrashPoint::DuringApply { after_operations: 1 },
141
- CrashPoint::DuringApply { after_operations: 2 },
152
+ CrashPoint::DuringApply {
153
+ after_operations: 1,
154
+ },
155
+ CrashPoint::DuringApply {
156
+ after_operations: 2,
157
+ },
142
158
  CrashPoint::AfterApply,
143
159
  ];
144
160
 
@@ -162,14 +178,22 @@ mod tests {
162
178
  // Nothing durable was written, so nothing may be visible.
163
179
  CrashPoint::BeforeWrite | CrashPoint::DuringWrite { .. } => {
164
180
  assert!(!fully_visible, "{}: nothing should have committed", context);
165
- assert!(report.committed.is_empty(), "{}: log must hold no record", context);
181
+ assert!(
182
+ report.committed.is_empty(),
183
+ "{}: log must hold no record",
184
+ context
185
+ );
166
186
  }
167
187
  // The record was fsynced, so recovery must replay all of it —
168
188
  // including when the crash happened part-way through applying.
169
189
  CrashPoint::AfterFsyncBeforeApply
170
190
  | CrashPoint::DuringApply { .. }
171
191
  | CrashPoint::AfterApply => {
172
- assert!(fully_visible, "{}: a committed transaction must fully recover", context);
192
+ assert!(
193
+ fully_visible,
194
+ "{}: a committed transaction must fully recover",
195
+ context
196
+ );
173
197
  assert_eq!(report.committed.len(), 1, "{}", context);
174
198
  }
175
199
  // Written but not fsynced: the operating system decides. Either
@@ -182,8 +206,8 @@ mod tests {
182
206
  }
183
207
 
184
208
  #[test]
185
- fn a_torn_final_record_is_discarded_and_the_log_stays_writable()
186
- -> Result<(), Box<dyn std::error::Error>> {
209
+ fn a_torn_final_record_is_discarded_and_the_log_stays_writable(
210
+ ) -> Result<(), Box<dyn std::error::Error>> {
187
211
  let dir = TempDir::new()?;
188
212
  let path = dir.path().join("tx.log");
189
213
 
@@ -200,7 +224,10 @@ mod tests {
200
224
  // Recovery keeps the committed transaction and discards the torn one.
201
225
  let (mut node, report) = TransactionalNode::open(&path, "node1")?;
202
226
  assert_eq!(report.committed.len(), 1);
203
- assert!(matches!(report.discarded_tail, Some(LogDefect::TornTail { .. })));
227
+ assert!(matches!(
228
+ report.discarded_tail,
229
+ Some(LogDefect::TornTail { .. })
230
+ ));
204
231
  assert_eq!(visible(&node, &first), 3);
205
232
 
206
233
  // The torn bytes were truncated, so a later write cannot splice onto
@@ -210,7 +237,10 @@ mod tests {
210
237
  drop(node);
211
238
 
212
239
  let (again, report) = TransactionalNode::open(&path, "node1")?;
213
- assert!(report.discarded_tail.is_none(), "the log must be clean after truncation");
240
+ assert!(
241
+ report.discarded_tail.is_none(),
242
+ "the log must be clean after truncation"
243
+ );
214
244
  assert_eq!(report.committed.len(), 2);
215
245
  assert_eq!(visible(&again, &first), 3);
216
246
  assert_eq!(visible(&again, &third), 3);
@@ -218,7 +248,8 @@ mod tests {
218
248
  }
219
249
 
220
250
  #[test]
221
- fn interior_corruption_is_refused_rather_than_guessed() -> Result<(), Box<dyn std::error::Error>> {
251
+ fn interior_corruption_is_refused_rather_than_guessed() -> Result<(), Box<dyn std::error::Error>>
252
+ {
222
253
  let dir = TempDir::new()?;
223
254
  let path = dir.path().join("tx.log");
224
255
 
@@ -234,9 +265,19 @@ mod tests {
234
265
  drop(node);
235
266
 
236
267
  let outcome = TransactionalNode::open(&path, "node1");
237
- assert!(outcome.is_err(), "recovery must refuse a corrupted interior record");
238
- let message = outcome.err().map(|error| error.to_string()).unwrap_or_default();
239
- assert!(message.contains("corrupted"), "unexpected error: {}", message);
268
+ assert!(
269
+ outcome.is_err(),
270
+ "recovery must refuse a corrupted interior record"
271
+ );
272
+ let message = outcome
273
+ .err()
274
+ .map(|error| error.to_string())
275
+ .unwrap_or_default();
276
+ assert!(
277
+ message.contains("corrupted"),
278
+ "unexpected error: {}",
279
+ message
280
+ );
240
281
  Ok(())
241
282
  }
242
283
 
@@ -247,15 +288,23 @@ mod tests {
247
288
 
248
289
  // Alter the body without recomputing the checksum, as a partial write
249
290
  // or a bit flip would.
250
- record.operations.push(TransactionOperation::set("accounts", "D", json!({ "balance": 9 })));
251
- assert!(!record.verify_checksum(), "a mutated record must fail its integrity check");
291
+ record.operations.push(TransactionOperation::set(
292
+ "accounts",
293
+ "D",
294
+ json!({ "balance": 9 }),
295
+ ));
296
+ assert!(
297
+ !record.verify_checksum(),
298
+ "a mutated record must fail its integrity check"
299
+ );
252
300
  Ok(())
253
301
  }
254
302
 
255
303
  // ---------------------------------------------------------------- replay
256
304
 
257
305
  #[test]
258
- fn replaying_a_committed_transaction_changes_nothing() -> Result<(), Box<dyn std::error::Error>> {
306
+ fn replaying_a_committed_transaction_changes_nothing() -> Result<(), Box<dyn std::error::Error>>
307
+ {
259
308
  let dir = TempDir::new()?;
260
309
  let (mut node, _) = TransactionalNode::open(dir.path().join("tx.log"), "node1")?;
261
310
 
@@ -265,10 +314,17 @@ mod tests {
265
314
 
266
315
  // Deliver the same transaction repeatedly.
267
316
  for _ in 0..5 {
268
- assert_eq!(node.accept_replicated(&record)?, ApplyOutcome::AlreadyApplied);
317
+ assert_eq!(
318
+ node.accept_replicated(&record)?,
319
+ ApplyOutcome::AlreadyApplied
320
+ );
269
321
  }
270
322
 
271
- assert_eq!(node.state().hash().as_hex(), hash_after_first, "replay must not change state");
323
+ assert_eq!(
324
+ node.state().hash().as_hex(),
325
+ hash_after_first,
326
+ "replay must not change state"
327
+ );
272
328
  Ok(())
273
329
  }
274
330
 
@@ -292,8 +348,10 @@ mod tests {
292
348
  hashes.push(node.state().hash().as_hex().to_string());
293
349
  }
294
350
 
295
- assert!(hashes.windows(2).all(|pair| pair[0] == pair[1]),
296
- "every restart must recover identical state");
351
+ assert!(
352
+ hashes.windows(2).all(|pair| pair[0] == pair[1]),
353
+ "every restart must recover identical state"
354
+ );
297
355
  Ok(())
298
356
  }
299
357
 
@@ -320,7 +378,11 @@ mod tests {
320
378
  assert_eq!(report.committed.len(), 3);
321
379
  for index in 1..=3 {
322
380
  let key = format!("A-tx{}", index);
323
- assert!(node.state().get_record("accounts", &key).is_some(), "tx{} lost", index);
381
+ assert!(
382
+ node.state().get_record("accounts", &key).is_some(),
383
+ "tx{} lost",
384
+ index
385
+ );
324
386
  }
325
387
  Ok(())
326
388
  }
@@ -353,15 +415,23 @@ mod tests {
353
415
  }
354
416
 
355
417
  // Sequences are unique and ordered, so the log defines one history.
356
- let sequences: Vec<u64> = report.committed.iter().map(|record| record.sequence).collect();
418
+ let sequences: Vec<u64> = report
419
+ .committed
420
+ .iter()
421
+ .map(|record| record.sequence)
422
+ .collect();
357
423
  let unique: HashSet<u64> = sequences.iter().copied().collect();
358
424
  assert_eq!(unique.len(), sequences.len(), "sequences must be unique");
359
- assert!(sequences.windows(2).all(|pair| pair[0] < pair[1]), "log must be ordered");
425
+ assert!(
426
+ sequences.windows(2).all(|pair| pair[0] < pair[1]),
427
+ "log must be ordered"
428
+ );
360
429
  Ok(())
361
430
  }
362
431
 
363
432
  #[test]
364
- fn a_losing_writer_is_rejected_without_partial_effect() -> Result<(), Box<dyn std::error::Error>> {
433
+ fn a_losing_writer_is_rejected_without_partial_effect() -> Result<(), Box<dyn std::error::Error>>
434
+ {
365
435
  let dir = TempDir::new()?;
366
436
  let path = dir.path().join("tx.log");
367
437
  let (mut node, _) = TransactionalNode::open(&path, "node1")?;
@@ -389,9 +459,14 @@ mod tests {
389
459
  assert!(matches!(node.commit(&second)?, ApplyOutcome::Rejected(_)));
390
460
 
391
461
  // The loser must leave nothing behind, including its uncontended write.
392
- assert!(node.state().get_record("accounts", "only-b").is_none(),
393
- "a rejected transaction must not apply its other operations");
394
- assert_eq!(node.state().get_record("accounts", "shared"), Some(&json!({ "owner": "a" })));
462
+ assert!(
463
+ node.state().get_record("accounts", "only-b").is_none(),
464
+ "a rejected transaction must not apply its other operations"
465
+ );
466
+ assert_eq!(
467
+ node.state().get_record("accounts", "shared"),
468
+ Some(&json!({ "owner": "a" }))
469
+ );
395
470
  drop(node);
396
471
 
397
472
  let (recovered, report) = TransactionalNode::open(&path, "node1")?;
@@ -412,16 +487,36 @@ mod tests {
412
487
  let record = leader.build("tx1", abc("tx1"));
413
488
  assert_eq!(leader.commit(&record)?, ApplyOutcome::Applied);
414
489
 
415
- assert_eq!(follower_b.accept_replicated(&record)?, ApplyOutcome::Applied);
416
- assert_eq!(follower_c.accept_replicated(&record)?, ApplyOutcome::Applied);
490
+ assert_eq!(
491
+ follower_b.accept_replicated(&record)?,
492
+ ApplyOutcome::Applied
493
+ );
494
+ assert_eq!(
495
+ follower_c.accept_replicated(&record)?,
496
+ ApplyOutcome::Applied
497
+ );
417
498
 
418
499
  // Duplicate delivery is safe.
419
- assert_eq!(follower_b.accept_replicated(&record)?, ApplyOutcome::AlreadyApplied);
420
- assert_eq!(follower_c.accept_replicated(&record)?, ApplyOutcome::AlreadyApplied);
500
+ assert_eq!(
501
+ follower_b.accept_replicated(&record)?,
502
+ ApplyOutcome::AlreadyApplied
503
+ );
504
+ assert_eq!(
505
+ follower_c.accept_replicated(&record)?,
506
+ ApplyOutcome::AlreadyApplied
507
+ );
421
508
 
422
509
  let leader_hash = leader.state().hash().as_hex().to_string();
423
- assert_eq!(follower_b.state().hash().as_hex(), leader_hash, "B diverged");
424
- assert_eq!(follower_c.state().hash().as_hex(), leader_hash, "C diverged");
510
+ assert_eq!(
511
+ follower_b.state().hash().as_hex(),
512
+ leader_hash,
513
+ "B diverged"
514
+ );
515
+ assert_eq!(
516
+ follower_c.state().hash().as_hex(),
517
+ leader_hash,
518
+ "C diverged"
519
+ );
425
520
 
426
521
  for node in [&follower_b, &follower_c] {
427
522
  assert_atomic(node, &record, "replicated follower");
@@ -430,8 +525,8 @@ mod tests {
430
525
  }
431
526
 
432
527
  #[test]
433
- fn a_follower_that_crashes_mid_replication_recovers_atomically()
434
- -> Result<(), Box<dyn std::error::Error>> {
528
+ fn a_follower_that_crashes_mid_replication_recovers_atomically(
529
+ ) -> Result<(), Box<dyn std::error::Error>> {
435
530
  let dir = TempDir::new()?;
436
531
  let leader_path = dir.path().join("a.log");
437
532
  let follower_path = dir.path().join("b.log");
@@ -450,27 +545,40 @@ mod tests {
450
545
  }
451
546
 
452
547
  let (mut follower, report) = TransactionalNode::open(&follower_path, "nodeB")?;
453
- assert!(report.committed.is_empty(), "a torn replicated record must not commit");
548
+ assert!(
549
+ report.committed.is_empty(),
550
+ "a torn replicated record must not commit"
551
+ );
454
552
  assert_atomic(&follower, &record, "follower after torn write");
455
553
  assert_eq!(follower.executor().visible_operations(&record), 0);
456
554
 
457
555
  // Retransmission after recovery succeeds and converges.
458
556
  assert_eq!(follower.accept_replicated(&record)?, ApplyOutcome::Applied);
459
557
  let (leader, _) = TransactionalNode::open(&leader_path, "nodeA")?;
460
- assert_eq!(follower.state().hash().as_hex(), leader.state().hash().as_hex());
558
+ assert_eq!(
559
+ follower.state().hash().as_hex(),
560
+ leader.state().hash().as_hex()
561
+ );
461
562
  Ok(())
462
563
  }
463
564
 
464
565
  #[test]
465
- fn a_replicated_record_that_fails_integrity_is_refused() -> Result<(), Box<dyn std::error::Error>> {
566
+ fn a_replicated_record_that_fails_integrity_is_refused(
567
+ ) -> Result<(), Box<dyn std::error::Error>> {
466
568
  let dir = TempDir::new()?;
467
569
  let (mut follower, _) = TransactionalNode::open(dir.path().join("b.log"), "nodeB")?;
468
570
 
469
571
  let mut record = TransactionRecord::new("tx1", "nodeA", 1, abc("tx1"));
470
572
  record.operations[0].record_id = "tampered".to_string();
471
573
 
472
- assert!(matches!(follower.accept_replicated(&record)?, ApplyOutcome::Rejected(_)));
473
- assert!(follower.state().records.is_empty(), "a tampered record must not apply");
574
+ assert!(matches!(
575
+ follower.accept_replicated(&record)?,
576
+ ApplyOutcome::Rejected(_)
577
+ ));
578
+ assert!(
579
+ follower.state().records.is_empty(),
580
+ "a tampered record must not apply"
581
+ );
474
582
  Ok(())
475
583
  }
476
584
 
@@ -490,11 +598,16 @@ mod tests {
490
598
 
491
599
  let (mut node, report) = TransactionalNode::open(&path, "node1")?;
492
600
  assert_eq!(report.committed[0].transaction_id, "stable-id");
493
- assert!(node.executor().applied_transactions().contains("stable-id"),
494
- "identity must be restored so replay stays idempotent");
601
+ assert!(
602
+ node.executor().applied_transactions().contains("stable-id"),
603
+ "identity must be restored so replay stays idempotent"
604
+ );
495
605
 
496
606
  // Redelivery after restart is still a no-op.
497
- assert_eq!(node.accept_replicated(&record)?, ApplyOutcome::AlreadyApplied);
607
+ assert_eq!(
608
+ node.accept_replicated(&record)?,
609
+ ApplyOutcome::AlreadyApplied
610
+ );
498
611
  Ok(())
499
612
  }
500
613
 
@@ -526,23 +639,34 @@ mod tests {
526
639
  drop(node);
527
640
 
528
641
  let (recovered, _) = TransactionalNode::open(&path, "node1")?;
529
- assert!(recovered.state().get_record("accounts", "old").is_none(), "delete must persist");
530
- assert!(recovered.state().get_record("accounts", "new").is_some(), "insert must persist");
531
- assert!(recovered.state().get_record("accounts", "keep").is_some(), "untouched record kept");
642
+ assert!(
643
+ recovered.state().get_record("accounts", "old").is_none(),
644
+ "delete must persist"
645
+ );
646
+ assert!(
647
+ recovered.state().get_record("accounts", "new").is_some(),
648
+ "insert must persist"
649
+ );
650
+ assert!(
651
+ recovered.state().get_record("accounts", "keep").is_some(),
652
+ "untouched record kept"
653
+ );
532
654
  Ok(())
533
655
  }
534
656
 
535
657
  /// A long mixed history under repeated crashes: no transaction may ever be
536
658
  /// partially visible, at any point, under any interleaving.
537
659
  #[test]
538
- fn a_long_crash_ridden_history_never_exposes_a_partial_transaction()
539
- -> Result<(), Box<dyn std::error::Error>> {
660
+ fn a_long_crash_ridden_history_never_exposes_a_partial_transaction(
661
+ ) -> Result<(), Box<dyn std::error::Error>> {
540
662
  let dir = TempDir::new()?;
541
663
  let path = dir.path().join("tx.log");
542
664
  let crashes = [
543
665
  CrashPoint::AfterApply,
544
666
  CrashPoint::DuringWrite { keep_percent: 30 },
545
- CrashPoint::DuringApply { after_operations: 2 },
667
+ CrashPoint::DuringApply {
668
+ after_operations: 2,
669
+ },
546
670
  CrashPoint::BeforeWrite,
547
671
  CrashPoint::AfterFsyncBeforeApply,
548
672
  CrashPoint::DuringWrite { keep_percent: 80 },
@@ -567,14 +691,26 @@ mod tests {
567
691
  // Final state: every transaction is wholly present or wholly absent,
568
692
  // and the committed count matches what the log actually holds.
569
693
  let (recovered, report) = TransactionalNode::open(&path, "node1")?;
570
- let committed_ids: HashSet<String> =
571
- report.committed.iter().map(|record| record.transaction_id.clone()).collect();
694
+ let committed_ids: HashSet<String> = report
695
+ .committed
696
+ .iter()
697
+ .map(|record| record.transaction_id.clone())
698
+ .collect();
572
699
  for record in &issued {
573
700
  let seen = recovered.executor().visible_operations(record);
574
701
  if committed_ids.contains(&record.transaction_id) {
575
- assert_eq!(seen, record.operations.len(), "{} committed but not whole", record.transaction_id);
702
+ assert_eq!(
703
+ seen,
704
+ record.operations.len(),
705
+ "{} committed but not whole",
706
+ record.transaction_id
707
+ );
576
708
  } else {
577
- assert_eq!(seen, 0, "{} not committed but visible", record.transaction_id);
709
+ assert_eq!(
710
+ seen, 0,
711
+ "{} not committed but visible",
712
+ record.transaction_id
713
+ );
578
714
  }
579
715
  }
580
716
  Ok(())
@@ -18,7 +18,6 @@
18
18
  ///
19
19
  /// Critical invariant: If A and B eventually receive all operations
20
20
  /// (in any order), they converge to the same state_hash.
21
-
22
21
  use crate::convergence::VectorClock;
23
22
  use crate::state_hash::CanonicalState;
24
23
  use std::collections::HashMap;
@@ -90,51 +89,53 @@ impl PartitionedReplica {
90
89
  }
91
90
 
92
91
  /// Apply an operation to partition A
93
- pub fn apply_to_partition_a(
94
- &mut self,
95
- operation_id: &str,
96
- record_id: &str,
97
- value: i32,
98
- ) {
92
+ pub fn apply_to_partition_a(&mut self, operation_id: &str, record_id: &str, value: i32) {
99
93
  if self.connected {
100
94
  // Both partitions receive the operation
101
95
  self.apply_to_both(operation_id, record_id, value);
102
96
  } else {
103
97
  // Only A receives it
104
- self.partition_a.apply_operation(operation_id, record_id, value);
98
+ self.partition_a
99
+ .apply_operation(operation_id, record_id, value);
105
100
  }
106
101
  }
107
102
 
108
103
  /// Apply an operation to partition B
109
- pub fn apply_to_partition_b(
110
- &mut self,
111
- operation_id: &str,
112
- record_id: &str,
113
- value: i32,
114
- ) {
104
+ pub fn apply_to_partition_b(&mut self, operation_id: &str, record_id: &str, value: i32) {
115
105
  if self.connected {
116
106
  // Both partitions receive the operation
117
107
  self.apply_to_both(operation_id, record_id, value);
118
108
  } else {
119
109
  // Only B receives it
120
- self.partition_b.apply_operation(operation_id, record_id, value);
110
+ self.partition_b
111
+ .apply_operation(operation_id, record_id, value);
121
112
  }
122
113
  }
123
114
 
124
115
  /// Apply operation to both partitions (when connected)
125
116
  fn apply_to_both(&mut self, operation_id: &str, record_id: &str, value: i32) {
126
- self.partition_a.apply_operation(operation_id, record_id, value);
127
- self.partition_b.apply_operation(operation_id, record_id, value);
117
+ self.partition_a
118
+ .apply_operation(operation_id, record_id, value);
119
+ self.partition_b
120
+ .apply_operation(operation_id, record_id, value);
128
121
  }
129
122
 
130
123
  /// Reconcile the two partitions using operation-based reconciliation
131
124
  /// Ensures all operations from both partitions are applied to both
132
125
  pub fn reconcile(&self) -> ReconciliationResult {
133
126
  // Collect all operations from both partitions
134
- let mut ops_from_a: std::collections::HashSet<_> =
135
- self.partition_a.applied_operations.iter().cloned().collect();
136
- let ops_from_b: std::collections::HashSet<_> =
137
- self.partition_b.applied_operations.iter().cloned().collect();
127
+ let mut ops_from_a: std::collections::HashSet<_> = self
128
+ .partition_a
129
+ .applied_operations
130
+ .iter()
131
+ .cloned()
132
+ .collect();
133
+ let ops_from_b: std::collections::HashSet<_> = self
134
+ .partition_b
135
+ .applied_operations
136
+ .iter()
137
+ .cloned()
138
+ .collect();
138
139
 
139
140
  // Find duplicates and unique ops
140
141
  let duplicates: Vec<String> = ops_from_a
@@ -248,9 +249,7 @@ pub struct PartitionTrace {
248
249
 
249
250
  impl PartitionDeterminismChecker {
250
251
  pub fn new() -> Self {
251
- Self {
252
- traces: Vec::new(),
253
- }
252
+ Self { traces: Vec::new() }
254
253
  }
255
254
 
256
255
  /// Record a scenario's outcome
@@ -414,7 +413,12 @@ mod tests {
414
413
  );
415
414
 
416
415
  let result = checker.verify_determinism();
417
- assert_eq!(result, DeterminismCheckResult::Satisfied { scenarios_checked: 2 });
416
+ assert_eq!(
417
+ result,
418
+ DeterminismCheckResult::Satisfied {
419
+ scenarios_checked: 2
420
+ }
421
+ );
418
422
  }
419
423
 
420
424
  #[test]
@@ -471,7 +475,13 @@ mod tests {
471
475
  assert_eq!(partitioned.partition_b.applied_operations.len(), 3);
472
476
 
473
477
  // But A still has op1 and B still has op2 (not synchronized yet)
474
- assert!(partitioned.partition_a.applied_operations.contains(&"op1".to_string()));
475
- assert!(partitioned.partition_b.applied_operations.contains(&"op2".to_string()));
478
+ assert!(partitioned
479
+ .partition_a
480
+ .applied_operations
481
+ .contains(&"op1".to_string()));
482
+ assert!(partitioned
483
+ .partition_b
484
+ .applied_operations
485
+ .contains(&"op2".to_string()));
476
486
  }
477
487
  }