@feltdb/core 0.8.4 → 0.8.6
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.
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +165 -0
- package/dist/create/server-source/Cargo.toml +9 -0
- package/dist/create/server-source/crates/feltdb/Cargo.toml +3 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +44 -44
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +12 -24
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +7 -13
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +13 -10
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +12 -9
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +78 -25
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +16 -13
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +13 -5
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +41 -22
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +33 -15
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +100 -33
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +56 -20
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +116 -41
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +186 -55
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +64 -26
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +46 -15
- package/dist/create/server-source/crates/feltdb/src/admission.rs +8 -15
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +43 -13
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +15 -42
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +65 -19
- package/dist/create/server-source/crates/feltdb/src/application.rs +113 -30
- package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +475 -140
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +17 -15
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -1
- package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +59 -15
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +266 -114
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +25 -7
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +10 -15
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +3 -11
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +14 -5
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +21 -25
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +14 -11
- package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +103 -22
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +18 -15
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +12 -8
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +1 -5
- package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +87 -23
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +3 -7
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +10 -9
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +1 -6
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +35 -38
- package/dist/create/server-source/crates/feltdb/src/lib.rs +1648 -46
- package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +4 -1
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -1
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +1 -2
- package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +107 -30
- package/dist/create/server-source/crates/feltdb/src/observability.rs +19 -6
- package/dist/create/server-source/crates/feltdb/src/operation.rs +39 -0
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +12 -11
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +9 -4
- package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +65 -18
- package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +193 -57
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +37 -27
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +38 -10
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +20 -14
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +394 -229
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +8 -6
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +11 -13
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +79 -70
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +3 -3
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +33 -11
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +6 -6
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +14 -56
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +29 -27
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +35 -17
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +8 -2
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +59 -15
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +70 -17
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +23 -6
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +12 -3
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +41 -11
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +56 -25
- package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +701 -245
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +31 -13
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +6 -8
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +5 -5
- package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +48 -18
- package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +30 -11
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +6 -3
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +4 -3
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +36 -10
- package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +516 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +13 -4
- package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +222 -0
- package/dist/create/server-source/crates/feltdb/src/state_facade.rs +82 -54
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +2 -2
- package/dist/create/server-source/crates/feltdb/src/state_model.rs +1565 -536
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +6 -3
- package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +672 -0
- package/dist/create/server-source/crates/feltdb/src/storage.rs +9 -3
- package/dist/create/server-source/crates/feltdb/src/submission.rs +5 -11
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +6 -8
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +24 -35
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +24 -8
- package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +248 -59
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +17 -20
- package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +749 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +1 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +512 -4
- package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +3 -4
- package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +132 -0
- package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +299 -0
- package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +272 -0
- package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +467 -0
- package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +309 -0
- package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +445 -0
- package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +518 -0
- package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +392 -0
- package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +436 -220
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +1916 -0
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +1878 -0
- package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +1862 -0
- package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +278 -0
- package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +2 -1
- package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +80 -25
- package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +5 -8
- package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +52 -44
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +33 -16
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +22 -7
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +41 -22
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +25 -8
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +9 -6
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +120 -69
- package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +21 -10
- package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +1365 -0
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +868 -0
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +221 -0
- package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +417 -0
- package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +338 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +1039 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +739 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +427 -0
- package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +3 -3
- package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +25 -22
- package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +1799 -0
- package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +1316 -0
- package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +53 -61
- package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +156 -61
- package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +299 -0
- package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +255 -0
- package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +249 -0
- package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +994 -0
- package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +442 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +16 -1
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +164 -13
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +695 -47
- package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +21 -0
- package/dist/studio-app/assets/{feltdb_wasm-CVQWgXO-.js → feltdb_wasm-C1VhI-U5.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-C8HXbAXb.wasm +0 -0
- package/dist/studio-app/assets/{index-DwgNAIIX.js → index-Bbos1m2U.js} +1 -1
- package/dist/studio-app/index.html +1 -1
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/workload.d.ts +2 -1
- package/dist/workload.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-CNVpvaZV.wasm +0 -0
|
@@ -20,7 +20,9 @@ mod phase_7_1_release_economics {
|
|
|
20
20
|
// Fill with blocked operations
|
|
21
21
|
for i in 0..*pending_size {
|
|
22
22
|
let mut required = VectorClock::new();
|
|
23
|
-
required
|
|
23
|
+
required
|
|
24
|
+
.clocks
|
|
25
|
+
.insert("node_a".to_string(), (1000 + i) as u64);
|
|
24
26
|
let _ = barrier.receive_operation(format!("pending_{}", i), required);
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -34,7 +36,10 @@ mod phase_7_1_release_economics {
|
|
|
34
36
|
let metrics = barrier.metrics();
|
|
35
37
|
println!(
|
|
36
38
|
"Pending={:5} | Cost={:?} | Entries examined={} | Released={}",
|
|
37
|
-
pending_size,
|
|
39
|
+
pending_size,
|
|
40
|
+
elapsed,
|
|
41
|
+
metrics.pending_entries_examined,
|
|
42
|
+
metrics.operations_released
|
|
38
43
|
);
|
|
39
44
|
}
|
|
40
45
|
}
|
|
@@ -74,14 +79,19 @@ mod phase_7_1_release_economics {
|
|
|
74
79
|
|
|
75
80
|
let metrics = barrier.metrics();
|
|
76
81
|
let released_pct = if metrics.pending_entries_examined > 0 {
|
|
77
|
-
(metrics.operations_released as f64 / metrics.pending_entries_examined as f64)
|
|
82
|
+
(metrics.operations_released as f64 / metrics.pending_entries_examined as f64)
|
|
83
|
+
* 100.0
|
|
78
84
|
} else {
|
|
79
85
|
0.0
|
|
80
86
|
};
|
|
81
87
|
|
|
82
88
|
println!(
|
|
83
89
|
"Ready%={:3} | Cost={:?} | Examined={:3} | Released={:3} | Ratio={:.1}%",
|
|
84
|
-
ready_percent,
|
|
90
|
+
ready_percent,
|
|
91
|
+
elapsed,
|
|
92
|
+
metrics.pending_entries_examined,
|
|
93
|
+
metrics.operations_released,
|
|
94
|
+
released_pct
|
|
85
95
|
);
|
|
86
96
|
}
|
|
87
97
|
}
|
|
@@ -102,7 +112,10 @@ mod phase_7_1_release_economics {
|
|
|
102
112
|
let _ = barrier.receive_operation(format!("seq_{}", i), required);
|
|
103
113
|
}
|
|
104
114
|
|
|
105
|
-
println!(
|
|
115
|
+
println!(
|
|
116
|
+
"Initial pending: {}",
|
|
117
|
+
barrier.get_pending_operations().len()
|
|
118
|
+
);
|
|
106
119
|
|
|
107
120
|
// Advance frontier multiple times
|
|
108
121
|
let start = Instant::now();
|
|
@@ -137,7 +150,10 @@ mod phase_7_1_release_economics {
|
|
|
137
150
|
|
|
138
151
|
// Analysis
|
|
139
152
|
if total_entries_examined > 500 {
|
|
140
|
-
println!(
|
|
153
|
+
println!(
|
|
154
|
+
"⚠️ WARNING: Examined {} entries for 100 pending (10 advances)",
|
|
155
|
+
total_entries_examined
|
|
156
|
+
);
|
|
141
157
|
println!(" Expected if O(m): ~100-200");
|
|
142
158
|
println!(" This suggests possible rescanning behavior");
|
|
143
159
|
} else {
|
|
@@ -159,7 +175,9 @@ mod phase_7_1_release_economics {
|
|
|
159
175
|
for i in 0..*pending_size {
|
|
160
176
|
let mut required = VectorClock::new();
|
|
161
177
|
let dependency_level = ((i * 997) % 1000) as u64 + 1;
|
|
162
|
-
required
|
|
178
|
+
required
|
|
179
|
+
.clocks
|
|
180
|
+
.insert("node_a".to_string(), dependency_level);
|
|
163
181
|
let _ = barrier.receive_operation(format!("mixed_{}", i), required);
|
|
164
182
|
}
|
|
165
183
|
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
/// - Execution interleaving
|
|
8
8
|
///
|
|
9
9
|
/// Verifies final state matches deterministic reference model.
|
|
10
|
-
|
|
11
10
|
use crate::convergence::VectorClock;
|
|
12
11
|
use crate::state_hash::StateHash;
|
|
13
12
|
use crate::transactions::*;
|
|
@@ -71,7 +70,9 @@ impl ConcurrencyFuzzer {
|
|
|
71
70
|
|
|
72
71
|
/// Generate random transaction
|
|
73
72
|
fn generate_transaction(&mut self, id: usize) -> FuzzedTransaction {
|
|
74
|
-
let num_ops = self
|
|
73
|
+
let num_ops = self
|
|
74
|
+
.rng
|
|
75
|
+
.gen_range(1..=self.config.max_operations_per_transaction);
|
|
75
76
|
let mut write_set = HashSet::new();
|
|
76
77
|
let mut read_set = HashSet::new();
|
|
77
78
|
let mut operations = Vec::new();
|
|
@@ -82,10 +83,7 @@ impl ConcurrencyFuzzer {
|
|
|
82
83
|
let base_version = StateVersion::new(vc.clone(), format!("genesis_{}", id));
|
|
83
84
|
|
|
84
85
|
for op_idx in 0..num_ops {
|
|
85
|
-
let record_id = format!(
|
|
86
|
-
"rec_{}",
|
|
87
|
-
self.rng.gen_range(0..self.config.max_records)
|
|
88
|
-
);
|
|
86
|
+
let record_id = format!("rec_{}", self.rng.gen_range(0..self.config.max_records));
|
|
89
87
|
let value = self.rng.gen_range(0..1000);
|
|
90
88
|
|
|
91
89
|
let op_id = OperationId::new("fuzzer".to_string(), ((id * 100) + op_idx) as u64);
|
|
@@ -109,10 +107,7 @@ impl ConcurrencyFuzzer {
|
|
|
109
107
|
|
|
110
108
|
// Randomly add read dependencies
|
|
111
109
|
if self.rng.gen_bool(0.3) {
|
|
112
|
-
let read_record = format!(
|
|
113
|
-
"rec_{}",
|
|
114
|
-
self.rng.gen_range(0..self.config.max_records)
|
|
115
|
-
);
|
|
110
|
+
let read_record = format!("rec_{}", self.rng.gen_range(0..self.config.max_records));
|
|
116
111
|
op = op.with_read_set(vec![StateDependency::new(
|
|
117
112
|
"fuzzing".to_string(),
|
|
118
113
|
read_record.clone(),
|
|
@@ -190,10 +185,7 @@ impl ConcurrencyFuzzer {
|
|
|
190
185
|
let conflict_graph = Self::compute_conflict_graph(workload);
|
|
191
186
|
let total_conflicts: usize = conflict_graph.iter().map(|(_, v)| v.len()).sum::<usize>() / 2; // Each edge counted twice
|
|
192
187
|
|
|
193
|
-
let concurrent_transactions = workload
|
|
194
|
-
.iter()
|
|
195
|
-
.filter(|t| t.operations.len() > 1)
|
|
196
|
-
.count();
|
|
188
|
+
let concurrent_transactions = workload.iter().filter(|t| t.operations.len() > 1).count();
|
|
197
189
|
let sequential_transactions = total_transactions - concurrent_transactions;
|
|
198
190
|
|
|
199
191
|
FuzzingStats {
|
|
@@ -422,6 +414,9 @@ mod tests {
|
|
|
422
414
|
.filter(|(tx1, tx2)| tx1.write_set != tx2.write_set)
|
|
423
415
|
.count();
|
|
424
416
|
|
|
425
|
-
assert!(
|
|
417
|
+
assert!(
|
|
418
|
+
diff_count > 0,
|
|
419
|
+
"Different seeds should produce different workloads"
|
|
420
|
+
);
|
|
426
421
|
}
|
|
427
422
|
}
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
/// 3. Consensus-Required Operations
|
|
19
19
|
/// - Must serialize through consensus (strong ordering)
|
|
20
20
|
/// - Examples: Transfer (requires mutual exclusion)
|
|
21
|
-
|
|
22
21
|
use crate::operation_algebra::{ConflictType, ConvergenceSemantic, OperationAlgebra};
|
|
23
22
|
use std::collections::HashMap;
|
|
24
23
|
|
|
@@ -245,9 +244,7 @@ pub enum NoDowngradeVerificationResult {
|
|
|
245
244
|
operations_checked: usize,
|
|
246
245
|
},
|
|
247
246
|
/// Silent downgrade detected
|
|
248
|
-
DowngradeDetected {
|
|
249
|
-
reason: String,
|
|
250
|
-
},
|
|
247
|
+
DowngradeDetected { reason: String },
|
|
251
248
|
}
|
|
252
249
|
|
|
253
250
|
/// Consensus requirement checker
|
|
@@ -307,9 +304,7 @@ pub enum ConsensusEnforcementResult {
|
|
|
307
304
|
/// No consensus operations
|
|
308
305
|
NoConsensusOps,
|
|
309
306
|
/// Consensus requirement satisfied
|
|
310
|
-
Satisfied {
|
|
311
|
-
consensus_operations: usize,
|
|
312
|
-
},
|
|
307
|
+
Satisfied { consensus_operations: usize },
|
|
313
308
|
/// Consensus requirement violated
|
|
314
309
|
Violated {
|
|
315
310
|
operations_requiring_consensus: Vec<String>,
|
|
@@ -399,10 +394,7 @@ mod tests {
|
|
|
399
394
|
let semantic = ConvergenceSemantic::DeterministicMerge;
|
|
400
395
|
|
|
401
396
|
let result = enforcer.verify_no_downgrade(&semantic);
|
|
402
|
-
assert_eq!(
|
|
403
|
-
result,
|
|
404
|
-
NoDowngradeVerificationResult::NoOperations
|
|
405
|
-
);
|
|
397
|
+
assert_eq!(result, NoDowngradeVerificationResult::NoOperations);
|
|
406
398
|
}
|
|
407
399
|
|
|
408
400
|
#[test]
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
/// Never:
|
|
19
19
|
/// - state reflects op but dedup missing (double-apply on retry)
|
|
20
20
|
/// - dedup has op but state wasn't mutated (phantom operation)
|
|
21
|
-
|
|
22
21
|
use crate::state_hash::CanonicalState;
|
|
23
22
|
use std::collections::HashSet;
|
|
24
23
|
|
|
@@ -37,7 +36,7 @@ pub enum CrashBoundaryPoint {
|
|
|
37
36
|
#[derive(Clone, Debug)]
|
|
38
37
|
pub struct CrashAtomicExecutor {
|
|
39
38
|
state: CanonicalState,
|
|
40
|
-
dedup_set: HashSet<String>,
|
|
39
|
+
dedup_set: HashSet<String>, // OperationId set
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
impl CrashAtomicExecutor {
|
|
@@ -57,7 +56,10 @@ impl CrashAtomicExecutor {
|
|
|
57
56
|
) -> Result<(), String> {
|
|
58
57
|
// Check for duplicate before applying
|
|
59
58
|
if self.dedup_set.contains(operation_id) {
|
|
60
|
-
return Err(format!(
|
|
59
|
+
return Err(format!(
|
|
60
|
+
"Operation {} already applied (dedup hit)",
|
|
61
|
+
operation_id
|
|
62
|
+
));
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
// Simulate state mutation
|
|
@@ -114,7 +116,11 @@ impl CrashAtomicExecutor {
|
|
|
114
116
|
pub fn recover(&self, crash_state: &CrashRecoveryState) -> RecoveryDecision {
|
|
115
117
|
let in_dedup = self.dedup_set.contains(&crash_state.operation_id);
|
|
116
118
|
|
|
117
|
-
match (
|
|
119
|
+
match (
|
|
120
|
+
crash_state.state_mutated,
|
|
121
|
+
crash_state.dedup_persisted,
|
|
122
|
+
in_dedup,
|
|
123
|
+
) {
|
|
118
124
|
// State mutated, dedup persisted → fully applied, don't retry
|
|
119
125
|
(true, true, true) => RecoveryDecision::AlreadyApplied,
|
|
120
126
|
|
|
@@ -266,7 +272,10 @@ mod tests {
|
|
|
266
272
|
));
|
|
267
273
|
|
|
268
274
|
let decision = executor.recover(&crash_state);
|
|
269
|
-
assert!(matches!(
|
|
275
|
+
assert!(matches!(
|
|
276
|
+
decision,
|
|
277
|
+
RecoveryDecision::PartiallyApplied { .. }
|
|
278
|
+
));
|
|
270
279
|
}
|
|
271
280
|
|
|
272
281
|
#[test]
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
///
|
|
3
3
|
/// Simulates crashes at persistence boundaries to verify recovery behavior.
|
|
4
4
|
/// Tests that recovery always produces valid state (pre-crash or post-crash, never partial).
|
|
5
|
-
|
|
6
5
|
use crate::convergence::VectorClock;
|
|
7
6
|
use crate::operation_log::MemoryOperationLog;
|
|
8
7
|
use crate::state_hash::{CanonicalState, StateHash};
|
|
@@ -34,17 +33,11 @@ pub struct ExecutionCheckpoint {
|
|
|
34
33
|
#[derive(Clone, Debug)]
|
|
35
34
|
pub enum RecoveryResult {
|
|
36
35
|
/// State is in a valid pre-transaction state
|
|
37
|
-
ValidPre {
|
|
38
|
-
checkpoint: ExecutionCheckpoint,
|
|
39
|
-
},
|
|
36
|
+
ValidPre { checkpoint: ExecutionCheckpoint },
|
|
40
37
|
/// State is in a valid post-transaction state
|
|
41
|
-
ValidPost {
|
|
42
|
-
checkpoint: ExecutionCheckpoint,
|
|
43
|
-
},
|
|
38
|
+
ValidPost { checkpoint: ExecutionCheckpoint },
|
|
44
39
|
/// State is corrupted or indeterminate
|
|
45
|
-
Invalid {
|
|
46
|
-
reason: String,
|
|
47
|
-
},
|
|
40
|
+
Invalid { reason: String },
|
|
48
41
|
}
|
|
49
42
|
|
|
50
43
|
/// Crash injection test harness
|
|
@@ -69,7 +62,11 @@ impl CrashInjectionTester {
|
|
|
69
62
|
|
|
70
63
|
/// Simulate crash before operation log append
|
|
71
64
|
pub fn crash_before_operation_append(&self, operation: &Operation) -> RecoveryResult {
|
|
72
|
-
assert_eq!(
|
|
65
|
+
assert_eq!(
|
|
66
|
+
self.operation_log.size(),
|
|
67
|
+
0,
|
|
68
|
+
"Operation should not be logged yet"
|
|
69
|
+
);
|
|
73
70
|
assert_eq!(
|
|
74
71
|
self.transition_store.size(),
|
|
75
72
|
0,
|
|
@@ -88,7 +85,9 @@ impl CrashInjectionTester {
|
|
|
88
85
|
|
|
89
86
|
/// Simulate crash after operation log append (before transition)
|
|
90
87
|
pub fn crash_after_operation_append(&self, operation: &Operation) -> RecoveryResult {
|
|
91
|
-
let op_appended = self
|
|
88
|
+
let op_appended = self
|
|
89
|
+
.operation_log
|
|
90
|
+
.contains_operation_id(&operation.operation_id);
|
|
92
91
|
|
|
93
92
|
if !op_appended {
|
|
94
93
|
return RecoveryResult::Invalid {
|
|
@@ -114,9 +113,7 @@ impl CrashInjectionTester {
|
|
|
114
113
|
|
|
115
114
|
/// Simulate crash after state transition recorded
|
|
116
115
|
pub fn crash_after_transition_recorded(&self, transaction_id: &str) -> RecoveryResult {
|
|
117
|
-
let transition = self
|
|
118
|
-
.transition_store
|
|
119
|
-
.get_by_transaction_id(transaction_id);
|
|
116
|
+
let transition = self.transition_store.get_by_transaction_id(transaction_id);
|
|
120
117
|
|
|
121
118
|
if transition.is_none() {
|
|
122
119
|
return RecoveryResult::Invalid {
|
|
@@ -279,16 +276,11 @@ mod tests {
|
|
|
279
276
|
let mut vc = VectorClock::new();
|
|
280
277
|
vc.increment("node1");
|
|
281
278
|
|
|
282
|
-
let from_version =
|
|
283
|
-
vc.clone(),
|
|
284
|
-
prev_hash.clone(),
|
|
285
|
-
);
|
|
279
|
+
let from_version =
|
|
280
|
+
crate::transactions::StateVersion::new(vc.clone(), prev_hash.clone());
|
|
286
281
|
|
|
287
282
|
vc.increment("node1");
|
|
288
|
-
let to_version = crate::transactions::StateVersion::new(
|
|
289
|
-
vc,
|
|
290
|
-
format!("hash{}", i),
|
|
291
|
-
);
|
|
283
|
+
let to_version = crate::transactions::StateVersion::new(vc, format!("hash{}", i));
|
|
292
284
|
|
|
293
285
|
let op = create_test_operation(i);
|
|
294
286
|
let transition = StateTransition::new(
|
|
@@ -298,7 +290,10 @@ mod tests {
|
|
|
298
290
|
ConsistencyContract::local(),
|
|
299
291
|
);
|
|
300
292
|
|
|
301
|
-
tester
|
|
293
|
+
tester
|
|
294
|
+
.transition_store
|
|
295
|
+
.record_transition(transition)
|
|
296
|
+
.unwrap();
|
|
302
297
|
prev_hash = format!("hash{}", i);
|
|
303
298
|
}
|
|
304
299
|
|
|
@@ -327,7 +322,8 @@ mod tests {
|
|
|
327
322
|
|
|
328
323
|
let mut vc2 = VectorClock::new();
|
|
329
324
|
vc2.increment("node1");
|
|
330
|
-
let from_version2 =
|
|
325
|
+
let from_version2 =
|
|
326
|
+
crate::transactions::StateVersion::new(vc2.clone(), "hash_broken".to_string());
|
|
331
327
|
|
|
332
328
|
vc2.increment("node1");
|
|
333
329
|
let to_version2 = crate::transactions::StateVersion::new(vc2, "hash2".to_string());
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
/// Comprehensive tests verifying that the transaction system can recover from
|
|
4
4
|
/// partial failures and crashes. Tests scenarios where operations are logged but
|
|
5
5
|
/// transitions not persisted, and vice versa.
|
|
6
|
-
|
|
7
6
|
use crate::convergence::VectorClock;
|
|
8
7
|
use crate::operation_log::MemoryOperationLog;
|
|
9
8
|
use crate::state_transition_store::MemoryStateTransitionStore;
|
|
@@ -68,7 +67,12 @@ mod crash_recovery {
|
|
|
68
67
|
|
|
69
68
|
let op_ids: Vec<_> = (1..=5)
|
|
70
69
|
.map(|i| {
|
|
71
|
-
let op = create_test_operation(
|
|
70
|
+
let op = create_test_operation(
|
|
71
|
+
i,
|
|
72
|
+
base_version.clone(),
|
|
73
|
+
&format!("rec{}", i),
|
|
74
|
+
i as i32 * 10,
|
|
75
|
+
);
|
|
72
76
|
let op_id = op.operation_id.clone();
|
|
73
77
|
log.append(op).unwrap();
|
|
74
78
|
op_id
|
|
@@ -176,7 +180,8 @@ mod crash_recovery {
|
|
|
176
180
|
let base_version = create_version(1, "hash0");
|
|
177
181
|
|
|
178
182
|
for i in 1..=10 {
|
|
179
|
-
let op =
|
|
183
|
+
let op =
|
|
184
|
+
create_test_operation(i, base_version.clone(), &format!("rec{}", i), i as i32 * 10);
|
|
180
185
|
log.append(op).unwrap();
|
|
181
186
|
}
|
|
182
187
|
|
|
@@ -256,12 +261,8 @@ mod crash_recovery {
|
|
|
256
261
|
assert!(log.contains_operation_id(&op_id));
|
|
257
262
|
|
|
258
263
|
let ops = vec![create_test_operation(2, base_version.clone(), "rec2", 200)];
|
|
259
|
-
let transition =
|
|
260
|
-
base_version,
|
|
261
|
-
ops,
|
|
262
|
-
to_version,
|
|
263
|
-
ConsistencyContract::local(),
|
|
264
|
-
);
|
|
264
|
+
let transition =
|
|
265
|
+
StateTransition::new(base_version, ops, to_version, ConsistencyContract::local());
|
|
265
266
|
|
|
266
267
|
store.record_transition(transition).unwrap();
|
|
267
268
|
assert_eq!(store.size(), 1);
|
|
@@ -275,7 +276,8 @@ mod crash_recovery {
|
|
|
275
276
|
let base_version = create_version(1, "hash0");
|
|
276
277
|
|
|
277
278
|
for i in 1..=5 {
|
|
278
|
-
let op =
|
|
279
|
+
let op =
|
|
280
|
+
create_test_operation(i, base_version.clone(), &format!("rec{}", i), i as i32 * 10);
|
|
279
281
|
log.append(op.clone()).unwrap();
|
|
280
282
|
|
|
281
283
|
let to_version = create_version(i as u64 + 1, &format!("hash{}", i));
|
|
@@ -304,7 +306,8 @@ mod crash_recovery {
|
|
|
304
306
|
let base_version = create_version(1, "hash0");
|
|
305
307
|
|
|
306
308
|
for i in 1..=10 {
|
|
307
|
-
let op =
|
|
309
|
+
let op =
|
|
310
|
+
create_test_operation(i, base_version.clone(), &format!("rec{}", i), i as i32 * 10);
|
|
308
311
|
log.append(op).unwrap();
|
|
309
312
|
}
|
|
310
313
|
|
|
@@ -55,7 +55,12 @@ mod tests {
|
|
|
55
55
|
/// Build an envelope with an explicitly chosen envelope sequence and an
|
|
56
56
|
/// explicitly chosen causal clock, so the two counters can be moved
|
|
57
57
|
/// independently -- which is exactly what a restart of the origin does.
|
|
58
|
-
fn envelope(
|
|
58
|
+
fn envelope(
|
|
59
|
+
origin: &str,
|
|
60
|
+
sequence: u64,
|
|
61
|
+
clock: &[(&str, u64)],
|
|
62
|
+
payload: &str,
|
|
63
|
+
) -> TransactionEnvelope {
|
|
59
64
|
let mut fields = HashMap::new();
|
|
60
65
|
fields.insert("record".to_string(), serde_json::json!(payload));
|
|
61
66
|
let operation = Operation::new(
|
|
@@ -112,7 +117,12 @@ mod tests {
|
|
|
112
117
|
for position in 1..=5u64 {
|
|
113
118
|
let outcome = executor
|
|
114
119
|
.receive_replicated(
|
|
115
|
-
message(envelope(
|
|
120
|
+
message(envelope(
|
|
121
|
+
"A",
|
|
122
|
+
position,
|
|
123
|
+
&[("A", position)],
|
|
124
|
+
&format!("r{position}"),
|
|
125
|
+
)),
|
|
116
126
|
zero_version(),
|
|
117
127
|
)
|
|
118
128
|
.expect("receive");
|
|
@@ -143,7 +153,10 @@ mod tests {
|
|
|
143
153
|
|
|
144
154
|
// B's envelope claims to have seen A through position 5.
|
|
145
155
|
let outcome = executor
|
|
146
|
-
.receive_replicated(
|
|
156
|
+
.receive_replicated(
|
|
157
|
+
message(envelope("B", 1, &[("A", 5), ("B", 1)], "b1")),
|
|
158
|
+
zero_version(),
|
|
159
|
+
)
|
|
147
160
|
.expect("receive");
|
|
148
161
|
|
|
149
162
|
assert!(
|
|
@@ -164,7 +177,12 @@ mod tests {
|
|
|
164
177
|
for position in 1..=3u64 {
|
|
165
178
|
executor
|
|
166
179
|
.receive_replicated(
|
|
167
|
-
message(envelope(
|
|
180
|
+
message(envelope(
|
|
181
|
+
"A",
|
|
182
|
+
position,
|
|
183
|
+
&[("A", position)],
|
|
184
|
+
&format!("a{position}"),
|
|
185
|
+
)),
|
|
168
186
|
zero_version(),
|
|
169
187
|
)
|
|
170
188
|
.expect("receive");
|
|
@@ -173,7 +191,10 @@ mod tests {
|
|
|
173
191
|
|
|
174
192
|
// B has seen A through 3, which this replica has too.
|
|
175
193
|
executor
|
|
176
|
-
.receive_replicated(
|
|
194
|
+
.receive_replicated(
|
|
195
|
+
message(envelope("B", 1, &[("A", 3), ("B", 1)], "b1")),
|
|
196
|
+
zero_version(),
|
|
197
|
+
)
|
|
177
198
|
.expect("receive");
|
|
178
199
|
|
|
179
200
|
assert_eq!(
|
|
@@ -193,12 +214,27 @@ mod tests {
|
|
|
193
214
|
|
|
194
215
|
// Two origins, interleaved, plus an out-of-order arrival that is held
|
|
195
216
|
// and later released -- so the applied set is not simply arrival order.
|
|
196
|
-
executor
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
executor
|
|
217
|
+
executor
|
|
218
|
+
.receive_replicated(message(envelope("A", 1, &[("A", 1)], "a1")), zero_version())
|
|
219
|
+
.unwrap();
|
|
220
|
+
executor
|
|
221
|
+
.receive_replicated(message(envelope("A", 3, &[("A", 3)], "a3")), zero_version())
|
|
222
|
+
.unwrap();
|
|
223
|
+
executor
|
|
224
|
+
.receive_replicated(message(envelope("A", 2, &[("A", 2)], "a2")), zero_version())
|
|
225
|
+
.unwrap();
|
|
226
|
+
executor
|
|
227
|
+
.receive_replicated(
|
|
228
|
+
message(envelope("B", 1, &[("A", 3), ("B", 1)], "b1")),
|
|
229
|
+
zero_version(),
|
|
230
|
+
)
|
|
231
|
+
.unwrap();
|
|
200
232
|
|
|
201
|
-
assert_eq!(
|
|
233
|
+
assert_eq!(
|
|
234
|
+
executor.pending_causal_count(),
|
|
235
|
+
0,
|
|
236
|
+
"everything was released"
|
|
237
|
+
);
|
|
202
238
|
assert_eq!(executor.causal_frontier().get("A"), 3);
|
|
203
239
|
assert_eq!(executor.causal_frontier().get("B"), 1);
|
|
204
240
|
|
|
@@ -206,7 +242,12 @@ mod tests {
|
|
|
206
242
|
// frontier already covers its clock position. So a frontier test would
|
|
207
243
|
// answer "already applied" for all of them.
|
|
208
244
|
let frontier = executor.causal_frontier().clone();
|
|
209
|
-
let log = executor
|
|
245
|
+
let log = executor
|
|
246
|
+
.operation_log
|
|
247
|
+
.as_ref()
|
|
248
|
+
.expect("log")
|
|
249
|
+
.load_all()
|
|
250
|
+
.expect("load");
|
|
210
251
|
for envelope in &log {
|
|
211
252
|
let origin = &envelope.originating_node;
|
|
212
253
|
let position = envelope.vector_clock.get(origin);
|
|
@@ -238,7 +279,12 @@ mod tests {
|
|
|
238
279
|
for position in 1..=3u64 {
|
|
239
280
|
executor
|
|
240
281
|
.receive_replicated(
|
|
241
|
-
message(envelope(
|
|
282
|
+
message(envelope(
|
|
283
|
+
"A",
|
|
284
|
+
position,
|
|
285
|
+
&[("A", position)],
|
|
286
|
+
&format!("a{position}"),
|
|
287
|
+
)),
|
|
242
288
|
zero_version(),
|
|
243
289
|
)
|
|
244
290
|
.expect("receive");
|
|
@@ -253,13 +299,21 @@ mod tests {
|
|
|
253
299
|
.expect("executor");
|
|
254
300
|
recovered.load_from_disk(path).expect("recover");
|
|
255
301
|
|
|
256
|
-
assert_eq!(
|
|
302
|
+
assert_eq!(
|
|
303
|
+
recovered.causal_frontier().get("A"),
|
|
304
|
+
3,
|
|
305
|
+
"the frontier is rebuilt from the log"
|
|
306
|
+
);
|
|
257
307
|
|
|
258
308
|
// A retransmission of an already-applied operation after the restart.
|
|
259
309
|
let again = recovered
|
|
260
310
|
.receive_replicated(message(envelope("A", 2, &[("A", 2)], "a2")), zero_version())
|
|
261
311
|
.expect("receive");
|
|
262
|
-
assert_eq!(
|
|
312
|
+
assert_eq!(
|
|
313
|
+
again,
|
|
314
|
+
ReceiveOutcome::AlreadyKnown,
|
|
315
|
+
"a duplicate after restart is refused"
|
|
316
|
+
);
|
|
263
317
|
}
|
|
264
318
|
|
|
265
319
|
// ---------------------------------------------------------------------
|
|
@@ -273,13 +327,22 @@ mod tests {
|
|
|
273
327
|
for position in 1..=200u64 {
|
|
274
328
|
executor
|
|
275
329
|
.receive_replicated(
|
|
276
|
-
message(envelope(
|
|
330
|
+
message(envelope(
|
|
331
|
+
"A",
|
|
332
|
+
position,
|
|
333
|
+
&[("A", position)],
|
|
334
|
+
&format!("a{position}"),
|
|
335
|
+
)),
|
|
277
336
|
zero_version(),
|
|
278
337
|
)
|
|
279
338
|
.expect("receive");
|
|
280
339
|
}
|
|
281
340
|
|
|
282
|
-
assert_eq!(
|
|
341
|
+
assert_eq!(
|
|
342
|
+
executor.pending_causal_count(),
|
|
343
|
+
0,
|
|
344
|
+
"the bounded population is empty"
|
|
345
|
+
);
|
|
283
346
|
assert_eq!(executor.deferred_causal_count(), 0);
|
|
284
347
|
assert_eq!(
|
|
285
348
|
executor.envelopes_seen.len(),
|
|
@@ -322,7 +385,10 @@ mod tests {
|
|
|
322
385
|
|
|
323
386
|
// Before the origin restarts: A's first operation, sequence 1, clock 1.
|
|
324
387
|
let first = executor
|
|
325
|
-
.receive_replicated(
|
|
388
|
+
.receive_replicated(
|
|
389
|
+
message(envelope("A", 1, &[("A", 1)], "before")),
|
|
390
|
+
zero_version(),
|
|
391
|
+
)
|
|
326
392
|
.expect("receive");
|
|
327
393
|
assert!(matches!(first, ReceiveOutcome::Applied { .. }));
|
|
328
394
|
|
|
@@ -347,7 +413,9 @@ mod tests {
|
|
|
347
413
|
);
|
|
348
414
|
let state = executor.canonical_state().expect("state");
|
|
349
415
|
assert!(
|
|
350
|
-
!state
|
|
416
|
+
!state
|
|
417
|
+
.records
|
|
418
|
+
.contains_key(&("items".to_string(), "after".to_string())),
|
|
351
419
|
"and the write is simply absent: no error, no divergence signal, no retry"
|
|
352
420
|
);
|
|
353
421
|
|
|
@@ -374,19 +442,32 @@ mod tests {
|
|
|
374
442
|
// before it died.
|
|
375
443
|
let (mut executor, _dir) = receiver();
|
|
376
444
|
executor
|
|
377
|
-
.receive_replicated(
|
|
445
|
+
.receive_replicated(
|
|
446
|
+
message(envelope("A", 1, &[("A", 1)], "one")),
|
|
447
|
+
zero_version(),
|
|
448
|
+
)
|
|
378
449
|
.expect("receive");
|
|
379
450
|
|
|
380
451
|
// Origin restarts after one operation and commits two more. Sequences
|
|
381
452
|
// 1 and 2; clock positions 2 and 3.
|
|
382
453
|
let dropped = executor
|
|
383
|
-
.receive_replicated(
|
|
454
|
+
.receive_replicated(
|
|
455
|
+
message(envelope("A", 1, &[("A", 2)], "two")),
|
|
456
|
+
zero_version(),
|
|
457
|
+
)
|
|
384
458
|
.expect("receive");
|
|
385
459
|
let admitted = executor
|
|
386
|
-
.receive_replicated(
|
|
460
|
+
.receive_replicated(
|
|
461
|
+
message(envelope("A", 2, &[("A", 3)], "three")),
|
|
462
|
+
zero_version(),
|
|
463
|
+
)
|
|
387
464
|
.expect("receive");
|
|
388
465
|
|
|
389
|
-
assert_eq!(
|
|
466
|
+
assert_eq!(
|
|
467
|
+
dropped,
|
|
468
|
+
ReceiveOutcome::AlreadyKnown,
|
|
469
|
+
"the first collides and is lost"
|
|
470
|
+
);
|
|
390
471
|
assert!(
|
|
391
472
|
matches!(admitted, ReceiveOutcome::PendingDependencies { .. }),
|
|
392
473
|
"the second does not collide, is admitted, and is then held forever waiting on \
|