@feltdb/core 0.8.3 → 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.
- package/dist/cli/commands.js +4 -1
- package/dist/cli/provisioning-neutrality.js +79 -0
- package/dist/collection.d.ts +43 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +192 -22
- package/dist/create/create.js +25 -21
- package/dist/create/managed-account.js +11 -0
- 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/equality_index.rs +595 -0
- 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 +1050 -117
- 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_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 +90 -72
- 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_execution_diagnostics.rs +126 -0
- 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 +305 -6
- 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 +1514 -537
- 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/submission.rs +5 -11
- package/dist/create/server-source/crates/feltdb/src/sync.rs +12 -0
- 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 +442 -0
- package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +299 -0
- package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +288 -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/pr34_query_collection.rs +234 -0
- package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +947 -0
- 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 +1032 -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/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-server/src/app_state.rs +2 -1
- package/dist/create/server-source/crates/feltdb-server/src/audit.rs +1137 -29
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +164 -13
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +800 -34
- package/dist/db.d.ts +33 -34
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +74 -20
- package/dist/deployment.d.ts +30 -0
- package/dist/deployment.d.ts.map +1 -0
- package/dist/deployment.js +130 -0
- package/dist/embedded-transaction.d.ts +22 -4
- package/dist/embedded-transaction.d.ts.map +1 -1
- package/dist/embedded-transaction.js +51 -5
- package/dist/feltdb.d.ts +14 -2
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.js +1 -1
- package/dist/http-client.d.ts +14 -0
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +23 -5
- package/dist/http-db.d.ts +119 -1
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +346 -31
- package/dist/index-core.d.ts +2 -0
- package/dist/index-core.d.ts.map +1 -1
- package/dist/index-core.js +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/indexeddb-db.d.ts.map +1 -1
- package/dist/indexeddb-db.js +35 -21
- package/dist/managed-recovery.d.ts +192 -0
- package/dist/managed-recovery.d.ts.map +1 -0
- package/dist/managed-recovery.js +242 -0
- package/dist/memory-db.js +1 -1
- package/dist/studio-app/assets/{feltdb_wasm-DB8cX151.js → feltdb_wasm-DaNwCLRX.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-DnsHNv6g.wasm +0 -0
- package/dist/studio-app/assets/index-j8IlhNqJ.js +29 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/transaction.d.ts +30 -0
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +41 -0
- 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-ClhDHp0S.wasm +0 -0
- package/dist/studio-app/assets/index-B0k4UAlI.js +0 -29
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
* - Query routing and distribution
|
|
9
9
|
* - Index merging strategies
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
11
|
use serde::{Deserialize, Serialize};
|
|
13
|
-
use std::collections::{HashMap, HashSet
|
|
12
|
+
use std::collections::{BTreeMap, HashMap, HashSet};
|
|
14
13
|
|
|
15
14
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
|
16
15
|
pub struct PeerId(pub String);
|
|
@@ -209,7 +208,9 @@ impl DistributedIndexManager {
|
|
|
209
208
|
/// Acknowledge sync from peer
|
|
210
209
|
pub fn acknowledge_sync(&mut self, peer_id: &str, index_name: &str) -> Result<(), String> {
|
|
211
210
|
if let Some(replication_state) = self.replication_states.get_mut(index_name) {
|
|
212
|
-
replication_state
|
|
211
|
+
replication_state
|
|
212
|
+
.replicated_peers
|
|
213
|
+
.insert(peer_id.to_string());
|
|
213
214
|
Ok(())
|
|
214
215
|
} else {
|
|
215
216
|
Err(format!("Index {} not found", index_name))
|
|
@@ -258,7 +259,8 @@ impl DistributedIndexManager {
|
|
|
258
259
|
self.peer_states
|
|
259
260
|
.keys()
|
|
260
261
|
.filter(|peer_id| {
|
|
261
|
-
peer_id != &&self.local_peer_id
|
|
262
|
+
peer_id != &&self.local_peer_id
|
|
263
|
+
&& !replication_state.replicated_peers.contains(*peer_id)
|
|
262
264
|
})
|
|
263
265
|
.cloned()
|
|
264
266
|
.collect()
|
|
@@ -273,7 +275,10 @@ impl DistributedIndexManager {
|
|
|
273
275
|
|
|
274
276
|
if let Some(replication_state) = self.replication_states.get(index_name) {
|
|
275
277
|
for (peer_id, peer_version) in &replication_state.peer_versions {
|
|
276
|
-
if replication_state
|
|
278
|
+
if replication_state
|
|
279
|
+
.local_version
|
|
280
|
+
.concurrent_with(peer_version)
|
|
281
|
+
{
|
|
277
282
|
conflicts.push(VersionConflict {
|
|
278
283
|
index_name: index_name.to_string(),
|
|
279
284
|
local_version: replication_state.local_version.clone(),
|
|
@@ -326,11 +331,7 @@ impl DistributedIndexManager {
|
|
|
326
331
|
fully_replicated,
|
|
327
332
|
partially_replicated: total_indexes - fully_replicated,
|
|
328
333
|
pending_operations: pending_total,
|
|
329
|
-
unreachable_peers: self
|
|
330
|
-
.peer_states
|
|
331
|
-
.values()
|
|
332
|
-
.filter(|p| !p.reachable)
|
|
333
|
-
.count(),
|
|
334
|
+
unreachable_peers: self.peer_states.values().filter(|p| !p.reachable).count(),
|
|
334
335
|
}
|
|
335
336
|
}
|
|
336
337
|
}
|
|
@@ -432,11 +433,13 @@ mod tests {
|
|
|
432
433
|
let mut manager = DistributedIndexManager::new("node1".to_string());
|
|
433
434
|
manager.register_peer("node2".to_string());
|
|
434
435
|
|
|
435
|
-
assert!(manager
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
436
|
+
assert!(manager
|
|
437
|
+
.create_replicated_index(
|
|
438
|
+
"status_idx".to_string(),
|
|
439
|
+
"status".to_string(),
|
|
440
|
+
"hash".to_string()
|
|
441
|
+
)
|
|
442
|
+
.is_ok());
|
|
440
443
|
|
|
441
444
|
let status = manager.get_replication_status("status_idx");
|
|
442
445
|
assert!(status.is_some());
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
/// - Reconciliation after divergence
|
|
8
8
|
/// - Write authority tracking and conflicts
|
|
9
9
|
/// - Recovery contracts
|
|
10
|
-
|
|
11
10
|
use serde::{Deserialize, Serialize};
|
|
12
11
|
use std::collections::HashMap;
|
|
13
12
|
|
|
@@ -125,7 +124,10 @@ impl DurabilityGuarantees {
|
|
|
125
124
|
authority_node: String,
|
|
126
125
|
) -> Result<(), String> {
|
|
127
126
|
if self.write_authorities.contains_key(&resource_id) {
|
|
128
|
-
return Err(format!(
|
|
127
|
+
return Err(format!(
|
|
128
|
+
"Write authority already established for {}",
|
|
129
|
+
resource_id
|
|
130
|
+
));
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
self.write_authorities.insert(
|
|
@@ -160,7 +162,11 @@ impl DurabilityGuarantees {
|
|
|
160
162
|
}
|
|
161
163
|
}
|
|
162
164
|
|
|
163
|
-
pub fn create_recovery_checkpoint(
|
|
165
|
+
pub fn create_recovery_checkpoint(
|
|
166
|
+
&mut self,
|
|
167
|
+
operations_count: u64,
|
|
168
|
+
data_hash: String,
|
|
169
|
+
) -> Result<RecoveryCheckpoint, String> {
|
|
164
170
|
self.checkpoint_counter += 1;
|
|
165
171
|
|
|
166
172
|
let checkpoint = RecoveryCheckpoint {
|
|
@@ -202,10 +208,7 @@ impl DurabilityGuarantees {
|
|
|
202
208
|
Ok(checkpoint)
|
|
203
209
|
}
|
|
204
210
|
|
|
205
|
-
pub fn detect_crash_consistency(
|
|
206
|
-
&self,
|
|
207
|
-
crash_phase: &str,
|
|
208
|
-
) -> Result<Vec<CrashPoint>, String> {
|
|
211
|
+
pub fn detect_crash_consistency(&self, crash_phase: &str) -> Result<Vec<CrashPoint>, String> {
|
|
209
212
|
Ok(self
|
|
210
213
|
.crash_points
|
|
211
214
|
.iter()
|
|
@@ -323,7 +326,8 @@ mod tests {
|
|
|
323
326
|
#[test]
|
|
324
327
|
fn test_restart_recovery() {
|
|
325
328
|
let mut dg = DurabilityGuarantees::new("node-1".to_string());
|
|
326
|
-
dg.create_recovery_checkpoint(100, "hash1".to_string())
|
|
329
|
+
dg.create_recovery_checkpoint(100, "hash1".to_string())
|
|
330
|
+
.unwrap();
|
|
327
331
|
|
|
328
332
|
let recovered = dg.restart_recovery(None).unwrap();
|
|
329
333
|
assert_eq!(recovered.operations_count, 100);
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
/// The dedup set prevents duplicate operations from being applied multiple times.
|
|
5
5
|
///
|
|
6
6
|
/// Invariant: Every envelope ID persisted survives process restart.
|
|
7
|
-
|
|
8
7
|
use crate::distributed_transactions::EnvelopeId;
|
|
9
8
|
use std::collections::HashSet;
|
|
10
9
|
use std::fs::{File, OpenOptions};
|
|
@@ -28,10 +27,7 @@ impl DeduplicationSet {
|
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
// Open dedup file in append mode
|
|
31
|
-
let file = OpenOptions::new()
|
|
32
|
-
.create(true)
|
|
33
|
-
.append(true)
|
|
34
|
-
.open(&path)?;
|
|
30
|
+
let file = OpenOptions::new().create(true).append(true).open(&path)?;
|
|
35
31
|
|
|
36
32
|
Ok(Self {
|
|
37
33
|
path,
|
|
@@ -83,7 +83,11 @@ mod tests {
|
|
|
83
83
|
///
|
|
84
84
|
/// The operation id is taken from the executor's next identity, which is
|
|
85
85
|
/// exactly what the binary does now that the executor owns allocation.
|
|
86
|
-
fn commit(
|
|
86
|
+
fn commit(
|
|
87
|
+
executor: &mut DistributedTransactionExecutor,
|
|
88
|
+
node: &str,
|
|
89
|
+
payload: &str,
|
|
90
|
+
) -> TransactionEnvelope {
|
|
87
91
|
let sequence = executor.next_origin_sequence();
|
|
88
92
|
let parent = chained_version(executor, node);
|
|
89
93
|
match executor
|
|
@@ -103,7 +107,9 @@ mod tests {
|
|
|
103
107
|
/// The parent version this node would commit on top of.
|
|
104
108
|
fn chained_version(executor: &DistributedTransactionExecutor, node: &str) -> StateVersion {
|
|
105
109
|
match executor.get_replica_state(node) {
|
|
106
|
-
Some(replica) =>
|
|
110
|
+
Some(replica) => {
|
|
111
|
+
StateVersion::new(replica.vector_clock.clone(), replica.state_hash.to_string())
|
|
112
|
+
}
|
|
107
113
|
None => zero_version(),
|
|
108
114
|
}
|
|
109
115
|
}
|
|
@@ -120,7 +126,11 @@ mod tests {
|
|
|
120
126
|
fn identity_a_fresh_origin_starts_at_one() {
|
|
121
127
|
let dir = tempfile::tempdir().unwrap();
|
|
122
128
|
let mut node = open("A", &dir.path().join("operations.jsonl"));
|
|
123
|
-
assert_eq!(
|
|
129
|
+
assert_eq!(
|
|
130
|
+
node.next_origin_sequence(),
|
|
131
|
+
1,
|
|
132
|
+
"a brand-new origin legitimately starts at 1"
|
|
133
|
+
);
|
|
124
134
|
assert_eq!(commit(&mut node, "A", "first").envelope_id.sequence, 1);
|
|
125
135
|
}
|
|
126
136
|
|
|
@@ -184,7 +194,9 @@ mod tests {
|
|
|
184
194
|
let mut clock = VectorClock::new();
|
|
185
195
|
clock.clocks.insert("A".to_string(), position);
|
|
186
196
|
envelope.vector_clock = clock;
|
|
187
|
-
receiver
|
|
197
|
+
receiver
|
|
198
|
+
.receive_replicated(message(envelope), zero_version())
|
|
199
|
+
.expect("receive");
|
|
188
200
|
}
|
|
189
201
|
|
|
190
202
|
assert_eq!(
|
|
@@ -192,10 +204,19 @@ mod tests {
|
|
|
192
204
|
1,
|
|
193
205
|
"nine applied envelopes from A leave this node's own identity space untouched"
|
|
194
206
|
);
|
|
195
|
-
assert_eq!(
|
|
207
|
+
assert_eq!(
|
|
208
|
+
commit(&mut receiver, "receiver", "mine")
|
|
209
|
+
.envelope_id
|
|
210
|
+
.sequence,
|
|
211
|
+
1
|
|
212
|
+
);
|
|
196
213
|
|
|
197
214
|
let restarted = open("receiver", &path);
|
|
198
|
-
assert_eq!(
|
|
215
|
+
assert_eq!(
|
|
216
|
+
restarted.next_origin_sequence(),
|
|
217
|
+
2,
|
|
218
|
+
"and recovery filters by origin too"
|
|
219
|
+
);
|
|
199
220
|
}
|
|
200
221
|
|
|
201
222
|
// -- crash boundaries --------------------------------------------------
|
|
@@ -279,7 +300,11 @@ mod tests {
|
|
|
279
300
|
)
|
|
280
301
|
.expect("submit");
|
|
281
302
|
assert!(matches!(outcome, SubmitOutcome::CapacityExceeded { .. }));
|
|
282
|
-
assert_eq!(
|
|
303
|
+
assert_eq!(
|
|
304
|
+
node.next_origin_sequence(),
|
|
305
|
+
before,
|
|
306
|
+
"a refusal consumes no identity"
|
|
307
|
+
);
|
|
283
308
|
}
|
|
284
309
|
|
|
285
310
|
#[test]
|
|
@@ -290,7 +315,11 @@ mod tests {
|
|
|
290
315
|
for cycle in 0..25 {
|
|
291
316
|
let mut node = open("A", &path);
|
|
292
317
|
for index in 0..4 {
|
|
293
|
-
issued.push(
|
|
318
|
+
issued.push(
|
|
319
|
+
commit(&mut node, "A", &format!("c{cycle}_{index}"))
|
|
320
|
+
.envelope_id
|
|
321
|
+
.sequence,
|
|
322
|
+
);
|
|
294
323
|
}
|
|
295
324
|
}
|
|
296
325
|
let mut unique = issued.clone();
|
|
@@ -319,7 +348,9 @@ mod tests {
|
|
|
319
348
|
commit(&mut origin, "A", "before")
|
|
320
349
|
};
|
|
321
350
|
assert!(matches!(
|
|
322
|
-
receiver
|
|
351
|
+
receiver
|
|
352
|
+
.receive_replicated(message(before), zero_version())
|
|
353
|
+
.expect("receive"),
|
|
323
354
|
ReceiveOutcome::Applied { .. }
|
|
324
355
|
));
|
|
325
356
|
|
|
@@ -328,18 +359,31 @@ mod tests {
|
|
|
328
359
|
let mut origin = open("A", &origin_path);
|
|
329
360
|
commit(&mut origin, "A", "after")
|
|
330
361
|
};
|
|
331
|
-
assert_eq!(
|
|
362
|
+
assert_eq!(
|
|
363
|
+
after.envelope_id.sequence, 2,
|
|
364
|
+
"the identity did not restart"
|
|
365
|
+
);
|
|
332
366
|
|
|
333
|
-
let outcome = receiver
|
|
367
|
+
let outcome = receiver
|
|
368
|
+
.receive_replicated(message(after), zero_version())
|
|
369
|
+
.expect("receive");
|
|
334
370
|
assert!(
|
|
335
371
|
matches!(outcome, ReceiveOutcome::Applied { .. }),
|
|
336
372
|
"a write from a restarted origin must be applied, not discarded: {outcome:?}"
|
|
337
373
|
);
|
|
338
|
-
assert_eq!(
|
|
374
|
+
assert_eq!(
|
|
375
|
+
receiver.causal_frontier().get("A"),
|
|
376
|
+
2,
|
|
377
|
+
"and the frontier advances"
|
|
378
|
+
);
|
|
339
379
|
|
|
340
380
|
let state = receiver.canonical_state().expect("state");
|
|
341
|
-
assert!(state
|
|
342
|
-
|
|
381
|
+
assert!(state
|
|
382
|
+
.records
|
|
383
|
+
.contains_key(&("items".to_string(), "after".to_string())));
|
|
384
|
+
assert!(state
|
|
385
|
+
.records
|
|
386
|
+
.contains_key(&("items".to_string(), "before".to_string())));
|
|
343
387
|
}
|
|
344
388
|
|
|
345
389
|
#[test]
|
|
@@ -352,9 +396,13 @@ mod tests {
|
|
|
352
396
|
let mut receiver = open("receiver", &receiver_dir.path().join("operations.jsonl"));
|
|
353
397
|
|
|
354
398
|
let first = commit(&mut origin, "A", "one");
|
|
355
|
-
receiver
|
|
399
|
+
receiver
|
|
400
|
+
.receive_replicated(message(first.clone()), zero_version())
|
|
401
|
+
.expect("receive");
|
|
356
402
|
assert_eq!(
|
|
357
|
-
receiver
|
|
403
|
+
receiver
|
|
404
|
+
.receive_replicated(message(first), zero_version())
|
|
405
|
+
.expect("receive"),
|
|
358
406
|
ReceiveOutcome::AlreadyKnown,
|
|
359
407
|
"a retransmission is refused"
|
|
360
408
|
);
|
|
@@ -362,7 +410,9 @@ mod tests {
|
|
|
362
410
|
let second = commit(&mut origin, "A", "two");
|
|
363
411
|
assert!(
|
|
364
412
|
matches!(
|
|
365
|
-
receiver
|
|
413
|
+
receiver
|
|
414
|
+
.receive_replicated(message(second), zero_version())
|
|
415
|
+
.expect("receive"),
|
|
366
416
|
ReceiveOutcome::Applied { .. }
|
|
367
417
|
),
|
|
368
418
|
"and a new operation after a duplicate is still accepted"
|
|
@@ -382,12 +432,17 @@ mod tests {
|
|
|
382
432
|
|
|
383
433
|
let (first, second) = {
|
|
384
434
|
let mut origin = open("A", &origin_path);
|
|
385
|
-
(
|
|
435
|
+
(
|
|
436
|
+
commit(&mut origin, "A", "one"),
|
|
437
|
+
commit(&mut origin, "A", "two"),
|
|
438
|
+
)
|
|
386
439
|
};
|
|
387
440
|
|
|
388
441
|
// Only the second is delivered, so it is held waiting for the first.
|
|
389
442
|
assert!(matches!(
|
|
390
|
-
receiver
|
|
443
|
+
receiver
|
|
444
|
+
.receive_replicated(message(second), zero_version())
|
|
445
|
+
.expect("receive"),
|
|
391
446
|
ReceiveOutcome::PendingDependencies { .. }
|
|
392
447
|
));
|
|
393
448
|
assert_eq!(receiver.pending_causal_count(), 1);
|
|
@@ -397,9 +452,14 @@ mod tests {
|
|
|
397
452
|
let mut origin = open("A", &origin_path);
|
|
398
453
|
commit(&mut origin, "A", "three")
|
|
399
454
|
};
|
|
400
|
-
assert_eq!(
|
|
455
|
+
assert_eq!(
|
|
456
|
+
third.envelope_id.sequence, 3,
|
|
457
|
+
"no collision with the held entry"
|
|
458
|
+
);
|
|
401
459
|
|
|
402
|
-
let held = receiver
|
|
460
|
+
let held = receiver
|
|
461
|
+
.receive_replicated(message(third), zero_version())
|
|
462
|
+
.expect("receive");
|
|
403
463
|
assert!(
|
|
404
464
|
matches!(held, ReceiveOutcome::PendingDependencies { .. }),
|
|
405
465
|
"it is held on its own merits, not mistaken for the entry already pending: {held:?}"
|
|
@@ -407,12 +467,16 @@ mod tests {
|
|
|
407
467
|
assert_eq!(receiver.pending_causal_count(), 2);
|
|
408
468
|
|
|
409
469
|
// The gap closes and everything applies exactly once.
|
|
410
|
-
receiver
|
|
470
|
+
receiver
|
|
471
|
+
.receive_replicated(message(first), zero_version())
|
|
472
|
+
.expect("receive");
|
|
411
473
|
assert_eq!(receiver.pending_causal_count(), 0);
|
|
412
474
|
assert_eq!(receiver.causal_frontier().get("A"), 3);
|
|
413
475
|
let state = receiver.canonical_state().expect("state");
|
|
414
476
|
for record in ["one", "two", "three"] {
|
|
415
|
-
assert!(state
|
|
477
|
+
assert!(state
|
|
478
|
+
.records
|
|
479
|
+
.contains_key(&("items".to_string(), record.to_string())));
|
|
416
480
|
}
|
|
417
481
|
}
|
|
418
482
|
}
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
/// The log is the source of truth for recovery; replica state and dedup sets are derived from it.
|
|
5
5
|
///
|
|
6
6
|
/// Invariant: Every successfully persisted operation can be recovered after process restart.
|
|
7
|
-
|
|
8
7
|
use crate::distributed_transactions::TransactionEnvelope;
|
|
9
8
|
use std::fs::{File, OpenOptions};
|
|
10
9
|
use std::io::{BufRead, BufReader, Write};
|
|
@@ -27,10 +26,7 @@ impl OperationLog {
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
// Open log file in append mode
|
|
30
|
-
let file = OpenOptions::new()
|
|
31
|
-
.create(true)
|
|
32
|
-
.append(true)
|
|
33
|
-
.open(&path)?;
|
|
29
|
+
let file = OpenOptions::new().create(true).append(true).open(&path)?;
|
|
34
30
|
|
|
35
31
|
Ok(Self {
|
|
36
32
|
path,
|
|
@@ -119,7 +115,7 @@ impl OperationLog {
|
|
|
119
115
|
#[cfg(test)]
|
|
120
116
|
mod tests {
|
|
121
117
|
use super::*;
|
|
122
|
-
use crate::transactions::{
|
|
118
|
+
use crate::transactions::{ConsistencyContract, ConsistencyLevel};
|
|
123
119
|
use tempfile::TempDir;
|
|
124
120
|
|
|
125
121
|
fn create_test_envelope(sequence: u64) -> TransactionEnvelope {
|
|
@@ -127,7 +123,7 @@ mod tests {
|
|
|
127
123
|
"node1".to_string(),
|
|
128
124
|
sequence,
|
|
129
125
|
format!("tx{}", sequence),
|
|
130
|
-
Vec::new(),
|
|
126
|
+
Vec::new(), // Empty operations for test
|
|
131
127
|
ConsistencyContract {
|
|
132
128
|
level: ConsistencyLevel::Causal,
|
|
133
129
|
timeout_ms: Some(1000),
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
/// - Checkpoints and high-water marks for recovery
|
|
8
8
|
/// - Replay and recovery mechanisms
|
|
9
9
|
/// - Backpressure handling for flow control
|
|
10
|
-
|
|
11
10
|
use serde::{Deserialize, Serialize};
|
|
12
11
|
use std::collections::HashMap;
|
|
13
12
|
|
|
@@ -118,7 +117,9 @@ impl DurableSyncManager {
|
|
|
118
117
|
}
|
|
119
118
|
|
|
120
119
|
self.sync_state.pending_changes.push(change);
|
|
121
|
-
self.sync_state
|
|
120
|
+
self.sync_state
|
|
121
|
+
.pending_changes
|
|
122
|
+
.sort_by_key(|c| c.change_id.sequence);
|
|
122
123
|
|
|
123
124
|
Ok(())
|
|
124
125
|
}
|
|
@@ -147,14 +148,13 @@ impl DurableSyncManager {
|
|
|
147
148
|
|
|
148
149
|
pub fn create_checkpoint(&mut self) -> Result<Checkpoint, String> {
|
|
149
150
|
let checkpoint = Checkpoint {
|
|
150
|
-
checkpoint_id: format!(
|
|
151
|
+
checkpoint_id: format!(
|
|
152
|
+
"cp-{}-{}",
|
|
153
|
+
self.node_id, self.sync_state.last_applied_sequence
|
|
154
|
+
),
|
|
151
155
|
node_id: self.node_id.clone(),
|
|
152
156
|
high_water_mark: self.sync_state.last_applied_sequence,
|
|
153
|
-
applied_changes: self
|
|
154
|
-
.applied_ids
|
|
155
|
-
.iter()
|
|
156
|
-
.cloned()
|
|
157
|
-
.collect(),
|
|
157
|
+
applied_changes: self.applied_ids.iter().cloned().collect(),
|
|
158
158
|
timestamp: std::time::SystemTime::now()
|
|
159
159
|
.duration_since(std::time::UNIX_EPOCH)
|
|
160
160
|
.unwrap_or_default()
|
|
@@ -189,7 +189,8 @@ impl DurableSyncManager {
|
|
|
189
189
|
|
|
190
190
|
pub fn replay_from_checkpoint(&mut self, checkpoint: &Checkpoint) -> Result<usize, String> {
|
|
191
191
|
let mut replayed = 0;
|
|
192
|
-
let applied_set: std::collections::HashSet<_> =
|
|
192
|
+
let applied_set: std::collections::HashSet<_> =
|
|
193
|
+
checkpoint.applied_changes.iter().cloned().collect();
|
|
193
194
|
|
|
194
195
|
for change in self.sync_state.pending_changes.drain(..) {
|
|
195
196
|
if !applied_set.contains(&change.idempotency_key)
|