@feltdb/core 0.4.12 → 0.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/dist/cli/commands.js +5 -2
  2. package/dist/cli/index.js +1 -1
  3. package/dist/create/cli.js +16 -1
  4. package/dist/create/create.js +30 -16
  5. package/dist/create/docker-compose-generator.js +48 -9
  6. package/dist/create/package-versions.js +1 -1
  7. package/dist/create/server-source/Cargo.lock +2238 -0
  8. package/dist/create/server-source/Cargo.toml +7 -0
  9. package/dist/create/server-source/crates/feltdb/Cargo.lock +175 -0
  10. package/dist/create/server-source/crates/feltdb/Cargo.toml +79 -0
  11. package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +370 -0
  12. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +589 -0
  13. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +259 -0
  14. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +446 -0
  15. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +272 -0
  16. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +378 -0
  17. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +414 -0
  18. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +486 -0
  19. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +273 -0
  20. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +587 -0
  21. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +513 -0
  22. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +395 -0
  23. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +469 -0
  24. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +409 -0
  25. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +627 -0
  26. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +383 -0
  27. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +298 -0
  28. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +1698 -0
  29. package/dist/create/server-source/crates/feltdb/src/acquisition.rs +286 -0
  30. package/dist/create/server-source/crates/feltdb/src/admission.rs +192 -0
  31. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +477 -0
  32. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +566 -0
  33. package/dist/create/server-source/crates/feltdb/src/analytics.rs +475 -0
  34. package/dist/create/server-source/crates/feltdb/src/application.rs +2244 -0
  35. package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +1070 -0
  36. package/dist/create/server-source/crates/feltdb/src/authorization.rs +1030 -0
  37. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +266 -0
  38. package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +8 -0
  39. package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +418 -0
  40. package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +482 -0
  41. package/dist/create/server-source/crates/feltdb/src/capability.rs +903 -0
  42. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +261 -0
  43. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +78 -0
  44. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +2214 -0
  45. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +194 -0
  46. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +427 -0
  47. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +453 -0
  48. package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +465 -0
  49. package/dist/create/server-source/crates/feltdb/src/convergence.rs +618 -0
  50. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +418 -0
  51. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +380 -0
  52. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +362 -0
  53. package/dist/create/server-source/crates/feltdb/src/cron.rs +294 -0
  54. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +474 -0
  55. package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +1003 -0
  56. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +536 -0
  57. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +364 -0
  58. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +235 -0
  59. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +207 -0
  60. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +316 -0
  61. package/dist/create/server-source/crates/feltdb/src/execution.rs +426 -0
  62. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +219 -0
  63. package/dist/create/server-source/crates/feltdb/src/indexing.rs +779 -0
  64. package/dist/create/server-source/crates/feltdb/src/lib.rs +2838 -0
  65. package/dist/create/server-source/crates/feltdb/src/materialization.rs +184 -0
  66. package/dist/create/server-source/crates/feltdb/src/metrics.rs +267 -0
  67. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +311 -0
  68. package/dist/create/server-source/crates/feltdb/src/observability.rs +366 -0
  69. package/dist/create/server-source/crates/feltdb/src/operation.rs +251 -0
  70. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +438 -0
  71. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +344 -0
  72. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +477 -0
  73. package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +166 -0
  74. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +261 -0
  75. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +560 -0
  76. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +3226 -0
  77. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +201 -0
  78. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +263 -0
  79. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +484 -0
  80. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +216 -0
  81. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +281 -0
  82. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +323 -0
  83. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +573 -0
  84. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +404 -0
  85. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +418 -0
  86. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +381 -0
  87. package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +523 -0
  88. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +334 -0
  89. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +306 -0
  90. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +355 -0
  91. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +395 -0
  92. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +346 -0
  93. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +430 -0
  94. package/dist/create/server-source/crates/feltdb/src/production_api.rs +400 -0
  95. package/dist/create/server-source/crates/feltdb/src/provenance.rs +207 -0
  96. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +217 -0
  97. package/dist/create/server-source/crates/feltdb/src/references.rs +404 -0
  98. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +401 -0
  99. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +160 -0
  100. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +132 -0
  101. package/dist/create/server-source/crates/feltdb/src/routing.rs +409 -0
  102. package/dist/create/server-source/crates/feltdb/src/sharding.rs +523 -0
  103. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +3118 -0
  104. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +291 -0
  105. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +376 -0
  106. package/dist/create/server-source/crates/feltdb/src/storage.rs +267 -0
  107. package/dist/create/server-source/crates/feltdb/src/submission.rs +477 -0
  108. package/dist/create/server-source/crates/feltdb/src/sync.rs +483 -0
  109. package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +822 -0
  110. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +366 -0
  111. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +473 -0
  112. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +949 -0
  113. package/dist/create/server-source/crates/feltdb/src/transactions.rs +646 -0
  114. package/dist/create/server-source/crates/feltdb/src/trigger.rs +390 -0
  115. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +928 -0
  116. package/dist/create/server-source/crates/feltdb/src/workflow.rs +713 -0
  117. package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +510 -0
  118. package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +354 -0
  119. package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +594 -0
  120. package/dist/create/server-source/crates/feltdb/src/workload.rs +1310 -0
  121. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +23 -0
  122. package/dist/create/server-source/crates/feltdb-server/README.md +79 -0
  123. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +73 -0
  124. package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +425 -0
  125. package/dist/create/server-source/crates/feltdb-server/src/artifacts.rs +449 -0
  126. package/dist/create/server-source/crates/feltdb-server/src/audit.rs +59 -0
  127. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +308 -0
  128. package/dist/create/server-source/crates/feltdb-server/src/authorization.rs +228 -0
  129. package/dist/create/server-source/crates/feltdb-server/src/backup.rs +416 -0
  130. package/dist/create/server-source/crates/feltdb-server/src/causal.rs +218 -0
  131. package/dist/create/server-source/crates/feltdb-server/src/certification.rs +223 -0
  132. package/dist/create/server-source/crates/feltdb-server/src/clock.rs +132 -0
  133. package/dist/create/server-source/crates/feltdb-server/src/cluster.rs +165 -0
  134. package/dist/create/server-source/crates/feltdb-server/src/connections.rs +1044 -0
  135. package/dist/create/server-source/crates/feltdb-server/src/content.rs +111 -0
  136. package/dist/create/server-source/crates/feltdb-server/src/identity.rs +250 -0
  137. package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +78 -0
  138. package/dist/create/server-source/crates/feltdb-server/src/key_provider.rs +247 -0
  139. package/dist/create/server-source/crates/feltdb-server/src/leases.rs +123 -0
  140. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +25 -0
  141. package/dist/create/server-source/crates/feltdb-server/src/main.rs +8715 -0
  142. package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +97 -0
  143. package/dist/create/server-source/crates/feltdb-server/src/portable_bundle.rs +350 -0
  144. package/dist/create/server-source/crates/feltdb-server/src/principals.rs +510 -0
  145. package/dist/create/server-source/crates/feltdb-server/src/providers.rs +269 -0
  146. package/dist/create/server-source/crates/feltdb-server/src/releases.rs +2563 -0
  147. package/dist/create/server-source/crates/feltdb-server/src/sessions.rs +156 -0
  148. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +1097 -0
  149. package/dist/create/server-source/crates/feltdb-server/src/versions.rs +264 -0
  150. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +31 -0
  151. package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +1086 -0
  152. package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
  153. package/dist/flowspec.d.ts +2 -0
  154. package/dist/flowspec.d.ts.map +1 -1
  155. package/dist/flowspec.js +28 -4
  156. package/dist/state-contract.d.ts +7 -1
  157. package/dist/state-contract.d.ts.map +1 -1
  158. package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
  159. package/dist/studio/components/index.js +1 -1
  160. package/dist/studio/{components-Q0Nx6gLE.js → components-bHTARBin.js} +156 -46
  161. package/dist/studio/index.js +31 -28
  162. package/dist/studio-app/assets/{index-BHOmLZF-.js → index-BqKquLuU.js} +8 -8
  163. package/dist/studio-app/index.html +1 -1
  164. 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
+ }