@feltdb/core 0.4.12 → 0.4.14
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 +42 -25
- package/dist/cli/index.js +1 -1
- package/dist/create/cli.js +39 -4
- package/dist/create/create.js +30 -18
- package/dist/create/docker-compose-generator.js +68 -11
- 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/ConflictExplorer.d.ts.map +1 -1
- package/dist/studio/components/index.js +1 -1
- package/dist/studio/{components-Q0Nx6gLE.js → components-9kDSWiGL.js} +163 -47
- package/dist/studio/index.js +31 -28
- package/dist/studio-app/assets/{index-BHOmLZF-.js → index-DVdvmf69.js} +9 -9
- package/dist/studio-app/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/// Durability & Authority Guarantees
|
|
2
|
+
///
|
|
3
|
+
/// Provides formal durability and authority contracts:
|
|
4
|
+
/// - Restart recovery with consistency verification
|
|
5
|
+
/// - Crash-point consistency across all phases
|
|
6
|
+
/// - Authoritative-write semantics (no write conflicts)
|
|
7
|
+
/// - Reconciliation after divergence
|
|
8
|
+
/// - Write authority tracking and conflicts
|
|
9
|
+
/// - Recovery contracts
|
|
10
|
+
|
|
11
|
+
use serde::{Deserialize, Serialize};
|
|
12
|
+
use std::collections::HashMap;
|
|
13
|
+
|
|
14
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
|
15
|
+
pub enum WriteAuthority {
|
|
16
|
+
Primary,
|
|
17
|
+
Secondary,
|
|
18
|
+
Replica,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
22
|
+
pub struct WriteAuthorityRecord {
|
|
23
|
+
pub resource_id: String,
|
|
24
|
+
pub authority: WriteAuthority,
|
|
25
|
+
pub authority_node: String,
|
|
26
|
+
pub version: u64,
|
|
27
|
+
pub timestamp: u64,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
31
|
+
pub struct DurabilityContract {
|
|
32
|
+
pub level: String,
|
|
33
|
+
pub guarantees: Vec<String>,
|
|
34
|
+
pub violation_scenarios: Vec<String>,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
38
|
+
pub struct CrashPoint {
|
|
39
|
+
pub phase: String,
|
|
40
|
+
pub description: String,
|
|
41
|
+
pub recovery_required: bool,
|
|
42
|
+
pub data_loss_possible: bool,
|
|
43
|
+
pub state_consistency: String,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
47
|
+
pub struct RecoveryCheckpoint {
|
|
48
|
+
pub checkpoint_id: String,
|
|
49
|
+
pub timestamp: u64,
|
|
50
|
+
pub operations_count: u64,
|
|
51
|
+
pub data_hash: String,
|
|
52
|
+
pub state_valid: bool,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
56
|
+
pub struct ReconciliationStrategy {
|
|
57
|
+
pub strategy_name: String,
|
|
58
|
+
pub detect_divergence: bool,
|
|
59
|
+
pub merge_strategy: String,
|
|
60
|
+
pub conflict_resolution: String,
|
|
61
|
+
pub final_authority: String,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
65
|
+
pub struct AuthorityConflict {
|
|
66
|
+
pub resource_id: String,
|
|
67
|
+
pub conflicting_writes: Vec<(String, u64)>,
|
|
68
|
+
pub detected_at: u64,
|
|
69
|
+
pub resolution: String,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pub struct DurabilityGuarantees {
|
|
73
|
+
node_id: String,
|
|
74
|
+
write_authorities: HashMap<String, WriteAuthorityRecord>,
|
|
75
|
+
crash_points: Vec<CrashPoint>,
|
|
76
|
+
recovery_checkpoints: Vec<RecoveryCheckpoint>,
|
|
77
|
+
authority_conflicts: Vec<AuthorityConflict>,
|
|
78
|
+
checkpoint_counter: u64,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
impl DurabilityGuarantees {
|
|
82
|
+
pub fn new(node_id: String) -> Self {
|
|
83
|
+
DurabilityGuarantees {
|
|
84
|
+
node_id,
|
|
85
|
+
write_authorities: HashMap::new(),
|
|
86
|
+
crash_points: vec![
|
|
87
|
+
CrashPoint {
|
|
88
|
+
phase: "execution".to_string(),
|
|
89
|
+
description: "During operation execution".to_string(),
|
|
90
|
+
recovery_required: true,
|
|
91
|
+
data_loss_possible: false,
|
|
92
|
+
state_consistency: "partial".to_string(),
|
|
93
|
+
},
|
|
94
|
+
CrashPoint {
|
|
95
|
+
phase: "journal".to_string(),
|
|
96
|
+
description: "During journal append".to_string(),
|
|
97
|
+
recovery_required: true,
|
|
98
|
+
data_loss_possible: false,
|
|
99
|
+
state_consistency: "consistent".to_string(),
|
|
100
|
+
},
|
|
101
|
+
CrashPoint {
|
|
102
|
+
phase: "replication".to_string(),
|
|
103
|
+
description: "During replication".to_string(),
|
|
104
|
+
recovery_required: false,
|
|
105
|
+
data_loss_possible: false,
|
|
106
|
+
state_consistency: "consistent".to_string(),
|
|
107
|
+
},
|
|
108
|
+
CrashPoint {
|
|
109
|
+
phase: "convergence".to_string(),
|
|
110
|
+
description: "During convergence".to_string(),
|
|
111
|
+
recovery_required: true,
|
|
112
|
+
data_loss_possible: false,
|
|
113
|
+
state_consistency: "eventual".to_string(),
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
recovery_checkpoints: Vec::new(),
|
|
117
|
+
authority_conflicts: Vec::new(),
|
|
118
|
+
checkpoint_counter: 0,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
pub fn establish_write_authority(
|
|
123
|
+
&mut self,
|
|
124
|
+
resource_id: String,
|
|
125
|
+
authority_node: String,
|
|
126
|
+
) -> Result<(), String> {
|
|
127
|
+
if self.write_authorities.contains_key(&resource_id) {
|
|
128
|
+
return Err(format!("Write authority already established for {}", resource_id));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
self.write_authorities.insert(
|
|
132
|
+
resource_id.clone(),
|
|
133
|
+
WriteAuthorityRecord {
|
|
134
|
+
resource_id,
|
|
135
|
+
authority: WriteAuthority::Primary,
|
|
136
|
+
authority_node,
|
|
137
|
+
version: 1,
|
|
138
|
+
timestamp: std::time::SystemTime::now()
|
|
139
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
140
|
+
.unwrap_or_default()
|
|
141
|
+
.as_secs(),
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
Ok(())
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
pub fn get_write_authority(&self, resource_id: &str) -> Option<WriteAuthorityRecord> {
|
|
149
|
+
self.write_authorities.get(resource_id).cloned()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
pub fn verify_write_authority(
|
|
153
|
+
&self,
|
|
154
|
+
resource_id: &str,
|
|
155
|
+
writing_node: &str,
|
|
156
|
+
) -> Result<bool, String> {
|
|
157
|
+
match self.write_authorities.get(resource_id) {
|
|
158
|
+
Some(record) => Ok(record.authority_node == writing_node),
|
|
159
|
+
None => Err(format!("No write authority for {}", resource_id)),
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
pub fn create_recovery_checkpoint(&mut self, operations_count: u64, data_hash: String) -> Result<RecoveryCheckpoint, String> {
|
|
164
|
+
self.checkpoint_counter += 1;
|
|
165
|
+
|
|
166
|
+
let checkpoint = RecoveryCheckpoint {
|
|
167
|
+
checkpoint_id: format!("cp-{}-{}", self.node_id, self.checkpoint_counter),
|
|
168
|
+
timestamp: std::time::SystemTime::now()
|
|
169
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
170
|
+
.unwrap_or_default()
|
|
171
|
+
.as_secs(),
|
|
172
|
+
operations_count,
|
|
173
|
+
data_hash,
|
|
174
|
+
state_valid: true,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
self.recovery_checkpoints.push(checkpoint.clone());
|
|
178
|
+
Ok(checkpoint)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
pub fn restart_recovery(
|
|
182
|
+
&mut self,
|
|
183
|
+
last_checkpoint_id: Option<&str>,
|
|
184
|
+
) -> Result<RecoveryCheckpoint, String> {
|
|
185
|
+
let checkpoint = if let Some(id) = last_checkpoint_id {
|
|
186
|
+
self.recovery_checkpoints
|
|
187
|
+
.iter()
|
|
188
|
+
.find(|cp| cp.checkpoint_id == id)
|
|
189
|
+
.cloned()
|
|
190
|
+
.ok_or_else(|| format!("Checkpoint not found: {}", id))?
|
|
191
|
+
} else {
|
|
192
|
+
self.recovery_checkpoints
|
|
193
|
+
.last()
|
|
194
|
+
.cloned()
|
|
195
|
+
.ok_or_else(|| "No recovery checkpoint available".to_string())?
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
if !checkpoint.state_valid {
|
|
199
|
+
return Err("Last checkpoint is invalid".to_string());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Ok(checkpoint)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
pub fn detect_crash_consistency(
|
|
206
|
+
&self,
|
|
207
|
+
crash_phase: &str,
|
|
208
|
+
) -> Result<Vec<CrashPoint>, String> {
|
|
209
|
+
Ok(self
|
|
210
|
+
.crash_points
|
|
211
|
+
.iter()
|
|
212
|
+
.filter(|cp| cp.phase == crash_phase || crash_phase == "all")
|
|
213
|
+
.cloned()
|
|
214
|
+
.collect())
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
pub fn record_authority_conflict(
|
|
218
|
+
&mut self,
|
|
219
|
+
resource_id: String,
|
|
220
|
+
write_nodes: Vec<(String, u64)>,
|
|
221
|
+
resolution: String,
|
|
222
|
+
) {
|
|
223
|
+
self.authority_conflicts.push(AuthorityConflict {
|
|
224
|
+
resource_id,
|
|
225
|
+
conflicting_writes: write_nodes,
|
|
226
|
+
detected_at: std::time::SystemTime::now()
|
|
227
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
228
|
+
.unwrap_or_default()
|
|
229
|
+
.as_secs(),
|
|
230
|
+
resolution,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
pub fn get_durability_contract(&self) -> DurabilityContract {
|
|
235
|
+
DurabilityContract {
|
|
236
|
+
level: "strong".to_string(),
|
|
237
|
+
guarantees: vec![
|
|
238
|
+
"No data loss after successful write".to_string(),
|
|
239
|
+
"Atomic operation execution".to_string(),
|
|
240
|
+
"Consistent state after recovery".to_string(),
|
|
241
|
+
"Write authority prevents conflicts".to_string(),
|
|
242
|
+
"All crash points handle gracefully".to_string(),
|
|
243
|
+
],
|
|
244
|
+
violation_scenarios: vec![
|
|
245
|
+
"Complete cluster loss before replication".to_string(),
|
|
246
|
+
"Byzantine failures (untrusted nodes)".to_string(),
|
|
247
|
+
"Corrupted storage medium".to_string(),
|
|
248
|
+
],
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
pub fn reconcile_divergence(
|
|
253
|
+
&mut self,
|
|
254
|
+
local_version: u64,
|
|
255
|
+
remote_version: u64,
|
|
256
|
+
strategy: ReconciliationStrategy,
|
|
257
|
+
) -> Result<u64, String> {
|
|
258
|
+
let final_version = match strategy.conflict_resolution.as_str() {
|
|
259
|
+
"last-write-wins" => local_version.max(remote_version),
|
|
260
|
+
"first-write-wins" => local_version.min(remote_version),
|
|
261
|
+
"authority-decides" => {
|
|
262
|
+
if strategy.final_authority == self.node_id {
|
|
263
|
+
local_version
|
|
264
|
+
} else {
|
|
265
|
+
remote_version
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
_ => local_version,
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
Ok(final_version)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
pub fn verify_crash_recovery(&self, checkpoint: &RecoveryCheckpoint) -> Result<bool, String> {
|
|
275
|
+
if !checkpoint.state_valid {
|
|
276
|
+
return Ok(false);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
Ok(true)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
pub fn get_authority_conflicts(&self) -> Vec<AuthorityConflict> {
|
|
283
|
+
self.authority_conflicts.clone()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
pub fn get_checkpoint_count(&self) -> usize {
|
|
287
|
+
self.recovery_checkpoints.len()
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
impl Default for DurabilityGuarantees {
|
|
292
|
+
fn default() -> Self {
|
|
293
|
+
Self::new("node".to_string())
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
#[cfg(test)]
|
|
298
|
+
mod tests {
|
|
299
|
+
use super::*;
|
|
300
|
+
|
|
301
|
+
#[test]
|
|
302
|
+
fn test_write_authority() {
|
|
303
|
+
let mut dg = DurabilityGuarantees::new("node-1".to_string());
|
|
304
|
+
dg.establish_write_authority("res-1".to_string(), "node-1".to_string())
|
|
305
|
+
.unwrap();
|
|
306
|
+
|
|
307
|
+
assert!(dg.verify_write_authority("res-1", "node-1").unwrap());
|
|
308
|
+
assert!(!dg.verify_write_authority("res-1", "node-2").unwrap());
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#[test]
|
|
312
|
+
fn test_recovery_checkpoint() {
|
|
313
|
+
let mut dg = DurabilityGuarantees::new("node-1".to_string());
|
|
314
|
+
let cp = dg
|
|
315
|
+
.create_recovery_checkpoint(100, "hash123".to_string())
|
|
316
|
+
.unwrap();
|
|
317
|
+
|
|
318
|
+
assert_eq!(cp.operations_count, 100);
|
|
319
|
+
assert!(cp.state_valid);
|
|
320
|
+
assert_eq!(dg.get_checkpoint_count(), 1);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
#[test]
|
|
324
|
+
fn test_restart_recovery() {
|
|
325
|
+
let mut dg = DurabilityGuarantees::new("node-1".to_string());
|
|
326
|
+
dg.create_recovery_checkpoint(100, "hash1".to_string()).unwrap();
|
|
327
|
+
|
|
328
|
+
let recovered = dg.restart_recovery(None).unwrap();
|
|
329
|
+
assert_eq!(recovered.operations_count, 100);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
#[test]
|
|
333
|
+
fn test_crash_consistency() {
|
|
334
|
+
let dg = DurabilityGuarantees::new("node-1".to_string());
|
|
335
|
+
let journal_crashes = dg.detect_crash_consistency("journal").unwrap();
|
|
336
|
+
|
|
337
|
+
assert!(journal_crashes.len() > 0);
|
|
338
|
+
assert_eq!(journal_crashes[0].phase, "journal");
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
#[test]
|
|
342
|
+
fn test_durability_contract() {
|
|
343
|
+
let dg = DurabilityGuarantees::new("node-1".to_string());
|
|
344
|
+
let contract = dg.get_durability_contract();
|
|
345
|
+
|
|
346
|
+
assert_eq!(contract.level, "strong");
|
|
347
|
+
assert!(contract.guarantees.len() > 0);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
#[test]
|
|
351
|
+
fn test_reconciliation() {
|
|
352
|
+
let mut dg = DurabilityGuarantees::new("node-1".to_string());
|
|
353
|
+
let strategy = ReconciliationStrategy {
|
|
354
|
+
strategy_name: "lww".to_string(),
|
|
355
|
+
detect_divergence: true,
|
|
356
|
+
merge_strategy: "timestamp".to_string(),
|
|
357
|
+
conflict_resolution: "last-write-wins".to_string(),
|
|
358
|
+
final_authority: "node-1".to_string(),
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
let result = dg.reconcile_divergence(100, 200, strategy).unwrap();
|
|
362
|
+
assert_eq!(result, 200);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/// Durable Deduplication Set
|
|
2
|
+
///
|
|
3
|
+
/// Provides persistent storage for the dedup set (seen envelope IDs).
|
|
4
|
+
/// The dedup set prevents duplicate operations from being applied multiple times.
|
|
5
|
+
///
|
|
6
|
+
/// Invariant: Every envelope ID persisted survives process restart.
|
|
7
|
+
|
|
8
|
+
use crate::distributed_transactions::EnvelopeId;
|
|
9
|
+
use std::collections::HashSet;
|
|
10
|
+
use std::fs::{File, OpenOptions};
|
|
11
|
+
use std::io::{BufRead, BufReader, Write};
|
|
12
|
+
use std::path::{Path, PathBuf};
|
|
13
|
+
|
|
14
|
+
/// Persistent dedup set (seen envelope IDs)
|
|
15
|
+
pub struct DeduplicationSet {
|
|
16
|
+
path: PathBuf,
|
|
17
|
+
file: Option<File>,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
impl DeduplicationSet {
|
|
21
|
+
/// Create or open a persistent dedup set at the given path
|
|
22
|
+
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, std::io::Error> {
|
|
23
|
+
let path = path.as_ref().to_path_buf();
|
|
24
|
+
|
|
25
|
+
// Create parent directory if needed
|
|
26
|
+
if let Some(parent) = path.parent() {
|
|
27
|
+
std::fs::create_dir_all(parent)?;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Open dedup file in append mode
|
|
31
|
+
let file = OpenOptions::new()
|
|
32
|
+
.create(true)
|
|
33
|
+
.append(true)
|
|
34
|
+
.open(&path)?;
|
|
35
|
+
|
|
36
|
+
Ok(Self {
|
|
37
|
+
path,
|
|
38
|
+
file: Some(file),
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Record an envelope ID as seen (persist immediately with fsync)
|
|
43
|
+
pub fn mark_seen(&mut self, envelope_id: &EnvelopeId) -> Result<(), std::io::Error> {
|
|
44
|
+
if let Some(ref mut file) = self.file {
|
|
45
|
+
// Serialize envelope ID to JSON line
|
|
46
|
+
let json = serde_json::to_string(envelope_id)
|
|
47
|
+
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
|
48
|
+
|
|
49
|
+
// Write ID line (newline-delimited)
|
|
50
|
+
writeln!(file, "{}", json)?;
|
|
51
|
+
|
|
52
|
+
// Force durable persistence to disk
|
|
53
|
+
file.sync_all()?;
|
|
54
|
+
|
|
55
|
+
Ok(())
|
|
56
|
+
} else {
|
|
57
|
+
Err(std::io::Error::new(
|
|
58
|
+
std::io::ErrorKind::NotConnected,
|
|
59
|
+
"Dedup set file not open",
|
|
60
|
+
))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Load all seen envelope IDs from persistent storage
|
|
65
|
+
pub fn load_all(&self) -> Result<HashSet<EnvelopeId>, std::io::Error> {
|
|
66
|
+
if !self.path.exists() {
|
|
67
|
+
return Ok(HashSet::new());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let file = File::open(&self.path)?;
|
|
71
|
+
let reader = BufReader::new(file);
|
|
72
|
+
let mut seen = HashSet::new();
|
|
73
|
+
|
|
74
|
+
for line in reader.lines() {
|
|
75
|
+
let line = line?;
|
|
76
|
+
if line.trim().is_empty() {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
match serde_json::from_str::<EnvelopeId>(&line) {
|
|
81
|
+
Ok(envelope_id) => {
|
|
82
|
+
seen.insert(envelope_id);
|
|
83
|
+
}
|
|
84
|
+
Err(e) => {
|
|
85
|
+
return Err(std::io::Error::new(
|
|
86
|
+
std::io::ErrorKind::InvalidData,
|
|
87
|
+
format!("Failed to deserialize envelope ID: {}", e),
|
|
88
|
+
))
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Ok(seen)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Get count of persisted envelope IDs
|
|
97
|
+
pub fn count(&self) -> Result<usize, std::io::Error> {
|
|
98
|
+
let seen = self.load_all()?;
|
|
99
|
+
Ok(seen.len())
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Clear the dedup set (used for testing)
|
|
103
|
+
pub fn clear(&mut self) -> Result<(), std::io::Error> {
|
|
104
|
+
if let Some(file) = self.file.take() {
|
|
105
|
+
drop(file);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
std::fs::remove_file(&self.path)?;
|
|
109
|
+
|
|
110
|
+
let file = OpenOptions::new()
|
|
111
|
+
.create(true)
|
|
112
|
+
.append(true)
|
|
113
|
+
.open(&self.path)?;
|
|
114
|
+
|
|
115
|
+
self.file = Some(file);
|
|
116
|
+
Ok(())
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#[cfg(test)]
|
|
121
|
+
mod tests {
|
|
122
|
+
use super::*;
|
|
123
|
+
use crate::distributed_transactions::EnvelopeId;
|
|
124
|
+
use tempfile::TempDir;
|
|
125
|
+
|
|
126
|
+
fn create_test_id(node: &str, seq: u64) -> EnvelopeId {
|
|
127
|
+
EnvelopeId::new(node.to_string(), seq)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#[test]
|
|
131
|
+
fn test_dedup_set_mark_and_load() -> Result<(), Box<dyn std::error::Error>> {
|
|
132
|
+
let temp_dir = TempDir::new()?;
|
|
133
|
+
let dedup_path = temp_dir.path().join("dedup.set");
|
|
134
|
+
|
|
135
|
+
// Mark some IDs as seen
|
|
136
|
+
{
|
|
137
|
+
let mut dedup = DeduplicationSet::new(&dedup_path)?;
|
|
138
|
+
let id1 = create_test_id("node1", 1);
|
|
139
|
+
let id2 = create_test_id("node1", 2);
|
|
140
|
+
|
|
141
|
+
dedup.mark_seen(&id1)?;
|
|
142
|
+
dedup.mark_seen(&id2)?;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Reload and verify
|
|
146
|
+
{
|
|
147
|
+
let dedup = DeduplicationSet::new(&dedup_path)?;
|
|
148
|
+
let seen = dedup.load_all()?;
|
|
149
|
+
|
|
150
|
+
assert_eq!(seen.len(), 2);
|
|
151
|
+
assert!(seen.contains(&create_test_id("node1", 1)));
|
|
152
|
+
assert!(seen.contains(&create_test_id("node1", 2)));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
Ok(())
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#[test]
|
|
159
|
+
fn test_dedup_set_persists_across_restart() -> Result<(), Box<dyn std::error::Error>> {
|
|
160
|
+
let temp_dir = TempDir::new()?;
|
|
161
|
+
let dedup_path = temp_dir.path().join("dedup.set");
|
|
162
|
+
|
|
163
|
+
// Create and populate
|
|
164
|
+
{
|
|
165
|
+
let mut dedup = DeduplicationSet::new(&dedup_path)?;
|
|
166
|
+
for seq in 1..=5 {
|
|
167
|
+
let id = create_test_id("nodeA", seq);
|
|
168
|
+
dedup.mark_seen(&id)?;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Restart and verify all are still there
|
|
173
|
+
{
|
|
174
|
+
let dedup = DeduplicationSet::new(&dedup_path)?;
|
|
175
|
+
let seen = dedup.load_all()?;
|
|
176
|
+
|
|
177
|
+
assert_eq!(seen.len(), 5);
|
|
178
|
+
for seq in 1..=5 {
|
|
179
|
+
assert!(seen.contains(&create_test_id("nodeA", seq)));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
Ok(())
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#[test]
|
|
187
|
+
fn test_dedup_set_empty_recovery() -> Result<(), Box<dyn std::error::Error>> {
|
|
188
|
+
let temp_dir = TempDir::new()?;
|
|
189
|
+
let dedup_path = temp_dir.path().join("nonexistent.dedup");
|
|
190
|
+
|
|
191
|
+
let dedup = DeduplicationSet::new(&dedup_path)?;
|
|
192
|
+
let seen = dedup.load_all()?;
|
|
193
|
+
|
|
194
|
+
assert_eq!(seen.len(), 0);
|
|
195
|
+
|
|
196
|
+
Ok(())
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#[test]
|
|
200
|
+
fn test_dedup_set_fsync_semantics() -> Result<(), Box<dyn std::error::Error>> {
|
|
201
|
+
let temp_dir = TempDir::new()?;
|
|
202
|
+
let dedup_path = temp_dir.path().join("dedup.set");
|
|
203
|
+
|
|
204
|
+
let mut dedup = DeduplicationSet::new(&dedup_path)?;
|
|
205
|
+
|
|
206
|
+
// Mark many IDs to verify fsync durability
|
|
207
|
+
for seq in 1..=50 {
|
|
208
|
+
let id = create_test_id("node", seq);
|
|
209
|
+
dedup.mark_seen(&id)?;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Verify all 50 persisted
|
|
213
|
+
let loaded = dedup.load_all()?;
|
|
214
|
+
assert_eq!(loaded.len(), 50);
|
|
215
|
+
|
|
216
|
+
Ok(())
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#[test]
|
|
220
|
+
fn test_dedup_set_count() -> Result<(), Box<dyn std::error::Error>> {
|
|
221
|
+
let temp_dir = TempDir::new()?;
|
|
222
|
+
let dedup_path = temp_dir.path().join("dedup.set");
|
|
223
|
+
|
|
224
|
+
let mut dedup = DeduplicationSet::new(&dedup_path)?;
|
|
225
|
+
|
|
226
|
+
for seq in 1..=3 {
|
|
227
|
+
let id = create_test_id("node", seq);
|
|
228
|
+
dedup.mark_seen(&id)?;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
assert_eq!(dedup.count()?, 3);
|
|
232
|
+
|
|
233
|
+
Ok(())
|
|
234
|
+
}
|
|
235
|
+
}
|