@feltdb/core 0.4.12 → 0.4.13
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 +5 -2
- package/dist/cli/index.js +1 -1
- package/dist/create/cli.js +16 -1
- package/dist/create/create.js +30 -16
- package/dist/create/docker-compose-generator.js +48 -9
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +2238 -0
- package/dist/create/server-source/Cargo.toml +7 -0
- package/dist/create/server-source/crates/feltdb/Cargo.lock +175 -0
- package/dist/create/server-source/crates/feltdb/Cargo.toml +79 -0
- package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +370 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +589 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +259 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +446 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +272 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +378 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +414 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +486 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +273 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +587 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +513 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +395 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +469 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +409 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +627 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +383 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +298 -0
- package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +1698 -0
- package/dist/create/server-source/crates/feltdb/src/acquisition.rs +286 -0
- package/dist/create/server-source/crates/feltdb/src/admission.rs +192 -0
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +566 -0
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +475 -0
- package/dist/create/server-source/crates/feltdb/src/application.rs +2244 -0
- package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +1070 -0
- package/dist/create/server-source/crates/feltdb/src/authorization.rs +1030 -0
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +266 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +8 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +482 -0
- package/dist/create/server-source/crates/feltdb/src/capability.rs +903 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +78 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +2214 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +194 -0
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +427 -0
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +453 -0
- package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +465 -0
- package/dist/create/server-source/crates/feltdb/src/convergence.rs +618 -0
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +380 -0
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +362 -0
- package/dist/create/server-source/crates/feltdb/src/cron.rs +294 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +474 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +1003 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +536 -0
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +364 -0
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +235 -0
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +316 -0
- package/dist/create/server-source/crates/feltdb/src/execution.rs +426 -0
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +219 -0
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +779 -0
- package/dist/create/server-source/crates/feltdb/src/lib.rs +2838 -0
- package/dist/create/server-source/crates/feltdb/src/materialization.rs +184 -0
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +311 -0
- package/dist/create/server-source/crates/feltdb/src/observability.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/operation.rs +251 -0
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +438 -0
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +344 -0
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +166 -0
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +560 -0
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +3226 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +201 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +263 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +484 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +216 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +281 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +323 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +573 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +381 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +334 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +306 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +355 -0
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +395 -0
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +346 -0
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +430 -0
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +400 -0
- package/dist/create/server-source/crates/feltdb/src/provenance.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +217 -0
- package/dist/create/server-source/crates/feltdb/src/references.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +401 -0
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +160 -0
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +132 -0
- package/dist/create/server-source/crates/feltdb/src/routing.rs +409 -0
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +3118 -0
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +291 -0
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +376 -0
- package/dist/create/server-source/crates/feltdb/src/storage.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/submission.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/sync.rs +483 -0
- package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +822 -0
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +473 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +949 -0
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +646 -0
- package/dist/create/server-source/crates/feltdb/src/trigger.rs +390 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +928 -0
- package/dist/create/server-source/crates/feltdb/src/workflow.rs +713 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +510 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +354 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +594 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +1310 -0
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +23 -0
- package/dist/create/server-source/crates/feltdb-server/README.md +79 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +73 -0
- package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +425 -0
- package/dist/create/server-source/crates/feltdb-server/src/artifacts.rs +449 -0
- package/dist/create/server-source/crates/feltdb-server/src/audit.rs +59 -0
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +308 -0
- package/dist/create/server-source/crates/feltdb-server/src/authorization.rs +228 -0
- package/dist/create/server-source/crates/feltdb-server/src/backup.rs +416 -0
- package/dist/create/server-source/crates/feltdb-server/src/causal.rs +218 -0
- package/dist/create/server-source/crates/feltdb-server/src/certification.rs +223 -0
- package/dist/create/server-source/crates/feltdb-server/src/clock.rs +132 -0
- package/dist/create/server-source/crates/feltdb-server/src/cluster.rs +165 -0
- package/dist/create/server-source/crates/feltdb-server/src/connections.rs +1044 -0
- package/dist/create/server-source/crates/feltdb-server/src/content.rs +111 -0
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +250 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +78 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_provider.rs +247 -0
- package/dist/create/server-source/crates/feltdb-server/src/leases.rs +123 -0
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +25 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +8715 -0
- package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +97 -0
- package/dist/create/server-source/crates/feltdb-server/src/portable_bundle.rs +350 -0
- package/dist/create/server-source/crates/feltdb-server/src/principals.rs +510 -0
- package/dist/create/server-source/crates/feltdb-server/src/providers.rs +269 -0
- package/dist/create/server-source/crates/feltdb-server/src/releases.rs +2563 -0
- package/dist/create/server-source/crates/feltdb-server/src/sessions.rs +156 -0
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +1097 -0
- package/dist/create/server-source/crates/feltdb-server/src/versions.rs +264 -0
- package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +31 -0
- package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +1086 -0
- package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
- package/dist/flowspec.d.ts +2 -0
- package/dist/flowspec.d.ts.map +1 -1
- package/dist/flowspec.js +28 -4
- package/dist/state-contract.d.ts +7 -1
- package/dist/state-contract.d.ts.map +1 -1
- package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
- package/dist/studio/components/index.js +1 -1
- package/dist/studio/{components-Q0Nx6gLE.js → components-bHTARBin.js} +156 -46
- package/dist/studio/index.js +31 -28
- package/dist/studio-app/assets/{index-BHOmLZF-.js → index-BqKquLuU.js} +8 -8
- package/dist/studio-app/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
/// Convergence layer module for distributed state consistency
|
|
2
|
+
/// Provides causal state versioning, conflict resolution, and deterministic merge
|
|
3
|
+
use serde::{Deserialize, Serialize};
|
|
4
|
+
use std::cmp::Ordering;
|
|
5
|
+
use std::collections::HashMap;
|
|
6
|
+
|
|
7
|
+
/// Vector clock for tracking causality in distributed systems
|
|
8
|
+
/// Each instance maintains a logical clock value
|
|
9
|
+
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
|
10
|
+
pub struct VectorClock {
|
|
11
|
+
/// Map of instance_id to logical clock value
|
|
12
|
+
pub clocks: HashMap<String, u64>,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
impl VectorClock {
|
|
16
|
+
/// Create a new vector clock
|
|
17
|
+
pub fn new() -> Self {
|
|
18
|
+
Self {
|
|
19
|
+
clocks: HashMap::new(),
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Create a vector clock from a HashMap
|
|
24
|
+
pub fn from_clocks(clocks: HashMap<String, u64>) -> Self {
|
|
25
|
+
Self { clocks }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Increment the clock for a specific instance
|
|
29
|
+
pub fn increment(&mut self, instance_id: &str) {
|
|
30
|
+
let entry = self.clocks.entry(instance_id.to_string()).or_insert(0);
|
|
31
|
+
*entry += 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Get the clock value for an instance
|
|
35
|
+
pub fn get(&self, instance_id: &str) -> u64 {
|
|
36
|
+
*self.clocks.get(instance_id).unwrap_or(&0)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Merge with another vector clock (takes maximum for each instance)
|
|
40
|
+
pub fn merge(&mut self, other: &VectorClock) {
|
|
41
|
+
for (instance_id, other_clock) in &other.clocks {
|
|
42
|
+
let entry = self.clocks.entry(instance_id.clone()).or_insert(0);
|
|
43
|
+
*entry = (*entry).max(*other_clock);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Check if this clock causally precedes another
|
|
48
|
+
pub fn happens_before(&self, other: &VectorClock) -> bool {
|
|
49
|
+
let mut at_least_one_less = false;
|
|
50
|
+
|
|
51
|
+
for (instance_id, other_value) in &other.clocks {
|
|
52
|
+
let self_value = self.get(instance_id);
|
|
53
|
+
if self_value > *other_value {
|
|
54
|
+
return false; // This clock is ahead in some dimension
|
|
55
|
+
}
|
|
56
|
+
if self_value < *other_value {
|
|
57
|
+
at_least_one_less = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Also check if self has clocks that other doesn't (or other is behind on them)
|
|
62
|
+
for (instance_id, self_value) in &self.clocks {
|
|
63
|
+
let other_value = other.get(instance_id);
|
|
64
|
+
if self_value > &other_value {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
at_least_one_less
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Check if two clocks are concurrent (neither happens before the other)
|
|
73
|
+
pub fn is_concurrent(&self, other: &VectorClock) -> bool {
|
|
74
|
+
!self.happens_before(other) && !other.happens_before(self)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Compare two vector clocks for causal ordering
|
|
78
|
+
pub fn causal_cmp(&self, other: &VectorClock) -> Ordering {
|
|
79
|
+
if self == other {
|
|
80
|
+
return Ordering::Equal;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if self.happens_before(other) {
|
|
84
|
+
Ordering::Less
|
|
85
|
+
} else if other.happens_before(self) {
|
|
86
|
+
Ordering::Greater
|
|
87
|
+
} else {
|
|
88
|
+
// Concurrent - use lexicographic comparison of instance IDs as tiebreaker
|
|
89
|
+
let all_instances: std::collections::BTreeSet<_> =
|
|
90
|
+
self.clocks.keys().chain(other.clocks.keys()).collect();
|
|
91
|
+
|
|
92
|
+
for instance_id in all_instances {
|
|
93
|
+
let self_val = self.get(instance_id);
|
|
94
|
+
let other_val = other.get(instance_id);
|
|
95
|
+
if self_val != other_val {
|
|
96
|
+
return if self_val < other_val {
|
|
97
|
+
Ordering::Less
|
|
98
|
+
} else {
|
|
99
|
+
Ordering::Greater
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
Ordering::Equal
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
impl Default for VectorClock {
|
|
109
|
+
fn default() -> Self {
|
|
110
|
+
Self::new()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// State version representing a causal snapshot of the system
|
|
115
|
+
/// Tracks which instance is at which logical clock value
|
|
116
|
+
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
|
117
|
+
pub struct StateVersion {
|
|
118
|
+
/// Vector clock representing this state
|
|
119
|
+
pub vector_clock: VectorClock,
|
|
120
|
+
/// Timestamp when this version was created (ms since UNIX epoch)
|
|
121
|
+
pub timestamp_ms: u128,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
impl StateVersion {
|
|
125
|
+
/// Create a new state version
|
|
126
|
+
pub fn new(vector_clock: VectorClock, timestamp_ms: u128) -> Self {
|
|
127
|
+
Self {
|
|
128
|
+
vector_clock,
|
|
129
|
+
timestamp_ms,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// Create a state version from clocks
|
|
134
|
+
pub fn from_clocks(clocks: HashMap<String, u64>, timestamp_ms: u128) -> Self {
|
|
135
|
+
Self {
|
|
136
|
+
vector_clock: VectorClock::from_clocks(clocks),
|
|
137
|
+
timestamp_ms,
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Check if this state version causally precedes another
|
|
142
|
+
pub fn happens_before(&self, other: &StateVersion) -> bool {
|
|
143
|
+
self.vector_clock.happens_before(&other.vector_clock)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/// Check if two state versions are concurrent
|
|
147
|
+
pub fn is_concurrent(&self, other: &StateVersion) -> bool {
|
|
148
|
+
self.vector_clock.is_concurrent(&other.vector_clock)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// Get causal ordering between two state versions
|
|
152
|
+
pub fn causal_cmp(&self, other: &StateVersion) -> Ordering {
|
|
153
|
+
self.vector_clock.causal_cmp(&other.vector_clock)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// Tracks the causal history of an operation
|
|
158
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
159
|
+
pub struct CausalHistory {
|
|
160
|
+
/// The vector clock when this operation occurred
|
|
161
|
+
pub state_version: StateVersion,
|
|
162
|
+
/// Instance that originated this operation
|
|
163
|
+
pub instance_id: String,
|
|
164
|
+
/// Sequence number from that instance
|
|
165
|
+
pub sequence: u64,
|
|
166
|
+
/// Any dependencies on other operations (by their vector clocks)
|
|
167
|
+
pub dependencies: Vec<VectorClock>,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
impl CausalHistory {
|
|
171
|
+
/// Create a new causal history entry
|
|
172
|
+
pub fn new(state_version: StateVersion, instance_id: String, sequence: u64) -> Self {
|
|
173
|
+
Self {
|
|
174
|
+
state_version,
|
|
175
|
+
instance_id,
|
|
176
|
+
sequence,
|
|
177
|
+
dependencies: Vec::new(),
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// Add a dependency on another operation
|
|
182
|
+
pub fn add_dependency(&mut self, dep_clock: VectorClock) {
|
|
183
|
+
if !self.dependencies.contains(&dep_clock) {
|
|
184
|
+
self.dependencies.push(dep_clock);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/// Check if this operation depends on another (transitively)
|
|
189
|
+
pub fn depends_on(&self, other: &CausalHistory) -> bool {
|
|
190
|
+
// Direct dependency
|
|
191
|
+
if self
|
|
192
|
+
.dependencies
|
|
193
|
+
.contains(&other.state_version.vector_clock)
|
|
194
|
+
{
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Indirect dependency would require full history traversal
|
|
199
|
+
// For now, check if other happens before self
|
|
200
|
+
other.state_version.happens_before(&self.state_version)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/// Conflict resolution strategy for concurrent operations
|
|
205
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
206
|
+
pub enum ConflictResolutionStrategy {
|
|
207
|
+
/// Last-Write-Wins based on timestamp with instance_id as tiebreaker
|
|
208
|
+
LastWriteWins,
|
|
209
|
+
/// Causally consistent - apply in causal order, keep concurrent
|
|
210
|
+
CausalConsistent,
|
|
211
|
+
/// Application-defined (returned as unresolved for app to handle)
|
|
212
|
+
ApplicationDefined,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/// Result of conflict resolution
|
|
216
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
217
|
+
pub struct ConflictResolution {
|
|
218
|
+
/// Which operation "won" (Some(index)) or if unresolved (None)
|
|
219
|
+
pub winner_index: Option<usize>,
|
|
220
|
+
/// The resolution strategy used
|
|
221
|
+
pub strategy: ConflictResolutionStrategy,
|
|
222
|
+
/// Whether operations are concurrent
|
|
223
|
+
pub is_concurrent: bool,
|
|
224
|
+
/// Whether operations have causal ordering
|
|
225
|
+
pub is_causal: bool,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
impl ConflictResolution {
|
|
229
|
+
/// Resolve concurrent operations using Last-Write-Wins strategy
|
|
230
|
+
pub fn resolve_lww(histories: &[CausalHistory]) -> Self {
|
|
231
|
+
if histories.is_empty() {
|
|
232
|
+
return Self {
|
|
233
|
+
winner_index: None,
|
|
234
|
+
strategy: ConflictResolutionStrategy::LastWriteWins,
|
|
235
|
+
is_concurrent: false,
|
|
236
|
+
is_causal: false,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if histories.len() == 1 {
|
|
241
|
+
return Self {
|
|
242
|
+
winner_index: Some(0),
|
|
243
|
+
strategy: ConflictResolutionStrategy::LastWriteWins,
|
|
244
|
+
is_concurrent: false,
|
|
245
|
+
is_causal: false,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check for causal ordering
|
|
250
|
+
let mut is_causal = false;
|
|
251
|
+
for i in 0..histories.len() {
|
|
252
|
+
for j in 0..histories.len() {
|
|
253
|
+
if i != j
|
|
254
|
+
&& histories[i]
|
|
255
|
+
.state_version
|
|
256
|
+
.happens_before(&histories[j].state_version)
|
|
257
|
+
{
|
|
258
|
+
is_causal = true;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if is_causal {
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// If there's causal ordering, return the one that happens after all others
|
|
268
|
+
if is_causal {
|
|
269
|
+
let mut winner_idx = 0;
|
|
270
|
+
for i in 1..histories.len() {
|
|
271
|
+
// If winner happens before i, then i is later
|
|
272
|
+
if histories[winner_idx]
|
|
273
|
+
.state_version
|
|
274
|
+
.happens_before(&histories[i].state_version)
|
|
275
|
+
{
|
|
276
|
+
winner_idx = i;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return Self {
|
|
280
|
+
winner_index: Some(winner_idx),
|
|
281
|
+
strategy: ConflictResolutionStrategy::LastWriteWins,
|
|
282
|
+
is_concurrent: false,
|
|
283
|
+
is_causal: true,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Concurrent operations - use LWW with tiebreaker
|
|
288
|
+
let mut winner_idx = 0;
|
|
289
|
+
for i in 1..histories.len() {
|
|
290
|
+
let ts_cmp = histories[i]
|
|
291
|
+
.state_version
|
|
292
|
+
.timestamp_ms
|
|
293
|
+
.cmp(&histories[winner_idx].state_version.timestamp_ms);
|
|
294
|
+
|
|
295
|
+
if ts_cmp == std::cmp::Ordering::Greater {
|
|
296
|
+
winner_idx = i;
|
|
297
|
+
} else if ts_cmp == std::cmp::Ordering::Equal {
|
|
298
|
+
// Tiebreaker: lexicographic comparison of instance_id
|
|
299
|
+
if histories[i].instance_id > histories[winner_idx].instance_id {
|
|
300
|
+
winner_idx = i;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
Self {
|
|
306
|
+
winner_index: Some(winner_idx),
|
|
307
|
+
strategy: ConflictResolutionStrategy::LastWriteWins,
|
|
308
|
+
is_concurrent: true,
|
|
309
|
+
is_causal: false,
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/// Deterministic merge result for reference graph convergence
|
|
315
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
316
|
+
pub struct MergeResult {
|
|
317
|
+
/// Merged vector clock representing the combined state
|
|
318
|
+
pub merged_clock: VectorClock,
|
|
319
|
+
/// Conflicts detected during merge
|
|
320
|
+
pub conflicts: Vec<(usize, usize, ConflictResolution)>,
|
|
321
|
+
/// Whether merge resulted in a new state or kept existing
|
|
322
|
+
pub changed: bool,
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
impl MergeResult {
|
|
326
|
+
/// Create a new merge result
|
|
327
|
+
pub fn new(merged_clock: VectorClock) -> Self {
|
|
328
|
+
Self {
|
|
329
|
+
merged_clock,
|
|
330
|
+
conflicts: Vec::new(),
|
|
331
|
+
changed: false,
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
#[cfg(test)]
|
|
337
|
+
mod tests {
|
|
338
|
+
use super::*;
|
|
339
|
+
|
|
340
|
+
#[test]
|
|
341
|
+
fn vector_clock_increment() {
|
|
342
|
+
let mut clock = VectorClock::new();
|
|
343
|
+
clock.increment("inst1");
|
|
344
|
+
assert_eq!(clock.get("inst1"), 1);
|
|
345
|
+
assert_eq!(clock.get("inst2"), 0);
|
|
346
|
+
|
|
347
|
+
clock.increment("inst1");
|
|
348
|
+
assert_eq!(clock.get("inst1"), 2);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
#[test]
|
|
352
|
+
fn vector_clock_merge() {
|
|
353
|
+
let mut clock1 = VectorClock::new();
|
|
354
|
+
clock1.increment("inst1");
|
|
355
|
+
clock1.increment("inst1");
|
|
356
|
+
|
|
357
|
+
let mut clock2 = VectorClock::new();
|
|
358
|
+
clock2.increment("inst2");
|
|
359
|
+
clock2.increment("inst2");
|
|
360
|
+
clock2.increment("inst2");
|
|
361
|
+
|
|
362
|
+
clock1.merge(&clock2);
|
|
363
|
+
assert_eq!(clock1.get("inst1"), 2);
|
|
364
|
+
assert_eq!(clock1.get("inst2"), 3);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
#[test]
|
|
368
|
+
fn vector_clock_happens_before() {
|
|
369
|
+
let mut clock1 = VectorClock::new();
|
|
370
|
+
clock1.increment("inst1");
|
|
371
|
+
|
|
372
|
+
let mut clock2 = VectorClock::new();
|
|
373
|
+
clock2.increment("inst1");
|
|
374
|
+
clock2.increment("inst1");
|
|
375
|
+
|
|
376
|
+
assert!(clock1.happens_before(&clock2));
|
|
377
|
+
assert!(!clock2.happens_before(&clock1));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
#[test]
|
|
381
|
+
fn vector_clock_concurrent() {
|
|
382
|
+
let mut clock1 = VectorClock::new();
|
|
383
|
+
clock1.increment("inst1");
|
|
384
|
+
|
|
385
|
+
let mut clock2 = VectorClock::new();
|
|
386
|
+
clock2.increment("inst2");
|
|
387
|
+
|
|
388
|
+
assert!(clock1.is_concurrent(&clock2));
|
|
389
|
+
assert!(clock2.is_concurrent(&clock1));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
#[test]
|
|
393
|
+
fn state_version_causal_ordering() {
|
|
394
|
+
let mut clock1 = VectorClock::new();
|
|
395
|
+
clock1.increment("inst1");
|
|
396
|
+
let version1 = StateVersion::new(clock1, 1000);
|
|
397
|
+
|
|
398
|
+
let mut clock2 = VectorClock::new();
|
|
399
|
+
clock2.increment("inst1");
|
|
400
|
+
clock2.increment("inst1");
|
|
401
|
+
let version2 = StateVersion::new(clock2, 2000);
|
|
402
|
+
|
|
403
|
+
assert!(version1.happens_before(&version2));
|
|
404
|
+
assert!(version1.causal_cmp(&version2) == Ordering::Less);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
#[test]
|
|
408
|
+
fn conflict_resolution_lww_causal() {
|
|
409
|
+
let mut clock1 = VectorClock::new();
|
|
410
|
+
clock1.increment("inst1");
|
|
411
|
+
let version1 = StateVersion::new(clock1, 1000);
|
|
412
|
+
let history1 = CausalHistory::new(version1, "inst1".to_string(), 1);
|
|
413
|
+
|
|
414
|
+
let mut clock2 = VectorClock::new();
|
|
415
|
+
clock2.increment("inst1");
|
|
416
|
+
clock2.increment("inst1");
|
|
417
|
+
let version2 = StateVersion::new(clock2, 2000);
|
|
418
|
+
let history2 = CausalHistory::new(version2, "inst1".to_string(), 2);
|
|
419
|
+
|
|
420
|
+
let resolution = ConflictResolution::resolve_lww(&[history1, history2]);
|
|
421
|
+
assert_eq!(resolution.winner_index, Some(1));
|
|
422
|
+
assert!(resolution.is_causal);
|
|
423
|
+
assert!(!resolution.is_concurrent);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
#[test]
|
|
427
|
+
fn conflict_resolution_lww_concurrent() {
|
|
428
|
+
let mut clock1 = VectorClock::new();
|
|
429
|
+
clock1.increment("inst1");
|
|
430
|
+
let version1 = StateVersion::new(clock1, 1000);
|
|
431
|
+
let history1 = CausalHistory::new(version1, "inst1".to_string(), 1);
|
|
432
|
+
|
|
433
|
+
let mut clock2 = VectorClock::new();
|
|
434
|
+
clock2.increment("inst2");
|
|
435
|
+
let version2 = StateVersion::new(clock2, 2000);
|
|
436
|
+
let history2 = CausalHistory::new(version2, "inst2".to_string(), 1);
|
|
437
|
+
|
|
438
|
+
let resolution = ConflictResolution::resolve_lww(&[history1, history2]);
|
|
439
|
+
assert!(resolution.winner_index.is_some());
|
|
440
|
+
assert!(!resolution.is_causal);
|
|
441
|
+
assert!(resolution.is_concurrent);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
#[test]
|
|
445
|
+
fn causal_history_dependency() {
|
|
446
|
+
let mut clock1 = VectorClock::new();
|
|
447
|
+
clock1.increment("inst1");
|
|
448
|
+
let version1 = StateVersion::new(clock1.clone(), 1000);
|
|
449
|
+
let history1 = CausalHistory::new(version1, "inst1".to_string(), 1);
|
|
450
|
+
|
|
451
|
+
let mut clock2 = VectorClock::new();
|
|
452
|
+
clock2.clone_from(&clock1);
|
|
453
|
+
clock2.increment("inst2");
|
|
454
|
+
let version2 = StateVersion::new(clock2, 2000);
|
|
455
|
+
let mut history2 = CausalHistory::new(version2, "inst2".to_string(), 1);
|
|
456
|
+
|
|
457
|
+
history2.add_dependency(clock1.clone());
|
|
458
|
+
assert!(history2.depends_on(&history1));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
#[test]
|
|
462
|
+
fn vector_clock_lexicographic_tiebreaker() {
|
|
463
|
+
let mut clock1 = VectorClock::new();
|
|
464
|
+
clock1.clocks.insert("inst-a".to_string(), 1);
|
|
465
|
+
clock1.clocks.insert("inst-b".to_string(), 1);
|
|
466
|
+
|
|
467
|
+
let clock2 = VectorClock::from_clocks(clock1.clocks.clone());
|
|
468
|
+
|
|
469
|
+
// Same clocks - should use lexicographic tiebreaker
|
|
470
|
+
let cmp_result = clock1.causal_cmp(&clock2);
|
|
471
|
+
assert_eq!(cmp_result, Ordering::Equal);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
#[test]
|
|
475
|
+
fn concurrent_mutations_deterministic_resolution() {
|
|
476
|
+
// Scenario: Two instances concurrently modify the same entity
|
|
477
|
+
// Result: Deterministic resolution based on LWW + instance_id tiebreaker
|
|
478
|
+
|
|
479
|
+
// Instance A modifies at timestamp 1000
|
|
480
|
+
let mut clock_a = VectorClock::new();
|
|
481
|
+
clock_a.increment("inst-a");
|
|
482
|
+
let version_a = StateVersion::new(clock_a, 1000);
|
|
483
|
+
let history_a = CausalHistory::new(version_a, "inst-a".to_string(), 1);
|
|
484
|
+
|
|
485
|
+
// Instance B modifies at timestamp 1000 (same time, but different instance)
|
|
486
|
+
let mut clock_b = VectorClock::new();
|
|
487
|
+
clock_b.increment("inst-b");
|
|
488
|
+
let version_b = StateVersion::new(clock_b, 1000);
|
|
489
|
+
let history_b = CausalHistory::new(version_b, "inst-b".to_string(), 1);
|
|
490
|
+
|
|
491
|
+
// Resolve conflict
|
|
492
|
+
let resolution = ConflictResolution::resolve_lww(&[history_a.clone(), history_b.clone()]);
|
|
493
|
+
assert!(resolution.is_concurrent);
|
|
494
|
+
assert!(!resolution.is_causal);
|
|
495
|
+
assert!(resolution.winner_index.is_some());
|
|
496
|
+
|
|
497
|
+
// Both resolutions should favor inst-b due to lexicographic ordering
|
|
498
|
+
assert_eq!(
|
|
499
|
+
resolution.winner_index,
|
|
500
|
+
Some(1), // inst-b when b is second
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
#[test]
|
|
505
|
+
fn causal_chain_three_operations() {
|
|
506
|
+
// Scenario: Three operations in a causal chain
|
|
507
|
+
// inst1 -> inst2 -> inst3
|
|
508
|
+
|
|
509
|
+
// Operation 1: inst1 creates something
|
|
510
|
+
let mut clock1 = VectorClock::new();
|
|
511
|
+
clock1.increment("inst1");
|
|
512
|
+
let version1 = StateVersion::new(clock1.clone(), 1000);
|
|
513
|
+
let history1 = CausalHistory::new(version1, "inst1".to_string(), 1);
|
|
514
|
+
|
|
515
|
+
// Operation 2: inst2 observes inst1 and adds to it
|
|
516
|
+
let mut clock2 = clock1.clone();
|
|
517
|
+
clock2.increment("inst2");
|
|
518
|
+
let version2 = StateVersion::new(clock2.clone(), 2000);
|
|
519
|
+
let history2 = CausalHistory::new(version2, "inst2".to_string(), 1);
|
|
520
|
+
|
|
521
|
+
// Operation 3: inst3 observes both and adds more
|
|
522
|
+
let mut clock3 = clock2.clone();
|
|
523
|
+
clock3.increment("inst3");
|
|
524
|
+
let version3 = StateVersion::new(clock3, 3000);
|
|
525
|
+
let history3 = CausalHistory::new(version3, "inst3".to_string(), 1);
|
|
526
|
+
|
|
527
|
+
// Verify causal chain
|
|
528
|
+
assert!(history1
|
|
529
|
+
.state_version
|
|
530
|
+
.happens_before(&history2.state_version));
|
|
531
|
+
assert!(history2
|
|
532
|
+
.state_version
|
|
533
|
+
.happens_before(&history3.state_version));
|
|
534
|
+
assert!(history1
|
|
535
|
+
.state_version
|
|
536
|
+
.happens_before(&history3.state_version));
|
|
537
|
+
|
|
538
|
+
// Resolve in different orders - should always choose the one that happens last
|
|
539
|
+
let resolution1 = ConflictResolution::resolve_lww(&[history1, history2, history3]);
|
|
540
|
+
assert_eq!(resolution1.winner_index, Some(2)); // history3 is latest
|
|
541
|
+
assert!(resolution1.is_causal);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
#[test]
|
|
545
|
+
fn merge_result_construction() {
|
|
546
|
+
let mut merged_clock = VectorClock::new();
|
|
547
|
+
merged_clock.increment("inst1");
|
|
548
|
+
merged_clock.increment("inst2");
|
|
549
|
+
|
|
550
|
+
let mut merge_result = MergeResult::new(merged_clock.clone());
|
|
551
|
+
assert!(!merge_result.changed);
|
|
552
|
+
assert_eq!(merge_result.conflicts.len(), 0);
|
|
553
|
+
|
|
554
|
+
// Add a conflict
|
|
555
|
+
let history_a = CausalHistory::new(
|
|
556
|
+
StateVersion::new(VectorClock::new(), 1000),
|
|
557
|
+
"inst-a".to_string(),
|
|
558
|
+
1,
|
|
559
|
+
);
|
|
560
|
+
let history_b = CausalHistory::new(
|
|
561
|
+
StateVersion::new(VectorClock::new(), 1000),
|
|
562
|
+
"inst-b".to_string(),
|
|
563
|
+
1,
|
|
564
|
+
);
|
|
565
|
+
let resolution = ConflictResolution::resolve_lww(&[history_a, history_b]);
|
|
566
|
+
merge_result.conflicts.push((0, 1, resolution));
|
|
567
|
+
|
|
568
|
+
assert_eq!(merge_result.conflicts.len(), 1);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
#[test]
|
|
572
|
+
fn vector_clock_partial_order() {
|
|
573
|
+
// Test partial ordering properties of vector clocks
|
|
574
|
+
|
|
575
|
+
let mut clock1 = VectorClock::new();
|
|
576
|
+
clock1.increment("inst1");
|
|
577
|
+
|
|
578
|
+
let mut clock2 = VectorClock::new();
|
|
579
|
+
clock2.increment("inst1");
|
|
580
|
+
clock2.increment("inst2");
|
|
581
|
+
|
|
582
|
+
let mut clock3 = VectorClock::new();
|
|
583
|
+
clock3.increment("inst2");
|
|
584
|
+
clock3.increment("inst2");
|
|
585
|
+
|
|
586
|
+
// clock1 happens before clock2
|
|
587
|
+
assert!(clock1.happens_before(&clock2));
|
|
588
|
+
assert!(!clock2.happens_before(&clock1));
|
|
589
|
+
|
|
590
|
+
// clock1 and clock3 are concurrent
|
|
591
|
+
assert!(clock1.is_concurrent(&clock3));
|
|
592
|
+
assert!(clock3.is_concurrent(&clock1));
|
|
593
|
+
|
|
594
|
+
// clock2 and clock3 are concurrent (neither is strictly before the other)
|
|
595
|
+
assert!(clock2.is_concurrent(&clock3));
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
#[test]
|
|
599
|
+
fn state_version_ordering() {
|
|
600
|
+
// Test that StateVersion uses vector clock ordering
|
|
601
|
+
|
|
602
|
+
let mut clock1 = VectorClock::new();
|
|
603
|
+
clock1.increment("inst1");
|
|
604
|
+
let version1 = StateVersion::new(clock1, 1000);
|
|
605
|
+
|
|
606
|
+
let mut clock2 = VectorClock::new();
|
|
607
|
+
clock2.increment("inst1");
|
|
608
|
+
clock2.increment("inst1");
|
|
609
|
+
let version2 = StateVersion::new(clock2, 2000);
|
|
610
|
+
|
|
611
|
+
// version1 causally precedes version2
|
|
612
|
+
assert!(version1.happens_before(&version2));
|
|
613
|
+
|
|
614
|
+
// version1 is ordered before version2
|
|
615
|
+
assert_eq!(version1.causal_cmp(&version2), Ordering::Less);
|
|
616
|
+
assert_eq!(version2.causal_cmp(&version1), Ordering::Greater);
|
|
617
|
+
}
|
|
618
|
+
}
|