@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,560 @@
|
|
|
1
|
+
/// Persistence Reality (Gate 7)
|
|
2
|
+
///
|
|
3
|
+
/// Tests actual durable implementations against crash scenarios.
|
|
4
|
+
/// What happens when:
|
|
5
|
+
/// process writes → crash → restart → recover
|
|
6
|
+
///
|
|
7
|
+
/// Critical test: Same data after arbitrary crash/recovery cycles.
|
|
8
|
+
|
|
9
|
+
use std::collections::HashMap;
|
|
10
|
+
|
|
11
|
+
/// Point at which to inject a crash during persistence
|
|
12
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
13
|
+
pub enum PersistenceCrashPoint {
|
|
14
|
+
/// Crash before any write begins
|
|
15
|
+
BeforeWrite,
|
|
16
|
+
/// Crash after operation enters buffer, before fsync
|
|
17
|
+
AfterBufferBeforeFsync,
|
|
18
|
+
/// Crash after fsync call starts, before completion
|
|
19
|
+
DuringFsync,
|
|
20
|
+
/// Crash after fsync completes, before returning
|
|
21
|
+
AfterFsyncBeforeReturn,
|
|
22
|
+
/// Crash after write fully returns
|
|
23
|
+
AfterWriteReturns,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Write-ahead log entry (simplified)
|
|
27
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
28
|
+
pub struct WALEntry {
|
|
29
|
+
pub lsn: u64, // Log sequence number
|
|
30
|
+
pub operation_id: String,
|
|
31
|
+
pub data: Vec<u8>,
|
|
32
|
+
pub checksum: u32,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl WALEntry {
|
|
36
|
+
pub fn new(lsn: u64, operation_id: String, data: Vec<u8>) -> Self {
|
|
37
|
+
let checksum = Self::compute_checksum(&data);
|
|
38
|
+
Self {
|
|
39
|
+
lsn,
|
|
40
|
+
operation_id,
|
|
41
|
+
data,
|
|
42
|
+
checksum,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fn compute_checksum(data: &[u8]) -> u32 {
|
|
47
|
+
let mut sum: u32 = 0;
|
|
48
|
+
for byte in data {
|
|
49
|
+
sum = sum.wrapping_add(*byte as u32);
|
|
50
|
+
}
|
|
51
|
+
sum
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fn verify_checksum(&self) -> bool {
|
|
55
|
+
Self::compute_checksum(&self.data) == self.checksum
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Simulate torn write: truncate data randomly
|
|
59
|
+
fn truncate_randomly(&mut self, max_bytes: usize) {
|
|
60
|
+
if self.data.len() > max_bytes {
|
|
61
|
+
self.data.truncate(max_bytes);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Simulates a durable write-ahead log with crash points
|
|
67
|
+
pub struct DurablePersistenceLayer {
|
|
68
|
+
/// In-memory buffer (not yet durable)
|
|
69
|
+
write_buffer: Vec<u8>,
|
|
70
|
+
/// Committed entries (durable)
|
|
71
|
+
committed_log: Vec<WALEntry>,
|
|
72
|
+
/// Next LSN
|
|
73
|
+
next_lsn: u64,
|
|
74
|
+
/// Tracks fsync state
|
|
75
|
+
fsynced_lsn: u64,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
impl DurablePersistenceLayer {
|
|
79
|
+
pub fn new() -> Self {
|
|
80
|
+
Self {
|
|
81
|
+
write_buffer: Vec::new(),
|
|
82
|
+
committed_log: Vec::new(),
|
|
83
|
+
next_lsn: 1,
|
|
84
|
+
fsynced_lsn: 0,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Write an operation with potential crash
|
|
89
|
+
pub fn write_with_crash(
|
|
90
|
+
&mut self,
|
|
91
|
+
operation_id: String,
|
|
92
|
+
data: Vec<u8>,
|
|
93
|
+
crash_point: PersistenceCrashPoint,
|
|
94
|
+
) -> WriteResult {
|
|
95
|
+
match crash_point {
|
|
96
|
+
PersistenceCrashPoint::BeforeWrite => {
|
|
97
|
+
// Crash before anything is written
|
|
98
|
+
WriteResult::Crashed {
|
|
99
|
+
written_to_buffer: false,
|
|
100
|
+
fsynced: false,
|
|
101
|
+
lsn: None,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
PersistenceCrashPoint::AfterBufferBeforeFsync => {
|
|
105
|
+
// Buffer write succeeds, but crash before fsync
|
|
106
|
+
self.write_buffer.extend_from_slice(&data);
|
|
107
|
+
WriteResult::Crashed {
|
|
108
|
+
written_to_buffer: true,
|
|
109
|
+
fsynced: false,
|
|
110
|
+
lsn: Some(self.next_lsn),
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
PersistenceCrashPoint::DuringFsync => {
|
|
114
|
+
// Partial fsync (some data written, some not)
|
|
115
|
+
self.write_buffer.extend_from_slice(&data);
|
|
116
|
+
let mut entry = WALEntry::new(self.next_lsn, operation_id, data);
|
|
117
|
+
|
|
118
|
+
// Simulate torn write: truncate at random point
|
|
119
|
+
entry.truncate_randomly(entry.data.len() / 2);
|
|
120
|
+
|
|
121
|
+
// Partially committed (corrupted)
|
|
122
|
+
self.committed_log.push(entry);
|
|
123
|
+
|
|
124
|
+
WriteResult::Crashed {
|
|
125
|
+
written_to_buffer: true,
|
|
126
|
+
fsynced: false,
|
|
127
|
+
lsn: Some(self.next_lsn),
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
PersistenceCrashPoint::AfterFsyncBeforeReturn => {
|
|
131
|
+
// Fsync succeeds but crash before returning
|
|
132
|
+
self.write_buffer.extend_from_slice(&data);
|
|
133
|
+
let entry = WALEntry::new(self.next_lsn, operation_id, data);
|
|
134
|
+
self.committed_log.push(entry.clone());
|
|
135
|
+
self.fsynced_lsn = self.next_lsn;
|
|
136
|
+
|
|
137
|
+
WriteResult::Crashed {
|
|
138
|
+
written_to_buffer: true,
|
|
139
|
+
fsynced: true,
|
|
140
|
+
lsn: Some(self.next_lsn),
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
PersistenceCrashPoint::AfterWriteReturns => {
|
|
144
|
+
// Full write succeeds
|
|
145
|
+
self.write_buffer.extend_from_slice(&data);
|
|
146
|
+
let entry = WALEntry::new(self.next_lsn, operation_id, data);
|
|
147
|
+
self.committed_log.push(entry);
|
|
148
|
+
self.fsynced_lsn = self.next_lsn;
|
|
149
|
+
|
|
150
|
+
let lsn = self.next_lsn;
|
|
151
|
+
self.next_lsn += 1;
|
|
152
|
+
|
|
153
|
+
WriteResult::Success { lsn }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// Recover from crash, returning what was durable
|
|
159
|
+
pub fn recover(&self) -> RecoveryResult {
|
|
160
|
+
let mut recovered_operations = Vec::new();
|
|
161
|
+
let mut corrupted_entries = Vec::new();
|
|
162
|
+
|
|
163
|
+
for entry in &self.committed_log {
|
|
164
|
+
if entry.verify_checksum() {
|
|
165
|
+
recovered_operations.push(entry.operation_id.clone());
|
|
166
|
+
} else {
|
|
167
|
+
corrupted_entries.push(entry.lsn);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
RecoveryResult {
|
|
172
|
+
recovered_operations,
|
|
173
|
+
corrupted_entries,
|
|
174
|
+
recovered_lsn: self.fsynced_lsn,
|
|
175
|
+
total_entries: self.committed_log.len(),
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// Simulate disk corruption: flip random bytes
|
|
180
|
+
pub fn corrupt_tail(&mut self, num_entries_affected: usize) {
|
|
181
|
+
let start = self.committed_log.len().saturating_sub(num_entries_affected);
|
|
182
|
+
for entry in &mut self.committed_log[start..] {
|
|
183
|
+
if !entry.data.is_empty() {
|
|
184
|
+
entry.data[0] ^= 0xFF; // Flip all bits in first byte
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Simulate truncation at arbitrary point
|
|
190
|
+
pub fn truncate_at_byte(&mut self, byte_offset: usize) {
|
|
191
|
+
let mut total_bytes = 0;
|
|
192
|
+
let mut entries_to_keep = 0;
|
|
193
|
+
|
|
194
|
+
for entry in &self.committed_log {
|
|
195
|
+
if total_bytes + entry.data.len() > byte_offset {
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
total_bytes += entry.data.len();
|
|
199
|
+
entries_to_keep += 1;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Truncate and corrupt last entry if partial
|
|
203
|
+
if entries_to_keep < self.committed_log.len() && entries_to_keep > 0 {
|
|
204
|
+
let last_entry = &mut self.committed_log[entries_to_keep - 1];
|
|
205
|
+
let bytes_into_entry = byte_offset - total_bytes;
|
|
206
|
+
if bytes_into_entry < last_entry.data.len() {
|
|
207
|
+
last_entry.data.truncate(bytes_into_entry);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
self.committed_log.truncate(entries_to_keep);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
pub fn get_log(&self) -> &[WALEntry] {
|
|
215
|
+
&self.committed_log
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
pub fn fsynced_lsn(&self) -> u64 {
|
|
219
|
+
self.fsynced_lsn
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
impl Default for DurablePersistenceLayer {
|
|
224
|
+
fn default() -> Self {
|
|
225
|
+
Self::new()
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// Result of a write operation
|
|
230
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
231
|
+
pub enum WriteResult {
|
|
232
|
+
Success { lsn: u64 },
|
|
233
|
+
Crashed {
|
|
234
|
+
written_to_buffer: bool,
|
|
235
|
+
fsynced: bool,
|
|
236
|
+
lsn: Option<u64>,
|
|
237
|
+
},
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// Result of recovery from crash
|
|
241
|
+
#[derive(Clone, Debug)]
|
|
242
|
+
pub struct RecoveryResult {
|
|
243
|
+
pub recovered_operations: Vec<String>,
|
|
244
|
+
pub corrupted_entries: Vec<u64>,
|
|
245
|
+
pub recovered_lsn: u64,
|
|
246
|
+
pub total_entries: usize,
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/// Persistence contract verifier
|
|
250
|
+
pub struct PersistenceContractVerifier {
|
|
251
|
+
scenarios: Vec<PersistenceScenario>,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#[derive(Clone, Debug)]
|
|
255
|
+
struct PersistenceScenario {
|
|
256
|
+
name: String,
|
|
257
|
+
written_operations: Vec<String>,
|
|
258
|
+
recovered_operations: Vec<String>,
|
|
259
|
+
corrupted_count: usize,
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
impl PersistenceContractVerifier {
|
|
263
|
+
pub fn new() -> Self {
|
|
264
|
+
Self {
|
|
265
|
+
scenarios: Vec::new(),
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/// Record a crash/recovery scenario
|
|
270
|
+
pub fn record_scenario(
|
|
271
|
+
&mut self,
|
|
272
|
+
name: String,
|
|
273
|
+
written: Vec<String>,
|
|
274
|
+
recovered: Vec<String>,
|
|
275
|
+
corrupted: usize,
|
|
276
|
+
) {
|
|
277
|
+
self.scenarios.push(PersistenceScenario {
|
|
278
|
+
name,
|
|
279
|
+
written_operations: written,
|
|
280
|
+
recovered_operations: recovered,
|
|
281
|
+
corrupted_count: corrupted,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/// Verify: if all operations were fsynced, they must all recover
|
|
286
|
+
pub fn verify_fsync_durability(&self) -> FsyncDurabilityResult {
|
|
287
|
+
for scenario in &self.scenarios {
|
|
288
|
+
if scenario.corrupted_count == 0 {
|
|
289
|
+
// No corruption = all written should be recovered
|
|
290
|
+
if scenario.written_operations.len() != scenario.recovered_operations.len() {
|
|
291
|
+
return FsyncDurabilityResult::Violated {
|
|
292
|
+
scenario: scenario.name.clone(),
|
|
293
|
+
written: scenario.written_operations.len(),
|
|
294
|
+
recovered: scenario.recovered_operations.len(),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Check all written are in recovered
|
|
299
|
+
for op in &scenario.written_operations {
|
|
300
|
+
if !scenario.recovered_operations.contains(op) {
|
|
301
|
+
return FsyncDurabilityResult::Violated {
|
|
302
|
+
scenario: scenario.name.clone(),
|
|
303
|
+
written: scenario.written_operations.len(),
|
|
304
|
+
recovered: scenario.recovered_operations.len(),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
FsyncDurabilityResult::Satisfied {
|
|
312
|
+
scenarios_checked: self.scenarios.len(),
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/// Verify: corrupted entries don't silently return garbage
|
|
317
|
+
pub fn verify_corruption_detection(&self) -> CorruptionDetectionResult {
|
|
318
|
+
let mut corrupted_undetected = 0;
|
|
319
|
+
|
|
320
|
+
for scenario in &self.scenarios {
|
|
321
|
+
if scenario.corrupted_count > 0 {
|
|
322
|
+
// If corruption occurred, it should be detected or entry dropped
|
|
323
|
+
// (not silently included in recovered)
|
|
324
|
+
let expected_max = scenario.written_operations.len().saturating_sub(scenario.corrupted_count);
|
|
325
|
+
if scenario.recovered_operations.len() > expected_max {
|
|
326
|
+
corrupted_undetected += 1;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if corrupted_undetected == 0 {
|
|
332
|
+
CorruptionDetectionResult::Satisfied {
|
|
333
|
+
scenarios_checked: self.scenarios.len(),
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
CorruptionDetectionResult::Violated {
|
|
337
|
+
undetected_corruptions: corrupted_undetected,
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
pub fn get_scenarios(&self) -> &[PersistenceScenario] {
|
|
343
|
+
&self.scenarios
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
impl Default for PersistenceContractVerifier {
|
|
348
|
+
fn default() -> Self {
|
|
349
|
+
Self::new()
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/// Result of fsync durability verification
|
|
354
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
355
|
+
pub enum FsyncDurabilityResult {
|
|
356
|
+
Satisfied { scenarios_checked: usize },
|
|
357
|
+
Violated {
|
|
358
|
+
scenario: String,
|
|
359
|
+
written: usize,
|
|
360
|
+
recovered: usize,
|
|
361
|
+
},
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/// Result of corruption detection verification
|
|
365
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
366
|
+
pub enum CorruptionDetectionResult {
|
|
367
|
+
Satisfied { scenarios_checked: usize },
|
|
368
|
+
Violated { undetected_corruptions: usize },
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
#[cfg(test)]
|
|
372
|
+
mod tests {
|
|
373
|
+
use super::*;
|
|
374
|
+
|
|
375
|
+
#[test]
|
|
376
|
+
fn test_wal_creation() {
|
|
377
|
+
let wal = DurablePersistenceLayer::new();
|
|
378
|
+
assert_eq!(wal.fsynced_lsn(), 0);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
#[test]
|
|
382
|
+
fn test_write_with_crash_before_write() {
|
|
383
|
+
let mut wal = DurablePersistenceLayer::new();
|
|
384
|
+
let result = wal.write_with_crash(
|
|
385
|
+
"op1".to_string(),
|
|
386
|
+
vec![1, 2, 3],
|
|
387
|
+
PersistenceCrashPoint::BeforeWrite,
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
assert!(matches!(result, WriteResult::Crashed { .. }));
|
|
391
|
+
assert_eq!(wal.get_log().len(), 0);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
#[test]
|
|
395
|
+
fn test_write_with_crash_after_fsync() {
|
|
396
|
+
let mut wal = DurablePersistenceLayer::new();
|
|
397
|
+
let result = wal.write_with_crash(
|
|
398
|
+
"op1".to_string(),
|
|
399
|
+
vec![1, 2, 3],
|
|
400
|
+
PersistenceCrashPoint::AfterFsyncBeforeReturn,
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
assert!(matches!(
|
|
404
|
+
result,
|
|
405
|
+
WriteResult::Crashed {
|
|
406
|
+
fsynced: true,
|
|
407
|
+
..
|
|
408
|
+
}
|
|
409
|
+
));
|
|
410
|
+
assert_eq!(wal.get_log().len(), 1);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
#[test]
|
|
414
|
+
fn test_write_success() {
|
|
415
|
+
let mut wal = DurablePersistenceLayer::new();
|
|
416
|
+
let result = wal.write_with_crash(
|
|
417
|
+
"op1".to_string(),
|
|
418
|
+
vec![1, 2, 3],
|
|
419
|
+
PersistenceCrashPoint::AfterWriteReturns,
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
assert!(matches!(result, WriteResult::Success { lsn: 1 }));
|
|
423
|
+
assert_eq!(wal.get_log().len(), 1);
|
|
424
|
+
assert_eq!(wal.fsynced_lsn(), 1);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
#[test]
|
|
428
|
+
fn test_recovery_with_no_corruption() {
|
|
429
|
+
let mut wal = DurablePersistenceLayer::new();
|
|
430
|
+
wal.write_with_crash(
|
|
431
|
+
"op1".to_string(),
|
|
432
|
+
vec![1, 2, 3],
|
|
433
|
+
PersistenceCrashPoint::AfterWriteReturns,
|
|
434
|
+
);
|
|
435
|
+
wal.write_with_crash(
|
|
436
|
+
"op2".to_string(),
|
|
437
|
+
vec![4, 5, 6],
|
|
438
|
+
PersistenceCrashPoint::AfterWriteReturns,
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
let recovery = wal.recover();
|
|
442
|
+
assert_eq!(recovery.recovered_operations.len(), 2);
|
|
443
|
+
assert_eq!(recovery.corrupted_entries.len(), 0);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
#[test]
|
|
447
|
+
fn test_recovery_detects_corruption() {
|
|
448
|
+
let mut wal = DurablePersistenceLayer::new();
|
|
449
|
+
wal.write_with_crash(
|
|
450
|
+
"op1".to_string(),
|
|
451
|
+
vec![1, 2, 3],
|
|
452
|
+
PersistenceCrashPoint::AfterWriteReturns,
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
// Corrupt the entry
|
|
456
|
+
wal.corrupt_tail(1);
|
|
457
|
+
|
|
458
|
+
let recovery = wal.recover();
|
|
459
|
+
assert!(recovery.corrupted_entries.len() > 0);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
#[test]
|
|
463
|
+
fn test_truncate_at_byte() {
|
|
464
|
+
let mut wal = DurablePersistenceLayer::new();
|
|
465
|
+
wal.write_with_crash(
|
|
466
|
+
"op1".to_string(),
|
|
467
|
+
vec![1, 2, 3, 4, 5],
|
|
468
|
+
PersistenceCrashPoint::AfterWriteReturns,
|
|
469
|
+
);
|
|
470
|
+
wal.write_with_crash(
|
|
471
|
+
"op2".to_string(),
|
|
472
|
+
vec![6, 7, 8, 9, 10],
|
|
473
|
+
PersistenceCrashPoint::AfterWriteReturns,
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
// Truncate after 7 bytes (partway through second entry)
|
|
477
|
+
wal.truncate_at_byte(7);
|
|
478
|
+
|
|
479
|
+
let recovery = wal.recover();
|
|
480
|
+
// Should have partial or truncated data
|
|
481
|
+
assert!(recovery.total_entries <= 2);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
#[test]
|
|
485
|
+
fn test_fsync_durability_contract() {
|
|
486
|
+
let mut verifier = PersistenceContractVerifier::new();
|
|
487
|
+
|
|
488
|
+
verifier.record_scenario(
|
|
489
|
+
"full_fsync".to_string(),
|
|
490
|
+
vec!["op1".to_string(), "op2".to_string()],
|
|
491
|
+
vec!["op1".to_string(), "op2".to_string()],
|
|
492
|
+
0,
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
let result = verifier.verify_fsync_durability();
|
|
496
|
+
assert!(matches!(result, FsyncDurabilityResult::Satisfied { .. }));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
#[test]
|
|
500
|
+
fn test_fsync_durability_violation() {
|
|
501
|
+
let mut verifier = PersistenceContractVerifier::new();
|
|
502
|
+
|
|
503
|
+
verifier.record_scenario(
|
|
504
|
+
"lost_data".to_string(),
|
|
505
|
+
vec!["op1".to_string(), "op2".to_string(), "op3".to_string()],
|
|
506
|
+
vec!["op1".to_string()],
|
|
507
|
+
0,
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
let result = verifier.verify_fsync_durability();
|
|
511
|
+
assert!(matches!(result, FsyncDurabilityResult::Violated { .. }));
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
#[test]
|
|
515
|
+
fn test_corruption_detection() {
|
|
516
|
+
let mut verifier = PersistenceContractVerifier::new();
|
|
517
|
+
|
|
518
|
+
// Corruption detected (not silently included)
|
|
519
|
+
verifier.record_scenario(
|
|
520
|
+
"detected_corruption".to_string(),
|
|
521
|
+
vec!["op1".to_string(), "op2".to_string()],
|
|
522
|
+
vec!["op1".to_string()], // op2 detected as corrupted and dropped
|
|
523
|
+
1,
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
let result = verifier.verify_corruption_detection();
|
|
527
|
+
assert!(matches!(result, CorruptionDetectionResult::Satisfied { .. }));
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
#[test]
|
|
531
|
+
fn test_multiple_crash_points() {
|
|
532
|
+
let crash_points = vec![
|
|
533
|
+
PersistenceCrashPoint::BeforeWrite,
|
|
534
|
+
PersistenceCrashPoint::AfterBufferBeforeFsync,
|
|
535
|
+
PersistenceCrashPoint::DuringFsync,
|
|
536
|
+
PersistenceCrashPoint::AfterFsyncBeforeReturn,
|
|
537
|
+
PersistenceCrashPoint::AfterWriteReturns,
|
|
538
|
+
];
|
|
539
|
+
|
|
540
|
+
for crash_point in crash_points {
|
|
541
|
+
let mut wal = DurablePersistenceLayer::new();
|
|
542
|
+
let result = wal.write_with_crash("op1".to_string(), vec![1, 2, 3], crash_point);
|
|
543
|
+
|
|
544
|
+
// Verify recovery is deterministic regardless of crash point
|
|
545
|
+
let _recovery = wal.recover();
|
|
546
|
+
// Should not panic
|
|
547
|
+
assert!(true);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
#[test]
|
|
552
|
+
fn test_wal_entry_checksum() {
|
|
553
|
+
let entry = WALEntry::new(1, "op1".to_string(), vec![1, 2, 3]);
|
|
554
|
+
assert!(entry.verify_checksum());
|
|
555
|
+
|
|
556
|
+
let mut corrupted = entry.clone();
|
|
557
|
+
corrupted.data[0] ^= 0xFF;
|
|
558
|
+
assert!(!corrupted.verify_checksum());
|
|
559
|
+
}
|
|
560
|
+
}
|