@feltdb/core 0.4.12 → 0.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands.js +42 -25
- package/dist/cli/index.js +1 -1
- package/dist/create/cli.js +39 -4
- package/dist/create/create.js +30 -18
- package/dist/create/docker-compose-generator.js +68 -11
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +2238 -0
- package/dist/create/server-source/Cargo.toml +7 -0
- package/dist/create/server-source/crates/feltdb/Cargo.lock +175 -0
- package/dist/create/server-source/crates/feltdb/Cargo.toml +79 -0
- package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +370 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +589 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +259 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +446 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +272 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +378 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +414 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +486 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +273 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +587 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +513 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +395 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +469 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +409 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +627 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +383 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +298 -0
- package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +1698 -0
- package/dist/create/server-source/crates/feltdb/src/acquisition.rs +286 -0
- package/dist/create/server-source/crates/feltdb/src/admission.rs +192 -0
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +566 -0
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +475 -0
- package/dist/create/server-source/crates/feltdb/src/application.rs +2244 -0
- package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +1070 -0
- package/dist/create/server-source/crates/feltdb/src/authorization.rs +1030 -0
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +266 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +8 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +482 -0
- package/dist/create/server-source/crates/feltdb/src/capability.rs +903 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +78 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +2214 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +194 -0
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +427 -0
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +453 -0
- package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +465 -0
- package/dist/create/server-source/crates/feltdb/src/convergence.rs +618 -0
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +380 -0
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +362 -0
- package/dist/create/server-source/crates/feltdb/src/cron.rs +294 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +474 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +1003 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +536 -0
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +364 -0
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +235 -0
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +316 -0
- package/dist/create/server-source/crates/feltdb/src/execution.rs +426 -0
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +219 -0
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +779 -0
- package/dist/create/server-source/crates/feltdb/src/lib.rs +2838 -0
- package/dist/create/server-source/crates/feltdb/src/materialization.rs +184 -0
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +311 -0
- package/dist/create/server-source/crates/feltdb/src/observability.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/operation.rs +251 -0
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +438 -0
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +344 -0
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +166 -0
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +560 -0
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +3226 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +201 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +263 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +484 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +216 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +281 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +323 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +573 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +381 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +334 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +306 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +355 -0
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +395 -0
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +346 -0
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +430 -0
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +400 -0
- package/dist/create/server-source/crates/feltdb/src/provenance.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +217 -0
- package/dist/create/server-source/crates/feltdb/src/references.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +401 -0
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +160 -0
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +132 -0
- package/dist/create/server-source/crates/feltdb/src/routing.rs +409 -0
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +3118 -0
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +291 -0
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +376 -0
- package/dist/create/server-source/crates/feltdb/src/storage.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/submission.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/sync.rs +483 -0
- package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +822 -0
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +473 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +949 -0
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +646 -0
- package/dist/create/server-source/crates/feltdb/src/trigger.rs +390 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +928 -0
- package/dist/create/server-source/crates/feltdb/src/workflow.rs +713 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +510 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +354 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +594 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +1310 -0
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +23 -0
- package/dist/create/server-source/crates/feltdb-server/README.md +79 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +73 -0
- package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +425 -0
- package/dist/create/server-source/crates/feltdb-server/src/artifacts.rs +449 -0
- package/dist/create/server-source/crates/feltdb-server/src/audit.rs +59 -0
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +308 -0
- package/dist/create/server-source/crates/feltdb-server/src/authorization.rs +228 -0
- package/dist/create/server-source/crates/feltdb-server/src/backup.rs +416 -0
- package/dist/create/server-source/crates/feltdb-server/src/causal.rs +218 -0
- package/dist/create/server-source/crates/feltdb-server/src/certification.rs +223 -0
- package/dist/create/server-source/crates/feltdb-server/src/clock.rs +132 -0
- package/dist/create/server-source/crates/feltdb-server/src/cluster.rs +165 -0
- package/dist/create/server-source/crates/feltdb-server/src/connections.rs +1044 -0
- package/dist/create/server-source/crates/feltdb-server/src/content.rs +111 -0
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +250 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +78 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_provider.rs +247 -0
- package/dist/create/server-source/crates/feltdb-server/src/leases.rs +123 -0
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +25 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +8715 -0
- package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +97 -0
- package/dist/create/server-source/crates/feltdb-server/src/portable_bundle.rs +350 -0
- package/dist/create/server-source/crates/feltdb-server/src/principals.rs +510 -0
- package/dist/create/server-source/crates/feltdb-server/src/providers.rs +269 -0
- package/dist/create/server-source/crates/feltdb-server/src/releases.rs +2563 -0
- package/dist/create/server-source/crates/feltdb-server/src/sessions.rs +156 -0
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +1097 -0
- package/dist/create/server-source/crates/feltdb-server/src/versions.rs +264 -0
- package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +31 -0
- package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +1086 -0
- package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
- package/dist/flowspec.d.ts +2 -0
- package/dist/flowspec.d.ts.map +1 -1
- package/dist/flowspec.js +28 -4
- package/dist/state-contract.d.ts +7 -1
- package/dist/state-contract.d.ts.map +1 -1
- package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
- package/dist/studio/components/ConflictExplorer.d.ts.map +1 -1
- package/dist/studio/components/index.js +1 -1
- package/dist/studio/{components-Q0Nx6gLE.js → components-9kDSWiGL.js} +163 -47
- package/dist/studio/index.js +31 -28
- package/dist/studio-app/assets/{index-BHOmLZF-.js → index-DVdvmf69.js} +9 -9
- package/dist/studio-app/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,903 @@
|
|
|
1
|
+
use crate::Operation;
|
|
2
|
+
/// Capability and Extension Runtime module
|
|
3
|
+
///
|
|
4
|
+
/// Capabilities are extensible projections that consume the FeltDB operation stream
|
|
5
|
+
/// and maintain rebuildable derived state. They follow a generic contract:
|
|
6
|
+
///
|
|
7
|
+
/// canonical operation → capability → derived state → capability query
|
|
8
|
+
///
|
|
9
|
+
/// This keeps the core log generic while allowing specialized behavior.
|
|
10
|
+
use serde::{Deserialize, Serialize};
|
|
11
|
+
use serde_json::Value;
|
|
12
|
+
use std::collections::HashMap;
|
|
13
|
+
use std::fmt;
|
|
14
|
+
|
|
15
|
+
/// Unique capability identifier
|
|
16
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
|
17
|
+
pub struct CapabilityId(pub String);
|
|
18
|
+
|
|
19
|
+
impl fmt::Display for CapabilityId {
|
|
20
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
21
|
+
write!(f, "{}", self.0)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl CapabilityId {
|
|
26
|
+
pub fn new(id: String) -> Self {
|
|
27
|
+
CapabilityId(id)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// The roles a capability can fulfill
|
|
32
|
+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
|
33
|
+
pub enum CapabilityRole {
|
|
34
|
+
/// Triggers and executes business logic
|
|
35
|
+
ExecutionEngine,
|
|
36
|
+
/// Maintains a projection/index of canonical state
|
|
37
|
+
Projection,
|
|
38
|
+
/// Both executes and maintains a projection
|
|
39
|
+
ExecutionWithProjection,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Lifecycle states for a capability
|
|
43
|
+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
|
44
|
+
pub enum CapabilityLifecycleState {
|
|
45
|
+
/// Capability has been registered but not yet initialized
|
|
46
|
+
Registered,
|
|
47
|
+
/// Capability is initializing its state
|
|
48
|
+
Initializing,
|
|
49
|
+
/// Capability is catching up with historical operations
|
|
50
|
+
CatchingUp,
|
|
51
|
+
/// Capability is active and processing new operations
|
|
52
|
+
Active,
|
|
53
|
+
/// Capability is paused
|
|
54
|
+
Paused,
|
|
55
|
+
/// Capability is being rebuilt
|
|
56
|
+
Rebuilding,
|
|
57
|
+
/// Capability has been removed
|
|
58
|
+
Removed,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Context passed to capability operations
|
|
62
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
63
|
+
pub struct CapabilityContext {
|
|
64
|
+
/// The capability ID
|
|
65
|
+
pub capability_id: CapabilityId,
|
|
66
|
+
/// Instance ID of the FeltDB instance
|
|
67
|
+
pub instance_id: String,
|
|
68
|
+
/// Current sequence number
|
|
69
|
+
pub sequence: u64,
|
|
70
|
+
/// Capability version
|
|
71
|
+
pub version: u32,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Query structure for capability queries
|
|
75
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
76
|
+
pub struct CapabilityQuery {
|
|
77
|
+
/// Query type (e.g., "search", "vector_find", "analytics")
|
|
78
|
+
pub query_type: String,
|
|
79
|
+
/// Query parameters
|
|
80
|
+
pub params: Value,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
impl CapabilityQuery {
|
|
84
|
+
pub fn new(query_type: String, params: Value) -> Self {
|
|
85
|
+
Self { query_type, params }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Checkpoint for capability progress tracking
|
|
90
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
91
|
+
pub struct CapabilityCheckpoint {
|
|
92
|
+
/// The capability ID
|
|
93
|
+
pub capability_id: CapabilityId,
|
|
94
|
+
/// Last processed instance ID
|
|
95
|
+
pub last_processed_instance_id: String,
|
|
96
|
+
/// Last processed sequence number
|
|
97
|
+
pub last_processed_sequence: u64,
|
|
98
|
+
/// Current lifecycle state
|
|
99
|
+
pub lifecycle_state: CapabilityLifecycleState,
|
|
100
|
+
/// Timestamp of last checkpoint (ms since UNIX epoch)
|
|
101
|
+
pub checkpoint_ms: u128,
|
|
102
|
+
/// Capability version
|
|
103
|
+
pub version: u32,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
impl CapabilityCheckpoint {
|
|
107
|
+
pub fn new(capability_id: CapabilityId, instance_id: String) -> Self {
|
|
108
|
+
Self {
|
|
109
|
+
capability_id,
|
|
110
|
+
last_processed_instance_id: instance_id,
|
|
111
|
+
last_processed_sequence: 0,
|
|
112
|
+
lifecycle_state: CapabilityLifecycleState::Registered,
|
|
113
|
+
checkpoint_ms: crate::now_ms(),
|
|
114
|
+
version: 1,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Update checkpoint with new processed position
|
|
119
|
+
pub fn update_position(&mut self, instance_id: String, sequence: u64) {
|
|
120
|
+
self.last_processed_instance_id = instance_id;
|
|
121
|
+
self.last_processed_sequence = sequence;
|
|
122
|
+
self.checkpoint_ms = crate::now_ms();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Set lifecycle state
|
|
126
|
+
pub fn set_state(&mut self, state: CapabilityLifecycleState) {
|
|
127
|
+
self.lifecycle_state = state;
|
|
128
|
+
self.checkpoint_ms = crate::now_ms();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Result of processing an operation through a capability
|
|
133
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
134
|
+
pub struct CapabilityOperationResult {
|
|
135
|
+
/// Whether the operation was successfully processed
|
|
136
|
+
pub success: bool,
|
|
137
|
+
/// Optional error message
|
|
138
|
+
pub error: Option<String>,
|
|
139
|
+
/// Optional result data
|
|
140
|
+
pub result: Option<Value>,
|
|
141
|
+
/// Whether this capability produced derived state
|
|
142
|
+
pub produced_derived_state: bool,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
impl CapabilityOperationResult {
|
|
146
|
+
pub fn success() -> Self {
|
|
147
|
+
Self {
|
|
148
|
+
success: true,
|
|
149
|
+
error: None,
|
|
150
|
+
result: None,
|
|
151
|
+
produced_derived_state: false,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
pub fn with_derived_state(result: Value) -> Self {
|
|
156
|
+
Self {
|
|
157
|
+
success: true,
|
|
158
|
+
error: None,
|
|
159
|
+
result: Some(result),
|
|
160
|
+
produced_derived_state: true,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
pub fn error(msg: String) -> Self {
|
|
165
|
+
Self {
|
|
166
|
+
success: false,
|
|
167
|
+
error: Some(msg),
|
|
168
|
+
result: None,
|
|
169
|
+
produced_derived_state: false,
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// Result of a capability query
|
|
175
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
176
|
+
pub struct CapabilityQueryResult {
|
|
177
|
+
/// Whether the query succeeded
|
|
178
|
+
pub success: bool,
|
|
179
|
+
/// Query results
|
|
180
|
+
pub results: Vec<Value>,
|
|
181
|
+
/// Optional error message
|
|
182
|
+
pub error: Option<String>,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
impl CapabilityQueryResult {
|
|
186
|
+
pub fn success(results: Vec<Value>) -> Self {
|
|
187
|
+
Self {
|
|
188
|
+
success: true,
|
|
189
|
+
results,
|
|
190
|
+
error: None,
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
pub fn empty() -> Self {
|
|
195
|
+
Self {
|
|
196
|
+
success: true,
|
|
197
|
+
results: Vec::new(),
|
|
198
|
+
error: None,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
pub fn error(msg: String) -> Self {
|
|
203
|
+
Self {
|
|
204
|
+
success: false,
|
|
205
|
+
results: Vec::new(),
|
|
206
|
+
error: Some(msg),
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// Access model for capability security
|
|
212
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
213
|
+
pub struct CapabilityAccessModel {
|
|
214
|
+
/// Collections this capability can read from
|
|
215
|
+
pub reads: Vec<String>,
|
|
216
|
+
/// Collections this capability can write to
|
|
217
|
+
pub writes: Vec<String>,
|
|
218
|
+
/// External systems this capability can execute
|
|
219
|
+
pub executes: Vec<String>,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
impl CapabilityAccessModel {
|
|
223
|
+
pub fn new(reads: Vec<String>, writes: Vec<String>, executes: Vec<String>) -> Self {
|
|
224
|
+
Self {
|
|
225
|
+
reads,
|
|
226
|
+
writes,
|
|
227
|
+
executes,
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/// Check if capability can read from collection
|
|
232
|
+
pub fn can_read(&self, collection: &str) -> bool {
|
|
233
|
+
self.reads.contains(&collection.to_string()) || self.reads.iter().any(|r| r == "*")
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/// Check if capability can write to collection
|
|
237
|
+
pub fn can_write(&self, collection: &str) -> bool {
|
|
238
|
+
self.writes.contains(&collection.to_string()) || self.writes.iter().any(|w| w == "*")
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/// Check if capability can execute external system
|
|
242
|
+
pub fn can_execute(&self, system: &str) -> bool {
|
|
243
|
+
self.executes.contains(&system.to_string()) || self.executes.iter().any(|e| e == "*")
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Capability metadata and configuration
|
|
248
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
249
|
+
pub struct CapabilityMetadata {
|
|
250
|
+
pub id: CapabilityId,
|
|
251
|
+
pub name: String,
|
|
252
|
+
pub description: String,
|
|
253
|
+
pub role: CapabilityRole,
|
|
254
|
+
pub access_model: CapabilityAccessModel,
|
|
255
|
+
pub version: u32,
|
|
256
|
+
pub created_ms: u128,
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
impl CapabilityMetadata {
|
|
260
|
+
pub fn new(
|
|
261
|
+
id: CapabilityId,
|
|
262
|
+
name: String,
|
|
263
|
+
description: String,
|
|
264
|
+
role: CapabilityRole,
|
|
265
|
+
access_model: CapabilityAccessModel,
|
|
266
|
+
) -> Self {
|
|
267
|
+
Self {
|
|
268
|
+
id,
|
|
269
|
+
name,
|
|
270
|
+
description,
|
|
271
|
+
role,
|
|
272
|
+
access_model,
|
|
273
|
+
version: 1,
|
|
274
|
+
created_ms: crate::now_ms(),
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/// Capability registry - tracks all capabilities
|
|
280
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
281
|
+
pub struct CapabilityRegistry {
|
|
282
|
+
/// All capabilities by ID
|
|
283
|
+
#[serde(default)]
|
|
284
|
+
capabilities: HashMap<CapabilityId, CapabilityMetadata>,
|
|
285
|
+
/// Checkpoints by capability ID
|
|
286
|
+
#[serde(default)]
|
|
287
|
+
checkpoints: HashMap<CapabilityId, CapabilityCheckpoint>,
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
impl CapabilityRegistry {
|
|
291
|
+
pub fn new() -> Self {
|
|
292
|
+
Self {
|
|
293
|
+
capabilities: HashMap::new(),
|
|
294
|
+
checkpoints: HashMap::new(),
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/// Register a new capability
|
|
299
|
+
pub fn register(&mut self, metadata: CapabilityMetadata) -> crate::Result<()> {
|
|
300
|
+
let id = metadata.id.clone();
|
|
301
|
+
if self.capabilities.contains_key(&id) {
|
|
302
|
+
return Err(crate::FlowError::CapabilityError(
|
|
303
|
+
"Capability already registered".to_string(),
|
|
304
|
+
));
|
|
305
|
+
}
|
|
306
|
+
let checkpoint = CapabilityCheckpoint::new(id.clone(), String::new());
|
|
307
|
+
self.capabilities.insert(id.clone(), metadata);
|
|
308
|
+
self.checkpoints.insert(id, checkpoint);
|
|
309
|
+
Ok(())
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/// Get capability metadata
|
|
313
|
+
pub fn get(&self, id: &CapabilityId) -> Option<&CapabilityMetadata> {
|
|
314
|
+
self.capabilities.get(id)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/// Get capability checkpoint
|
|
318
|
+
pub fn get_checkpoint(&self, id: &CapabilityId) -> Option<&CapabilityCheckpoint> {
|
|
319
|
+
self.checkpoints.get(id)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/// Update capability checkpoint
|
|
323
|
+
pub fn update_checkpoint(&mut self, checkpoint: CapabilityCheckpoint) {
|
|
324
|
+
self.checkpoints
|
|
325
|
+
.insert(checkpoint.capability_id.clone(), checkpoint);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/// List all capabilities
|
|
329
|
+
pub fn list(&self) -> Vec<&CapabilityMetadata> {
|
|
330
|
+
self.capabilities.values().collect()
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/// Remove a capability
|
|
334
|
+
pub fn remove(&mut self, id: &CapabilityId) -> Option<CapabilityMetadata> {
|
|
335
|
+
self.checkpoints.remove(id);
|
|
336
|
+
self.capabilities.remove(id)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
impl Default for CapabilityRegistry {
|
|
341
|
+
fn default() -> Self {
|
|
342
|
+
Self::new()
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/// Trait for implementing capabilities
|
|
347
|
+
/// This is the core contract for extending FeltDB with specialized behavior
|
|
348
|
+
pub trait FlowCapability: Send + Sync {
|
|
349
|
+
/// Get the capability ID
|
|
350
|
+
fn id(&self) -> CapabilityId;
|
|
351
|
+
|
|
352
|
+
/// Get the capability metadata
|
|
353
|
+
fn metadata(&self) -> CapabilityMetadata;
|
|
354
|
+
|
|
355
|
+
/// Initialize the capability (called on registration)
|
|
356
|
+
fn initialize(&mut self, context: &CapabilityContext) -> crate::Result<()>;
|
|
357
|
+
|
|
358
|
+
/// Process an operation - maintain derived state
|
|
359
|
+
fn on_operation(
|
|
360
|
+
&mut self,
|
|
361
|
+
operation: &Operation,
|
|
362
|
+
context: &CapabilityContext,
|
|
363
|
+
) -> crate::Result<CapabilityOperationResult>;
|
|
364
|
+
|
|
365
|
+
/// Query the capability's derived state
|
|
366
|
+
fn query(
|
|
367
|
+
&self,
|
|
368
|
+
query: &CapabilityQuery,
|
|
369
|
+
context: &CapabilityContext,
|
|
370
|
+
) -> crate::Result<CapabilityQueryResult>;
|
|
371
|
+
|
|
372
|
+
/// Rebuild the capability's derived state from scratch
|
|
373
|
+
fn rebuild(
|
|
374
|
+
&mut self,
|
|
375
|
+
operations: &[Operation],
|
|
376
|
+
context: &CapabilityContext,
|
|
377
|
+
) -> crate::Result<()>;
|
|
378
|
+
|
|
379
|
+
/// Pause the capability
|
|
380
|
+
fn pause(&mut self) -> crate::Result<()> {
|
|
381
|
+
Ok(())
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/// Resume the capability
|
|
385
|
+
fn resume(&mut self) -> crate::Result<()> {
|
|
386
|
+
Ok(())
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/// Check if the capability is ready (fully caught up)
|
|
390
|
+
fn is_ready(&self) -> bool {
|
|
391
|
+
true
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/// Reference to a capability with namespace and version
|
|
396
|
+
/// Canonical form: flow://capability/namespace:name@version
|
|
397
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
|
398
|
+
pub struct CapabilityRef {
|
|
399
|
+
/// Namespace (e.g., "docs", "images")
|
|
400
|
+
pub namespace: String,
|
|
401
|
+
/// Capability name (e.g., "vector-search", "image-analysis")
|
|
402
|
+
pub capability: String,
|
|
403
|
+
/// Capability version
|
|
404
|
+
pub version: u32,
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
impl CapabilityRef {
|
|
408
|
+
/// Create a new capability reference
|
|
409
|
+
pub fn new(namespace: String, capability: String, version: u32) -> Self {
|
|
410
|
+
Self {
|
|
411
|
+
namespace,
|
|
412
|
+
capability,
|
|
413
|
+
version,
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/// Get canonical form for serialization
|
|
418
|
+
pub fn canonical(&self) -> String {
|
|
419
|
+
format!(
|
|
420
|
+
"flow://capability/{}/{}@{}",
|
|
421
|
+
self.namespace, self.capability, self.version
|
|
422
|
+
)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/// Parse from canonical form
|
|
426
|
+
pub fn from_canonical(canonical: &str) -> crate::Result<Self> {
|
|
427
|
+
if !canonical.starts_with("flow://capability/") {
|
|
428
|
+
return Err(crate::FlowError::CapabilityError(
|
|
429
|
+
"Invalid capability reference format".to_string(),
|
|
430
|
+
));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
let rest = &canonical[18..]; // Skip "flow://capability/"
|
|
434
|
+
let parts: Vec<&str> = rest.split('/').collect();
|
|
435
|
+
if parts.len() != 2 {
|
|
436
|
+
return Err(crate::FlowError::CapabilityError(
|
|
437
|
+
"Invalid capability reference format".to_string(),
|
|
438
|
+
));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
let namespace = parts[0].to_string();
|
|
442
|
+
let cap_parts: Vec<&str> = parts[1].split('@').collect();
|
|
443
|
+
if cap_parts.len() != 2 {
|
|
444
|
+
return Err(crate::FlowError::CapabilityError(
|
|
445
|
+
"Invalid capability reference format".to_string(),
|
|
446
|
+
));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
let capability = cap_parts[0].to_string();
|
|
450
|
+
let version = cap_parts[1]
|
|
451
|
+
.parse::<u32>()
|
|
452
|
+
.map_err(|_| crate::FlowError::CapabilityError("Invalid version number".to_string()))?;
|
|
453
|
+
|
|
454
|
+
Ok(Self::new(namespace, capability, version))
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
impl fmt::Display for CapabilityRef {
|
|
459
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
460
|
+
write!(f, "{}", self.canonical())
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/// Represents a required state for a capability to execute
|
|
465
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
466
|
+
pub struct StateRequirement {
|
|
467
|
+
/// Namespace containing the required state
|
|
468
|
+
pub namespace: String,
|
|
469
|
+
/// Minimum version required (0 means any)
|
|
470
|
+
pub min_version: u64,
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
impl StateRequirement {
|
|
474
|
+
pub fn new(namespace: String, min_version: u64) -> Self {
|
|
475
|
+
Self {
|
|
476
|
+
namespace,
|
|
477
|
+
min_version,
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/// Requirements for a capability to execute
|
|
483
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
484
|
+
pub struct CapabilityRequirements {
|
|
485
|
+
/// Required namespaces and minimum versions
|
|
486
|
+
pub required_state: Vec<StateRequirement>,
|
|
487
|
+
/// Maximum acceptable latency for state (ms)
|
|
488
|
+
pub max_state_latency_ms: Option<u64>,
|
|
489
|
+
/// Whether execution requires single-owner semantics
|
|
490
|
+
pub requires_ownership: bool,
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
impl CapabilityRequirements {
|
|
494
|
+
pub fn new() -> Self {
|
|
495
|
+
Self {
|
|
496
|
+
required_state: Vec::new(),
|
|
497
|
+
max_state_latency_ms: None,
|
|
498
|
+
requires_ownership: false,
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
pub fn with_required_state(mut self, requirement: StateRequirement) -> Self {
|
|
503
|
+
self.required_state.push(requirement);
|
|
504
|
+
self
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
pub fn with_max_latency(mut self, latency_ms: u64) -> Self {
|
|
508
|
+
self.max_state_latency_ms = Some(latency_ms);
|
|
509
|
+
self
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
pub fn with_ownership_requirement(mut self, requires: bool) -> Self {
|
|
513
|
+
self.requires_ownership = requires;
|
|
514
|
+
self
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
impl Default for CapabilityRequirements {
|
|
519
|
+
fn default() -> Self {
|
|
520
|
+
Self::new()
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/// Advertises a capability at a specific peer with its requirements
|
|
525
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
526
|
+
pub struct DistributedCapabilityAdvertisement {
|
|
527
|
+
/// The capability reference
|
|
528
|
+
pub capability_ref: CapabilityRef,
|
|
529
|
+
/// Peer ID providing this capability
|
|
530
|
+
pub peer_id: String,
|
|
531
|
+
/// Requirements for this capability
|
|
532
|
+
pub requirements: CapabilityRequirements,
|
|
533
|
+
/// Timestamp of advertisement
|
|
534
|
+
pub advertised_ms: u128,
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
impl DistributedCapabilityAdvertisement {
|
|
538
|
+
pub fn new(capability_ref: CapabilityRef, peer_id: String) -> Self {
|
|
539
|
+
Self {
|
|
540
|
+
capability_ref,
|
|
541
|
+
peer_id,
|
|
542
|
+
requirements: CapabilityRequirements::new(),
|
|
543
|
+
advertised_ms: crate::now_ms(),
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
pub fn with_requirements(mut self, requirements: CapabilityRequirements) -> Self {
|
|
548
|
+
self.requirements = requirements;
|
|
549
|
+
self
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/// Context for executing a capability (local or remote)
|
|
554
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
555
|
+
pub struct CapabilityExecutionContext {
|
|
556
|
+
/// Unique execution ID
|
|
557
|
+
pub execution_id: String,
|
|
558
|
+
/// The capability being executed
|
|
559
|
+
pub capability_ref: CapabilityRef,
|
|
560
|
+
/// References to input data (instead of serialized payloads)
|
|
561
|
+
pub input_refs: Vec<String>,
|
|
562
|
+
/// Required state version for execution
|
|
563
|
+
pub required_state_version: u64,
|
|
564
|
+
/// Requester instance/peer ID
|
|
565
|
+
pub requester: String,
|
|
566
|
+
/// Timestamp when execution was requested
|
|
567
|
+
pub requested_ms: u128,
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
impl CapabilityExecutionContext {
|
|
571
|
+
pub fn new(execution_id: String, capability_ref: CapabilityRef, requester: String) -> Self {
|
|
572
|
+
Self {
|
|
573
|
+
execution_id,
|
|
574
|
+
capability_ref,
|
|
575
|
+
input_refs: Vec::new(),
|
|
576
|
+
required_state_version: 0,
|
|
577
|
+
requester,
|
|
578
|
+
requested_ms: crate::now_ms(),
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
pub fn with_input_ref(mut self, input_ref: String) -> Self {
|
|
583
|
+
self.input_refs.push(input_ref);
|
|
584
|
+
self
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
pub fn with_required_state_version(mut self, version: u64) -> Self {
|
|
588
|
+
self.required_state_version = version;
|
|
589
|
+
self
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/// Reference to an execution result with provenance
|
|
594
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
595
|
+
pub struct ExecutionRef {
|
|
596
|
+
/// Unique execution ID
|
|
597
|
+
pub execution_id: String,
|
|
598
|
+
/// The capability that was executed
|
|
599
|
+
pub capability_ref: CapabilityRef,
|
|
600
|
+
/// Peer that executed the capability
|
|
601
|
+
pub provider_peer: String,
|
|
602
|
+
/// Input references used
|
|
603
|
+
pub input_refs: Vec<String>,
|
|
604
|
+
/// State version at time of execution
|
|
605
|
+
pub input_state_version: u64,
|
|
606
|
+
/// Content identity of the result
|
|
607
|
+
pub result_content_identity: String,
|
|
608
|
+
/// Operation that recorded this result
|
|
609
|
+
pub created_operation: Option<String>,
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
impl ExecutionRef {
|
|
613
|
+
pub fn new(execution_id: String, capability_ref: CapabilityRef, provider_peer: String) -> Self {
|
|
614
|
+
Self {
|
|
615
|
+
execution_id,
|
|
616
|
+
capability_ref,
|
|
617
|
+
provider_peer,
|
|
618
|
+
input_refs: Vec::new(),
|
|
619
|
+
input_state_version: 0,
|
|
620
|
+
result_content_identity: String::new(),
|
|
621
|
+
created_operation: None,
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/// Result of a capability execution with full provenance
|
|
627
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
628
|
+
pub struct CapabilityExecutionResult {
|
|
629
|
+
/// Reference to the execution
|
|
630
|
+
pub execution_ref: ExecutionRef,
|
|
631
|
+
/// The actual result data
|
|
632
|
+
pub result: Value,
|
|
633
|
+
/// Timestamp when execution completed
|
|
634
|
+
pub completed_ms: u128,
|
|
635
|
+
/// Any error that occurred
|
|
636
|
+
pub error: Option<String>,
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
impl CapabilityExecutionResult {
|
|
640
|
+
pub fn success(execution_ref: ExecutionRef, result: Value) -> Self {
|
|
641
|
+
Self {
|
|
642
|
+
execution_ref,
|
|
643
|
+
result,
|
|
644
|
+
completed_ms: crate::now_ms(),
|
|
645
|
+
error: None,
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
pub fn error(execution_ref: ExecutionRef, error: String) -> Self {
|
|
650
|
+
Self {
|
|
651
|
+
execution_ref,
|
|
652
|
+
result: Value::Null,
|
|
653
|
+
completed_ms: crate::now_ms(),
|
|
654
|
+
error: Some(error),
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/// Resolver for capabilities - routes to appropriate provider
|
|
660
|
+
pub struct CapabilityResolver {
|
|
661
|
+
/// Registered capabilities by ref
|
|
662
|
+
capabilities: HashMap<String, DistributedCapabilityAdvertisement>,
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
impl CapabilityResolver {
|
|
666
|
+
pub fn new() -> Self {
|
|
667
|
+
Self {
|
|
668
|
+
capabilities: HashMap::new(),
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/// Register a capability advertisement
|
|
673
|
+
pub fn register(&mut self, ad: DistributedCapabilityAdvertisement) {
|
|
674
|
+
self.capabilities.insert(ad.capability_ref.canonical(), ad);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/// Find a provider for a capability
|
|
678
|
+
pub fn resolve(&self, cap_ref: &CapabilityRef) -> Option<&DistributedCapabilityAdvertisement> {
|
|
679
|
+
self.capabilities.get(&cap_ref.canonical())
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/// Find all providers for a capability (by name only)
|
|
683
|
+
pub fn resolve_all(
|
|
684
|
+
&self,
|
|
685
|
+
namespace: &str,
|
|
686
|
+
capability: &str,
|
|
687
|
+
) -> Vec<&DistributedCapabilityAdvertisement> {
|
|
688
|
+
self.capabilities
|
|
689
|
+
.values()
|
|
690
|
+
.filter(|ad| {
|
|
691
|
+
ad.capability_ref.namespace == namespace
|
|
692
|
+
&& ad.capability_ref.capability == capability
|
|
693
|
+
})
|
|
694
|
+
.collect()
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
impl Default for CapabilityResolver {
|
|
699
|
+
fn default() -> Self {
|
|
700
|
+
Self::new()
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
#[cfg(test)]
|
|
705
|
+
mod tests {
|
|
706
|
+
use super::*;
|
|
707
|
+
|
|
708
|
+
#[test]
|
|
709
|
+
fn test_capability_id_creation() {
|
|
710
|
+
let id = CapabilityId::new("search".to_string());
|
|
711
|
+
assert_eq!(id.0, "search");
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
#[test]
|
|
715
|
+
fn test_capability_checkpoint() {
|
|
716
|
+
let mut checkpoint = CapabilityCheckpoint::new(
|
|
717
|
+
CapabilityId::new("search".to_string()),
|
|
718
|
+
"instance-1".to_string(),
|
|
719
|
+
);
|
|
720
|
+
assert_eq!(checkpoint.last_processed_sequence, 0);
|
|
721
|
+
|
|
722
|
+
checkpoint.update_position("instance-1".to_string(), 100);
|
|
723
|
+
assert_eq!(checkpoint.last_processed_sequence, 100);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
#[test]
|
|
727
|
+
fn test_capability_access_model() {
|
|
728
|
+
let access = CapabilityAccessModel::new(
|
|
729
|
+
vec!["docs".to_string()],
|
|
730
|
+
vec!["docs.search".to_string()],
|
|
731
|
+
vec!["embedding-api".to_string()],
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
assert!(access.can_read("docs"));
|
|
735
|
+
assert!(!access.can_read("users"));
|
|
736
|
+
assert!(access.can_write("docs.search"));
|
|
737
|
+
assert!(access.can_execute("embedding-api"));
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
#[test]
|
|
741
|
+
fn test_capability_access_wildcard() {
|
|
742
|
+
let access =
|
|
743
|
+
CapabilityAccessModel::new(vec!["*".to_string()], vec!["*".to_string()], vec![]);
|
|
744
|
+
|
|
745
|
+
assert!(access.can_read("anything"));
|
|
746
|
+
assert!(access.can_write("anything"));
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
#[test]
|
|
750
|
+
fn test_capability_registry() {
|
|
751
|
+
let mut registry = CapabilityRegistry::new();
|
|
752
|
+
let metadata = CapabilityMetadata::new(
|
|
753
|
+
CapabilityId::new("search".to_string()),
|
|
754
|
+
"Search Capability".to_string(),
|
|
755
|
+
"Full-text search".to_string(),
|
|
756
|
+
CapabilityRole::Projection,
|
|
757
|
+
CapabilityAccessModel::new(
|
|
758
|
+
vec!["docs".to_string()],
|
|
759
|
+
vec!["docs.search".to_string()],
|
|
760
|
+
vec![],
|
|
761
|
+
),
|
|
762
|
+
);
|
|
763
|
+
|
|
764
|
+
registry.register(metadata).unwrap();
|
|
765
|
+
let cap_id = CapabilityId::new("search".to_string());
|
|
766
|
+
assert!(registry.get(&cap_id).is_some());
|
|
767
|
+
assert!(registry.get_checkpoint(&cap_id).is_some());
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
#[test]
|
|
771
|
+
fn test_capability_ref_creation_and_canonical() {
|
|
772
|
+
let cap_ref = CapabilityRef::new("docs".to_string(), "vector-search".to_string(), 1);
|
|
773
|
+
assert_eq!(cap_ref.namespace, "docs");
|
|
774
|
+
assert_eq!(cap_ref.capability, "vector-search");
|
|
775
|
+
assert_eq!(cap_ref.version, 1);
|
|
776
|
+
assert_eq!(
|
|
777
|
+
cap_ref.canonical(),
|
|
778
|
+
"flow://capability/docs/vector-search@1"
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
#[test]
|
|
783
|
+
fn test_capability_ref_parse_canonical() {
|
|
784
|
+
let canonical = "flow://capability/docs/vector-search@1";
|
|
785
|
+
let cap_ref = CapabilityRef::from_canonical(canonical).unwrap();
|
|
786
|
+
assert_eq!(cap_ref.namespace, "docs");
|
|
787
|
+
assert_eq!(cap_ref.capability, "vector-search");
|
|
788
|
+
assert_eq!(cap_ref.version, 1);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
#[test]
|
|
792
|
+
fn test_capability_ref_display() {
|
|
793
|
+
let cap_ref = CapabilityRef::new("images".to_string(), "image-analysis".to_string(), 2);
|
|
794
|
+
assert_eq!(
|
|
795
|
+
cap_ref.to_string(),
|
|
796
|
+
"flow://capability/images/image-analysis@2"
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
#[test]
|
|
801
|
+
fn test_state_requirement_creation() {
|
|
802
|
+
let req = StateRequirement::new("docs".to_string(), 847);
|
|
803
|
+
assert_eq!(req.namespace, "docs");
|
|
804
|
+
assert_eq!(req.min_version, 847);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
#[test]
|
|
808
|
+
fn test_capability_requirements_builder() {
|
|
809
|
+
let reqs = CapabilityRequirements::new()
|
|
810
|
+
.with_required_state(StateRequirement::new("docs".to_string(), 847))
|
|
811
|
+
.with_max_latency(5000)
|
|
812
|
+
.with_ownership_requirement(true);
|
|
813
|
+
|
|
814
|
+
assert_eq!(reqs.required_state.len(), 1);
|
|
815
|
+
assert_eq!(reqs.max_state_latency_ms, Some(5000));
|
|
816
|
+
assert!(reqs.requires_ownership);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
#[test]
|
|
820
|
+
fn test_distributed_capability_advertisement() {
|
|
821
|
+
let cap_ref = CapabilityRef::new("docs".to_string(), "vector-search".to_string(), 1);
|
|
822
|
+
let ad = DistributedCapabilityAdvertisement::new(cap_ref.clone(), "peer-b".to_string())
|
|
823
|
+
.with_requirements(
|
|
824
|
+
CapabilityRequirements::new()
|
|
825
|
+
.with_required_state(StateRequirement::new("docs".to_string(), 847)),
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
assert_eq!(ad.capability_ref, cap_ref);
|
|
829
|
+
assert_eq!(ad.peer_id, "peer-b");
|
|
830
|
+
assert_eq!(ad.requirements.required_state.len(), 1);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
#[test]
|
|
834
|
+
fn test_capability_execution_context() {
|
|
835
|
+
let cap_ref = CapabilityRef::new("docs".to_string(), "vector-search".to_string(), 1);
|
|
836
|
+
let ctx = CapabilityExecutionContext::new(
|
|
837
|
+
"exec-123".to_string(),
|
|
838
|
+
cap_ref.clone(),
|
|
839
|
+
"requester".to_string(),
|
|
840
|
+
)
|
|
841
|
+
.with_input_ref("flow://docs#123".to_string())
|
|
842
|
+
.with_required_state_version(851);
|
|
843
|
+
|
|
844
|
+
assert_eq!(ctx.execution_id, "exec-123");
|
|
845
|
+
assert_eq!(ctx.capability_ref, cap_ref);
|
|
846
|
+
assert_eq!(ctx.input_refs.len(), 1);
|
|
847
|
+
assert_eq!(ctx.required_state_version, 851);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
#[test]
|
|
851
|
+
fn test_execution_ref_creation() {
|
|
852
|
+
let cap_ref = CapabilityRef::new("docs".to_string(), "vector-search".to_string(), 1);
|
|
853
|
+
let exec_ref = ExecutionRef::new(
|
|
854
|
+
"exec-123".to_string(),
|
|
855
|
+
cap_ref.clone(),
|
|
856
|
+
"peer-b".to_string(),
|
|
857
|
+
);
|
|
858
|
+
|
|
859
|
+
assert_eq!(exec_ref.execution_id, "exec-123");
|
|
860
|
+
assert_eq!(exec_ref.capability_ref, cap_ref);
|
|
861
|
+
assert_eq!(exec_ref.provider_peer, "peer-b");
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
#[test]
|
|
865
|
+
fn test_capability_execution_result_success() {
|
|
866
|
+
let cap_ref = CapabilityRef::new("docs".to_string(), "vector-search".to_string(), 1);
|
|
867
|
+
let exec_ref = ExecutionRef::new("exec-123".to_string(), cap_ref, "peer-b".to_string());
|
|
868
|
+
let result = serde_json::json!({"results": [1, 2, 3]});
|
|
869
|
+
|
|
870
|
+
let exec_result = CapabilityExecutionResult::success(exec_ref.clone(), result.clone());
|
|
871
|
+
assert!(exec_result.error.is_none());
|
|
872
|
+
assert_eq!(exec_result.result, result);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
#[test]
|
|
876
|
+
fn test_capability_resolver_registration() {
|
|
877
|
+
let mut resolver = CapabilityResolver::new();
|
|
878
|
+
let cap_ref = CapabilityRef::new("docs".to_string(), "vector-search".to_string(), 1);
|
|
879
|
+
let ad = DistributedCapabilityAdvertisement::new(cap_ref.clone(), "peer-b".to_string());
|
|
880
|
+
|
|
881
|
+
resolver.register(ad);
|
|
882
|
+
|
|
883
|
+
let resolved = resolver.resolve(&cap_ref);
|
|
884
|
+
assert!(resolved.is_some());
|
|
885
|
+
assert_eq!(resolved.unwrap().peer_id, "peer-b");
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
#[test]
|
|
889
|
+
fn test_capability_resolver_resolve_all() {
|
|
890
|
+
let mut resolver = CapabilityResolver::new();
|
|
891
|
+
|
|
892
|
+
let cap_ref_v1 = CapabilityRef::new("docs".to_string(), "vector-search".to_string(), 1);
|
|
893
|
+
let ad1 = DistributedCapabilityAdvertisement::new(cap_ref_v1, "peer-a".to_string());
|
|
894
|
+
resolver.register(ad1);
|
|
895
|
+
|
|
896
|
+
let cap_ref_v2 = CapabilityRef::new("docs".to_string(), "vector-search".to_string(), 2);
|
|
897
|
+
let ad2 = DistributedCapabilityAdvertisement::new(cap_ref_v2, "peer-b".to_string());
|
|
898
|
+
resolver.register(ad2);
|
|
899
|
+
|
|
900
|
+
let all = resolver.resolve_all("docs", "vector-search");
|
|
901
|
+
assert_eq!(all.len(), 2);
|
|
902
|
+
}
|
|
903
|
+
}
|