@feltdb/core 0.4.12 → 0.4.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands.js +5 -2
- package/dist/cli/index.js +1 -1
- package/dist/create/cli.js +16 -1
- package/dist/create/create.js +30 -16
- package/dist/create/docker-compose-generator.js +48 -9
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +2238 -0
- package/dist/create/server-source/Cargo.toml +7 -0
- package/dist/create/server-source/crates/feltdb/Cargo.lock +175 -0
- package/dist/create/server-source/crates/feltdb/Cargo.toml +79 -0
- package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +370 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +589 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +259 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +446 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +272 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +378 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +414 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +486 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +273 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +587 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +513 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +395 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +469 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +409 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +627 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +383 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +298 -0
- package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +1698 -0
- package/dist/create/server-source/crates/feltdb/src/acquisition.rs +286 -0
- package/dist/create/server-source/crates/feltdb/src/admission.rs +192 -0
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +566 -0
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +475 -0
- package/dist/create/server-source/crates/feltdb/src/application.rs +2244 -0
- package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +1070 -0
- package/dist/create/server-source/crates/feltdb/src/authorization.rs +1030 -0
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +266 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +8 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +482 -0
- package/dist/create/server-source/crates/feltdb/src/capability.rs +903 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +78 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +2214 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +194 -0
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +427 -0
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +453 -0
- package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +465 -0
- package/dist/create/server-source/crates/feltdb/src/convergence.rs +618 -0
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +380 -0
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +362 -0
- package/dist/create/server-source/crates/feltdb/src/cron.rs +294 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +474 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +1003 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +536 -0
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +364 -0
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +235 -0
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +316 -0
- package/dist/create/server-source/crates/feltdb/src/execution.rs +426 -0
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +219 -0
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +779 -0
- package/dist/create/server-source/crates/feltdb/src/lib.rs +2838 -0
- package/dist/create/server-source/crates/feltdb/src/materialization.rs +184 -0
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +311 -0
- package/dist/create/server-source/crates/feltdb/src/observability.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/operation.rs +251 -0
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +438 -0
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +344 -0
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +166 -0
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +560 -0
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +3226 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +201 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +263 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +484 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +216 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +281 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +323 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +573 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +381 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +334 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +306 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +355 -0
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +395 -0
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +346 -0
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +430 -0
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +400 -0
- package/dist/create/server-source/crates/feltdb/src/provenance.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +217 -0
- package/dist/create/server-source/crates/feltdb/src/references.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +401 -0
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +160 -0
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +132 -0
- package/dist/create/server-source/crates/feltdb/src/routing.rs +409 -0
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +3118 -0
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +291 -0
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +376 -0
- package/dist/create/server-source/crates/feltdb/src/storage.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/submission.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/sync.rs +483 -0
- package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +822 -0
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +473 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +949 -0
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +646 -0
- package/dist/create/server-source/crates/feltdb/src/trigger.rs +390 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +928 -0
- package/dist/create/server-source/crates/feltdb/src/workflow.rs +713 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +510 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +354 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +594 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +1310 -0
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +23 -0
- package/dist/create/server-source/crates/feltdb-server/README.md +79 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +73 -0
- package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +425 -0
- package/dist/create/server-source/crates/feltdb-server/src/artifacts.rs +449 -0
- package/dist/create/server-source/crates/feltdb-server/src/audit.rs +59 -0
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +308 -0
- package/dist/create/server-source/crates/feltdb-server/src/authorization.rs +228 -0
- package/dist/create/server-source/crates/feltdb-server/src/backup.rs +416 -0
- package/dist/create/server-source/crates/feltdb-server/src/causal.rs +218 -0
- package/dist/create/server-source/crates/feltdb-server/src/certification.rs +223 -0
- package/dist/create/server-source/crates/feltdb-server/src/clock.rs +132 -0
- package/dist/create/server-source/crates/feltdb-server/src/cluster.rs +165 -0
- package/dist/create/server-source/crates/feltdb-server/src/connections.rs +1044 -0
- package/dist/create/server-source/crates/feltdb-server/src/content.rs +111 -0
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +250 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +78 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_provider.rs +247 -0
- package/dist/create/server-source/crates/feltdb-server/src/leases.rs +123 -0
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +25 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +8715 -0
- package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +97 -0
- package/dist/create/server-source/crates/feltdb-server/src/portable_bundle.rs +350 -0
- package/dist/create/server-source/crates/feltdb-server/src/principals.rs +510 -0
- package/dist/create/server-source/crates/feltdb-server/src/providers.rs +269 -0
- package/dist/create/server-source/crates/feltdb-server/src/releases.rs +2563 -0
- package/dist/create/server-source/crates/feltdb-server/src/sessions.rs +156 -0
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +1097 -0
- package/dist/create/server-source/crates/feltdb-server/src/versions.rs +264 -0
- package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +31 -0
- package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +1086 -0
- package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
- package/dist/flowspec.d.ts +2 -0
- package/dist/flowspec.d.ts.map +1 -1
- package/dist/flowspec.js +28 -4
- package/dist/state-contract.d.ts +7 -1
- package/dist/state-contract.d.ts.map +1 -1
- package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
- package/dist/studio/components/index.js +1 -1
- package/dist/studio/{components-Q0Nx6gLE.js → components-bHTARBin.js} +156 -46
- package/dist/studio/index.js +31 -28
- package/dist/studio-app/assets/{index-BHOmLZF-.js → index-BqKquLuU.js} +8 -8
- package/dist/studio-app/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/// Content acquisition and materialization module
|
|
2
|
+
/// Handles distributed content transfer, verification, and local materialization
|
|
3
|
+
use crate::{ContentIdentity, FlowRef, PeerId};
|
|
4
|
+
use serde::{Deserialize, Serialize};
|
|
5
|
+
use std::collections::HashMap;
|
|
6
|
+
use std::sync::{Arc, Mutex};
|
|
7
|
+
use std::time::SystemTime;
|
|
8
|
+
use std::time::UNIX_EPOCH;
|
|
9
|
+
|
|
10
|
+
/// Policy for acquiring content from the distributed fabric
|
|
11
|
+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
|
|
12
|
+
pub enum AcquisitionPolicy {
|
|
13
|
+
/// Fail if content is not available locally
|
|
14
|
+
LocalOnly,
|
|
15
|
+
/// Prefer local, fall back to remote if needed
|
|
16
|
+
PreferLocal,
|
|
17
|
+
/// Allow automatic remote acquisition
|
|
18
|
+
RemoteAllowed,
|
|
19
|
+
/// Block until content is available (required)
|
|
20
|
+
Required,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
impl Default for AcquisitionPolicy {
|
|
24
|
+
fn default() -> Self {
|
|
25
|
+
AcquisitionPolicy::PreferLocal
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Error types for content acquisition
|
|
30
|
+
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
|
31
|
+
pub enum ContentTransferError {
|
|
32
|
+
/// Content not found on provider
|
|
33
|
+
NotFound,
|
|
34
|
+
/// Access denied to content
|
|
35
|
+
Unauthorized,
|
|
36
|
+
/// Provider temporarily unavailable
|
|
37
|
+
Unavailable,
|
|
38
|
+
/// Content hash does not match expected
|
|
39
|
+
HashMismatch { expected: String, computed: String },
|
|
40
|
+
/// Requested version not available
|
|
41
|
+
VersionUnavailable,
|
|
42
|
+
/// Generic acquisition error
|
|
43
|
+
AcquisitionError(String),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
impl std::fmt::Display for ContentTransferError {
|
|
47
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
48
|
+
match self {
|
|
49
|
+
ContentTransferError::NotFound => write!(f, "Content not found"),
|
|
50
|
+
ContentTransferError::Unauthorized => write!(f, "Access denied"),
|
|
51
|
+
ContentTransferError::Unavailable => write!(f, "Provider unavailable"),
|
|
52
|
+
ContentTransferError::HashMismatch { expected, computed } => {
|
|
53
|
+
write!(f, "Hash mismatch: expected {}, got {}", expected, computed)
|
|
54
|
+
}
|
|
55
|
+
ContentTransferError::VersionUnavailable => write!(f, "Version unavailable"),
|
|
56
|
+
ContentTransferError::AcquisitionError(msg) => write!(f, "Acquisition error: {}", msg),
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
impl std::error::Error for ContentTransferError {}
|
|
62
|
+
|
|
63
|
+
/// Request for content from a remote peer
|
|
64
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
65
|
+
pub struct ContentRequest {
|
|
66
|
+
/// Unique request ID for correlation
|
|
67
|
+
pub request_id: String,
|
|
68
|
+
/// Reference to the content being requested
|
|
69
|
+
pub flow_ref: FlowRef,
|
|
70
|
+
/// Expected content identity for verification
|
|
71
|
+
pub content_identity: Option<ContentIdentity>,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
impl ContentRequest {
|
|
75
|
+
/// Create a new content request
|
|
76
|
+
pub fn new(
|
|
77
|
+
request_id: String,
|
|
78
|
+
flow_ref: FlowRef,
|
|
79
|
+
content_identity: Option<ContentIdentity>,
|
|
80
|
+
) -> Self {
|
|
81
|
+
Self {
|
|
82
|
+
request_id,
|
|
83
|
+
flow_ref,
|
|
84
|
+
content_identity,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Response containing acquired content
|
|
90
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
91
|
+
pub struct ContentResponse {
|
|
92
|
+
/// Correlation ID matching the request
|
|
93
|
+
pub request_id: String,
|
|
94
|
+
/// Content identity of the payload
|
|
95
|
+
pub content_identity: ContentIdentity,
|
|
96
|
+
/// The actual content bytes
|
|
97
|
+
pub payload: Vec<u8>,
|
|
98
|
+
/// Peer that provided this content
|
|
99
|
+
pub provider: PeerId,
|
|
100
|
+
/// Timestamp when content was acquired
|
|
101
|
+
pub acquired_at_ms: u128,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
impl ContentResponse {
|
|
105
|
+
/// Create a new content response
|
|
106
|
+
pub fn new(
|
|
107
|
+
request_id: String,
|
|
108
|
+
content_identity: ContentIdentity,
|
|
109
|
+
payload: Vec<u8>,
|
|
110
|
+
provider: PeerId,
|
|
111
|
+
) -> Self {
|
|
112
|
+
let now_ms = SystemTime::now()
|
|
113
|
+
.duration_since(UNIX_EPOCH)
|
|
114
|
+
.map(|d| d.as_millis())
|
|
115
|
+
.unwrap_or(0);
|
|
116
|
+
|
|
117
|
+
Self {
|
|
118
|
+
request_id,
|
|
119
|
+
content_identity,
|
|
120
|
+
payload,
|
|
121
|
+
provider,
|
|
122
|
+
acquired_at_ms: now_ms,
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// Result of content acquisition attempt
|
|
128
|
+
#[derive(Debug, Clone)]
|
|
129
|
+
pub struct AcquisitionResult {
|
|
130
|
+
/// The acquired content
|
|
131
|
+
pub payload: Vec<u8>,
|
|
132
|
+
/// Where the content came from
|
|
133
|
+
pub source_peer: PeerId,
|
|
134
|
+
/// When it was acquired
|
|
135
|
+
pub acquired_at_ms: u128,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
impl AcquisitionResult {
|
|
139
|
+
/// Create a new acquisition result
|
|
140
|
+
pub fn new(payload: Vec<u8>, source_peer: PeerId) -> Self {
|
|
141
|
+
let now_ms = SystemTime::now()
|
|
142
|
+
.duration_since(UNIX_EPOCH)
|
|
143
|
+
.map(|d| d.as_millis())
|
|
144
|
+
.unwrap_or(0);
|
|
145
|
+
|
|
146
|
+
Self {
|
|
147
|
+
payload,
|
|
148
|
+
source_peer,
|
|
149
|
+
acquired_at_ms: now_ms,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// In-flight acquisition tracking for deduplication
|
|
155
|
+
#[derive(Clone)]
|
|
156
|
+
pub struct AcquisitionDeduplicator {
|
|
157
|
+
/// Map from acquisition key to in-flight acquisitions
|
|
158
|
+
in_flight: Arc<
|
|
159
|
+
Mutex<
|
|
160
|
+
HashMap<
|
|
161
|
+
String,
|
|
162
|
+
Vec<
|
|
163
|
+
tokio::sync::oneshot::Receiver<Result<AcquisitionResult, ContentTransferError>>,
|
|
164
|
+
>,
|
|
165
|
+
>,
|
|
166
|
+
>,
|
|
167
|
+
>,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
impl AcquisitionDeduplicator {
|
|
171
|
+
/// Create a new acquisition deduplicator
|
|
172
|
+
pub fn new() -> Self {
|
|
173
|
+
Self {
|
|
174
|
+
in_flight: Arc::new(Mutex::new(HashMap::new())),
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/// Check if an acquisition is already in flight
|
|
179
|
+
pub fn is_in_flight(&self, key: &str) -> Result<bool, String> {
|
|
180
|
+
let in_flight = self.in_flight.lock().map_err(|e| e.to_string())?;
|
|
181
|
+
Ok(in_flight.contains_key(key) && !in_flight[key].is_empty())
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// Register a new in-flight acquisition
|
|
185
|
+
/// Returns true if this is the first acquisition, false if joining existing
|
|
186
|
+
pub fn register_acquisition(
|
|
187
|
+
&self,
|
|
188
|
+
key: String,
|
|
189
|
+
receiver: tokio::sync::oneshot::Receiver<Result<AcquisitionResult, ContentTransferError>>,
|
|
190
|
+
) -> Result<bool, String> {
|
|
191
|
+
let mut in_flight = self.in_flight.lock().map_err(|e| e.to_string())?;
|
|
192
|
+
let is_first = !in_flight.contains_key(&key) || in_flight[&key].is_empty();
|
|
193
|
+
|
|
194
|
+
in_flight.entry(key).or_insert_with(Vec::new).push(receiver);
|
|
195
|
+
|
|
196
|
+
Ok(is_first)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// Mark an acquisition as complete and notify all waiters
|
|
200
|
+
pub fn mark_complete(
|
|
201
|
+
&self,
|
|
202
|
+
key: &str,
|
|
203
|
+
_result: Result<AcquisitionResult, ContentTransferError>,
|
|
204
|
+
) -> Result<(), String> {
|
|
205
|
+
// In a real implementation, we would notify all waiting receivers
|
|
206
|
+
// For now, we just remove the entry
|
|
207
|
+
let mut in_flight = self.in_flight.lock().map_err(|e| e.to_string())?;
|
|
208
|
+
in_flight.remove(key);
|
|
209
|
+
Ok(())
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
impl Default for AcquisitionDeduplicator {
|
|
214
|
+
fn default() -> Self {
|
|
215
|
+
Self::new()
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#[cfg(test)]
|
|
220
|
+
mod tests {
|
|
221
|
+
use super::*;
|
|
222
|
+
|
|
223
|
+
#[test]
|
|
224
|
+
fn test_acquisition_policy_default() {
|
|
225
|
+
assert_eq!(AcquisitionPolicy::default(), AcquisitionPolicy::PreferLocal);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#[test]
|
|
229
|
+
fn test_content_request_creation() {
|
|
230
|
+
let flow_ref = FlowRef::new("docs", "123");
|
|
231
|
+
let content_id = ContentIdentity::new("abc123");
|
|
232
|
+
|
|
233
|
+
let request = ContentRequest::new("req-1".to_string(), flow_ref.clone(), Some(content_id));
|
|
234
|
+
|
|
235
|
+
assert_eq!(request.request_id, "req-1");
|
|
236
|
+
assert_eq!(request.flow_ref.object_id, "123");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#[test]
|
|
240
|
+
fn test_content_response_creation() {
|
|
241
|
+
let content_id = ContentIdentity::new("abc123");
|
|
242
|
+
let peer = PeerId::new("peer-a");
|
|
243
|
+
let payload = vec![1, 2, 3];
|
|
244
|
+
|
|
245
|
+
let response = ContentResponse::new(
|
|
246
|
+
"req-1".to_string(),
|
|
247
|
+
content_id,
|
|
248
|
+
payload.clone(),
|
|
249
|
+
peer.clone(),
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
assert_eq!(response.request_id, "req-1");
|
|
253
|
+
assert_eq!(response.payload, payload);
|
|
254
|
+
assert_eq!(response.provider, peer);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
#[test]
|
|
258
|
+
fn test_acquisition_result_creation() {
|
|
259
|
+
let peer = PeerId::new("peer-a");
|
|
260
|
+
let payload = vec![1, 2, 3];
|
|
261
|
+
|
|
262
|
+
let result = AcquisitionResult::new(payload.clone(), peer.clone());
|
|
263
|
+
|
|
264
|
+
assert_eq!(result.payload, payload);
|
|
265
|
+
assert_eq!(result.source_peer, peer);
|
|
266
|
+
assert!(result.acquired_at_ms > 0);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
#[test]
|
|
270
|
+
fn test_acquisition_deduplicator_creation() {
|
|
271
|
+
let dedup = AcquisitionDeduplicator::new();
|
|
272
|
+
assert!(!dedup.is_in_flight("key-1").unwrap());
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
#[test]
|
|
276
|
+
fn test_content_transfer_error_display() {
|
|
277
|
+
let err = ContentTransferError::NotFound;
|
|
278
|
+
assert_eq!(err.to_string(), "Content not found");
|
|
279
|
+
|
|
280
|
+
let err = ContentTransferError::HashMismatch {
|
|
281
|
+
expected: "abc".to_string(),
|
|
282
|
+
computed: "def".to_string(),
|
|
283
|
+
};
|
|
284
|
+
assert!(err.to_string().contains("abc"));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/// Production admission contract for FeltDB
|
|
2
|
+
///
|
|
3
|
+
/// Defines explicit, machine-readable submission semantics.
|
|
4
|
+
/// Never silently drops operations. Rejection is explicit and retryable.
|
|
5
|
+
|
|
6
|
+
use serde::{Deserialize, Serialize};
|
|
7
|
+
use std::fmt;
|
|
8
|
+
|
|
9
|
+
/// Outcome of a submission attempt
|
|
10
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
11
|
+
pub enum SubmissionOutcome {
|
|
12
|
+
/// Operation accepted for processing
|
|
13
|
+
/// Operations are accepted in order but may not yet be committed.
|
|
14
|
+
/// Caller should wait for CommitAck before assuming durability.
|
|
15
|
+
Accepted {
|
|
16
|
+
operation_id: u64,
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
/// Operation rejected due to backpressure
|
|
20
|
+
/// Caller should retry with backoff
|
|
21
|
+
Rejected(RejectionReason),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl SubmissionOutcome {
|
|
25
|
+
pub fn is_accepted(&self) -> bool {
|
|
26
|
+
matches!(self, SubmissionOutcome::Accepted { .. })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pub fn is_rejected(&self) -> bool {
|
|
30
|
+
matches!(self, SubmissionOutcome::Rejected(_))
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Explicit reason for rejection
|
|
35
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
36
|
+
pub enum RejectionReason {
|
|
37
|
+
/// Admission path saturated due to lock contention
|
|
38
|
+
/// Safe to retry. Caller should apply exponential backoff.
|
|
39
|
+
AdmissionBackpressure {
|
|
40
|
+
/// Microseconds to wait before retry
|
|
41
|
+
retry_after_us: u64,
|
|
42
|
+
/// Suggested backoff for caller's exponential strategy (milliseconds)
|
|
43
|
+
suggested_backoff_ms: u32,
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/// Transient lock contention at submission time
|
|
47
|
+
/// Immediate retry likely to succeed
|
|
48
|
+
TransientLockContention,
|
|
49
|
+
|
|
50
|
+
/// Operation queue at capacity
|
|
51
|
+
/// Rare in normal operation; indicates severe overload
|
|
52
|
+
QueueFull,
|
|
53
|
+
|
|
54
|
+
/// Deduplication rejected duplicate (same content_hash already processed)
|
|
55
|
+
/// Not an error; operation will be applied exactly once
|
|
56
|
+
DuplicateDetected {
|
|
57
|
+
/// The original operation_id that processed this content
|
|
58
|
+
original_operation_id: u64,
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
impl fmt::Display for RejectionReason {
|
|
63
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
64
|
+
match self {
|
|
65
|
+
RejectionReason::AdmissionBackpressure {
|
|
66
|
+
retry_after_us,
|
|
67
|
+
suggested_backoff_ms,
|
|
68
|
+
} => write!(
|
|
69
|
+
f,
|
|
70
|
+
"admission backpressure (retry after {}µs, suggested backoff {}ms)",
|
|
71
|
+
retry_after_us, suggested_backoff_ms
|
|
72
|
+
),
|
|
73
|
+
RejectionReason::TransientLockContention => {
|
|
74
|
+
write!(f, "transient lock contention")
|
|
75
|
+
}
|
|
76
|
+
RejectionReason::QueueFull => write!(f, "admission queue full"),
|
|
77
|
+
RejectionReason::DuplicateDetected {
|
|
78
|
+
original_operation_id,
|
|
79
|
+
} => write!(
|
|
80
|
+
f,
|
|
81
|
+
"duplicate detected (original op_id: {})",
|
|
82
|
+
original_operation_id
|
|
83
|
+
),
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Production retry/backoff policy for admission
|
|
89
|
+
/// Bounded, non-infinite, based on measured contention curve
|
|
90
|
+
pub struct ProductionRetryPolicy {
|
|
91
|
+
/// Maximum number of lock acquisition attempts
|
|
92
|
+
pub max_attempts: usize,
|
|
93
|
+
/// Backoff durations (microseconds) for each attempt
|
|
94
|
+
pub backoff_sequence: Vec<u64>,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
impl ProductionRetryPolicy {
|
|
98
|
+
/// Create the standard production policy
|
|
99
|
+
/// Based on Gate 14A-SYNC measurements:
|
|
100
|
+
/// - 3 retries → ~1% rejection at 8 writers
|
|
101
|
+
/// - 4 retries → near-zero rejection at intended operating envelope
|
|
102
|
+
pub fn standard() -> Self {
|
|
103
|
+
ProductionRetryPolicy {
|
|
104
|
+
max_attempts: 4,
|
|
105
|
+
backoff_sequence: vec![
|
|
106
|
+
0, // Attempt 1: no backoff (immediate)
|
|
107
|
+
100, // Attempt 2: 100µs
|
|
108
|
+
200, // Attempt 3: 200µs
|
|
109
|
+
400, // Attempt 4: 400µs
|
|
110
|
+
// Total: 700µs of bounded admission attempts
|
|
111
|
+
],
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Create a policy for testing/stress scenarios
|
|
116
|
+
/// Higher attempts for high-concurrency testing
|
|
117
|
+
pub fn stress_test() -> Self {
|
|
118
|
+
ProductionRetryPolicy {
|
|
119
|
+
max_attempts: 10,
|
|
120
|
+
backoff_sequence: vec![
|
|
121
|
+
0, 50, 100, 150, 200, 250, 300, 350, 400, 450,
|
|
122
|
+
],
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// Get suggested backoff for a given attempt number
|
|
127
|
+
pub fn backoff_for_attempt(&self, attempt: usize) -> u64 {
|
|
128
|
+
if attempt == 0 {
|
|
129
|
+
0
|
|
130
|
+
} else if attempt <= self.backoff_sequence.len() {
|
|
131
|
+
self.backoff_sequence[attempt - 1]
|
|
132
|
+
} else {
|
|
133
|
+
// Beyond configured attempts, suggest exponential backoff to caller
|
|
134
|
+
self.backoff_sequence.last().copied().unwrap_or(400) * 2
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Total admission time budget (sum of all backoffs)
|
|
139
|
+
pub fn total_budget_us(&self) -> u64 {
|
|
140
|
+
self.backoff_sequence.iter().sum()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
impl Default for ProductionRetryPolicy {
|
|
145
|
+
fn default() -> Self {
|
|
146
|
+
Self::standard()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#[cfg(test)]
|
|
151
|
+
mod tests {
|
|
152
|
+
use super::*;
|
|
153
|
+
|
|
154
|
+
#[test]
|
|
155
|
+
fn production_policy_is_bounded() {
|
|
156
|
+
let policy = ProductionRetryPolicy::standard();
|
|
157
|
+
assert_eq!(policy.max_attempts, 4);
|
|
158
|
+
assert_eq!(policy.backoff_sequence.len(), 4);
|
|
159
|
+
assert_eq!(policy.total_budget_us(), 700);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#[test]
|
|
163
|
+
fn stress_policy_has_higher_budget() {
|
|
164
|
+
let prod = ProductionRetryPolicy::standard();
|
|
165
|
+
let stress = ProductionRetryPolicy::stress_test();
|
|
166
|
+
assert!(stress.total_budget_us() > prod.total_budget_us());
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[test]
|
|
170
|
+
fn outcome_accessors_work() {
|
|
171
|
+
let accepted = SubmissionOutcome::Accepted {
|
|
172
|
+
operation_id: 123,
|
|
173
|
+
};
|
|
174
|
+
assert!(accepted.is_accepted());
|
|
175
|
+
assert!(!accepted.is_rejected());
|
|
176
|
+
|
|
177
|
+
let rejected = SubmissionOutcome::Rejected(RejectionReason::TransientLockContention);
|
|
178
|
+
assert!(!rejected.is_accepted());
|
|
179
|
+
assert!(rejected.is_rejected());
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#[test]
|
|
183
|
+
fn rejection_reason_display() {
|
|
184
|
+
let backpressure = RejectionReason::AdmissionBackpressure {
|
|
185
|
+
retry_after_us: 1000,
|
|
186
|
+
suggested_backoff_ms: 10,
|
|
187
|
+
};
|
|
188
|
+
let msg = format!("{}", backpressure);
|
|
189
|
+
assert!(msg.contains("backpressure"));
|
|
190
|
+
assert!(msg.contains("1000µs"));
|
|
191
|
+
}
|
|
192
|
+
}
|