@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
|
@@ -13,7 +13,7 @@ mod tests {
|
|
|
13
13
|
use crate::distributed_transactions::TransactionEnvelope;
|
|
14
14
|
use crate::durable_operation_log::OperationLog;
|
|
15
15
|
use crate::state_hash::StateHash;
|
|
16
|
-
use crate::transactions::{
|
|
16
|
+
use crate::transactions::{ConsistencyContract, ConsistencyLevel};
|
|
17
17
|
use tempfile::TempDir;
|
|
18
18
|
|
|
19
19
|
fn create_test_envelope(node: &str, sequence: u64) -> TransactionEnvelope {
|
|
@@ -68,8 +68,7 @@ mod tests {
|
|
|
68
68
|
/// 2. Recover log from disk
|
|
69
69
|
/// 3. Verify all 3 recovered in order
|
|
70
70
|
#[test]
|
|
71
|
-
fn test_durable_log_multiple_envelopes_in_order(
|
|
72
|
-
) -> Result<(), Box<dyn std::error::Error>> {
|
|
71
|
+
fn test_durable_log_multiple_envelopes_in_order() -> Result<(), Box<dyn std::error::Error>> {
|
|
73
72
|
let temp_dir = TempDir::new()?;
|
|
74
73
|
let log_path = temp_dir.path().join("operations.log");
|
|
75
74
|
|
|
@@ -89,8 +88,11 @@ mod tests {
|
|
|
89
88
|
|
|
90
89
|
assert_eq!(loaded.len(), 3, "Should have recovered 3 envelopes");
|
|
91
90
|
for (i, envelope) in loaded.iter().enumerate() {
|
|
92
|
-
assert_eq!(
|
|
93
|
-
|
|
91
|
+
assert_eq!(
|
|
92
|
+
envelope.envelope_id.sequence,
|
|
93
|
+
(i + 1) as u64,
|
|
94
|
+
"Envelopes should be in order"
|
|
95
|
+
);
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -110,7 +112,7 @@ mod tests {
|
|
|
110
112
|
|
|
111
113
|
for seq in 1..=100 {
|
|
112
114
|
let envelope = create_test_envelope("nodeA", seq);
|
|
113
|
-
log.append(&envelope)?;
|
|
115
|
+
log.append(&envelope)?; // Each append must fsync
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
// Verify all 100 can be loaded (fsync guarantee)
|
|
@@ -13,7 +13,7 @@ mod tests {
|
|
|
13
13
|
use crate::distributed_transactions::{DistributedTransactionExecutor, TransactionEnvelope};
|
|
14
14
|
use crate::durable_dedup_set::DeduplicationSet;
|
|
15
15
|
use crate::state_hash::StateHash;
|
|
16
|
-
use crate::transactions::{
|
|
16
|
+
use crate::transactions::{ConsistencyContract, ConsistencyLevel};
|
|
17
17
|
use tempfile::TempDir;
|
|
18
18
|
|
|
19
19
|
fn create_test_envelope(node: &str, sequence: u64) -> TransactionEnvelope {
|
|
@@ -79,12 +79,11 @@ mod tests {
|
|
|
79
79
|
|
|
80
80
|
// Step 1 & 2: Create executor with log and simulate loading persisted state
|
|
81
81
|
{
|
|
82
|
-
let executor =
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
)?;
|
|
82
|
+
let executor = DistributedTransactionExecutor::with_log(
|
|
83
|
+
"nodeA".to_string(),
|
|
84
|
+
StateHash::from_hex("state_v0".to_string()),
|
|
85
|
+
Some(log_path.clone()),
|
|
86
|
+
)?;
|
|
88
87
|
|
|
89
88
|
// Verify executor has dedup set configured
|
|
90
89
|
let node_id = executor.get_local_node_id();
|
|
@@ -93,12 +92,11 @@ mod tests {
|
|
|
93
92
|
|
|
94
93
|
// Step 3: Create another executor and load from disk
|
|
95
94
|
{
|
|
96
|
-
let mut executor =
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
)?;
|
|
95
|
+
let mut executor = DistributedTransactionExecutor::with_log(
|
|
96
|
+
"nodeA".to_string(),
|
|
97
|
+
StateHash::from_hex("state_v0".to_string()),
|
|
98
|
+
Some(log_path.clone()),
|
|
99
|
+
)?;
|
|
102
100
|
|
|
103
101
|
// This should set up both operation log and dedup set
|
|
104
102
|
let recovered = executor.load_from_disk(log_path)?;
|
|
@@ -144,12 +144,11 @@ mod tests {
|
|
|
144
144
|
let log_path = temp_dir.path().join("operations.log");
|
|
145
145
|
|
|
146
146
|
// Setup: Node A and B both have operations 1-3
|
|
147
|
-
let mut node_a =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
)?;
|
|
147
|
+
let mut node_a = DistributedTransactionExecutor::with_log(
|
|
148
|
+
"nodeA".to_string(),
|
|
149
|
+
StateHash::from_hex("hash0".to_string()),
|
|
150
|
+
Some(log_path.clone()),
|
|
151
|
+
)?;
|
|
153
152
|
|
|
154
153
|
let node_b = DistributedTransactionExecutor::new(
|
|
155
154
|
"nodeB".to_string(),
|
|
@@ -157,7 +156,10 @@ mod tests {
|
|
|
157
156
|
);
|
|
158
157
|
|
|
159
158
|
// Simulate: Both nodes have same state
|
|
160
|
-
node_a.register_replica(
|
|
159
|
+
node_a.register_replica(
|
|
160
|
+
"nodeB".to_string(),
|
|
161
|
+
StateHash::from_hex("hash0".to_string()),
|
|
162
|
+
);
|
|
161
163
|
|
|
162
164
|
// Verify convergence
|
|
163
165
|
assert!(node_a.check_convergence());
|
|
@@ -183,12 +185,11 @@ mod tests {
|
|
|
183
185
|
let log_path = temp_dir.path().join("operations.log");
|
|
184
186
|
|
|
185
187
|
// Setup: Node A (restarted, missing op), Node B (authoritative)
|
|
186
|
-
let mut node_a =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
)?;
|
|
188
|
+
let mut node_a = DistributedTransactionExecutor::with_log(
|
|
189
|
+
"nodeA".to_string(),
|
|
190
|
+
StateHash::from_hex("hash0".to_string()),
|
|
191
|
+
Some(log_path.clone()),
|
|
192
|
+
)?;
|
|
192
193
|
|
|
193
194
|
let mut node_b = DistributedTransactionExecutor::new(
|
|
194
195
|
"nodeB".to_string(),
|
|
@@ -196,8 +197,14 @@ mod tests {
|
|
|
196
197
|
);
|
|
197
198
|
|
|
198
199
|
// Register each other
|
|
199
|
-
node_a.register_replica(
|
|
200
|
-
|
|
200
|
+
node_a.register_replica(
|
|
201
|
+
"nodeB".to_string(),
|
|
202
|
+
StateHash::from_hex("hash0".to_string()),
|
|
203
|
+
);
|
|
204
|
+
node_b.register_replica(
|
|
205
|
+
"nodeA".to_string(),
|
|
206
|
+
StateHash::from_hex("hash0".to_string()),
|
|
207
|
+
);
|
|
201
208
|
|
|
202
209
|
// Peer (B) has operation 1
|
|
203
210
|
let missing_op = create_test_envelope("nodeB", 1);
|
|
@@ -222,20 +229,25 @@ mod tests {
|
|
|
222
229
|
let temp_dir = TempDir::new()?;
|
|
223
230
|
let log_path = temp_dir.path().join("operations.log");
|
|
224
231
|
|
|
225
|
-
let mut node_a =
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
)?;
|
|
232
|
+
let mut node_a = DistributedTransactionExecutor::with_log(
|
|
233
|
+
"nodeA".to_string(),
|
|
234
|
+
StateHash::from_hex("hash0".to_string()),
|
|
235
|
+
Some(log_path.clone()),
|
|
236
|
+
)?;
|
|
231
237
|
|
|
232
238
|
let mut node_b = DistributedTransactionExecutor::new(
|
|
233
239
|
"nodeB".to_string(),
|
|
234
240
|
StateHash::from_hex("hash0".to_string()),
|
|
235
241
|
);
|
|
236
242
|
|
|
237
|
-
node_a.register_replica(
|
|
238
|
-
|
|
243
|
+
node_a.register_replica(
|
|
244
|
+
"nodeB".to_string(),
|
|
245
|
+
StateHash::from_hex("hash0".to_string()),
|
|
246
|
+
);
|
|
247
|
+
node_b.register_replica(
|
|
248
|
+
"nodeA".to_string(),
|
|
249
|
+
StateHash::from_hex("hash0".to_string()),
|
|
250
|
+
);
|
|
239
251
|
|
|
240
252
|
// Peer has operations 1-5
|
|
241
253
|
let missing_ops: Vec<_> = (1..=5)
|
|
@@ -261,12 +273,11 @@ mod tests {
|
|
|
261
273
|
let temp_dir = TempDir::new()?;
|
|
262
274
|
let log_path = temp_dir.path().join("operations.log");
|
|
263
275
|
|
|
264
|
-
let mut node_a =
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
)?;
|
|
276
|
+
let mut node_a = DistributedTransactionExecutor::with_log(
|
|
277
|
+
"nodeA".to_string(),
|
|
278
|
+
StateHash::from_hex("hash0".to_string()),
|
|
279
|
+
Some(log_path.clone()),
|
|
280
|
+
)?;
|
|
270
281
|
|
|
271
282
|
let node_b = DistributedTransactionExecutor::new(
|
|
272
283
|
"nodeB".to_string(),
|
|
@@ -275,9 +286,7 @@ mod tests {
|
|
|
275
286
|
|
|
276
287
|
// Pre-populate node_a's seen set to simulate prior durability
|
|
277
288
|
let envelope = create_test_envelope("nodeB", 1);
|
|
278
|
-
node_a
|
|
279
|
-
.envelopes_seen
|
|
280
|
-
.insert(envelope.envelope_id.clone());
|
|
289
|
+
node_a.envelopes_seen.insert(envelope.envelope_id.clone());
|
|
281
290
|
|
|
282
291
|
// Recovery tries to apply same envelope (duplicate)
|
|
283
292
|
let mut recovery = RecoveryOrchestrator::new(node_a, "nodeB".to_string());
|
|
@@ -285,7 +294,10 @@ mod tests {
|
|
|
285
294
|
recovery.apply_missing_operations(vec![envelope.clone()])?;
|
|
286
295
|
|
|
287
296
|
// Should not error on duplicate—dedup handles it
|
|
288
|
-
assert!(recovery
|
|
297
|
+
assert!(recovery
|
|
298
|
+
.executor
|
|
299
|
+
.envelopes_seen
|
|
300
|
+
.contains(&envelope.envelope_id));
|
|
289
301
|
recovery.finalize_recovery()?;
|
|
290
302
|
|
|
291
303
|
assert_eq!(recovery.get_state(), RecoveryState::Ready);
|
|
@@ -303,12 +315,11 @@ mod tests {
|
|
|
303
315
|
let log_path = temp_dir.path().join("operations.log");
|
|
304
316
|
|
|
305
317
|
// First recovery attempt (simulated interruption)
|
|
306
|
-
let node_a_attempt1 =
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
)?;
|
|
318
|
+
let node_a_attempt1 = DistributedTransactionExecutor::with_log(
|
|
319
|
+
"nodeA".to_string(),
|
|
320
|
+
StateHash::from_hex("hash0".to_string()),
|
|
321
|
+
Some(log_path.clone()),
|
|
322
|
+
)?;
|
|
312
323
|
|
|
313
324
|
let node_b = DistributedTransactionExecutor::new(
|
|
314
325
|
"nodeB".to_string(),
|
|
@@ -325,12 +336,11 @@ mod tests {
|
|
|
325
336
|
recovery1.apply_missing_operations(ops[0..2].to_vec())?;
|
|
326
337
|
|
|
327
338
|
// Second recovery attempt (after restart)
|
|
328
|
-
let mut node_a_attempt2 =
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
)?;
|
|
339
|
+
let mut node_a_attempt2 = DistributedTransactionExecutor::with_log(
|
|
340
|
+
"nodeA".to_string(),
|
|
341
|
+
StateHash::from_hex("hash0".to_string()),
|
|
342
|
+
Some(log_path.clone()),
|
|
343
|
+
)?;
|
|
334
344
|
node_a_attempt2
|
|
335
345
|
.envelopes_seen
|
|
336
346
|
.extend(recovery1.executor.envelopes_seen.clone());
|
|
@@ -355,12 +365,11 @@ mod tests {
|
|
|
355
365
|
let temp_dir = TempDir::new()?;
|
|
356
366
|
let log_path = temp_dir.path().join("operations.log");
|
|
357
367
|
|
|
358
|
-
let node_a =
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
)?;
|
|
368
|
+
let node_a = DistributedTransactionExecutor::with_log(
|
|
369
|
+
"nodeA".to_string(),
|
|
370
|
+
StateHash::from_hex("hash0".to_string()),
|
|
371
|
+
Some(log_path.clone()),
|
|
372
|
+
)?;
|
|
364
373
|
|
|
365
374
|
// No peer available - simulate peer_state being unavailable
|
|
366
375
|
let mut recovery = RecoveryOrchestrator::new(node_a, "unavailable_peer".to_string());
|
|
@@ -398,16 +407,18 @@ mod tests {
|
|
|
398
407
|
);
|
|
399
408
|
|
|
400
409
|
// Create restarted node with same initial hash
|
|
401
|
-
let mut local =
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
)?;
|
|
410
|
+
let mut local = DistributedTransactionExecutor::with_log(
|
|
411
|
+
"local".to_string(),
|
|
412
|
+
StateHash::from_hex("hash0".to_string()),
|
|
413
|
+
Some(log_path.clone()),
|
|
414
|
+
)?;
|
|
407
415
|
|
|
408
416
|
// Register each other with same state
|
|
409
417
|
local.register_replica("peer".to_string(), StateHash::from_hex("hash0".to_string()));
|
|
410
|
-
peer.register_replica(
|
|
418
|
+
peer.register_replica(
|
|
419
|
+
"local".to_string(),
|
|
420
|
+
StateHash::from_hex("hash0".to_string()),
|
|
421
|
+
);
|
|
411
422
|
|
|
412
423
|
// Reconcile: Both have same state
|
|
413
424
|
let mut recovery = RecoveryOrchestrator::new(local, "peer".to_string());
|
|
@@ -439,24 +450,22 @@ mod tests {
|
|
|
439
450
|
|
|
440
451
|
// Phase 1: Initial execution and persistence
|
|
441
452
|
{
|
|
442
|
-
let executor =
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
)?;
|
|
453
|
+
let executor = DistributedTransactionExecutor::with_log(
|
|
454
|
+
"nodeA".to_string(),
|
|
455
|
+
StateHash::from_hex("hash0".to_string()),
|
|
456
|
+
Some(log_path.clone()),
|
|
457
|
+
)?;
|
|
448
458
|
|
|
449
459
|
let _node_id = executor.get_local_node_id();
|
|
450
460
|
assert_eq!(_node_id, "nodeA");
|
|
451
461
|
}
|
|
452
462
|
|
|
453
463
|
// Phase 2: "Crash" and restart
|
|
454
|
-
let mut recovered_executor =
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
)?;
|
|
464
|
+
let mut recovered_executor = DistributedTransactionExecutor::with_log(
|
|
465
|
+
"nodeA".to_string(),
|
|
466
|
+
StateHash::from_hex("hash0".to_string()),
|
|
467
|
+
Some(log_path.clone()),
|
|
468
|
+
)?;
|
|
460
469
|
|
|
461
470
|
// Phase 3: Load from disk (reconstruct dedup from operation log)
|
|
462
471
|
let count = recovered_executor.load_from_disk(log_path)?;
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
#[cfg(test)]
|
|
11
11
|
mod tests {
|
|
12
|
-
use crate::durable_operation_log::OperationLog;
|
|
13
12
|
use crate::distributed_transactions::TransactionEnvelope;
|
|
13
|
+
use crate::durable_operation_log::OperationLog;
|
|
14
14
|
use crate::state_hash::StateHash;
|
|
15
|
-
use crate::transactions::{
|
|
15
|
+
use crate::transactions::{ConsistencyContract, ConsistencyLevel};
|
|
16
16
|
use tempfile::TempDir;
|
|
17
17
|
|
|
18
18
|
fn create_test_envelope(node: &str, sequence: u64) -> TransactionEnvelope {
|
|
@@ -95,7 +95,7 @@ mod tests {
|
|
|
95
95
|
let mut log = OperationLog::new(&log_path)?;
|
|
96
96
|
let envelope = create_test_envelope("nodeA", 1);
|
|
97
97
|
log.append(&envelope)?; // fsync ensures this is durable
|
|
98
|
-
|
|
98
|
+
// ... hypothetical crash here ...
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
// Scenario B: Restart and recover
|
|
@@ -66,8 +66,15 @@ mod tests {
|
|
|
66
66
|
|
|
67
67
|
// Verify all standard operations are registered
|
|
68
68
|
let standard_ops = vec![
|
|
69
|
-
"SetField",
|
|
70
|
-
"
|
|
69
|
+
"SetField",
|
|
70
|
+
"Increment",
|
|
71
|
+
"AddToSet",
|
|
72
|
+
"RemoveFromSet",
|
|
73
|
+
"Create",
|
|
74
|
+
"Delete",
|
|
75
|
+
"Transfer",
|
|
76
|
+
"Append",
|
|
77
|
+
"Noop",
|
|
71
78
|
];
|
|
72
79
|
|
|
73
80
|
for op_type in &standard_ops {
|
|
@@ -125,7 +132,10 @@ mod tests {
|
|
|
125
132
|
create_test_operation(3, "records", "rec3", 30),
|
|
126
133
|
];
|
|
127
134
|
let schedules = PermutationScheduler::minimal_schedules(&ops);
|
|
128
|
-
println!(
|
|
135
|
+
println!(
|
|
136
|
+
"Gate 1: Generated {} permutation schedules",
|
|
137
|
+
schedules.len()
|
|
138
|
+
);
|
|
129
139
|
|
|
130
140
|
// Integration insight:
|
|
131
141
|
// - Gate 1 creates permutations
|
|
@@ -148,11 +158,14 @@ mod tests {
|
|
|
148
158
|
"Increment should be commutative"
|
|
149
159
|
);
|
|
150
160
|
assert_eq!(
|
|
151
|
-
increment_algebra.merge_function_description,
|
|
161
|
+
increment_algebra.merge_function_description,
|
|
162
|
+
"Counter CRDT: sum(all_increments)"
|
|
152
163
|
);
|
|
153
164
|
|
|
154
|
-
println!(
|
|
155
|
-
|
|
165
|
+
println!(
|
|
166
|
+
"✓ Increment declared as commutative (algebra: {})",
|
|
167
|
+
increment_algebra.merge_function_description
|
|
168
|
+
);
|
|
156
169
|
}
|
|
157
170
|
|
|
158
171
|
#[test]
|
|
@@ -165,7 +178,8 @@ mod tests {
|
|
|
165
178
|
// SetField uses LWW (Last-Write-Wins) for determinism
|
|
166
179
|
assert!(setfield_algebra.is_deterministic());
|
|
167
180
|
assert_eq!(
|
|
168
|
-
setfield_algebra.merge_function_description,
|
|
181
|
+
setfield_algebra.merge_function_description,
|
|
182
|
+
"LWW: max(timestamp, value)"
|
|
169
183
|
);
|
|
170
184
|
|
|
171
185
|
println!(
|
|
@@ -209,7 +223,9 @@ mod tests {
|
|
|
209
223
|
// Test that convergence holds as operation count increases
|
|
210
224
|
for op_count in [1, 3, 5, 10].iter() {
|
|
211
225
|
let ops: Vec<_> = (1..=*op_count)
|
|
212
|
-
.map(|seq|
|
|
226
|
+
.map(|seq| {
|
|
227
|
+
create_test_operation(seq, "records", &format!("rec{}", seq), seq as i32 * 10)
|
|
228
|
+
})
|
|
213
229
|
.collect();
|
|
214
230
|
|
|
215
231
|
let simulator = MultiNodeConvergenceSimulator::new(3);
|
|
@@ -263,8 +279,11 @@ mod tests {
|
|
|
263
279
|
];
|
|
264
280
|
let simulator = MultiNodeConvergenceSimulator::new(3);
|
|
265
281
|
let result = simulator.test_minimal_convergence(&ops);
|
|
266
|
-
println!(
|
|
267
|
-
|
|
282
|
+
println!(
|
|
283
|
+
" ✓ {} nodes executed {} operations",
|
|
284
|
+
result.node_results.len(),
|
|
285
|
+
ops.len()
|
|
286
|
+
);
|
|
268
287
|
println!(" ✓ All converged to: {}", result.canonical_hash);
|
|
269
288
|
|
|
270
289
|
println!("\nGate 2: Operation Algebra Declaration");
|
|
@@ -272,7 +291,10 @@ mod tests {
|
|
|
272
291
|
let algebras = registry.all_algebras();
|
|
273
292
|
println!(" ✓ {} operation types declared", algebras.len());
|
|
274
293
|
for alg in algebras.iter().take(5) {
|
|
275
|
-
println!(
|
|
294
|
+
println!(
|
|
295
|
+
" - {}: {:?}",
|
|
296
|
+
alg.operation_type, alg.convergence_semantic
|
|
297
|
+
);
|
|
276
298
|
}
|
|
277
299
|
|
|
278
300
|
println!("\n✅ Phase 5 Foundation Established");
|
|
@@ -60,7 +60,10 @@ mod tests {
|
|
|
60
60
|
// Gate 2: Increment is commutative
|
|
61
61
|
let registry = OperationAlgebraRegistry::new();
|
|
62
62
|
let increment_algebra = registry.get("Increment").unwrap();
|
|
63
|
-
assert!(
|
|
63
|
+
assert!(
|
|
64
|
+
increment_algebra.is_commutative(),
|
|
65
|
+
"Gate 2: Increment not commutative"
|
|
66
|
+
);
|
|
64
67
|
|
|
65
68
|
println!(
|
|
66
69
|
"✅ Scenario 1: Basic Convergence (Gates 1,2,4,6) - {} nodes converged to {}",
|
|
@@ -128,9 +131,7 @@ mod tests {
|
|
|
128
131
|
crate::crash_atomic_boundary::InvariantCheckResult::Satisfied
|
|
129
132
|
));
|
|
130
133
|
|
|
131
|
-
println!(
|
|
132
|
-
"✅ Scenario 3: Exactly-Once with Crash (Gates 3,4) - invariant satisfied"
|
|
133
|
-
);
|
|
134
|
+
println!("✅ Scenario 3: Exactly-Once with Crash (Gates 3,4) - invariant satisfied");
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
#[test]
|
|
@@ -267,8 +268,7 @@ mod tests {
|
|
|
267
268
|
|
|
268
269
|
let mut barrier = CausalDependencyBarrier::new();
|
|
269
270
|
let op_empty = VectorClock::new();
|
|
270
|
-
let recv_result =
|
|
271
|
-
barrier.receive_operation("op1".to_string(), op_empty);
|
|
271
|
+
let recv_result = barrier.receive_operation("op1".to_string(), op_empty);
|
|
272
272
|
assert!(matches!(
|
|
273
273
|
recv_result,
|
|
274
274
|
crate::causal_dependency_barrier::ReceiveResult::ReadyToApply { .. }
|
|
@@ -57,18 +57,9 @@ mod tests {
|
|
|
57
57
|
assert_eq!(transport.receive(n4).len(), 1);
|
|
58
58
|
|
|
59
59
|
// Gate 9: Partition n1 from {n2, n3, n4}
|
|
60
|
-
transport.apply_fault(NetworkEvent::Partition {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
});
|
|
64
|
-
transport.apply_fault(NetworkEvent::Partition {
|
|
65
|
-
from: n1,
|
|
66
|
-
to: n3,
|
|
67
|
-
});
|
|
68
|
-
transport.apply_fault(NetworkEvent::Partition {
|
|
69
|
-
from: n1,
|
|
70
|
-
to: n4,
|
|
71
|
-
});
|
|
60
|
+
transport.apply_fault(NetworkEvent::Partition { from: n1, to: n2 });
|
|
61
|
+
transport.apply_fault(NetworkEvent::Partition { from: n1, to: n3 });
|
|
62
|
+
transport.apply_fault(NetworkEvent::Partition { from: n1, to: n4 });
|
|
72
63
|
|
|
73
64
|
// n1 tries to send but gets dropped by partition
|
|
74
65
|
transport.send(n1, n2, b"during_partition".to_vec());
|
|
@@ -83,18 +74,9 @@ mod tests {
|
|
|
83
74
|
assert_eq!(transport.receive(n4).len(), 1);
|
|
84
75
|
|
|
85
76
|
// Gate 9: Heal partition
|
|
86
|
-
transport.apply_fault(NetworkEvent::Heal {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
});
|
|
90
|
-
transport.apply_fault(NetworkEvent::Heal {
|
|
91
|
-
from: n1,
|
|
92
|
-
to: n3,
|
|
93
|
-
});
|
|
94
|
-
transport.apply_fault(NetworkEvent::Heal {
|
|
95
|
-
from: n1,
|
|
96
|
-
to: n4,
|
|
97
|
-
});
|
|
77
|
+
transport.apply_fault(NetworkEvent::Heal { from: n1, to: n2 });
|
|
78
|
+
transport.apply_fault(NetworkEvent::Heal { from: n1, to: n3 });
|
|
79
|
+
transport.apply_fault(NetworkEvent::Heal { from: n1, to: n4 });
|
|
98
80
|
|
|
99
81
|
// Now all nodes can communicate again
|
|
100
82
|
transport.send(n1, n2, b"after_heal".to_vec());
|
|
@@ -121,10 +103,7 @@ mod tests {
|
|
|
121
103
|
assert_eq!(transport.receive(n3).len(), 1);
|
|
122
104
|
|
|
123
105
|
// Gate 9: Create asymmetric partition (n1→n2 broken but n2→n1 ok)
|
|
124
|
-
transport.apply_fault(NetworkEvent::Partition {
|
|
125
|
-
from: n1,
|
|
126
|
-
to: n2,
|
|
127
|
-
});
|
|
106
|
+
transport.apply_fault(NetworkEvent::Partition { from: n1, to: n2 });
|
|
128
107
|
|
|
129
108
|
// n1 can't reach n2
|
|
130
109
|
transport.send(n1, n2, b"blocked".to_vec());
|
|
@@ -429,10 +408,7 @@ mod tests {
|
|
|
429
408
|
});
|
|
430
409
|
|
|
431
410
|
// And partition n1 from n3
|
|
432
|
-
transport.apply_fault(NetworkEvent::Partition {
|
|
433
|
-
from: n1,
|
|
434
|
-
to: n3,
|
|
435
|
-
});
|
|
411
|
+
transport.apply_fault(NetworkEvent::Partition { from: n1, to: n3 });
|
|
436
412
|
|
|
437
413
|
// Byzantine messages from n2 still deliver
|
|
438
414
|
transport.tick();
|
|
@@ -466,20 +442,11 @@ mod tests {
|
|
|
466
442
|
assert_eq!(transport.receive(n3).len(), 1);
|
|
467
443
|
|
|
468
444
|
// Round 2-4: Cascade of partitions
|
|
469
|
-
transport.apply_fault(NetworkEvent::Partition {
|
|
470
|
-
from: n1,
|
|
471
|
-
to: n2,
|
|
472
|
-
});
|
|
445
|
+
transport.apply_fault(NetworkEvent::Partition { from: n1, to: n2 });
|
|
473
446
|
transport.tick();
|
|
474
|
-
transport.apply_fault(NetworkEvent::Partition {
|
|
475
|
-
from: n1,
|
|
476
|
-
to: n3,
|
|
477
|
-
});
|
|
447
|
+
transport.apply_fault(NetworkEvent::Partition { from: n1, to: n3 });
|
|
478
448
|
transport.tick();
|
|
479
|
-
transport.apply_fault(NetworkEvent::Partition {
|
|
480
|
-
from: n2,
|
|
481
|
-
to: n3,
|
|
482
|
-
});
|
|
449
|
+
transport.apply_fault(NetworkEvent::Partition { from: n2, to: n3 });
|
|
483
450
|
transport.tick();
|
|
484
451
|
|
|
485
452
|
// All nodes isolated
|
|
@@ -488,10 +455,7 @@ mod tests {
|
|
|
488
455
|
assert_eq!(transport.receive(n2).len(), 0);
|
|
489
456
|
|
|
490
457
|
// Round 5-7: Heal partitions one by one
|
|
491
|
-
transport.apply_fault(NetworkEvent::Heal {
|
|
492
|
-
from: n1,
|
|
493
|
-
to: n2,
|
|
494
|
-
});
|
|
458
|
+
transport.apply_fault(NetworkEvent::Heal { from: n1, to: n2 });
|
|
495
459
|
transport.tick();
|
|
496
460
|
|
|
497
461
|
// n1 ↔ n2 now connected
|
|
@@ -499,10 +463,7 @@ mod tests {
|
|
|
499
463
|
transport.tick();
|
|
500
464
|
assert_eq!(transport.receive(n2).len(), 1);
|
|
501
465
|
|
|
502
|
-
transport.apply_fault(NetworkEvent::Heal {
|
|
503
|
-
from: n2,
|
|
504
|
-
to: n3,
|
|
505
|
-
});
|
|
466
|
+
transport.apply_fault(NetworkEvent::Heal { from: n2, to: n3 });
|
|
506
467
|
transport.tick();
|
|
507
468
|
|
|
508
469
|
// n2 ↔ n3 now connected
|
|
@@ -510,10 +471,7 @@ mod tests {
|
|
|
510
471
|
transport.tick();
|
|
511
472
|
assert_eq!(transport.receive(n3).len(), 1);
|
|
512
473
|
|
|
513
|
-
transport.apply_fault(NetworkEvent::Heal {
|
|
514
|
-
from: n1,
|
|
515
|
-
to: n3,
|
|
516
|
-
});
|
|
474
|
+
transport.apply_fault(NetworkEvent::Heal { from: n1, to: n3 });
|
|
517
475
|
transport.tick();
|
|
518
476
|
|
|
519
477
|
// All fully connected again
|