@feltdb/core 0.4.11 → 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-q43NSTTH.js → components-bHTARBin.js} +160 -89
- package/dist/studio/index.js +31 -28
- package/dist/studio-app/assets/{index-DZpBoVMp.js → index-BqKquLuU.js} +8 -8
- package/dist/studio-app/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
use crate::now_ms;
|
|
2
|
+
/// Operation log module for durable append-only mutations
|
|
3
|
+
use serde::{Deserialize, Serialize};
|
|
4
|
+
use serde_json::Value;
|
|
5
|
+
use sha2::{Digest, Sha256};
|
|
6
|
+
|
|
7
|
+
/// A durable operation that can be appended to the log
|
|
8
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
9
|
+
pub struct Operation {
|
|
10
|
+
/// Unique operation ID (monotonically increasing)
|
|
11
|
+
pub op_id: u64,
|
|
12
|
+
/// Instance ID - unique identifier for the FeltDB instance that created this operation
|
|
13
|
+
pub instance_id: String,
|
|
14
|
+
/// Sequence number - monotonically increasing sequence per instance
|
|
15
|
+
pub sequence: u64,
|
|
16
|
+
/// Type of operation (insert, update, delete)
|
|
17
|
+
pub op_type: OperationType,
|
|
18
|
+
/// The key being operated on (e.g., "users:123")
|
|
19
|
+
pub key: String,
|
|
20
|
+
/// The value (for insert/update operations)
|
|
21
|
+
pub value: Option<Value>,
|
|
22
|
+
/// Timestamp in milliseconds since UNIX epoch
|
|
23
|
+
pub timestamp_ms: u128,
|
|
24
|
+
/// The data type being operated on
|
|
25
|
+
pub rust_type: String,
|
|
26
|
+
/// Capability/namespace
|
|
27
|
+
pub capability: String,
|
|
28
|
+
/// Content hash - deterministic hash of the operation's content for deduplication
|
|
29
|
+
pub content_hash: String,
|
|
30
|
+
/// Vector clock for causal tracking (optional for backward compatibility)
|
|
31
|
+
#[serde(default)]
|
|
32
|
+
pub vector_clock: Option<std::collections::HashMap<String, u64>>,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Types of mutations that become durable operations
|
|
36
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
37
|
+
pub enum OperationType {
|
|
38
|
+
/// Insert a new record
|
|
39
|
+
Insert,
|
|
40
|
+
/// Update an existing record
|
|
41
|
+
Update,
|
|
42
|
+
/// Delete an existing record
|
|
43
|
+
Delete,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
impl Operation {
|
|
47
|
+
/// Create a new insert operation
|
|
48
|
+
pub fn insert(
|
|
49
|
+
op_id: u64,
|
|
50
|
+
instance_id: String,
|
|
51
|
+
sequence: u64,
|
|
52
|
+
key: String,
|
|
53
|
+
value: Value,
|
|
54
|
+
rust_type: String,
|
|
55
|
+
capability: String,
|
|
56
|
+
) -> Self {
|
|
57
|
+
let op = Self {
|
|
58
|
+
op_id,
|
|
59
|
+
instance_id,
|
|
60
|
+
sequence,
|
|
61
|
+
op_type: OperationType::Insert,
|
|
62
|
+
key,
|
|
63
|
+
value: Some(value),
|
|
64
|
+
timestamp_ms: now_ms(),
|
|
65
|
+
rust_type,
|
|
66
|
+
capability,
|
|
67
|
+
content_hash: String::new(), // Will be computed below
|
|
68
|
+
vector_clock: None,
|
|
69
|
+
};
|
|
70
|
+
let mut result = op;
|
|
71
|
+
result.content_hash = result.compute_content_hash();
|
|
72
|
+
result
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Create a new update operation
|
|
76
|
+
pub fn update(
|
|
77
|
+
op_id: u64,
|
|
78
|
+
instance_id: String,
|
|
79
|
+
sequence: u64,
|
|
80
|
+
key: String,
|
|
81
|
+
value: Value,
|
|
82
|
+
rust_type: String,
|
|
83
|
+
capability: String,
|
|
84
|
+
) -> Self {
|
|
85
|
+
let op = Self {
|
|
86
|
+
op_id,
|
|
87
|
+
instance_id,
|
|
88
|
+
sequence,
|
|
89
|
+
op_type: OperationType::Update,
|
|
90
|
+
key,
|
|
91
|
+
value: Some(value),
|
|
92
|
+
timestamp_ms: now_ms(),
|
|
93
|
+
rust_type,
|
|
94
|
+
capability,
|
|
95
|
+
content_hash: String::new(), // Will be computed below
|
|
96
|
+
vector_clock: None,
|
|
97
|
+
};
|
|
98
|
+
let mut result = op;
|
|
99
|
+
result.content_hash = result.compute_content_hash();
|
|
100
|
+
result
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Create a new delete operation
|
|
104
|
+
pub fn delete(
|
|
105
|
+
op_id: u64,
|
|
106
|
+
instance_id: String,
|
|
107
|
+
sequence: u64,
|
|
108
|
+
key: String,
|
|
109
|
+
rust_type: String,
|
|
110
|
+
capability: String,
|
|
111
|
+
) -> Self {
|
|
112
|
+
let op = Self {
|
|
113
|
+
op_id,
|
|
114
|
+
instance_id,
|
|
115
|
+
sequence,
|
|
116
|
+
op_type: OperationType::Delete,
|
|
117
|
+
key,
|
|
118
|
+
value: None,
|
|
119
|
+
timestamp_ms: now_ms(),
|
|
120
|
+
rust_type,
|
|
121
|
+
capability,
|
|
122
|
+
content_hash: String::new(), // Will be computed below
|
|
123
|
+
vector_clock: None,
|
|
124
|
+
};
|
|
125
|
+
let mut result = op;
|
|
126
|
+
result.content_hash = result.compute_content_hash();
|
|
127
|
+
result
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// Compute deterministic content hash for this operation
|
|
131
|
+
/// This is used for deduplication across peers
|
|
132
|
+
pub fn compute_content_hash(&self) -> String {
|
|
133
|
+
let mut hasher = Sha256::new();
|
|
134
|
+
|
|
135
|
+
// Hash the operation type
|
|
136
|
+
hasher.update(format!("{:?}", self.op_type).as_bytes());
|
|
137
|
+
|
|
138
|
+
// Hash the key
|
|
139
|
+
hasher.update(self.key.as_bytes());
|
|
140
|
+
|
|
141
|
+
// Hash the value if present
|
|
142
|
+
if let Some(val) = &self.value {
|
|
143
|
+
hasher.update(val.to_string().as_bytes());
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Hash the rust type
|
|
147
|
+
hasher.update(self.rust_type.as_bytes());
|
|
148
|
+
|
|
149
|
+
// Hash the capability
|
|
150
|
+
hasher.update(self.capability.as_bytes());
|
|
151
|
+
|
|
152
|
+
let result = hasher.finalize();
|
|
153
|
+
format!("{:x}", result)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/// Set the vector clock for this operation
|
|
157
|
+
pub fn with_vector_clock(
|
|
158
|
+
mut self,
|
|
159
|
+
vector_clock: std::collections::HashMap<String, u64>,
|
|
160
|
+
) -> Self {
|
|
161
|
+
self.vector_clock = Some(vector_clock);
|
|
162
|
+
self
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// Get the vector clock for this operation
|
|
166
|
+
pub fn get_vector_clock(&self) -> Option<&std::collections::HashMap<String, u64>> {
|
|
167
|
+
self.vector_clock.as_ref()
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/// Mutation ordering specification for FeltDB
|
|
172
|
+
/// This defines the order in which mutation side effects occur
|
|
173
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
174
|
+
pub enum MutationOrder {
|
|
175
|
+
/// Conservative: Apply locally first, then persist in background
|
|
176
|
+
/// Mutation → Validate → Apply state transition → Emit diff → Persist
|
|
177
|
+
/// Good for: Interactive feel, optimistic updates
|
|
178
|
+
ApplyFirst,
|
|
179
|
+
|
|
180
|
+
/// Durable: Persist first, then apply and emit
|
|
181
|
+
/// Mutation → Validate → Persist → Apply state transition → Emit diff
|
|
182
|
+
/// Good for: Durability guarantee, no data loss
|
|
183
|
+
PersistFirst,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// The default mutation ordering for FeltDB
|
|
187
|
+
/// We choose PersistFirst to ensure durability guarantees
|
|
188
|
+
pub const DEFAULT_MUTATION_ORDER: MutationOrder = MutationOrder::PersistFirst;
|
|
189
|
+
|
|
190
|
+
#[cfg(test)]
|
|
191
|
+
mod tests {
|
|
192
|
+
use super::*;
|
|
193
|
+
|
|
194
|
+
#[test]
|
|
195
|
+
fn operation_insert_creates_insert_op() {
|
|
196
|
+
let op = Operation::insert(
|
|
197
|
+
1,
|
|
198
|
+
"inst1".to_string(),
|
|
199
|
+
1,
|
|
200
|
+
"users:123".to_string(),
|
|
201
|
+
serde_json::json!({"name": "Alice"}),
|
|
202
|
+
"User".to_string(),
|
|
203
|
+
"users".to_string(),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
assert_eq!(op.op_id, 1);
|
|
207
|
+
assert_eq!(op.instance_id, "inst1");
|
|
208
|
+
assert_eq!(op.sequence, 1);
|
|
209
|
+
assert_eq!(op.op_type, OperationType::Insert);
|
|
210
|
+
assert_eq!(op.key, "users:123");
|
|
211
|
+
assert!(op.value.is_some());
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#[test]
|
|
215
|
+
fn operation_delete_has_no_value() {
|
|
216
|
+
let op = Operation::delete(
|
|
217
|
+
2,
|
|
218
|
+
"inst1".to_string(),
|
|
219
|
+
2,
|
|
220
|
+
"users:456".to_string(),
|
|
221
|
+
"User".to_string(),
|
|
222
|
+
"users".to_string(),
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
assert_eq!(op.op_type, OperationType::Delete);
|
|
226
|
+
assert!(op.value.is_none());
|
|
227
|
+
assert_eq!(op.instance_id, "inst1");
|
|
228
|
+
assert_eq!(op.sequence, 2);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
#[test]
|
|
232
|
+
fn operations_are_serializable() {
|
|
233
|
+
let op = Operation::update(
|
|
234
|
+
1,
|
|
235
|
+
"inst1".to_string(),
|
|
236
|
+
1,
|
|
237
|
+
"users:789".to_string(),
|
|
238
|
+
serde_json::json!({"status": "active"}),
|
|
239
|
+
"User".to_string(),
|
|
240
|
+
"users".to_string(),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
let json = serde_json::to_string(&op).expect("serialize");
|
|
244
|
+
let deserialized: Operation = serde_json::from_str(&json).expect("deserialize");
|
|
245
|
+
|
|
246
|
+
assert_eq!(deserialized.op_id, op.op_id);
|
|
247
|
+
assert_eq!(deserialized.op_type, op.op_type);
|
|
248
|
+
assert_eq!(deserialized.instance_id, op.instance_id);
|
|
249
|
+
assert_eq!(deserialized.sequence, op.sequence);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/// Operation Algebra Registry
|
|
2
|
+
///
|
|
3
|
+
/// Declares the convergence semantics and merge algebra for every
|
|
4
|
+
/// operation type in FeltDB. Gate 2 requires every replicated operation
|
|
5
|
+
/// to declare how it converges under concurrent application.
|
|
6
|
+
///
|
|
7
|
+
/// This registry is the foundation of operation-specific CRDT semantics.
|
|
8
|
+
|
|
9
|
+
use std::collections::HashMap;
|
|
10
|
+
|
|
11
|
+
/// Conflict classification for an operation
|
|
12
|
+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
13
|
+
pub enum ConflictType {
|
|
14
|
+
/// Commutative: can be applied in any order
|
|
15
|
+
Commutative,
|
|
16
|
+
/// Register: LWW (last-write-wins by timestamp)
|
|
17
|
+
Register,
|
|
18
|
+
/// Set CRDT: union on add, tombstone on remove
|
|
19
|
+
SetCRDT,
|
|
20
|
+
/// Counter CRDT: additive
|
|
21
|
+
CounterCRDT,
|
|
22
|
+
/// Custom: user-provided merge semantics
|
|
23
|
+
Custom(String),
|
|
24
|
+
/// Transaction: requires precondition/consensus
|
|
25
|
+
Transaction,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// How this operation converges with concurrent applications
|
|
29
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
30
|
+
pub enum ConvergenceSemantic {
|
|
31
|
+
/// All orderings produce identical result
|
|
32
|
+
StrictlyCommutative,
|
|
33
|
+
/// Deterministic merge function resolves concurrent versions
|
|
34
|
+
DeterministicMerge,
|
|
35
|
+
/// Requires consensus/total ordering
|
|
36
|
+
ConsensusRequired,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Declared convergence algebra for an operation type
|
|
40
|
+
#[derive(Clone, Debug)]
|
|
41
|
+
pub struct OperationAlgebra {
|
|
42
|
+
pub operation_type: String,
|
|
43
|
+
pub read_set_bound: String, // "single_record", "multiple_records", etc
|
|
44
|
+
pub write_set_bound: String, // "single_record", "multiple_records", etc
|
|
45
|
+
pub conflict_type: ConflictType,
|
|
46
|
+
pub convergence_semantic: ConvergenceSemantic,
|
|
47
|
+
pub merge_function_description: String,
|
|
48
|
+
pub commutativity_proof: Option<String>, // Reference to commutative algebra proof
|
|
49
|
+
pub determinism_proof: Option<String>, // Reference to determinism proof
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl OperationAlgebra {
|
|
53
|
+
pub fn new(
|
|
54
|
+
operation_type: &str,
|
|
55
|
+
read_set_bound: &str,
|
|
56
|
+
write_set_bound: &str,
|
|
57
|
+
conflict_type: ConflictType,
|
|
58
|
+
convergence_semantic: ConvergenceSemantic,
|
|
59
|
+
merge_function_description: &str,
|
|
60
|
+
) -> Self {
|
|
61
|
+
Self {
|
|
62
|
+
operation_type: operation_type.to_string(),
|
|
63
|
+
read_set_bound: read_set_bound.to_string(),
|
|
64
|
+
write_set_bound: write_set_bound.to_string(),
|
|
65
|
+
conflict_type,
|
|
66
|
+
convergence_semantic,
|
|
67
|
+
merge_function_description: merge_function_description.to_string(),
|
|
68
|
+
commutativity_proof: None,
|
|
69
|
+
determinism_proof: None,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
pub fn with_proofs(mut self, commutativity: Option<String>, determinism: Option<String>) -> Self {
|
|
74
|
+
self.commutativity_proof = commutativity;
|
|
75
|
+
self.determinism_proof = determinism;
|
|
76
|
+
self
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
pub fn is_commutative(&self) -> bool {
|
|
80
|
+
self.convergence_semantic == ConvergenceSemantic::StrictlyCommutative
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pub fn is_deterministic(&self) -> bool {
|
|
84
|
+
self.convergence_semantic != ConvergenceSemantic::ConsensusRequired
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Registry of all operation types and their convergence algebra
|
|
89
|
+
pub struct OperationAlgebraRegistry {
|
|
90
|
+
algebras: HashMap<String, OperationAlgebra>,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
impl OperationAlgebraRegistry {
|
|
94
|
+
/// Create new registry with standard operations
|
|
95
|
+
pub fn new() -> Self {
|
|
96
|
+
let mut registry = Self {
|
|
97
|
+
algebras: HashMap::new(),
|
|
98
|
+
};
|
|
99
|
+
registry.register_standard_operations();
|
|
100
|
+
registry
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Register all standard FeltDB operation types
|
|
104
|
+
fn register_standard_operations(&mut self) {
|
|
105
|
+
// SetField: Last-Write-Wins register
|
|
106
|
+
self.register(
|
|
107
|
+
OperationAlgebra::new(
|
|
108
|
+
"SetField",
|
|
109
|
+
"single_record.field",
|
|
110
|
+
"single_record.field",
|
|
111
|
+
ConflictType::Register,
|
|
112
|
+
ConvergenceSemantic::DeterministicMerge,
|
|
113
|
+
"LWW: max(timestamp, value)",
|
|
114
|
+
)
|
|
115
|
+
.with_proofs(
|
|
116
|
+
None,
|
|
117
|
+
Some("Deterministic: higher timestamp wins deterministically".to_string()),
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Increment: Counter CRDT (commutative addition)
|
|
122
|
+
self.register(
|
|
123
|
+
OperationAlgebra::new(
|
|
124
|
+
"Increment",
|
|
125
|
+
"single_record.field",
|
|
126
|
+
"single_record.field",
|
|
127
|
+
ConflictType::CounterCRDT,
|
|
128
|
+
ConvergenceSemantic::StrictlyCommutative,
|
|
129
|
+
"Counter CRDT: sum(all_increments)",
|
|
130
|
+
)
|
|
131
|
+
.with_proofs(
|
|
132
|
+
Some("Commutative: addition commutes over integers".to_string()),
|
|
133
|
+
Some("Deterministic: sum is deterministic".to_string()),
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// AddToSet: Set CRDT (union on add)
|
|
138
|
+
self.register(
|
|
139
|
+
OperationAlgebra::new(
|
|
140
|
+
"AddToSet",
|
|
141
|
+
"single_record.set",
|
|
142
|
+
"single_record.set",
|
|
143
|
+
ConflictType::SetCRDT,
|
|
144
|
+
ConvergenceSemantic::StrictlyCommutative,
|
|
145
|
+
"Set CRDT: union(all_adds)",
|
|
146
|
+
)
|
|
147
|
+
.with_proofs(
|
|
148
|
+
Some("Commutative: set union is commutative".to_string()),
|
|
149
|
+
Some("Deterministic: union is deterministic".to_string()),
|
|
150
|
+
),
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// RemoveFromSet: Set CRDT (tombstone semantics)
|
|
154
|
+
self.register(
|
|
155
|
+
OperationAlgebra::new(
|
|
156
|
+
"RemoveFromSet",
|
|
157
|
+
"single_record.set",
|
|
158
|
+
"single_record.set",
|
|
159
|
+
ConflictType::SetCRDT,
|
|
160
|
+
ConvergenceSemantic::DeterministicMerge,
|
|
161
|
+
"Set CRDT: (set - tombstones) ∪ (tombstones resolved by causal ordering)",
|
|
162
|
+
)
|
|
163
|
+
.with_proofs(
|
|
164
|
+
None,
|
|
165
|
+
Some("Deterministic: tombstone semantics with causal ordering".to_string()),
|
|
166
|
+
),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Create: Conflict detection (earliest timestamp wins deterministically)
|
|
170
|
+
self.register(
|
|
171
|
+
OperationAlgebra::new(
|
|
172
|
+
"Create",
|
|
173
|
+
"none",
|
|
174
|
+
"single_record",
|
|
175
|
+
ConflictType::Register,
|
|
176
|
+
ConvergenceSemantic::DeterministicMerge,
|
|
177
|
+
"Deterministic: min(timestamp, id) for create conflicts",
|
|
178
|
+
)
|
|
179
|
+
.with_proofs(
|
|
180
|
+
None,
|
|
181
|
+
Some("Deterministic: total order via (timestamp, node_id)".to_string()),
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Delete: Tombstone semantics (deterministic)
|
|
186
|
+
self.register(
|
|
187
|
+
OperationAlgebra::new(
|
|
188
|
+
"Delete",
|
|
189
|
+
"single_record",
|
|
190
|
+
"single_record",
|
|
191
|
+
ConflictType::Register,
|
|
192
|
+
ConvergenceSemantic::DeterministicMerge,
|
|
193
|
+
"Tombstone: delete_timestamp determines visibility",
|
|
194
|
+
)
|
|
195
|
+
.with_proofs(
|
|
196
|
+
None,
|
|
197
|
+
Some("Deterministic: tombstone ordering via timestamp and node_id".to_string()),
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Transfer: Business invariant (requires consensus/transaction)
|
|
202
|
+
self.register(
|
|
203
|
+
OperationAlgebra::new(
|
|
204
|
+
"Transfer",
|
|
205
|
+
"source_record,dest_record",
|
|
206
|
+
"source_record,dest_record",
|
|
207
|
+
ConflictType::Transaction,
|
|
208
|
+
ConvergenceSemantic::ConsensusRequired,
|
|
209
|
+
"Transaction: requires precondition validation and consensus ordering",
|
|
210
|
+
)
|
|
211
|
+
.with_proofs(
|
|
212
|
+
None,
|
|
213
|
+
Some("Consensus: total ordering via distributed log".to_string()),
|
|
214
|
+
),
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// Append: Sequence ordering (vector clock ordering)
|
|
218
|
+
self.register(
|
|
219
|
+
OperationAlgebra::new(
|
|
220
|
+
"Append",
|
|
221
|
+
"single_sequence",
|
|
222
|
+
"single_sequence",
|
|
223
|
+
ConflictType::Custom("SequenceOrdering".to_string()),
|
|
224
|
+
ConvergenceSemantic::DeterministicMerge,
|
|
225
|
+
"Sequence: vector clock establishes total order",
|
|
226
|
+
)
|
|
227
|
+
.with_proofs(
|
|
228
|
+
None,
|
|
229
|
+
Some("Deterministic: vector clock provides causal lineage".to_string()),
|
|
230
|
+
),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// Noop: No-operation (trivially commutative)
|
|
234
|
+
self.register(
|
|
235
|
+
OperationAlgebra::new(
|
|
236
|
+
"Noop",
|
|
237
|
+
"none",
|
|
238
|
+
"none",
|
|
239
|
+
ConflictType::Commutative,
|
|
240
|
+
ConvergenceSemantic::StrictlyCommutative,
|
|
241
|
+
"No-operation: idempotent, affects nothing",
|
|
242
|
+
)
|
|
243
|
+
.with_proofs(
|
|
244
|
+
Some("Trivially commutative: no state affected".to_string()),
|
|
245
|
+
Some("Trivially deterministic: no change produced".to_string()),
|
|
246
|
+
),
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/// Register an operation algebra
|
|
251
|
+
pub fn register(&mut self, algebra: OperationAlgebra) {
|
|
252
|
+
self.algebras.insert(algebra.operation_type.clone(), algebra);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/// Look up algebra for operation type
|
|
256
|
+
pub fn get(&self, operation_type: &str) -> Option<&OperationAlgebra> {
|
|
257
|
+
self.algebras.get(operation_type)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/// Check if operation type is registered
|
|
261
|
+
pub fn contains(&self, operation_type: &str) -> bool {
|
|
262
|
+
self.algebras.contains_key(operation_type)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/// Get all registered algebras
|
|
266
|
+
pub fn all_algebras(&self) -> Vec<&OperationAlgebra> {
|
|
267
|
+
self.algebras.values().collect()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/// Verify all registered operations declare convergence semantics
|
|
271
|
+
pub fn verify_all_declared(&self) -> Result<(), Vec<String>> {
|
|
272
|
+
let undeclared: Vec<String> = self
|
|
273
|
+
.algebras
|
|
274
|
+
.values()
|
|
275
|
+
.filter(|a| a.convergence_semantic == ConvergenceSemantic::ConsensusRequired)
|
|
276
|
+
.map(|a| a.operation_type.clone())
|
|
277
|
+
.collect();
|
|
278
|
+
|
|
279
|
+
if undeclared.is_empty() {
|
|
280
|
+
Ok(())
|
|
281
|
+
} else {
|
|
282
|
+
Err(undeclared)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/// Count operations by convergence semantic
|
|
287
|
+
pub fn convergence_statistics(&self) -> HashMap<String, usize> {
|
|
288
|
+
let mut stats = HashMap::new();
|
|
289
|
+
for algebra in self.algebras.values() {
|
|
290
|
+
let key = format!("{:?}", algebra.convergence_semantic);
|
|
291
|
+
*stats.entry(key).or_insert(0) += 1;
|
|
292
|
+
}
|
|
293
|
+
stats
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
impl Default for OperationAlgebraRegistry {
|
|
298
|
+
fn default() -> Self {
|
|
299
|
+
Self::new()
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
#[cfg(test)]
|
|
304
|
+
mod tests {
|
|
305
|
+
use super::*;
|
|
306
|
+
|
|
307
|
+
#[test]
|
|
308
|
+
fn test_registry_creation() {
|
|
309
|
+
let registry = OperationAlgebraRegistry::new();
|
|
310
|
+
assert!(registry.algebras.len() > 0);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
#[test]
|
|
314
|
+
fn test_setfield_is_lww_register() {
|
|
315
|
+
let registry = OperationAlgebraRegistry::new();
|
|
316
|
+
let setfield = registry.get("SetField").unwrap();
|
|
317
|
+
|
|
318
|
+
assert_eq!(setfield.conflict_type, ConflictType::Register);
|
|
319
|
+
assert_eq!(
|
|
320
|
+
setfield.convergence_semantic,
|
|
321
|
+
ConvergenceSemantic::DeterministicMerge
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
#[test]
|
|
326
|
+
fn test_increment_is_commutative() {
|
|
327
|
+
let registry = OperationAlgebraRegistry::new();
|
|
328
|
+
let increment = registry.get("Increment").unwrap();
|
|
329
|
+
|
|
330
|
+
assert!(increment.is_commutative());
|
|
331
|
+
assert_eq!(increment.conflict_type, ConflictType::CounterCRDT);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
#[test]
|
|
335
|
+
fn test_addtoset_is_commutative() {
|
|
336
|
+
let registry = OperationAlgebraRegistry::new();
|
|
337
|
+
let addtoset = registry.get("AddToSet").unwrap();
|
|
338
|
+
|
|
339
|
+
assert!(addtoset.is_commutative());
|
|
340
|
+
assert_eq!(addtoset.conflict_type, ConflictType::SetCRDT);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
#[test]
|
|
344
|
+
fn test_transfer_requires_consensus() {
|
|
345
|
+
let registry = OperationAlgebraRegistry::new();
|
|
346
|
+
let transfer = registry.get("Transfer").unwrap();
|
|
347
|
+
|
|
348
|
+
assert!(!transfer.is_commutative());
|
|
349
|
+
assert_eq!(
|
|
350
|
+
transfer.convergence_semantic,
|
|
351
|
+
ConvergenceSemantic::ConsensusRequired
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
#[test]
|
|
356
|
+
fn test_custom_registration() {
|
|
357
|
+
let mut registry = OperationAlgebraRegistry::new();
|
|
358
|
+
let custom = OperationAlgebra::new(
|
|
359
|
+
"MyCustomOp",
|
|
360
|
+
"any",
|
|
361
|
+
"any",
|
|
362
|
+
ConflictType::Custom("my_merge".to_string()),
|
|
363
|
+
ConvergenceSemantic::DeterministicMerge,
|
|
364
|
+
"Custom merge semantics",
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
registry.register(custom);
|
|
368
|
+
assert!(registry.contains("MyCustomOp"));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
#[test]
|
|
372
|
+
fn test_convergence_statistics() {
|
|
373
|
+
let registry = OperationAlgebraRegistry::new();
|
|
374
|
+
let stats = registry.convergence_statistics();
|
|
375
|
+
|
|
376
|
+
// Should have entries for different convergence semantics
|
|
377
|
+
assert!(stats.len() > 0);
|
|
378
|
+
// Most operations should be deterministic or commutative
|
|
379
|
+
let has_deterministic = stats.values().any(|count| *count > 0);
|
|
380
|
+
assert!(has_deterministic);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
#[test]
|
|
384
|
+
fn test_algebra_with_proofs() {
|
|
385
|
+
let algebra = OperationAlgebra::new(
|
|
386
|
+
"TestOp",
|
|
387
|
+
"test",
|
|
388
|
+
"test",
|
|
389
|
+
ConflictType::Commutative,
|
|
390
|
+
ConvergenceSemantic::StrictlyCommutative,
|
|
391
|
+
"test merge",
|
|
392
|
+
)
|
|
393
|
+
.with_proofs(
|
|
394
|
+
Some("proof1".to_string()),
|
|
395
|
+
Some("proof2".to_string()),
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
assert!(algebra.commutativity_proof.is_some());
|
|
399
|
+
assert!(algebra.determinism_proof.is_some());
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
#[test]
|
|
403
|
+
fn test_all_standard_operations_registered() {
|
|
404
|
+
let registry = OperationAlgebraRegistry::new();
|
|
405
|
+
|
|
406
|
+
let operations = vec![
|
|
407
|
+
"SetField",
|
|
408
|
+
"Increment",
|
|
409
|
+
"AddToSet",
|
|
410
|
+
"RemoveFromSet",
|
|
411
|
+
"Create",
|
|
412
|
+
"Delete",
|
|
413
|
+
"Transfer",
|
|
414
|
+
"Append",
|
|
415
|
+
"Noop",
|
|
416
|
+
];
|
|
417
|
+
|
|
418
|
+
for op in operations {
|
|
419
|
+
assert!(registry.contains(op), "Operation {} not registered", op);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
#[test]
|
|
424
|
+
fn test_operation_is_deterministic() {
|
|
425
|
+
let registry = OperationAlgebraRegistry::new();
|
|
426
|
+
|
|
427
|
+
// Commutative and deterministic merge are deterministic
|
|
428
|
+
let setfield = registry.get("SetField").unwrap();
|
|
429
|
+
assert!(setfield.is_deterministic());
|
|
430
|
+
|
|
431
|
+
let increment = registry.get("Increment").unwrap();
|
|
432
|
+
assert!(increment.is_deterministic());
|
|
433
|
+
|
|
434
|
+
// Consensus operations are NOT deterministic without consensus authority
|
|
435
|
+
let transfer = registry.get("Transfer").unwrap();
|
|
436
|
+
assert!(!transfer.is_deterministic());
|
|
437
|
+
}
|
|
438
|
+
}
|